Update some run tests to work when either D8 or R8 is used on libcore. am: 24f2c15bb6

Original change: https://android-review.googlesource.com/c/platform/art/+/3710386

Change-Id: I4c7ee34921d183f855e094c09c657d8f924179da
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/.clang-format b/.clang-format
index 0d6a160..ba0bff3 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,3 +1,4 @@
+# Please keep in sync with .clang-format-java-2 where applicable.
 ---
 BasedOnStyle: Google
 ---
@@ -22,3 +23,24 @@
 FixNamespaceComments: true
 PointerAlignment: Left
 TabWidth: 2
+
+---
+
+Language: Java
+
+ColumnLimit: 100
+CommentPragmas: (NOLINT|CHECK[^ ]*):.*
+ContinuationIndentWidth: 8
+IndentWidth: 4
+JavaImportGroups:
+- android
+- androidx
+- com.android
+- dalvik
+- libcore
+- com
+- junit
+- net
+- org
+- java
+- javax
diff --git a/.clang-format-java-2 b/.clang-format-java-2
new file mode 100644
index 0000000..956bdcd
--- /dev/null
+++ b/.clang-format-java-2
@@ -0,0 +1,48 @@
+# Variant of .clang-format but with 2 space indent for Java files, for use in
+# subdirectories with that style.
+# Please keep in sync with .clang-format where applicable.
+---
+BasedOnStyle: Google
+---
+
+Language: Cpp
+
+AlignConsecutiveMacros: AcrossComments
+AllowShortBlocksOnASingleLine: Empty
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AttributeMacros: ['__', 'NO_RETURN']
+BinPackArguments: false
+BinPackParameters: false
+BreakConstructorInitializers: BeforeColon
+BreakBeforeTernaryOperators: false
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+FixNamespaceComments: true
+PointerAlignment: Left
+TabWidth: 2
+
+---
+
+Language: Java
+
+ColumnLimit: 100
+CommentPragmas: (NOLINT|CHECK[^ ]*):.*
+ContinuationIndentWidth: 4
+IndentWidth: 2
+JavaImportGroups:
+- android
+- androidx
+- com.android
+- dalvik
+- libcore
+- com
+- junit
+- net
+- org
+- java
+- javax
diff --git a/Android.mk b/Android.mk
index 9257c22..d560276 100644
--- a/Android.mk
+++ b/Android.mk
@@ -380,76 +380,6 @@
 
 art_apex_manifest_file :=
 
-#######################
-# Fake packages for ART
-
-# The art-runtime package depends on the core ART libraries and binaries. It exists so we can
-# manipulate the set of things shipped, e.g., add debug versions and so on.
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := art-runtime
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
-
-# Reference the libraries and binaries in the appropriate APEX module, because
-# they don't have platform variants. However if
-# ART_MODULE_BUILD_FROM_SOURCE isn't true then the APEX
-# modules are disabled, so Soong won't apply the APEX mutators to them, and
-# then they are available with their plain names.
-ifeq (true,$(ART_MODULE_BUILD_FROM_SOURCE))
-  art_module_lib = $(1).com.android.art
-  art_module_debug_lib = $(1).com.android.art.debug
-else
-  art_module_lib = $(1)
-  art_module_debug_lib = $(1)
-endif
-
-# Base requirements.
-LOCAL_REQUIRED_MODULES := \
-    $(call art_module_lib,dalvikvm) \
-    $(call art_module_lib,dex2oat) \
-    $(call art_module_lib,dexoptanalyzer) \
-    $(call art_module_lib,libart) \
-    $(call art_module_lib,libart-compiler) \
-    $(call art_module_lib,libopenjdkjvm) \
-    $(call art_module_lib,libopenjdkjvmti) \
-    $(call art_module_lib,odrefresh) \
-    $(call art_module_lib,profman) \
-    $(call art_module_lib,libadbconnection) \
-    $(call art_module_lib,libperfetto_hprof) \
-
-# Potentially add in debug variants:
-#
-# * We will never add them if PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD = false.
-# * We will always add them if PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD = true.
-# * Otherwise, we will add them by default to eng builds.
-art_target_include_debug_build := $(PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD)
-ifneq (false,$(art_target_include_debug_build))
-ifneq (,$(filter eng,$(TARGET_BUILD_VARIANT)))
-  art_target_include_debug_build := true
-endif
-ifeq (true,$(art_target_include_debug_build))
-LOCAL_REQUIRED_MODULES += \
-    $(call art_module_debug_lib,dex2oatd) \
-    $(call art_module_debug_lib,dexoptanalyzerd) \
-    $(call art_module_debug_lib,libartd) \
-    $(call art_module_debug_lib,libartd-compiler) \
-    $(call art_module_debug_lib,libopenjdkd) \
-    $(call art_module_debug_lib,libopenjdkjvmd) \
-    $(call art_module_debug_lib,libopenjdkjvmtid) \
-    $(call art_module_debug_lib,profmand) \
-    $(call art_module_debug_lib,libadbconnectiond) \
-    $(call art_module_debug_lib,libperfetto_hprofd) \
-
-endif
-endif
-
-art_module_lib :=
-art_module_debug_lib :=
-
-include $(BUILD_PHONY_PACKAGE)
-
 ####################################################################################################
 # Fake packages to ensure generation of libopenjdkd when one builds with mm/mmm/mmma.
 #
@@ -521,7 +451,6 @@
   lib/libart-disassembler.so \
   lib/libartpalette.so \
   lib/libart.so \
-  lib/libbacktrace.so \
   lib/libdexfile.so \
   lib/libdt_fd_forward.so \
   lib/libdt_socket.so \
@@ -542,7 +471,6 @@
   lib/libprofile.so \
   lib/libsigchain.so \
   lib/libunwindstack.so \
-  lib/libziparchive.so \
   lib64/libadbconnection.so \
   lib64/libandroidio.so \
   lib64/libartbase.so \
@@ -551,7 +479,6 @@
   lib64/libart-disassembler.so \
   lib64/libartpalette.so \
   lib64/libart.so \
-  lib64/libbacktrace.so \
   lib64/libdexfile.so \
   lib64/libdt_fd_forward.so \
   lib64/libdt_socket.so \
@@ -572,7 +499,6 @@
   lib64/libprofile.so \
   lib64/libsigchain.so \
   lib64/libunwindstack.so \
-  lib64/libziparchive.so \
 
 PRIVATE_RUNTIME_APEX_DEPENDENCY_FILES := \
   bin/linker \
@@ -625,7 +551,10 @@
     rm -rf $$apex_dir && \
     mkdir -p $$apex_dir && \
     debugfs=$(HOST_OUT)/bin/debugfs_static && \
-    $(HOST_OUT)/bin/deapexer --debugfs_path $$debugfs extract $$apex_file $$apex_dir; \
+    blkid=$(HOST_OUT)/bin/blkid_static && \
+    fsckerofs=$(HOST_OUT)/bin/fsck.erofs && \
+    $(HOST_OUT)/bin/deapexer --debugfs_path $$debugfs --blkid_path $$blkid \
+		--fsckerofs_path $$fsckerofs extract $$apex_file $$apex_dir; \
   fi && \
   for f in $(2); do \
     sf=$$apex_dir/$$f && \
@@ -649,6 +578,13 @@
 #
 # TODO(b/129332183): Remove this when Golem has full support for the
 # ART APEX.
+#
+# TODO(b/129332183): This approach is flawed: We mix DSOs from prebuilt APEXes
+# and prebuilts/runtime/mainline/platform/impl with source built ones, and both
+# may depend on the same DSOs, and some of them don't have stable ABIs.
+# libbase.so in particular is such a problematic dependency. When those
+# dependencies eventually don't work anymore we don't have much choice but to
+# update all prebuilts.
 .PHONY: standalone-apex-files
 standalone-apex-files: deapexer \
                        $(RELEASE_ART_APEX) \
@@ -664,16 +600,11 @@
 	# Also, platform libraries are installed in prebuilts, so copy them over.
 	$(call extract-from-apex,$(RUNTIME_APEX),\
 	  $(PRIVATE_RUNTIME_APEX_DEPENDENCY_FILES)) && \
-	  for libdir in $(TARGET_OUT)/lib $(TARGET_OUT)/lib64; do \
-	    if [ -d $$libdir/bionic ]; then \
-	      mv -f $$libdir/bionic/*.so $$libdir; \
-	    fi || exit 1; \
-	  done && \
-	  for libdir in $(TARGET_OUT)/lib $(TARGET_OUT)/lib64; do \
-	    if [ -d $$libdir ]; then \
-          cp prebuilts/runtime/mainline/platform/impl/$(TARGET_ARCH)/*.so $$libdir; \
-	    fi || exit 1; \
-	  done
+	  libdir=$(TARGET_OUT)/lib$$(expr $(TARGET_ARCH) : '.*\(64\)' || :) && \
+	  if [ -d $$libdir/bionic ]; then \
+	    mv -f $$libdir/bionic/*.so $$libdir; \
+	  fi && \
+	  cp prebuilts/runtime/mainline/platform/impl/$(TARGET_ARCH)/*.so $$libdir
 	$(call extract-from-apex,$(CONSCRYPT_APEX),\
 	  $(PRIVATE_CONSCRYPT_APEX_DEPENDENCY_LIBS))
 	$(call extract-from-apex,$(I18N_APEX),\
@@ -706,6 +637,7 @@
                         $(TARGET_OUT_EXECUTABLES)/dex2oat_wrapper \
                         $(ART_TARGET_PLATFORM_DEPENDENCIES) \
                         $(ART_TARGET_SHARED_LIBRARY_BENCHMARK) \
+			$(TARGET_OUT_SHARED_LIBRARIES)/libgolemtiagent.so \
                         $(PRODUCT_OUT)/apex/art_boot_images/javalib/$(TARGET_ARCH)/boot.art \
                         standalone-apex-files
 	# remove debug libraries from public.libraries.txt because golem builds
@@ -723,22 +655,21 @@
 ART_HOST_SHARED_LIBRARY_BENCHMARK := $(ART_HOST_OUT_SHARED_LIBRARIES)/libartbenchmark.so
 build-art-host-golem: build-art-host \
                       $(ART_HOST_SHARED_LIBRARY_BENCHMARK) \
+		      $(ART_HOST_OUT_SHARED_LIBRARIES)/libgolemtiagent.so \
                       $(HOST_OUT_EXECUTABLES)/dex2oat_wrapper
 
 ########################################################################
-# Phony target for building what go/lem requires for syncing /system to target.
-.PHONY: build-art-unbundled-golem
-art_apex_jars := $(foreach pair,$(ART_APEX_JARS), $(call word-colon,2,$(pair)))
-build-art-unbundled-golem: art-runtime linker oatdump $(art_apex_jars) conscrypt crash_dump
-
-########################################################################
 # Rules for building all dependencies for tests.
 
 .PHONY: build-art-host-gtests build-art-host-run-tests build-art-host-tests
 
 build-art-host-gtests: build-art-host $(ART_TEST_HOST_GTEST_DEPENDENCIES)
 
-build-art-host-run-tests: build-art-host $(TEST_ART_RUN_TEST_DEPENDENCIES) $(ART_TEST_HOST_RUN_TEST_DEPENDENCIES)
+build-art-host-run-tests: build-art-host \
+                          $(TEST_ART_RUN_TEST_DEPENDENCIES) \
+                          $(ART_TEST_HOST_RUN_TEST_DEPENDENCIES) \
+                          art-run-test-host-data \
+                          art-run-test-jvm-data
 
 build-art-host-tests: build-art-host-gtests build-art-host-run-tests
 
@@ -746,7 +677,10 @@
 
 build-art-target-gtests: build-art-target $(ART_TEST_TARGET_GTEST_DEPENDENCIES)
 
-build-art-target-run-tests: build-art-target $(TEST_ART_RUN_TEST_DEPENDENCIES) $(ART_TEST_TARGET_RUN_TEST_DEPENDENCIES)
+build-art-target-run-tests: build-art-target \
+                            $(TEST_ART_RUN_TEST_DEPENDENCIES) \
+                            $(ART_TEST_TARGET_RUN_TEST_DEPENDENCIES) \
+                            art-run-test-target-data
 
 build-art-target-tests: build-art-target-gtests build-art-target-run-tests
 
@@ -882,7 +816,7 @@
 define create_public_sdk_dex
 public_sdk_$(1)_stub := $$(call get_public_sdk_stub_dex,$(1))
 $$(public_sdk_$(1)_stub): PRIVATE_MIN_SDK_VERSION := $(1)
-$$(public_sdk_$(1)_stub): $$(call resolve-prebuilt-sdk-jar-path,$(1)) $$(DX) $$(ZIP2ZIP)
+$$(public_sdk_$(1)_stub): $$(call resolve-prebuilt-sdk-jar-path,$(1)) $$(D8) $$(ZIP2ZIP)
 	$$(transform-classes.jar-to-dex)
 
 $$(call declare-1p-target,$$(public_sdk_$(1)_stub),art)
diff --git a/CPPLINT.cfg b/CPPLINT.cfg
index 8328842..71a5a26 100644
--- a/CPPLINT.cfg
+++ b/CPPLINT.cfg
@@ -31,3 +31,4 @@
 filter=-runtime/printf,-runtime/references,-runtime/sizeof,-runtime/threadsafe_fn
 # TODO: this should be re-enabled.
 filter=-whitespace/line_length
+filter=-whitespace/braces
diff --git a/OWNERS b/OWNERS
index 06c7d08..cfaa94b 100644
--- a/OWNERS
+++ b/OWNERS
@@ -4,16 +4,18 @@
 [email protected]
 [email protected]
 [email protected]
[email protected]
[email protected]
 [email protected]
 [email protected]
[email protected]
[email protected]
[email protected]
+
+# Core Libraries.
[email protected]
[email protected]
 [email protected]
 [email protected]
 [email protected]
[email protected]
 [email protected]
[email protected]
 [email protected]
 [email protected]
[email protected]
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index b8ee3c5..c5b3c9c 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -7,11 +7,53 @@
 # so we don't need the custom runtests script and this check.
 check_libnativebridge_test_field = libnativebridge/tests/preupload_check_test_tag.sh ${PREUPLOAD_COMMIT_MESSAGE} ${PREUPLOAD_FILES}
 
+check_expectation_jsons = tools/check_presubmit_json_expectations.sh ${REPO_ROOT} ${PREUPLOAD_FILES}
+
 [Builtin Hooks]
-cpplint = true
 bpfmt = true
+clang_format = true
+cpplint = true
 gofmt = true
 
 [Builtin Hooks Options]
+# Enable clang-format in all directories except test/, because there are many
+# test Java files with 2 space indent which won't be handled well if they
+# change. Unfortunately there is no way to exclude a directory for a builtin
+# hook.
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file
+               adbconnection/
+               artd/
+               benchmark/
+               build/
+               cmdline/
+               compiler/
+               dalvikvm/
+               dex2oat/
+               dexdump/
+               dexlayout/
+               dexlist/
+               dexoptanalyzer/
+               disassembler/
+               dt_fd_forward/
+               imgdiag/
+               libartbase/
+               libartpalette/
+               libartservice/
+               libarttools/
+               libdexfile/
+               libelffile/
+               libnativebridge/
+               libnativeloader/
+               libprofile/
+               oatdump/
+               odrefresh/
+               openjdkjvm/
+               openjdkjvmti/
+               perfetto_hprof/
+               profman/
+               runtime/
+               sigchainlib/
+               simulator/
+               tools/
 # Cpplint prints nothing unless there were errors.
 cpplint = --quiet ${PREUPLOAD_FILES}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 300526f..d9d5431 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -2,9 +2,6 @@
 {
   "mainline-presubmit": [
     {
-      "name": "ComposHostTestCases[com.google.android.art.apex]"
-    },
-    {
       "name": "art-run-test-001-HelloWorld[com.google.android.art.apex]"
     },
     {
@@ -71,6 +68,9 @@
       "name": "art-run-test-022-interface[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-024-illegal-access[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-025-access-controller[com.google.android.art.apex]"
     },
     {
@@ -86,6 +86,9 @@
       "name": "art-run-test-029-assert[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-032-concrete-sub[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-033-class-init-deadlock[com.google.android.art.apex]"
     },
     {
@@ -107,6 +110,9 @@
       "name": "art-run-test-041-narrowing[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-042-new-instance[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-043-privates[com.google.android.art.apex]"
     },
     {
@@ -155,6 +161,9 @@
       "name": "art-run-test-067-preemptive-unpark[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-069-field-type[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-070-nio-buffer[com.google.android.art.apex]"
     },
     {
@@ -164,12 +173,21 @@
       "name": "art-run-test-072-reachability-fence[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-073-mismatched-field[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-074-gc-thrash[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-075-verification-error[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-076-boolean-put[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-077-method-override[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-078-polymorphic-virtual[com.google.android.art.apex]"
     },
     {
@@ -305,15 +323,9 @@
       "name": "art-run-test-143-string-value[com.google.android.art.apex]"
     },
     {
-      "name": "art-run-test-144-static-field-sigquit[com.google.android.art.apex]"
-    },
-    {
       "name": "art-run-test-145-alloc-tracking-stress[com.google.android.art.apex]"
     },
     {
-      "name": "art-run-test-151-OpenFileLimit[com.google.android.art.apex]"
-    },
-    {
       "name": "art-run-test-152-dead-large-object[com.google.android.art.apex]"
     },
     {
@@ -350,6 +362,9 @@
       "name": "art-run-test-176-app-image-string[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-182-method-linking[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-1960-checker-bounds-codegen[com.google.android.art.apex]"
     },
     {
@@ -401,10 +416,25 @@
       "name": "art-run-test-2042-checker-dce-always-throw[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-2042-reference-processing[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2043-reference-pauses[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2044-get-stack-traces[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2046-checker-comparison[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2047-checker-const-string-length[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-2231-checker-heap-poisoning[com.google.android.art.apex]"
     },
     {
-      "name": "art-run-test-2232-write-metrics-to-log[com.google.android.art.apex]"
+      "name": "art-run-test-2233-checker-remove-loop-suspend-check[com.google.android.art.apex]"
     },
     {
       "name": "art-run-test-2234-checker-remove-entry-suspendcheck[com.google.android.art.apex]"
@@ -413,6 +443,57 @@
       "name": "art-run-test-2236-JdkUnsafeGetLong-regression[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-2241-checker-inline-try-catch[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2242-checker-lse-acquire-release-operations[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2243-checker-not-inline-into-throw[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2244-checker-remove-try-boundary[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2247-checker-write-barrier-elimination[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2249-checker-return-try-boundary-exit-in-loop[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2250-inline-throw-into-try[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2252-rem-optimization-dividend-divisor[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2253-checker-devirtualize-always-throws[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2254-checker-not-var-analyzed-pathological[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2254-class-value-before-and-after-u[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2255-checker-branch-redirection[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2256-checker-vector-replacement[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2257-checker-constant-folding-before-codegen[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2258-checker-valid-rti[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2259-checker-code-sinking-infinite-try-catch[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-2260-checker-inline-unimplemented-intrinsics[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-300-package-override[com.google.android.art.apex]"
     },
     {
@@ -800,9 +881,15 @@
       "name": "art-run-test-536-checker-intrinsic-optimization[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-536-checker-needs-access-check[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-537-checker-arraycopy[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-537-checker-inline-and-unverified[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-537-checker-jump-over-jump[com.google.android.art.apex]"
     },
     {
@@ -1100,6 +1187,9 @@
       "name": "art-run-test-662-regression-alias[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-663-checker-select-generator[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-665-checker-simd-zero[com.google.android.art.apex]"
     },
     {
@@ -1223,16 +1313,42 @@
       "name": "art-run-test-835-b216762268[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-838-override[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-839-clinit-throw[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-841-defaults[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-843-default-interface[com.google.android.art.apex]"
+    },
+    {
       "name": "art-run-test-963-default-range-smali[com.google.android.art.apex]"
     },
     {
+      "name": "art-run-test-965-default-verify[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-967-default-ame[com.google.android.art.apex]"
+    },
+    {
       "name": "art_libnativebridge_cts_tests[com.google.android.art.apex]"
     },
     {
+      "name": "art_standalone_artd_tests[com.google.android.art.apex]"
+    },
+    {
       "name": "art_standalone_cmdline_tests[com.google.android.art.apex]"
     },
     {
-      "name": "art_standalone_compiler_tests[com.google.android.art.apex]"
+      "name": "art_standalone_compiler_tests[com.google.android.art.apex]",
+      "options": [
+        {
+          "exclude-filter": "JniCompilerTest*"
+        }
+      ]
     },
     {
       "name": "art_standalone_dex2oat_tests[com.google.android.art.apex]"
@@ -1250,7 +1366,12 @@
       "name": "art_standalone_libartbase_tests[com.google.android.art.apex]"
     },
     {
-      "name": "art_standalone_libartpalette_tests[com.google.android.art.apex]"
+      "name": "art_standalone_libartpalette_tests[com.google.android.art.apex]",
+      "options": [
+        {
+          "exclude-filter": "PaletteClientJniTest*"
+        }
+      ]
     },
     {
       "name": "art_standalone_libartservice_tests[com.google.android.art.apex]"
@@ -1271,10 +1392,10 @@
       "name": "art_standalone_oatdump_tests[com.google.android.art.apex]"
     },
     {
-      "name": "art_standalone_profman_tests[com.google.android.art.apex]"
+      "name": "art_standalone_odrefresh_tests[com.google.android.art.apex]"
     },
     {
-      "name": "art_standalone_runtime_compiler_tests[com.google.android.art.apex]"
+      "name": "art_standalone_profman_tests[com.google.android.art.apex]"
     },
     {
       "name": "art_standalone_runtime_tests[com.google.android.art.apex]"
@@ -1283,21 +1404,24 @@
       "name": "art_standalone_sigchain_tests[com.google.android.art.apex]"
     },
     {
+      "name": "libnativeloader_e2e_tests[com.google.android.art.apex]"
+    },
+    {
       "name": "libnativeloader_test[com.google.android.art.apex]"
     }
   ],
   "presubmit": [
     {
-      "name": "CtsJdwpTestCases"
+      "name": "ArtServiceTests"
     },
     {
       "name": "BootImageProfileTest"
     },
     {
-      "name": "ArtServiceTests"
+      "name": "CtsJdwpTestCases"
     },
     {
-      "name": "ComposHostTestCases"
+      "name": "art-apex-update-rollback"
     },
     {
       "name": "art_standalone_dexpreopt_tests"
@@ -1369,6 +1493,9 @@
       "name": "art-run-test-022-interface"
     },
     {
+      "name": "art-run-test-024-illegal-access"
+    },
+    {
       "name": "art-run-test-025-access-controller"
     },
     {
@@ -1384,6 +1511,9 @@
       "name": "art-run-test-029-assert"
     },
     {
+      "name": "art-run-test-032-concrete-sub"
+    },
+    {
       "name": "art-run-test-033-class-init-deadlock"
     },
     {
@@ -1405,6 +1535,9 @@
       "name": "art-run-test-041-narrowing"
     },
     {
+      "name": "art-run-test-042-new-instance"
+    },
+    {
       "name": "art-run-test-043-privates"
     },
     {
@@ -1453,6 +1586,9 @@
       "name": "art-run-test-067-preemptive-unpark"
     },
     {
+      "name": "art-run-test-069-field-type"
+    },
+    {
       "name": "art-run-test-070-nio-buffer"
     },
     {
@@ -1462,12 +1598,21 @@
       "name": "art-run-test-072-reachability-fence"
     },
     {
+      "name": "art-run-test-073-mismatched-field"
+    },
+    {
       "name": "art-run-test-074-gc-thrash"
     },
     {
+      "name": "art-run-test-075-verification-error"
+    },
+    {
       "name": "art-run-test-076-boolean-put"
     },
     {
+      "name": "art-run-test-077-method-override"
+    },
+    {
       "name": "art-run-test-078-polymorphic-virtual"
     },
     {
@@ -1603,15 +1748,9 @@
       "name": "art-run-test-143-string-value"
     },
     {
-      "name": "art-run-test-144-static-field-sigquit"
-    },
-    {
       "name": "art-run-test-145-alloc-tracking-stress"
     },
     {
-      "name": "art-run-test-151-OpenFileLimit"
-    },
-    {
       "name": "art-run-test-152-dead-large-object"
     },
     {
@@ -1648,6 +1787,9 @@
       "name": "art-run-test-176-app-image-string"
     },
     {
+      "name": "art-run-test-182-method-linking"
+    },
+    {
       "name": "art-run-test-1960-checker-bounds-codegen"
     },
     {
@@ -1699,10 +1841,25 @@
       "name": "art-run-test-2042-checker-dce-always-throw"
     },
     {
+      "name": "art-run-test-2042-reference-processing"
+    },
+    {
+      "name": "art-run-test-2043-reference-pauses"
+    },
+    {
+      "name": "art-run-test-2044-get-stack-traces"
+    },
+    {
+      "name": "art-run-test-2046-checker-comparison"
+    },
+    {
+      "name": "art-run-test-2047-checker-const-string-length"
+    },
+    {
       "name": "art-run-test-2231-checker-heap-poisoning"
     },
     {
-      "name": "art-run-test-2232-write-metrics-to-log"
+      "name": "art-run-test-2233-checker-remove-loop-suspend-check"
     },
     {
       "name": "art-run-test-2234-checker-remove-entry-suspendcheck"
@@ -1711,6 +1868,57 @@
       "name": "art-run-test-2236-JdkUnsafeGetLong-regression"
     },
     {
+      "name": "art-run-test-2241-checker-inline-try-catch"
+    },
+    {
+      "name": "art-run-test-2242-checker-lse-acquire-release-operations"
+    },
+    {
+      "name": "art-run-test-2243-checker-not-inline-into-throw"
+    },
+    {
+      "name": "art-run-test-2244-checker-remove-try-boundary"
+    },
+    {
+      "name": "art-run-test-2247-checker-write-barrier-elimination"
+    },
+    {
+      "name": "art-run-test-2249-checker-return-try-boundary-exit-in-loop"
+    },
+    {
+      "name": "art-run-test-2250-inline-throw-into-try"
+    },
+    {
+      "name": "art-run-test-2252-rem-optimization-dividend-divisor"
+    },
+    {
+      "name": "art-run-test-2253-checker-devirtualize-always-throws"
+    },
+    {
+      "name": "art-run-test-2254-checker-not-var-analyzed-pathological"
+    },
+    {
+      "name": "art-run-test-2254-class-value-before-and-after-u"
+    },
+    {
+      "name": "art-run-test-2255-checker-branch-redirection"
+    },
+    {
+      "name": "art-run-test-2256-checker-vector-replacement"
+    },
+    {
+      "name": "art-run-test-2257-checker-constant-folding-before-codegen"
+    },
+    {
+      "name": "art-run-test-2258-checker-valid-rti"
+    },
+    {
+      "name": "art-run-test-2259-checker-code-sinking-infinite-try-catch"
+    },
+    {
+      "name": "art-run-test-2260-checker-inline-unimplemented-intrinsics"
+    },
+    {
       "name": "art-run-test-300-package-override"
     },
     {
@@ -2098,9 +2306,15 @@
       "name": "art-run-test-536-checker-intrinsic-optimization"
     },
     {
+      "name": "art-run-test-536-checker-needs-access-check"
+    },
+    {
       "name": "art-run-test-537-checker-arraycopy"
     },
     {
+      "name": "art-run-test-537-checker-inline-and-unverified"
+    },
+    {
       "name": "art-run-test-537-checker-jump-over-jump"
     },
     {
@@ -2398,6 +2612,9 @@
       "name": "art-run-test-662-regression-alias"
     },
     {
+      "name": "art-run-test-663-checker-select-generator"
+    },
+    {
       "name": "art-run-test-665-checker-simd-zero"
     },
     {
@@ -2521,12 +2738,33 @@
       "name": "art-run-test-835-b216762268"
     },
     {
+      "name": "art-run-test-838-override"
+    },
+    {
+      "name": "art-run-test-839-clinit-throw"
+    },
+    {
+      "name": "art-run-test-841-defaults"
+    },
+    {
+      "name": "art-run-test-843-default-interface"
+    },
+    {
       "name": "art-run-test-963-default-range-smali"
     },
     {
+      "name": "art-run-test-965-default-verify"
+    },
+    {
+      "name": "art-run-test-967-default-ame"
+    },
+    {
       "name": "art_libnativebridge_cts_tests"
     },
     {
+      "name": "art_standalone_artd_tests"
+    },
+    {
       "name": "art_standalone_cmdline_tests"
     },
     {
@@ -2575,7 +2813,1413 @@
       "name": "art_standalone_profman_tests"
     },
     {
-      "name": "art_standalone_runtime_compiler_tests"
+      "name": "art_standalone_runtime_tests"
+    },
+    {
+      "name": "art_standalone_sigchain_tests"
+    },
+    {
+      "name": "libnativeloader_e2e_tests"
+    },
+    {
+      "name": "libnativeloader_test"
+    }
+  ],
+  "hwasan-presubmit": [
+    {
+      "name": "ArtServiceTests"
+    },
+    {
+      "name": "art-apex-update-rollback"
+    },
+    {
+      "name": "art_standalone_dexpreopt_tests"
+    },
+    {
+      "name": "art-run-test-001-HelloWorld"
+    },
+    {
+      "name": "art-run-test-001-Main"
+    },
+    {
+      "name": "art-run-test-002-sleep"
+    },
+    {
+      "name": "art-run-test-004-InterfaceTest"
+    },
+    {
+      "name": "art-run-test-004-NativeAllocations"
+    },
+    {
+      "name": "art-run-test-004-checker-UnsafeTest18"
+    },
+    {
+      "name": "art-run-test-006-args"
+    },
+    {
+      "name": "art-run-test-007-count10"
+    },
+    {
+      "name": "art-run-test-009-instanceof"
+    },
+    {
+      "name": "art-run-test-010-instance"
+    },
+    {
+      "name": "art-run-test-011-array-copy"
+    },
+    {
+      "name": "art-run-test-012-math"
+    },
+    {
+      "name": "art-run-test-013-math2"
+    },
+    {
+      "name": "art-run-test-014-math3"
+    },
+    {
+      "name": "art-run-test-015-switch"
+    },
+    {
+      "name": "art-run-test-016-intern"
+    },
+    {
+      "name": "art-run-test-017-float"
+    },
+    {
+      "name": "art-run-test-018-stack-overflow"
+    },
+    {
+      "name": "art-run-test-019-wrong-array-type"
+    },
+    {
+      "name": "art-run-test-020-string"
+    },
+    {
+      "name": "art-run-test-021-string2"
+    },
+    {
+      "name": "art-run-test-022-interface"
+    },
+    {
+      "name": "art-run-test-024-illegal-access"
+    },
+    {
+      "name": "art-run-test-025-access-controller"
+    },
+    {
+      "name": "art-run-test-026-access"
+    },
+    {
+      "name": "art-run-test-027-arithmetic"
+    },
+    {
+      "name": "art-run-test-028-array-write"
+    },
+    {
+      "name": "art-run-test-029-assert"
+    },
+    {
+      "name": "art-run-test-032-concrete-sub"
+    },
+    {
+      "name": "art-run-test-033-class-init-deadlock"
+    },
+    {
+      "name": "art-run-test-035-enum"
+    },
+    {
+      "name": "art-run-test-036-finalizer"
+    },
+    {
+      "name": "art-run-test-037-inherit"
+    },
+    {
+      "name": "art-run-test-039-join-main"
+    },
+    {
+      "name": "art-run-test-040-miranda"
+    },
+    {
+      "name": "art-run-test-041-narrowing"
+    },
+    {
+      "name": "art-run-test-042-new-instance"
+    },
+    {
+      "name": "art-run-test-043-privates"
+    },
+    {
+      "name": "art-run-test-045-reflect-array"
+    },
+    {
+      "name": "art-run-test-046-reflect"
+    },
+    {
+      "name": "art-run-test-047-returns"
+    },
+    {
+      "name": "art-run-test-048-reflect-v8"
+    },
+    {
+      "name": "art-run-test-049-show-object"
+    },
+    {
+      "name": "art-run-test-050-sync-test"
+    },
+    {
+      "name": "art-run-test-052-verifier-fun"
+    },
+    {
+      "name": "art-run-test-053-wait-some"
+    },
+    {
+      "name": "art-run-test-055-enum-performance"
+    },
+    {
+      "name": "art-run-test-058-enum-order"
+    },
+    {
+      "name": "art-run-test-059-finalizer-throw"
+    },
+    {
+      "name": "art-run-test-061-out-of-memory"
+    },
+    {
+      "name": "art-run-test-062-character-encodings"
+    },
+    {
+      "name": "art-run-test-063-process-manager"
+    },
+    {
+      "name": "art-run-test-067-preemptive-unpark"
+    },
+    {
+      "name": "art-run-test-069-field-type"
+    },
+    {
+      "name": "art-run-test-070-nio-buffer"
+    },
+    {
+      "name": "art-run-test-072-precise-gc"
+    },
+    {
+      "name": "art-run-test-072-reachability-fence"
+    },
+    {
+      "name": "art-run-test-073-mismatched-field"
+    },
+    {
+      "name": "art-run-test-074-gc-thrash"
+    },
+    {
+      "name": "art-run-test-075-verification-error"
+    },
+    {
+      "name": "art-run-test-076-boolean-put"
+    },
+    {
+      "name": "art-run-test-077-method-override"
+    },
+    {
+      "name": "art-run-test-078-polymorphic-virtual"
+    },
+    {
+      "name": "art-run-test-079-phantom"
+    },
+    {
+      "name": "art-run-test-080-oom-fragmentation"
+    },
+    {
+      "name": "art-run-test-080-oom-throw"
+    },
+    {
+      "name": "art-run-test-080-oom-throw-with-finalizer"
+    },
+    {
+      "name": "art-run-test-081-hot-exceptions"
+    },
+    {
+      "name": "art-run-test-082-inline-execute"
+    },
+    {
+      "name": "art-run-test-083-compiler-regressions"
+    },
+    {
+      "name": "art-run-test-084-class-init"
+    },
+    {
+      "name": "art-run-test-090-loop-formation"
+    },
+    {
+      "name": "art-run-test-092-locale"
+    },
+    {
+      "name": "art-run-test-093-serialization"
+    },
+    {
+      "name": "art-run-test-094-pattern"
+    },
+    {
+      "name": "art-run-test-095-switch-MAX_INT"
+    },
+    {
+      "name": "art-run-test-096-array-copy-concurrent-gc"
+    },
+    {
+      "name": "art-run-test-099-vmdebug"
+    },
+    {
+      "name": "art-run-test-100-reflect2"
+    },
+    {
+      "name": "art-run-test-1000-non-moving-space-stress"
+    },
+    {
+      "name": "art-run-test-1004-checker-volatile-ref-load"
+    },
+    {
+      "name": "art-run-test-101-fibonacci"
+    },
+    {
+      "name": "art-run-test-102-concurrent-gc"
+    },
+    {
+      "name": "art-run-test-103-string-append"
+    },
+    {
+      "name": "art-run-test-104-growth-limit"
+    },
+    {
+      "name": "art-run-test-105-invoke"
+    },
+    {
+      "name": "art-run-test-106-exceptions2"
+    },
+    {
+      "name": "art-run-test-107-int-math2"
+    },
+    {
+      "name": "art-run-test-108-check-cast"
+    },
+    {
+      "name": "art-run-test-109-suspend-check"
+    },
+    {
+      "name": "art-run-test-110-field-access"
+    },
+    {
+      "name": "art-run-test-112-double-math"
+    },
+    {
+      "name": "art-run-test-114-ParallelGC"
+    },
+    {
+      "name": "art-run-test-120-hashcode"
+    },
+    {
+      "name": "art-run-test-121-simple-suspend-check"
+    },
+    {
+      "name": "art-run-test-122-npe"
+    },
+    {
+      "name": "art-run-test-123-compiler-regressions-mt"
+    },
+    {
+      "name": "art-run-test-123-inline-execute2"
+    },
+    {
+      "name": "art-run-test-125-gc-and-classloading"
+    },
+    {
+      "name": "art-run-test-128-reg-spill-on-implicit-nullcheck"
+    },
+    {
+      "name": "art-run-test-129-ThreadGetId"
+    },
+    {
+      "name": "art-run-test-132-daemon-locks-shutdown"
+    },
+    {
+      "name": "art-run-test-133-static-invoke-super"
+    },
+    {
+      "name": "art-run-test-1338-gc-no-los"
+    },
+    {
+      "name": "art-run-test-140-dce-regression"
+    },
+    {
+      "name": "art-run-test-140-field-packing"
+    },
+    {
+      "name": "art-run-test-143-string-value"
+    },
+    {
+      "name": "art-run-test-145-alloc-tracking-stress"
+    },
+    {
+      "name": "art-run-test-152-dead-large-object"
+    },
+    {
+      "name": "art-run-test-153-reference-stress"
+    },
+    {
+      "name": "art-run-test-156-register-dex-file-multi-loader"
+    },
+    {
+      "name": "art-run-test-159-app-image-fields"
+    },
+    {
+      "name": "art-run-test-160-read-barrier-stress"
+    },
+    {
+      "name": "art-run-test-163-app-image-methods"
+    },
+    {
+      "name": "art-run-test-165-lock-owner-proxy"
+    },
+    {
+      "name": "art-run-test-168-vmstack-annotated"
+    },
+    {
+      "name": "art-run-test-170-interface-init"
+    },
+    {
+      "name": "art-run-test-174-escaping-instance-of-bad-class"
+    },
+    {
+      "name": "art-run-test-175-alloc-big-bignums"
+    },
+    {
+      "name": "art-run-test-176-app-image-string"
+    },
+    {
+      "name": "art-run-test-182-method-linking"
+    },
+    {
+      "name": "art-run-test-1960-checker-bounds-codegen"
+    },
+    {
+      "name": "art-run-test-1961-checker-loop-vectorizer"
+    },
+    {
+      "name": "art-run-test-201-built-in-except-detail-messages"
+    },
+    {
+      "name": "art-run-test-2019-constantcalculationsinking"
+    },
+    {
+      "name": "art-run-test-202-thread-oome"
+    },
+    {
+      "name": "art-run-test-2020-InvokeVirtual-Inlining"
+    },
+    {
+      "name": "art-run-test-2021-InvokeStatic-Inlining"
+    },
+    {
+      "name": "art-run-test-2022-Invariantloops"
+    },
+    {
+      "name": "art-run-test-2023-InvariantLoops_typecast"
+    },
+    {
+      "name": "art-run-test-2024-InvariantNegativeLoop"
+    },
+    {
+      "name": "art-run-test-2025-ChangedArrayValue"
+    },
+    {
+      "name": "art-run-test-2026-DifferentMemoryLSCouples"
+    },
+    {
+      "name": "art-run-test-2027-TwiceTheSameMemoryCouple"
+    },
+    {
+      "name": "art-run-test-2028-MultiBackward"
+    },
+    {
+      "name": "art-run-test-2029-contended-monitors"
+    },
+    {
+      "name": "art-run-test-2030-long-running-child"
+    },
+    {
+      "name": "art-run-test-2042-checker-dce-always-throw"
+    },
+    {
+      "name": "art-run-test-2042-reference-processing"
+    },
+    {
+      "name": "art-run-test-2043-reference-pauses"
+    },
+    {
+      "name": "art-run-test-2044-get-stack-traces"
+    },
+    {
+      "name": "art-run-test-2046-checker-comparison"
+    },
+    {
+      "name": "art-run-test-2047-checker-const-string-length"
+    },
+    {
+      "name": "art-run-test-2231-checker-heap-poisoning"
+    },
+    {
+      "name": "art-run-test-2233-checker-remove-loop-suspend-check"
+    },
+    {
+      "name": "art-run-test-2234-checker-remove-entry-suspendcheck"
+    },
+    {
+      "name": "art-run-test-2236-JdkUnsafeGetLong-regression"
+    },
+    {
+      "name": "art-run-test-2241-checker-inline-try-catch"
+    },
+    {
+      "name": "art-run-test-2242-checker-lse-acquire-release-operations"
+    },
+    {
+      "name": "art-run-test-2243-checker-not-inline-into-throw"
+    },
+    {
+      "name": "art-run-test-2244-checker-remove-try-boundary"
+    },
+    {
+      "name": "art-run-test-2247-checker-write-barrier-elimination"
+    },
+    {
+      "name": "art-run-test-2249-checker-return-try-boundary-exit-in-loop"
+    },
+    {
+      "name": "art-run-test-2250-inline-throw-into-try"
+    },
+    {
+      "name": "art-run-test-2252-rem-optimization-dividend-divisor"
+    },
+    {
+      "name": "art-run-test-2253-checker-devirtualize-always-throws"
+    },
+    {
+      "name": "art-run-test-2254-checker-not-var-analyzed-pathological"
+    },
+    {
+      "name": "art-run-test-2254-class-value-before-and-after-u"
+    },
+    {
+      "name": "art-run-test-2255-checker-branch-redirection"
+    },
+    {
+      "name": "art-run-test-2256-checker-vector-replacement"
+    },
+    {
+      "name": "art-run-test-2257-checker-constant-folding-before-codegen"
+    },
+    {
+      "name": "art-run-test-2258-checker-valid-rti"
+    },
+    {
+      "name": "art-run-test-2259-checker-code-sinking-infinite-try-catch"
+    },
+    {
+      "name": "art-run-test-2260-checker-inline-unimplemented-intrinsics"
+    },
+    {
+      "name": "art-run-test-300-package-override"
+    },
+    {
+      "name": "art-run-test-301-abstract-protected"
+    },
+    {
+      "name": "art-run-test-302-float-conversion"
+    },
+    {
+      "name": "art-run-test-304-method-tracing"
+    },
+    {
+      "name": "art-run-test-401-optimizing-compiler"
+    },
+    {
+      "name": "art-run-test-402-optimizing-control-flow"
+    },
+    {
+      "name": "art-run-test-403-optimizing-long"
+    },
+    {
+      "name": "art-run-test-404-optimizing-allocator"
+    },
+    {
+      "name": "art-run-test-405-optimizing-long-allocator"
+    },
+    {
+      "name": "art-run-test-406-fields"
+    },
+    {
+      "name": "art-run-test-407-arrays"
+    },
+    {
+      "name": "art-run-test-408-move-bug"
+    },
+    {
+      "name": "art-run-test-409-materialized-condition"
+    },
+    {
+      "name": "art-run-test-410-floats"
+    },
+    {
+      "name": "art-run-test-411-checker-hdiv-hrem-const"
+    },
+    {
+      "name": "art-run-test-411-checker-hdiv-hrem-pow2"
+    },
+    {
+      "name": "art-run-test-411-checker-instruct-simplifier-hrem"
+    },
+    {
+      "name": "art-run-test-411-optimizing-arith"
+    },
+    {
+      "name": "art-run-test-413-regalloc-regression"
+    },
+    {
+      "name": "art-run-test-414-static-fields"
+    },
+    {
+      "name": "art-run-test-418-const-string"
+    },
+    {
+      "name": "art-run-test-419-long-parameter"
+    },
+    {
+      "name": "art-run-test-420-const-class"
+    },
+    {
+      "name": "art-run-test-421-exceptions"
+    },
+    {
+      "name": "art-run-test-421-large-frame"
+    },
+    {
+      "name": "art-run-test-422-instanceof"
+    },
+    {
+      "name": "art-run-test-422-type-conversion"
+    },
+    {
+      "name": "art-run-test-423-invoke-interface"
+    },
+    {
+      "name": "art-run-test-424-checkcast"
+    },
+    {
+      "name": "art-run-test-426-monitor"
+    },
+    {
+      "name": "art-run-test-427-bitwise"
+    },
+    {
+      "name": "art-run-test-427-bounds"
+    },
+    {
+      "name": "art-run-test-429-ssa-builder"
+    },
+    {
+      "name": "art-run-test-430-live-register-slow-path"
+    },
+    {
+      "name": "art-run-test-433-gvn"
+    },
+    {
+      "name": "art-run-test-434-shifter-operand"
+    },
+    {
+      "name": "art-run-test-435-try-finally-without-catch"
+    },
+    {
+      "name": "art-run-test-436-rem-float"
+    },
+    {
+      "name": "art-run-test-436-shift-constant"
+    },
+    {
+      "name": "art-run-test-437-inline"
+    },
+    {
+      "name": "art-run-test-438-volatile"
+    },
+    {
+      "name": "art-run-test-439-npe"
+    },
+    {
+      "name": "art-run-test-439-swap-double"
+    },
+    {
+      "name": "art-run-test-440-stmp"
+    },
+    {
+      "name": "art-run-test-441-checker-inliner"
+    },
+    {
+      "name": "art-run-test-443-not-bool-inline"
+    },
+    {
+      "name": "art-run-test-444-checker-nce"
+    },
+    {
+      "name": "art-run-test-445-checker-licm"
+    },
+    {
+      "name": "art-run-test-446-checker-inliner2"
+    },
+    {
+      "name": "art-run-test-447-checker-inliner3"
+    },
+    {
+      "name": "art-run-test-449-checker-bce-rem"
+    },
+    {
+      "name": "art-run-test-450-checker-types"
+    },
+    {
+      "name": "art-run-test-451-regression-add-float"
+    },
+    {
+      "name": "art-run-test-451-spill-splot"
+    },
+    {
+      "name": "art-run-test-455-checker-gvn"
+    },
+    {
+      "name": "art-run-test-456-baseline-array-set"
+    },
+    {
+      "name": "art-run-test-458-long-to-fpu"
+    },
+    {
+      "name": "art-run-test-464-checker-inline-sharpen-calls"
+    },
+    {
+      "name": "art-run-test-465-checker-clinit-gvn"
+    },
+    {
+      "name": "art-run-test-469-condition-materialization"
+    },
+    {
+      "name": "art-run-test-470-huge-method"
+    },
+    {
+      "name": "art-run-test-471-deopt-environment"
+    },
+    {
+      "name": "art-run-test-472-type-propagation"
+    },
+    {
+      "name": "art-run-test-473-checker-inliner-constants"
+    },
+    {
+      "name": "art-run-test-473-remove-dead-block"
+    },
+    {
+      "name": "art-run-test-474-checker-boolean-input"
+    },
+    {
+      "name": "art-run-test-474-fp-sub-neg"
+    },
+    {
+      "name": "art-run-test-475-simplify-mul-zero"
+    },
+    {
+      "name": "art-run-test-476-checker-ctor-fence-redun-elim"
+    },
+    {
+      "name": "art-run-test-476-checker-ctor-memory-barrier"
+    },
+    {
+      "name": "art-run-test-476-clinit-inline-static-invoke"
+    },
+    {
+      "name": "art-run-test-477-checker-bound-type"
+    },
+    {
+      "name": "art-run-test-477-long-2-float-convers-precision"
+    },
+    {
+      "name": "art-run-test-478-checker-clinit-check-pruning"
+    },
+    {
+      "name": "art-run-test-478-checker-inline-noreturn"
+    },
+    {
+      "name": "art-run-test-478-checker-inliner-nested-loop"
+    },
+    {
+      "name": "art-run-test-479-regression-implicit-null-check"
+    },
+    {
+      "name": "art-run-test-480-checker-dead-blocks"
+    },
+    {
+      "name": "art-run-test-481-regression-phi-cond"
+    },
+    {
+      "name": "art-run-test-482-checker-loop-back-edge-use"
+    },
+    {
+      "name": "art-run-test-483-dce-block"
+    },
+    {
+      "name": "art-run-test-485-checker-dce-switch"
+    },
+    {
+      "name": "art-run-test-486-checker-must-do-null-check"
+    },
+    {
+      "name": "art-run-test-487-checker-inline-calls"
+    },
+    {
+      "name": "art-run-test-488-checker-inline-recursive-calls"
+    },
+    {
+      "name": "art-run-test-489-current-method-regression"
+    },
+    {
+      "name": "art-run-test-490-checker-inline"
+    },
+    {
+      "name": "art-run-test-491-current-method"
+    },
+    {
+      "name": "art-run-test-492-checker-inline-invoke-interface"
+    },
+    {
+      "name": "art-run-test-493-checker-inline-invoke-interface"
+    },
+    {
+      "name": "art-run-test-494-checker-instanceof-tests"
+    },
+    {
+      "name": "art-run-test-495-checker-checkcast-tests"
+    },
+    {
+      "name": "art-run-test-496-checker-inlining-class-loader"
+    },
+    {
+      "name": "art-run-test-499-bce-phi-array-length"
+    },
+    {
+      "name": "art-run-test-500-instanceof"
+    },
+    {
+      "name": "art-run-test-505-simplifier-type-propagation"
+    },
+    {
+      "name": "art-run-test-507-boolean-test"
+    },
+    {
+      "name": "art-run-test-507-referrer"
+    },
+    {
+      "name": "art-run-test-508-checker-disassembly"
+    },
+    {
+      "name": "art-run-test-508-referrer-method"
+    },
+    {
+      "name": "art-run-test-513-array-deopt"
+    },
+    {
+      "name": "art-run-test-514-shifts"
+    },
+    {
+      "name": "art-run-test-519-bound-load-class"
+    },
+    {
+      "name": "art-run-test-521-checker-array-set-null"
+    },
+    {
+      "name": "art-run-test-521-regression-integer-field-set"
+    },
+    {
+      "name": "art-run-test-524-boolean-simplifier-regression"
+    },
+    {
+      "name": "art-run-test-525-checker-arrays-fields1"
+    },
+    {
+      "name": "art-run-test-525-checker-arrays-fields2"
+    },
+    {
+      "name": "art-run-test-526-checker-caller-callee-regs"
+    },
+    {
+      "name": "art-run-test-526-long-regalloc"
+    },
+    {
+      "name": "art-run-test-527-checker-array-access-simd"
+    },
+    {
+      "name": "art-run-test-527-checker-array-access-split"
+    },
+    {
+      "name": "art-run-test-528-long-hint"
+    },
+    {
+      "name": "art-run-test-529-long-split"
+    },
+    {
+      "name": "art-run-test-530-checker-loops-try-catch"
+    },
+    {
+      "name": "art-run-test-530-checker-loops1"
+    },
+    {
+      "name": "art-run-test-530-checker-loops2"
+    },
+    {
+      "name": "art-run-test-530-checker-loops3"
+    },
+    {
+      "name": "art-run-test-530-checker-loops4"
+    },
+    {
+      "name": "art-run-test-530-checker-loops5"
+    },
+    {
+      "name": "art-run-test-530-checker-lse"
+    },
+    {
+      "name": "art-run-test-530-checker-lse-ctor-fences"
+    },
+    {
+      "name": "art-run-test-530-checker-lse-simd"
+    },
+    {
+      "name": "art-run-test-530-checker-lse-try-catch"
+    },
+    {
+      "name": "art-run-test-530-instanceof-checkcast"
+    },
+    {
+      "name": "art-run-test-532-checker-nonnull-arrayset"
+    },
+    {
+      "name": "art-run-test-534-checker-bce-deoptimization"
+    },
+    {
+      "name": "art-run-test-535-deopt-and-inlining"
+    },
+    {
+      "name": "art-run-test-536-checker-intrinsic-optimization"
+    },
+    {
+      "name": "art-run-test-536-checker-needs-access-check"
+    },
+    {
+      "name": "art-run-test-537-checker-arraycopy"
+    },
+    {
+      "name": "art-run-test-537-checker-inline-and-unverified"
+    },
+    {
+      "name": "art-run-test-537-checker-jump-over-jump"
+    },
+    {
+      "name": "art-run-test-538-checker-embed-constants"
+    },
+    {
+      "name": "art-run-test-540-checker-rtp-bug"
+    },
+    {
+      "name": "art-run-test-542-bitfield-rotates"
+    },
+    {
+      "name": "art-run-test-542-inline-trycatch"
+    },
+    {
+      "name": "art-run-test-542-unresolved-access-check"
+    },
+    {
+      "name": "art-run-test-545-tracing-and-jit"
+    },
+    {
+      "name": "art-run-test-548-checker-inlining-and-dce"
+    },
+    {
+      "name": "art-run-test-549-checker-types-merge"
+    },
+    {
+      "name": "art-run-test-550-checker-multiply-accumulate"
+    },
+    {
+      "name": "art-run-test-550-new-instance-clinit"
+    },
+    {
+      "name": "art-run-test-551-checker-clinit"
+    },
+    {
+      "name": "art-run-test-551-checker-shifter-operand"
+    },
+    {
+      "name": "art-run-test-551-implicit-null-checks"
+    },
+    {
+      "name": "art-run-test-552-checker-x86-avx2-bit-manipulation"
+    },
+    {
+      "name": "art-run-test-554-checker-rtp-checkcast"
+    },
+    {
+      "name": "art-run-test-557-checker-instruct-simplifier-ror"
+    },
+    {
+      "name": "art-run-test-558-switch"
+    },
+    {
+      "name": "art-run-test-559-bce-ssa"
+    },
+    {
+      "name": "art-run-test-559-checker-rtp-ifnotnull"
+    },
+    {
+      "name": "art-run-test-560-packed-switch"
+    },
+    {
+      "name": "art-run-test-561-divrem"
+    },
+    {
+      "name": "art-run-test-561-shared-slowpaths"
+    },
+    {
+      "name": "art-run-test-562-bce-preheader"
+    },
+    {
+      "name": "art-run-test-562-checker-no-intermediate"
+    },
+    {
+      "name": "art-run-test-563-checker-invoke-super"
+    },
+    {
+      "name": "art-run-test-564-checker-bitcount"
+    },
+    {
+      "name": "art-run-test-564-checker-inline-loop"
+    },
+    {
+      "name": "art-run-test-564-checker-negbitwise"
+    },
+    {
+      "name": "art-run-test-565-checker-condition-liveness"
+    },
+    {
+      "name": "art-run-test-566-checker-codegen-select"
+    },
+    {
+      "name": "art-run-test-567-checker-builder-intrinsics"
+    },
+    {
+      "name": "art-run-test-568-checker-onebit"
+    },
+    {
+      "name": "art-run-test-570-checker-select"
+    },
+    {
+      "name": "art-run-test-572-checker-array-get-regression"
+    },
+    {
+      "name": "art-run-test-573-checker-checkcast-regression"
+    },
+    {
+      "name": "art-run-test-576-polymorphic-inlining"
+    },
+    {
+      "name": "art-run-test-577-checker-fp2int"
+    },
+    {
+      "name": "art-run-test-578-bce-visit"
+    },
+    {
+      "name": "art-run-test-578-polymorphic-inlining"
+    },
+    {
+      "name": "art-run-test-579-inline-infinite"
+    },
+    {
+      "name": "art-run-test-580-checker-fp16"
+    },
+    {
+      "name": "art-run-test-580-checker-round"
+    },
+    {
+      "name": "art-run-test-580-checker-string-fact-intrinsics"
+    },
+    {
+      "name": "art-run-test-580-crc32"
+    },
+    {
+      "name": "art-run-test-581-checker-rtp"
+    },
+    {
+      "name": "art-run-test-582-checker-bce-length"
+    },
+    {
+      "name": "art-run-test-583-checker-zero"
+    },
+    {
+      "name": "art-run-test-584-checker-div-bool"
+    },
+    {
+      "name": "art-run-test-589-super-imt"
+    },
+    {
+      "name": "art-run-test-590-checker-arr-set-null-regression"
+    },
+    {
+      "name": "art-run-test-591-checker-regression-dead-loop"
+    },
+    {
+      "name": "art-run-test-593-checker-long-2-float-regression"
+    },
+    {
+      "name": "art-run-test-594-checker-array-alias"
+    },
+    {
+      "name": "art-run-test-594-load-string-regression"
+    },
+    {
+      "name": "art-run-test-603-checker-instanceof"
+    },
+    {
+      "name": "art-run-test-605-new-string-from-bytes"
+    },
+    {
+      "name": "art-run-test-607-daemon-stress"
+    },
+    {
+      "name": "art-run-test-609-checker-inline-interface"
+    },
+    {
+      "name": "art-run-test-609-checker-x86-bounds-check"
+    },
+    {
+      "name": "art-run-test-610-arraycopy"
+    },
+    {
+      "name": "art-run-test-611-checker-simplify-if"
+    },
+    {
+      "name": "art-run-test-614-checker-dump-constant-location"
+    },
+    {
+      "name": "art-run-test-615-checker-arm64-store-zero"
+    },
+    {
+      "name": "art-run-test-617-clinit-oome"
+    },
+    {
+      "name": "art-run-test-618-checker-induction"
+    },
+    {
+      "name": "art-run-test-619-checker-current-method"
+    },
+    {
+      "name": "art-run-test-620-checker-bce-intrinsics"
+    },
+    {
+      "name": "art-run-test-622-checker-bce-regressions"
+    },
+    {
+      "name": "art-run-test-625-checker-licm-regressions"
+    },
+    {
+      "name": "art-run-test-627-checker-unroll"
+    },
+    {
+      "name": "art-run-test-628-vdex"
+    },
+    {
+      "name": "art-run-test-631-checker-get-class"
+    },
+    {
+      "name": "art-run-test-632-checker-char-at-bounds"
+    },
+    {
+      "name": "art-run-test-635-checker-arm64-volatile-load-cc"
+    },
+    {
+      "name": "art-run-test-636-arm64-veneer-pool"
+    },
+    {
+      "name": "art-run-test-637-checker-throw-inline"
+    },
+    {
+      "name": "art-run-test-639-checker-code-sinking"
+    },
+    {
+      "name": "art-run-test-640-checker-boolean-simd"
+    },
+    {
+      "name": "art-run-test-640-checker-integer-valueof"
+    },
+    {
+      "name": "art-run-test-640-checker-simd"
+    },
+    {
+      "name": "art-run-test-641-checker-arraycopy"
+    },
+    {
+      "name": "art-run-test-641-iterations"
+    },
+    {
+      "name": "art-run-test-643-checker-bogus-ic"
+    },
+    {
+      "name": "art-run-test-645-checker-abs-simd"
+    },
+    {
+      "name": "art-run-test-646-checker-arraycopy-large-cst-pos"
+    },
+    {
+      "name": "art-run-test-646-checker-long-const-to-int"
+    },
+    {
+      "name": "art-run-test-646-checker-simd-hadd"
+    },
+    {
+      "name": "art-run-test-650-checker-inline-access-thunks"
+    },
+    {
+      "name": "art-run-test-654-checker-periodic"
+    },
+    {
+      "name": "art-run-test-655-checker-simd-arm-opt"
+    },
+    {
+      "name": "art-run-test-656-checker-simd-opt"
+    },
+    {
+      "name": "art-run-test-657-branches"
+    },
+    {
+      "name": "art-run-test-658-fp-read-barrier"
+    },
+    {
+      "name": "art-run-test-659-unpadded-array"
+    },
+    {
+      "name": "art-run-test-660-checker-sad"
+    },
+    {
+      "name": "art-run-test-660-checker-simd-sad"
+    },
+    {
+      "name": "art-run-test-661-checker-simd-reduc"
+    },
+    {
+      "name": "art-run-test-662-regression-alias"
+    },
+    {
+      "name": "art-run-test-663-checker-select-generator"
+    },
+    {
+      "name": "art-run-test-665-checker-simd-zero"
+    },
+    {
+      "name": "art-run-test-666-dex-cache-itf"
+    },
+    {
+      "name": "art-run-test-667-checker-simd-alignment"
+    },
+    {
+      "name": "art-run-test-667-out-of-bounds"
+    },
+    {
+      "name": "art-run-test-669-checker-break"
+    },
+    {
+      "name": "art-run-test-671-npe-field-opts"
+    },
+    {
+      "name": "art-run-test-672-checker-throw-method"
+    },
+    {
+      "name": "art-run-test-673-checker-throw-vmethod"
+    },
+    {
+      "name": "art-run-test-676-proxy-jit-at-first-use"
+    },
+    {
+      "name": "art-run-test-677-fsi2"
+    },
+    {
+      "name": "art-run-test-678-quickening"
+    },
+    {
+      "name": "art-run-test-684-checker-simd-dotprod"
+    },
+    {
+      "name": "art-run-test-684-select-condition"
+    },
+    {
+      "name": "art-run-test-689-multi-catch"
+    },
+    {
+      "name": "art-run-test-694-clinit-jit"
+    },
+    {
+      "name": "art-run-test-695-simplify-throws"
+    },
+    {
+      "name": "art-run-test-696-loop"
+    },
+    {
+      "name": "art-run-test-697-checker-string-append"
+    },
+    {
+      "name": "art-run-test-698-selects"
+    },
+    {
+      "name": "art-run-test-700-LoadArgRegs"
+    },
+    {
+      "name": "art-run-test-703-floating-point-div"
+    },
+    {
+      "name": "art-run-test-704-multiply-accumulate"
+    },
+    {
+      "name": "art-run-test-705-register-conflict"
+    },
+    {
+      "name": "art-run-test-711-checker-type-conversion"
+    },
+    {
+      "name": "art-run-test-713-varhandle-invokers"
+    },
+    {
+      "name": "art-run-test-718-zipfile-finalizer"
+    },
+    {
+      "name": "art-run-test-719-varhandle-concurrency"
+    },
+    {
+      "name": "art-run-test-721-osr"
+    },
+    {
+      "name": "art-run-test-726-array-store"
+    },
+    {
+      "name": "art-run-test-730-checker-inlining-super"
+    },
+    {
+      "name": "art-run-test-731-bounds-check-slow-path"
+    },
+    {
+      "name": "art-run-test-805-TooDeepClassInstanceOf"
+    },
+    {
+      "name": "art-run-test-806-TooWideClassInstanceOf"
+    },
+    {
+      "name": "art-run-test-812-recursive-default"
+    },
+    {
+      "name": "art-run-test-814-large-field-offsets"
+    },
+    {
+      "name": "art-run-test-815-invokeinterface-default"
+    },
+    {
+      "name": "art-run-test-818-clinit-nterp"
+    },
+    {
+      "name": "art-run-test-821-madvise-willneed"
+    },
+    {
+      "name": "art-run-test-828-partial-lse"
+    },
+    {
+      "name": "art-run-test-834-lse"
+    },
+    {
+      "name": "art-run-test-835-b216762268"
+    },
+    {
+      "name": "art-run-test-838-override"
+    },
+    {
+      "name": "art-run-test-839-clinit-throw"
+    },
+    {
+      "name": "art-run-test-841-defaults"
+    },
+    {
+      "name": "art-run-test-843-default-interface"
+    },
+    {
+      "name": "art-run-test-963-default-range-smali"
+    },
+    {
+      "name": "art-run-test-965-default-verify"
+    },
+    {
+      "name": "art-run-test-967-default-ame"
+    },
+    {
+      "name": "art_libnativebridge_cts_tests"
+    },
+    {
+      "name": "art_standalone_artd_tests"
+    },
+    {
+      "name": "art_standalone_cmdline_tests"
+    },
+    {
+      "name": "art_standalone_compiler_tests"
+    },
+    {
+      "name": "art_standalone_dex2oat_tests"
+    },
+    {
+      "name": "art_standalone_dexdump_tests"
+    },
+    {
+      "name": "art_standalone_dexlist_tests"
+    },
+    {
+      "name": "art_standalone_dexoptanalyzer_tests"
+    },
+    {
+      "name": "art_standalone_libartbase_tests"
+    },
+    {
+      "name": "art_standalone_libartpalette_tests"
+    },
+    {
+      "name": "art_standalone_libartservice_tests"
+    },
+    {
+      "name": "art_standalone_libarttools_tests"
+    },
+    {
+      "name": "art_standalone_libdexfile_support_tests"
+    },
+    {
+      "name": "art_standalone_libdexfile_tests"
+    },
+    {
+      "name": "art_standalone_libprofile_tests"
+    },
+    {
+      "name": "art_standalone_oatdump_tests"
+    },
+    {
+      "name": "art_standalone_odrefresh_tests"
+    },
+    {
+      "name": "art_standalone_profman_tests"
     },
     {
       "name": "art_standalone_runtime_tests"
@@ -2584,7 +4228,15 @@
       "name": "art_standalone_sigchain_tests"
     },
     {
+      "name": "libnativeloader_e2e_tests"
+    },
+    {
       "name": "libnativeloader_test"
     }
+  ],
+  "avf-presubmit": [
+    {
+      "name": "ComposHostTestCases"
+    }
   ]
 }
diff --git a/adbconnection/adbconnection.cc b/adbconnection/adbconnection.cc
index f9ebe40..239fe31 100644
--- a/adbconnection/adbconnection.cc
+++ b/adbconnection/adbconnection.cc
@@ -23,6 +23,8 @@
 #include "adbconnection/client.h"
 #include "android-base/endian.h"
 #include "android-base/stringprintf.h"
+#include "art_field-inl.h"
+#include "art_method-alloc-inl.h"
 #include "base/file_utils.h"
 #include "base/globals.h"
 #include "base/logging.h"
@@ -32,6 +34,7 @@
 #include "debugger.h"
 #include "jni/java_vm_ext.h"
 #include "jni/jni_env_ext.h"
+#include "mirror/class-alloc-inl.h"
 #include "mirror/throwable.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "runtime-inl.h"
@@ -180,21 +183,25 @@
   }
 }
 
-static jobject CreateAdbConnectionThread(art::Thread* thr) {
-  JNIEnv* env = thr->GetJniEnv();
-  // Move to native state to talk with the jnienv api.
-  art::ScopedThreadStateChange stsc(thr, art::ThreadState::kNative);
-  ScopedLocalRef<jstring> thr_name(env, env->NewStringUTF(kAdbConnectionThreadName));
-  ScopedLocalRef<jobject> thr_group(
-      env,
-      env->GetStaticObjectField(art::WellKnownClasses::java_lang_ThreadGroup,
-                                art::WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup));
-  return env->NewObject(art::WellKnownClasses::java_lang_Thread,
-                        art::WellKnownClasses::java_lang_Thread_init,
-                        thr_group.get(),
-                        thr_name.get(),
-                        /*Priority=*/ 0,
-                        /*Daemon=*/ true);
+static art::ObjPtr<art::mirror::Object> CreateAdbConnectionThread(art::Thread* self)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  art::StackHandleScope<3u> hs(self);
+  art::Handle<art::mirror::String> thr_name =
+      hs.NewHandle(art::mirror::String::AllocFromModifiedUtf8(self, kAdbConnectionThreadName));
+  if (thr_name == nullptr) {
+    DCHECK(self->IsExceptionPending());
+    return nullptr;
+  }
+  art::ArtField* system_thread_group_field =
+      art::WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup;
+  DCHECK(system_thread_group_field->GetDeclaringClass()->IsInitialized());
+  // Avoid using `ArtField::GetObject` as it requires linking against `libdexfile` for
+  // `operator<<(std::ostream&, Primitive::Type)`.
+  art::Handle<art::mirror::Object> system_thread_group = hs.NewHandle(
+      system_thread_group_field->GetDeclaringClass()->GetFieldObject<art::mirror::Object>(
+          system_thread_group_field->GetOffset()));
+  return art::WellKnownClasses::java_lang_Thread_init->NewObject<'L', 'L', 'I', 'Z'>(
+      hs, self, system_thread_group, thr_name, /*priority=*/ 0, /*daemon=*/ true).Get();
 }
 
 struct CallbackData {
@@ -274,10 +281,14 @@
     }
     runtime->StartThreadBirth();
   }
-  ScopedLocalRef<jobject> thr(soa.Env(), CreateAdbConnectionThread(soa.Self()));
+  jobject thr = soa.Env()->GetVm()->AddGlobalRef(self, CreateAdbConnectionThread(soa.Self()));
+  if (thr == nullptr) {
+    LOG(ERROR) << "Failed to create debugger thread!";
+    return;
+  }
   // Note: Using pthreads instead of std::thread to not abort when the thread cannot be
   //       created (exception support required).
-  std::unique_ptr<CallbackData> data(new CallbackData { this, soa.Env()->NewGlobalRef(thr.get()) });
+  std::unique_ptr<CallbackData> data(new CallbackData { this, thr });
   started_debugger_threads_ = true;
   gPthread.emplace();
   int pthread_create_result = pthread_create(&gPthread.value(),
@@ -289,7 +300,7 @@
     started_debugger_threads_ = false;
     // If the create succeeded the other thread will call EndThreadBirth.
     art::Runtime* runtime = art::Runtime::Current();
-    soa.Env()->DeleteGlobalRef(data->thr_);
+    soa.Env()->DeleteGlobalRef(thr);
     LOG(ERROR) << "Failed to create thread for adb-jdwp connection manager!";
     art::MutexLock mu(art::Thread::Current(), *art::Locks::runtime_shutdown_lock_);
     runtime->EndThreadBirth();
@@ -810,8 +821,12 @@
 void AdbConnectionState::AttachJdwpAgent(art::Thread* self) {
   art::Runtime* runtime = art::Runtime::Current();
   self->AssertNoPendingException();
+
+  std::string args = MakeAgentArg();
+  VLOG(jdwp) << "Attaching JDWP agent with args '" << args << "'";
+
   runtime->AttachAgent(/* env= */ nullptr,
-                       MakeAgentArg(),
+                       args,
                        /* class_loader= */ nullptr);
   if (self->IsExceptionPending()) {
     LOG(ERROR) << "Failed to load agent " << agent_name_;
diff --git a/artd/Android.bp b/artd/Android.bp
index 6db1287..1288ebc 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -22,31 +22,83 @@
     default_applicable_licenses: ["art_license"],
 }
 
-art_cc_binary {
-    name: "artd",
+cc_defaults {
+    name: "artd_defaults",
     defaults: ["art_defaults"],
-
     srcs: [
         "artd.cc",
+        "file_utils.cc",
+        "path_utils.cc",
     ],
-
+    header_libs: [
+        "art_cmdlineparser_headers",
+        "profman_headers",
+    ],
     shared_libs: [
-        "artd-aidl-ndk",
-        "libartbase",
-        "libarttools",
+        "libarttools", // Contains "libc++fs".
         "libbase",
         "libbinder_ndk",
+        "libselinux",
     ],
+    static_libs: [
+        "artd-aidl-ndk",
+    ],
+}
 
+art_cc_binary {
+    name: "artd",
+    defaults: ["artd_defaults"],
+    srcs: [
+        "artd_main.cc",
+    ],
+    shared_libs: [
+        "libart",
+        "libartbase",
+    ],
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
     ],
 }
 
-prebuilt_etc {
-    name: "com.android.art.artd.init.rc",
-    src: "artd.rc",
-    filename: "init.rc",
-    installable: false,
+art_cc_defaults {
+    name: "art_artd_tests_defaults",
+    defaults: ["artd_defaults"],
+    static_libs: [
+        "libgmock",
+    ],
+    srcs: [
+        "artd_test.cc",
+        "file_utils_test.cc",
+        "path_utils_test.cc",
+    ],
+    data: [
+        ":art-gtest-jars-Main",
+        ":art-gtest-jars-Nested",
+    ],
+}
+
+// Version of ART gtest `art_artd_tests` bundled with the ART APEX on target.
+//
+// This test requires the full libbinder_ndk implementation on host, which is
+// not available as a prebuilt on the thin master-art branch. Hence it won't
+// work there, and there's a conditional in Android.gtest.mk to exclude it from
+// test-art-host-gtest.
+art_cc_test {
+    name: "art_artd_tests",
+    defaults: [
+        "art_gtest_defaults",
+        "art_artd_tests_defaults",
+    ],
+}
+
+// Standalone version of ART gtest `art_artd_tests`, not bundled with the ART
+// APEX on target.
+art_cc_test {
+    name: "art_standalone_artd_tests",
+    defaults: [
+        "art_standalone_gtest_defaults",
+        "art_artd_tests_defaults",
+    ],
+    test_config_template: "art_standalone_artd_tests.xml",
 }
diff --git a/artd/README.md b/artd/README.md
new file mode 100644
index 0000000..a2dfc09
--- /dev/null
+++ b/artd/README.md
@@ -0,0 +1,16 @@
+## artd
+
+artd is a component of ART Service. It is a shim service to do tasks that
+require elevated permissions that are not available to system_server, such as
+manipulation of the file system and invoking dex2oat. It publishes a binder
+interface that is internal to ART service's Java code. When it invokes other
+binaries, it passes input and output files as FDs and drops capability before
+exec.
+
+### System properties
+
+artd can be controlled by the system properties listed below. Note that the list
+doesn't include options passed to dex2oat and other processes.
+
+- `dalvik.vm.artd-verbose`: Log verbosity of the artd process. The syntax is the
+  same as the runtime's `-verbose` flag.
diff --git a/artd/art_standalone_artd_tests.xml b/artd/art_standalone_artd_tests.xml
new file mode 100644
index 0000000..9125046
--- /dev/null
+++ b/artd/art_standalone_artd_tests.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<!-- Note: This test config file for {MODULE} is generated from a template. -->
+<configuration description="Runs {MODULE}.">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}/{MODULE}" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="art-gtest-jars-Main.jar->/data/local/tmp/{MODULE}/art-gtest-jars-Main.jar" />
+        <option name="push" value="art-gtest-jars-Nested.jar->/data/local/tmp/{MODULE}/art-gtest-jars-Nested.jar" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp/{MODULE}" />
+        <option name="module-name" value="{MODULE}" />
+        <option name="ld-library-path-32" value="/apex/com.android.art/lib" />
+        <option name="ld-library-path-64" value="/apex/com.android.art/lib64" />
+    </test>
+
+    <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+         one of the Mainline modules below is present on the device used for testing. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <!-- ART Mainline Module (internal version). -->
+        <option name="mainline-module-package-name" value="com.google.android.art" />
+        <!-- ART Mainline Module (external (AOSP) version). -->
+        <option name="mainline-module-package-name" value="com.android.art" />
+    </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <!-- TODO(jiakaiz): Change this to U once `ro.build.version.sdk` is bumped. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/artd/artd.cc b/artd/artd.cc
index 1dcd2e8..c44c0bd 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -1,87 +1,1330 @@
 /*
-** Copyright 2021, 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) 2021 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.
+ */
 
-#include <string>
-#define LOG_TAG "artd"
+#include "artd.h"
 
-#include <android/binder_manager.h>
-#include <android/binder_process.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
 #include <unistd.h>
-#include <utils/Errors.h>
 
-#include "aidl/android/os/BnArtd.h"
+#include <climits>
+#include <csignal>
+#include <cstdint>
+#include <cstring>
+#include <filesystem>
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <type_traits>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "aidl/com/android/server/art/DexoptTrigger.h"
+#include "aidl/com/android/server/art/IArtdCancellationSignal.h"
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/result.h"
+#include "android-base/scopeguard.h"
+#include "android-base/strings.h"
+#include "android/binder_auto_utils.h"
+#include "android/binder_interface_utils.h"
+#include "android/binder_manager.h"
+#include "android/binder_process.h"
+#include "base/compiler_filter.h"
+#include "base/file_utils.h"
+#include "base/globals.h"
 #include "base/logging.h"
-#include "base/macros.h"
+#include "base/os.h"
+#include "cmdline_types.h"
+#include "exec_utils.h"
+#include "file_utils.h"
+#include "fmt/format.h"
+#include "oat_file_assistant.h"
+#include "oat_file_assistant_context.h"
+#include "path_utils.h"
+#include "profman/profman_result.h"
+#include "selinux/android.h"
+#include "tools/cmdline_builder.h"
 #include "tools/tools.h"
 
-using ::ndk::ScopedAStatus;
+#ifdef __BIONIC__
+#include <linux/incrementalfs.h>
+#endif
 
-namespace android {
+namespace art {
 namespace artd {
 
-class Artd : public aidl::android::os::BnArtd {
-  constexpr static const char* const SERVICE_NAME = "artd";
+namespace {
 
- public:
-  Artd() {}
+using ::aidl::com::android::server::art::ArtdDexoptResult;
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::DexoptOptions;
+using ::aidl::com::android::server::art::DexoptTrigger;
+using ::aidl::com::android::server::art::FileVisibility;
+using ::aidl::com::android::server::art::FsPermission;
+using ::aidl::com::android::server::art::GetDexoptNeededResult;
+using ::aidl::com::android::server::art::GetDexoptStatusResult;
+using ::aidl::com::android::server::art::IArtdCancellationSignal;
+using ::aidl::com::android::server::art::MergeProfileOptions;
+using ::aidl::com::android::server::art::OutputArtifacts;
+using ::aidl::com::android::server::art::OutputProfile;
+using ::aidl::com::android::server::art::PriorityClass;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::Dirname;
+using ::android::base::Error;
+using ::android::base::Join;
+using ::android::base::make_scope_guard;
+using ::android::base::ReadFileToString;
+using ::android::base::Result;
+using ::android::base::Split;
+using ::android::base::StringReplace;
+using ::android::base::WriteStringToFd;
+using ::art::tools::CmdlineBuilder;
+using ::ndk::ScopedAStatus;
 
-  /*
-   * Binder API
-   */
+using ::fmt::literals::operator""_format;  // NOLINT
 
-  ScopedAStatus isAlive(bool* _aidl_return) {
-    *_aidl_return = true;
-    return ScopedAStatus::ok();
-  }
+using ArtifactsLocation = GetDexoptNeededResult::ArtifactsLocation;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
 
-  /*
-   * Server API
-   */
+constexpr const char* kServiceName = "artd";
+constexpr const char* kArtdCancellationSignalType = "ArtdCancellationSignal";
 
-  ScopedAStatus Start() {
-    LOG(INFO) << "Starting artd";
+// Timeout for short operations, such as merging profiles.
+constexpr int kShortTimeoutSec = 60;  // 1 minute.
 
-    status_t ret = AServiceManager_addService(this->asBinder().get(), SERVICE_NAME);
-    if (ret != android::OK) {
-      return ScopedAStatus::fromStatus(ret);
+// Timeout for long operations, such as compilation. We set it to be smaller than the Package
+// Manager watchdog (PackageManagerService.WATCHDOG_TIMEOUT, 10 minutes), so that if the operation
+// is called from the Package Manager's thread handler, it will be aborted before that watchdog
+// would take down the system server.
+constexpr int kLongTimeoutSec = 570;  // 9.5 minutes.
+
+std::optional<int64_t> GetSize(std::string_view path) {
+  std::error_code ec;
+  int64_t size = std::filesystem::file_size(path, ec);
+  if (ec) {
+    // It is okay if the file does not exist. We don't have to log it.
+    if (ec.value() != ENOENT) {
+      LOG(ERROR) << "Failed to get the file size of '{}': {}"_format(path, ec.message());
     }
-
-    ABinderProcess_startThreadPool();
-
-    return ScopedAStatus::ok();
+    return std::nullopt;
   }
+  return size;
+}
+
+// Deletes a file. Returns the size of the deleted file, or 0 if the deleted file is empty or an
+// error occurs.
+int64_t GetSizeAndDeleteFile(const std::string& path) {
+  std::optional<int64_t> size = GetSize(path);
+  if (!size.has_value()) {
+    return 0;
+  }
+
+  std::error_code ec;
+  if (!std::filesystem::remove(path, ec)) {
+    LOG(ERROR) << "Failed to remove '{}': {}"_format(path, ec.message());
+    return 0;
+  }
+
+  return size.value();
+}
+
+std::string EscapeErrorMessage(const std::string& message) {
+  return StringReplace(message, std::string("\0", /*n=*/1), "\\0", /*all=*/true);
+}
+
+// Indicates an error that should never happen (e.g., illegal arguments passed by service-art
+// internally). System server should crash if this kind of error happens.
+ScopedAStatus Fatal(const std::string& message) {
+  return ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE,
+                                                     EscapeErrorMessage(message).c_str());
+}
+
+// Indicates an error that service-art should handle (e.g., I/O errors, sub-process crashes).
+// The scope of the error depends on the function that throws it, so service-art should catch the
+// error at every call site and take different actions.
+// Ideally, this should be a checked exception or an additional return value that forces service-art
+// to handle it, but `ServiceSpecificException` (a separate runtime exception type) is the best
+// approximate we have given the limitation of Java and Binder.
+ScopedAStatus NonFatal(const std::string& message) {
+  constexpr int32_t kArtdNonFatalErrorCode = 1;
+  return ScopedAStatus::fromServiceSpecificErrorWithMessage(kArtdNonFatalErrorCode,
+                                                            EscapeErrorMessage(message).c_str());
+}
+
+Result<CompilerFilter::Filter> ParseCompilerFilter(const std::string& compiler_filter_str) {
+  CompilerFilter::Filter compiler_filter;
+  if (!CompilerFilter::ParseCompilerFilter(compiler_filter_str.c_str(), &compiler_filter)) {
+    return Errorf("Failed to parse compiler filter '{}'", compiler_filter_str);
+  }
+  return compiler_filter;
+}
+
+OatFileAssistant::DexOptTrigger DexOptTriggerFromAidl(int32_t aidl_value) {
+  OatFileAssistant::DexOptTrigger trigger{};
+  if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_BETTER)) != 0) {
+    trigger.targetFilterIsBetter = true;
+  }
+  if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_SAME)) != 0) {
+    trigger.targetFilterIsSame = true;
+  }
+  if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_WORSE)) != 0) {
+    trigger.targetFilterIsWorse = true;
+  }
+  if ((aidl_value & static_cast<int32_t>(DexoptTrigger::PRIMARY_BOOT_IMAGE_BECOMES_USABLE)) != 0) {
+    trigger.primaryBootImageBecomesUsable = true;
+  }
+  if ((aidl_value & static_cast<int32_t>(DexoptTrigger::NEED_EXTRACTION)) != 0) {
+    trigger.needExtraction = true;
+  }
+  return trigger;
+}
+
+ArtifactsLocation ArtifactsLocationToAidl(OatFileAssistant::Location location) {
+  switch (location) {
+    case OatFileAssistant::Location::kLocationNoneOrError:
+      return ArtifactsLocation::NONE_OR_ERROR;
+    case OatFileAssistant::Location::kLocationOat:
+      return ArtifactsLocation::DALVIK_CACHE;
+    case OatFileAssistant::Location::kLocationOdex:
+      return ArtifactsLocation::NEXT_TO_DEX;
+    case OatFileAssistant::Location::kLocationDm:
+      return ArtifactsLocation::DM;
+      // No default. All cases should be explicitly handled, or the compilation will fail.
+  }
+  // This should never happen. Just in case we get a non-enumerator value.
+  LOG(FATAL) << "Unexpected Location " << location;
+}
+
+Result<void> PrepareArtifactsDir(const std::string& path, const FsPermission& fs_permission) {
+  std::error_code ec;
+  bool created = std::filesystem::create_directory(path, ec);
+  if (ec) {
+    return Errorf("Failed to create directory '{}': {}", path, ec.message());
+  }
+
+  auto cleanup = make_scope_guard([&] {
+    if (created) {
+      std::filesystem::remove(path, ec);
+    }
+  });
+
+  if (chmod(path.c_str(), DirFsPermissionToMode(fs_permission)) != 0) {
+    return ErrnoErrorf("Failed to chmod directory '{}'", path);
+  }
+  OR_RETURN(Chown(path, fs_permission));
+
+  cleanup.Disable();
+  return {};
+}
+
+Result<void> PrepareArtifactsDirs(const OutputArtifacts& output_artifacts,
+                                  /*out*/ std::string* oat_dir_path) {
+  if (output_artifacts.artifactsPath.isInDalvikCache) {
+    return {};
+  }
+
+  std::filesystem::path oat_path(OR_RETURN(BuildOatPath(output_artifacts.artifactsPath)));
+  std::filesystem::path isa_dir = oat_path.parent_path();
+  std::filesystem::path oat_dir = isa_dir.parent_path();
+  DCHECK_EQ(oat_dir.filename(), "oat");
+
+  OR_RETURN(PrepareArtifactsDir(oat_dir, output_artifacts.permissionSettings.dirFsPermission));
+  OR_RETURN(PrepareArtifactsDir(isa_dir, output_artifacts.permissionSettings.dirFsPermission));
+  *oat_dir_path = oat_dir;
+  return {};
+}
+
+Result<void> Restorecon(
+    const std::string& path,
+    const std::optional<OutputArtifacts::PermissionSettings::SeContext>& se_context) {
+  if (!kIsTargetAndroid) {
+    return {};
+  }
+
+  int res = 0;
+  if (se_context.has_value()) {
+    res = selinux_android_restorecon_pkgdir(path.c_str(),
+                                            se_context->seInfo.c_str(),
+                                            se_context->uid,
+                                            SELINUX_ANDROID_RESTORECON_RECURSE);
+  } else {
+    res = selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE);
+  }
+  if (res != 0) {
+    return ErrnoErrorf("Failed to restorecon directory '{}'", path);
+  }
+  return {};
+}
+
+Result<FileVisibility> GetFileVisibility(const std::string& file) {
+  std::error_code ec;
+  std::filesystem::file_status status = std::filesystem::status(file, ec);
+  if (!std::filesystem::status_known(status)) {
+    return Errorf("Failed to get status of '{}': {}", file, ec.message());
+  }
+  if (!std::filesystem::exists(status)) {
+    return FileVisibility::NOT_FOUND;
+  }
+
+  return (status.permissions() & std::filesystem::perms::others_read) !=
+                 std::filesystem::perms::none ?
+             FileVisibility::OTHER_READABLE :
+             FileVisibility::NOT_OTHER_READABLE;
+}
+
+Result<ArtdCancellationSignal*> ToArtdCancellationSignal(IArtdCancellationSignal* input) {
+  if (input == nullptr) {
+    return Error() << "Cancellation signal must not be nullptr";
+  }
+  // We cannot use `dynamic_cast` because ART code is compiled with `-fno-rtti`, so we have to check
+  // the magic number.
+  int64_t type;
+  if (!input->getType(&type).isOk() ||
+      type != reinterpret_cast<intptr_t>(kArtdCancellationSignalType)) {
+    // The cancellation signal must be created by `Artd::createCancellationSignal`.
+    return Error() << "Invalid cancellation signal type";
+  }
+  return static_cast<ArtdCancellationSignal*>(input);
+}
+
+Result<void> CopyFile(const std::string& src_path, const NewFile& dst_file) {
+  std::string content;
+  if (!ReadFileToString(src_path, &content)) {
+    return Errorf("Failed to read file '{}': {}", src_path, strerror(errno));
+  }
+  if (!WriteStringToFd(content, dst_file.Fd())) {
+    return Errorf("Failed to write file '{}': {}", dst_file.TempPath(), strerror(errno));
+  }
+  if (fsync(dst_file.Fd()) != 0) {
+    return Errorf("Failed to flush file '{}': {}", dst_file.TempPath(), strerror(errno));
+  }
+  if (lseek(dst_file.Fd(), /*offset=*/0, SEEK_SET) != 0) {
+    return Errorf(
+        "Failed to reset the offset for file '{}': {}", dst_file.TempPath(), strerror(errno));
+  }
+  return {};
+}
+
+Result<void> SetLogVerbosity() {
+  std::string options = android::base::GetProperty("dalvik.vm.artd-verbose", /*default_value=*/"");
+  if (options.empty()) {
+    return {};
+  }
+
+  CmdlineType<LogVerbosity> parser;
+  CmdlineParseResult<LogVerbosity> result = parser.Parse(options);
+  if (!result.IsSuccess()) {
+    return Error() << result.GetMessage();
+  }
+
+  gLogVerbosity = result.ReleaseValue();
+  return {};
+}
+
+class FdLogger {
+ public:
+  void Add(const NewFile& file) { fd_mapping_.emplace_back(file.Fd(), file.TempPath()); }
+  void Add(const File& file) { fd_mapping_.emplace_back(file.Fd(), file.GetPath()); }
+
+  std::string GetFds() {
+    std::vector<int> fds;
+    fds.reserve(fd_mapping_.size());
+    for (const auto& [fd, path] : fd_mapping_) {
+      fds.push_back(fd);
+    }
+    return Join(fds, ':');
+  }
+
+ private:
+  std::vector<std::pair<int, std::string>> fd_mapping_;
+
+  friend std::ostream& operator<<(std::ostream& os, const FdLogger& fd_logger);
 };
 
-}  // namespace artd
-}  // namespace android
+std::ostream& operator<<(std::ostream& os, const FdLogger& fd_logger) {
+  for (const auto& [fd, path] : fd_logger.fd_mapping_) {
+    os << fd << ":" << path << ' ';
+  }
+  return os;
+}
 
-int main(const int argc __attribute__((unused)), char* argv[]) {
-  setenv("ANDROID_LOG_TAGS", "*:v", 1);
-  android::base::InitLogging(argv);
+}  // namespace
 
-  android::artd::Artd artd;
+#define OR_RETURN_ERROR(func, expr)         \
+  ({                                        \
+    decltype(expr)&& tmp = (expr);          \
+    if (!tmp.ok()) {                        \
+      return (func)(tmp.error().message()); \
+    }                                       \
+    std::move(tmp).value();                 \
+  })
 
-  if (auto ret = artd.Start(); !ret.isOk()) {
-    LOG(ERROR) << "Unable to start artd: " << ret.getMessage();
-    exit(1);
+#define OR_RETURN_FATAL(expr)     OR_RETURN_ERROR(Fatal, expr)
+#define OR_RETURN_NON_FATAL(expr) OR_RETURN_ERROR(NonFatal, expr)
+
+ScopedAStatus Artd::isAlive(bool* _aidl_return) {
+  *_aidl_return = true;
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::deleteArtifacts(const ArtifactsPath& in_artifactsPath, int64_t* _aidl_return) {
+  std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_artifactsPath));
+
+  *_aidl_return = 0;
+  *_aidl_return += GetSizeAndDeleteFile(oat_path);
+  *_aidl_return += GetSizeAndDeleteFile(OatPathToVdexPath(oat_path));
+  *_aidl_return += GetSizeAndDeleteFile(OatPathToArtPath(oat_path));
+
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::getDexoptStatus(const std::string& in_dexFile,
+                                    const std::string& in_instructionSet,
+                                    const std::optional<std::string>& in_classLoaderContext,
+                                    GetDexoptStatusResult* _aidl_return) {
+  Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext();
+  if (!ofa_context.ok()) {
+    return NonFatal("Failed to get runtime options: " + ofa_context.error().message());
   }
 
-  ABinderProcess_joinThreadPool();
+  std::unique_ptr<ClassLoaderContext> context;
+  std::string error_msg;
+  auto oat_file_assistant = OatFileAssistant::Create(in_dexFile,
+                                                     in_instructionSet,
+                                                     in_classLoaderContext,
+                                                     /*load_executable=*/false,
+                                                     /*only_load_trusted_executable=*/true,
+                                                     ofa_context.value(),
+                                                     &context,
+                                                     &error_msg);
+  if (oat_file_assistant == nullptr) {
+    return NonFatal("Failed to create OatFileAssistant: " + error_msg);
+  }
 
-  LOG(INFO) << "artd shutting down";
+  std::string ignored_odex_status;
+  oat_file_assistant->GetOptimizationStatus(&_aidl_return->locationDebugString,
+                                            &_aidl_return->compilerFilter,
+                                            &_aidl_return->compilationReason,
+                                            &ignored_odex_status);
 
-  return 0;
+  // We ignore odex_status because it is not meaningful. It can only be either "up-to-date",
+  // "apk-more-recent", or "io-error-no-oat", which means it doesn't give us information in addition
+  // to what we can learn from compiler_filter because compiler_filter will be the actual compiler
+  // filter, "run-from-apk-fallback", and "run-from-apk" in those three cases respectively.
+  DCHECK(ignored_odex_status == "up-to-date" || ignored_odex_status == "apk-more-recent" ||
+         ignored_odex_status == "io-error-no-oat");
+
+  return ScopedAStatus::ok();
 }
+
+ndk::ScopedAStatus Artd::isProfileUsable(const ProfilePath& in_profile,
+                                         const std::string& in_dexFile,
+                                         bool* _aidl_return) {
+  std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+
+  FdLogger fd_logger;
+
+  CmdlineBuilder art_exec_args;
+  art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities");
+
+  CmdlineBuilder args;
+  args.Add(OR_RETURN_FATAL(GetProfman()));
+
+  Result<std::unique_ptr<File>> profile = OpenFileForReading(profile_path);
+  if (!profile.ok()) {
+    if (profile.error().code() == ENOENT) {
+      *_aidl_return = false;
+      return ScopedAStatus::ok();
+    }
+    return NonFatal(
+        "Failed to open profile '{}': {}"_format(profile_path, profile.error().message()));
+  }
+  args.Add("--reference-profile-file-fd=%d", profile.value()->Fd());
+  fd_logger.Add(*profile.value());
+
+  std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+  args.Add("--apk-fd=%d", dex_file->Fd());
+  fd_logger.Add(*dex_file);
+
+  art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args));
+
+  LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ")
+            << "\nOpened FDs: " << fd_logger;
+
+  Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec);
+  if (!result.ok()) {
+    return NonFatal("Failed to run profman: " + result.error().message());
+  }
+
+  LOG(INFO) << "profman returned code {}"_format(result.value());
+
+  if (result.value() != ProfmanResult::kSkipCompilationSmallDelta &&
+      result.value() != ProfmanResult::kSkipCompilationEmptyProfiles) {
+    return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+  }
+
+  *_aidl_return = result.value() == ProfmanResult::kSkipCompilationSmallDelta;
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src,
+                                               OutputProfile* in_dst,
+                                               const std::string& in_dexFile,
+                                               bool* _aidl_return) {
+  std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src));
+  std::string dst_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_dst->profilePath));
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+
+  FdLogger fd_logger;
+
+  CmdlineBuilder art_exec_args;
+  art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities");
+
+  CmdlineBuilder args;
+  args.Add(OR_RETURN_FATAL(GetProfman())).Add("--copy-and-update-profile-key");
+
+  Result<std::unique_ptr<File>> src = OpenFileForReading(src_path);
+  if (!src.ok()) {
+    if (src.error().code() == ENOENT) {
+      *_aidl_return = false;
+      return ScopedAStatus::ok();
+    }
+    return NonFatal("Failed to open src profile '{}': {}"_format(src_path, src.error().message()));
+  }
+  args.Add("--profile-file-fd=%d", src.value()->Fd());
+  fd_logger.Add(*src.value());
+
+  std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+  args.Add("--apk-fd=%d", dex_file->Fd());
+  fd_logger.Add(*dex_file);
+
+  std::unique_ptr<NewFile> dst =
+      OR_RETURN_NON_FATAL(NewFile::Create(dst_path, in_dst->fsPermission));
+  args.Add("--reference-profile-file-fd=%d", dst->Fd());
+  fd_logger.Add(*dst);
+
+  art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args));
+
+  LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ")
+            << "\nOpened FDs: " << fd_logger;
+
+  Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec);
+  if (!result.ok()) {
+    return NonFatal("Failed to run profman: " + result.error().message());
+  }
+
+  LOG(INFO) << "profman returned code {}"_format(result.value());
+
+  if (result.value() == ProfmanResult::kCopyAndUpdateNoMatch) {
+    *_aidl_return = false;
+    return ScopedAStatus::ok();
+  }
+
+  if (result.value() != ProfmanResult::kCopyAndUpdateSuccess) {
+    return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+  }
+
+  OR_RETURN_NON_FATAL(dst->Keep());
+  *_aidl_return = true;
+  in_dst->profilePath.id = dst->TempId();
+  in_dst->profilePath.tmpPath = dst->TempPath();
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::commitTmpProfile(const TmpProfilePath& in_profile) {
+  std::string tmp_profile_path = OR_RETURN_FATAL(BuildTmpProfilePath(in_profile));
+  std::string ref_profile_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_profile));
+
+  std::error_code ec;
+  std::filesystem::rename(tmp_profile_path, ref_profile_path, ec);
+  if (ec) {
+    return NonFatal(
+        "Failed to move '{}' to '{}': {}"_format(tmp_profile_path, ref_profile_path, ec.message()));
+  }
+
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::deleteProfile(const ProfilePath& in_profile) {
+  std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+
+  std::error_code ec;
+  std::filesystem::remove(profile_path, ec);
+  if (ec) {
+    LOG(ERROR) << "Failed to remove '{}': {}"_format(profile_path, ec.message());
+  }
+
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getProfileVisibility(const ProfilePath& in_profile,
+                                              FileVisibility* _aidl_return) {
+  std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+  *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(profile_path));
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getArtifactsVisibility(const ArtifactsPath& in_artifactsPath,
+                                                FileVisibility* _aidl_return) {
+  std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_artifactsPath));
+  *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(oat_path));
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getDexFileVisibility(const std::string& in_dexFile,
+                                              FileVisibility* _aidl_return) {
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+  *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(in_dexFile));
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getDmFileVisibility(const DexMetadataPath& in_dmFile,
+                                             FileVisibility* _aidl_return) {
+  std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile));
+  *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(dm_path));
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profiles,
+                                       const std::optional<ProfilePath>& in_referenceProfile,
+                                       OutputProfile* in_outputProfile,
+                                       const std::vector<std::string>& in_dexFiles,
+                                       const MergeProfileOptions& in_options,
+                                       bool* _aidl_return) {
+  std::vector<std::string> profile_paths;
+  for (const ProfilePath& profile : in_profiles) {
+    std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(profile));
+    if (profile.getTag() == ProfilePath::dexMetadataPath) {
+      return Fatal("Does not support DM file, got '{}'"_format(profile_path));
+    }
+    profile_paths.push_back(std::move(profile_path));
+  }
+  std::string output_profile_path =
+      OR_RETURN_FATAL(BuildFinalProfilePath(in_outputProfile->profilePath));
+  for (const std::string& dex_file : in_dexFiles) {
+    OR_RETURN_FATAL(ValidateDexPath(dex_file));
+  }
+  if (in_options.forceMerge + in_options.dumpOnly + in_options.dumpClassesAndMethods > 1) {
+    return Fatal("Only one of 'forceMerge', 'dumpOnly', and 'dumpClassesAndMethods' can be set");
+  }
+
+  FdLogger fd_logger;
+
+  CmdlineBuilder art_exec_args;
+  art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities");
+
+  CmdlineBuilder args;
+  args.Add(OR_RETURN_FATAL(GetProfman()));
+
+  std::vector<std::unique_ptr<File>> profile_files;
+  for (const std::string& profile_path : profile_paths) {
+    Result<std::unique_ptr<File>> profile_file = OpenFileForReading(profile_path);
+    if (!profile_file.ok()) {
+      if (profile_file.error().code() == ENOENT) {
+        // Skip non-existing file.
+        continue;
+      }
+      return NonFatal(
+          "Failed to open profile '{}': {}"_format(profile_path, profile_file.error().message()));
+    }
+    args.Add("--profile-file-fd=%d", profile_file.value()->Fd());
+    fd_logger.Add(*profile_file.value());
+    profile_files.push_back(std::move(profile_file.value()));
+  }
+
+  if (profile_files.empty()) {
+    LOG(INFO) << "Merge skipped because there are no existing profiles";
+    *_aidl_return = false;
+    return ScopedAStatus::ok();
+  }
+
+  std::unique_ptr<NewFile> output_profile_file =
+      OR_RETURN_NON_FATAL(NewFile::Create(output_profile_path, in_outputProfile->fsPermission));
+
+  if (in_referenceProfile.has_value()) {
+    if (in_options.forceMerge || in_options.dumpOnly || in_options.dumpClassesAndMethods) {
+      return Fatal(
+          "Reference profile must not be set when 'forceMerge', 'dumpOnly', or "
+          "'dumpClassesAndMethods' is set");
+    }
+    std::string reference_profile_path =
+        OR_RETURN_FATAL(BuildProfileOrDmPath(*in_referenceProfile));
+    if (in_referenceProfile->getTag() == ProfilePath::dexMetadataPath) {
+      return Fatal("Does not support DM file, got '{}'"_format(reference_profile_path));
+    }
+    OR_RETURN_NON_FATAL(CopyFile(reference_profile_path, *output_profile_file));
+  }
+
+  if (in_options.dumpOnly || in_options.dumpClassesAndMethods) {
+    args.Add("--dump-output-to-fd=%d", output_profile_file->Fd());
+  } else {
+    // profman is ok with this being an empty file when in_referenceProfile isn't set.
+    args.Add("--reference-profile-file-fd=%d", output_profile_file->Fd());
+  }
+  fd_logger.Add(*output_profile_file);
+
+  std::vector<std::unique_ptr<File>> dex_files;
+  for (const std::string& dex_path : in_dexFiles) {
+    std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(dex_path));
+    args.Add("--apk-fd=%d", dex_file->Fd());
+    fd_logger.Add(*dex_file);
+    dex_files.push_back(std::move(dex_file));
+  }
+
+  if (in_options.dumpOnly || in_options.dumpClassesAndMethods) {
+    args.Add(in_options.dumpOnly ? "--dump-only" : "--dump-classes-and-methods");
+  } else {
+    args.AddIfNonEmpty("--min-new-classes-percent-change=%s",
+                       props_->GetOrEmpty("dalvik.vm.bgdexopt.new-classes-percent"))
+        .AddIfNonEmpty("--min-new-methods-percent-change=%s",
+                       props_->GetOrEmpty("dalvik.vm.bgdexopt.new-methods-percent"))
+        .AddIf(in_options.forceMerge, "--force-merge")
+        .AddIf(in_options.forBootImage, "--boot-image-merge");
+  }
+
+  art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args));
+
+  LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ")
+            << "\nOpened FDs: " << fd_logger;
+
+  Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec);
+  if (!result.ok()) {
+    return NonFatal("Failed to run profman: " + result.error().message());
+  }
+
+  LOG(INFO) << "profman returned code {}"_format(result.value());
+
+  if (result.value() == ProfmanResult::kSkipCompilationSmallDelta ||
+      result.value() == ProfmanResult::kSkipCompilationEmptyProfiles) {
+    *_aidl_return = false;
+    return ScopedAStatus::ok();
+  }
+
+  ProfmanResult::ProcessingResult expected_result =
+      (in_options.forceMerge || in_options.dumpOnly || in_options.dumpClassesAndMethods) ?
+          ProfmanResult::kSuccess :
+          ProfmanResult::kCompile;
+  if (result.value() != expected_result) {
+    return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+  }
+
+  OR_RETURN_NON_FATAL(output_profile_file->Keep());
+  *_aidl_return = true;
+  in_outputProfile->profilePath.id = output_profile_file->TempId();
+  in_outputProfile->profilePath.tmpPath = output_profile_file->TempPath();
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getDexoptNeeded(const std::string& in_dexFile,
+                                         const std::string& in_instructionSet,
+                                         const std::optional<std::string>& in_classLoaderContext,
+                                         const std::string& in_compilerFilter,
+                                         int32_t in_dexoptTrigger,
+                                         GetDexoptNeededResult* _aidl_return) {
+  Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext();
+  if (!ofa_context.ok()) {
+    return NonFatal("Failed to get runtime options: " + ofa_context.error().message());
+  }
+
+  std::unique_ptr<ClassLoaderContext> context;
+  std::string error_msg;
+  auto oat_file_assistant = OatFileAssistant::Create(in_dexFile,
+                                                     in_instructionSet,
+                                                     in_classLoaderContext,
+                                                     /*load_executable=*/false,
+                                                     /*only_load_trusted_executable=*/true,
+                                                     ofa_context.value(),
+                                                     &context,
+                                                     &error_msg);
+  if (oat_file_assistant == nullptr) {
+    return NonFatal("Failed to create OatFileAssistant: " + error_msg);
+  }
+
+  OatFileAssistant::DexOptStatus status;
+  _aidl_return->isDexoptNeeded =
+      oat_file_assistant->GetDexOptNeeded(OR_RETURN_FATAL(ParseCompilerFilter(in_compilerFilter)),
+                                          DexOptTriggerFromAidl(in_dexoptTrigger),
+                                          &status);
+  _aidl_return->isVdexUsable = status.IsVdexUsable();
+  _aidl_return->artifactsLocation = ArtifactsLocationToAidl(status.GetLocation());
+
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::dexopt(
+    const OutputArtifacts& in_outputArtifacts,
+    const std::string& in_dexFile,
+    const std::string& in_instructionSet,
+    const std::optional<std::string>& in_classLoaderContext,
+    const std::string& in_compilerFilter,
+    const std::optional<ProfilePath>& in_profile,
+    const std::optional<VdexPath>& in_inputVdex,
+    const std::optional<DexMetadataPath>& in_dmFile,
+    PriorityClass in_priorityClass,
+    const DexoptOptions& in_dexoptOptions,
+    const std::shared_ptr<IArtdCancellationSignal>& in_cancellationSignal,
+    ArtdDexoptResult* _aidl_return) {
+  _aidl_return->cancelled = false;
+
+  std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_outputArtifacts.artifactsPath));
+  std::string vdex_path = OatPathToVdexPath(oat_path);
+  std::string art_path = OatPathToArtPath(oat_path);
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+  std::optional<std::string> profile_path =
+      in_profile.has_value() ?
+          std::make_optional(OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile.value()))) :
+          std::nullopt;
+  ArtdCancellationSignal* cancellation_signal =
+      OR_RETURN_FATAL(ToArtdCancellationSignal(in_cancellationSignal.get()));
+
+  std::unique_ptr<ClassLoaderContext> context = nullptr;
+  if (in_classLoaderContext.has_value()) {
+    context = ClassLoaderContext::Create(in_classLoaderContext->c_str());
+    if (context == nullptr) {
+      return Fatal("Class loader context '{}' is invalid"_format(in_classLoaderContext.value()));
+    }
+  }
+
+  std::string oat_dir_path;  // For restorecon, can be empty if the artifacts are in dalvik-cache.
+  OR_RETURN_NON_FATAL(PrepareArtifactsDirs(in_outputArtifacts, &oat_dir_path));
+
+  // First-round restorecon. artd doesn't have the permission to create files with the
+  // `apk_data_file` label, so we need to restorecon the "oat" directory first so that files will
+  // inherit `dalvikcache_data_file` rather than `apk_data_file`.
+  if (!in_outputArtifacts.artifactsPath.isInDalvikCache) {
+    OR_RETURN_NON_FATAL(Restorecon(oat_dir_path, in_outputArtifacts.permissionSettings.seContext));
+  }
+
+  FdLogger fd_logger;
+
+  CmdlineBuilder art_exec_args;
+  art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities");
+
+  CmdlineBuilder args;
+  args.Add(OR_RETURN_FATAL(GetDex2Oat()));
+
+  const FsPermission& fs_permission = in_outputArtifacts.permissionSettings.fileFsPermission;
+
+  std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+  args.Add("--zip-fd=%d", dex_file->Fd()).Add("--zip-location=%s", in_dexFile);
+  fd_logger.Add(*dex_file);
+  struct stat dex_st = OR_RETURN_NON_FATAL(Fstat(*dex_file));
+  if ((dex_st.st_mode & S_IROTH) == 0) {
+    if (fs_permission.isOtherReadable) {
+      return NonFatal(
+          "Outputs cannot be other-readable because the dex file '{}' is not other-readable"_format(
+              dex_file->GetPath()));
+    }
+    // Negative numbers mean no `chown`. 0 means root.
+    // Note: this check is more strict than it needs to be. For example, it doesn't allow the
+    // outputs to belong to a group that is a subset of the dex file's group. This is for
+    // simplicity, and it's okay as we don't have to handle such complicated cases in practice.
+    if ((fs_permission.uid > 0 && static_cast<uid_t>(fs_permission.uid) != dex_st.st_uid) ||
+        (fs_permission.gid > 0 && static_cast<gid_t>(fs_permission.gid) != dex_st.st_uid &&
+         static_cast<gid_t>(fs_permission.gid) != dex_st.st_gid)) {
+      return NonFatal(
+          "Outputs' owner doesn't match the dex file '{}' (outputs: {}:{}, dex file: {}:{})"_format(
+              dex_file->GetPath(),
+              fs_permission.uid,
+              fs_permission.gid,
+              dex_st.st_uid,
+              dex_st.st_gid));
+    }
+  }
+
+  std::unique_ptr<NewFile> oat_file = OR_RETURN_NON_FATAL(NewFile::Create(oat_path, fs_permission));
+  args.Add("--oat-fd=%d", oat_file->Fd()).Add("--oat-location=%s", oat_path);
+  fd_logger.Add(*oat_file);
+
+  std::unique_ptr<NewFile> vdex_file =
+      OR_RETURN_NON_FATAL(NewFile::Create(vdex_path, fs_permission));
+  args.Add("--output-vdex-fd=%d", vdex_file->Fd());
+  fd_logger.Add(*vdex_file);
+
+  std::vector<NewFile*> files_to_commit{oat_file.get(), vdex_file.get()};
+  std::vector<std::string_view> files_to_delete;
+
+  std::unique_ptr<NewFile> art_file = nullptr;
+  if (in_dexoptOptions.generateAppImage) {
+    art_file = OR_RETURN_NON_FATAL(NewFile::Create(art_path, fs_permission));
+    args.Add("--app-image-fd=%d", art_file->Fd());
+    args.AddIfNonEmpty("--image-format=%s", props_->GetOrEmpty("dalvik.vm.appimageformat"));
+    fd_logger.Add(*art_file);
+    files_to_commit.push_back(art_file.get());
+  } else {
+    files_to_delete.push_back(art_path);
+  }
+
+  std::unique_ptr<NewFile> swap_file = nullptr;
+  if (ShouldCreateSwapFileForDexopt()) {
+    swap_file = OR_RETURN_NON_FATAL(
+        NewFile::Create("{}.swap"_format(oat_path), FsPermission{.uid = -1, .gid = -1}));
+    args.Add("--swap-fd=%d", swap_file->Fd());
+    fd_logger.Add(*swap_file);
+  }
+
+  std::vector<std::unique_ptr<File>> context_files;
+  if (context != nullptr) {
+    std::vector<std::string> flattened_context = context->FlattenDexPaths();
+    std::string dex_dir = Dirname(in_dexFile.c_str());
+    std::vector<int> context_fds;
+    for (const std::string& context_element : flattened_context) {
+      std::string context_path = std::filesystem::path(dex_dir).append(context_element);
+      OR_RETURN_FATAL(ValidateDexPath(context_path));
+      std::unique_ptr<File> context_file = OR_RETURN_NON_FATAL(OpenFileForReading(context_path));
+      context_fds.push_back(context_file->Fd());
+      fd_logger.Add(*context_file);
+      context_files.push_back(std::move(context_file));
+    }
+    args.AddIfNonEmpty("--class-loader-context-fds=%s", Join(context_fds, /*separator=*/':'))
+        .Add("--class-loader-context=%s", in_classLoaderContext.value())
+        .Add("--classpath-dir=%s", dex_dir);
+  }
+
+  std::unique_ptr<File> input_vdex_file = nullptr;
+  if (in_inputVdex.has_value()) {
+    std::string input_vdex_path = OR_RETURN_FATAL(BuildVdexPath(in_inputVdex.value()));
+    input_vdex_file = OR_RETURN_NON_FATAL(OpenFileForReading(input_vdex_path));
+    args.Add("--input-vdex-fd=%d", input_vdex_file->Fd());
+    fd_logger.Add(*input_vdex_file);
+  }
+
+  std::unique_ptr<File> dm_file = nullptr;
+  if (in_dmFile.has_value()) {
+    std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile.value()));
+    dm_file = OR_RETURN_NON_FATAL(OpenFileForReading(dm_path));
+    args.Add("--dm-fd=%d", dm_file->Fd());
+    fd_logger.Add(*dm_file);
+  }
+
+  std::unique_ptr<File> profile_file = nullptr;
+  if (profile_path.has_value()) {
+    profile_file = OR_RETURN_NON_FATAL(OpenFileForReading(profile_path.value()));
+    args.Add("--profile-file-fd=%d", profile_file->Fd());
+    fd_logger.Add(*profile_file);
+    struct stat profile_st = OR_RETURN_NON_FATAL(Fstat(*profile_file));
+    if (fs_permission.isOtherReadable && (profile_st.st_mode & S_IROTH) == 0) {
+      return NonFatal(
+          "Outputs cannot be other-readable because the profile '{}' is not other-readable"_format(
+              profile_file->GetPath()));
+    }
+    // TODO(b/260228411): Check uid and gid.
+  }
+
+  // Second-round restorecon. Restorecon recursively after the output files are created, so that the
+  // SELinux context is applied to all of them. The SELinux context of a file is mostly inherited
+  // from the parent directory upon creation, but the MLS label is not inherited, so we need to
+  // restorecon every file so that they have the right MLS label. If the files are in dalvik-cache,
+  // there's no need to restorecon because they inherits the SELinux context of the dalvik-cache
+  // directory and they don't need to have MLS labels.
+  if (!in_outputArtifacts.artifactsPath.isInDalvikCache) {
+    OR_RETURN_NON_FATAL(Restorecon(oat_dir_path, in_outputArtifacts.permissionSettings.seContext));
+  }
+
+  AddBootImageFlags(args);
+  AddCompilerConfigFlags(
+      in_instructionSet, in_compilerFilter, in_priorityClass, in_dexoptOptions, args);
+  AddPerfConfigFlags(in_priorityClass, art_exec_args, args);
+
+  // For being surfaced in crash reports on crashes.
+  args.Add("--comments=%s", in_dexoptOptions.comments);
+
+  art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args));
+
+  LOG(INFO) << "Running dex2oat: " << Join(art_exec_args.Get(), /*separator=*/" ")
+            << "\nOpened FDs: " << fd_logger;
+
+  ExecCallbacks callbacks{
+      .on_start =
+          [&](pid_t pid) {
+            std::lock_guard<std::mutex> lock(cancellation_signal->mu_);
+            cancellation_signal->pids_.insert(pid);
+            // Handle cancellation signals sent before the process starts.
+            if (cancellation_signal->is_cancelled_) {
+              int res = kill_(pid, SIGKILL);
+              DCHECK_EQ(res, 0);
+            }
+          },
+      .on_end =
+          [&](pid_t pid) {
+            std::lock_guard<std::mutex> lock(cancellation_signal->mu_);
+            // The pid should no longer receive kill signals sent by `cancellation_signal`.
+            cancellation_signal->pids_.erase(pid);
+          },
+  };
+
+  ProcessStat stat;
+  Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kLongTimeoutSec, callbacks, &stat);
+  _aidl_return->wallTimeMs = stat.wall_time_ms;
+  _aidl_return->cpuTimeMs = stat.cpu_time_ms;
+  if (!result.ok()) {
+    {
+      std::lock_guard<std::mutex> lock(cancellation_signal->mu_);
+      if (cancellation_signal->is_cancelled_) {
+        _aidl_return->cancelled = true;
+        return ScopedAStatus::ok();
+      }
+    }
+    return NonFatal("Failed to run dex2oat: " + result.error().message());
+  }
+
+  LOG(INFO) << "dex2oat returned code {}"_format(result.value());
+
+  if (result.value() != 0) {
+    return NonFatal("dex2oat returned an unexpected code: {}"_format(result.value()));
+  }
+
+  int64_t size_bytes = 0;
+  int64_t size_before_bytes = 0;
+  for (const NewFile* file : files_to_commit) {
+    size_bytes += GetSize(file->TempPath()).value_or(0);
+    size_before_bytes += GetSize(file->FinalPath()).value_or(0);
+  }
+  for (std::string_view path : files_to_delete) {
+    size_before_bytes += GetSize(path).value_or(0);
+  }
+  OR_RETURN_NON_FATAL(NewFile::CommitAllOrAbandon(files_to_commit, files_to_delete));
+
+  _aidl_return->sizeBytes = size_bytes;
+  _aidl_return->sizeBeforeBytes = size_before_bytes;
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus ArtdCancellationSignal::cancel() {
+  std::lock_guard<std::mutex> lock(mu_);
+  is_cancelled_ = true;
+  for (pid_t pid : pids_) {
+    int res = kill_(pid, SIGKILL);
+    DCHECK_EQ(res, 0);
+  }
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus ArtdCancellationSignal::getType(int64_t* _aidl_return) {
+  *_aidl_return = reinterpret_cast<intptr_t>(kArtdCancellationSignalType);
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::createCancellationSignal(
+    std::shared_ptr<IArtdCancellationSignal>* _aidl_return) {
+  *_aidl_return = ndk::SharedRefBase::make<ArtdCancellationSignal>(kill_);
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::cleanup(const std::vector<ProfilePath>& in_profilesToKeep,
+                            const std::vector<ArtifactsPath>& in_artifactsToKeep,
+                            const std::vector<VdexPath>& in_vdexFilesToKeep,
+                            int64_t* _aidl_return) {
+  std::unordered_set<std::string> files_to_keep;
+  for (const ProfilePath& profile : in_profilesToKeep) {
+    files_to_keep.insert(OR_RETURN_FATAL(BuildProfileOrDmPath(profile)));
+  }
+  for (const ArtifactsPath& artifacts : in_artifactsToKeep) {
+    std::string oat_path = OR_RETURN_FATAL(BuildOatPath(artifacts));
+    files_to_keep.insert(OatPathToVdexPath(oat_path));
+    files_to_keep.insert(OatPathToArtPath(oat_path));
+    files_to_keep.insert(std::move(oat_path));
+  }
+  for (const VdexPath& vdex : in_vdexFilesToKeep) {
+    files_to_keep.insert(OR_RETURN_FATAL(BuildVdexPath(vdex)));
+  }
+  *_aidl_return = 0;
+  for (const std::string& file : OR_RETURN_NON_FATAL(ListManagedFiles())) {
+    if (files_to_keep.find(file) == files_to_keep.end()) {
+      LOG(INFO) << "Cleaning up obsolete file '{}'"_format(file);
+      *_aidl_return += GetSizeAndDeleteFile(file);
+    }
+  }
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::isIncrementalFsPath(const std::string& in_dexFile [[maybe_unused]],
+                                        bool* _aidl_return) {
+#ifdef __BIONIC__
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+  struct statfs st;
+  if (statfs(in_dexFile.c_str(), &st) != 0) {
+    PLOG(ERROR) << "Failed to statfs '{}'"_format(in_dexFile);
+    *_aidl_return = false;
+    return ScopedAStatus::ok();
+  }
+  *_aidl_return = st.f_type == INCFS_MAGIC_NUMBER;
+  return ScopedAStatus::ok();
+#else
+  *_aidl_return = false;
+  return ScopedAStatus::ok();
+#endif
+}
+
+Result<void> Artd::Start() {
+  OR_RETURN(SetLogVerbosity());
+
+  ScopedAStatus status = ScopedAStatus::fromStatus(
+      AServiceManager_registerLazyService(this->asBinder().get(), kServiceName));
+  if (!status.isOk()) {
+    return Error() << status.getDescription();
+  }
+
+  ABinderProcess_startThreadPool();
+
+  return {};
+}
+
+Result<OatFileAssistantContext*> Artd::GetOatFileAssistantContext() {
+  std::lock_guard<std::mutex> lock(ofa_context_mu_);
+
+  if (ofa_context_ == nullptr) {
+    ofa_context_ = std::make_unique<OatFileAssistantContext>(
+        std::make_unique<OatFileAssistantContext::RuntimeOptions>(
+            OatFileAssistantContext::RuntimeOptions{
+                .image_locations = *OR_RETURN(GetBootImageLocations()),
+                .boot_class_path = *OR_RETURN(GetBootClassPath()),
+                .boot_class_path_locations = *OR_RETURN(GetBootClassPath()),
+                .deny_art_apex_data_files = DenyArtApexDataFiles(),
+            }));
+    std::string error_msg;
+    if (!ofa_context_->FetchAll(&error_msg)) {
+      return Error() << error_msg;
+    }
+  }
+
+  return ofa_context_.get();
+}
+
+Result<const std::vector<std::string>*> Artd::GetBootImageLocations() {
+  std::lock_guard<std::mutex> lock(cache_mu_);
+
+  if (!cached_boot_image_locations_.has_value()) {
+    std::string location_str;
+
+    if (UseJitZygoteLocked()) {
+      location_str = GetJitZygoteBootImageLocation();
+    } else if (std::string value = GetUserDefinedBootImageLocationsLocked(); !value.empty()) {
+      location_str = std::move(value);
+    } else {
+      std::string error_msg;
+      std::string android_root = GetAndroidRootSafe(&error_msg);
+      if (!error_msg.empty()) {
+        return Errorf("Failed to get ANDROID_ROOT: {}", error_msg);
+      }
+      location_str = GetDefaultBootImageLocation(android_root, DenyArtApexDataFilesLocked());
+    }
+
+    cached_boot_image_locations_ = Split(location_str, ":");
+  }
+
+  return &cached_boot_image_locations_.value();
+}
+
+Result<const std::vector<std::string>*> Artd::GetBootClassPath() {
+  std::lock_guard<std::mutex> lock(cache_mu_);
+
+  if (!cached_boot_class_path_.has_value()) {
+    const char* env_value = getenv("BOOTCLASSPATH");
+    if (env_value == nullptr || strlen(env_value) == 0) {
+      return Errorf("Failed to get environment variable 'BOOTCLASSPATH'");
+    }
+    cached_boot_class_path_ = Split(env_value, ":");
+  }
+
+  return &cached_boot_class_path_.value();
+}
+
+bool Artd::UseJitZygote() {
+  std::lock_guard<std::mutex> lock(cache_mu_);
+  return UseJitZygoteLocked();
+}
+
+bool Artd::UseJitZygoteLocked() {
+  if (!cached_use_jit_zygote_.has_value()) {
+    cached_use_jit_zygote_ =
+        props_->GetBool("persist.device_config.runtime_native_boot.profilebootclasspath",
+                        "dalvik.vm.profilebootclasspath",
+                        /*default_value=*/false);
+  }
+
+  return cached_use_jit_zygote_.value();
+}
+
+const std::string& Artd::GetUserDefinedBootImageLocations() {
+  std::lock_guard<std::mutex> lock(cache_mu_);
+  return GetUserDefinedBootImageLocationsLocked();
+}
+
+const std::string& Artd::GetUserDefinedBootImageLocationsLocked() {
+  if (!cached_user_defined_boot_image_locations_.has_value()) {
+    cached_user_defined_boot_image_locations_ = props_->GetOrEmpty("dalvik.vm.boot-image");
+  }
+
+  return cached_user_defined_boot_image_locations_.value();
+}
+
+bool Artd::DenyArtApexDataFiles() {
+  std::lock_guard<std::mutex> lock(cache_mu_);
+  return DenyArtApexDataFilesLocked();
+}
+
+bool Artd::DenyArtApexDataFilesLocked() {
+  if (!cached_deny_art_apex_data_files_.has_value()) {
+    cached_deny_art_apex_data_files_ =
+        !props_->GetBool("odsign.verification.success", /*default_value=*/false);
+  }
+
+  return cached_deny_art_apex_data_files_.value();
+}
+
+Result<std::string> Artd::GetProfman() { return BuildArtBinPath("profman"); }
+
+Result<std::string> Artd::GetArtExec() { return BuildArtBinPath("art_exec"); }
+
+bool Artd::ShouldUseDex2Oat64() {
+  return !props_->GetOrEmpty("ro.product.cpu.abilist64").empty() &&
+         props_->GetBool("dalvik.vm.dex2oat64.enabled", /*default_value=*/false);
+}
+
+Result<std::string> Artd::GetDex2Oat() {
+  std::string binary_name = ShouldUseDex2Oat64() ? "dex2oat64" : "dex2oat32";
+  // TODO(b/234351700): Should we use the "d" variant?
+  return BuildArtBinPath(binary_name);
+}
+
+bool Artd::ShouldCreateSwapFileForDexopt() {
+  // Create a swap file by default. Dex2oat will decide whether to use it or not.
+  return props_->GetBool("dalvik.vm.dex2oat-swap", /*default_value=*/true);
+}
+
+void Artd::AddBootImageFlags(/*out*/ CmdlineBuilder& args) {
+  if (UseJitZygote()) {
+    args.Add("--force-jit-zygote");
+  } else {
+    args.AddIfNonEmpty("--boot-image=%s", GetUserDefinedBootImageLocations());
+  }
+}
+
+void Artd::AddCompilerConfigFlags(const std::string& instruction_set,
+                                  const std::string& compiler_filter,
+                                  PriorityClass priority_class,
+                                  const DexoptOptions& dexopt_options,
+                                  /*out*/ CmdlineBuilder& args) {
+  args.Add("--instruction-set=%s", instruction_set);
+  std::string features_prop = "dalvik.vm.isa.{}.features"_format(instruction_set);
+  args.AddIfNonEmpty("--instruction-set-features=%s", props_->GetOrEmpty(features_prop));
+  std::string variant_prop = "dalvik.vm.isa.{}.variant"_format(instruction_set);
+  args.AddIfNonEmpty("--instruction-set-variant=%s", props_->GetOrEmpty(variant_prop));
+
+  args.Add("--compiler-filter=%s", compiler_filter)
+      .Add("--compilation-reason=%s", dexopt_options.compilationReason);
+
+  args.AddIf(priority_class >= PriorityClass::INTERACTIVE, "--compact-dex-level=none");
+
+  args.AddIfNonEmpty("--max-image-block-size=%s",
+                     props_->GetOrEmpty("dalvik.vm.dex2oat-max-image-block-size"))
+      .AddIfNonEmpty("--very-large-app-threshold=%s",
+                     props_->GetOrEmpty("dalvik.vm.dex2oat-very-large"))
+      .AddIfNonEmpty(
+          "--resolve-startup-const-strings=%s",
+          props_->GetOrEmpty("persist.device_config.runtime.dex2oat_resolve_startup_strings",
+                             "dalvik.vm.dex2oat-resolve-startup-strings"));
+
+  args.AddIf(dexopt_options.debuggable, "--debuggable")
+      .AddIf(props_->GetBool("debug.generate-debug-info", /*default_value=*/false),
+             "--generate-debug-info")
+      .AddIf(props_->GetBool("dalvik.vm.dex2oat-minidebuginfo", /*default_value=*/false),
+             "--generate-mini-debug-info");
+
+  args.AddRuntimeIf(DenyArtApexDataFiles(), "-Xdeny-art-apex-data-files")
+      .AddRuntime("-Xtarget-sdk-version:%d", dexopt_options.targetSdkVersion)
+      .AddRuntimeIf(dexopt_options.hiddenApiPolicyEnabled, "-Xhidden-api-policy:enabled");
+}
+
+void Artd::AddPerfConfigFlags(PriorityClass priority_class,
+                              /*out*/ CmdlineBuilder& art_exec_args,
+                              /*out*/ CmdlineBuilder& dex2oat_args) {
+  // CPU set and number of threads.
+  std::string default_cpu_set_prop = "dalvik.vm.dex2oat-cpu-set";
+  std::string default_threads_prop = "dalvik.vm.dex2oat-threads";
+  std::string cpu_set;
+  std::string threads;
+  if (priority_class >= PriorityClass::BOOT) {
+    cpu_set = props_->GetOrEmpty("dalvik.vm.boot-dex2oat-cpu-set");
+    threads = props_->GetOrEmpty("dalvik.vm.boot-dex2oat-threads");
+  } else if (priority_class >= PriorityClass::INTERACTIVE_FAST) {
+    cpu_set = props_->GetOrEmpty("dalvik.vm.restore-dex2oat-cpu-set", default_cpu_set_prop);
+    threads = props_->GetOrEmpty("dalvik.vm.restore-dex2oat-threads", default_threads_prop);
+  } else if (priority_class <= PriorityClass::BACKGROUND) {
+    cpu_set = props_->GetOrEmpty("dalvik.vm.background-dex2oat-cpu-set", default_cpu_set_prop);
+    threads = props_->GetOrEmpty("dalvik.vm.background-dex2oat-threads", default_threads_prop);
+  } else {
+    cpu_set = props_->GetOrEmpty(default_cpu_set_prop);
+    threads = props_->GetOrEmpty(default_threads_prop);
+  }
+  dex2oat_args.AddIfNonEmpty("--cpu-set=%s", cpu_set).AddIfNonEmpty("-j%s", threads);
+
+  if (priority_class < PriorityClass::BOOT) {
+    art_exec_args
+        .Add(priority_class <= PriorityClass::BACKGROUND ? "--set-task-profile=Dex2OatBackground" :
+                                                           "--set-task-profile=Dex2OatBootComplete")
+        .Add("--set-priority=background");
+  }
+
+  dex2oat_args.AddRuntimeIfNonEmpty("-Xms%s", props_->GetOrEmpty("dalvik.vm.dex2oat-Xms"))
+      .AddRuntimeIfNonEmpty("-Xmx%s", props_->GetOrEmpty("dalvik.vm.dex2oat-Xmx"));
+
+  // Enable compiling dex files in isolation on low ram devices.
+  // It takes longer but reduces the memory footprint.
+  dex2oat_args.AddIf(props_->GetBool("ro.config.low_ram", /*default_value=*/false),
+                     "--compile-individually");
+}
+
+Result<int> Artd::ExecAndReturnCode(const std::vector<std::string>& args,
+                                    int timeout_sec,
+                                    const ExecCallbacks& callbacks,
+                                    ProcessStat* stat) const {
+  std::string error_msg;
+  ExecResult result =
+      exec_utils_->ExecAndReturnResult(args, timeout_sec, callbacks, stat, &error_msg);
+  if (result.status != ExecResult::kExited) {
+    return Error() << error_msg;
+  }
+  return result.exit_code;
+}
+
+Result<struct stat> Artd::Fstat(const File& file) const {
+  struct stat st;
+  if (fstat_(file.Fd(), &st) != 0) {
+    return Errorf("Unable to fstat file '{}'", file.GetPath());
+  }
+  return st;
+}
+
+}  // namespace artd
+}  // namespace art
diff --git a/artd/artd.h b/artd/artd.h
new file mode 100644
index 0000000..f90110d
--- /dev/null
+++ b/artd/artd.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2022 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_ARTD_ARTD_H_
+#define ART_ARTD_ARTD_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <csignal>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "aidl/com/android/server/art/BnArtdCancellationSignal.h"
+#include "android-base/result.h"
+#include "android-base/thread_annotations.h"
+#include "android/binder_auto_utils.h"
+#include "base/os.h"
+#include "exec_utils.h"
+#include "oat_file_assistant_context.h"
+#include "tools/cmdline_builder.h"
+#include "tools/system_properties.h"
+
+namespace art {
+namespace artd {
+
+class ArtdCancellationSignal : public aidl::com::android::server::art::BnArtdCancellationSignal {
+ public:
+  explicit ArtdCancellationSignal(std::function<int(pid_t, int)> kill_func)
+      : kill_(std::move(kill_func)) {}
+
+  ndk::ScopedAStatus cancel() override;
+
+  ndk::ScopedAStatus getType(int64_t* _aidl_return) override;
+
+ private:
+  std::mutex mu_;
+  // True if cancellation has been signaled.
+  bool is_cancelled_ GUARDED_BY(mu_) = false;
+  // The pids of currently running child processes that are bound to this signal.
+  std::unordered_set<pid_t> pids_ GUARDED_BY(mu_);
+
+  std::function<int(pid_t, int)> kill_;
+
+  friend class Artd;
+};
+
+class Artd : public aidl::com::android::server::art::BnArtd {
+ public:
+  explicit Artd(std::unique_ptr<art::tools::SystemProperties> props =
+                    std::make_unique<art::tools::SystemProperties>(),
+                std::unique_ptr<ExecUtils> exec_utils = std::make_unique<ExecUtils>(),
+                std::function<int(pid_t, int)> kill_func = kill,
+                std::function<int(int, struct stat*)> fstat_func = fstat)
+      : props_(std::move(props)),
+        exec_utils_(std::move(exec_utils)),
+        kill_(std::move(kill_func)),
+        fstat_(std::move(fstat_func)) {}
+
+  ndk::ScopedAStatus isAlive(bool* _aidl_return) override;
+
+  ndk::ScopedAStatus deleteArtifacts(
+      const aidl::com::android::server::art::ArtifactsPath& in_artifactsPath,
+      int64_t* _aidl_return) override;
+
+  ndk::ScopedAStatus getDexoptStatus(
+      const std::string& in_dexFile,
+      const std::string& in_instructionSet,
+      const std::optional<std::string>& in_classLoaderContext,
+      aidl::com::android::server::art::GetDexoptStatusResult* _aidl_return) override;
+
+  ndk::ScopedAStatus isProfileUsable(const aidl::com::android::server::art::ProfilePath& in_profile,
+                                     const std::string& in_dexFile,
+                                     bool* _aidl_return) override;
+
+  ndk::ScopedAStatus copyAndRewriteProfile(
+      const aidl::com::android::server::art::ProfilePath& in_src,
+      aidl::com::android::server::art::OutputProfile* in_dst,
+      const std::string& in_dexFile,
+      bool* _aidl_return) override;
+
+  ndk::ScopedAStatus commitTmpProfile(
+      const aidl::com::android::server::art::ProfilePath::TmpProfilePath& in_profile) override;
+
+  ndk::ScopedAStatus deleteProfile(
+      const aidl::com::android::server::art::ProfilePath& in_profile) override;
+
+  ndk::ScopedAStatus getProfileVisibility(
+      const aidl::com::android::server::art::ProfilePath& in_profile,
+      aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
+  ndk::ScopedAStatus mergeProfiles(
+      const std::vector<aidl::com::android::server::art::ProfilePath>& in_profiles,
+      const std::optional<aidl::com::android::server::art::ProfilePath>& in_referenceProfile,
+      aidl::com::android::server::art::OutputProfile* in_outputProfile,
+      const std::vector<std::string>& in_dexFiles,
+      const aidl::com::android::server::art::MergeProfileOptions& in_options,
+      bool* _aidl_return) override;
+
+  ndk::ScopedAStatus getArtifactsVisibility(
+      const aidl::com::android::server::art::ArtifactsPath& in_artifactsPath,
+      aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
+  ndk::ScopedAStatus getDexFileVisibility(
+      const std::string& in_dexFile,
+      aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
+  ndk::ScopedAStatus getDmFileVisibility(
+      const aidl::com::android::server::art::DexMetadataPath& in_dmFile,
+      aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
+  ndk::ScopedAStatus getDexoptNeeded(
+      const std::string& in_dexFile,
+      const std::string& in_instructionSet,
+      const std::optional<std::string>& in_classLoaderContext,
+      const std::string& in_compilerFilter,
+      int32_t in_dexoptTrigger,
+      aidl::com::android::server::art::GetDexoptNeededResult* _aidl_return) override;
+
+  ndk::ScopedAStatus dexopt(
+      const aidl::com::android::server::art::OutputArtifacts& in_outputArtifacts,
+      const std::string& in_dexFile,
+      const std::string& in_instructionSet,
+      const std::optional<std::string>& in_classLoaderContext,
+      const std::string& in_compilerFilter,
+      const std::optional<aidl::com::android::server::art::ProfilePath>& in_profile,
+      const std::optional<aidl::com::android::server::art::VdexPath>& in_inputVdex,
+      const std::optional<aidl::com::android::server::art::DexMetadataPath>& in_dmFile,
+      aidl::com::android::server::art::PriorityClass in_priorityClass,
+      const aidl::com::android::server::art::DexoptOptions& in_dexoptOptions,
+      const std::shared_ptr<aidl::com::android::server::art::IArtdCancellationSignal>&
+          in_cancellationSignal,
+      aidl::com::android::server::art::ArtdDexoptResult* _aidl_return) override;
+
+  ndk::ScopedAStatus createCancellationSignal(
+      std::shared_ptr<aidl::com::android::server::art::IArtdCancellationSignal>* _aidl_return)
+      override;
+
+  ndk::ScopedAStatus cleanup(
+      const std::vector<aidl::com::android::server::art::ProfilePath>& in_profilesToKeep,
+      const std::vector<aidl::com::android::server::art::ArtifactsPath>& in_artifactsToKeep,
+      const std::vector<aidl::com::android::server::art::VdexPath>& in_vdexFilesToKeep,
+      int64_t* _aidl_return) override;
+
+  ndk::ScopedAStatus isIncrementalFsPath(const std::string& in_dexFile,
+                                         bool* _aidl_return) override;
+
+  android::base::Result<void> Start();
+
+ private:
+  android::base::Result<OatFileAssistantContext*> GetOatFileAssistantContext()
+      EXCLUDES(ofa_context_mu_);
+
+  android::base::Result<const std::vector<std::string>*> GetBootImageLocations()
+      EXCLUDES(cache_mu_);
+
+  android::base::Result<const std::vector<std::string>*> GetBootClassPath() EXCLUDES(cache_mu_);
+
+  bool UseJitZygote() EXCLUDES(cache_mu_);
+  bool UseJitZygoteLocked() REQUIRES(cache_mu_);
+
+  const std::string& GetUserDefinedBootImageLocations() EXCLUDES(cache_mu_);
+  const std::string& GetUserDefinedBootImageLocationsLocked() REQUIRES(cache_mu_);
+
+  bool DenyArtApexDataFiles() EXCLUDES(cache_mu_);
+  bool DenyArtApexDataFilesLocked() REQUIRES(cache_mu_);
+
+  android::base::Result<int> ExecAndReturnCode(const std::vector<std::string>& arg_vector,
+                                               int timeout_sec,
+                                               const ExecCallbacks& callbacks = ExecCallbacks(),
+                                               ProcessStat* stat = nullptr) const;
+
+  android::base::Result<std::string> GetProfman();
+
+  android::base::Result<std::string> GetArtExec();
+
+  bool ShouldUseDex2Oat64();
+
+  android::base::Result<std::string> GetDex2Oat();
+
+  bool ShouldCreateSwapFileForDexopt();
+
+  void AddBootImageFlags(/*out*/ art::tools::CmdlineBuilder& args);
+
+  void AddCompilerConfigFlags(const std::string& instruction_set,
+                              const std::string& compiler_filter,
+                              aidl::com::android::server::art::PriorityClass priority_class,
+                              const aidl::com::android::server::art::DexoptOptions& dexopt_options,
+                              /*out*/ art::tools::CmdlineBuilder& args);
+
+  void AddPerfConfigFlags(aidl::com::android::server::art::PriorityClass priority_class,
+                          /*out*/ art::tools::CmdlineBuilder& art_exec_args,
+                          /*out*/ art::tools::CmdlineBuilder& args);
+
+  android::base::Result<struct stat> Fstat(const art::File& file) const;
+
+  std::mutex cache_mu_;
+  std::optional<std::vector<std::string>> cached_boot_image_locations_ GUARDED_BY(cache_mu_);
+  std::optional<std::vector<std::string>> cached_boot_class_path_ GUARDED_BY(cache_mu_);
+  std::optional<bool> cached_use_jit_zygote_ GUARDED_BY(cache_mu_);
+  std::optional<std::string> cached_user_defined_boot_image_locations_ GUARDED_BY(cache_mu_);
+  std::optional<bool> cached_deny_art_apex_data_files_ GUARDED_BY(cache_mu_);
+
+  std::mutex ofa_context_mu_;
+  std::unique_ptr<OatFileAssistantContext> ofa_context_ GUARDED_BY(ofa_context_mu_);
+
+  const std::unique_ptr<art::tools::SystemProperties> props_;
+  const std::unique_ptr<ExecUtils> exec_utils_;
+  const std::function<int(pid_t, int)> kill_;
+  const std::function<int(int, struct stat*)> fstat_;
+};
+
+}  // namespace artd
+}  // namespace art
+
+#endif  // ART_ARTD_ARTD_H_
diff --git a/artd/artd.rc b/artd/artd.rc
deleted file mode 100644
index eebec2d..0000000
--- a/artd/artd.rc
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2021 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.
-
-# This service is disabled until b/192042812 is resolved.
-service artd /apex/com.android.art/bin/artd
-    disabled
-    class core
-    user artd
\ No newline at end of file
diff --git a/artd/artd_main.cc b/artd/artd_main.cc
new file mode 100644
index 0000000..3644eba
--- /dev/null
+++ b/artd/artd_main.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <stdlib.h>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "android/binder_interface_utils.h"
+#include "android/binder_process.h"
+#include "artd.h"
+
+int main(int argc ATTRIBUTE_UNUSED, char* argv[]) {
+  android::base::InitLogging(argv);
+
+  auto artd = ndk::SharedRefBase::make<art::artd::Artd>();
+
+  LOG(INFO) << "Starting artd";
+
+  if (auto ret = artd->Start(); !ret.ok()) {
+    LOG(ERROR) << "Unable to start artd: " << ret.error();
+    exit(1);
+  }
+
+  ABinderProcess_joinThreadPool();
+
+  LOG(INFO) << "artd shutting down";
+
+  return 0;
+}
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
new file mode 100644
index 0000000..1fca5a7
--- /dev/null
+++ b/artd/artd_test.cc
@@ -0,0 +1,1961 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "artd.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <chrono>
+#include <condition_variable>
+#include <csignal>
+#include <filesystem>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <thread>
+#include <type_traits>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "aidl/com/android/server/art/ArtConstants.h"
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/collections.h"
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/parseint.h"
+#include "android-base/result.h"
+#include "android-base/scopeguard.h"
+#include "android-base/strings.h"
+#include "android/binder_auto_utils.h"
+#include "android/binder_status.h"
+#include "base/array_ref.h"
+#include "base/common_art_test.h"
+#include "exec_utils.h"
+#include "fmt/format.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "oat_file.h"
+#include "path_utils.h"
+#include "profman/profman_result.h"
+#include "testing.h"
+#include "tools/system_properties.h"
+
+namespace art {
+namespace artd {
+namespace {
+
+using ::aidl::com::android::server::art::ArtConstants;
+using ::aidl::com::android::server::art::ArtdDexoptResult;
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::DexoptOptions;
+using ::aidl::com::android::server::art::FileVisibility;
+using ::aidl::com::android::server::art::FsPermission;
+using ::aidl::com::android::server::art::GetDexoptStatusResult;
+using ::aidl::com::android::server::art::IArtdCancellationSignal;
+using ::aidl::com::android::server::art::OutputArtifacts;
+using ::aidl::com::android::server::art::OutputProfile;
+using ::aidl::com::android::server::art::PriorityClass;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::Append;
+using ::android::base::Error;
+using ::android::base::make_scope_guard;
+using ::android::base::ParseInt;
+using ::android::base::ReadFdToString;
+using ::android::base::ReadFileToString;
+using ::android::base::Result;
+using ::android::base::ScopeGuard;
+using ::android::base::Split;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::AnyNumber;
+using ::testing::AnyOf;
+using ::testing::Contains;
+using ::testing::ContainsRegex;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::Field;
+using ::testing::HasSubstr;
+using ::testing::IsEmpty;
+using ::testing::Matcher;
+using ::testing::MockFunction;
+using ::testing::Not;
+using ::testing::Property;
+using ::testing::ResultOf;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::UnorderedElementsAreArray;
+using ::testing::WithArg;
+
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+
+using ::fmt::literals::operator""_format;  // NOLINT
+
+ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& logger) {
+  android::base::LogFunction old_logger = android::base::SetLogger(std::move(logger));
+  return make_scope_guard([old_logger = std::move(old_logger)]() mutable {
+    android::base::SetLogger(std::move(old_logger));
+  });
+}
+
+void CheckContent(const std::string& path, const std::string& expected_content) {
+  std::string actual_content;
+  ASSERT_TRUE(ReadFileToString(path, &actual_content));
+  EXPECT_EQ(actual_content, expected_content);
+}
+
+void CheckOtherReadable(const std::string& path, bool expected_value) {
+  EXPECT_EQ((std::filesystem::status(path).permissions() & std::filesystem::perms::others_read) !=
+                std::filesystem::perms::none,
+            expected_value);
+}
+
+Result<std::vector<std::string>> GetFlagValues(ArrayRef<const std::string> args,
+                                               std::string_view flag) {
+  std::vector<std::string> values;
+  for (const std::string& arg : args) {
+    std::string_view value(arg);
+    if (android::base::ConsumePrefix(&value, flag)) {
+      values.emplace_back(value);
+    }
+  }
+  if (values.empty()) {
+    return Errorf("Flag '{}' not found", flag);
+  }
+  return values;
+}
+
+Result<std::string> GetFlagValue(ArrayRef<const std::string> args, std::string_view flag) {
+  std::vector<std::string> flag_values = OR_RETURN(GetFlagValues(args, flag));
+  if (flag_values.size() > 1) {
+    return Errorf("Duplicate flag '{}'", flag);
+  }
+  return flag_values[0];
+}
+
+void WriteToFdFlagImpl(const std::vector<std::string>& args,
+                       std::string_view flag,
+                       std::string_view content,
+                       bool assume_empty) {
+  std::string value = OR_FAIL(GetFlagValue(ArrayRef<const std::string>(args), flag));
+  ASSERT_NE(value, "");
+  int fd;
+  ASSERT_TRUE(ParseInt(value, &fd));
+  if (assume_empty) {
+    ASSERT_EQ(lseek(fd, /*offset=*/0, SEEK_CUR), 0);
+  } else {
+    ASSERT_EQ(ftruncate(fd, /*length=*/0), 0);
+    ASSERT_EQ(lseek(fd, /*offset=*/0, SEEK_SET), 0);
+  }
+  ASSERT_TRUE(WriteStringToFd(content, fd));
+}
+
+// Writes `content` to the FD specified by the `flag`.
+ACTION_P(WriteToFdFlag, flag, content) {
+  WriteToFdFlagImpl(arg0, flag, content, /*assume_empty=*/true);
+}
+
+// Clears any existing content and writes `content` to the FD specified by the `flag`.
+ACTION_P(ClearAndWriteToFdFlag, flag, content) {
+  WriteToFdFlagImpl(arg0, flag, content, /*assume_empty=*/false);
+}
+
+// Matches a flag that starts with `flag` and whose value matches `matcher`.
+MATCHER_P2(Flag, flag, matcher, "") {
+  std::string_view value(arg);
+  if (!android::base::ConsumePrefix(&value, flag)) {
+    return false;
+  }
+  return ExplainMatchResult(matcher, std::string(value), result_listener);
+}
+
+// Matches a flag that starts with `flag` and whose value is a colon-separated list that matches
+// `matcher`. The matcher acts on an `std::vector<std::string>` of the split list argument.
+MATCHER_P2(ListFlag, flag, matcher, "") {
+  return ExplainMatchResult(
+      Flag(flag, ResultOf(std::bind(Split, std::placeholders::_1, ":"), matcher)),
+      arg,
+      result_listener);
+}
+
+// Matches an FD of a file whose path matches `matcher`.
+MATCHER_P(FdOf, matcher, "") {
+  std::string proc_path = "/proc/self/fd/{}"_format(arg);
+  char path[PATH_MAX];
+  ssize_t len = readlink(proc_path.c_str(), path, sizeof(path));
+  if (len < 0) {
+    return false;
+  }
+  return ExplainMatchResult(matcher, std::string(path, static_cast<size_t>(len)), result_listener);
+}
+
+// Matches an FD of a file whose content matches `matcher`.
+MATCHER_P(FdHasContent, matcher, "") {
+  int fd;
+  if (!ParseInt(arg, &fd)) {
+    return false;
+  }
+  std::string actual_content;
+  if (!ReadFdToString(fd, &actual_content)) {
+    return false;
+  }
+  return ExplainMatchResult(matcher, actual_content, result_listener);
+}
+
+template <typename T, typename U>
+Result<std::pair<ArrayRef<const T>, ArrayRef<const T>>> SplitBy(const std::vector<T>& list,
+                                                                const U& separator) {
+  auto it = std::find(list.begin(), list.end(), separator);
+  if (it == list.end()) {
+    return Errorf("'{}' not found", separator);
+  }
+  size_t pos = it - list.begin();
+  return std::make_pair(ArrayRef<const T>(list).SubArray(0, pos),
+                        ArrayRef<const T>(list).SubArray(pos + 1));
+}
+
+// Matches a container that, when split by `separator`, the first part matches `head_matcher`, and
+// the second part matches `tail_matcher`.
+MATCHER_P3(WhenSplitBy, separator, head_matcher, tail_matcher, "") {
+  auto [head, tail] = OR_MISMATCH(SplitBy(arg, separator));
+  return ExplainMatchResult(head_matcher, head, result_listener) &&
+         ExplainMatchResult(tail_matcher, tail, result_listener);
+}
+
+MATCHER_P(HasKeepFdsForImpl, fd_flags, "") {
+  auto [head, tail] = OR_MISMATCH(SplitBy(arg, "--"));
+  std::string keep_fds_value = OR_MISMATCH(GetFlagValue(head, "--keep-fds="));
+  std::vector<std::string> keep_fds = Split(keep_fds_value, ":");
+  std::vector<std::string> fd_flag_values;
+  for (std::string_view fd_flag : fd_flags) {
+    for (const std::string& fd_flag_value : OR_MISMATCH(GetFlagValues(tail, fd_flag))) {
+      for (std::string& fd : Split(fd_flag_value, ":")) {
+        fd_flag_values.push_back(std::move(fd));
+      }
+    }
+  }
+  return ExplainMatchResult(UnorderedElementsAreArray(fd_flag_values), keep_fds, result_listener);
+}
+
+// Matches an argument list that has the "--keep-fds=" flag before "--", whose value is a
+// semicolon-separated list that contains exactly the values of the given flags after "--".
+//
+// E.g., if the flags after "--" are "--foo=1", "--bar=2:3", "--baz=4", "--baz=5", and the matcher
+// is `HasKeepFdsFor("--foo=", "--bar=", "--baz=")`, then it requires the "--keep-fds=" flag before
+// "--" to contain exactly 1, 2, 3, 4, and 5.
+template <typename... Args>
+auto HasKeepFdsFor(Args&&... args) {
+  std::vector<std::string_view> fd_flags;
+  Append(fd_flags, std::forward<Args>(args)...);
+  return HasKeepFdsForImpl(fd_flags);
+}
+
+class MockSystemProperties : public tools::SystemProperties {
+ public:
+  MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override));
+};
+
+class MockExecUtils : public ExecUtils {
+ public:
+  // A workaround to avoid MOCK_METHOD on a method with an `std::string*` parameter, which will lead
+  // to a conflict between gmock and android-base/logging.h (b/132668253).
+  ExecResult ExecAndReturnResult(const std::vector<std::string>& arg_vector,
+                                 int,
+                                 const ExecCallbacks& callbacks,
+                                 ProcessStat* stat,
+                                 std::string*) const override {
+    Result<int> code = DoExecAndReturnCode(arg_vector, callbacks, stat);
+    if (code.ok()) {
+      return {.status = ExecResult::kExited, .exit_code = code.value()};
+    }
+    return {.status = ExecResult::kUnknown};
+  }
+
+  MOCK_METHOD(Result<int>,
+              DoExecAndReturnCode,
+              (const std::vector<std::string>& arg_vector,
+               const ExecCallbacks& callbacks,
+               ProcessStat* stat),
+              (const));
+};
+
+class ArtdTest : public CommonArtTest {
+ protected:
+  void SetUp() override {
+    CommonArtTest::SetUp();
+    auto mock_props = std::make_unique<MockSystemProperties>();
+    mock_props_ = mock_props.get();
+    EXPECT_CALL(*mock_props_, GetProperty).Times(AnyNumber()).WillRepeatedly(Return(""));
+    auto mock_exec_utils = std::make_unique<MockExecUtils>();
+    mock_exec_utils_ = mock_exec_utils.get();
+    artd_ = ndk::SharedRefBase::make<Artd>(std::move(mock_props),
+                                           std::move(mock_exec_utils),
+                                           mock_kill_.AsStdFunction(),
+                                           mock_fstat_.AsStdFunction());
+    scratch_dir_ = std::make_unique<ScratchDir>();
+    scratch_path_ = scratch_dir_->GetPath();
+    // Remove the trailing '/';
+    scratch_path_.resize(scratch_path_.length() - 1);
+
+    ON_CALL(mock_fstat_, Call).WillByDefault(fstat);
+
+    // Use an arbitrary existing directory as ART root.
+    art_root_ = scratch_path_ + "/com.android.art";
+    std::filesystem::create_directories(art_root_);
+    setenv("ANDROID_ART_ROOT", art_root_.c_str(), /*overwrite=*/1);
+
+    // Use an arbitrary existing directory as Android data.
+    android_data_ = scratch_path_ + "/data";
+    std::filesystem::create_directories(android_data_);
+    setenv("ANDROID_DATA", android_data_.c_str(), /*overwrite=*/1);
+
+    // Use an arbitrary existing directory as Android expand.
+    android_expand_ = scratch_path_ + "/mnt/expand";
+    std::filesystem::create_directories(android_expand_);
+    setenv("ANDROID_EXPAND", android_expand_.c_str(), /*overwrite=*/1);
+
+    dex_file_ = scratch_path_ + "/a/b.apk";
+    isa_ = "arm64";
+    artifacts_path_ = ArtifactsPath{
+        .dexPath = dex_file_,
+        .isa = isa_,
+        .isInDalvikCache = false,
+    };
+    struct stat st;
+    ASSERT_EQ(stat(scratch_path_.c_str(), &st), 0);
+    output_artifacts_ = OutputArtifacts{
+        .artifactsPath = artifacts_path_,
+        .permissionSettings =
+            OutputArtifacts::PermissionSettings{
+                .dirFsPermission =
+                    FsPermission{
+                        .uid = static_cast<int32_t>(st.st_uid),
+                        .gid = static_cast<int32_t>(st.st_gid),
+                        .isOtherReadable = true,
+                        .isOtherExecutable = true,
+                    },
+                .fileFsPermission =
+                    FsPermission{
+                        .uid = static_cast<int32_t>(st.st_uid),
+                        .gid = static_cast<int32_t>(st.st_gid),
+                        .isOtherReadable = true,
+                    },
+            },
+    };
+    clc_1_ = GetTestDexFileName("Main");
+    clc_2_ = GetTestDexFileName("Nested");
+    class_loader_context_ = "PCL[{}:{}]"_format(clc_1_, clc_2_);
+    compiler_filter_ = "speed";
+    TmpProfilePath tmp_profile_path{
+        .finalPath =
+            PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+        .id = "12345"};
+    profile_path_ = tmp_profile_path;
+    vdex_path_ = artifacts_path_;
+    dm_path_ = DexMetadataPath{.dexPath = dex_file_};
+    std::filesystem::create_directories(
+        std::filesystem::path(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))).parent_path());
+  }
+
+  void TearDown() override {
+    scratch_dir_.reset();
+    CommonArtTest::TearDown();
+  }
+
+  void RunDexopt(binder_exception_t expected_status = EX_NONE,
+                 Matcher<ArtdDexoptResult> aidl_return_matcher = Field(&ArtdDexoptResult::cancelled,
+                                                                       false),
+                 std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) {
+    RunDexopt(Property(&ndk::ScopedAStatus::getExceptionCode, expected_status),
+              std::move(aidl_return_matcher),
+              cancellation_signal);
+  }
+
+  void RunDexopt(Matcher<ndk::ScopedAStatus> status_matcher,
+                 Matcher<ArtdDexoptResult> aidl_return_matcher = Field(&ArtdDexoptResult::cancelled,
+                                                                       false),
+                 std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) {
+    InitFilesBeforeDexopt();
+    if (cancellation_signal == nullptr) {
+      ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
+    }
+    ArtdDexoptResult aidl_return;
+    ndk::ScopedAStatus status = artd_->dexopt(output_artifacts_,
+                                              dex_file_,
+                                              isa_,
+                                              class_loader_context_,
+                                              compiler_filter_,
+                                              profile_path_,
+                                              vdex_path_,
+                                              dm_path_,
+                                              priority_class_,
+                                              dexopt_options_,
+                                              cancellation_signal,
+                                              &aidl_return);
+    ASSERT_THAT(status, std::move(status_matcher)) << status.getMessage();
+    if (status.isOk()) {
+      ASSERT_THAT(aidl_return, std::move(aidl_return_matcher));
+    }
+  }
+
+  void CreateFile(const std::string& filename, const std::string& content = "") {
+    std::filesystem::path path(filename);
+    std::filesystem::create_directories(path.parent_path());
+    ASSERT_TRUE(WriteStringToFile(content, filename));
+  }
+
+  std::shared_ptr<Artd> artd_;
+  std::unique_ptr<ScratchDir> scratch_dir_;
+  std::string scratch_path_;
+  std::string art_root_;
+  std::string android_data_;
+  std::string android_expand_;
+  MockFunction<android::base::LogFunction> mock_logger_;
+  ScopedUnsetEnvironmentVariable art_root_env_ = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT");
+  ScopedUnsetEnvironmentVariable android_data_env_ = ScopedUnsetEnvironmentVariable("ANDROID_DATA");
+  ScopedUnsetEnvironmentVariable android_expand_env_ =
+      ScopedUnsetEnvironmentVariable("ANDROID_EXPAND");
+  MockSystemProperties* mock_props_;
+  MockExecUtils* mock_exec_utils_;
+  MockFunction<int(pid_t, int)> mock_kill_;
+  MockFunction<int(int, struct stat*)> mock_fstat_;
+
+  std::string dex_file_;
+  std::string isa_;
+  ArtifactsPath artifacts_path_;
+  OutputArtifacts output_artifacts_;
+  std::string clc_1_;
+  std::string clc_2_;
+  std::optional<std::string> class_loader_context_;
+  std::string compiler_filter_;
+  std::optional<VdexPath> vdex_path_;
+  std::optional<DexMetadataPath> dm_path_;
+  PriorityClass priority_class_ = PriorityClass::BACKGROUND;
+  DexoptOptions dexopt_options_;
+  std::optional<ProfilePath> profile_path_;
+  bool dex_file_other_readable_ = true;
+  bool profile_other_readable_ = true;
+
+ private:
+  void InitFilesBeforeDexopt() {
+    // Required files.
+    CreateFile(dex_file_);
+    std::filesystem::permissions(dex_file_,
+                                 std::filesystem::perms::others_read,
+                                 dex_file_other_readable_ ? std::filesystem::perm_options::add :
+                                                            std::filesystem::perm_options::remove);
+
+    // Optional files.
+    if (vdex_path_.has_value()) {
+      CreateFile(OR_FATAL(BuildVdexPath(vdex_path_.value())), "old_vdex");
+    }
+    if (dm_path_.has_value()) {
+      CreateFile(OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+    }
+    if (profile_path_.has_value()) {
+      std::string path = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+      CreateFile(path);
+      std::filesystem::permissions(path,
+                                   std::filesystem::perms::others_read,
+                                   profile_other_readable_ ? std::filesystem::perm_options::add :
+                                                             std::filesystem::perm_options::remove);
+    }
+
+    // Files to be replaced.
+    std::string oat_path = OR_FATAL(BuildOatPath(artifacts_path_));
+    CreateFile(oat_path, "old_oat");
+    CreateFile(OatPathToVdexPath(oat_path), "old_vdex");
+    CreateFile(OatPathToArtPath(oat_path), "old_art");
+  }
+};
+
+TEST_F(ArtdTest, ConstantsAreInSync) { EXPECT_EQ(ArtConstants::REASON_VDEX, kReasonVdex); }
+
+TEST_F(ArtdTest, isAlive) {
+  bool result = false;
+  artd_->isAlive(&result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(ArtdTest, deleteArtifacts) {
+  std::string oat_dir = scratch_path_ + "/a/oat/arm64";
+  std::filesystem::create_directories(oat_dir);
+  ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex"));  // 4 bytes.
+  ASSERT_TRUE(WriteStringToFile("ab", oat_dir + "/b.vdex"));    // 2 bytes.
+  ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art"));      // 1 byte.
+
+  int64_t result = -1;
+  EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
+  EXPECT_EQ(result, 4 + 2 + 1);
+
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.odex"));
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.vdex"));
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.art"));
+}
+
+TEST_F(ArtdTest, deleteArtifactsMissingFile) {
+  // Missing VDEX file.
+  std::string oat_dir = android_data_ + "/dalvik-cache/arm64";
+  std::filesystem::create_directories(oat_dir);
+  ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/[email protected]@classes.dex"));  // 4 bytes.
+  ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/[email protected]@classes.art"));     // 1 byte.
+
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(0);
+
+  int64_t result = -1;
+  EXPECT_TRUE(artd_
+                  ->deleteArtifacts(
+                      ArtifactsPath{
+                          .dexPath = "/a/b.apk",
+                          .isa = "arm64",
+                          .isInDalvikCache = true,
+                      },
+                      &result)
+                  .isOk());
+  EXPECT_EQ(result, 4 + 1);
+
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/[email protected]@classes.dex"));
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/[email protected]@classes.art"));
+}
+
+TEST_F(ArtdTest, deleteArtifactsNoFile) {
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(0);
+
+  int64_t result = -1;
+  EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
+  EXPECT_EQ(result, 0);
+}
+
+TEST_F(ArtdTest, deleteArtifactsPermissionDenied) {
+  std::string oat_dir = scratch_path_ + "/a/oat/arm64";
+  std::filesystem::create_directories(oat_dir);
+  ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex"));  // 4 bytes.
+  ASSERT_TRUE(WriteStringToFile("ab", oat_dir + "/b.vdex"));    // 2 bytes.
+  ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art"));      // 1 byte.
+
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(3);
+
+  auto scoped_inaccessible = ScopedInaccessible(oat_dir);
+  auto scoped_unroot = ScopedUnroot();
+
+  int64_t result = -1;
+  EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
+  EXPECT_EQ(result, 0);
+}
+
+TEST_F(ArtdTest, deleteArtifactsFileIsDir) {
+  // VDEX file is a directory.
+  std::string oat_dir = scratch_path_ + "/a/oat/arm64";
+  std::filesystem::create_directories(oat_dir);
+  std::filesystem::create_directories(oat_dir + "/b.vdex");
+  ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex"));  // 4 bytes.
+  ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art"));      // 1 byte.
+
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(mock_logger_,
+              Call(_, _, _, _, _, ContainsRegex(R"re(Failed to get the file size.*b\.vdex)re")))
+      .Times(1);
+
+  int64_t result = -1;
+  EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
+  EXPECT_EQ(result, 4 + 1);
+
+  // The directory is kept because getting the file size failed.
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.odex"));
+  EXPECT_TRUE(std::filesystem::exists(oat_dir + "/b.vdex"));
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.art"));
+}
+
+TEST_F(ArtdTest, dexopt) {
+  dexopt_options_.generateAppImage = true;
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(WhenSplitBy(
+                    "--",
+                    AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                    AllOf(Contains(art_root_ + "/bin/dex2oat32"),
+                          Contains(Flag("--zip-fd=", FdOf(dex_file_))),
+                          Contains(Flag("--zip-location=", dex_file_)),
+                          Contains(Flag("--oat-location=", scratch_path_ + "/a/oat/arm64/b.odex")),
+                          Contains(Flag("--instruction-set=", "arm64")),
+                          Contains(Flag("--compiler-filter=", "speed")),
+                          Contains(Flag(
+                              "--profile-file-fd=",
+                              FdOf(android_data_ +
+                                   "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"))),
+                          Contains(Flag("--input-vdex-fd=",
+                                        FdOf(scratch_path_ + "/a/oat/arm64/b.vdex"))),
+                          Contains(Flag("--dm-fd=", FdOf(scratch_path_ + "/a/b.dm"))))),
+                HasKeepFdsFor("--zip-fd=",
+                              "--profile-file-fd=",
+                              "--input-vdex-fd=",
+                              "--dm-fd=",
+                              "--oat-fd=",
+                              "--output-vdex-fd=",
+                              "--app-image-fd=",
+                              "--class-loader-context-fds=",
+                              "--swap-fd=")),
+          _,
+          _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")),
+                      WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")),
+                      SetArgPointee<2>(ProcessStat{.wall_time_ms = 100, .cpu_time_ms = 400}),
+                      Return(0)));
+  RunDexopt(
+      EX_NONE,
+      AllOf(Field(&ArtdDexoptResult::cancelled, false),
+            Field(&ArtdDexoptResult::wallTimeMs, 100),
+            Field(&ArtdDexoptResult::cpuTimeMs, 400),
+            Field(&ArtdDexoptResult::sizeBytes, strlen("art") + strlen("oat") + strlen("vdex")),
+            Field(&ArtdDexoptResult::sizeBeforeBytes,
+                  strlen("old_art") + strlen("old_oat") + strlen("old_vdex"))));
+
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "art");
+  CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.odex", true);
+  CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.vdex", true);
+  CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.art", true);
+}
+
+TEST_F(ArtdTest, dexoptClassLoaderContext) {
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--",
+                      _,
+                      AllOf(Contains(ListFlag("--class-loader-context-fds=",
+                                              ElementsAre(FdOf(clc_1_), FdOf(clc_2_)))),
+                            Contains(Flag("--class-loader-context=", class_loader_context_)),
+                            Contains(Flag("--classpath-dir=", scratch_path_ + "/a")))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptClassLoaderContextNull) {
+  class_loader_context_ = std::nullopt;
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(WhenSplitBy("--",
+                                      _,
+                                      AllOf(Not(Contains(Flag("--class-loader-context-fds=", _))),
+                                            Not(Contains(Flag("--class-loader-context=", _))),
+                                            Not(Contains(Flag("--classpath-dir=", _))))),
+                          _,
+                          _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptNoOptionalInputFiles) {
+  profile_path_ = std::nullopt;
+  vdex_path_ = std::nullopt;
+  dm_path_ = std::nullopt;
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(WhenSplitBy("--",
+                                              _,
+                                              AllOf(Not(Contains(Flag("--profile-file-fd=", _))),
+                                                    Not(Contains(Flag("--input-vdex-fd=", _))),
+                                                    Not(Contains(Flag("--dm-fd=", _))))),
+                                  _,
+                                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptPriorityClassBoot) {
+  priority_class_ = PriorityClass::BOOT;
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(WhenSplitBy("--",
+                                              AllOf(Not(Contains(Flag("--set-task-profile=", _))),
+                                                    Not(Contains(Flag("--set-priority=", _)))),
+                                              Contains(Flag("--compact-dex-level=", "none"))),
+                                  _,
+                                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptPriorityClassInteractive) {
+  priority_class_ = PriorityClass::INTERACTIVE;
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")),
+                                    Contains(Flag("--set-priority=", "background"))),
+                              Contains(Flag("--compact-dex-level=", "none"))),
+                  _,
+                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptPriorityClassInteractiveFast) {
+  priority_class_ = PriorityClass::INTERACTIVE_FAST;
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")),
+                                    Contains(Flag("--set-priority=", "background"))),
+                              Contains(Flag("--compact-dex-level=", "none"))),
+                  _,
+                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptPriorityClassBackground) {
+  priority_class_ = PriorityClass::BACKGROUND;
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBackground")),
+                                    Contains(Flag("--set-priority=", "background"))),
+                              Not(Contains(Flag("--compact-dex-level=", _)))),
+                  _,
+                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptDexoptOptions) {
+  dexopt_options_ = DexoptOptions{
+      .compilationReason = "install",
+      .targetSdkVersion = 123,
+      .debuggable = false,
+      .generateAppImage = false,
+      .hiddenApiPolicyEnabled = false,
+      .comments = "my-comments",
+  };
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(WhenSplitBy("--",
+                                      _,
+                                      AllOf(Contains(Flag("--compilation-reason=", "install")),
+                                            Contains(Flag("-Xtarget-sdk-version:", "123")),
+                                            Not(Contains("--debuggable")),
+                                            Not(Contains(Flag("--app-image-fd=", _))),
+                                            Not(Contains(Flag("-Xhidden-api-policy:", _))),
+                                            Contains(Flag("--comments=", "my-comments")))),
+                          _,
+                          _))
+      .WillOnce(Return(0));
+
+  // `sizeBeforeBytes` should include the size of the old ART file even if no new ART file is
+  // generated.
+  RunDexopt(EX_NONE,
+            Field(&ArtdDexoptResult::sizeBeforeBytes,
+                  strlen("old_art") + strlen("old_oat") + strlen("old_vdex")));
+}
+
+TEST_F(ArtdTest, dexoptDexoptOptions2) {
+  dexopt_options_ = DexoptOptions{
+      .compilationReason = "bg-dexopt",
+      .targetSdkVersion = 456,
+      .debuggable = true,
+      .generateAppImage = true,
+      .hiddenApiPolicyEnabled = true,
+  };
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(WhenSplitBy("--",
+                                      _,
+                                      AllOf(Contains(Flag("--compilation-reason=", "bg-dexopt")),
+                                            Contains(Flag("-Xtarget-sdk-version:", "456")),
+                                            Contains("--debuggable"),
+                                            Contains(Flag("--app-image-fd=", _)),
+                                            Contains(Flag("-Xhidden-api-policy:", "enabled")))),
+                          _,
+                          _))
+      .WillOnce(Return(0));
+
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptDefaultFlagsWhenNoSystemProps) {
+  dexopt_options_.generateAppImage = true;
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              _,
+                              AllOf(Contains(Flag("--swap-fd=", FdOf(_))),
+                                    Not(Contains(Flag("--instruction-set-features=", _))),
+                                    Not(Contains(Flag("--instruction-set-variant=", _))),
+                                    Not(Contains(Flag("--max-image-block-size=", _))),
+                                    Not(Contains(Flag("--very-large-app-threshold=", _))),
+                                    Not(Contains(Flag("--resolve-startup-const-strings=", _))),
+                                    Not(Contains("--generate-debug-info")),
+                                    Not(Contains("--generate-mini-debug-info")),
+                                    Contains("-Xdeny-art-apex-data-files"),
+                                    Not(Contains(Flag("--cpu-set=", _))),
+                                    Not(Contains(Flag("-j", _))),
+                                    Not(Contains(Flag("-Xms", _))),
+                                    Not(Contains(Flag("-Xmx", _))),
+                                    Not(Contains("--compile-individually")),
+                                    Not(Contains(Flag("--image-format=", _))),
+                                    Not(Contains("--force-jit-zygote")),
+                                    Not(Contains(Flag("--boot-image=", _))))),
+                  _,
+                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptFlagsFromSystemProps) {
+  dexopt_options_.generateAppImage = true;
+
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-swap")).WillOnce(Return("0"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.isa.arm64.features"))
+      .WillOnce(Return("features"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.isa.arm64.variant")).WillOnce(Return("variant"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-max-image-block-size"))
+      .WillOnce(Return("size"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-very-large"))
+      .WillOnce(Return("threshold"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-resolve-startup-strings"))
+      .WillOnce(Return("strings"));
+  EXPECT_CALL(*mock_props_, GetProperty("debug.generate-debug-info")).WillOnce(Return("1"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-minidebuginfo")).WillOnce(Return("1"));
+  EXPECT_CALL(*mock_props_, GetProperty("odsign.verification.success")).WillOnce(Return("1"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-Xms")).WillOnce(Return("xms"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-Xmx")).WillOnce(Return("xmx"));
+  EXPECT_CALL(*mock_props_, GetProperty("ro.config.low_ram")).WillOnce(Return("1"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.appimageformat")).WillOnce(Return("imgfmt"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.boot-image")).WillOnce(Return("boot-image"));
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              _,
+                              AllOf(Not(Contains(Flag("--swap-fd=", _))),
+                                    Contains(Flag("--instruction-set-features=", "features")),
+                                    Contains(Flag("--instruction-set-variant=", "variant")),
+                                    Contains(Flag("--max-image-block-size=", "size")),
+                                    Contains(Flag("--very-large-app-threshold=", "threshold")),
+                                    Contains(Flag("--resolve-startup-const-strings=", "strings")),
+                                    Contains("--generate-debug-info"),
+                                    Contains("--generate-mini-debug-info"),
+                                    Not(Contains("-Xdeny-art-apex-data-files")),
+                                    Contains(Flag("-Xms", "xms")),
+                                    Contains(Flag("-Xmx", "xmx")),
+                                    Contains("--compile-individually"),
+                                    Contains(Flag("--image-format=", "imgfmt")),
+                                    Not(Contains("--force-jit-zygote")),
+                                    Contains(Flag("--boot-image=", "boot-image")))),
+                  _,
+                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptFlagsForceJitZygote) {
+  EXPECT_CALL(*mock_props_,
+              GetProperty("persist.device_config.runtime_native_boot.profilebootclasspath"))
+      .WillOnce(Return("true"));
+  ON_CALL(*mock_props_, GetProperty("dalvik.vm.boot-image")).WillByDefault(Return("boot-image"));
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(WhenSplitBy("--",
+                                              _,
+                                              AllOf(Contains("--force-jit-zygote"),
+                                                    Not(Contains(Flag("--boot-image=", _))))),
+                                  _,
+                                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+static void SetDefaultResourceControlProps(MockSystemProperties* mock_props) {
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-cpu-set")).WillRepeatedly(Return("0,2"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-threads")).WillRepeatedly(Return("4"));
+}
+
+TEST_F(ArtdTest, dexoptDefaultResourceControlBoot) {
+  SetDefaultResourceControlProps(mock_props_);
+
+  // The default resource control properties don't apply to BOOT.
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Not(Contains(Flag("--cpu-set=", _))), Contains(Not(Flag("-j", _))))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  priority_class_ = PriorityClass::BOOT;
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptDefaultResourceControlOther) {
+  SetDefaultResourceControlProps(mock_props_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))),
+          _,
+          _))
+      .Times(3)
+      .WillRepeatedly(Return(0));
+  priority_class_ = PriorityClass::INTERACTIVE_FAST;
+  RunDexopt();
+  priority_class_ = PriorityClass::INTERACTIVE;
+  RunDexopt();
+  priority_class_ = PriorityClass::BACKGROUND;
+  RunDexopt();
+}
+
+static void SetAllResourceControlProps(MockSystemProperties* mock_props) {
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-cpu-set")).WillRepeatedly(Return("0,2"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-threads")).WillRepeatedly(Return("4"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.boot-dex2oat-cpu-set"))
+      .WillRepeatedly(Return("0,1,2,3"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.boot-dex2oat-threads"))
+      .WillRepeatedly(Return("8"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.restore-dex2oat-cpu-set"))
+      .WillRepeatedly(Return("0,2,3"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.restore-dex2oat-threads"))
+      .WillRepeatedly(Return("6"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.background-dex2oat-cpu-set"))
+      .WillRepeatedly(Return("0"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.background-dex2oat-threads"))
+      .WillRepeatedly(Return("2"));
+}
+
+TEST_F(ArtdTest, dexoptAllResourceControlBoot) {
+  SetAllResourceControlProps(mock_props_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,1,2,3")), Contains(Flag("-j", "8")))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  priority_class_ = PriorityClass::BOOT;
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptAllResourceControlInteractiveFast) {
+  SetAllResourceControlProps(mock_props_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2,3")), Contains(Flag("-j", "6")))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  priority_class_ = PriorityClass::INTERACTIVE_FAST;
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptAllResourceControlInteractive) {
+  SetAllResourceControlProps(mock_props_);
+
+  // INTERACTIVE always uses the default resource control properties.
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  priority_class_ = PriorityClass::INTERACTIVE;
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptAllResourceControlBackground) {
+  SetAllResourceControlProps(mock_props_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--", _, AllOf(Contains(Flag("--cpu-set=", "0")), Contains(Flag("-j", "2")))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  priority_class_ = PriorityClass::BACKGROUND;
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptFailed) {
+  dexopt_options_.generateAppImage = true;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
+                      WithArg<0>(WriteToFdFlag("--app-image-fd=", "new_art")),
+                      Return(1)));
+  RunDexopt(EX_SERVICE_SPECIFIC);
+
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
+}
+
+TEST_F(ArtdTest, dexoptFailedToCommit) {
+  std::unique_ptr<ScopeGuard<std::function<void()>>> scoped_inaccessible;
+  std::unique_ptr<ScopeGuard<std::function<void()>>> scoped_unroot;
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
+                      [&](auto, auto, auto) {
+                        scoped_inaccessible = std::make_unique<ScopeGuard<std::function<void()>>>(
+                            ScopedInaccessible(scratch_path_ + "/a/oat/arm64"));
+                        scoped_unroot =
+                            std::make_unique<ScopeGuard<std::function<void()>>>(ScopedUnroot());
+                        return 0;
+                      }));
+
+  RunDexopt(
+      EX_SERVICE_SPECIFIC,
+      AllOf(Field(&ArtdDexoptResult::sizeBytes, 0), Field(&ArtdDexoptResult::sizeBeforeBytes, 0)));
+}
+
+TEST_F(ArtdTest, dexoptCancelledBeforeDex2oat) {
+  std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
+  ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
+
+  constexpr pid_t kPid = 123;
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce([&](auto, const ExecCallbacks& callbacks, auto) {
+        callbacks.on_start(kPid);
+        callbacks.on_end(kPid);
+        return Error();
+      });
+  EXPECT_CALL(mock_kill_, Call(kPid, SIGKILL));
+
+  cancellation_signal->cancel();
+
+  RunDexopt(EX_NONE, Field(&ArtdDexoptResult::cancelled, true), cancellation_signal);
+
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
+}
+
+TEST_F(ArtdTest, dexoptCancelledDuringDex2oat) {
+  std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
+  ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
+
+  constexpr pid_t kPid = 123;
+  constexpr std::chrono::duration<int> kTimeout = std::chrono::seconds(1);
+
+  std::condition_variable process_started_cv, process_killed_cv;
+  std::mutex mu;
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce([&](auto, const ExecCallbacks& callbacks, auto) {
+        std::unique_lock<std::mutex> lock(mu);
+        // Step 2.
+        callbacks.on_start(kPid);
+        process_started_cv.notify_one();
+        EXPECT_EQ(process_killed_cv.wait_for(lock, kTimeout), std::cv_status::no_timeout);
+        // Step 5.
+        callbacks.on_end(kPid);
+        return Error();
+      });
+
+  EXPECT_CALL(mock_kill_, Call(kPid, SIGKILL)).WillOnce([&](auto, auto) {
+    // Step 4.
+    process_killed_cv.notify_one();
+    return 0;
+  });
+
+  std::thread t;
+  {
+    std::unique_lock<std::mutex> lock(mu);
+    // Step 1.
+    t = std::thread([&] {
+      RunDexopt(EX_NONE, Field(&ArtdDexoptResult::cancelled, true), cancellation_signal);
+    });
+    EXPECT_EQ(process_started_cv.wait_for(lock, kTimeout), std::cv_status::no_timeout);
+    // Step 3.
+    cancellation_signal->cancel();
+  }
+
+  t.join();
+
+  // Step 6.
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
+}
+
+TEST_F(ArtdTest, dexoptCancelledAfterDex2oat) {
+  std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
+  ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
+
+  constexpr pid_t kPid = 123;
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
+                      [&](auto, const ExecCallbacks& callbacks, auto) {
+                        callbacks.on_start(kPid);
+                        callbacks.on_end(kPid);
+                        return 0;
+                      }));
+  EXPECT_CALL(mock_kill_, Call).Times(0);
+
+  RunDexopt(EX_NONE, Field(&ArtdDexoptResult::cancelled, false), cancellation_signal);
+
+  // This signal should be ignored.
+  cancellation_signal->cancel();
+
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "new_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "new_vdex");
+  EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.art"));
+}
+
+TEST_F(ArtdTest, dexoptDexFileNotOtherReadable) {
+  dex_file_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
+  RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                  Property(&ndk::ScopedAStatus::getMessage,
+                           HasSubstr("Outputs cannot be other-readable because the dex file"))));
+}
+
+TEST_F(ArtdTest, dexoptProfileNotOtherReadable) {
+  profile_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
+  RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                  Property(&ndk::ScopedAStatus::getMessage,
+                           HasSubstr("Outputs cannot be other-readable because the profile"))));
+}
+
+TEST_F(ArtdTest, dexoptOutputNotOtherReadable) {
+  output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false;
+  dex_file_other_readable_ = false;
+  profile_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(0));
+  RunDexopt();
+  CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.odex", false);
+  CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.vdex", false);
+}
+
+TEST_F(ArtdTest, dexoptUidMismatch) {
+  output_artifacts_.permissionSettings.fileFsPermission.uid = 12345;
+  output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false;
+  dex_file_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
+  RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                  Property(&ndk::ScopedAStatus::getMessage,
+                           HasSubstr("Outputs' owner doesn't match the dex file"))));
+}
+
+TEST_F(ArtdTest, dexoptGidMismatch) {
+  output_artifacts_.permissionSettings.fileFsPermission.gid = 12345;
+  output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false;
+  dex_file_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
+  RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                  Property(&ndk::ScopedAStatus::getMessage,
+                           HasSubstr("Outputs' owner doesn't match the dex file"))));
+}
+
+TEST_F(ArtdTest, dexoptGidMatchesUid) {
+  output_artifacts_.permissionSettings.fileFsPermission = {
+      .uid = 123, .gid = 123, .isOtherReadable = false};
+  EXPECT_CALL(mock_fstat_, Call(_, _)).WillRepeatedly(fstat);  // For profile.
+  EXPECT_CALL(mock_fstat_, Call(FdOf(dex_file_), _))
+      .WillOnce(DoAll(SetArgPointee<1>((struct stat){
+                          .st_mode = S_IRUSR | S_IRGRP, .st_uid = 123, .st_gid = 456}),
+                      Return(0)));
+  ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0));
+  // It's okay to fail on chown. This happens when the test is not run as root.
+  RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE),
+                  AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                        Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown")))));
+}
+
+TEST_F(ArtdTest, dexoptGidMatchesGid) {
+  output_artifacts_.permissionSettings.fileFsPermission = {
+      .uid = 123, .gid = 456, .isOtherReadable = false};
+  EXPECT_CALL(mock_fstat_, Call(_, _)).WillRepeatedly(fstat);  // For profile.
+  EXPECT_CALL(mock_fstat_, Call(FdOf(dex_file_), _))
+      .WillOnce(DoAll(SetArgPointee<1>((struct stat){
+                          .st_mode = S_IRUSR | S_IRGRP, .st_uid = 123, .st_gid = 456}),
+                      Return(0)));
+  ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0));
+  // It's okay to fail on chown. This happens when the test is not run as root.
+  RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE),
+                  AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                        Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown")))));
+}
+
+TEST_F(ArtdTest, dexoptUidGidChangeOk) {
+  // The dex file is other-readable, so we don't check uid and gid.
+  output_artifacts_.permissionSettings.fileFsPermission = {
+      .uid = 12345, .gid = 12345, .isOtherReadable = false};
+  ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0));
+  // It's okay to fail on chown. This happens when the test is not run as root.
+  RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE),
+                  AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                        Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown")))));
+}
+
+TEST_F(ArtdTest, dexoptNoUidGidChange) {
+  output_artifacts_.permissionSettings.fileFsPermission = {
+      .uid = -1, .gid = -1, .isOtherReadable = false};
+  dex_file_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, isProfileUsable) {
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  CreateFile(profile_file);
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(WhenSplitBy(
+                    "--",
+                    AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                    AllOf(Contains(art_root_ + "/bin/profman"),
+                          Contains(Flag("--reference-profile-file-fd=", FdOf(profile_file))),
+                          Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
+                HasKeepFdsFor("--reference-profile-file-fd=", "--apk-fd=")),
+          _,
+          _))
+      .WillOnce(Return(ProfmanResult::kSkipCompilationSmallDelta));
+
+  bool result;
+  EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
+  EXPECT_TRUE(result);
+}
+
+TEST_F(ArtdTest, isProfileUsableFalse) {
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  CreateFile(profile_file);
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(Return(ProfmanResult::kSkipCompilationEmptyProfiles));
+
+  bool result;
+  EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
+  EXPECT_FALSE(result);
+}
+
+TEST_F(ArtdTest, isProfileUsableNotFound) {
+  CreateFile(dex_file_);
+
+  bool result;
+  EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
+  EXPECT_FALSE(result);
+}
+
+TEST_F(ArtdTest, isProfileUsableFailed) {
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  CreateFile(profile_file);
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(100));
+
+  bool result;
+  ndk::ScopedAStatus status = artd_->isProfileUsable(profile_path_.value(), dex_file_, &result);
+
+  EXPECT_FALSE(status.isOk());
+  EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+  EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100"));
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfile) {
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
+  CreateFile(src_file, "abc");
+  OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  dst.profilePath.id = "";
+  dst.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(WhenSplitBy(
+                    "--",
+                    AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                    AllOf(Contains(art_root_ + "/bin/profman"),
+                          Contains("--copy-and-update-profile-key"),
+                          Contains(Flag("--profile-file-fd=", FdOf(src_file))),
+                          Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
+                HasKeepFdsFor("--profile-file-fd=", "--reference-profile-file-fd=", "--apk-fd=")),
+          _,
+          _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "def")),
+                      Return(ProfmanResult::kCopyAndUpdateSuccess)));
+
+  bool result;
+  EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(dst.profilePath.id, Not(IsEmpty()));
+  std::string real_path = OR_FATAL(BuildTmpProfilePath(dst.profilePath));
+  EXPECT_EQ(dst.profilePath.tmpPath, real_path);
+  CheckContent(real_path, "def");
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileFalse) {
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
+  CreateFile(src_file, "abc");
+  OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  dst.profilePath.id = "";
+  dst.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(Return(ProfmanResult::kCopyAndUpdateNoMatch));
+
+  bool result;
+  EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+  EXPECT_FALSE(result);
+  EXPECT_THAT(dst.profilePath.id, IsEmpty());
+  EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileNotFound) {
+  CreateFile(dex_file_);
+
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  dst.profilePath.id = "";
+  dst.profilePath.tmpPath = "";
+
+  bool result;
+  EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+  EXPECT_FALSE(result);
+  EXPECT_THAT(dst.profilePath.id, IsEmpty());
+  EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileFailed) {
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
+  CreateFile(src_file, "abc");
+  OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  dst.profilePath.id = "";
+  dst.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(100));
+
+  bool result;
+  ndk::ScopedAStatus status = artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result);
+
+  EXPECT_FALSE(status.isOk());
+  EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+  EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100"));
+  EXPECT_THAT(dst.profilePath.id, IsEmpty());
+  EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, commitTmpProfile) {
+  const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string tmp_profile_file = OR_FATAL(BuildTmpProfilePath(tmp_profile_path));
+  CreateFile(tmp_profile_file);
+
+  EXPECT_TRUE(artd_->commitTmpProfile(tmp_profile_path).isOk());
+
+  EXPECT_FALSE(std::filesystem::exists(tmp_profile_file));
+  EXPECT_TRUE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))));
+}
+
+TEST_F(ArtdTest, commitTmpProfileFailed) {
+  const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  ndk::ScopedAStatus status = artd_->commitTmpProfile(tmp_profile_path);
+
+  EXPECT_FALSE(status.isOk());
+  EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+  EXPECT_THAT(
+      status.getMessage(),
+      ContainsRegex(R"re(Failed to move .*primary\.prof\.12345\.tmp.* to .*primary\.prof)re"));
+
+  EXPECT_FALSE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))));
+}
+
+TEST_F(ArtdTest, deleteProfile) {
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  CreateFile(profile_file);
+
+  EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+
+  EXPECT_FALSE(std::filesystem::exists(profile_file));
+}
+
+TEST_F(ArtdTest, deleteProfileDoesNotExist) {
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(mock_logger_, Call).Times(0);
+
+  EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+}
+
+TEST_F(ArtdTest, deleteProfileFailed) {
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(
+      mock_logger_,
+      Call(_, _, _, _, _, ContainsRegex(R"re(Failed to remove .*primary\.prof\.12345\.tmp)re")));
+
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(profile_file).parent_path());
+  auto scoped_unroot = ScopedUnroot();
+
+  EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+}
+
+class ArtdGetVisibilityTest : public ArtdTest {
+ protected:
+  template <typename PathType>
+  using Method = ndk::ScopedAStatus (Artd::*)(const PathType&, FileVisibility*);
+
+  template <typename PathType>
+  void TestGetVisibilityOtherReadable(Method<PathType> method,
+                                      const PathType& input,
+                                      const std::string& path) {
+    CreateFile(path);
+    std::filesystem::permissions(
+        path, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
+
+    FileVisibility result;
+    ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+    EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
+  }
+
+  template <typename PathType>
+  void TestGetVisibilityNotOtherReadable(Method<PathType> method,
+                                         const PathType& input,
+                                         const std::string& path) {
+    CreateFile(path);
+    std::filesystem::permissions(
+        path, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
+
+    FileVisibility result;
+    ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+    EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
+  }
+
+  template <typename PathType>
+  void TestGetVisibilityNotFound(Method<PathType> method, const PathType& input) {
+    FileVisibility result;
+    ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+    EXPECT_EQ(result, FileVisibility::NOT_FOUND);
+  }
+
+  template <typename PathType>
+  void TestGetVisibilityPermissionDenied(Method<PathType> method,
+                                         const PathType& input,
+                                         const std::string& path) {
+    CreateFile(path);
+
+    auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(path).parent_path());
+    auto scoped_unroot = ScopedUnroot();
+
+    FileVisibility result;
+    ndk::ScopedAStatus status = ((*artd_).*method)(input, &result);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+    EXPECT_THAT(status.getMessage(), HasSubstr("Failed to get status of"));
+  }
+};
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(&Artd::getProfileVisibility,
+                                 profile_path_.value(),
+                                 OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(&Artd::getProfileVisibility,
+                                    profile_path_.value(),
+                                    OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getProfileVisibility, profile_path_.value());
+}
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(&Artd::getProfileVisibility,
+                                    profile_path_.value(),
+                                    OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(
+      &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(
+      &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getArtifactsVisibility, artifacts_path_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(
+      &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getDexFileVisibility, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(&Artd::getDexFileVisibility, dex_file_, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(&Artd::getDmFileVisibility,
+                                 dm_path_.value(),
+                                 OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(&Artd::getDmFileVisibility,
+                                    dm_path_.value(),
+                                    OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getDmFileVisibility, dm_path_.value());
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(&Artd::getDmFileVisibility,
+                                    dm_path_.value(),
+                                    OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
+
+TEST_F(ArtdTest, mergeProfiles) {
+  const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path));
+  CreateFile(reference_profile_file, "abc");
+
+  // Doesn't exist.
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+
+  PrimaryCurProfilePath profile_1_path{
+      .userId = 1, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path));
+  CreateFile(profile_1_file, "def");
+
+  OutputProfile output_profile{.profilePath = reference_profile_path,
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  std::string dex_file_1 = scratch_path_ + "/a/b.apk";
+  std::string dex_file_2 = scratch_path_ + "/a/c.apk";
+  CreateFile(dex_file_1);
+  CreateFile(dex_file_2);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(WhenSplitBy(
+                    "--",
+                    AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                    AllOf(Contains(art_root_ + "/bin/profman"),
+                          Not(Contains(Flag("--profile-file-fd=", FdOf(profile_0_file)))),
+                          Contains(Flag("--profile-file-fd=", FdOf(profile_1_file))),
+                          Contains(Flag("--reference-profile-file-fd=", FdHasContent("abc"))),
+                          Contains(Flag("--apk-fd=", FdOf(dex_file_1))),
+                          Contains(Flag("--apk-fd=", FdOf(dex_file_2))),
+                          Not(Contains("--force-merge")),
+                          Not(Contains("--boot-image-merge")))),
+                HasKeepFdsFor("--profile-file-fd=", "--reference-profile-file-fd=", "--apk-fd=")),
+          _,
+          _))
+      .WillOnce(DoAll(WithArg<0>(ClearAndWriteToFdFlag("--reference-profile-file-fd=", "merged")),
+                      Return(ProfmanResult::kCompile)));
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path, profile_1_path},
+                                  reference_profile_path,
+                                  &output_profile,
+                                  {dex_file_1, dex_file_2},
+                                  /*in_options=*/{},
+                                  &result)
+                  .isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
+  std::string real_path = OR_FATAL(BuildTmpProfilePath(output_profile.profilePath));
+  EXPECT_EQ(output_profile.profilePath.tmpPath, real_path);
+  CheckContent(real_path, "merged");
+}
+
+TEST_F(ArtdTest, mergeProfilesEmptyReferenceProfile) {
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+  CreateFile(profile_0_file, "def");
+
+  OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(),
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--",
+                      AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                      AllOf(Contains(art_root_ + "/bin/profman"),
+                            Contains(Flag("--profile-file-fd=", FdOf(profile_0_file))),
+                            Contains(Flag("--reference-profile-file-fd=", FdHasContent(""))),
+                            Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
+          _,
+          _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "merged")),
+                      Return(ProfmanResult::kCompile)));
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path},
+                                  std::nullopt,
+                                  &output_profile,
+                                  {dex_file_},
+                                  /*in_options=*/{},
+                                  &result)
+                  .isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
+  EXPECT_THAT(output_profile.profilePath.tmpPath, Not(IsEmpty()));
+}
+
+TEST_F(ArtdTest, mergeProfilesProfilesDontExist) {
+  const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path));
+  CreateFile(reference_profile_file, "abc");
+
+  // Doesn't exist.
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+
+  // Doesn't exist.
+  PrimaryCurProfilePath profile_1_path{
+      .userId = 1, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path));
+
+  OutputProfile output_profile{.profilePath = reference_profile_path,
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode).Times(0);
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path},
+                                  std::nullopt,
+                                  &output_profile,
+                                  {dex_file_},
+                                  /*in_options=*/{},
+                                  &result)
+                  .isOk());
+  EXPECT_FALSE(result);
+  EXPECT_THAT(output_profile.profilePath.id, IsEmpty());
+  EXPECT_THAT(output_profile.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, mergeProfilesWithOptionsForceMerge) {
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+  CreateFile(profile_0_file, "def");
+
+  OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(),
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--", _, AllOf(Contains("--force-merge"), Contains("--boot-image-merge"))),
+          _,
+          _))
+      .WillOnce(Return(ProfmanResult::kSuccess));
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path},
+                                  std::nullopt,
+                                  &output_profile,
+                                  {dex_file_},
+                                  {.forceMerge = true, .forBootImage = true},
+                                  &result)
+                  .isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
+  EXPECT_THAT(output_profile.profilePath.tmpPath, Not(IsEmpty()));
+}
+
+TEST_F(ArtdTest, mergeProfilesWithOptionsDumpOnly) {
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+  CreateFile(profile_0_file, "def");
+
+  OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(),
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  AllOf(WhenSplitBy("--",
+                                    _,
+                                    AllOf(Contains("--dump-only"),
+                                          Not(Contains(Flag("--reference-profile-file-fd=", _))))),
+                        HasKeepFdsFor("--profile-file-fd=", "--apk-fd=", "--dump-output-to-fd=")),
+                  _,
+                  _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--dump-output-to-fd=", "dump")),
+                      Return(ProfmanResult::kSuccess)));
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path},
+                                  std::nullopt,
+                                  &output_profile,
+                                  {dex_file_},
+                                  {.dumpOnly = true},
+                                  &result)
+                  .isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
+  CheckContent(output_profile.profilePath.tmpPath, "dump");
+}
+
+TEST_F(ArtdTest, mergeProfilesWithOptionsDumpClassesAndMethods) {
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+  CreateFile(profile_0_file, "def");
+
+  OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(),
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              _,
+                              AllOf(Contains("--dump-classes-and-methods"),
+                                    Not(Contains(Flag("--reference-profile-file-fd=", _))))),
+                  _,
+                  _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--dump-output-to-fd=", "dump")),
+                      Return(ProfmanResult::kSuccess)));
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path},
+                                  std::nullopt,
+                                  &output_profile,
+                                  {dex_file_},
+                                  {.dumpClassesAndMethods = true},
+                                  &result)
+                  .isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
+  CheckContent(output_profile.profilePath.tmpPath, "dump");
+}
+
+TEST_F(ArtdTest, cleanup) {
+  std::vector<std::string> gc_removed_files;
+  std::vector<std::string> gc_kept_files;
+
+  auto CreateGcRemovedFile = [&](const std::string& path) {
+    CreateFile(path);
+    gc_removed_files.push_back(path);
+  };
+
+  auto CreateGcKeptFile = [&](const std::string& path) {
+    CreateFile(path);
+    gc_kept_files.push_back(path);
+  };
+
+  // Unmanaged files.
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/1.odex");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.odex");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.txt");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.txt");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.tmp");
+
+  // Files to keep.
+  CreateGcKeptFile(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof");
+  CreateGcKeptFile(android_data_ + "/misc/profiles/cur/3/com.android.foo/primary.prof");
+  CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@[email protected]@classes.dex");
+  CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@[email protected]@classes.vdex");
+  CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@[email protected]@classes.art");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.vdex");
+  CreateGcKeptFile(
+      android_expand_ +
+      "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.odex");
+  CreateGcKeptFile(
+      android_expand_ +
+      "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.vdex");
+  CreateGcKeptFile(
+      android_expand_ +
+      "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.art");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.odex");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.vdex");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.art");
+
+  // Files to remove.
+  CreateGcRemovedFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof");
+  CreateGcRemovedFile(android_data_ + "/misc/profiles/cur/2/com.android.foo/primary.prof");
+  CreateGcRemovedFile(android_data_ + "/misc/profiles/cur/3/com.android.bar/primary.prof");
+  CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/extra.odex");
+  CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@[email protected]@classes.dex");
+  CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@[email protected]@classes.vdex");
+  CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@[email protected]@classes.art");
+  CreateGcRemovedFile(
+      android_expand_ +
+      "/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.odex");
+  CreateGcRemovedFile(
+      android_expand_ +
+      "/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.vdex");
+  CreateGcRemovedFile(
+      android_expand_ +
+      "/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.art");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/1.prof");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/1.prof.123456.tmp");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.odex");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.vdex");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.art");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.odex.123456.tmp");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/2.odex.123456.tmp");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.odex");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.art");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.vdex.123456.tmp");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.odex");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.vdex");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.art");
+  CreateGcRemovedFile(android_data_ +
+                      "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.art.123456.tmp");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.bar/aaa/oat/arm64/1.vdex");
+
+  int64_t aidl_return;
+  ASSERT_TRUE(
+      artd_
+          ->cleanup(
+              {
+                  PrimaryCurProfilePath{
+                      .userId = 1, .packageName = "com.android.foo", .profileName = "primary"},
+                  PrimaryCurProfilePath{
+                      .userId = 3, .packageName = "com.android.foo", .profileName = "primary"},
+              },
+              {
+                  ArtifactsPath{.dexPath = "/system/app/Foo/Foo.apk",
+                                .isa = "arm64",
+                                .isInDalvikCache = true},
+                  ArtifactsPath{
+                      .dexPath =
+                          android_expand_ +
+                          "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/base.apk",
+                      .isa = "arm64",
+                      .isInDalvikCache = false},
+                  ArtifactsPath{.dexPath = android_data_ + "/user_de/0/com.android.foo/aaa/2.apk",
+                                .isa = "arm64",
+                                .isInDalvikCache = false},
+              },
+              {
+                  VdexPath{ArtifactsPath{
+                      .dexPath = android_data_ + "/user_de/0/com.android.foo/aaa/1.apk",
+                      .isa = "arm64",
+                      .isInDalvikCache = false}},
+              },
+              &aidl_return)
+          .isOk());
+
+  for (const std::string& path : gc_removed_files) {
+    EXPECT_FALSE(std::filesystem::exists(path)) << "'{}' should be removed"_format(path);
+  }
+
+  for (const std::string& path : gc_kept_files) {
+    EXPECT_TRUE(std::filesystem::exists(path)) << "'{}' should be kept"_format(path);
+  }
+}
+
+}  // namespace
+}  // namespace artd
+}  // namespace art
diff --git a/artd/binder/Android.bp b/artd/binder/Android.bp
index 6acfe4e..b6fd5b8 100644
--- a/artd/binder/Android.bp
+++ b/artd/binder/Android.bp
@@ -25,12 +25,16 @@
 aidl_interface {
     name: "artd-aidl",
     srcs: [
-        "android/os/IArtd.aidl",
+        "com/android/server/art/*.aidl",
     ],
     host_supported: true,
     backend: {
         java: {
             enabled: true,
+            apex_available: [
+                "com.android.art",
+                "com.android.art.debug",
+            ],
         },
         cpp: {
             enabled: false,
@@ -40,9 +44,7 @@
             apex_available: [
                 "com.android.art",
                 "com.android.art.debug",
-                "com.android.compos",
             ],
-            min_sdk_version: "31",
         },
     },
     unstable: true,
@@ -50,4 +52,5 @@
         "//system/tools/aidl/build",
         "//art:__subpackages__",
     ],
+    min_sdk_version: "31",
 }
diff --git a/artd/binder/android/os/IArtd.aidl b/artd/binder/android/os/IArtd.aidl
deleted file mode 100644
index a16764b..0000000
--- a/artd/binder/android/os/IArtd.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-package android.os;
-
-/** {@hide} */
-interface IArtd {
-    // Test to see if the artd service is available.
-    boolean isAlive();
-}
diff --git a/artd/binder/com/android/server/art/ArtConstants.aidl b/artd/binder/com/android/server/art/ArtConstants.aidl
new file mode 100644
index 0000000..e9f702e
--- /dev/null
+++ b/artd/binder/com/android/server/art/ArtConstants.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Constants used by ART Service Java code that must be kept in sync with those in ART native code.
+ *
+ * @hide
+ */
+parcelable ArtConstants {
+    /**
+     * A special compilation reason to indicate that only the VDEX file is usable. Keep in sync with
+     * {@code kReasonVdex} in art/runtime/oat_file.h.
+     *
+     * This isn't a valid reason to feed into DexoptParams.
+     */
+    const @utf8InCpp String REASON_VDEX = "vdex";
+}
diff --git a/artd/binder/com/android/server/art/ArtdDexoptResult.aidl b/artd/binder/com/android/server/art/ArtdDexoptResult.aidl
new file mode 100644
index 0000000..6f031f2
--- /dev/null
+++ b/artd/binder/com/android/server/art/ArtdDexoptResult.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * The result of {@code IArtd.dexopt}.
+ *
+ * @hide
+ */
+parcelable ArtdDexoptResult {
+    /** True if the operation is cancelled. */
+    boolean cancelled;
+    /**
+     * The wall time of the dex2oat invocation, in milliseconds, or 0 if dex2oat is not run or if
+     * failed to get the value.
+     */
+    long wallTimeMs;
+    /**
+     * The CPU time of the dex2oat invocation, in milliseconds, or 0 if dex2oat is not run or if
+     * failed to get the value.
+     */
+    long cpuTimeMs;
+    /**
+     * The total size, in bytes, of the dexopt artifacts, or 0 if dex2oat fails, is cancelled, or
+     * is not run.
+     */
+    long sizeBytes;
+    /**
+     * The total size, in bytes, of the previous dexopt artifacts that have been replaced, or
+     * 0 if there were no previous dexopt artifacts or dex2oat fails, is cancelled, or is not
+     * run.
+     */
+    long sizeBeforeBytes;
+}
diff --git a/artd/binder/com/android/server/art/ArtifactsPath.aidl b/artd/binder/com/android/server/art/ArtifactsPath.aidl
new file mode 100644
index 0000000..3122f0f
--- /dev/null
+++ b/artd/binder/com/android/server/art/ArtifactsPath.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to the dexopt artifacts of a dex file (i.e., ART, OAT, and VDEX files).
+ *
+ * @hide
+ */
+parcelable ArtifactsPath {
+    /** The absolute path starting with '/' to the dex file (i.e., APK or JAR file). */
+    @utf8InCpp String dexPath;
+    /** The instruction set of the dexopt artifacts. */
+    @utf8InCpp String isa;
+    /** Whether the dexopt artifacts are in the dalvik-cache folder. */
+    boolean isInDalvikCache;
+}
diff --git a/artd/binder/com/android/server/art/DexMetadataPath.aidl b/artd/binder/com/android/server/art/DexMetadataPath.aidl
new file mode 100644
index 0000000..5f9ab81
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexMetadataPath.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to a dex metadata file.
+ *
+ * @hide
+ */
+parcelable DexMetadataPath {
+    /**
+     * The absolute path starting with '/' to the dex file that the dex metadata file is next to.
+     */
+    @utf8InCpp String dexPath;
+}
diff --git a/artd/binder/com/android/server/art/DexoptOptions.aidl b/artd/binder/com/android/server/art/DexoptOptions.aidl
new file mode 100644
index 0000000..305445e
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexoptOptions.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Miscellaneous options for performing dexopt. Every field corresponds to a dex2oat command line
+ * flag.
+ *
+ * DO NOT add fields for flags that artd can determine directly with trivial logic. That includes
+ * static flags, and flags that only depend on system properties or other passed parameters, such as
+ * the priority class.
+ *
+ * All fields are required.
+ *
+ * @hide
+ */
+parcelable DexoptOptions {
+    /** --compilation-reason */
+    @utf8InCpp String compilationReason;
+    /** -Xtarget-sdk-version */
+    int targetSdkVersion;
+    /** --debuggable */
+    boolean debuggable;
+    /** --app-image-fd */
+    boolean generateAppImage;
+    /** -Xhidden-api-policy:enabled */
+    boolean hiddenApiPolicyEnabled;
+    /** --comments */
+    @utf8InCpp String comments;
+}
diff --git a/artd/binder/com/android/server/art/DexoptTrigger.aidl b/artd/binder/com/android/server/art/DexoptTrigger.aidl
new file mode 100644
index 0000000..79621a9
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexoptTrigger.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the conditions where dexopt should be performed.
+ * See `OatFileAssistant::DexOptTrigger`.
+ *
+ * This is actually used as a bit field, but is declared as an enum because AIDL doesn't support bit
+ * fields.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum DexoptTrigger {
+    COMPILER_FILTER_IS_BETTER = 1 << 0,
+    COMPILER_FILTER_IS_SAME = 1 << 1,
+    COMPILER_FILTER_IS_WORSE = 1 << 2,
+    PRIMARY_BOOT_IMAGE_BECOMES_USABLE = 1 << 3,
+    NEED_EXTRACTION = 1 << 4,
+}
diff --git a/artd/binder/com/android/server/art/FileVisibility.aidl b/artd/binder/com/android/server/art/FileVisibility.aidl
new file mode 100644
index 0000000..ceaa818
--- /dev/null
+++ b/artd/binder/com/android/server/art/FileVisibility.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Indicates the visibility of a file. I.e., whether the file has the "read" bit for "others"
+ * (S_IROTH).
+ *
+ * Theoretically, even if the value is {@code OTHER_READABLE}, others' access can still be denied
+ * due to the lack of the "exec" bit on parent directories. However, for compilation artifacts, all
+ * parent directories do have the "exec" bit for "others" in practice.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum FileVisibility {
+    NOT_FOUND = 0,
+    OTHER_READABLE = 1,
+    NOT_OTHER_READABLE = 2,
+}
diff --git a/artd/binder/com/android/server/art/FsPermission.aidl b/artd/binder/com/android/server/art/FsPermission.aidl
new file mode 100644
index 0000000..9c2ddb9
--- /dev/null
+++ b/artd/binder/com/android/server/art/FsPermission.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the Linux filesystem permission of a file or a directory.
+ *
+ * If both `uid` and `gid` are negative, no `chown` will be performed.
+ *
+ * If none of the booleans are set, the default permission bits are `rw-r-----` for a file, and
+ * `rwxr-x---` for a directory.
+ *
+ * @hide
+ */
+parcelable FsPermission {
+    int uid;
+    int gid;
+    /**
+     * Whether the file/directory should have the "read" bit for "others" (S_IROTH).
+     */
+    boolean isOtherReadable;
+    /**
+     * Whether the file/directory should have the "execute" bit for "others" (S_IXOTH).
+     */
+    boolean isOtherExecutable;
+}
diff --git a/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl b/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl
new file mode 100644
index 0000000..99c4951
--- /dev/null
+++ b/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * The result of {@code IArtd.getDexoptNeeded}.
+ *
+ * @hide
+ */
+parcelable GetDexoptNeededResult {
+    /** Whether dexopt is needed. */
+    boolean isDexoptNeeded;
+    /** Whether there is a usable VDEX file. Note that this can be true even if dexopt is needed. */
+    boolean isVdexUsable;
+    /** The location of the best usable artifacts. */
+    ArtifactsLocation artifactsLocation = ArtifactsLocation.NONE_OR_ERROR;
+
+    enum ArtifactsLocation {
+        /** No usable artifacts. */
+        NONE_OR_ERROR = 0,
+        /** In the global "dalvik-cache" folder. */
+        DALVIK_CACHE = 1,
+        /** In the "oat" folder next to the dex file. */
+        NEXT_TO_DEX = 2,
+        /** In the dex metadata file. This means the only usable artifact is the VDEX file. */
+        DM = 3,
+    }
+}
diff --git a/artd/binder/com/android/server/art/GetDexoptStatusResult.aidl b/artd/binder/com/android/server/art/GetDexoptStatusResult.aidl
new file mode 100644
index 0000000..08786ca
--- /dev/null
+++ b/artd/binder/com/android/server/art/GetDexoptStatusResult.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * The result of {@code IArtd.getDexoptStatus}. Each field corresponds to a field in
+ * {@code com.android.server.art.model.DexoptStatus.DexFileDexoptStatus}.
+ *
+ * @hide
+ */
+parcelable GetDexoptStatusResult {
+    @utf8InCpp String compilerFilter;
+    @utf8InCpp String compilationReason;
+    @utf8InCpp String locationDebugString;
+}
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
new file mode 100644
index 0000000..a130e96
--- /dev/null
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.server.art;
+
+/** @hide */
+interface IArtd {
+    // Test to see if the artd service is available.
+    boolean isAlive();
+
+    /**
+     * Deletes artifacts and returns the released space, in bytes.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    long deleteArtifacts(in com.android.server.art.ArtifactsPath artifactsPath);
+
+    /**
+     * Returns the dexopt status of a dex file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.GetDexoptStatusResult getDexoptStatus(
+            @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
+            @nullable @utf8InCpp String classLoaderContext);
+
+    /**
+     * Returns true if the profile exists and contains entries for the given dex file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    boolean isProfileUsable(in com.android.server.art.ProfilePath profile,
+            @utf8InCpp String dexFile);
+
+    /**
+     * Copies the profile and rewrites it for the given dex file. Returns true and fills
+     * `dst.profilePath.id` if the operation succeeds and `src` exists and contains entries that
+     * match the given dex file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    boolean copyAndRewriteProfile(in com.android.server.art.ProfilePath src,
+            inout com.android.server.art.OutputProfile dst, @utf8InCpp String dexFile);
+
+    /**
+     * Moves the temporary profile to the permanent location.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    void commitTmpProfile(in com.android.server.art.ProfilePath.TmpProfilePath profile);
+
+    /**
+     * Deletes the profile. Does nothing of the profile doesn't exist.
+     *
+     * Operates on the whole DM file if given one.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    void deleteProfile(in com.android.server.art.ProfilePath profile);
+
+    /**
+     * Returns the visibility of the profile.
+     *
+     * Operates on the whole DM file if given one.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.FileVisibility getProfileVisibility(
+            in com.android.server.art.ProfilePath profile);
+
+    /**
+     * Merges profiles. Both `profiles` and `referenceProfile` are inputs, while the difference is
+     * that `referenceProfile` is also used as the reference to calculate the diff. `profiles` that
+     * don't exist are skipped, while `referenceProfile`, if provided, must exist. Returns true,
+     * writes the merge result to `outputProfile` and fills `outputProfile.profilePath.id` and
+     * `outputProfile.profilePath.tmpPath` if a merge has been performed.
+     *
+     * When `options.forceMerge`, `options.dumpOnly`, or `options.dumpClassesAndMethods` is set,
+     * `referenceProfile` must not be set. I.e., all inputs must be provided by `profiles`. This is
+     * because the merge will always happen, and hence no reference profile is needed to calculate
+     * the diff.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    boolean mergeProfiles(in List<com.android.server.art.ProfilePath> profiles,
+            in @nullable com.android.server.art.ProfilePath referenceProfile,
+            inout com.android.server.art.OutputProfile outputProfile,
+            in @utf8InCpp List<String> dexFiles,
+            in com.android.server.art.MergeProfileOptions options);
+
+    /**
+     * Returns the visibility of the artifacts.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.FileVisibility getArtifactsVisibility(
+            in com.android.server.art.ArtifactsPath artifactsPath);
+
+    /**
+     * Returns the visibility of the dex file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.FileVisibility getDexFileVisibility(@utf8InCpp String dexFile);
+
+    /**
+     * Returns the visibility of the DM file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.FileVisibility getDmFileVisibility(
+            in com.android.server.art.DexMetadataPath dmFile);
+
+    /**
+     * Returns true if dexopt is needed. `dexoptTrigger` is a bit field that consists of values
+     * defined in `com.android.server.art.DexoptTrigger`.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.GetDexoptNeededResult getDexoptNeeded(
+            @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
+            @nullable @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
+            int dexoptTrigger);
+
+    /**
+     * Dexopts a dex file for the given instruction set.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.ArtdDexoptResult dexopt(
+            in com.android.server.art.OutputArtifacts outputArtifacts,
+            @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
+            @nullable @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
+            in @nullable com.android.server.art.ProfilePath profile,
+            in @nullable com.android.server.art.VdexPath inputVdex,
+            in @nullable com.android.server.art.DexMetadataPath dmFile,
+            com.android.server.art.PriorityClass priorityClass,
+            in com.android.server.art.DexoptOptions dexoptOptions,
+            in com.android.server.art.IArtdCancellationSignal cancellationSignal);
+
+    /**
+     * Returns a cancellation signal which can be used to cancel {@code dexopt} calls.
+     */
+    com.android.server.art.IArtdCancellationSignal createCancellationSignal();
+
+    /**
+     * Deletes all files that are managed by artd, except those specified in the arguments. Returns
+     * the size of the freed space, in bytes.
+     *
+     * For each entry in `artifactsToKeep`, all three kinds of artifacts (ODEX, VDEX, ART) are
+     * kept. For each entry in `vdexFilesToKeep`, only the VDEX file will be kept. Note that VDEX
+     * files included in `artifactsToKeep` don't have to be listed in `vdexFilesToKeep`.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    long cleanup(in List<com.android.server.art.ProfilePath> profilesToKeep,
+            in List<com.android.server.art.ArtifactsPath> artifactsToKeep,
+            in List<com.android.server.art.VdexPath> vdexFilesToKeep);
+
+    /**
+     * Returns whether the dex file is in Incremental FS.
+     *
+     * Throws fatal errors. On non-fatal errors, logs the error and returns false.
+     */
+    boolean isIncrementalFsPath(@utf8InCpp String dexFile);
+}
diff --git a/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl b/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl
new file mode 100644
index 0000000..fb15e64
--- /dev/null
+++ b/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Similar to `android.os.CancellationSignal` but for artd. Must be created by
+ * `IArtd.createCancellationSignal`.
+ *
+ * @hide
+ */
+interface IArtdCancellationSignal {
+    oneway void cancel();
+
+    /** For artd internal type-checking. DO NOT USE. */
+    long getType();
+}
diff --git a/artd/binder/com/android/server/art/MergeProfileOptions.aidl b/artd/binder/com/android/server/art/MergeProfileOptions.aidl
new file mode 100644
index 0000000..2d007f9
--- /dev/null
+++ b/artd/binder/com/android/server/art/MergeProfileOptions.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Miscellaneous options for merging profiles. Every field corresponds to a profman command line
+ * flag.
+ *
+ * DO NOT add fields for flags that artd can determine directly with trivial logic. That includes
+ * static flags, and flags that only depend on system properties or other passed parameters.
+ *
+ * All fields are required.
+ *
+ * @hide
+ */
+parcelable MergeProfileOptions {
+    /** --force-merge */
+    boolean forceMerge;
+    /** --boot-image-merge */
+    boolean forBootImage;
+    /** --dump-only */
+    boolean dumpOnly;
+    /** --dump-classes-and-methods */
+    boolean dumpClassesAndMethods;
+}
diff --git a/artd/binder/com/android/server/art/OutputArtifacts.aidl b/artd/binder/com/android/server/art/OutputArtifacts.aidl
new file mode 100644
index 0000000..9a53965
--- /dev/null
+++ b/artd/binder/com/android/server/art/OutputArtifacts.aidl
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents output dexopt artifacts of a dex file (i.e., ART, OAT, and VDEX files).
+ *
+ * @hide
+ */
+parcelable OutputArtifacts {
+    /** The path to the output. */
+    com.android.server.art.ArtifactsPath artifactsPath;
+
+    parcelable PermissionSettings {
+        /**
+         * The permission of the directories that contain the artifacts. Has no effect if
+         * `artifactsPath.isInDalvikCache` is true.
+         */
+        com.android.server.art.FsPermission dirFsPermission;
+
+        /** The permission of the files. */
+        com.android.server.art.FsPermission fileFsPermission;
+
+        /** The tuple used for looking up for the SELinux context. */
+        parcelable SeContext {
+            /** The seinfo tag in SELinux policy. */
+            @utf8InCpp String seInfo;
+
+            /** The uid that represents the combination of the user id and the app id. */
+            int uid;
+        }
+
+        /**
+         * Determines the SELinux context of the directories and the files. If empty, the default
+         * context based on the file path will be used. Has no effect if
+         * `artifactsPath.isInDalvikCache` is true.
+         */
+        @nullable SeContext seContext;
+    }
+
+    PermissionSettings permissionSettings;
+}
diff --git a/artd/binder/com/android/server/art/OutputProfile.aidl b/artd/binder/com/android/server/art/OutputProfile.aidl
new file mode 100644
index 0000000..50efda2
--- /dev/null
+++ b/artd/binder/com/android/server/art/OutputProfile.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents output profile file.
+ *
+ * @hide
+ */
+parcelable OutputProfile {
+    /**
+     * The path to the output.
+     *
+     * Only outputing to a temporary file is supported to avoid race condition.
+     */
+    com.android.server.art.ProfilePath.TmpProfilePath profilePath;
+
+    /** The permission of the file. */
+    com.android.server.art.FsPermission fsPermission;
+}
diff --git a/artd/binder/com/android/server/art/PriorityClass.aidl b/artd/binder/com/android/server/art/PriorityClass.aidl
new file mode 100644
index 0000000..abea3f3
--- /dev/null
+++ b/artd/binder/com/android/server/art/PriorityClass.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Keep in sync with {@link ArtFlags.PriorityClassApi}.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum PriorityClass {
+    BOOT = 100,
+    INTERACTIVE_FAST = 80,
+    INTERACTIVE = 60,
+    BACKGROUND = 40,
+}
diff --git a/artd/binder/com/android/server/art/ProfilePath.aidl b/artd/binder/com/android/server/art/ProfilePath.aidl
new file mode 100644
index 0000000..43df531
--- /dev/null
+++ b/artd/binder/com/android/server/art/ProfilePath.aidl
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to a profile file.
+ *
+ * @hide
+ */
+union ProfilePath {
+    PrimaryRefProfilePath primaryRefProfilePath;
+    PrebuiltProfilePath prebuiltProfilePath;
+    PrimaryCurProfilePath primaryCurProfilePath;
+    SecondaryRefProfilePath secondaryRefProfilePath;
+    SecondaryCurProfilePath secondaryCurProfilePath;
+    TmpProfilePath tmpProfilePath;
+
+    /** Represents a profile in the dex metadata file. */
+    com.android.server.art.DexMetadataPath dexMetadataPath;
+
+    /** Represents a reference profile. */
+    parcelable PrimaryRefProfilePath {
+        /** The name of the package. */
+        @utf8InCpp String packageName;
+        /** The stem of the profile file */
+        @utf8InCpp String profileName;
+    }
+
+    /**
+     * Represents a profile next to a dex file. This is usually a prebuilt profile in the system
+     * image, but it can also be a profile that package manager can potentially put along with the
+     * APK during installation. The latter one is not officially supported by package manager, but
+     * OEMs can customize package manager to support that.
+     */
+    parcelable PrebuiltProfilePath {
+        /** The path to the dex file that the profile is next to. */
+        @utf8InCpp String dexPath;
+    }
+
+    /** Represents a current profile. */
+    parcelable PrimaryCurProfilePath {
+        /** The user ID of the user that owns the profile. */
+        int userId;
+        /** The name of the package. */
+        @utf8InCpp String packageName;
+        /** The stem of the profile file */
+        @utf8InCpp String profileName;
+    }
+
+    /** Represents a reference profile of a secondary dex file. */
+    parcelable SecondaryRefProfilePath {
+        /**
+         * The path to the dex file that the profile is next to.
+         *
+         * Currently, possible paths are in the format of
+         * `{/data,/mnt/expand/<volume-uuid>}/{user,user_de}/<user-id>/<package-name>/...`.
+         */
+        @utf8InCpp String dexPath;
+    }
+
+    /** Represents a current profile of a secondary dex file. */
+    parcelable SecondaryCurProfilePath {
+        /** The path to the dex file that the profile is next to. */
+        @utf8InCpp String dexPath;
+    }
+
+    /** All types of profile paths that artd can write to. */
+    union WritableProfilePath {
+        PrimaryRefProfilePath forPrimary;
+        SecondaryRefProfilePath forSecondary;
+    }
+
+    /** Represents a temporary profile. */
+    parcelable TmpProfilePath {
+        /** The path that this temporary file will eventually be committed to. */
+        WritableProfilePath finalPath;
+        /** A unique identifier to distinguish this temporary file from others. Filled by artd. */
+        @utf8InCpp String id;
+        /** The path to the temporary file. Filled by artd. */
+        @utf8InCpp String tmpPath;
+    }
+}
diff --git a/artd/binder/com/android/server/art/VdexPath.aidl b/artd/binder/com/android/server/art/VdexPath.aidl
new file mode 100644
index 0000000..6112e7a
--- /dev/null
+++ b/artd/binder/com/android/server/art/VdexPath.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to a VDEX file.
+ *
+ * @hide
+ */
+union VdexPath {
+    /** Represents a VDEX file as part of the artifacts. */
+    com.android.server.art.ArtifactsPath artifactsPath;
+}
diff --git a/artd/file_utils.cc b/artd/file_utils.cc
new file mode 100644
index 0000000..fb55dec
--- /dev/null
+++ b/artd/file_utils.cc
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "file_utils.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <utility>
+
+#include "aidl/com/android/server/art/FsPermission.h"
+#include "android-base/errors.h"
+#include "android-base/logging.h"
+#include "android-base/result.h"
+#include "android-base/scopeguard.h"
+#include "base/os.h"
+#include "base/unix_file/fd_file.h"
+#include "fmt/format.h"
+
+namespace art {
+namespace artd {
+
+namespace {
+
+using ::aidl::com::android::server::art::FsPermission;
+using ::android::base::make_scope_guard;
+using ::android::base::Result;
+
+using ::fmt::literals::operator""_format;  // NOLINT
+
+void UnlinkIfExists(const std::string& path) {
+  std::error_code ec;
+  std::filesystem::remove(path, ec);
+  if (ec) {
+    LOG(WARNING) << "Failed to remove file '{}': {}"_format(path, ec.message());
+  }
+}
+
+}  // namespace
+
+Result<std::unique_ptr<NewFile>> NewFile::Create(const std::string& path,
+                                                 const FsPermission& fs_permission) {
+  std::unique_ptr<NewFile> output_file(new NewFile(path, fs_permission));
+  OR_RETURN(output_file->Init());
+  return output_file;
+}
+
+NewFile::~NewFile() { Cleanup(); }
+
+Result<void> NewFile::Keep() {
+  if (close(std::exchange(fd_, -1)) != 0) {
+    return ErrnoErrorf("Failed to close file '{}'", temp_path_);
+  }
+  return {};
+}
+
+Result<void> NewFile::CommitOrAbandon() {
+  auto cleanup = make_scope_guard([this] { Unlink(); });
+  OR_RETURN(Keep());
+  std::error_code ec;
+  std::filesystem::rename(temp_path_, final_path_, ec);
+  if (ec) {
+    // If this fails because the temp file doesn't exist, it could be that the file is deleted by
+    // `Artd::cleanup` if that method is run simultaneously. At the time of writing, this should
+    // never happen because `Artd::cleanup` is only called at the end of the backgrond dexopt job.
+    return Errorf(
+        "Failed to move new file '{}' to path '{}': {}", temp_path_, final_path_, ec.message());
+  }
+  cleanup.Disable();
+  committed_ = true;
+  return {};
+}
+
+void NewFile::Cleanup() {
+  if (fd_ >= 0) {
+    Unlink();
+    if (close(std::exchange(fd_, -1)) != 0) {
+      // Nothing we can do. If the file is already unlinked, it will go away when the process exits.
+      PLOG(WARNING) << "Failed to close file '" << temp_path_ << "'";
+    }
+  }
+}
+
+Result<void> NewFile::Init() {
+  mode_t mode = FileFsPermissionToMode(fs_permission_);
+  // "<path_>.XXXXXX.tmp".
+  temp_path_ = BuildTempPath(final_path_, "XXXXXX");
+  fd_ = mkstemps(temp_path_.data(), /*suffixlen=*/4);
+  if (fd_ < 0) {
+    return ErrnoErrorf("Failed to create temp file for '{}'", final_path_);
+  }
+  temp_id_ = temp_path_.substr(/*pos=*/final_path_.length() + 1, /*count=*/6);
+  if (fchmod(fd_, mode) != 0) {
+    return ErrnoErrorf("Failed to chmod file '{}'", temp_path_);
+  }
+  OR_RETURN(Chown(temp_path_, fs_permission_));
+  return {};
+}
+
+void NewFile::Unlink() {
+  // This should never fail. We were able to create the file, so we should be able to remove it.
+  UnlinkIfExists(temp_path_);
+}
+
+Result<void> NewFile::CommitAllOrAbandon(const std::vector<NewFile*>& files_to_commit,
+                                         const std::vector<std::string_view>& files_to_remove) {
+  std::vector<std::pair<std::string_view, std::string>> moved_files;
+
+  auto cleanup = make_scope_guard([&]() {
+    // Clean up new files.
+    for (NewFile* new_file : files_to_commit) {
+      if (new_file->committed_) {
+        UnlinkIfExists(new_file->FinalPath());
+      } else {
+        new_file->Cleanup();
+      }
+    }
+
+    // Move old files back.
+    for (const auto& [original_path, temp_path] : moved_files) {
+      std::error_code ec;
+      std::filesystem::rename(temp_path, original_path, ec);
+      if (ec) {
+        // This should never happen. We were able to move the file from `original_path` to
+        // `temp_path`. We should be able to move it back.
+        LOG(WARNING) << "Failed to move old file '{}' back from temporary path '{}': {}"_format(
+            original_path, temp_path, ec.message());
+      }
+    }
+  });
+
+  // Move old files to temporary locations.
+  std::vector<std::string_view> all_files_to_remove;
+  all_files_to_remove.reserve(files_to_commit.size() + files_to_remove.size());
+  for (NewFile* file : files_to_commit) {
+    all_files_to_remove.push_back(file->FinalPath());
+  }
+  all_files_to_remove.insert(
+      all_files_to_remove.end(), files_to_remove.begin(), files_to_remove.end());
+
+  for (std::string_view original_path : all_files_to_remove) {
+    std::error_code ec;
+    std::filesystem::file_status status = std::filesystem::status(original_path, ec);
+    if (!std::filesystem::status_known(status)) {
+      return Errorf("Failed to get status of old file '{}': {}", original_path, ec.message());
+    }
+    if (std::filesystem::is_directory(status)) {
+      return ErrnoErrorf("Old file '{}' is a directory", original_path);
+    }
+    if (std::filesystem::exists(status)) {
+      std::string temp_path = BuildTempPath(original_path, "XXXXXX");
+      int fd = mkstemps(temp_path.data(), /*suffixlen=*/4);
+      if (fd < 0) {
+        return ErrnoErrorf("Failed to create temporary path for old file '{}'", original_path);
+      }
+      close(fd);
+
+      std::filesystem::rename(original_path, temp_path, ec);
+      if (ec) {
+        UnlinkIfExists(temp_path);
+        return Errorf("Failed to move old file '{}' to temporary path '{}': {}",
+                      original_path,
+                      temp_path,
+                      ec.message());
+      }
+
+      moved_files.push_back({original_path, std::move(temp_path)});
+    }
+  }
+
+  // Commit new files.
+  for (NewFile* file : files_to_commit) {
+    OR_RETURN(file->CommitOrAbandon());
+  }
+
+  cleanup.Disable();
+
+  // Clean up old files.
+  for (const auto& [original_path, temp_path] : moved_files) {
+    // This should never fail.  We were able to move the file to `temp_path`. We should be able to
+    // remove it.
+    UnlinkIfExists(temp_path);
+  }
+
+  return {};
+}
+
+std::string NewFile::BuildTempPath(std::string_view final_path, const std::string& id) {
+  return "{}.{}.tmp"_format(final_path, id);
+}
+
+Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path) {
+  std::unique_ptr<File> file(OS::OpenFileForReading(path.c_str()));
+  if (file == nullptr) {
+    return ErrnoErrorf("Failed to open file '{}'", path);
+  }
+  return file;
+}
+
+mode_t FileFsPermissionToMode(const FsPermission& fs_permission) {
+  return S_IRUSR | S_IWUSR | S_IRGRP | (fs_permission.isOtherReadable ? S_IROTH : 0) |
+         (fs_permission.isOtherExecutable ? S_IXOTH : 0);
+}
+
+mode_t DirFsPermissionToMode(const FsPermission& fs_permission) {
+  return FileFsPermissionToMode(fs_permission) | S_IXUSR | S_IXGRP;
+}
+
+Result<void> Chown(const std::string& path, const FsPermission& fs_permission) {
+  if (fs_permission.uid < 0 && fs_permission.gid < 0) {
+    // Keep the default owner.
+  } else if (fs_permission.uid < 0 || fs_permission.gid < 0) {
+    return Errorf("uid and gid must be both non-negative or both negative, got {} and {}.",
+                  fs_permission.uid,
+                  fs_permission.gid);
+  }
+  if (chown(path.c_str(), fs_permission.uid, fs_permission.gid) != 0) {
+    return ErrnoErrorf("Failed to chown '{}'", path);
+  }
+  return {};
+}
+
+}  // namespace artd
+}  // namespace art
diff --git a/artd/file_utils.h b/artd/file_utils.h
new file mode 100644
index 0000000..b5fd170
--- /dev/null
+++ b/artd/file_utils.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 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_ARTD_FILE_UTILS_H_
+#define ART_ARTD_FILE_UTILS_H_
+
+#include <sys/types.h>
+
+#include <memory>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "aidl/com/android/server/art/FsPermission.h"
+#include "android-base/result.h"
+#include "base/os.h"
+
+namespace art {
+namespace artd {
+
+// A class that creates a new file that will eventually be committed to the given path. The new file
+// is created at a temporary location. It will not overwrite the file at the given path until
+// `CommitOrAbandon` has been called and will be automatically cleaned up on object destruction
+// unless `CommitOrAbandon` has been called.
+// The new file is opened without O_CLOEXEC so that it can be passed to subprocesses.
+class NewFile {
+ public:
+  // Creates a new file at the given path with the given permission.
+  static android::base::Result<std::unique_ptr<NewFile>> Create(
+      const std::string& path, const aidl::com::android::server::art::FsPermission& fs_permission);
+
+  NewFile(const NewFile&) = delete;
+  NewFile& operator=(const NewFile&) = delete;
+  NewFile(NewFile&& other) noexcept
+      : fd_(std::exchange(other.fd_, -1)),
+        final_path_(std::move(other.final_path_)),
+        temp_path_(std::move(other.temp_path_)),
+        temp_id_(std::move(other.temp_id_)),
+        fs_permission_(other.fs_permission_) {}
+
+  // Deletes the file if it is not committed.
+  virtual ~NewFile();
+
+  int Fd() const { return fd_; }
+
+  // The path that the file will eventually be committed to.
+  const std::string& FinalPath() const { return final_path_; }
+
+  // The path to the new file.
+  const std::string& TempPath() const { return temp_path_; }
+
+  // The unique ID of the new file. Can be used by `BuildTempPath` for reconstructing the path to
+  // the file.
+  const std::string& TempId() const { return temp_id_; }
+
+  // Closes the new file, keeps it, moves the file to the final path, and overwrites any existing
+  // file at that path, or abandons the file on failure. The fd will be invalid after this function
+  // is called.
+  android::base::Result<void> CommitOrAbandon();
+
+  // Closes the new file and keeps it at the temporary location. The file will not be automatically
+  // cleaned up on object destruction. The file can be found at `TempPath()` (i.e.,
+  // `BuildTempPath(FinalPath(), TempId())`). The fd will be invalid after this function is called.
+  virtual android::base::Result<void> Keep();
+
+  // Unlinks and closes the new file if it is not committed. The fd will be invalid after this
+  // function is called.
+  void Cleanup();
+
+  // Commits all new files, replacing old files, and removes given files in addition. Or abandons
+  // new files and restores old files at best effort if any error occurs. The fds will be invalid
+  // after this function is called.
+  //
+  // Note: This function is NOT thread-safe. It is intended to be used in single-threaded code or in
+  // cases where some race condition is acceptable.
+  //
+  // Usage:
+  //
+  // Commit `file_1` and `file_2`, and remove the file at "path_3":
+  //   CommitAllOrAbandon({file_1, file_2}, {"path_3"});
+  static android::base::Result<void> CommitAllOrAbandon(
+      const std::vector<NewFile*>& files_to_commit,
+      const std::vector<std::string_view>& files_to_remove = {});
+
+  // Returns the path to a temporary file. See `Keep`.
+  static std::string BuildTempPath(std::string_view final_path, const std::string& id);
+
+ private:
+  NewFile(const std::string& path,
+          const aidl::com::android::server::art::FsPermission& fs_permission)
+      : final_path_(path), fs_permission_(fs_permission) {}
+
+  android::base::Result<void> Init();
+
+  // Unlinks the new file. The fd will still be valid after this function is called.
+  void Unlink();
+
+  int fd_ = -1;
+  std::string final_path_;
+  std::string temp_path_;
+  std::string temp_id_;
+  aidl::com::android::server::art::FsPermission fs_permission_;
+  bool committed_ = false;
+};
+
+// Opens a file for reading.
+android::base::Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path);
+
+// Converts FsPermission to Linux access mode for a file.
+mode_t FileFsPermissionToMode(const aidl::com::android::server::art::FsPermission& fs_permission);
+
+// Converts FsPermission to Linux access mode for a directory.
+mode_t DirFsPermissionToMode(const aidl::com::android::server::art::FsPermission& fs_permission);
+
+// Changes the owner based on FsPermission.
+android::base::Result<void> Chown(
+    const std::string& path, const aidl::com::android::server::art::FsPermission& fs_permission);
+
+}  // namespace artd
+}  // namespace art
+
+#endif  // ART_ARTD_FILE_UTILS_H_
diff --git a/artd/file_utils_test.cc b/artd/file_utils_test.cc
new file mode 100644
index 0000000..8f79d5d
--- /dev/null
+++ b/artd/file_utils_test.cc
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "file_utils.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <memory>
+#include <string>
+
+#include "aidl/com/android/server/art/FsPermission.h"
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "android-base/result-gmock.h"
+#include "android-base/result.h"
+#include "base/common_art_test.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace artd {
+namespace {
+
+using ::aidl::com::android::server::art::FsPermission;
+using ::android::base::Error;
+using ::android::base::ReadFileToString;
+using ::android::base::Result;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+using ::android::base::testing::HasError;
+using ::android::base::testing::HasValue;
+using ::android::base::testing::Ok;
+using ::android::base::testing::WithMessage;
+using ::testing::ContainsRegex;
+using ::testing::IsEmpty;
+using ::testing::NotNull;
+
+void CheckContent(const std::string& path, const std::string& expected_content) {
+  std::string actual_content;
+  ASSERT_TRUE(ReadFileToString(path, &actual_content));
+  EXPECT_EQ(actual_content, expected_content);
+}
+
+// A file that will always fail on `Commit`.
+class UncommittableFile : public NewFile {
+ public:
+  static Result<std::unique_ptr<UncommittableFile>> Create(const std::string& path,
+                                                           const FsPermission& fs_permission) {
+    std::unique_ptr<NewFile> new_file = OR_RETURN(NewFile::Create(path, fs_permission));
+    return std::unique_ptr<UncommittableFile>(new UncommittableFile(std::move(*new_file)));
+  }
+
+  Result<void> Keep() override { return Error() << "Uncommittable file"; }
+
+ private:
+  explicit UncommittableFile(NewFile&& other) : NewFile(std::move(other)) {}
+};
+
+class FileUtilsTest : public CommonArtTest {
+ protected:
+  void SetUp() override {
+    CommonArtTest::SetUp();
+    scratch_dir_ = std::make_unique<ScratchDir>();
+    struct stat st;
+    ASSERT_EQ(stat(scratch_dir_->GetPath().c_str(), &st), 0);
+    fs_permission_ = FsPermission{.uid = static_cast<int32_t>(st.st_uid),
+                                  .gid = static_cast<int32_t>(st.st_gid)};
+  }
+
+  void TearDown() override {
+    scratch_dir_.reset();
+    CommonArtTest::TearDown();
+  }
+
+  FsPermission fs_permission_;
+  std::unique_ptr<ScratchDir> scratch_dir_;
+};
+
+TEST_F(FileUtilsTest, NewFileCreate) {
+  std::string path = scratch_dir_->GetPath() + "/file.tmp";
+
+  Result<std::unique_ptr<NewFile>> new_file = NewFile::Create(path, fs_permission_);
+  ASSERT_THAT(new_file, HasValue(NotNull()));
+  EXPECT_GE((*new_file)->Fd(), 0);
+  EXPECT_EQ((*new_file)->FinalPath(), path);
+  EXPECT_THAT((*new_file)->TempPath(), Not(IsEmpty()));
+  EXPECT_THAT((*new_file)->TempId(), Not(IsEmpty()));
+
+  EXPECT_FALSE(std::filesystem::exists((*new_file)->FinalPath()));
+  EXPECT_TRUE(std::filesystem::exists((*new_file)->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCreateNonExistentDir) {
+  std::string path = scratch_dir_->GetPath() + "/non_existent_dir/file.tmp";
+
+  EXPECT_THAT(NewFile::Create(path, fs_permission_),
+              HasError(WithMessage(
+                  ContainsRegex("Failed to create temp file for .*/non_existent_dir/file.tmp"))));
+}
+
+TEST_F(FileUtilsTest, NewFileExplicitCleanup) {
+  std::string path = scratch_dir_->GetPath() + "/file.tmp";
+  std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
+  new_file->Cleanup();
+
+  EXPECT_FALSE(std::filesystem::exists(path));
+  EXPECT_FALSE(std::filesystem::exists(new_file->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileImplicitCleanup) {
+  std::string path = scratch_dir_->GetPath() + "/file.tmp";
+  std::string temp_path;
+
+  // Cleanup on object destruction.
+  {
+    std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
+    temp_path = new_file->TempPath();
+  }
+
+  EXPECT_FALSE(std::filesystem::exists(path));
+  EXPECT_FALSE(std::filesystem::exists(temp_path));
+}
+
+TEST_F(FileUtilsTest, NewFileCommit) {
+  std::string path = scratch_dir_->GetPath() + "/file.tmp";
+  std::string temp_path;
+
+  {
+    std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
+    temp_path = new_file->TempPath();
+    new_file->CommitOrAbandon();
+  }
+
+  EXPECT_TRUE(std::filesystem::exists(path));
+  EXPECT_FALSE(std::filesystem::exists(temp_path));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllNoOldFile) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
+
+  // New files are committed.
+  CheckContent(file_1_path, "new_file_1");
+  CheckContent(file_2_path, "new_file_2");
+
+  // New files are no longer at the temporary paths.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllReplacesOldFiles) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+
+  ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
+
+  // New files are committed.
+  CheckContent(file_1_path, "new_file_1");
+  CheckContent(file_2_path, "new_file_2");
+
+  // New files are no longer at the temporary paths.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllReplacesLessOldFiles) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+
+  ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));  // No old_file_2.
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
+
+  // New files are committed.
+  CheckContent(file_1_path, "new_file_1");
+  CheckContent(file_2_path, "new_file_2");
+
+  // New files are no longer at the temporary paths.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllReplacesMoreOldFiles) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+  std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
+
+  ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path));  // Extra file.
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
+              Ok());
+
+  // New files are committed.
+  CheckContent(file_1_path, "new_file_1");
+  CheckContent(file_2_path, "new_file_2");
+  EXPECT_FALSE(std::filesystem::exists(file_3_path));  // Extra file removed.
+
+  // New files are no longer at the temporary paths.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllFailedToCommit) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+  std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
+
+  ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path));  // Extra file.
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  // Uncommittable file.
+  std::unique_ptr<NewFile> new_file_2 =
+      OR_FATAL(UncommittableFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
+              HasError(WithMessage("Uncommittable file")));
+
+  // Old files are fine.
+  CheckContent(file_1_path, "old_file_1");
+  CheckContent(file_2_path, "old_file_2");
+  CheckContent(file_3_path, "old_file_3");
+
+  // New files are abandoned.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllFailedToMoveOldFile) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+  std::filesystem::create_directory(file_2_path);
+  std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
+
+  ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path));  // Extra file.
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  // file_2 is not movable because it is a directory.
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
+              HasError(WithMessage(ContainsRegex("Old file '.*/file_2' is a directory"))));
+
+  // Old files are fine.
+  CheckContent(file_1_path, "old_file_1");
+  EXPECT_TRUE(std::filesystem::is_directory(file_2_path));
+  CheckContent(file_3_path, "old_file_3");
+
+  // New files are abandoned.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, BuildTempPath) {
+  EXPECT_EQ(NewFile::BuildTempPath("/a/b/original_path", "123456"),
+            "/a/b/original_path.123456.tmp");
+}
+
+TEST_F(FileUtilsTest, OpenFileForReading) {
+  std::string path = scratch_dir_->GetPath() + "/foo";
+  ASSERT_TRUE(WriteStringToFile("foo", path));
+
+  EXPECT_THAT(OpenFileForReading(path), HasValue(NotNull()));
+}
+
+TEST_F(FileUtilsTest, OpenFileForReadingFailed) {
+  std::string path = scratch_dir_->GetPath() + "/foo";
+
+  EXPECT_THAT(OpenFileForReading(path),
+              HasError(WithMessage(ContainsRegex("Failed to open file .*/foo"))));
+}
+
+TEST_F(FileUtilsTest, FileFsPermissionToMode) {
+  EXPECT_EQ(FileFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IRGRP);
+  EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherReadable = true}),
+            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+  EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherExecutable = true}),
+            S_IRUSR | S_IWUSR | S_IRGRP | S_IXOTH);
+  EXPECT_EQ(
+      FileFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}),
+      S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IXOTH);
+}
+
+TEST_F(FileUtilsTest, DirFsPermissionToMode) {
+  EXPECT_EQ(DirFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP);
+  EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true}),
+            S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH);
+  EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherExecutable = true}),
+            S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IXOTH);
+  EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}),
+            S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+}
+
+}  // namespace
+}  // namespace artd
+}  // namespace art
diff --git a/artd/path_utils.cc b/artd/path_utils.cc
new file mode 100644
index 0000000..6ef0eaf
--- /dev/null
+++ b/artd/path_utils.cc
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "path_utils.h"
+
+#include <filesystem>
+#include <string>
+#include <vector>
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/errors.h"
+#include "android-base/result.h"
+#include "android-base/strings.h"
+#include "arch/instruction_set.h"
+#include "base/file_utils.h"
+#include "file_utils.h"
+#include "fmt/format.h"
+#include "oat_file_assistant.h"
+#include "tools/tools.h"
+
+namespace art {
+namespace artd {
+
+namespace {
+
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::Error;
+using ::android::base::Result;
+
+using ::fmt::literals::operator""_format;  // NOLINT
+
+using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using SecondaryCurProfilePath = ProfilePath::SecondaryCurProfilePath;
+using SecondaryRefProfilePath = ProfilePath::SecondaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+using WritableProfilePath = ProfilePath::WritableProfilePath;
+
+Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) {
+  if (path_str.empty()) {
+    return Errorf("Path is empty");
+  }
+  if (path_str.find('\0') != std::string::npos) {
+    return Errorf("Path '{}' has invalid character '\\0'", path_str);
+  }
+  std::filesystem::path path(path_str);
+  if (!path.is_absolute()) {
+    return Errorf("Path '{}' is not an absolute path", path_str);
+  }
+  if (path.lexically_normal() != path_str) {
+    return Errorf("Path '{}' is not in normal form", path_str);
+  }
+  return {};
+}
+
+Result<void> ValidatePathElementSubstring(const std::string& path_element_substring,
+                                          const std::string& name) {
+  if (path_element_substring.empty()) {
+    return Errorf("{} is empty", name);
+  }
+  if (path_element_substring.find('/') != std::string::npos) {
+    return Errorf("{} '{}' has invalid character '/'", name, path_element_substring);
+  }
+  if (path_element_substring.find('\0') != std::string::npos) {
+    return Errorf("{} '{}' has invalid character '\\0'", name, path_element_substring);
+  }
+  return {};
+}
+
+Result<void> ValidatePathElement(const std::string& path_element, const std::string& name) {
+  OR_RETURN(ValidatePathElementSubstring(path_element, name));
+  if (path_element == "." || path_element == "..") {
+    return Errorf("Invalid {} '{}'", name, path_element);
+  }
+  return {};
+}
+
+Result<std::string> GetAndroidDataOrError() {
+  std::string error_msg;
+  std::string result = GetAndroidDataSafe(&error_msg);
+  if (!error_msg.empty()) {
+    return Error() << error_msg;
+  }
+  return result;
+}
+
+Result<std::string> GetAndroidExpandOrError() {
+  std::string error_msg;
+  std::string result = GetAndroidExpandSafe(&error_msg);
+  if (!error_msg.empty()) {
+    return Error() << error_msg;
+  }
+  return result;
+}
+
+Result<std::string> GetArtRootOrError() {
+  std::string error_msg;
+  std::string result = GetArtRootSafe(&error_msg);
+  if (!error_msg.empty()) {
+    return Error() << error_msg;
+  }
+  return result;
+}
+
+}  // namespace
+
+Result<std::vector<std::string>> ListManagedFiles() {
+  std::string android_data = OR_RETURN(GetAndroidDataOrError());
+  std::string android_expand = OR_RETURN(GetAndroidExpandOrError());
+
+  // See `art::tools::Glob` for the syntax.
+  std::vector<std::string> patterns = {
+      // Profiles for primary dex files.
+      android_data + "/misc/profiles/**",
+      // Artifacts for primary dex files.
+      android_data + "/dalvik-cache/**",
+  };
+
+  for (const std::string& data_root : {android_data, android_expand + "/*"}) {
+    // Artifacts for primary dex files.
+    patterns.push_back(data_root + "/app/*/*/oat/**");
+    // Profiles and artifacts for secondary dex files. Those files are in app data directories, so
+    // we use more granular patterns to avoid accidentally deleting apps' files.
+    for (const char* user_dir : {"/user", "/user_de"}) {
+      std::string secondary_oat_dir = data_root + user_dir + "/*/*/**/oat";
+      for (const char* maybe_tmp_suffix : {"", ".*.tmp"}) {
+        patterns.push_back(secondary_oat_dir + "/*.prof" + maybe_tmp_suffix);
+        patterns.push_back(secondary_oat_dir + "/*/*.odex" + maybe_tmp_suffix);
+        patterns.push_back(secondary_oat_dir + "/*/*.vdex" + maybe_tmp_suffix);
+        patterns.push_back(secondary_oat_dir + "/*/*.art" + maybe_tmp_suffix);
+      }
+    }
+  }
+
+  return tools::Glob(patterns);
+}
+
+Result<void> ValidateDexPath(const std::string& dex_path) {
+  OR_RETURN(ValidateAbsoluteNormalPath(dex_path));
+  return {};
+}
+
+Result<std::string> BuildArtBinPath(const std::string& binary_name) {
+  return "{}/bin/{}"_format(OR_RETURN(GetArtRootOrError()), binary_name);
+}
+
+Result<std::string> BuildOatPath(const ArtifactsPath& artifacts_path) {
+  OR_RETURN(ValidateDexPath(artifacts_path.dexPath));
+
+  InstructionSet isa = GetInstructionSetFromString(artifacts_path.isa.c_str());
+  if (isa == InstructionSet::kNone) {
+    return Errorf("Instruction set '{}' is invalid", artifacts_path.isa);
+  }
+
+  std::string error_msg;
+  std::string path;
+  if (artifacts_path.isInDalvikCache) {
+    // Apps' OAT files are never in ART APEX data.
+    if (!OatFileAssistant::DexLocationToOatFilename(
+            artifacts_path.dexPath, isa, /*deny_art_apex_data_files=*/true, &path, &error_msg)) {
+      return Error() << error_msg;
+    }
+    return path;
+  } else {
+    if (!OatFileAssistant::DexLocationToOdexFilename(
+            artifacts_path.dexPath, isa, &path, &error_msg)) {
+      return Error() << error_msg;
+    }
+    return path;
+  }
+}
+
+Result<std::string> BuildPrimaryRefProfilePath(
+    const PrimaryRefProfilePath& primary_ref_profile_path) {
+  OR_RETURN(ValidatePathElement(primary_ref_profile_path.packageName, "packageName"));
+  OR_RETURN(ValidatePathElementSubstring(primary_ref_profile_path.profileName, "profileName"));
+  return "{}/misc/profiles/ref/{}/{}.prof"_format(OR_RETURN(GetAndroidDataOrError()),
+                                                  primary_ref_profile_path.packageName,
+                                                  primary_ref_profile_path.profileName);
+}
+
+Result<std::string> BuildPrebuiltProfilePath(const PrebuiltProfilePath& prebuilt_profile_path) {
+  OR_RETURN(ValidateDexPath(prebuilt_profile_path.dexPath));
+  return prebuilt_profile_path.dexPath + ".prof";
+}
+
+Result<std::string> BuildPrimaryCurProfilePath(
+    const PrimaryCurProfilePath& primary_cur_profile_path) {
+  OR_RETURN(ValidatePathElement(primary_cur_profile_path.packageName, "packageName"));
+  OR_RETURN(ValidatePathElementSubstring(primary_cur_profile_path.profileName, "profileName"));
+  return "{}/misc/profiles/cur/{}/{}/{}.prof"_format(OR_RETURN(GetAndroidDataOrError()),
+                                                     primary_cur_profile_path.userId,
+                                                     primary_cur_profile_path.packageName,
+                                                     primary_cur_profile_path.profileName);
+}
+
+Result<std::string> BuildSecondaryRefProfilePath(
+    const SecondaryRefProfilePath& secondary_ref_profile_path) {
+  OR_RETURN(ValidateDexPath(secondary_ref_profile_path.dexPath));
+  std::filesystem::path dex_path(secondary_ref_profile_path.dexPath);
+  return "{}/oat/{}.prof"_format(dex_path.parent_path().string(), dex_path.filename().string());
+}
+
+Result<std::string> BuildSecondaryCurProfilePath(
+    const SecondaryCurProfilePath& secondary_cur_profile_path) {
+  OR_RETURN(ValidateDexPath(secondary_cur_profile_path.dexPath));
+  std::filesystem::path dex_path(secondary_cur_profile_path.dexPath);
+  return "{}/oat/{}.cur.prof"_format(dex_path.parent_path().string(), dex_path.filename().string());
+}
+
+Result<std::string> BuildFinalProfilePath(const TmpProfilePath& tmp_profile_path) {
+  const WritableProfilePath& final_path = tmp_profile_path.finalPath;
+  switch (final_path.getTag()) {
+    case WritableProfilePath::forPrimary:
+      return BuildPrimaryRefProfilePath(final_path.get<WritableProfilePath::forPrimary>());
+    case WritableProfilePath::forSecondary:
+      return BuildSecondaryRefProfilePath(final_path.get<WritableProfilePath::forSecondary>());
+      // No default. All cases should be explicitly handled, or the compilation will fail.
+  }
+  // This should never happen. Just in case we get a non-enumerator value.
+  LOG(FATAL) << "Unexpected writable profile path type {}"_format(final_path.getTag());
+}
+
+Result<std::string> BuildTmpProfilePath(const TmpProfilePath& tmp_profile_path) {
+  OR_RETURN(ValidatePathElementSubstring(tmp_profile_path.id, "id"));
+  return NewFile::BuildTempPath(OR_RETURN(BuildFinalProfilePath(tmp_profile_path)),
+                                tmp_profile_path.id);
+}
+
+Result<std::string> BuildDexMetadataPath(const DexMetadataPath& dex_metadata_path) {
+  OR_RETURN(ValidateDexPath(dex_metadata_path.dexPath));
+  return ReplaceFileExtension(dex_metadata_path.dexPath, "dm");
+}
+
+Result<std::string> BuildProfileOrDmPath(const ProfilePath& profile_path) {
+  switch (profile_path.getTag()) {
+    case ProfilePath::primaryRefProfilePath:
+      return BuildPrimaryRefProfilePath(profile_path.get<ProfilePath::primaryRefProfilePath>());
+    case ProfilePath::prebuiltProfilePath:
+      return BuildPrebuiltProfilePath(profile_path.get<ProfilePath::prebuiltProfilePath>());
+    case ProfilePath::primaryCurProfilePath:
+      return BuildPrimaryCurProfilePath(profile_path.get<ProfilePath::primaryCurProfilePath>());
+    case ProfilePath::secondaryRefProfilePath:
+      return BuildSecondaryRefProfilePath(profile_path.get<ProfilePath::secondaryRefProfilePath>());
+    case ProfilePath::secondaryCurProfilePath:
+      return BuildSecondaryCurProfilePath(profile_path.get<ProfilePath::secondaryCurProfilePath>());
+    case ProfilePath::tmpProfilePath:
+      return BuildTmpProfilePath(profile_path.get<ProfilePath::tmpProfilePath>());
+    case ProfilePath::dexMetadataPath:
+      return BuildDexMetadataPath(profile_path.get<ProfilePath::dexMetadataPath>());
+      // No default. All cases should be explicitly handled, or the compilation will fail.
+  }
+  // This should never happen. Just in case we get a non-enumerator value.
+  LOG(FATAL) << "Unexpected profile path type {}"_format(profile_path.getTag());
+}
+
+Result<std::string> BuildVdexPath(const VdexPath& vdex_path) {
+  DCHECK(vdex_path.getTag() == VdexPath::artifactsPath);
+  return OatPathToVdexPath(OR_RETURN(BuildOatPath(vdex_path.get<VdexPath::artifactsPath>())));
+}
+
+}  // namespace artd
+}  // namespace art
diff --git a/artd/path_utils.h b/artd/path_utils.h
new file mode 100644
index 0000000..1063f91
--- /dev/null
+++ b/artd/path_utils.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 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_ARTD_PATH_UTILS_H_
+#define ART_ARTD_PATH_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/result.h"
+#include "base/file_utils.h"
+
+namespace art {
+namespace artd {
+
+// Returns all existing files that are managed by artd.
+android::base::Result<std::vector<std::string>> ListManagedFiles();
+
+android::base::Result<void> ValidateDexPath(const std::string& dex_path);
+
+android::base::Result<std::string> BuildArtBinPath(const std::string& binary_name);
+
+// Returns the absolute path to the OAT file built from the `ArtifactsPath`.
+android::base::Result<std::string> BuildOatPath(
+    const aidl::com::android::server::art::ArtifactsPath& artifacts_path);
+
+// Returns the path to the VDEX file that corresponds to the OAT file.
+inline std::string OatPathToVdexPath(const std::string& oat_path) {
+  return ReplaceFileExtension(oat_path, "vdex");
+}
+
+// Returns the path to the ART file that corresponds to the OAT file.
+inline std::string OatPathToArtPath(const std::string& oat_path) {
+  return ReplaceFileExtension(oat_path, "art");
+}
+
+android::base::Result<std::string> BuildPrimaryRefProfilePath(
+    const aidl::com::android::server::art::ProfilePath::PrimaryRefProfilePath&
+        primary_ref_profile_path);
+
+android::base::Result<std::string> BuildPrebuiltProfilePath(
+    const aidl::com::android::server::art::ProfilePath::PrebuiltProfilePath& prebuilt_profile_path);
+
+android::base::Result<std::string> BuildPrimaryCurProfilePath(
+    const aidl::com::android::server::art::ProfilePath::PrimaryCurProfilePath&
+        primary_cur_profile_path);
+
+android::base::Result<std::string> BuildSecondaryRefProfilePath(
+    const aidl::com::android::server::art::ProfilePath::SecondaryRefProfilePath&
+        secondary_ref_profile_path);
+
+android::base::Result<std::string> BuildSecondaryCurProfilePath(
+    const aidl::com::android::server::art::ProfilePath::SecondaryCurProfilePath&
+        secondary_cur_profile_path);
+
+android::base::Result<std::string> BuildFinalProfilePath(
+    const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path);
+
+android::base::Result<std::string> BuildTmpProfilePath(
+    const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path);
+
+android::base::Result<std::string> BuildDexMetadataPath(
+    const aidl::com::android::server::art::DexMetadataPath& dex_metadata_path);
+
+android::base::Result<std::string> BuildProfileOrDmPath(
+    const aidl::com::android::server::art::ProfilePath& profile_path);
+
+android::base::Result<std::string> BuildVdexPath(
+    const aidl::com::android::server::art::VdexPath& vdex_path);
+
+}  // namespace artd
+}  // namespace art
+
+#endif  // ART_ARTD_PATH_UTILS_H_
diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc
new file mode 100644
index 0000000..b0cc827
--- /dev/null
+++ b/artd/path_utils_test.cc
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "path_utils.h"
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/result-gmock.h"
+#include "base/common_art_test.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace artd {
+namespace {
+
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::testing::HasError;
+using ::android::base::testing::HasValue;
+using ::android::base::testing::WithMessage;
+
+using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using SecondaryCurProfilePath = ProfilePath::SecondaryCurProfilePath;
+using SecondaryRefProfilePath = ProfilePath::SecondaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+
+using std::literals::operator""s;  // NOLINT
+
+class PathUtilsTest : public CommonArtTest {};
+
+TEST_F(PathUtilsTest, BuildArtBinPath) {
+  auto scratch_dir = std::make_unique<ScratchDir>();
+  auto art_root_env = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT");
+  setenv("ANDROID_ART_ROOT", scratch_dir->GetPath().c_str(), /*overwrite=*/1);
+  EXPECT_THAT(BuildArtBinPath("foo"), HasValue(scratch_dir->GetPath() + "/bin/foo"));
+}
+
+TEST_F(PathUtilsTest, BuildOatPath) {
+  EXPECT_THAT(
+      BuildOatPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}),
+      HasValue("/a/oat/arm64/b.odex"));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathDalvikCache) {
+  EXPECT_THAT(
+      BuildOatPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = true}),
+      HasValue(android_data_ + "/dalvik-cache/arm64/[email protected]@classes.dex"));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathEmptyDexPath) {
+  EXPECT_THAT(BuildOatPath(ArtifactsPath{.dexPath = "", .isa = "arm64", .isInDalvikCache = false}),
+              HasError(WithMessage("Path is empty")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathRelativeDexPath) {
+  EXPECT_THAT(
+      BuildOatPath(ArtifactsPath{.dexPath = "a/b.apk", .isa = "arm64", .isInDalvikCache = false}),
+      HasError(WithMessage("Path 'a/b.apk' is not an absolute path")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathNonNormalDexPath) {
+  EXPECT_THAT(BuildOatPath(ArtifactsPath{
+                  .dexPath = "/a/c/../b.apk", .isa = "arm64", .isInDalvikCache = false}),
+              HasError(WithMessage("Path '/a/c/../b.apk' is not in normal form")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathNul) {
+  EXPECT_THAT(BuildOatPath(ArtifactsPath{
+                  .dexPath = "/a/\0/b.apk"s, .isa = "arm64", .isInDalvikCache = false}),
+              HasError(WithMessage("Path '/a/\0/b.apk' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathInvalidIsa) {
+  EXPECT_THAT(BuildOatPath(
+                  ArtifactsPath{.dexPath = "/a/b.apk", .isa = "invalid", .isInDalvikCache = false}),
+              HasError(WithMessage("Instruction set 'invalid' is invalid")));
+}
+
+TEST_F(PathUtilsTest, OatPathToVdexPath) {
+  EXPECT_EQ(OatPathToVdexPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.vdex");
+}
+
+TEST_F(PathUtilsTest, OatPathToArtPath) {
+  EXPECT_EQ(OatPathToArtPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.art");
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePath) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                               .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameOk) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "...", .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/ref/.../primary.prof"));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "!@#$%^&*()_+-=", .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/ref/!@#$%^&*()_+-=/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameWrong) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "", .profileName = "primary"}),
+              HasError(WithMessage("packageName is empty")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = ".", .profileName = "primary"}),
+              HasError(WithMessage("Invalid packageName '.'")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "..", .profileName = "primary"}),
+              HasError(WithMessage("Invalid packageName '..'")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "a/b", .profileName = "primary"}),
+              HasError(WithMessage("packageName 'a/b' has invalid character '/'")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "a\0b"s, .profileName = "primary"}),
+              HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameOk) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "."}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/..prof"));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ".."}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/...prof"));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                               .profileName = "!@#$%^&*()_+-="}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/!@#$%^&*()_+-=.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameWrong) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ""}),
+              HasError(WithMessage("profileName is empty")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a/b"}),
+              HasError(WithMessage("profileName 'a/b' has invalid character '/'")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a\0b"s}),
+              HasError(WithMessage("profileName 'a\0b' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildFinalProfilePathForPrimary) {
+  EXPECT_THAT(BuildFinalProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
+                  .id = "12345"}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildFinalProfilePathForSecondary) {
+  EXPECT_THAT(BuildFinalProfilePath(TmpProfilePath{
+                  .finalPath = SecondaryRefProfilePath{.dexPath = android_data_ +
+                                                                  "/user/0/com.android.foo/a.apk"},
+                  .id = "12345"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathForPrimary) {
+  EXPECT_THAT(
+      BuildTmpProfilePath(TmpProfilePath{
+          .finalPath =
+              PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+          .id = "12345"}),
+      HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathForSecondary) {
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = SecondaryRefProfilePath{.dexPath = android_data_ +
+                                                                  "/user/0/com.android.foo/a.apk"},
+                  .id = "12345"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof.12345.tmp"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathIdWrong) {
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
+                  .id = ""}),
+              HasError(WithMessage("id is empty")));
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
+                  .id = "123/45"}),
+              HasError(WithMessage("id '123/45' has invalid character '/'")));
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
+                  .id = "123\0a"s}),
+              HasError(WithMessage("id '123\0a' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildPrebuiltProfilePath) {
+  EXPECT_THAT(BuildPrebuiltProfilePath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}),
+              HasValue("/a/b.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryCurProfilePath) {
+  EXPECT_THAT(BuildPrimaryCurProfilePath(PrimaryCurProfilePath{
+                  .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildSecondaryRefProfilePath) {
+  EXPECT_THAT(BuildSecondaryRefProfilePath(SecondaryRefProfilePath{
+                  .dexPath = android_data_ + "/user/0/com.android.foo/a.apk"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildSecondaryCurProfilePath) {
+  EXPECT_THAT(BuildSecondaryCurProfilePath(SecondaryCurProfilePath{
+                  .dexPath = android_data_ + "/user/0/com.android.foo/a.apk"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.cur.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildDexMetadataPath) {
+  EXPECT_THAT(BuildDexMetadataPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm"));
+}
+
+TEST_F(PathUtilsTest, BuildProfilePath) {
+  EXPECT_THAT(BuildProfileOrDmPath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                         .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+  EXPECT_THAT(
+      BuildProfileOrDmPath(TmpProfilePath{
+          .finalPath =
+              PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+          .id = "12345"}),
+      HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"));
+  EXPECT_THAT(BuildProfileOrDmPath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}),
+              HasValue("/a/b.apk.prof"));
+  EXPECT_THAT(BuildProfileOrDmPath(PrimaryCurProfilePath{
+                  .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof"));
+  EXPECT_THAT(BuildProfileOrDmPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm"));
+}
+
+TEST_F(PathUtilsTest, BuildVdexPath) {
+  EXPECT_THAT(
+      BuildVdexPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}),
+      HasValue("/a/oat/arm64/b.vdex"));
+}
+
+}  // namespace
+}  // namespace artd
+}  // namespace art
diff --git a/artd/testing.h b/artd/testing.h
new file mode 100644
index 0000000..df01a9a
--- /dev/null
+++ b/artd/testing.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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_ARTD_TESTING_H_
+#define ART_ARTD_TESTING_H_
+
+// Returns the value of the given `android::base::Result`, or reports the error as a gMock matcher
+// mismatch. This is only to be used in a gMock matcher.
+#define OR_MISMATCH(expr)                          \
+  ({                                               \
+    decltype(expr)&& tmp__ = (expr);               \
+    if (!tmp__.ok()) {                             \
+      *result_listener << tmp__.error().message(); \
+      return false;                                \
+    }                                              \
+    std::move(tmp__).value();                      \
+  })
+
+// Returns the value of the given `android::base::Result`, or fails the GoogleTest.
+#define OR_FAIL(expr)                                   \
+  ({                                                    \
+    decltype(expr)&& tmp__ = (expr);                    \
+    ASSERT_TRUE(tmp__.ok()) << tmp__.error().message(); \
+    std::move(tmp__).value();                           \
+  })
+
+#endif  // ART_ARTD_TESTING_H_
diff --git a/artd/tests/src/com/android/art/ArtdIntegrationTests.java b/artd/tests/src/com/android/art/ArtdIntegrationTests.java
index 7d40adb..2a32972 100644
--- a/artd/tests/src/com/android/art/ArtdIntegrationTests.java
+++ b/artd/tests/src/com/android/art/ArtdIntegrationTests.java
@@ -16,11 +16,12 @@
 
 package com.android.art;
 
-import android.os.IArtd;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
+import com.android.server.art.IArtd;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/benchmark/Android.bp b/benchmark/Android.bp
index d781f84..c7f9f6b 100644
--- a/benchmark/Android.bp
+++ b/benchmark/Android.bp
@@ -51,6 +51,7 @@
     // TODO(ngeoffray): find a way to link against the libraries in the apex.
     shared_libs: [
         "libart",
+        "libartbase",
         "libbase",
     ],
 }
@@ -82,3 +83,30 @@
         },
     },
 }
+
+art_cc_library {
+    name: "libgolemtiagent",
+    host_supported: true,
+    defaults: ["art_defaults"],
+    srcs: [
+        "golem-tiagent/golem-tiagent.cc",
+    ],
+    target: {
+        // This has to be duplicated for android and host to make sure it
+        // comes after the -Wframe-larger-than warnings inserted by art.go
+        // target-specific properties
+        android: {
+            cflags: ["-Wno-frame-larger-than="],
+        },
+        host: {
+            cflags: ["-Wno-frame-larger-than="],
+        },
+    },
+    header_libs: [
+        "libnativehelper_header_only",
+	"libopenjdkjvmti_headers"
+    ],
+    shared_libs: [
+     "libbase",
+    ],
+}
diff --git a/benchmark/golem-tiagent/golem-tiagent.cc b/benchmark/golem-tiagent/golem-tiagent.cc
new file mode 100644
index 0000000..be2c727
--- /dev/null
+++ b/benchmark/golem-tiagent/golem-tiagent.cc
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "android-base/macros.h"
+#include "android-base/logging.h"
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace art {
+
+jvmtiEnv* jvmti_env = nullptr;
+
+void CheckJvmtiError(jvmtiEnv* env, jvmtiError error) {
+  if (error != JVMTI_ERROR_NONE) {
+    char* error_name;
+    jvmtiError name_error = env->GetErrorName(error, &error_name);
+    if (name_error != JVMTI_ERROR_NONE) {
+      LOG(FATAL) << "Unable to get error name for " << error;
+    }
+    LOG(FATAL) << "Unexpected error: " << error_name;
+  }
+}
+
+static void JNICALL VMInitCallback(jvmtiEnv *jenv ATTRIBUTE_UNUSED,
+                                   JNIEnv* jni_env,
+                                   jthread thread ATTRIBUTE_UNUSED) {
+  // Set a breakpoint on a rare method that we won't expect to be hit.
+  // java.lang.Thread.stop is deprecated and not expected to be used.
+  jclass cl = jni_env->FindClass("java/lang/Thread");
+  if (cl == nullptr) {
+    LOG(FATAL) << "Cannot find class java/lang/Thread to set a breakpoint";
+  }
+
+  jmethodID method = jni_env->GetMethodID(cl, "stop", "()V");
+  if (method == nullptr) {
+    LOG(FATAL) << "Cannot find method to set a breapoint";
+  }
+
+  jlong start = 0;
+  jlong end;
+  CheckJvmtiError(jvmti_env, jvmti_env->GetMethodLocation(method, &start, &end));
+  CheckJvmtiError(jvmti_env, jvmti_env->SetBreakpoint(method, start));
+}
+
+extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm,
+                                               char* options ATTRIBUTE_UNUSED,
+                                               void* reserved ATTRIBUTE_UNUSED) {
+  // Setup jvmti_env
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0) != 0) {
+    LOG(ERROR) << "Unable to get jvmti env!";
+    return 1;
+  }
+
+  // Enable breakpoint capability
+  jvmtiCapabilities capabilities;
+  memset(&capabilities, 0, sizeof(capabilities));
+  capabilities.can_generate_breakpoint_events = 1;
+  CheckJvmtiError(jvmti_env, jvmti_env->AddCapabilities(&capabilities));
+
+  // Set a callback for VM_INIT phase so we can set a breakpoint. We cannot just
+  // set a breakpoint here since vm isn't fully initialized here.
+  jvmtiEventCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+  callbacks.VMInit = VMInitCallback;
+  CheckJvmtiError(jvmti_env, jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)));
+  CheckJvmtiError(jvmti_env,
+                  jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, nullptr));
+
+  return 0;
+}
+
+}  // namespace art
diff --git a/benchmark/stringbuilder-append/src/StringBuilderAppendBenchmark.java b/benchmark/stringbuilder-append/src/StringBuilderAppendBenchmark.java
index 1550e81..d710e34 100644
--- a/benchmark/stringbuilder-append/src/StringBuilderAppendBenchmark.java
+++ b/benchmark/stringbuilder-append/src/StringBuilderAppendBenchmark.java
@@ -20,6 +20,10 @@
     public static String longString1 = "This is a long string 1";
     public static String longString2 = "This is a long string 2";
     public static int int1 = 42;
+    public static double double1 = 42.0;
+    public static double double2 = 1.0E308;
+    public static float float1 = 42.0f;
+    public static float float2 = 1.0E38f;
 
     public void timeAppendStrings(int count) {
         String s1 = string1;
@@ -59,4 +63,74 @@
             throw new AssertionError();
         }
     }
+
+    public void timeAppendStringAndDouble(int count) {
+        String s1 = string1;
+        double d1 = double1;
+        int sum = 0;
+        for (int i = 0; i < count; ++i) {
+            String result = s1 + d1;
+            sum += result.length();  // Make sure the append is not optimized away.
+        }
+        if (sum != count * (s1.length() + Double.toString(d1).length())) {
+            throw new AssertionError();
+        }
+    }
+
+    public void timeAppendStringAndHugeDouble(int count) {
+        String s1 = string1;
+        double d2 = double2;
+        int sum = 0;
+        for (int i = 0; i < count; ++i) {
+            String result = s1 + d2;
+            sum += result.length();  // Make sure the append is not optimized away.
+        }
+        if (sum != count * (s1.length() + Double.toString(d2).length())) {
+            throw new AssertionError();
+        }
+    }
+
+    public void timeAppendStringAndFloat(int count) {
+        String s1 = string1;
+        float f1 = float1;
+        int sum = 0;
+        for (int i = 0; i < count; ++i) {
+            String result = s1 + f1;
+            sum += result.length();  // Make sure the append is not optimized away.
+        }
+        if (sum != count * (s1.length() + Float.toString(f1).length())) {
+            throw new AssertionError();
+        }
+    }
+
+    public void timeAppendStringAndHugeFloat(int count) {
+        String s1 = string1;
+        float f2 = float2;
+        int sum = 0;
+        for (int i = 0; i < count; ++i) {
+            String result = s1 + f2;
+            sum += result.length();  // Make sure the append is not optimized away.
+        }
+        if (sum != count * (s1.length() + Float.toString(f2).length())) {
+            throw new AssertionError();
+        }
+    }
+
+    public void timeAppendStringDoubleStringAndFloat(int count) {
+        String s1 = string1;
+        String s2 = string2;
+        double d1 = double1;
+        float f1 = float1;
+        int sum = 0;
+        for (int i = 0; i < count; ++i) {
+            String result = s1 + d1 + s2 + f1;
+            sum += result.length();  // Make sure the append is not optimized away.
+        }
+        if (sum != count * (s1.length() +
+                            Double.toString(d1).length() +
+                            s2.length() +
+                            Float.toString(f1).length())) {
+            throw new AssertionError();
+        }
+    }
 }
diff --git a/build/Android.bp b/build/Android.bp
index d2545a4..77f7313 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -28,28 +28,28 @@
 }
 
 art_clang_tidy_errors = [
-    "android-cloexec-open",
-    "bugprone-lambda-function-name",
-    "bugprone-unused-raii", // Protect scoped things like MutexLock.
-    "bugprone-virtual-near-miss",
-    "modernize-use-bool-literals",
-    "performance-implicit-conversion-in-loop",
-    "performance-unnecessary-copy-initialization",
-]
-
-art_clang_tidy_allowed = [
-    // Many files have these warnings. Move them to art_clang_tidy_errors
-    // when all files are free of these warnings.
     "android-cloexec-dup",
+    "android-cloexec-fopen",
+    "android-cloexec-open",
     "bugprone-argument-comment",
+    "bugprone-lambda-function-name",
+    "bugprone-macro-parentheses",
+    "bugprone-unused-raii", // Protect scoped things like MutexLock.
     "bugprone-unused-return-value",
+    "bugprone-virtual-near-miss",
     "misc-unused-using-decls",
+    "modernize-use-bool-literals",
     "modernize-use-nullptr",
-    "modernize-use-using",
     "performance-faster-string-find",
     "performance-for-range-copy",
+    "performance-implicit-conversion-in-loop",
+    "performance-inefficient-vector-operation",
+    "performance-no-automatic-move",
     "performance-noexcept-move-constructor",
+    "performance-unnecessary-copy-initialization",
     "performance-unnecessary-value-param",
+    "readability-duplicate-include",
+    "readability-redundant-string-cstr",
 ]
 
 art_clang_tidy_disabled = [
@@ -66,6 +66,9 @@
     "-modernize-return-braced-init-list",
     "-modernize-use-default-member-init",
     "-modernize-pass-by-value",
+    // The only two remaining offenders are art/openjdkjvmti/include/jvmti.h and
+    // libcore/ojluni/src/main/native/jvm.h, which are both external files by Oracle
+    "-modernize-use-using",
 ]
 
 soong_config_module_type_import {
@@ -185,6 +188,10 @@
                 // Apple, it's a pain.
                 "-Wmissing-noreturn",
             ],
+            // Don't export symbols of statically linked libziparchive.
+            // Workaround to fix ODR violations (b/264235288) in gtests.
+            // `--exclude-libs` flag is not supported on windows/darwin.
+            ldflags: ["-Wl,--exclude-libs=libziparchive.a"],
         },
         linux_bionic: {
             strip: {
@@ -238,6 +245,9 @@
         arm64: {
             cflags: ["-DART_ENABLE_CODEGEN_arm64"],
         },
+        riscv64: {
+            cflags: ["-DART_ENABLE_CODEGEN_riscv64"],
+        },
         x86: {
             cflags: ["-DART_ENABLE_CODEGEN_x86"],
         },
@@ -246,7 +256,7 @@
         },
     },
 
-    tidy_checks: art_clang_tidy_errors + art_clang_tidy_allowed + art_clang_tidy_disabled,
+    tidy_checks: art_clang_tidy_errors + art_clang_tidy_disabled,
 
     tidy_checks_as_errors: art_clang_tidy_errors,
 
@@ -275,6 +285,9 @@
         arm64: {
             ldflags: ["-z max-page-size=0x200000"],
         },
+        riscv64: {
+            ldflags: ["-z max-page-size=0x200000"],
+        },
         x86_64: {
             ldflags: ["-z max-page-size=0x200000"],
         },
diff --git a/build/Android.common.mk b/build/Android.common.mk
index 9ca4d0f..966f99d 100644
--- a/build/Android.common.mk
+++ b/build/Android.common.mk
@@ -17,7 +17,7 @@
 ifndef ART_ANDROID_COMMON_MK
 ART_ANDROID_COMMON_MK = true
 
-ART_TARGET_SUPPORTED_ARCH := arm arm64 x86 x86_64
+ART_TARGET_SUPPORTED_ARCH := arm arm64 riscv64 x86 x86_64
 ART_HOST_SUPPORTED_ARCH := x86 x86_64
 ART_DEXPREOPT_BOOT_JAR_DIR := apex/com.android.art/javalib
 CONSCRYPT_DEXPREOPT_BOOT_JAR_DIR := apex/com.android.conscrypt/javalib
diff --git a/build/Android.common_path.mk b/build/Android.common_path.mk
index 6231b86..cfc6089 100644
--- a/build/Android.common_path.mk
+++ b/build/Android.common_path.mk
@@ -67,7 +67,7 @@
 # `dexpreopt_bootjars.go` uses a single source of input regardless of variants, so we should use the
 # same source for `CORE_IMG_JARS` to avoid checksum mismatches on the oat files.
 HOST_BOOT_IMAGE_JARS := $(foreach jar,$(CORE_IMG_JARS),$(HOST_OUT)/apex/com.android.art/javalib/$(jar).jar)
-CORE_IMG_JAR_DIR := $(OUT_DIR)/soong/$(PRODUCT_DEVICE)/dex_artjars_input
+CORE_IMG_JAR_DIR := $(OUT_DIR)/soong/dexpreopt_$(TARGET_ARCH)/dex_artjars_input
 $(HOST_BOOT_IMAGE_JARS): $(HOST_OUT)/apex/com.android.art/javalib/%.jar : $(CORE_IMG_JAR_DIR)/%.jar
 	$(copy-file-to-target)
 
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index def3190..770e988 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -27,7 +27,7 @@
 
 # Manually add system libraries that we need to run the host ART tools.
 my_files += \
-  $(foreach lib, libbacktrace libbase libc++ libicu libicu_jni liblog libsigchain libunwindstack \
+  $(foreach lib, libbase libc++ libicu libicu_jni liblog libsigchain libunwindstack \
     libziparchive libjavacore libandroidio libopenjdkd liblz4 liblzma, \
     $(call intermediates-dir-for,SHARED_LIBRARIES,$(lib),HOST)/$(lib).so:lib64/$(lib).so \
     $(call intermediates-dir-for,SHARED_LIBRARIES,$(lib),HOST,,2ND)/$(lib).so:lib/$(lib).so) \
@@ -45,7 +45,7 @@
 # that is used in host gtests, and hence can't lead to checksum mismatches.
 my_files += \
   $(foreach jar,$(CORE_IMG_JARS),\
-    $(OUT_DIR)/soong/$(PRODUCT_DEVICE)/dex_artjars_input/$(jar).jar:apex/com.android.art/javalib/$(jar).jar) \
+    $(OUT_DIR)/soong/dexpreopt_$(TARGET_ARCH)/dex_artjars_input/$(jar).jar:apex/com.android.art/javalib/$(jar).jar) \
   $(HOST_OUT_JAVA_LIBRARIES)/conscrypt-hostdex.jar:apex/com.android.conscrypt/javalib/conscrypt.jar\
   $(HOST_OUT_JAVA_LIBRARIES)/core-icu4j-hostdex.jar:apex/com.android.i18n/javalib/core-icu4j.jar \
   $(icu_data_file):com.android.i18n/etc/icu/$(notdir $(icu_data_file))
@@ -107,13 +107,13 @@
     art_cmdline_tests \
     art_compiler_host_tests \
     art_compiler_tests \
-    art_dex2oat_tests \
     art_dexanalyze_tests \
     art_dexdiag_tests \
     art_dexdump_tests \
     art_dexlayout_tests \
     art_dexlist_tests \
     art_dexoptanalyzer_tests \
+    art_disassembler_tests \
     art_hiddenapi_tests \
     art_imgdiag_tests \
     art_libartbase_tests \
@@ -127,13 +127,28 @@
     art_libprofile_tests \
     art_oatdump_tests \
     art_profman_tests \
-    art_runtime_compiler_tests \
     art_runtime_tests \
-    art_sigchain_tests \
 
-ART_TEST_MODULES_TARGET := $(ART_TEST_MODULES_COMMON) art_odrefresh_tests
+# b/258770641 Temporarily disable sigchain and dex2oat tests on ASAN configuration while we
+# investigate the failures.
+ifeq (,$(SANITIZE_HOST))
+  ART_TEST_MODULES_COMMON += art_sigchain_tests
+  ART_TEST_MODULES_COMMON += art_dex2oat_tests
+endif
+
+ART_TEST_MODULES_TARGET := $(ART_TEST_MODULES_COMMON) \
+    art_artd_tests \
+    art_odrefresh_tests \
+
 ART_TEST_MODULES_HOST := $(ART_TEST_MODULES_COMMON)
 
+ifneq (,$(wildcard frameworks/native/libs/binder))
+  # Only include the artd host tests if we have the binder sources available and
+  # can build the libbinder_ndk dependency. It is not available as a prebuilt on
+  # master-art.
+  ART_TEST_MODULES_HOST += art_artd_tests
+endif
+
 ART_TARGET_GTEST_NAMES := $(foreach tm,$(ART_TEST_MODULES_TARGET),\
   $(foreach path,$(ART_TEST_LIST_device_$(TARGET_ARCH)_$(tm)),\
     $(notdir $(path))\
diff --git a/build/README.md b/build/README.md
index 22726ee..a94d0fc 100644
--- a/build/README.md
+++ b/build/README.md
@@ -31,13 +31,13 @@
 
     See the [Android source access
     instructions](https://source.android.com/setup/build/downloading) for
-    further details.
+    further details. Google internal users please see [go/sync](http://go/sync).
 
 2.  Set up the development environment:
 
     ```
     banchan com.android.art <arch>
-    export SOONG_ALLOW_MISSING_DEPENDENCIES=true
+    export SOONG_ALLOW_MISSING_DEPENDENCIES=true BUILD_BROKEN_DISABLE_BAZEL=true
     ```
 
     For Google internal builds on the internal master-art branch, specify
@@ -45,7 +45,7 @@
 
     ```
     banchan com.google.android.art mainline_modules_<arch>
-    export SOONG_ALLOW_MISSING_DEPENDENCIES=true
+    export SOONG_ALLOW_MISSING_DEPENDENCIES=true BUILD_BROKEN_DISABLE_BAZEL=true
     ```
 
     `<arch>` is the device architecture, one of `arm`, `arm64`, `x86`, or
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index fb071f1..d5349b6 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -59,7 +59,6 @@
     // TODO(b/124476339): Clean up the following libraries once "required"
     // dependencies work with APEX libraries.
     "libart-compiler",
-    "libartservice",
     "libdt_fd_forward",
     "libdt_socket",
     "libjdwp",
@@ -167,6 +166,8 @@
 ]
 
 // Core Java libraries.
+// This list must be the same as art-bootclasspath-fragment because it's that which is pulled in
+// through bootclasspath_fragments below. (com.android.art-device-defaults-minus-odrefresh)
 libcore_java_libs = [
     "core-oj",
     "core-libart",
@@ -251,6 +252,13 @@
     },
 }
 
+prebuilt_etc {
+    name: "com.android.art.init.rc",
+    src: "art.rc",
+    filename: "init.rc",
+    installable: false,
+}
+
 // Default values shared by device ART APEXes.
 apex_defaults {
     name: "com.android.art-device-defaults-minus-odrefresh",
@@ -263,11 +271,15 @@
     bootclasspath_fragments: ["art-bootclasspath-fragment"],
     systemserverclasspath_fragments: ["art-systemserverclasspath-fragment"],
     compat_configs: ["libcore-platform-compat-config"],
-    java_libs: libcore_java_libs,
     native_shared_libs: art_runtime_base_native_shared_libs +
         art_runtime_base_native_device_only_shared_libs +
         libcore_native_shared_libs,
+    jni_libs: [
+        "libartservice",
+    ],
     binaries: [
+        "art_boot",
+        "art_exec",
         "artd",
     ],
     multilib: {
@@ -287,7 +299,7 @@
     ],
     prebuilts: [
         "art-linker-config",
-        "com.android.art.artd.init.rc",
+        "com.android.art.init.rc",
         "current_sdkinfo",
     ],
     // ART APEXes depend on bouncycastle which is disabled for PDK builds.
@@ -298,9 +310,6 @@
             enabled: false,
         },
     },
-    // Indicates that pre-installed version of this apex can be compressed.
-    // Whether it actually will be compressed is controlled on per-device basis.
-    compressible: true,
 }
 
 apex_defaults {
@@ -321,6 +330,9 @@
         art_runtime_run_test_libs +
         art_runtime_debug_native_shared_libs +
         libcore_debug_native_shared_libs,
+    jni_libs: [
+        "libartserviced",
+    ],
     multilib: {
         both: {
             binaries: art_tools_debug_binaries_both +
@@ -358,6 +370,7 @@
     file_contexts: ":com.android.art-file_contexts",
     certificate: ":com.android.art.certificate",
     installable: false,
+    compressible: false,
 }
 
 apex_test {
@@ -395,6 +408,7 @@
 
 // ART gtests with dependencies on internal ART APEX libraries.
 art_gtests = [
+    "art_artd_tests",
     "art_cmdline_tests",
     "art_compiler_tests",
     "art_dex2oat_tests",
@@ -403,6 +417,7 @@
     "art_dexdump_tests",
     "art_dexlayout_tests",
     "art_dexlist_tests",
+    "art_disassembler_tests",
     "art_dexoptanalyzer_tests",
     "art_imgdiag_tests",
     "art_libartbase_tests",
@@ -415,7 +430,6 @@
     "art_oatdump_tests",
     "art_odrefresh_tests",
     "art_profman_tests",
-    "art_runtime_compiler_tests",
     "art_runtime_tests",
     "art_sigchain_tests",
 ]
@@ -430,7 +444,21 @@
     certificate: ":com.android.art.certificate",
     tests: art_gtests,
     binaries: ["signal_dumper"], // Need signal_dumper for run-tests.
+    // Mark this test APEX as non-updatable, as its contains
+    // additional files (used only for testing) that would not pass
+    // dependency checks performed on updatable APEXes (see
+    // go/apex-allowed-deps-error).
     updatable: false,
+    // Because this APEX is non-updatable, some of its native shared
+    // libraries (implicitly added as dependencies) are eligible to
+    // the symlink optimization. As we want this APEX to be
+    // self-contained (for testing purposes), we want to package
+    // these dependencies in this APEX, instead of symbolic links to
+    // their counterparts on the `system` partition, which may not
+    // even exist, as in the case of `libbacktrace` (see b/232790938
+    // and b/233357459). Marking this APEX as "future updatable"
+    // disables all symlink optimizations for it.
+    future_updatable: true,
 }
 
 // TODO: Do this better. art_apex_test_host will disable host builds when
@@ -505,6 +533,8 @@
 art_check_apex_gen_stem = "$(location art-apex-tester)" +
     " --deapexer $(location deapexer)" +
     " --debugfs $(location debugfs_static)" +
+    " --fsckerofs $(location fsck.erofs)" +
+    " --blkid $(location blkid_static)" +
     " --tmpdir $(genDir)"
 
 // The non-flattened APEXes are always checked, as they are always generated
@@ -514,8 +544,10 @@
     defaults: ["art_module_source_build_genrule_defaults"],
     tools: [
         "art-apex-tester",
+        "blkid_static",
         "deapexer",
         "debugfs_static",
+        "fsck.erofs",
     ],
 }
 
diff --git a/build/apex/art.rc b/build/apex/art.rc
new file mode 100644
index 0000000..563ee97
--- /dev/null
+++ b/build/apex/art.rc
@@ -0,0 +1,29 @@
+# Copyright (C) 2023 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.
+
+# A lazy service that is started and stopped dynamically as needed.
+service artd /apex/com.android.art/bin/artd
+    interface aidl artd
+    disabled  # Prevents the service from automatically starting at boot.
+    oneshot  # Prevents the service from automatically restarting each time it is stopped.
+    class core
+    user artd
+    group artd
+    capabilities DAC_OVERRIDE DAC_READ_SEARCH FOWNER CHOWN
+
+# Run at boot in Android U and later.
+service art_boot /apex/com.android.art/bin/art_boot
+    disabled  # Started explicitly from system/core/rootdir/init.rc
+    oneshot
+    class core
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 428baf4..c2549cc 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -44,7 +44,7 @@
 
 # Architectures supported by APEX packages.
 ARCHS_32 = ["arm", "x86"]
-ARCHS_64 = ["arm64", "x86_64"]
+ARCHS_64 = ["arm64", "riscv64", "x86_64"]
 
 # Multilib options
 MULTILIB_32 = '32'
@@ -65,12 +65,14 @@
   return var in os.environ and os.environ[var] == 'true'
 
 
-def extract_apex(apex_path, deapexer_path, debugfs_path, tmpdir):
+def extract_apex(apex_path, deapexer_path, debugfs_path, fsckerofs_path,
+                 blkid_path, tmpdir):
   _, apex_name = os.path.split(apex_path)
   extract_path = os.path.join(tmpdir, apex_name)
   if os.path.exists(extract_path):
     shutil.rmtree(extract_path)
   subprocess.check_call([deapexer_path, '--debugfs', debugfs_path,
+                         '--fsckerofs', fsckerofs_path, '--blkid', blkid_path,
                          'extract', apex_path, extract_path],
                         stdout=subprocess.DEVNULL)
   return extract_path
@@ -217,6 +219,8 @@
       return False, 'Could not find %s'
     if fs_object.is_dir:
       return False, '%s is a directory'
+    if fs_object.is_symlink:
+      return False, '%s is a symlink'
     return True, ''
 
   def is_dir(self, path):
@@ -272,17 +276,22 @@
   def check_art_test_executable(self, filename, multilib=None):
     dirs = self.arch_dirs_for_path(ART_TEST_DIR, multilib)
     if not dirs:
-      self.fail('ART test binary missing: %s', filename)
+      self.fail('Directories for ART test binary missing: %s', filename)
+      return
     for dir in dirs:
       test_path = '%s/%s' % (dir, filename)
       self._expected_file_globs.add(test_path)
-      if not self._provider.get(test_path).is_exec:
+      file_obj = self._provider.get(test_path)
+      if not file_obj:
+        self.fail('ART test binary missing: %s', test_path)
+      elif not file_obj.is_exec:
         self.fail('%s is not executable', test_path)
 
   def check_art_test_data(self, filename):
     dirs = self.arch_dirs_for_path(ART_TEST_DIR)
     if not dirs:
-      self.fail('ART test data missing: %s', filename)
+      self.fail('Directories for ART test data missing: %s', filename)
+      return
     for dir in dirs:
       if not self.check_file('%s/%s' % (dir, filename)):
         return
@@ -471,7 +480,6 @@
     self._checker.check_native_library('libart-disassembler')
     self._checker.check_native_library('libartbase')
     self._checker.check_native_library('libartpalette')
-    self._checker.check_native_library('libartservice')
     self._checker.check_native_library('libarttools')
     self._checker.check_native_library('libdt_fd_forward')
     self._checker.check_native_library('libopenjdkjvm')
@@ -504,7 +512,6 @@
     # catch invalid dependencies on /system or other APEXes that should go
     # through an exported library with stubs (b/128708192 tracks implementing a
     # better approach for that).
-    self._checker.check_native_library('libbacktrace')
     self._checker.check_native_library('libbase')
     self._checker.check_native_library('libc++')
     self._checker.check_native_library('libdt_socket')
@@ -513,7 +520,6 @@
     self._checker.check_native_library('liblzma')
     self._checker.check_native_library('libnpt')
     self._checker.check_native_library('libunwindstack')
-    self._checker.check_native_library('libziparchive')
 
     # Allow extra dependencies that appear in ASAN builds.
     self._checker.check_optional_native_library('libclang_rt.asan*')
@@ -544,14 +550,16 @@
     # removed in Android R.
 
     # Check binaries for ART.
+    self._checker.check_executable('art_boot')
+    self._checker.check_executable('art_exec')
     self._checker.check_executable('artd')
     self._checker.check_executable('oatdump')
     self._checker.check_executable("odrefresh")
     self._checker.check_symlinked_multilib_executable('dex2oat')
 
     # Check internal libraries for ART.
+    self._checker.check_native_library('libartservice')
     self._checker.check_native_library('libperfetto_hprof')
-    self._checker.check_prefer64_library('artd-aidl-ndk')
 
     # Check internal Java libraries
     self._checker.check_java_library("service-art")
@@ -637,6 +645,7 @@
     self._checker.check_symlinked_multilib_executable('dex2oatd')
 
     # Check ART internal libraries.
+    self._checker.check_native_library('libartserviced')
     self._checker.check_native_library('libperfetto_hprofd')
 
     # Check internal native library dependencies.
@@ -664,6 +673,7 @@
 
   def run(self):
     # Check ART test binaries.
+    self._checker.check_art_test_executable('art_artd_tests')
     self._checker.check_art_test_executable('art_cmdline_tests')
     self._checker.check_art_test_executable('art_compiler_tests')
     self._checker.check_art_test_executable('art_dex2oat_tests')
@@ -673,6 +683,7 @@
     self._checker.check_art_test_executable('art_dexlayout_tests')
     self._checker.check_art_test_executable('art_dexlist_tests')
     self._checker.check_art_test_executable('art_dexoptanalyzer_tests')
+    self._checker.check_art_test_executable('art_disassembler_tests')
     self._checker.check_art_test_executable('art_imgdiag_tests')
     self._checker.check_art_test_executable('art_libartbase_tests')
     self._checker.check_art_test_executable('art_libartpalette_tests')
@@ -684,7 +695,6 @@
     self._checker.check_art_test_executable('art_oatdump_tests')
     self._checker.check_art_test_executable('art_odrefresh_tests')
     self._checker.check_art_test_executable('art_profman_tests')
-    self._checker.check_art_test_executable('art_runtime_compiler_tests')
     self._checker.check_art_test_executable('art_runtime_tests')
     self._checker.check_art_test_executable('art_sigchain_tests')
 
@@ -698,6 +708,7 @@
 
     # Check ART jar files which are needed for gtests.
     self._checker.check_art_test_data('art-gtest-jars-AbstractMethod.jar')
+    self._checker.check_art_test_data('art-gtest-jars-ArrayClassWithUnresolvedComponent.dex')
     self._checker.check_art_test_data('art-gtest-jars-MyClassNatives.jar')
     self._checker.check_art_test_data('art-gtest-jars-Main.jar')
     self._checker.check_art_test_data('art-gtest-jars-ProtoCompare.jar')
@@ -749,6 +760,7 @@
     self._checker.check_art_test_data('art-gtest-jars-MainEmptyUncompressed.jar')
     self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexTestDex.jar')
     self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexPublicSdkDex.dex')
+    self._checker.check_art_test_data('art-gtest-jars-SuperWithAccessChecks.dex')
 
 
 class NoSuperfluousBinariesChecker:
@@ -889,6 +901,12 @@
     if not test_args.debugfs:
       logging.error("Need debugfs.")
       return 1
+    if not test_args.fsckerofs:
+      logging.error("Need fsck.erofs.")
+      return 1
+    if not test_args.blkid:
+      logging.error("Need blkid.")
+      return 1
 
   if test_args.host:
     # Host APEX.
@@ -928,7 +946,7 @@
         # Extract the apex. It would be nice to use the output from "deapexer list"
         # to avoid this work, but it doesn't provide info about executable bits.
         apex_dir = extract_apex(test_args.apex, test_args.deapexer, test_args.debugfs,
-                                test_args.tmpdir)
+                                test_args.fsckerofs, test_args.blkid, test_args.tmpdir)
       apex_provider = TargetApexProvider(apex_dir)
   except (zipfile.BadZipFile, zipfile.LargeZipFile) as e:
     logging.error('Failed to create provider: %s', e)
@@ -1014,6 +1032,8 @@
 
   test_args = test_parser.parse_args(['unused'])  # For consistency.
   test_args.debugfs = '%s/bin/debugfs' % host_out
+  test_args.fsckerofs = '%s/bin/fsck.erofs' % host_out
+  test_args.blkid = '%s/bin/blkid_static' % host_out
   test_args.tmpdir = '.'
   test_args.tree = False
   test_args.list = False
@@ -1069,6 +1089,8 @@
   parser.add_argument('--tmpdir', help='Directory for temp files')
   parser.add_argument('--deapexer', help='Path to deapexer')
   parser.add_argument('--debugfs', help='Path to debugfs')
+  parser.add_argument('--fsckerofs', help='Path to fsck.erofs')
+  parser.add_argument('--blkid', help='Path to blkid')
 
   parser.add_argument('--bitness', help='Bitness to check', choices=BITNESS_ALL,
                       default=BITNESS_AUTO)
diff --git a/build/apex/manifest-art.json b/build/apex/manifest-art.json
index bf45076..4f20be6 100644
--- a/build/apex/manifest-art.json
+++ b/build/apex/manifest-art.json
@@ -1,6 +1,10 @@
 {
   "name": "com.android.art",
-  "version": 339990000,
+
+  // Placeholder module version to be replaced during build.
+  // Do not change!
+  "version": 0,
+
   "provideNativeLibs": [
     "libjdwp.so"
   ],
diff --git a/build/apex/runtests.sh b/build/apex/runtests.sh
index a452fe6..cdbb7c0 100755
--- a/build/apex/runtests.sh
+++ b/build/apex/runtests.sh
@@ -59,14 +59,27 @@
   export TARGET_BUILD_UNBUNDLED=true
 fi
 
-have_deapexer_p=false
+deapex_binaries=(
+  blkid_static
+  deapexer
+  debugfs_static
+  fsck.erofs
+)
+
+have_deapex_binaries=false
 if [[ "$TARGET_FLATTEN_APEX" != true ]]; then
-  if [ ! -e "$HOST_OUT/bin/deapexer" -o ! -e "$HOST_OUT/bin/debugfs_static" ] ; then
-    say "Could not find deapexer and/or debugfs_static, building now."
-    build/soong/soong_ui.bash --make-mode deapexer debugfs_static-host || \
-      die "Cannot build deapexer and debugfs_static"
+  have_deapex_binaries=true
+  for f in ${deapex_binaries[@]}; do
+    if [ ! -e "$HOST_OUT/bin/$f" ]; then
+      have_deapex_binaries=false
+    fi
+  done
+  if $have_deapex_binaries; then :; else
+    deapex_targets=( ${deapex_binaries[@]/%/-host} )
+    say "Building host binaries for deapexer: ${deapex_targets[*]}"
+    build/soong/soong_ui.bash --make-mode ${deapex_targets[@]} || \
+      die "Failed to build: ${deapex_targets[*]}"
   fi
-  have_deapexer_p=true
 fi
 
 # Fail early.
@@ -203,9 +216,11 @@
         apex_path="$PRODUCT_OUT/system/apex/${apex_module}.apex"
       fi
     fi
-    if $have_deapexer_p; then
+    if $have_deapex_binaries; then
       art_apex_test_args="$art_apex_test_args --deapexer $HOST_OUT/bin/deapexer"
       art_apex_test_args="$art_apex_test_args --debugfs $HOST_OUT/bin/debugfs_static"
+      art_apex_test_args="$art_apex_test_args --fsckerofs $HOST_OUT/bin/fsck.erofs"
+      art_apex_test_args="$art_apex_test_args --blkid $HOST_OUT/bin/blkid_static"
     fi
     case $apex_module in
       (*.debug)   test_only_args="--flavor debug";;
diff --git a/build/art.go b/build/art.go
index 7914950..dd2106e 100644
--- a/build/art.go
+++ b/build/art.go
@@ -29,7 +29,7 @@
 	"android/soong/cc/config"
 )
 
-var supportedArches = []string{"arm", "arm64", "x86", "x86_64"}
+var supportedArches = []string{"arm", "arm64", "riscv64", "x86", "x86_64"}
 
 func globalFlags(ctx android.LoadHookContext) ([]string, []string) {
 	var cflags []string
@@ -39,8 +39,7 @@
 	cflags = append(cflags, opt)
 
 	tlab := false
-
-	gcType := ctx.Config().GetenvWithDefault("ART_DEFAULT_GC_TYPE", "CMS")
+	gcType := ctx.Config().GetenvWithDefault("ART_DEFAULT_GC_TYPE", "CMC")
 
 	if ctx.Config().IsEnvTrue("ART_TEST_DEBUG_GC") {
 		gcType = "SS"
@@ -48,9 +47,6 @@
 	}
 
 	cflags = append(cflags, "-DART_DEFAULT_GC_TYPE_IS_"+gcType)
-	if tlab {
-		cflags = append(cflags, "-DART_USE_TLAB=1")
-	}
 
 	if ctx.Config().IsEnvTrue("ART_HEAP_POISONING") {
 		cflags = append(cflags, "-DART_HEAP_POISONING=1")
@@ -70,10 +66,22 @@
 		asflags = append(asflags,
 			"-DART_USE_READ_BARRIER=1",
 			"-DART_READ_BARRIER_TYPE_IS_"+barrierType+"=1")
+
+		if !ctx.Config().IsEnvFalse("ART_USE_GENERATIONAL_CC") {
+			cflags = append(cflags, "-DART_USE_GENERATIONAL_CC=1")
+		}
+		// Force CC only if ART_USE_READ_BARRIER was set to true explicitly during
+		// build time.
+		if ctx.Config().IsEnvTrue("ART_USE_READ_BARRIER") {
+			cflags = append(cflags, "-DART_FORCE_USE_READ_BARRIER=1")
+		}
+		tlab = true
+	} else if gcType == "CMC" {
+		tlab = true
 	}
 
-	if !ctx.Config().IsEnvFalse("ART_USE_GENERATIONAL_CC") {
-		cflags = append(cflags, "-DART_USE_GENERATIONAL_CC=1")
+	if tlab {
+		cflags = append(cflags, "-DART_USE_TLAB=1")
 	}
 
 	cdexLevel := ctx.Config().GetenvWithDefault("ART_DEFAULT_COMPACT_DEX_LEVEL", "fast")
@@ -86,14 +94,16 @@
 	//       the debug version. So make the gap consistent (and adjust for the worst).
 	if len(ctx.Config().SanitizeDevice()) > 0 || len(ctx.Config().SanitizeHost()) > 0 {
 		cflags = append(cflags,
-			"-DART_STACK_OVERFLOW_GAP_arm=8192",
+			"-DART_STACK_OVERFLOW_GAP_arm=16384",
 			"-DART_STACK_OVERFLOW_GAP_arm64=16384",
+			"-DART_STACK_OVERFLOW_GAP_riscv64=16384",
 			"-DART_STACK_OVERFLOW_GAP_x86=16384",
 			"-DART_STACK_OVERFLOW_GAP_x86_64=20480")
 	} else {
 		cflags = append(cflags,
 			"-DART_STACK_OVERFLOW_GAP_arm=8192",
 			"-DART_STACK_OVERFLOW_GAP_arm64=8192",
+			"-DART_STACK_OVERFLOW_GAP_riscv64=8192",
 			"-DART_STACK_OVERFLOW_GAP_x86=8192",
 			"-DART_STACK_OVERFLOW_GAP_x86_64=8192")
 	}
@@ -132,7 +142,7 @@
 	)
 
 	cflags = append(cflags, "-DART_BASE_ADDRESS="+ctx.Config().LibartImgDeviceBaseAddress())
-	minDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_TARGET_MIN_BASE_ADDRESS_DELTA", "-0x1000000")
+	minDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_TARGET_MIN_BASE_ADDRESS_DELTA", "(-0x1000000)")
 	maxDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_TARGET_MAX_BASE_ADDRESS_DELTA", "0x1000000")
 	cflags = append(cflags, "-DART_BASE_ADDRESS_MIN_DELTA="+minDelta)
 	cflags = append(cflags, "-DART_BASE_ADDRESS_MAX_DELTA="+maxDelta)
@@ -146,7 +156,11 @@
 	if len(ctx.Config().SanitizeHost()) > 0 {
 		// art/test/137-cfi/cfi.cc
 		// error: stack frame size of 1944 bytes in function 'Java_Main_unwindInProcess'
-		hostFrameSizeLimit = 6400
+		// b/249586057, need larger stack frame for newer clang compilers
+		hostFrameSizeLimit = 10000
+		// cannot add "-fsanitize-address-use-after-return=never" everywhere,
+		// or some file like compiler_driver.o can have stack frame of 30072 bytes.
+		// cflags = append(cflags, "-fsanitize-address-use-after-return=never")
 	}
 	cflags = append(cflags,
 		fmt.Sprintf("-Wframe-larger-than=%d", hostFrameSizeLimit),
@@ -154,7 +168,7 @@
 	)
 
 	cflags = append(cflags, "-DART_BASE_ADDRESS="+ctx.Config().LibartImgHostBaseAddress())
-	minDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_HOST_MIN_BASE_ADDRESS_DELTA", "-0x1000000")
+	minDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_HOST_MIN_BASE_ADDRESS_DELTA", "(-0x1000000)")
 	maxDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_HOST_MAX_BASE_ADDRESS_DELTA", "0x1000000")
 	cflags = append(cflags, "-DART_BASE_ADDRESS_MIN_DELTA="+minDelta)
 	cflags = append(cflags, "-DART_BASE_ADDRESS_MAX_DELTA="+maxDelta)
@@ -165,7 +179,7 @@
 	}
 
 	clang_path := filepath.Join(config.ClangDefaultBase, ctx.Config().PrebuiltOS(), config.ClangDefaultVersion)
-	cflags = append(cflags, "-DART_CLANG_PATH=\""+clang_path+"\"")
+	cflags = append(cflags, fmt.Sprintf("-DART_CLANG_PATH=\"%s\"", clang_path))
 
 	return cflags
 }
@@ -305,7 +319,7 @@
 // The 'key' is the file in testcases and 'value' is the path to copy it from.
 // The actual copy will be done in make since soong does not do installations.
 func addTestcasesFile(ctx android.InstallHookContext) {
-	if ctx.Os() != ctx.Config().BuildOS || ctx.Module().IsSkipInstall() {
+	if ctx.Os() != ctx.Config().BuildOS || ctx.Target().HostCross || ctx.Module().IsSkipInstall() {
 		return
 	}
 
@@ -456,7 +470,8 @@
 }
 
 func artTest() android.Module {
-	module := cc.TestFactory()
+	// Disable bp2build.
+	module := cc.NewTest(android.HostAndDeviceSupported, false /* bazelable */).Init()
 
 	installCodegenCustomizer(module, binary)
 
diff --git a/build/boot/boot-image-profile.txt b/build/boot/boot-image-profile.txt
index a842df1..a2828fe 100644
--- a/build/boot/boot-image-profile.txt
+++ b/build/boot/boot-image-profile.txt
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+HSPLandroid/compat/Compatibility$BehaviorChangeDelegate;->isChangeEnabled(J)Z
 HSPLandroid/compat/Compatibility;->isChangeEnabled(J)Z
 HSPLandroid/compat/Compatibility;->setBehaviorChangeDelegate(Landroid/compat/Compatibility$BehaviorChangeDelegate;)V
 HSPLandroid/system/ErrnoException;-><init>(Ljava/lang/String;I)V
@@ -33,8 +34,8 @@
 HSPLandroid/system/Os;->fstat(Ljava/io/FileDescriptor;)Landroid/system/StructStat;
 HSPLandroid/system/Os;->getpeername(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;
 HSPLandroid/system/Os;->getpgid(I)I
-HSPLandroid/system/Os;->getpid()I
-HSPLandroid/system/Os;->gettid()I
+HSPLandroid/system/Os;->getpid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
+HSPLandroid/system/Os;->gettid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
 HSPLandroid/system/Os;->getuid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
 HSPLandroid/system/Os;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B
 HSPLandroid/system/Os;->ioctlInt(Ljava/io/FileDescriptor;I)I
@@ -140,12 +141,12 @@
 HSPLcom/android/okhttp/Headers$Builder;->build()Lcom/android/okhttp/Headers;
 HSPLcom/android/okhttp/Headers$Builder;->checkNameAndValue(Ljava/lang/String;Ljava/lang/String;)V
 HSPLcom/android/okhttp/Headers$Builder;->get(Ljava/lang/String;)Ljava/lang/String;
-HSPLcom/android/okhttp/Headers$Builder;->removeAll(Ljava/lang/String;)Lcom/android/okhttp/Headers$Builder;
+HSPLcom/android/okhttp/Headers$Builder;->removeAll(Ljava/lang/String;)Lcom/android/okhttp/Headers$Builder;+]Ljava/lang/String;Ljava/lang/String;]Ljava/util/List;Ljava/util/ArrayList;
 HSPLcom/android/okhttp/Headers$Builder;->set(Ljava/lang/String;Ljava/lang/String;)Lcom/android/okhttp/Headers$Builder;
 HSPLcom/android/okhttp/Headers;-><init>(Lcom/android/okhttp/Headers$Builder;)V
 HSPLcom/android/okhttp/Headers;-><init>(Lcom/android/okhttp/Headers$Builder;Lcom/android/okhttp/Headers$1;)V
 HSPLcom/android/okhttp/Headers;->get(Ljava/lang/String;)Ljava/lang/String;
-HSPLcom/android/okhttp/Headers;->get([Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+HSPLcom/android/okhttp/Headers;->get([Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLcom/android/okhttp/Headers;->name(I)Ljava/lang/String;
 HSPLcom/android/okhttp/Headers;->newBuilder()Lcom/android/okhttp/Headers$Builder;
 HSPLcom/android/okhttp/Headers;->size()I
@@ -181,7 +182,7 @@
 HSPLcom/android/okhttp/HttpUrl;-><init>(Lcom/android/okhttp/HttpUrl$Builder;)V
 HSPLcom/android/okhttp/HttpUrl;-><init>(Lcom/android/okhttp/HttpUrl$Builder;Lcom/android/okhttp/HttpUrl$1;)V
 HSPLcom/android/okhttp/HttpUrl;->access$200(Ljava/lang/String;IILjava/lang/String;)I
-HSPLcom/android/okhttp/HttpUrl;->canonicalize(Ljava/lang/String;IILjava/lang/String;ZZZZ)Ljava/lang/String;
+HSPLcom/android/okhttp/HttpUrl;->canonicalize(Ljava/lang/String;IILjava/lang/String;ZZZZ)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLcom/android/okhttp/HttpUrl;->canonicalize(Ljava/lang/String;Ljava/lang/String;ZZZZ)Ljava/lang/String;
 HSPLcom/android/okhttp/HttpUrl;->decodeHexDigit(C)I
 HSPLcom/android/okhttp/HttpUrl;->defaultPort(Ljava/lang/String;)I
@@ -201,7 +202,7 @@
 HSPLcom/android/okhttp/HttpUrl;->newBuilder()Lcom/android/okhttp/HttpUrl$Builder;
 HSPLcom/android/okhttp/HttpUrl;->pathSegmentsToString(Ljava/lang/StringBuilder;Ljava/util/List;)V
 HSPLcom/android/okhttp/HttpUrl;->percentDecode(Lcom/android/okhttp/okio/Buffer;Ljava/lang/String;IIZ)V
-HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/lang/String;IIZ)Ljava/lang/String;
+HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/lang/String;IIZ)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/lang/String;Z)Ljava/lang/String;
 HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/util/List;Z)Ljava/util/List;
 HSPLcom/android/okhttp/HttpUrl;->port()I
@@ -415,7 +416,7 @@
 HSPLcom/android/okhttp/internal/http/Http1xStream$FixedLengthSink;->write(Lcom/android/okhttp/okio/Buffer;J)V
 HSPLcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;-><init>(Lcom/android/okhttp/internal/http/Http1xStream;J)V
 HSPLcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;->close()V
-HSPLcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;->read(Lcom/android/okhttp/okio/Buffer;J)J+]Lcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;Lcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;]Lcom/android/okhttp/okio/BufferedSource;Lcom/android/okhttp/okio/RealBufferedSource;
+HSPLcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;->read(Lcom/android/okhttp/okio/Buffer;J)J
 HSPLcom/android/okhttp/internal/http/Http1xStream;-><init>(Lcom/android/okhttp/internal/http/StreamAllocation;Lcom/android/okhttp/okio/BufferedSource;Lcom/android/okhttp/okio/BufferedSink;)V
 HSPLcom/android/okhttp/internal/http/Http1xStream;->access$300(Lcom/android/okhttp/internal/http/Http1xStream;)Lcom/android/okhttp/okio/BufferedSink;
 HSPLcom/android/okhttp/internal/http/Http1xStream;->access$400(Lcom/android/okhttp/internal/http/Http1xStream;Lcom/android/okhttp/okio/ForwardingTimeout;)V
@@ -470,8 +471,8 @@
 HSPLcom/android/okhttp/internal/http/HttpEngine;->writingRequestHeaders()V
 HSPLcom/android/okhttp/internal/http/HttpMethod;->permitsRequestBody(Ljava/lang/String;)Z
 HSPLcom/android/okhttp/internal/http/HttpMethod;->requiresRequestBody(Ljava/lang/String;)Z
-HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
-HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/String;Ljava/lang/String;)I
+HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Lcom/android/okhttp/internal/http/OkHeaders$1;Lcom/android/okhttp/internal/http/OkHeaders$1;
+HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/String;Ljava/lang/String;)I+]Ljava/util/Comparator;Ljava/lang/String$CaseInsensitiveComparator;
 HSPLcom/android/okhttp/internal/http/OkHeaders;->contentLength(Lcom/android/okhttp/Headers;)J
 HSPLcom/android/okhttp/internal/http/OkHeaders;->contentLength(Lcom/android/okhttp/Request;)J
 HSPLcom/android/okhttp/internal/http/OkHeaders;->contentLength(Lcom/android/okhttp/Response;)J
@@ -658,7 +659,7 @@
 HSPLcom/android/okhttp/okio/AsyncTimeout;->exit(Ljava/io/IOException;)Ljava/io/IOException;
 HSPLcom/android/okhttp/okio/AsyncTimeout;->exit(Z)V
 HSPLcom/android/okhttp/okio/AsyncTimeout;->remainingNanos(J)J
-HSPLcom/android/okhttp/okio/AsyncTimeout;->scheduleTimeout(Lcom/android/okhttp/okio/AsyncTimeout;JZ)V+]Ljava/lang/Object;Ljava/lang/Class;
+HSPLcom/android/okhttp/okio/AsyncTimeout;->scheduleTimeout(Lcom/android/okhttp/okio/AsyncTimeout;JZ)V
 HSPLcom/android/okhttp/okio/AsyncTimeout;->sink(Lcom/android/okhttp/okio/Sink;)Lcom/android/okhttp/okio/Sink;
 HSPLcom/android/okhttp/okio/AsyncTimeout;->source(Lcom/android/okhttp/okio/Source;)Lcom/android/okhttp/okio/Source;
 HSPLcom/android/okhttp/okio/Buffer;-><init>()V
@@ -669,7 +670,7 @@
 HSPLcom/android/okhttp/okio/Buffer;->getByte(J)B
 HSPLcom/android/okhttp/okio/Buffer;->indexOf(BJ)J
 HSPLcom/android/okhttp/okio/Buffer;->read(Lcom/android/okhttp/okio/Buffer;J)J
-HSPLcom/android/okhttp/okio/Buffer;->read([BII)I+]Lcom/android/okhttp/okio/Segment;Lcom/android/okhttp/okio/Segment;
+HSPLcom/android/okhttp/okio/Buffer;->read([BII)I
 HSPLcom/android/okhttp/okio/Buffer;->readByte()B
 HSPLcom/android/okhttp/okio/Buffer;->readByteArray()[B
 HSPLcom/android/okhttp/okio/Buffer;->readByteArray(J)[B
@@ -681,17 +682,17 @@
 HSPLcom/android/okhttp/okio/Buffer;->readShort()S
 HSPLcom/android/okhttp/okio/Buffer;->readString(JLjava/nio/charset/Charset;)Ljava/lang/String;
 HSPLcom/android/okhttp/okio/Buffer;->readUtf8()Ljava/lang/String;
-HSPLcom/android/okhttp/okio/Buffer;->readUtf8(J)Ljava/lang/String;
-HSPLcom/android/okhttp/okio/Buffer;->readUtf8Line(J)Ljava/lang/String;
+HSPLcom/android/okhttp/okio/Buffer;->readUtf8(J)Ljava/lang/String;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;
+HSPLcom/android/okhttp/okio/Buffer;->readUtf8Line(J)Ljava/lang/String;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/Buffer;->size()J
 HSPLcom/android/okhttp/okio/Buffer;->skip(J)V+]Lcom/android/okhttp/okio/Segment;Lcom/android/okhttp/okio/Segment;
 HSPLcom/android/okhttp/okio/Buffer;->writableSegment(I)Lcom/android/okhttp/okio/Segment;
-HSPLcom/android/okhttp/okio/Buffer;->write(Lcom/android/okhttp/okio/Buffer;J)V+]Lcom/android/okhttp/okio/Segment;Lcom/android/okhttp/okio/Segment;
+HSPLcom/android/okhttp/okio/Buffer;->write(Lcom/android/okhttp/okio/Buffer;J)V
 HSPLcom/android/okhttp/okio/Buffer;->write([BII)Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/Buffer;->writeByte(I)Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/Buffer;->writeHexadecimalUnsignedLong(J)Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/Buffer;->writeUtf8(Ljava/lang/String;)Lcom/android/okhttp/okio/Buffer;
-HSPLcom/android/okhttp/okio/Buffer;->writeUtf8(Ljava/lang/String;II)Lcom/android/okhttp/okio/Buffer;
+HSPLcom/android/okhttp/okio/Buffer;->writeUtf8(Ljava/lang/String;II)Lcom/android/okhttp/okio/Buffer;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/Buffer;->writeUtf8CodePoint(I)Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/ByteString;-><init>([B)V
 HSPLcom/android/okhttp/okio/ByteString;->hex()Ljava/lang/String;
@@ -717,7 +718,7 @@
 HSPLcom/android/okhttp/okio/Okio$1;->flush()V
 HSPLcom/android/okhttp/okio/Okio$1;->write(Lcom/android/okhttp/okio/Buffer;J)V
 HSPLcom/android/okhttp/okio/Okio$2;-><init>(Lcom/android/okhttp/okio/Timeout;Ljava/io/InputStream;)V
-HSPLcom/android/okhttp/okio/Okio$2;->read(Lcom/android/okhttp/okio/Buffer;J)J+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/Timeout;Lcom/android/okhttp/okio/Okio$3;]Ljava/io/InputStream;Lcom/android/org/conscrypt/ConscryptEngineSocket$SSLInputStream;
+HSPLcom/android/okhttp/okio/Okio$2;->read(Lcom/android/okhttp/okio/Buffer;J)J
 HSPLcom/android/okhttp/okio/Okio$3;-><init>(Ljava/net/Socket;)V
 HSPLcom/android/okhttp/okio/Okio$3;->newTimeoutException(Ljava/io/IOException;)Ljava/io/IOException;
 HSPLcom/android/okhttp/okio/Okio$3;->timedOut()V
@@ -731,7 +732,7 @@
 HSPLcom/android/okhttp/okio/RealBufferedSink$1;-><init>(Lcom/android/okhttp/okio/RealBufferedSink;)V
 HSPLcom/android/okhttp/okio/RealBufferedSink$1;->close()V
 HSPLcom/android/okhttp/okio/RealBufferedSink$1;->flush()V
-HSPLcom/android/okhttp/okio/RealBufferedSink$1;->write([BII)V+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/RealBufferedSink;Lcom/android/okhttp/okio/RealBufferedSink;
+HSPLcom/android/okhttp/okio/RealBufferedSink$1;->write([BII)V
 HSPLcom/android/okhttp/okio/RealBufferedSink;-><init>(Lcom/android/okhttp/okio/Sink;)V
 HSPLcom/android/okhttp/okio/RealBufferedSink;-><init>(Lcom/android/okhttp/okio/Sink;Lcom/android/okhttp/okio/Buffer;)V
 HSPLcom/android/okhttp/okio/RealBufferedSink;->access$000(Lcom/android/okhttp/okio/RealBufferedSink;)Z
@@ -749,22 +750,22 @@
 HSPLcom/android/okhttp/okio/RealBufferedSource$1;->available()I
 HSPLcom/android/okhttp/okio/RealBufferedSource$1;->close()V
 HSPLcom/android/okhttp/okio/RealBufferedSource$1;->read()I
-HSPLcom/android/okhttp/okio/RealBufferedSource$1;->read([BII)I+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;
+HSPLcom/android/okhttp/okio/RealBufferedSource$1;->read([BII)I
 HSPLcom/android/okhttp/okio/RealBufferedSource;-><init>(Lcom/android/okhttp/okio/Source;)V
 HSPLcom/android/okhttp/okio/RealBufferedSource;-><init>(Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/okio/Buffer;)V
 HSPLcom/android/okhttp/okio/RealBufferedSource;->access$000(Lcom/android/okhttp/okio/RealBufferedSource;)Z
 HSPLcom/android/okhttp/okio/RealBufferedSource;->buffer()Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/RealBufferedSource;->close()V
 HSPLcom/android/okhttp/okio/RealBufferedSource;->exhausted()Z
-HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(B)J
-HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(BJ)J
+HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(B)J+]Lcom/android/okhttp/okio/RealBufferedSource;Lcom/android/okhttp/okio/RealBufferedSource;
+HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(BJ)J+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/okio/AsyncTimeout$2;
 HSPLcom/android/okhttp/okio/RealBufferedSource;->inputStream()Ljava/io/InputStream;
-HSPLcom/android/okhttp/okio/RealBufferedSource;->read(Lcom/android/okhttp/okio/Buffer;J)J+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/okio/AsyncTimeout$2;
-HSPLcom/android/okhttp/okio/RealBufferedSource;->readHexadecimalUnsignedLong()J
+HSPLcom/android/okhttp/okio/RealBufferedSource;->read(Lcom/android/okhttp/okio/Buffer;J)J
+HSPLcom/android/okhttp/okio/RealBufferedSource;->readHexadecimalUnsignedLong()J+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/RealBufferedSource;Lcom/android/okhttp/okio/RealBufferedSource;
 HSPLcom/android/okhttp/okio/RealBufferedSource;->readIntLe()I
 HSPLcom/android/okhttp/okio/RealBufferedSource;->readShort()S
-HSPLcom/android/okhttp/okio/RealBufferedSource;->readUtf8LineStrict()Ljava/lang/String;
-HSPLcom/android/okhttp/okio/RealBufferedSource;->request(J)Z
+HSPLcom/android/okhttp/okio/RealBufferedSource;->readUtf8LineStrict()Ljava/lang/String;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/RealBufferedSource;Lcom/android/okhttp/okio/RealBufferedSource;
+HSPLcom/android/okhttp/okio/RealBufferedSource;->request(J)Z+]Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/okio/AsyncTimeout$2;
 HSPLcom/android/okhttp/okio/RealBufferedSource;->require(J)V
 HSPLcom/android/okhttp/okio/RealBufferedSource;->skip(J)V
 HSPLcom/android/okhttp/okio/RealBufferedSource;->timeout()Lcom/android/okhttp/okio/Timeout;
@@ -805,10 +806,10 @@
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->createPrimitiveDERObject(ILcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;[[B)Lcom/android/org/bouncycastle/asn1/ASN1Primitive;
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->getBuffer(Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;[[B)[B
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readLength()I
-HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readLength(Ljava/io/InputStream;IZ)I+]Ljava/io/InputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;,Lcom/android/org/bouncycastle/asn1/ASN1InputStream;
+HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readLength(Ljava/io/InputStream;IZ)I
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readObject()Lcom/android/org/bouncycastle/asn1/ASN1Primitive;
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readTagNumber(Ljava/io/InputStream;I)I
-HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readVector(Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;)Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;+]Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;]Lcom/android/org/bouncycastle/asn1/ASN1InputStream;Lcom/android/org/bouncycastle/asn1/ASN1InputStream;]Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;
+HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readVector(Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;)Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;
 HSPLcom/android/org/bouncycastle/asn1/ASN1Integer;-><init>(Ljava/math/BigInteger;)V
 HSPLcom/android/org/bouncycastle/asn1/ASN1Integer;-><init>([BZ)V
 HSPLcom/android/org/bouncycastle/asn1/ASN1Integer;->encode(Lcom/android/org/bouncycastle/asn1/ASN1OutputStream;Z)V
@@ -874,13 +875,13 @@
 HSPLcom/android/org/bouncycastle/asn1/DERSequence;->getBodyLength()I
 HSPLcom/android/org/bouncycastle/asn1/DERSequence;->toDERObject()Lcom/android/org/bouncycastle/asn1/ASN1Primitive;
 HSPLcom/android/org/bouncycastle/asn1/DLFactory;-><clinit>()V
-HSPLcom/android/org/bouncycastle/asn1/DLFactory;->createSequence(Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;)Lcom/android/org/bouncycastle/asn1/ASN1Sequence;+]Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;
+HSPLcom/android/org/bouncycastle/asn1/DLFactory;->createSequence(Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;)Lcom/android/org/bouncycastle/asn1/ASN1Sequence;
 HSPLcom/android/org/bouncycastle/asn1/DLSequence;-><init>()V
 HSPLcom/android/org/bouncycastle/asn1/DLSequence;-><init>(Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;)V
 HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->getRemaining()I
 HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->read()I
 HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->read([BII)I
-HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->readAllIntoByteArray([B)V+]Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;
+HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->readAllIntoByteArray([B)V
 HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->toByteArray()[B
 HSPLcom/android/org/bouncycastle/asn1/LimitedInputStream;-><init>(Ljava/io/InputStream;I)V
 HSPLcom/android/org/bouncycastle/asn1/LimitedInputStream;->getLimit()I
@@ -911,11 +912,11 @@
 HSPLcom/android/org/bouncycastle/crypto/digests/AndroidDigestFactoryOpenSSL;->getSHA1()Lcom/android/org/bouncycastle/crypto/Digest;
 HSPLcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest$SHA1;-><init>()V
 HSPLcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest;-><init>(Ljava/lang/String;I)V
-HSPLcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest;->doFinal([BI)I+]Ljava/security/MessageDigest;Ljava/security/MessageDigest$Delegate;
+HSPLcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest;->doFinal([BI)I
 HSPLcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest;->getByteLength()I
 HSPLcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest;->getDigestSize()I
 HSPLcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest;->reset()V
-HSPLcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest;->update([BII)V+]Ljava/security/MessageDigest;Ljava/security/MessageDigest$Delegate;
+HSPLcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest;->update([BII)V
 HSPLcom/android/org/bouncycastle/crypto/engines/AESEngine;-><init>()V
 HSPLcom/android/org/bouncycastle/crypto/engines/AESEngine;->generateWorkingKey([BZ)[[I
 HSPLcom/android/org/bouncycastle/crypto/engines/AESEngine;->getAlgorithmName()Ljava/lang/String;
@@ -932,10 +933,10 @@
 HSPLcom/android/org/bouncycastle/crypto/engines/DESEngine;->generateWorkingKey(Z[B)[I
 HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;-><init>(Lcom/android/org/bouncycastle/crypto/Digest;)V
 HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;->adjust([BI[B)V
-HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;->generateDerivedKey(II)[B+]Lcom/android/org/bouncycastle/crypto/Digest;Lcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest$SHA1;,Lcom/android/org/bouncycastle/crypto/digests/SHA1Digest;
+HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;->generateDerivedKey(II)[B
 HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;-><init>(Lcom/android/org/bouncycastle/crypto/Digest;)V
-HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->F([BI[B[BI)V+]Lcom/android/org/bouncycastle/crypto/Mac;Lcom/android/org/bouncycastle/crypto/macs/HMac;
-HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedKey(I)[B+]Lcom/android/org/bouncycastle/crypto/Mac;Lcom/android/org/bouncycastle/crypto/macs/HMac;
+HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->F([BI[B[BI)V
+HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedKey(I)[B
 HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedMacParameters(I)Lcom/android/org/bouncycastle/crypto/CipherParameters;
 HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedParameters(I)Lcom/android/org/bouncycastle/crypto/CipherParameters;
 HSPLcom/android/org/bouncycastle/crypto/macs/HMac;-><clinit>()V
@@ -956,11 +957,11 @@
 HSPLcom/android/org/bouncycastle/crypto/paddings/PKCS7Padding;->padCount([B)I
 HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;-><init>(Lcom/android/org/bouncycastle/crypto/BlockCipher;)V
 HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;-><init>(Lcom/android/org/bouncycastle/crypto/BlockCipher;Lcom/android/org/bouncycastle/crypto/paddings/BlockCipherPadding;)V
-HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->doFinal([BI)I+]Lcom/android/org/bouncycastle/crypto/paddings/BlockCipherPadding;Lcom/android/org/bouncycastle/crypto/paddings/PKCS7Padding;]Lcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;Lcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;]Lcom/android/org/bouncycastle/crypto/BlockCipher;Lcom/android/org/bouncycastle/crypto/modes/CBCBlockCipher;
+HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->doFinal([BI)I
 HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->getOutputSize(I)I
 HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->getUpdateOutputSize(I)I
 HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->init(ZLcom/android/org/bouncycastle/crypto/CipherParameters;)V
-HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->processBytes([BII[BI)I+]Lcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;Lcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;]Lcom/android/org/bouncycastle/crypto/BlockCipher;Lcom/android/org/bouncycastle/crypto/engines/AESEngine;,Lcom/android/org/bouncycastle/crypto/modes/CBCBlockCipher;
+HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->processBytes([BII[BI)I
 HSPLcom/android/org/bouncycastle/crypto/params/AsymmetricKeyParameter;-><init>(Z)V
 HSPLcom/android/org/bouncycastle/crypto/params/DSAKeyParameters;-><init>(ZLcom/android/org/bouncycastle/crypto/params/DSAParameters;)V
 HSPLcom/android/org/bouncycastle/crypto/params/DSAParameters;-><init>(Ljava/math/BigInteger;Ljava/math/BigInteger;Ljava/math/BigInteger;)V
@@ -1047,7 +1048,7 @@
 HSPLcom/android/org/kxml2/io/KXmlParser;->getLineNumber()I
 HSPLcom/android/org/kxml2/io/KXmlParser;->getName()Ljava/lang/String;
 HSPLcom/android/org/kxml2/io/KXmlParser;->getNamespace()Ljava/lang/String;
-HSPLcom/android/org/kxml2/io/KXmlParser;->getNamespace(Ljava/lang/String;)Ljava/lang/String;+]Lcom/android/org/kxml2/io/KXmlParser;Lcom/android/org/kxml2/io/KXmlParser;
+HSPLcom/android/org/kxml2/io/KXmlParser;->getNamespace(Ljava/lang/String;)Ljava/lang/String;
 HSPLcom/android/org/kxml2/io/KXmlParser;->getNamespaceCount(I)I
 HSPLcom/android/org/kxml2/io/KXmlParser;->getText()Ljava/lang/String;
 HSPLcom/android/org/kxml2/io/KXmlParser;->keepNamespaceAttributes()V
@@ -1062,7 +1063,7 @@
 HSPLcom/android/org/kxml2/io/KXmlParser;->read([C)V
 HSPLcom/android/org/kxml2/io/KXmlParser;->readComment(Z)Ljava/lang/String;
 HSPLcom/android/org/kxml2/io/KXmlParser;->readEndTag()V
-HSPLcom/android/org/kxml2/io/KXmlParser;->readEntity(Ljava/lang/StringBuilder;ZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)V
+HSPLcom/android/org/kxml2/io/KXmlParser;->readEntity(Ljava/lang/StringBuilder;ZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Map;Ljava/util/HashMap;
 HSPLcom/android/org/kxml2/io/KXmlParser;->readName()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/internal/StringPool;Llibcore/internal/StringPool;
 HSPLcom/android/org/kxml2/io/KXmlParser;->readUntil([CZ)Ljava/lang/String;
 HSPLcom/android/org/kxml2/io/KXmlParser;->readValue(CZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/internal/StringPool;Llibcore/internal/StringPool;
@@ -1074,7 +1075,7 @@
 HSPLcom/android/org/kxml2/io/KXmlParser;->skip()V
 HSPLcom/android/org/kxml2/io/KXmlSerializer;->append(C)V
 HSPLcom/android/org/kxml2/io/KXmlSerializer;->append(Ljava/lang/String;)V
-HSPLcom/android/org/kxml2/io/KXmlSerializer;->append(Ljava/lang/String;II)V+]Ljava/lang/String;Ljava/lang/String;
+HSPLcom/android/org/kxml2/io/KXmlSerializer;->append(Ljava/lang/String;II)V
 HSPLcom/android/org/kxml2/io/KXmlSerializer;->attribute(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer;
 HSPLcom/android/org/kxml2/io/KXmlSerializer;->check(Z)V
 HSPLcom/android/org/kxml2/io/KXmlSerializer;->endDocument()V
@@ -1092,7 +1093,7 @@
 HSPLdalvik/system/BaseDexClassLoader;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;)V
 HSPLdalvik/system/BaseDexClassLoader;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;Z)V
 HSPLdalvik/system/BaseDexClassLoader;->addNativePath(Ljava/util/Collection;)V
-HSPLdalvik/system/BaseDexClassLoader;->findClass(Ljava/lang/String;)Ljava/lang/Class;
+HSPLdalvik/system/BaseDexClassLoader;->findClass(Ljava/lang/String;)Ljava/lang/Class;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ldalvik/system/DexPathList;Ldalvik/system/DexPathList;]Ljava/util/List;Ljava/util/ArrayList;]Ljava/lang/ClassLoader;Ldalvik/system/PathClassLoader;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr;]Ljava/lang/ClassNotFoundException;Ljava/lang/ClassNotFoundException;
 HSPLdalvik/system/BaseDexClassLoader;->findLibrary(Ljava/lang/String;)Ljava/lang/String;
 HSPLdalvik/system/BaseDexClassLoader;->findResource(Ljava/lang/String;)Ljava/net/URL;
 HSPLdalvik/system/BaseDexClassLoader;->findResources(Ljava/lang/String;)Ljava/util/Enumeration;
@@ -1111,7 +1112,7 @@
 HSPLdalvik/system/BlockGuard$3;->initialValue()Ljava/lang/Object;
 HSPLdalvik/system/BlockGuard;->getThreadPolicy()Ldalvik/system/BlockGuard$Policy;+]Ljava/lang/ThreadLocal;Ldalvik/system/BlockGuard$3;
 HSPLdalvik/system/BlockGuard;->getVmPolicy()Ldalvik/system/BlockGuard$VmPolicy;
-HSPLdalvik/system/BlockGuard;->setThreadPolicy(Ldalvik/system/BlockGuard$Policy;)V
+HSPLdalvik/system/BlockGuard;->setThreadPolicy(Ldalvik/system/BlockGuard$Policy;)V+]Ljava/lang/ThreadLocal;Ldalvik/system/BlockGuard$3;
 HSPLdalvik/system/BlockGuard;->setVmPolicy(Ldalvik/system/BlockGuard$VmPolicy;)V
 HSPLdalvik/system/CloseGuard;-><init>()V
 HSPLdalvik/system/CloseGuard;->close()V
@@ -1121,7 +1122,7 @@
 HSPLdalvik/system/CloseGuard;->openWithCallSite(Ljava/lang/String;Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLdalvik/system/CloseGuard;->setEnabled(Z)V
 HSPLdalvik/system/CloseGuard;->setReporter(Ldalvik/system/CloseGuard$Reporter;)V
-HSPLdalvik/system/CloseGuard;->warnIfOpen()V+]Ldalvik/system/CloseGuard$Reporter;Landroid/os/StrictMode$AndroidCloseGuardReporter;
+HSPLdalvik/system/CloseGuard;->warnIfOpen()V
 HSPLdalvik/system/DelegateLastClassLoader;-><init>(Ljava/lang/String;Ljava/lang/ClassLoader;)V
 HSPLdalvik/system/DelegateLastClassLoader;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;Z)V
 HSPLdalvik/system/DelegateLastClassLoader;->loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
@@ -1140,7 +1141,7 @@
 HSPLdalvik/system/DexPathList$Element;->findClass(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/util/List;)Ljava/lang/Class;
 HSPLdalvik/system/DexPathList$Element;->findResource(Ljava/lang/String;)Ljava/net/URL;
 HSPLdalvik/system/DexPathList$Element;->maybeInit()V
-HSPLdalvik/system/DexPathList$Element;->toString()Ljava/lang/String;
+HSPLdalvik/system/DexPathList$Element;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Boolean;Ljava/lang/Boolean;
 HSPLdalvik/system/DexPathList$NativeLibraryElement;-><init>(Ljava/io/File;)V
 HSPLdalvik/system/DexPathList$NativeLibraryElement;-><init>(Ljava/io/File;Ljava/lang/String;)V
 HSPLdalvik/system/DexPathList$NativeLibraryElement;->equals(Ljava/lang/Object;)Z
@@ -1173,8 +1174,10 @@
 HSPLdalvik/system/SocketTagger;->set(Ldalvik/system/SocketTagger;)V
 HSPLdalvik/system/SocketTagger;->tag(Ljava/net/Socket;)V
 HSPLdalvik/system/SocketTagger;->untag(Ljava/net/Socket;)V
+HSPLdalvik/system/VMRuntime$SdkVersionContainer;->-$$Nest$sfgetsdkVersion()I
 HSPLdalvik/system/VMRuntime;->getInstructionSet(Ljava/lang/String;)Ljava/lang/String;
 HSPLdalvik/system/VMRuntime;->getRuntime()Ldalvik/system/VMRuntime;
+HSPLdalvik/system/VMRuntime;->getSdkVersion()I
 HSPLdalvik/system/VMRuntime;->getTargetSdkVersion()I
 HSPLdalvik/system/VMRuntime;->hiddenApiUsed(ILjava/lang/String;Ljava/lang/String;IZ)V
 HSPLdalvik/system/VMRuntime;->notifyNativeAllocation()V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
@@ -1186,6 +1189,10 @@
 HSPLdalvik/system/VMRuntime;->setHiddenApiUsageLogger(Ldalvik/system/VMRuntime$HiddenApiUsageLogger;)V
 HSPLdalvik/system/VMRuntime;->setNonSdkApiUsageConsumer(Ljava/util/function/Consumer;)V
 HSPLdalvik/system/VMRuntime;->setTargetSdkVersion(I)V
+HSPLdalvik/system/ZipPathValidator$Callback;->onZipEntryAccess(Ljava/lang/String;)V
+HSPLdalvik/system/ZipPathValidator;->clearCallback()V
+HSPLdalvik/system/ZipPathValidator;->getInstance()Ldalvik/system/ZipPathValidator$Callback;
+HSPLdalvik/system/ZipPathValidator;->setCallback(Ldalvik/system/ZipPathValidator$Callback;)V
 HSPLdalvik/system/ZygoteHooks;->cleanLocaleCaches()V
 HSPLdalvik/system/ZygoteHooks;->gcAndFinalize()V
 HSPLdalvik/system/ZygoteHooks;->isIndefiniteThreadSuspensionSafe()Z
@@ -1206,25 +1213,24 @@
 HSPLjava/io/Bits;->putInt([BII)V
 HSPLjava/io/Bits;->putLong([BIJ)V
 HSPLjava/io/Bits;->putShort([BIS)V
-HSPLjava/io/BufferedInputStream$$ExternalSyntheticBackportWithForwarding0;->m(Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
 HSPLjava/io/BufferedInputStream;-><init>(Ljava/io/InputStream;)V
 HSPLjava/io/BufferedInputStream;-><init>(Ljava/io/InputStream;I)V
 HSPLjava/io/BufferedInputStream;->available()I
 HSPLjava/io/BufferedInputStream;->close()V
-HSPLjava/io/BufferedInputStream;->fill()V+]Ljava/io/InputStream;Ljava/io/FileInputStream;
+HSPLjava/io/BufferedInputStream;->fill()V
 HSPLjava/io/BufferedInputStream;->getBufIfOpen()[B
 HSPLjava/io/BufferedInputStream;->getInIfOpen()Ljava/io/InputStream;
 HSPLjava/io/BufferedInputStream;->mark(I)V
 HSPLjava/io/BufferedInputStream;->markSupported()Z
 HSPLjava/io/BufferedInputStream;->read()I
-HSPLjava/io/BufferedInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/FileInputStream;
+HSPLjava/io/BufferedInputStream;->read([BII)I
 HSPLjava/io/BufferedInputStream;->read1([BII)I
 HSPLjava/io/BufferedInputStream;->reset()V
 HSPLjava/io/BufferedInputStream;->skip(J)J
 HSPLjava/io/BufferedOutputStream;-><init>(Ljava/io/OutputStream;)V
 HSPLjava/io/BufferedOutputStream;-><init>(Ljava/io/OutputStream;I)V
-HSPLjava/io/BufferedOutputStream;->flush()V
-HSPLjava/io/BufferedOutputStream;->flushBuffer()V
+HSPLjava/io/BufferedOutputStream;->flush()V+]Ljava/io/OutputStream;Ljava/io/FileOutputStream;
+HSPLjava/io/BufferedOutputStream;->flushBuffer()V+]Ljava/io/OutputStream;Ljava/io/FileOutputStream;
 HSPLjava/io/BufferedOutputStream;->write(I)V
 HSPLjava/io/BufferedOutputStream;->write([BII)V
 HSPLjava/io/BufferedReader;-><init>(Ljava/io/Reader;)V
@@ -1235,7 +1241,7 @@
 HSPLjava/io/BufferedReader;->read()I
 HSPLjava/io/BufferedReader;->read([CII)I
 HSPLjava/io/BufferedReader;->read1([CII)I
-HSPLjava/io/BufferedReader;->readLine()Ljava/lang/String;+]Ljava/io/BufferedReader;Ljava/io/BufferedReader;
+HSPLjava/io/BufferedReader;->readLine()Ljava/lang/String;
 HSPLjava/io/BufferedReader;->readLine(Z)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/io/BufferedWriter;-><init>(Ljava/io/Writer;)V
 HSPLjava/io/BufferedWriter;-><init>(Ljava/io/Writer;I)V
@@ -1247,7 +1253,7 @@
 HSPLjava/io/BufferedWriter;->newLine()V
 HSPLjava/io/BufferedWriter;->write(I)V
 HSPLjava/io/BufferedWriter;->write(Ljava/lang/String;II)V
-HSPLjava/io/BufferedWriter;->write([CII)V
+HSPLjava/io/BufferedWriter;->write([CII)V+]Ljava/io/BufferedWriter;Ljava/io/BufferedWriter;
 HSPLjava/io/ByteArrayInputStream;-><init>([B)V
 HSPLjava/io/ByteArrayInputStream;-><init>([BII)V
 HSPLjava/io/ByteArrayInputStream;->available()I
@@ -1283,37 +1289,37 @@
 HSPLjava/io/DataInputStream;->read([B)I
 HSPLjava/io/DataInputStream;->read([BII)I
 HSPLjava/io/DataInputStream;->readBoolean()Z
-HSPLjava/io/DataInputStream;->readByte()B+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
+HSPLjava/io/DataInputStream;->readByte()B
 HSPLjava/io/DataInputStream;->readFully([B)V
-HSPLjava/io/DataInputStream;->readFully([BII)V+]Ljava/io/InputStream;missing_types
-HSPLjava/io/DataInputStream;->readInt()I
+HSPLjava/io/DataInputStream;->readFully([BII)V+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/ByteArrayInputStream;,Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/DataInputStream;->readInt()I+]Ljava/io/DataInputStream;Ljava/io/DataInputStream;
 HSPLjava/io/DataInputStream;->readLong()J
-HSPLjava/io/DataInputStream;->readShort()S+]Ljava/io/DataInputStream;Ljava/io/DataInputStream;
+HSPLjava/io/DataInputStream;->readShort()S
 HSPLjava/io/DataInputStream;->readUTF()Ljava/lang/String;
-HSPLjava/io/DataInputStream;->readUTF(Ljava/io/DataInput;)Ljava/lang/String;+]Ljava/io/DataInput;Ljava/io/DataInputStream;
-HSPLjava/io/DataInputStream;->readUnsignedByte()I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
-HSPLjava/io/DataInputStream;->readUnsignedShort()I+]Ljava/io/DataInputStream;Ljava/io/DataInputStream;
+HSPLjava/io/DataInputStream;->readUTF(Ljava/io/DataInput;)Ljava/lang/String;
+HSPLjava/io/DataInputStream;->readUnsignedByte()I
+HSPLjava/io/DataInputStream;->readUnsignedShort()I
 HSPLjava/io/DataInputStream;->skipBytes(I)I
 HSPLjava/io/DataOutputStream;-><init>(Ljava/io/OutputStream;)V
 HSPLjava/io/DataOutputStream;->flush()V
 HSPLjava/io/DataOutputStream;->incCount(I)V
-HSPLjava/io/DataOutputStream;->write(I)V+]Ljava/io/OutputStream;Ljava/io/ByteArrayOutputStream;
-HSPLjava/io/DataOutputStream;->write([BII)V
+HSPLjava/io/DataOutputStream;->write(I)V
+HSPLjava/io/DataOutputStream;->write([BII)V+]Ljava/io/OutputStream;missing_types
 HSPLjava/io/DataOutputStream;->writeBoolean(Z)V
 HSPLjava/io/DataOutputStream;->writeByte(I)V
-HSPLjava/io/DataOutputStream;->writeInt(I)V+]Ljava/io/OutputStream;Ljava/io/ByteArrayOutputStream;
+HSPLjava/io/DataOutputStream;->writeInt(I)V+]Ljava/io/OutputStream;missing_types
 HSPLjava/io/DataOutputStream;->writeLong(J)V
-HSPLjava/io/DataOutputStream;->writeShort(I)V+]Ljava/io/OutputStream;Ljava/io/ByteArrayOutputStream;
+HSPLjava/io/DataOutputStream;->writeShort(I)V
 HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;)V
-HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;Ljava/io/DataOutput;)I
+HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;Ljava/io/DataOutput;)I+]Ljava/io/DataOutput;Ljava/io/DataOutputStream;
 HSPLjava/io/EOFException;-><init>()V
 HSPLjava/io/EOFException;-><init>(Ljava/lang/String;)V
 HSPLjava/io/ExpiringCache;->clear()V
 HSPLjava/io/File$TempDirectory;->generateFile(Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Ljava/io/File;
 HSPLjava/io/File;-><init>(Ljava/io/File;Ljava/lang/String;)V+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
-HSPLjava/io/File;-><init>(Ljava/lang/String;)V
+HSPLjava/io/File;-><init>(Ljava/lang/String;)V+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
 HSPLjava/io/File;-><init>(Ljava/lang/String;I)V
-HSPLjava/io/File;-><init>(Ljava/lang/String;Ljava/io/File;)V+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
+HSPLjava/io/File;-><init>(Ljava/lang/String;Ljava/io/File;)V
 HSPLjava/io/File;-><init>(Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/io/File;->canExecute()Z
 HSPLjava/io/File;->canRead()Z
@@ -1323,23 +1329,23 @@
 HSPLjava/io/File;->createNewFile()Z
 HSPLjava/io/File;->createTempFile(Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Ljava/io/File;
 HSPLjava/io/File;->delete()Z
-HSPLjava/io/File;->equals(Ljava/lang/Object;)Z+]Ljava/io/File;Ljava/io/File;
-HSPLjava/io/File;->exists()Z
+HSPLjava/io/File;->equals(Ljava/lang/Object;)Z
+HSPLjava/io/File;->exists()Z+]Ljava/io/File;Ljava/io/File;]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
 HSPLjava/io/File;->getAbsoluteFile()Ljava/io/File;
 HSPLjava/io/File;->getAbsolutePath()Ljava/lang/String;
 HSPLjava/io/File;->getCanonicalFile()Ljava/io/File;
 HSPLjava/io/File;->getCanonicalPath()Ljava/lang/String;
 HSPLjava/io/File;->getFreeSpace()J
 HSPLjava/io/File;->getName()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
-HSPLjava/io/File;->getParent()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
-HSPLjava/io/File;->getParentFile()Ljava/io/File;+]Ljava/io/File;Ljava/io/File;
+HSPLjava/io/File;->getParent()Ljava/lang/String;
+HSPLjava/io/File;->getParentFile()Ljava/io/File;
 HSPLjava/io/File;->getPath()Ljava/lang/String;
 HSPLjava/io/File;->getPrefixLength()I
 HSPLjava/io/File;->getTotalSpace()J
 HSPLjava/io/File;->getUsableSpace()J
 HSPLjava/io/File;->hashCode()I+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
 HSPLjava/io/File;->isAbsolute()Z
-HSPLjava/io/File;->isDirectory()Z+]Ljava/io/File;Ljava/io/File;]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
+HSPLjava/io/File;->isDirectory()Z
 HSPLjava/io/File;->isFile()Z
 HSPLjava/io/File;->isInvalid()Z
 HSPLjava/io/File;->lastModified()J
@@ -1373,7 +1379,7 @@
 HSPLjava/io/FileDescriptor;->setInt$(I)V
 HSPLjava/io/FileDescriptor;->setOwnerId$(J)V
 HSPLjava/io/FileDescriptor;->valid()Z
-HSPLjava/io/FileInputStream;-><init>(Ljava/io/File;)V
+HSPLjava/io/FileInputStream;-><init>(Ljava/io/File;)V+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard;
 HSPLjava/io/FileInputStream;-><init>(Ljava/io/FileDescriptor;)V
 HSPLjava/io/FileInputStream;-><init>(Ljava/io/FileDescriptor;Z)V
 HSPLjava/io/FileInputStream;-><init>(Ljava/lang/String;)V
@@ -1383,12 +1389,12 @@
 HSPLjava/io/FileInputStream;->getChannel()Ljava/nio/channels/FileChannel;
 HSPLjava/io/FileInputStream;->getFD()Ljava/io/FileDescriptor;
 HSPLjava/io/FileInputStream;->read()I
-HSPLjava/io/FileInputStream;->read([B)I+]Ljava/io/FileInputStream;Ljava/io/FileInputStream;,Landroid/os/ParcelFileDescriptor$AutoCloseInputStream;
+HSPLjava/io/FileInputStream;->read([B)I+]Ljava/io/FileInputStream;Ljava/io/FileInputStream;
 HSPLjava/io/FileInputStream;->read([BII)I+]Llibcore/io/IoTracker;Llibcore/io/IoTracker;
-HSPLjava/io/FileInputStream;->skip(J)J+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLjava/io/FileInputStream;->skip(J)J
 HSPLjava/io/FileNotFoundException;-><init>(Ljava/lang/String;)V
 HSPLjava/io/FileOutputStream;-><init>(Ljava/io/File;)V
-HSPLjava/io/FileOutputStream;-><init>(Ljava/io/File;Z)V+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard;
+HSPLjava/io/FileOutputStream;-><init>(Ljava/io/File;Z)V
 HSPLjava/io/FileOutputStream;-><init>(Ljava/io/FileDescriptor;)V
 HSPLjava/io/FileOutputStream;-><init>(Ljava/io/FileDescriptor;Z)V
 HSPLjava/io/FileOutputStream;-><init>(Ljava/lang/String;)V
@@ -1409,9 +1415,9 @@
 HSPLjava/io/FilterInputStream;->close()V
 HSPLjava/io/FilterInputStream;->mark(I)V
 HSPLjava/io/FilterInputStream;->markSupported()Z
-HSPLjava/io/FilterInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/ByteArrayInputStream;,Ljava/util/jar/JarVerifier$VerifierStream;,Ljava/io/PushbackInputStream;,Ljava/util/zip/InflaterInputStream;
-HSPLjava/io/FilterInputStream;->read([B)I+]Ljava/io/FilterInputStream;Ljava/security/DigestInputStream;,Ljava/util/zip/InflaterInputStream;
-HSPLjava/io/FilterInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;
+HSPLjava/io/FilterInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/PushbackInputStream;,Ljava/io/ByteArrayInputStream;,Ljava/io/FileInputStream;
+HSPLjava/io/FilterInputStream;->read([B)I
+HSPLjava/io/FilterInputStream;->read([BII)I+]Ljava/io/InputStream;missing_types
 HSPLjava/io/FilterInputStream;->reset()V
 HSPLjava/io/FilterInputStream;->skip(J)J
 HSPLjava/io/FilterOutputStream;-><init>(Ljava/io/OutputStream;)V
@@ -1441,15 +1447,15 @@
 HSPLjava/io/InterruptedIOException;-><init>()V
 HSPLjava/io/InterruptedIOException;-><init>(Ljava/lang/String;)V
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;-><init>(Ljava/io/ObjectInputStream;Ljava/io/InputStream;)V
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->close()V+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->close()V
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->currentBlockRemaining()I
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->getBlockDataMode()Z
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->peek()I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->peekByte()B+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read()I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read([BII)I
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read([BIIZ)I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBlockHeader(Z)I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read([BIIZ)I
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBlockHeader(Z)I
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBoolean()Z
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readByte()B+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readFloat()F
@@ -1457,7 +1463,7 @@
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readInt()I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;]Ljava/io/DataInputStream;Ljava/io/DataInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readLong()J+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readShort()S+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUTF()Ljava/lang/String;
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUTF()Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUTFBody(J)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUTFChar(Ljava/lang/StringBuilder;J)I
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUTFSpan(Ljava/lang/StringBuilder;J)J+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
@@ -1466,13 +1472,14 @@
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->setBlockDataMode(Z)Z
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->skipBlockData()V
 HSPLjava/io/ObjectInputStream$GetField;-><init>()V
-HSPLjava/io/ObjectInputStream$GetFieldImpl;-><init>(Ljava/io/ObjectInputStream;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;
+HSPLjava/io/ObjectInputStream$GetFieldImpl;-><init>(Ljava/io/ObjectInputStream;Ljava/io/ObjectStreamClass;)V
+HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;D)D
 HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;I)I
 HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;J)J
-HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
+HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;Z)Z
-HSPLjava/io/ObjectInputStream$GetFieldImpl;->getFieldOffset(Ljava/lang/String;Ljava/lang/Class;)I+]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;
-HSPLjava/io/ObjectInputStream$GetFieldImpl;->readFields()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;
+HSPLjava/io/ObjectInputStream$GetFieldImpl;->getFieldOffset(Ljava/lang/String;Ljava/lang/Class;)I
+HSPLjava/io/ObjectInputStream$GetFieldImpl;->readFields()V
 HSPLjava/io/ObjectInputStream$HandleTable$HandleList;-><init>()V
 HSPLjava/io/ObjectInputStream$HandleTable$HandleList;->add(I)V
 HSPLjava/io/ObjectInputStream$HandleTable;-><init>(I)V
@@ -1482,48 +1489,48 @@
 HSPLjava/io/ObjectInputStream$HandleTable;->grow()V
 HSPLjava/io/ObjectInputStream$HandleTable;->lookupException(I)Ljava/lang/ClassNotFoundException;
 HSPLjava/io/ObjectInputStream$HandleTable;->lookupObject(I)Ljava/lang/Object;
-HSPLjava/io/ObjectInputStream$HandleTable;->markDependency(II)V
+HSPLjava/io/ObjectInputStream$HandleTable;->markDependency(II)V+]Ljava/io/ObjectInputStream$HandleTable$HandleList;Ljava/io/ObjectInputStream$HandleTable$HandleList;
 HSPLjava/io/ObjectInputStream$HandleTable;->setObject(ILjava/lang/Object;)V
 HSPLjava/io/ObjectInputStream$HandleTable;->size()I
 HSPLjava/io/ObjectInputStream$PeekInputStream;-><init>(Ljava/io/InputStream;)V
-HSPLjava/io/ObjectInputStream$PeekInputStream;->close()V
-HSPLjava/io/ObjectInputStream$PeekInputStream;->peek()I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
+HSPLjava/io/ObjectInputStream$PeekInputStream;->close()V+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
+HSPLjava/io/ObjectInputStream$PeekInputStream;->peek()I+]Ljava/io/InputStream;missing_types
 HSPLjava/io/ObjectInputStream$PeekInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
-HSPLjava/io/ObjectInputStream$PeekInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
+HSPLjava/io/ObjectInputStream$PeekInputStream;->read([BII)I+]Ljava/io/InputStream;missing_types
 HSPLjava/io/ObjectInputStream$PeekInputStream;->readFully([BII)V+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
 HSPLjava/io/ObjectInputStream$ValidationList;-><init>()V
 HSPLjava/io/ObjectInputStream$ValidationList;->clear()V
 HSPLjava/io/ObjectInputStream$ValidationList;->doCallbacks()V
-HSPLjava/io/ObjectInputStream;-><init>(Ljava/io/InputStream;)V+]Ljava/io/ObjectInputStream;Ljava/io/ObjectInputStream;,Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/ObjectInputStream;-><init>(Ljava/io/InputStream;)V+]Ljava/io/ObjectInputStream;Ljava/io/ObjectInputStream;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/ObjectInputStream;->checkResolve(Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/io/ObjectInputStream;->clear()V
+HSPLjava/io/ObjectInputStream;->clear()V+]Ljava/io/ObjectInputStream$ValidationList;Ljava/io/ObjectInputStream$ValidationList;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
 HSPLjava/io/ObjectInputStream;->close()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
-HSPLjava/io/ObjectInputStream;->defaultReadFields(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V
+HSPLjava/io/ObjectInputStream;->defaultReadFields(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/io/ObjectInputStream;->defaultReadObject()V
 HSPLjava/io/ObjectInputStream;->isCustomSubclass()Z
 HSPLjava/io/ObjectInputStream;->latestUserDefinedLoader()Ljava/lang/ClassLoader;
 HSPLjava/io/ObjectInputStream;->readArray(Z)Ljava/lang/Object;
 HSPLjava/io/ObjectInputStream;->readBoolean()Z
 HSPLjava/io/ObjectInputStream;->readByte()B
-HSPLjava/io/ObjectInputStream;->readClassDesc(Z)Ljava/io/ObjectStreamClass;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/ObjectInputStream;->readClassDesc(Z)Ljava/io/ObjectStreamClass;
 HSPLjava/io/ObjectInputStream;->readClassDescriptor()Ljava/io/ObjectStreamClass;
 HSPLjava/io/ObjectInputStream;->readEnum(Z)Ljava/lang/Enum;
-HSPLjava/io/ObjectInputStream;->readFields()Ljava/io/ObjectInputStream$GetField;+]Ljava/io/ObjectInputStream$GetFieldImpl;Ljava/io/ObjectInputStream$GetFieldImpl;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/SerialCallbackContext;Ljava/io/SerialCallbackContext;
+HSPLjava/io/ObjectInputStream;->readFields()Ljava/io/ObjectInputStream$GetField;
 HSPLjava/io/ObjectInputStream;->readFloat()F
 HSPLjava/io/ObjectInputStream;->readHandle(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
-HSPLjava/io/ObjectInputStream;->readInt()I
+HSPLjava/io/ObjectInputStream;->readInt()I+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/ObjectInputStream;->readLong()J
-HSPLjava/io/ObjectInputStream;->readNonProxyDesc(Z)Ljava/io/ObjectStreamClass;+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
+HSPLjava/io/ObjectInputStream;->readNonProxyDesc(Z)Ljava/io/ObjectStreamClass;+]Ljava/io/ObjectInputStream;Ljava/io/ObjectInputStream;,Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
 HSPLjava/io/ObjectInputStream;->readNull()Ljava/lang/Object;
-HSPLjava/io/ObjectInputStream;->readObject()Ljava/lang/Object;+]Ljava/io/ObjectInputStream$ValidationList;Ljava/io/ObjectInputStream$ValidationList;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
+HSPLjava/io/ObjectInputStream;->readObject()Ljava/lang/Object;
 HSPLjava/io/ObjectInputStream;->readObject0(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/ObjectInputStream;->readOrdinaryObject(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
 HSPLjava/io/ObjectInputStream;->readSerialData(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;]Ljava/io/SerialCallbackContext;Ljava/io/SerialCallbackContext;
 HSPLjava/io/ObjectInputStream;->readShort()S
 HSPLjava/io/ObjectInputStream;->readStreamHeader()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/ObjectInputStream;->readString(Z)Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
-HSPLjava/io/ObjectInputStream;->readTypeString()Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
-HSPLjava/io/ObjectInputStream;->readUTF()Ljava/lang/String;
+HSPLjava/io/ObjectInputStream;->readTypeString()Ljava/lang/String;
+HSPLjava/io/ObjectInputStream;->readUTF()Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/ObjectInputStream;->resolveClass(Ljava/io/ObjectStreamClass;)Ljava/lang/Class;
 HSPLjava/io/ObjectInputStream;->skipCustomData()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/ObjectInputStream;->verifySubclass()V
@@ -1543,7 +1550,7 @@
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeLong(J)V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeShort(I)V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeUTF(Ljava/lang/String;)V
-HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeUTF(Ljava/lang/String;J)V+]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream;
+HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeUTF(Ljava/lang/String;J)V
 HSPLjava/io/ObjectOutputStream$HandleTable;-><init>(IF)V
 HSPLjava/io/ObjectOutputStream$HandleTable;->assign(Ljava/lang/Object;)I
 HSPLjava/io/ObjectOutputStream$HandleTable;->clear()V
@@ -1580,7 +1587,7 @@
 HSPLjava/io/ObjectOutputStream;->writeEnum(Ljava/lang/Enum;Ljava/io/ObjectStreamClass;Z)V
 HSPLjava/io/ObjectOutputStream;->writeFields()V
 HSPLjava/io/ObjectOutputStream;->writeFloat(F)V
-HSPLjava/io/ObjectOutputStream;->writeHandle(I)V
+HSPLjava/io/ObjectOutputStream;->writeHandle(I)V+]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream;
 HSPLjava/io/ObjectOutputStream;->writeInt(I)V
 HSPLjava/io/ObjectOutputStream;->writeLong(J)V
 HSPLjava/io/ObjectOutputStream;->writeNonProxyDesc(Ljava/io/ObjectStreamClass;Z)V
@@ -1622,8 +1629,8 @@
 HSPLjava/io/ObjectStreamClass$FieldReflector;->getFields()[Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass$FieldReflector;->getObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V
 HSPLjava/io/ObjectStreamClass$FieldReflector;->getPrimFieldValues(Ljava/lang/Object;[B)V
-HSPLjava/io/ObjectStreamClass$FieldReflector;->setObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V
-HSPLjava/io/ObjectStreamClass$FieldReflector;->setPrimFieldValues(Ljava/lang/Object;[B)V
+HSPLjava/io/ObjectStreamClass$FieldReflector;->setObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectStreamClass$FieldReflector;->setPrimFieldValues(Ljava/lang/Object;[B)V+]Lsun/misc/Unsafe;Lsun/misc/Unsafe;
 HSPLjava/io/ObjectStreamClass$FieldReflectorKey;-><init>(Ljava/lang/Class;[Ljava/io/ObjectStreamField;Ljava/lang/ref/ReferenceQueue;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass$FieldReflectorKey;->equals(Ljava/lang/Object;)Z+]Ljava/io/ObjectStreamClass$FieldReflectorKey;Ljava/io/ObjectStreamClass$FieldReflectorKey;
 HSPLjava/io/ObjectStreamClass$FieldReflectorKey;->hashCode()I
@@ -1656,7 +1663,7 @@
 HSPLjava/io/ObjectStreamClass;->checkDefaultSerialize()V
 HSPLjava/io/ObjectStreamClass;->checkDeserialize()V
 HSPLjava/io/ObjectStreamClass;->checkSerialize()V
-HSPLjava/io/ObjectStreamClass;->classNamesEqual(Ljava/lang/String;Ljava/lang/String;)Z
+HSPLjava/io/ObjectStreamClass;->classNamesEqual(Ljava/lang/String;Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/io/ObjectStreamClass;->computeDefaultSUID(Ljava/lang/Class;)J
 HSPLjava/io/ObjectStreamClass;->computeFieldOffsets()V+]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass;->forClass()Ljava/lang/Class;
@@ -1666,7 +1673,7 @@
 HSPLjava/io/ObjectStreamClass;->getDeclaredSUID(Ljava/lang/Class;)Ljava/lang/Long;
 HSPLjava/io/ObjectStreamClass;->getDeclaredSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass;->getDefaultSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField;
-HSPLjava/io/ObjectStreamClass;->getField(Ljava/lang/String;Ljava/lang/Class;)Ljava/io/ObjectStreamField;+]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectStreamClass;->getField(Ljava/lang/String;Ljava/lang/Class;)Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass;->getFields(Z)[Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass;->getInheritableMethod(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Method;
 HSPLjava/io/ObjectStreamClass;->getMethodSignature([Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/String;
@@ -1677,10 +1684,10 @@
 HSPLjava/io/ObjectStreamClass;->getPrimDataSize()I
 HSPLjava/io/ObjectStreamClass;->getPrimFieldValues(Ljava/lang/Object;[B)V
 HSPLjava/io/ObjectStreamClass;->getPrivateMethod(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Method;
-HSPLjava/io/ObjectStreamClass;->getReflector([Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamClass;)Ljava/io/ObjectStreamClass$FieldReflector;+]Ljava/lang/ref/Reference;Ljava/lang/ref/SoftReference;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/io/ObjectStreamClass$EntryFuture;Ljava/io/ObjectStreamClass$EntryFuture;
+HSPLjava/io/ObjectStreamClass;->getReflector([Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamClass;)Ljava/io/ObjectStreamClass$FieldReflector;+]Ljava/lang/ref/Reference;Ljava/lang/ref/SoftReference;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;
 HSPLjava/io/ObjectStreamClass;->getResolveException()Ljava/lang/ClassNotFoundException;
 HSPLjava/io/ObjectStreamClass;->getSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField;
-HSPLjava/io/ObjectStreamClass;->getSerialVersionUID()J
+HSPLjava/io/ObjectStreamClass;->getSerialVersionUID()J+]Ljava/lang/Long;Ljava/lang/Long;
 HSPLjava/io/ObjectStreamClass;->getSerializableConstructor(Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
 HSPLjava/io/ObjectStreamClass;->getSuperDesc()Ljava/io/ObjectStreamClass;
 HSPLjava/io/ObjectStreamClass;->getVariantFor(Ljava/lang/Class;)Ljava/io/ObjectStreamClass;
@@ -1689,7 +1696,7 @@
 HSPLjava/io/ObjectStreamClass;->hasWriteObjectData()Z
 HSPLjava/io/ObjectStreamClass;->hasWriteObjectMethod()Z
 HSPLjava/io/ObjectStreamClass;->hasWriteReplaceMethod()Z
-HSPLjava/io/ObjectStreamClass;->initNonProxy(Ljava/io/ObjectStreamClass;Ljava/lang/Class;Ljava/lang/ClassNotFoundException;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamClass$FieldReflector;Ljava/io/ObjectStreamClass$FieldReflector;]Ljava/lang/Long;Ljava/lang/Long;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectStreamClass;->initNonProxy(Ljava/io/ObjectStreamClass;Ljava/lang/Class;Ljava/lang/ClassNotFoundException;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass$FieldReflector;Ljava/io/ObjectStreamClass$FieldReflector;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/lang/Long;Ljava/lang/Long;]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/io/ObjectStreamClass;->invokeReadObject(Ljava/lang/Object;Ljava/io/ObjectInputStream;)V
 HSPLjava/io/ObjectStreamClass;->invokeReadResolve(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/io/ObjectStreamClass;->invokeWriteObject(Ljava/lang/Object;Ljava/io/ObjectOutputStream;)V
@@ -1703,7 +1710,7 @@
 HSPLjava/io/ObjectStreamClass;->newInstance()Ljava/lang/Object;
 HSPLjava/io/ObjectStreamClass;->packageEquals(Ljava/lang/Class;Ljava/lang/Class;)Z
 HSPLjava/io/ObjectStreamClass;->processQueue(Ljava/lang/ref/ReferenceQueue;Ljava/util/concurrent/ConcurrentMap;)V
-HSPLjava/io/ObjectStreamClass;->readNonProxy(Ljava/io/ObjectInputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;
+HSPLjava/io/ObjectStreamClass;->readNonProxy(Ljava/io/ObjectInputStream;)V+]Ljava/io/ObjectInputStream;Ljava/io/ObjectInputStream;,Landroid/os/Parcel$2;
 HSPLjava/io/ObjectStreamClass;->requireInitialized()V
 HSPLjava/io/ObjectStreamClass;->setObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V
 HSPLjava/io/ObjectStreamClass;->setPrimFieldValues(Ljava/lang/Object;[B)V
@@ -1748,27 +1755,27 @@
 HSPLjava/io/PrintWriter;-><init>(Ljava/io/Writer;)V
 HSPLjava/io/PrintWriter;-><init>(Ljava/io/Writer;Z)V
 HSPLjava/io/PrintWriter;->append(C)Ljava/io/PrintWriter;
-HSPLjava/io/PrintWriter;->append(Ljava/lang/CharSequence;)Ljava/io/PrintWriter;
+HSPLjava/io/PrintWriter;->append(Ljava/lang/CharSequence;)Ljava/io/PrintWriter;+]Ljava/io/PrintWriter;Lcom/android/internal/util/FastPrintWriter;
 HSPLjava/io/PrintWriter;->append(Ljava/lang/CharSequence;)Ljava/lang/Appendable;
 HSPLjava/io/PrintWriter;->close()V
 HSPLjava/io/PrintWriter;->ensureOpen()V
-HSPLjava/io/PrintWriter;->flush()V
+HSPLjava/io/PrintWriter;->flush()V+]Ljava/io/Writer;Landroid/util/Log$ImmediateLogWriter;
 HSPLjava/io/PrintWriter;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;
-HSPLjava/io/PrintWriter;->newLine()V+]Ljava/io/Writer;Ljava/io/StringWriter;
+HSPLjava/io/PrintWriter;->newLine()V+]Ljava/io/Writer;missing_types
 HSPLjava/io/PrintWriter;->print(C)V
 HSPLjava/io/PrintWriter;->print(I)V
 HSPLjava/io/PrintWriter;->print(J)V
-HSPLjava/io/PrintWriter;->print(Ljava/lang/String;)V
+HSPLjava/io/PrintWriter;->print(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Ljava/io/PrintWriter;
 HSPLjava/io/PrintWriter;->print(Z)V
 HSPLjava/io/PrintWriter;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;
 HSPLjava/io/PrintWriter;->println()V
 HSPLjava/io/PrintWriter;->println(I)V
-HSPLjava/io/PrintWriter;->println(Ljava/lang/Object;)V
-HSPLjava/io/PrintWriter;->println(Ljava/lang/String;)V
+HSPLjava/io/PrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;
+HSPLjava/io/PrintWriter;->println(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Ljava/io/PrintWriter;
 HSPLjava/io/PrintWriter;->write(I)V
-HSPLjava/io/PrintWriter;->write(Ljava/lang/String;)V
+HSPLjava/io/PrintWriter;->write(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Ljava/io/PrintWriter;
 HSPLjava/io/PrintWriter;->write(Ljava/lang/String;II)V
-HSPLjava/io/PrintWriter;->write([CII)V
+HSPLjava/io/PrintWriter;->write([CII)V+]Ljava/io/Writer;Landroid/util/Log$ImmediateLogWriter;
 HSPLjava/io/PushbackInputStream;-><init>(Ljava/io/InputStream;I)V
 HSPLjava/io/PushbackInputStream;->close()V
 HSPLjava/io/PushbackInputStream;->ensureOpen()V
@@ -1793,7 +1800,7 @@
 HSPLjava/io/RandomAccessFile;->read([B)I
 HSPLjava/io/RandomAccessFile;->read([BII)I
 HSPLjava/io/RandomAccessFile;->readByte()B
-HSPLjava/io/RandomAccessFile;->readBytes([BII)I
+HSPLjava/io/RandomAccessFile;->readBytes([BII)I+]Llibcore/io/IoTracker;Llibcore/io/IoTracker;
 HSPLjava/io/RandomAccessFile;->readFully([B)V
 HSPLjava/io/RandomAccessFile;->readFully([BII)V
 HSPLjava/io/RandomAccessFile;->readInt()I
@@ -1829,35 +1836,35 @@
 HSPLjava/io/StringWriter;-><init>(I)V
 HSPLjava/io/StringWriter;->append(C)Ljava/io/StringWriter;
 HSPLjava/io/StringWriter;->append(C)Ljava/io/Writer;
-HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/StringWriter;
-HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/Writer;
+HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/StringWriter;+]Ljava/io/StringWriter;Ljava/io/StringWriter;
+HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/Writer;+]Ljava/io/StringWriter;Ljava/io/StringWriter;
 HSPLjava/io/StringWriter;->close()V
 HSPLjava/io/StringWriter;->flush()V
 HSPLjava/io/StringWriter;->getBuffer()Ljava/lang/StringBuffer;
 HSPLjava/io/StringWriter;->toString()Ljava/lang/String;
-HSPLjava/io/StringWriter;->write(I)V
-HSPLjava/io/StringWriter;->write(Ljava/lang/String;)V
-HSPLjava/io/StringWriter;->write(Ljava/lang/String;II)V
+HSPLjava/io/StringWriter;->write(I)V+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
+HSPLjava/io/StringWriter;->write(Ljava/lang/String;)V+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
+HSPLjava/io/StringWriter;->write(Ljava/lang/String;II)V+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
 HSPLjava/io/StringWriter;->write([CII)V
 HSPLjava/io/UnixFileSystem;->canonicalize(Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/io/UnixFileSystem;->checkAccess(Ljava/io/File;I)Z
+HSPLjava/io/UnixFileSystem;->checkAccess(Ljava/io/File;I)Z+]Ljava/io/File;Ljava/io/File;]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
 HSPLjava/io/UnixFileSystem;->compare(Ljava/io/File;Ljava/io/File;)I+]Ljava/io/File;Ljava/io/File;
 HSPLjava/io/UnixFileSystem;->createDirectory(Ljava/io/File;)Z
 HSPLjava/io/UnixFileSystem;->createFileExclusively(Ljava/lang/String;)Z
 HSPLjava/io/UnixFileSystem;->delete(Ljava/io/File;)Z
-HSPLjava/io/UnixFileSystem;->getBooleanAttributes(Ljava/io/File;)I+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
+HSPLjava/io/UnixFileSystem;->getBooleanAttributes(Ljava/io/File;)I+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
 HSPLjava/io/UnixFileSystem;->getDefaultParent()Ljava/lang/String;
-HSPLjava/io/UnixFileSystem;->getLastModifiedTime(Ljava/io/File;)J+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
-HSPLjava/io/UnixFileSystem;->getLength(Ljava/io/File;)J+]Ljava/io/File;Ljava/io/File;]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
+HSPLjava/io/UnixFileSystem;->getLastModifiedTime(Ljava/io/File;)J
+HSPLjava/io/UnixFileSystem;->getLength(Ljava/io/File;)J
 HSPLjava/io/UnixFileSystem;->getSpace(Ljava/io/File;I)J
 HSPLjava/io/UnixFileSystem;->hashCode(Ljava/io/File;)I+]Ljava/lang/String;Ljava/lang/String;]Ljava/io/File;Ljava/io/File;
 HSPLjava/io/UnixFileSystem;->isAbsolute(Ljava/io/File;)Z
-HSPLjava/io/UnixFileSystem;->list(Ljava/io/File;)[Ljava/lang/String;+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;]Ldalvik/system/BlockGuard$Policy;Landroid/os/StrictMode$AndroidBlockGuardPolicy;,Ldalvik/system/BlockGuard$1;
+HSPLjava/io/UnixFileSystem;->list(Ljava/io/File;)[Ljava/lang/String;+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
 HSPLjava/io/UnixFileSystem;->normalize(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/io/UnixFileSystem;->prefixLength(Ljava/lang/String;)I
 HSPLjava/io/UnixFileSystem;->rename(Ljava/io/File;Ljava/io/File;)Z
 HSPLjava/io/UnixFileSystem;->resolve(Ljava/io/File;)Ljava/lang/String;
-HSPLjava/io/UnixFileSystem;->resolve(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+HSPLjava/io/UnixFileSystem;->resolve(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/io/UnixFileSystem;->setLastModifiedTime(Ljava/io/File;J)Z
 HSPLjava/io/UnixFileSystem;->setPermission(Ljava/io/File;IZZ)Z
 HSPLjava/io/Writer;-><init>()V
@@ -1866,49 +1873,58 @@
 HSPLjava/io/Writer;->append(Ljava/lang/CharSequence;)Ljava/io/Writer;
 HSPLjava/io/Writer;->write(Ljava/lang/String;)V
 HSPLjava/lang/AbstractStringBuilder;-><init>(I)V
-HSPLjava/lang/AbstractStringBuilder;->append(C)Ljava/lang/AbstractStringBuilder;
+HSPLjava/lang/AbstractStringBuilder;->append(C)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
 HSPLjava/lang/AbstractStringBuilder;->append(D)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->append(F)Ljava/lang/AbstractStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->append(I)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->append(J)Ljava/lang/AbstractStringBuilder;
+HSPLjava/lang/AbstractStringBuilder;->append(I)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
+HSPLjava/lang/AbstractStringBuilder;->append(J)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/AbstractStringBuilder;)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/CharSequence;Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
-HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/CharSequence;Ljava/lang/String;,Landroid/icu/impl/FormattedStringBuilder;,Ljava/nio/HeapCharBuffer;
+HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/StringBuffer;)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->append(Z)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->append([CII)Ljava/lang/AbstractStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->appendCodePoint(I)Ljava/lang/AbstractStringBuilder;
+HSPLjava/lang/AbstractStringBuilder;->appendChars(Ljava/lang/CharSequence;II)V+]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
+HSPLjava/lang/AbstractStringBuilder;->appendChars([CII)V+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
+HSPLjava/lang/AbstractStringBuilder;->appendCodePoint(I)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->appendNull()Ljava/lang/AbstractStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->charAt(I)C
+HSPLjava/lang/AbstractStringBuilder;->charAt(I)C+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
+HSPLjava/lang/AbstractStringBuilder;->checkRange(III)V
+HSPLjava/lang/AbstractStringBuilder;->checkRangeSIOOBE(III)V
 HSPLjava/lang/AbstractStringBuilder;->codePointAt(I)I
 HSPLjava/lang/AbstractStringBuilder;->delete(II)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->deleteCharAt(I)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->ensureCapacity(I)V
 HSPLjava/lang/AbstractStringBuilder;->ensureCapacityInternal(I)V
+HSPLjava/lang/AbstractStringBuilder;->getBytes([BIB)V
 HSPLjava/lang/AbstractStringBuilder;->getChars(II[CI)V
+HSPLjava/lang/AbstractStringBuilder;->getCoder()B
 HSPLjava/lang/AbstractStringBuilder;->indexOf(Ljava/lang/String;)I
 HSPLjava/lang/AbstractStringBuilder;->indexOf(Ljava/lang/String;I)I
-HSPLjava/lang/AbstractStringBuilder;->insert(IC)Ljava/lang/AbstractStringBuilder;
+HSPLjava/lang/AbstractStringBuilder;->insert(IC)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->insert(II)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->insert(ILjava/lang/String;)Ljava/lang/AbstractStringBuilder;
+HSPLjava/lang/AbstractStringBuilder;->isLatin1()Z
 HSPLjava/lang/AbstractStringBuilder;->lastIndexOf(Ljava/lang/String;I)I
 HSPLjava/lang/AbstractStringBuilder;->length()I
 HSPLjava/lang/AbstractStringBuilder;->newCapacity(I)I
+HSPLjava/lang/AbstractStringBuilder;->putStringAt(ILjava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
 HSPLjava/lang/AbstractStringBuilder;->replace(IILjava/lang/String;)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->reverse()Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->setCharAt(IC)V
 HSPLjava/lang/AbstractStringBuilder;->setLength(I)V
+HSPLjava/lang/AbstractStringBuilder;->shift(II)V
 HSPLjava/lang/AbstractStringBuilder;->subSequence(II)Ljava/lang/CharSequence;
 HSPLjava/lang/AbstractStringBuilder;->substring(I)Ljava/lang/String;
-HSPLjava/lang/AbstractStringBuilder;->substring(II)Ljava/lang/String;
+HSPLjava/lang/AbstractStringBuilder;->substring(II)Ljava/lang/String;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/ArrayIndexOutOfBoundsException;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Boolean;-><init>(Z)V
 HSPLjava/lang/Boolean;->booleanValue()Z
 HSPLjava/lang/Boolean;->compare(ZZ)I
 HSPLjava/lang/Boolean;->compareTo(Ljava/lang/Boolean;)I
 HSPLjava/lang/Boolean;->compareTo(Ljava/lang/Object;)I
-HSPLjava/lang/Boolean;->equals(Ljava/lang/Object;)Z
+HSPLjava/lang/Boolean;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Boolean;Ljava/lang/Boolean;
 HSPLjava/lang/Boolean;->getBoolean(Ljava/lang/String;)Z
 HSPLjava/lang/Boolean;->hashCode()I
 HSPLjava/lang/Boolean;->hashCode(Z)I
@@ -1944,7 +1960,7 @@
 HSPLjava/lang/Character;-><init>(C)V
 HSPLjava/lang/Character;->charCount(I)I
 HSPLjava/lang/Character;->charValue()C
-HSPLjava/lang/Character;->codePointAt(Ljava/lang/CharSequence;I)I+]Ljava/lang/CharSequence;Landroid/text/method/ReplacementTransformationMethod$SpannedReplacementCharSequence;,Ljava/lang/StringBuilder;,Ljava/lang/String;,Landroid/text/SpannedString;
+HSPLjava/lang/Character;->codePointAt(Ljava/lang/CharSequence;I)I+]Ljava/lang/CharSequence;megamorphic_types
 HSPLjava/lang/Character;->codePointAtImpl([CII)I
 HSPLjava/lang/Character;->codePointBefore(Ljava/lang/CharSequence;I)I
 HSPLjava/lang/Character;->codePointCount(Ljava/lang/CharSequence;II)I
@@ -1998,19 +2014,19 @@
 HSPLjava/lang/Character;->toUpperCase(I)I
 HSPLjava/lang/Character;->valueOf(C)Ljava/lang/Character;
 HSPLjava/lang/Class;->asSubclass(Ljava/lang/Class;)Ljava/lang/Class;
-HSPLjava/lang/Class;->cast(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/lang/Class;->cast(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->classNameImpliesTopLevel()Z+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->desiredAssertionStatus()Z
 HSPLjava/lang/Class;->findInterfaceMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
 HSPLjava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;
 HSPLjava/lang/Class;->forName(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;
 HSPLjava/lang/Class;->getAccessFlags()I
-HSPLjava/lang/Class;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
+HSPLjava/lang/Class;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getCanonicalName()Ljava/lang/String;
 HSPLjava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getComponentType()Ljava/lang/Class;
 HSPLjava/lang/Class;->getConstructor([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
-HSPLjava/lang/Class;->getConstructor0([Ljava/lang/Class;I)Ljava/lang/reflect/Constructor;
+HSPLjava/lang/Class;->getConstructor0([Ljava/lang/Class;I)Ljava/lang/reflect/Constructor;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor;
 HSPLjava/lang/Class;->getConstructors()[Ljava/lang/reflect/Constructor;
 HSPLjava/lang/Class;->getDeclaredConstructor([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
 HSPLjava/lang/Class;->getDeclaredConstructors()[Ljava/lang/reflect/Constructor;
@@ -2023,12 +2039,12 @@
 HSPLjava/lang/Class;->getField(Ljava/lang/String;)Ljava/lang/reflect/Field;
 HSPLjava/lang/Class;->getFields()[Ljava/lang/reflect/Field;
 HSPLjava/lang/Class;->getGenericInterfaces()[Ljava/lang/reflect/Type;
-HSPLjava/lang/Class;->getGenericSuperclass()Ljava/lang/reflect/Type;
+HSPLjava/lang/Class;->getGenericSuperclass()Ljava/lang/reflect/Type;+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getInterfaces()[Ljava/lang/Class;
 HSPLjava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
 HSPLjava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;Z)Ljava/lang/reflect/Method;
 HSPLjava/lang/Class;->getMethods()[Ljava/lang/reflect/Method;
-HSPLjava/lang/Class;->getModifiers()I
+HSPLjava/lang/Class;->getModifiers()I+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getName()Ljava/lang/String;
 HSPLjava/lang/Class;->getPackage()Ljava/lang/Package;
 HSPLjava/lang/Class;->getPackageName()Ljava/lang/String;
@@ -2037,7 +2053,7 @@
 HSPLjava/lang/Class;->getPublicMethodRecursive(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
 HSPLjava/lang/Class;->getPublicMethodsInternal(Ljava/util/List;)V
 HSPLjava/lang/Class;->getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;
-HSPLjava/lang/Class;->getSignatureAttribute()Ljava/lang/String;
+HSPLjava/lang/Class;->getSignatureAttribute()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/Class;->getSimpleName()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getSuperclass()Ljava/lang/Class;+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getTypeName()Ljava/lang/String;
@@ -2051,7 +2067,7 @@
 HSPLjava/lang/Class;->isInterface()Z
 HSPLjava/lang/Class;->isLocalClass()Z+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->isLocalOrAnonymousClass()Z
-HSPLjava/lang/Class;->isMemberClass()Z+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Class;->isMemberClass()Z
 HSPLjava/lang/Class;->isPrimitive()Z
 HSPLjava/lang/Class;->isProxy()Z
 HSPLjava/lang/Class;->resolveName(Ljava/lang/String;)Ljava/lang/String;
@@ -2083,19 +2099,19 @@
 HSPLjava/lang/Daemons$Daemon;->stop()V
 HSPLjava/lang/Daemons$FinalizerDaemon;->-$$Nest$fgetprogressCounter(Ljava/lang/Daemons$FinalizerDaemon;)Ljava/util/concurrent/atomic/AtomicInteger;
 HSPLjava/lang/Daemons$FinalizerDaemon;->-$$Nest$sfgetINSTANCE()Ljava/lang/Daemons$FinalizerDaemon;
-HSPLjava/lang/Daemons$FinalizerDaemon;->doFinalize(Ljava/lang/ref/FinalizerReference;)V+]Ljava/lang/Object;megamorphic_types]Ljava/lang/ref/FinalizerReference;Ljava/lang/ref/FinalizerReference;
+HSPLjava/lang/Daemons$FinalizerDaemon;->doFinalize(Ljava/lang/ref/FinalizerReference;)V+]Ljava/lang/Object;missing_types]Ljava/lang/ref/FinalizerReference;Ljava/lang/ref/FinalizerReference;
 HSPLjava/lang/Daemons$FinalizerDaemon;->runInternal()V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->-$$Nest$mmonitoringNeeded(Ljava/lang/Daemons$FinalizerWatchdogDaemon;I)V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->-$$Nest$mmonitoringNotNeeded(Ljava/lang/Daemons$FinalizerWatchdogDaemon;I)V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->-$$Nest$sfgetINSTANCE()Ljava/lang/Daemons$FinalizerWatchdogDaemon;
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->isActive(I)Z
-HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->monitoringNeeded(I)V+]Ljava/lang/Object;Ljava/lang/Daemons$FinalizerWatchdogDaemon;
+HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->monitoringNeeded(I)V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->monitoringNotNeeded(I)V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->resetTimeouts()V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->runInternal()V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->sleepForNanos(J)Z
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->sleepUntilNeeded()Z
-HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->waitForProgress()Ljava/util/concurrent/TimeoutException;+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->waitForProgress()Ljava/util/concurrent/TimeoutException;
 HSPLjava/lang/Daemons$HeapTaskDaemon;->interrupt(Ljava/lang/Thread;)V
 HSPLjava/lang/Daemons$HeapTaskDaemon;->runInternal()V
 HSPLjava/lang/Daemons$ReferenceQueueDaemon;->-$$Nest$fgetprogressCounter(Ljava/lang/Daemons$ReferenceQueueDaemon;)Ljava/util/concurrent/atomic/AtomicInteger;
@@ -2116,6 +2132,7 @@
 HSPLjava/lang/Double;->hashCode(D)I
 HSPLjava/lang/Double;->intValue()I
 HSPLjava/lang/Double;->isInfinite(D)Z
+HSPLjava/lang/Double;->isNaN()Z
 HSPLjava/lang/Double;->isNaN(D)Z
 HSPLjava/lang/Double;->longValue()J
 HSPLjava/lang/Double;->parseDouble(Ljava/lang/String;)D
@@ -2131,13 +2148,13 @@
 HSPLjava/lang/Enum;->compareTo(Ljava/lang/Object;)I
 HSPLjava/lang/Enum;->enumValues(Ljava/lang/Class;)[Ljava/lang/Object;
 HSPLjava/lang/Enum;->equals(Ljava/lang/Object;)Z
-HSPLjava/lang/Enum;->getDeclaringClass()Ljava/lang/Class;+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Enum;->getDeclaringClass()Ljava/lang/Class;
 HSPLjava/lang/Enum;->getSharedConstants(Ljava/lang/Class;)[Ljava/lang/Enum;
 HSPLjava/lang/Enum;->hashCode()I
 HSPLjava/lang/Enum;->name()Ljava/lang/String;
 HSPLjava/lang/Enum;->ordinal()I
 HSPLjava/lang/Enum;->toString()Ljava/lang/String;
-HSPLjava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;+]Ljava/lang/Enum;Landroid/net/NetworkInfo$State;,Landroid/net/NetworkInfo$DetailedState;
+HSPLjava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;+]Ljava/lang/Enum;missing_types
 HSPLjava/lang/Error;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Exception;-><init>()V
 HSPLjava/lang/Exception;-><init>(Ljava/lang/String;)V
@@ -2175,12 +2192,13 @@
 HSPLjava/lang/InheritableThreadLocal;->childValue(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/lang/InheritableThreadLocal;->createMap(Ljava/lang/Thread;Ljava/lang/Object;)V
 HSPLjava/lang/InheritableThreadLocal;->getMap(Ljava/lang/Thread;)Ljava/lang/ThreadLocal$ThreadLocalMap;
+HSPLjava/lang/InstantiationException;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Integer;-><init>(I)V
 HSPLjava/lang/Integer;->bitCount(I)I
 HSPLjava/lang/Integer;->byteValue()B
 HSPLjava/lang/Integer;->compare(II)I
 HSPLjava/lang/Integer;->compareTo(Ljava/lang/Integer;)I
-HSPLjava/lang/Integer;->compareTo(Ljava/lang/Object;)I
+HSPLjava/lang/Integer;->compareTo(Ljava/lang/Object;)I+]Ljava/lang/Integer;Ljava/lang/Integer;
 HSPLjava/lang/Integer;->decode(Ljava/lang/String;)Ljava/lang/Integer;
 HSPLjava/lang/Integer;->divideUnsigned(II)I
 HSPLjava/lang/Integer;->doubleValue()D
@@ -2223,18 +2241,18 @@
 HSPLjava/lang/Integer;->valueOf(Ljava/lang/String;)Ljava/lang/Integer;
 HSPLjava/lang/Integer;->valueOf(Ljava/lang/String;I)Ljava/lang/Integer;
 HSPLjava/lang/InterruptedException;-><init>()V
-HSPLjava/lang/Iterable;->forEach(Ljava/util/function/Consumer;)V+]Ljava/lang/Iterable;Ljava/util/HashSet;,Ljava/util/WeakHashMap$KeySet;]Ljava/util/Iterator;Ljava/util/HashMap$KeyIterator;,Ljava/util/WeakHashMap$KeyIterator;]Ljava/util/function/Consumer;missing_types
+HSPLjava/lang/Iterable;->forEach(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;missing_types]Ljava/util/Iterator;missing_types]Ljava/lang/Iterable;missing_types
 HSPLjava/lang/LinkageError;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Long;-><init>(J)V
 HSPLjava/lang/Long;->bitCount(J)I
 HSPLjava/lang/Long;->compare(JJ)I
 HSPLjava/lang/Long;->compareTo(Ljava/lang/Long;)I
-HSPLjava/lang/Long;->compareTo(Ljava/lang/Object;)I
+HSPLjava/lang/Long;->compareTo(Ljava/lang/Object;)I+]Ljava/lang/Long;Ljava/lang/Long;
 HSPLjava/lang/Long;->compareUnsigned(JJ)I
 HSPLjava/lang/Long;->decode(Ljava/lang/String;)Ljava/lang/Long;
 HSPLjava/lang/Long;->divideUnsigned(JJ)J
 HSPLjava/lang/Long;->doubleValue()D
-HSPLjava/lang/Long;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Long;Ljava/lang/Long;
+HSPLjava/lang/Long;->equals(Ljava/lang/Object;)Z
 HSPLjava/lang/Long;->formatUnsignedLong0(JI[BII)V
 HSPLjava/lang/Long;->getChars(JI[B)I
 HSPLjava/lang/Long;->getChars(JI[C)I
@@ -2248,6 +2266,7 @@
 HSPLjava/lang/Long;->lowestOneBit(J)J
 HSPLjava/lang/Long;->numberOfLeadingZeros(J)I
 HSPLjava/lang/Long;->numberOfTrailingZeros(J)I
+HSPLjava/lang/Long;->parseLong(Ljava/lang/CharSequence;III)J
 HSPLjava/lang/Long;->parseLong(Ljava/lang/String;)J
 HSPLjava/lang/Long;->parseLong(Ljava/lang/String;I)J
 HSPLjava/lang/Long;->reverse(J)J
@@ -2296,7 +2315,7 @@
 HSPLjava/lang/Math;->nextAfter(DD)D
 HSPLjava/lang/Math;->powerOfTwoD(I)D
 HSPLjava/lang/Math;->powerOfTwoF(I)F
-HSPLjava/lang/Math;->random()D
+HSPLjava/lang/Math;->random()D+]Ljava/util/Random;Ljava/util/Random;
 HSPLjava/lang/Math;->randomLongInternal()J
 HSPLjava/lang/Math;->round(D)J
 HSPLjava/lang/Math;->round(F)I
@@ -2316,7 +2335,8 @@
 HSPLjava/lang/NullPointerException;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Number;-><init>()V
 HSPLjava/lang/NumberFormatException;-><init>(Ljava/lang/String;)V
-HSPLjava/lang/NumberFormatException;->forInputString(Ljava/lang/String;)Ljava/lang/NumberFormatException;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/lang/NumberFormatException;->forInputString(Ljava/lang/String;)Ljava/lang/NumberFormatException;
+HSPLjava/lang/NumberFormatException;->forInputString(Ljava/lang/String;I)Ljava/lang/NumberFormatException;
 HSPLjava/lang/Object;-><init>()V
 HSPLjava/lang/Object;->clone()Ljava/lang/Object;
 HSPLjava/lang/Object;->equals(Ljava/lang/Object;)Z
@@ -2380,12 +2400,15 @@
 HSPLjava/lang/StackTraceElement;->hashCode()I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/StackTraceElement;->isNativeMethod()Z
 HSPLjava/lang/StackTraceElement;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/StackTraceElement;Ljava/lang/StackTraceElement;
-HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Ljava/lang/String$CaseInsensitiveComparator;Ljava/lang/String$CaseInsensitiveComparator;
 HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/String;Ljava/lang/String;)I
-HSPLjava/lang/String;->checkBoundsBeginEnd(III)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/lang/String;->checkBoundsBeginEnd(III)V
+HSPLjava/lang/String;->checkBoundsOffCount(III)V
 HSPLjava/lang/String;->checkIndex(II)V
+HSPLjava/lang/String;->checkOffset(II)V
 HSPLjava/lang/String;->codePointAt(I)I
 HSPLjava/lang/String;->codePointCount(II)I
+HSPLjava/lang/String;->coder()B
 HSPLjava/lang/String;->compareTo(Ljava/lang/Object;)I
 HSPLjava/lang/String;->compareToIgnoreCase(Ljava/lang/String;)I
 HSPLjava/lang/String;->contains(Ljava/lang/CharSequence;)Z+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/CharSequence;Ljava/lang/String;
@@ -2394,11 +2417,12 @@
 HSPLjava/lang/String;->endsWith(Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->equals(Ljava/lang/Object;)Z
 HSPLjava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
-HSPLjava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;+]Ljava/util/Formatter;Ljava/util/Formatter;
-HSPLjava/lang/String;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
-HSPLjava/lang/String;->getBytes()[B
+HSPLjava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
+HSPLjava/lang/String;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;+]Ljava/util/Formatter;Ljava/util/Formatter;
+HSPLjava/lang/String;->getBytes()[B+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->getBytes(Ljava/lang/String;)[B
 HSPLjava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B
+HSPLjava/lang/String;->getBytes([BIB)V+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->getChars(II[CI)V
 HSPLjava/lang/String;->getChars([CI)V
 HSPLjava/lang/String;->hashCode()I
@@ -2407,17 +2431,16 @@
 HSPLjava/lang/String;->indexOf(Ljava/lang/String;)I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->indexOf(Ljava/lang/String;I)I
 HSPLjava/lang/String;->indexOf(Ljava/lang/String;Ljava/lang/String;I)I
-HSPLjava/lang/String;->indexOf([CIILjava/lang/String;I)I
-HSPLjava/lang/String;->indexOf([CII[CIII)I
+HSPLjava/lang/String;->indexOf([BBILjava/lang/String;I)I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->isEmpty()Z
 HSPLjava/lang/String;->join(Ljava/lang/CharSequence;Ljava/lang/Iterable;)Ljava/lang/String;
 HSPLjava/lang/String;->join(Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/String;
-HSPLjava/lang/String;->lastIndexOf(I)I
+HSPLjava/lang/String;->lastIndexOf(I)I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->lastIndexOf(II)I
 HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;)I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;I)I
 HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;Ljava/lang/String;I)I
-HSPLjava/lang/String;->lastIndexOf([CIILjava/lang/String;I)I
+HSPLjava/lang/String;->lastIndexOf([BBILjava/lang/String;I)I
 HSPLjava/lang/String;->lastIndexOf([CII[CIII)I
 HSPLjava/lang/String;->length()I
 HSPLjava/lang/String;->matches(Ljava/lang/String;)Z
@@ -2434,7 +2457,7 @@
 HSPLjava/lang/String;->subSequence(II)Ljava/lang/CharSequence;+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->substring(I)Ljava/lang/String;
 HSPLjava/lang/String;->substring(II)Ljava/lang/String;
-HSPLjava/lang/String;->toLowerCase()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/lang/String;->toLowerCase()Ljava/lang/String;
 HSPLjava/lang/String;->toLowerCase(Ljava/util/Locale;)Ljava/lang/String;
 HSPLjava/lang/String;->toString()Ljava/lang/String;
 HSPLjava/lang/String;->toUpperCase()Ljava/lang/String;
@@ -2454,6 +2477,7 @@
 HSPLjava/lang/StringBuffer;->append(C)Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuffer;->append(I)Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuffer;->append(J)Ljava/lang/StringBuffer;
+HSPLjava/lang/StringBuffer;->append(Ljava/lang/AbstractStringBuilder;)Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuffer;->append(Ljava/lang/CharSequence;)Ljava/lang/Appendable;
 HSPLjava/lang/StringBuffer;->append(Ljava/lang/CharSequence;)Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuffer;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;
@@ -2466,15 +2490,16 @@
 HSPLjava/lang/StringBuffer;->append([CII)Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuffer;->charAt(I)C
 HSPLjava/lang/StringBuffer;->codePointAt(I)I
+HSPLjava/lang/StringBuffer;->getBytes([BIB)V
 HSPLjava/lang/StringBuffer;->getChars(II[CI)V
 HSPLjava/lang/StringBuffer;->length()I
 HSPLjava/lang/StringBuffer;->setLength(I)V
-HSPLjava/lang/StringBuffer;->toString()Ljava/lang/String;
+HSPLjava/lang/StringBuffer;->toString()Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuilder;-><init>()V
 HSPLjava/lang/StringBuilder;-><init>(I)V
 HSPLjava/lang/StringBuilder;-><init>(Ljava/lang/CharSequence;)V
-HSPLjava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
-HSPLjava/lang/StringBuilder;->append(C)Ljava/lang/Appendable;
+HSPLjava/lang/StringBuilder;-><init>(Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/lang/StringBuilder;->append(C)Ljava/lang/Appendable;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(D)Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(F)Ljava/lang/StringBuilder;
@@ -2483,7 +2508,7 @@
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/Appendable;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;
-HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;
+HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
@@ -2512,7 +2537,7 @@
 HSPLjava/lang/StringBuilder;->subSequence(II)Ljava/lang/CharSequence;
 HSPLjava/lang/StringBuilder;->substring(I)Ljava/lang/String;
 HSPLjava/lang/StringBuilder;->substring(II)Ljava/lang/String;
-HSPLjava/lang/StringBuilder;->toString()Ljava/lang/String;
+HSPLjava/lang/StringBuilder;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/StringFactory;->newEmptyString()Ljava/lang/String;
 HSPLjava/lang/StringFactory;->newStringFromBytes([B)Ljava/lang/String;
 HSPLjava/lang/StringFactory;->newStringFromBytes([BI)Ljava/lang/String;
@@ -2523,6 +2548,20 @@
 HSPLjava/lang/StringFactory;->newStringFromBytes([BLjava/nio/charset/Charset;)Ljava/lang/String;
 HSPLjava/lang/StringFactory;->newStringFromChars([C)Ljava/lang/String;
 HSPLjava/lang/StringFactory;->newStringFromChars([CII)Ljava/lang/String;
+HSPLjava/lang/StringLatin1;->canEncode(I)Z
+HSPLjava/lang/StringLatin1;->indexOf([BILjava/lang/String;II)I
+HSPLjava/lang/StringLatin1;->inflate([BI[BII)V
+HSPLjava/lang/StringLatin1;->lastIndexOf([BILjava/lang/String;II)I
+HSPLjava/lang/StringLatin1;->newString([BII)Ljava/lang/String;
+HSPLjava/lang/StringUTF16;->checkBoundsBeginEnd(II[B)V
+HSPLjava/lang/StringUTF16;->checkBoundsOffCount(II[B)V
+HSPLjava/lang/StringUTF16;->getChar([BI)C
+HSPLjava/lang/StringUTF16;->getChars(II[B)I
+HSPLjava/lang/StringUTF16;->getChars([BII[CI)V
+HSPLjava/lang/StringUTF16;->inflate([BI[BII)V
+HSPLjava/lang/StringUTF16;->length([B)I
+HSPLjava/lang/StringUTF16;->newBytesFor(I)[B
+HSPLjava/lang/StringUTF16;->putChar([BII)V
 HSPLjava/lang/System$PropertiesWithNonOverrideableDefaults;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/lang/System$PropertiesWithNonOverrideableDefaults;->remove(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/lang/System;->arraycopy([BI[BII)V
@@ -2536,7 +2575,7 @@
 HSPLjava/lang/System;->clearProperty(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/lang/System;->gc()V
 HSPLjava/lang/System;->getProperties()Ljava/util/Properties;
-HSPLjava/lang/System;->getProperty(Ljava/lang/String;)Ljava/lang/String;
+HSPLjava/lang/System;->getProperty(Ljava/lang/String;)Ljava/lang/String;+]Ljava/util/Properties;Ljava/lang/System$PropertiesWithNonOverrideableDefaults;
 HSPLjava/lang/System;->getProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/lang/System;->getSecurityManager()Ljava/lang/SecurityManager;
 HSPLjava/lang/System;->getenv(Ljava/lang/String;)Ljava/lang/String;
@@ -2572,7 +2611,7 @@
 HSPLjava/lang/Thread;->getState()Ljava/lang/Thread$State;
 HSPLjava/lang/Thread;->getThreadGroup()Ljava/lang/ThreadGroup;
 HSPLjava/lang/Thread;->getUncaughtExceptionHandler()Ljava/lang/Thread$UncaughtExceptionHandler;
-HSPLjava/lang/Thread;->init2(Ljava/lang/Thread;Z)V+]Ljava/lang/Thread;Landroid/os/HandlerThread;,Ljava/lang/Thread;,Landroid/net/ConnectivityThread;
+HSPLjava/lang/Thread;->init2(Ljava/lang/Thread;Z)V
 HSPLjava/lang/Thread;->interrupt()V
 HSPLjava/lang/Thread;->isAlive()Z
 HSPLjava/lang/Thread;->isDaemon()Z
@@ -2580,6 +2619,7 @@
 HSPLjava/lang/Thread;->join(J)V
 HSPLjava/lang/Thread;->nextThreadID()J
 HSPLjava/lang/Thread;->nextThreadNum()I
+HSPLjava/lang/Thread;->onSpinWait()V
 HSPLjava/lang/Thread;->run()V
 HSPLjava/lang/Thread;->setContextClassLoader(Ljava/lang/ClassLoader;)V
 HSPLjava/lang/Thread;->setDaemon(Z)V
@@ -2603,7 +2643,7 @@
 HSPLjava/lang/ThreadGroup;->checkAccess()V
 HSPLjava/lang/ThreadGroup;->checkParentAccess(Ljava/lang/ThreadGroup;)Ljava/lang/Void;
 HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/Thread;)I
-HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/Thread;IZ)I
+HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/Thread;IZ)I+]Ljava/lang/Thread;missing_types
 HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/ThreadGroup;)I
 HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/ThreadGroup;IZ)I
 HSPLjava/lang/ThreadGroup;->getMaxPriority()I
@@ -2620,7 +2660,7 @@
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;-><init>(Ljava/lang/ThreadLocal;Ljava/lang/Object;)V
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->cleanSomeSlots(II)Z
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->expungeStaleEntries()V
-HSPLjava/lang/ThreadLocal$ThreadLocalMap;->expungeStaleEntry(I)I
+HSPLjava/lang/ThreadLocal$ThreadLocalMap;->expungeStaleEntry(I)I+]Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->getEntry(Ljava/lang/ThreadLocal;)Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->getEntryAfterMiss(Ljava/lang/ThreadLocal;ILjava/lang/ThreadLocal$ThreadLocalMap$Entry;)Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->nextIndex(II)I
@@ -2639,9 +2679,9 @@
 HSPLjava/lang/ThreadLocal;->getMap(Ljava/lang/Thread;)Ljava/lang/ThreadLocal$ThreadLocalMap;
 HSPLjava/lang/ThreadLocal;->initialValue()Ljava/lang/Object;
 HSPLjava/lang/ThreadLocal;->nextHashCode()I
-HSPLjava/lang/ThreadLocal;->remove()V
-HSPLjava/lang/ThreadLocal;->set(Ljava/lang/Object;)V+]Ljava/lang/ThreadLocal;megamorphic_types
-HSPLjava/lang/ThreadLocal;->setInitialValue()Ljava/lang/Object;
+HSPLjava/lang/ThreadLocal;->remove()V+]Ljava/lang/ThreadLocal;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;,Ljava/lang/ThreadLocal;
+HSPLjava/lang/ThreadLocal;->set(Ljava/lang/Object;)V+]Ljava/lang/ThreadLocal;missing_types
+HSPLjava/lang/ThreadLocal;->setInitialValue()Ljava/lang/Object;+]Ljava/lang/ThreadLocal;missing_types
 HSPLjava/lang/ThreadLocal;->withInitial(Ljava/util/function/Supplier;)Ljava/lang/ThreadLocal;
 HSPLjava/lang/Throwable$PrintStreamOrWriter;-><init>()V
 HSPLjava/lang/Throwable$PrintStreamOrWriter;-><init>(Ljava/lang/Throwable$PrintStreamOrWriter-IA;)V
@@ -2650,9 +2690,9 @@
 HSPLjava/lang/Throwable$WrappedPrintStream;->println(Ljava/lang/Object;)V
 HSPLjava/lang/Throwable$WrappedPrintWriter;-><init>(Ljava/io/PrintWriter;)V
 HSPLjava/lang/Throwable$WrappedPrintWriter;->lock()Ljava/lang/Object;
-HSPLjava/lang/Throwable$WrappedPrintWriter;->println(Ljava/lang/Object;)V
-HSPLjava/lang/Throwable;-><init>()V
-HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;)V+]Ljava/lang/Throwable;megamorphic_types
+HSPLjava/lang/Throwable$WrappedPrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Lcom/android/internal/util/FastPrintWriter;,Ljava/io/PrintWriter;
+HSPLjava/lang/Throwable;-><init>()V+]Ljava/lang/Throwable;missing_types
+HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;)V+]Ljava/lang/Throwable;missing_types
 HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V
 HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;ZZ)V
 HSPLjava/lang/Throwable;-><init>(Ljava/lang/Throwable;)V
@@ -2669,10 +2709,10 @@
 HSPLjava/lang/Throwable;->printStackTrace()V
 HSPLjava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V
 HSPLjava/lang/Throwable;->printStackTrace(Ljava/io/PrintWriter;)V
-HSPLjava/lang/Throwable;->printStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;)V
+HSPLjava/lang/Throwable;->printStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Throwable$PrintStreamOrWriter;Ljava/lang/Throwable$WrappedPrintWriter;]Ljava/lang/Throwable;missing_types]Ljava/util/Set;Ljava/util/Collections$SetFromMap;
 HSPLjava/lang/Throwable;->readObject(Ljava/io/ObjectInputStream;)V
 HSPLjava/lang/Throwable;->setStackTrace([Ljava/lang/StackTraceElement;)V
-HSPLjava/lang/Throwable;->toString()Ljava/lang/String;
+HSPLjava/lang/Throwable;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Object;missing_types]Ljava/lang/Throwable;missing_types]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Throwable;->writeObject(Ljava/io/ObjectOutputStream;)V
 HSPLjava/lang/UNIXProcess$2;-><init>(Ljava/lang/UNIXProcess;[I)V
 HSPLjava/lang/UNIXProcess$2;->run()Ljava/lang/Object;
@@ -2698,7 +2738,7 @@
 HSPLjava/lang/UnsupportedOperationException;-><init>()V
 HSPLjava/lang/UnsupportedOperationException;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/VMClassLoader;->getResource(Ljava/lang/String;)Ljava/net/URL;
-HSPLjava/lang/VMClassLoader;->getResources(Ljava/lang/String;)Ljava/util/List;
+HSPLjava/lang/VMClassLoader;->getResources(Ljava/lang/String;)Ljava/util/List;+]Llibcore/io/ClassPathURLStreamHandler;Llibcore/io/ClassPathURLStreamHandler;
 HSPLjava/lang/invoke/FieldVarHandle;-><init>(Ljava/lang/reflect/Field;Ljava/lang/Class;)V
 HSPLjava/lang/invoke/FieldVarHandle;->create(Ljava/lang/reflect/Field;)Ljava/lang/invoke/FieldVarHandle;
 HSPLjava/lang/invoke/MethodHandle;-><init>(JILjava/lang/invoke/MethodType;)V
@@ -2789,10 +2829,10 @@
 HSPLjava/lang/reflect/AccessibleObject;->getAnnotations()[Ljava/lang/annotation/Annotation;
 HSPLjava/lang/reflect/AccessibleObject;->isAccessible()Z
 HSPLjava/lang/reflect/AccessibleObject;->setAccessible(Z)V
-HSPLjava/lang/reflect/AccessibleObject;->setAccessible0(Ljava/lang/reflect/AccessibleObject;Z)V
+HSPLjava/lang/reflect/AccessibleObject;->setAccessible0(Ljava/lang/reflect/AccessibleObject;Z)V+]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor;
 HSPLjava/lang/reflect/Array;->get(Ljava/lang/Object;I)Ljava/lang/Object;
 HSPLjava/lang/reflect/Array;->getLength(Ljava/lang/Object;)I
-HSPLjava/lang/reflect/Array;->newArray(Ljava/lang/Class;I)Ljava/lang/Object;
+HSPLjava/lang/reflect/Array;->newArray(Ljava/lang/Class;I)Ljava/lang/Object;+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/reflect/Array;->newInstance(Ljava/lang/Class;I)Ljava/lang/Object;
 HSPLjava/lang/reflect/Array;->newInstance(Ljava/lang/Class;[I)Ljava/lang/Object;
 HSPLjava/lang/reflect/Array;->set(Ljava/lang/Object;ILjava/lang/Object;)V
@@ -2834,9 +2874,9 @@
 HSPLjava/lang/reflect/Field;->getDeclaringClass()Ljava/lang/Class;
 HSPLjava/lang/reflect/Field;->getGenericType()Ljava/lang/reflect/Type;+]Ljava/lang/reflect/Field;Ljava/lang/reflect/Field;]Ljava/lang/Class;Ljava/lang/Class;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLjava/lang/reflect/Field;->getModifiers()I
-HSPLjava/lang/reflect/Field;->getName()Ljava/lang/String;
+HSPLjava/lang/reflect/Field;->getName()Ljava/lang/String;+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/reflect/Field;->getOffset()I
-HSPLjava/lang/reflect/Field;->getSignatureAttribute()Ljava/lang/String;
+HSPLjava/lang/reflect/Field;->getSignatureAttribute()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/reflect/Field;->getType()Ljava/lang/Class;
 HSPLjava/lang/reflect/Field;->hashCode()I
 HSPLjava/lang/reflect/Field;->isAnnotationPresent(Ljava/lang/Class;)Z
@@ -2858,7 +2898,7 @@
 HSPLjava/lang/reflect/Method;->getParameterAnnotations()[[Ljava/lang/annotation/Annotation;
 HSPLjava/lang/reflect/Method;->getParameterTypes()[Ljava/lang/Class;
 HSPLjava/lang/reflect/Method;->getReturnType()Ljava/lang/Class;+]Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;
-HSPLjava/lang/reflect/Method;->hashCode()I+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/reflect/Method;->hashCode()I
 HSPLjava/lang/reflect/Method;->isBridge()Z
 HSPLjava/lang/reflect/Method;->isDefault()Z
 HSPLjava/lang/reflect/Method;->isSynthetic()Z
@@ -2898,7 +2938,7 @@
 HSPLjava/lang/reflect/Proxy;->intersectExceptions([Ljava/lang/Class;[Ljava/lang/Class;)[Ljava/lang/Class;
 HSPLjava/lang/reflect/Proxy;->invoke(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/reflect/InvocationHandler;Llibcore/reflect/AnnotationFactory;
 HSPLjava/lang/reflect/Proxy;->isProxyClass(Ljava/lang/Class;)Z
-HSPLjava/lang/reflect/Proxy;->newProxyInstance(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;
+HSPLjava/lang/reflect/Proxy;->newProxyInstance(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;+][Ljava/lang/Class;[Ljava/lang/Class;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor;
 HSPLjava/lang/reflect/Proxy;->validateReturnTypes(Ljava/util/List;)V
 HSPLjava/lang/reflect/WeakCache$CacheKey;-><init>(Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V
 HSPLjava/lang/reflect/WeakCache$CacheKey;->equals(Ljava/lang/Object;)Z
@@ -2911,7 +2951,7 @@
 HSPLjava/lang/reflect/WeakCache;->-$$Nest$fgetreverseMap(Ljava/lang/reflect/WeakCache;)Ljava/util/concurrent/ConcurrentMap;
 HSPLjava/lang/reflect/WeakCache;->-$$Nest$fgetvalueFactory(Ljava/lang/reflect/WeakCache;)Ljava/util/function/BiFunction;
 HSPLjava/lang/reflect/WeakCache;->expungeStaleEntries()V
-HSPLjava/lang/reflect/WeakCache;->get(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/lang/reflect/WeakCache;->get(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/function/BiFunction;Ljava/lang/reflect/Proxy$KeyFactory;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/util/function/Supplier;Ljava/lang/reflect/WeakCache$CacheValue;
 HSPLjava/math/BigDecimal;-><init>(I)V
 HSPLjava/math/BigDecimal;-><init>(J)V
 HSPLjava/math/BigDecimal;-><init>(Ljava/lang/String;)V
@@ -2932,11 +2972,11 @@
 HSPLjava/math/BigDecimal;->divide(JIJIII)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->divide(Ljava/math/BigDecimal;II)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->divide(Ljava/math/BigDecimal;ILjava/math/RoundingMode;)Ljava/math/BigDecimal;
-HSPLjava/math/BigDecimal;->divide(Ljava/math/BigDecimal;Ljava/math/RoundingMode;)Ljava/math/BigDecimal;+]Ljava/math/BigDecimal;Ljava/math/BigDecimal;
+HSPLjava/math/BigDecimal;->divide(Ljava/math/BigDecimal;Ljava/math/RoundingMode;)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->divideAndRound(JJIII)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->getValueString(ILjava/lang/String;I)Ljava/lang/String;
 HSPLjava/math/BigDecimal;->inflated()Ljava/math/BigInteger;
-HSPLjava/math/BigDecimal;->layoutChars(Z)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/math/BigDecimal$StringBuilderHelper;Ljava/math/BigDecimal$StringBuilderHelper;]Ljava/lang/ThreadLocal;Ljava/math/BigDecimal$1;]Ljava/math/BigDecimal;Ljava/math/BigDecimal;]Ljava/lang/String;Ljava/lang/String;]Ljava/math/BigInteger;Ljava/math/BigInteger;
+HSPLjava/math/BigDecimal;->layoutChars(Z)Ljava/lang/String;
 HSPLjava/math/BigDecimal;->longCompareMagnitude(JJ)I
 HSPLjava/math/BigDecimal;->longMultiplyPowerTen(JI)J
 HSPLjava/math/BigDecimal;->longValueExact()J
@@ -2945,12 +2985,15 @@
 HSPLjava/math/BigDecimal;->multiply(JJ)J
 HSPLjava/math/BigDecimal;->multiply(JJI)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
+HSPLjava/math/BigDecimal;->needIncrement(JIIJJ)Z
 HSPLjava/math/BigDecimal;->scale()I
+HSPLjava/math/BigDecimal;->scaleByPowerOfTen(I)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->setScale(II)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->setScale(ILjava/math/RoundingMode;)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->signum()I
 HSPLjava/math/BigDecimal;->stripTrailingZeros()Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->subtract(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
+HSPLjava/math/BigDecimal;->toBigInteger()Ljava/math/BigInteger;
 HSPLjava/math/BigDecimal;->toBigIntegerExact()Ljava/math/BigInteger;
 HSPLjava/math/BigDecimal;->toPlainString()Ljava/lang/String;
 HSPLjava/math/BigDecimal;->toString()Ljava/lang/String;
@@ -2958,8 +3001,6 @@
 HSPLjava/math/BigDecimal;->valueOf(JI)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->zeroValueOf(I)Ljava/math/BigDecimal;
 HSPLjava/math/BigInteger$UnsafeHolder;-><clinit>()V
-HSPLjava/math/BigInteger$UnsafeHolder;->putMag(Ljava/math/BigInteger;[I)V
-HSPLjava/math/BigInteger$UnsafeHolder;->putSign(Ljava/math/BigInteger;I)V
 HSPLjava/math/BigInteger;-><init>(I[B)V
 HSPLjava/math/BigInteger;-><init>(I[BII)V
 HSPLjava/math/BigInteger;-><init>(I[I)V
@@ -2995,10 +3036,11 @@
 HSPLjava/math/BigInteger;->multiply(Ljava/math/BigInteger;Z)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->multiplyByInt([III)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->multiplyToLen([II[II[I)[I
-HSPLjava/math/BigInteger;->pow(I)Ljava/math/BigInteger;+]Ljava/math/BigInteger;Ljava/math/BigInteger;
+HSPLjava/math/BigInteger;->padWithZeros(Ljava/lang/StringBuilder;I)V
+HSPLjava/math/BigInteger;->pow(I)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->readObject(Ljava/io/ObjectInputStream;)V
 HSPLjava/math/BigInteger;->remainder(Ljava/math/BigInteger;)Ljava/math/BigInteger;
-HSPLjava/math/BigInteger;->remainderKnuth(Ljava/math/BigInteger;)Ljava/math/BigInteger;+]Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;
+HSPLjava/math/BigInteger;->remainderKnuth(Ljava/math/BigInteger;)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->reverse([I)[I
 HSPLjava/math/BigInteger;->shiftLeft(I)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->shiftLeft([II)[I
@@ -3006,15 +3048,16 @@
 HSPLjava/math/BigInteger;->shiftRightImpl(I)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->signInt()I
 HSPLjava/math/BigInteger;->signum()I
-HSPLjava/math/BigInteger;->smallToString(I)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/math/BigInteger;Ljava/math/BigInteger;]Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;
+HSPLjava/math/BigInteger;->smallToString(ILjava/lang/StringBuilder;I)V
 HSPLjava/math/BigInteger;->stripLeadingZeroBytes([BII)[I
 HSPLjava/math/BigInteger;->stripLeadingZeroInts([I)[I
 HSPLjava/math/BigInteger;->subtract(Ljava/math/BigInteger;)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->subtract([I[I)[I
 HSPLjava/math/BigInteger;->testBit(I)Z
-HSPLjava/math/BigInteger;->toByteArray()[B+]Ljava/math/BigInteger;Ljava/math/BigInteger;
+HSPLjava/math/BigInteger;->toByteArray()[B
 HSPLjava/math/BigInteger;->toString()Ljava/lang/String;
 HSPLjava/math/BigInteger;->toString(I)Ljava/lang/String;
+HSPLjava/math/BigInteger;->toString(Ljava/math/BigInteger;Ljava/lang/StringBuilder;II)V
 HSPLjava/math/BigInteger;->trustedStripLeadingZeroInts([I)[I
 HSPLjava/math/BigInteger;->valueOf(J)Ljava/math/BigInteger;
 HSPLjava/math/MathContext;->equals(Ljava/lang/Object;)Z
@@ -3043,6 +3086,7 @@
 HSPLjava/math/MutableBigInteger;->rightShift(I)V
 HSPLjava/math/MutableBigInteger;->toBigInteger(I)Ljava/math/BigInteger;
 HSPLjava/math/MutableBigInteger;->unsignedLongCompare(JJ)Z
+HSPLjava/math/RoundingMode;->valueOf(I)Ljava/math/RoundingMode;
 HSPLjava/math/RoundingMode;->values()[Ljava/math/RoundingMode;
 HSPLjava/net/AbstractPlainDatagramSocketImpl;-><init>()V
 HSPLjava/net/AbstractPlainDatagramSocketImpl;->bind(ILjava/net/InetAddress;)V
@@ -3123,7 +3167,7 @@
 HSPLjava/net/DatagramSocket;->getImpl()Ljava/net/DatagramSocketImpl;
 HSPLjava/net/DatagramSocket;->isBound()Z
 HSPLjava/net/DatagramSocket;->isClosed()Z
-HSPLjava/net/DatagramSocket;->receive(Ljava/net/DatagramPacket;)V
+HSPLjava/net/DatagramSocket;->receive(Ljava/net/DatagramPacket;)V+]Ljava/net/DatagramSocket;Ljava/net/DatagramSocket;,Ljava/net/MulticastSocket;]Ljava/net/DatagramSocketImpl;Ljava/net/PlainDatagramSocketImpl;
 HSPLjava/net/DatagramSocket;->send(Ljava/net/DatagramPacket;)V
 HSPLjava/net/DatagramSocket;->setReuseAddress(Z)V
 HSPLjava/net/DatagramSocket;->setSoTimeout(I)V
@@ -3138,9 +3182,9 @@
 HSPLjava/net/HttpCookie;-><init>(Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/net/HttpCookie;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/net/HttpCookie;->assignAttribute(Ljava/net/HttpCookie;Ljava/lang/String;Ljava/lang/String;)V
-HSPLjava/net/HttpCookie;->domainMatches(Ljava/lang/String;Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
-HSPLjava/net/HttpCookie;->equals(Ljava/lang/Object;)Z+]Ljava/net/HttpCookie;Ljava/net/HttpCookie;
-HSPLjava/net/HttpCookie;->equalsIgnoreCase(Ljava/lang/String;Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/net/HttpCookie;->domainMatches(Ljava/lang/String;Ljava/lang/String;)Z
+HSPLjava/net/HttpCookie;->equals(Ljava/lang/Object;)Z
+HSPLjava/net/HttpCookie;->equalsIgnoreCase(Ljava/lang/String;Ljava/lang/String;)Z
 HSPLjava/net/HttpCookie;->getDomain()Ljava/lang/String;
 HSPLjava/net/HttpCookie;->getMaxAge()J
 HSPLjava/net/HttpCookie;->getName()Ljava/lang/String;
@@ -3175,13 +3219,13 @@
 HSPLjava/net/IDN;->toASCII(Ljava/lang/String;I)Ljava/lang/String;
 HSPLjava/net/InMemoryCookieStore;-><init>()V
 HSPLjava/net/InMemoryCookieStore;-><init>(I)V
-HSPLjava/net/InMemoryCookieStore;->add(Ljava/net/URI;Ljava/net/HttpCookie;)V+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;
+HSPLjava/net/InMemoryCookieStore;->add(Ljava/net/URI;Ljava/net/HttpCookie;)V
 HSPLjava/net/InMemoryCookieStore;->addIndex(Ljava/util/Map;Ljava/lang/Object;Ljava/net/HttpCookie;)V
 HSPLjava/net/InMemoryCookieStore;->get(Ljava/net/URI;)Ljava/util/List;
 HSPLjava/net/InMemoryCookieStore;->getEffectiveURI(Ljava/net/URI;)Ljava/net/URI;
 HSPLjava/net/InMemoryCookieStore;->getInternal1(Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)V
 HSPLjava/net/InMemoryCookieStore;->getInternal2(Ljava/util/List;Ljava/util/Map;Ljava/lang/Comparable;)V
-HSPLjava/net/InMemoryCookieStore;->netscapeDomainMatches(Ljava/lang/String;Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/net/InMemoryCookieStore;->netscapeDomainMatches(Ljava/lang/String;Ljava/lang/String;)Z
 HSPLjava/net/Inet4Address;-><init>()V
 HSPLjava/net/Inet4Address;-><init>(Ljava/lang/String;[B)V
 HSPLjava/net/Inet4Address;->equals(Ljava/lang/Object;)Z
@@ -3269,7 +3313,7 @@
 HSPLjava/net/NetworkInterface$1checkedAddresses;->nextElement()Ljava/net/InetAddress;
 HSPLjava/net/NetworkInterface;-><init>(Ljava/lang/String;I[Ljava/net/InetAddress;)V
 HSPLjava/net/NetworkInterface;->getAll()[Ljava/net/NetworkInterface;
-HSPLjava/net/NetworkInterface;->getByName(Ljava/lang/String;)Ljava/net/NetworkInterface;+]Ljava/net/NetworkInterface;Ljava/net/NetworkInterface;
+HSPLjava/net/NetworkInterface;->getByName(Ljava/lang/String;)Ljava/net/NetworkInterface;
 HSPLjava/net/NetworkInterface;->getFlags()I
 HSPLjava/net/NetworkInterface;->getHardwareAddress()[B
 HSPLjava/net/NetworkInterface;->getIndex()I
@@ -3286,7 +3330,7 @@
 HSPLjava/net/PlainDatagramSocketImpl;->bind0(ILjava/net/InetAddress;)V
 HSPLjava/net/PlainDatagramSocketImpl;->datagramSocketClose()V
 HSPLjava/net/PlainDatagramSocketImpl;->datagramSocketCreate()V
-HSPLjava/net/PlainDatagramSocketImpl;->doRecv(Ljava/net/DatagramPacket;I)V
+HSPLjava/net/PlainDatagramSocketImpl;->doRecv(Ljava/net/DatagramPacket;I)V+]Ljava/net/DatagramPacket;Ljava/net/DatagramPacket;]Ljava/net/PlainDatagramSocketImpl;Ljava/net/PlainDatagramSocketImpl;
 HSPLjava/net/PlainDatagramSocketImpl;->receive0(Ljava/net/DatagramPacket;)V
 HSPLjava/net/PlainDatagramSocketImpl;->send(Ljava/net/DatagramPacket;)V
 HSPLjava/net/PlainDatagramSocketImpl;->socketSetOption(ILjava/lang/Object;)V
@@ -3432,8 +3476,8 @@
 HSPLjava/net/URI;->checkPath(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/net/URI;->compare(Ljava/lang/String;Ljava/lang/String;)I
 HSPLjava/net/URI;->compareIgnoringCase(Ljava/lang/String;Ljava/lang/String;)I
-HSPLjava/net/URI;->compareTo(Ljava/lang/Object;)I+]Ljava/net/URI;Ljava/net/URI;
-HSPLjava/net/URI;->compareTo(Ljava/net/URI;)I+]Ljava/net/URI;Ljava/net/URI;
+HSPLjava/net/URI;->compareTo(Ljava/lang/Object;)I
+HSPLjava/net/URI;->compareTo(Ljava/net/URI;)I
 HSPLjava/net/URI;->create(Ljava/lang/String;)Ljava/net/URI;
 HSPLjava/net/URI;->decode(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/net/URI;->defineString()V
@@ -3502,12 +3546,12 @@
 HSPLjava/net/URLConnection;->setReadTimeout(I)V
 HSPLjava/net/URLConnection;->setUseCaches(Z)V
 HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/net/URLDecoder;->isValidHexChar(C)Z
 HSPLjava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/util/BitSet;Ljava/util/BitSet;]Ljava/io/CharArrayWriter;Ljava/io/CharArrayWriter;
 HSPLjava/net/URLStreamHandler;-><init>()V
-HSPLjava/net/URLStreamHandler;->parseURL(Ljava/net/URL;Ljava/lang/String;II)V
+HSPLjava/net/URLStreamHandler;->parseURL(Ljava/net/URL;Ljava/lang/String;II)V+]Ljava/net/URLStreamHandler;Lcom/android/okhttp/HttpsHandler;]Ljava/lang/String;Ljava/lang/String;]Ljava/net/URL;Ljava/net/URL;
 HSPLjava/net/URLStreamHandler;->setURL(Ljava/net/URL;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/net/URLStreamHandler;->toExternalForm(Ljava/net/URL;)Ljava/lang/String;
 HSPLjava/net/UnknownHostException;-><init>(Ljava/lang/String;)V
@@ -3517,14 +3561,14 @@
 HSPLjava/nio/Bits;->getFloat(Ljava/nio/ByteBuffer;IZ)F
 HSPLjava/nio/Bits;->getFloatL(Ljava/nio/ByteBuffer;I)F
 HSPLjava/nio/Bits;->getInt(Ljava/nio/ByteBuffer;IZ)I
-HSPLjava/nio/Bits;->getIntB(Ljava/nio/ByteBuffer;I)I
-HSPLjava/nio/Bits;->getIntL(Ljava/nio/ByteBuffer;I)I+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->getIntB(Ljava/nio/ByteBuffer;I)I+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->getIntL(Ljava/nio/ByteBuffer;I)I
 HSPLjava/nio/Bits;->getLong(Ljava/nio/ByteBuffer;IZ)J
 HSPLjava/nio/Bits;->getLongB(Ljava/nio/ByteBuffer;I)J
-HSPLjava/nio/Bits;->getLongL(Ljava/nio/ByteBuffer;I)J+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->getLongL(Ljava/nio/ByteBuffer;I)J
 HSPLjava/nio/Bits;->getShort(Ljava/nio/ByteBuffer;IZ)S
-HSPLjava/nio/Bits;->getShortB(Ljava/nio/ByteBuffer;I)S+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/Bits;->getShortL(Ljava/nio/ByteBuffer;I)S+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->getShortB(Ljava/nio/ByteBuffer;I)S
+HSPLjava/nio/Bits;->getShortL(Ljava/nio/ByteBuffer;I)S
 HSPLjava/nio/Bits;->int0(I)B
 HSPLjava/nio/Bits;->int1(I)B
 HSPLjava/nio/Bits;->int2(I)B
@@ -3548,13 +3592,13 @@
 HSPLjava/nio/Bits;->putFloat(Ljava/nio/ByteBuffer;IFZ)V
 HSPLjava/nio/Bits;->putInt(Ljava/nio/ByteBuffer;IIZ)V
 HSPLjava/nio/Bits;->putIntB(Ljava/nio/ByteBuffer;II)V
-HSPLjava/nio/Bits;->putIntL(Ljava/nio/ByteBuffer;II)V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->putIntL(Ljava/nio/ByteBuffer;II)V
 HSPLjava/nio/Bits;->putLong(Ljava/nio/ByteBuffer;IJZ)V
 HSPLjava/nio/Bits;->putLongB(Ljava/nio/ByteBuffer;IJ)V
-HSPLjava/nio/Bits;->putLongL(Ljava/nio/ByteBuffer;IJ)V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->putLongL(Ljava/nio/ByteBuffer;IJ)V
 HSPLjava/nio/Bits;->putShort(Ljava/nio/ByteBuffer;ISZ)V
-HSPLjava/nio/Bits;->putShortB(Ljava/nio/ByteBuffer;IS)V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/Bits;->putShortL(Ljava/nio/ByteBuffer;IS)V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->putShortB(Ljava/nio/ByteBuffer;IS)V
+HSPLjava/nio/Bits;->putShortL(Ljava/nio/ByteBuffer;IS)V
 HSPLjava/nio/Bits;->short0(S)B
 HSPLjava/nio/Bits;->short1(S)B
 HSPLjava/nio/Bits;->unsafe()Lsun/misc/Unsafe;
@@ -3589,19 +3633,20 @@
 HSPLjava/nio/ByteBuffer;->arrayOffset()I
 HSPLjava/nio/ByteBuffer;->clear()Ljava/nio/Buffer;
 HSPLjava/nio/ByteBuffer;->compare(BB)I
-HSPLjava/nio/ByteBuffer;->compareTo(Ljava/nio/ByteBuffer;)I+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;,Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/ByteBuffer;->compareTo(Ljava/lang/Object;)I
+HSPLjava/nio/ByteBuffer;->compareTo(Ljava/nio/ByteBuffer;)I
 HSPLjava/nio/ByteBuffer;->equals(BB)Z
 HSPLjava/nio/ByteBuffer;->equals(Ljava/lang/Object;)Z
 HSPLjava/nio/ByteBuffer;->flip()Ljava/nio/Buffer;
 HSPLjava/nio/ByteBuffer;->get([B)Ljava/nio/ByteBuffer;
 HSPLjava/nio/ByteBuffer;->hasArray()Z
-HSPLjava/nio/ByteBuffer;->hashCode()I+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;,Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/ByteBuffer;->hashCode()I
 HSPLjava/nio/ByteBuffer;->limit(I)Ljava/nio/Buffer;
 HSPLjava/nio/ByteBuffer;->mark()Ljava/nio/Buffer;
 HSPLjava/nio/ByteBuffer;->order()Ljava/nio/ByteOrder;
 HSPLjava/nio/ByteBuffer;->order(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;
 HSPLjava/nio/ByteBuffer;->position(I)Ljava/nio/Buffer;
-HSPLjava/nio/ByteBuffer;->put(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;,Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/ByteBuffer;->put(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;
 HSPLjava/nio/ByteBuffer;->put([B)Ljava/nio/ByteBuffer;
 HSPLjava/nio/ByteBuffer;->reset()Ljava/nio/Buffer;
 HSPLjava/nio/ByteBuffer;->rewind()Ljava/nio/Buffer;
@@ -3609,7 +3654,7 @@
 HSPLjava/nio/ByteBuffer;->wrap([BII)Ljava/nio/ByteBuffer;
 HSPLjava/nio/ByteBufferAsCharBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V
 HSPLjava/nio/ByteBufferAsCharBuffer;->duplicate()Ljava/nio/CharBuffer;
-HSPLjava/nio/ByteBufferAsCharBuffer;->get(I)C
+HSPLjava/nio/ByteBufferAsCharBuffer;->get(I)C+]Ljava/nio/ByteBuffer;Ljava/nio/DirectByteBuffer;]Ljava/nio/ByteBufferAsCharBuffer;Ljava/nio/ByteBufferAsCharBuffer;
 HSPLjava/nio/ByteBufferAsCharBuffer;->get([CII)Ljava/nio/CharBuffer;
 HSPLjava/nio/ByteBufferAsCharBuffer;->isDirect()Z
 HSPLjava/nio/ByteBufferAsCharBuffer;->ix(I)I
@@ -3618,12 +3663,14 @@
 HSPLjava/nio/ByteBufferAsCharBuffer;->toString(II)Ljava/lang/String;
 HSPLjava/nio/ByteBufferAsFloatBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V
 HSPLjava/nio/ByteBufferAsFloatBuffer;->ix(I)I
-HSPLjava/nio/ByteBufferAsFloatBuffer;->put(IF)Ljava/nio/FloatBuffer;+]Ljava/nio/ByteBufferAsFloatBuffer;Ljava/nio/ByteBufferAsFloatBuffer;]Ljava/nio/ByteBuffer;Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/ByteBufferAsFloatBuffer;->put(IF)Ljava/nio/FloatBuffer;
 HSPLjava/nio/ByteBufferAsFloatBuffer;->put([FII)Ljava/nio/FloatBuffer;
 HSPLjava/nio/ByteBufferAsIntBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V
 HSPLjava/nio/ByteBufferAsIntBuffer;->get([III)Ljava/nio/IntBuffer;
 HSPLjava/nio/ByteBufferAsIntBuffer;->ix(I)I
-HSPLjava/nio/ByteBufferAsIntBuffer;->put([III)Ljava/nio/IntBuffer;+]Ljava/nio/ByteBufferAsIntBuffer;Ljava/nio/ByteBufferAsIntBuffer;]Ljava/nio/ByteBuffer;Ljava/nio/DirectByteBuffer;,Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/ByteBufferAsIntBuffer;->put(I)Ljava/nio/IntBuffer;
+HSPLjava/nio/ByteBufferAsIntBuffer;->put(II)Ljava/nio/IntBuffer;
+HSPLjava/nio/ByteBufferAsIntBuffer;->put([III)Ljava/nio/IntBuffer;
 HSPLjava/nio/ByteBufferAsLongBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V
 HSPLjava/nio/ByteBufferAsLongBuffer;->get([JII)Ljava/nio/LongBuffer;
 HSPLjava/nio/ByteBufferAsLongBuffer;->ix(I)I
@@ -3637,7 +3684,7 @@
 HSPLjava/nio/CharBuffer;->allocate(I)Ljava/nio/CharBuffer;
 HSPLjava/nio/CharBuffer;->array()[C
 HSPLjava/nio/CharBuffer;->arrayOffset()I
-HSPLjava/nio/CharBuffer;->charAt(I)C
+HSPLjava/nio/CharBuffer;->charAt(I)C+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;,Ljava/nio/ByteBufferAsCharBuffer;
 HSPLjava/nio/CharBuffer;->clear()Ljava/nio/Buffer;
 HSPLjava/nio/CharBuffer;->flip()Ljava/nio/Buffer;
 HSPLjava/nio/CharBuffer;->get([C)Ljava/nio/CharBuffer;
@@ -3646,7 +3693,7 @@
 HSPLjava/nio/CharBuffer;->length()I
 HSPLjava/nio/CharBuffer;->limit(I)Ljava/nio/Buffer;
 HSPLjava/nio/CharBuffer;->position(I)Ljava/nio/Buffer;
-HSPLjava/nio/CharBuffer;->toString()Ljava/lang/String;+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;
+HSPLjava/nio/CharBuffer;->toString()Ljava/lang/String;
 HSPLjava/nio/CharBuffer;->wrap(Ljava/lang/CharSequence;)Ljava/nio/CharBuffer;
 HSPLjava/nio/CharBuffer;->wrap(Ljava/lang/CharSequence;II)Ljava/nio/CharBuffer;
 HSPLjava/nio/CharBuffer;->wrap([C)Ljava/nio/CharBuffer;
@@ -3662,12 +3709,13 @@
 HSPLjava/nio/DirectByteBuffer;->asCharBuffer()Ljava/nio/CharBuffer;
 HSPLjava/nio/DirectByteBuffer;->asFloatBuffer()Ljava/nio/FloatBuffer;
 HSPLjava/nio/DirectByteBuffer;->asIntBuffer()Ljava/nio/IntBuffer;
-HSPLjava/nio/DirectByteBuffer;->asReadOnlyBuffer()Ljava/nio/ByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->asReadOnlyBuffer()Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->asShortBuffer()Ljava/nio/ShortBuffer;
 HSPLjava/nio/DirectByteBuffer;->cleaner()Lsun/misc/Cleaner;
 HSPLjava/nio/DirectByteBuffer;->duplicate()Ljava/nio/ByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->duplicate()Ljava/nio/MappedByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->get()B
-HSPLjava/nio/DirectByteBuffer;->get(I)B+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->get(I)B
 HSPLjava/nio/DirectByteBuffer;->get(J)B
 HSPLjava/nio/DirectByteBuffer;->get([BII)Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->getChar()C
@@ -3676,7 +3724,7 @@
 HSPLjava/nio/DirectByteBuffer;->getInt()I
 HSPLjava/nio/DirectByteBuffer;->getInt(I)I+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->getInt(J)I
-HSPLjava/nio/DirectByteBuffer;->getLong(I)J+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->getLong(I)J
 HSPLjava/nio/DirectByteBuffer;->getLong(J)J
 HSPLjava/nio/DirectByteBuffer;->getShort()S
 HSPLjava/nio/DirectByteBuffer;->getShort(I)S+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
@@ -3696,14 +3744,15 @@
 HSPLjava/nio/DirectByteBuffer;->putFloat(JF)Ljava/nio/ByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->putFloatUnchecked(IF)V
 HSPLjava/nio/DirectByteBuffer;->putInt(I)Ljava/nio/ByteBuffer;
-HSPLjava/nio/DirectByteBuffer;->putInt(II)Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->putInt(II)Ljava/nio/ByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->putInt(JI)Ljava/nio/ByteBuffer;
-HSPLjava/nio/DirectByteBuffer;->putLong(IJ)Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
-HSPLjava/nio/DirectByteBuffer;->putLong(J)Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->putLong(IJ)Ljava/nio/ByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->putLong(J)Ljava/nio/ByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->putLong(JJ)Ljava/nio/ByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->putUnchecked(I[FII)V
 HSPLjava/nio/DirectByteBuffer;->setAccessible(Z)V
 HSPLjava/nio/DirectByteBuffer;->slice()Ljava/nio/ByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->slice()Ljava/nio/MappedByteBuffer;
 HSPLjava/nio/FloatBuffer;-><init>(IIII)V
 HSPLjava/nio/FloatBuffer;-><init>(IIII[FI)V
 HSPLjava/nio/FloatBuffer;->limit(I)Ljava/nio/Buffer;
@@ -3718,38 +3767,38 @@
 HSPLjava/nio/HeapByteBuffer;->_put(IB)V
 HSPLjava/nio/HeapByteBuffer;->asIntBuffer()Ljava/nio/IntBuffer;
 HSPLjava/nio/HeapByteBuffer;->asLongBuffer()Ljava/nio/LongBuffer;
-HSPLjava/nio/HeapByteBuffer;->asReadOnlyBuffer()Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->asReadOnlyBuffer()Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->asShortBuffer()Ljava/nio/ShortBuffer;
-HSPLjava/nio/HeapByteBuffer;->compact()Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->compact()Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->duplicate()Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->get()B+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->get(I)B+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->get(I)B
 HSPLjava/nio/HeapByteBuffer;->get([BII)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->getFloat()F
 HSPLjava/nio/HeapByteBuffer;->getFloat(I)F
 HSPLjava/nio/HeapByteBuffer;->getInt()I+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->getInt(I)I+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->getLong()J+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->getInt(I)I
+HSPLjava/nio/HeapByteBuffer;->getLong()J
 HSPLjava/nio/HeapByteBuffer;->getLong(I)J
-HSPLjava/nio/HeapByteBuffer;->getShort()S+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->getShort(I)S+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->getShort()S
+HSPLjava/nio/HeapByteBuffer;->getShort(I)S
 HSPLjava/nio/HeapByteBuffer;->getUnchecked(I[III)V
 HSPLjava/nio/HeapByteBuffer;->getUnchecked(I[SII)V
 HSPLjava/nio/HeapByteBuffer;->isDirect()Z
 HSPLjava/nio/HeapByteBuffer;->isReadOnly()Z
 HSPLjava/nio/HeapByteBuffer;->ix(I)I
 HSPLjava/nio/HeapByteBuffer;->put(B)Ljava/nio/ByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->put(IB)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->put([BII)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->put(IB)Ljava/nio/ByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->put([BII)Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->putChar(C)Ljava/nio/ByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->putFloat(F)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->putInt(I)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->putInt(II)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->putFloat(F)Ljava/nio/ByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->putInt(I)Ljava/nio/ByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->putInt(II)Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->putLong(IJ)Ljava/nio/ByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->putLong(J)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->putShort(IS)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->putShort(S)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->slice()Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->putLong(J)Ljava/nio/ByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->putShort(IS)Ljava/nio/ByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->putShort(S)Ljava/nio/ByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->slice()Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapCharBuffer;-><init>(II)V
 HSPLjava/nio/HeapCharBuffer;-><init>(IIZ)V
 HSPLjava/nio/HeapCharBuffer;-><init>([CII)V
@@ -3757,7 +3806,7 @@
 HSPLjava/nio/HeapCharBuffer;-><init>([CIIZ)V
 HSPLjava/nio/HeapCharBuffer;->get(I)C
 HSPLjava/nio/HeapCharBuffer;->ix(I)I
-HSPLjava/nio/HeapCharBuffer;->put(Ljava/nio/CharBuffer;)Ljava/nio/CharBuffer;+]Ljava/nio/HeapCharBuffer;Ljava/nio/HeapCharBuffer;
+HSPLjava/nio/HeapCharBuffer;->put(Ljava/nio/CharBuffer;)Ljava/nio/CharBuffer;
 HSPLjava/nio/HeapCharBuffer;->put([CII)Ljava/nio/CharBuffer;
 HSPLjava/nio/HeapCharBuffer;->slice()Ljava/nio/CharBuffer;
 HSPLjava/nio/HeapCharBuffer;->toString(II)Ljava/lang/String;
@@ -3780,13 +3829,13 @@
 HSPLjava/nio/MappedByteBuffer;-><init>(IIIILjava/io/FileDescriptor;)V
 HSPLjava/nio/MappedByteBuffer;-><init>(IIII[BI)V
 HSPLjava/nio/MappedByteBuffer;->checkMapped()V
-HSPLjava/nio/MappedByteBuffer;->load()Ljava/nio/MappedByteBuffer;+]Ljava/nio/MappedByteBuffer;Ljava/nio/DirectByteBuffer;]Lsun/misc/Unsafe;Lsun/misc/Unsafe;
+HSPLjava/nio/MappedByteBuffer;->load()Ljava/nio/MappedByteBuffer;
 HSPLjava/nio/MappedByteBuffer;->mappingAddress(J)J
 HSPLjava/nio/MappedByteBuffer;->mappingLength(J)J
 HSPLjava/nio/MappedByteBuffer;->mappingOffset()J
 HSPLjava/nio/NIOAccess;->getBaseArray(Ljava/nio/Buffer;)Ljava/lang/Object;
 HSPLjava/nio/NIOAccess;->getBaseArrayOffset(Ljava/nio/Buffer;)I
-HSPLjava/nio/NioUtils;->freeDirectBuffer(Ljava/nio/ByteBuffer;)V
+HSPLjava/nio/NioUtils;->freeDirectBuffer(Ljava/nio/ByteBuffer;)V+]Ljava/nio/DirectByteBuffer$MemoryRef;Ljava/nio/DirectByteBuffer$MemoryRef;
 HSPLjava/nio/ShortBuffer;-><init>(IIII)V
 HSPLjava/nio/ShortBuffer;-><init>(IIII[SI)V
 HSPLjava/nio/ShortBuffer;->get([S)Ljava/nio/ShortBuffer;
@@ -3823,9 +3872,9 @@
 HSPLjava/nio/channels/SocketChannel;->validOps()I
 HSPLjava/nio/channels/spi/AbstractInterruptibleChannel$1;-><init>(Ljava/nio/channels/spi/AbstractInterruptibleChannel;)V
 HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;-><init>()V
-HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->begin()V+]Ljava/lang/Thread;Landroid/os/HandlerThread;
-HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->blockedOn(Lsun/nio/ch/Interruptible;)V+]Ljava/lang/Thread;Landroid/os/HandlerThread;
-HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->close()V
+HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->begin()V
+HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->blockedOn(Lsun/nio/ch/Interruptible;)V
+HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->close()V+]Ljava/nio/channels/spi/AbstractInterruptibleChannel;Lsun/nio/ch/FileChannelImpl;
 HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->end(Z)V
 HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->isOpen()Z
 HSPLjava/nio/channels/spi/AbstractSelectableChannel;-><init>(Ljava/nio/channels/spi/SelectorProvider;)V
@@ -3858,7 +3907,6 @@
 HSPLjava/nio/channels/spi/SelectorProvider;->provider()Ljava/nio/channels/spi/SelectorProvider;
 HSPLjava/nio/charset/Charset;-><init>(Ljava/lang/String;[Ljava/lang/String;)V
 HSPLjava/nio/charset/Charset;->aliases()Ljava/util/Set;
-HSPLjava/nio/charset/Charset;->atBugLevel(Ljava/lang/String;)Z
 HSPLjava/nio/charset/Charset;->cache(Ljava/lang/String;Ljava/nio/charset/Charset;)V
 HSPLjava/nio/charset/Charset;->checkName(Ljava/lang/String;)V
 HSPLjava/nio/charset/Charset;->decode(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;
@@ -3878,19 +3926,19 @@
 HSPLjava/nio/charset/CharsetDecoder;->averageCharsPerByte()F
 HSPLjava/nio/charset/CharsetDecoder;->charset()Ljava/nio/charset/Charset;
 HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;
-HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;Ljava/nio/CharBuffer;Z)Ljava/nio/charset/CoderResult;
-HSPLjava/nio/charset/CharsetDecoder;->flush(Ljava/nio/CharBuffer;)Ljava/nio/charset/CoderResult;
+HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;Ljava/nio/CharBuffer;Z)Ljava/nio/charset/CoderResult;+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
+HSPLjava/nio/charset/CharsetDecoder;->flush(Ljava/nio/CharBuffer;)Ljava/nio/charset/CoderResult;+]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/nio/charset/CharsetDecoder;->implFlush(Ljava/nio/CharBuffer;)Ljava/nio/charset/CoderResult;
 HSPLjava/nio/charset/CharsetDecoder;->implOnMalformedInput(Ljava/nio/charset/CodingErrorAction;)V
 HSPLjava/nio/charset/CharsetDecoder;->implOnUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)V
 HSPLjava/nio/charset/CharsetDecoder;->implReset()V
 HSPLjava/nio/charset/CharsetDecoder;->malformedInputAction()Ljava/nio/charset/CodingErrorAction;
 HSPLjava/nio/charset/CharsetDecoder;->maxCharsPerByte()F
-HSPLjava/nio/charset/CharsetDecoder;->onMalformedInput(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;
+HSPLjava/nio/charset/CharsetDecoder;->onMalformedInput(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/nio/charset/CharsetDecoder;->onUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;
-HSPLjava/nio/charset/CharsetDecoder;->replaceWith(Ljava/lang/String;)Ljava/nio/charset/CharsetDecoder;
+HSPLjava/nio/charset/CharsetDecoder;->replaceWith(Ljava/lang/String;)Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/nio/charset/CharsetDecoder;->replacement()Ljava/lang/String;
-HSPLjava/nio/charset/CharsetDecoder;->reset()Ljava/nio/charset/CharsetDecoder;
+HSPLjava/nio/charset/CharsetDecoder;->reset()Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/nio/charset/CharsetDecoder;->unmappableCharacterAction()Ljava/nio/charset/CodingErrorAction;
 HSPLjava/nio/charset/CharsetEncoder;-><init>(Ljava/nio/charset/Charset;FF)V
 HSPLjava/nio/charset/CharsetEncoder;-><init>(Ljava/nio/charset/Charset;FF[B)V
@@ -3900,8 +3948,8 @@
 HSPLjava/nio/charset/CharsetEncoder;->canEncode(Ljava/nio/CharBuffer;)Z
 HSPLjava/nio/charset/CharsetEncoder;->charset()Ljava/nio/charset/Charset;
 HSPLjava/nio/charset/CharsetEncoder;->encode(Ljava/nio/CharBuffer;)Ljava/nio/ByteBuffer;
-HSPLjava/nio/charset/CharsetEncoder;->encode(Ljava/nio/CharBuffer;Ljava/nio/ByteBuffer;Z)Ljava/nio/charset/CoderResult;
-HSPLjava/nio/charset/CharsetEncoder;->flush(Ljava/nio/ByteBuffer;)Ljava/nio/charset/CoderResult;
+HSPLjava/nio/charset/CharsetEncoder;->encode(Ljava/nio/CharBuffer;Ljava/nio/ByteBuffer;Z)Ljava/nio/charset/CoderResult;+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;
+HSPLjava/nio/charset/CharsetEncoder;->flush(Ljava/nio/ByteBuffer;)Ljava/nio/charset/CoderResult;+]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;
 HSPLjava/nio/charset/CharsetEncoder;->implFlush(Ljava/nio/ByteBuffer;)Ljava/nio/charset/CoderResult;
 HSPLjava/nio/charset/CharsetEncoder;->implOnMalformedInput(Ljava/nio/charset/CodingErrorAction;)V
 HSPLjava/nio/charset/CharsetEncoder;->implOnUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)V
@@ -3912,7 +3960,7 @@
 HSPLjava/nio/charset/CharsetEncoder;->onUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetEncoder;
 HSPLjava/nio/charset/CharsetEncoder;->replaceWith([B)Ljava/nio/charset/CharsetEncoder;
 HSPLjava/nio/charset/CharsetEncoder;->replacement()[B
-HSPLjava/nio/charset/CharsetEncoder;->reset()Ljava/nio/charset/CharsetEncoder;
+HSPLjava/nio/charset/CharsetEncoder;->reset()Ljava/nio/charset/CharsetEncoder;+]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;
 HSPLjava/nio/charset/CharsetEncoder;->unmappableCharacterAction()Ljava/nio/charset/CodingErrorAction;
 HSPLjava/nio/charset/CoderResult;->isError()Z
 HSPLjava/nio/charset/CoderResult;->isOverflow()Z
@@ -3943,6 +3991,7 @@
 HSPLjava/nio/file/attribute/FileTime;-><init>(JLjava/util/concurrent/TimeUnit;Ljava/time/Instant;)V
 HSPLjava/nio/file/attribute/FileTime;->append(Ljava/lang/StringBuilder;II)Ljava/lang/StringBuilder;
 HSPLjava/nio/file/attribute/FileTime;->from(JLjava/util/concurrent/TimeUnit;)Ljava/nio/file/attribute/FileTime;
+HSPLjava/nio/file/attribute/FileTime;->toMillis()J
 HSPLjava/nio/file/attribute/FileTime;->toString()Ljava/lang/String;
 HSPLjava/nio/file/spi/FileSystemProvider;->newInputStream(Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Ljava/io/InputStream;
 HSPLjava/security/AccessControlContext;-><init>([Ljava/security/ProtectionDomain;)V
@@ -3954,7 +4003,7 @@
 HSPLjava/security/CodeSigner;-><init>(Ljava/security/cert/CertPath;Ljava/security/Timestamp;)V
 HSPLjava/security/CodeSigner;->getSignerCertPath()Ljava/security/cert/CertPath;
 HSPLjava/security/DigestInputStream;-><init>(Ljava/io/InputStream;Ljava/security/MessageDigest;)V
-HSPLjava/security/DigestInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/FileInputStream;]Ljava/security/MessageDigest;Ljava/security/MessageDigest$Delegate;
+HSPLjava/security/DigestInputStream;->read([BII)I
 HSPLjava/security/DigestInputStream;->setMessageDigest(Ljava/security/MessageDigest;)V
 HSPLjava/security/GeneralSecurityException;-><init>(Ljava/lang/String;)V
 HSPLjava/security/KeyFactory;-><init>(Ljava/lang/String;)V
@@ -3995,16 +4044,16 @@
 HSPLjava/security/MessageDigest$Delegate;-><init>(Ljava/security/MessageDigestSpi;Ljava/lang/String;)V
 HSPLjava/security/MessageDigest$Delegate;->clone()Ljava/lang/Object;
 HSPLjava/security/MessageDigest$Delegate;->engineDigest()[B
-HSPLjava/security/MessageDigest$Delegate;->engineDigest([BII)I+]Ljava/security/MessageDigestSpi;Lcom/android/org/conscrypt/OpenSSLMessageDigestJDK$SHA1;
+HSPLjava/security/MessageDigest$Delegate;->engineDigest([BII)I
 HSPLjava/security/MessageDigest$Delegate;->engineGetDigestLength()I
 HSPLjava/security/MessageDigest$Delegate;->engineReset()V
 HSPLjava/security/MessageDigest$Delegate;->engineUpdate(B)V
 HSPLjava/security/MessageDigest$Delegate;->engineUpdate(Ljava/nio/ByteBuffer;)V
-HSPLjava/security/MessageDigest$Delegate;->engineUpdate([BII)V+]Ljava/security/MessageDigestSpi;Lcom/android/org/conscrypt/OpenSSLMessageDigestJDK$SHA1;
+HSPLjava/security/MessageDigest$Delegate;->engineUpdate([BII)V+]Ljava/security/MessageDigestSpi;missing_types
 HSPLjava/security/MessageDigest;-><init>(Ljava/lang/String;)V
 HSPLjava/security/MessageDigest;->digest()[B
 HSPLjava/security/MessageDigest;->digest([B)[B
-HSPLjava/security/MessageDigest;->digest([BII)I+]Ljava/security/MessageDigest;Ljava/security/MessageDigest$Delegate;
+HSPLjava/security/MessageDigest;->digest([BII)I
 HSPLjava/security/MessageDigest;->getDigestLength()I
 HSPLjava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;
 HSPLjava/security/MessageDigest;->getInstance(Ljava/lang/String;Ljava/lang/String;)Ljava/security/MessageDigest;
@@ -4014,9 +4063,9 @@
 HSPLjava/security/MessageDigest;->update(B)V
 HSPLjava/security/MessageDigest;->update(Ljava/nio/ByteBuffer;)V
 HSPLjava/security/MessageDigest;->update([B)V
-HSPLjava/security/MessageDigest;->update([BII)V+]Ljava/security/MessageDigest;Ljava/security/MessageDigest$Delegate;
+HSPLjava/security/MessageDigest;->update([BII)V
 HSPLjava/security/MessageDigestSpi;-><init>()V
-HSPLjava/security/MessageDigestSpi;->engineDigest([BII)I+]Ljava/security/MessageDigestSpi;Lcom/android/org/conscrypt/OpenSSLMessageDigestJDK$SHA1;
+HSPLjava/security/MessageDigestSpi;->engineDigest([BII)I
 HSPLjava/security/MessageDigestSpi;->engineUpdate(Ljava/nio/ByteBuffer;)V
 HSPLjava/security/NoSuchAlgorithmException;-><init>(Ljava/lang/String;)V
 HSPLjava/security/Provider$EngineDescription;->getConstructorParameterClass()Ljava/lang/Class;
@@ -4032,20 +4081,20 @@
 HSPLjava/security/Provider$Service;->getAlgorithm()Ljava/lang/String;
 HSPLjava/security/Provider$Service;->getAttribute(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/security/Provider$Service;->getClassName()Ljava/lang/String;
-HSPLjava/security/Provider$Service;->getImplClass()Ljava/lang/Class;
+HSPLjava/security/Provider$Service;->getImplClass()Ljava/lang/Class;+]Ljava/lang/ref/Reference;Ljava/lang/ref/WeakReference;
 HSPLjava/security/Provider$Service;->getKeyClass(Ljava/lang/String;)Ljava/lang/Class;
 HSPLjava/security/Provider$Service;->getProvider()Ljava/security/Provider;
 HSPLjava/security/Provider$Service;->getType()Ljava/lang/String;
 HSPLjava/security/Provider$Service;->hasKeyAttributes()Z
 HSPLjava/security/Provider$Service;->isValid()Z
-HSPLjava/security/Provider$Service;->newInstance(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/security/Provider$Service;->newInstance(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/security/Provider;missing_types]Ljava/util/Map;Ljava/util/HashMap;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor;
 HSPLjava/security/Provider$Service;->supportsKeyClass(Ljava/security/Key;)Z
 HSPLjava/security/Provider$Service;->supportsKeyFormat(Ljava/security/Key;)Z
 HSPLjava/security/Provider$Service;->supportsParameter(Ljava/lang/Object;)Z
 HSPLjava/security/Provider$ServiceKey;-><init>(Ljava/lang/String;Ljava/lang/String;Z)V
 HSPLjava/security/Provider$ServiceKey;-><init>(Ljava/lang/String;Ljava/lang/String;ZLjava/security/Provider$ServiceKey-IA;)V
 HSPLjava/security/Provider$ServiceKey;->equals(Ljava/lang/Object;)Z
-HSPLjava/security/Provider$ServiceKey;->hashCode()I
+HSPLjava/security/Provider$ServiceKey;->hashCode()I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/security/Provider$ServiceKey;->matches(Ljava/lang/String;Ljava/lang/String;)Z
 HSPLjava/security/Provider$UString;-><init>(Ljava/lang/String;)V
 HSPLjava/security/Provider$UString;->equals(Ljava/lang/Object;)Z
@@ -4058,7 +4107,7 @@
 HSPLjava/security/Provider;->ensureLegacyParsed()V
 HSPLjava/security/Provider;->getEngineName(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/security/Provider;->getName()Ljava/lang/String;
-HSPLjava/security/Provider;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;
+HSPLjava/security/Provider;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;+]Ljava/security/Provider$ServiceKey;Ljava/security/Provider$ServiceKey;]Ljava/util/Map;Ljava/util/LinkedHashMap;
 HSPLjava/security/Provider;->getServices()Ljava/util/Set;
 HSPLjava/security/Provider;->getTypeAndAlgorithm(Ljava/lang/String;)[Ljava/lang/String;
 HSPLjava/security/Provider;->implPut(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
@@ -4079,7 +4128,7 @@
 HSPLjava/security/SecureRandom;->setSeed(J)V
 HSPLjava/security/SecureRandomSpi;-><init>()V
 HSPLjava/security/Security;->addProvider(Ljava/security/Provider;)I
-HSPLjava/security/Security;->getImpl(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/Object;
+HSPLjava/security/Security;->getImpl(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/Object;+]Lsun/security/jca/GetInstance$Instance;Lsun/security/jca/GetInstance$Instance;
 HSPLjava/security/Security;->getImpl(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)[Ljava/lang/Object;
 HSPLjava/security/Security;->getProperty(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/security/Security;->getProvider(Ljava/lang/String;)Ljava/security/Provider;
@@ -4224,7 +4273,7 @@
 HSPLjava/text/Collator;->setDecomposition(I)V
 HSPLjava/text/Collator;->setStrength(I)V
 HSPLjava/text/DateFormat;-><init>()V
-HSPLjava/text/DateFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
+HSPLjava/text/DateFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;+]Ljava/lang/Number;Ljava/lang/Long;]Ljava/text/DateFormat;Ljava/text/SimpleDateFormat;
 HSPLjava/text/DateFormat;->format(Ljava/util/Date;)Ljava/lang/String;
 HSPLjava/text/DateFormat;->get(IIILjava/util/Locale;)Ljava/text/DateFormat;
 HSPLjava/text/DateFormat;->getDateInstance(ILjava/util/Locale;)Ljava/text/DateFormat;
@@ -4256,7 +4305,7 @@
 HSPLjava/text/DecimalFormat;->clone()Ljava/lang/Object;
 HSPLjava/text/DecimalFormat;->equals(Ljava/lang/Object;)Z
 HSPLjava/text/DecimalFormat;->format(DLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
-HSPLjava/text/DecimalFormat;->format(JLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
+HSPLjava/text/DecimalFormat;->format(JLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;+]Ljava/text/FieldPosition;Ljava/text/DontCareFieldPosition;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
 HSPLjava/text/DecimalFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
 HSPLjava/text/DecimalFormat;->getDecimalFormatSymbols()Ljava/text/DecimalFormatSymbols;
 HSPLjava/text/DecimalFormat;->getIcuFieldPosition(Ljava/text/FieldPosition;)Ljava/text/FieldPosition;
@@ -4275,25 +4324,26 @@
 HSPLjava/text/DecimalFormat;->setDecimalSeparatorAlwaysShown(Z)V
 HSPLjava/text/DecimalFormat;->setGroupingUsed(Z)V
 HSPLjava/text/DecimalFormat;->setMaximumFractionDigits(I)V
-HSPLjava/text/DecimalFormat;->setMaximumIntegerDigits(I)V
+HSPLjava/text/DecimalFormat;->setMaximumIntegerDigits(I)V+]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
 HSPLjava/text/DecimalFormat;->setMinimumFractionDigits(I)V
-HSPLjava/text/DecimalFormat;->setMinimumIntegerDigits(I)V
+HSPLjava/text/DecimalFormat;->setMinimumIntegerDigits(I)V+]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
 HSPLjava/text/DecimalFormat;->setParseIntegerOnly(Z)V
 HSPLjava/text/DecimalFormat;->toPattern()Ljava/lang/String;
-HSPLjava/text/DecimalFormat;->updateFieldsFromIcu()V
+HSPLjava/text/DecimalFormat;->updateFieldsFromIcu()V+]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
 HSPLjava/text/DecimalFormatSymbols;-><init>(Ljava/util/Locale;)V
 HSPLjava/text/DecimalFormatSymbols;->clone()Ljava/lang/Object;
-HSPLjava/text/DecimalFormatSymbols;->fromIcuInstance(Landroid/icu/text/DecimalFormatSymbols;)Ljava/text/DecimalFormatSymbols;
+HSPLjava/text/DecimalFormatSymbols;->findNonFormatChar(Ljava/lang/String;C)C
+HSPLjava/text/DecimalFormatSymbols;->fromIcuInstance(Landroid/icu/text/DecimalFormatSymbols;)Ljava/text/DecimalFormatSymbols;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Landroid/icu/text/DecimalFormatSymbols;Landroid/icu/text/DecimalFormatSymbols;]Landroid/icu/util/Currency;Landroid/icu/util/Currency;
 HSPLjava/text/DecimalFormatSymbols;->getCurrency()Ljava/util/Currency;
 HSPLjava/text/DecimalFormatSymbols;->getDecimalSeparator()C
 HSPLjava/text/DecimalFormatSymbols;->getGroupingSeparator()C
-HSPLjava/text/DecimalFormatSymbols;->getIcuDecimalFormatSymbols()Landroid/icu/text/DecimalFormatSymbols;
+HSPLjava/text/DecimalFormatSymbols;->getIcuDecimalFormatSymbols()Landroid/icu/text/DecimalFormatSymbols;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Ljava/util/Currency;Ljava/util/Currency;]Landroid/icu/text/DecimalFormatSymbols;Landroid/icu/text/DecimalFormatSymbols;
 HSPLjava/text/DecimalFormatSymbols;->getInfinity()Ljava/lang/String;
 HSPLjava/text/DecimalFormatSymbols;->getInstance(Ljava/util/Locale;)Ljava/text/DecimalFormatSymbols;
 HSPLjava/text/DecimalFormatSymbols;->getNaN()Ljava/lang/String;
 HSPLjava/text/DecimalFormatSymbols;->getZeroDigit()C
-HSPLjava/text/DecimalFormatSymbols;->initialize(Ljava/util/Locale;)V
-HSPLjava/text/DecimalFormatSymbols;->initializeCurrency(Ljava/util/Locale;)V+]Ljava/util/Currency;Ljava/util/Currency;]Ljava/util/Locale;Ljava/util/Locale;
+HSPLjava/text/DecimalFormatSymbols;->initialize(Ljava/util/Locale;)V+]Llibcore/icu/DecimalFormatData;Llibcore/icu/DecimalFormatData;
+HSPLjava/text/DecimalFormatSymbols;->initializeCurrency(Ljava/util/Locale;)V
 HSPLjava/text/DecimalFormatSymbols;->maybeStripMarkers(Ljava/lang/String;C)C
 HSPLjava/text/DecimalFormatSymbols;->setCurrency(Ljava/util/Currency;)V
 HSPLjava/text/DecimalFormatSymbols;->setCurrencySymbol(Ljava/lang/String;)V
@@ -4305,6 +4355,7 @@
 HSPLjava/text/DecimalFormatSymbols;->setInternationalCurrencySymbol(Ljava/lang/String;)V
 HSPLjava/text/DecimalFormatSymbols;->setMinusSign(C)V
 HSPLjava/text/DecimalFormatSymbols;->setMonetaryDecimalSeparator(C)V
+HSPLjava/text/DecimalFormatSymbols;->setMonetaryGroupingSeparator(C)V
 HSPLjava/text/DecimalFormatSymbols;->setNaN(Ljava/lang/String;)V
 HSPLjava/text/DecimalFormatSymbols;->setPatternSeparator(C)V
 HSPLjava/text/DecimalFormatSymbols;->setPerMill(C)V
@@ -4325,12 +4376,12 @@
 HSPLjava/text/FieldPosition;->setEndIndex(I)V
 HSPLjava/text/Format;-><init>()V
 HSPLjava/text/Format;->clone()Ljava/lang/Object;
-HSPLjava/text/Format;->format(Ljava/lang/Object;)Ljava/lang/String;
+HSPLjava/text/Format;->format(Ljava/lang/Object;)Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/text/Format;Ljava/text/SimpleDateFormat;
 HSPLjava/text/IcuIteratorWrapper;-><init>(Landroid/icu/text/BreakIterator;)V
 HSPLjava/text/IcuIteratorWrapper;->checkOffset(ILjava/text/CharacterIterator;)V
-HSPLjava/text/IcuIteratorWrapper;->following(I)I+]Landroid/icu/text/BreakIterator;Landroid/icu/text/RuleBasedBreakIterator;]Ljava/text/IcuIteratorWrapper;Ljava/text/IcuIteratorWrapper;
+HSPLjava/text/IcuIteratorWrapper;->following(I)I
 HSPLjava/text/IcuIteratorWrapper;->getText()Ljava/text/CharacterIterator;
-HSPLjava/text/IcuIteratorWrapper;->isBoundary(I)Z+]Landroid/icu/text/BreakIterator;Landroid/icu/text/RuleBasedBreakIterator;]Ljava/text/IcuIteratorWrapper;Ljava/text/IcuIteratorWrapper;
+HSPLjava/text/IcuIteratorWrapper;->isBoundary(I)Z
 HSPLjava/text/IcuIteratorWrapper;->next()I
 HSPLjava/text/IcuIteratorWrapper;->preceding(I)I
 HSPLjava/text/IcuIteratorWrapper;->setText(Ljava/lang/String;)V
@@ -4350,7 +4401,7 @@
 HSPLjava/text/NumberFormat;->format(J)Ljava/lang/String;
 HSPLjava/text/NumberFormat;->getInstance()Ljava/text/NumberFormat;
 HSPLjava/text/NumberFormat;->getInstance(Ljava/util/Locale;)Ljava/text/NumberFormat;
-HSPLjava/text/NumberFormat;->getInstance(Ljava/util/Locale;I)Ljava/text/NumberFormat;
+HSPLjava/text/NumberFormat;->getInstance(Ljava/util/Locale;Ljava/text/NumberFormat$Style;I)Ljava/text/NumberFormat;
 HSPLjava/text/NumberFormat;->getIntegerInstance()Ljava/text/NumberFormat;
 HSPLjava/text/NumberFormat;->getIntegerInstance(Ljava/util/Locale;)Ljava/text/NumberFormat;
 HSPLjava/text/NumberFormat;->getNumberInstance(Ljava/util/Locale;)Ljava/text/NumberFormat;
@@ -4376,10 +4427,10 @@
 HSPLjava/text/SimpleDateFormat;-><init>(Ljava/lang/String;)V
 HSPLjava/text/SimpleDateFormat;-><init>(Ljava/lang/String;Ljava/util/Locale;)V
 HSPLjava/text/SimpleDateFormat;->checkNegativeNumberExpression()V
-HSPLjava/text/SimpleDateFormat;->compile(Ljava/lang/String;)[C+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/text/SimpleDateFormat;->compile(Ljava/lang/String;)[C
 HSPLjava/text/SimpleDateFormat;->encode(IILjava/lang/StringBuilder;)V
-HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
-HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/Format$FieldDelegate;)Ljava/lang/StringBuffer;
+HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;+]Ljava/text/FieldPosition;Ljava/text/FieldPosition;
+HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/Format$FieldDelegate;)Ljava/lang/StringBuffer;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
 HSPLjava/text/SimpleDateFormat;->formatMonth(IIILjava/lang/StringBuffer;ZZII)Ljava/lang/String;
 HSPLjava/text/SimpleDateFormat;->formatWeekday(IIZZ)Ljava/lang/String;
 HSPLjava/text/SimpleDateFormat;->getDateTimeFormat(IILjava/util/Locale;)Ljava/lang/String;
@@ -4419,6 +4470,7 @@
 HSPLjava/time/Clock$SystemClock;->instant()Ljava/time/Instant;
 HSPLjava/time/Clock$SystemClock;->millis()J
 HSPLjava/time/Clock;-><init>()V
+HSPLjava/time/Clock;->currentInstant()Ljava/time/Instant;
 HSPLjava/time/Clock;->systemDefaultZone()Ljava/time/Clock;
 HSPLjava/time/Clock;->systemUTC()Ljava/time/Clock;
 HSPLjava/time/DayOfWeek;->getValue()I
@@ -4451,6 +4503,7 @@
 HSPLjava/time/Instant;->isAfter(Ljava/time/Instant;)Z
 HSPLjava/time/Instant;->isSupported(Ljava/time/temporal/TemporalField;)Z
 HSPLjava/time/Instant;->minus(JLjava/time/temporal/TemporalUnit;)Ljava/time/Instant;
+HSPLjava/time/Instant;->minusMillis(J)Ljava/time/Instant;
 HSPLjava/time/Instant;->nanosUntil(Ljava/time/Instant;)J
 HSPLjava/time/Instant;->now()Ljava/time/Instant;
 HSPLjava/time/Instant;->ofEpochMilli(J)Ljava/time/Instant;
@@ -4508,7 +4561,7 @@
 HSPLjava/time/LocalDateTime;->isBefore(Ljava/time/chrono/ChronoLocalDateTime;)Z
 HSPLjava/time/LocalDateTime;->isSupported(Ljava/time/temporal/TemporalField;)Z
 HSPLjava/time/LocalDateTime;->now()Ljava/time/LocalDateTime;
-HSPLjava/time/LocalDateTime;->now(Ljava/time/Clock;)Ljava/time/LocalDateTime;+]Ljava/time/Clock;Ljava/time/Clock$SystemClock;]Ljava/time/Instant;Ljava/time/Instant;]Ljava/time/ZoneId;Ljava/time/ZoneRegion;]Ljava/time/zone/ZoneRules;Ljava/time/zone/ZoneRules;
+HSPLjava/time/LocalDateTime;->now(Ljava/time/Clock;)Ljava/time/LocalDateTime;
 HSPLjava/time/LocalDateTime;->of(Ljava/time/LocalDate;Ljava/time/LocalTime;)Ljava/time/LocalDateTime;
 HSPLjava/time/LocalDateTime;->ofEpochSecond(JILjava/time/ZoneOffset;)Ljava/time/LocalDateTime;
 HSPLjava/time/LocalDateTime;->ofInstant(Ljava/time/Instant;Ljava/time/ZoneId;)Ljava/time/LocalDateTime;
@@ -4612,7 +4665,6 @@
 HSPLjava/time/format/DateTimeFormatter;->parse(Ljava/lang/CharSequence;Ljava/time/temporal/TemporalQuery;)Ljava/lang/Object;
 HSPLjava/time/format/DateTimeFormatter;->parseResolved0(Ljava/lang/CharSequence;Ljava/text/ParsePosition;)Ljava/time/temporal/TemporalAccessor;
 HSPLjava/time/format/DateTimeFormatter;->parseUnresolved0(Ljava/lang/CharSequence;Ljava/text/ParsePosition;)Ljava/time/format/DateTimeParseContext;
-HSPLjava/time/format/DateTimeFormatterBuilder$3;-><clinit>()V
 HSPLjava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;-><init>(C)V
 HSPLjava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;->format(Ljava/time/format/DateTimePrintContext;Ljava/lang/StringBuilder;)Z
 HSPLjava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;->parse(Ljava/time/format/DateTimeParseContext;Ljava/lang/CharSequence;I)I
@@ -4663,7 +4715,7 @@
 HSPLjava/time/format/DateTimeParseContext;->toResolved(Ljava/time/format/ResolverStyle;Ljava/util/Set;)Ljava/time/temporal/TemporalAccessor;
 HSPLjava/time/format/DateTimePrintContext;-><init>(Ljava/time/temporal/TemporalAccessor;Ljava/time/format/DateTimeFormatter;)V
 HSPLjava/time/format/DateTimePrintContext;->adjust(Ljava/time/temporal/TemporalAccessor;Ljava/time/format/DateTimeFormatter;)Ljava/time/temporal/TemporalAccessor;
-HSPLjava/time/format/DateTimePrintContext;->getDecimalStyle()Ljava/time/format/DecimalStyle;+]Ljava/time/format/DateTimeFormatter;Ljava/time/format/DateTimeFormatter;
+HSPLjava/time/format/DateTimePrintContext;->getDecimalStyle()Ljava/time/format/DecimalStyle;
 HSPLjava/time/format/DateTimePrintContext;->getValue(Ljava/time/temporal/TemporalField;)Ljava/lang/Long;
 HSPLjava/time/format/DecimalStyle;->convertNumberToI18N(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/time/format/DecimalStyle;->convertToDigit(C)I
@@ -4758,16 +4810,16 @@
 HSPLjava/time/zone/ZoneRulesProvider;->getProvider(Ljava/lang/String;)Ljava/time/zone/ZoneRulesProvider;
 HSPLjava/time/zone/ZoneRulesProvider;->getRules(Ljava/lang/String;Z)Ljava/time/zone/ZoneRules;
 HSPLjava/util/AbstractCollection;-><init>()V
-HSPLjava/util/AbstractCollection;->addAll(Ljava/util/Collection;)Z+]Ljava/util/AbstractCollection;missing_types]Ljava/util/Collection;missing_types]Ljava/util/Iterator;missing_types
+HSPLjava/util/AbstractCollection;->addAll(Ljava/util/Collection;)Z+]Ljava/util/AbstractCollection;Ljava/util/HashSet;,Ljava/util/LinkedHashSet;]Ljava/util/Collection;megamorphic_types]Ljava/util/Iterator;megamorphic_types
 HSPLjava/util/AbstractCollection;->clear()V
-HSPLjava/util/AbstractCollection;->contains(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types]Ljava/util/Iterator;Ljava/util/AbstractList$Itr;
+HSPLjava/util/AbstractCollection;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/AbstractCollection;->containsAll(Ljava/util/Collection;)Z
 HSPLjava/util/AbstractCollection;->isEmpty()Z+]Ljava/util/AbstractCollection;missing_types
 HSPLjava/util/AbstractCollection;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/AbstractCollection;->removeAll(Ljava/util/Collection;)Z
 HSPLjava/util/AbstractCollection;->retainAll(Ljava/util/Collection;)Z
-HSPLjava/util/AbstractCollection;->toArray()[Ljava/lang/Object;+]Ljava/util/AbstractCollection;missing_types]Ljava/util/Iterator;megamorphic_types
-HSPLjava/util/AbstractCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/util/AbstractCollection;Ljava/util/ArrayList$SubList;,Lsun/security/jca/ProviderList$3;,Ljava/util/HashMap$Values;,Ljava/util/HashSet;]Ljava/lang/Object;missing_types]Ljava/lang/Class;Ljava/lang/Class;]Ljava/util/Iterator;Ljava/util/HashMap$KeyIterator;,Ljava/util/AbstractList$Itr;,Ljava/util/ArrayList$SubList$1;,Ljava/util/HashMap$ValueIterator;
+HSPLjava/util/AbstractCollection;->toArray()[Ljava/lang/Object;+]Ljava/util/AbstractCollection;missing_types]Ljava/util/Iterator;missing_types
+HSPLjava/util/AbstractCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/AbstractCollection;->toString()Ljava/lang/String;
 HSPLjava/util/AbstractList$Itr;-><init>(Ljava/util/AbstractList;)V
 HSPLjava/util/AbstractList$Itr;-><init>(Ljava/util/AbstractList;Ljava/util/AbstractList$Itr-IA;)V
@@ -4802,8 +4854,8 @@
 HSPLjava/util/AbstractList;-><init>()V
 HSPLjava/util/AbstractList;->add(Ljava/lang/Object;)Z
 HSPLjava/util/AbstractList;->clear()V
-HSPLjava/util/AbstractList;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types]Ljava/util/ListIterator;Ljava/util/ArrayList$ListItr;,Ljava/util/Collections$UnmodifiableList$1;]Ljava/util/AbstractList;Ljava/util/ArrayList;]Ljava/util/List;Ljava/util/ArrayList;,Ljava/util/Collections$UnmodifiableRandomAccessList;
-HSPLjava/util/AbstractList;->hashCode()I+]Ljava/lang/Object;missing_types]Ljava/util/AbstractList;Ljava/util/Collections$SingletonList;,Ljava/util/ArrayList;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr;,Ljava/util/Collections$1;
+HSPLjava/util/AbstractList;->equals(Ljava/lang/Object;)Z
+HSPLjava/util/AbstractList;->hashCode()I
 HSPLjava/util/AbstractList;->indexOf(Ljava/lang/Object;)I
 HSPLjava/util/AbstractList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/AbstractList;->listIterator()Ljava/util/ListIterator;
@@ -4820,7 +4872,7 @@
 HSPLjava/util/AbstractMap$SimpleEntry;->getKey()Ljava/lang/Object;
 HSPLjava/util/AbstractMap$SimpleEntry;->getValue()Ljava/lang/Object;
 HSPLjava/util/AbstractMap$SimpleImmutableEntry;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
-HSPLjava/util/AbstractMap$SimpleImmutableEntry;-><init>(Ljava/util/Map$Entry;)V
+HSPLjava/util/AbstractMap$SimpleImmutableEntry;-><init>(Ljava/util/Map$Entry;)V+]Ljava/util/Map$Entry;Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/AbstractMap$SimpleImmutableEntry;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/AbstractMap$SimpleImmutableEntry;->getKey()Ljava/lang/Object;
 HSPLjava/util/AbstractMap$SimpleImmutableEntry;->getValue()Ljava/lang/Object;
@@ -4829,11 +4881,11 @@
 HSPLjava/util/AbstractMap;->clear()V
 HSPLjava/util/AbstractMap;->clone()Ljava/lang/Object;
 HSPLjava/util/AbstractMap;->eq(Ljava/lang/Object;Ljava/lang/Object;)Z
-HSPLjava/util/AbstractMap;->equals(Ljava/lang/Object;)Z+]Ljava/util/Map$Entry;Ljava/util/HashMap$Node;]Ljava/util/AbstractMap;Ljava/util/HashMap;]Ljava/util/Map;Ljava/util/HashMap;]Ljava/util/Iterator;Ljava/util/HashMap$EntryIterator;]Ljava/util/Set;Ljava/util/HashMap$EntrySet;
-HSPLjava/util/AbstractMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map$Entry;Ljava/util/AbstractMap$SimpleImmutableEntry;]Ljava/lang/Object;Ljava/lang/Boolean;]Ljava/util/Iterator;Ljava/util/Collections$UnmodifiableCollection$1;
-HSPLjava/util/AbstractMap;->hashCode()I+]Ljava/util/Map$Entry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;,Ljava/util/HashMap$Node;]Ljava/util/AbstractMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;]Ljava/util/Iterator;Ljava/util/HashMap$EntryIterator;,Ljava/util/LinkedHashMap$LinkedEntryIterator;]Ljava/util/Set;Ljava/util/LinkedHashMap$LinkedEntrySet;,Ljava/util/HashMap$EntrySet;
-HSPLjava/util/AbstractMap;->isEmpty()Z
-HSPLjava/util/AbstractMap;->putAll(Ljava/util/Map;)V
+HSPLjava/util/AbstractMap;->equals(Ljava/lang/Object;)Z+]Ljava/util/Map$Entry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;]Ljava/lang/Object;missing_types]Ljava/util/AbstractMap;Ljava/util/LinkedHashMap;]Ljava/util/Map;Ljava/util/LinkedHashMap;]Ljava/util/Iterator;Ljava/util/LinkedHashMap$LinkedEntryIterator;]Ljava/util/Set;Ljava/util/LinkedHashMap$LinkedEntrySet;
+HSPLjava/util/AbstractMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/AbstractMap;->hashCode()I
+HSPLjava/util/AbstractMap;->isEmpty()Z+]Ljava/util/AbstractMap;missing_types
+HSPLjava/util/AbstractMap;->putAll(Ljava/util/Map;)V+]Ljava/util/Map$Entry;Ljava/util/AbstractMap$SimpleImmutableEntry;]Ljava/util/Map;Ljava/util/Collections$SingletonMap;]Ljava/util/Iterator;Ljava/util/Collections$1;]Ljava/util/Set;Ljava/util/Collections$SingletonSet;
 HSPLjava/util/AbstractMap;->size()I
 HSPLjava/util/AbstractMap;->toString()Ljava/lang/String;
 HSPLjava/util/AbstractMap;->values()Ljava/util/Collection;
@@ -4846,32 +4898,38 @@
 HSPLjava/util/AbstractSequentialList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/AbstractSet;-><init>()V
 HSPLjava/util/AbstractSet;->equals(Ljava/lang/Object;)Z
-HSPLjava/util/AbstractSet;->hashCode()I+]Ljava/lang/Object;missing_types]Ljava/util/AbstractSet;Ljava/util/HashSet;,Ljava/util/LinkedHashSet;]Ljava/util/Iterator;Ljava/util/HashMap$KeyIterator;,Ljava/util/LinkedHashMap$LinkedKeyIterator;
-HSPLjava/util/AbstractSet;->removeAll(Ljava/util/Collection;)Z+]Ljava/util/Collection;missing_types]Ljava/util/AbstractSet;Ljava/util/TreeSet;,Ljava/util/HashSet;]Ljava/util/Iterator;missing_types
+HSPLjava/util/AbstractSet;->hashCode()I
+HSPLjava/util/AbstractSet;->removeAll(Ljava/util/Collection;)Z
+HSPLjava/util/ArrayDeque$$ExternalSyntheticLambda1;-><init>(Ljava/util/ArrayDeque;)V
+HSPLjava/util/ArrayDeque$$ExternalSyntheticLambda1;->accept(Ljava/lang/Object;)V
 HSPLjava/util/ArrayDeque$DeqIterator;-><init>(Ljava/util/ArrayDeque;)V
-HSPLjava/util/ArrayDeque$DeqIterator;-><init>(Ljava/util/ArrayDeque;Ljava/util/ArrayDeque$DeqIterator-IA;)V
 HSPLjava/util/ArrayDeque$DeqIterator;->hasNext()Z
 HSPLjava/util/ArrayDeque$DeqIterator;->next()Ljava/lang/Object;
+HSPLjava/util/ArrayDeque$DeqIterator;->postDelete(Z)V
 HSPLjava/util/ArrayDeque$DeqIterator;->remove()V
 HSPLjava/util/ArrayDeque$DescendingIterator;-><init>(Ljava/util/ArrayDeque;)V
-HSPLjava/util/ArrayDeque$DescendingIterator;-><init>(Ljava/util/ArrayDeque;Ljava/util/ArrayDeque$DescendingIterator-IA;)V
-HSPLjava/util/ArrayDeque$DescendingIterator;->hasNext()Z
 HSPLjava/util/ArrayDeque$DescendingIterator;->next()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;-><init>()V
 HSPLjava/util/ArrayDeque;-><init>(I)V
 HSPLjava/util/ArrayDeque;-><init>(Ljava/util/Collection;)V
 HSPLjava/util/ArrayDeque;->add(Ljava/lang/Object;)Z+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque;
+HSPLjava/util/ArrayDeque;->addAll(Ljava/util/Collection;)Z+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque;]Ljava/util/Collection;Ljava/util/ArrayDeque;
 HSPLjava/util/ArrayDeque;->addFirst(Ljava/lang/Object;)V
 HSPLjava/util/ArrayDeque;->addLast(Ljava/lang/Object;)V
-HSPLjava/util/ArrayDeque;->allocateElements(I)V
 HSPLjava/util/ArrayDeque;->checkInvariants()V
+HSPLjava/util/ArrayDeque;->circularClear([Ljava/lang/Object;II)V
 HSPLjava/util/ArrayDeque;->clear()V
 HSPLjava/util/ArrayDeque;->contains(Ljava/lang/Object;)Z
+HSPLjava/util/ArrayDeque;->copyElements(Ljava/util/Collection;)V
+HSPLjava/util/ArrayDeque;->dec(II)I
 HSPLjava/util/ArrayDeque;->delete(I)Z
 HSPLjava/util/ArrayDeque;->descendingIterator()Ljava/util/Iterator;
-HSPLjava/util/ArrayDeque;->doubleCapacity()V
+HSPLjava/util/ArrayDeque;->elementAt([Ljava/lang/Object;I)Ljava/lang/Object;
+HSPLjava/util/ArrayDeque;->forEach(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;Ljava/util/ArrayDeque$$ExternalSyntheticLambda1;
 HSPLjava/util/ArrayDeque;->getFirst()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->getLast()Ljava/lang/Object;
+HSPLjava/util/ArrayDeque;->grow(I)V
+HSPLjava/util/ArrayDeque;->inc(II)I
 HSPLjava/util/ArrayDeque;->isEmpty()Z
 HSPLjava/util/ArrayDeque;->iterator()Ljava/util/Iterator;
 HSPLjava/util/ArrayDeque;->offer(Ljava/lang/Object;)Z
@@ -4890,72 +4948,93 @@
 HSPLjava/util/ArrayDeque;->removeFirstOccurrence(Ljava/lang/Object;)Z
 HSPLjava/util/ArrayDeque;->removeLast()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->size()I
+HSPLjava/util/ArrayDeque;->sub(III)I
 HSPLjava/util/ArrayDeque;->toArray()[Ljava/lang/Object;
+HSPLjava/util/ArrayDeque;->toArray(Ljava/lang/Class;)[Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/ArrayList$ArrayListSpliterator;-><init>(Ljava/util/ArrayList;III)V
 HSPLjava/util/ArrayList$ArrayListSpliterator;->characteristics()I
 HSPLjava/util/ArrayList$ArrayListSpliterator;->estimateSize()J
 HSPLjava/util/ArrayList$ArrayListSpliterator;->forEachRemaining(Ljava/util/function/Consumer;)V
 HSPLjava/util/ArrayList$ArrayListSpliterator;->getFence()I
-HSPLjava/util/ArrayList$ArrayListSpliterator;->tryAdvance(Ljava/util/function/Consumer;)Z+]Ljava/util/function/Consumer;Ljava/util/stream/ReferencePipeline$2$1;,Ljava/util/stream/MatchOps$1MatchSink;
+HSPLjava/util/ArrayList$ArrayListSpliterator;->tryAdvance(Ljava/util/function/Consumer;)Z
 HSPLjava/util/ArrayList$Itr;-><init>(Ljava/util/ArrayList;)V
-HSPLjava/util/ArrayList$Itr;-><init>(Ljava/util/ArrayList;Ljava/util/ArrayList$Itr-IA;)V
+HSPLjava/util/ArrayList$Itr;->checkForComodification()V
 HSPLjava/util/ArrayList$Itr;->hasNext()Z
-HSPLjava/util/ArrayList$Itr;->next()Ljava/lang/Object;
+HSPLjava/util/ArrayList$Itr;->next()Ljava/lang/Object;+]Ljava/util/ArrayList$Itr;Ljava/util/ArrayList$ListItr;,Ljava/util/ArrayList$Itr;
 HSPLjava/util/ArrayList$Itr;->remove()V
 HSPLjava/util/ArrayList$ListItr;-><init>(Ljava/util/ArrayList;I)V
 HSPLjava/util/ArrayList$ListItr;->hasPrevious()Z
 HSPLjava/util/ArrayList$ListItr;->nextIndex()I
 HSPLjava/util/ArrayList$ListItr;->previous()Ljava/lang/Object;
 HSPLjava/util/ArrayList$ListItr;->set(Ljava/lang/Object;)V
-HSPLjava/util/ArrayList$SubList$1;-><init>(Ljava/util/ArrayList$SubList;II)V
+HSPLjava/util/ArrayList$SubList$1;-><init>(Ljava/util/ArrayList$SubList;I)V
+HSPLjava/util/ArrayList$SubList$1;->checkForComodification()V
 HSPLjava/util/ArrayList$SubList$1;->hasNext()Z
-HSPLjava/util/ArrayList$SubList$1;->next()Ljava/lang/Object;
-HSPLjava/util/ArrayList$SubList;-><init>(Ljava/util/ArrayList;Ljava/util/AbstractList;III)V
+HSPLjava/util/ArrayList$SubList$1;->next()Ljava/lang/Object;+]Ljava/util/ArrayList$SubList$1;Ljava/util/ArrayList$SubList$1;
+HSPLjava/util/ArrayList$SubList;->-$$Nest$fgetoffset(Ljava/util/ArrayList$SubList;)I
+HSPLjava/util/ArrayList$SubList;->-$$Nest$fgetroot(Ljava/util/ArrayList$SubList;)Ljava/util/ArrayList;
+HSPLjava/util/ArrayList$SubList;->-$$Nest$fgetsize(Ljava/util/ArrayList$SubList;)I
+HSPLjava/util/ArrayList$SubList;-><init>(Ljava/util/ArrayList;II)V
+HSPLjava/util/ArrayList$SubList;->checkForComodification()V
 HSPLjava/util/ArrayList$SubList;->get(I)Ljava/lang/Object;
 HSPLjava/util/ArrayList$SubList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/ArrayList$SubList;->listIterator(I)Ljava/util/ListIterator;
+HSPLjava/util/ArrayList$SubList;->rangeCheckForAdd(I)V
 HSPLjava/util/ArrayList$SubList;->removeRange(II)V
 HSPLjava/util/ArrayList$SubList;->size()I
 HSPLjava/util/ArrayList$SubList;->subList(II)Ljava/util/List;
+HSPLjava/util/ArrayList$SubList;->toArray()[Ljava/lang/Object;
+HSPLjava/util/ArrayList$SubList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/ArrayList;->-$$Nest$fgetsize(Ljava/util/ArrayList;)I
 HSPLjava/util/ArrayList;-><init>()V
 HSPLjava/util/ArrayList;-><init>(I)V
-HSPLjava/util/ArrayList;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;missing_types]Ljava/util/Collection;megamorphic_types
+HSPLjava/util/ArrayList;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;Landroid/net/Uri$PathSegments;]Ljava/util/Collection;Ljava/util/Collections$EmptyList;,Landroid/net/Uri$PathSegments;
 HSPLjava/util/ArrayList;->add(ILjava/lang/Object;)V
 HSPLjava/util/ArrayList;->add(Ljava/lang/Object;)Z
+HSPLjava/util/ArrayList;->add(Ljava/lang/Object;[Ljava/lang/Object;I)V
 HSPLjava/util/ArrayList;->addAll(ILjava/util/Collection;)Z
 HSPLjava/util/ArrayList;->addAll(Ljava/util/Collection;)Z+]Ljava/util/Collection;missing_types
-HSPLjava/util/ArrayList;->batchRemove(Ljava/util/Collection;Z)Z
+HSPLjava/util/ArrayList;->batchRemove(Ljava/util/Collection;ZII)Z
+HSPLjava/util/ArrayList;->checkForComodification(I)V
 HSPLjava/util/ArrayList;->clear()V
 HSPLjava/util/ArrayList;->clone()Ljava/lang/Object;
-HSPLjava/util/ArrayList;->contains(Ljava/lang/Object;)Z+]Ljava/util/ArrayList;Ljava/util/ArrayList;
+HSPLjava/util/ArrayList;->contains(Ljava/lang/Object;)Z
+HSPLjava/util/ArrayList;->elementAt([Ljava/lang/Object;I)Ljava/lang/Object;
+HSPLjava/util/ArrayList;->elementData(I)Ljava/lang/Object;
 HSPLjava/util/ArrayList;->ensureCapacity(I)V
-HSPLjava/util/ArrayList;->ensureCapacityInternal(I)V
-HSPLjava/util/ArrayList;->ensureExplicitCapacity(I)V
-HSPLjava/util/ArrayList;->fastRemove(I)V
+HSPLjava/util/ArrayList;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Object;Ljava/util/ArrayList;
+HSPLjava/util/ArrayList;->equalsArrayList(Ljava/util/ArrayList;)Z
+HSPLjava/util/ArrayList;->equalsRange(Ljava/util/List;II)Z
+HSPLjava/util/ArrayList;->fastRemove([Ljava/lang/Object;I)V
 HSPLjava/util/ArrayList;->forEach(Ljava/util/function/Consumer;)V
-HSPLjava/util/ArrayList;->get(I)Ljava/lang/Object;
-HSPLjava/util/ArrayList;->grow(I)V
-HSPLjava/util/ArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types
+HSPLjava/util/ArrayList;->get(I)Ljava/lang/Object;+]Ljava/util/ArrayList;missing_types
+HSPLjava/util/ArrayList;->grow()[Ljava/lang/Object;
+HSPLjava/util/ArrayList;->grow(I)[Ljava/lang/Object;
+HSPLjava/util/ArrayList;->hashCode()I
+HSPLjava/util/ArrayList;->hashCodeRange(II)I
+HSPLjava/util/ArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/util/ArrayList;Ljava/util/ArrayList;
+HSPLjava/util/ArrayList;->indexOfRange(Ljava/lang/Object;II)I+]Ljava/lang/Object;missing_types
 HSPLjava/util/ArrayList;->isEmpty()Z
 HSPLjava/util/ArrayList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/ArrayList;->lastIndexOf(Ljava/lang/Object;)I
 HSPLjava/util/ArrayList;->listIterator()Ljava/util/ListIterator;
 HSPLjava/util/ArrayList;->listIterator(I)Ljava/util/ListIterator;
+HSPLjava/util/ArrayList;->rangeCheckForAdd(I)V
 HSPLjava/util/ArrayList;->readObject(Ljava/io/ObjectInputStream;)V
 HSPLjava/util/ArrayList;->remove(I)Ljava/lang/Object;
 HSPLjava/util/ArrayList;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/ArrayList;->removeAll(Ljava/util/Collection;)Z
 HSPLjava/util/ArrayList;->removeIf(Ljava/util/function/Predicate;)Z
+HSPLjava/util/ArrayList;->removeIf(Ljava/util/function/Predicate;II)Z
 HSPLjava/util/ArrayList;->removeRange(II)V
 HSPLjava/util/ArrayList;->retainAll(Ljava/util/Collection;)Z
-HSPLjava/util/ArrayList;->set(ILjava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/ArrayList;->set(ILjava/lang/Object;)Ljava/lang/Object;+]Ljava/util/ArrayList;Ljava/util/ArrayList;
+HSPLjava/util/ArrayList;->shiftTailOverGap([Ljava/lang/Object;II)V
 HSPLjava/util/ArrayList;->size()I
 HSPLjava/util/ArrayList;->sort(Ljava/util/Comparator;)V
 HSPLjava/util/ArrayList;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/ArrayList;->subList(II)Ljava/util/List;
-HSPLjava/util/ArrayList;->subListRangeCheck(III)V
 HSPLjava/util/ArrayList;->toArray()[Ljava/lang/Object;
 HSPLjava/util/ArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/lang/Object;missing_types
 HSPLjava/util/ArrayList;->trimToSize()V
@@ -4973,7 +5052,7 @@
 HSPLjava/util/Arrays$ArrayList;->size()I
 HSPLjava/util/Arrays$ArrayList;->sort(Ljava/util/Comparator;)V
 HSPLjava/util/Arrays$ArrayList;->spliterator()Ljava/util/Spliterator;
-HSPLjava/util/Arrays$ArrayList;->toArray()[Ljava/lang/Object;
+HSPLjava/util/Arrays$ArrayList;->toArray()[Ljava/lang/Object;+][Ljava/lang/Object;missing_types
 HSPLjava/util/Arrays$ArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/Arrays;->asList([Ljava/lang/Object;)Ljava/util/List;
 HSPLjava/util/Arrays;->binarySearch([CC)I
@@ -5031,7 +5110,7 @@
 HSPLjava/util/Arrays;->hashCode([F)I
 HSPLjava/util/Arrays;->hashCode([I)I
 HSPLjava/util/Arrays;->hashCode([J)I
-HSPLjava/util/Arrays;->hashCode([Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types
+HSPLjava/util/Arrays;->hashCode([Ljava/lang/Object;)I
 HSPLjava/util/Arrays;->rangeCheck(III)V
 HSPLjava/util/Arrays;->sort([C)V
 HSPLjava/util/Arrays;->sort([F)V
@@ -5048,7 +5127,7 @@
 HSPLjava/util/Arrays;->stream([III)Ljava/util/stream/IntStream;
 HSPLjava/util/Arrays;->stream([Ljava/lang/Object;)Ljava/util/stream/Stream;
 HSPLjava/util/Arrays;->stream([Ljava/lang/Object;II)Ljava/util/stream/Stream;
-HSPLjava/util/Arrays;->toString([B)Ljava/lang/String;
+HSPLjava/util/Arrays;->toString([B)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/util/Arrays;->toString([F)Ljava/lang/String;
 HSPLjava/util/Arrays;->toString([I)Ljava/lang/String;
 HSPLjava/util/Arrays;->toString([J)Ljava/lang/String;
@@ -5056,7 +5135,6 @@
 HSPLjava/util/Base64$Decoder;->decode(Ljava/lang/String;)[B
 HSPLjava/util/Base64$Decoder;->decode([B)[B
 HSPLjava/util/Base64$Decoder;->decode0([BII[B)I
-HSPLjava/util/Base64$Decoder;->outLength([BII)I
 HSPLjava/util/Base64;->getDecoder()Ljava/util/Base64$Decoder;
 HSPLjava/util/Base64;->getEncoder()Ljava/util/Base64$Encoder;
 HSPLjava/util/Base64;->getMimeDecoder()Ljava/util/Base64$Decoder;
@@ -5069,7 +5147,7 @@
 HSPLjava/util/BitSet;->checkRange(II)V
 HSPLjava/util/BitSet;->clear()V
 HSPLjava/util/BitSet;->clear(I)V
-HSPLjava/util/BitSet;->clone()Ljava/lang/Object;
+HSPLjava/util/BitSet;->clone()Ljava/lang/Object;+][J[J
 HSPLjava/util/BitSet;->ensureCapacity(I)V
 HSPLjava/util/BitSet;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/BitSet;->expandTo(I)V
@@ -5089,6 +5167,7 @@
 HSPLjava/util/BitSet;->size()I
 HSPLjava/util/BitSet;->toString()Ljava/lang/String;
 HSPLjava/util/BitSet;->trimToSize()V
+HSPLjava/util/BitSet;->valueOf(Ljava/nio/ByteBuffer;)Ljava/util/BitSet;+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
 HSPLjava/util/BitSet;->valueOf([J)Ljava/util/BitSet;
 HSPLjava/util/BitSet;->wordIndex(I)I
 HSPLjava/util/Calendar;-><init>()V
@@ -5101,7 +5180,8 @@
 HSPLjava/util/Calendar;->compareTo(Ljava/util/Calendar;)I
 HSPLjava/util/Calendar;->complete()V
 HSPLjava/util/Calendar;->createCalendar(Ljava/util/TimeZone;Ljava/util/Locale;)Ljava/util/Calendar;
-HSPLjava/util/Calendar;->get(I)I
+HSPLjava/util/Calendar;->defaultTimeZone(Ljava/util/Locale;)Ljava/util/TimeZone;
+HSPLjava/util/Calendar;->get(I)I+]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/Calendar;->getFirstDayOfWeek()I
 HSPLjava/util/Calendar;->getInstance()Ljava/util/Calendar;
 HSPLjava/util/Calendar;->getInstance(Ljava/util/Locale;)Ljava/util/Calendar;
@@ -5123,19 +5203,19 @@
 HSPLjava/util/Calendar;->isPartiallyNormalized()Z
 HSPLjava/util/Calendar;->isSet(I)Z
 HSPLjava/util/Calendar;->selectFields()I
-HSPLjava/util/Calendar;->set(II)V+]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
+HSPLjava/util/Calendar;->set(II)V
 HSPLjava/util/Calendar;->set(III)V
 HSPLjava/util/Calendar;->set(IIIIII)V
 HSPLjava/util/Calendar;->setFieldsComputed(I)V
 HSPLjava/util/Calendar;->setFieldsNormalized(I)V
 HSPLjava/util/Calendar;->setLenient(Z)V
-HSPLjava/util/Calendar;->setTime(Ljava/util/Date;)V
+HSPLjava/util/Calendar;->setTime(Ljava/util/Date;)V+]Ljava/util/Date;Ljava/util/Date;]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/Calendar;->setTimeInMillis(J)V
 HSPLjava/util/Calendar;->setTimeZone(Ljava/util/TimeZone;)V
-HSPLjava/util/Calendar;->setWeekCountData(Ljava/util/Locale;)V
+HSPLjava/util/Calendar;->setWeekCountData(Ljava/util/Locale;)V+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/lang/Integer;Ljava/lang/Integer;
 HSPLjava/util/Calendar;->setZoneShared(Z)V
 HSPLjava/util/Calendar;->updateTime()V
-HSPLjava/util/Collection;->removeIf(Ljava/util/function/Predicate;)Z+]Ljava/util/Collection;Landroid/util/MapCollections$ValuesCollection;,Ljava/util/HashMap$EntrySet;,Landroid/util/MapCollections$KeySet;,Ljava/util/LinkedList;,Lcom/android/internal/telephony/data/DataNetworkController$NetworkRequestList;,Landroid/util/MapCollections$EntrySet;,Ljava/util/HashSet;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;,Ljava/util/HashMap$EntryIterator;,Ljava/util/LinkedList$ListItr;,Landroid/util/MapCollections$MapIterator;,Ljava/util/HashMap$KeyIterator;]Ljava/util/function/Predicate;Lcom/android/internal/telephony/data/DataNetworkController$$ExternalSyntheticLambda24;,Lcom/android/internal/telephony/data/DataNetworkController$$ExternalSyntheticLambda15;
+HSPLjava/util/Collection;->removeIf(Ljava/util/function/Predicate;)Z+]Ljava/util/Collection;Landroid/util/MapCollections$ValuesCollection;,Ljava/util/HashMap$EntrySet;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;,Ljava/util/HashMap$EntryIterator;
 HSPLjava/util/Collection;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/Collection;->stream()Ljava/util/stream/Stream;+]Ljava/util/Collection;megamorphic_types
 HSPLjava/util/Collections$1;-><init>(Ljava/lang/Object;)V
@@ -5152,7 +5232,7 @@
 HSPLjava/util/Collections$EmptyIterator;->hasNext()Z
 HSPLjava/util/Collections$EmptyList;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$EmptyList;->containsAll(Ljava/util/Collection;)Z
-HSPLjava/util/Collections$EmptyList;->equals(Ljava/lang/Object;)Z+]Ljava/util/List;Ljava/util/Collections$EmptyList;,Ljava/util/ArrayList;
+HSPLjava/util/Collections$EmptyList;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$EmptyList;->isEmpty()Z
 HSPLjava/util/Collections$EmptyList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/Collections$EmptyList;->listIterator()Ljava/util/ListIterator;
@@ -5182,7 +5262,7 @@
 HSPLjava/util/Collections$ReverseComparator;->compare(Ljava/lang/Comparable;Ljava/lang/Comparable;)I
 HSPLjava/util/Collections$ReverseComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
 HSPLjava/util/Collections$SetFromMap;-><init>(Ljava/util/Map;)V
-HSPLjava/util/Collections$SetFromMap;->add(Ljava/lang/Object;)Z+]Ljava/util/Map;Ljava/util/WeakHashMap;,Ljava/util/concurrent/ConcurrentHashMap;,Ljava/util/IdentityHashMap;
+HSPLjava/util/Collections$SetFromMap;->add(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$SetFromMap;->clear()V
 HSPLjava/util/Collections$SetFromMap;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$SetFromMap;->forEach(Ljava/util/function/Consumer;)V
@@ -5195,6 +5275,7 @@
 HSPLjava/util/Collections$SingletonList;-><init>(Ljava/lang/Object;)V
 HSPLjava/util/Collections$SingletonList;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$SingletonList;->get(I)Ljava/lang/Object;
+HSPLjava/util/Collections$SingletonList;->hashCode()I
 HSPLjava/util/Collections$SingletonList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/Collections$SingletonList;->size()I
 HSPLjava/util/Collections$SingletonMap;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
@@ -5219,11 +5300,12 @@
 HSPLjava/util/Collections$SynchronizedCollection;->isEmpty()Z
 HSPLjava/util/Collections$SynchronizedCollection;->iterator()Ljava/util/Iterator;
 HSPLjava/util/Collections$SynchronizedCollection;->remove(Ljava/lang/Object;)Z
-HSPLjava/util/Collections$SynchronizedCollection;->size()I
+HSPLjava/util/Collections$SynchronizedCollection;->size()I+]Ljava/util/Collection;Ljava/util/ArrayList;
 HSPLjava/util/Collections$SynchronizedCollection;->toArray()[Ljava/lang/Object;
-HSPLjava/util/Collections$SynchronizedCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLjava/util/Collections$SynchronizedCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/util/Collection;Ljava/util/ArrayList;
+HSPLjava/util/Collections$SynchronizedCollection;->toString()Ljava/lang/String;
 HSPLjava/util/Collections$SynchronizedList;-><init>(Ljava/util/List;)V
-HSPLjava/util/Collections$SynchronizedList;->get(I)Ljava/lang/Object;
+HSPLjava/util/Collections$SynchronizedList;->get(I)Ljava/lang/Object;+]Ljava/util/List;Ljava/util/ArrayList;
 HSPLjava/util/Collections$SynchronizedMap;-><init>(Ljava/util/Map;)V
 HSPLjava/util/Collections$SynchronizedMap;->clear()V
 HSPLjava/util/Collections$SynchronizedMap;->containsKey(Ljava/lang/Object;)Z
@@ -5233,7 +5315,7 @@
 HSPLjava/util/Collections$SynchronizedMap;->isEmpty()Z
 HSPLjava/util/Collections$SynchronizedMap;->keySet()Ljava/util/Set;
 HSPLjava/util/Collections$SynchronizedMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/Collections$SynchronizedMap;->putAll(Ljava/util/Map;)V+]Ljava/util/Map;Ljava/util/HashMap;
+HSPLjava/util/Collections$SynchronizedMap;->putAll(Ljava/util/Map;)V
 HSPLjava/util/Collections$SynchronizedMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/Collections$SynchronizedMap;->size()I
 HSPLjava/util/Collections$SynchronizedMap;->values()Ljava/util/Collection;
@@ -5241,16 +5323,16 @@
 HSPLjava/util/Collections$SynchronizedSet;-><init>(Ljava/util/Set;)V
 HSPLjava/util/Collections$SynchronizedSet;-><init>(Ljava/util/Set;Ljava/lang/Object;)V
 HSPLjava/util/Collections$SynchronizedSet;->equals(Ljava/lang/Object;)Z
-HSPLjava/util/Collections$UnmodifiableCollection$1;-><init>(Ljava/util/Collections$UnmodifiableCollection;)V
-HSPLjava/util/Collections$UnmodifiableCollection$1;->hasNext()Z+]Ljava/util/Iterator;missing_types
-HSPLjava/util/Collections$UnmodifiableCollection$1;->next()Ljava/lang/Object;+]Ljava/util/Iterator;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection$1;-><init>(Ljava/util/Collections$UnmodifiableCollection;)V+]Ljava/util/Collection;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection$1;->hasNext()Z+]Ljava/util/Iterator;megamorphic_types
+HSPLjava/util/Collections$UnmodifiableCollection$1;->next()Ljava/lang/Object;+]Ljava/util/Iterator;megamorphic_types
 HSPLjava/util/Collections$UnmodifiableCollection;-><init>(Ljava/util/Collection;)V
-HSPLjava/util/Collections$UnmodifiableCollection;->contains(Ljava/lang/Object;)Z+]Ljava/util/Collection;Ljava/util/HashSet;,Ljava/util/RegularEnumSet;,Ljava/util/ArrayList;,Ljava/util/LinkedHashSet;
+HSPLjava/util/Collections$UnmodifiableCollection;->contains(Ljava/lang/Object;)Z+]Ljava/util/Collection;megamorphic_types
 HSPLjava/util/Collections$UnmodifiableCollection;->containsAll(Ljava/util/Collection;)Z
 HSPLjava/util/Collections$UnmodifiableCollection;->forEach(Ljava/util/function/Consumer;)V
-HSPLjava/util/Collections$UnmodifiableCollection;->isEmpty()Z
+HSPLjava/util/Collections$UnmodifiableCollection;->isEmpty()Z+]Ljava/util/Collection;Ljava/util/ArrayList;,Ljava/util/TreeMap$KeySet;
 HSPLjava/util/Collections$UnmodifiableCollection;->iterator()Ljava/util/Iterator;
-HSPLjava/util/Collections$UnmodifiableCollection;->size()I
+HSPLjava/util/Collections$UnmodifiableCollection;->size()I+]Ljava/util/Collection;missing_types
 HSPLjava/util/Collections$UnmodifiableCollection;->stream()Ljava/util/stream/Stream;
 HSPLjava/util/Collections$UnmodifiableCollection;->toArray()[Ljava/lang/Object;
 HSPLjava/util/Collections$UnmodifiableCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
@@ -5261,7 +5343,7 @@
 HSPLjava/util/Collections$UnmodifiableList$1;->nextIndex()I
 HSPLjava/util/Collections$UnmodifiableList;-><init>(Ljava/util/List;)V
 HSPLjava/util/Collections$UnmodifiableList;->equals(Ljava/lang/Object;)Z
-HSPLjava/util/Collections$UnmodifiableList;->get(I)Ljava/lang/Object;
+HSPLjava/util/Collections$UnmodifiableList;->get(I)Ljava/lang/Object;+]Ljava/util/List;missing_types
 HSPLjava/util/Collections$UnmodifiableList;->hashCode()I
 HSPLjava/util/Collections$UnmodifiableList;->indexOf(Ljava/lang/Object;)I
 HSPLjava/util/Collections$UnmodifiableList;->listIterator()Ljava/util/ListIterator;
@@ -5280,7 +5362,7 @@
 HSPLjava/util/Collections$UnmodifiableMap;->entrySet()Ljava/util/Set;
 HSPLjava/util/Collections$UnmodifiableMap;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$UnmodifiableMap;->forEach(Ljava/util/function/BiConsumer;)V
-HSPLjava/util/Collections$UnmodifiableMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/Collections$UnmodifiableMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;missing_types
 HSPLjava/util/Collections$UnmodifiableMap;->hashCode()I
 HSPLjava/util/Collections$UnmodifiableMap;->isEmpty()Z
 HSPLjava/util/Collections$UnmodifiableMap;->keySet()Ljava/util/Set;
@@ -5293,10 +5375,10 @@
 HSPLjava/util/Collections$UnmodifiableSet;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$UnmodifiableSortedMap;-><init>(Ljava/util/SortedMap;)V
 HSPLjava/util/Collections$UnmodifiableSortedSet;-><init>(Ljava/util/SortedSet;)V
-HSPLjava/util/Collections;->addAll(Ljava/util/Collection;[Ljava/lang/Object;)Z
+HSPLjava/util/Collections;->addAll(Ljava/util/Collection;[Ljava/lang/Object;)Z+]Ljava/util/Collection;Ljava/util/ArrayList;
 HSPLjava/util/Collections;->binarySearch(Ljava/util/List;Ljava/lang/Object;)I
 HSPLjava/util/Collections;->binarySearch(Ljava/util/List;Ljava/lang/Object;Ljava/util/Comparator;)I
-HSPLjava/util/Collections;->disjoint(Ljava/util/Collection;Ljava/util/Collection;)Z+]Ljava/util/Iterator;Ljava/util/AbstractList$Itr;
+HSPLjava/util/Collections;->disjoint(Ljava/util/Collection;Ljava/util/Collection;)Z
 HSPLjava/util/Collections;->emptyEnumeration()Ljava/util/Enumeration;
 HSPLjava/util/Collections;->emptyIterator()Ljava/util/Iterator;
 HSPLjava/util/Collections;->emptyList()Ljava/util/List;
@@ -5305,7 +5387,7 @@
 HSPLjava/util/Collections;->emptySet()Ljava/util/Set;
 HSPLjava/util/Collections;->enumeration(Ljava/util/Collection;)Ljava/util/Enumeration;
 HSPLjava/util/Collections;->eq(Ljava/lang/Object;Ljava/lang/Object;)Z
-HSPLjava/util/Collections;->indexedBinarySearch(Ljava/util/List;Ljava/lang/Object;)I
+HSPLjava/util/Collections;->indexedBinarySearch(Ljava/util/List;Ljava/lang/Object;)I+]Ljava/util/List;Ljava/util/ArrayList;]Ljava/lang/Comparable;Ljava/lang/Integer;
 HSPLjava/util/Collections;->indexedBinarySearch(Ljava/util/List;Ljava/lang/Object;Ljava/util/Comparator;)I
 HSPLjava/util/Collections;->list(Ljava/util/Enumeration;)Ljava/util/ArrayList;
 HSPLjava/util/Collections;->max(Ljava/util/Collection;)Ljava/lang/Object;
@@ -5325,7 +5407,7 @@
 HSPLjava/util/Collections;->singletonList(Ljava/lang/Object;)Ljava/util/List;
 HSPLjava/util/Collections;->singletonMap(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;
 HSPLjava/util/Collections;->sort(Ljava/util/List;)V
-HSPLjava/util/Collections;->sort(Ljava/util/List;Ljava/util/Comparator;)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;]Ljava/util/List;Ljava/util/ArrayList;
+HSPLjava/util/Collections;->sort(Ljava/util/List;Ljava/util/Comparator;)V
 HSPLjava/util/Collections;->swap(Ljava/util/List;II)V
 HSPLjava/util/Collections;->synchronizedCollection(Ljava/util/Collection;)Ljava/util/Collection;
 HSPLjava/util/Collections;->synchronizedCollection(Ljava/util/Collection;Ljava/lang/Object;)Ljava/util/Collection;
@@ -5334,8 +5416,8 @@
 HSPLjava/util/Collections;->synchronizedSet(Ljava/util/Set;)Ljava/util/Set;
 HSPLjava/util/Collections;->synchronizedSet(Ljava/util/Set;Ljava/lang/Object;)Ljava/util/Set;
 HSPLjava/util/Collections;->unmodifiableCollection(Ljava/util/Collection;)Ljava/util/Collection;
-HSPLjava/util/Collections;->unmodifiableList(Ljava/util/List;)Ljava/util/List;
-HSPLjava/util/Collections;->unmodifiableMap(Ljava/util/Map;)Ljava/util/Map;
+HSPLjava/util/Collections;->unmodifiableList(Ljava/util/List;)Ljava/util/List;+]Ljava/lang/Object;missing_types
+HSPLjava/util/Collections;->unmodifiableMap(Ljava/util/Map;)Ljava/util/Map;+]Ljava/lang/Object;missing_types
 HSPLjava/util/Collections;->unmodifiableSet(Ljava/util/Set;)Ljava/util/Set;
 HSPLjava/util/Collections;->unmodifiableSortedMap(Ljava/util/SortedMap;)Ljava/util/SortedMap;
 HSPLjava/util/Collections;->unmodifiableSortedSet(Ljava/util/SortedSet;)Ljava/util/SortedSet;
@@ -5366,17 +5448,19 @@
 HSPLjava/util/Comparator;->comparingLong(Ljava/util/function/ToLongFunction;)Ljava/util/Comparator;
 HSPLjava/util/Comparator;->lambda$comparing$77a9974f$1(Ljava/util/function/Function;Ljava/lang/Object;Ljava/lang/Object;)I
 HSPLjava/util/Comparator;->lambda$comparingInt$7b0bb60$1(Ljava/util/function/ToIntFunction;Ljava/lang/Object;Ljava/lang/Object;)I
-HSPLjava/util/Comparator;->lambda$thenComparing$36697e65$1(Ljava/util/Comparator;Ljava/util/Comparator;Ljava/lang/Object;Ljava/lang/Object;)I
 HSPLjava/util/Comparator;->naturalOrder()Ljava/util/Comparator;
+HSPLjava/util/Comparator;->nullsFirst(Ljava/util/Comparator;)Ljava/util/Comparator;
 HSPLjava/util/Comparator;->reversed()Ljava/util/Comparator;
 HSPLjava/util/Comparator;->thenComparing(Ljava/util/Comparator;)Ljava/util/Comparator;
-HSPLjava/util/Comparator;->thenComparing(Ljava/util/function/Function;)Ljava/util/Comparator;
+HSPLjava/util/Comparator;->thenComparing(Ljava/util/function/Function;)Ljava/util/Comparator;+]Ljava/util/Comparator;Ljava/util/Comparator$$ExternalSyntheticLambda5;
 HSPLjava/util/Comparators$NaturalOrderComparator;->compare(Ljava/lang/Comparable;Ljava/lang/Comparable;)I
 HSPLjava/util/Comparators$NaturalOrderComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLjava/util/Comparators$NullComparator;-><init>(ZLjava/util/Comparator;)V
+HSPLjava/util/Comparators$NullComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
 HSPLjava/util/Currency;-><init>(Landroid/icu/util/Currency;)V
 HSPLjava/util/Currency;->getCurrencyCode()Ljava/lang/String;
 HSPLjava/util/Currency;->getInstance(Ljava/lang/String;)Ljava/util/Currency;
-HSPLjava/util/Currency;->getInstance(Ljava/util/Locale;)Ljava/util/Currency;
+HSPLjava/util/Currency;->getInstance(Ljava/util/Locale;)Ljava/util/Currency;+]Ljava/util/Locale;Ljava/util/Locale;]Landroid/icu/util/Currency;Landroid/icu/util/Currency;
 HSPLjava/util/Currency;->getSymbol(Ljava/util/Locale;)Ljava/lang/String;
 HSPLjava/util/Date;-><init>()V
 HSPLjava/util/Date;-><init>(J)V
@@ -5401,16 +5485,19 @@
 HSPLjava/util/Date;->toInstant()Ljava/time/Instant;
 HSPLjava/util/Date;->toString()Ljava/lang/String;
 HSPLjava/util/Dictionary;-><init>()V
-HSPLjava/util/DualPivotQuicksort;->doSort([CII[CII)V
-HSPLjava/util/DualPivotQuicksort;->doSort([FII[FII)V
-HSPLjava/util/DualPivotQuicksort;->sort([CIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([CII[CII)V
-HSPLjava/util/DualPivotQuicksort;->sort([FIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([FII[FII)V
-HSPLjava/util/DualPivotQuicksort;->sort([IIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([III[III)V
-HSPLjava/util/DualPivotQuicksort;->sort([JIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([JII[JII)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([CII)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([FII)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([III)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([JII)V
+HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[FIII)V
+HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[IIII)V
+HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[JIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([CIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([FIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([IIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([JIII)V
+HSPLjava/util/DualPivotQuicksort;->tryMergeRuns(Ljava/util/DualPivotQuicksort$Sorter;[III)Z
+HSPLjava/util/DualPivotQuicksort;->tryMergeRuns(Ljava/util/DualPivotQuicksort$Sorter;[JII)Z
 HSPLjava/util/EnumMap$EntryIterator$Entry;-><init>(Ljava/util/EnumMap$EntryIterator;I)V
 HSPLjava/util/EnumMap$EntryIterator$Entry;->checkIndexForEntryUse()V
 HSPLjava/util/EnumMap$EntryIterator$Entry;->getKey()Ljava/lang/Enum;
@@ -5441,9 +5528,9 @@
 HSPLjava/util/EnumMap;->clear()V
 HSPLjava/util/EnumMap;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/EnumMap;->entrySet()Ljava/util/Set;
-HSPLjava/util/EnumMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/EnumMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Enum;missing_types
 HSPLjava/util/EnumMap;->getKeyUniverse(Ljava/lang/Class;)[Ljava/lang/Enum;
-HSPLjava/util/EnumMap;->isValidKey(Ljava/lang/Object;)Z
+HSPLjava/util/EnumMap;->isValidKey(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types
 HSPLjava/util/EnumMap;->keySet()Ljava/util/Set;
 HSPLjava/util/EnumMap;->maskNull(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/EnumMap;->put(Ljava/lang/Enum;Ljava/lang/Object;)Ljava/lang/Object;
@@ -5475,35 +5562,36 @@
 HSPLjava/util/Formatter$Conversion;->isText(C)Z
 HSPLjava/util/Formatter$Conversion;->isValid(C)Z
 HSPLjava/util/Formatter$DateTime;->isValid(C)Z
-HSPLjava/util/Formatter$FixedString;-><init>(Ljava/util/Formatter;Ljava/lang/String;)V
+HSPLjava/util/Formatter$FixedString;-><init>(Ljava/util/Formatter;Ljava/lang/String;II)V
 HSPLjava/util/Formatter$FixedString;->index()I
-HSPLjava/util/Formatter$FixedString;->print(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Appendable;Ljava/lang/StringBuilder;
+HSPLjava/util/Formatter$FixedString;->print(Ljava/lang/Object;Ljava/util/Locale;)V
+HSPLjava/util/Formatter$Flags;->-$$Nest$madd(Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;)Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$Flags;-><init>(I)V
-HSPLjava/util/Formatter$Flags;->add(Ljava/util/Formatter$Flags;)Ljava/util/Formatter$Flags;
+HSPLjava/util/Formatter$Flags;->add(Ljava/util/Formatter$Flags;)Ljava/util/Formatter$Flags;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$Flags;->contains(Ljava/util/Formatter$Flags;)Z+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$Flags;->parse(C)Ljava/util/Formatter$Flags;
-HSPLjava/util/Formatter$Flags;->parse(Ljava/lang/String;)Ljava/util/Formatter$Flags;+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/util/Formatter$Flags;->parse(Ljava/lang/String;II)Ljava/util/Formatter$Flags;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$Flags;->valueOf()I
 HSPLjava/util/Formatter$FormatSpecifier;-><init>(Ljava/util/Formatter;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLjava/util/Formatter$FormatSpecifier;->addZeros([CI)[C
+HSPLjava/util/Formatter$FormatSpecifier;->addZeros(Ljava/lang/StringBuilder;I)V
 HSPLjava/util/Formatter$FormatSpecifier;->adjustWidth(ILjava/util/Formatter$Flags;Z)I
+HSPLjava/util/Formatter$FormatSpecifier;->appendJustified(Ljava/lang/Appendable;Ljava/lang/CharSequence;)Ljava/lang/Appendable;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/lang/StringBuilder;]Ljava/lang/Appendable;Ljava/lang/StringBuilder;
 HSPLjava/util/Formatter$FormatSpecifier;->checkBadFlags([Ljava/util/Formatter$Flags;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$FormatSpecifier;->checkCharacter()V
 HSPLjava/util/Formatter$FormatSpecifier;->checkDateTime()V
 HSPLjava/util/Formatter$FormatSpecifier;->checkFloat()V
-HSPLjava/util/Formatter$FormatSpecifier;->checkGeneral()V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
+HSPLjava/util/Formatter$FormatSpecifier;->checkGeneral()V
 HSPLjava/util/Formatter$FormatSpecifier;->checkInteger()V
 HSPLjava/util/Formatter$FormatSpecifier;->checkNumeric()V
 HSPLjava/util/Formatter$FormatSpecifier;->checkText()V
-HSPLjava/util/Formatter$FormatSpecifier;->conversion(Ljava/lang/String;)C
+HSPLjava/util/Formatter$FormatSpecifier;->conversion(C)C
 HSPLjava/util/Formatter$FormatSpecifier;->flags(Ljava/lang/String;)Ljava/util/Formatter$Flags;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$FormatSpecifier;->getZero(Ljava/util/Locale;)C
 HSPLjava/util/Formatter$FormatSpecifier;->index()I
 HSPLjava/util/Formatter$FormatSpecifier;->index(Ljava/lang/String;)I
-HSPLjava/util/Formatter$FormatSpecifier;->justify(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/Formatter$FormatSpecifier;->leadingSign(Ljava/lang/StringBuilder;Z)Ljava/lang/StringBuilder;
 HSPLjava/util/Formatter$FormatSpecifier;->localizedMagnitude(Ljava/lang/StringBuilder;JLjava/util/Formatter$Flags;ILjava/util/Locale;)Ljava/lang/StringBuilder;
-HSPLjava/util/Formatter$FormatSpecifier;->localizedMagnitude(Ljava/lang/StringBuilder;[CLjava/util/Formatter$Flags;ILjava/util/Locale;)Ljava/lang/StringBuilder;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/util/Locale;Ljava/util/Locale;
+HSPLjava/util/Formatter$FormatSpecifier;->localizedMagnitude(Ljava/lang/StringBuilder;Ljava/lang/CharSequence;ILjava/util/Formatter$Flags;ILjava/util/Locale;)Ljava/lang/StringBuilder;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/util/Locale;Ljava/util/Locale;]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/lang/StringBuilder;
 HSPLjava/util/Formatter$FormatSpecifier;->precision(Ljava/lang/String;)I
 HSPLjava/util/Formatter$FormatSpecifier;->print(BLjava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->print(DLjava/util/Locale;)V
@@ -5511,26 +5599,27 @@
 HSPLjava/util/Formatter$FormatSpecifier;->print(ILjava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->print(JLjava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/Object;Ljava/util/Locale;)V
-HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/String;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/lang/Appendable;Ljava/lang/StringBuilder;
+HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/String;Ljava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/StringBuilder;DLjava/util/Locale;Ljava/util/Formatter$Flags;CIZ)V
 HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/StringBuilder;Ljava/util/Calendar;CLjava/util/Locale;)Ljava/lang/Appendable;
 HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/math/BigInteger;Ljava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/util/Calendar;CLjava/util/Locale;)V
-HSPLjava/util/Formatter$FormatSpecifier;->printBoolean(Ljava/lang/Object;)V
-HSPLjava/util/Formatter$FormatSpecifier;->printCharacter(Ljava/lang/Object;)V
+HSPLjava/util/Formatter$FormatSpecifier;->printBoolean(Ljava/lang/Object;Ljava/util/Locale;)V
+HSPLjava/util/Formatter$FormatSpecifier;->printCharacter(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Character;Ljava/lang/Character;
 HSPLjava/util/Formatter$FormatSpecifier;->printDateTime(Ljava/lang/Object;Ljava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->printFloat(Ljava/lang/Object;Ljava/util/Locale;)V
-HSPLjava/util/Formatter$FormatSpecifier;->printInteger(Ljava/lang/Object;Ljava/util/Locale;)V
-HSPLjava/util/Formatter$FormatSpecifier;->printString(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/lang/Object;missing_types
+HSPLjava/util/Formatter$FormatSpecifier;->printInteger(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Integer;Ljava/lang/Integer;]Ljava/lang/Long;Ljava/lang/Long;]Ljava/lang/Byte;Ljava/lang/Byte;
+HSPLjava/util/Formatter$FormatSpecifier;->printString(Ljava/lang/Object;Ljava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->trailingSign(Ljava/lang/StringBuilder;Z)Ljava/lang/StringBuilder;
+HSPLjava/util/Formatter$FormatSpecifier;->trailingZeros(Ljava/lang/StringBuilder;I)V
 HSPLjava/util/Formatter$FormatSpecifier;->width(Ljava/lang/String;)I
-HSPLjava/util/Formatter$FormatSpecifierParser;-><init>(Ljava/util/Formatter;Ljava/lang/String;I)V
+HSPLjava/util/Formatter$FormatSpecifierParser;-><init>(Ljava/util/Formatter;Ljava/lang/String;I)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/util/Formatter$FormatSpecifierParser;->advance()C
 HSPLjava/util/Formatter$FormatSpecifierParser;->back(I)V
 HSPLjava/util/Formatter$FormatSpecifierParser;->getEndIdx()I
 HSPLjava/util/Formatter$FormatSpecifierParser;->getFormatSpecifier()Ljava/util/Formatter$FormatSpecifier;
 HSPLjava/util/Formatter$FormatSpecifierParser;->isEnd()Z
-HSPLjava/util/Formatter$FormatSpecifierParser;->nextInt()Ljava/lang/String;
+HSPLjava/util/Formatter$FormatSpecifierParser;->nextInt()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/util/Formatter$FormatSpecifierParser;->nextIsInt()Z
 HSPLjava/util/Formatter$FormatSpecifierParser;->peek()C
 HSPLjava/util/Formatter;->-$$Nest$fgeta(Ljava/util/Formatter;)Ljava/lang/Appendable;
@@ -5542,30 +5631,30 @@
 HSPLjava/util/Formatter;-><init>(Ljava/util/Locale;Ljava/lang/Appendable;)V
 HSPLjava/util/Formatter;->close()V
 HSPLjava/util/Formatter;->ensureOpen()V
-HSPLjava/util/Formatter;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;+]Ljava/util/Formatter;Ljava/util/Formatter;
-HSPLjava/util/Formatter;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;+]Ljava/util/Formatter$FormatString;Ljava/util/Formatter$FixedString;,Ljava/util/Formatter$FormatSpecifier;
-HSPLjava/util/Formatter;->getZero(Ljava/util/Locale;)C+]Ljava/util/Locale;Ljava/util/Locale;
+HSPLjava/util/Formatter;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;
+HSPLjava/util/Formatter;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;+]Ljava/util/List;Ljava/util/ArrayList;]Ljava/util/Formatter$FormatString;Ljava/util/Formatter$FixedString;,Ljava/util/Formatter$FormatSpecifier;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr;
+HSPLjava/util/Formatter;->getZero(Ljava/util/Locale;)C+]Ljava/util/Locale;Ljava/util/Locale;]Llibcore/icu/DecimalFormatData;Llibcore/icu/DecimalFormatData;
 HSPLjava/util/Formatter;->locale()Ljava/util/Locale;
 HSPLjava/util/Formatter;->nonNullAppendable(Ljava/lang/Appendable;)Ljava/lang/Appendable;
 HSPLjava/util/Formatter;->out()Ljava/lang/Appendable;
-HSPLjava/util/Formatter;->parse(Ljava/lang/String;)[Ljava/util/Formatter$FormatString;+]Ljava/lang/String;Ljava/lang/String;]Ljava/util/Formatter$FormatSpecifierParser;Ljava/util/Formatter$FormatSpecifierParser;]Ljava/util/ArrayList;Ljava/util/ArrayList;
-HSPLjava/util/Formatter;->toString()Ljava/lang/String;+]Ljava/lang/Object;Ljava/lang/StringBuilder;
-HSPLjava/util/GregorianCalendar;-><init>()V
+HSPLjava/util/Formatter;->parse(Ljava/lang/String;)Ljava/util/List;+]Ljava/util/Formatter$FormatSpecifierParser;Ljava/util/Formatter$FormatSpecifierParser;]Ljava/util/ArrayList;Ljava/util/ArrayList;
+HSPLjava/util/Formatter;->toString()Ljava/lang/String;
+HSPLjava/util/GregorianCalendar;-><init>()V+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/GregorianCalendar;-><init>(IIIIII)V
 HSPLjava/util/GregorianCalendar;-><init>(IIIIIII)V
 HSPLjava/util/GregorianCalendar;-><init>(Ljava/util/TimeZone;)V
-HSPLjava/util/GregorianCalendar;-><init>(Ljava/util/TimeZone;Ljava/util/Locale;)V
+HSPLjava/util/GregorianCalendar;-><init>(Ljava/util/TimeZone;Ljava/util/Locale;)V+]Lsun/util/calendar/Gregorian;Lsun/util/calendar/Gregorian;]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/GregorianCalendar;->add(II)V
 HSPLjava/util/GregorianCalendar;->adjustDstOffsetForInvalidWallClock(JLjava/util/TimeZone;I)I
 HSPLjava/util/GregorianCalendar;->adjustForZoneAndDaylightSavingsTime(IJLjava/util/TimeZone;)J
 HSPLjava/util/GregorianCalendar;->clone()Ljava/lang/Object;
-HSPLjava/util/GregorianCalendar;->computeFields()V
-HSPLjava/util/GregorianCalendar;->computeFields(II)I+]Lsun/util/calendar/Gregorian;Lsun/util/calendar/Gregorian;]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;]Ljava/util/TimeZone;Ljava/util/SimpleTimeZone;]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;]Llibcore/util/ZoneInfo;Llibcore/util/ZoneInfo;
+HSPLjava/util/GregorianCalendar;->computeFields()V+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
+HSPLjava/util/GregorianCalendar;->computeFields(II)I+]Lsun/util/calendar/Gregorian;Lsun/util/calendar/Gregorian;]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;]Llibcore/util/ZoneInfo;Llibcore/util/ZoneInfo;]Ljava/util/TimeZone;Ljava/util/SimpleTimeZone;
 HSPLjava/util/GregorianCalendar;->computeTime()V+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/GregorianCalendar;->getActualMaximum(I)I
 HSPLjava/util/GregorianCalendar;->getCalendarDate(J)Lsun/util/calendar/BaseCalendar$Date;
 HSPLjava/util/GregorianCalendar;->getCurrentFixedDate()J
-HSPLjava/util/GregorianCalendar;->getFixedDate(Lsun/util/calendar/BaseCalendar;II)J+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;
+HSPLjava/util/GregorianCalendar;->getFixedDate(Lsun/util/calendar/BaseCalendar;II)J
 HSPLjava/util/GregorianCalendar;->getGregorianCutoverDate()Lsun/util/calendar/BaseCalendar$Date;
 HSPLjava/util/GregorianCalendar;->getJulianCalendarSystem()Lsun/util/calendar/BaseCalendar;
 HSPLjava/util/GregorianCalendar;->getLeastMaximum(I)I
@@ -5573,7 +5662,7 @@
 HSPLjava/util/GregorianCalendar;->getMinimum(I)I
 HSPLjava/util/GregorianCalendar;->getNormalizedCalendar()Ljava/util/GregorianCalendar;
 HSPLjava/util/GregorianCalendar;->getTimeZone()Ljava/util/TimeZone;
-HSPLjava/util/GregorianCalendar;->getWeekNumber(JJ)I
+HSPLjava/util/GregorianCalendar;->getWeekNumber(JJ)I+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/GregorianCalendar;->internalGetEra()I
 HSPLjava/util/GregorianCalendar;->isCutoverYear(I)Z
 HSPLjava/util/GregorianCalendar;->isLeapYear(I)Z
@@ -5607,9 +5696,12 @@
 HSPLjava/util/HashMap$KeySet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/HashMap$KeySet;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/HashMap$KeySet;->size()I
+HSPLjava/util/HashMap$KeySet;->toArray()[Ljava/lang/Object;
+HSPLjava/util/HashMap$KeySet;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/HashMap$KeySpliterator;-><init>(Ljava/util/HashMap;IIII)V
 HSPLjava/util/HashMap$KeySpliterator;->characteristics()I
 HSPLjava/util/HashMap$KeySpliterator;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;Ljava/util/stream/ReferencePipeline$4$1;
+HSPLjava/util/HashMap$KeySpliterator;->tryAdvance(Ljava/util/function/Consumer;)Z
 HSPLjava/util/HashMap$Node;-><init>(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)V
 HSPLjava/util/HashMap$Node;->getKey()Ljava/lang/Object;
 HSPLjava/util/HashMap$Node;->getValue()Ljava/lang/Object;
@@ -5627,7 +5719,7 @@
 HSPLjava/util/HashMap$TreeNode;->treeify([Ljava/util/HashMap$Node;)V
 HSPLjava/util/HashMap$TreeNode;->untreeify(Ljava/util/HashMap;)Ljava/util/HashMap$Node;
 HSPLjava/util/HashMap$ValueIterator;-><init>(Ljava/util/HashMap;)V
-HSPLjava/util/HashMap$ValueIterator;->next()Ljava/lang/Object;+]Ljava/util/HashMap$ValueIterator;Ljava/util/HashMap$ValueIterator;
+HSPLjava/util/HashMap$ValueIterator;->next()Ljava/lang/Object;
 HSPLjava/util/HashMap$ValueSpliterator;-><init>(Ljava/util/HashMap;IIII)V
 HSPLjava/util/HashMap$ValueSpliterator;->characteristics()I
 HSPLjava/util/HashMap$ValueSpliterator;->forEachRemaining(Ljava/util/function/Consumer;)V
@@ -5637,10 +5729,12 @@
 HSPLjava/util/HashMap$Values;->iterator()Ljava/util/Iterator;
 HSPLjava/util/HashMap$Values;->size()I
 HSPLjava/util/HashMap$Values;->spliterator()Ljava/util/Spliterator;
+HSPLjava/util/HashMap$Values;->toArray()[Ljava/lang/Object;
+HSPLjava/util/HashMap$Values;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/HashMap;-><init>()V
 HSPLjava/util/HashMap;-><init>(I)V
 HSPLjava/util/HashMap;-><init>(IF)V
-HSPLjava/util/HashMap;-><init>(Ljava/util/Map;)V
+HSPLjava/util/HashMap;-><init>(Ljava/util/Map;)V+]Ljava/util/HashMap;Ljava/util/HashMap;
 HSPLjava/util/HashMap;->afterNodeAccess(Ljava/util/HashMap$Node;)V
 HSPLjava/util/HashMap;->afterNodeInsertion(Z)V
 HSPLjava/util/HashMap;->afterNodeRemoval(Ljava/util/HashMap$Node;)V
@@ -5652,17 +5746,19 @@
 HSPLjava/util/HashMap;->containsValue(Ljava/lang/Object;)Z
 HSPLjava/util/HashMap;->entrySet()Ljava/util/Set;
 HSPLjava/util/HashMap;->forEach(Ljava/util/function/BiConsumer;)V
-HSPLjava/util/HashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;megamorphic_types
-HSPLjava/util/HashMap;->getNode(ILjava/lang/Object;)Ljava/util/HashMap$Node;+]Ljava/lang/Object;megamorphic_types]Ljava/util/HashMap$TreeNode;Ljava/util/HashMap$TreeNode;
+HSPLjava/util/HashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types
+HSPLjava/util/HashMap;->getNode(Ljava/lang/Object;)Ljava/util/HashMap$Node;+]Ljava/lang/Object;megamorphic_types
 HSPLjava/util/HashMap;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/HashMap;->hash(Ljava/lang/Object;)I+]Ljava/lang/Object;megamorphic_types
 HSPLjava/util/HashMap;->internalWriteEntries(Ljava/io/ObjectOutputStream;)V
 HSPLjava/util/HashMap;->isEmpty()Z
 HSPLjava/util/HashMap;->keySet()Ljava/util/Set;
+HSPLjava/util/HashMap;->keysToArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/HashMap;->loadFactor()F
-HSPLjava/util/HashMap;->merge(Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;+]Ljava/util/HashMap;Ljava/util/HashMap;]Ljava/util/function/BiFunction;Lcom/android/internal/graphics/palette/QuantizerMap$$ExternalSyntheticLambda0;]Ljava/lang/Object;Ljava/lang/Integer;]Ljava/util/HashMap$TreeNode;Ljava/util/HashMap$TreeNode;
+HSPLjava/util/HashMap;->merge(Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
 HSPLjava/util/HashMap;->newNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node;
 HSPLjava/util/HashMap;->newTreeNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode;
+HSPLjava/util/HashMap;->prepareArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types
 HSPLjava/util/HashMap;->putAll(Ljava/util/Map;)V
 HSPLjava/util/HashMap;->putIfAbsent(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
@@ -5671,7 +5767,7 @@
 HSPLjava/util/HashMap;->readObject(Ljava/io/ObjectInputStream;)V
 HSPLjava/util/HashMap;->reinitialize()V
 HSPLjava/util/HashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types
-HSPLjava/util/HashMap;->removeNode(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/util/HashMap$Node;+]Ljava/util/HashMap;missing_types]Ljava/lang/Object;missing_types]Ljava/util/HashMap$TreeNode;Ljava/util/HashMap$TreeNode;
+HSPLjava/util/HashMap;->removeNode(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/util/HashMap$Node;+]Ljava/util/HashMap;missing_types]Ljava/lang/Object;missing_types
 HSPLjava/util/HashMap;->replacementNode(Ljava/util/HashMap$Node;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node;
 HSPLjava/util/HashMap;->replacementTreeNode(Ljava/util/HashMap$Node;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode;
 HSPLjava/util/HashMap;->resize()[Ljava/util/HashMap$Node;
@@ -5679,6 +5775,7 @@
 HSPLjava/util/HashMap;->tableSizeFor(I)I
 HSPLjava/util/HashMap;->treeifyBin([Ljava/util/HashMap$Node;I)V
 HSPLjava/util/HashMap;->values()Ljava/util/Collection;
+HSPLjava/util/HashMap;->valuesToArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/HashMap;->writeObject(Ljava/io/ObjectOutputStream;)V
 HSPLjava/util/HashSet;-><init>()V
 HSPLjava/util/HashSet;-><init>(I)V
@@ -5690,10 +5787,10 @@
 HSPLjava/util/HashSet;->clone()Ljava/lang/Object;
 HSPLjava/util/HashSet;->contains(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
 HSPLjava/util/HashSet;->isEmpty()Z
-HSPLjava/util/HashSet;->iterator()Ljava/util/Iterator;
+HSPLjava/util/HashSet;->iterator()Ljava/util/Iterator;+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;]Ljava/util/Set;Ljava/util/HashMap$KeySet;,Ljava/util/LinkedHashMap$LinkedKeySet;
 HSPLjava/util/HashSet;->readObject(Ljava/io/ObjectInputStream;)V
-HSPLjava/util/HashSet;->remove(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
-HSPLjava/util/HashSet;->size()I
+HSPLjava/util/HashSet;->remove(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/util/HashMap;
+HSPLjava/util/HashSet;->size()I+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
 HSPLjava/util/HashSet;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/HashSet;->writeObject(Ljava/io/ObjectOutputStream;)V
 HSPLjava/util/Hashtable$EntrySet;-><init>(Ljava/util/Hashtable;)V
@@ -5718,12 +5815,12 @@
 HSPLjava/util/Hashtable;-><init>()V
 HSPLjava/util/Hashtable;-><init>(I)V
 HSPLjava/util/Hashtable;-><init>(IF)V
-HSPLjava/util/Hashtable;->addEntry(ILjava/lang/Object;Ljava/lang/Object;I)V+]Ljava/lang/Object;Ljava/lang/String;]Ljava/util/Hashtable;Ljava/util/Hashtable;
+HSPLjava/util/Hashtable;->addEntry(ILjava/lang/Object;Ljava/lang/Object;I)V
 HSPLjava/util/Hashtable;->clear()V
 HSPLjava/util/Hashtable;->clone()Ljava/lang/Object;
 HSPLjava/util/Hashtable;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/Hashtable;->entrySet()Ljava/util/Set;
-HSPLjava/util/Hashtable;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/Hashtable;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;Ljava/lang/String;
 HSPLjava/util/Hashtable;->getEnumeration(I)Ljava/util/Enumeration;
 HSPLjava/util/Hashtable;->getIterator(I)Ljava/util/Iterator;
 HSPLjava/util/Hashtable;->isEmpty()Z
@@ -5744,6 +5841,7 @@
 HSPLjava/util/IdentityHashMap$EntryIterator;->next()Ljava/util/Map$Entry;
 HSPLjava/util/IdentityHashMap$EntrySet;-><init>(Ljava/util/IdentityHashMap;)V
 HSPLjava/util/IdentityHashMap$EntrySet;->iterator()Ljava/util/Iterator;
+HSPLjava/util/IdentityHashMap$EntrySet;->size()I
 HSPLjava/util/IdentityHashMap$IdentityHashMapIterator;-><init>(Ljava/util/IdentityHashMap;)V
 HSPLjava/util/IdentityHashMap$IdentityHashMapIterator;->hasNext()Z
 HSPLjava/util/IdentityHashMap$IdentityHashMapIterator;->nextIndex()I
@@ -5779,24 +5877,30 @@
 HSPLjava/util/IdentityHashMap;->values()Ljava/util/Collection;
 HSPLjava/util/ImmutableCollections$AbstractImmutableCollection;-><init>()V
 HSPLjava/util/ImmutableCollections$AbstractImmutableList;-><init>()V
-HSPLjava/util/ImmutableCollections$AbstractImmutableList;->iterator()Ljava/util/Iterator;+]Ljava/util/ImmutableCollections$AbstractImmutableList;Ljava/util/ImmutableCollections$ListN;,Ljava/util/ImmutableCollections$List12;
+HSPLjava/util/ImmutableCollections$AbstractImmutableList;->iterator()Ljava/util/Iterator;
+HSPLjava/util/ImmutableCollections$AbstractImmutableMap;-><init>()V
 HSPLjava/util/ImmutableCollections$AbstractImmutableSet;-><init>()V
 HSPLjava/util/ImmutableCollections$List12;-><init>(Ljava/lang/Object;)V
 HSPLjava/util/ImmutableCollections$List12;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/ImmutableCollections$List12;->get(I)Ljava/lang/Object;
 HSPLjava/util/ImmutableCollections$List12;->size()I
-HSPLjava/util/ImmutableCollections$ListN;-><init>([Ljava/lang/Object;)V
+HSPLjava/util/ImmutableCollections$ListItr;-><init>(Ljava/util/List;I)V
+HSPLjava/util/ImmutableCollections$ListItr;->hasNext()Z
+HSPLjava/util/ImmutableCollections$ListItr;->next()Ljava/lang/Object;
 HSPLjava/util/ImmutableCollections$ListN;->get(I)Ljava/lang/Object;
 HSPLjava/util/ImmutableCollections$ListN;->size()I
+HSPLjava/util/ImmutableCollections$Map1;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/ImmutableCollections$Map1;->entrySet()Ljava/util/Set;
+HSPLjava/util/ImmutableCollections$MapN;-><init>([Ljava/lang/Object;)V
+HSPLjava/util/ImmutableCollections$MapN;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/ImmutableCollections$MapN;->get(Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/ImmutableCollections$MapN;->probe(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/String;,Ljava/lang/Integer;,Landroid/hardware/biometrics/BiometricSourceType;
-HSPLjava/util/ImmutableCollections$Set0;->instance()Ljava/util/ImmutableCollections$Set0;
-HSPLjava/util/ImmutableCollections$Set1;-><init>(Ljava/lang/Object;)V
-HSPLjava/util/ImmutableCollections$Set1;->iterator()Ljava/util/Iterator;
+HSPLjava/util/ImmutableCollections$MapN;->probe(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/String;
+HSPLjava/util/ImmutableCollections$SetN;-><init>([Ljava/lang/Object;)V
+HSPLjava/util/ImmutableCollections$SetN;->contains(Ljava/lang/Object;)Z
+HSPLjava/util/ImmutableCollections$SetN;->probe(Ljava/lang/Object;)I
 HSPLjava/util/ImmutableCollections;-><clinit>()V
-HSPLjava/util/ImmutableCollections;->emptyList()Ljava/util/List;
-HSPLjava/util/Iterator;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;missing_types]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;,Ljava/util/AbstractList$Itr;
+HSPLjava/util/ImmutableCollections;->listCopy(Ljava/util/Collection;)Ljava/util/List;
+HSPLjava/util/Iterator;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;missing_types]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;
 HSPLjava/util/JumboEnumSet$EnumSetIterator;-><init>(Ljava/util/JumboEnumSet;)V
 HSPLjava/util/JumboEnumSet$EnumSetIterator;->hasNext()Z
 HSPLjava/util/JumboEnumSet$EnumSetIterator;->next()Ljava/lang/Enum;
@@ -5812,8 +5916,8 @@
 HSPLjava/util/KeyValueHolder;->getKey()Ljava/lang/Object;
 HSPLjava/util/KeyValueHolder;->getValue()Ljava/lang/Object;
 HSPLjava/util/LinkedHashMap$LinkedEntryIterator;-><init>(Ljava/util/LinkedHashMap;)V
-HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/lang/Object;
-HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/util/Map$Entry;
+HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/lang/Object;+]Ljava/util/LinkedHashMap$LinkedEntryIterator;Ljava/util/LinkedHashMap$LinkedEntryIterator;
+HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/util/Map$Entry;+]Ljava/util/LinkedHashMap$LinkedEntryIterator;Ljava/util/LinkedHashMap$LinkedEntryIterator;
 HSPLjava/util/LinkedHashMap$LinkedEntrySet;-><init>(Ljava/util/LinkedHashMap;)V
 HSPLjava/util/LinkedHashMap$LinkedEntrySet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/LinkedHashMap$LinkedEntrySet;->size()I
@@ -5823,7 +5927,7 @@
 HSPLjava/util/LinkedHashMap$LinkedHashIterator;->remove()V
 HSPLjava/util/LinkedHashMap$LinkedHashMapEntry;-><init>(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)V
 HSPLjava/util/LinkedHashMap$LinkedKeyIterator;-><init>(Ljava/util/LinkedHashMap;)V
-HSPLjava/util/LinkedHashMap$LinkedKeyIterator;->next()Ljava/lang/Object;+]Ljava/util/LinkedHashMap$LinkedHashMapEntry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;]Ljava/util/LinkedHashMap$LinkedKeyIterator;Ljava/util/LinkedHashMap$LinkedKeyIterator;
+HSPLjava/util/LinkedHashMap$LinkedKeyIterator;->next()Ljava/lang/Object;
 HSPLjava/util/LinkedHashMap$LinkedKeySet;-><init>(Ljava/util/LinkedHashMap;)V
 HSPLjava/util/LinkedHashMap$LinkedKeySet;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/LinkedHashMap$LinkedKeySet;->iterator()Ljava/util/Iterator;
@@ -5848,6 +5952,7 @@
 HSPLjava/util/LinkedHashMap;->keySet()Ljava/util/Set;
 HSPLjava/util/LinkedHashMap;->linkNodeLast(Ljava/util/LinkedHashMap$LinkedHashMapEntry;)V
 HSPLjava/util/LinkedHashMap;->newNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node;
+HSPLjava/util/LinkedHashMap;->newTreeNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode;
 HSPLjava/util/LinkedHashMap;->reinitialize()V
 HSPLjava/util/LinkedHashMap;->removeEldestEntry(Ljava/util/Map$Entry;)Z
 HSPLjava/util/LinkedHashMap;->replacementTreeNode(Ljava/util/HashMap$Node;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode;
@@ -5856,12 +5961,12 @@
 HSPLjava/util/LinkedHashSet;-><init>()V
 HSPLjava/util/LinkedHashSet;-><init>(I)V
 HSPLjava/util/LinkedHashSet;-><init>(Ljava/util/Collection;)V
-HSPLjava/util/LinkedList$ListItr;-><init>(Ljava/util/LinkedList;I)V+]Ljava/util/LinkedList;Ljava/util/LinkedList;
-HSPLjava/util/LinkedList$ListItr;->add(Ljava/lang/Object;)V+]Ljava/util/LinkedList;Ljava/util/LinkedList;]Ljava/util/LinkedList$ListItr;Ljava/util/LinkedList$ListItr;
+HSPLjava/util/LinkedList$ListItr;-><init>(Ljava/util/LinkedList;I)V
+HSPLjava/util/LinkedList$ListItr;->add(Ljava/lang/Object;)V
 HSPLjava/util/LinkedList$ListItr;->checkForComodification()V
 HSPLjava/util/LinkedList$ListItr;->hasNext()Z
 HSPLjava/util/LinkedList$ListItr;->hasPrevious()Z
-HSPLjava/util/LinkedList$ListItr;->next()Ljava/lang/Object;+]Ljava/util/LinkedList$ListItr;Ljava/util/LinkedList$ListItr;
+HSPLjava/util/LinkedList$ListItr;->next()Ljava/lang/Object;
 HSPLjava/util/LinkedList$ListItr;->previous()Ljava/lang/Object;+]Ljava/util/LinkedList$ListItr;Ljava/util/LinkedList$ListItr;
 HSPLjava/util/LinkedList$ListItr;->remove()V
 HSPLjava/util/LinkedList$ListItr;->set(Ljava/lang/Object;)V
@@ -5869,8 +5974,8 @@
 HSPLjava/util/LinkedList;-><init>()V
 HSPLjava/util/LinkedList;-><init>(Ljava/util/Collection;)V
 HSPLjava/util/LinkedList;->add(ILjava/lang/Object;)V
-HSPLjava/util/LinkedList;->add(Ljava/lang/Object;)Z
-HSPLjava/util/LinkedList;->addAll(ILjava/util/Collection;)Z+]Ljava/util/Collection;Ljava/util/LinkedList;]Ljava/util/LinkedList;Ljava/util/LinkedList;
+HSPLjava/util/LinkedList;->add(Ljava/lang/Object;)Z+]Ljava/util/LinkedList;missing_types
+HSPLjava/util/LinkedList;->addAll(ILjava/util/Collection;)Z
 HSPLjava/util/LinkedList;->addAll(Ljava/util/Collection;)Z
 HSPLjava/util/LinkedList;->addFirst(Ljava/lang/Object;)V
 HSPLjava/util/LinkedList;->addLast(Ljava/lang/Object;)V
@@ -5896,10 +6001,11 @@
 HSPLjava/util/LinkedList;->peekLast()Ljava/lang/Object;
 HSPLjava/util/LinkedList;->poll()Ljava/lang/Object;
 HSPLjava/util/LinkedList;->pollFirst()Ljava/lang/Object;
+HSPLjava/util/LinkedList;->pollLast()Ljava/lang/Object;
 HSPLjava/util/LinkedList;->pop()Ljava/lang/Object;
 HSPLjava/util/LinkedList;->push(Ljava/lang/Object;)V
 HSPLjava/util/LinkedList;->remove()Ljava/lang/Object;
-HSPLjava/util/LinkedList;->remove(I)Ljava/lang/Object;
+HSPLjava/util/LinkedList;->remove(I)Ljava/lang/Object;+]Ljava/util/LinkedList;Ljava/util/LinkedList;
 HSPLjava/util/LinkedList;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/LinkedList;->removeFirst()Ljava/lang/Object;
 HSPLjava/util/LinkedList;->removeLast()Ljava/lang/Object;
@@ -5910,10 +6016,13 @@
 HSPLjava/util/LinkedList;->unlink(Ljava/util/LinkedList$Node;)Ljava/lang/Object;
 HSPLjava/util/LinkedList;->unlinkFirst(Ljava/util/LinkedList$Node;)Ljava/lang/Object;
 HSPLjava/util/LinkedList;->unlinkLast(Ljava/util/LinkedList$Node;)Ljava/lang/Object;
+HSPLjava/util/List;->copyOf(Ljava/util/Collection;)Ljava/util/List;
+HSPLjava/util/List;->of()Ljava/util/List;
 HSPLjava/util/List;->of(Ljava/lang/Object;)Ljava/util/List;
 HSPLjava/util/List;->of(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
 HSPLjava/util/List;->of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
-HSPLjava/util/List;->sort(Ljava/util/Comparator;)V+]Ljava/util/ListIterator;Ljava/util/LinkedList$ListItr;]Ljava/util/List;Ljava/util/LinkedList;
+HSPLjava/util/List;->of([Ljava/lang/Object;)Ljava/util/List;
+HSPLjava/util/List;->sort(Ljava/util/Comparator;)V+]Ljava/util/ListIterator;Ljava/util/LinkedList$ListItr;,Ljava/util/ArrayList$SubList$1;]Ljava/util/List;Ljava/util/LinkedList;,Ljava/util/ArrayList$SubList;
 HSPLjava/util/List;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/Locale$Builder;-><init>()V
 HSPLjava/util/Locale$Builder;->build()Ljava/util/Locale;
@@ -5922,12 +6031,12 @@
 HSPLjava/util/Locale$Builder;->setScript(Ljava/lang/String;)Ljava/util/Locale$Builder;
 HSPLjava/util/Locale$Builder;->setVariant(Ljava/lang/String;)Ljava/util/Locale$Builder;
 HSPLjava/util/Locale$Cache;->createObject(Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/Locale$Cache;->createObject(Ljava/util/Locale$LocaleKey;)Ljava/util/Locale;
+HSPLjava/util/Locale$Cache;->createObject(Ljava/lang/Object;)Ljava/util/Locale;
 HSPLjava/util/Locale$LocaleKey;->-$$Nest$fgetbase(Ljava/util/Locale$LocaleKey;)Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale$LocaleKey;->-$$Nest$fgetexts(Ljava/util/Locale$LocaleKey;)Lsun/util/locale/LocaleExtensions;
 HSPLjava/util/Locale$LocaleKey;-><init>(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;)V
 HSPLjava/util/Locale$LocaleKey;-><init>(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;Ljava/util/Locale$LocaleKey-IA;)V
-HSPLjava/util/Locale$LocaleKey;->equals(Ljava/lang/Object;)Z+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
+HSPLjava/util/Locale$LocaleKey;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/Locale$LocaleKey;->hashCode()I
 HSPLjava/util/Locale;-><init>(Ljava/lang/String;)V
 HSPLjava/util/Locale;-><init>(Ljava/lang/String;Ljava/lang/String;)V
@@ -5942,7 +6051,7 @@
 HSPLjava/util/Locale;->getAvailableLocales()[Ljava/util/Locale;
 HSPLjava/util/Locale;->getBaseLocale()Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale;->getCompatibilityExtensions(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lsun/util/locale/LocaleExtensions;
-HSPLjava/util/Locale;->getCountry()Ljava/lang/String;
+HSPLjava/util/Locale;->getCountry()Ljava/lang/String;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale;->getDefault()Ljava/util/Locale;
 HSPLjava/util/Locale;->getDefault(Ljava/util/Locale$Category;)Ljava/util/Locale;+]Ljava/util/Locale$Category;Ljava/util/Locale$Category;
 HSPLjava/util/Locale;->getDisplayCountry(Ljava/util/Locale;)Ljava/lang/String;
@@ -5956,22 +6065,25 @@
 HSPLjava/util/Locale;->getInstance(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;)Ljava/util/Locale;
 HSPLjava/util/Locale;->getLanguage()Ljava/lang/String;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale;->getScript()Ljava/lang/String;
-HSPLjava/util/Locale;->getVariant()Ljava/lang/String;
+HSPLjava/util/Locale;->getUnicodeLocaleType(Ljava/lang/String;)Ljava/lang/String;
+HSPLjava/util/Locale;->getVariant()Ljava/lang/String;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale;->hasExtensions()Z
 HSPLjava/util/Locale;->hashCode()I
+HSPLjava/util/Locale;->isUnicodeExtensionKey(Ljava/lang/String;)Z
 HSPLjava/util/Locale;->isValidBcp47Alpha(Ljava/lang/String;II)Z
 HSPLjava/util/Locale;->normalizeAndValidateLanguage(Ljava/lang/String;Z)Ljava/lang/String;
 HSPLjava/util/Locale;->normalizeAndValidateRegion(Ljava/lang/String;Z)Ljava/lang/String;
-HSPLjava/util/Locale;->readObject(Ljava/io/ObjectInputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$GetField;Ljava/io/ObjectInputStream$GetFieldImpl;
-HSPLjava/util/Locale;->readResolve()Ljava/lang/Object;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
+HSPLjava/util/Locale;->readObject(Ljava/io/ObjectInputStream;)V
+HSPLjava/util/Locale;->readResolve()Ljava/lang/Object;
 HSPLjava/util/Locale;->setDefault(Ljava/util/Locale$Category;Ljava/util/Locale;)V
 HSPLjava/util/Locale;->setDefault(Ljava/util/Locale;)V
-HSPLjava/util/Locale;->toLanguageTag()Ljava/lang/String;
-HSPLjava/util/Locale;->toString()Ljava/lang/String;
+HSPLjava/util/Locale;->toLanguageTag()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/util/locale/LanguageTag;Lsun/util/locale/LanguageTag;]Ljava/util/List;Ljava/util/Collections$EmptyList;]Ljava/util/Iterator;Ljava/util/Collections$EmptyIterator;
+HSPLjava/util/Locale;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale;->writeObject(Ljava/io/ObjectOutputStream;)V
 HSPLjava/util/Map;->computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;+]Ljava/util/function/Function;missing_types]Ljava/util/Map;Landroid/util/ArrayMap;
 HSPLjava/util/Map;->forEach(Ljava/util/function/BiConsumer;)V
 HSPLjava/util/Map;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;Landroid/util/ArrayMap;,Ljava/util/ImmutableCollections$MapN;
+HSPLjava/util/Map;->of(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;
 HSPLjava/util/MissingResourceException;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/util/NoSuchElementException;-><init>()V
 HSPLjava/util/NoSuchElementException;-><init>(Ljava/lang/String;)V
@@ -5979,7 +6091,7 @@
 HSPLjava/util/Objects;->checkIndex(II)I
 HSPLjava/util/Objects;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z+]Ljava/lang/Object;megamorphic_types
 HSPLjava/util/Objects;->hash([Ljava/lang/Object;)I
-HSPLjava/util/Objects;->hashCode(Ljava/lang/Object;)I+]Ljava/lang/Object;megamorphic_types
+HSPLjava/util/Objects;->hashCode(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/String;,Ljava/lang/Integer;,Ljava/lang/Long;
 HSPLjava/util/Objects;->nonNull(Ljava/lang/Object;)Z
 HSPLjava/util/Objects;->requireNonNull(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/Objects;->requireNonNull(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
@@ -6010,8 +6122,10 @@
 HSPLjava/util/OptionalDouble;-><init>(D)V
 HSPLjava/util/OptionalDouble;->of(D)Ljava/util/OptionalDouble;
 HSPLjava/util/OptionalDouble;->orElseGet(Ljava/util/function/DoubleSupplier;)D
+HSPLjava/util/OptionalInt;-><init>(I)V
 HSPLjava/util/OptionalInt;->empty()Ljava/util/OptionalInt;
 HSPLjava/util/OptionalInt;->isPresent()Z
+HSPLjava/util/OptionalInt;->of(I)Ljava/util/OptionalInt;
 HSPLjava/util/PriorityQueue$Itr;-><init>(Ljava/util/PriorityQueue;)V
 HSPLjava/util/PriorityQueue$Itr;->hasNext()Z
 HSPLjava/util/PriorityQueue$Itr;->next()Ljava/lang/Object;
@@ -6034,11 +6148,9 @@
 HSPLjava/util/PriorityQueue;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/PriorityQueue;->removeAt(I)Ljava/lang/Object;
 HSPLjava/util/PriorityQueue;->siftDown(ILjava/lang/Object;)V
-HSPLjava/util/PriorityQueue;->siftDownComparable(ILjava/lang/Object;)V
-HSPLjava/util/PriorityQueue;->siftDownUsingComparator(ILjava/lang/Object;)V
+HSPLjava/util/PriorityQueue;->siftDownComparable(ILjava/lang/Object;[Ljava/lang/Object;I)V
+HSPLjava/util/PriorityQueue;->siftDownUsingComparator(ILjava/lang/Object;[Ljava/lang/Object;ILjava/util/Comparator;)V
 HSPLjava/util/PriorityQueue;->siftUp(ILjava/lang/Object;)V
-HSPLjava/util/PriorityQueue;->siftUpComparable(ILjava/lang/Object;)V
-HSPLjava/util/PriorityQueue;->siftUpUsingComparator(ILjava/lang/Object;)V
 HSPLjava/util/PriorityQueue;->size()I
 HSPLjava/util/PriorityQueue;->toArray()[Ljava/lang/Object;
 HSPLjava/util/PriorityQueue;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
@@ -6047,13 +6159,13 @@
 HSPLjava/util/Properties$LineReader;->readLine()I
 HSPLjava/util/Properties;-><init>()V
 HSPLjava/util/Properties;-><init>(Ljava/util/Properties;)V
-HSPLjava/util/Properties;->getProperty(Ljava/lang/String;)Ljava/lang/String;
+HSPLjava/util/Properties;->getProperty(Ljava/lang/String;)Ljava/lang/String;+]Ljava/util/Properties;Ljava/util/Properties;
 HSPLjava/util/Properties;->getProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/Properties;->load(Ljava/io/InputStream;)V
 HSPLjava/util/Properties;->load(Ljava/io/Reader;)V
 HSPLjava/util/Properties;->load0(Ljava/util/Properties$LineReader;)V
 HSPLjava/util/Properties;->loadConvert([CII[C)Ljava/lang/String;
-HSPLjava/util/Properties;->saveConvert(Ljava/lang/String;ZZ)Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
+HSPLjava/util/Properties;->saveConvert(Ljava/lang/String;ZZ)Ljava/lang/String;
 HSPLjava/util/Properties;->setProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
 HSPLjava/util/Properties;->store(Ljava/io/OutputStream;Ljava/lang/String;)V
 HSPLjava/util/Properties;->store0(Ljava/io/BufferedWriter;Ljava/lang/String;Z)V
@@ -6087,7 +6199,7 @@
 HSPLjava/util/RegularEnumSet;->addAll(Ljava/util/Collection;)Z
 HSPLjava/util/RegularEnumSet;->clear()V
 HSPLjava/util/RegularEnumSet;->complement()V
-HSPLjava/util/RegularEnumSet;->contains(Ljava/lang/Object;)Z
+HSPLjava/util/RegularEnumSet;->contains(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types]Ljava/lang/Enum;missing_types
 HSPLjava/util/RegularEnumSet;->containsAll(Ljava/util/Collection;)Z
 HSPLjava/util/RegularEnumSet;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/RegularEnumSet;->isEmpty()Z
@@ -6097,8 +6209,6 @@
 HSPLjava/util/ResourceBundle$BundleReference;-><init>(Ljava/util/ResourceBundle;Ljava/lang/ref/ReferenceQueue;Ljava/util/ResourceBundle$CacheKey;)V
 HSPLjava/util/ResourceBundle$BundleReference;->getCacheKey()Ljava/util/ResourceBundle$CacheKey;
 HSPLjava/util/ResourceBundle$CacheKey;-><init>(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;)V
-HSPLjava/util/ResourceBundle$CacheKey;->calculateHashCode()V
-HSPLjava/util/ResourceBundle$CacheKey;->clone()Ljava/lang/Object;
 HSPLjava/util/ResourceBundle$CacheKey;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/ResourceBundle$CacheKey;->getCause()Ljava/lang/Throwable;
 HSPLjava/util/ResourceBundle$CacheKey;->getLoader()Ljava/lang/ClassLoader;
@@ -6107,7 +6217,6 @@
 HSPLjava/util/ResourceBundle$CacheKey;->hashCode()I
 HSPLjava/util/ResourceBundle$CacheKey;->setFormat(Ljava/lang/String;)V
 HSPLjava/util/ResourceBundle$CacheKey;->setLocale(Ljava/util/Locale;)Ljava/util/ResourceBundle$CacheKey;
-HSPLjava/util/ResourceBundle$Control$1;-><init>(Ljava/util/ResourceBundle$Control;ZLjava/lang/ClassLoader;Ljava/lang/String;)V
 HSPLjava/util/ResourceBundle$Control$1;->run()Ljava/io/InputStream;
 HSPLjava/util/ResourceBundle$Control$1;->run()Ljava/lang/Object;
 HSPLjava/util/ResourceBundle$Control$CandidateListCache;->createObject(Ljava/lang/Object;)Ljava/lang/Object;
@@ -6121,7 +6230,6 @@
 HSPLjava/util/ResourceBundle$Control;->toBundleName(Ljava/lang/String;Ljava/util/Locale;)Ljava/lang/String;
 HSPLjava/util/ResourceBundle$Control;->toResourceName(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/ResourceBundle$Control;->toResourceName0(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/util/ResourceBundle$LoaderReference;-><init>(Ljava/lang/ClassLoader;Ljava/lang/ref/ReferenceQueue;Ljava/util/ResourceBundle$CacheKey;)V
 HSPLjava/util/ResourceBundle;-><init>()V
 HSPLjava/util/ResourceBundle;->findBundle(Ljava/util/ResourceBundle$CacheKey;Ljava/util/List;Ljava/util/List;ILjava/util/ResourceBundle$Control;Ljava/util/ResourceBundle;)Ljava/util/ResourceBundle;
 HSPLjava/util/ResourceBundle;->findBundleInCache(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;
@@ -6135,9 +6243,6 @@
 HSPLjava/util/ResourceBundle;->putBundleInCache(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;
 HSPLjava/util/ResourceBundle;->setExpirationTime(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle$Control;)V
 HSPLjava/util/ResourceBundle;->setParent(Ljava/util/ResourceBundle;)V
-HSPLjava/util/Scanner$1;-><init>(Ljava/util/Scanner;I)V
-HSPLjava/util/Scanner$1;->create(Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/Scanner$1;->create(Ljava/lang/String;)Ljava/util/regex/Pattern;
 HSPLjava/util/Scanner;-><init>(Ljava/io/InputStream;)V
 HSPLjava/util/Scanner;-><init>(Ljava/io/InputStream;Ljava/lang/String;)V
 HSPLjava/util/Scanner;-><init>(Ljava/lang/Readable;Ljava/util/regex/Pattern;)V
@@ -6175,6 +6280,7 @@
 HSPLjava/util/ServiceLoader;->parseLine(Ljava/lang/Class;Ljava/net/URL;Ljava/io/BufferedReader;ILjava/util/List;)I
 HSPLjava/util/ServiceLoader;->reload()V
 HSPLjava/util/Set;->of(Ljava/lang/Object;)Ljava/util/Set;
+HSPLjava/util/Set;->of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Set;
 HSPLjava/util/Set;->of([Ljava/lang/Object;)Ljava/util/Set;
 HSPLjava/util/Set;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/SimpleTimeZone;-><init>(ILjava/lang/String;)V
@@ -6193,13 +6299,15 @@
 HSPLjava/util/Spliterators$ArraySpliterator;->tryAdvance(Ljava/util/function/Consumer;)Z
 HSPLjava/util/Spliterators$EmptySpliterator$OfInt;->forEachRemaining(Ljava/util/function/IntConsumer;)V
 HSPLjava/util/Spliterators$EmptySpliterator$OfRef;->forEachRemaining(Ljava/util/function/Consumer;)V
+HSPLjava/util/Spliterators$EmptySpliterator$OfRef;->tryAdvance(Ljava/util/function/Consumer;)Z
 HSPLjava/util/Spliterators$EmptySpliterator;->characteristics()I
 HSPLjava/util/Spliterators$EmptySpliterator;->estimateSize()J
 HSPLjava/util/Spliterators$EmptySpliterator;->forEachRemaining(Ljava/lang/Object;)V
+HSPLjava/util/Spliterators$EmptySpliterator;->tryAdvance(Ljava/lang/Object;)Z
 HSPLjava/util/Spliterators$IntArraySpliterator;-><init>([IIII)V
 HSPLjava/util/Spliterators$IntArraySpliterator;->characteristics()I
 HSPLjava/util/Spliterators$IntArraySpliterator;->estimateSize()J
-HSPLjava/util/Spliterators$IntArraySpliterator;->forEachRemaining(Ljava/util/function/IntConsumer;)V+]Ljava/util/function/IntConsumer;Ljava/util/stream/IntPipeline$4$1;
+HSPLjava/util/Spliterators$IntArraySpliterator;->forEachRemaining(Ljava/util/function/IntConsumer;)V
 HSPLjava/util/Spliterators$IntArraySpliterator;->tryAdvance(Ljava/util/function/IntConsumer;)Z
 HSPLjava/util/Spliterators$IteratorSpliterator;-><init>(Ljava/util/Collection;I)V
 HSPLjava/util/Spliterators$IteratorSpliterator;->characteristics()I
@@ -6221,7 +6329,8 @@
 HSPLjava/util/StringJoiner;-><init>(Ljava/lang/CharSequence;)V
 HSPLjava/util/StringJoiner;-><init>(Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)V
 HSPLjava/util/StringJoiner;->add(Ljava/lang/CharSequence;)Ljava/util/StringJoiner;
-HSPLjava/util/StringJoiner;->prepareBuilder()Ljava/lang/StringBuilder;
+HSPLjava/util/StringJoiner;->compactElts()V
+HSPLjava/util/StringJoiner;->getChars(Ljava/lang/String;[CI)I
 HSPLjava/util/StringJoiner;->toString()Ljava/lang/String;
 HSPLjava/util/StringTokenizer;-><init>(Ljava/lang/String;)V
 HSPLjava/util/StringTokenizer;-><init>(Ljava/lang/String;Ljava/lang/String;)V
@@ -6243,7 +6352,7 @@
 HSPLjava/util/TaskQueue;->removeMin()V
 HSPLjava/util/TaskQueue;->rescheduleMin(J)V
 HSPLjava/util/TimSort;-><init>([Ljava/lang/Object;Ljava/util/Comparator;[Ljava/lang/Object;II)V
-HSPLjava/util/TimSort;->binarySort([Ljava/lang/Object;IIILjava/util/Comparator;)V+]Ljava/util/Comparator;missing_types
+HSPLjava/util/TimSort;->binarySort([Ljava/lang/Object;IIILjava/util/Comparator;)V
 HSPLjava/util/TimSort;->countRunAndMakeAscending([Ljava/lang/Object;IILjava/util/Comparator;)I
 HSPLjava/util/TimSort;->ensureCapacity(I)[Ljava/lang/Object;
 HSPLjava/util/TimSort;->gallopLeft(Ljava/lang/Object;[Ljava/lang/Object;IIILjava/util/Comparator;)I
@@ -6252,7 +6361,7 @@
 HSPLjava/util/TimSort;->mergeCollapse()V
 HSPLjava/util/TimSort;->mergeForceCollapse()V
 HSPLjava/util/TimSort;->mergeHi(IIII)V
-HSPLjava/util/TimSort;->mergeLo(IIII)V+]Ljava/util/Comparator;Lcom/android/internal/graphics/palette/WSMeansQuantizer$$ExternalSyntheticLambda0;
+HSPLjava/util/TimSort;->mergeLo(IIII)V
 HSPLjava/util/TimSort;->minRunLength(I)I
 HSPLjava/util/TimSort;->pushRun(II)V
 HSPLjava/util/TimSort;->reverseRange([Ljava/lang/Object;II)V
@@ -6279,6 +6388,7 @@
 HSPLjava/util/Timer;->cancel()V
 HSPLjava/util/Timer;->sched(Ljava/util/TimerTask;JJ)V
 HSPLjava/util/Timer;->schedule(Ljava/util/TimerTask;J)V
+HSPLjava/util/Timer;->schedule(Ljava/util/TimerTask;JJ)V
 HSPLjava/util/Timer;->scheduleAtFixedRate(Ljava/util/TimerTask;JJ)V
 HSPLjava/util/Timer;->serialNumber()I
 HSPLjava/util/TimerTask;-><init>()V
@@ -6293,21 +6403,21 @@
 HSPLjava/util/TreeMap$AscendingSubMap;->keyIterator()Ljava/util/Iterator;
 HSPLjava/util/TreeMap$DescendingSubMap;-><init>(Ljava/util/TreeMap;ZLjava/lang/Object;ZZLjava/lang/Object;Z)V
 HSPLjava/util/TreeMap$DescendingSubMap;->keyIterator()Ljava/util/Iterator;
-HSPLjava/util/TreeMap$DescendingSubMap;->subLowest()Ljava/util/TreeMap$TreeMapEntry;+]Ljava/util/TreeMap$DescendingSubMap;Ljava/util/TreeMap$DescendingSubMap;
+HSPLjava/util/TreeMap$DescendingSubMap;->subLowest()Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap$EntryIterator;-><init>(Ljava/util/TreeMap;Ljava/util/TreeMap$TreeMapEntry;)V
-HSPLjava/util/TreeMap$EntryIterator;->next()Ljava/lang/Object;
-HSPLjava/util/TreeMap$EntryIterator;->next()Ljava/util/Map$Entry;
+HSPLjava/util/TreeMap$EntryIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$EntryIterator;Ljava/util/TreeMap$EntryIterator;
+HSPLjava/util/TreeMap$EntryIterator;->next()Ljava/util/Map$Entry;+]Ljava/util/TreeMap$EntryIterator;Ljava/util/TreeMap$EntryIterator;
 HSPLjava/util/TreeMap$EntrySet;-><init>(Ljava/util/TreeMap;)V
-HSPLjava/util/TreeMap$EntrySet;->iterator()Ljava/util/Iterator;
+HSPLjava/util/TreeMap$EntrySet;->iterator()Ljava/util/Iterator;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
 HSPLjava/util/TreeMap$EntrySet;->size()I
 HSPLjava/util/TreeMap$KeyIterator;-><init>(Ljava/util/TreeMap;Ljava/util/TreeMap$TreeMapEntry;)V
-HSPLjava/util/TreeMap$KeyIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$KeyIterator;Ljava/util/TreeMap$KeyIterator;
+HSPLjava/util/TreeMap$KeyIterator;->next()Ljava/lang/Object;
 HSPLjava/util/TreeMap$KeySet;-><init>(Ljava/util/NavigableMap;)V
 HSPLjava/util/TreeMap$KeySet;->isEmpty()Z
 HSPLjava/util/TreeMap$KeySet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/TreeMap$KeySet;->size()I
 HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;-><init>(Ljava/util/TreeMap$NavigableSubMap;Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;)V
-HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;Ljava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;
+HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;->next()Ljava/lang/Object;
 HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;-><init>(Ljava/util/TreeMap$NavigableSubMap;)V
 HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;->isEmpty()Z
 HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;->size()I
@@ -6327,8 +6437,8 @@
 HSPLjava/util/TreeMap$NavigableSubMap;->absHighest()Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap$NavigableSubMap;->absLowFence()Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap$NavigableSubMap;->absLowest()Ljava/util/TreeMap$TreeMapEntry;
-HSPLjava/util/TreeMap$NavigableSubMap;->firstKey()Ljava/lang/Object;+]Ljava/util/TreeMap$NavigableSubMap;Ljava/util/TreeMap$DescendingSubMap;
-HSPLjava/util/TreeMap$NavigableSubMap;->inRange(Ljava/lang/Object;)Z+]Ljava/util/TreeMap$NavigableSubMap;Ljava/util/TreeMap$AscendingSubMap;
+HSPLjava/util/TreeMap$NavigableSubMap;->firstKey()Ljava/lang/Object;
+HSPLjava/util/TreeMap$NavigableSubMap;->inRange(Ljava/lang/Object;)Z
 HSPLjava/util/TreeMap$NavigableSubMap;->isEmpty()Z
 HSPLjava/util/TreeMap$NavigableSubMap;->navigableKeySet()Ljava/util/NavigableSet;
 HSPLjava/util/TreeMap$NavigableSubMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
@@ -6356,14 +6466,14 @@
 HSPLjava/util/TreeMap;->buildFromSorted(IIIILjava/util/Iterator;Ljava/io/ObjectInputStream;Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap;->buildFromSorted(ILjava/util/Iterator;Ljava/io/ObjectInputStream;Ljava/lang/Object;)V
 HSPLjava/util/TreeMap;->ceilingEntry(Ljava/lang/Object;)Ljava/util/Map$Entry;
-HSPLjava/util/TreeMap;->ceilingKey(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/TreeMap;->ceilingKey(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
 HSPLjava/util/TreeMap;->clear()V
 HSPLjava/util/TreeMap;->clone()Ljava/lang/Object;
 HSPLjava/util/TreeMap;->colorOf(Ljava/util/TreeMap$TreeMapEntry;)Z
 HSPLjava/util/TreeMap;->comparator()Ljava/util/Comparator;
-HSPLjava/util/TreeMap;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLjava/util/TreeMap;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Ljava/util/Comparator;missing_types]Ljava/lang/Comparable;missing_types
 HSPLjava/util/TreeMap;->computeRedLevel(I)I
-HSPLjava/util/TreeMap;->containsKey(Ljava/lang/Object;)Z+]Ljava/util/TreeMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeMap;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/TreeMap;->deleteEntry(Ljava/util/TreeMap$TreeMapEntry;)V
 HSPLjava/util/TreeMap;->descendingKeySet()Ljava/util/NavigableSet;
 HSPLjava/util/TreeMap;->descendingMap()Ljava/util/NavigableMap;
@@ -6395,7 +6505,7 @@
 HSPLjava/util/TreeMap;->parentOf(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap;->pollFirstEntry()Ljava/util/Map$Entry;
 HSPLjava/util/TreeMap;->predecessor(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry;
-HSPLjava/util/TreeMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Comparator;missing_types]Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;]Ljava/util/TreeMap;missing_types]Ljava/lang/Comparable;missing_types
+HSPLjava/util/TreeMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Comparator;missing_types]Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;]Ljava/util/TreeMap;Ljava/util/TreeMap;]Ljava/lang/Comparable;missing_types
 HSPLjava/util/TreeMap;->putAll(Ljava/util/Map;)V
 HSPLjava/util/TreeMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
 HSPLjava/util/TreeMap;->rightOf(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry;
@@ -6413,19 +6523,19 @@
 HSPLjava/util/TreeSet;-><init>(Ljava/util/Comparator;)V
 HSPLjava/util/TreeSet;-><init>(Ljava/util/NavigableMap;)V
 HSPLjava/util/TreeSet;-><init>(Ljava/util/SortedSet;)V
-HSPLjava/util/TreeSet;->add(Ljava/lang/Object;)Z+]Ljava/util/NavigableMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeSet;->add(Ljava/lang/Object;)Z
 HSPLjava/util/TreeSet;->addAll(Ljava/util/Collection;)Z
 HSPLjava/util/TreeSet;->ceiling(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/TreeSet;->clear()V
 HSPLjava/util/TreeSet;->comparator()Ljava/util/Comparator;
-HSPLjava/util/TreeSet;->contains(Ljava/lang/Object;)Z+]Ljava/util/NavigableMap;Ljava/util/TreeMap;
-HSPLjava/util/TreeSet;->descendingSet()Ljava/util/NavigableSet;+]Ljava/util/NavigableMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeSet;->contains(Ljava/lang/Object;)Z
+HSPLjava/util/TreeSet;->descendingSet()Ljava/util/NavigableSet;
 HSPLjava/util/TreeSet;->first()Ljava/lang/Object;
-HSPLjava/util/TreeSet;->floor(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/NavigableMap;Ljava/util/TreeMap;
-HSPLjava/util/TreeSet;->isEmpty()Z
+HSPLjava/util/TreeSet;->floor(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/TreeSet;->isEmpty()Z+]Ljava/util/NavigableMap;Ljava/util/TreeMap;
 HSPLjava/util/TreeSet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/TreeSet;->last()Ljava/lang/Object;
-HSPLjava/util/TreeSet;->remove(Ljava/lang/Object;)Z+]Ljava/util/NavigableMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeSet;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/TreeSet;->size()I
 HSPLjava/util/TreeSet;->subSet(Ljava/lang/Object;ZLjava/lang/Object;Z)Ljava/util/NavigableSet;
 HSPLjava/util/TreeSet;->tailSet(Ljava/lang/Object;Z)Ljava/util/NavigableSet;
@@ -6434,12 +6544,13 @@
 HSPLjava/util/UUID;->digits(JI)Ljava/lang/String;
 HSPLjava/util/UUID;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/UUID;->fromString(Ljava/lang/String;)Ljava/util/UUID;
+HSPLjava/util/UUID;->fromStringJava8(Ljava/lang/String;)Ljava/util/UUID;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Long;Ljava/lang/Long;
 HSPLjava/util/UUID;->getLeastSignificantBits()J
 HSPLjava/util/UUID;->getMostSignificantBits()J
 HSPLjava/util/UUID;->hashCode()I
 HSPLjava/util/UUID;->nameUUIDFromBytes([B)Ljava/util/UUID;
 HSPLjava/util/UUID;->randomUUID()Ljava/util/UUID;
-HSPLjava/util/UUID;->toString()Ljava/lang/String;
+HSPLjava/util/UUID;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/util/Vector$1;-><init>(Ljava/util/Vector;)V
 HSPLjava/util/Vector$1;->hasMoreElements()Z
 HSPLjava/util/Vector$1;->nextElement()Ljava/lang/Object;
@@ -6451,6 +6562,7 @@
 HSPLjava/util/Vector;-><init>(I)V
 HSPLjava/util/Vector;-><init>(II)V
 HSPLjava/util/Vector;->add(Ljava/lang/Object;)Z
+HSPLjava/util/Vector;->add(Ljava/lang/Object;[Ljava/lang/Object;I)V
 HSPLjava/util/Vector;->addElement(Ljava/lang/Object;)V
 HSPLjava/util/Vector;->clear()V
 HSPLjava/util/Vector;->contains(Ljava/lang/Object;)Z
@@ -6458,9 +6570,7 @@
 HSPLjava/util/Vector;->elementAt(I)Ljava/lang/Object;
 HSPLjava/util/Vector;->elementData(I)Ljava/lang/Object;
 HSPLjava/util/Vector;->elements()Ljava/util/Enumeration;
-HSPLjava/util/Vector;->ensureCapacityHelper(I)V
 HSPLjava/util/Vector;->get(I)Ljava/lang/Object;
-HSPLjava/util/Vector;->grow(I)V
 HSPLjava/util/Vector;->indexOf(Ljava/lang/Object;)I
 HSPLjava/util/Vector;->indexOf(Ljava/lang/Object;I)I
 HSPLjava/util/Vector;->isEmpty()Z
@@ -6473,7 +6583,7 @@
 HSPLjava/util/Vector;->toArray()[Ljava/lang/Object;
 HSPLjava/util/Vector;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/WeakHashMap$Entry;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;ILjava/util/WeakHashMap$Entry;)V
-HSPLjava/util/WeakHashMap$Entry;->getKey()Ljava/lang/Object;+]Ljava/util/WeakHashMap$Entry;Ljava/util/WeakHashMap$Entry;
+HSPLjava/util/WeakHashMap$Entry;->getKey()Ljava/lang/Object;
 HSPLjava/util/WeakHashMap$Entry;->getValue()Ljava/lang/Object;
 HSPLjava/util/WeakHashMap$EntryIterator;-><init>(Ljava/util/WeakHashMap;)V
 HSPLjava/util/WeakHashMap$EntryIterator;->next()Ljava/lang/Object;
@@ -6485,7 +6595,7 @@
 HSPLjava/util/WeakHashMap$HashIterator;->nextEntry()Ljava/util/WeakHashMap$Entry;
 HSPLjava/util/WeakHashMap$KeyIterator;-><init>(Ljava/util/WeakHashMap;)V
 HSPLjava/util/WeakHashMap$KeyIterator;-><init>(Ljava/util/WeakHashMap;Ljava/util/WeakHashMap$KeyIterator-IA;)V
-HSPLjava/util/WeakHashMap$KeyIterator;->next()Ljava/lang/Object;+]Ljava/util/WeakHashMap$KeyIterator;Ljava/util/WeakHashMap$KeyIterator;]Ljava/util/WeakHashMap$Entry;Ljava/util/WeakHashMap$Entry;
+HSPLjava/util/WeakHashMap$KeyIterator;->next()Ljava/lang/Object;
 HSPLjava/util/WeakHashMap$KeySet;-><init>(Ljava/util/WeakHashMap;)V
 HSPLjava/util/WeakHashMap$KeySet;-><init>(Ljava/util/WeakHashMap;Ljava/util/WeakHashMap$KeySet-IA;)V
 HSPLjava/util/WeakHashMap$KeySet;->iterator()Ljava/util/Iterator;
@@ -6500,9 +6610,8 @@
 HSPLjava/util/WeakHashMap;->clear()V
 HSPLjava/util/WeakHashMap;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/WeakHashMap;->entrySet()Ljava/util/Set;
-HSPLjava/util/WeakHashMap;->eq(Ljava/lang/Object;Ljava/lang/Object;)Z
 HSPLjava/util/WeakHashMap;->expungeStaleEntries()V+]Ljava/lang/ref/ReferenceQueue;Ljava/lang/ref/ReferenceQueue;
-HSPLjava/util/WeakHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/WeakHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/WeakHashMap;Ljava/util/WeakHashMap;]Ljava/util/WeakHashMap$Entry;Ljava/util/WeakHashMap$Entry;
 HSPLjava/util/WeakHashMap;->getEntry(Ljava/lang/Object;)Ljava/util/WeakHashMap$Entry;
 HSPLjava/util/WeakHashMap;->getTable()[Ljava/util/WeakHashMap$Entry;
 HSPLjava/util/WeakHashMap;->hash(Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types
@@ -6529,7 +6638,7 @@
 HSPLjava/util/concurrent/ArrayBlockingQueue;-><init>(IZ)V
 HSPLjava/util/concurrent/ArrayBlockingQueue;->add(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/ArrayBlockingQueue;->dequeue()Ljava/lang/Object;
-HSPLjava/util/concurrent/ArrayBlockingQueue;->drainTo(Ljava/util/Collection;)I+]Ljava/util/concurrent/ArrayBlockingQueue;Ljava/util/concurrent/ArrayBlockingQueue;
+HSPLjava/util/concurrent/ArrayBlockingQueue;->drainTo(Ljava/util/Collection;)I
 HSPLjava/util/concurrent/ArrayBlockingQueue;->drainTo(Ljava/util/Collection;I)I
 HSPLjava/util/concurrent/ArrayBlockingQueue;->enqueue(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/ArrayBlockingQueue;->itemAt(I)Ljava/lang/Object;
@@ -6545,7 +6654,7 @@
 HSPLjava/util/concurrent/CompletableFuture$AsyncRun;-><init>(Ljava/util/concurrent/CompletableFuture;Ljava/lang/Runnable;)V
 HSPLjava/util/concurrent/CompletableFuture$AsyncRun;->run()V
 HSPLjava/util/concurrent/CompletableFuture$AsyncSupply;-><init>(Ljava/util/concurrent/CompletableFuture;Ljava/util/function/Supplier;)V
-HSPLjava/util/concurrent/CompletableFuture$AsyncSupply;->run()V+]Ljava/util/concurrent/CompletableFuture;Ljava/util/concurrent/CompletableFuture;]Ljava/util/function/Supplier;Landroid/telephony/ims/feature/MmTelFeature$1$$ExternalSyntheticLambda0;,Landroid/telephony/ims/stub/ImsRegistrationImplBase$1$$ExternalSyntheticLambda3;,Landroid/telephony/ims/stub/ImsConfigImplBase$ImsConfigStub$$ExternalSyntheticLambda5;,Landroid/telephony/ims/stub/ImsCallSessionImplBase$1$$ExternalSyntheticLambda0;
+HSPLjava/util/concurrent/CompletableFuture$AsyncSupply;->run()V
 HSPLjava/util/concurrent/CompletableFuture$Completion;-><init>()V
 HSPLjava/util/concurrent/CompletableFuture$Signaller;-><init>(ZJJ)V
 HSPLjava/util/concurrent/CompletableFuture$Signaller;->block()Z
@@ -6553,7 +6662,7 @@
 HSPLjava/util/concurrent/CompletableFuture$Signaller;->tryFire(I)Ljava/util/concurrent/CompletableFuture;
 HSPLjava/util/concurrent/CompletableFuture;-><init>()V
 HSPLjava/util/concurrent/CompletableFuture;->asyncRunStage(Ljava/util/concurrent/Executor;Ljava/lang/Runnable;)Ljava/util/concurrent/CompletableFuture;
-HSPLjava/util/concurrent/CompletableFuture;->asyncSupplyStage(Ljava/util/concurrent/Executor;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture;+]Ljava/util/concurrent/Executor;Ljava/util/concurrent/ForkJoinPool;,Ljava/util/concurrent/Executors$FinalizableDelegatedExecutorService;,Landroid/app/PendingIntent$$ExternalSyntheticLambda1;
+HSPLjava/util/concurrent/CompletableFuture;->asyncSupplyStage(Ljava/util/concurrent/Executor;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture;
 HSPLjava/util/concurrent/CompletableFuture;->complete(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CompletableFuture;->completeNull()Z
 HSPLjava/util/concurrent/CompletableFuture;->completeValue(Ljava/lang/Object;)Z
@@ -6576,7 +6685,7 @@
 HSPLjava/util/concurrent/ConcurrentHashMap$BaseIterator;->remove()V
 HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;-><init>(Ljava/util/concurrent/ConcurrentHashMap;)V
 HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;->size()I
-HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;->toArray()[Ljava/lang/Object;
+HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;->toArray()[Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentHashMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/util/Iterator;Ljava/util/concurrent/ConcurrentHashMap$ValueIterator;,Ljava/util/concurrent/ConcurrentHashMap$KeyIterator;]Ljava/util/concurrent/ConcurrentHashMap$CollectionView;Ljava/util/concurrent/ConcurrentHashMap$KeySetView;,Ljava/util/concurrent/ConcurrentHashMap$ValuesView;
 HSPLjava/util/concurrent/ConcurrentHashMap$CounterCell;-><init>(J)V
 HSPLjava/util/concurrent/ConcurrentHashMap$EntryIterator;-><init>([Ljava/util/concurrent/ConcurrentHashMap$Node;IIILjava/util/concurrent/ConcurrentHashMap;)V
 HSPLjava/util/concurrent/ConcurrentHashMap$EntryIterator;->next()Ljava/lang/Object;
@@ -6586,7 +6695,7 @@
 HSPLjava/util/concurrent/ConcurrentHashMap$ForwardingNode;-><init>([Ljava/util/concurrent/ConcurrentHashMap$Node;)V
 HSPLjava/util/concurrent/ConcurrentHashMap$ForwardingNode;->find(ILjava/lang/Object;)Ljava/util/concurrent/ConcurrentHashMap$Node;
 HSPLjava/util/concurrent/ConcurrentHashMap$KeyIterator;-><init>([Ljava/util/concurrent/ConcurrentHashMap$Node;IIILjava/util/concurrent/ConcurrentHashMap;)V
-HSPLjava/util/concurrent/ConcurrentHashMap$KeyIterator;->next()Ljava/lang/Object;
+HSPLjava/util/concurrent/ConcurrentHashMap$KeyIterator;->next()Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentHashMap$KeyIterator;Ljava/util/concurrent/ConcurrentHashMap$KeyIterator;
 HSPLjava/util/concurrent/ConcurrentHashMap$KeySetView;-><init>(Ljava/util/concurrent/ConcurrentHashMap;Ljava/lang/Object;)V
 HSPLjava/util/concurrent/ConcurrentHashMap$KeySetView;->iterator()Ljava/util/Iterator;
 HSPLjava/util/concurrent/ConcurrentHashMap$KeySetView;->spliterator()Ljava/util/Spliterator;
@@ -6608,14 +6717,15 @@
 HSPLjava/util/concurrent/ConcurrentHashMap;-><init>(I)V
 HSPLjava/util/concurrent/ConcurrentHashMap;-><init>(IFI)V
 HSPLjava/util/concurrent/ConcurrentHashMap;-><init>(Ljava/util/Map;)V
-HSPLjava/util/concurrent/ConcurrentHashMap;->addCount(JI)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;]Ljava/util/concurrent/ConcurrentHashMap;Ljava/util/concurrent/ConcurrentHashMap;
+HSPLjava/util/concurrent/ConcurrentHashMap;->addCount(JI)V
 HSPLjava/util/concurrent/ConcurrentHashMap;->casTabAt([Ljava/util/concurrent/ConcurrentHashMap$Node;ILjava/util/concurrent/ConcurrentHashMap$Node;Ljava/util/concurrent/ConcurrentHashMap$Node;)Z
 HSPLjava/util/concurrent/ConcurrentHashMap;->clear()V
 HSPLjava/util/concurrent/ConcurrentHashMap;->computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;
 HSPLjava/util/concurrent/ConcurrentHashMap;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/ConcurrentHashMap;->entrySet()Ljava/util/Set;
+HSPLjava/util/concurrent/ConcurrentHashMap;->forEach(Ljava/util/function/BiConsumer;)V
 HSPLjava/util/concurrent/ConcurrentHashMap;->fullAddCount(JZ)V
-HSPLjava/util/concurrent/ConcurrentHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;missing_types]Ljava/util/concurrent/ConcurrentHashMap$Node;Ljava/util/concurrent/ConcurrentHashMap$ForwardingNode;
+HSPLjava/util/concurrent/ConcurrentHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;megamorphic_types
 HSPLjava/util/concurrent/ConcurrentHashMap;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/concurrent/ConcurrentHashMap;->helpTransfer([Ljava/util/concurrent/ConcurrentHashMap$Node;Ljava/util/concurrent/ConcurrentHashMap$Node;)[Ljava/util/concurrent/ConcurrentHashMap$Node;
 HSPLjava/util/concurrent/ConcurrentHashMap;->initTable()[Ljava/util/concurrent/ConcurrentHashMap$Node;
@@ -6625,16 +6735,17 @@
 HSPLjava/util/concurrent/ConcurrentHashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/concurrent/ConcurrentHashMap;->putAll(Ljava/util/Map;)V
 HSPLjava/util/concurrent/ConcurrentHashMap;->putIfAbsent(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/concurrent/ConcurrentHashMap;->putVal(Ljava/lang/Object;Ljava/lang/Object;Z)Ljava/lang/Object;+]Ljava/lang/Object;missing_types
+HSPLjava/util/concurrent/ConcurrentHashMap;->putVal(Ljava/lang/Object;Ljava/lang/Object;Z)Ljava/lang/Object;+]Ljava/lang/Object;Lsun/util/locale/BaseLocale$Key;
 HSPLjava/util/concurrent/ConcurrentHashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/concurrent/ConcurrentHashMap;->remove(Ljava/lang/Object;Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/ConcurrentHashMap;->replace(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
-HSPLjava/util/concurrent/ConcurrentHashMap;->replaceNode(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/concurrent/ConcurrentHashMap;->replaceNode(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;Lsun/nio/ch/FileKey;,Ljava/lang/reflect/Proxy$Key1;,Lsun/util/locale/BaseLocale;
 HSPLjava/util/concurrent/ConcurrentHashMap;->resizeStamp(I)I
 HSPLjava/util/concurrent/ConcurrentHashMap;->setTabAt([Ljava/util/concurrent/ConcurrentHashMap$Node;ILjava/util/concurrent/ConcurrentHashMap$Node;)V
 HSPLjava/util/concurrent/ConcurrentHashMap;->size()I
 HSPLjava/util/concurrent/ConcurrentHashMap;->spread(I)I
 HSPLjava/util/concurrent/ConcurrentHashMap;->sumCount()J
-HSPLjava/util/concurrent/ConcurrentHashMap;->tabAt([Ljava/util/concurrent/ConcurrentHashMap$Node;I)Ljava/util/concurrent/ConcurrentHashMap$Node;
+HSPLjava/util/concurrent/ConcurrentHashMap;->tabAt([Ljava/util/concurrent/ConcurrentHashMap$Node;I)Ljava/util/concurrent/ConcurrentHashMap$Node;+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/ConcurrentHashMap;->tableSizeFor(I)I
 HSPLjava/util/concurrent/ConcurrentHashMap;->transfer([Ljava/util/concurrent/ConcurrentHashMap$Node;[Ljava/util/concurrent/ConcurrentHashMap$Node;)V
 HSPLjava/util/concurrent/ConcurrentHashMap;->treeifyBin([Ljava/util/concurrent/ConcurrentHashMap$Node;I)V
@@ -6670,10 +6781,10 @@
 HSPLjava/util/concurrent/ConcurrentLinkedQueue$Node;->casItem(Ljava/lang/Object;Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;-><init>()V
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->add(Ljava/lang/Object;)Z
-HSPLjava/util/concurrent/ConcurrentLinkedQueue;->bulkRemove(Ljava/util/function/Predicate;)Z+]Ljava/util/concurrent/ConcurrentLinkedQueue$Node;Ljava/util/concurrent/ConcurrentLinkedQueue$Node;]Ljava/util/function/Predicate;Ljava/util/concurrent/ConcurrentLinkedQueue$$ExternalSyntheticLambda0;,Ljava/util/concurrent/ConcurrentLinkedQueue$$ExternalSyntheticLambda2;
+HSPLjava/util/concurrent/ConcurrentLinkedQueue;->bulkRemove(Ljava/util/function/Predicate;)Z
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->clear()V
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->contains(Ljava/lang/Object;)Z
-HSPLjava/util/concurrent/ConcurrentLinkedQueue;->first()Ljava/util/concurrent/ConcurrentLinkedQueue$Node;+]Ljava/util/concurrent/ConcurrentLinkedQueue;Ljava/util/concurrent/ConcurrentLinkedQueue;
+HSPLjava/util/concurrent/ConcurrentLinkedQueue;->first()Ljava/util/concurrent/ConcurrentLinkedQueue$Node;
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->isEmpty()Z
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->iterator()Ljava/util/Iterator;
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->lambda$clear$2(Ljava/lang/Object;)Z
@@ -6711,8 +6822,8 @@
 HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;-><init>([Ljava/lang/Object;I)V
 HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;->hasNext()Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;->next()Ljava/lang/Object;+]Ljava/util/concurrent/CopyOnWriteArrayList$COWIterator;Ljava/util/concurrent/CopyOnWriteArrayList$COWIterator;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>()V
-HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>(Ljava/util/Collection;)V
+HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>()V+]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
+HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;Ljava/util/concurrent/CopyOnWriteArrayList;]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>([Ljava/lang/Object;)V
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->add(ILjava/lang/Object;)V
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->add(Ljava/lang/Object;)Z
@@ -6720,29 +6831,29 @@
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->addAllAbsent(Ljava/util/Collection;)I
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->addIfAbsent(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->addIfAbsent(Ljava/lang/Object;[Ljava/lang/Object;)Z
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->bulkRemove(Ljava/util/function/Predicate;)Z+]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->bulkRemove(Ljava/util/function/Predicate;II)Z+]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;]Ljava/util/function/Predicate;Ljava/util/concurrent/CopyOnWriteArrayList$$ExternalSyntheticLambda2;
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->bulkRemove(Ljava/util/function/Predicate;)Z
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->bulkRemove(Ljava/util/function/Predicate;II)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->clear()V
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->elementAt([Ljava/lang/Object;I)Ljava/lang/Object;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->get(I)Ljava/lang/Object;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->getArray()[Ljava/lang/Object;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOfRange(Ljava/lang/Object;[Ljava/lang/Object;II)I+]Ljava/lang/Object;missing_types
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->isEmpty()Z
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->iterator()Ljava/util/Iterator;+]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOf(Ljava/lang/Object;)I
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOfRange(Ljava/lang/Object;[Ljava/lang/Object;II)I
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->isEmpty()Z+]Ljava/util/concurrent/CopyOnWriteArrayList;missing_types
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->iterator()Ljava/util/Iterator;+]Ljava/util/concurrent/CopyOnWriteArrayList;missing_types
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->lambda$removeAll$0(Ljava/util/Collection;Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->remove(I)Ljava/lang/Object;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->remove(Ljava/lang/Object;[Ljava/lang/Object;I)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->removeAll(Ljava/util/Collection;)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->setArray([Ljava/lang/Object;)V
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->size()I
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->size()I+]Ljava/util/concurrent/CopyOnWriteArrayList;missing_types
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray()[Ljava/lang/Object;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/lang/Object;[Ljava/util/logging/Handler;]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->toString()Ljava/lang/String;
 HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>()V
-HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;missing_types]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
+HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;Ljava/util/concurrent/CopyOnWriteArraySet;
 HSPLjava/util/concurrent/CopyOnWriteArraySet;->add(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CopyOnWriteArraySet;->addAll(Ljava/util/Collection;)Z
 HSPLjava/util/concurrent/CopyOnWriteArraySet;->clear()V
@@ -6776,7 +6887,7 @@
 HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->submit(Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;
 HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;-><init>(Ljava/util/concurrent/ScheduledExecutorService;)V
 HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
-HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;+]Ljava/util/concurrent/ScheduledExecutorService;Ljava/util/concurrent/ScheduledThreadPoolExecutor;
+HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->scheduleAtFixedRate(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->scheduleWithFixedDelay(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/Executors$FinalizableDelegatedExecutorService;-><init>(Ljava/util/concurrent/ExecutorService;)V
@@ -6799,6 +6910,7 @@
 HSPLjava/util/concurrent/Executors;->unconfigurableExecutorService(Ljava/util/concurrent/ExecutorService;)Ljava/util/concurrent/ExecutorService;
 HSPLjava/util/concurrent/Executors;->unconfigurableScheduledExecutorService(Ljava/util/concurrent/ScheduledExecutorService;)Ljava/util/concurrent/ScheduledExecutorService;
 HSPLjava/util/concurrent/ForkJoinPool;->managedBlock(Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;)V
+HSPLjava/util/concurrent/ForkJoinPool;->unmanagedBlock(Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;)V+]Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;
 HSPLjava/util/concurrent/ForkJoinTask;-><init>()V
 HSPLjava/util/concurrent/FutureTask$WaitNode;-><init>()V
 HSPLjava/util/concurrent/FutureTask;-><init>(Ljava/lang/Runnable;Ljava/lang/Object;)V
@@ -6806,7 +6918,7 @@
 HSPLjava/util/concurrent/FutureTask;->awaitDone(ZJ)I
 HSPLjava/util/concurrent/FutureTask;->cancel(Z)Z
 HSPLjava/util/concurrent/FutureTask;->done()V
-HSPLjava/util/concurrent/FutureTask;->finishCompletion()V
+HSPLjava/util/concurrent/FutureTask;->finishCompletion()V+]Ljava/util/concurrent/FutureTask;missing_types
 HSPLjava/util/concurrent/FutureTask;->get()Ljava/lang/Object;
 HSPLjava/util/concurrent/FutureTask;->get(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;
 HSPLjava/util/concurrent/FutureTask;->handlePossibleCancellationInterrupt(I)V
@@ -6814,7 +6926,7 @@
 HSPLjava/util/concurrent/FutureTask;->isDone()Z
 HSPLjava/util/concurrent/FutureTask;->removeWaiter(Ljava/util/concurrent/FutureTask$WaitNode;)V
 HSPLjava/util/concurrent/FutureTask;->report(I)Ljava/lang/Object;
-HSPLjava/util/concurrent/FutureTask;->run()V
+HSPLjava/util/concurrent/FutureTask;->run()V+]Ljava/util/concurrent/Callable;missing_types]Ljava/util/concurrent/FutureTask;missing_types
 HSPLjava/util/concurrent/FutureTask;->runAndReset()Z
 HSPLjava/util/concurrent/FutureTask;->set(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/FutureTask;->setException(Ljava/lang/Throwable;)V
@@ -6850,12 +6962,12 @@
 HSPLjava/util/concurrent/LinkedBlockingQueue;->fullyLock()V
 HSPLjava/util/concurrent/LinkedBlockingQueue;->fullyUnlock()V
 HSPLjava/util/concurrent/LinkedBlockingQueue;->offer(Ljava/lang/Object;)Z
-HSPLjava/util/concurrent/LinkedBlockingQueue;->poll()Ljava/lang/Object;+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/LinkedBlockingQueue;->poll()Ljava/lang/Object;
 HSPLjava/util/concurrent/LinkedBlockingQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;
-HSPLjava/util/concurrent/LinkedBlockingQueue;->put(Ljava/lang/Object;)V+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
-HSPLjava/util/concurrent/LinkedBlockingQueue;->signalNotEmpty()V
+HSPLjava/util/concurrent/LinkedBlockingQueue;->put(Ljava/lang/Object;)V
+HSPLjava/util/concurrent/LinkedBlockingQueue;->signalNotEmpty()V+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
 HSPLjava/util/concurrent/LinkedBlockingQueue;->signalNotFull()V
-HSPLjava/util/concurrent/LinkedBlockingQueue;->size()I
+HSPLjava/util/concurrent/LinkedBlockingQueue;->size()I+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
 HSPLjava/util/concurrent/LinkedBlockingQueue;->take()Ljava/lang/Object;
 HSPLjava/util/concurrent/PriorityBlockingQueue;-><init>()V
 HSPLjava/util/concurrent/PriorityBlockingQueue;-><init>(ILjava/util/Comparator;)V
@@ -6889,14 +7001,14 @@
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->grow()V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->indexOf(Ljava/lang/Object;)I
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->isEmpty()Z
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->iterator()Ljava/util/Iterator;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->offer(Ljava/lang/Runnable;)Z
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->iterator()Ljava/util/Iterator;+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->offer(Ljava/lang/Runnable;)Z+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/RunnableScheduledFuture;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->remove(Ljava/lang/Object;)Z
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->remove(Ljava/lang/Object;)Z+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->setIndex(Ljava/util/concurrent/RunnableScheduledFuture;I)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftDown(ILjava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftUp(ILjava/util/concurrent/RunnableScheduledFuture;)V
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftUp(ILjava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->size()I
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/lang/Object;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/util/concurrent/RunnableScheduledFuture;
@@ -6904,7 +7016,7 @@
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/lang/Runnable;Ljava/lang/Object;JJ)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/lang/Runnable;Ljava/lang/Object;JJJ)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/Callable;JJ)V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->cancel(Z)Z
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->cancel(Z)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->compareTo(Ljava/lang/Object;)I+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->compareTo(Ljava/util/concurrent/Delayed;)I
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->getDelay(Ljava/util/concurrent/TimeUnit;)J
@@ -6914,16 +7026,16 @@
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(I)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(ILjava/util/concurrent/ThreadFactory;)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(ILjava/util/concurrent/ThreadFactory;Ljava/util/concurrent/RejectedExecutionHandler;)V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->canRunInCurrentRunState(Ljava/util/concurrent/RunnableScheduledFuture;)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->canRunInCurrentRunState(Ljava/util/concurrent/RunnableScheduledFuture;)Z
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->decorateTask(Ljava/lang/Runnable;Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->decorateTask(Ljava/util/concurrent/Callable;Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->delayedExecute(Ljava/util/concurrent/RunnableScheduledFuture;)V
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->delayedExecute(Ljava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->getContinueExistingPeriodicTasksAfterShutdownPolicy()Z
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->getExecuteExistingDelayedTasksAfterShutdownPolicy()Z
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->onShutdown()V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->reExecutePeriodic(Ljava/util/concurrent/RunnableScheduledFuture;)V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types]Ljava/util/concurrent/atomic/AtomicLong;Ljava/util/concurrent/atomic/AtomicLong;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->scheduleAtFixedRate(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->scheduleWithFixedDelay(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
@@ -6933,7 +7045,7 @@
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->submit(Ljava/lang/Runnable;)Ljava/util/concurrent/Future;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->submit(Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(J)J
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(JLjava/util/concurrent/TimeUnit;)J
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(JLjava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
 HSPLjava/util/concurrent/Semaphore$FairSync;-><init>(I)V
 HSPLjava/util/concurrent/Semaphore$FairSync;->tryAcquireShared(I)I
 HSPLjava/util/concurrent/Semaphore$NonfairSync;-><init>(I)V
@@ -6953,26 +7065,28 @@
 HSPLjava/util/concurrent/Semaphore;->tryAcquire(IJLjava/util/concurrent/TimeUnit;)Z
 HSPLjava/util/concurrent/Semaphore;->tryAcquire(JLjava/util/concurrent/TimeUnit;)Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;-><init>(Ljava/lang/Object;)V
+HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->block()Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->casNext(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z
+HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->forgetWaiter()V
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->isCancelled()Z
-HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->tryCancel()V
+HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->isReleasable()Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->tryMatch(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;-><init>()V
-HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->awaitFulfill(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;ZJ)Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;+]Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;]Ljava/lang/Thread;Ljava/lang/Thread;]Ljava/util/concurrent/SynchronousQueue$TransferStack;Ljava/util/concurrent/SynchronousQueue$TransferStack;
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->casHead(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->clean(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)V
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->isFulfilling(I)Z
-HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->shouldSpin(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->snode(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/lang/Object;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;I)Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;
-HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->transfer(Ljava/lang/Object;ZJ)Ljava/lang/Object;+]Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;]Ljava/util/concurrent/SynchronousQueue$TransferStack;Ljava/util/concurrent/SynchronousQueue$TransferStack;
+HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->transfer(Ljava/lang/Object;ZJ)Ljava/lang/Object;
 HSPLjava/util/concurrent/SynchronousQueue$Transferer;-><init>()V
 HSPLjava/util/concurrent/SynchronousQueue;-><init>()V
 HSPLjava/util/concurrent/SynchronousQueue;-><init>(Z)V
 HSPLjava/util/concurrent/SynchronousQueue;->isEmpty()Z
-HSPLjava/util/concurrent/SynchronousQueue;->offer(Ljava/lang/Object;)Z
+HSPLjava/util/concurrent/SynchronousQueue;->offer(Ljava/lang/Object;)Z+]Ljava/util/concurrent/SynchronousQueue$Transferer;Ljava/util/concurrent/SynchronousQueue$TransferStack;
 HSPLjava/util/concurrent/SynchronousQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;+]Ljava/util/concurrent/SynchronousQueue$Transferer;Ljava/util/concurrent/SynchronousQueue$TransferStack;]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
 HSPLjava/util/concurrent/SynchronousQueue;->size()I
 HSPLjava/util/concurrent/SynchronousQueue;->take()Ljava/lang/Object;
+HSPLjava/util/concurrent/ThreadLocalRandom;-><clinit>()V
+HSPLjava/util/concurrent/ThreadLocalRandom;-><init>()V
 HSPLjava/util/concurrent/ThreadLocalRandom;->current()Ljava/util/concurrent/ThreadLocalRandom;
 HSPLjava/util/concurrent/ThreadLocalRandom;->getProbe()I
 HSPLjava/util/concurrent/ThreadLocalRandom;->localInit()V
@@ -6981,17 +7095,18 @@
 HSPLjava/util/concurrent/ThreadLocalRandom;->nextInt()I
 HSPLjava/util/concurrent/ThreadLocalRandom;->nextSecondarySeed()I
 HSPLjava/util/concurrent/ThreadLocalRandom;->nextSeed()J
+HSPLjava/util/concurrent/ThreadLocalRandom;->setSeed(J)V
 HSPLjava/util/concurrent/ThreadPoolExecutor$DiscardPolicy;-><init>()V
 HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;-><init>(Ljava/util/concurrent/ThreadPoolExecutor;Ljava/lang/Runnable;)V
 HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->interruptIfStarted()V
 HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->isHeldExclusively()Z
 HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->isLocked()Z
-HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->lock()V
+HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->lock()V+]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
 HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->run()V
-HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->tryAcquire(I)Z
+HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->tryAcquire(I)Z+]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
 HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->tryLock()Z
-HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->tryRelease(I)Z
-HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->unlock()V
+HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->tryRelease(I)Z+]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
+HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->unlock()V+]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
 HSPLjava/util/concurrent/ThreadPoolExecutor;-><init>(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;)V
 HSPLjava/util/concurrent/ThreadPoolExecutor;-><init>(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/RejectedExecutionHandler;)V
 HSPLjava/util/concurrent/ThreadPoolExecutor;-><init>(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;)V
@@ -7008,7 +7123,7 @@
 HSPLjava/util/concurrent/ThreadPoolExecutor;->ctlOf(II)I
 HSPLjava/util/concurrent/ThreadPoolExecutor;->decrementWorkerCount()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->drainQueue()Ljava/util/List;
-HSPLjava/util/concurrent/ThreadPoolExecutor;->ensurePrestart()V
+HSPLjava/util/concurrent/ThreadPoolExecutor;->ensurePrestart()V+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->finalize()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->getCorePoolSize()I
@@ -7026,13 +7141,13 @@
 HSPLjava/util/concurrent/ThreadPoolExecutor;->onShutdown()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->prestartAllCoreThreads()I
 HSPLjava/util/concurrent/ThreadPoolExecutor;->prestartCoreThread()Z
-HSPLjava/util/concurrent/ThreadPoolExecutor;->processWorkerExit(Ljava/util/concurrent/ThreadPoolExecutor$Worker;Z)V
-HSPLjava/util/concurrent/ThreadPoolExecutor;->purge()V
-HSPLjava/util/concurrent/ThreadPoolExecutor;->remove(Ljava/lang/Runnable;)Z
+HSPLjava/util/concurrent/ThreadPoolExecutor;->processWorkerExit(Ljava/util/concurrent/ThreadPoolExecutor$Worker;Z)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;,Ljava/util/concurrent/SynchronousQueue;,Ljava/util/concurrent/LinkedBlockingQueue;]Ljava/util/concurrent/ThreadPoolExecutor;missing_types]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/HashSet;Ljava/util/HashSet;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/ThreadPoolExecutor;->purge()V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/Iterator;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr;
+HSPLjava/util/concurrent/ThreadPoolExecutor;->remove(Ljava/lang/Runnable;)Z+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ThreadPoolExecutor;missing_types
 HSPLjava/util/concurrent/ThreadPoolExecutor;->runStateAtLeast(II)Z
 HSPLjava/util/concurrent/ThreadPoolExecutor;->runStateLessThan(II)Z
 HSPLjava/util/concurrent/ThreadPoolExecutor;->runStateOf(I)I
-HSPLjava/util/concurrent/ThreadPoolExecutor;->runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V
+HSPLjava/util/concurrent/ThreadPoolExecutor;->runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;]Ljava/lang/Runnable;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->setCorePoolSize(I)V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->setKeepAliveTime(JLjava/util/concurrent/TimeUnit;)V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->setMaximumPoolSize(I)V
@@ -7042,9 +7157,9 @@
 HSPLjava/util/concurrent/ThreadPoolExecutor;->shutdownNow()Ljava/util/List;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->terminated()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->toString()Ljava/lang/String;
-HSPLjava/util/concurrent/ThreadPoolExecutor;->tryTerminate()V
+HSPLjava/util/concurrent/ThreadPoolExecutor;->tryTerminate()V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/LinkedBlockingQueue;,Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ThreadPoolExecutor;,Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->workerCountOf(I)I
-HSPLjava/util/concurrent/TimeUnit;->convert(JLjava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
+HSPLjava/util/concurrent/TimeUnit;->convert(JLjava/util/concurrent/TimeUnit;)J
 HSPLjava/util/concurrent/TimeUnit;->cvt(JJJ)J
 HSPLjava/util/concurrent/TimeUnit;->sleep(J)V
 HSPLjava/util/concurrent/TimeUnit;->toDays(J)J
@@ -7076,14 +7191,15 @@
 HSPLjava/util/concurrent/atomic/AtomicInteger;->getAndIncrement()I
 HSPLjava/util/concurrent/atomic/AtomicInteger;->getAndSet(I)I
 HSPLjava/util/concurrent/atomic/AtomicInteger;->incrementAndGet()I
-HSPLjava/util/concurrent/atomic/AtomicInteger;->intValue()I+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/atomic/AtomicInteger;->intValue()I
 HSPLjava/util/concurrent/atomic/AtomicInteger;->lazySet(I)V
 HSPLjava/util/concurrent/atomic/AtomicInteger;->set(I)V
+HSPLjava/util/concurrent/atomic/AtomicInteger;->weakCompareAndSetVolatile(II)Z
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;-><init>(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)V
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->accessCheck(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;II)Z
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->decrementAndGet(Ljava/lang/Object;)I
-HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->getAndAdd(Ljava/lang/Object;I)I
+HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->getAndAdd(Ljava/lang/Object;I)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->incrementAndGet(Ljava/lang/Object;)I
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->set(Ljava/lang/Object;I)V
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater;-><init>()V
@@ -7102,10 +7218,10 @@
 HSPLjava/util/concurrent/atomic/AtomicLong;->set(J)V
 HSPLjava/util/concurrent/atomic/AtomicLong;->toString()Ljava/lang/String;
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;-><init>(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)V
-HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->accessCheck(Ljava/lang/Object;)V
+HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->accessCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->addAndGet(Ljava/lang/Object;J)J
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->compareAndSet(Ljava/lang/Object;JJ)Z
-HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->getAndAdd(Ljava/lang/Object;J)J
+HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->getAndAdd(Ljava/lang/Object;J)J+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->incrementAndGet(Ljava/lang/Object;)J
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater;-><init>()V
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater;->newUpdater(Ljava/lang/Class;Ljava/lang/String;)Ljava/util/concurrent/atomic/AtomicLongFieldUpdater;
@@ -7122,16 +7238,18 @@
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;-><init>(I)V
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->compareAndSet(ILjava/lang/Object;Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->get(I)Ljava/lang/Object;
+HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->getAcquire(I)Ljava/lang/Object;
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->getAndSet(ILjava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->lazySet(ILjava/lang/Object;)V
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->length()I
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->set(ILjava/lang/Object;)V
+HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->setRelease(ILjava/lang/Object;)V
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;-><init>(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)V
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->accessCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
-HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->get(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->getAndSet(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->lazySet(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->lazySet(Ljava/lang/Object;Ljava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->valueCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater;-><init>()V
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater;->newUpdater(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;
@@ -7144,102 +7262,96 @@
 HSPLjava/util/concurrent/atomic/Striped64;->casBase(JJ)Z
 HSPLjava/util/concurrent/atomic/Striped64;->casCellsBusy()Z
 HSPLjava/util/concurrent/atomic/Striped64;->getProbe()I
-HSPLjava/util/concurrent/atomic/Striped64;->longAccumulate(JLjava/util/function/LongBinaryOperator;Z)V
 HSPLjava/util/concurrent/locks/AbstractOwnableSynchronizer;-><init>()V
 HSPLjava/util/concurrent/locks/AbstractOwnableSynchronizer;->getExclusiveOwnerThread()Ljava/lang/Thread;
 HSPLjava/util/concurrent/locks/AbstractOwnableSynchronizer;->setExclusiveOwnerThread(Ljava/lang/Thread;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;-><init>()V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;->block()Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;->isReleasable()Z
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;-><init>(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->addConditionWaiter()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->await()V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->awaitNanos(J)J
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->checkInterruptWhileWaiting(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)I
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->doSignal(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->doSignalAll(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->hasWaiters()Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->canReacquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->doSignal(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;Z)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->enableWait(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;)I
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->hasWaiters()Z
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->isOwnedBy(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->reportInterruptAfterWait(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->signal()V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->signal()V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->signalAll()V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->unlinkCancelledWaiters()V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->unlinkCancelledWaiters(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode;-><init>()V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;-><init>()V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;-><init>(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;-><init>(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->compareAndSetNext(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->compareAndSetWaitStatus(II)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->isShared()Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->predecessor()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->setPrevRelaxed(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->clearStatus()V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->getAndUnsetStatus(I)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->setPrevRelaxed(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->setStatusRelaxed(I)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode;-><init>()V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->-$$Nest$sfgetU()Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;-><init>()V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquire(I)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;IZZZJ)I
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireInterruptibly(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireQueued(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;I)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireShared(I)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireShared(I)V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireSharedInterruptibly(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->addWaiter(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->apparentlyFirstQueuedIsExclusive()Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->cancelAcquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->casTail(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->cleanQueue()V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->compareAndSetState(II)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->compareAndSetTail(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireInterruptibly(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireShared(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireSharedInterruptibly(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireSharedNanos(IJ)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doReleaseShared()V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->enq(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->findNodeFromTail(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->fullyRelease(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)I
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->enqueue(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->getFirstQueuedThread()Ljava/lang/Thread;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->getState()I
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasQueuedPredecessors()Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasWaiters(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->initializeSyncQueue()V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->isOnSyncQueue(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->owns(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->parkAndCheckInterrupt()Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->release(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$FairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->releaseShared(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->selfInterrupt()V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->setHead(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->setHeadAndPropagate(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;I)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasQueuedThreads()Z
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasWaiters(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;)Z
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->isEnqueued(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->owns(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;)Z
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->release(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->releaseShared(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/CountDownLatch$Sync;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->setState(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->shouldParkAfterFailedAcquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->transferAfterCancelledWait(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->transferForSignal(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->tryAcquireNanos(IJ)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->signalNext(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode;,Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;,Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->signalNextIfShared(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->tryAcquireNanos(IJ)Z
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->tryAcquireSharedNanos(IJ)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->unparkSuccessor(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->tryInitializeHead()V
+HSPLjava/util/concurrent/locks/LockSupport;->park()V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/locks/LockSupport;->park(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/locks/LockSupport;->parkNanos(J)V
 HSPLjava/util/concurrent/locks/LockSupport;->parkNanos(Ljava/lang/Object;J)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
-HSPLjava/util/concurrent/locks/LockSupport;->setBlocker(Ljava/lang/Thread;Ljava/lang/Object;)V
-HSPLjava/util/concurrent/locks/LockSupport;->unpark(Ljava/lang/Thread;)V
+HSPLjava/util/concurrent/locks/LockSupport;->setBlocker(Ljava/lang/Thread;Ljava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/LockSupport;->setCurrentBlocker(Ljava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/LockSupport;->unpark(Ljava/lang/Thread;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/locks/ReentrantLock$FairSync;-><init>()V
+HSPLjava/util/concurrent/locks/ReentrantLock$FairSync;->initialTryLock()Z+]Ljava/util/concurrent/locks/ReentrantLock$FairSync;Ljava/util/concurrent/locks/ReentrantLock$FairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock$FairSync;->tryAcquire(I)Z
 HSPLjava/util/concurrent/locks/ReentrantLock$NonfairSync;-><init>()V
+HSPLjava/util/concurrent/locks/ReentrantLock$NonfairSync;->initialTryLock()Z+]Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock$NonfairSync;->tryAcquire(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock$Sync;-><init>()V
-HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->isHeldExclusively()Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->isHeldExclusively()Z
+HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->lock()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->lockInterruptibly()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->newCondition()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
-HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->nonfairTryAcquire(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->tryLock()Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->tryRelease(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantLock;-><init>(Z)V
-HSPLjava/util/concurrent/locks/ReentrantLock;->hasWaiters(Ljava/util/concurrent/locks/Condition;)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantLock;->isHeldByCurrentThread()Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantLock;->hasWaiters(Ljava/util/concurrent/locks/Condition;)Z
+HSPLjava/util/concurrent/locks/ReentrantLock;->isHeldByCurrentThread()Z
 HSPLjava/util/concurrent/locks/ReentrantLock;->lock()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantLock;->lockInterruptibly()V
+HSPLjava/util/concurrent/locks/ReentrantLock;->lockInterruptibly()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock;->newCondition()Ljava/util/concurrent/locks/Condition;
 HSPLjava/util/concurrent/locks/ReentrantLock;->tryLock()Z
-HSPLjava/util/concurrent/locks/ReentrantLock;->tryLock(JLjava/util/concurrent/TimeUnit;)Z+]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantLock;->tryLock(JLjava/util/concurrent/TimeUnit;)Z
 HSPLjava/util/concurrent/locks/ReentrantLock;->unlock()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$FairSync;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$FairSync;->readerShouldBlock()Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$FairSync;->writerShouldBlock()Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;-><init>()V
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;->readerShouldBlock()Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;->readerShouldBlock()Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;->writerShouldBlock()Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;-><init>(Ljava/util/concurrent/locks/ReentrantReadWriteLock;)V
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->lock()V+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->unlock()V+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->lock()V
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->unlock()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync$HoldCounter;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;->initialValue()Ljava/lang/Object;
@@ -7247,17 +7359,20 @@
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->exclusiveCount(I)I
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->fullTryAcquireShared(Ljava/lang/Thread;)I
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadHoldCount()I
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadLockCount()I
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->isHeldExclusively()Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->sharedCount(I)I
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquire(I)Z
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquireShared(I)I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquire(I)Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquireShared(I)I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryRelease(I)Z
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryReleaseShared(I)Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryReleaseShared(I)Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;-><init>(Ljava/util/concurrent/locks/ReentrantReadWriteLock;)V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;->lock()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;->unlock()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;-><init>(Z)V
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->getReadHoldCount()I
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->readLock()Ljava/util/concurrent/locks/Lock;+]Ljava/util/concurrent/locks/ReentrantReadWriteLock;Ljava/util/concurrent/locks/ReentrantReadWriteLock;
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->readLock()Ljava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->writeLock()Ljava/util/concurrent/locks/Lock;
@@ -7267,7 +7382,6 @@
 HSPLjava/util/function/DoubleUnaryOperator$$ExternalSyntheticLambda1;-><init>(Ljava/util/function/DoubleUnaryOperator;Ljava/util/function/DoubleUnaryOperator;)V
 HSPLjava/util/function/DoubleUnaryOperator$$ExternalSyntheticLambda1;->applyAsDouble(D)D
 HSPLjava/util/function/DoubleUnaryOperator;->andThen(Ljava/util/function/DoubleUnaryOperator;)Ljava/util/function/DoubleUnaryOperator;
-HSPLjava/util/function/DoubleUnaryOperator;->lambda$andThen$1(Ljava/util/function/DoubleUnaryOperator;Ljava/util/function/DoubleUnaryOperator;D)D+]Ljava/util/function/DoubleUnaryOperator;Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda3;,Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda1;,Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda0;
 HSPLjava/util/function/Function$$ExternalSyntheticLambda1;-><init>()V
 HSPLjava/util/function/Function$$ExternalSyntheticLambda1;->apply(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/function/Function$$ExternalSyntheticLambda2;->apply(Ljava/lang/Object;)Ljava/lang/Object;
@@ -7275,21 +7389,19 @@
 HSPLjava/util/function/Function;->lambda$identity$2(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/jar/Attributes$Name;-><init>(Ljava/lang/String;)V
 HSPLjava/util/jar/Attributes$Name;->equals(Ljava/lang/Object;)Z
+HSPLjava/util/jar/Attributes$Name;->hash(Ljava/lang/String;)I
 HSPLjava/util/jar/Attributes$Name;->hashCode()I
-HSPLjava/util/jar/Attributes$Name;->isAlpha(C)Z
-HSPLjava/util/jar/Attributes$Name;->isDigit(C)Z
-HSPLjava/util/jar/Attributes$Name;->isValid(C)Z
-HSPLjava/util/jar/Attributes$Name;->isValid(Ljava/lang/String;)Z
 HSPLjava/util/jar/Attributes$Name;->toString()Ljava/lang/String;
 HSPLjava/util/jar/Attributes;-><init>()V
 HSPLjava/util/jar/Attributes;-><init>(I)V
 HSPLjava/util/jar/Attributes;->entrySet()Ljava/util/Set;
 HSPLjava/util/jar/Attributes;->get(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/jar/Attributes;->getValue(Ljava/util/jar/Attributes$Name;)Ljava/lang/String;
-HSPLjava/util/jar/Attributes;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;Ljava/util/HashMap;
-HSPLjava/util/jar/Attributes;->putValue(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/util/jar/Attributes;->read(Ljava/util/jar/Manifest$FastInputStream;[B)V+]Ljava/util/jar/Attributes;Ljava/util/jar/Attributes;]Ljava/util/jar/Manifest$FastInputStream;Ljava/util/jar/Manifest$FastInputStream;
-HSPLjava/util/jar/Attributes;->size()I
+HSPLjava/util/jar/Attributes;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;Ljava/util/LinkedHashMap;
+HSPLjava/util/jar/Attributes;->putValue(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;+]Ljava/util/jar/Attributes;Ljava/util/jar/Attributes;
+HSPLjava/util/jar/Attributes;->read(Ljava/util/jar/Manifest$FastInputStream;[B)V
+HSPLjava/util/jar/Attributes;->read(Ljava/util/jar/Manifest$FastInputStream;[BLjava/lang/String;I)I+]Ljava/util/jar/Attributes;Ljava/util/jar/Attributes;]Ljava/util/jar/Manifest$FastInputStream;Ljava/util/jar/Manifest$FastInputStream;
+HSPLjava/util/jar/Attributes;->size()I+]Ljava/util/Map;Ljava/util/LinkedHashMap;
 HSPLjava/util/jar/JarEntry;-><init>(Ljava/util/zip/ZipEntry;)V
 HSPLjava/util/jar/JarFile$JarFileEntry;-><init>(Ljava/util/jar/JarFile;Ljava/util/zip/ZipEntry;)V
 HSPLjava/util/jar/JarFile;-><init>(Ljava/io/File;ZI)V
@@ -7309,7 +7421,6 @@
 HSPLjava/util/jar/JarVerifier$VerifierStream;->close()V
 HSPLjava/util/jar/JarVerifier$VerifierStream;->read()I
 HSPLjava/util/jar/JarVerifier$VerifierStream;->read([BII)I
-HSPLjava/util/jar/JarVerifier;-><init>([B)V
 HSPLjava/util/jar/JarVerifier;->beginEntry(Ljava/util/jar/JarEntry;Lsun/security/util/ManifestEntryVerifier;)V
 HSPLjava/util/jar/JarVerifier;->doneWithMeta()V
 HSPLjava/util/jar/JarVerifier;->mapSignersToCertArray([Ljava/security/CodeSigner;)[Ljava/security/cert/Certificate;
@@ -7325,11 +7436,11 @@
 HSPLjava/util/jar/Manifest$FastInputStream;->readLine([BII)I
 HSPLjava/util/jar/Manifest;-><init>()V
 HSPLjava/util/jar/Manifest;-><init>(Ljava/io/InputStream;)V
-HSPLjava/util/jar/Manifest;->getAttributes(Ljava/lang/String;)Ljava/util/jar/Attributes;+]Ljava/util/jar/Manifest;Ljava/util/jar/Manifest;]Ljava/util/Map;Ljava/util/HashMap;
+HSPLjava/util/jar/Manifest;->getAttributes(Ljava/lang/String;)Ljava/util/jar/Attributes;
 HSPLjava/util/jar/Manifest;->getEntries()Ljava/util/Map;
 HSPLjava/util/jar/Manifest;->getMainAttributes()Ljava/util/jar/Attributes;
 HSPLjava/util/jar/Manifest;->parseName([BI)Ljava/lang/String;
-HSPLjava/util/jar/Manifest;->read(Ljava/io/InputStream;)V+]Ljava/util/jar/Attributes;Ljava/util/jar/Attributes;]Ljava/util/jar/Manifest;Ljava/util/jar/Manifest;]Ljava/util/Map;Ljava/util/HashMap;]Ljava/util/jar/Manifest$FastInputStream;Ljava/util/jar/Manifest$FastInputStream;
+HSPLjava/util/jar/Manifest;->read(Ljava/io/InputStream;)V
 HSPLjava/util/jar/Manifest;->toLower(I)I
 HSPLjava/util/logging/ConsoleHandler;->close()V
 HSPLjava/util/logging/ErrorManager;-><init>()V
@@ -7340,7 +7451,7 @@
 HSPLjava/util/logging/FileHandler$MeteredStream;-><init>(Ljava/util/logging/FileHandler;Ljava/io/OutputStream;I)V
 HSPLjava/util/logging/FileHandler$MeteredStream;->close()V
 HSPLjava/util/logging/FileHandler$MeteredStream;->flush()V
-HSPLjava/util/logging/FileHandler$MeteredStream;->write([BII)V
+HSPLjava/util/logging/FileHandler$MeteredStream;->write([BII)V+]Ljava/io/OutputStream;Ljava/io/BufferedOutputStream;
 HSPLjava/util/logging/FileHandler;->-$$Nest$mrotate(Ljava/util/logging/FileHandler;)V
 HSPLjava/util/logging/FileHandler;-><clinit>()V
 HSPLjava/util/logging/FileHandler;-><init>(Ljava/lang/String;IIZ)V
@@ -7349,7 +7460,7 @@
 HSPLjava/util/logging/FileHandler;->isParentWritable(Ljava/nio/file/Path;)Z
 HSPLjava/util/logging/FileHandler;->open(Ljava/io/File;Z)V
 HSPLjava/util/logging/FileHandler;->openFiles()V
-HSPLjava/util/logging/FileHandler;->publish(Ljava/util/logging/LogRecord;)V
+HSPLjava/util/logging/FileHandler;->publish(Ljava/util/logging/LogRecord;)V+]Ljava/util/logging/FileHandler;Ljava/util/logging/FileHandler;
 HSPLjava/util/logging/FileHandler;->rotate()V
 HSPLjava/util/logging/Formatter;-><init>()V
 HSPLjava/util/logging/Formatter;->getHead(Ljava/util/logging/Handler;)Ljava/lang/String;
@@ -7360,7 +7471,7 @@
 HSPLjava/util/logging/Handler;->getFilter()Ljava/util/logging/Filter;
 HSPLjava/util/logging/Handler;->getFormatter()Ljava/util/logging/Formatter;
 HSPLjava/util/logging/Handler;->getLevel()Ljava/util/logging/Level;
-HSPLjava/util/logging/Handler;->isLoggable(Ljava/util/logging/LogRecord;)Z
+HSPLjava/util/logging/Handler;->isLoggable(Ljava/util/logging/LogRecord;)Z+]Ljava/util/logging/Handler;Ljava/util/logging/FileHandler;]Ljava/util/logging/Level;Ljava/util/logging/Level;]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord;
 HSPLjava/util/logging/Handler;->setEncoding(Ljava/lang/String;)V
 HSPLjava/util/logging/Handler;->setErrorManager(Ljava/util/logging/ErrorManager;)V
 HSPLjava/util/logging/Handler;->setFilter(Ljava/util/logging/Filter;)V
@@ -7427,7 +7538,7 @@
 HSPLjava/util/logging/LogManager;->parseClassNames(Ljava/lang/String;)[Ljava/lang/String;
 HSPLjava/util/logging/LogManager;->reset()V
 HSPLjava/util/logging/LogManager;->resetLogger(Ljava/util/logging/Logger;)V
-HSPLjava/util/logging/LogRecord;-><init>(Ljava/util/logging/Level;Ljava/lang/String;)V
+HSPLjava/util/logging/LogRecord;-><init>(Ljava/util/logging/Level;Ljava/lang/String;)V+]Ljava/lang/Object;Ljava/util/logging/Level;]Ljava/util/concurrent/atomic/AtomicLong;Ljava/util/concurrent/atomic/AtomicLong;
 HSPLjava/util/logging/LogRecord;->defaultThreadID()I
 HSPLjava/util/logging/LogRecord;->getLevel()Ljava/util/logging/Level;
 HSPLjava/util/logging/LogRecord;->getLoggerName()Ljava/lang/String;
@@ -7448,12 +7559,12 @@
 HSPLjava/util/logging/Logger;->addHandler(Ljava/util/logging/Handler;)V
 HSPLjava/util/logging/Logger;->checkPermission()V
 HSPLjava/util/logging/Logger;->demandLogger(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)Ljava/util/logging/Logger;
-HSPLjava/util/logging/Logger;->doLog(Ljava/util/logging/LogRecord;)V
+HSPLjava/util/logging/Logger;->doLog(Ljava/util/logging/LogRecord;)V+]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord;]Ljava/util/logging/Logger;Ljava/util/logging/Logger;
 HSPLjava/util/logging/Logger;->doSetParent(Ljava/util/logging/Logger;)V
 HSPLjava/util/logging/Logger;->findResourceBundle(Ljava/lang/String;Z)Ljava/util/ResourceBundle;
 HSPLjava/util/logging/Logger;->findSystemResourceBundle(Ljava/util/Locale;)Ljava/util/ResourceBundle;
 HSPLjava/util/logging/Logger;->getCallersClassLoader()Ljava/lang/ClassLoader;
-HSPLjava/util/logging/Logger;->getEffectiveLoggerBundle()Ljava/util/logging/Logger$LoggerBundle;
+HSPLjava/util/logging/Logger;->getEffectiveLoggerBundle()Ljava/util/logging/Logger$LoggerBundle;+]Ljava/util/logging/Logger$LoggerBundle;Ljava/util/logging/Logger$LoggerBundle;]Ljava/util/logging/Logger;Ljava/util/logging/LogManager$RootLogger;,Ljava/util/logging/Logger;
 HSPLjava/util/logging/Logger;->getHandlers()[Ljava/util/logging/Handler;
 HSPLjava/util/logging/Logger;->getLogger(Ljava/lang/String;)Ljava/util/logging/Logger;
 HSPLjava/util/logging/Logger;->getName()Ljava/lang/String;
@@ -7463,10 +7574,10 @@
 HSPLjava/util/logging/Logger;->getResourceBundleName()Ljava/lang/String;
 HSPLjava/util/logging/Logger;->getUseParentHandlers()Z
 HSPLjava/util/logging/Logger;->info(Ljava/lang/String;)V
-HSPLjava/util/logging/Logger;->isLoggable(Ljava/util/logging/Level;)Z
+HSPLjava/util/logging/Logger;->isLoggable(Ljava/util/logging/Level;)Z+]Ljava/util/logging/Level;Ljava/util/logging/Level;
 HSPLjava/util/logging/Logger;->log(Ljava/util/logging/Level;Ljava/lang/String;)V
-HSPLjava/util/logging/Logger;->log(Ljava/util/logging/LogRecord;)V
-HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+HSPLjava/util/logging/Logger;->log(Ljava/util/logging/LogRecord;)V+]Ljava/util/logging/Handler;Ljava/util/logging/FileHandler;]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord;]Ljava/util/logging/Logger;Ljava/util/logging/Logger;
+HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V+]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord;]Ljava/util/logging/Logger;Ljava/util/logging/Logger;
 HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V
 HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
 HSPLjava/util/logging/Logger;->removeChildLogger(Ljava/util/logging/LogManager$LoggerWeakRef;)V
@@ -7485,42 +7596,45 @@
 HSPLjava/util/logging/StreamHandler;-><init>()V
 HSPLjava/util/logging/StreamHandler;->close()V
 HSPLjava/util/logging/StreamHandler;->configure()V
-HSPLjava/util/logging/StreamHandler;->flush()V
+HSPLjava/util/logging/StreamHandler;->flush()V+]Ljava/io/Writer;Ljava/io/OutputStreamWriter;
 HSPLjava/util/logging/StreamHandler;->flushAndClose()V
 HSPLjava/util/logging/StreamHandler;->isLoggable(Ljava/util/logging/LogRecord;)Z
-HSPLjava/util/logging/StreamHandler;->publish(Ljava/util/logging/LogRecord;)V
+HSPLjava/util/logging/StreamHandler;->publish(Ljava/util/logging/LogRecord;)V+]Ljava/io/Writer;Ljava/io/OutputStreamWriter;]Ljava/util/logging/StreamHandler;Ljava/util/logging/FileHandler;
 HSPLjava/util/logging/StreamHandler;->setEncoding(Ljava/lang/String;)V
 HSPLjava/util/logging/StreamHandler;->setOutputStream(Ljava/io/OutputStream;)V
 HSPLjava/util/logging/XMLFormatter;-><init>()V
-HSPLjava/util/regex/Matcher;-><init>(Ljava/util/regex/Pattern;Ljava/lang/CharSequence;)V
-HSPLjava/util/regex/Matcher;->appendEvaluated(Ljava/lang/StringBuffer;Ljava/lang/String;)V
-HSPLjava/util/regex/Matcher;->appendReplacement(Ljava/lang/StringBuffer;Ljava/lang/String;)Ljava/util/regex/Matcher;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;-><init>(Ljava/util/regex/Pattern;Ljava/lang/CharSequence;)V+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->appendEvaluated(Ljava/lang/StringBuilder;Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/util/regex/Matcher;->appendReplacement(Ljava/lang/StringBuffer;Ljava/lang/String;)Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->appendReplacement(Ljava/lang/StringBuilder;Ljava/lang/String;)Ljava/util/regex/Matcher;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->appendReplacementInternal(Ljava/lang/StringBuilder;Ljava/lang/String;)V+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
 HSPLjava/util/regex/Matcher;->appendTail(Ljava/lang/StringBuffer;)Ljava/lang/StringBuffer;
+HSPLjava/util/regex/Matcher;->appendTail(Ljava/lang/StringBuilder;)Ljava/lang/StringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/util/regex/Matcher;->end()I
-HSPLjava/util/regex/Matcher;->end(I)I+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->end(I)I
 HSPLjava/util/regex/Matcher;->ensureMatch()V
-HSPLjava/util/regex/Matcher;->find()Z
+HSPLjava/util/regex/Matcher;->find()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
 HSPLjava/util/regex/Matcher;->find(I)Z
 HSPLjava/util/regex/Matcher;->getSubSequence(II)Ljava/lang/CharSequence;
 HSPLjava/util/regex/Matcher;->getTextLength()I
 HSPLjava/util/regex/Matcher;->group()Ljava/lang/String;
-HSPLjava/util/regex/Matcher;->group(I)Ljava/lang/String;+]Ljava/lang/CharSequence;Ljava/lang/String;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->groupCount()I
-HSPLjava/util/regex/Matcher;->hitEnd()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
+HSPLjava/util/regex/Matcher;->group(I)Ljava/lang/String;
+HSPLjava/util/regex/Matcher;->groupCount()I+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
+HSPLjava/util/regex/Matcher;->hitEnd()Z
 HSPLjava/util/regex/Matcher;->lookingAt()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
 HSPLjava/util/regex/Matcher;->matches()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
 HSPLjava/util/regex/Matcher;->pattern()Ljava/util/regex/Pattern;
 HSPLjava/util/regex/Matcher;->region(II)Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->replaceAll(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->replaceAll(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
 HSPLjava/util/regex/Matcher;->replaceFirst(Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/util/regex/Matcher;->reset()Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;II)Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->resetForInput()V
+HSPLjava/util/regex/Matcher;->reset()Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;
+HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/lang/StringBuilder;
+HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;II)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;
+HSPLjava/util/regex/Matcher;->resetForInput()V+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
 HSPLjava/util/regex/Matcher;->start()I
 HSPLjava/util/regex/Matcher;->start(I)I
 HSPLjava/util/regex/Matcher;->useAnchoringBounds(Z)Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->usePattern(Ljava/util/regex/Pattern;)Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->usePattern(Ljava/util/regex/Pattern;)Ljava/util/regex/Matcher;+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
 HSPLjava/util/regex/Matcher;->useTransparentBounds(Z)Ljava/util/regex/Matcher;
 HSPLjava/util/regex/Pattern;-><init>(Ljava/lang/String;I)V
 HSPLjava/util/regex/Pattern;->compile()V
@@ -7532,29 +7646,29 @@
 HSPLjava/util/regex/Pattern;->pattern()Ljava/lang/String;
 HSPLjava/util/regex/Pattern;->quote(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/regex/Pattern;->split(Ljava/lang/CharSequence;)[Ljava/lang/String;
-HSPLjava/util/regex/Pattern;->split(Ljava/lang/CharSequence;I)[Ljava/lang/String;
+HSPLjava/util/regex/Pattern;->split(Ljava/lang/CharSequence;I)[Ljava/lang/String;+]Ljava/util/List;Ljava/util/ArrayList$SubList;]Ljava/lang/CharSequence;Ljava/lang/String;]Ljava/util/regex/Pattern;Ljava/util/regex/Pattern;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;]Ljava/util/ArrayList;Ljava/util/ArrayList;
 HSPLjava/util/regex/Pattern;->toString()Ljava/lang/String;
 HSPLjava/util/stream/AbstractPipeline;-><init>(Ljava/util/Spliterator;IZ)V
-HSPLjava/util/stream/AbstractPipeline;-><init>(Ljava/util/stream/AbstractPipeline;I)V+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4;
+HSPLjava/util/stream/AbstractPipeline;-><init>(Ljava/util/stream/AbstractPipeline;I)V+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/SortedOps$OfRef;,Ljava/util/stream/ReferencePipeline$3;,Ljava/util/stream/ReferencePipeline$4;
 HSPLjava/util/stream/AbstractPipeline;->close()V
 HSPLjava/util/stream/AbstractPipeline;->copyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)V+]Ljava/util/Spliterator;Ljava/util/Spliterators$IntArraySpliterator;,Ljava/util/HashMap$KeySpliterator;]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4;]Ljava/util/stream/Sink;Ljava/util/stream/ReferencePipeline$4$1;,Ljava/util/stream/IntPipeline$4$1;]Ljava/util/stream/StreamOpFlag;Ljava/util/stream/StreamOpFlag;
-HSPLjava/util/stream/AbstractPipeline;->copyIntoWithCancel(Ljava/util/stream/Sink;Ljava/util/Spliterator;)V
-HSPLjava/util/stream/AbstractPipeline;->evaluate(Ljava/util/Spliterator;ZLjava/util/function/IntFunction;)Ljava/util/stream/Node;+]Ljava/util/stream/Node$Builder;Ljava/util/stream/Nodes$IntFixedNodeBuilder;]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/ReferencePipeline$4;
-HSPLjava/util/stream/AbstractPipeline;->evaluate(Ljava/util/stream/TerminalOp;)Ljava/lang/Object;+]Ljava/util/stream/TerminalOp;Ljava/util/stream/ReduceOps$3;]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;
-HSPLjava/util/stream/AbstractPipeline;->evaluateToArrayNode(Ljava/util/function/IntFunction;)Ljava/util/stream/Node;+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/ReferencePipeline$4;
-HSPLjava/util/stream/AbstractPipeline;->exactOutputSizeIfKnown(Ljava/util/Spliterator;)J+]Ljava/util/Spliterator;Ljava/util/HashMap$KeySpliterator;]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/ReferencePipeline$4;]Ljava/util/stream/StreamOpFlag;Ljava/util/stream/StreamOpFlag;
+HSPLjava/util/stream/AbstractPipeline;->evaluate(Ljava/util/Spliterator;ZLjava/util/function/IntFunction;)Ljava/util/stream/Node;
+HSPLjava/util/stream/AbstractPipeline;->evaluate(Ljava/util/stream/TerminalOp;)Ljava/lang/Object;
+HSPLjava/util/stream/AbstractPipeline;->evaluateToArrayNode(Ljava/util/function/IntFunction;)Ljava/util/stream/Node;
+HSPLjava/util/stream/AbstractPipeline;->exactOutputSizeIfKnown(Ljava/util/Spliterator;)J
 HSPLjava/util/stream/AbstractPipeline;->getStreamAndOpFlags()I
 HSPLjava/util/stream/AbstractPipeline;->isParallel()Z
 HSPLjava/util/stream/AbstractPipeline;->onClose(Ljava/lang/Runnable;)Ljava/util/stream/BaseStream;
 HSPLjava/util/stream/AbstractPipeline;->sequential()Ljava/util/stream/BaseStream;
-HSPLjava/util/stream/AbstractPipeline;->sourceSpliterator(I)Ljava/util/Spliterator;
+HSPLjava/util/stream/AbstractPipeline;->sourceSpliterator(I)Ljava/util/Spliterator;+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4;
 HSPLjava/util/stream/AbstractPipeline;->sourceStageSpliterator()Ljava/util/Spliterator;
 HSPLjava/util/stream/AbstractPipeline;->spliterator()Ljava/util/Spliterator;
-HSPLjava/util/stream/AbstractPipeline;->wrapAndCopyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)Ljava/util/stream/Sink;+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4;
-HSPLjava/util/stream/AbstractPipeline;->wrapSink(Ljava/util/stream/Sink;)Ljava/util/stream/Sink;+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4;
+HSPLjava/util/stream/AbstractPipeline;->wrapAndCopyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)Ljava/util/stream/Sink;
+HSPLjava/util/stream/AbstractPipeline;->wrapSink(Ljava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/AbstractSpinedBuffer;-><init>()V
 HSPLjava/util/stream/AbstractSpinedBuffer;->count()J
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda0;-><init>()V
+HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda0;->accept(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda15;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda1;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda20;->accept(Ljava/lang/Object;Ljava/lang/Object;)V
@@ -7564,20 +7678,20 @@
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda41;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda41;->get()Ljava/lang/Object;
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda42;-><init>()V
-HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda42;->accept(Ljava/lang/Object;Ljava/lang/Object;)V+]Ljava/util/Set;Ljava/util/HashSet;
+HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda42;->accept(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda50;-><init>(Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda50;->get()Ljava/lang/Object;
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda51;-><init>()V
-HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda51;->accept(Ljava/lang/Object;Ljava/lang/Object;)V+]Ljava/util/StringJoiner;Ljava/util/StringJoiner;
+HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda51;->accept(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda52;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda53;-><init>()V
-HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda53;->apply(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/StringJoiner;Ljava/util/StringJoiner;
+HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda53;->apply(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda65;->get()Ljava/lang/Object;
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda66;->get()Ljava/lang/Object;
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda74;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda74;->get()Ljava/lang/Object;
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda75;-><init>()V
-HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda75;->accept(Ljava/lang/Object;Ljava/lang/Object;)V+]Ljava/util/List;Ljava/util/ArrayList;
+HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda75;->accept(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda76;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda87;-><init>()V
 HSPLjava/util/stream/Collectors$CollectorImpl;-><init>(Ljava/util/function/Supplier;Ljava/util/function/BiConsumer;Ljava/util/function/BinaryOperator;Ljava/util/Set;)V
@@ -7595,7 +7709,7 @@
 HSPLjava/util/stream/Collectors;->joining(Ljava/lang/CharSequence;)Ljava/util/stream/Collector;
 HSPLjava/util/stream/Collectors;->joining(Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/util/stream/Collector;
 HSPLjava/util/stream/Collectors;->lambda$joining$11(Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/util/StringJoiner;
-HSPLjava/util/stream/Collectors;->lambda$uniqKeysMapAccumulator$1(Ljava/util/function/Function;Ljava/util/function/Function;Ljava/util/Map;Ljava/lang/Object;)V+]Ljava/util/Map;Ljava/util/HashMap;
+HSPLjava/util/stream/Collectors;->lambda$uniqKeysMapAccumulator$1(Ljava/util/function/Function;Ljava/util/function/Function;Ljava/util/Map;Ljava/lang/Object;)V
 HSPLjava/util/stream/Collectors;->mapMerger(Ljava/util/function/BinaryOperator;)Ljava/util/function/BinaryOperator;
 HSPLjava/util/stream/Collectors;->toCollection(Ljava/util/function/Supplier;)Ljava/util/stream/Collector;
 HSPLjava/util/stream/Collectors;->toList()Ljava/util/stream/Collector;
@@ -7637,16 +7751,18 @@
 HSPLjava/util/stream/ForEachOps$ForEachOp;->get()Ljava/lang/Void;
 HSPLjava/util/stream/ForEachOps$ForEachOp;->getOpFlags()I
 HSPLjava/util/stream/ForEachOps;->makeRef(Ljava/util/function/Consumer;Z)Ljava/util/stream/TerminalOp;
+HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda13;->applyAsInt(II)I
 HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda7;-><init>()V
 HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda7;->apply(I)Ljava/lang/Object;
+HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda8;-><init>()V
 HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda8;->apply(I)Ljava/lang/Object;
 HSPLjava/util/stream/IntPipeline$4$1;-><init>(Ljava/util/stream/IntPipeline$4;Ljava/util/stream/Sink;)V
 HSPLjava/util/stream/IntPipeline$4$1;->accept(I)V
 HSPLjava/util/stream/IntPipeline$4;-><init>(Ljava/util/stream/IntPipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/IntFunction;)V
 HSPLjava/util/stream/IntPipeline$4;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/IntPipeline$9$1;-><init>(Ljava/util/stream/IntPipeline$9;Ljava/util/stream/Sink;)V
-HSPLjava/util/stream/IntPipeline$9$1;->accept(I)V+]Ljava/util/stream/Sink;megamorphic_types]Ljava/util/function/IntPredicate;missing_types
-HSPLjava/util/stream/IntPipeline$9$1;->begin(J)V+]Ljava/util/stream/Sink;Ljava/util/stream/IntPipeline$4$1;,Ljava/util/stream/ReduceOps$6ReducingSink;,Ljava/util/stream/ForEachOps$ForEachOp$OfInt;,Ljava/util/stream/IntPipeline$3$1;
+HSPLjava/util/stream/IntPipeline$9$1;->accept(I)V
+HSPLjava/util/stream/IntPipeline$9$1;->begin(J)V
 HSPLjava/util/stream/IntPipeline$9;-><init>(Ljava/util/stream/IntPipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/IntPredicate;)V
 HSPLjava/util/stream/IntPipeline$9;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/IntPipeline$Head;-><init>(Ljava/util/Spliterator;IZ)V
@@ -7659,15 +7775,14 @@
 HSPLjava/util/stream/IntPipeline;->adapt(Ljava/util/Spliterator;)Ljava/util/Spliterator$OfInt;
 HSPLjava/util/stream/IntPipeline;->adapt(Ljava/util/stream/Sink;)Ljava/util/function/IntConsumer;
 HSPLjava/util/stream/IntPipeline;->allMatch(Ljava/util/function/IntPredicate;)Z
-HSPLjava/util/stream/IntPipeline;->boxed()Ljava/util/stream/Stream;+]Ljava/util/stream/IntPipeline;Ljava/util/stream/IntPipeline$Head;
+HSPLjava/util/stream/IntPipeline;->boxed()Ljava/util/stream/Stream;
 HSPLjava/util/stream/IntPipeline;->distinct()Ljava/util/stream/IntStream;
 HSPLjava/util/stream/IntPipeline;->filter(Ljava/util/function/IntPredicate;)Ljava/util/stream/IntStream;
-HSPLjava/util/stream/IntPipeline;->forEachWithCancel(Ljava/util/Spliterator;Ljava/util/stream/Sink;)V
 HSPLjava/util/stream/IntPipeline;->makeNodeBuilder(JLjava/util/function/IntFunction;)Ljava/util/stream/Node$Builder;
 HSPLjava/util/stream/IntPipeline;->mapToObj(Ljava/util/function/IntFunction;)Ljava/util/stream/Stream;
 HSPLjava/util/stream/IntPipeline;->reduce(ILjava/util/function/IntBinaryOperator;)I
 HSPLjava/util/stream/IntPipeline;->sum()I
-HSPLjava/util/stream/IntPipeline;->toArray()[I+]Ljava/util/stream/IntPipeline;Ljava/util/stream/ReferencePipeline$4;]Ljava/util/stream/Node$OfInt;Ljava/util/stream/Nodes$IntFixedNodeBuilder;
+HSPLjava/util/stream/IntPipeline;->toArray()[I
 HSPLjava/util/stream/IntStream;->empty()Ljava/util/stream/IntStream;
 HSPLjava/util/stream/IntStream;->of([I)Ljava/util/stream/IntStream;
 HSPLjava/util/stream/IntStream;->range(II)Ljava/util/stream/IntStream;
@@ -7687,7 +7802,7 @@
 HSPLjava/util/stream/MatchOps$1MatchSink;-><init>(Ljava/util/stream/MatchOps$MatchKind;Ljava/util/function/Predicate;)V
 HSPLjava/util/stream/MatchOps$1MatchSink;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/MatchOps$2MatchSink;-><init>(Ljava/util/stream/MatchOps$MatchKind;Ljava/util/function/IntPredicate;)V
-HSPLjava/util/stream/MatchOps$2MatchSink;->accept(I)V+]Ljava/util/function/IntPredicate;missing_types
+HSPLjava/util/stream/MatchOps$2MatchSink;->accept(I)V
 HSPLjava/util/stream/MatchOps$BooleanTerminalSink;-><init>(Ljava/util/stream/MatchOps$MatchKind;)V
 HSPLjava/util/stream/MatchOps$BooleanTerminalSink;->cancellationRequested()Z
 HSPLjava/util/stream/MatchOps$BooleanTerminalSink;->getAndClearState()Z
@@ -7720,6 +7835,11 @@
 HSPLjava/util/stream/Nodes$IntFixedNodeBuilder;->end()V
 HSPLjava/util/stream/Nodes$SpinedNodeBuilder;-><clinit>()V
 HSPLjava/util/stream/Nodes$SpinedNodeBuilder;-><init>()V
+HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->asArray(Ljava/util/function/IntFunction;)[Ljava/lang/Object;
+HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->begin(J)V
+HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->build()Ljava/util/stream/Node;
+HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->copyInto([Ljava/lang/Object;I)V
+HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->end()V
 HSPLjava/util/stream/Nodes;->builder()Ljava/util/stream/Node$Builder;
 HSPLjava/util/stream/Nodes;->builder(JLjava/util/function/IntFunction;)Ljava/util/stream/Node$Builder;
 HSPLjava/util/stream/Nodes;->flatten(Ljava/util/stream/Node;Ljava/util/function/IntFunction;)Ljava/util/stream/Node;
@@ -7738,21 +7858,22 @@
 HSPLjava/util/stream/ReduceOps$2;->makeSink()Ljava/util/stream/ReduceOps$2ReducingSink;
 HSPLjava/util/stream/ReduceOps$2;->makeSink()Ljava/util/stream/ReduceOps$AccumulatingSink;
 HSPLjava/util/stream/ReduceOps$2ReducingSink;-><init>(Ljava/util/function/BinaryOperator;)V
-HSPLjava/util/stream/ReduceOps$2ReducingSink;->accept(Ljava/lang/Object;)V+]Ljava/util/function/BinaryOperator;Ljava/util/function/BinaryOperator$$ExternalSyntheticLambda0;
+HSPLjava/util/stream/ReduceOps$2ReducingSink;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/ReduceOps$2ReducingSink;->begin(J)V
 HSPLjava/util/stream/ReduceOps$2ReducingSink;->get()Ljava/lang/Object;
 HSPLjava/util/stream/ReduceOps$2ReducingSink;->get()Ljava/util/Optional;
 HSPLjava/util/stream/ReduceOps$3;-><init>(Ljava/util/stream/StreamShape;Ljava/util/function/BinaryOperator;Ljava/util/function/BiConsumer;Ljava/util/function/Supplier;Ljava/util/stream/Collector;)V
-HSPLjava/util/stream/ReduceOps$3;->getOpFlags()I+]Ljava/util/stream/Collector;Ljava/util/stream/Collectors$CollectorImpl;]Ljava/util/Set;Ljava/util/Collections$UnmodifiableSet;
+HSPLjava/util/stream/ReduceOps$3;->getOpFlags()I
 HSPLjava/util/stream/ReduceOps$3;->makeSink()Ljava/util/stream/ReduceOps$3ReducingSink;
 HSPLjava/util/stream/ReduceOps$3;->makeSink()Ljava/util/stream/ReduceOps$AccumulatingSink;
 HSPLjava/util/stream/ReduceOps$3ReducingSink;-><init>(Ljava/util/function/Supplier;Ljava/util/function/BiConsumer;Ljava/util/function/BinaryOperator;)V
 HSPLjava/util/stream/ReduceOps$3ReducingSink;->accept(Ljava/lang/Object;)V
-HSPLjava/util/stream/ReduceOps$3ReducingSink;->begin(J)V+]Ljava/util/function/Supplier;Ljava/util/stream/Collectors$$ExternalSyntheticLambda74;
+HSPLjava/util/stream/ReduceOps$3ReducingSink;->begin(J)V
 HSPLjava/util/stream/ReduceOps$5;-><init>(Ljava/util/stream/StreamShape;Ljava/util/function/IntBinaryOperator;I)V
 HSPLjava/util/stream/ReduceOps$5;->makeSink()Ljava/util/stream/ReduceOps$5ReducingSink;
 HSPLjava/util/stream/ReduceOps$5;->makeSink()Ljava/util/stream/ReduceOps$AccumulatingSink;
 HSPLjava/util/stream/ReduceOps$5ReducingSink;-><init>(ILjava/util/function/IntBinaryOperator;)V
+HSPLjava/util/stream/ReduceOps$5ReducingSink;->accept(I)V
 HSPLjava/util/stream/ReduceOps$5ReducingSink;->begin(J)V
 HSPLjava/util/stream/ReduceOps$5ReducingSink;->get()Ljava/lang/Integer;
 HSPLjava/util/stream/ReduceOps$5ReducingSink;->get()Ljava/lang/Object;
@@ -7767,7 +7888,7 @@
 HSPLjava/util/stream/ReduceOps$Box;-><init>()V
 HSPLjava/util/stream/ReduceOps$Box;->get()Ljava/lang/Object;
 HSPLjava/util/stream/ReduceOps$ReduceOp;-><init>(Ljava/util/stream/StreamShape;)V
-HSPLjava/util/stream/ReduceOps$ReduceOp;->evaluateSequential(Ljava/util/stream/PipelineHelper;Ljava/util/Spliterator;)Ljava/lang/Object;+]Ljava/util/stream/ReduceOps$AccumulatingSink;Ljava/util/stream/ReduceOps$3ReducingSink;]Ljava/util/stream/PipelineHelper;Ljava/util/stream/IntPipeline$4;]Ljava/util/stream/ReduceOps$ReduceOp;Ljava/util/stream/ReduceOps$3;
+HSPLjava/util/stream/ReduceOps$ReduceOp;->evaluateSequential(Ljava/util/stream/PipelineHelper;Ljava/util/Spliterator;)Ljava/lang/Object;
 HSPLjava/util/stream/ReduceOps;->makeDouble(Ljava/util/function/DoubleBinaryOperator;)Ljava/util/stream/TerminalOp;
 HSPLjava/util/stream/ReduceOps;->makeInt(ILjava/util/function/IntBinaryOperator;)Ljava/util/stream/TerminalOp;
 HSPLjava/util/stream/ReduceOps;->makeLong(JLjava/util/function/LongBinaryOperator;)Ljava/util/stream/TerminalOp;
@@ -7776,7 +7897,7 @@
 HSPLjava/util/stream/ReferencePipeline$$ExternalSyntheticLambda2;-><init>()V
 HSPLjava/util/stream/ReferencePipeline$$ExternalSyntheticLambda2;->applyAsLong(Ljava/lang/Object;)J
 HSPLjava/util/stream/ReferencePipeline$2$1;-><init>(Ljava/util/stream/ReferencePipeline$2;Ljava/util/stream/Sink;)V
-HSPLjava/util/stream/ReferencePipeline$2$1;->accept(Ljava/lang/Object;)V+]Ljava/util/stream/Sink;megamorphic_types
+HSPLjava/util/stream/ReferencePipeline$2$1;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/ReferencePipeline$2$1;->begin(J)V
 HSPLjava/util/stream/ReferencePipeline$2;-><init>(Ljava/util/stream/ReferencePipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/Predicate;)V
 HSPLjava/util/stream/ReferencePipeline$2;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
@@ -7785,7 +7906,7 @@
 HSPLjava/util/stream/ReferencePipeline$3;-><init>(Ljava/util/stream/ReferencePipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/Function;)V
 HSPLjava/util/stream/ReferencePipeline$3;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/ReferencePipeline$4$1;-><init>(Ljava/util/stream/ReferencePipeline$4;Ljava/util/stream/Sink;)V
-HSPLjava/util/stream/ReferencePipeline$4$1;->accept(Ljava/lang/Object;)V+]Ljava/util/function/ToIntFunction;Landroid/media/AudioPort$$ExternalSyntheticLambda0;]Ljava/util/stream/Sink;Ljava/util/stream/Nodes$IntFixedNodeBuilder;
+HSPLjava/util/stream/ReferencePipeline$4$1;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/ReferencePipeline$4;-><init>(Ljava/util/stream/ReferencePipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/ToIntFunction;)V
 HSPLjava/util/stream/ReferencePipeline$4;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/ReferencePipeline$5$1;-><init>(Ljava/util/stream/ReferencePipeline$5;Ljava/util/stream/Sink;)V
@@ -7797,7 +7918,7 @@
 HSPLjava/util/stream/ReferencePipeline$6;-><init>(Ljava/util/stream/ReferencePipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/ToDoubleFunction;)V
 HSPLjava/util/stream/ReferencePipeline$6;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/ReferencePipeline$7$1;-><init>(Ljava/util/stream/ReferencePipeline$7;Ljava/util/stream/Sink;)V
-HSPLjava/util/stream/ReferencePipeline$7$1;->accept(Ljava/lang/Object;)V+]Ljava/util/function/Function;missing_types]Ljava/util/stream/Stream;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$Head;,Ljava/util/stream/ReferencePipeline$3;
+HSPLjava/util/stream/ReferencePipeline$7$1;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/ReferencePipeline$7$1;->begin(J)V
 HSPLjava/util/stream/ReferencePipeline$7;-><init>(Ljava/util/stream/ReferencePipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/Function;)V
 HSPLjava/util/stream/ReferencePipeline$7;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
@@ -7809,17 +7930,17 @@
 HSPLjava/util/stream/ReferencePipeline$StatelessOp;->opIsStateful()Z
 HSPLjava/util/stream/ReferencePipeline;-><init>(Ljava/util/Spliterator;IZ)V
 HSPLjava/util/stream/ReferencePipeline;-><init>(Ljava/util/stream/AbstractPipeline;I)V
-HSPLjava/util/stream/ReferencePipeline;->allMatch(Ljava/util/function/Predicate;)Z+]Ljava/util/stream/ReferencePipeline;Ljava/util/stream/ReferencePipeline$Head;]Ljava/lang/Boolean;Ljava/lang/Boolean;
+HSPLjava/util/stream/ReferencePipeline;->allMatch(Ljava/util/function/Predicate;)Z
 HSPLjava/util/stream/ReferencePipeline;->anyMatch(Ljava/util/function/Predicate;)Z
-HSPLjava/util/stream/ReferencePipeline;->collect(Ljava/util/stream/Collector;)Ljava/lang/Object;+]Ljava/util/stream/Collector;Ljava/util/stream/Collectors$CollectorImpl;]Ljava/util/stream/ReferencePipeline;Ljava/util/stream/IntPipeline$4;]Ljava/util/Set;Ljava/util/Collections$UnmodifiableSet;
+HSPLjava/util/stream/ReferencePipeline;->collect(Ljava/util/stream/Collector;)Ljava/lang/Object;
 HSPLjava/util/stream/ReferencePipeline;->count()J
 HSPLjava/util/stream/ReferencePipeline;->distinct()Ljava/util/stream/Stream;
 HSPLjava/util/stream/ReferencePipeline;->filter(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
 HSPLjava/util/stream/ReferencePipeline;->findAny()Ljava/util/Optional;
-HSPLjava/util/stream/ReferencePipeline;->findFirst()Ljava/util/Optional;+]Ljava/util/stream/ReferencePipeline;Ljava/util/stream/ReferencePipeline$2;
+HSPLjava/util/stream/ReferencePipeline;->findFirst()Ljava/util/Optional;
 HSPLjava/util/stream/ReferencePipeline;->flatMap(Ljava/util/function/Function;)Ljava/util/stream/Stream;
 HSPLjava/util/stream/ReferencePipeline;->forEach(Ljava/util/function/Consumer;)V
-HSPLjava/util/stream/ReferencePipeline;->forEachWithCancel(Ljava/util/Spliterator;Ljava/util/stream/Sink;)V+]Ljava/util/Spliterator;Ljava/util/Spliterators$ArraySpliterator;,Ljava/util/ArrayList$ArrayListSpliterator;]Ljava/util/stream/Sink;Ljava/util/stream/ReferencePipeline$2$1;,Ljava/util/stream/MatchOps$1MatchSink;
+HSPLjava/util/stream/ReferencePipeline;->forEachWithCancel(Ljava/util/Spliterator;Ljava/util/stream/Sink;)Z
 HSPLjava/util/stream/ReferencePipeline;->lambda$count$2(Ljava/lang/Object;)J
 HSPLjava/util/stream/ReferencePipeline;->makeNodeBuilder(JLjava/util/function/IntFunction;)Ljava/util/stream/Node$Builder;
 HSPLjava/util/stream/ReferencePipeline;->map(Ljava/util/function/Function;)Ljava/util/stream/Stream;
@@ -7838,26 +7959,32 @@
 HSPLjava/util/stream/Sink$ChainedInt;->end()V
 HSPLjava/util/stream/Sink$ChainedReference;-><init>(Ljava/util/stream/Sink;)V
 HSPLjava/util/stream/Sink$ChainedReference;->begin(J)V
-HSPLjava/util/stream/Sink$ChainedReference;->cancellationRequested()Z+]Ljava/util/stream/Sink;Ljava/util/stream/FindOps$FindSink$OfRef;,Ljava/util/stream/ReferencePipeline$3$1;,Ljava/util/stream/ReferencePipeline$2$1;,Ljava/util/stream/MatchOps$1MatchSink;
-HSPLjava/util/stream/Sink$ChainedReference;->end()V+]Ljava/util/stream/Sink;Ljava/util/stream/Nodes$IntFixedNodeBuilder;
+HSPLjava/util/stream/Sink$ChainedReference;->cancellationRequested()Z
+HSPLjava/util/stream/Sink$ChainedReference;->end()V
 HSPLjava/util/stream/Sink;->begin(J)V
 HSPLjava/util/stream/Sink;->end()V
 HSPLjava/util/stream/SortedOps$AbstractRefSortingSink;-><init>(Ljava/util/stream/Sink;Ljava/util/Comparator;)V
 HSPLjava/util/stream/SortedOps$OfRef;-><init>(Ljava/util/stream/AbstractPipeline;Ljava/util/Comparator;)V
 HSPLjava/util/stream/SortedOps$OfRef;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/SortedOps$RefSortingSink$$ExternalSyntheticLambda0;-><init>(Ljava/util/stream/Sink;)V
+HSPLjava/util/stream/SortedOps$RefSortingSink$$ExternalSyntheticLambda0;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/SortedOps$RefSortingSink;-><init>(Ljava/util/stream/Sink;Ljava/util/Comparator;)V
+HSPLjava/util/stream/SortedOps$RefSortingSink;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/SortedOps$RefSortingSink;->begin(J)V
 HSPLjava/util/stream/SortedOps$RefSortingSink;->end()V
 HSPLjava/util/stream/SortedOps$SizedRefSortingSink;-><init>(Ljava/util/stream/Sink;Ljava/util/Comparator;)V
 HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->begin(J)V
-HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->end()V+]Ljava/util/stream/Sink;Ljava/util/stream/ReduceOps$3ReducingSink;,Ljava/util/stream/ForEachOps$ForEachOp$OfRef;
+HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->end()V
 HSPLjava/util/stream/SortedOps;->makeRef(Ljava/util/stream/AbstractPipeline;Ljava/util/Comparator;)Ljava/util/stream/Stream;
 HSPLjava/util/stream/SpinedBuffer;-><init>()V
-HSPLjava/util/stream/SpinedBuffer;->accept(Ljava/lang/Object;)V+]Ljava/util/stream/SpinedBuffer;Ljava/util/stream/Nodes$SpinedNodeBuilder;
+HSPLjava/util/stream/SpinedBuffer;->accept(Ljava/lang/Object;)V
+HSPLjava/util/stream/SpinedBuffer;->asArray(Ljava/util/function/IntFunction;)[Ljava/lang/Object;
+HSPLjava/util/stream/SpinedBuffer;->capacity()J
 HSPLjava/util/stream/SpinedBuffer;->clear()V
+HSPLjava/util/stream/SpinedBuffer;->copyInto([Ljava/lang/Object;I)V
 HSPLjava/util/stream/SpinedBuffer;->count()J
+HSPLjava/util/stream/SpinedBuffer;->ensureCapacity(J)V
 HSPLjava/util/stream/Stream;->builder()Ljava/util/stream/Stream$Builder;
 HSPLjava/util/stream/Stream;->concat(Ljava/util/stream/Stream;Ljava/util/stream/Stream;)Ljava/util/stream/Stream;
 HSPLjava/util/stream/Stream;->of([Ljava/lang/Object;)Ljava/util/stream/Stream;
@@ -7889,13 +8016,15 @@
 HSPLjava/util/zip/CRC32;->update(I)V
 HSPLjava/util/zip/CRC32;->update([B)V
 HSPLjava/util/zip/CRC32;->update([BII)V
+HSPLjava/util/zip/CRC32;->updateByteBuffer(IJII)I
+HSPLjava/util/zip/CRC32;->updateBytes(I[BII)I
 HSPLjava/util/zip/CheckedInputStream;-><init>(Ljava/io/InputStream;Ljava/util/zip/Checksum;)V
 HSPLjava/util/zip/CheckedInputStream;->read()I
 HSPLjava/util/zip/CheckedInputStream;->read([BII)I
 HSPLjava/util/zip/Deflater;-><init>()V
 HSPLjava/util/zip/Deflater;-><init>(IZ)V
 HSPLjava/util/zip/Deflater;->deflate([BII)I
-HSPLjava/util/zip/Deflater;->deflate([BIII)I+]Ljava/util/zip/ZStreamRef;Ljava/util/zip/ZStreamRef;
+HSPLjava/util/zip/Deflater;->deflate([BIII)I
 HSPLjava/util/zip/Deflater;->end()V
 HSPLjava/util/zip/Deflater;->ensureOpen()V
 HSPLjava/util/zip/Deflater;->finalize()V
@@ -7940,40 +8069,41 @@
 HSPLjava/util/zip/Inflater;-><init>(Z)V
 HSPLjava/util/zip/Inflater;->end()V
 HSPLjava/util/zip/Inflater;->ended()Z
-HSPLjava/util/zip/Inflater;->ensureOpen()V+]Ljava/util/zip/ZStreamRef;Ljava/util/zip/ZStreamRef;
+HSPLjava/util/zip/Inflater;->ensureOpen()V
 HSPLjava/util/zip/Inflater;->finalize()V
 HSPLjava/util/zip/Inflater;->finished()Z
 HSPLjava/util/zip/Inflater;->getBytesRead()J
 HSPLjava/util/zip/Inflater;->getBytesWritten()J
 HSPLjava/util/zip/Inflater;->getRemaining()I
 HSPLjava/util/zip/Inflater;->getTotalOut()I
-HSPLjava/util/zip/Inflater;->inflate([BII)I+]Ljava/util/zip/ZStreamRef;Ljava/util/zip/ZStreamRef;
+HSPLjava/util/zip/Inflater;->inflate([BII)I
 HSPLjava/util/zip/Inflater;->needsDictionary()Z
 HSPLjava/util/zip/Inflater;->needsInput()Z
-HSPLjava/util/zip/Inflater;->reset()V+]Ljava/util/zip/ZStreamRef;Ljava/util/zip/ZStreamRef;
+HSPLjava/util/zip/Inflater;->reset()V
 HSPLjava/util/zip/Inflater;->setInput([BII)V
 HSPLjava/util/zip/InflaterInputStream;-><init>(Ljava/io/InputStream;Ljava/util/zip/Inflater;)V
 HSPLjava/util/zip/InflaterInputStream;-><init>(Ljava/io/InputStream;Ljava/util/zip/Inflater;I)V
 HSPLjava/util/zip/InflaterInputStream;->available()I
 HSPLjava/util/zip/InflaterInputStream;->close()V
 HSPLjava/util/zip/InflaterInputStream;->ensureOpen()V
-HSPLjava/util/zip/InflaterInputStream;->fill()V+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/ByteArrayInputStream;,Lcom/android/okhttp/okio/RealBufferedSource$1;,Ljava/io/PushbackInputStream;]Ljava/util/zip/Inflater;Ljava/util/zip/Inflater;
+HSPLjava/util/zip/InflaterInputStream;->fill()V
 HSPLjava/util/zip/InflaterInputStream;->read()I
-HSPLjava/util/zip/InflaterInputStream;->read([BII)I+]Ljava/util/zip/InflaterInputStream;Ljava/util/zip/GZIPInputStream;,Ljava/util/zip/ZipFile$ZipFileInflaterInputStream;,Ljava/util/zip/ZipInputStream;]Ljava/util/zip/Inflater;Ljava/util/zip/Inflater;
+HSPLjava/util/zip/InflaterInputStream;->read([BII)I
 HSPLjava/util/zip/ZStreamRef;-><init>(J)V
 HSPLjava/util/zip/ZStreamRef;->address()J
 HSPLjava/util/zip/ZStreamRef;->clear()V
 HSPLjava/util/zip/ZipCoder;-><init>(Ljava/nio/charset/Charset;)V
-HSPLjava/util/zip/ZipCoder;->decoder()Ljava/nio/charset/CharsetDecoder;
+HSPLjava/util/zip/ZipCoder;->decoder()Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/Charset;Lcom/android/icu/charset/CharsetICU;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/util/zip/ZipCoder;->encoder()Ljava/nio/charset/CharsetEncoder;
 HSPLjava/util/zip/ZipCoder;->get(Ljava/nio/charset/Charset;)Ljava/util/zip/ZipCoder;
 HSPLjava/util/zip/ZipCoder;->getBytes(Ljava/lang/String;)[B+]Ljava/lang/String;Ljava/lang/String;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;
 HSPLjava/util/zip/ZipCoder;->isUTF8()Z
 HSPLjava/util/zip/ZipCoder;->toString([BI)Ljava/lang/String;+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/util/zip/ZipEntry;-><init>()V
-HSPLjava/util/zip/ZipEntry;-><init>(Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/util/zip/ZipEntry;-><init>(Ljava/lang/String;)V
 HSPLjava/util/zip/ZipEntry;-><init>(Ljava/util/zip/ZipEntry;)V
 HSPLjava/util/zip/ZipEntry;->getCompressedSize()J
+HSPLjava/util/zip/ZipEntry;->getCrc()J
 HSPLjava/util/zip/ZipEntry;->getMethod()I
 HSPLjava/util/zip/ZipEntry;->getName()Ljava/lang/String;
 HSPLjava/util/zip/ZipEntry;->getSize()J
@@ -8010,7 +8140,8 @@
 HSPLjava/util/zip/ZipFile;->getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;+]Ljava/util/zip/ZipCoder;Ljava/util/zip/ZipCoder;
 HSPLjava/util/zip/ZipFile;->getInflater()Ljava/util/zip/Inflater;
 HSPLjava/util/zip/ZipFile;->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;
-HSPLjava/util/zip/ZipFile;->getZipEntry(Ljava/lang/String;J)Ljava/util/zip/ZipEntry;+]Ljava/util/zip/ZipEntry;Ljava/util/zip/ZipEntry;]Ljava/util/zip/ZipCoder;Ljava/util/zip/ZipCoder;
+HSPLjava/util/zip/ZipFile;->getZipEntry(Ljava/lang/String;J)Ljava/util/zip/ZipEntry;
+HSPLjava/util/zip/ZipFile;->onZipEntryAccess([BI)V+]Ldalvik/system/ZipPathValidator$Callback;Ldalvik/system/ZipPathValidator$1;]Ljava/util/zip/ZipCoder;Ljava/util/zip/ZipCoder;
 HSPLjava/util/zip/ZipFile;->releaseInflater(Ljava/util/zip/Inflater;)V
 HSPLjava/util/zip/ZipInputStream;-><init>(Ljava/io/InputStream;)V
 HSPLjava/util/zip/ZipInputStream;-><init>(Ljava/io/InputStream;Ljava/nio/charset/Charset;)V
@@ -8018,11 +8149,11 @@
 HSPLjava/util/zip/ZipInputStream;->closeEntry()V
 HSPLjava/util/zip/ZipInputStream;->createZipEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;
 HSPLjava/util/zip/ZipInputStream;->ensureOpen()V
-HSPLjava/util/zip/ZipInputStream;->getNextEntry()Ljava/util/zip/ZipEntry;+]Ljava/util/zip/CRC32;Ljava/util/zip/CRC32;]Ljava/util/zip/Inflater;Ljava/util/zip/Inflater;
-HSPLjava/util/zip/ZipInputStream;->read([BII)I+]Ljava/util/zip/CRC32;Ljava/util/zip/CRC32;]Ljava/io/InputStream;Ljava/io/PushbackInputStream;
-HSPLjava/util/zip/ZipInputStream;->readEnd(Ljava/util/zip/ZipEntry;)V+]Ljava/util/zip/CRC32;Ljava/util/zip/CRC32;]Ljava/io/PushbackInputStream;Ljava/io/PushbackInputStream;]Ljava/util/zip/Inflater;Ljava/util/zip/Inflater;
-HSPLjava/util/zip/ZipInputStream;->readFully([BII)V+]Ljava/io/InputStream;Ljava/io/PushbackInputStream;
-HSPLjava/util/zip/ZipInputStream;->readLOC()Ljava/util/zip/ZipEntry;+]Ljava/util/zip/ZipEntry;Ljava/util/zip/ZipEntry;]Ljava/util/zip/ZipInputStream;Ljava/util/zip/ZipInputStream;]Ljava/util/zip/ZipCoder;Ljava/util/zip/ZipCoder;
+HSPLjava/util/zip/ZipInputStream;->getNextEntry()Ljava/util/zip/ZipEntry;
+HSPLjava/util/zip/ZipInputStream;->read([BII)I
+HSPLjava/util/zip/ZipInputStream;->readEnd(Ljava/util/zip/ZipEntry;)V
+HSPLjava/util/zip/ZipInputStream;->readFully([BII)V
+HSPLjava/util/zip/ZipInputStream;->readLOC()Ljava/util/zip/ZipEntry;
 HSPLjava/util/zip/ZipUtils;->get16([BI)I
 HSPLjava/util/zip/ZipUtils;->get32([BI)J
 HSPLjava/util/zip/ZipUtils;->unixTimeToFileTime(J)Ljava/nio/file/attribute/FileTime;
@@ -8038,7 +8169,7 @@
 HSPLjavax/crypto/Cipher;->chooseProvider(Ljavax/crypto/Cipher$InitType;ILjava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;Ljava/security/AlgorithmParameters;Ljava/security/SecureRandom;)V
 HSPLjavax/crypto/Cipher;->createCipher(Ljava/lang/String;Ljava/security/Provider;)Ljavax/crypto/Cipher;
 HSPLjavax/crypto/Cipher;->doFinal()[B
-HSPLjavax/crypto/Cipher;->doFinal(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I+]Ljavax/crypto/Cipher;Ljavax/crypto/Cipher;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljavax/crypto/CipherSpi;Lcom/android/org/conscrypt/OpenSSLEvpCipherAES$AES$CBC$NoPadding;,Lcom/android/org/conscrypt/OpenSSLAeadCipherAES$GCM;
+HSPLjavax/crypto/Cipher;->doFinal(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I
 HSPLjavax/crypto/Cipher;->doFinal([B)[B
 HSPLjavax/crypto/Cipher;->doFinal([BI)I
 HSPLjavax/crypto/Cipher;->doFinal([BII)[B
@@ -8057,11 +8188,13 @@
 HSPLjavax/crypto/Cipher;->tryCombinations(Ljavax/crypto/Cipher$InitParams;Ljava/security/Provider;[Ljava/lang/String;)Ljavax/crypto/Cipher$CipherSpiAndProvider;
 HSPLjavax/crypto/Cipher;->tryTransformWithProvider(Ljavax/crypto/Cipher$InitParams;[Ljava/lang/String;Ljavax/crypto/Cipher$NeedToSet;Ljava/security/Provider$Service;)Ljavax/crypto/Cipher$CipherSpiAndProvider;
 HSPLjavax/crypto/Cipher;->unwrap([BLjava/lang/String;I)Ljava/security/Key;
-HSPLjavax/crypto/Cipher;->update([BII[BI)I+]Ljavax/crypto/Cipher;Ljavax/crypto/Cipher;]Ljavax/crypto/CipherSpi;missing_types
+HSPLjavax/crypto/Cipher;->update([BII[BI)I
 HSPLjavax/crypto/Cipher;->updateAAD([B)V
 HSPLjavax/crypto/Cipher;->updateAAD([BII)V
 HSPLjavax/crypto/Cipher;->updateProviderIfNeeded()V
 HSPLjavax/crypto/CipherSpi;-><init>()V
+HSPLjavax/crypto/CipherSpi;->bufferCrypt(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;Z)I
+HSPLjavax/crypto/CipherSpi;->engineDoFinal(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I
 HSPLjavax/crypto/JarVerifier;-><init>(Ljava/net/URL;Z)V
 HSPLjavax/crypto/JarVerifier;->verify()V
 HSPLjavax/crypto/JceSecurity$1;-><init>(Ljava/lang/Class;)V
@@ -8082,12 +8215,12 @@
 HSPLjavax/crypto/Mac;-><init>(Ljava/lang/String;)V
 HSPLjavax/crypto/Mac;-><init>(Ljavax/crypto/MacSpi;Ljava/security/Provider;Ljava/lang/String;)V
 HSPLjavax/crypto/Mac;->chooseFirstProvider()V
-HSPLjavax/crypto/Mac;->chooseProvider(Ljava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;)V+]Ljava/security/Provider$Service;Ljava/security/Provider$Service;]Ljava/util/List;Lsun/security/jca/ProviderList$ServiceList;]Ljava/util/Iterator;Lsun/security/jca/ProviderList$ServiceList$1;
+HSPLjavax/crypto/Mac;->chooseProvider(Ljava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;)V
 HSPLjavax/crypto/Mac;->doFinal()[B
 HSPLjavax/crypto/Mac;->doFinal([B)[B
 HSPLjavax/crypto/Mac;->doFinal([BI)V
 HSPLjavax/crypto/Mac;->getAlgorithm()Ljava/lang/String;
-HSPLjavax/crypto/Mac;->getInstance(Ljava/lang/String;)Ljavax/crypto/Mac;+]Ljava/security/Provider$Service;Ljava/security/Provider$Service;]Ljava/util/List;Lsun/security/jca/ProviderList$ServiceList;]Ljava/util/Iterator;Lsun/security/jca/ProviderList$ServiceList$1;
+HSPLjavax/crypto/Mac;->getInstance(Ljava/lang/String;)Ljavax/crypto/Mac;
 HSPLjavax/crypto/Mac;->getInstance(Ljava/lang/String;Ljava/security/Provider;)Ljavax/crypto/Mac;
 HSPLjavax/crypto/Mac;->getMacLength()I
 HSPLjavax/crypto/Mac;->init(Ljava/security/Key;)V
@@ -8229,11 +8362,11 @@
 HSPLjdk/internal/math/FDBigInteger;-><init>(J[CII)V
 HSPLjdk/internal/math/FDBigInteger;-><init>([II)V
 HSPLjdk/internal/math/FDBigInteger;->add(Ljdk/internal/math/FDBigInteger;)Ljdk/internal/math/FDBigInteger;
-HSPLjdk/internal/math/FDBigInteger;->addAndCmp(Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;)I+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->addAndCmp(Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;)I
 HSPLjdk/internal/math/FDBigInteger;->big5pow(I)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FDBigInteger;->checkZeroTail([II)I
 HSPLjdk/internal/math/FDBigInteger;->cmp(Ljdk/internal/math/FDBigInteger;)I
-HSPLjdk/internal/math/FDBigInteger;->cmpPow52(II)I+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->cmpPow52(II)I
 HSPLjdk/internal/math/FDBigInteger;->getNormalizationBias()I
 HSPLjdk/internal/math/FDBigInteger;->leftInplaceSub(Ljdk/internal/math/FDBigInteger;)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FDBigInteger;->leftShift(I)Ljdk/internal/math/FDBigInteger;
@@ -8244,26 +8377,26 @@
 HSPLjdk/internal/math/FDBigInteger;->multAddMe(II)V
 HSPLjdk/internal/math/FDBigInteger;->multAndCarryBy10([II[I)I
 HSPLjdk/internal/math/FDBigInteger;->multBy10()Ljdk/internal/math/FDBigInteger;
-HSPLjdk/internal/math/FDBigInteger;->multByPow52(II)Ljdk/internal/math/FDBigInteger;+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->multByPow52(II)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FDBigInteger;->multDiffMe(JLjdk/internal/math/FDBigInteger;)J
 HSPLjdk/internal/math/FDBigInteger;->quoRemIteration(Ljdk/internal/math/FDBigInteger;)I
 HSPLjdk/internal/math/FDBigInteger;->rightInplaceSub(Ljdk/internal/math/FDBigInteger;)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FDBigInteger;->size()I
 HSPLjdk/internal/math/FDBigInteger;->trimLeadingZeros()V
-HSPLjdk/internal/math/FDBigInteger;->valueOfMulPow52(JII)Ljdk/internal/math/FDBigInteger;+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->valueOfMulPow52(JII)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FDBigInteger;->valueOfPow2(I)Ljdk/internal/math/FDBigInteger;
-HSPLjdk/internal/math/FDBigInteger;->valueOfPow52(II)Ljdk/internal/math/FDBigInteger;+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->valueOfPow52(II)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FloatingDecimal$1;->initialValue()Ljava/lang/Object;
 HSPLjdk/internal/math/FloatingDecimal$1;->initialValue()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
 HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;-><init>(ZI[CI)V
 HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->doubleValue()D+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
-HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->floatValue()F+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->floatValue()F
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->-$$Nest$mdtoa(Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;IJIZ)V
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->-$$Nest$msetSign(Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;Z)V
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;-><init>()V
-HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->appendTo(Ljava/lang/Appendable;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
+HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->appendTo(Ljava/lang/Appendable;)V
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->developLongDigits(IJI)V
-HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->dtoa(IJIZ)V+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->dtoa(IJIZ)V
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->estimateDecExp(JI)I
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->getChars([C)I
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->getDecimalExponent()I
@@ -8276,12 +8409,12 @@
 HSPLjdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer;->doubleValue()D
 HSPLjdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer;->floatValue()F
 HSPLjdk/internal/math/FloatingDecimal;->appendTo(FLjava/lang/Appendable;)V
-HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIBuffer()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;+]Ljava/lang/ThreadLocal;Ljdk/internal/math/FloatingDecimal$1;
+HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIBuffer()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
 HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(D)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
 HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(DZ)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
 HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(F)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
-HSPLjdk/internal/math/FloatingDecimal;->parseDouble(Ljava/lang/String;)D+]Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;
-HSPLjdk/internal/math/FloatingDecimal;->parseFloat(Ljava/lang/String;)F+]Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;
+HSPLjdk/internal/math/FloatingDecimal;->parseDouble(Ljava/lang/String;)D
+HSPLjdk/internal/math/FloatingDecimal;->parseFloat(Ljava/lang/String;)F
 HSPLjdk/internal/math/FloatingDecimal;->readJavaFormatString(Ljava/lang/String;)Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjdk/internal/math/FloatingDecimal;->toJavaFormatString(D)Ljava/lang/String;
 HSPLjdk/internal/math/FloatingDecimal;->toJavaFormatString(F)Ljava/lang/String;
@@ -8292,15 +8425,19 @@
 HSPLjdk/internal/math/FormattedFloatingDecimal$Form;-><init>(Ljava/lang/String;I)V
 HSPLjdk/internal/math/FormattedFloatingDecimal$Form;->values()[Ljdk/internal/math/FormattedFloatingDecimal$Form;
 HSPLjdk/internal/math/FormattedFloatingDecimal;-><clinit>()V
-HSPLjdk/internal/math/FormattedFloatingDecimal;-><init>(ILjdk/internal/math/FormattedFloatingDecimal$Form;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;)V+]Ljdk/internal/math/FormattedFloatingDecimal$Form;Ljdk/internal/math/FormattedFloatingDecimal$Form;]Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
+HSPLjdk/internal/math/FormattedFloatingDecimal;-><init>(ILjdk/internal/math/FormattedFloatingDecimal$Form;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;)V
 HSPLjdk/internal/math/FormattedFloatingDecimal;->applyPrecision(I[CII)I
 HSPLjdk/internal/math/FormattedFloatingDecimal;->create(ZI)[C
 HSPLjdk/internal/math/FormattedFloatingDecimal;->fillDecimal(I[CIIZ)V
-HSPLjdk/internal/math/FormattedFloatingDecimal;->getBuffer()[C+]Ljava/lang/ThreadLocal;Ljdk/internal/math/FormattedFloatingDecimal$1;
+HSPLjdk/internal/math/FormattedFloatingDecimal;->getBuffer()[C
+HSPLjdk/internal/math/FormattedFloatingDecimal;->getExponent()[C
+HSPLjdk/internal/math/FormattedFloatingDecimal;->getExponentRounded()I
 HSPLjdk/internal/math/FormattedFloatingDecimal;->getMantissa()[C
 HSPLjdk/internal/math/FormattedFloatingDecimal;->valueOf(DILjdk/internal/math/FormattedFloatingDecimal$Form;)Ljdk/internal/math/FormattedFloatingDecimal;
+HSPLjdk/internal/misc/Unsafe;->compareAndSetObject(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z
 HSPLjdk/internal/misc/Unsafe;->getAndAddInt(Ljava/lang/Object;JI)I
 HSPLjdk/internal/misc/Unsafe;->getAndAddLong(Ljava/lang/Object;JJ)J
+HSPLjdk/internal/misc/Unsafe;->getAndBitwiseAndInt(Ljava/lang/Object;JI)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjdk/internal/misc/Unsafe;->getAndSetInt(Ljava/lang/Object;JI)I
 HSPLjdk/internal/misc/Unsafe;->getAndSetLong(Ljava/lang/Object;JJ)J
 HSPLjdk/internal/misc/Unsafe;->getAndSetObject(Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object;
@@ -8308,17 +8445,32 @@
 HSPLjdk/internal/misc/Unsafe;->getIntUnaligned(Ljava/lang/Object;J)I
 HSPLjdk/internal/misc/Unsafe;->getLongAcquire(Ljava/lang/Object;J)J
 HSPLjdk/internal/misc/Unsafe;->getLongUnaligned(Ljava/lang/Object;J)J
+HSPLjdk/internal/misc/Unsafe;->getObject(Ljava/lang/Object;J)Ljava/lang/Object;
 HSPLjdk/internal/misc/Unsafe;->getObjectAcquire(Ljava/lang/Object;J)Ljava/lang/Object;
+HSPLjdk/internal/misc/Unsafe;->getObjectVolatile(Ljava/lang/Object;J)Ljava/lang/Object;
+HSPLjdk/internal/misc/Unsafe;->getReferenceAcquire(Ljava/lang/Object;J)Ljava/lang/Object;+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjdk/internal/misc/Unsafe;->getUnsafe()Ljdk/internal/misc/Unsafe;
 HSPLjdk/internal/misc/Unsafe;->makeLong(II)J
+HSPLjdk/internal/misc/Unsafe;->objectFieldOffset(Ljava/lang/Class;Ljava/lang/String;)J
 HSPLjdk/internal/misc/Unsafe;->objectFieldOffset(Ljava/lang/reflect/Field;)J
+HSPLjdk/internal/misc/Unsafe;->pickPos(II)I
+HSPLjdk/internal/misc/Unsafe;->putIntOpaque(Ljava/lang/Object;JI)V
 HSPLjdk/internal/misc/Unsafe;->putIntRelease(Ljava/lang/Object;JI)V
 HSPLjdk/internal/misc/Unsafe;->putLongRelease(Ljava/lang/Object;JJ)V
+HSPLjdk/internal/misc/Unsafe;->putObject(Ljava/lang/Object;JLjava/lang/Object;)V
 HSPLjdk/internal/misc/Unsafe;->putObjectRelease(Ljava/lang/Object;JLjava/lang/Object;)V
+HSPLjdk/internal/misc/Unsafe;->putObjectVolatile(Ljava/lang/Object;JLjava/lang/Object;)V
+HSPLjdk/internal/misc/Unsafe;->putReferenceOpaque(Ljava/lang/Object;JLjava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjdk/internal/misc/Unsafe;->putReferenceRelease(Ljava/lang/Object;JLjava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjdk/internal/misc/Unsafe;->toUnsignedLong(I)J
+HSPLjdk/internal/misc/Unsafe;->weakCompareAndSetInt(Ljava/lang/Object;JII)Z
+HSPLjdk/internal/misc/Unsafe;->weakCompareAndSetReference(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjdk/internal/misc/VM;->getSavedProperty(Ljava/lang/String;)Ljava/lang/String;
 HSPLjdk/internal/reflect/Reflection;->getCallerClass()Ljava/lang/Class;
 HSPLjdk/internal/util/ArraysSupport;->mismatch([B[BI)I
 HSPLjdk/internal/util/ArraysSupport;->mismatch([FI[FII)I
 HSPLjdk/internal/util/ArraysSupport;->mismatch([I[II)I
+HSPLjdk/internal/util/ArraysSupport;->mismatch([J[JI)I
 HSPLjdk/internal/util/ArraysSupport;->mismatch([Z[ZI)I
 HSPLjdk/internal/util/ArraysSupport;->vectorizedMismatch(Ljava/lang/Object;JLjava/lang/Object;JII)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjdk/internal/util/Preconditions;->checkFromIndexSize(IIILjava/util/function/BiFunction;)I
@@ -8326,7 +8478,7 @@
 HSPLlibcore/content/type/MimeMap$Builder$Element;-><init>(Ljava/lang/String;Z)V
 HSPLlibcore/content/type/MimeMap$Builder$Element;->ofExtensionSpec(Ljava/lang/String;)Llibcore/content/type/MimeMap$Builder$Element;
 HSPLlibcore/content/type/MimeMap$Builder$Element;->ofMimeSpec(Ljava/lang/String;)Llibcore/content/type/MimeMap$Builder$Element;
-HSPLlibcore/content/type/MimeMap$Builder;->addMimeMapping(Ljava/lang/String;Ljava/util/List;)Llibcore/content/type/MimeMap$Builder;+]Ljava/util/List;Ljava/util/ArrayList$SubList;]Ljava/util/Iterator;Ljava/util/ArrayList$SubList$1;
+HSPLlibcore/content/type/MimeMap$Builder;->addMimeMapping(Ljava/lang/String;Ljava/util/List;)Llibcore/content/type/MimeMap$Builder;
 HSPLlibcore/content/type/MimeMap$Builder;->maybePut(Ljava/util/Map;Llibcore/content/type/MimeMap$Builder$Element;Ljava/lang/String;)Ljava/lang/String;
 HSPLlibcore/content/type/MimeMap$MemoizingSupplier;->get()Ljava/lang/Object;
 HSPLlibcore/content/type/MimeMap;-><init>(Ljava/util/Map;Ljava/util/Map;)V
@@ -8344,8 +8496,10 @@
 HSPLlibcore/icu/DecimalFormatData;->getExponentSeparator()Ljava/lang/String;
 HSPLlibcore/icu/DecimalFormatData;->getGroupingSeparator()C
 HSPLlibcore/icu/DecimalFormatData;->getInfinity()Ljava/lang/String;
-HSPLlibcore/icu/DecimalFormatData;->getInstance(Ljava/util/Locale;)Llibcore/icu/DecimalFormatData;+]Ljava/util/Locale;Ljava/util/Locale;]Ljava/util/concurrent/ConcurrentHashMap;Ljava/util/concurrent/ConcurrentHashMap;
+HSPLlibcore/icu/DecimalFormatData;->getInstance(Ljava/util/Locale;)Llibcore/icu/DecimalFormatData;
 HSPLlibcore/icu/DecimalFormatData;->getMinusSign()Ljava/lang/String;
+HSPLlibcore/icu/DecimalFormatData;->getMonetaryGroupSeparator()Ljava/lang/String;
+HSPLlibcore/icu/DecimalFormatData;->getMonetarySeparator()Ljava/lang/String;
 HSPLlibcore/icu/DecimalFormatData;->getNaN()Ljava/lang/String;
 HSPLlibcore/icu/DecimalFormatData;->getNumberPattern()Ljava/lang/String;
 HSPLlibcore/icu/DecimalFormatData;->getPatternSeparator()C
@@ -8366,19 +8520,19 @@
 HSPLlibcore/icu/ICU;->transformIcuDateTimePattern(Ljava/lang/String;)Ljava/lang/String;
 HSPLlibcore/icu/ICU;->transformIcuDateTimePattern_forJavaText(Ljava/lang/String;)Ljava/lang/String;
 HSPLlibcore/icu/LocaleData;->get(Ljava/util/Locale;)Llibcore/icu/LocaleData;
-HSPLlibcore/icu/LocaleData;->getCompatibleLocaleForBug159514442(Ljava/util/Locale;)Ljava/util/Locale;
+HSPLlibcore/icu/LocaleData;->getCompatibleLocaleForBug159514442(Ljava/util/Locale;)Ljava/util/Locale;+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;]Ljava/util/Locale;Ljava/util/Locale;
 HSPLlibcore/icu/LocaleData;->initLocaleData(Ljava/util/Locale;)Llibcore/icu/LocaleData;
 HSPLlibcore/icu/LocaleData;->initializeCalendarData(Ljava/util/Locale;)V
 HSPLlibcore/icu/LocaleData;->initializeDateFormatData(Ljava/util/Locale;)V
 HSPLlibcore/icu/LocaleData;->mapInvalidAndNullLocales(Ljava/util/Locale;)Ljava/util/Locale;
 HSPLlibcore/icu/SimpleDateFormatData;->getDateFormat(I)Ljava/lang/String;
-HSPLlibcore/icu/SimpleDateFormatData;->getInstance(Ljava/util/Locale;)Llibcore/icu/SimpleDateFormatData;+]Ljava/util/Locale;Ljava/util/Locale;]Ljava/util/concurrent/ConcurrentHashMap;Ljava/util/concurrent/ConcurrentHashMap;
-HSPLlibcore/icu/SimpleDateFormatData;->getTimeFormat(I)Ljava/lang/String;+]Ljava/lang/Boolean;Ljava/lang/Boolean;
+HSPLlibcore/icu/SimpleDateFormatData;->getInstance(Ljava/util/Locale;)Llibcore/icu/SimpleDateFormatData;
+HSPLlibcore/icu/SimpleDateFormatData;->getTimeFormat(I)Ljava/lang/String;
 HSPLlibcore/internal/StringPool;-><init>()V
 HSPLlibcore/internal/StringPool;->contentEquals(Ljava/lang/String;[CII)Z
 HSPLlibcore/internal/StringPool;->get([CII)Ljava/lang/String;
 HSPLlibcore/io/BlockGuardOs;->accept(Ljava/io/FileDescriptor;Ljava/net/SocketAddress;)Ljava/io/FileDescriptor;
-HSPLlibcore/io/BlockGuardOs;->access(Ljava/lang/String;I)Z
+HSPLlibcore/io/BlockGuardOs;->access(Ljava/lang/String;I)Z+]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
 HSPLlibcore/io/BlockGuardOs;->android_getaddrinfo(Ljava/lang/String;Landroid/system/StructAddrinfo;I)[Ljava/net/InetAddress;
 HSPLlibcore/io/BlockGuardOs;->chmod(Ljava/lang/String;I)V
 HSPLlibcore/io/BlockGuardOs;->close(Ljava/io/FileDescriptor;)V
@@ -8400,7 +8554,7 @@
 HSPLlibcore/io/BlockGuardOs;->open(Ljava/lang/String;II)Ljava/io/FileDescriptor;
 HSPLlibcore/io/BlockGuardOs;->poll([Landroid/system/StructPollfd;I)I
 HSPLlibcore/io/BlockGuardOs;->posix_fallocate(Ljava/io/FileDescriptor;JJ)V
-HSPLlibcore/io/BlockGuardOs;->pread(Ljava/io/FileDescriptor;[BIIJ)I+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLlibcore/io/BlockGuardOs;->pread(Ljava/io/FileDescriptor;[BIIJ)I
 HSPLlibcore/io/BlockGuardOs;->read(Ljava/io/FileDescriptor;[BII)I+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
 HSPLlibcore/io/BlockGuardOs;->readlink(Ljava/lang/String;)Ljava/lang/String;
 HSPLlibcore/io/BlockGuardOs;->recvfrom(Ljava/io/FileDescriptor;[BIIILjava/net/InetSocketAddress;)I
@@ -8419,12 +8573,12 @@
 HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection;->connect()V
 HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection;->getInputStream()Ljava/io/InputStream;
 HSPLlibcore/io/ClassPathURLStreamHandler;-><init>(Ljava/lang/String;)V
-HSPLlibcore/io/ClassPathURLStreamHandler;->getEntryUrlOrNull(Ljava/lang/String;)Ljava/net/URL;
+HSPLlibcore/io/ClassPathURLStreamHandler;->getEntryUrlOrNull(Ljava/lang/String;)Ljava/net/URL;+]Ljava/util/jar/JarFile;Ljava/util/jar/JarFile;
 HSPLlibcore/io/ClassPathURLStreamHandler;->isEntryStored(Ljava/lang/String;)Z
 HSPLlibcore/io/ClassPathURLStreamHandler;->openConnection(Ljava/net/URL;)Ljava/net/URLConnection;
 HSPLlibcore/io/ForwardingOs;-><init>(Llibcore/io/Os;)V
 HSPLlibcore/io/ForwardingOs;->accept(Ljava/io/FileDescriptor;Ljava/net/SocketAddress;)Ljava/io/FileDescriptor;
-HSPLlibcore/io/ForwardingOs;->access(Ljava/lang/String;I)Z
+HSPLlibcore/io/ForwardingOs;->access(Ljava/lang/String;I)Z+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
 HSPLlibcore/io/ForwardingOs;->android_fdsan_exchange_owner_tag(Ljava/io/FileDescriptor;JJ)V
 HSPLlibcore/io/ForwardingOs;->android_getaddrinfo(Ljava/lang/String;Landroid/system/StructAddrinfo;I)[Ljava/net/InetAddress;
 HSPLlibcore/io/ForwardingOs;->bind(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V
@@ -8445,11 +8599,11 @@
 HSPLlibcore/io/ForwardingOs;->getnameinfo(Ljava/net/InetAddress;I)Ljava/lang/String;
 HSPLlibcore/io/ForwardingOs;->getpeername(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;
 HSPLlibcore/io/ForwardingOs;->getpgid(I)I
-HSPLlibcore/io/ForwardingOs;->getpid()I
+HSPLlibcore/io/ForwardingOs;->getpid()I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
 HSPLlibcore/io/ForwardingOs;->getsockname(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;
 HSPLlibcore/io/ForwardingOs;->getsockoptInt(Ljava/io/FileDescriptor;II)I
 HSPLlibcore/io/ForwardingOs;->getsockoptLinger(Ljava/io/FileDescriptor;II)Landroid/system/StructLinger;
-HSPLlibcore/io/ForwardingOs;->gettid()I
+HSPLlibcore/io/ForwardingOs;->gettid()I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
 HSPLlibcore/io/ForwardingOs;->getuid()I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
 HSPLlibcore/io/ForwardingOs;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B
 HSPLlibcore/io/ForwardingOs;->if_nametoindex(Ljava/lang/String;)I
@@ -8487,7 +8641,7 @@
 HSPLlibcore/io/IoBridge;->bind(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V
 HSPLlibcore/io/IoBridge;->booleanFromInt(I)Z
 HSPLlibcore/io/IoBridge;->booleanToInt(Z)I
-HSPLlibcore/io/IoBridge;->closeAndSignalBlockedThreads(Ljava/io/FileDescriptor;)V+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;]Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;
+HSPLlibcore/io/IoBridge;->closeAndSignalBlockedThreads(Ljava/io/FileDescriptor;)V
 HSPLlibcore/io/IoBridge;->connect(Ljava/io/FileDescriptor;Ljava/net/InetAddress;II)V
 HSPLlibcore/io/IoBridge;->connectErrno(Ljava/io/FileDescriptor;Ljava/net/InetAddress;II)V
 HSPLlibcore/io/IoBridge;->createMessageForException(Ljava/io/FileDescriptor;Ljava/net/InetAddress;IILjava/lang/Exception;)Ljava/lang/String;
@@ -8554,7 +8708,7 @@
 HSPLlibcore/net/http/HttpURLConnectionFactory;->openConnection(Ljava/net/URL;Ljavax/net/SocketFactory;Ljava/net/Proxy;)Ljava/net/URLConnection;
 HSPLlibcore/net/http/HttpURLConnectionFactory;->setDns(Llibcore/net/http/Dns;)V
 HSPLlibcore/net/http/HttpURLConnectionFactory;->setNewConnectionPool(IJLjava/util/concurrent/TimeUnit;)V
-HSPLlibcore/reflect/AnnotationFactory;-><init>(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)V
+HSPLlibcore/reflect/AnnotationFactory;-><init>(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)V+]Llibcore/reflect/AnnotationMember;Llibcore/reflect/AnnotationMember;
 HSPLlibcore/reflect/AnnotationFactory;->createAnnotation(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)Ljava/lang/annotation/Annotation;
 HSPLlibcore/reflect/AnnotationFactory;->getElementsDescription(Ljava/lang/Class;)[Llibcore/reflect/AnnotationMember;
 HSPLlibcore/reflect/AnnotationFactory;->invoke(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;]Llibcore/reflect/AnnotationMember;Llibcore/reflect/AnnotationMember;
@@ -8565,26 +8719,26 @@
 HSPLlibcore/reflect/AnnotationMember;->validateValue()Ljava/lang/Object;+]Ljava/lang/Object;missing_types]Llibcore/reflect/AnnotationMember;Llibcore/reflect/AnnotationMember;
 HSPLlibcore/reflect/GenericArrayTypeImpl;->getGenericComponentType()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/GenericSignatureParser;-><init>(Ljava/lang/ClassLoader;)V
-HSPLlibcore/reflect/GenericSignatureParser;->expect(C)V
+HSPLlibcore/reflect/GenericSignatureParser;->expect(C)V+]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/GenericSignatureParser;->isStopSymbol(C)Z
 HSPLlibcore/reflect/GenericSignatureParser;->parseClassSignature()V
-HSPLlibcore/reflect/GenericSignatureParser;->parseClassTypeSignature()Ljava/lang/reflect/Type;
+HSPLlibcore/reflect/GenericSignatureParser;->parseClassTypeSignature()Ljava/lang/reflect/Type;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/GenericSignatureParser;->parseFieldTypeSignature()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/GenericSignatureParser;->parseForClass(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V
-HSPLlibcore/reflect/GenericSignatureParser;->parseForConstructor(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;[Ljava/lang/Class;)V+]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor;
-HSPLlibcore/reflect/GenericSignatureParser;->parseForField(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V
+HSPLlibcore/reflect/GenericSignatureParser;->parseForConstructor(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;[Ljava/lang/Class;)V
+HSPLlibcore/reflect/GenericSignatureParser;->parseForField(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V+]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/GenericSignatureParser;->parseForMethod(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;[Ljava/lang/Class;)V
 HSPLlibcore/reflect/GenericSignatureParser;->parseFormalTypeParameter()Llibcore/reflect/TypeVariableImpl;
 HSPLlibcore/reflect/GenericSignatureParser;->parseMethodTypeSignature([Ljava/lang/Class;)V
 HSPLlibcore/reflect/GenericSignatureParser;->parseOptFormalTypeParameters()V
-HSPLlibcore/reflect/GenericSignatureParser;->parseOptTypeArguments()Llibcore/reflect/ListOfTypes;
+HSPLlibcore/reflect/GenericSignatureParser;->parseOptTypeArguments()Llibcore/reflect/ListOfTypes;+]Llibcore/reflect/ListOfTypes;Llibcore/reflect/ListOfTypes;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/GenericSignatureParser;->parseReturnType()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/GenericSignatureParser;->parseTypeArgument()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/GenericSignatureParser;->parseTypeSignature()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/GenericSignatureParser;->parseTypeVariableSignature()Llibcore/reflect/TypeVariableImpl;
-HSPLlibcore/reflect/GenericSignatureParser;->scanIdentifier()V
+HSPLlibcore/reflect/GenericSignatureParser;->scanIdentifier()V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/GenericSignatureParser;->scanSymbol()V
-HSPLlibcore/reflect/GenericSignatureParser;->setInput(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V
+HSPLlibcore/reflect/GenericSignatureParser;->setInput(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/ListOfTypes;-><init>(I)V
 HSPLlibcore/reflect/ListOfTypes;-><init>([Ljava/lang/reflect/Type;)V
 HSPLlibcore/reflect/ListOfTypes;->add(Ljava/lang/reflect/Type;)V
@@ -8595,16 +8749,17 @@
 HSPLlibcore/reflect/ListOfVariables;->add(Ljava/lang/reflect/TypeVariable;)V
 HSPLlibcore/reflect/ListOfVariables;->getArray()[Ljava/lang/reflect/TypeVariable;
 HSPLlibcore/reflect/ParameterizedTypeImpl;-><init>(Llibcore/reflect/ParameterizedTypeImpl;Ljava/lang/String;Llibcore/reflect/ListOfTypes;Ljava/lang/ClassLoader;)V
+HSPLlibcore/reflect/ParameterizedTypeImpl;->equals(Ljava/lang/Object;)Z
 HSPLlibcore/reflect/ParameterizedTypeImpl;->getActualTypeArguments()[Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/ParameterizedTypeImpl;->getOwnerType()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/ParameterizedTypeImpl;->getRawType()Ljava/lang/Class;
-HSPLlibcore/reflect/ParameterizedTypeImpl;->getRawType()Ljava/lang/reflect/Type;
+HSPLlibcore/reflect/ParameterizedTypeImpl;->getRawType()Ljava/lang/reflect/Type;+]Llibcore/reflect/ParameterizedTypeImpl;Llibcore/reflect/ParameterizedTypeImpl;
 HSPLlibcore/reflect/ParameterizedTypeImpl;->getResolvedType()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/TypeVariableImpl;-><init>(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V
 HSPLlibcore/reflect/TypeVariableImpl;-><init>(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;Llibcore/reflect/ListOfTypes;)V
 HSPLlibcore/reflect/TypeVariableImpl;->equals(Ljava/lang/Object;)Z
 HSPLlibcore/reflect/TypeVariableImpl;->findFormalVar(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)Ljava/lang/reflect/TypeVariable;
-HSPLlibcore/reflect/TypeVariableImpl;->getBounds()[Ljava/lang/reflect/Type;+]Llibcore/reflect/ListOfTypes;Llibcore/reflect/ListOfTypes;]Llibcore/reflect/TypeVariableImpl;Llibcore/reflect/TypeVariableImpl;][Ljava/lang/reflect/Type;[Ljava/lang/reflect/Type;
+HSPLlibcore/reflect/TypeVariableImpl;->getBounds()[Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/TypeVariableImpl;->getGenericDeclaration()Ljava/lang/reflect/GenericDeclaration;
 HSPLlibcore/reflect/TypeVariableImpl;->getName()Ljava/lang/String;
 HSPLlibcore/reflect/TypeVariableImpl;->hashCode()I
@@ -8648,17 +8803,17 @@
 HSPLlibcore/util/NativeAllocationRegistry;->createNonmalloced(Ljava/lang/ClassLoader;JJ)Llibcore/util/NativeAllocationRegistry;
 HSPLlibcore/util/NativeAllocationRegistry;->registerNativeAllocation(J)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;
 HSPLlibcore/util/NativeAllocationRegistry;->registerNativeAllocation(Ljava/lang/Object;J)Ljava/lang/Runnable;+]Llibcore/util/NativeAllocationRegistry$CleanerThunk;Llibcore/util/NativeAllocationRegistry$CleanerThunk;
-HSPLlibcore/util/NativeAllocationRegistry;->registerNativeFree(J)V
+HSPLlibcore/util/NativeAllocationRegistry;->registerNativeFree(J)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;
 HSPLlibcore/util/SneakyThrow;->sneakyThrow(Ljava/lang/Throwable;)V
 HSPLlibcore/util/SneakyThrow;->sneakyThrow_(Ljava/lang/Throwable;)V
 HSPLlibcore/util/XmlObjectFactory;->newXmlPullParser()Lorg/xmlpull/v1/XmlPullParser;
-HSPLlibcore/util/ZoneInfo;-><init>(Lcom/android/i18n/timezone/ZoneInfoData;IZ)V
+HSPLlibcore/util/ZoneInfo;-><init>(Lcom/android/i18n/timezone/ZoneInfoData;IZ)V+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;]Llibcore/util/ZoneInfo;Llibcore/util/ZoneInfo;
 HSPLlibcore/util/ZoneInfo;->clone()Ljava/lang/Object;
 HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;)Llibcore/util/ZoneInfo;
 HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;J)Llibcore/util/ZoneInfo;
 HSPLlibcore/util/ZoneInfo;->getDSTSavings()I
 HSPLlibcore/util/ZoneInfo;->getOffset(J)I
-HSPLlibcore/util/ZoneInfo;->getOffsetsByUtcTime(J[I)I
+HSPLlibcore/util/ZoneInfo;->getOffsetsByUtcTime(J[I)I+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;
 HSPLlibcore/util/ZoneInfo;->getRawOffset()I
 HSPLlibcore/util/ZoneInfo;->hasSameRules(Ljava/util/TimeZone;)Z
 HSPLlibcore/util/ZoneInfo;->hashCode()I
@@ -8681,9 +8836,12 @@
 HSPLorg/apache/harmony/xml/ExpatParser;->startDocument()V
 HSPLorg/apache/harmony/xml/ExpatParser;->startElement(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JI)V
 HSPLorg/apache/harmony/xml/ExpatReader;-><init>()V
-HSPLorg/apache/harmony/xml/ExpatReader;->parse(Lorg/xml/sax/InputSource;)V+]Lorg/xml/sax/InputSource;Lorg/xml/sax/InputSource;
+HSPLorg/apache/harmony/xml/ExpatReader;->parse(Lorg/xml/sax/InputSource;)V
 HSPLorg/apache/harmony/xml/ExpatReader;->setContentHandler(Lorg/xml/sax/ContentHandler;)V
-HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;-><init>(Lorg/apache/harmony/xml/dom/DocumentImpl;Ljava/lang/String;)V+]Lorg/apache/harmony/xml/dom/CharacterDataImpl;Lorg/apache/harmony/xml/dom/TextImpl;,Lorg/apache/harmony/xml/dom/CommentImpl;
+HSPLorg/apache/harmony/xml/dom/AttrImpl;->getNodeType()S
+HSPLorg/apache/harmony/xml/dom/AttrImpl;->getOwnerElement()Lorg/w3c/dom/Element;
+HSPLorg/apache/harmony/xml/dom/AttrImpl;->setValue(Ljava/lang/String;)V
+HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;-><init>(Lorg/apache/harmony/xml/dom/DocumentImpl;Ljava/lang/String;)V
 HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;->getData()Ljava/lang/String;
 HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;->getNodeValue()Ljava/lang/String;
 HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;->setData(Ljava/lang/String;)V
@@ -8716,7 +8874,7 @@
 HSPLorg/apache/harmony/xml/dom/LeafNodeImpl;->isParentOf(Lorg/w3c/dom/Node;)Z
 HSPLorg/apache/harmony/xml/dom/NodeImpl;-><init>(Lorg/apache/harmony/xml/dom/DocumentImpl;)V
 HSPLorg/apache/harmony/xml/dom/NodeImpl;->getTextContent()Ljava/lang/String;
-HSPLorg/apache/harmony/xml/dom/NodeImpl;->setName(Lorg/apache/harmony/xml/dom/NodeImpl;Ljava/lang/String;)V+]Lorg/apache/harmony/xml/dom/NodeImpl;Lorg/apache/harmony/xml/dom/AttrImpl;,Lorg/apache/harmony/xml/dom/ElementImpl;]Ljava/lang/String;Ljava/lang/String;
+HSPLorg/apache/harmony/xml/dom/NodeImpl;->setName(Lorg/apache/harmony/xml/dom/NodeImpl;Ljava/lang/String;)V
 HSPLorg/apache/harmony/xml/dom/NodeListImpl;-><init>()V
 HSPLorg/apache/harmony/xml/dom/NodeListImpl;->add(Lorg/apache/harmony/xml/dom/NodeImpl;)V
 HSPLorg/apache/harmony/xml/dom/NodeListImpl;->getLength()I
@@ -8727,9 +8885,9 @@
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderFactoryImpl;->newDocumentBuilder()Ljavax/xml/parsers/DocumentBuilder;
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;-><clinit>()V
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;-><init>()V
-HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->appendText(Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;ILjava/lang/String;)V+]Lorg/w3c/dom/Node;Lorg/apache/harmony/xml/dom/ElementImpl;,Lorg/apache/harmony/xml/dom/CommentImpl;,Lorg/apache/harmony/xml/dom/TextImpl;]Lorg/w3c/dom/Text;Lorg/apache/harmony/xml/dom/TextImpl;
-HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lcom/android/org/kxml2/io/KXmlParser;Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;I)V+]Lorg/w3c/dom/Node;Lorg/apache/harmony/xml/dom/ElementImpl;,Lorg/apache/harmony/xml/dom/DocumentImpl;]Lorg/w3c/dom/Element;Lorg/apache/harmony/xml/dom/ElementImpl;]Lcom/android/org/kxml2/io/KXmlParser;Lcom/android/org/kxml2/io/KXmlParser;]Lorg/w3c/dom/Attr;Lorg/apache/harmony/xml/dom/AttrImpl;]Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/apache/harmony/xml/dom/DocumentImpl;]Ljava/lang/String;Ljava/lang/String;
-HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lorg/xml/sax/InputSource;)Lorg/w3c/dom/Document;+]Lcom/android/org/kxml2/io/KXmlParser;Lcom/android/org/kxml2/io/KXmlParser;]Lorg/xml/sax/InputSource;Lorg/xml/sax/InputSource;]Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/apache/harmony/xml/dom/DocumentImpl;
+HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->appendText(Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;ILjava/lang/String;)V
+HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lcom/android/org/kxml2/io/KXmlParser;Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;I)V
+HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lorg/xml/sax/InputSource;)Lorg/w3c/dom/Document;
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->setCoalescing(Z)V
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->setIgnoreComments(Z)V
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->setIgnoreElementContentWhitespace(Z)V
@@ -8741,7 +8899,7 @@
 HSPLorg/json/JSON;->toInteger(Ljava/lang/Object;)Ljava/lang/Integer;
 HSPLorg/json/JSON;->toLong(Ljava/lang/Object;)Ljava/lang/Long;
 HSPLorg/json/JSON;->toString(Ljava/lang/Object;)Ljava/lang/String;
-HSPLorg/json/JSON;->typeMismatch(Ljava/lang/Object;Ljava/lang/String;)Lorg/json/JSONException;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Object;Ljava/lang/String;,Lorg/json/JSONObject$1;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLorg/json/JSON;->typeMismatch(Ljava/lang/Object;Ljava/lang/String;)Lorg/json/JSONException;
 HSPLorg/json/JSONArray;-><init>()V
 HSPLorg/json/JSONArray;-><init>(Ljava/lang/String;)V
 HSPLorg/json/JSONArray;-><init>(Ljava/util/Collection;)V
@@ -8816,20 +8974,20 @@
 HSPLorg/json/JSONStringer;->newline()V
 HSPLorg/json/JSONStringer;->object()Lorg/json/JSONStringer;
 HSPLorg/json/JSONStringer;->open(Lorg/json/JSONStringer$Scope;Ljava/lang/String;)Lorg/json/JSONStringer;
-HSPLorg/json/JSONStringer;->peek()Lorg/json/JSONStringer$Scope;
-HSPLorg/json/JSONStringer;->replaceTop(Lorg/json/JSONStringer$Scope;)V
+HSPLorg/json/JSONStringer;->peek()Lorg/json/JSONStringer$Scope;+]Ljava/util/List;Ljava/util/ArrayList;
+HSPLorg/json/JSONStringer;->replaceTop(Lorg/json/JSONStringer$Scope;)V+]Ljava/util/List;Ljava/util/ArrayList;
 HSPLorg/json/JSONStringer;->string(Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLorg/json/JSONStringer;->toString()Ljava/lang/String;
 HSPLorg/json/JSONStringer;->value(Ljava/lang/Object;)Lorg/json/JSONStringer;
 HSPLorg/json/JSONTokener;-><init>(Ljava/lang/String;)V
 HSPLorg/json/JSONTokener;->nextCleanInternal()I
-HSPLorg/json/JSONTokener;->nextString(C)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLorg/json/JSONTokener;->nextString(C)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;
 HSPLorg/json/JSONTokener;->nextToInternal(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
-HSPLorg/json/JSONTokener;->nextValue()Ljava/lang/Object;
-HSPLorg/json/JSONTokener;->readArray()Lorg/json/JSONArray;
+HSPLorg/json/JSONTokener;->nextValue()Ljava/lang/Object;+]Lorg/json/JSONTokener;Lorg/json/JSONTokener;
+HSPLorg/json/JSONTokener;->readArray()Lorg/json/JSONArray;+]Lorg/json/JSONTokener;Lorg/json/JSONTokener;]Lorg/json/JSONArray;Lorg/json/JSONArray;
 HSPLorg/json/JSONTokener;->readEscapeCharacter()C
-HSPLorg/json/JSONTokener;->readLiteral()Ljava/lang/Object;+]Ljava/lang/String;Ljava/lang/String;
-HSPLorg/json/JSONTokener;->readObject()Lorg/json/JSONObject;+]Lorg/json/JSONObject;Lorg/json/JSONObject;]Lorg/json/JSONTokener;Lorg/json/JSONTokener;
+HSPLorg/json/JSONTokener;->readLiteral()Ljava/lang/Object;
+HSPLorg/json/JSONTokener;->readObject()Lorg/json/JSONObject;
 HSPLorg/json/JSONTokener;->syntaxError(Ljava/lang/String;)Lorg/json/JSONException;
 HSPLorg/json/JSONTokener;->toString()Ljava/lang/String;
 HSPLorg/xml/sax/InputSource;-><init>(Ljava/io/InputStream;)V
@@ -8866,14 +9024,14 @@
 HSPLsun/misc/ASCIICaseInsensitiveComparator;->toLower(I)I
 HSPLsun/misc/Cleaner;-><init>(Ljava/lang/Object;Ljava/lang/Runnable;)V
 HSPLsun/misc/Cleaner;->add(Lsun/misc/Cleaner;)Lsun/misc/Cleaner;
-HSPLsun/misc/Cleaner;->clean()V
+HSPLsun/misc/Cleaner;->clean()V+]Ljava/lang/Runnable;Landroid/graphics/HardwareRenderer$DestroyContextRunnable;,Llibcore/util/NativeAllocationRegistry$CleanerThunk;
 HSPLsun/misc/Cleaner;->create(Ljava/lang/Object;Ljava/lang/Runnable;)Lsun/misc/Cleaner;
 HSPLsun/misc/Cleaner;->remove(Lsun/misc/Cleaner;)Z
 HSPLsun/misc/CompoundEnumeration;-><init>([Ljava/util/Enumeration;)V
 HSPLsun/misc/CompoundEnumeration;->hasMoreElements()Z
 HSPLsun/misc/CompoundEnumeration;->next()Z
 HSPLsun/misc/CompoundEnumeration;->nextElement()Ljava/lang/Object;
-HSPLsun/misc/IOUtils;->readFully(Ljava/io/InputStream;IZ)[B
+HSPLsun/misc/IOUtils;->readFully(Ljava/io/InputStream;IZ)[B+]Ljava/io/InputStream;Lsun/security/util/DerInputBuffer;,Ljava/io/ByteArrayInputStream;
 HSPLsun/misc/LRUCache;-><init>(I)V
 HSPLsun/misc/LRUCache;->forName(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLsun/misc/LRUCache;->moveToFront([Ljava/lang/Object;I)V
@@ -8925,7 +9083,7 @@
 HSPLsun/nio/ch/FileChannelImpl$Unmapper;-><init>(JJILjava/io/FileDescriptor;Lsun/nio/ch/FileChannelImpl$Unmapper-IA;)V
 HSPLsun/nio/ch/FileChannelImpl$Unmapper;->run()V
 HSPLsun/nio/ch/FileChannelImpl;-><init>(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZLjava/lang/Object;)V
-HSPLsun/nio/ch/FileChannelImpl;->ensureOpen()V+]Lsun/nio/ch/FileChannelImpl;Lsun/nio/ch/FileChannelImpl;
+HSPLsun/nio/ch/FileChannelImpl;->ensureOpen()V
 HSPLsun/nio/ch/FileChannelImpl;->fileLockTable()Lsun/nio/ch/FileLockTable;
 HSPLsun/nio/ch/FileChannelImpl;->finalize()V
 HSPLsun/nio/ch/FileChannelImpl;->force(Z)V
@@ -8936,7 +9094,7 @@
 HSPLsun/nio/ch/FileChannelImpl;->open(Ljava/io/FileDescriptor;Ljava/lang/String;ZZLjava/lang/Object;)Ljava/nio/channels/FileChannel;
 HSPLsun/nio/ch/FileChannelImpl;->open(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZLjava/lang/Object;)Ljava/nio/channels/FileChannel;
 HSPLsun/nio/ch/FileChannelImpl;->position()J
-HSPLsun/nio/ch/FileChannelImpl;->position(J)Ljava/nio/channels/FileChannel;+]Lsun/nio/ch/NativeThreadSet;Lsun/nio/ch/NativeThreadSet;]Lsun/nio/ch/FileChannelImpl;Lsun/nio/ch/FileChannelImpl;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLsun/nio/ch/FileChannelImpl;->position(J)Ljava/nio/channels/FileChannel;
 HSPLsun/nio/ch/FileChannelImpl;->read(Ljava/nio/ByteBuffer;)I
 HSPLsun/nio/ch/FileChannelImpl;->release(Lsun/nio/ch/FileLockImpl;)V
 HSPLsun/nio/ch/FileChannelImpl;->size()J
@@ -9055,7 +9213,7 @@
 HSPLsun/nio/ch/Util$1;->initialValue()Lsun/nio/ch/Util$BufferCache;
 HSPLsun/nio/ch/Util$3;-><init>(Ljava/util/Set;)V
 HSPLsun/nio/ch/Util$BufferCache;-><init>()V
-HSPLsun/nio/ch/Util$BufferCache;->get(I)Ljava/nio/ByteBuffer;+]Ljava/nio/ByteBuffer;Ljava/nio/DirectByteBuffer;
+HSPLsun/nio/ch/Util$BufferCache;->get(I)Ljava/nio/ByteBuffer;
 HSPLsun/nio/ch/Util$BufferCache;->isEmpty()Z
 HSPLsun/nio/ch/Util$BufferCache;->next(I)I
 HSPLsun/nio/ch/Util$BufferCache;->offerFirst(Ljava/nio/ByteBuffer;)Z
@@ -9094,11 +9252,11 @@
 HSPLsun/nio/cs/StreamEncoder;->implClose()V
 HSPLsun/nio/cs/StreamEncoder;->implFlush()V
 HSPLsun/nio/cs/StreamEncoder;->implFlushBuffer()V
-HSPLsun/nio/cs/StreamEncoder;->implWrite([CII)V
+HSPLsun/nio/cs/StreamEncoder;->implWrite([CII)V+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;
 HSPLsun/nio/cs/StreamEncoder;->write(I)V
 HSPLsun/nio/cs/StreamEncoder;->write(Ljava/lang/String;II)V
 HSPLsun/nio/cs/StreamEncoder;->write([CII)V
-HSPLsun/nio/cs/StreamEncoder;->writeBytes()V
+HSPLsun/nio/cs/StreamEncoder;->writeBytes()V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/io/OutputStream;Ljava/util/logging/FileHandler$MeteredStream;,Ljava/io/FileOutputStream;
 HSPLsun/nio/cs/ThreadLocalCoders$1;->create(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLsun/nio/cs/ThreadLocalCoders$1;->hasName(Ljava/lang/Object;Ljava/lang/Object;)Z
 HSPLsun/nio/cs/ThreadLocalCoders$2;->create(Ljava/lang/Object;)Ljava/lang/Object;
@@ -9122,7 +9280,7 @@
 HSPLsun/nio/fs/NativeBuffer;->setOwner(Ljava/lang/Object;)V
 HSPLsun/nio/fs/NativeBuffer;->size()I
 HSPLsun/nio/fs/NativeBuffers;->allocNativeBuffer(I)Lsun/nio/fs/NativeBuffer;
-HSPLsun/nio/fs/NativeBuffers;->copyCStringToNativeBuffer([BLsun/nio/fs/NativeBuffer;)V+]Lsun/nio/fs/NativeBuffer;Lsun/nio/fs/NativeBuffer;]Lsun/misc/Unsafe;Lsun/misc/Unsafe;
+HSPLsun/nio/fs/NativeBuffers;->copyCStringToNativeBuffer([BLsun/nio/fs/NativeBuffer;)V
 HSPLsun/nio/fs/NativeBuffers;->getNativeBufferFromCache(I)Lsun/nio/fs/NativeBuffer;
 HSPLsun/nio/fs/NativeBuffers;->releaseNativeBuffer(Lsun/nio/fs/NativeBuffer;)V
 HSPLsun/nio/fs/UnixChannelFactory$1;-><clinit>()V
@@ -9135,6 +9293,8 @@
 HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;-><init>(Lsun/nio/fs/UnixDirectoryStream;Ljava/nio/file/DirectoryStream;)V
 HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->hasNext()Z
 HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->isSelfOrParent([B)Z
+HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->next()Ljava/lang/Object;
+HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->next()Ljava/nio/file/Path;
 HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->readNextEntry()Ljava/nio/file/Path;
 HSPLsun/nio/fs/UnixDirectoryStream;->-$$Nest$fgetdp(Lsun/nio/fs/UnixDirectoryStream;)J
 HSPLsun/nio/fs/UnixDirectoryStream;-><init>(Lsun/nio/fs/UnixPath;JLjava/nio/file/DirectoryStream$Filter;)V
@@ -9147,6 +9307,7 @@
 HSPLsun/nio/fs/UnixDirectoryStream;->writeLock()Ljava/util/concurrent/locks/Lock;
 HSPLsun/nio/fs/UnixException;-><init>(I)V
 HSPLsun/nio/fs/UnixException;->errno()I
+HSPLsun/nio/fs/UnixException;->fillInStackTrace()Ljava/lang/Throwable;
 HSPLsun/nio/fs/UnixException;->rethrowAsIOException(Lsun/nio/fs/UnixPath;)V
 HSPLsun/nio/fs/UnixException;->rethrowAsIOException(Lsun/nio/fs/UnixPath;Lsun/nio/fs/UnixPath;)V
 HSPLsun/nio/fs/UnixException;->translateToIOException(Ljava/lang/String;Ljava/lang/String;)Ljava/io/IOException;
@@ -9157,6 +9318,7 @@
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->creationTime()Ljava/nio/file/attribute/FileTime;
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->isDirectory()Z
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->isRegularFile()Z
+HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->isSymbolicLink()Z
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->lastAccessTime()Ljava/nio/file/attribute/FileTime;
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->lastModifiedTime()Ljava/nio/file/attribute/FileTime;
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->size()J
@@ -9191,7 +9353,7 @@
 HSPLsun/nio/fs/UnixNativeDispatcher;->lstat(Lsun/nio/fs/UnixPath;Lsun/nio/fs/UnixFileAttributes;)V
 HSPLsun/nio/fs/UnixNativeDispatcher;->open(Lsun/nio/fs/UnixPath;II)I
 HSPLsun/nio/fs/UnixNativeDispatcher;->openatSupported()Z
-HSPLsun/nio/fs/UnixNativeDispatcher;->stat(Lsun/nio/fs/UnixPath;Lsun/nio/fs/UnixFileAttributes;)V+]Lsun/nio/fs/NativeBuffer;Lsun/nio/fs/NativeBuffer;
+HSPLsun/nio/fs/UnixNativeDispatcher;->stat(Lsun/nio/fs/UnixPath;Lsun/nio/fs/UnixFileAttributes;)V
 HSPLsun/nio/fs/UnixPath;-><init>(Lsun/nio/fs/UnixFileSystem;Ljava/lang/String;)V
 HSPLsun/nio/fs/UnixPath;-><init>(Lsun/nio/fs/UnixFileSystem;[B)V
 HSPLsun/nio/fs/UnixPath;->asByteArray()[B
@@ -9207,7 +9369,7 @@
 HSPLsun/nio/fs/UnixPath;->getPathForExceptionMessage()Ljava/lang/String;
 HSPLsun/nio/fs/UnixPath;->initOffsets()V
 HSPLsun/nio/fs/UnixPath;->isEmpty()Z
-HSPLsun/nio/fs/UnixPath;->normalize(Ljava/lang/String;II)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLsun/nio/fs/UnixPath;->normalize(Ljava/lang/String;II)Ljava/lang/String;
 HSPLsun/nio/fs/UnixPath;->normalizeAndCheck(Ljava/lang/String;)Ljava/lang/String;
 HSPLsun/nio/fs/UnixPath;->resolve(Ljava/nio/file/Path;)Ljava/nio/file/Path;
 HSPLsun/nio/fs/UnixPath;->resolve(Ljava/nio/file/Path;)Lsun/nio/fs/UnixPath;
@@ -9235,11 +9397,11 @@
 HSPLsun/security/jca/GetInstance$Instance;-><init>(Ljava/security/Provider;Ljava/lang/Object;Lsun/security/jca/GetInstance$Instance-IA;)V
 HSPLsun/security/jca/GetInstance$Instance;->toArray()[Ljava/lang/Object;
 HSPLsun/security/jca/GetInstance;->checkSuperClass(Ljava/security/Provider$Service;Ljava/lang/Class;Ljava/lang/Class;)V
-HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)Lsun/security/jca/GetInstance$Instance;
+HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)Lsun/security/jca/GetInstance$Instance;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/security/jca/ProviderList;Lsun/security/jca/ProviderList;
 HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Object;)Lsun/security/jca/GetInstance$Instance;
 HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Lsun/security/jca/GetInstance$Instance;
 HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/security/Provider;)Lsun/security/jca/GetInstance$Instance;
-HSPLsun/security/jca/GetInstance;->getInstance(Ljava/security/Provider$Service;Ljava/lang/Class;)Lsun/security/jca/GetInstance$Instance;
+HSPLsun/security/jca/GetInstance;->getInstance(Ljava/security/Provider$Service;Ljava/lang/Class;)Lsun/security/jca/GetInstance$Instance;+]Ljava/security/Provider$Service;Ljava/security/Provider$Service;
 HSPLsun/security/jca/GetInstance;->getInstance(Ljava/security/Provider$Service;Ljava/lang/Class;Ljava/lang/Object;)Lsun/security/jca/GetInstance$Instance;
 HSPLsun/security/jca/GetInstance;->getService(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;
 HSPLsun/security/jca/GetInstance;->getService(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)Ljava/security/Provider$Service;
@@ -9259,18 +9421,19 @@
 HSPLsun/security/jca/ProviderList$ServiceList$1;->hasNext()Z
 HSPLsun/security/jca/ProviderList$ServiceList$1;->next()Ljava/lang/Object;
 HSPLsun/security/jca/ProviderList$ServiceList$1;->next()Ljava/security/Provider$Service;
+HSPLsun/security/jca/ProviderList$ServiceList;->-$$Nest$mtryGet(Lsun/security/jca/ProviderList$ServiceList;I)Ljava/security/Provider$Service;
 HSPLsun/security/jca/ProviderList$ServiceList;-><init>(Lsun/security/jca/ProviderList;Ljava/lang/String;Ljava/lang/String;)V
 HSPLsun/security/jca/ProviderList$ServiceList;->addService(Ljava/security/Provider$Service;)V
 HSPLsun/security/jca/ProviderList$ServiceList;->iterator()Ljava/util/Iterator;
-HSPLsun/security/jca/ProviderList$ServiceList;->tryGet(I)Ljava/security/Provider$Service;+]Lsun/security/jca/ProviderList;Lsun/security/jca/ProviderList;]Ljava/util/List;Ljava/util/ArrayList;]Ljava/security/Provider;missing_types
+HSPLsun/security/jca/ProviderList$ServiceList;->tryGet(I)Ljava/security/Provider$Service;
 HSPLsun/security/jca/ProviderList;->-$$Nest$fgetconfigs(Lsun/security/jca/ProviderList;)[Lsun/security/jca/ProviderConfig;
 HSPLsun/security/jca/ProviderList;-><init>([Lsun/security/jca/ProviderConfig;Z)V
 HSPLsun/security/jca/ProviderList;->getIndex(Ljava/lang/String;)I
 HSPLsun/security/jca/ProviderList;->getJarList([Ljava/lang/String;)Lsun/security/jca/ProviderList;
-HSPLsun/security/jca/ProviderList;->getProvider(I)Ljava/security/Provider;+]Lsun/security/jca/ProviderConfig;Lsun/security/jca/ProviderConfig;
+HSPLsun/security/jca/ProviderList;->getProvider(I)Ljava/security/Provider;
 HSPLsun/security/jca/ProviderList;->getProvider(Ljava/lang/String;)Ljava/security/Provider;
 HSPLsun/security/jca/ProviderList;->getProviderConfig(Ljava/lang/String;)Lsun/security/jca/ProviderConfig;
-HSPLsun/security/jca/ProviderList;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;
+HSPLsun/security/jca/ProviderList;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;+]Lsun/security/jca/ProviderList;Lsun/security/jca/ProviderList;]Ljava/security/Provider;missing_types
 HSPLsun/security/jca/ProviderList;->getServices(Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;
 HSPLsun/security/jca/ProviderList;->insertAt(Lsun/security/jca/ProviderList;Ljava/security/Provider;I)Lsun/security/jca/ProviderList;
 HSPLsun/security/jca/ProviderList;->loadAll()I
@@ -9404,7 +9567,7 @@
 HSPLsun/security/provider/certpath/PolicyChecker;->processParents(IZZLsun/security/provider/certpath/PolicyNodeImpl;Ljava/lang/String;Ljava/util/Set;Z)Z
 HSPLsun/security/provider/certpath/PolicyChecker;->processPolicies(ILjava/util/Set;IIIZLsun/security/provider/certpath/PolicyNodeImpl;Lsun/security/x509/X509CertImpl;Z)Lsun/security/provider/certpath/PolicyNodeImpl;
 HSPLsun/security/provider/certpath/PolicyChecker;->processPolicyMappings(Lsun/security/x509/X509CertImpl;IILsun/security/provider/certpath/PolicyNodeImpl;ZLjava/util/Set;)Lsun/security/provider/certpath/PolicyNodeImpl;
-HSPLsun/security/provider/certpath/PolicyNodeImpl;-><init>(Lsun/security/provider/certpath/PolicyNodeImpl;Ljava/lang/String;Ljava/util/Set;ZLjava/util/Set;Z)V
+HSPLsun/security/provider/certpath/PolicyNodeImpl;-><init>(Lsun/security/provider/certpath/PolicyNodeImpl;Ljava/lang/String;Ljava/util/Set;ZLjava/util/Set;Z)V+]Lsun/security/provider/certpath/PolicyNodeImpl;Lsun/security/provider/certpath/PolicyNodeImpl;
 HSPLsun/security/provider/certpath/PolicyNodeImpl;-><init>(Lsun/security/provider/certpath/PolicyNodeImpl;Lsun/security/provider/certpath/PolicyNodeImpl;)V
 HSPLsun/security/provider/certpath/PolicyNodeImpl;->addChild(Lsun/security/provider/certpath/PolicyNodeImpl;)V
 HSPLsun/security/provider/certpath/PolicyNodeImpl;->copyTree()Lsun/security/provider/certpath/PolicyNodeImpl;
@@ -9442,6 +9605,7 @@
 HSPLsun/security/util/AlgorithmDecomposer;->decomposeOneHash(Ljava/lang/String;)Ljava/util/Set;
 HSPLsun/security/util/AlgorithmDecomposer;->hasLoop(Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;)V
 HSPLsun/security/util/BitArray;-><init>(I[B)V
+HSPLsun/security/util/BitArray;-><init>(I[BI)V
 HSPLsun/security/util/BitArray;->get(I)Z
 HSPLsun/security/util/BitArray;->length()I
 HSPLsun/security/util/BitArray;->position(I)I
@@ -9457,7 +9621,7 @@
 HSPLsun/security/util/DerIndefLenConverter;->isLongForm(I)Z
 HSPLsun/security/util/DerInputBuffer;-><init>([B)V
 HSPLsun/security/util/DerInputBuffer;-><init>([BII)V
-HSPLsun/security/util/DerInputBuffer;->dup()Lsun/security/util/DerInputBuffer;
+HSPLsun/security/util/DerInputBuffer;->dup()Lsun/security/util/DerInputBuffer;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Ljava/lang/Object;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputBuffer;->getBigInteger(IZ)Ljava/math/BigInteger;
 HSPLsun/security/util/DerInputBuffer;->getBitString()[B
 HSPLsun/security/util/DerInputBuffer;->getBitString(I)[B
@@ -9470,24 +9634,24 @@
 HSPLsun/security/util/DerInputBuffer;->getUnalignedBitString()Lsun/security/util/BitArray;
 HSPLsun/security/util/DerInputBuffer;->peek()I
 HSPLsun/security/util/DerInputBuffer;->toByteArray()[B
-HSPLsun/security/util/DerInputBuffer;->truncate(I)V
+HSPLsun/security/util/DerInputBuffer;->truncate(I)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;-><init>(Lsun/security/util/DerInputBuffer;)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;-><init>([B)V
-HSPLsun/security/util/DerInputStream;->available()I
+HSPLsun/security/util/DerInputStream;->available()I+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;->getBigInteger()Ljava/math/BigInteger;
-HSPLsun/security/util/DerInputStream;->getByte()I
+HSPLsun/security/util/DerInputStream;->getByte()I+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;->getBytes([B)V
 HSPLsun/security/util/DerInputStream;->getDerValue()Lsun/security/util/DerValue;
 HSPLsun/security/util/DerInputStream;->getEnumerated()I
 HSPLsun/security/util/DerInputStream;->getGeneralizedTime()Ljava/util/Date;
 HSPLsun/security/util/DerInputStream;->getLength()I
 HSPLsun/security/util/DerInputStream;->getLength(ILjava/io/InputStream;)I
-HSPLsun/security/util/DerInputStream;->getLength(Ljava/io/InputStream;)I
+HSPLsun/security/util/DerInputStream;->getLength(Ljava/io/InputStream;)I+]Ljava/io/InputStream;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;->getOID()Lsun/security/util/ObjectIdentifier;
 HSPLsun/security/util/DerInputStream;->getOctetString()[B
 HSPLsun/security/util/DerInputStream;->getSequence(I)[Lsun/security/util/DerValue;
-HSPLsun/security/util/DerInputStream;->getSequence(IZ)[Lsun/security/util/DerValue;
-HSPLsun/security/util/DerInputStream;->getSet(I)[Lsun/security/util/DerValue;
+HSPLsun/security/util/DerInputStream;->getSequence(IZ)[Lsun/security/util/DerValue;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
+HSPLsun/security/util/DerInputStream;->getSet(I)[Lsun/security/util/DerValue;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DerInputStream;->getSet(IZ)[Lsun/security/util/DerValue;
 HSPLsun/security/util/DerInputStream;->getSet(IZZ)[Lsun/security/util/DerValue;
 HSPLsun/security/util/DerInputStream;->getUTCTime()Ljava/util/Date;
@@ -9497,7 +9661,7 @@
 HSPLsun/security/util/DerInputStream;->peekByte()I
 HSPLsun/security/util/DerInputStream;->readVector(I)[Lsun/security/util/DerValue;
 HSPLsun/security/util/DerInputStream;->readVector(IZ)[Lsun/security/util/DerValue;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Ljava/util/Vector;Ljava/util/Vector;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
-HSPLsun/security/util/DerInputStream;->reset()V
+HSPLsun/security/util/DerInputStream;->reset()V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;->subStream(IZ)Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DerInputStream;->toByteArray()[B
 HSPLsun/security/util/DerOutputStream;-><init>()V
@@ -9521,7 +9685,7 @@
 HSPLsun/security/util/DerValue;->getBitString()[B
 HSPLsun/security/util/DerValue;->getBoolean()Z
 HSPLsun/security/util/DerValue;->getData()Lsun/security/util/DerInputStream;
-HSPLsun/security/util/DerValue;->getDataBytes()[B
+HSPLsun/security/util/DerValue;->getDataBytes()[B+]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DerValue;->getIA5String()Ljava/lang/String;
 HSPLsun/security/util/DerValue;->getInteger()I
 HSPLsun/security/util/DerValue;->getOID()Lsun/security/util/ObjectIdentifier;
@@ -9537,7 +9701,7 @@
 HSPLsun/security/util/DerValue;->isPrintableStringChar(C)Z
 HSPLsun/security/util/DerValue;->length()I
 HSPLsun/security/util/DerValue;->resetTag(B)V
-HSPLsun/security/util/DerValue;->toByteArray()[B
+HSPLsun/security/util/DerValue;->toByteArray()[B+]Lsun/security/util/DerValue;Lsun/security/util/DerValue;]Lsun/security/util/DerOutputStream;Lsun/security/util/DerOutputStream;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DerValue;->toDerInputStream()Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DisabledAlgorithmConstraints$Constraints;->getConstraints(Ljava/lang/String;)Ljava/util/Set;
 HSPLsun/security/util/DisabledAlgorithmConstraints$Constraints;->permits(Ljava/security/Key;)Z
@@ -9577,7 +9741,7 @@
 HSPLsun/security/util/ObjectIdentifier;->encode(Lsun/security/util/DerOutputStream;)V
 HSPLsun/security/util/ObjectIdentifier;->equals(Ljava/lang/Object;)Z
 HSPLsun/security/util/ObjectIdentifier;->hashCode()I
-HSPLsun/security/util/ObjectIdentifier;->toString()Ljava/lang/String;
+HSPLsun/security/util/ObjectIdentifier;->toString()Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
 HSPLsun/security/util/SignatureFileVerifier;-><init>(Ljava/util/ArrayList;Lsun/security/util/ManifestDigester;Ljava/lang/String;[B)V
 HSPLsun/security/util/SignatureFileVerifier;->getDigest(Ljava/lang/String;)Ljava/security/MessageDigest;
 HSPLsun/security/util/SignatureFileVerifier;->getSigners([Lsun/security/pkcs/SignerInfo;Lsun/security/pkcs/PKCS7;)[Ljava/security/CodeSigner;
@@ -9600,7 +9764,7 @@
 HSPLsun/security/x509/AVA;->toKeyword(ILjava/util/Map;)Ljava/lang/String;
 HSPLsun/security/x509/AVA;->toRFC2253CanonicalString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Lsun/security/util/DerValue;Lsun/security/util/DerValue;
 HSPLsun/security/x509/AVA;->toRFC2253String(Ljava/util/Map;)Ljava/lang/String;
-HSPLsun/security/x509/AVAKeyword;->getKeyword(Lsun/security/util/ObjectIdentifier;ILjava/util/Map;)Ljava/lang/String;
+HSPLsun/security/x509/AVAKeyword;->getKeyword(Lsun/security/util/ObjectIdentifier;ILjava/util/Map;)Ljava/lang/String;+]Lsun/security/util/ObjectIdentifier;Lsun/security/util/ObjectIdentifier;]Ljava/util/Map;Ljava/util/HashMap;,Ljava/util/Collections$EmptyMap;
 HSPLsun/security/x509/AVAKeyword;->getOID(Ljava/lang/String;ILjava/util/Map;)Lsun/security/util/ObjectIdentifier;
 HSPLsun/security/x509/AVAKeyword;->isCompliant(I)Z
 HSPLsun/security/x509/AccessDescription;-><init>(Lsun/security/util/DerValue;)V
@@ -9707,20 +9871,20 @@
 HSPLsun/security/x509/X500Name;->asX500Principal()Ljavax/security/auth/x500/X500Principal;
 HSPLsun/security/x509/X500Name;->checkNoNewLinesNorTabsAtBeginningOfDN(Ljava/lang/String;)V
 HSPLsun/security/x509/X500Name;->countQuotes(Ljava/lang/String;II)I
-HSPLsun/security/x509/X500Name;->equals(Ljava/lang/Object;)Z
+HSPLsun/security/x509/X500Name;->equals(Ljava/lang/Object;)Z+]Lsun/security/x509/X500Name;Lsun/security/x509/X500Name;
 HSPLsun/security/x509/X500Name;->escaped(IILjava/lang/String;)Z
 HSPLsun/security/x509/X500Name;->generateRFC2253DN(Ljava/util/Map;)Ljava/lang/String;
 HSPLsun/security/x509/X500Name;->getEncoded()[B
 HSPLsun/security/x509/X500Name;->getEncodedInternal()[B
-HSPLsun/security/x509/X500Name;->getRFC2253CanonicalName()Ljava/lang/String;
+HSPLsun/security/x509/X500Name;->getRFC2253CanonicalName()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/security/x509/RDN;Lsun/security/x509/RDN;
 HSPLsun/security/x509/X500Name;->getRFC2253Name()Ljava/lang/String;
 HSPLsun/security/x509/X500Name;->getRFC2253Name(Ljava/util/Map;)Ljava/lang/String;
 HSPLsun/security/x509/X500Name;->hashCode()I
 HSPLsun/security/x509/X500Name;->intern(Lsun/security/util/ObjectIdentifier;)Lsun/security/util/ObjectIdentifier;
 HSPLsun/security/x509/X500Name;->isEmpty()Z
-HSPLsun/security/x509/X500Name;->parseDER(Lsun/security/util/DerInputStream;)V
+HSPLsun/security/x509/X500Name;->parseDER(Lsun/security/util/DerInputStream;)V+]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/x509/X500Name;->parseDN(Ljava/lang/String;Ljava/util/Map;)V
-HSPLsun/security/x509/X509AttributeName;-><init>(Ljava/lang/String;)V
+HSPLsun/security/x509/X509AttributeName;-><init>(Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;
 HSPLsun/security/x509/X509AttributeName;->getPrefix()Ljava/lang/String;
 HSPLsun/security/x509/X509AttributeName;->getSuffix()Ljava/lang/String;
 HSPLsun/security/x509/X509CertImpl;-><init>([B)V
@@ -9780,7 +9944,7 @@
 HSPLsun/util/calendar/BaseCalendar;->getCalendarDateFromFixedDate(Lsun/util/calendar/CalendarDate;J)V+]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;
 HSPLsun/util/calendar/BaseCalendar;->getDayOfWeekFromFixedDate(J)I
 HSPLsun/util/calendar/BaseCalendar;->getDayOfYear(III)J
-HSPLsun/util/calendar/BaseCalendar;->getFixedDate(IIILsun/util/calendar/BaseCalendar$Date;)J
+HSPLsun/util/calendar/BaseCalendar;->getFixedDate(IIILsun/util/calendar/BaseCalendar$Date;)J+]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;
 HSPLsun/util/calendar/BaseCalendar;->getFixedDate(Lsun/util/calendar/CalendarDate;)J
 HSPLsun/util/calendar/BaseCalendar;->getGregorianYearFromFixedDate(J)I
 HSPLsun/util/calendar/BaseCalendar;->isLeapYear(I)Z
@@ -9789,7 +9953,7 @@
 HSPLsun/util/calendar/CalendarDate;-><init>(Ljava/util/TimeZone;)V
 HSPLsun/util/calendar/CalendarDate;->clone()Ljava/lang/Object;
 HSPLsun/util/calendar/CalendarDate;->getDayOfMonth()I
-HSPLsun/util/calendar/CalendarDate;->getDayOfWeek()I
+HSPLsun/util/calendar/CalendarDate;->getDayOfWeek()I+]Lsun/util/calendar/CalendarDate;Lsun/util/calendar/Gregorian$Date;
 HSPLsun/util/calendar/CalendarDate;->getEra()Lsun/util/calendar/Era;
 HSPLsun/util/calendar/CalendarDate;->getHours()I
 HSPLsun/util/calendar/CalendarDate;->getMillis()I
@@ -9831,7 +9995,7 @@
 HSPLsun/util/calendar/CalendarUtils;->mod(JJ)J
 HSPLsun/util/calendar/CalendarUtils;->sprintf0d(Ljava/lang/StringBuilder;II)Ljava/lang/StringBuilder;
 HSPLsun/util/calendar/Gregorian$Date;-><init>(Ljava/util/TimeZone;)V
-HSPLsun/util/calendar/Gregorian$Date;->getNormalizedYear()I
+HSPLsun/util/calendar/Gregorian$Date;->getNormalizedYear()I+]Lsun/util/calendar/Gregorian$Date;Lsun/util/calendar/Gregorian$Date;
 HSPLsun/util/calendar/Gregorian$Date;->setNormalizedYear(I)V
 HSPLsun/util/calendar/Gregorian;->getCalendarDate(JLjava/util/TimeZone;)Lsun/util/calendar/CalendarDate;
 HSPLsun/util/calendar/Gregorian;->getCalendarDate(JLjava/util/TimeZone;)Lsun/util/calendar/Gregorian$Date;
@@ -9853,17 +10017,16 @@
 HSPLsun/util/locale/BaseLocale$Cache;->createObject(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale;
 HSPLsun/util/locale/BaseLocale$Cache;->normalizeKey(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLsun/util/locale/BaseLocale$Cache;->normalizeKey(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale$Key;
-HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetlang(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference;
-HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetregn(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference;
-HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetscrt(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference;
-HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetvart(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference;
-HSPLsun/util/locale/BaseLocale$Key;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$mgetBaseLocale(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale;
 HSPLsun/util/locale/BaseLocale$Key;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
-HSPLsun/util/locale/BaseLocale$Key;->equals(Ljava/lang/Object;)Z+]Ljava/lang/ref/SoftReference;Ljava/lang/ref/SoftReference;
+HSPLsun/util/locale/BaseLocale$Key;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLsun/util/locale/BaseLocale$Key-IA;)V
+HSPLsun/util/locale/BaseLocale$Key;->equals(Ljava/lang/Object;)Z
+HSPLsun/util/locale/BaseLocale$Key;->getBaseLocale()Lsun/util/locale/BaseLocale;
 HSPLsun/util/locale/BaseLocale$Key;->hashCode()I
+HSPLsun/util/locale/BaseLocale$Key;->hashCode(Lsun/util/locale/BaseLocale;)I
 HSPLsun/util/locale/BaseLocale$Key;->normalize(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale$Key;
-HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lsun/util/locale/BaseLocale-IA;)V
+HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V+]Ljava/lang/String;Ljava/lang/String;
+HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLsun/util/locale/BaseLocale-IA;)V
 HSPLsun/util/locale/BaseLocale;->cleanCache()V
 HSPLsun/util/locale/BaseLocale;->equals(Ljava/lang/Object;)Z
 HSPLsun/util/locale/BaseLocale;->getInstance(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lsun/util/locale/BaseLocale;
@@ -9912,7 +10075,7 @@
 HSPLsun/util/locale/LocaleObjectCache$CacheEntry;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V
 HSPLsun/util/locale/LocaleObjectCache$CacheEntry;->getKey()Ljava/lang/Object;
 HSPLsun/util/locale/LocaleObjectCache;->cleanStaleEntries()V
-HSPLsun/util/locale/LocaleObjectCache;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLsun/util/locale/LocaleObjectCache;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Lsun/util/locale/LocaleObjectCache;Lsun/util/locale/BaseLocale$Cache;]Lsun/util/locale/LocaleObjectCache$CacheEntry;Lsun/util/locale/LocaleObjectCache$CacheEntry;
 HSPLsun/util/locale/LocaleObjectCache;->normalizeKey(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLsun/util/locale/LocaleUtils;->caseIgnoreMatch(Ljava/lang/String;Ljava/lang/String;)Z
 HSPLsun/util/locale/LocaleUtils;->isAlpha(C)Z
@@ -9942,6 +10105,7 @@
 HSPLsun/util/locale/StringTokenIterator;->next()Ljava/lang/String;
 HSPLsun/util/locale/StringTokenIterator;->nextDelimiter(I)I
 HSPLsun/util/locale/StringTokenIterator;->setStart(I)Lsun/util/locale/StringTokenIterator;
+HSPLsun/util/locale/provider/CalendarDataUtility;->retrieveFirstDayOfWeek(Ljava/util/Locale;I)I
 HSPLsun/util/logging/LoggingSupport$2;-><init>()V
 HSPLsun/util/logging/LoggingSupport$2;->run()Ljava/lang/Object;
 HSPLsun/util/logging/LoggingSupport$2;->run()Ljava/lang/String;
@@ -9951,7 +10115,7 @@
 HSPLsun/util/logging/PlatformLogger$JavaLoggerProxy;-><init>(Ljava/lang/String;Lsun/util/logging/PlatformLogger$Level;)V
 HSPLsun/util/logging/PlatformLogger$LoggerProxy;-><init>(Ljava/lang/String;)V
 HSPLsun/util/logging/PlatformLogger;-><init>(Ljava/lang/String;)V
-HSPLsun/util/logging/PlatformLogger;->getLogger(Ljava/lang/String;)Lsun/util/logging/PlatformLogger;+]Ljava/lang/ref/WeakReference;Ljava/lang/ref/WeakReference;]Ljava/util/Map;Ljava/util/HashMap;
+HSPLsun/util/logging/PlatformLogger;->getLogger(Ljava/lang/String;)Lsun/util/logging/PlatformLogger;
 Landroid/compat/Compatibility$1;
 Landroid/compat/Compatibility$BehaviorChangeDelegate;
 Landroid/compat/Compatibility$ChangeConfig;
@@ -10019,6 +10183,7 @@
 Lcom/android/okhttp/HttpUrl;
 Lcom/android/okhttp/HttpsHandler;
 Lcom/android/okhttp/Interceptor$Chain;
+Lcom/android/okhttp/MediaType;
 Lcom/android/okhttp/OkCacheContainer;
 Lcom/android/okhttp/OkHttpClient$1;
 Lcom/android/okhttp/OkHttpClient;
@@ -10052,8 +10217,11 @@
 Lcom/android/okhttp/internal/Version;
 Lcom/android/okhttp/internal/framed/FrameWriter;
 Lcom/android/okhttp/internal/framed/FramedConnection$Builder;
+Lcom/android/okhttp/internal/framed/FramedConnection$Listener$1;
 Lcom/android/okhttp/internal/framed/FramedConnection$Listener;
 Lcom/android/okhttp/internal/framed/FramedConnection;
+Lcom/android/okhttp/internal/framed/Header;
+Lcom/android/okhttp/internal/framed/PushObserver$1;
 Lcom/android/okhttp/internal/framed/PushObserver;
 Lcom/android/okhttp/internal/framed/Settings;
 Lcom/android/okhttp/internal/http/AuthenticatorAdapter;
@@ -10338,7 +10506,10 @@
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/DES;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/DESede$Mappings;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/DESede;
+Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$BasePBKDF2;
+Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$BasePBKDF2WithHmacSHA1;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$Mappings;
+Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$PBKDF2WithHmacSHA1UTF8;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12$Mappings;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12;
@@ -10360,6 +10531,7 @@
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/BlockCipherProvider;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/ClassUtil;
+Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/GcmSpecUtil$2;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/GcmSpecUtil;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/PBE$Util;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/PBE;
@@ -10368,6 +10540,7 @@
 Lcom/android/org/bouncycastle/jcajce/provider/util/AsymmetricKeyInfoConverter;
 Lcom/android/org/bouncycastle/jcajce/provider/util/DigestFactory;
 Lcom/android/org/bouncycastle/jcajce/spec/AEADParameterSpec;
+Lcom/android/org/bouncycastle/jcajce/spec/PBKDF2KeySpec;
 Lcom/android/org/bouncycastle/jcajce/util/BCJcaJceHelper;
 Lcom/android/org/bouncycastle/jcajce/util/DefaultJcaJceHelper;
 Lcom/android/org/bouncycastle/jcajce/util/JcaJceHelper;
@@ -10376,6 +10549,7 @@
 Lcom/android/org/bouncycastle/jce/interfaces/BCKeyStore;
 Lcom/android/org/bouncycastle/jce/interfaces/PKCS12BagAttributeCarrier;
 Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProvider$1;
+Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProvider$PrivateProvider;
 Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProvider;
 Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration;
 Lcom/android/org/bouncycastle/jce/provider/CertStoreCollectionSpi;
@@ -10406,6 +10580,7 @@
 Ldalvik/annotation/optimization/CriticalNative;
 Ldalvik/annotation/optimization/FastNative;
 Ldalvik/annotation/optimization/NeverCompile;
+Ldalvik/annotation/optimization/NeverInline;
 Ldalvik/system/AppSpecializationHooks;
 Ldalvik/system/BaseDexClassLoader$Reporter;
 Ldalvik/system/BaseDexClassLoader;
@@ -10440,13 +10615,16 @@
 Ldalvik/system/SocketTagger;
 Ldalvik/system/VMDebug;
 Ldalvik/system/VMRuntime$HiddenApiUsageLogger;
+Ldalvik/system/VMRuntime$SdkVersionContainer;
 Ldalvik/system/VMRuntime;
 Ldalvik/system/VMStack;
+Ldalvik/system/ZipPathValidator$1;
+Ldalvik/system/ZipPathValidator$Callback;
+Ldalvik/system/ZipPathValidator;
 Ldalvik/system/ZygoteHooks;
 Ljava/awt/font/NumericShaper;
 Ljava/awt/font/TextAttribute;
 Ljava/io/Bits;
-Ljava/io/BufferedInputStream$$ExternalSyntheticBackportWithForwarding0;
 Ljava/io/BufferedInputStream;
 Ljava/io/BufferedOutputStream;
 Ljava/io/BufferedReader;
@@ -10557,6 +10735,7 @@
 Ljava/io/StringBufferInputStream;
 Ljava/io/StringReader;
 Ljava/io/StringWriter;
+Ljava/io/SyncFailedException;
 Ljava/io/UTFDataFormatException;
 Ljava/io/UncheckedIOException;
 Ljava/io/UnixFileSystem;
@@ -10564,6 +10743,8 @@
 Ljava/io/WriteAbortedException;
 Ljava/io/Writer;
 Ljava/lang/AbstractMethodError;
+Ljava/lang/AbstractStringBuilder$$ExternalSyntheticLambda0;
+Ljava/lang/AbstractStringBuilder$$ExternalSyntheticLambda1;
 Ljava/lang/AbstractStringBuilder;
 Ljava/lang/AndroidHardcodedSystemProperties;
 Ljava/lang/Appendable;
@@ -10594,6 +10775,8 @@
 Ljava/lang/ClassLoader$SystemClassLoader;
 Ljava/lang/ClassLoader;
 Ljava/lang/ClassNotFoundException;
+Ljava/lang/ClassValue$Entry;
+Ljava/lang/ClassValue;
 Ljava/lang/CloneNotSupportedException;
 Ljava/lang/Cloneable;
 Ljava/lang/Comparable;
@@ -10624,8 +10807,6 @@
 Ljava/lang/InheritableThreadLocal;
 Ljava/lang/InstantiationError;
 Ljava/lang/InstantiationException;
-Ljava/lang/Integer$$ExternalSyntheticBackport0;
-Ljava/lang/Integer$$ExternalSyntheticBackport1;
 Ljava/lang/Integer$IntegerCache;
 Ljava/lang/Integer;
 Ljava/lang/InternalError;
@@ -10671,9 +10852,19 @@
 Ljava/lang/SecurityManager;
 Ljava/lang/Short$ShortCache;
 Ljava/lang/Short;
+Ljava/lang/StackFrameInfo;
 Ljava/lang/StackOverflowError;
+Ljava/lang/StackStreamFactory$AbstractStackWalker;
+Ljava/lang/StackStreamFactory;
 Ljava/lang/StackTraceElement;
+Ljava/lang/StackWalker$Option;
+Ljava/lang/StackWalker$StackFrame;
+Ljava/lang/StackWalker;
 Ljava/lang/StrictMath;
+Ljava/lang/String$$ExternalSyntheticLambda0;
+Ljava/lang/String$$ExternalSyntheticLambda1;
+Ljava/lang/String$$ExternalSyntheticLambda2;
+Ljava/lang/String$$ExternalSyntheticLambda3;
 Ljava/lang/String$CaseInsensitiveComparator-IA;
 Ljava/lang/String$CaseInsensitiveComparator;
 Ljava/lang/String;
@@ -10681,8 +10872,13 @@
 Ljava/lang/StringBuilder;
 Ljava/lang/StringFactory;
 Ljava/lang/StringIndexOutOfBoundsException;
+Ljava/lang/StringLatin1$CharsSpliterator;
+Ljava/lang/StringLatin1$LinesSpliterator;
+Ljava/lang/StringLatin1;
 Ljava/lang/StringUTF16$CharsSpliterator;
+Ljava/lang/StringUTF16$CharsSpliteratorForString;
 Ljava/lang/StringUTF16$CodePointsSpliterator;
+Ljava/lang/StringUTF16$CodePointsSpliteratorForString;
 Ljava/lang/StringUTF16;
 Ljava/lang/System$PropertiesWithNonOverrideableDefaults;
 Ljava/lang/System;
@@ -10696,6 +10892,7 @@
 Ljava/lang/ThreadGroup;
 Ljava/lang/ThreadLocal$SuppliedThreadLocal;
 Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;
+Ljava/lang/ThreadLocal$ThreadLocalMap-IA;
 Ljava/lang/ThreadLocal$ThreadLocalMap;
 Ljava/lang/ThreadLocal;
 Ljava/lang/Throwable$PrintStreamOrWriter;
@@ -10713,6 +10910,7 @@
 Ljava/lang/UNIXProcess$ProcessReaperThreadFactory;
 Ljava/lang/UNIXProcess;
 Ljava/lang/UnsatisfiedLinkError;
+Ljava/lang/UnsupportedClassVersionError;
 Ljava/lang/UnsupportedOperationException;
 Ljava/lang/VMClassLoader;
 Ljava/lang/VerifyError;
@@ -10790,10 +10988,14 @@
 Ljava/lang/invoke/Transformers$ReferenceArrayElementSetter;
 Ljava/lang/invoke/Transformers$ReferenceIdentity;
 Ljava/lang/invoke/Transformers$Spreader;
+Ljava/lang/invoke/Transformers$TableSwitch;
 Ljava/lang/invoke/Transformers$Transformer;
 Ljava/lang/invoke/Transformers$TryFinally;
 Ljava/lang/invoke/Transformers$VarargsCollector;
 Ljava/lang/invoke/Transformers$ZeroValue;
+Ljava/lang/invoke/TypeDescriptor$OfField;
+Ljava/lang/invoke/TypeDescriptor$OfMethod;
+Ljava/lang/invoke/TypeDescriptor;
 Ljava/lang/invoke/VarHandle$1;
 Ljava/lang/invoke/VarHandle$AccessMode;
 Ljava/lang/invoke/VarHandle$AccessType;
@@ -10850,6 +11052,7 @@
 Ljava/lang/reflect/WildcardType;
 Ljava/math/BigDecimal$1;
 Ljava/math/BigDecimal$LongOverflow;
+Ljava/math/BigDecimal$StringBuilderHelper;
 Ljava/math/BigDecimal;
 Ljava/math/BigInteger$UnsafeHolder;
 Ljava/math/BigInteger;
@@ -10926,6 +11129,7 @@
 Ljava/net/Proxy;
 Ljava/net/ProxySelector;
 Ljava/net/ResponseCache;
+Ljava/net/ServerSocket$1;
 Ljava/net/ServerSocket;
 Ljava/net/Socket$1;
 Ljava/net/Socket$2;
@@ -10959,6 +11163,7 @@
 Ljava/net/UnknownServiceException;
 Ljava/nio/Bits;
 Ljava/nio/Buffer;
+Ljava/nio/BufferMismatch;
 Ljava/nio/BufferOverflowException;
 Ljava/nio/BufferUnderflowException;
 Ljava/nio/ByteBuffer;
@@ -11032,6 +11237,7 @@
 Ljava/nio/channels/spi/AbstractSelectionKey;
 Ljava/nio/channels/spi/AbstractSelector$1;
 Ljava/nio/channels/spi/AbstractSelector;
+Ljava/nio/channels/spi/AsynchronousChannelProvider;
 Ljava/nio/channels/spi/SelectorProvider$1;
 Ljava/nio/channels/spi/SelectorProvider;
 Ljava/nio/charset/CharacterCodingException;
@@ -11039,8 +11245,6 @@
 Ljava/nio/charset/CharsetDecoder;
 Ljava/nio/charset/CharsetEncoder;
 Ljava/nio/charset/CoderMalfunctionError;
-Ljava/nio/charset/CoderResult$1;
-Ljava/nio/charset/CoderResult$2;
 Ljava/nio/charset/CoderResult$Cache;
 Ljava/nio/charset/CoderResult;
 Ljava/nio/charset/CodingErrorAction;
@@ -11061,6 +11265,8 @@
 Ljava/nio/file/FileSystems$DefaultFileSystemHolder$1;
 Ljava/nio/file/FileSystems$DefaultFileSystemHolder;
 Ljava/nio/file/FileSystems;
+Ljava/nio/file/FileVisitResult;
+Ljava/nio/file/FileVisitor;
 Ljava/nio/file/Files$AcceptAllFilter;
 Ljava/nio/file/Files;
 Ljava/nio/file/InvalidPathException;
@@ -11072,6 +11278,7 @@
 Ljava/nio/file/Paths;
 Ljava/nio/file/ProviderMismatchException;
 Ljava/nio/file/SecureDirectoryStream;
+Ljava/nio/file/SimpleFileVisitor;
 Ljava/nio/file/StandardCopyOption;
 Ljava/nio/file/StandardOpenOption;
 Ljava/nio/file/Watchable;
@@ -11290,6 +11497,7 @@
 Ljava/time/Duration;
 Ljava/time/Instant$1;
 Ljava/time/Instant;
+Ljava/time/InstantSource;
 Ljava/time/LocalDate$1;
 Ljava/time/LocalDate;
 Ljava/time/LocalDateTime;
@@ -11319,10 +11527,11 @@
 Ljava/time/format/DateTimeFormatterBuilder$$ExternalSyntheticLambda0;
 Ljava/time/format/DateTimeFormatterBuilder$1;
 Ljava/time/format/DateTimeFormatterBuilder$2;
-Ljava/time/format/DateTimeFormatterBuilder$3;
 Ljava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;
 Ljava/time/format/DateTimeFormatterBuilder$CompositePrinterParser;
 Ljava/time/format/DateTimeFormatterBuilder$DateTimePrinterParser;
+Ljava/time/format/DateTimeFormatterBuilder$DayPeriod$$ExternalSyntheticLambda0;
+Ljava/time/format/DateTimeFormatterBuilder$DayPeriod;
 Ljava/time/format/DateTimeFormatterBuilder$FractionPrinterParser;
 Ljava/time/format/DateTimeFormatterBuilder$InstantPrinterParser;
 Ljava/time/format/DateTimeFormatterBuilder$NumberPrinterParser;
@@ -11407,14 +11616,15 @@
 Ljava/util/AbstractQueue;
 Ljava/util/AbstractSequentialList;
 Ljava/util/AbstractSet;
+Ljava/util/ArrayDeque$$ExternalSyntheticLambda1;
 Ljava/util/ArrayDeque$DeqIterator;
 Ljava/util/ArrayDeque$DescendingIterator;
 Ljava/util/ArrayDeque;
 Ljava/util/ArrayList$ArrayListSpliterator;
-Ljava/util/ArrayList$Itr-IA;
 Ljava/util/ArrayList$Itr;
 Ljava/util/ArrayList$ListItr;
 Ljava/util/ArrayList$SubList$1;
+Ljava/util/ArrayList$SubList$2;
 Ljava/util/ArrayList$SubList;
 Ljava/util/ArrayList;
 Ljava/util/ArrayPrefixHelpers$CumulateTask;
@@ -11429,18 +11639,12 @@
 Ljava/util/Arrays$ArrayList;
 Ljava/util/Arrays$NaturalOrder;
 Ljava/util/Arrays;
-Ljava/util/ArraysParallelSortHelpers$FJByte$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJChar$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJDouble$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJFloat$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJInt$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJLong$Sorter;
 Ljava/util/ArraysParallelSortHelpers$FJObject$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJShort$Sorter;
 Ljava/util/Base64$Decoder;
 Ljava/util/Base64$Encoder;
 Ljava/util/Base64;
 Ljava/util/BitSet;
+Ljava/util/Calendar$$ExternalSyntheticLambda0;
 Ljava/util/Calendar$Builder;
 Ljava/util/Calendar;
 Ljava/util/Collection;
@@ -11515,6 +11719,7 @@
 Ljava/util/Date;
 Ljava/util/Deque;
 Ljava/util/Dictionary;
+Ljava/util/DualPivotQuicksort$Sorter;
 Ljava/util/DualPivotQuicksort;
 Ljava/util/DuplicateFormatFlagsException;
 Ljava/util/EmptyStackException;
@@ -11587,14 +11792,15 @@
 Ljava/util/ImmutableCollections$AbstractImmutableMap;
 Ljava/util/ImmutableCollections$AbstractImmutableSet;
 Ljava/util/ImmutableCollections$List12;
+Ljava/util/ImmutableCollections$ListItr;
 Ljava/util/ImmutableCollections$ListN;
-Ljava/util/ImmutableCollections$Map0;
 Ljava/util/ImmutableCollections$Map1;
+Ljava/util/ImmutableCollections$MapN$1;
+Ljava/util/ImmutableCollections$MapN$MapNIterator;
 Ljava/util/ImmutableCollections$MapN;
-Ljava/util/ImmutableCollections$Set0;
-Ljava/util/ImmutableCollections$Set1;
-Ljava/util/ImmutableCollections$Set2;
+Ljava/util/ImmutableCollections$Set12;
 Ljava/util/ImmutableCollections$SetN;
+Ljava/util/ImmutableCollections$SubList;
 Ljava/util/ImmutableCollections;
 Ljava/util/InputMismatchException;
 Ljava/util/Iterator;
@@ -11611,6 +11817,7 @@
 Ljava/util/LinkedHashMap$LinkedValues;
 Ljava/util/LinkedHashMap;
 Ljava/util/LinkedHashSet;
+Ljava/util/LinkedList$DescendingIterator;
 Ljava/util/LinkedList$ListItr;
 Ljava/util/LinkedList$Node;
 Ljava/util/LinkedList;
@@ -11622,6 +11829,10 @@
 Ljava/util/Locale$Cache;
 Ljava/util/Locale$Category;
 Ljava/util/Locale$FilteringMode;
+Ljava/util/Locale$IsoCountryCode$1;
+Ljava/util/Locale$IsoCountryCode$2;
+Ljava/util/Locale$IsoCountryCode$3;
+Ljava/util/Locale$IsoCountryCode;
 Ljava/util/Locale$LanguageRange;
 Ljava/util/Locale$LocaleKey;
 Ljava/util/Locale$NoImagePreloadHolder;
@@ -11658,13 +11869,16 @@
 Ljava/util/ResourceBundle$BundleReference;
 Ljava/util/ResourceBundle$CacheKey;
 Ljava/util/ResourceBundle$CacheKeyReference;
+Ljava/util/ResourceBundle$Control$$ExternalSyntheticLambda0;
 Ljava/util/ResourceBundle$Control$1;
 Ljava/util/ResourceBundle$Control$CandidateListCache;
 Ljava/util/ResourceBundle$Control;
-Ljava/util/ResourceBundle$LoaderReference;
+Ljava/util/ResourceBundle$KeyElementReference;
+Ljava/util/ResourceBundle$RBClassLoader$1;
+Ljava/util/ResourceBundle$RBClassLoader;
 Ljava/util/ResourceBundle$SingleFormatControl;
 Ljava/util/ResourceBundle;
-Ljava/util/Scanner$1;
+Ljava/util/Scanner$PatternLRUCache;
 Ljava/util/Scanner;
 Ljava/util/ServiceConfigurationError;
 Ljava/util/ServiceLoader$1;
@@ -11719,6 +11933,7 @@
 Ljava/util/TreeMap$Values;
 Ljava/util/TreeMap;
 Ljava/util/TreeSet;
+Ljava/util/Tripwire$$ExternalSyntheticLambda0;
 Ljava/util/Tripwire;
 Ljava/util/UUID$Holder;
 Ljava/util/UUID;
@@ -11747,6 +11962,8 @@
 Ljava/util/concurrent/Callable;
 Ljava/util/concurrent/CancellationException;
 Ljava/util/concurrent/CompletableFuture$AltResult;
+Ljava/util/concurrent/CompletableFuture$AsyncRun;
+Ljava/util/concurrent/CompletableFuture$AsyncSupply;
 Ljava/util/concurrent/CompletableFuture$AsynchronousCompletionTask;
 Ljava/util/concurrent/CompletableFuture$Completion;
 Ljava/util/concurrent/CompletableFuture$Signaller;
@@ -11843,12 +12060,13 @@
 Ljava/util/concurrent/Executors$RunnableAdapter;
 Ljava/util/concurrent/Executors;
 Ljava/util/concurrent/ForkJoinPool$1;
+Ljava/util/concurrent/ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory;
 Ljava/util/concurrent/ForkJoinPool$DefaultForkJoinWorkerThreadFactory;
 Ljava/util/concurrent/ForkJoinPool$ForkJoinWorkerThreadFactory;
 Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;
 Ljava/util/concurrent/ForkJoinPool$WorkQueue;
 Ljava/util/concurrent/ForkJoinPool;
-Ljava/util/concurrent/ForkJoinTask$ExceptionNode;
+Ljava/util/concurrent/ForkJoinTask$Aux;
 Ljava/util/concurrent/ForkJoinTask;
 Ljava/util/concurrent/ForkJoinWorkerThread;
 Ljava/util/concurrent/Future;
@@ -11861,6 +12079,7 @@
 Ljava/util/concurrent/LinkedBlockingQueue$Itr;
 Ljava/util/concurrent/LinkedBlockingQueue$Node;
 Ljava/util/concurrent/LinkedBlockingQueue;
+Ljava/util/concurrent/Phaser;
 Ljava/util/concurrent/PriorityBlockingQueue;
 Ljava/util/concurrent/RejectedExecutionException;
 Ljava/util/concurrent/RejectedExecutionHandler;
@@ -11910,8 +12129,11 @@
 Ljava/util/concurrent/atomic/Striped64$Cell;
 Ljava/util/concurrent/atomic/Striped64;
 Ljava/util/concurrent/locks/AbstractOwnableSynchronizer;
+Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;
 Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
+Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode;
 Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
+Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode;
 Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;
 Ljava/util/concurrent/locks/Condition;
 Ljava/util/concurrent/locks/Lock;
@@ -11958,9 +12180,13 @@
 Ljava/util/function/IntUnaryOperator;
 Ljava/util/function/LongBinaryOperator;
 Ljava/util/function/LongConsumer;
+Ljava/util/function/LongFunction;
 Ljava/util/function/LongPredicate;
 Ljava/util/function/LongSupplier;
 Ljava/util/function/LongUnaryOperator;
+Ljava/util/function/ObjDoubleConsumer;
+Ljava/util/function/ObjIntConsumer;
+Ljava/util/function/ObjLongConsumer;
 Ljava/util/function/Predicate;
 Ljava/util/function/Supplier;
 Ljava/util/function/ToDoubleBiFunction;
@@ -12037,6 +12263,7 @@
 Ljava/util/stream/Collector$Characteristics;
 Ljava/util/stream/Collector;
 Ljava/util/stream/Collectors$$ExternalSyntheticLambda0;
+Ljava/util/stream/Collectors$$ExternalSyntheticLambda13;
 Ljava/util/stream/Collectors$$ExternalSyntheticLambda15;
 Ljava/util/stream/Collectors$$ExternalSyntheticLambda1;
 Ljava/util/stream/Collectors$$ExternalSyntheticLambda20;
@@ -12102,6 +12329,8 @@
 Ljava/util/stream/IntPipeline$$ExternalSyntheticLambda8;
 Ljava/util/stream/IntPipeline$4$1;
 Ljava/util/stream/IntPipeline$4;
+Ljava/util/stream/IntPipeline$9$1;
+Ljava/util/stream/IntPipeline$9;
 Ljava/util/stream/IntPipeline$Head;
 Ljava/util/stream/IntPipeline$StatelessOp;
 Ljava/util/stream/IntPipeline;
@@ -12174,6 +12403,7 @@
 Ljava/util/stream/ReferencePipeline$5;
 Ljava/util/stream/ReferencePipeline$6$1;
 Ljava/util/stream/ReferencePipeline$6;
+Ljava/util/stream/ReferencePipeline$7$1;
 Ljava/util/stream/ReferencePipeline$7;
 Ljava/util/stream/ReferencePipeline$Head;
 Ljava/util/stream/ReferencePipeline$StatefulOp;
@@ -12218,10 +12448,12 @@
 Ljava/util/stream/Streams;
 Ljava/util/stream/TerminalOp;
 Ljava/util/stream/TerminalSink;
+Ljava/util/stream/Tripwire$$ExternalSyntheticLambda0;
 Ljava/util/stream/Tripwire;
 Ljava/util/zip/Adler32;
 Ljava/util/zip/CRC32;
 Ljava/util/zip/CheckedInputStream;
+Ljava/util/zip/Checksum$1;
 Ljava/util/zip/Checksum;
 Ljava/util/zip/DataFormatException;
 Ljava/util/zip/Deflater;
@@ -12332,6 +12564,7 @@
 Ljavax/net/ssl/SSLSocket;
 Ljavax/net/ssl/SSLSocketFactory$1;
 Ljavax/net/ssl/SSLSocketFactory;
+Ljavax/net/ssl/StandardConstants;
 Ljavax/net/ssl/TrustManager;
 Ljavax/net/ssl/TrustManagerFactory$1;
 Ljavax/net/ssl/TrustManagerFactory;
@@ -12351,6 +12584,7 @@
 Ljavax/security/cert/CertificateException;
 Ljavax/security/cert/X509Certificate$1;
 Ljavax/security/cert/X509Certificate;
+Ljavax/xml/datatype/DatatypeConfigurationException;
 Ljavax/xml/datatype/DatatypeConstants$Field;
 Ljavax/xml/datatype/DatatypeConstants;
 Ljavax/xml/datatype/Duration;
@@ -12367,6 +12601,7 @@
 Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
 Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
 Ljdk/internal/math/FloatingDecimal$ExceptionalBinaryToASCIIBuffer;
+Ljdk/internal/math/FloatingDecimal$HexFloatPattern;
 Ljdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer;
 Ljdk/internal/math/FloatingDecimal;
 Ljdk/internal/math/FormattedFloatingDecimal$1;
@@ -12375,11 +12610,15 @@
 Ljdk/internal/math/FormattedFloatingDecimal;
 Ljdk/internal/misc/JavaObjectInputStreamAccess;
 Ljdk/internal/misc/SharedSecrets;
+Ljdk/internal/misc/TerminatingThreadLocal$1;
+Ljdk/internal/misc/TerminatingThreadLocal;
 Ljdk/internal/misc/Unsafe;
+Ljdk/internal/misc/UnsafeConstants;
 Ljdk/internal/misc/VM;
 Ljdk/internal/reflect/Reflection;
 Ljdk/internal/util/ArraysSupport;
 Ljdk/internal/util/Preconditions;
+Ljdk/internal/util/StaticProperty;
 Llibcore/content/type/MimeMap$$ExternalSyntheticLambda0;
 Llibcore/content/type/MimeMap$Builder$Element;
 Llibcore/content/type/MimeMap$Builder;
@@ -12387,7 +12626,6 @@
 Llibcore/content/type/MimeMap;
 Llibcore/icu/CollationKeyICU;
 Llibcore/icu/DateIntervalFormat;
-Llibcore/icu/DateUtilsBridge;
 Llibcore/icu/DecimalFormatData;
 Llibcore/icu/ICU;
 Llibcore/icu/LocaleData;
@@ -12460,17 +12698,24 @@
 Lorg/apache/harmony/xml/ExpatException;
 Lorg/apache/harmony/xml/ExpatParser$CurrentAttributes;
 Lorg/apache/harmony/xml/ExpatParser$ExpatLocator;
+Lorg/apache/harmony/xml/ExpatParser$ParseException;
 Lorg/apache/harmony/xml/ExpatParser;
 Lorg/apache/harmony/xml/ExpatReader;
+Lorg/apache/harmony/xml/dom/AttrImpl;
+Lorg/apache/harmony/xml/dom/CDATASectionImpl;
 Lorg/apache/harmony/xml/dom/CharacterDataImpl;
+Lorg/apache/harmony/xml/dom/CommentImpl;
 Lorg/apache/harmony/xml/dom/DOMImplementationImpl;
 Lorg/apache/harmony/xml/dom/DocumentImpl;
+Lorg/apache/harmony/xml/dom/DocumentTypeImpl;
 Lorg/apache/harmony/xml/dom/ElementImpl;
+Lorg/apache/harmony/xml/dom/EntityReferenceImpl;
 Lorg/apache/harmony/xml/dom/InnerNodeImpl;
 Lorg/apache/harmony/xml/dom/LeafNodeImpl;
 Lorg/apache/harmony/xml/dom/NodeImpl$1;
 Lorg/apache/harmony/xml/dom/NodeImpl;
 Lorg/apache/harmony/xml/dom/NodeListImpl;
+Lorg/apache/harmony/xml/dom/ProcessingInstructionImpl;
 Lorg/apache/harmony/xml/dom/TextImpl;
 Lorg/apache/harmony/xml/parsers/DocumentBuilderFactoryImpl;
 Lorg/apache/harmony/xml/parsers/DocumentBuilderImpl;
@@ -12484,15 +12729,20 @@
 Lorg/json/JSONStringer$Scope;
 Lorg/json/JSONStringer;
 Lorg/json/JSONTokener;
+Lorg/w3c/dom/Attr;
+Lorg/w3c/dom/CDATASection;
 Lorg/w3c/dom/CharacterData;
+Lorg/w3c/dom/Comment;
 Lorg/w3c/dom/DOMException;
 Lorg/w3c/dom/DOMImplementation;
 Lorg/w3c/dom/Document;
 Lorg/w3c/dom/DocumentFragment;
 Lorg/w3c/dom/DocumentType;
 Lorg/w3c/dom/Element;
+Lorg/w3c/dom/EntityReference;
 Lorg/w3c/dom/Node;
 Lorg/w3c/dom/NodeList;
+Lorg/w3c/dom/ProcessingInstruction;
 Lorg/w3c/dom/Text;
 Lorg/w3c/dom/TypeInfo;
 Lorg/xml/sax/AttributeList;
@@ -12508,6 +12758,7 @@
 Lorg/xml/sax/SAXException;
 Lorg/xml/sax/SAXNotRecognizedException;
 Lorg/xml/sax/SAXNotSupportedException;
+Lorg/xml/sax/SAXParseException;
 Lorg/xml/sax/XMLFilter;
 Lorg/xml/sax/XMLReader;
 Lorg/xml/sax/ext/DeclHandler;
@@ -12516,6 +12767,7 @@
 Lorg/xml/sax/ext/LexicalHandler;
 Lorg/xml/sax/helpers/AttributesImpl;
 Lorg/xml/sax/helpers/DefaultHandler;
+Lorg/xml/sax/helpers/LocatorImpl;
 Lorg/xml/sax/helpers/NamespaceSupport;
 Lorg/xml/sax/helpers/XMLFilterImpl;
 Lorg/xmlpull/v1/XmlPullParser;
@@ -12536,7 +12788,6 @@
 Lsun/misc/JavaIOFileDescriptorAccess;
 Lsun/misc/LRUCache;
 Lsun/misc/SharedSecrets;
-Lsun/misc/Unsafe$$ExternalSyntheticBackportWithForwarding0;
 Lsun/misc/Unsafe;
 Lsun/misc/VM;
 Lsun/misc/Version;
@@ -12576,6 +12827,7 @@
 Lsun/nio/ch/IOStatus;
 Lsun/nio/ch/IOUtil;
 Lsun/nio/ch/Interruptible;
+Lsun/nio/ch/LinuxAsynchronousChannelProvider;
 Lsun/nio/ch/NativeDispatcher;
 Lsun/nio/ch/NativeObject;
 Lsun/nio/ch/NativeThread;
@@ -12667,6 +12919,7 @@
 Lsun/security/jca/Providers;
 Lsun/security/jca/ServiceId;
 Lsun/security/pkcs/ContentInfo;
+Lsun/security/pkcs/ESSCertId;
 Lsun/security/pkcs/PKCS7$VerbatimX509Certificate;
 Lsun/security/pkcs/PKCS7$WrappedX509Certificate;
 Lsun/security/pkcs/PKCS7;
@@ -12705,6 +12958,7 @@
 Lsun/security/util/AbstractAlgorithmConstraints$1;
 Lsun/security/util/AbstractAlgorithmConstraints;
 Lsun/security/util/AlgorithmDecomposer;
+Lsun/security/util/AnchorCertificates$1;
 Lsun/security/util/AnchorCertificates;
 Lsun/security/util/BitArray;
 Lsun/security/util/ByteArrayLexOrder;
@@ -12725,6 +12979,7 @@
 Lsun/security/util/DisabledAlgorithmConstraints$Constraints;
 Lsun/security/util/DisabledAlgorithmConstraints$KeySizeConstraint;
 Lsun/security/util/DisabledAlgorithmConstraints;
+Lsun/security/util/FilePaths;
 Lsun/security/util/KeyUtil;
 Lsun/security/util/Length;
 Lsun/security/util/ManifestDigester$Entry;
@@ -12737,6 +12992,8 @@
 Lsun/security/util/MemoryCache$SoftCacheEntry;
 Lsun/security/util/MemoryCache;
 Lsun/security/util/ObjectIdentifier;
+Lsun/security/util/PropertyExpander;
+Lsun/security/util/Resources;
 Lsun/security/util/ResourcesMgr$1;
 Lsun/security/util/ResourcesMgr;
 Lsun/security/util/SecurityConstants;
@@ -12813,6 +13070,7 @@
 Lsun/util/calendar/BaseCalendar$Date;
 Lsun/util/calendar/BaseCalendar;
 Lsun/util/calendar/CalendarDate;
+Lsun/util/calendar/CalendarSystem$GregorianHolder;
 Lsun/util/calendar/CalendarSystem;
 Lsun/util/calendar/CalendarUtils;
 Lsun/util/calendar/Era;
@@ -12838,6 +13096,7 @@
 Lsun/util/locale/ParseStatus;
 Lsun/util/locale/StringTokenIterator;
 Lsun/util/locale/UnicodeLocaleExtension;
+Lsun/util/locale/provider/CalendarDataUtility;
 Lsun/util/logging/LoggingProxy;
 Lsun/util/logging/LoggingSupport$1;
 Lsun/util/logging/LoggingSupport$2;
@@ -12862,10 +13121,12 @@
 [Lcom/android/okhttp/HttpUrl$Builder$ParseResult;
 [Lcom/android/okhttp/Protocol;
 [Lcom/android/okhttp/TlsVersion;
+[Lcom/android/okhttp/okio/ByteString;
 [Lcom/android/org/bouncycastle/asn1/ASN1Encodable;
 [Lcom/android/org/bouncycastle/asn1/ASN1Enumerated;
 [Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;
 [Lcom/android/org/bouncycastle/asn1/ASN1OctetString;
+[Lcom/android/org/bouncycastle/asn1/ASN1Primitive;
 [Lcom/android/org/bouncycastle/crypto/params/DHParameters;
 [Lcom/android/org/bouncycastle/crypto/params/DSAParameters;
 [Lcom/android/org/bouncycastle/jcajce/provider/asymmetric/x509/PEMUtil$Boundaries;
@@ -12889,6 +13150,7 @@
 [Ljava/lang/Character;
 [Ljava/lang/Class;
 [Ljava/lang/ClassLoader;
+[Ljava/lang/ClassValue$Entry;
 [Ljava/lang/Comparable;
 [Ljava/lang/Daemons$Daemon;
 [Ljava/lang/Double;
@@ -12896,11 +13158,13 @@
 [Ljava/lang/Float;
 [Ljava/lang/Integer;
 [Ljava/lang/Long;
+[Ljava/lang/Number;
 [Ljava/lang/Object;
 [Ljava/lang/Package;
 [Ljava/lang/Runnable;
 [Ljava/lang/Short;
 [Ljava/lang/StackTraceElement;
+[Ljava/lang/StackWalker$Option;
 [Ljava/lang/String;
 [Ljava/lang/StringBuilder;
 [Ljava/lang/Thread$State;
@@ -12912,6 +13176,7 @@
 [Ljava/lang/annotation/Annotation;
 [Ljava/lang/invoke/MethodHandle;
 [Ljava/lang/invoke/MethodType;
+[Ljava/lang/invoke/TypeDescriptor$OfField;
 [Ljava/lang/invoke/VarHandle$AccessMode;
 [Ljava/lang/invoke/VarHandle$AccessType;
 [Ljava/lang/ref/WeakReference;
@@ -12934,8 +13199,10 @@
 [Ljava/net/StandardProtocolFamily;
 [Ljava/nio/ByteBuffer;
 [Ljava/nio/channels/SelectionKey;
+[Ljava/nio/charset/CoderResult;
 [Ljava/nio/file/AccessMode;
 [Ljava/nio/file/CopyOption;
+[Ljava/nio/file/FileVisitResult;
 [Ljava/nio/file/LinkOption;
 [Ljava/nio/file/OpenOption;
 [Ljava/nio/file/StandardCopyOption;
@@ -12943,6 +13210,7 @@
 [Ljava/nio/file/attribute/FileAttribute;
 [Ljava/security/CodeSigner;
 [Ljava/security/CryptoPrimitive;
+[Ljava/security/MessageDigest;
 [Ljava/security/Permission;
 [Ljava/security/Principal;
 [Ljava/security/ProtectionDomain;
@@ -12982,15 +13250,16 @@
 [Ljava/util/Comparators$NaturalOrderComparator;
 [Ljava/util/Enumeration;
 [Ljava/util/Formatter$Flags;
-[Ljava/util/Formatter$FormatString;
 [Ljava/util/HashMap$Node;
 [Ljava/util/HashMap;
 [Ljava/util/Hashtable$HashtableEntry;
 [Ljava/util/List;
 [Ljava/util/Locale$Category;
 [Ljava/util/Locale$FilteringMode;
+[Ljava/util/Locale$IsoCountryCode;
 [Ljava/util/Locale;
 [Ljava/util/Map$Entry;
+[Ljava/util/Set;
 [Ljava/util/TimerTask;
 [Ljava/util/UUID;
 [Ljava/util/WeakHashMap$Entry;
@@ -12998,7 +13267,6 @@
 [Ljava/util/concurrent/ConcurrentHashMap$Node;
 [Ljava/util/concurrent/ConcurrentHashMap$Segment;
 [Ljava/util/concurrent/ForkJoinPool$WorkQueue;
-[Ljava/util/concurrent/ForkJoinTask$ExceptionNode;
 [Ljava/util/concurrent/ForkJoinTask;
 [Ljava/util/concurrent/RunnableScheduledFuture;
 [Ljava/util/concurrent/TimeUnit;
@@ -13050,6 +13318,7 @@
 [Z
 [[B
 [[C
+[[D
 [[F
 [[I
 [[J
@@ -13061,6 +13330,9 @@
 [[Ljava/lang/annotation/Annotation;
 [[Ljava/lang/invoke/MethodHandle;
 [[Ljava/math/BigInteger;
+[[Ljava/security/cert/Certificate;
+[[Ljava/security/cert/X509Certificate;
 [[S
+[[Z
 [[[B
 [[[I
diff --git a/build/boot/hiddenapi/hiddenapi-max-target-o-low-priority.txt b/build/boot/hiddenapi/hiddenapi-max-target-o-low-priority.txt
index f7b21a5..2a00be8 100644
--- a/build/boot/hiddenapi/hiddenapi-max-target-o-low-priority.txt
+++ b/build/boot/hiddenapi/hiddenapi-max-target-o-low-priority.txt
@@ -1453,7 +1453,6 @@
 Ldalvik/system/VMDebug;->dumpHprofDataDdms()V
 Ldalvik/system/VMDebug;->getAllocCount(I)I
 Ldalvik/system/VMDebug;->getHeapSpaceStats([J)V
-Ldalvik/system/VMDebug;->getInstancesOfClasses([Ljava/lang/Class;Z)[[Ljava/lang/Object;
 Ldalvik/system/VMDebug;->getInstructionCount([I)V
 Ldalvik/system/VMDebug;->getLoadedClassCount()I
 Ldalvik/system/VMDebug;->getMethodTracingMode()I
diff --git a/build/boot/preloaded-classes b/build/boot/preloaded-classes
index 791c7e2..2264b95 100644
--- a/build/boot/preloaded-classes
+++ b/build/boot/preloaded-classes
@@ -88,6 +88,7 @@
 com.android.okhttp.HttpUrl
 com.android.okhttp.HttpsHandler
 com.android.okhttp.Interceptor$Chain
+com.android.okhttp.MediaType
 com.android.okhttp.OkCacheContainer
 com.android.okhttp.OkHttpClient$1
 com.android.okhttp.OkHttpClient
@@ -119,8 +120,15 @@
 com.android.okhttp.internal.Util$1
 com.android.okhttp.internal.Util
 com.android.okhttp.internal.Version
+com.android.okhttp.internal.framed.FrameWriter
 com.android.okhttp.internal.framed.FramedConnection$Builder
+com.android.okhttp.internal.framed.FramedConnection$Listener$1
+com.android.okhttp.internal.framed.FramedConnection$Listener
 com.android.okhttp.internal.framed.FramedConnection
+com.android.okhttp.internal.framed.Header
+com.android.okhttp.internal.framed.PushObserver$1
+com.android.okhttp.internal.framed.PushObserver
+com.android.okhttp.internal.framed.Settings
 com.android.okhttp.internal.http.AuthenticatorAdapter
 com.android.okhttp.internal.http.CacheRequest
 com.android.okhttp.internal.http.CacheStrategy$Factory
@@ -227,13 +235,17 @@
 com.android.org.bouncycastle.asn1.ASN1TaggedObject
 com.android.org.bouncycastle.asn1.ASN1TaggedObjectParser
 com.android.org.bouncycastle.asn1.ASN1UTCTime
+com.android.org.bouncycastle.asn1.BERApplicationSpecific
 com.android.org.bouncycastle.asn1.BERApplicationSpecificParser
 com.android.org.bouncycastle.asn1.BEROctetString
 com.android.org.bouncycastle.asn1.BEROctetStringParser
+com.android.org.bouncycastle.asn1.BERSequence
 com.android.org.bouncycastle.asn1.BERSequenceParser
+com.android.org.bouncycastle.asn1.BERSet
 com.android.org.bouncycastle.asn1.BERSetParser
 com.android.org.bouncycastle.asn1.BERTaggedObjectParser
 com.android.org.bouncycastle.asn1.BERTags
+com.android.org.bouncycastle.asn1.ConstructedOctetStream
 com.android.org.bouncycastle.asn1.DERBMPString
 com.android.org.bouncycastle.asn1.DERBitString
 com.android.org.bouncycastle.asn1.DERExternalParser
@@ -260,6 +272,7 @@
 com.android.org.bouncycastle.asn1.DLExternal
 com.android.org.bouncycastle.asn1.DLFactory
 com.android.org.bouncycastle.asn1.DLSequence
+com.android.org.bouncycastle.asn1.DLSet
 com.android.org.bouncycastle.asn1.DefiniteLengthInputStream
 com.android.org.bouncycastle.asn1.InMemoryRepresentable
 com.android.org.bouncycastle.asn1.IndefiniteLengthInputStream
@@ -398,7 +411,10 @@
 com.android.org.bouncycastle.jcajce.provider.symmetric.DES
 com.android.org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings
 com.android.org.bouncycastle.jcajce.provider.symmetric.DESede
+com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$BasePBKDF2
+com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$BasePBKDF2WithHmacSHA1
 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings
+com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$PBKDF2WithHmacSHA1UTF8
 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2
 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings
 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12
@@ -420,6 +436,7 @@
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.ClassUtil
+com.android.org.bouncycastle.jcajce.provider.symmetric.util.GcmSpecUtil$2
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.GcmSpecUtil
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.PBE$Util
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.PBE
@@ -428,6 +445,7 @@
 com.android.org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
 com.android.org.bouncycastle.jcajce.provider.util.DigestFactory
 com.android.org.bouncycastle.jcajce.spec.AEADParameterSpec
+com.android.org.bouncycastle.jcajce.spec.PBKDF2KeySpec
 com.android.org.bouncycastle.jcajce.util.BCJcaJceHelper
 com.android.org.bouncycastle.jcajce.util.DefaultJcaJceHelper
 com.android.org.bouncycastle.jcajce.util.JcaJceHelper
@@ -436,6 +454,7 @@
 com.android.org.bouncycastle.jce.interfaces.BCKeyStore
 com.android.org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier
 com.android.org.bouncycastle.jce.provider.BouncyCastleProvider$1
+com.android.org.bouncycastle.jce.provider.BouncyCastleProvider$PrivateProvider
 com.android.org.bouncycastle.jce.provider.BouncyCastleProvider
 com.android.org.bouncycastle.jce.provider.BouncyCastleProviderConfiguration
 com.android.org.bouncycastle.jce.provider.CertStoreCollectionSpi
@@ -466,6 +485,7 @@
 dalvik.annotation.optimization.CriticalNative
 dalvik.annotation.optimization.FastNative
 dalvik.annotation.optimization.NeverCompile
+dalvik.annotation.optimization.NeverInline
 dalvik.system.AppSpecializationHooks
 dalvik.system.BaseDexClassLoader$Reporter
 dalvik.system.BaseDexClassLoader
@@ -500,13 +520,16 @@
 dalvik.system.SocketTagger
 dalvik.system.VMDebug
 dalvik.system.VMRuntime$HiddenApiUsageLogger
+dalvik.system.VMRuntime$SdkVersionContainer
 dalvik.system.VMRuntime
 dalvik.system.VMStack
+dalvik.system.ZipPathValidator$1
+dalvik.system.ZipPathValidator$Callback
+dalvik.system.ZipPathValidator
 dalvik.system.ZygoteHooks
 java.awt.font.NumericShaper
 java.awt.font.TextAttribute
 java.io.Bits
-java.io.BufferedInputStream$$ExternalSyntheticBackportWithForwarding0
 java.io.BufferedInputStream
 java.io.BufferedOutputStream
 java.io.BufferedReader
@@ -515,6 +538,7 @@
 java.io.ByteArrayOutputStream
 java.io.CharArrayReader
 java.io.CharArrayWriter
+java.io.CharConversionException
 java.io.Closeable
 java.io.Console
 java.io.DataInput
@@ -600,6 +624,8 @@
 java.io.OptionalDataException
 java.io.OutputStream
 java.io.OutputStreamWriter
+java.io.PipedInputStream
+java.io.PipedOutputStream
 java.io.PrintStream
 java.io.PrintWriter
 java.io.PushbackInputStream
@@ -621,6 +647,8 @@
 java.io.WriteAbortedException
 java.io.Writer
 java.lang.AbstractMethodError
+java.lang.AbstractStringBuilder$$ExternalSyntheticLambda0
+java.lang.AbstractStringBuilder$$ExternalSyntheticLambda1
 java.lang.AbstractStringBuilder
 java.lang.AndroidHardcodedSystemProperties
 java.lang.Appendable
@@ -651,6 +679,8 @@
 java.lang.ClassLoader$SystemClassLoader
 java.lang.ClassLoader
 java.lang.ClassNotFoundException
+java.lang.ClassValue$Entry
+java.lang.ClassValue
 java.lang.CloneNotSupportedException
 java.lang.Cloneable
 java.lang.Comparable
@@ -681,8 +711,6 @@
 java.lang.InheritableThreadLocal
 java.lang.InstantiationError
 java.lang.InstantiationException
-java.lang.Integer$$ExternalSyntheticBackport0
-java.lang.Integer$$ExternalSyntheticBackport1
 java.lang.Integer$IntegerCache
 java.lang.Integer
 java.lang.InternalError
@@ -708,6 +736,8 @@
 java.lang.Process
 java.lang.ProcessBuilder$NullInputStream
 java.lang.ProcessBuilder$NullOutputStream
+java.lang.ProcessBuilder$Redirect$1
+java.lang.ProcessBuilder$Redirect$2
 java.lang.ProcessBuilder$Redirect
 java.lang.ProcessBuilder
 java.lang.ProcessEnvironment$ExternalData
@@ -726,9 +756,19 @@
 java.lang.SecurityManager
 java.lang.Short$ShortCache
 java.lang.Short
+java.lang.StackFrameInfo
 java.lang.StackOverflowError
+java.lang.StackStreamFactory$AbstractStackWalker
+java.lang.StackStreamFactory
 java.lang.StackTraceElement
+java.lang.StackWalker$Option
+java.lang.StackWalker$StackFrame
+java.lang.StackWalker
 java.lang.StrictMath
+java.lang.String$$ExternalSyntheticLambda0
+java.lang.String$$ExternalSyntheticLambda1
+java.lang.String$$ExternalSyntheticLambda2
+java.lang.String$$ExternalSyntheticLambda3
 java.lang.String$CaseInsensitiveComparator-IA
 java.lang.String$CaseInsensitiveComparator
 java.lang.String
@@ -736,8 +776,13 @@
 java.lang.StringBuilder
 java.lang.StringFactory
 java.lang.StringIndexOutOfBoundsException
+java.lang.StringLatin1$CharsSpliterator
+java.lang.StringLatin1$LinesSpliterator
+java.lang.StringLatin1
 java.lang.StringUTF16$CharsSpliterator
+java.lang.StringUTF16$CharsSpliteratorForString
 java.lang.StringUTF16$CodePointsSpliterator
+java.lang.StringUTF16$CodePointsSpliteratorForString
 java.lang.StringUTF16
 java.lang.System$PropertiesWithNonOverrideableDefaults
 java.lang.System
@@ -751,6 +796,7 @@
 java.lang.ThreadGroup
 java.lang.ThreadLocal$SuppliedThreadLocal
 java.lang.ThreadLocal$ThreadLocalMap$Entry
+java.lang.ThreadLocal$ThreadLocalMap-IA
 java.lang.ThreadLocal$ThreadLocalMap
 java.lang.ThreadLocal
 java.lang.Throwable$PrintStreamOrWriter
@@ -768,6 +814,7 @@
 java.lang.UNIXProcess$ProcessReaperThreadFactory
 java.lang.UNIXProcess
 java.lang.UnsatisfiedLinkError
+java.lang.UnsupportedClassVersionError
 java.lang.UnsupportedOperationException
 java.lang.VMClassLoader
 java.lang.VerifyError
@@ -845,10 +892,14 @@
 java.lang.invoke.Transformers$ReferenceArrayElementSetter
 java.lang.invoke.Transformers$ReferenceIdentity
 java.lang.invoke.Transformers$Spreader
+java.lang.invoke.Transformers$TableSwitch
 java.lang.invoke.Transformers$Transformer
 java.lang.invoke.Transformers$TryFinally
 java.lang.invoke.Transformers$VarargsCollector
 java.lang.invoke.Transformers$ZeroValue
+java.lang.invoke.TypeDescriptor$OfField
+java.lang.invoke.TypeDescriptor$OfMethod
+java.lang.invoke.TypeDescriptor
 java.lang.invoke.VarHandle$1
 java.lang.invoke.VarHandle$AccessMode
 java.lang.invoke.VarHandle$AccessType
@@ -905,6 +956,7 @@
 java.lang.reflect.WildcardType
 java.math.BigDecimal$1
 java.math.BigDecimal$LongOverflow
+java.math.BigDecimal$StringBuilderHelper
 java.math.BigDecimal
 java.math.BigInteger$UnsafeHolder
 java.math.BigInteger
@@ -981,6 +1033,7 @@
 java.net.Proxy
 java.net.ProxySelector
 java.net.ResponseCache
+java.net.ServerSocket$1
 java.net.ServerSocket
 java.net.Socket$1
 java.net.Socket$2
@@ -1014,6 +1067,7 @@
 java.net.UnknownServiceException
 java.nio.Bits
 java.nio.Buffer
+java.nio.BufferMismatch
 java.nio.BufferOverflowException
 java.nio.BufferUnderflowException
 java.nio.ByteBuffer
@@ -1087,6 +1141,7 @@
 java.nio.channels.spi.AbstractSelectionKey
 java.nio.channels.spi.AbstractSelector$1
 java.nio.channels.spi.AbstractSelector
+java.nio.channels.spi.AsynchronousChannelProvider
 java.nio.channels.spi.SelectorProvider$1
 java.nio.channels.spi.SelectorProvider
 java.nio.charset.CharacterCodingException
@@ -1094,14 +1149,13 @@
 java.nio.charset.CharsetDecoder
 java.nio.charset.CharsetEncoder
 java.nio.charset.CoderMalfunctionError
-java.nio.charset.CoderResult$1
-java.nio.charset.CoderResult$2
 java.nio.charset.CoderResult$Cache
 java.nio.charset.CoderResult
 java.nio.charset.CodingErrorAction
 java.nio.charset.IllegalCharsetNameException
 java.nio.charset.StandardCharsets
 java.nio.charset.UnsupportedCharsetException
+java.nio.charset.spi.CharsetProvider
 java.nio.file.AccessDeniedException
 java.nio.file.AccessMode
 java.nio.file.CopyMoveHelper
@@ -1115,6 +1169,8 @@
 java.nio.file.FileSystems$DefaultFileSystemHolder$1
 java.nio.file.FileSystems$DefaultFileSystemHolder
 java.nio.file.FileSystems
+java.nio.file.FileVisitResult
+java.nio.file.FileVisitor
 java.nio.file.Files$AcceptAllFilter
 java.nio.file.Files
 java.nio.file.InvalidPathException
@@ -1126,6 +1182,7 @@
 java.nio.file.Paths
 java.nio.file.ProviderMismatchException
 java.nio.file.SecureDirectoryStream
+java.nio.file.SimpleFileVisitor
 java.nio.file.StandardCopyOption
 java.nio.file.StandardOpenOption
 java.nio.file.Watchable
@@ -1167,12 +1224,14 @@
 java.security.KeyPairGenerator
 java.security.KeyPairGeneratorSpi
 java.security.KeyStore$1
+java.security.KeyStore$CallbackHandlerProtection
 java.security.KeyStore$Entry
 java.security.KeyStore$LoadStoreParameter
 java.security.KeyStore$PasswordProtection
 java.security.KeyStore$PrivateKeyEntry
 java.security.KeyStore$ProtectionParameter
 java.security.KeyStore$SecretKeyEntry
+java.security.KeyStore$SimpleLoadStoreParameter
 java.security.KeyStore$TrustedCertificateEntry
 java.security.KeyStore
 java.security.KeyStoreException
@@ -1342,6 +1401,7 @@
 java.time.Duration
 java.time.Instant$1
 java.time.Instant
+java.time.InstantSource
 java.time.LocalDate$1
 java.time.LocalDate
 java.time.LocalDateTime
@@ -1371,15 +1431,17 @@
 java.time.format.DateTimeFormatterBuilder$$ExternalSyntheticLambda0
 java.time.format.DateTimeFormatterBuilder$1
 java.time.format.DateTimeFormatterBuilder$2
-java.time.format.DateTimeFormatterBuilder$3
 java.time.format.DateTimeFormatterBuilder$CharLiteralPrinterParser
 java.time.format.DateTimeFormatterBuilder$CompositePrinterParser
 java.time.format.DateTimeFormatterBuilder$DateTimePrinterParser
+java.time.format.DateTimeFormatterBuilder$DayPeriod$$ExternalSyntheticLambda0
+java.time.format.DateTimeFormatterBuilder$DayPeriod
 java.time.format.DateTimeFormatterBuilder$FractionPrinterParser
 java.time.format.DateTimeFormatterBuilder$InstantPrinterParser
 java.time.format.DateTimeFormatterBuilder$NumberPrinterParser
 java.time.format.DateTimeFormatterBuilder$OffsetIdPrinterParser
 java.time.format.DateTimeFormatterBuilder$PadPrinterParserDecorator
+java.time.format.DateTimeFormatterBuilder$PrefixTree$CI
 java.time.format.DateTimeFormatterBuilder$PrefixTree
 java.time.format.DateTimeFormatterBuilder$SettingsParser
 java.time.format.DateTimeFormatterBuilder$StringLiteralPrinterParser
@@ -1458,14 +1520,15 @@
 java.util.AbstractQueue
 java.util.AbstractSequentialList
 java.util.AbstractSet
+java.util.ArrayDeque$$ExternalSyntheticLambda1
 java.util.ArrayDeque$DeqIterator
 java.util.ArrayDeque$DescendingIterator
 java.util.ArrayDeque
 java.util.ArrayList$ArrayListSpliterator
-java.util.ArrayList$Itr-IA
 java.util.ArrayList$Itr
 java.util.ArrayList$ListItr
 java.util.ArrayList$SubList$1
+java.util.ArrayList$SubList$2
 java.util.ArrayList$SubList
 java.util.ArrayList
 java.util.ArrayPrefixHelpers$CumulateTask
@@ -1480,18 +1543,12 @@
 java.util.Arrays$ArrayList
 java.util.Arrays$NaturalOrder
 java.util.Arrays
-java.util.ArraysParallelSortHelpers$FJByte$Sorter
-java.util.ArraysParallelSortHelpers$FJChar$Sorter
-java.util.ArraysParallelSortHelpers$FJDouble$Sorter
-java.util.ArraysParallelSortHelpers$FJFloat$Sorter
-java.util.ArraysParallelSortHelpers$FJInt$Sorter
-java.util.ArraysParallelSortHelpers$FJLong$Sorter
 java.util.ArraysParallelSortHelpers$FJObject$Sorter
-java.util.ArraysParallelSortHelpers$FJShort$Sorter
 java.util.Base64$Decoder
 java.util.Base64$Encoder
 java.util.Base64
 java.util.BitSet
+java.util.Calendar$$ExternalSyntheticLambda0
 java.util.Calendar$Builder
 java.util.Calendar
 java.util.Collection
@@ -1566,6 +1623,7 @@
 java.util.Date
 java.util.Deque
 java.util.Dictionary
+java.util.DualPivotQuicksort$Sorter
 java.util.DualPivotQuicksort
 java.util.DuplicateFormatFlagsException
 java.util.EmptyStackException
@@ -1638,15 +1696,15 @@
 java.util.ImmutableCollections$AbstractImmutableMap
 java.util.ImmutableCollections$AbstractImmutableSet
 java.util.ImmutableCollections$List12
+java.util.ImmutableCollections$ListItr
 java.util.ImmutableCollections$ListN
-java.util.ImmutableCollections$Map0
 java.util.ImmutableCollections$Map1
+java.util.ImmutableCollections$MapN$1
+java.util.ImmutableCollections$MapN$MapNIterator
 java.util.ImmutableCollections$MapN
-java.util.ImmutableCollections$Set0
-java.util.ImmutableCollections$Set1
-java.util.ImmutableCollections$Set2
+java.util.ImmutableCollections$Set12
 java.util.ImmutableCollections$SetN
-java.util.ImmutableCollections
+java.util.ImmutableCollections$SubList
 java.util.InputMismatchException
 java.util.Iterator
 java.util.JumboEnumSet$EnumSetIterator
@@ -1662,6 +1720,7 @@
 java.util.LinkedHashMap$LinkedValues
 java.util.LinkedHashMap
 java.util.LinkedHashSet
+java.util.LinkedList$DescendingIterator
 java.util.LinkedList$ListItr
 java.util.LinkedList$Node
 java.util.LinkedList
@@ -1673,6 +1732,10 @@
 java.util.Locale$Cache
 java.util.Locale$Category
 java.util.Locale$FilteringMode
+java.util.Locale$IsoCountryCode$1
+java.util.Locale$IsoCountryCode$2
+java.util.Locale$IsoCountryCode$3
+java.util.Locale$IsoCountryCode
 java.util.Locale$LanguageRange
 java.util.Locale$LocaleKey
 java.util.Locale$NoImagePreloadHolder
@@ -1709,13 +1772,16 @@
 java.util.ResourceBundle$BundleReference
 java.util.ResourceBundle$CacheKey
 java.util.ResourceBundle$CacheKeyReference
+java.util.ResourceBundle$Control$$ExternalSyntheticLambda0
 java.util.ResourceBundle$Control$1
 java.util.ResourceBundle$Control$CandidateListCache
 java.util.ResourceBundle$Control
-java.util.ResourceBundle$LoaderReference
+java.util.ResourceBundle$KeyElementReference
+java.util.ResourceBundle$RBClassLoader$1
+java.util.ResourceBundle$RBClassLoader
 java.util.ResourceBundle$SingleFormatControl
 java.util.ResourceBundle
-java.util.Scanner$1
+java.util.Scanner$PatternLRUCache
 java.util.Scanner
 java.util.ServiceConfigurationError
 java.util.ServiceLoader$1
@@ -1770,6 +1836,7 @@
 java.util.TreeMap$Values
 java.util.TreeMap
 java.util.TreeSet
+java.util.Tripwire$$ExternalSyntheticLambda0
 java.util.Tripwire
 java.util.UUID$Holder
 java.util.UUID
@@ -1798,6 +1865,8 @@
 java.util.concurrent.Callable
 java.util.concurrent.CancellationException
 java.util.concurrent.CompletableFuture$AltResult
+java.util.concurrent.CompletableFuture$AsyncRun
+java.util.concurrent.CompletableFuture$AsyncSupply
 java.util.concurrent.CompletableFuture$AsynchronousCompletionTask
 java.util.concurrent.CompletableFuture$Completion
 java.util.concurrent.CompletableFuture$Signaller
@@ -1860,6 +1929,7 @@
 java.util.concurrent.ConcurrentHashMap
 java.util.concurrent.ConcurrentLinkedDeque$Node
 java.util.concurrent.ConcurrentLinkedDeque
+java.util.concurrent.ConcurrentLinkedQueue$$ExternalSyntheticLambda0
 java.util.concurrent.ConcurrentLinkedQueue$Itr
 java.util.concurrent.ConcurrentLinkedQueue$Node
 java.util.concurrent.ConcurrentLinkedQueue
@@ -1893,12 +1963,13 @@
 java.util.concurrent.Executors$RunnableAdapter
 java.util.concurrent.Executors
 java.util.concurrent.ForkJoinPool$1
+java.util.concurrent.ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory
 java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory
 java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory
 java.util.concurrent.ForkJoinPool$ManagedBlocker
 java.util.concurrent.ForkJoinPool$WorkQueue
 java.util.concurrent.ForkJoinPool
-java.util.concurrent.ForkJoinTask$ExceptionNode
+java.util.concurrent.ForkJoinTask$Aux
 java.util.concurrent.ForkJoinTask
 java.util.concurrent.ForkJoinWorkerThread
 java.util.concurrent.Future
@@ -1911,6 +1982,7 @@
 java.util.concurrent.LinkedBlockingQueue$Itr
 java.util.concurrent.LinkedBlockingQueue$Node
 java.util.concurrent.LinkedBlockingQueue
+java.util.concurrent.Phaser
 java.util.concurrent.PriorityBlockingQueue
 java.util.concurrent.RejectedExecutionException
 java.util.concurrent.RejectedExecutionHandler
@@ -1926,13 +1998,13 @@
 java.util.concurrent.Semaphore$NonfairSync
 java.util.concurrent.Semaphore$Sync
 java.util.concurrent.Semaphore
+java.util.concurrent.SynchronousQueue$TransferQueue$QNode
 java.util.concurrent.SynchronousQueue$TransferQueue
 java.util.concurrent.SynchronousQueue$TransferStack$SNode
 java.util.concurrent.SynchronousQueue$TransferStack
 java.util.concurrent.SynchronousQueue$Transferer
 java.util.concurrent.SynchronousQueue
 java.util.concurrent.ThreadFactory
-java.util.concurrent.ThreadLocalRandom
 java.util.concurrent.ThreadPoolExecutor$AbortPolicy
 java.util.concurrent.ThreadPoolExecutor$DiscardPolicy
 java.util.concurrent.ThreadPoolExecutor$Worker
@@ -1959,8 +2031,11 @@
 java.util.concurrent.atomic.Striped64$Cell
 java.util.concurrent.atomic.Striped64
 java.util.concurrent.locks.AbstractOwnableSynchronizer
+java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode
 java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject
+java.util.concurrent.locks.AbstractQueuedSynchronizer$ExclusiveNode
 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node
+java.util.concurrent.locks.AbstractQueuedSynchronizer$SharedNode
 java.util.concurrent.locks.AbstractQueuedSynchronizer
 java.util.concurrent.locks.Condition
 java.util.concurrent.locks.Lock
@@ -2007,8 +2082,13 @@
 java.util.function.IntUnaryOperator
 java.util.function.LongBinaryOperator
 java.util.function.LongConsumer
+java.util.function.LongFunction
+java.util.function.LongPredicate
 java.util.function.LongSupplier
 java.util.function.LongUnaryOperator
+java.util.function.ObjDoubleConsumer
+java.util.function.ObjIntConsumer
+java.util.function.ObjLongConsumer
 java.util.function.Predicate
 java.util.function.Supplier
 java.util.function.ToDoubleBiFunction
@@ -2030,6 +2110,7 @@
 java.util.jar.JarVerifier
 java.util.jar.Manifest$FastInputStream
 java.util.jar.Manifest
+java.util.logging.ConsoleHandler
 java.util.logging.ErrorManager
 java.util.logging.FileHandler$1
 java.util.logging.FileHandler$InitializationErrorManager
@@ -2084,6 +2165,7 @@
 java.util.stream.Collector$Characteristics
 java.util.stream.Collector
 java.util.stream.Collectors$$ExternalSyntheticLambda0
+java.util.stream.Collectors$$ExternalSyntheticLambda13
 java.util.stream.Collectors$$ExternalSyntheticLambda15
 java.util.stream.Collectors$$ExternalSyntheticLambda1
 java.util.stream.Collectors$$ExternalSyntheticLambda20
@@ -2149,6 +2231,8 @@
 java.util.stream.IntPipeline$$ExternalSyntheticLambda8
 java.util.stream.IntPipeline$4$1
 java.util.stream.IntPipeline$4
+java.util.stream.IntPipeline$9$1
+java.util.stream.IntPipeline$9
 java.util.stream.IntPipeline$Head
 java.util.stream.IntPipeline$StatelessOp
 java.util.stream.IntPipeline
@@ -2221,6 +2305,7 @@
 java.util.stream.ReferencePipeline$5
 java.util.stream.ReferencePipeline$6$1
 java.util.stream.ReferencePipeline$6
+java.util.stream.ReferencePipeline$7$1
 java.util.stream.ReferencePipeline$7
 java.util.stream.ReferencePipeline$Head
 java.util.stream.ReferencePipeline$StatefulOp
@@ -2265,10 +2350,12 @@
 java.util.stream.Streams
 java.util.stream.TerminalOp
 java.util.stream.TerminalSink
+java.util.stream.Tripwire$$ExternalSyntheticLambda0
 java.util.stream.Tripwire
 java.util.zip.Adler32
 java.util.zip.CRC32
 java.util.zip.CheckedInputStream
+java.util.zip.Checksum$1
 java.util.zip.Checksum
 java.util.zip.DataFormatException
 java.util.zip.Deflater
@@ -2301,6 +2388,7 @@
 javax.crypto.Cipher$SpiAndProviderUpdater
 javax.crypto.Cipher$Transform
 javax.crypto.Cipher
+javax.crypto.CipherInputStream
 javax.crypto.CipherOutputStream
 javax.crypto.CipherSpi
 javax.crypto.CryptoPermissions
@@ -2377,6 +2465,7 @@
 javax.net.ssl.SSLSocket
 javax.net.ssl.SSLSocketFactory$1
 javax.net.ssl.SSLSocketFactory
+javax.net.ssl.StandardConstants
 javax.net.ssl.TrustManager
 javax.net.ssl.TrustManagerFactory$1
 javax.net.ssl.TrustManagerFactory
@@ -2386,6 +2475,9 @@
 javax.net.ssl.X509KeyManager
 javax.net.ssl.X509TrustManager
 javax.security.auth.Destroyable
+javax.security.auth.callback.Callback
+javax.security.auth.callback.CallbackHandler
+javax.security.auth.callback.PasswordCallback
 javax.security.auth.callback.UnsupportedCallbackException
 javax.security.auth.x500.X500Principal
 javax.security.cert.Certificate
@@ -2393,9 +2485,11 @@
 javax.security.cert.CertificateException
 javax.security.cert.X509Certificate$1
 javax.security.cert.X509Certificate
+javax.xml.datatype.DatatypeConfigurationException
 javax.xml.datatype.DatatypeConstants$Field
 javax.xml.datatype.DatatypeConstants
 javax.xml.datatype.Duration
+javax.xml.namespace.QName
 javax.xml.parsers.DocumentBuilder
 javax.xml.parsers.DocumentBuilderFactory
 javax.xml.parsers.ParserConfigurationException
@@ -2408,17 +2502,24 @@
 jdk.internal.math.FloatingDecimal$BinaryToASCIIBuffer
 jdk.internal.math.FloatingDecimal$BinaryToASCIIConverter
 jdk.internal.math.FloatingDecimal$ExceptionalBinaryToASCIIBuffer
+jdk.internal.math.FloatingDecimal$HexFloatPattern
 jdk.internal.math.FloatingDecimal$PreparedASCIIToBinaryBuffer
 jdk.internal.math.FloatingDecimal
+jdk.internal.math.FormattedFloatingDecimal$1
+jdk.internal.math.FormattedFloatingDecimal$2
 jdk.internal.math.FormattedFloatingDecimal$Form
 jdk.internal.math.FormattedFloatingDecimal
 jdk.internal.misc.JavaObjectInputStreamAccess
 jdk.internal.misc.SharedSecrets
+jdk.internal.misc.TerminatingThreadLocal$1
+jdk.internal.misc.TerminatingThreadLocal
 jdk.internal.misc.Unsafe
+jdk.internal.misc.UnsafeConstants
 jdk.internal.misc.VM
 jdk.internal.reflect.Reflection
 jdk.internal.util.ArraysSupport
 jdk.internal.util.Preconditions
+jdk.internal.util.StaticProperty
 libcore.content.type.MimeMap$$ExternalSyntheticLambda0
 libcore.content.type.MimeMap$Builder$Element
 libcore.content.type.MimeMap$Builder
@@ -2426,7 +2527,6 @@
 libcore.content.type.MimeMap
 libcore.icu.CollationKeyICU
 libcore.icu.DateIntervalFormat
-libcore.icu.DateUtilsBridge
 libcore.icu.DecimalFormatData
 libcore.icu.ICU
 libcore.icu.LocaleData
@@ -2499,17 +2599,24 @@
 org.apache.harmony.xml.ExpatException
 org.apache.harmony.xml.ExpatParser$CurrentAttributes
 org.apache.harmony.xml.ExpatParser$ExpatLocator
+org.apache.harmony.xml.ExpatParser$ParseException
 org.apache.harmony.xml.ExpatParser
 org.apache.harmony.xml.ExpatReader
+org.apache.harmony.xml.dom.AttrImpl
+org.apache.harmony.xml.dom.CDATASectionImpl
 org.apache.harmony.xml.dom.CharacterDataImpl
+org.apache.harmony.xml.dom.CommentImpl
 org.apache.harmony.xml.dom.DOMImplementationImpl
 org.apache.harmony.xml.dom.DocumentImpl
+org.apache.harmony.xml.dom.DocumentTypeImpl
 org.apache.harmony.xml.dom.ElementImpl
+org.apache.harmony.xml.dom.EntityReferenceImpl
 org.apache.harmony.xml.dom.InnerNodeImpl
 org.apache.harmony.xml.dom.LeafNodeImpl
 org.apache.harmony.xml.dom.NodeImpl$1
 org.apache.harmony.xml.dom.NodeImpl
 org.apache.harmony.xml.dom.NodeListImpl
+org.apache.harmony.xml.dom.ProcessingInstructionImpl
 org.apache.harmony.xml.dom.TextImpl
 org.apache.harmony.xml.parsers.DocumentBuilderFactoryImpl
 org.apache.harmony.xml.parsers.DocumentBuilderImpl
@@ -2523,15 +2630,20 @@
 org.json.JSONStringer$Scope
 org.json.JSONStringer
 org.json.JSONTokener
+org.w3c.dom.Attr
+org.w3c.dom.CDATASection
 org.w3c.dom.CharacterData
+org.w3c.dom.Comment
 org.w3c.dom.DOMException
 org.w3c.dom.DOMImplementation
 org.w3c.dom.Document
 org.w3c.dom.DocumentFragment
 org.w3c.dom.DocumentType
 org.w3c.dom.Element
+org.w3c.dom.EntityReference
 org.w3c.dom.Node
 org.w3c.dom.NodeList
+org.w3c.dom.ProcessingInstruction
 org.w3c.dom.Text
 org.w3c.dom.TypeInfo
 org.xml.sax.AttributeList
@@ -2547,6 +2659,7 @@
 org.xml.sax.SAXException
 org.xml.sax.SAXNotRecognizedException
 org.xml.sax.SAXNotSupportedException
+org.xml.sax.SAXParseException
 org.xml.sax.XMLFilter
 org.xml.sax.XMLReader
 org.xml.sax.ext.DeclHandler
@@ -2555,6 +2668,7 @@
 org.xml.sax.ext.LexicalHandler
 org.xml.sax.helpers.AttributesImpl
 org.xml.sax.helpers.DefaultHandler
+org.xml.sax.helpers.LocatorImpl
 org.xml.sax.helpers.NamespaceSupport
 org.xml.sax.helpers.XMLFilterImpl
 org.xmlpull.v1.XmlPullParser
@@ -2575,7 +2689,6 @@
 sun.misc.JavaIOFileDescriptorAccess
 sun.misc.LRUCache
 sun.misc.SharedSecrets
-sun.misc.Unsafe$$ExternalSyntheticBackportWithForwarding0
 sun.misc.Unsafe
 sun.misc.VM
 sun.misc.Version
@@ -2615,6 +2728,7 @@
 sun.nio.ch.IOStatus
 sun.nio.ch.IOUtil
 sun.nio.ch.Interruptible
+sun.nio.ch.LinuxAsynchronousChannelProvider
 sun.nio.ch.NativeDispatcher
 sun.nio.ch.NativeObject
 sun.nio.ch.NativeThread
@@ -2705,6 +2819,7 @@
 sun.security.jca.Providers
 sun.security.jca.ServiceId
 sun.security.pkcs.ContentInfo
+sun.security.pkcs.ESSCertId
 sun.security.pkcs.PKCS7$VerbatimX509Certificate
 sun.security.pkcs.PKCS7$WrappedX509Certificate
 sun.security.pkcs.PKCS7
@@ -2743,6 +2858,7 @@
 sun.security.util.AbstractAlgorithmConstraints$1
 sun.security.util.AbstractAlgorithmConstraints
 sun.security.util.AlgorithmDecomposer
+sun.security.util.AnchorCertificates$1
 sun.security.util.AnchorCertificates
 sun.security.util.BitArray
 sun.security.util.ByteArrayLexOrder
@@ -2763,6 +2879,7 @@
 sun.security.util.DisabledAlgorithmConstraints$Constraints
 sun.security.util.DisabledAlgorithmConstraints$KeySizeConstraint
 sun.security.util.DisabledAlgorithmConstraints
+sun.security.util.FilePaths
 sun.security.util.KeyUtil
 sun.security.util.Length
 sun.security.util.ManifestDigester$Entry
@@ -2775,6 +2892,9 @@
 sun.security.util.MemoryCache$SoftCacheEntry
 sun.security.util.MemoryCache
 sun.security.util.ObjectIdentifier
+sun.security.util.PropertyExpander
+sun.security.util.Resources
+sun.security.util.ResourcesMgr$1
 sun.security.util.ResourcesMgr
 sun.security.util.SecurityConstants
 sun.security.util.SignatureFileVerifier
@@ -2850,6 +2970,7 @@
 sun.util.calendar.BaseCalendar$Date
 sun.util.calendar.BaseCalendar
 sun.util.calendar.CalendarDate
+sun.util.calendar.CalendarSystem$GregorianHolder
 sun.util.calendar.CalendarSystem
 sun.util.calendar.CalendarUtils
 sun.util.calendar.Era
@@ -2875,6 +2996,7 @@
 sun.util.locale.ParseStatus
 sun.util.locale.StringTokenIterator
 sun.util.locale.UnicodeLocaleExtension
+sun.util.locale.provider.CalendarDataUtility
 sun.util.logging.LoggingProxy
 sun.util.logging.LoggingSupport$1
 sun.util.logging.LoggingSupport$2
@@ -2892,21 +3014,26 @@
 [I
 [J
 [Landroid.system.StructCapUserData;
+[Landroid.system.StructIfaddrs;
 [Landroid.system.StructPollfd;
 [Lcom.android.okhttp.CipherSuite;
 [Lcom.android.okhttp.ConnectionSpec;
 [Lcom.android.okhttp.HttpUrl$Builder$ParseResult;
 [Lcom.android.okhttp.Protocol;
 [Lcom.android.okhttp.TlsVersion;
+[Lcom.android.okhttp.okio.ByteString;
 [Lcom.android.org.bouncycastle.asn1.ASN1Encodable;
+[Lcom.android.org.bouncycastle.asn1.ASN1Enumerated;
 [Lcom.android.org.bouncycastle.asn1.ASN1ObjectIdentifier;
 [Lcom.android.org.bouncycastle.asn1.ASN1OctetString;
+[Lcom.android.org.bouncycastle.asn1.ASN1Primitive;
 [Lcom.android.org.bouncycastle.crypto.params.DHParameters;
 [Lcom.android.org.bouncycastle.crypto.params.DSAParameters;
 [Lcom.android.org.bouncycastle.jcajce.provider.asymmetric.x509.PEMUtil$Boundaries;
 [Lcom.android.org.kxml2.io.KXmlParser$ValueContext;
 [Ldalvik.system.DexPathList$Element;
 [Ldalvik.system.DexPathList$NativeLibraryElement;
+[Ljava.io.Closeable;
 [Ljava.io.File$PathStatus;
 [Ljava.io.File;
 [Ljava.io.FileDescriptor;
@@ -2916,23 +3043,28 @@
 [Ljava.io.ObjectStreamClass$MemberSignature;
 [Ljava.io.ObjectStreamField;
 [Ljava.io.Serializable;
+[Ljava.lang.Boolean;
 [Ljava.lang.Byte;
 [Ljava.lang.CharSequence;
 [Ljava.lang.Character$UnicodeBlock;
 [Ljava.lang.Character;
 [Ljava.lang.Class;
 [Ljava.lang.ClassLoader;
+[Ljava.lang.ClassValue$Entry;
 [Ljava.lang.Comparable;
 [Ljava.lang.Daemons$Daemon;
+[Ljava.lang.Double;
 [Ljava.lang.Enum;
 [Ljava.lang.Float;
 [Ljava.lang.Integer;
 [Ljava.lang.Long;
+[Ljava.lang.Number;
 [Ljava.lang.Object;
 [Ljava.lang.Package;
 [Ljava.lang.Runnable;
 [Ljava.lang.Short;
 [Ljava.lang.StackTraceElement;
+[Ljava.lang.StackWalker$Option;
 [Ljava.lang.String;
 [Ljava.lang.StringBuilder;
 [Ljava.lang.Thread$State;
@@ -2944,6 +3076,7 @@
 [Ljava.lang.annotation.Annotation;
 [Ljava.lang.invoke.MethodHandle;
 [Ljava.lang.invoke.MethodType;
+[Ljava.lang.invoke.TypeDescriptor$OfField;
 [Ljava.lang.invoke.VarHandle$AccessMode;
 [Ljava.lang.invoke.VarHandle$AccessType;
 [Ljava.lang.ref.WeakReference;
@@ -2966,8 +3099,10 @@
 [Ljava.net.StandardProtocolFamily;
 [Ljava.nio.ByteBuffer;
 [Ljava.nio.channels.SelectionKey;
+[Ljava.nio.charset.CoderResult;
 [Ljava.nio.file.AccessMode;
 [Ljava.nio.file.CopyOption;
+[Ljava.nio.file.FileVisitResult;
 [Ljava.nio.file.LinkOption;
 [Ljava.nio.file.OpenOption;
 [Ljava.nio.file.StandardCopyOption;
@@ -2975,12 +3110,15 @@
 [Ljava.nio.file.attribute.FileAttribute;
 [Ljava.security.CodeSigner;
 [Ljava.security.CryptoPrimitive;
+[Ljava.security.MessageDigest;
 [Ljava.security.Permission;
 [Ljava.security.Principal;
 [Ljava.security.ProtectionDomain;
 [Ljava.security.Provider;
 [Ljava.security.cert.CRLReason;
+[Ljava.security.cert.CertPathValidatorException$BasicReason;
 [Ljava.security.cert.Certificate;
+[Ljava.security.cert.PKIXReason;
 [Ljava.security.cert.PKIXRevocationChecker$Option;
 [Ljava.security.cert.X509CRL;
 [Ljava.security.cert.X509Certificate;
@@ -3012,15 +3150,16 @@
 [Ljava.util.Comparators$NaturalOrderComparator;
 [Ljava.util.Enumeration;
 [Ljava.util.Formatter$Flags;
-[Ljava.util.Formatter$FormatString;
 [Ljava.util.HashMap$Node;
 [Ljava.util.HashMap;
 [Ljava.util.Hashtable$HashtableEntry;
 [Ljava.util.List;
 [Ljava.util.Locale$Category;
 [Ljava.util.Locale$FilteringMode;
+[Ljava.util.Locale$IsoCountryCode;
 [Ljava.util.Locale;
 [Ljava.util.Map$Entry;
+[Ljava.util.Set;
 [Ljava.util.TimerTask;
 [Ljava.util.UUID;
 [Ljava.util.WeakHashMap$Entry;
@@ -3028,7 +3167,6 @@
 [Ljava.util.concurrent.ConcurrentHashMap$Node;
 [Ljava.util.concurrent.ConcurrentHashMap$Segment;
 [Ljava.util.concurrent.ForkJoinPool$WorkQueue;
-[Ljava.util.concurrent.ForkJoinTask$ExceptionNode;
 [Ljava.util.concurrent.ForkJoinTask;
 [Ljava.util.concurrent.RunnableScheduledFuture;
 [Ljava.util.concurrent.TimeUnit;
@@ -3047,9 +3185,11 @@
 [Ljavax.net.ssl.SSLEngineResult$HandshakeStatus;
 [Ljavax.net.ssl.SSLEngineResult$Status;
 [Ljavax.net.ssl.TrustManager;
+[Ljavax.security.auth.callback.Callback;
 [Ljavax.security.auth.x500.X500Principal;
 [Ljavax.security.cert.X509Certificate;
 [Ljdk.internal.math.FDBigInteger;
+[Ljdk.internal.math.FormattedFloatingDecimal$Form;
 [Llibcore.io.ClassPathURLStreamHandler;
 [Llibcore.io.IoTracker$Mode;
 [Llibcore.reflect.AnnotationMember$DefaultValues;
@@ -3078,6 +3218,7 @@
 [Z
 [[B
 [[C
+[[D
 [[F
 [[I
 [[J
@@ -3089,6 +3230,9 @@
 [[Ljava.lang.annotation.Annotation;
 [[Ljava.lang.invoke.MethodHandle;
 [[Ljava.math.BigInteger;
+[[Ljava.security.cert.Certificate;
+[[Ljava.security.cert.X509Certificate;
 [[S
+[[Z
 [[[B
 [[[I
diff --git a/build/build-art-module.sh b/build/build-art-module.sh
deleted file mode 100755
index c6726f1..0000000
--- a/build/build-art-module.sh
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/bin/bash -e
-
-# This script builds the APEX modules, SDKs and module exports that the ART
-# Module provides.
-
-if [ ! -e build/make/core/Makefile ]; then
-  echo "$0 must be run from the top of the tree"
-  exit 1
-fi
-
-skip_apex=
-skip_module_sdk=
-build_args=()
-for arg; do
-  case "$arg" in
-    --skip-apex) skip_apex=true ;;
-    --skip-module-sdk) skip_module_sdk=true ;;
-    *) build_args+=("$arg") ;;
-  esac
-  shift
-done
-
-if [ -z "$skip_apex" ]; then
-  # Take the list of modules from MAINLINE_MODULES.
-  if [ -n "${MAINLINE_MODULES}" ]; then
-    read -r -a MAINLINE_MODULES <<< "${MAINLINE_MODULES}"
-  else
-    MAINLINE_MODULES=(
-      com.android.art
-      com.android.art.debug
-    )
-  fi
-else
-  MAINLINE_MODULES=()
-fi
-
-# Take the list of products to build the modules for from
-# MAINLINE_MODULE_PRODUCTS.
-if [ -n "${MAINLINE_MODULE_PRODUCTS}" ]; then
-  read -r -a MAINLINE_MODULE_PRODUCTS <<< "${MAINLINE_MODULE_PRODUCTS}"
-else
-  # The default products are the same as in
-  # build/soong/scripts/build-mainline-modules.sh.
-  MAINLINE_MODULE_PRODUCTS=(
-    art_module_arm
-    art_module_arm64
-    art_module_x86
-    art_module_x86_64
-  )
-fi
-
-MODULE_SDKS_AND_EXPORTS=()
-if [ -z "$skip_module_sdk" ]; then
-  MODULE_SDKS_AND_EXPORTS=(
-    art-module-sdk
-    art-module-host-exports
-    art-module-test-exports
-  )
-fi
-
-echo_and_run() {
-  echo "$*"
-  "$@"
-}
-
-export OUT_DIR=${OUT_DIR:-out}
-export DIST_DIR=${DIST_DIR:-${OUT_DIR}/dist}
-
-# Use same build settings as build_unbundled_mainline_module.sh, for build
-# consistency.
-# TODO(mast): Call out to a common script for building APEXes.
-export UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true
-export TARGET_BUILD_VARIANT=${TARGET_BUILD_VARIANT:-"user"}
-export TARGET_BUILD_DENSITY=alldpi
-export TARGET_BUILD_TYPE=release
-
-if [ ! -d frameworks/base ]; then
-  # Configure the build system for the reduced manifest branch.
-  export SOONG_ALLOW_MISSING_DEPENDENCIES=true
-fi
-
-if [ ${#MAINLINE_MODULES[*]} -gt 0 ]; then
-  (
-    export TARGET_BUILD_APPS="${MAINLINE_MODULES[*]}"
-
-    # We require .apex files here, so ensure we get them regardless of product
-    # settings.
-    export OVERRIDE_TARGET_FLATTEN_APEX=false
-
-    for product in ${MAINLINE_MODULE_PRODUCTS[*]}; do
-      echo_and_run build/soong/soong_ui.bash --make-mode \
-        TARGET_PRODUCT=${product} "${build_args[@]}" ${MAINLINE_MODULES[*]}
-
-      vars="$(TARGET_PRODUCT=${product} build/soong/soong_ui.bash \
-              --dumpvars-mode --vars="PRODUCT_OUT TARGET_ARCH")"
-      # Assign to a variable and eval that, since bash ignores any error status
-      # from the command substitution if it's directly on the eval line.
-      eval $vars
-
-      mkdir -p ${DIST_DIR}/${TARGET_ARCH}
-      for module in ${MAINLINE_MODULES[*]}; do
-        echo_and_run cp ${PRODUCT_OUT}/system/apex/${module}.apex \
-          ${DIST_DIR}/${TARGET_ARCH}/
-      done
-    done
-  )
-fi
-
-if [ ${#MODULE_SDKS_AND_EXPORTS[*]} -gt 0 ]; then
-  # Create multi-arch SDKs in a different out directory. The multi-arch script
-  # uses Soong in --soong-only mode which cannot use the same directory as
-  # normal mode with make.
-  export OUT_DIR=${OUT_DIR}/aml
-
-  # Put the build system in apps building mode so we don't trip on platform
-  # dependencies, but there are no actual apps to build here.
-  export TARGET_BUILD_APPS=none
-
-  # We use force building LLVM components flag (even though we actually don't
-  # compile them) because we don't have bionic host prebuilts
-  # for them.
-  export FORCE_BUILD_LLVM_COMPONENTS=true
-
-  echo_and_run build/soong/scripts/build-aml-prebuilts.sh \
-    TARGET_PRODUCT=mainline_sdk "${build_args[@]}" ${MODULE_SDKS_AND_EXPORTS[*]}
-
-  rm -rf ${DIST_DIR}/mainline-sdks
-  mkdir -p ${DIST_DIR}
-  echo_and_run cp -r ${OUT_DIR}/soong/mainline-sdks ${DIST_DIR}/
-fi
diff --git a/build/codegen.go b/build/codegen.go
index 96dd223..3cc51a8 100644
--- a/build/codegen.go
+++ b/build/codegen.go
@@ -62,6 +62,8 @@
 			arch = &c.Codegen.Arm
 		case "arm64":
 			arch = &c.Codegen.Arm64
+		case "riscv64":
+			arch = &c.Codegen.Riscv64
 		case "x86":
 			arch = &c.Codegen.X86
 		case "x86_64":
@@ -205,7 +207,7 @@
 
 type codegenProperties struct {
 	Codegen struct {
-		Arm, Arm64, X86, X86_64 codegenArchProperties
+		Arm, Arm64, Riscv64, X86, X86_64 codegenArchProperties
 	}
 }
 
@@ -216,6 +218,8 @@
 		arches[s] = true
 		if s == "arm64" {
 			arches["arm"] = true
+		} else if s == "riscv64" {
+			arches["riscv64"] = true
 		} else if s == "x86_64" {
 			arches["x86"] = true
 		}
diff --git a/build/go.mod b/build/go.mod
new file mode 100644
index 0000000..0fe5ebd
--- /dev/null
+++ b/build/go.mod
@@ -0,0 +1,8 @@
+module art
+
+go 1.19
+
+require (
+	github.com/google/blueprint v0.0.0
+	android/soong v0.0.0
+)
diff --git a/build/go.work b/build/go.work
new file mode 100644
index 0000000..f3a0829
--- /dev/null
+++ b/build/go.work
@@ -0,0 +1,21 @@
+go 1.19
+
+use (
+	.
+	../../external/go-cmp
+	../../external/golang-protobuf
+	../../prebuilts/bazel/common/proto/analysis_v2
+	../../prebuilts/bazel/common/proto/build
+	../../build/blueprint
+	../../build/soong
+)
+
+replace (
+	github.com/golang/protobuf v0.0.0 => ../../external/golang-protobuf
+	github.com/google/blueprint v0.0.0 => ../../build/blueprint
+	github.com/google/go-cmp v0.0.0 => ../../external/go-cmp
+	google.golang.org/protobuf v0.0.0 => ../../external/golang-protobuf
+	prebuilts/bazel/common/proto/analysis_v2 v0.0.0 => ../../prebuilts/bazel/common/proto/analysis_v2
+	prebuilts/bazel/common/proto/build v0.0.0 => ../../prebuilts/bazel/common/proto/build
+	android/soong v0.0.0 => ../../build/soong
+)
diff --git a/build/makevars.go b/build/makevars.go
index 1965b5d..cc8b9cd 100644
--- a/build/makevars.go
+++ b/build/makevars.go
@@ -34,7 +34,7 @@
 		"bin/llvm-addr2line",
 		"bin/llvm-dwarfdump",
 		"bin/llvm-objdump",
-		"lib64/libc++.so.1",
+		"lib/libc++.so.1",
 	}
 )
 
@@ -62,7 +62,7 @@
 	// Create list of copy commands to install the content of the testcases directory.
 	testcasesContent := testcasesContent(ctx.Config())
 	copy_cmds := []string{}
-	for _, key := range android.SortedStringKeys(testcasesContent) {
+	for _, key := range android.SortedKeys(testcasesContent) {
 		copy_cmds = append(copy_cmds, testcasesContent[key]+":"+key)
 	}
 	ctx.Strict("ART_TESTCASES_CONTENT", strings.Join(copy_cmds, " "))
diff --git a/build/sdk/Android.bp b/build/sdk/Android.bp
index a9ee675..b47bdc2 100644
--- a/build/sdk/Android.bp
+++ b/build/sdk/Android.bp
@@ -93,17 +93,10 @@
         },
 
         android: {
-            bootclasspath_fragments: [
-                // Adds the fragment and its contents to the sdk.
-                "art-bootclasspath-fragment",
-            ],
-
-            systemserverclasspath_fragments: [
-                "art-systemserverclasspath-fragment",
-            ],
-
-            compat_configs: [
-                "libcore-platform-compat-config",
+            apexes: [
+                // Adds exportable dependencies of the API to the sdk,
+                // e.g. *classpath_fragments.
+                "com.android.art",
             ],
 
             java_header_libs: [
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc
index 38c37ec..effbee9 100644
--- a/cmdline/cmdline_parser_test.cc
+++ b/cmdline/cmdline_parser_test.cc
@@ -528,10 +528,12 @@
 TEST_F(CmdlineParserTest, TestIgnoreUnrecognized) {
   RuntimeParser::Builder parserBuilder;
 
+  // clang-format off
   parserBuilder
       .Define("-help")
           .IntoKey(M::Help)
       .IgnoreUnrecognized(true);
+  // clang-format on
 
   parser_.reset(new RuntimeParser(parserBuilder.Build()));
 
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h
index dc2f8b7..b16f069 100644
--- a/cmdline/cmdline_types.h
+++ b/cmdline/cmdline_types.h
@@ -527,6 +527,8 @@
     return gc::kCollectorTypeSS;
   } else if (option == "CC") {
     return gc::kCollectorTypeCC;
+  } else if (option == "CMC") {
+    return gc::kCollectorTypeCMC;
   } else {
     return gc::kCollectorTypeNone;
   }
@@ -539,7 +541,7 @@
   bool verify_pre_gc_heap_ = false;
   bool verify_pre_sweeping_heap_ = kIsDebugBuild;
   bool generational_cc = kEnableGenerationalCCByDefault;
-  bool verify_post_gc_heap_ = false;
+  bool verify_post_gc_heap_ = kIsDebugBuild;
   bool verify_pre_gc_rosalloc_ = kIsDebugBuild;
   bool verify_pre_sweeping_rosalloc_ = false;
   bool verify_post_gc_rosalloc_ = false;
diff --git a/cmdline/detail/cmdline_parse_argument_detail.h b/cmdline/detail/cmdline_parse_argument_detail.h
index de0a588..c47efe1 100644
--- a/cmdline/detail/cmdline_parse_argument_detail.h
+++ b/cmdline/detail/cmdline_parse_argument_detail.h
@@ -150,7 +150,7 @@
     for (auto cname : names_) {
       std::string_view name = cname;
       if (using_blanks_) {
-        name = name.substr(0, name.find("_"));
+        name = name.substr(0, name.find('_'));
       }
       auto& os = vios.Stream();
       auto print_once = [&]() {
@@ -485,6 +485,7 @@
 
       // Error case: Fail, telling the user what the allowed values were.
       std::vector<std::string> allowed_values;
+      allowed_values.reserve(argument_info_.names_.size());
       for (auto&& arg_name : argument_info_.names_) {
         allowed_values.push_back(arg_name);
       }
diff --git a/cmdline/token_range.h b/cmdline/token_range.h
index e28ead9..e917e1d 100644
--- a/cmdline/token_range.h
+++ b/cmdline/token_range.h
@@ -81,7 +81,7 @@
   {}
 
   // Non-copying constructor. Retain reference to existing list of tokens.
-  TokenRange(std::shared_ptr<TokenList> token_list,
+  TokenRange(const std::shared_ptr<TokenList>& token_list,
              TokenList::const_iterator it_begin,
              TokenList::const_iterator it_end)
     : token_list_(token_list),
@@ -98,7 +98,7 @@
   TokenRange(TokenRange&&) = default;
 
   // Non-copying constructor. Retains reference to an existing list of tokens, with offset.
-  explicit TokenRange(std::shared_ptr<TokenList> token_list)
+  explicit TokenRange(const std::shared_ptr<TokenList>& token_list)
     : token_list_(token_list),
       begin_(token_list_->begin()),
       end_(token_list_->end())
diff --git a/cmdline/unit.h b/cmdline/unit.h
index f73981f..0b5c344 100644
--- a/cmdline/unit.h
+++ b/cmdline/unit.h
@@ -21,13 +21,7 @@
 
 // Used for arguments that simply indicate presence (e.g. "-help") without any values.
 struct Unit {
-  // Historical note: We specified a user-defined constructor to avoid
-  // 'Conditional jump or move depends on uninitialised value(s)' errors
-  // when running Valgrind.
-  Unit() {}
-  Unit(const Unit&) = default;
-  ~Unit() {}
-  bool operator==(Unit) const {
+  bool operator==(const Unit&) const {
     return true;
   }
 };
diff --git a/compiler/Android.bp b/compiler/Android.bp
index de98fdb..a879bd8 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -33,11 +33,8 @@
     defaults: ["art_defaults"],
     host_supported: true,
     srcs: [
-        "compiled_method.cc",
         "debug/elf_debug_writer.cc",
         "dex/inline_method_analyser.cc",
-        "dex/verification_results.cc",
-        "driver/compiled_method_storage.cc",
         "driver/compiler_options.cc",
         "driver/dex_compilation_unit.cc",
         "jit/jit_compiler.cc",
@@ -94,10 +91,10 @@
         "optimizing/ssa_phi_elimination.cc",
         "optimizing/stack_map_stream.cc",
         "optimizing/superblock_cloner.cc",
+        "optimizing/write_barrier_elimination.cc",
         "trampolines/trampoline_compiler.cc",
         "utils/assembler.cc",
         "utils/jni_macro_assembler.cc",
-        "utils/swap_space.cc",
         "compiler.cc",
     ],
 
@@ -133,6 +130,11 @@
                 "utils/arm64/managed_register_arm64.cc",
             ],
         },
+        riscv64: {
+            srcs: [
+                "utils/riscv64/managed_register_riscv64.cc",
+            ],
+        },
         x86: {
             srcs: [
                 "jni/quick/x86/calling_convention_x86.cc",
@@ -176,6 +178,8 @@
     ],
 
     export_include_dirs: ["."],
+    // Not using .map.txt because this is an internal API
+    version_script: "libart-compiler.map",
 }
 
 cc_defaults {
@@ -228,7 +232,7 @@
         "libprofile",
         "libdexfile",
     ],
-    whole_static_libs: ["libelffile"],
+    static_libs: ["libelffile"],
     runtime_libs: [
         // `art::HGraphVisualizerDisassembler::HGraphVisualizerDisassembler` may dynamically load
         // `libart-disassembler.so`.
@@ -245,6 +249,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -296,7 +301,7 @@
         "libprofiled",
         "libdexfiled",
     ],
-    whole_static_libs: ["libelffiled"],
+    static_libs: ["libelffiled"],
     runtime_libs: [
         // `art::HGraphVisualizerDisassembler::HGraphVisualizerDisassembler` may dynamically load
         // `libartd-disassembler.so`.
@@ -369,6 +374,7 @@
     data: [
         ":art-gtest-jars-ExceptionHandle",
         ":art-gtest-jars-Interfaces",
+        ":art-gtest-jars-Main",
         ":art-gtest-jars-MyClassNatives",
     ],
     tidy_timeout_srcs: [
@@ -381,9 +387,9 @@
         "optimizing/ssa_test.cc",
     ],
     srcs: [
+        "compiler_reflection_test.cc",
         "debug/dwarf/dwarf_test.cc",
         "debug/src_map_elem_test.cc",
-        "driver/compiled_method_storage_test.cc",
         "exception_test.cc",
         "jni/jni_compiler_test.cc",
         "linker/linker_patch_test.cc",
@@ -419,7 +425,6 @@
         "optimizing/suspend_check_test.cc",
         "utils/atomic_dex_ref_map_test.cc",
         "utils/dedupe_set_test.cc",
-        "utils/swap_space_test.cc",
 
         "jni/jni_cfi_test.cc",
         "optimizing/codegen_test.cc",
@@ -442,6 +447,11 @@
                 "utils/arm64/managed_register_arm64_test.cc",
             ],
         },
+        riscv64: {
+            srcs: [
+                "utils/riscv64/managed_register_riscv64_test.cc",
+            ],
+        },
         x86: {
             srcs: [
                 "utils/x86/managed_register_x86_test.cc",
@@ -465,8 +475,8 @@
     ],
 
     shared_libs: [
-        "libbacktrace",
         "libnativeloader",
+        "libunwindstack",
     ],
 
     target: {
@@ -488,10 +498,12 @@
     ],
     shared_libs: [
         "libprofiled",
-        "libartd-compiler",
         "libartd-simulator-container",
+        "liblzma",
     ],
     static_libs: [
+        "libartd-compiler",
+        "libelffiled",
         "libvixld",
     ],
 }
@@ -506,7 +518,8 @@
     data: [":generate-boot-image"],
     shared_libs: [
         "libprofile",
-        "libart-compiler",
+        "liblzma",
+        "libartpalette",
     ],
     static_libs: [
         // For now, link `libart-simulator-container` statically for simplicity,
@@ -515,6 +528,8 @@
         // TODO(b/192070541): Consider linking `libart-simulator-container`
         // dynamically.
         "libart-simulator-container",
+        "libart-compiler",
+        "libelffile",
         "libvixl",
     ],
     test_config: "art_standalone_compiler_tests.xml",
@@ -548,9 +563,11 @@
         },
     },
     shared_libs: [
-        "libartd-compiler",
+        "liblzma",
     ],
     static_libs: [
+        "libartd-compiler",
+        "libelffiled",
         "libvixld",
     ],
 }
diff --git a/compiler/art_standalone_compiler_tests.xml b/compiler/art_standalone_compiler_tests.xml
index f723971..394ac8d 100644
--- a/compiler/art_standalone_compiler_tests.xml
+++ b/compiler/art_standalone_compiler_tests.xml
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 <configuration description="Runs art_standalone_compiler_tests.">
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
+
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="art_standalone_compiler_tests->/data/local/tmp/art_standalone_compiler_tests/art_standalone_compiler_tests" />
@@ -24,6 +26,7 @@
         <option name="cleanup" value="true" />
         <option name="push" value="art-gtest-jars-ExceptionHandle.jar->/data/local/tmp/art_standalone_compiler_tests/art-gtest-jars-ExceptionHandle.jar" />
         <option name="push" value="art-gtest-jars-Interfaces.jar->/data/local/tmp/art_standalone_compiler_tests/art-gtest-jars-Interfaces.jar" />
+        <option name="push" value="art-gtest-jars-Main.jar->/data/local/tmp/art_standalone_compiler_tests/art-gtest-jars-Main.jar" />
         <option name="push" value="art-gtest-jars-MyClassNatives.jar->/data/local/tmp/art_standalone_compiler_tests/art-gtest-jars-MyClassNatives.jar" />
     </target_preparer>
 
diff --git a/compiler/cfi_test.h b/compiler/cfi_test.h
index 9755ef1..e65bee8 100644
--- a/compiler/cfi_test.h
+++ b/compiler/cfi_test.h
@@ -23,6 +23,7 @@
 
 #include "arch/instruction_set.h"
 #include "base/enums.h"
+#include "base/macros.h"
 #include "debug/dwarf/dwarf_test.h"
 #include "disassembler.h"
 #include "dwarf/dwarf_constants.h"
@@ -30,7 +31,7 @@
 #include "gtest/gtest.h"
 #include "thread.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CFITest : public dwarf::DwarfTest {
  public:
diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc
index bbb2016..442b96e 100644
--- a/compiler/common_compiler_test.cc
+++ b/compiler/common_compiler_test.cc
@@ -28,10 +28,8 @@
 #include "base/memfd.h"
 #include "base/utils.h"
 #include "class_linker.h"
-#include "compiled_method-inl.h"
 #include "dex/descriptors_names.h"
-#include "dex/verification_results.h"
-#include "driver/compiled_method_storage.h"
+#include "driver/compiled_code_storage.h"
 #include "driver/compiler_options.h"
 #include "jni/java_vm_ext.h"
 #include "interpreter/interpreter.h"
@@ -44,7 +42,7 @@
 #include "thread-current-inl.h"
 #include "utils/atomic_dex_ref_map-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CommonCompilerTestImpl::CodeAndMetadata {
  public:
@@ -58,10 +56,10 @@
     const uint32_t vmap_table_offset = vmap_table.empty() ? 0u
         : sizeof(OatQuickMethodHeader) + vmap_table.size();
     OatQuickMethodHeader method_header(vmap_table_offset);
-    const size_t code_alignment = GetInstructionSetAlignment(instruction_set);
+    const size_t code_alignment = GetInstructionSetCodeAlignment(instruction_set);
     DCHECK_ALIGNED_PARAM(kPageSize, code_alignment);
-    code_offset_ = RoundUp(vmap_table.size() + sizeof(method_header), code_alignment);
-    const uint32_t capacity = RoundUp(code_offset_ + code_size, kPageSize);
+    const uint32_t code_offset = RoundUp(vmap_table.size() + sizeof(method_header), code_alignment);
+    const uint32_t capacity = RoundUp(code_offset + code_size, kPageSize);
 
     // Create a memfd handle with sufficient capacity.
     android::base::unique_fd mem_fd(art::memfd_create_compat("test code", /*flags=*/ 0));
@@ -82,12 +80,12 @@
     CHECK(rw_map_.IsValid()) << error_msg;
 
     // Store data.
-    uint8_t* code_addr = rw_map_.Begin() + code_offset_;
+    uint8_t* code_addr = rw_map_.Begin() + code_offset;
     CHECK_ALIGNED_PARAM(code_addr, code_alignment);
-    CHECK_LE(vmap_table_offset, code_offset_);
+    CHECK_LE(vmap_table_offset, code_offset);
     memcpy(code_addr - vmap_table_offset, vmap_table.data(), vmap_table.size());
     static_assert(std::is_trivially_copyable<OatQuickMethodHeader>::value, "Cannot use memcpy");
-    CHECK_LE(sizeof(method_header), code_offset_);
+    CHECK_LE(sizeof(method_header), code_offset);
     memcpy(code_addr - sizeof(method_header), &method_header, sizeof(method_header));
     CHECK_LE(code_size, static_cast<size_t>(rw_map_.End() - code_addr));
     memcpy(code_addr, code.data(), code_size);
@@ -108,22 +106,84 @@
                               /*filename=*/ "test code",
                               &error_msg);
     CHECK(rx_map_.IsValid()) << error_msg;
+
+    DCHECK_LT(code_offset, rx_map_.Size());
+    size_t adjustment = GetInstructionSetEntryPointAdjustment(instruction_set);
+    entry_point_ = rx_map_.Begin() + code_offset + adjustment;
   }
 
-  const void* GetCodePointer() const {
+  const void* GetEntryPoint() const {
     DCHECK(rx_map_.IsValid());
-    DCHECK_LE(code_offset_, rx_map_.Size());
-    return rx_map_.Begin() + code_offset_;
+    return entry_point_;
   }
 
  private:
   MemMap rw_map_;
   MemMap rx_map_;
-  uint32_t code_offset_;
+  const void* entry_point_;
 
   DISALLOW_COPY_AND_ASSIGN(CodeAndMetadata);
 };
 
+class CommonCompilerTestImpl::OneCompiledMethodStorage final : public CompiledCodeStorage {
+ public:
+  OneCompiledMethodStorage() {}
+  ~OneCompiledMethodStorage() {}
+
+  CompiledMethod* CreateCompiledMethod(InstructionSet instruction_set,
+                                       ArrayRef<const uint8_t> code,
+                                       ArrayRef<const uint8_t> stack_map,
+                                       ArrayRef<const uint8_t> cfi ATTRIBUTE_UNUSED,
+                                       ArrayRef<const linker::LinkerPatch> patches,
+                                       bool is_intrinsic ATTRIBUTE_UNUSED) override {
+    // Supports only one method at a time.
+    CHECK_EQ(instruction_set_, InstructionSet::kNone);
+    CHECK_NE(instruction_set, InstructionSet::kNone);
+    instruction_set_ = instruction_set;
+    CHECK(code_.empty());
+    CHECK(!code.empty());
+    code_.assign(code.begin(), code.end());
+    CHECK(stack_map_.empty());
+    CHECK(!stack_map.empty());
+    stack_map_.assign(stack_map.begin(), stack_map.end());
+    CHECK(patches.empty()) << "Linker patches are unsupported for compiler gtests.";
+    return reinterpret_cast<CompiledMethod*>(this);
+  }
+
+  ArrayRef<const uint8_t> GetThunkCode(const linker::LinkerPatch& patch ATTRIBUTE_UNUSED,
+                                       /*out*/ std::string* debug_name  ATTRIBUTE_UNUSED) override {
+    LOG(FATAL) << "Unsupported.";
+    UNREACHABLE();
+  }
+
+  void SetThunkCode(const linker::LinkerPatch& patch ATTRIBUTE_UNUSED,
+                    ArrayRef<const uint8_t> code ATTRIBUTE_UNUSED,
+                    const std::string& debug_name ATTRIBUTE_UNUSED) override {
+    LOG(FATAL) << "Unsupported.";
+    UNREACHABLE();
+  }
+
+  InstructionSet GetInstructionSet() const {
+    CHECK_NE(instruction_set_, InstructionSet::kNone);
+    return instruction_set_;
+  }
+
+  ArrayRef<const uint8_t> GetCode() const {
+    CHECK(!code_.empty());
+    return ArrayRef<const uint8_t>(code_);
+  }
+
+  ArrayRef<const uint8_t> GetStackMap() const {
+    CHECK(!stack_map_.empty());
+    return ArrayRef<const uint8_t>(stack_map_);
+  }
+
+ private:
+  InstructionSet instruction_set_ = InstructionSet::kNone;
+  std::vector<uint8_t> code_;
+  std::vector<uint8_t> stack_map_;
+};
+
 std::unique_ptr<CompilerOptions> CommonCompilerTestImpl::CreateCompilerOptions(
     InstructionSet instruction_set, const std::string& variant) {
   std::unique_ptr<CompilerOptions> compiler_options = std::make_unique<CompilerOptions>();
@@ -143,24 +203,7 @@
                                                    InstructionSet instruction_set) {
   CHECK_NE(code.size(), 0u);
   code_and_metadata_.emplace_back(code, vmap_table, instruction_set);
-  return code_and_metadata_.back().GetCodePointer();
-}
-
-void CommonCompilerTestImpl::MakeExecutable(ArtMethod* method,
-                                            const CompiledMethod* compiled_method) {
-  CHECK(method != nullptr);
-  const void* method_code = nullptr;
-  // If the code size is 0 it means the method was skipped due to profile guided compilation.
-  if (compiled_method != nullptr && compiled_method->GetQuickCode().size() != 0u) {
-    const void* code_ptr = MakeExecutable(compiled_method->GetQuickCode(),
-                                          compiled_method->GetVmapTable(),
-                                          compiled_method->GetInstructionSet());
-    method_code =
-        CompiledMethod::CodePointer(code_ptr, compiled_method->GetInstructionSet());
-    LOG(INFO) << "MakeExecutable " << method->PrettyMethod() << " code=" << method_code;
-  }
-  Runtime::Current()->GetInstrumentation()->InitializeMethodsCode(
-      method, /*aot_code=*/ method_code);
+  return code_and_metadata_.back().GetEntryPoint();
 }
 
 void CommonCompilerTestImpl::SetUp() {
@@ -207,7 +250,6 @@
 
 void CommonCompilerTestImpl::SetUpRuntimeOptionsImpl() {
   compiler_options_.reset(new CompilerOptions);
-  verification_results_.reset(new VerificationResults());
   ApplyInstructionSet();
 }
 
@@ -221,7 +263,6 @@
 
 void CommonCompilerTestImpl::TearDown() {
   code_and_metadata_.clear();
-  verification_results_.reset();
   compiler_options_.reset();
 }
 
@@ -229,7 +270,7 @@
   CHECK(method != nullptr);
   TimingLogger timings("CommonCompilerTestImpl::CompileMethod", false, false);
   TimingLogger::ScopedTiming t(__FUNCTION__, &timings);
-  CompiledMethodStorage storage(/*swap_fd=*/ -1);
+  OneCompiledMethodStorage storage;
   CompiledMethod* compiled_method = nullptr;
   {
     DCHECK(!Runtime::Current()->IsStarted());
@@ -241,7 +282,6 @@
     Handle<mirror::DexCache> dex_cache =
         hs.NewHandle(GetClassLinker()->FindDexCache(self, dex_file));
     Handle<mirror::ClassLoader> class_loader = hs.NewHandle(method->GetClassLoader());
-    compiler_options_->verification_results_ = verification_results_.get();
     if (method->IsNative()) {
       compiled_method = compiler->JniCompile(method->GetAccessFlags(),
                                              method->GetDexMethodIndex(),
@@ -257,48 +297,17 @@
                                           dex_file,
                                           dex_cache);
     }
-    compiler_options_->verification_results_ = nullptr;
+    CHECK(compiled_method != nullptr) << "Failed to compile " << method->PrettyMethod();
+    CHECK_EQ(reinterpret_cast<OneCompiledMethodStorage*>(compiled_method), &storage);
   }
-  CHECK(method != nullptr);
   {
     TimingLogger::ScopedTiming t2("MakeExecutable", &timings);
-    MakeExecutable(method, compiled_method);
+    const void* method_code = MakeExecutable(storage.GetCode(),
+                                             storage.GetStackMap(),
+                                             storage.GetInstructionSet());
+    LOG(INFO) << "MakeExecutable " << method->PrettyMethod() << " code=" << method_code;
+    GetRuntime()->GetInstrumentation()->InitializeMethodsCode(method, /*aot_code=*/ method_code);
   }
-  CompiledMethod::ReleaseSwapAllocatedCompiledMethod(&storage, compiled_method);
-}
-
-void CommonCompilerTestImpl::CompileDirectMethod(Handle<mirror::ClassLoader> class_loader,
-                                                 const char* class_name,
-                                                 const char* method_name,
-                                                 const char* signature) {
-  std::string class_descriptor(DotToDescriptor(class_name));
-  Thread* self = Thread::Current();
-  ClassLinker* class_linker = GetClassLinker();
-  ObjPtr<mirror::Class> klass =
-      class_linker->FindClass(self, class_descriptor.c_str(), class_loader);
-  CHECK(klass != nullptr) << "Class not found " << class_name;
-  auto pointer_size = class_linker->GetImagePointerSize();
-  ArtMethod* method = klass->FindClassMethod(method_name, signature, pointer_size);
-  CHECK(method != nullptr && method->IsDirect()) << "Direct method not found: "
-      << class_name << "." << method_name << signature;
-  CompileMethod(method);
-}
-
-void CommonCompilerTestImpl::CompileVirtualMethod(Handle<mirror::ClassLoader> class_loader,
-                                                  const char* class_name,
-                                                  const char* method_name,
-                                                  const char* signature) {
-  std::string class_descriptor(DotToDescriptor(class_name));
-  Thread* self = Thread::Current();
-  ClassLinker* class_linker = GetClassLinker();
-  ObjPtr<mirror::Class> klass =
-      class_linker->FindClass(self, class_descriptor.c_str(), class_loader);
-  CHECK(klass != nullptr) << "Class not found " << class_name;
-  auto pointer_size = class_linker->GetImagePointerSize();
-  ArtMethod* method = klass->FindClassMethod(method_name, signature, pointer_size);
-  CHECK(method != nullptr && !method->IsDirect()) << "Virtual method not found: "
-      << class_name << "." << method_name << signature;
-  CompileMethod(method);
 }
 
 void CommonCompilerTestImpl::ClearBootImageOption() {
diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h
index 89cc1fa..f3cd132 100644
--- a/compiler/common_compiler_test.h
+++ b/compiler/common_compiler_test.h
@@ -24,25 +24,25 @@
 
 #include "arch/instruction_set.h"
 #include "arch/instruction_set_features.h"
+#include "base/macros.h"
 #include "common_runtime_test.h"
 #include "compiler.h"
 #include "oat_file.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace mirror {
 class ClassLoader;
 }  // namespace mirror
 
-class CompiledMethod;
 class CompilerOptions;
 class CumulativeLogger;
 class DexFile;
 class TimingLogger;
-class VerificationResults;
 
 template<class T> class Handle;
 
-class CommonCompilerTestImpl {
+// Export all symbols in `CommonCompilerTestImpl` for dex2oat tests.
+class EXPORT CommonCompilerTestImpl {
  public:
   static std::unique_ptr<CompilerOptions> CreateCompilerOptions(InstructionSet instruction_set,
                                                                 const std::string& variant);
@@ -55,9 +55,6 @@
                              ArrayRef<const uint8_t> vmap_table,
                              InstructionSet instruction_set);
 
-  void MakeExecutable(ArtMethod* method, const CompiledMethod* compiled_method)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
  protected:
   void SetUp();
 
@@ -74,14 +71,6 @@
 
   void CompileMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void CompileDirectMethod(Handle<mirror::ClassLoader> class_loader, const char* class_name,
-                           const char* method_name, const char* signature)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
-  void CompileVirtualMethod(Handle<mirror::ClassLoader> class_loader, const char* class_name,
-                            const char* method_name, const char* signature)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   void ApplyInstructionSet();
   void OverrideInstructionSetFeatures(InstructionSet instruction_set, const std::string& variant);
 
@@ -96,7 +85,6 @@
       = InstructionSetFeatures::FromCppDefines();
 
   std::unique_ptr<CompilerOptions> compiler_options_;
-  std::unique_ptr<VerificationResults> verification_results_;
 
  protected:
   virtual ClassLinker* GetClassLinker() = 0;
@@ -104,6 +92,8 @@
 
  private:
   class CodeAndMetadata;
+  class OneCompiledMethodStorage;
+
   std::vector<CodeAndMetadata> code_and_metadata_;
 };
 
diff --git a/compiler/compiled_method-inl.h b/compiler/compiled_method-inl.h
deleted file mode 100644
index e60b30f..0000000
--- a/compiler/compiled_method-inl.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ART_COMPILER_COMPILED_METHOD_INL_H_
-#define ART_COMPILER_COMPILED_METHOD_INL_H_
-
-#include "compiled_method.h"
-
-#include "base/array_ref.h"
-#include "base/length_prefixed_array.h"
-#include "linker/linker_patch.h"
-
-namespace art {
-
-inline ArrayRef<const uint8_t> CompiledCode::GetQuickCode() const {
-  return GetArray(quick_code_);
-}
-
-template <typename T>
-inline ArrayRef<const T> CompiledCode::GetArray(const LengthPrefixedArray<T>* array) {
-  if (array == nullptr) {
-    return ArrayRef<const T>();
-  }
-  DCHECK_NE(array->size(), 0u);
-  return ArrayRef<const T>(&array->At(0), array->size());
-}
-
-inline ArrayRef<const uint8_t> CompiledMethod::GetVmapTable() const {
-  return GetArray(vmap_table_);
-}
-
-inline ArrayRef<const uint8_t> CompiledMethod::GetCFIInfo() const {
-  return GetArray(cfi_info_);
-}
-
-inline ArrayRef<const linker::LinkerPatch> CompiledMethod::GetPatches() const {
-  return GetArray(patches_);
-}
-
-}  // namespace art
-
-#endif  // ART_COMPILER_COMPILED_METHOD_INL_H_
diff --git a/compiler/compiled_method.cc b/compiler/compiled_method.cc
deleted file mode 100644
index 03b87ef..0000000
--- a/compiler/compiled_method.cc
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-
-#include "compiled_method.h"
-
-#include "driver/compiled_method_storage.h"
-#include "utils/swap_space.h"
-
-namespace art {
-
-CompiledCode::CompiledCode(CompiledMethodStorage* storage,
-                           InstructionSet instruction_set,
-                           const ArrayRef<const uint8_t>& quick_code)
-    : storage_(storage),
-      quick_code_(storage->DeduplicateCode(quick_code)),
-      packed_fields_(InstructionSetField::Encode(instruction_set)) {
-}
-
-CompiledCode::~CompiledCode() {
-  GetStorage()->ReleaseCode(quick_code_);
-}
-
-bool CompiledCode::operator==(const CompiledCode& rhs) const {
-  if (quick_code_ != nullptr) {
-    if (rhs.quick_code_ == nullptr) {
-      return false;
-    } else if (quick_code_->size() != rhs.quick_code_->size()) {
-      return false;
-    } else {
-      return std::equal(quick_code_->begin(), quick_code_->end(), rhs.quick_code_->begin());
-    }
-  }
-  return (rhs.quick_code_ == nullptr);
-}
-
-size_t CompiledCode::AlignCode(size_t offset) const {
-  return AlignCode(offset, GetInstructionSet());
-}
-
-size_t CompiledCode::AlignCode(size_t offset, InstructionSet instruction_set) {
-  return RoundUp(offset, GetInstructionSetAlignment(instruction_set));
-}
-
-size_t CompiledCode::CodeDelta() const {
-  return CodeDelta(GetInstructionSet());
-}
-
-size_t CompiledCode::CodeDelta(InstructionSet instruction_set) {
-  switch (instruction_set) {
-    case InstructionSet::kArm:
-    case InstructionSet::kArm64:
-    case InstructionSet::kX86:
-    case InstructionSet::kX86_64:
-      return 0;
-    case InstructionSet::kThumb2: {
-      // +1 to set the low-order bit so a BLX will switch to Thumb mode
-      return 1;
-    }
-    default:
-      LOG(FATAL) << "Unknown InstructionSet: " << instruction_set;
-      UNREACHABLE();
-  }
-}
-
-const void* CompiledCode::CodePointer(const void* code_pointer, InstructionSet instruction_set) {
-  switch (instruction_set) {
-    case InstructionSet::kArm:
-    case InstructionSet::kArm64:
-    case InstructionSet::kX86:
-    case InstructionSet::kX86_64:
-      return code_pointer;
-    case InstructionSet::kThumb2: {
-      uintptr_t address = reinterpret_cast<uintptr_t>(code_pointer);
-      // Set the low-order bit so a BLX will switch to Thumb mode
-      address |= 0x1;
-      return reinterpret_cast<const void*>(address);
-    }
-    default:
-      LOG(FATAL) << "Unknown InstructionSet: " << instruction_set;
-      UNREACHABLE();
-  }
-}
-
-CompiledMethod::CompiledMethod(CompiledMethodStorage* storage,
-                               InstructionSet instruction_set,
-                               const ArrayRef<const uint8_t>& quick_code,
-                               const ArrayRef<const uint8_t>& vmap_table,
-                               const ArrayRef<const uint8_t>& cfi_info,
-                               const ArrayRef<const linker::LinkerPatch>& patches)
-    : CompiledCode(storage, instruction_set, quick_code),
-      vmap_table_(storage->DeduplicateVMapTable(vmap_table)),
-      cfi_info_(storage->DeduplicateCFIInfo(cfi_info)),
-      patches_(storage->DeduplicateLinkerPatches(patches)) {
-}
-
-CompiledMethod* CompiledMethod::SwapAllocCompiledMethod(
-    CompiledMethodStorage* storage,
-    InstructionSet instruction_set,
-    const ArrayRef<const uint8_t>& quick_code,
-    const ArrayRef<const uint8_t>& vmap_table,
-    const ArrayRef<const uint8_t>& cfi_info,
-    const ArrayRef<const linker::LinkerPatch>& patches) {
-  SwapAllocator<CompiledMethod> alloc(storage->GetSwapSpaceAllocator());
-  CompiledMethod* ret = alloc.allocate(1);
-  alloc.construct(ret,
-                  storage,
-                  instruction_set,
-                  quick_code,
-                  vmap_table,
-                  cfi_info, patches);
-  return ret;
-}
-
-void CompiledMethod::ReleaseSwapAllocatedCompiledMethod(CompiledMethodStorage* storage,
-                                                        CompiledMethod* m) {
-  SwapAllocator<CompiledMethod> alloc(storage->GetSwapSpaceAllocator());
-  alloc.destroy(m);
-  alloc.deallocate(m, 1);
-}
-
-CompiledMethod::~CompiledMethod() {
-  CompiledMethodStorage* storage = GetStorage();
-  storage->ReleaseLinkerPatches(patches_);
-  storage->ReleaseCFIInfo(cfi_info_);
-  storage->ReleaseVMapTable(vmap_table_);
-}
-
-}  // namespace art
diff --git a/compiler/compiled_method.h b/compiler/compiled_method.h
deleted file mode 100644
index e92777f..0000000
--- a/compiler/compiled_method.h
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2011 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_COMPILER_COMPILED_METHOD_H_
-#define ART_COMPILER_COMPILED_METHOD_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "arch/instruction_set.h"
-#include "base/bit_field.h"
-#include "base/bit_utils.h"
-
-namespace art {
-
-template <typename T> class ArrayRef;
-class CompiledMethodStorage;
-template<typename T> class LengthPrefixedArray;
-
-namespace linker {
-class LinkerPatch;
-}  // namespace linker
-
-class CompiledCode {
- public:
-  // For Quick to supply an code blob
-  CompiledCode(CompiledMethodStorage* storage,
-               InstructionSet instruction_set,
-               const ArrayRef<const uint8_t>& quick_code);
-
-  virtual ~CompiledCode();
-
-  InstructionSet GetInstructionSet() const {
-    return GetPackedField<InstructionSetField>();
-  }
-
-  ArrayRef<const uint8_t> GetQuickCode() const;
-
-  bool operator==(const CompiledCode& rhs) const;
-
-  // To align an offset from a page-aligned value to make it suitable
-  // for code storage. For example on ARM, to ensure that PC relative
-  // valu computations work out as expected.
-  size_t AlignCode(size_t offset) const;
-  static size_t AlignCode(size_t offset, InstructionSet instruction_set);
-
-  // returns the difference between the code address and a usable PC.
-  // mainly to cope with kThumb2 where the lower bit must be set.
-  size_t CodeDelta() const;
-  static size_t CodeDelta(InstructionSet instruction_set);
-
-  // Returns a pointer suitable for invoking the code at the argument
-  // code_pointer address.  Mainly to cope with kThumb2 where the
-  // lower bit must be set to indicate Thumb mode.
-  static const void* CodePointer(const void* code_pointer, InstructionSet instruction_set);
-
- protected:
-  static constexpr size_t kInstructionSetFieldSize =
-      MinimumBitsToStore(static_cast<size_t>(InstructionSet::kLast));
-  static constexpr size_t kNumberOfCompiledCodePackedBits = kInstructionSetFieldSize;
-  static constexpr size_t kMaxNumberOfPackedBits = sizeof(uint32_t) * kBitsPerByte;
-
-  template <typename T>
-  static ArrayRef<const T> GetArray(const LengthPrefixedArray<T>* array);
-
-  CompiledMethodStorage* GetStorage() {
-    return storage_;
-  }
-
-  template <typename BitFieldType>
-  typename BitFieldType::value_type GetPackedField() const {
-    return BitFieldType::Decode(packed_fields_);
-  }
-
-  template <typename BitFieldType>
-  void SetPackedField(typename BitFieldType::value_type value) {
-    DCHECK(IsUint<BitFieldType::size>(static_cast<uintptr_t>(value)));
-    packed_fields_ = BitFieldType::Update(value, packed_fields_);
-  }
-
- private:
-  using InstructionSetField = BitField<InstructionSet, 0u, kInstructionSetFieldSize>;
-
-  CompiledMethodStorage* const storage_;
-
-  // Used to store the compiled code.
-  const LengthPrefixedArray<uint8_t>* const quick_code_;
-
-  uint32_t packed_fields_;
-};
-
-class CompiledMethod final : public CompiledCode {
- public:
-  // Constructs a CompiledMethod.
-  // Note: Consider using the static allocation methods below that will allocate the CompiledMethod
-  //       in the swap space.
-  CompiledMethod(CompiledMethodStorage* storage,
-                 InstructionSet instruction_set,
-                 const ArrayRef<const uint8_t>& quick_code,
-                 const ArrayRef<const uint8_t>& vmap_table,
-                 const ArrayRef<const uint8_t>& cfi_info,
-                 const ArrayRef<const linker::LinkerPatch>& patches);
-
-  virtual ~CompiledMethod();
-
-  static CompiledMethod* SwapAllocCompiledMethod(
-      CompiledMethodStorage* storage,
-      InstructionSet instruction_set,
-      const ArrayRef<const uint8_t>& quick_code,
-      const ArrayRef<const uint8_t>& vmap_table,
-      const ArrayRef<const uint8_t>& cfi_info,
-      const ArrayRef<const linker::LinkerPatch>& patches);
-
-  static void ReleaseSwapAllocatedCompiledMethod(CompiledMethodStorage* storage, CompiledMethod* m);
-
-  bool IsIntrinsic() const {
-    return GetPackedField<IsIntrinsicField>();
-  }
-
-  // Marks the compiled method as being generated using an intrinsic codegen.
-  // Such methods have no relationships to their code items.
-  // This affects debug information generated at link time.
-  void MarkAsIntrinsic() {
-    DCHECK(!IsIntrinsic());
-    SetPackedField<IsIntrinsicField>(/* value= */ true);
-  }
-
-  ArrayRef<const uint8_t> GetVmapTable() const;
-
-  ArrayRef<const uint8_t> GetCFIInfo() const;
-
-  ArrayRef<const linker::LinkerPatch> GetPatches() const;
-
- private:
-  static constexpr size_t kIsIntrinsicLsb = kNumberOfCompiledCodePackedBits;
-  static constexpr size_t kIsIntrinsicSize = 1u;
-  static constexpr size_t kNumberOfCompiledMethodPackedBits = kIsIntrinsicLsb + kIsIntrinsicSize;
-  static_assert(kNumberOfCompiledMethodPackedBits <= CompiledCode::kMaxNumberOfPackedBits,
-                "Too many packed fields.");
-
-  using IsIntrinsicField = BitField<bool, kIsIntrinsicLsb, kIsIntrinsicSize>;
-
-  // For quick code, holds code infos which contain stack maps, inline information, and etc.
-  const LengthPrefixedArray<uint8_t>* const vmap_table_;
-  // For quick code, a FDE entry for the debug_frame section.
-  const LengthPrefixedArray<uint8_t>* const cfi_info_;
-  // For quick code, linker patches needed by the method.
-  const LengthPrefixedArray<linker::LinkerPatch>* const patches_;
-};
-
-}  // namespace art
-
-#endif  // ART_COMPILER_COMPILED_METHOD_H_
diff --git a/compiler/compiler.cc b/compiler/compiler.cc
index 98d7339..e2587c1 100644
--- a/compiler/compiler.cc
+++ b/compiler/compiler.cc
@@ -25,10 +25,10 @@
 #include "oat.h"
 #include "optimizing/optimizing_compiler.h"
 
-namespace art {
+namespace art HIDDEN {
 
 Compiler* Compiler::Create(const CompilerOptions& compiler_options,
-                           CompiledMethodStorage* storage,
+                           CompiledCodeStorage* storage,
                            Compiler::Kind kind) {
   // Check that oat version when runtime was compiled matches the oat version of the compiler.
   constexpr std::array<uint8_t, 4> compiler_oat_version = OatHeader::kOatVersion;
diff --git a/compiler/compiler.h b/compiler/compiler.h
index afa0dba..ce785bb 100644
--- a/compiler/compiler.h
+++ b/compiler/compiler.h
@@ -17,12 +17,13 @@
 #ifndef ART_COMPILER_COMPILER_H_
 #define ART_COMPILER_COMPILER_H_
 
+#include "base/macros.h"
 #include "base/mutex.h"
 #include "base/os.h"
 #include "compilation_kind.h"
 #include "dex/invoke_type.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace dex {
 struct CodeItem;
@@ -38,8 +39,8 @@
 }  // namespace mirror
 
 class ArtMethod;
+class CompiledCodeStorage;
 class CompiledMethod;
-class CompiledMethodStorage;
 class CompilerOptions;
 class DexFile;
 template<class T> class Handle;
@@ -52,9 +53,9 @@
     kOptimizing
   };
 
-  static Compiler* Create(const CompilerOptions& compiler_options,
-                          CompiledMethodStorage* storage,
-                          Kind kind);
+  EXPORT static Compiler* Create(const CompilerOptions& compiler_options,
+                                 CompiledCodeStorage* storage,
+                                 Kind kind);
 
   virtual bool CanCompileMethod(uint32_t method_idx, const DexFile& dex_file) const = 0;
 
@@ -99,7 +100,7 @@
 
  protected:
   Compiler(const CompilerOptions& compiler_options,
-           CompiledMethodStorage* storage,
+           CompiledCodeStorage* storage,
            uint64_t warning) :
       compiler_options_(compiler_options),
       storage_(storage),
@@ -110,13 +111,13 @@
     return compiler_options_;
   }
 
-  CompiledMethodStorage* GetCompiledMethodStorage() const {
+  CompiledCodeStorage* GetCompiledCodeStorage() const {
     return storage_;
   }
 
  private:
   const CompilerOptions& compiler_options_;
-  CompiledMethodStorage* const storage_;
+  CompiledCodeStorage* const storage_;
   const uint64_t maximum_compilation_time_before_warning_;
 
   DISALLOW_COPY_AND_ASSIGN(Compiler);
diff --git a/compiler/compiler_reflection_test.cc b/compiler/compiler_reflection_test.cc
new file mode 100644
index 0000000..f3c07db
--- /dev/null
+++ b/compiler/compiler_reflection_test.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "reflection.h"
+
+#include "base/macros.h"
+#include "class_linker.h"
+#include "common_compiler_test.h"
+#include "handle_scope-inl.h"
+#include "jni/jni_internal.h"
+#include "mirror/class.h"
+#include "mirror/class_loader.h"
+
+namespace art HIDDEN {
+
+class CompilerReflectionTest : public CommonCompilerTest {};
+
+TEST_F(CompilerReflectionTest, StaticMainMethod) {
+  ScopedObjectAccess soa(Thread::Current());
+  jobject jclass_loader = LoadDex("Main");
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::ClassLoader> class_loader(
+      hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader)));
+
+  ObjPtr<mirror::Class> klass = class_linker_->FindClass(soa.Self(), "LMain;", class_loader);
+  ASSERT_TRUE(klass != nullptr);
+
+  ArtMethod* method = klass->FindClassMethod("main",
+                                             "([Ljava/lang/String;)V",
+                                             kRuntimePointerSize);
+  ASSERT_TRUE(method != nullptr);
+  ASSERT_TRUE(method->IsStatic());
+
+  CompileMethod(method);
+
+  // Start runtime.
+  bool started = runtime_->Start();
+  CHECK(started);
+  soa.Self()->TransitionFromSuspendedToRunnable();
+
+  jvalue args[1];
+  args[0].l = nullptr;
+  InvokeWithJValues(soa, nullptr, jni::EncodeArtMethod(method), args);
+}
+
+}  // namespace art
diff --git a/compiler/debug/debug_info.h b/compiler/debug/debug_info.h
index 04c6991..4027f11 100644
--- a/compiler/debug/debug_info.h
+++ b/compiler/debug/debug_info.h
@@ -20,9 +20,10 @@
 #include <map>
 
 #include "base/array_ref.h"
+#include "base/macros.h"
 #include "method_debug_info.h"
 
-namespace art {
+namespace art HIDDEN {
 class DexFile;
 
 namespace debug {
diff --git a/compiler/debug/dwarf/dwarf_test.cc b/compiler/debug/dwarf/dwarf_test.cc
index 8897e45..14c92b2 100644
--- a/compiler/debug/dwarf/dwarf_test.cc
+++ b/compiler/debug/dwarf/dwarf_test.cc
@@ -23,7 +23,7 @@
 #include "dwarf/headers.h"
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace dwarf {
 
 // Run the tests only on host since we need objdump.
diff --git a/compiler/debug/dwarf/dwarf_test.h b/compiler/debug/dwarf/dwarf_test.h
index bad986a..1a0a798 100644
--- a/compiler/debug/dwarf/dwarf_test.h
+++ b/compiler/debug/dwarf/dwarf_test.h
@@ -26,6 +26,7 @@
 #include <set>
 #include <string>
 
+#include "base/macros.h"
 #include "base/os.h"
 #include "base/unix_file/fd_file.h"
 #include "common_compiler_test.h"
@@ -33,7 +34,7 @@
 #include "gtest/gtest.h"
 #include "stream/file_output_stream.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace dwarf {
 
 #define DW_CHECK(substring) Check(substring, false, __FILE__, __LINE__)
diff --git a/compiler/debug/elf_compilation_unit.h b/compiler/debug/elf_compilation_unit.h
index b1d89eb..1d7523c 100644
--- a/compiler/debug/elf_compilation_unit.h
+++ b/compiler/debug/elf_compilation_unit.h
@@ -19,9 +19,10 @@
 
 #include <vector>
 
+#include "base/macros.h"
 #include "debug/method_debug_info.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace debug {
 
 struct ElfCompilationUnit {
diff --git a/compiler/debug/elf_debug_frame_writer.h b/compiler/debug/elf_debug_frame_writer.h
index 094e887..6b72262 100644
--- a/compiler/debug/elf_debug_frame_writer.h
+++ b/compiler/debug/elf_debug_frame_writer.h
@@ -20,13 +20,14 @@
 #include <vector>
 
 #include "arch/instruction_set.h"
+#include "base/macros.h"
 #include "debug/method_debug_info.h"
 #include "dwarf/debug_frame_opcode_writer.h"
 #include "dwarf/dwarf_constants.h"
 #include "dwarf/headers.h"
 #include "elf/elf_builder.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace debug {
 
 static constexpr bool kWriteDebugFrameHdr = false;
@@ -88,6 +89,10 @@
       WriteCIE(is64bit, return_reg, opcodes, buffer);
       return;
     }
+    case InstructionSet::kRiscv64: {
+      UNIMPLEMENTED(FATAL);
+      return;
+    }
     case InstructionSet::kX86: {
       // FIXME: Add fp registers once libunwind adds support for them. Bug: 20491296
       constexpr bool generate_opcodes_for_x86_fp = false;
diff --git a/compiler/debug/elf_debug_info_writer.h b/compiler/debug/elf_debug_info_writer.h
index 986c7e8..9915a24 100644
--- a/compiler/debug/elf_debug_info_writer.h
+++ b/compiler/debug/elf_debug_info_writer.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "art_field-inl.h"
+#include "base/macros.h"
 #include "debug/elf_compilation_unit.h"
 #include "debug/elf_debug_loc_writer.h"
 #include "debug/method_debug_info.h"
@@ -32,14 +33,14 @@
 #include "dwarf/debug_info_entry_writer.h"
 #include "elf/elf_builder.h"
 #include "heap_poisoning.h"
-#include "linear_alloc.h"
+#include "linear_alloc-inl.h"
 #include "mirror/array.h"
 #include "mirror/class-inl.h"
 #include "mirror/class.h"
 #include "oat_file.h"
 #include "obj_ptr-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace debug {
 
 static std::vector<const char*> GetParamNames(const MethodDebugInfo* mi) {
@@ -478,7 +479,9 @@
     if (methods_ptr == nullptr) {
       // Some types might have no methods.  Allocate empty array instead.
       LinearAlloc* allocator = Runtime::Current()->GetLinearAlloc();
-      void* storage = allocator->Alloc(Thread::Current(), sizeof(LengthPrefixedArray<ArtMethod>));
+      void* storage = allocator->Alloc(Thread::Current(),
+                                       sizeof(LengthPrefixedArray<ArtMethod>),
+                                       LinearAllocKind::kNoGCRoots);
       methods_ptr = new (storage) LengthPrefixedArray<ArtMethod>(0);
       type->SetMethodsPtr(methods_ptr, 0, 0);
       DCHECK(type->GetMethodsPtr() != nullptr);
diff --git a/compiler/debug/elf_debug_line_writer.h b/compiler/debug/elf_debug_line_writer.h
index 8d62747..4896bc1 100644
--- a/compiler/debug/elf_debug_line_writer.h
+++ b/compiler/debug/elf_debug_line_writer.h
@@ -20,6 +20,7 @@
 #include <unordered_set>
 #include <vector>
 
+#include "base/macros.h"
 #include "debug/elf_compilation_unit.h"
 #include "debug/src_map_elem.h"
 #include "dex/dex_file-inl.h"
@@ -29,7 +30,7 @@
 #include "oat_file.h"
 #include "stack_map.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace debug {
 
 using PositionInfos = std::vector<DexFile::PositionInfo>;
@@ -73,6 +74,7 @@
         code_factor_bits_ = 2;  // 32-bit instructions
         break;
       case InstructionSet::kNone:
+      case InstructionSet::kRiscv64:
       case InstructionSet::kX86:
       case InstructionSet::kX86_64:
         break;
diff --git a/compiler/debug/elf_debug_loc_writer.h b/compiler/debug/elf_debug_loc_writer.h
index 37ab948..8cf476e 100644
--- a/compiler/debug/elf_debug_loc_writer.h
+++ b/compiler/debug/elf_debug_loc_writer.h
@@ -21,13 +21,13 @@
 #include <map>
 
 #include "arch/instruction_set.h"
-#include "compiled_method.h"
+#include "base/macros.h"
 #include "debug/method_debug_info.h"
 #include "dwarf/debug_info_entry_writer.h"
 #include "dwarf/register.h"
 #include "stack_map.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace debug {
 using Reg = dwarf::Reg;
 
@@ -38,6 +38,8 @@
       return Reg::ArmCore(machine_reg);
     case InstructionSet::kArm64:
       return Reg::Arm64Core(machine_reg);
+    case InstructionSet::kRiscv64:
+      return Reg::Riscv64Core(machine_reg);
     case InstructionSet::kX86:
       return Reg::X86Core(machine_reg);
     case InstructionSet::kX86_64:
@@ -55,6 +57,8 @@
       return Reg::ArmFp(machine_reg);
     case InstructionSet::kArm64:
       return Reg::Arm64Fp(machine_reg);
+    case InstructionSet::kRiscv64:
+      return Reg::Riscv64Fp(machine_reg);
     case InstructionSet::kX86:
       return Reg::X86Fp(machine_reg);
     case InstructionSet::kX86_64:
diff --git a/compiler/debug/elf_debug_writer.cc b/compiler/debug/elf_debug_writer.cc
index 765a81d..8f64d73 100644
--- a/compiler/debug/elf_debug_writer.cc
+++ b/compiler/debug/elf_debug_writer.cc
@@ -38,7 +38,7 @@
 #include "oat.h"
 #include "stream/vector_output_stream.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace debug {
 
 using ElfRuntimeTypes = std::conditional<sizeof(void*) == 4, ElfTypes32, ElfTypes64>::type;
@@ -208,7 +208,8 @@
     using Reader = ElfDebugReader<ElfTypes>;
     Reader reader(buffer);
     reader.VisitFunctionSymbols([&](Elf_Sym sym, const char*) {
-      DCHECK_EQ(sym.st_value, method_info.code_address + CompiledMethod::CodeDelta(isa));
+      DCHECK_EQ(sym.st_value,
+                method_info.code_address + GetInstructionSetEntryPointAdjustment(isa));
       DCHECK_EQ(sym.st_size, method_info.code_size);
       num_syms++;
     });
diff --git a/compiler/debug/elf_debug_writer.h b/compiler/debug/elf_debug_writer.h
index 1ce3c6f..72b028c 100644
--- a/compiler/debug/elf_debug_writer.h
+++ b/compiler/debug/elf_debug_writer.h
@@ -27,7 +27,7 @@
 #include "dwarf/dwarf_constants.h"
 #include "elf/elf_builder.h"
 
-namespace art {
+namespace art HIDDEN {
 class OatHeader;
 struct JITCodeEntry;
 namespace mirror {
@@ -37,11 +37,11 @@
 struct MethodDebugInfo;
 
 template <typename ElfTypes>
-void WriteDebugInfo(
+EXPORT void WriteDebugInfo(
     ElfBuilder<ElfTypes>* builder,
     const DebugInfo& debug_info);
 
-std::vector<uint8_t> MakeMiniDebugInfo(
+EXPORT std::vector<uint8_t> MakeMiniDebugInfo(
     InstructionSet isa,
     const InstructionSetFeatures* features,
     uint64_t text_section_address,
diff --git a/compiler/debug/elf_symtab_writer.h b/compiler/debug/elf_symtab_writer.h
index 410f704..fcd6696 100644
--- a/compiler/debug/elf_symtab_writer.h
+++ b/compiler/debug/elf_symtab_writer.h
@@ -21,6 +21,7 @@
 #include <unordered_set>
 #include <unordered_map>
 
+#include "base/macros.h"
 #include "base/utils.h"
 #include "debug/debug_info.h"
 #include "debug/method_debug_info.h"
@@ -29,7 +30,7 @@
 #include "dex/dex_file-inl.h"
 #include "elf/elf_builder.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace debug {
 
 // The ARM specification defines three special mapping symbols
@@ -153,7 +154,7 @@
     uint64_t address = info.code_address;
     address += info.is_code_address_text_relative ? text->GetAddress() : 0;
     // Add in code delta, e.g., thumb bit 0 for Thumb2 code.
-    address += CompiledMethod::CodeDelta(info.isa);
+    address += GetInstructionSetEntryPointAdjustment(info.isa);
     symtab->Add(name_offset, text, address, info.code_size, STB_GLOBAL, STT_FUNC);
   }
   // Add symbols for dex files.
diff --git a/compiler/debug/method_debug_info.h b/compiler/debug/method_debug_info.h
index 152db6e..b83c6e2 100644
--- a/compiler/debug/method_debug_info.h
+++ b/compiler/debug/method_debug_info.h
@@ -21,9 +21,10 @@
 
 #include "arch/instruction_set.h"
 #include "base/array_ref.h"
+#include "base/macros.h"
 #include "dex/dex_file.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace debug {
 
 struct MethodDebugInfo {
diff --git a/compiler/debug/src_map_elem.h b/compiler/debug/src_map_elem.h
index 5286b8c..646a1f0 100644
--- a/compiler/debug/src_map_elem.h
+++ b/compiler/debug/src_map_elem.h
@@ -19,7 +19,9 @@
 
 #include <stdint.h>
 
-namespace art {
+#include "base/macros.h"
+
+namespace art HIDDEN {
 
 class SrcMapElem {
  public:
diff --git a/compiler/debug/src_map_elem_test.cc b/compiler/debug/src_map_elem_test.cc
index ceaa53f..bdbafd5 100644
--- a/compiler/debug/src_map_elem_test.cc
+++ b/compiler/debug/src_map_elem_test.cc
@@ -20,7 +20,7 @@
 
 #include "base/macros.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace debug {
 
 TEST(SrcMapElem, Operators) {
diff --git a/compiler/dex/inline_method_analyser.cc b/compiler/dex/inline_method_analyser.cc
index 3201965..381db3d 100644
--- a/compiler/dex/inline_method_analyser.cc
+++ b/compiler/dex/inline_method_analyser.cc
@@ -33,7 +33,7 @@
  * only to allow the debugger to check whether a method has been inlined.
  */
 
-namespace art {
+namespace art HIDDEN {
 
 namespace {  // anonymous namespace
 
diff --git a/compiler/dex/inline_method_analyser.h b/compiler/dex/inline_method_analyser.h
index e1d652a..99d07c6 100644
--- a/compiler/dex/inline_method_analyser.h
+++ b/compiler/dex/inline_method_analyser.h
@@ -28,7 +28,7 @@
  * only to allow the debugger to check whether a method has been inlined.
  */
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeItemDataAccessor;
 
diff --git a/compiler/dex/verification_results.cc b/compiler/dex/verification_results.cc
deleted file mode 100644
index b819d0e..0000000
--- a/compiler/dex/verification_results.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2013 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.
- */
-
-#include "verification_results.h"
-
-#include <android-base/logging.h>
-
-#include "base/mutex-inl.h"
-#include "base/stl_util.h"
-#include "runtime.h"
-#include "thread-current-inl.h"
-#include "thread.h"
-
-namespace art {
-
-VerificationResults::VerificationResults()
-    : uncompilable_methods_lock_("compiler uncompilable methods lock"),
-      rejected_classes_lock_("compiler rejected classes lock") {}
-
-// Non-inline version of the destructor, as it does some implicit work not worth
-// inlining.
-VerificationResults::~VerificationResults() {}
-
-void VerificationResults::AddRejectedClass(ClassReference ref) {
-  {
-    WriterMutexLock mu(Thread::Current(), rejected_classes_lock_);
-    rejected_classes_.insert(ref);
-  }
-  DCHECK(IsClassRejected(ref));
-}
-
-bool VerificationResults::IsClassRejected(ClassReference ref) const {
-  ReaderMutexLock mu(Thread::Current(), rejected_classes_lock_);
-  return rejected_classes_.find(ref) != rejected_classes_.end();
-}
-
-void VerificationResults::AddUncompilableMethod(MethodReference ref) {
-  {
-    WriterMutexLock mu(Thread::Current(), uncompilable_methods_lock_);
-    uncompilable_methods_.insert(ref);
-  }
-  DCHECK(IsUncompilableMethod(ref));
-}
-
-bool VerificationResults::IsUncompilableMethod(MethodReference ref) const {
-  ReaderMutexLock mu(Thread::Current(), uncompilable_methods_lock_);
-  return uncompilable_methods_.find(ref) != uncompilable_methods_.end();
-}
-
-
-}  // namespace art
diff --git a/compiler/dex/verification_results.h b/compiler/dex/verification_results.h
deleted file mode 100644
index b294ed3..0000000
--- a/compiler/dex/verification_results.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2013 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_COMPILER_DEX_VERIFICATION_RESULTS_H_
-#define ART_COMPILER_DEX_VERIFICATION_RESULTS_H_
-
-#include <set>
-
-#include "base/macros.h"
-#include "base/mutex.h"
-#include "dex/class_reference.h"
-#include "dex/method_reference.h"
-
-namespace art {
-
-namespace verifier {
-class VerifierDepsTest;
-}  // namespace verifier
-
-// Used by CompilerCallbacks to track verification information from the Runtime.
-class VerificationResults {
- public:
-  VerificationResults();
-  ~VerificationResults();
-
-  void AddRejectedClass(ClassReference ref) REQUIRES(!rejected_classes_lock_);
-  bool IsClassRejected(ClassReference ref) const REQUIRES(!rejected_classes_lock_);
-
-  void AddUncompilableMethod(MethodReference ref) REQUIRES(!uncompilable_methods_lock_);
-  bool IsUncompilableMethod(MethodReference ref) const REQUIRES(!uncompilable_methods_lock_);
-
- private:
-  // TODO: External locking during CompilerDriver::PreCompile(), no locking during compilation.
-  mutable ReaderWriterMutex uncompilable_methods_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
-  std::set<MethodReference> uncompilable_methods_ GUARDED_BY(uncompilable_methods_lock_);
-
-  // Rejected classes.
-  // TODO: External locking during CompilerDriver::PreCompile(), no locking during compilation.
-  mutable ReaderWriterMutex rejected_classes_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
-  std::set<ClassReference> rejected_classes_ GUARDED_BY(rejected_classes_lock_);
-
-  friend class verifier::VerifierDepsTest;
-};
-
-}  // namespace art
-
-#endif  // ART_COMPILER_DEX_VERIFICATION_RESULTS_H_
diff --git a/compiler/driver/compiled_code_storage.h b/compiler/driver/compiled_code_storage.h
new file mode 100644
index 0000000..cef7398
--- /dev/null
+++ b/compiler/driver/compiled_code_storage.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 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_COMPILER_DRIVER_COMPILED_CODE_STORAGE_H_
+#define ART_COMPILER_DRIVER_COMPILED_CODE_STORAGE_H_
+
+#include <string>
+
+#include "base/array_ref.h"
+#include "base/macros.h"
+
+namespace art HIDDEN {
+
+namespace linker {
+class LinkerPatch;
+}  // namespace linker
+
+class CompiledMethod;
+enum class InstructionSet;
+
+// Interface for storing AOT-compiled artifacts.
+// These artifacts include compiled method code and related stack maps and
+// linker patches as well as the compiled thunk code required for some kinds
+// of linker patches.
+//
+// This interface is used for passing AOT-compiled code and metadata produced
+// by the `libart-compiler` to `dex2oat`. The `CompiledMethod` created by
+// `dex2oat` is completely opaque to the `libart-compiler`.
+class CompiledCodeStorage {
+ public:
+  virtual CompiledMethod* CreateCompiledMethod(InstructionSet instruction_set,
+                                               ArrayRef<const uint8_t> code,
+                                               ArrayRef<const uint8_t> stack_map,
+                                               ArrayRef<const uint8_t> cfi,
+                                               ArrayRef<const linker::LinkerPatch> patches,
+                                               bool is_intrinsic) = 0;
+
+  // TODO: Rewrite the interface for passing thunks to the `dex2oat` to reduce
+  // locking. The `OptimizingCompiler` is currently calling `GetThunkCode()`
+  // and locking a mutex there for every `LinkerPatch` that needs a thunk to
+  // check whether we need to compile it. Using a thunk compiler interface,
+  // we could drive this from the `dex2oat` side and lock the mutex at most
+  // once per `CreateCompiledMethod()` for any number of patches.
+  virtual ArrayRef<const uint8_t> GetThunkCode(const linker::LinkerPatch& patch,
+                                               /*out*/ std::string* debug_name = nullptr) = 0;
+  virtual void SetThunkCode(const linker::LinkerPatch& patch,
+                            ArrayRef<const uint8_t> code,
+                            const std::string& debug_name) = 0;
+
+ protected:
+  CompiledCodeStorage() {}
+  ~CompiledCodeStorage() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CompiledCodeStorage);
+};
+
+}  // namespace art
+
+#endif  // ART_COMPILER_DRIVER_COMPILED_CODE_STORAGE_H_
diff --git a/compiler/driver/compiled_method_storage.cc b/compiler/driver/compiled_method_storage.cc
deleted file mode 100644
index 4857ec0..0000000
--- a/compiler/driver/compiled_method_storage.cc
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2015 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.
- */
-
-#include <algorithm>
-#include <ostream>
-
-#include "compiled_method_storage.h"
-
-#include <android-base/logging.h>
-
-#include "base/data_hash.h"
-#include "base/utils.h"
-#include "compiled_method.h"
-#include "linker/linker_patch.h"
-#include "thread-current-inl.h"
-#include "utils/dedupe_set-inl.h"
-#include "utils/swap_space.h"
-
-namespace art {
-
-namespace {  // anonymous namespace
-
-template <typename T>
-const LengthPrefixedArray<T>* CopyArray(SwapSpace* swap_space, const ArrayRef<const T>& array) {
-  DCHECK(!array.empty());
-  SwapAllocator<uint8_t> allocator(swap_space);
-  void* storage = allocator.allocate(LengthPrefixedArray<T>::ComputeSize(array.size()));
-  LengthPrefixedArray<T>* array_copy = new(storage) LengthPrefixedArray<T>(array.size());
-  std::copy(array.begin(), array.end(), array_copy->begin());
-  return array_copy;
-}
-
-template <typename T>
-void ReleaseArray(SwapSpace* swap_space, const LengthPrefixedArray<T>* array) {
-  SwapAllocator<uint8_t> allocator(swap_space);
-  size_t size = LengthPrefixedArray<T>::ComputeSize(array->size());
-  array->~LengthPrefixedArray<T>();
-  allocator.deallocate(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(array)), size);
-}
-
-}  // anonymous namespace
-
-template <typename T, typename DedupeSetType>
-inline const LengthPrefixedArray<T>* CompiledMethodStorage::AllocateOrDeduplicateArray(
-    const ArrayRef<const T>& data,
-    DedupeSetType* dedupe_set) {
-  if (data.empty()) {
-    return nullptr;
-  } else if (!DedupeEnabled()) {
-    return CopyArray(swap_space_.get(), data);
-  } else {
-    return dedupe_set->Add(Thread::Current(), data);
-  }
-}
-
-template <typename T>
-inline void CompiledMethodStorage::ReleaseArrayIfNotDeduplicated(
-    const LengthPrefixedArray<T>* array) {
-  if (array != nullptr && !DedupeEnabled()) {
-    ReleaseArray(swap_space_.get(), array);
-  }
-}
-
-template <typename ContentType>
-class CompiledMethodStorage::DedupeHashFunc {
- private:
-  static constexpr bool kUseMurmur3Hash = true;
-
- public:
-  size_t operator()(const ArrayRef<ContentType>& array) const {
-    return DataHash()(array);
-  }
-};
-
-template <typename T>
-class CompiledMethodStorage::LengthPrefixedArrayAlloc {
- public:
-  explicit LengthPrefixedArrayAlloc(SwapSpace* swap_space)
-      : swap_space_(swap_space) {
-  }
-
-  const LengthPrefixedArray<T>* Copy(const ArrayRef<const T>& array) {
-    return CopyArray(swap_space_, array);
-  }
-
-  void Destroy(const LengthPrefixedArray<T>* array) {
-    ReleaseArray(swap_space_, array);
-  }
-
- private:
-  SwapSpace* const swap_space_;
-};
-
-class CompiledMethodStorage::ThunkMapKey {
- public:
-  ThunkMapKey(linker::LinkerPatch::Type type, uint32_t custom_value1, uint32_t custom_value2)
-      : type_(type), custom_value1_(custom_value1), custom_value2_(custom_value2) {}
-
-  bool operator<(const ThunkMapKey& other) const {
-    if (custom_value1_ != other.custom_value1_) {
-      return custom_value1_ < other.custom_value1_;
-    }
-    if (custom_value2_ != other.custom_value2_) {
-      return custom_value2_ < other.custom_value2_;
-    }
-    return type_ < other.type_;
-  }
-
- private:
-  linker::LinkerPatch::Type type_;
-  uint32_t custom_value1_;
-  uint32_t custom_value2_;
-};
-
-class CompiledMethodStorage::ThunkMapValue {
- public:
-  ThunkMapValue(std::vector<uint8_t, SwapAllocator<uint8_t>>&& code,
-                const std::string& debug_name)
-      : code_(std::move(code)), debug_name_(debug_name) {}
-
-  ArrayRef<const uint8_t> GetCode() const {
-    return ArrayRef<const uint8_t>(code_);
-  }
-
-  const std::string& GetDebugName() const {
-    return debug_name_;
-  }
-
- private:
-  std::vector<uint8_t, SwapAllocator<uint8_t>> code_;
-  std::string debug_name_;
-};
-
-CompiledMethodStorage::CompiledMethodStorage(int swap_fd)
-    : swap_space_(swap_fd == -1 ? nullptr : new SwapSpace(swap_fd, 10 * MB)),
-      dedupe_enabled_(true),
-      dedupe_code_("dedupe code", LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())),
-      dedupe_vmap_table_("dedupe vmap table",
-                         LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())),
-      dedupe_cfi_info_("dedupe cfi info", LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())),
-      dedupe_linker_patches_("dedupe cfi info",
-                             LengthPrefixedArrayAlloc<linker::LinkerPatch>(swap_space_.get())),
-      thunk_map_lock_("thunk_map_lock"),
-      thunk_map_(std::less<ThunkMapKey>(), SwapAllocator<ThunkMapValueType>(swap_space_.get())) {
-}
-
-CompiledMethodStorage::~CompiledMethodStorage() {
-  // All done by member destructors.
-}
-
-void CompiledMethodStorage::DumpMemoryUsage(std::ostream& os, bool extended) const {
-  if (swap_space_.get() != nullptr) {
-    const size_t swap_size = swap_space_->GetSize();
-    os << " swap=" << PrettySize(swap_size) << " (" << swap_size << "B)";
-  }
-  if (extended) {
-    Thread* self = Thread::Current();
-    os << "\nCode dedupe: " << dedupe_code_.DumpStats(self);
-    os << "\nVmap table dedupe: " << dedupe_vmap_table_.DumpStats(self);
-    os << "\nCFI info dedupe: " << dedupe_cfi_info_.DumpStats(self);
-  }
-}
-
-const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateCode(
-    const ArrayRef<const uint8_t>& code) {
-  return AllocateOrDeduplicateArray(code, &dedupe_code_);
-}
-
-void CompiledMethodStorage::ReleaseCode(const LengthPrefixedArray<uint8_t>* code) {
-  ReleaseArrayIfNotDeduplicated(code);
-}
-
-size_t CompiledMethodStorage::UniqueCodeEntries() const {
-  DCHECK(DedupeEnabled());
-  return dedupe_code_.Size(Thread::Current());
-}
-
-const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateVMapTable(
-    const ArrayRef<const uint8_t>& table) {
-  return AllocateOrDeduplicateArray(table, &dedupe_vmap_table_);
-}
-
-void CompiledMethodStorage::ReleaseVMapTable(const LengthPrefixedArray<uint8_t>* table) {
-  ReleaseArrayIfNotDeduplicated(table);
-}
-
-size_t CompiledMethodStorage::UniqueVMapTableEntries() const {
-  DCHECK(DedupeEnabled());
-  return dedupe_vmap_table_.Size(Thread::Current());
-}
-
-const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateCFIInfo(
-    const ArrayRef<const uint8_t>& cfi_info) {
-  return AllocateOrDeduplicateArray(cfi_info, &dedupe_cfi_info_);
-}
-
-void CompiledMethodStorage::ReleaseCFIInfo(const LengthPrefixedArray<uint8_t>* cfi_info) {
-  ReleaseArrayIfNotDeduplicated(cfi_info);
-}
-
-size_t CompiledMethodStorage::UniqueCFIInfoEntries() const {
-  DCHECK(DedupeEnabled());
-  return dedupe_cfi_info_.Size(Thread::Current());
-}
-
-const LengthPrefixedArray<linker::LinkerPatch>* CompiledMethodStorage::DeduplicateLinkerPatches(
-    const ArrayRef<const linker::LinkerPatch>& linker_patches) {
-  return AllocateOrDeduplicateArray(linker_patches, &dedupe_linker_patches_);
-}
-
-void CompiledMethodStorage::ReleaseLinkerPatches(
-    const LengthPrefixedArray<linker::LinkerPatch>* linker_patches) {
-  ReleaseArrayIfNotDeduplicated(linker_patches);
-}
-
-size_t CompiledMethodStorage::UniqueLinkerPatchesEntries() const {
-  DCHECK(DedupeEnabled());
-  return dedupe_linker_patches_.Size(Thread::Current());
-}
-
-CompiledMethodStorage::ThunkMapKey CompiledMethodStorage::GetThunkMapKey(
-    const linker::LinkerPatch& linker_patch) {
-  uint32_t custom_value1 = 0u;
-  uint32_t custom_value2 = 0u;
-  switch (linker_patch.GetType()) {
-    case linker::LinkerPatch::Type::kCallEntrypoint:
-      custom_value1 = linker_patch.EntrypointOffset();
-      break;
-    case linker::LinkerPatch::Type::kBakerReadBarrierBranch:
-      custom_value1 = linker_patch.GetBakerCustomValue1();
-      custom_value2 = linker_patch.GetBakerCustomValue2();
-      break;
-    case linker::LinkerPatch::Type::kCallRelative:
-      // No custom values.
-      break;
-    default:
-      LOG(FATAL) << "Unexpected patch type: " << linker_patch.GetType();
-      UNREACHABLE();
-  }
-  return ThunkMapKey(linker_patch.GetType(), custom_value1, custom_value2);
-}
-
-ArrayRef<const uint8_t> CompiledMethodStorage::GetThunkCode(const linker::LinkerPatch& linker_patch,
-                                                            /*out*/ std::string* debug_name) {
-  ThunkMapKey key = GetThunkMapKey(linker_patch);
-  MutexLock lock(Thread::Current(), thunk_map_lock_);
-  auto it = thunk_map_.find(key);
-  if (it != thunk_map_.end()) {
-    const ThunkMapValue& value = it->second;
-    if (debug_name != nullptr) {
-      *debug_name = value.GetDebugName();
-    }
-    return value.GetCode();
-  } else {
-    if (debug_name != nullptr) {
-      *debug_name = std::string();
-    }
-    return ArrayRef<const uint8_t>();
-  }
-}
-
-void CompiledMethodStorage::SetThunkCode(const linker::LinkerPatch& linker_patch,
-                                         ArrayRef<const uint8_t> code,
-                                         const std::string& debug_name) {
-  DCHECK(!code.empty());
-  ThunkMapKey key = GetThunkMapKey(linker_patch);
-  std::vector<uint8_t, SwapAllocator<uint8_t>> code_copy(
-      code.begin(), code.end(), SwapAllocator<uint8_t>(swap_space_.get()));
-  ThunkMapValue value(std::move(code_copy), debug_name);
-  MutexLock lock(Thread::Current(), thunk_map_lock_);
-  // Note: Multiple threads can try and compile the same thunk, so this may not create a new entry.
-  thunk_map_.emplace(key, std::move(value));
-}
-
-}  // namespace art
diff --git a/compiler/driver/compiled_method_storage.h b/compiler/driver/compiled_method_storage.h
deleted file mode 100644
index f9f3401..0000000
--- a/compiler/driver/compiled_method_storage.h
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2015 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_COMPILER_DRIVER_COMPILED_METHOD_STORAGE_H_
-#define ART_COMPILER_DRIVER_COMPILED_METHOD_STORAGE_H_
-
-#include <iosfwd>
-#include <map>
-#include <memory>
-
-#include "base/array_ref.h"
-#include "base/length_prefixed_array.h"
-#include "base/macros.h"
-#include "utils/dedupe_set.h"
-#include "utils/swap_space.h"
-
-namespace art {
-
-namespace linker {
-class LinkerPatch;
-}  // namespace linker
-
-class CompiledMethodStorage {
- public:
-  explicit CompiledMethodStorage(int swap_fd);
-  ~CompiledMethodStorage();
-
-  void DumpMemoryUsage(std::ostream& os, bool extended) const;
-
-  void SetDedupeEnabled(bool dedupe_enabled) {
-    dedupe_enabled_ = dedupe_enabled;
-  }
-  bool DedupeEnabled() const {
-    return dedupe_enabled_;
-  }
-
-  SwapAllocator<void> GetSwapSpaceAllocator() {
-    return SwapAllocator<void>(swap_space_.get());
-  }
-
-  const LengthPrefixedArray<uint8_t>* DeduplicateCode(const ArrayRef<const uint8_t>& code);
-  void ReleaseCode(const LengthPrefixedArray<uint8_t>* code);
-  size_t UniqueCodeEntries() const;
-
-  const LengthPrefixedArray<uint8_t>* DeduplicateVMapTable(const ArrayRef<const uint8_t>& table);
-  void ReleaseVMapTable(const LengthPrefixedArray<uint8_t>* table);
-  size_t UniqueVMapTableEntries() const;
-
-  const LengthPrefixedArray<uint8_t>* DeduplicateCFIInfo(const ArrayRef<const uint8_t>& cfi_info);
-  void ReleaseCFIInfo(const LengthPrefixedArray<uint8_t>* cfi_info);
-  size_t UniqueCFIInfoEntries() const;
-
-  const LengthPrefixedArray<linker::LinkerPatch>* DeduplicateLinkerPatches(
-      const ArrayRef<const linker::LinkerPatch>& linker_patches);
-  void ReleaseLinkerPatches(const LengthPrefixedArray<linker::LinkerPatch>* linker_patches);
-  size_t UniqueLinkerPatchesEntries() const;
-
-  // Returns the code associated with the given patch.
-  // If the code has not been set, returns empty data.
-  // If `debug_name` is not null, stores the associated debug name in `*debug_name`.
-  ArrayRef<const uint8_t> GetThunkCode(const linker::LinkerPatch& linker_patch,
-                                       /*out*/ std::string* debug_name = nullptr);
-
-  // Sets the code and debug name associated with the given patch.
-  void SetThunkCode(const linker::LinkerPatch& linker_patch,
-                    ArrayRef<const uint8_t> code,
-                    const std::string& debug_name);
-
- private:
-  class ThunkMapKey;
-  class ThunkMapValue;
-  using ThunkMapValueType = std::pair<const ThunkMapKey, ThunkMapValue>;
-  using ThunkMap = std::map<ThunkMapKey,
-                            ThunkMapValue,
-                            std::less<ThunkMapKey>,
-                            SwapAllocator<ThunkMapValueType>>;
-  static_assert(std::is_same<ThunkMapValueType, ThunkMap::value_type>::value, "Value type check.");
-
-  static ThunkMapKey GetThunkMapKey(const linker::LinkerPatch& linker_patch);
-
-  template <typename T, typename DedupeSetType>
-  const LengthPrefixedArray<T>* AllocateOrDeduplicateArray(const ArrayRef<const T>& data,
-                                                           DedupeSetType* dedupe_set);
-
-  template <typename T>
-  void ReleaseArrayIfNotDeduplicated(const LengthPrefixedArray<T>* array);
-
-  // DeDuplication data structures.
-  template <typename ContentType>
-  class DedupeHashFunc;
-
-  template <typename T>
-  class LengthPrefixedArrayAlloc;
-
-  template <typename T>
-  using ArrayDedupeSet = DedupeSet<ArrayRef<const T>,
-                                   LengthPrefixedArray<T>,
-                                   LengthPrefixedArrayAlloc<T>,
-                                   size_t,
-                                   DedupeHashFunc<const T>,
-                                   4>;
-
-  // Swap pool and allocator used for native allocations. May be file-backed. Needs to be first
-  // as other fields rely on this.
-  std::unique_ptr<SwapSpace> swap_space_;
-
-  bool dedupe_enabled_;
-
-  ArrayDedupeSet<uint8_t> dedupe_code_;
-  ArrayDedupeSet<uint8_t> dedupe_vmap_table_;
-  ArrayDedupeSet<uint8_t> dedupe_cfi_info_;
-  ArrayDedupeSet<linker::LinkerPatch> dedupe_linker_patches_;
-
-  Mutex thunk_map_lock_;
-  ThunkMap thunk_map_ GUARDED_BY(thunk_map_lock_);
-
-  DISALLOW_COPY_AND_ASSIGN(CompiledMethodStorage);
-};
-
-}  // namespace art
-
-#endif  // ART_COMPILER_DRIVER_COMPILED_METHOD_STORAGE_H_
diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc
index 51cd999..603596f 100644
--- a/compiler/driver/compiler_options.cc
+++ b/compiler/driver/compiler_options.cc
@@ -23,6 +23,7 @@
 
 #include "arch/instruction_set.h"
 #include "arch/instruction_set_features.h"
+#include "art_method-inl.h"
 #include "base/runtime_debug.h"
 #include "base/string_view_cpp20.h"
 #include "base/variant_map.h"
@@ -30,12 +31,11 @@
 #include "cmdline_parser.h"
 #include "compiler_options_map-inl.h"
 #include "dex/dex_file-inl.h"
-#include "dex/verification_results.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "simple_compiler_options_map.h"
 
-namespace art {
+namespace art HIDDEN {
 
 CompilerOptions::CompilerOptions()
     : compiler_filter_(CompilerFilter::kDefaultCompilerFilter),
@@ -48,7 +48,6 @@
       no_inline_from_(),
       dex_files_for_oat_file_(),
       image_classes_(),
-      verification_results_(nullptr),
       compiler_type_(CompilerType::kAotCompiler),
       image_type_(ImageType::kNone),
       multi_image_(false),
@@ -146,14 +145,34 @@
 
 bool CompilerOptions::IsImageClass(const char* descriptor) const {
   // Historical note: We used to hold the set indirectly and there was a distinction between an
-  // empty set and a null, null meaning to include all classes. However, the distiction has been
+  // empty set and a null, null meaning to include all classes. However, the distinction has been
   // removed; if we don't have a profile, we treat it as an empty set of classes. b/77340429
   return image_classes_.find(std::string_view(descriptor)) != image_classes_.end();
 }
 
-const VerificationResults* CompilerOptions::GetVerificationResults() const {
-  DCHECK(Runtime::Current()->IsAotCompiler());
-  return verification_results_;
+bool CompilerOptions::IsPreloadedClass(const char* pretty_descriptor) const {
+  return preloaded_classes_.find(std::string_view(pretty_descriptor)) != preloaded_classes_.end();
+}
+
+bool CompilerOptions::ShouldCompileWithClinitCheck(ArtMethod* method) const {
+  if (method != nullptr &&
+      Runtime::Current()->IsAotCompiler() &&
+      method->IsStatic() &&
+      !method->IsConstructor() &&
+      // Compiled code for native methods never do a clinit check, so we may put the resolution
+      // trampoline for native methods. This means that it's possible post zygote fork for the
+      // entry to be dirtied. We could resolve this by either:
+      // - Make these methods use the generic JNI entrypoint, but that's not
+      //   desirable for a method that is in the profile.
+      // - Ensure the declaring class of such native methods are always in the
+      //   preloaded-classes list.
+      // - Emit the clinit check in the compiled code of native methods.
+      !method->IsNative()) {
+    ScopedObjectAccess soa(Thread::Current());
+    ObjPtr<mirror::Class> cls = method->GetDeclaringClass<kWithoutReadBarrier>();
+    return cls->IsInBootImageAndNotInPreloadedClasses();
+  }
+  return false;
 }
 
 }  // namespace art
diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h
index 1bffdb1..c8a41ce 100644
--- a/compiler/driver/compiler_options.h
+++ b/compiler/driver/compiler_options.h
@@ -30,7 +30,7 @@
 #include "base/utils.h"
 #include "optimizing/register_allocator.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace jit {
 class JitCompiler;
@@ -44,11 +44,11 @@
 class Arm64RelativePatcherTest;
 }  // namespace linker
 
+class ArtMethod;
 class DexFile;
 enum class InstructionSet;
 class InstructionSetFeatures;
 class ProfileCompilationInfo;
-class VerificationResults;
 
 // Enum for CheckProfileMethodsCompiled. Outside CompilerOptions so it can be forward-declared.
 enum class ProfileMethodsCheck : uint8_t {
@@ -83,8 +83,8 @@
     kAppImage,                // Creating app image.
   };
 
-  CompilerOptions();
-  ~CompilerOptions();
+  EXPORT CompilerOptions();
+  EXPORT ~CompilerOptions();
 
   CompilerFilter::Filter GetCompilerFilter() const {
     return compiler_filter_;
@@ -114,12 +114,10 @@
     return compiler_filter_ == CompilerFilter::kAssumeVerified;
   }
 
-  bool VerifyAtRuntime() const {
-    return compiler_filter_ == CompilerFilter::kExtract;
-  }
-
   bool IsAnyCompilationEnabled() const {
-    return CompilerFilter::IsAnyCompilationEnabled(compiler_filter_);
+    return CompilerFilter::IsAnyCompilationEnabled(compiler_filter_) &&
+           // TODO(riscv64): remove this when we have compiler support for RISC-V
+           GetInstructionSet() != InstructionSet::kRiscv64;
   }
 
   size_t GetHugeMethodThreshold() const {
@@ -298,9 +296,11 @@
     return image_classes_;
   }
 
-  bool IsImageClass(const char* descriptor) const;
+  EXPORT bool IsImageClass(const char* descriptor) const;
 
-  const VerificationResults* GetVerificationResults() const;
+  // Returns whether the given `pretty_descriptor` is in the list of preloaded
+  // classes. `pretty_descriptor` should be the result of calling `PrettyDescriptor`.
+  EXPORT bool IsPreloadedClass(const char* pretty_descriptor) const;
 
   bool ParseCompilerOptions(const std::vector<std::string>& options,
                             bool ignore_unrecognized,
@@ -383,9 +383,15 @@
     return ContainsElement(GetDexFilesForOatFile(), dex_file);
   }
 
+  // If this is a static non-constructor method in the boot classpath, and its class isn't
+  // initialized at compile-time, or won't be initialized by the zygote, add
+  // initialization checks at entry. This will avoid the need of trampolines
+  // which at runtime we will need to dirty after initialization.
+  EXPORT bool ShouldCompileWithClinitCheck(ArtMethod* method) const;
+
  private:
-  bool ParseDumpInitFailures(const std::string& option, std::string* error_msg);
-  bool ParseRegisterAllocationStrategy(const std::string& option, std::string* error_msg);
+  EXPORT bool ParseDumpInitFailures(const std::string& option, std::string* error_msg);
+  EXPORT bool ParseRegisterAllocationStrategy(const std::string& option, std::string* error_msg);
 
   CompilerFilter::Filter compiler_filter_;
   size_t huge_method_threshold_;
@@ -408,8 +414,9 @@
   // Must not be empty for real boot image, only for tests pretending to compile boot image.
   HashSet<std::string> image_classes_;
 
-  // Results of AOT verification.
-  const VerificationResults* verification_results_;
+  // Classes listed in the preloaded-classes file, used for boot image and
+  // boot image extension compilation.
+  HashSet<std::string> preloaded_classes_;
 
   CompilerType compiler_type_;
   ImageType image_type_;
diff --git a/compiler/driver/compiler_options_map-inl.h b/compiler/driver/compiler_options_map-inl.h
index fcbc0f2..79a5962 100644
--- a/compiler/driver/compiler_options_map-inl.h
+++ b/compiler/driver/compiler_options_map-inl.h
@@ -29,7 +29,7 @@
 #include "cmdline_parser.h"
 #include "compiler_options.h"
 
-namespace art {
+namespace art HIDDEN {
 
 template <>
 struct CmdlineType<CompilerFilter::Filter> : CmdlineTypeParser<CompilerFilter::Filter> {
@@ -118,6 +118,7 @@
 
 template <typename Map, typename Builder>
 inline void AddCompilerOptionsArgumentParserOptions(Builder& b) {
+  // clang-format off
   b.
       Define("--compiler-filter=_")
           .template WithType<CompilerFilter::Filter>()
@@ -256,6 +257,7 @@
           .template WithType<unsigned int>()
           .WithHelp("Maximum solid block size for compressed images.")
           .IntoKey(Map::MaxImageBlockSize);
+  // clang-format on
 }
 
 #pragma GCC diagnostic pop
diff --git a/compiler/driver/compiler_options_map.h b/compiler/driver/compiler_options_map.h
index 7e2f846..b2dd57d 100644
--- a/compiler/driver/compiler_options_map.h
+++ b/compiler/driver/compiler_options_map.h
@@ -21,10 +21,11 @@
 #include <vector>
 
 #include "base/compiler_filter.h"
+#include "base/macros.h"
 #include "base/variant_map.h"
 #include "cmdline_types.h"
 
-namespace art {
+namespace art HIDDEN {
 
 enum class ProfileMethodsCheck : uint8_t;
 
diff --git a/compiler/driver/dex_compilation_unit.cc b/compiler/driver/dex_compilation_unit.cc
index 0d0f074..ccebfa9 100644
--- a/compiler/driver/dex_compilation_unit.cc
+++ b/compiler/driver/dex_compilation_unit.cc
@@ -25,7 +25,7 @@
 #include "mirror/dex_cache.h"
 #include "scoped_thread_state_change-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 DexCompilationUnit::DexCompilationUnit(Handle<mirror::ClassLoader> class_loader,
                                        ClassLinker* class_linker,
diff --git a/compiler/driver/dex_compilation_unit.h b/compiler/driver/dex_compilation_unit.h
index def90fa..d595c0a 100644
--- a/compiler/driver/dex_compilation_unit.h
+++ b/compiler/driver/dex_compilation_unit.h
@@ -20,11 +20,12 @@
 #include <stdint.h>
 
 #include "base/arena_object.h"
+#include "base/macros.h"
 #include "dex/code_item_accessors.h"
 #include "dex/dex_file.h"
 #include "handle.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace mirror {
 class Class;
 class ClassLoader;
diff --git a/compiler/driver/simple_compiler_options_map.h b/compiler/driver/simple_compiler_options_map.h
index e7a51a4..6663c0c 100644
--- a/compiler/driver/simple_compiler_options_map.h
+++ b/compiler/driver/simple_compiler_options_map.h
@@ -23,9 +23,10 @@
 #include <memory>
 
 #include "compiler_options_map-inl.h"
+#include "base/macros.h"
 #include "base/variant_map.h"
 
-namespace art {
+namespace art HIDDEN {
 
 template <typename TValue>
 struct SimpleParseArgumentMapKey : VariantMapKey<TValue> {
diff --git a/compiler/exception_test.cc b/compiler/exception_test.cc
index 495398b..82c4998 100644
--- a/compiler/exception_test.cc
+++ b/compiler/exception_test.cc
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <android-base/test_utils.h>
+
 #include <memory>
 #include <type_traits>
 
@@ -22,6 +24,7 @@
 #include "base/callee_save_type.h"
 #include "base/enums.h"
 #include "base/leb128.h"
+#include "base/macros.h"
 #include "base/malloc_arena_pool.h"
 #include "class_linker.h"
 #include "common_runtime_test.h"
@@ -42,7 +45,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ExceptionTest : public CommonRuntimeTest {
  protected:
@@ -78,7 +81,12 @@
     ArenaStack arena_stack(&pool);
     ScopedArenaAllocator allocator(&arena_stack);
     StackMapStream stack_maps(&allocator, kRuntimeISA);
-    stack_maps.BeginMethod(4 * sizeof(void*), 0u, 0u, 0u);
+    stack_maps.BeginMethod(/* frame_size_in_bytes= */ 4 * sizeof(void*),
+                           /* core_spill_mask= */ 0u,
+                           /* fp_spill_mask= */ 0u,
+                           /* num_dex_registers= */ 0u,
+                           /* baseline= */ false,
+                           /* debuggable= */ false);
     stack_maps.BeginStackMapEntry(kDexPc, native_pc_offset);
     stack_maps.EndStackMapEntry();
     stack_maps.EndMethod(code_size);
@@ -86,7 +94,7 @@
 
     const size_t stack_maps_size = stack_map.size();
     const size_t header_size = sizeof(OatQuickMethodHeader);
-    const size_t code_alignment = GetInstructionSetAlignment(kRuntimeISA);
+    const size_t code_alignment = GetInstructionSetCodeAlignment(kRuntimeISA);
 
     fake_header_code_and_maps_.resize(stack_maps_size + header_size + code_size + code_alignment);
     // NB: The start of the vector might not have been allocated the desired alignment.
@@ -187,15 +195,24 @@
     fake_stack.push_back(0);
   }
 
-  fake_stack.push_back(method_g_->GetOatQuickMethodHeader(0)->ToNativeQuickPc(
-      method_g_, kDexPc, /* is_for_catch_handler= */ false));  // return pc
+  OatQuickMethodHeader* header = OatQuickMethodHeader::FromEntryPoint(
+      method_g_->GetEntryPointFromQuickCompiledCode());
+  // Untag native pc when running with hwasan since the pcs on the stack aren't tagged and we use
+  // this to create a fake stack. See OatQuickMethodHeader::Contains where we untag code pointers
+  // before comparing it with the PC from the stack.
+  uintptr_t native_pc = header->ToNativeQuickPc(method_g_, kDexPc);
+  if (running_with_hwasan()) {
+    // TODO(228989263): Use HWASanUntag once we have a hwasan target for tests too. HWASanUntag
+    // uses static checks which won't work if we don't have a dedicated target.
+    native_pc = (native_pc & ((1ULL << 56) - 1));
+  }
+  fake_stack.push_back(native_pc);  // return pc
 
   // Create/push fake 16byte stack frame for method g
   fake_stack.push_back(reinterpret_cast<uintptr_t>(method_g_));
   fake_stack.push_back(0);
   fake_stack.push_back(0);
-  fake_stack.push_back(method_g_->GetOatQuickMethodHeader(0)->ToNativeQuickPc(
-      method_g_, kDexPc, /* is_for_catch_handler= */ false));  // return pc
+  fake_stack.push_back(native_pc);  // return pc.
 
   // Create/push fake 16byte stack frame for method f
   fake_stack.push_back(reinterpret_cast<uintptr_t>(method_f_));
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index 7002636..e672367 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -34,13 +34,17 @@
 #include "jit/jit_code_cache.h"
 #include "jit/jit_logger.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace jit {
 
 JitCompiler* JitCompiler::Create() {
   return new JitCompiler();
 }
 
+void JitCompiler::SetDebuggableCompilerOption(bool value) {
+  compiler_options_->SetDebuggable(value);
+}
+
 void JitCompiler::ParseCompilerOptions() {
   // Special case max code units for inlining, whose default is "unset" (implictly
   // meaning no limit). Do this before parsing the actual passed options.
@@ -85,7 +89,7 @@
     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_features = InstructionSetFeatures::FromVariantAndHwcap(
           instruction_set, str, &error_msg);
       if (instruction_set_features == nullptr) {
         LOG(WARNING) << "Error parsing " << option << " message=" << error_msg;
@@ -121,7 +125,7 @@
   }
 }
 
-extern "C" JitCompilerInterface* jit_load() {
+EXPORT extern "C" JitCompilerInterface* jit_load() {
   VLOG(jit) << "Create jit compiler";
   auto* const jit_compiler = JitCompiler::Create();
   CHECK(jit_compiler != nullptr);
@@ -199,6 +203,8 @@
     VLOG(jit) << "Compilation of " << method->PrettyMethod() << " took "
               << PrettyDuration(UsToNs(duration_us));
     runtime->GetMetrics()->JitMethodCompileCount()->AddOne();
+    runtime->GetMetrics()->JitMethodCompileTotalTimeDelta()->Add(duration_us);
+    runtime->GetMetrics()->JitMethodCompileCountDelta()->AddOne();
   }
 
   // Trim maps to reduce memory usage.
diff --git a/compiler/jit/jit_compiler.h b/compiler/jit/jit_compiler.h
index 8e9966d..5a919fb 100644
--- a/compiler/jit/jit_compiler.h
+++ b/compiler/jit/jit_compiler.h
@@ -17,12 +17,13 @@
 #ifndef ART_COMPILER_JIT_JIT_COMPILER_H_
 #define ART_COMPILER_JIT_JIT_COMPILER_H_
 
+#include "base/macros.h"
 #include "base/mutex.h"
 #include "compilation_kind.h"
 
 #include "jit/jit.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArtMethod;
 class Compiler;
@@ -50,6 +51,8 @@
 
   bool IsBaselineCompiler() const override;
 
+  void SetDebuggableCompilerOption(bool val) override;
+
   bool GenerateDebugInfo() override;
 
   void ParseCompilerOptions() override;
diff --git a/compiler/jit/jit_logger.cc b/compiler/jit/jit_logger.cc
index 6b9453f..3284526 100644
--- a/compiler/jit/jit_logger.cc
+++ b/compiler/jit/jit_logger.cc
@@ -24,7 +24,7 @@
 #include "jit/jit_code_cache.h"
 #include "oat_file-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace jit {
 
 #ifdef ART_TARGET_ANDROID
diff --git a/compiler/jit/jit_logger.h b/compiler/jit/jit_logger.h
index f4ef75a..9d1f307 100644
--- a/compiler/jit/jit_logger.h
+++ b/compiler/jit/jit_logger.h
@@ -19,11 +19,11 @@
 
 #include <memory>
 
+#include "base/macros.h"
 #include "base/mutex.h"
 #include "base/os.h"
-#include "compiled_method.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArtMethod;
 
diff --git a/compiler/jni/jni_cfi_test.cc b/compiler/jni/jni_cfi_test.cc
index 9e3bb86..70cf2d4 100644
--- a/compiler/jni/jni_cfi_test.cc
+++ b/compiler/jni/jni_cfi_test.cc
@@ -20,6 +20,7 @@
 #include "arch/instruction_set.h"
 #include "base/arena_allocator.h"
 #include "base/enums.h"
+#include "base/macros.h"
 #include "base/malloc_arena_pool.h"
 #include "cfi_test.h"
 #include "gtest/gtest.h"
@@ -30,7 +31,7 @@
 
 #include "jni/jni_cfi_test_expected.inc"
 
-namespace art {
+namespace art HIDDEN {
 
 // Run the tests only on host.
 #ifndef ART_TARGET_ANDROID
@@ -124,22 +125,31 @@
     TestImpl(InstructionSet::isa, #isa, expected_asm, expected_cfi);  \
   }
 
+// We can't use compile-time macros for read-barrier as the introduction
+// of userfaultfd-GC has made it a runtime choice.
+#define TEST_ISA_ONLY_CC(isa)                                           \
+  TEST_F(JNICFITest, isa) {                                             \
+    if (kUseBakerReadBarrier && gUseReadBarrier) {                      \
+      std::vector<uint8_t> expected_asm(expected_asm_##isa,             \
+          expected_asm_##isa + arraysize(expected_asm_##isa));          \
+      std::vector<uint8_t> expected_cfi(expected_cfi_##isa,             \
+          expected_cfi_##isa + arraysize(expected_cfi_##isa));          \
+      TestImpl(InstructionSet::isa, #isa, expected_asm, expected_cfi);  \
+    }                                                                   \
+  }
+
 #ifdef ART_ENABLE_CODEGEN_arm
 // Run the tests for ARM only with Baker read barriers, as the
 // expected generated code contains a Marking Register refresh
 // instruction.
-#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
-TEST_ISA(kThumb2)
-#endif
+TEST_ISA_ONLY_CC(kThumb2)
 #endif
 
 #ifdef ART_ENABLE_CODEGEN_arm64
 // Run the tests for ARM64 only with Baker read barriers, as the
 // expected generated code contains a Marking Register refresh
 // instruction.
-#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
-TEST_ISA(kArm64)
-#endif
+TEST_ISA_ONLY_CC(kArm64)
 #endif
 
 #ifdef ART_ENABLE_CODEGEN_x86
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc
index 0a1f017..397db25 100644
--- a/compiler/jni/jni_compiler_test.cc
+++ b/compiler/jni/jni_compiler_test.cc
@@ -22,11 +22,13 @@
 #include "art_method-inl.h"
 #include "base/bit_utils.h"
 #include "base/casts.h"
+#include "base/macros.h"
 #include "base/mem_map.h"
 #include "class_linker.h"
 #include "common_compiler_test.h"
 #include "compiler.h"
 #include "dex/dex_file.h"
+#include "driver/compiler_options.h"
 #include "entrypoints/entrypoint_utils-inl.h"
 #include "gtest/gtest.h"
 #include "indirect_reference_table.h"
@@ -43,7 +45,7 @@
 #include "oat_quick_method_header.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
-#include "thread.h"
+#include "thread-inl.h"
 
 extern "C" JNIEXPORT jint JNICALL Java_MyClassNatives_bar(JNIEnv*, jobject, jint count) {
   return count + 1;
@@ -71,7 +73,7 @@
 // TODO: In the Baker read barrier configuration, add checks to ensure
 // the Marking Register's value is correct.
 
-namespace art {
+namespace art HIDDEN {
 
 enum class JniKind {
   kNormal,      // Regular kind of un-annotated natives.
@@ -236,13 +238,14 @@
                       bool direct,
                       const char* method_name,
                       const char* method_sig) {
-    ScopedObjectAccess soa(Thread::Current());
-    StackHandleScope<2> hs(soa.Self());
+    Thread* self = Thread::Current();
+    ScopedObjectAccess soa(self);
+    StackHandleScope<2> hs(self);
     Handle<mirror::ClassLoader> loader(
         hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
     // Compile the native method before starting the runtime
     Handle<mirror::Class> c =
-        hs.NewHandle(class_linker_->FindClass(soa.Self(), "LMyClassNatives;", loader));
+        hs.NewHandle(class_linker_->FindClass(self, "LMyClassNatives;", loader));
     const auto pointer_size = class_linker_->GetImagePointerSize();
     ArtMethod* method = c->FindClassMethod(method_name, method_sig, pointer_size);
     ASSERT_TRUE(method != nullptr) << method_name << " " << method_sig;
@@ -251,8 +254,11 @@
       // Class initialization could replace the entrypoint, so force
       // the initialization before we set up the entrypoint below.
       class_linker_->EnsureInitialized(
-          soa.Self(), c, /*can_init_fields=*/ true, /*can_init_parents=*/ true);
-      class_linker_->MakeInitializedClassesVisiblyInitialized(soa.Self(), /*wait=*/ true);
+          self, c, /*can_init_fields=*/ true, /*can_init_parents=*/ true);
+      {
+        ScopedThreadSuspension sts(self, ThreadState::kNative);
+        class_linker_->MakeInitializedClassesVisiblyInitialized(self, /*wait=*/ true);
+      }
     }
     if (check_generic_jni_) {
       method->SetEntryPointFromQuickCompiledCode(class_linker_->GetRuntimeQuickGenericJniStub());
@@ -402,7 +408,7 @@
 jobject JniCompilerTest::class_loader_;
 
 void JniCompilerTest::AssertCallerObjectLocked(JNIEnv* env) {
-  Thread* self = down_cast<JNIEnvExt*>(env)->GetSelf();
+  Thread* self = Thread::ForEnv(env);
   CHECK_EQ(self, Thread::Current());
   ScopedObjectAccess soa(self);
   ArtMethod** caller_frame = self->GetManagedStack()->GetTopQuickFrame();
@@ -414,7 +420,7 @@
   CHECK(!caller->IsCriticalNative());
   CHECK(caller->IsSynchronized());
   ObjPtr<mirror::Object> lock;
-  if (self->GetManagedStack()->GetTopQuickFrameTag()) {
+  if (self->GetManagedStack()->GetTopQuickFrameGenericJniTag()) {
     // Generic JNI.
     lock = GetGenericJniSynchronizationObject(self, caller);
   } else if (caller->IsStatic()) {
@@ -845,6 +851,7 @@
   return x | y;
 }
 
+EXPORT  // Defined in `libart.so`.
 void InitEntryPoints(JniEntryPoints* jpoints,
                      QuickEntryPoints* qpoints,
                      bool monitor_jni_entry_exit);
@@ -1307,7 +1314,7 @@
       CompileForTestWithCurrentJni(class_loader_, false, "synchronizedThrowException", "()V");
     }
   }
-  // Start runtime to avoid re-initialization in SetupForTest.
+  // Start runtime to avoid re-initialization in SetUpForTest.
   Thread::Current()->TransitionFromSuspendedToRunnable();
   bool started = runtime_->Start();
   CHECK(started);
@@ -1547,6 +1554,10 @@
 }
 
 void JniCompilerTest::UpcallReturnTypeChecking_InstanceImpl() {
+  // Set debuggable so that the JNI compiler does not emit a fast-path that would skip the
+  // runtime call where we do these checks. Note that while normal gtests use the debug build
+  // which disables the fast path, `art_standalone_compiler_tests` run in the release build.
+  compiler_options_->SetDebuggable(true);
   SetUpForTest(false, "instanceMethodThatShouldReturnClass", "()Ljava/lang/Class;",
                CURRENT_JNI_WRAPPER(Java_MyClassNatives_instanceMethodThatShouldReturnClass));
 
@@ -1574,6 +1585,10 @@
 JNI_TEST(UpcallReturnTypeChecking_Instance)
 
 void JniCompilerTest::UpcallReturnTypeChecking_StaticImpl() {
+  // Set debuggable so that the JNI compiler does not emit a fast-path that would skip the
+  // runtime call where we do these checks. Note that while normal gtests use the debug build
+  // which disables the fast path, `art_standalone_compiler_tests` run in the release build.
+  compiler_options_->SetDebuggable(true);
   SetUpForTest(true, "staticMethodThatShouldReturnClass", "()Ljava/lang/Class;",
                CURRENT_JNI_WRAPPER(Java_MyClassNatives_staticMethodThatShouldReturnClass));
 
diff --git a/compiler/jni/quick/arm/calling_convention_arm.cc b/compiler/jni/quick/arm/calling_convention_arm.cc
index c1afdb8..d81ca77 100644
--- a/compiler/jni/quick/arm/calling_convention_arm.cc
+++ b/compiler/jni/quick/arm/calling_convention_arm.cc
@@ -23,7 +23,7 @@
 #include "base/macros.h"
 #include "utils/arm/managed_register_arm.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 //
@@ -199,6 +199,10 @@
   return ArmManagedRegister::FromCoreRegister(R0);
 }
 
+ManagedRegister ArmManagedRuntimeCallingConvention::ArgumentRegisterForMethodExitHook() {
+  return ArmManagedRegister::FromCoreRegister(R2);
+}
+
 void ArmManagedRuntimeCallingConvention::ResetIterator(FrameOffset displacement) {
   ManagedRuntimeCallingConvention::ResetIterator(displacement);
   gpr_index_ = 1u;  // Skip r0 for ArtMethod*
diff --git a/compiler/jni/quick/arm/calling_convention_arm.h b/compiler/jni/quick/arm/calling_convention_arm.h
index 4526d9e..3a09d4e 100644
--- a/compiler/jni/quick/arm/calling_convention_arm.h
+++ b/compiler/jni/quick/arm/calling_convention_arm.h
@@ -18,9 +18,10 @@
 #define ART_COMPILER_JNI_QUICK_ARM_CALLING_CONVENTION_ARM_H_
 
 #include "base/enums.h"
+#include "base/macros.h"
 #include "jni/quick/calling_convention.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 class ArmManagedRuntimeCallingConvention final : public ManagedRuntimeCallingConvention {
@@ -39,6 +40,7 @@
   void ResetIterator(FrameOffset displacement) override;
   // Managed runtime calling convention
   ManagedRegister MethodRegister() override;
+  ManagedRegister ArgumentRegisterForMethodExitHook() override;
   void Next() override;
   bool IsCurrentParamInRegister() override;
   bool IsCurrentParamOnStack() override;
diff --git a/compiler/jni/quick/arm64/calling_convention_arm64.cc b/compiler/jni/quick/arm64/calling_convention_arm64.cc
index ec77db3..e716502 100644
--- a/compiler/jni/quick/arm64/calling_convention_arm64.cc
+++ b/compiler/jni/quick/arm64/calling_convention_arm64.cc
@@ -22,7 +22,7 @@
 #include "arch/instruction_set.h"
 #include "utils/arm64/managed_register_arm64.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 static constexpr ManagedRegister kXArgumentRegisters[] = {
@@ -174,6 +174,10 @@
   return Arm64ManagedRegister::FromXRegister(X0);
 }
 
+ManagedRegister Arm64ManagedRuntimeCallingConvention::ArgumentRegisterForMethodExitHook() {
+  return Arm64ManagedRegister::FromXRegister(X4);
+}
+
 bool Arm64ManagedRuntimeCallingConvention::IsCurrentParamInRegister() {
   if (IsCurrentParamAFloatOrDouble()) {
     return itr_float_and_doubles_ < kMaxFloatOrDoubleRegisterArguments;
diff --git a/compiler/jni/quick/arm64/calling_convention_arm64.h b/compiler/jni/quick/arm64/calling_convention_arm64.h
index 176271e..f29eb15 100644
--- a/compiler/jni/quick/arm64/calling_convention_arm64.h
+++ b/compiler/jni/quick/arm64/calling_convention_arm64.h
@@ -18,9 +18,10 @@
 #define ART_COMPILER_JNI_QUICK_ARM64_CALLING_CONVENTION_ARM64_H_
 
 #include "base/enums.h"
+#include "base/macros.h"
 #include "jni/quick/calling_convention.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 class Arm64ManagedRuntimeCallingConvention final : public ManagedRuntimeCallingConvention {
@@ -35,6 +36,7 @@
   ManagedRegister ReturnRegister() const override;
   // Managed runtime calling convention
   ManagedRegister MethodRegister() override;
+  ManagedRegister ArgumentRegisterForMethodExitHook() override;
   bool IsCurrentParamInRegister() override;
   bool IsCurrentParamOnStack() override;
   ManagedRegister CurrentParamRegister() override;
diff --git a/compiler/jni/quick/calling_convention.cc b/compiler/jni/quick/calling_convention.cc
index eb4d372..2b9da6b 100644
--- a/compiler/jni/quick/calling_convention.cc
+++ b/compiler/jni/quick/calling_convention.cc
@@ -37,7 +37,7 @@
 #include "jni/quick/x86_64/calling_convention_x86_64.h"
 #endif
 
-namespace art {
+namespace art HIDDEN {
 
 // Managed runtime calling convention
 
@@ -74,6 +74,10 @@
               is_static, is_synchronized, shorty));
 #endif
     default:
+      UNUSED(allocator);
+      UNUSED(is_static);
+      UNUSED(is_synchronized);
+      UNUSED(shorty);
       LOG(FATAL) << "Unknown InstructionSet: " << instruction_set;
       UNREACHABLE();
   }
@@ -165,6 +169,12 @@
               is_static, is_synchronized, is_fast_native, is_critical_native, shorty));
 #endif
     default:
+      UNUSED(allocator);
+      UNUSED(is_static);
+      UNUSED(is_synchronized);
+      UNUSED(is_fast_native);
+      UNUSED(is_critical_native);
+      UNUSED(shorty);
       LOG(FATAL) << "Unknown InstructionSet: " << instruction_set;
       UNREACHABLE();
   }
diff --git a/compiler/jni/quick/calling_convention.h b/compiler/jni/quick/calling_convention.h
index e2f3bfb..0187b14 100644
--- a/compiler/jni/quick/calling_convention.h
+++ b/compiler/jni/quick/calling_convention.h
@@ -20,11 +20,12 @@
 #include "base/arena_object.h"
 #include "base/array_ref.h"
 #include "base/enums.h"
+#include "base/macros.h"
 #include "dex/primitive.h"
 #include "thread.h"
 #include "utils/managed_register.h"
 
-namespace art {
+namespace art HIDDEN {
 
 enum class InstructionSet;
 
@@ -244,6 +245,11 @@
   // Register that holds the incoming method argument
   virtual ManagedRegister MethodRegister() = 0;
 
+  // Register that is used to pass frame size for method exit hook call. This
+  // shouldn't be the same as the return register since method exit hook also expects
+  // return values in the return register.
+  virtual ManagedRegister ArgumentRegisterForMethodExitHook() = 0;
+
   // Iterator interface
   bool HasNext();
   virtual void Next();
diff --git a/compiler/jni/quick/jni_compiler.cc b/compiler/jni/quick/jni_compiler.cc
index 6cb5021..c60d974 100644
--- a/compiler/jni/quick/jni_compiler.cc
+++ b/compiler/jni/quick/jni_compiler.cc
@@ -36,7 +36,9 @@
 #include "dex/dex_file-inl.h"
 #include "driver/compiler_options.h"
 #include "entrypoints/quick/quick_entrypoints.h"
+#include "instrumentation.h"
 #include "jni/jni_env_ext.h"
+#include "runtime.h"
 #include "thread.h"
 #include "utils/arm/managed_register_arm.h"
 #include "utils/arm64/managed_register_arm64.h"
@@ -47,7 +49,7 @@
 
 #define __ jni_asm->
 
-namespace art {
+namespace art HIDDEN {
 
 constexpr size_t kIRTCookieSize = JniCallingConvention::SavedLocalReferenceCookieSize();
 
@@ -68,6 +70,12 @@
                                ManagedRegister in_reg);
 
 template <PointerSize kPointerSize>
+static void CallDecodeReferenceResult(JNIMacroAssembler<kPointerSize>* jni_asm,
+                                      JniCallingConvention* jni_conv,
+                                      ManagedRegister mr_return_reg,
+                                      size_t main_out_arg_size);
+
+template <PointerSize kPointerSize>
 static std::unique_ptr<JNIMacroAssembler<kPointerSize>> GetMacroAssembler(
     ArenaAllocator* allocator, InstructionSet isa, const InstructionSetFeatures* features) {
   return JNIMacroAssembler<kPointerSize>::Create(allocator, isa, features);
@@ -101,6 +109,24 @@
   // i.e. if the method was annotated with @CriticalNative
   const bool is_critical_native = (access_flags & kAccCriticalNative) != 0u;
 
+  bool is_debuggable = compiler_options.GetDebuggable();
+  bool needs_entry_exit_hooks = is_debuggable && compiler_options.IsJitCompiler();
+  // We don't support JITing stubs for critical native methods in debuggable runtimes yet.
+  // TODO(mythria): Add support required for calling method entry / exit hooks from critical native
+  // methods.
+  DCHECK_IMPLIES(needs_entry_exit_hooks, !is_critical_native);
+
+  // The fast-path for decoding a reference skips CheckJNI checks, so we do not inline the
+  // decoding in debug build or for debuggable apps (both cases enable CheckJNI by default).
+  bool inline_decode_reference = !kIsDebugBuild && !is_debuggable;
+
+  // When  walking the stack the top frame doesn't have a pc associated with it. We then depend on
+  // the invariant that we don't have JITed code when AOT code is available. In debuggable runtimes
+  // this invariant doesn't hold. So we tag the SP for JITed code to indentify if we are executing
+  // JITed code or AOT code. Since tagging involves additional instructions we tag only in
+  // debuggable runtimes.
+  bool should_tag_sp = needs_entry_exit_hooks;
+
   VLOG(jni) << "JniCompile: Method :: "
               << dex_file.PrettyMethod(method_idx, /* with signature */ true)
               << " :: access_flags = " << std::hex << access_flags << std::dec;
@@ -182,7 +208,7 @@
   //      Skip this for @CriticalNative because we're not passing a `jclass` to the native method.
   std::unique_ptr<JNIMacroLabel> jclass_read_barrier_slow_path;
   std::unique_ptr<JNIMacroLabel> jclass_read_barrier_return;
-  if (kUseReadBarrier && is_static && LIKELY(!is_critical_native)) {
+  if (gUseReadBarrier && is_static && LIKELY(!is_critical_native)) {
     jclass_read_barrier_slow_path = __ CreateLabel();
     jclass_read_barrier_return = __ CreateLabel();
 
@@ -219,7 +245,22 @@
   //       because garbage collections are disabled within the execution of a
   //       @CriticalNative method.
   if (LIKELY(!is_critical_native)) {
-    __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>());
+    __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>(), should_tag_sp);
+  }
+
+  // 1.5. Call any method entry hooks if required.
+  // For critical native methods, we don't JIT stubs in debuggable runtimes (see
+  // OptimizingCompiler::JitCompile).
+  // TODO(mythria): Add support to call method entry / exit hooks for critical native methods too.
+  std::unique_ptr<JNIMacroLabel> method_entry_hook_slow_path;
+  std::unique_ptr<JNIMacroLabel> method_entry_hook_return;
+  if (UNLIKELY(needs_entry_exit_hooks)) {
+    uint64_t address = reinterpret_cast64<uint64_t>(Runtime::Current()->GetInstrumentation());
+    int offset = instrumentation::Instrumentation::HaveMethodEntryListenersOffset().Int32Value();
+    method_entry_hook_slow_path = __ CreateLabel();
+    method_entry_hook_return = __ CreateLabel();
+    __ TestByteAndJumpIfNotZero(address + offset, method_entry_hook_slow_path.get());
+    __ Bind(method_entry_hook_return.get());
   }
 
   // 2. Lock the object (if synchronized) and transition out of Runnable (if normal native).
@@ -442,8 +483,7 @@
     __ Bind(transition_to_runnable_resume.get());
   }
 
-  // 5.2. For methods that return a reference, do an early exception check so that the
-  //      `JniDecodeReferenceResult()` in the main path does not need to check for exceptions.
+  // 5.2. For methods that return a reference, do an exception check before decoding the reference.
   std::unique_ptr<JNIMacroLabel> exception_slow_path =
       LIKELY(!is_critical_native) ? __ CreateLabel() : nullptr;
   if (reference_return) {
@@ -462,23 +502,23 @@
     __ Bind(suspend_check_resume.get());
   }
 
-  // 5.4 For methods with reference return, decode the `jobject` with `JniDecodeReferenceResult()`.
+  // 5.4 For methods with reference return, decode the `jobject`, either directly
+  //     or with a call to `JniDecodeReferenceResult()`.
+  std::unique_ptr<JNIMacroLabel> decode_reference_slow_path;
+  std::unique_ptr<JNIMacroLabel> decode_reference_resume;
   if (reference_return) {
     DCHECK(!is_critical_native);
-    // We abuse the JNI calling convention here, that is guaranteed to support passing
-    // two pointer arguments, `JNIEnv*` and `jclass`/`jobject`.
-    main_jni_conv->ResetIterator(FrameOffset(main_out_arg_size));
-    ThreadOffset<kPointerSize> jni_decode_reference_result =
-        QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniDecodeReferenceResult);
-    // Pass result.
-    SetNativeParameter(jni_asm.get(), main_jni_conv.get(), mr_conv->ReturnRegister());
-    main_jni_conv->Next();
-    if (main_jni_conv->IsCurrentParamInRegister()) {
-      __ GetCurrentThread(main_jni_conv->CurrentParamRegister());
-      __ Call(main_jni_conv->CurrentParamRegister(), Offset(jni_decode_reference_result));
+    if (inline_decode_reference) {
+      // Decode local and JNI transition references in the main path.
+      decode_reference_slow_path = __ CreateLabel();
+      decode_reference_resume = __ CreateLabel();
+      __ DecodeJNITransitionOrLocalJObject(mr_conv->ReturnRegister(),
+                                           decode_reference_slow_path.get(),
+                                           decode_reference_resume.get());
+      __ Bind(decode_reference_resume.get());
     } else {
-      __ GetCurrentThread(main_jni_conv->CurrentParamStackOffset());
-      __ CallFromThread(jni_decode_reference_result);
+      CallDecodeReferenceResult<kPointerSize>(
+          jni_asm.get(), main_jni_conv.get(), mr_conv->ReturnRegister(), main_out_arg_size);
     }
   }  // if (!is_critical_native)
 
@@ -532,7 +572,21 @@
     __ Bind(suspend_check_resume.get());
   }
 
-  // 7.5. Remove activation - need to restore callee save registers since the GC
+  // 7.5. Check if method exit hooks needs to be called
+  // For critical native methods, we don't JIT stubs in debuggable runtimes.
+  // TODO(mythria): Add support to call method entry / exit hooks for critical native methods too.
+  std::unique_ptr<JNIMacroLabel> method_exit_hook_slow_path;
+  std::unique_ptr<JNIMacroLabel> method_exit_hook_return;
+  if (UNLIKELY(needs_entry_exit_hooks)) {
+    uint64_t address = reinterpret_cast64<uint64_t>(Runtime::Current()->GetInstrumentation());
+    int offset = instrumentation::Instrumentation::RunExitHooksOffset().Int32Value();
+    method_exit_hook_slow_path = __ CreateLabel();
+    method_exit_hook_return = __ CreateLabel();
+    __ TestByteAndJumpIfNotZero(address + offset, method_exit_hook_slow_path.get());
+    __ Bind(method_exit_hook_return.get());
+  }
+
+  // 7.6. Remove activation - need to restore callee save registers since the GC
   //      may have changed them.
   DCHECK_EQ(jni_asm->cfi().GetCurrentCFAOffset(), static_cast<int>(current_frame_size));
   if (LIKELY(!is_critical_native) || !main_jni_conv->UseTailCall()) {
@@ -547,7 +601,7 @@
 
   // 8.1. Read barrier slow path for the declaring class in the method for a static call.
   //      Skip this for @CriticalNative because we're not passing a `jclass` to the native method.
-  if (kUseReadBarrier && is_static && !is_critical_native) {
+  if (gUseReadBarrier && is_static && !is_critical_native) {
     __ Bind(jclass_read_barrier_slow_path.get());
 
     // Construct slow path for read barrier:
@@ -594,27 +648,7 @@
     __ Jump(transition_to_runnable_resume.get());
   }
 
-  // 8.4. Suspend check slow path.
-  if (UNLIKELY(is_fast_native)) {
-    __ Bind(suspend_check_slow_path.get());
-    if (reference_return && main_out_arg_size != 0) {
-      jni_asm->cfi().AdjustCFAOffset(main_out_arg_size);
-      __ DecreaseFrameSize(main_out_arg_size);
-    }
-    __ CallFromThread(QUICK_ENTRYPOINT_OFFSET(kPointerSize, pTestSuspend));
-    if (reference_return) {
-      // Suspend check entry point overwrites top of managed stack and leaves it clobbered.
-      // We need to restore the top for subsequent runtime call to `JniDecodeReferenceResult()`.
-      __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>());
-    }
-    if (reference_return && main_out_arg_size != 0) {
-      __ IncreaseFrameSize(main_out_arg_size);
-      jni_asm->cfi().AdjustCFAOffset(-main_out_arg_size);
-    }
-    __ Jump(suspend_check_resume.get());
-  }
-
-  // 8.5. Exception poll slow path(s).
+  // 8.4. Exception poll slow path(s).
   if (LIKELY(!is_critical_native)) {
     __ Bind(exception_slow_path.get());
     if (reference_return) {
@@ -630,6 +664,61 @@
     __ DeliverPendingException();
   }
 
+  // 8.5 Slow path for decoding the `jobject`.
+  if (reference_return && inline_decode_reference) {
+    __ Bind(decode_reference_slow_path.get());
+    if (main_out_arg_size != 0) {
+      jni_asm->cfi().AdjustCFAOffset(main_out_arg_size);
+    }
+    CallDecodeReferenceResult<kPointerSize>(
+        jni_asm.get(), main_jni_conv.get(), mr_conv->ReturnRegister(), main_out_arg_size);
+    __ Jump(decode_reference_resume.get());
+    if (main_out_arg_size != 0) {
+      jni_asm->cfi().AdjustCFAOffset(-main_out_arg_size);
+    }
+  }
+
+  // 8.6. Suspend check slow path.
+  if (UNLIKELY(is_fast_native)) {
+    __ Bind(suspend_check_slow_path.get());
+    if (reference_return && main_out_arg_size != 0) {
+      jni_asm->cfi().AdjustCFAOffset(main_out_arg_size);
+      __ DecreaseFrameSize(main_out_arg_size);
+    }
+    __ CallFromThread(QUICK_ENTRYPOINT_OFFSET(kPointerSize, pTestSuspend));
+    if (reference_return) {
+      // Suspend check entry point overwrites top of managed stack and leaves it clobbered.
+      // We need to restore the top for subsequent runtime call to `JniDecodeReferenceResult()`.
+      __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>(), should_tag_sp);
+    }
+    if (reference_return && main_out_arg_size != 0) {
+      __ IncreaseFrameSize(main_out_arg_size);
+    }
+    __ Jump(suspend_check_resume.get());
+    if (reference_return && main_out_arg_size != 0) {
+      jni_asm->cfi().AdjustCFAOffset(-main_out_arg_size);
+    }
+  }
+
+  // 8.7. Method entry / exit hooks slow paths.
+  if (UNLIKELY(needs_entry_exit_hooks)) {
+    __ Bind(method_entry_hook_slow_path.get());
+    // Use Jni specific method entry hook that saves all the arguments. We have only saved the
+    // callee save registers at this point. So go through Jni specific stub that saves the rest
+    // of the live registers.
+    __ CallFromThread(QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodEntryHook));
+    __ ExceptionPoll(exception_slow_path.get());
+    __ Jump(method_entry_hook_return.get());
+
+    __ Bind(method_exit_hook_slow_path.get());
+    // Method exit hooks is called just before tearing down the frame. So there are no live
+    // registers and we can directly call the method exit hook and don't need a Jni specific
+    // entrypoint.
+    __ Move(mr_conv->ArgumentRegisterForMethodExitHook(), managed_frame_size);
+    __ CallFromThread(QUICK_ENTRYPOINT_OFFSET(kPointerSize, pMethodExitHook));
+    __ Jump(method_exit_hook_return.get());
+  }
+
   // 9. Finalize code generation.
   __ FinalizeCode();
   size_t cs = __ CodeSize();
@@ -693,6 +782,31 @@
   }
 }
 
+template <PointerSize kPointerSize>
+static void CallDecodeReferenceResult(JNIMacroAssembler<kPointerSize>* jni_asm,
+                                      JniCallingConvention* jni_conv,
+                                      ManagedRegister mr_return_reg,
+                                      size_t main_out_arg_size) {
+  // We abuse the JNI calling convention here, that is guaranteed to support passing
+  // two pointer arguments, `JNIEnv*` and `jclass`/`jobject`.
+  jni_conv->ResetIterator(FrameOffset(main_out_arg_size));
+  ThreadOffset<kPointerSize> jni_decode_reference_result =
+      QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniDecodeReferenceResult);
+  // Pass result.
+  SetNativeParameter(jni_asm, jni_conv, mr_return_reg);
+  jni_conv->Next();
+  if (jni_conv->IsCurrentParamInRegister()) {
+    __ GetCurrentThread(jni_conv->CurrentParamRegister());
+    __ Call(jni_conv->CurrentParamRegister(), Offset(jni_decode_reference_result));
+  } else {
+    __ GetCurrentThread(jni_conv->CurrentParamStackOffset());
+    __ CallFromThread(jni_decode_reference_result);
+  }
+  // Note: If the native ABI returns the pointer in a register different from
+  // `mr_return_register`, the `JniDecodeReferenceResult` entrypoint must be
+  // a stub that moves the result to `mr_return_register`.
+}
+
 JniCompiledMethod ArtQuickJniCompileMethod(const CompilerOptions& compiler_options,
                                            uint32_t access_flags,
                                            uint32_t method_idx,
diff --git a/compiler/jni/quick/jni_compiler.h b/compiler/jni/quick/jni_compiler.h
index 52a6f3c..d43b2a9 100644
--- a/compiler/jni/quick/jni_compiler.h
+++ b/compiler/jni/quick/jni_compiler.h
@@ -21,8 +21,9 @@
 
 #include "arch/instruction_set.h"
 #include "base/array_ref.h"
+#include "base/macros.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArenaAllocator;
 class ArtMethod;
diff --git a/compiler/jni/quick/x86/calling_convention_x86.cc b/compiler/jni/quick/x86/calling_convention_x86.cc
index 65be92c..598e8e7 100644
--- a/compiler/jni/quick/x86/calling_convention_x86.cc
+++ b/compiler/jni/quick/x86/calling_convention_x86.cc
@@ -22,7 +22,7 @@
 #include "arch/x86/jni_frame_x86.h"
 #include "utils/x86/managed_register_x86.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 static constexpr ManagedRegister kManagedCoreArgumentRegisters[] = {
@@ -143,6 +143,10 @@
   return X86ManagedRegister::FromCpuRegister(EAX);
 }
 
+ManagedRegister X86ManagedRuntimeCallingConvention::ArgumentRegisterForMethodExitHook() {
+  return X86ManagedRegister::FromCpuRegister(EBX);
+}
+
 void X86ManagedRuntimeCallingConvention::ResetIterator(FrameOffset displacement) {
   ManagedRuntimeCallingConvention::ResetIterator(displacement);
   gpr_arg_count_ = 1u;  // Skip EAX for ArtMethod*
diff --git a/compiler/jni/quick/x86/calling_convention_x86.h b/compiler/jni/quick/x86/calling_convention_x86.h
index cd7ef5b..f0d663d 100644
--- a/compiler/jni/quick/x86/calling_convention_x86.h
+++ b/compiler/jni/quick/x86/calling_convention_x86.h
@@ -18,9 +18,10 @@
 #define ART_COMPILER_JNI_QUICK_X86_CALLING_CONVENTION_X86_H_
 
 #include "base/enums.h"
+#include "base/macros.h"
 #include "jni/quick/calling_convention.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 class X86ManagedRuntimeCallingConvention final : public ManagedRuntimeCallingConvention {
@@ -37,6 +38,7 @@
   void ResetIterator(FrameOffset displacement) override;
   // Managed runtime calling convention
   ManagedRegister MethodRegister() override;
+  ManagedRegister ArgumentRegisterForMethodExitHook() override;
   void Next() override;
   bool IsCurrentParamInRegister() override;
   bool IsCurrentParamOnStack() override;
diff --git a/compiler/jni/quick/x86_64/calling_convention_x86_64.cc b/compiler/jni/quick/x86_64/calling_convention_x86_64.cc
index 862ee5e..9d0761d 100644
--- a/compiler/jni/quick/x86_64/calling_convention_x86_64.cc
+++ b/compiler/jni/quick/x86_64/calling_convention_x86_64.cc
@@ -23,7 +23,7 @@
 #include "base/bit_utils.h"
 #include "utils/x86_64/managed_register_x86_64.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 static constexpr ManagedRegister kCoreArgumentRegisters[] = {
@@ -147,6 +147,10 @@
   return X86_64ManagedRegister::FromCpuRegister(RDI);
 }
 
+ManagedRegister X86_64ManagedRuntimeCallingConvention::ArgumentRegisterForMethodExitHook() {
+  return X86_64ManagedRegister::FromCpuRegister(R8);
+}
+
 bool X86_64ManagedRuntimeCallingConvention::IsCurrentParamInRegister() {
   if (IsCurrentParamAFloatOrDouble()) {
     return itr_float_and_doubles_ < kMaxFloatOrDoubleRegisterArguments;
diff --git a/compiler/jni/quick/x86_64/calling_convention_x86_64.h b/compiler/jni/quick/x86_64/calling_convention_x86_64.h
index 483f1f5..859a277 100644
--- a/compiler/jni/quick/x86_64/calling_convention_x86_64.h
+++ b/compiler/jni/quick/x86_64/calling_convention_x86_64.h
@@ -18,9 +18,10 @@
 #define ART_COMPILER_JNI_QUICK_X86_64_CALLING_CONVENTION_X86_64_H_
 
 #include "base/enums.h"
+#include "base/macros.h"
 #include "jni/quick/calling_convention.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 class X86_64ManagedRuntimeCallingConvention final : public ManagedRuntimeCallingConvention {
@@ -35,6 +36,7 @@
   ManagedRegister ReturnRegister() const override;
   // Managed runtime calling convention
   ManagedRegister MethodRegister() override;
+  ManagedRegister ArgumentRegisterForMethodExitHook() override;
   bool IsCurrentParamInRegister() override;
   bool IsCurrentParamOnStack() override;
   ManagedRegister CurrentParamRegister() override;
diff --git a/compiler/libart-compiler.map b/compiler/libart-compiler.map
new file mode 100644
index 0000000..f66052a
--- /dev/null
+++ b/compiler/libart-compiler.map
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2022 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.
+#
+
+ART_COMPILER {
+  global:
+    extern "C++" {
+      art::debug::MakeMiniDebugInfo*;
+      *art::debug::WriteDebugInfo*;
+      art::Compiler::Create*;
+      art::CompilerOptions::*;
+      art::CreateTrampoline*;
+      art::IntrinsicObjects::*;
+      art::linker::operator*art::linker::LinkerPatch::Type*;
+      art::operator*art::Whence*;
+    };
+
+    jit_load;
+
+  local:
+    *;
+};
diff --git a/compiler/linker/linker_patch.h b/compiler/linker/linker_patch.h
index 7da1e82..8ed7fce 100644
--- a/compiler/linker/linker_patch.h
+++ b/compiler/linker/linker_patch.h
@@ -23,9 +23,10 @@
 #include <android-base/logging.h>
 
 #include "base/bit_utils.h"
+#include "base/macros.h"
 #include "dex/method_reference.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class DexFile;
 
@@ -328,7 +329,7 @@
   friend bool operator==(const LinkerPatch& lhs, const LinkerPatch& rhs);
   friend bool operator<(const LinkerPatch& lhs, const LinkerPatch& rhs);
 };
-std::ostream& operator<<(std::ostream& os, LinkerPatch::Type type);
+EXPORT std::ostream& operator<<(std::ostream& os, LinkerPatch::Type type);
 
 inline bool operator==(const LinkerPatch& lhs, const LinkerPatch& rhs) {
   return lhs.literal_offset_ == rhs.literal_offset_ &&
diff --git a/compiler/linker/linker_patch_test.cc b/compiler/linker/linker_patch_test.cc
index 997418c..1c46da1 100644
--- a/compiler/linker/linker_patch_test.cc
+++ b/compiler/linker/linker_patch_test.cc
@@ -16,9 +16,10 @@
 
 #include <gtest/gtest.h>
 
+#include "base/macros.h"
 #include "linker_patch.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace linker {
 
 TEST(LinkerPatch, LinkerPatchOperators) {
diff --git a/compiler/linker/output_stream_test.cc b/compiler/linker/output_stream_test.cc
index f1af4cb8..22b174f 100644
--- a/compiler/linker/output_stream_test.cc
+++ b/compiler/linker/output_stream_test.cc
@@ -16,17 +16,17 @@
 
 #include <android-base/logging.h>
 
+#include "base/common_art_test.h"
 #include "base/macros.h"
 #include "base/unix_file/fd_file.h"
-#include "common_runtime_test.h"
 #include "stream/buffered_output_stream.h"
 #include "stream/file_output_stream.h"
 #include "stream/vector_output_stream.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace linker {
 
-class OutputStreamTest : public CommonRuntimeTest {
+class OutputStreamTest : public CommonArtTest {
  protected:
   void CheckOffset(off_t expected) {
     off_t actual = output_stream_->Seek(0, kSeekCurrent);
diff --git a/compiler/optimizing/block_builder.cc b/compiler/optimizing/block_builder.cc
index e1f061a..703584c 100644
--- a/compiler/optimizing/block_builder.cc
+++ b/compiler/optimizing/block_builder.cc
@@ -22,7 +22,7 @@
 #include "dex/dex_file_exception_helpers.h"
 #include "quicken_info.h"
 
-namespace art {
+namespace art HIDDEN {
 
 HBasicBlockBuilder::HBasicBlockBuilder(HGraph* graph,
                                        const DexFile* const dex_file,
diff --git a/compiler/optimizing/block_builder.h b/compiler/optimizing/block_builder.h
index 42a3f32..8668ef8 100644
--- a/compiler/optimizing/block_builder.h
+++ b/compiler/optimizing/block_builder.h
@@ -17,13 +17,14 @@
 #ifndef ART_COMPILER_OPTIMIZING_BLOCK_BUILDER_H_
 #define ART_COMPILER_OPTIMIZING_BLOCK_BUILDER_H_
 
+#include "base/macros.h"
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 #include "dex/code_item_accessors.h"
 #include "dex/dex_file.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class HBasicBlockBuilder : public ValueObject {
  public:
diff --git a/compiler/optimizing/block_namer.cc b/compiler/optimizing/block_namer.cc
index d30448c..029e26b 100644
--- a/compiler/optimizing/block_namer.cc
+++ b/compiler/optimizing/block_namer.cc
@@ -18,7 +18,7 @@
 
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 std::ostream& BlockNamer::PrintName(std::ostream& os, HBasicBlock* blk) const {
   os << "B";
diff --git a/compiler/optimizing/block_namer.h b/compiler/optimizing/block_namer.h
index ed396b9..39c5973 100644
--- a/compiler/optimizing/block_namer.h
+++ b/compiler/optimizing/block_namer.h
@@ -19,7 +19,9 @@
 
 #include <ostream>
 
-namespace art {
+#include "base/macros.h"
+
+namespace art HIDDEN {
 class HBasicBlock;
 
 struct BlockNamer {
diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc
index dad3c81..919abfd 100644
--- a/compiler/optimizing/bounds_check_elimination.cc
+++ b/compiler/optimizing/bounds_check_elimination.cc
@@ -24,7 +24,7 @@
 #include "nodes.h"
 #include "side_effects_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class MonotonicValueRange;
 
@@ -490,7 +490,7 @@
   DISALLOW_COPY_AND_ASSIGN(MonotonicValueRange);
 };
 
-class BCEVisitor : public HGraphVisitor {
+class BCEVisitor final : public HGraphVisitor {
  public:
   // The least number of bounds checks that should be eliminated by triggering
   // the deoptimization technique.
@@ -564,6 +564,19 @@
     early_exit_loop_.clear();
     taken_test_loop_.clear();
     finite_loop_.clear();
+
+    // We may have eliminated all bounds checks so we should update the flag.
+    // TODO(solanes): Do this without a linear pass of the graph?
+    GetGraph()->SetHasBoundsChecks(false);
+    for (HBasicBlock* block : GetGraph()->GetReversePostOrder()) {
+      for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
+        HInstruction* instruction = it.Current();
+        if (instruction->IsBoundsCheck()) {
+          GetGraph()->SetHasBoundsChecks(true);
+          return;
+        }
+      }
+    }
   }
 
  private:
@@ -1818,6 +1831,7 @@
                          HInstruction* condition,
                          bool is_null_check = false) {
     HInstruction* suspend = loop->GetSuspendCheck();
+    DCHECK(suspend != nullptr);
     block->InsertInstructionBefore(condition, block->GetLastInstruction());
     DeoptimizationKind kind =
         is_null_check ? DeoptimizationKind::kLoopNullBCE : DeoptimizationKind::kLoopBoundsBCE;
@@ -1997,7 +2011,7 @@
     phi->SetRawInputAt(0, instruction);
     phi->SetRawInputAt(1, zero);
     if (type == DataType::Type::kReference) {
-      phi->SetReferenceTypeInfo(instruction->GetReferenceTypeInfo());
+      phi->SetReferenceTypeInfoIfValid(instruction->GetReferenceTypeInfo());
     }
     new_preheader->AddPhi(phi);
     return phi;
diff --git a/compiler/optimizing/bounds_check_elimination.h b/compiler/optimizing/bounds_check_elimination.h
index ef08877..f210fa9 100644
--- a/compiler/optimizing/bounds_check_elimination.h
+++ b/compiler/optimizing/bounds_check_elimination.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_BOUNDS_CHECK_ELIMINATION_H_
 #define ART_COMPILER_OPTIMIZING_BOUNDS_CHECK_ELIMINATION_H_
 
+#include "base/macros.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class SideEffectsAnalysis;
 class HInductionVarAnalysis;
diff --git a/compiler/optimizing/bounds_check_elimination_test.cc b/compiler/optimizing/bounds_check_elimination_test.cc
index 5927d68..929a9e7 100644
--- a/compiler/optimizing/bounds_check_elimination_test.cc
+++ b/compiler/optimizing/bounds_check_elimination_test.cc
@@ -17,6 +17,7 @@
 #include "bounds_check_elimination.h"
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "gvn.h"
 #include "induction_var_analysis.h"
@@ -27,7 +28,7 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Fixture class for the BoundsCheckElimination tests.
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc
index e7826bb..48d1a9d 100644
--- a/compiler/optimizing/builder.cc
+++ b/compiler/optimizing/builder.cc
@@ -33,7 +33,7 @@
 #include "ssa_builder.h"
 #include "thread.h"
 
-namespace art {
+namespace art HIDDEN {
 
 HGraphBuilder::HGraphBuilder(HGraph* graph,
                              const CodeItemDebugInfoAccessor& accessor,
@@ -103,7 +103,6 @@
   graph_->SetNumberOfVRegs(code_item_accessor_.RegistersSize());
   graph_->SetNumberOfInVRegs(code_item_accessor_.InsSize());
   graph_->SetMaximumNumberOfOutVRegs(code_item_accessor_.OutsSize());
-  graph_->SetHasTryCatch(code_item_accessor_.TriesSize() != 0);
 
   // Use ScopedArenaAllocator for all local allocations.
   ScopedArenaAllocator local_allocator(graph_->GetArenaStack());
@@ -168,7 +167,6 @@
   graph_->SetNumberOfVRegs(return_vregs + num_arg_vregs);
   graph_->SetNumberOfInVRegs(num_arg_vregs);
   graph_->SetMaximumNumberOfOutVRegs(num_arg_vregs);
-  graph_->SetHasTryCatch(false);
 
   // Use ScopedArenaAllocator for all local allocations.
   ScopedArenaAllocator local_allocator(graph_->GetArenaStack());
diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h
index 580769e..ef225d9 100644
--- a/compiler/optimizing/builder.h
+++ b/compiler/optimizing/builder.h
@@ -19,12 +19,13 @@
 
 #include "base/arena_object.h"
 #include "base/array_ref.h"
+#include "base/macros.h"
 #include "dex/code_item_accessors.h"
 #include "dex/dex_file-inl.h"
 #include "dex/dex_file.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArtMethod;
 class CodeGenerator;
diff --git a/compiler/optimizing/cha_guard_optimization.cc b/compiler/optimizing/cha_guard_optimization.cc
index c6232ef..20a763c 100644
--- a/compiler/optimizing/cha_guard_optimization.cc
+++ b/compiler/optimizing/cha_guard_optimization.cc
@@ -16,7 +16,7 @@
 
 #include "cha_guard_optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Note we can only do CHA guard elimination/motion in a single pass, since
 // if a guard is not removed, another guard might be removed due to
@@ -200,6 +200,7 @@
 
     block->RemoveInstruction(deopt);
     HInstruction* suspend = loop_info->GetSuspendCheck();
+    DCHECK(suspend != nullptr);
     // Need a new deoptimize instruction that copies the environment
     // of the suspend instruction for the loop.
     HDeoptimize* deoptimize = new (GetGraph()->GetAllocator()) HDeoptimize(
diff --git a/compiler/optimizing/cha_guard_optimization.h b/compiler/optimizing/cha_guard_optimization.h
index 440d51a..5c1fdd9 100644
--- a/compiler/optimizing/cha_guard_optimization.h
+++ b/compiler/optimizing/cha_guard_optimization.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_CHA_GUARD_OPTIMIZATION_H_
 #define ART_COMPILER_OPTIMIZING_CHA_GUARD_OPTIMIZATION_H_
 
+#include "base/macros.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Optimize CHA guards by removing/moving them.
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index 27eabaf..c9f42b5 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -15,6 +15,7 @@
  */
 
 #include "code_generator.h"
+#include "base/globals.h"
 
 #ifdef ART_ENABLE_CODEGEN_arm
 #include "code_generator_arm_vixl.h"
@@ -24,6 +25,10 @@
 #include "code_generator_arm64.h"
 #endif
 
+#ifdef ART_ENABLE_CODEGEN_riscv64
+#include "code_generator_riscv64.h"
+#endif
+
 #ifdef ART_ENABLE_CODEGEN_x86
 #include "code_generator_x86.h"
 #endif
@@ -39,7 +44,6 @@
 #include "base/leb128.h"
 #include "class_linker.h"
 #include "class_root-inl.h"
-#include "compiled_method.h"
 #include "dex/bytecode_utils.h"
 #include "dex/code_item_accessors-inl.h"
 #include "graph_visualizer.h"
@@ -61,7 +65,7 @@
 #include "thread-current-inl.h"
 #include "utils/assembler.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Return whether a location is consistent with a type.
 static bool CheckType(DataType::Type type, Location location) {
@@ -389,7 +393,8 @@
                                    core_spill_mask_,
                                    fpu_spill_mask_,
                                    GetGraph()->GetNumberOfVRegs(),
-                                   GetGraph()->IsCompilingBaseline());
+                                   GetGraph()->IsCompilingBaseline(),
+                                   GetGraph()->IsDebuggable());
 
   size_t frame_start = GetAssembler()->CodeSize();
   GenerateFrameEntry();
@@ -412,7 +417,13 @@
     for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
       HInstruction* current = it.Current();
       if (current->HasEnvironment()) {
-        // Create stackmap for HNativeDebugInfo or any instruction which calls native code.
+        // Catch StackMaps are dealt with later on in `RecordCatchBlockInfo`.
+        if (block->IsCatchBlock() && block->GetFirstInstruction() == current) {
+          DCHECK(current->IsNop());
+          continue;
+        }
+
+        // Create stackmap for HNop or any instruction which calls native code.
         // Note that we need correct mapping for the native PC of the call instruction,
         // so the runtime's stackmap is not sufficient since it is at PC after the call.
         MaybeRecordNativeDebugInfo(current, block->GetDexPc());
@@ -1030,6 +1041,9 @@
     }
 #endif
     default:
+      UNUSED(allocator);
+      UNUSED(graph);
+      UNUSED(stats);
       return nullptr;
   }
 }
@@ -1041,7 +1055,8 @@
                              uint32_t core_callee_save_mask,
                              uint32_t fpu_callee_save_mask,
                              const CompilerOptions& compiler_options,
-                             OptimizingCompilerStats* stats)
+                             OptimizingCompilerStats* stats,
+                             const art::ArrayRef<const bool>& unimplemented_intrinsics)
     : frame_size_(0),
       core_spill_mask_(0),
       fpu_spill_mask_(0),
@@ -1066,7 +1081,8 @@
       is_leaf_(true),
       needs_suspend_check_entry_(false),
       requires_current_method_(false),
-      code_generation_data_() {
+      code_generation_data_(),
+      unimplemented_intrinsics_(unimplemented_intrinsics) {
   if (GetGraph()->IsCompilingOsr()) {
     // Make OSR methods have all registers spilled, this simplifies the logic of
     // jumping to the compiled code directly.
@@ -1123,7 +1139,7 @@
   for (HBasicBlock* block : graph.GetReversePostOrder()) {
     if (block->IsLoopHeader()) {
       HSuspendCheck* suspend_check = block->GetLoopInformation()->GetSuspendCheck();
-      if (!suspend_check->GetEnvironment()->IsFromInlinedInvoke()) {
+      if (suspend_check != nullptr && !suspend_check->GetEnvironment()->IsFromInlinedInvoke()) {
         loop_headers.push_back(suspend_check);
       }
     }
@@ -1333,53 +1349,43 @@
       continue;
     }
 
-    uint32_t dex_pc = block->GetDexPc();
-    uint32_t num_vregs = graph_->GetNumberOfVRegs();
-    uint32_t native_pc = GetAddressOf(block);
+    // Get the outer dex_pc. We save the full environment list for DCHECK purposes in kIsDebugBuild.
+    std::vector<uint32_t> dex_pc_list_for_verification;
+    if (kIsDebugBuild) {
+      dex_pc_list_for_verification.push_back(block->GetDexPc());
+    }
+    DCHECK(block->GetFirstInstruction()->IsNop());
+    DCHECK(block->GetFirstInstruction()->AsNop()->NeedsEnvironment());
+    HEnvironment* const environment = block->GetFirstInstruction()->GetEnvironment();
+    DCHECK(environment != nullptr);
+    HEnvironment* outer_environment = environment;
+    while (outer_environment->GetParent() != nullptr) {
+      outer_environment = outer_environment->GetParent();
+      if (kIsDebugBuild) {
+        dex_pc_list_for_verification.push_back(outer_environment->GetDexPc());
+      }
+    }
 
-    stack_map_stream->BeginStackMapEntry(dex_pc,
+    if (kIsDebugBuild) {
+      // dex_pc_list_for_verification is set from innnermost to outermost. Let's reverse it
+      // since we are expected to pass from outermost to innermost.
+      std::reverse(dex_pc_list_for_verification.begin(), dex_pc_list_for_verification.end());
+      DCHECK_EQ(dex_pc_list_for_verification.front(), outer_environment->GetDexPc());
+    }
+
+    uint32_t native_pc = GetAddressOf(block);
+    stack_map_stream->BeginStackMapEntry(outer_environment->GetDexPc(),
                                          native_pc,
                                          /* register_mask= */ 0,
                                          /* sp_mask= */ nullptr,
-                                         StackMap::Kind::Catch);
+                                         StackMap::Kind::Catch,
+                                         /* needs_vreg_info= */ true,
+                                         dex_pc_list_for_verification);
 
-    HInstruction* current_phi = block->GetFirstPhi();
-    for (size_t vreg = 0; vreg < num_vregs; ++vreg) {
-      while (current_phi != nullptr && current_phi->AsPhi()->GetRegNumber() < vreg) {
-        HInstruction* next_phi = current_phi->GetNext();
-        DCHECK(next_phi == nullptr ||
-               current_phi->AsPhi()->GetRegNumber() <= next_phi->AsPhi()->GetRegNumber())
-            << "Phis need to be sorted by vreg number to keep this a linear-time loop.";
-        current_phi = next_phi;
-      }
-
-      if (current_phi == nullptr || current_phi->AsPhi()->GetRegNumber() != vreg) {
-        stack_map_stream->AddDexRegisterEntry(DexRegisterLocation::Kind::kNone, 0);
-      } else {
-        Location location = current_phi->GetLocations()->Out();
-        switch (location.GetKind()) {
-          case Location::kStackSlot: {
-            stack_map_stream->AddDexRegisterEntry(
-                DexRegisterLocation::Kind::kInStack, location.GetStackIndex());
-            break;
-          }
-          case Location::kDoubleStackSlot: {
-            stack_map_stream->AddDexRegisterEntry(
-                DexRegisterLocation::Kind::kInStack, location.GetStackIndex());
-            stack_map_stream->AddDexRegisterEntry(
-                DexRegisterLocation::Kind::kInStack, location.GetHighStackIndex(kVRegSize));
-            ++vreg;
-            DCHECK_LT(vreg, num_vregs);
-            break;
-          }
-          default: {
-            // All catch phis must be allocated to a stack slot.
-            LOG(FATAL) << "Unexpected kind " << location.GetKind();
-            UNREACHABLE();
-          }
-        }
-      }
-    }
+    EmitEnvironment(environment,
+                    /* slow_path= */ nullptr,
+                    /* needs_vreg_info= */ true,
+                    /* is_for_catch_handler= */ true);
 
     stack_map_stream->EndStackMapEntry();
   }
@@ -1390,7 +1396,9 @@
   code_generation_data_->AddSlowPath(slow_path);
 }
 
-void CodeGenerator::EmitVRegInfo(HEnvironment* environment, SlowPathCode* slow_path) {
+void CodeGenerator::EmitVRegInfo(HEnvironment* environment,
+                                 SlowPathCode* slow_path,
+                                 bool is_for_catch_handler) {
   StackMapStream* stack_map_stream = GetStackMapStream();
   // Walk over the environment, and record the location of dex registers.
   for (size_t i = 0, environment_size = environment->Size(); i < environment_size; ++i) {
@@ -1445,6 +1453,7 @@
       }
 
       case Location::kRegister : {
+        DCHECK(!is_for_catch_handler);
         int id = location.reg();
         if (slow_path != nullptr && slow_path->IsCoreRegisterSaved(id)) {
           uint32_t offset = slow_path->GetStackOffsetOfCoreRegister(id);
@@ -1466,6 +1475,7 @@
       }
 
       case Location::kFpuRegister : {
+        DCHECK(!is_for_catch_handler);
         int id = location.reg();
         if (slow_path != nullptr && slow_path->IsFpuRegisterSaved(id)) {
           uint32_t offset = slow_path->GetStackOffsetOfFpuRegister(id);
@@ -1487,6 +1497,7 @@
       }
 
       case Location::kFpuRegisterPair : {
+        DCHECK(!is_for_catch_handler);
         int low = location.low();
         int high = location.high();
         if (slow_path != nullptr && slow_path->IsFpuRegisterSaved(low)) {
@@ -1508,6 +1519,7 @@
       }
 
       case Location::kRegisterPair : {
+        DCHECK(!is_for_catch_handler);
         int low = location.low();
         int high = location.high();
         if (slow_path != nullptr && slow_path->IsCoreRegisterSaved(low)) {
@@ -1538,9 +1550,54 @@
   }
 }
 
+void CodeGenerator::EmitVRegInfoOnlyCatchPhis(HEnvironment* environment) {
+  StackMapStream* stack_map_stream = GetStackMapStream();
+  DCHECK(environment->GetHolder()->GetBlock()->IsCatchBlock());
+  DCHECK_EQ(environment->GetHolder()->GetBlock()->GetFirstInstruction(), environment->GetHolder());
+  HInstruction* current_phi = environment->GetHolder()->GetBlock()->GetFirstPhi();
+  for (size_t vreg = 0; vreg < environment->Size(); ++vreg) {
+    while (current_phi != nullptr && current_phi->AsPhi()->GetRegNumber() < vreg) {
+      HInstruction* next_phi = current_phi->GetNext();
+      DCHECK(next_phi == nullptr ||
+             current_phi->AsPhi()->GetRegNumber() <= next_phi->AsPhi()->GetRegNumber())
+          << "Phis need to be sorted by vreg number to keep this a linear-time loop.";
+      current_phi = next_phi;
+    }
+
+    if (current_phi == nullptr || current_phi->AsPhi()->GetRegNumber() != vreg) {
+      stack_map_stream->AddDexRegisterEntry(DexRegisterLocation::Kind::kNone, 0);
+    } else {
+      Location location = current_phi->GetLocations()->Out();
+      switch (location.GetKind()) {
+        case Location::kStackSlot: {
+          stack_map_stream->AddDexRegisterEntry(DexRegisterLocation::Kind::kInStack,
+                                                location.GetStackIndex());
+          break;
+        }
+        case Location::kDoubleStackSlot: {
+          stack_map_stream->AddDexRegisterEntry(DexRegisterLocation::Kind::kInStack,
+                                                location.GetStackIndex());
+          stack_map_stream->AddDexRegisterEntry(DexRegisterLocation::Kind::kInStack,
+                                                location.GetHighStackIndex(kVRegSize));
+          ++vreg;
+          DCHECK_LT(vreg, environment->Size());
+          break;
+        }
+        default: {
+          LOG(FATAL) << "All catch phis must be allocated to a stack slot. Unexpected kind "
+                     << location.GetKind();
+          UNREACHABLE();
+        }
+      }
+    }
+  }
+}
+
 void CodeGenerator::EmitEnvironment(HEnvironment* environment,
                                     SlowPathCode* slow_path,
-                                    bool needs_vreg_info) {
+                                    bool needs_vreg_info,
+                                    bool is_for_catch_handler,
+                                    bool innermost_environment) {
   if (environment == nullptr) return;
 
   StackMapStream* stack_map_stream = GetStackMapStream();
@@ -1548,7 +1605,11 @@
 
   if (emit_inline_info) {
     // We emit the parent environment first.
-    EmitEnvironment(environment->GetParent(), slow_path, needs_vreg_info);
+    EmitEnvironment(environment->GetParent(),
+                    slow_path,
+                    needs_vreg_info,
+                    is_for_catch_handler,
+                    /* innermost_environment= */ false);
     stack_map_stream->BeginInlineInfoEntry(environment->GetMethod(),
                                            environment->GetDexPc(),
                                            needs_vreg_info ? environment->Size() : 0,
@@ -1556,9 +1617,13 @@
                                            this);
   }
 
+  // If a dex register map is not required we just won't emit it.
   if (needs_vreg_info) {
-    // If a dex register map is not required we just won't emit it.
-    EmitVRegInfo(environment, slow_path);
+    if (innermost_environment && is_for_catch_handler) {
+      EmitVRegInfoOnlyCatchPhis(environment);
+    } else {
+      EmitVRegInfo(environment, slow_path, is_for_catch_handler);
+    }
   }
 
   if (emit_inline_info) {
@@ -1671,7 +1736,7 @@
              // When (non-Baker) read barriers are enabled, some instructions
              // use a slow path to emit a read barrier, which does not trigger
              // GC.
-             (kEmitCompilerReadBarrier &&
+             (gUseReadBarrier &&
               !kUseBakerReadBarrier &&
               (instruction->IsInstanceFieldGet() ||
                instruction->IsPredicatedInstanceFieldGet() ||
diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h
index d81a7b5..9872efa 100644
--- a/compiler/optimizing/code_generator.h
+++ b/compiler/optimizing/code_generator.h
@@ -26,6 +26,7 @@
 #include "base/bit_utils.h"
 #include "base/enums.h"
 #include "base/globals.h"
+#include "base/macros.h"
 #include "base/memory_region.h"
 #include "class_root.h"
 #include "dex/string_reference.h"
@@ -33,13 +34,15 @@
 #include "graph_visualizer.h"
 #include "locations.h"
 #include "nodes.h"
+#include "oat_quick_method_header.h"
 #include "optimizing_compiler_stats.h"
 #include "read_barrier_option.h"
 #include "stack.h"
+#include "subtype_check.h"
 #include "utils/assembler.h"
 #include "utils/label.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Binary encoding of 2^32 for type double.
 static int64_t constexpr k2Pow32EncodingForDouble = INT64_C(0x41F0000000000000);
@@ -56,8 +59,18 @@
 // Maximum value for a primitive long.
 static int64_t constexpr kPrimLongMax = INT64_C(0x7fffffffffffffff);
 
-static constexpr ReadBarrierOption kCompilerReadBarrierOption =
-    kEmitCompilerReadBarrier ? kWithReadBarrier : kWithoutReadBarrier;
+static const ReadBarrierOption gCompilerReadBarrierOption =
+    gUseReadBarrier ? kWithReadBarrier : kWithoutReadBarrier;
+
+constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
+constexpr size_t status_byte_offset =
+    mirror::Class::StatusOffset().SizeValue() + (status_lsb_position / kBitsPerByte);
+constexpr uint32_t shifted_visibly_initialized_value =
+    enum_cast<uint32_t>(ClassStatus::kVisiblyInitialized) << (status_lsb_position % kBitsPerByte);
+constexpr uint32_t shifted_initializing_value =
+    enum_cast<uint32_t>(ClassStatus::kInitializing) << (status_lsb_position % kBitsPerByte);
+constexpr uint32_t shifted_initialized_value =
+    enum_cast<uint32_t>(ClassStatus::kInitialized) << (status_lsb_position % kBitsPerByte);
 
 class Assembler;
 class CodeGenerator;
@@ -291,6 +304,12 @@
   // Returns whether we should split long moves in parallel moves.
   virtual bool ShouldSplitLongMoves() const { return false; }
 
+  // Returns true if `invoke` is an implemented intrinsic in this codegen's arch.
+  bool IsImplementedIntrinsic(HInvoke* invoke) const {
+    return invoke->IsIntrinsic() &&
+           !unimplemented_intrinsics_[static_cast<size_t>(invoke->GetIntrinsic())];
+  }
+
   size_t GetNumberOfCoreCalleeSaveRegisters() const {
     return POPCOUNT(core_callee_save_mask_);
   }
@@ -460,7 +479,7 @@
     // If the target class is in the boot image, it's non-moveable and it doesn't matter
     // if we compare it with a from-space or to-space reference, the result is the same.
     // It's OK to traverse a class hierarchy jumping between from-space and to-space.
-    return kEmitCompilerReadBarrier && !instance_of->GetTargetClass()->IsInBootImage();
+    return gUseReadBarrier && !instance_of->GetTargetClass()->IsInBootImage();
   }
 
   static ReadBarrierOption ReadBarrierOptionForInstanceOf(HInstanceOf* instance_of) {
@@ -475,7 +494,7 @@
       case TypeCheckKind::kArrayObjectCheck:
       case TypeCheckKind::kInterfaceCheck: {
         bool needs_read_barrier =
-            kEmitCompilerReadBarrier && !check_cast->GetTargetClass()->IsInBootImage();
+            gUseReadBarrier && !check_cast->GetTargetClass()->IsInBootImage();
         // We do not emit read barriers for HCheckCast, so we can get false negatives
         // and the slow path shall re-check and simply return if the cast is actually OK.
         return !needs_read_barrier;
@@ -678,7 +697,7 @@
         return LocationSummary::kCallOnMainOnly;
       case HLoadString::LoadKind::kJitTableAddress:
         DCHECK(!load->NeedsEnvironment());
-        return kEmitCompilerReadBarrier
+        return gUseReadBarrier
             ? LocationSummary::kCallOnSlowPath
             : LocationSummary::kNoCall;
         break;
@@ -736,7 +755,8 @@
                 uint32_t core_callee_save_mask,
                 uint32_t fpu_callee_save_mask,
                 const CompilerOptions& compiler_options,
-                OptimizingCompilerStats* stats);
+                OptimizingCompilerStats* stats,
+                const art::ArrayRef<const bool>& unimplemented_intrinsics);
 
   virtual HGraphVisitor* GetLocationBuilder() = 0;
   virtual HGraphVisitor* GetInstructionVisitor() = 0;
@@ -836,8 +856,11 @@
   void BlockIfInRegister(Location location, bool is_out = false) const;
   void EmitEnvironment(HEnvironment* environment,
                        SlowPathCode* slow_path,
-                       bool needs_vreg_info = true);
-  void EmitVRegInfo(HEnvironment* environment, SlowPathCode* slow_path);
+                       bool needs_vreg_info = true,
+                       bool is_for_catch_handler = false,
+                       bool innermost_environment = true);
+  void EmitVRegInfo(HEnvironment* environment, SlowPathCode* slow_path, bool is_for_catch_handler);
+  void EmitVRegInfoOnlyCatchPhis(HEnvironment* environment);
 
   static void PrepareCriticalNativeArgumentMoves(
       HInvokeStaticOrDirect* invoke,
@@ -877,6 +900,9 @@
   // CodeGenerator::Compile() and remains alive until the CodeGenerator is destroyed.
   std::unique_ptr<CodeGenerationData> code_generation_data_;
 
+  // Which intrinsics we don't have handcrafted code for.
+  art::ArrayRef<const bool> unimplemented_intrinsics_;
+
   friend class OptimizingCFITest;
   ART_FRIEND_TEST(CodegenTest, ARM64FrameSizeSIMD);
   ART_FRIEND_TEST(CodegenTest, ARM64FrameSizeNoSIMD);
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 2a0b481..41db9a2 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -27,7 +27,6 @@
 #include "class_root-inl.h"
 #include "class_table.h"
 #include "code_generator_utils.h"
-#include "compiled_method.h"
 #include "entrypoints/quick/quick_entrypoints.h"
 #include "entrypoints/quick/quick_entrypoints_enum.h"
 #include "gc/accounting/card_table.h"
@@ -44,6 +43,7 @@
 #include "mirror/var_handle.h"
 #include "offsets.h"
 #include "optimizing/common_arm64.h"
+#include "optimizing/nodes.h"
 #include "thread.h"
 #include "utils/arm64/assembler_arm64.h"
 #include "utils/assembler.h"
@@ -58,7 +58,7 @@
 #error "ARM64 Codegen VIXL macro-assembler macro already defined."
 #endif
 
-namespace art {
+namespace art HIDDEN {
 
 template<class MirrorType>
 class GcRoot;
@@ -77,7 +77,6 @@
 using helpers::InputOperandAt;
 using helpers::InputRegisterAt;
 using helpers::Int64FromLocation;
-using helpers::IsConstantZeroBitPattern;
 using helpers::LocationFrom;
 using helpers::OperandFromMemOperand;
 using helpers::OutputCPURegister;
@@ -583,7 +582,7 @@
         obj_(obj),
         offset_(offset),
         index_(index) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     // If `obj` is equal to `out` or `ref`, it means the initial object
     // has been overwritten by (or after) the heap object reference load
     // to be instrumented, e.g.:
@@ -762,7 +761,7 @@
  public:
   ReadBarrierForRootSlowPathARM64(HInstruction* instruction, Location out, Location root)
       : SlowPathCodeARM64(instruction), out_(out), root_(root) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
   }
 
   void EmitNativeCode(CodeGenerator* codegen) override {
@@ -825,6 +824,9 @@
     CodeGeneratorARM64* arm64_codegen = down_cast<CodeGeneratorARM64*>(codegen);
     __ Bind(GetEntryLabel());
     SaveLiveRegisters(codegen, locations);
+    if (instruction_->IsMethodExitHook()) {
+      __ Mov(vixl::aarch64::x4, arm64_codegen->GetFrameSize());
+    }
     arm64_codegen->InvokeRuntime(entry_point, instruction_, instruction_->GetDexPc(), this);
     RestoreLiveRegisters(codegen, locations);
     __ B(GetExitLabel());
@@ -933,6 +935,33 @@
   return Location::RegisterLocation(x15.GetCode());
 }
 
+namespace detail {
+// Mark which intrinsics we don't have handcrafted code for.
+template <Intrinsics T>
+struct IsUnimplemented {
+  bool is_unimplemented = false;
+};
+
+#define TRUE_OVERRIDE(Name)                     \
+  template <>                                   \
+  struct IsUnimplemented<Intrinsics::k##Name> { \
+    bool is_unimplemented = true;               \
+  };
+UNIMPLEMENTED_INTRINSIC_LIST_ARM64(TRUE_OVERRIDE)
+#undef TRUE_OVERRIDE
+
+#include "intrinsics_list.h"
+static constexpr bool kIsIntrinsicUnimplemented[] = {
+  false,  // kNone
+#define IS_UNIMPLEMENTED(Intrinsic, ...) \
+  IsUnimplemented<Intrinsics::k##Intrinsic>().is_unimplemented,
+  INTRINSICS_LIST(IS_UNIMPLEMENTED)
+#undef IS_UNIMPLEMENTED
+};
+#undef INTRINSICS_LIST
+
+}  // namespace detail
+
 CodeGeneratorARM64::CodeGeneratorARM64(HGraph* graph,
                                        const CompilerOptions& compiler_options,
                                        OptimizingCompilerStats* stats)
@@ -943,7 +972,8 @@
                     callee_saved_core_registers.GetList(),
                     callee_saved_fp_registers.GetList(),
                     compiler_options,
-                    stats),
+                    stats,
+                    ArrayRef<const bool>(detail::kIsIntrinsicUnimplemented)),
       block_labels_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
       jump_tables_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
       location_builder_neon_(graph, this),
@@ -1169,9 +1199,21 @@
       new (codegen_->GetScopedAllocator()) MethodEntryExitHooksSlowPathARM64(instruction);
   codegen_->AddSlowPath(slow_path);
 
+  if (instruction->IsMethodExitHook()) {
+    // Check if we are required to check if the caller needs a deoptimization. Strictly speaking it
+    // would be sufficient to check if CheckCallerForDeopt bit is set. Though it is faster to check
+    // if it is just non-zero. kCHA bit isn't used in debuggable runtimes as cha optimization is
+    // disabled in debuggable runtime. The other bit is used when this method itself requires a
+    // deoptimization due to redefinition. So it is safe to just check for non-zero value here.
+    __ Ldr(value, MemOperand(sp, codegen_->GetStackOffsetOfShouldDeoptimizeFlag()));
+    __ Cbnz(value, slow_path->GetEntryLabel());
+  }
+
   uint64_t address = reinterpret_cast64<uint64_t>(Runtime::Current()->GetInstrumentation());
-  int offset = instrumentation::Instrumentation::NeedsEntryExitHooksOffset().Int32Value();
-  __ Mov(temp, address + offset);
+  MemberOffset  offset = instruction->IsMethodExitHook() ?
+      instrumentation::Instrumentation::HaveMethodExitListenersOffset() :
+      instrumentation::Instrumentation::HaveMethodEntryListenersOffset();
+  __ Mov(temp, address + offset.Int32Value());
   __ Ldrb(value, MemOperand(temp, 0));
   __ Cbnz(value, slow_path->GetEntryLabel());
   __ Bind(slow_path->GetExitLabel());
@@ -1233,6 +1275,54 @@
 
 void CodeGeneratorARM64::GenerateFrameEntry() {
   MacroAssembler* masm = GetVIXLAssembler();
+
+  // Check if we need to generate the clinit check. We will jump to the
+  // resolution stub if the class is not initialized and the executing thread is
+  // not the thread initializing it.
+  // We do this before constructing the frame to get the correct stack trace if
+  // an exception is thrown.
+  if (GetCompilerOptions().ShouldCompileWithClinitCheck(GetGraph()->GetArtMethod())) {
+    UseScratchRegisterScope temps(masm);
+    vixl::aarch64::Label resolution;
+    vixl::aarch64::Label memory_barrier;
+
+    Register temp1 = temps.AcquireW();
+    Register temp2 = temps.AcquireW();
+
+    // Check if we're visibly initialized.
+
+    // We don't emit a read barrier here to save on code size. We rely on the
+    // resolution trampoline to do a suspend check before re-entering this code.
+    __ Ldr(temp1, MemOperand(kArtMethodRegister, ArtMethod::DeclaringClassOffset().Int32Value()));
+    __ Ldrb(temp2, HeapOperand(temp1, status_byte_offset));
+    __ Cmp(temp2, shifted_visibly_initialized_value);
+    __ B(hs, &frame_entry_label_);
+
+    // Check if we're initialized and jump to code that does a memory barrier if
+    // so.
+    __ Cmp(temp2, shifted_initialized_value);
+    __ B(hs, &memory_barrier);
+
+    // Check if we're initializing and the thread initializing is the one
+    // executing the code.
+    __ Cmp(temp2, shifted_initializing_value);
+    __ B(lo, &resolution);
+
+    __ Ldr(temp1, HeapOperand(temp1, mirror::Class::ClinitThreadIdOffset().Int32Value()));
+    __ Ldr(temp2, MemOperand(tr, Thread::TidOffset<kArm64PointerSize>().Int32Value()));
+    __ Cmp(temp1, temp2);
+    __ B(eq, &frame_entry_label_);
+    __ Bind(&resolution);
+
+    // Jump to the resolution stub.
+    ThreadOffset64 entrypoint_offset =
+        GetThreadOffset<kArm64PointerSize>(kQuickQuickResolutionTrampoline);
+    __ Ldr(temp1.X(), MemOperand(tr, entrypoint_offset.Int32Value()));
+    __ Br(temp1.X());
+
+    __ Bind(&memory_barrier);
+    GenerateMemoryBarrier(MemBarrierKind::kAnyAny);
+  }
   __ Bind(&frame_entry_label_);
 
   bool do_overflow_check =
@@ -1364,12 +1454,12 @@
   }
 }
 
-void CodeGeneratorARM64::MarkGCCard(Register object, Register value, bool value_can_be_null) {
+void CodeGeneratorARM64::MarkGCCard(Register object, Register value, bool emit_null_check) {
   UseScratchRegisterScope temps(GetVIXLAssembler());
   Register card = temps.AcquireX();
   Register temp = temps.AcquireW();   // Index within the CardTable - 32bit.
   vixl::aarch64::Label done;
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ Cbz(value, &done);
   }
   // Load the address of the card table into `card`.
@@ -1391,7 +1481,7 @@
   // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
   // (no need to explicitly load `kCardDirty` as an immediate value).
   __ Strb(card, MemOperand(card, temp.X()));
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ Bind(&done);
   }
 }
@@ -1904,11 +1994,6 @@
                                                                      Register class_reg) {
   UseScratchRegisterScope temps(GetVIXLAssembler());
   Register temp = temps.AcquireW();
-  constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
-  const size_t status_byte_offset =
-      mirror::Class::StatusOffset().SizeValue() + (status_lsb_position / kBitsPerByte);
-  constexpr uint32_t shifted_visibly_initialized_value =
-      enum_cast<uint32_t>(ClassStatus::kVisiblyInitialized) << (status_lsb_position % kBitsPerByte);
 
   // CMP (immediate) is limited to imm12 or imm12<<12, so we would need to materialize
   // the constant 0xf0000000 for comparison with the full 32-bit field. To reduce the code
@@ -1974,6 +2059,13 @@
 
 void InstructionCodeGeneratorARM64::GenerateSuspendCheck(HSuspendCheck* instruction,
                                                          HBasicBlock* successor) {
+  if (instruction->IsNoOp()) {
+    if (successor != nullptr) {
+      __ B(codegen_->GetLabelOf(successor));
+    }
+    return;
+  }
+
   if (codegen_->CanUseImplicitSuspendCheck()) {
     __ Ldr(kImplicitSuspendCheckRegister, MemOperand(kImplicitSuspendCheckRegister));
     codegen_->RecordPcInfo(instruction, instruction->GetDexPc());
@@ -2051,7 +2143,7 @@
   bool is_predicated = instruction->IsPredicatedInstanceFieldGet();
 
   bool object_field_get_with_read_barrier =
-      kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+      gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(instruction,
                                                        object_field_get_with_read_barrier
@@ -2107,7 +2199,7 @@
   MemOperand field =
       HeapOperand(InputRegisterAt(instruction, receiver_input), field_info.GetFieldOffset());
 
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier &&
+  if (gUseReadBarrier && kUseBakerReadBarrier &&
       load_type == DataType::Type::kReference) {
     // Object FieldGet with Baker's read barrier case.
     // /* HeapReference<Object> */ out = *(base + offset)
@@ -2154,9 +2246,10 @@
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(instruction, LocationSummary::kNoCall);
   locations->SetInAt(0, Location::RequiresRegister());
-  if (IsConstantZeroBitPattern(instruction->InputAt(1))) {
-    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
-  } else if (DataType::IsFloatingPointType(instruction->InputAt(1)->GetType())) {
+  HInstruction* value = instruction->InputAt(1);
+  if (IsZeroBitPattern(value)) {
+    locations->SetInAt(1, Location::ConstantLocation(value));
+  } else if (DataType::IsFloatingPointType(value->GetType())) {
     locations->SetInAt(1, Location::RequiresFpuRegister());
   } else {
     locations->SetInAt(1, Location::RequiresRegister());
@@ -2165,7 +2258,8 @@
 
 void InstructionCodeGeneratorARM64::HandleFieldSet(HInstruction* instruction,
                                                    const FieldInfo& field_info,
-                                                   bool value_can_be_null) {
+                                                   bool value_can_be_null,
+                                                   WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
   bool is_predicated =
       instruction->IsInstanceFieldSet() && instruction->AsInstanceFieldSet()->GetIsPredicatedSet();
@@ -2205,8 +2299,12 @@
     }
   }
 
-  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) {
-    codegen_->MarkGCCard(obj, Register(value), value_can_be_null);
+  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1)) &&
+      write_barrier_kind != WriteBarrierKind::kDontEmit) {
+    codegen_->MarkGCCard(
+        obj,
+        Register(value),
+        value_can_be_null && write_barrier_kind == WriteBarrierKind::kEmitWithNullCheck);
   }
 
   if (is_predicated) {
@@ -2382,7 +2480,7 @@
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(instruction, LocationSummary::kNoCall);
   if (instruction->GetInstrKind() == HInstruction::kNeg) {
-    locations->SetInAt(0, Location::ConstantLocation(instruction->InputAt(0)->AsConstant()));
+    locations->SetInAt(0, Location::ConstantLocation(instruction->InputAt(0)));
   } else {
     locations->SetInAt(0, Location::RequiresRegister());
   }
@@ -2475,7 +2573,7 @@
   // data offset constant generation out of the loop and reduce the critical path length in the
   // loop.
   locations->SetInAt(1, shift->GetValue() == 0
-                        ? Location::ConstantLocation(instruction->GetOffset()->AsIntConstant())
+                        ? Location::ConstantLocation(instruction->GetOffset())
                         : Location::RequiresRegister());
   locations->SetInAt(2, Location::ConstantLocation(shift));
   locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
@@ -2549,7 +2647,7 @@
 
 void LocationsBuilderARM64::VisitArrayGet(HArrayGet* instruction) {
   bool object_array_get_with_read_barrier =
-      kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+      gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(instruction,
                                                        object_array_get_with_read_barrier
@@ -2605,10 +2703,10 @@
   // does not support the HIntermediateAddress instruction.
   DCHECK(!((type == DataType::Type::kReference) &&
            instruction->GetArray()->IsIntermediateAddress() &&
-           kEmitCompilerReadBarrier &&
+           gUseReadBarrier &&
            !kUseBakerReadBarrier));
 
-  if (type == DataType::Type::kReference && kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (type == DataType::Type::kReference && gUseReadBarrier && kUseBakerReadBarrier) {
     // Object ArrayGet with Baker's read barrier case.
     // Note that a potential implicit null check is handled in the
     // CodeGeneratorARM64::GenerateArrayLoadWithBakerReadBarrier call.
@@ -2750,9 +2848,10 @@
       instruction,
       needs_type_check ? LocationSummary::kCallOnSlowPath : LocationSummary::kNoCall);
   locations->SetInAt(0, Location::RequiresRegister());
-  locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
-  if (IsConstantZeroBitPattern(instruction->InputAt(2))) {
-    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)->AsConstant()));
+  locations->SetInAt(1, Location::RegisterOrConstant(instruction->GetIndex()));
+  HInstruction* value = instruction->GetValue();
+  if (IsZeroBitPattern(value)) {
+    locations->SetInAt(2, Location::ConstantLocation(value));
   } else if (DataType::IsFloatingPointType(value_type)) {
     locations->SetInAt(2, Location::RequiresFpuRegister());
   } else {
@@ -2871,7 +2970,11 @@
       }
     }
 
-    codegen_->MarkGCCard(array, value.W(), /* value_can_be_null= */ false);
+    if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+      DCHECK_EQ(instruction->GetWriteBarrierKind(), WriteBarrierKind::kEmitNoNullCheck)
+          << " Already null checked so we shouldn't do it again.";
+      codegen_->MarkGCCard(array, value.W(), /* emit_null_check= */ false);
+    }
 
     if (can_value_be_null) {
       DCHECK(do_store.IsLinked());
@@ -2929,10 +3032,10 @@
   HInstruction* length = instruction->InputAt(1);
   bool both_const = index->IsConstant() && length->IsConstant();
   locations->SetInAt(0, both_const
-      ? Location::ConstantLocation(index->AsConstant())
+      ? Location::ConstantLocation(index)
       : ARM64EncodableConstantOrRegister(index, instruction));
   locations->SetInAt(1, both_const
-      ? Location::ConstantLocation(length->AsConstant())
+      ? Location::ConstantLocation(length)
       : ARM64EncodableConstantOrRegister(length, instruction));
 }
 
@@ -3030,6 +3133,7 @@
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(compare, LocationSummary::kNoCall);
   DataType::Type in_type = compare->InputAt(0)->GetType();
+  HInstruction* rhs = compare->InputAt(1);
   switch (in_type) {
     case DataType::Type::kBool:
     case DataType::Type::kUint8:
@@ -3039,7 +3143,7 @@
     case DataType::Type::kInt32:
     case DataType::Type::kInt64: {
       locations->SetInAt(0, Location::RequiresRegister());
-      locations->SetInAt(1, ARM64EncodableConstantOrRegister(compare->InputAt(1), compare));
+      locations->SetInAt(1, ARM64EncodableConstantOrRegister(rhs, compare));
       locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
       break;
     }
@@ -3047,8 +3151,8 @@
     case DataType::Type::kFloat64: {
       locations->SetInAt(0, Location::RequiresFpuRegister());
       locations->SetInAt(1,
-                         IsFloatingPointZeroConstant(compare->InputAt(1))
-                             ? Location::ConstantLocation(compare->InputAt(1)->AsConstant())
+                         IsFloatingPointZeroConstant(rhs)
+                             ? Location::ConstantLocation(rhs)
                              : Location::RequiresFpuRegister());
       locations->SetOut(Location::RequiresRegister());
       break;
@@ -3096,16 +3200,17 @@
 void LocationsBuilderARM64::HandleCondition(HCondition* instruction) {
   LocationSummary* locations = new (GetGraph()->GetAllocator()) LocationSummary(instruction);
 
+  HInstruction* rhs = instruction->InputAt(1);
   if (DataType::IsFloatingPointType(instruction->InputAt(0)->GetType())) {
     locations->SetInAt(0, Location::RequiresFpuRegister());
     locations->SetInAt(1,
-                       IsFloatingPointZeroConstant(instruction->InputAt(1))
-                           ? Location::ConstantLocation(instruction->InputAt(1)->AsConstant())
+                       IsFloatingPointZeroConstant(rhs)
+                           ? Location::ConstantLocation(rhs)
                            : Location::RequiresFpuRegister());
   } else {
     // Integer cases.
     locations->SetInAt(0, Location::RequiresRegister());
-    locations->SetInAt(1, ARM64EncodableConstantOrRegister(instruction->InputAt(1), instruction));
+    locations->SetInAt(1, ARM64EncodableConstantOrRegister(rhs, instruction));
   }
 
   if (!instruction->IsEmittedAtUseSite()) {
@@ -3845,12 +3950,12 @@
   }
 }
 
-void LocationsBuilderARM64::VisitNativeDebugInfo(HNativeDebugInfo* info) {
-  new (GetGraph()->GetAllocator()) LocationSummary(info);
+void LocationsBuilderARM64::VisitNop(HNop* nop) {
+  new (GetGraph()->GetAllocator()) LocationSummary(nop);
 }
 
-void InstructionCodeGeneratorARM64::VisitNativeDebugInfo(HNativeDebugInfo*) {
-  // MaybeRecordNativeDebugInfo is already called implicitly in CodeGenerator::Compile.
+void InstructionCodeGeneratorARM64::VisitNop(HNop*) {
+  // The environment recording already happened in CodeGenerator::Compile.
 }
 
 void CodeGeneratorARM64::IncreaseFrame(size_t adjustment) {
@@ -3893,12 +3998,15 @@
 }
 
 void InstructionCodeGeneratorARM64::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 // Temp is used for read barrier.
 static size_t NumberOfInstanceOfTemps(TypeCheckKind type_check_kind) {
-  if (kEmitCompilerReadBarrier &&
+  if (gUseReadBarrier &&
       (kUseBakerReadBarrier ||
           type_check_kind == TypeCheckKind::kAbstractClassCheck ||
           type_check_kind == TypeCheckKind::kClassHierarchyCheck ||
@@ -3948,9 +4056,9 @@
   }
   locations->SetInAt(0, Location::RequiresRegister());
   if (type_check_kind == TypeCheckKind::kBitstringCheck) {
-    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
-    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)->AsConstant()));
-    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)->AsConstant()));
+    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
+    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)));
+    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)));
   } else {
     locations->SetInAt(1, Location::RequiresRegister());
   }
@@ -4194,9 +4302,9 @@
       new (GetGraph()->GetAllocator()) LocationSummary(instruction, call_kind);
   locations->SetInAt(0, Location::RequiresRegister());
   if (type_check_kind == TypeCheckKind::kBitstringCheck) {
-    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
-    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)->AsConstant()));
-    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)->AsConstant()));
+    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
+    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)));
+    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)));
   } else {
     locations->SetInAt(1, Location::RequiresRegister());
   }
@@ -5313,7 +5421,7 @@
             load_kind == HLoadClass::LoadKind::kBssEntryPublic ||
                 load_kind == HLoadClass::LoadKind::kBssEntryPackage);
 
-  const bool requires_read_barrier = kEmitCompilerReadBarrier && !cls->IsInBootImage();
+  const bool requires_read_barrier = gUseReadBarrier && !cls->IsInBootImage();
   LocationSummary::CallKind call_kind = (cls->NeedsEnvironment() || requires_read_barrier)
       ? LocationSummary::kCallOnSlowPath
       : LocationSummary::kNoCall;
@@ -5327,7 +5435,7 @@
   }
   locations->SetOut(Location::RequiresRegister());
   if (cls->GetLoadKind() == HLoadClass::LoadKind::kBssEntry) {
-    if (!kUseReadBarrier || kUseBakerReadBarrier) {
+    if (!gUseReadBarrier || kUseBakerReadBarrier) {
       // Rely on the type resolution or initialization and marking to save everything we need.
       locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
     } else {
@@ -5354,7 +5462,7 @@
 
   const ReadBarrierOption read_barrier_option = cls->IsInBootImage()
       ? kWithoutReadBarrier
-      : kCompilerReadBarrierOption;
+      : gCompilerReadBarrierOption;
   bool generate_null_check = false;
   switch (load_kind) {
     case HLoadClass::LoadKind::kReferrersClass: {
@@ -5523,7 +5631,7 @@
   } else {
     locations->SetOut(Location::RequiresRegister());
     if (load->GetLoadKind() == HLoadString::LoadKind::kBssEntry) {
-      if (!kUseReadBarrier || kUseBakerReadBarrier) {
+      if (!gUseReadBarrier || kUseBakerReadBarrier) {
         // Rely on the pResolveString and marking to save everything we need.
         locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
       } else {
@@ -5577,7 +5685,7 @@
                                         temp,
                                         /* offset placeholder */ 0u,
                                         ldr_label,
-                                        kCompilerReadBarrierOption);
+                                        gCompilerReadBarrierOption);
       SlowPathCodeARM64* slow_path =
           new (codegen_->GetScopedAllocator()) LoadStringSlowPathARM64(load);
       codegen_->AddSlowPath(slow_path);
@@ -5601,7 +5709,7 @@
                                         out.X(),
                                         /* offset= */ 0,
                                         /* fixup_label= */ nullptr,
-                                        kCompilerReadBarrierOption);
+                                        gCompilerReadBarrierOption);
       return;
     }
     default:
@@ -6156,7 +6264,10 @@
 }
 
 void InstructionCodeGeneratorARM64::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderARM64::VisitStringBuilderAppend(HStringBuilderAppend* instruction) {
@@ -6462,7 +6573,7 @@
   DataType::Type type = DataType::Type::kReference;
   Register out_reg = RegisterFrom(out, type);
   if (read_barrier_option == kWithReadBarrier) {
-    CHECK(kEmitCompilerReadBarrier);
+    CHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       // Load with fast path based Baker's read barrier.
       // /* HeapReference<Object> */ out = *(out + offset)
@@ -6503,7 +6614,7 @@
   Register out_reg = RegisterFrom(out, type);
   Register obj_reg = RegisterFrom(obj, type);
   if (read_barrier_option == kWithReadBarrier) {
-    CHECK(kEmitCompilerReadBarrier);
+    CHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       // Load with fast path based Baker's read barrier.
       // /* HeapReference<Object> */ out = *(obj + offset)
@@ -6538,7 +6649,7 @@
   DCHECK(fixup_label == nullptr || offset == 0u);
   Register root_reg = RegisterFrom(root, DataType::Type::kReference);
   if (read_barrier_option == kWithReadBarrier) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       // Fast path implementation of art::ReadBarrier::BarrierForRoot when
       // Baker's read barrier are used.
@@ -6604,7 +6715,7 @@
 void CodeGeneratorARM64::GenerateIntrinsicCasMoveWithBakerReadBarrier(
     vixl::aarch64::Register marked_old_value,
     vixl::aarch64::Register old_value) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   // Similar to the Baker RB path in GenerateGcRootFieldLoad(), with a MOV instead of LDR.
@@ -6626,7 +6737,7 @@
                                                                const vixl::aarch64::MemOperand& src,
                                                                bool needs_null_check,
                                                                bool use_load_acquire) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
@@ -6722,7 +6833,7 @@
                                                                uint32_t data_offset,
                                                                Location index,
                                                                bool needs_null_check) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   static_assert(
@@ -6800,7 +6911,7 @@
 
 void CodeGeneratorARM64::MaybeGenerateMarkingRegisterCheck(int code, Location temp_loc) {
   // The following condition is a compile-time one, so it does not have a run-time cost.
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier && kIsDebugBuild) {
+  if (kIsDebugBuild && gUseReadBarrier && kUseBakerReadBarrier) {
     // The following condition is a run-time one; it is executed after the
     // previous compile-time test, to avoid penalizing non-debug builds.
     if (GetCompilerOptions().EmitRunTimeChecksInDebugMode()) {
@@ -6829,7 +6940,7 @@
                                                  Location obj,
                                                  uint32_t offset,
                                                  Location index) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
 
   // Insert a slow path based read barrier *after* the reference load.
   //
@@ -6854,7 +6965,7 @@
                                                       Location obj,
                                                       uint32_t offset,
                                                       Location index) {
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     // Baker's read barriers shall be handled by the fast path
     // (CodeGeneratorARM64::GenerateReferenceLoadWithBakerReadBarrier).
     DCHECK(!kUseBakerReadBarrier);
@@ -6869,7 +6980,7 @@
 void CodeGeneratorARM64::GenerateReadBarrierForRootSlow(HInstruction* instruction,
                                                         Location out,
                                                         Location root) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
 
   // Insert a slow path based read barrier *after* the GC root load.
   //
@@ -7003,6 +7114,7 @@
                                      vixl::aarch64::MemOperand& lock_word,
                                      vixl::aarch64::Label* slow_path,
                                      vixl::aarch64::Label* throw_npe = nullptr) {
+  vixl::aarch64::Label throw_npe_cont;
   // Load the lock word containing the rb_state.
   __ Ldr(ip0.W(), lock_word);
   // Given the numeric representation, it's enough to check the low bit of the rb_state.
@@ -7014,7 +7126,7 @@
       "Field and array LDR offsets must be the same to reuse the same code.");
   // To throw NPE, we return to the fast path; the artificial dependence below does not matter.
   if (throw_npe != nullptr) {
-    __ Bind(throw_npe);
+    __ Bind(&throw_npe_cont);
   }
   // Adjust the return address back to the LDR (1 instruction; 2 for heap poisoning).
   static_assert(BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET == (kPoisonHeapReferences ? -8 : -4),
@@ -7026,6 +7138,12 @@
   // a memory barrier (which would be more expensive).
   __ Add(base_reg, base_reg, Operand(ip0, LSR, 32));
   __ Br(lr);          // And return back to the function.
+  if (throw_npe != nullptr) {
+    // Clear IP0 before returning to the fast path.
+    __ Bind(throw_npe);
+    __ Mov(ip0.X(), xzr);
+    __ B(&throw_npe_cont);
+  }
   // Note: The fake dependency is unnecessary for the slow path.
 }
 
diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h
index f4d652c..6190364 100644
--- a/compiler/optimizing/code_generator_arm64.h
+++ b/compiler/optimizing/code_generator_arm64.h
@@ -18,6 +18,7 @@
 #define ART_COMPILER_OPTIMIZING_CODE_GENERATOR_ARM64_H_
 
 #include "base/bit_field.h"
+#include "base/macros.h"
 #include "class_root.h"
 #include "code_generator.h"
 #include "common_arm64.h"
@@ -36,7 +37,7 @@
 #include "aarch64/macro-assembler-aarch64.h"
 #pragma GCC diagnostic pop
 
-namespace art {
+namespace art HIDDEN {
 
 namespace linker {
 class Arm64RelativePatcherTest;
@@ -92,7 +93,10 @@
     vixl::aarch64::CPURegList(
         tr,
         // Reserve X20 as Marking Register when emitting Baker read barriers.
-        ((kEmitCompilerReadBarrier && kUseBakerReadBarrier) ? mr : vixl::aarch64::NoCPUReg),
+        // TODO: We don't need to reserve marking-register for userfaultfd GC. But
+        // that would require some work in the assembler code as the right GC is
+        // chosen at load-time and not compile time.
+        (kReserveMarkingRegister ? mr : vixl::aarch64::NoCPUReg),
         kImplicitSuspendCheckRegister,
         vixl::aarch64::lr);
 
@@ -111,9 +115,7 @@
 const vixl::aarch64::CPURegList callee_saved_core_registers(
     vixl::aarch64::CPURegister::kRegister,
     vixl::aarch64::kXRegSize,
-    ((kEmitCompilerReadBarrier && kUseBakerReadBarrier)
-         ? vixl::aarch64::x21.GetCode()
-         : vixl::aarch64::x20.GetCode()),
+    (kReserveMarkingRegister ? vixl::aarch64::x21.GetCode() : vixl::aarch64::x20.GetCode()),
      vixl::aarch64::x30.GetCode());
 const vixl::aarch64::CPURegList callee_saved_fp_registers(vixl::aarch64::CPURegister::kVRegister,
                                                           vixl::aarch64::kDRegSize,
@@ -121,6 +123,41 @@
                                                           vixl::aarch64::d15.GetCode());
 Location ARM64ReturnLocation(DataType::Type return_type);
 
+#define UNIMPLEMENTED_INTRINSIC_LIST_ARM64(V) \
+  V(StringStringIndexOf)                      \
+  V(StringStringIndexOfAfter)                 \
+  V(StringBufferAppend)                       \
+  V(StringBufferLength)                       \
+  V(StringBufferToString)                     \
+  V(StringBuilderAppendObject)                \
+  V(StringBuilderAppendString)                \
+  V(StringBuilderAppendCharSequence)          \
+  V(StringBuilderAppendCharArray)             \
+  V(StringBuilderAppendBoolean)               \
+  V(StringBuilderAppendChar)                  \
+  V(StringBuilderAppendInt)                   \
+  V(StringBuilderAppendLong)                  \
+  V(StringBuilderAppendFloat)                 \
+  V(StringBuilderAppendDouble)                \
+  V(StringBuilderLength)                      \
+  V(StringBuilderToString)                    \
+  V(SystemArrayCopyByte)                      \
+  V(SystemArrayCopyInt)                       \
+  /* 1.8 */                                   \
+  V(UnsafeGetAndAddInt)                       \
+  V(UnsafeGetAndAddLong)                      \
+  V(UnsafeGetAndSetInt)                       \
+  V(UnsafeGetAndSetLong)                      \
+  V(UnsafeGetAndSetObject)                    \
+  V(MethodHandleInvokeExact)                  \
+  V(MethodHandleInvoke)                       \
+  /* OpenJDK 11 */                            \
+  V(JdkUnsafeGetAndAddInt)                    \
+  V(JdkUnsafeGetAndAddLong)                   \
+  V(JdkUnsafeGetAndSetInt)                    \
+  V(JdkUnsafeGetAndSetLong)                   \
+  V(JdkUnsafeGetAndSetObject)
+
 class SlowPathCodeARM64 : public SlowPathCode {
  public:
   explicit SlowPathCodeARM64(HInstruction* instruction)
@@ -327,7 +364,8 @@
 
   void HandleFieldSet(HInstruction* instruction,
                       const FieldInfo& field_info,
-                      bool value_can_be_null);
+                      bool value_can_be_null,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
   void HandleCondition(HCondition* instruction);
 
@@ -615,7 +653,7 @@
   // Emit a write barrier.
   void MarkGCCard(vixl::aarch64::Register object,
                   vixl::aarch64::Register value,
-                  bool value_can_be_null);
+                  bool emit_null_check);
 
   void GenerateMemoryBarrier(MemBarrierKind kind);
 
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 09fa598..d69e770 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -26,7 +26,6 @@
 #include "class_table.h"
 #include "code_generator_utils.h"
 #include "common_arm.h"
-#include "compiled_method.h"
 #include "entrypoints/quick/quick_entrypoints.h"
 #include "gc/accounting/card_table.h"
 #include "gc/space/image_space.h"
@@ -46,7 +45,7 @@
 #include "utils/assembler.h"
 #include "utils/stack_checks.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 namespace vixl32 = vixl::aarch32;
@@ -744,7 +743,7 @@
         obj_(obj),
         offset_(offset),
         index_(index) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     // If `obj` is equal to `out` or `ref`, it means the initial object
     // has been overwritten by (or after) the heap object reference load
     // to be instrumented, e.g.:
@@ -922,7 +921,7 @@
  public:
   ReadBarrierForRootSlowPathARMVIXL(HInstruction* instruction, Location out, Location root)
       : SlowPathCodeARMVIXL(instruction), out_(out), root_(root) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
   }
 
   void EmitNativeCode(CodeGenerator* codegen) override {
@@ -974,6 +973,10 @@
         (instruction_->IsMethodEntryHook()) ? kQuickMethodEntryHook : kQuickMethodExitHook;
     __ Bind(GetEntryLabel());
     SaveLiveRegisters(codegen, locations);
+    if (instruction_->IsMethodExitHook()) {
+      // Load frame size to pass to the exit hooks
+      __ Mov(vixl::aarch32::Register(R2), arm_codegen->GetFrameSize());
+    }
     arm_codegen->InvokeRuntime(entry_point, instruction_, instruction_->GetDexPc(), this);
     RestoreLiveRegisters(codegen, locations);
     __ B(GetExitLabel());
@@ -1845,7 +1848,7 @@
   DCHECK(!DataType::IsFloatingPointType(constant->GetType()));
 
   if (constant->IsConstant() && CanEncodeConstantAs8BitImmediate(constant->AsConstant())) {
-    return Location::ConstantLocation(constant->AsConstant());
+    return Location::ConstantLocation(constant);
   }
 
   return Location::RequiresRegister();
@@ -1904,6 +1907,33 @@
   return final_label;
 }
 
+namespace detail {
+// Mark which intrinsics we don't have handcrafted code for.
+template <Intrinsics T>
+struct IsUnimplemented {
+  bool is_unimplemented = false;
+};
+
+#define TRUE_OVERRIDE(Name)                     \
+  template <>                                   \
+  struct IsUnimplemented<Intrinsics::k##Name> { \
+    bool is_unimplemented = true;               \
+  };
+UNIMPLEMENTED_INTRINSIC_LIST_ARM(TRUE_OVERRIDE)
+#undef TRUE_OVERRIDE
+
+#include "intrinsics_list.h"
+static constexpr bool kIsIntrinsicUnimplemented[] = {
+  false,  // kNone
+#define IS_UNIMPLEMENTED(Intrinsic, ...) \
+  IsUnimplemented<Intrinsics::k##Intrinsic>().is_unimplemented,
+  INTRINSICS_LIST(IS_UNIMPLEMENTED)
+#undef IS_UNIMPLEMENTED
+};
+#undef INTRINSICS_LIST
+
+}  // namespace detail
+
 CodeGeneratorARMVIXL::CodeGeneratorARMVIXL(HGraph* graph,
                                            const CompilerOptions& compiler_options,
                                            OptimizingCompilerStats* stats)
@@ -1914,7 +1944,8 @@
                     kCoreCalleeSaves.GetList(),
                     ComputeSRegisterListMask(kFpuCalleeSaves),
                     compiler_options,
-                    stats),
+                    stats,
+                    ArrayRef<const bool>(detail::kIsIntrinsicUnimplemented)),
       block_labels_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
       jump_tables_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)),
       location_builder_(graph, this),
@@ -2101,7 +2132,10 @@
   blocked_core_registers_[LR] = true;
   blocked_core_registers_[PC] = true;
 
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  // TODO: We don't need to reserve marking-register for userfaultfd GC. But
+  // that would require some work in the assembler code as the right GC is
+  // chosen at load-time and not compile time.
+  if (kReserveMarkingRegister) {
     // Reserve marking register.
     blocked_core_registers_[MR] = true;
   }
@@ -2164,9 +2198,24 @@
       new (codegen_->GetScopedAllocator()) MethodEntryExitHooksSlowPathARMVIXL(instruction);
   codegen_->AddSlowPath(slow_path);
 
-  int offset = instrumentation::Instrumentation::NeedsEntryExitHooksOffset().Int32Value();
+  if (instruction->IsMethodExitHook()) {
+    // Check if we are required to check if the caller needs a deoptimization. Strictly speaking it
+    // would be sufficient to check if CheckCallerForDeopt bit is set. Though it is faster to check
+    // if it is just non-zero. kCHA bit isn't used in debuggable runtimes as cha optimization is
+    // disabled in debuggable runtime. The other bit is used when this method itself requires a
+    // deoptimization due to redefinition. So it is safe to just check for non-zero value here.
+    GetAssembler()->LoadFromOffset(kLoadWord,
+                                   temp,
+                                   sp,
+                                   codegen_->GetStackOffsetOfShouldDeoptimizeFlag());
+    __ CompareAndBranchIfNonZero(temp, slow_path->GetEntryLabel());
+  }
+
+  MemberOffset  offset = instruction->IsMethodExitHook() ?
+      instrumentation::Instrumentation::HaveMethodExitListenersOffset() :
+      instrumentation::Instrumentation::HaveMethodEntryListenersOffset();
   uint32_t address = reinterpret_cast32<uint32_t>(Runtime::Current()->GetInstrumentation());
-  __ Mov(temp, address + offset);
+  __ Mov(temp, address + offset.Int32Value());
   __ Ldrb(temp, MemOperand(temp, 0));
   __ CompareAndBranchIfNonZero(temp, slow_path->GetEntryLabel());
   __ Bind(slow_path->GetExitLabel());
@@ -2234,6 +2283,61 @@
   bool skip_overflow_check =
       IsLeafMethod() && !FrameNeedsStackCheck(GetFrameSize(), InstructionSet::kArm);
   DCHECK(GetCompilerOptions().GetImplicitStackOverflowChecks());
+
+  // Check if we need to generate the clinit check. We will jump to the
+  // resolution stub if the class is not initialized and the executing thread is
+  // not the thread initializing it.
+  // We do this before constructing the frame to get the correct stack trace if
+  // an exception is thrown.
+  if (GetCompilerOptions().ShouldCompileWithClinitCheck(GetGraph()->GetArtMethod())) {
+    UseScratchRegisterScope temps(GetVIXLAssembler());
+    vixl32::Label resolution;
+    vixl32::Label memory_barrier;
+
+    // Check if we're visibly initialized.
+
+    vixl32::Register temp1 = temps.Acquire();
+    // Use r4 as other temporary register.
+    DCHECK(!blocked_core_registers_[R4]);
+    DCHECK(!kCoreCalleeSaves.Includes(r4));
+    vixl32::Register temp2 = r4;
+    for (vixl32::Register reg : kParameterCoreRegistersVIXL) {
+      DCHECK(!reg.Is(r4));
+    }
+
+    // We don't emit a read barrier here to save on code size. We rely on the
+    // resolution trampoline to do a suspend check before re-entering this code.
+    __ Ldr(temp1, MemOperand(kMethodRegister, ArtMethod::DeclaringClassOffset().Int32Value()));
+    __ Ldrb(temp2, MemOperand(temp1, status_byte_offset));
+    __ Cmp(temp2, shifted_visibly_initialized_value);
+    __ B(cs, &frame_entry_label_);
+
+    // Check if we're initialized and jump to code that does a memory barrier if
+    // so.
+    __ Cmp(temp2, shifted_initialized_value);
+    __ B(cs, &memory_barrier);
+
+    // Check if we're initializing and the thread initializing is the one
+    // executing the code.
+    __ Cmp(temp2, shifted_initializing_value);
+    __ B(lo, &resolution);
+
+    __ Ldr(temp1, MemOperand(temp1, mirror::Class::ClinitThreadIdOffset().Int32Value()));
+    __ Ldr(temp2, MemOperand(tr, Thread::TidOffset<kArmPointerSize>().Int32Value()));
+    __ Cmp(temp1, temp2);
+    __ B(eq, &frame_entry_label_);
+    __ Bind(&resolution);
+
+    // Jump to the resolution stub.
+    ThreadOffset32 entrypoint_offset =
+        GetThreadOffset<kArmPointerSize>(kQuickQuickResolutionTrampoline);
+    __ Ldr(temp1, MemOperand(tr, entrypoint_offset.Int32Value()));
+    __ Bx(temp1);
+
+    __ Bind(&memory_barrier);
+    GenerateMemoryBarrier(MemBarrierKind::kAnyAny);
+  }
+
   __ Bind(&frame_entry_label_);
 
   if (HasEmptyFrame()) {
@@ -3069,12 +3173,12 @@
   }
 }
 
-void LocationsBuilderARMVIXL::VisitNativeDebugInfo(HNativeDebugInfo* info) {
-  new (GetGraph()->GetAllocator()) LocationSummary(info);
+void LocationsBuilderARMVIXL::VisitNop(HNop* nop) {
+  new (GetGraph()->GetAllocator()) LocationSummary(nop);
 }
 
-void InstructionCodeGeneratorARMVIXL::VisitNativeDebugInfo(HNativeDebugInfo*) {
-  // MaybeRecordNativeDebugInfo is already called implicitly in CodeGenerator::Compile.
+void InstructionCodeGeneratorARMVIXL::VisitNop(HNop*) {
+  // The environment recording already happened in CodeGenerator::Compile.
 }
 
 void CodeGeneratorARMVIXL::IncreaseFrame(size_t adjustment) {
@@ -4514,10 +4618,11 @@
 
   switch (div->GetResultType()) {
     case DataType::Type::kInt32: {
-      if (div->InputAt(1)->IsConstant()) {
+      HInstruction* divisor = div->InputAt(1);
+      if (divisor->IsConstant()) {
         locations->SetInAt(0, Location::RequiresRegister());
-        locations->SetInAt(1, Location::ConstantLocation(div->InputAt(1)->AsConstant()));
-        int32_t value = Int32ConstantFrom(div->InputAt(1));
+        locations->SetInAt(1, Location::ConstantLocation(divisor));
+        int32_t value = Int32ConstantFrom(divisor);
         Location::OutputOverlap out_overlaps = Location::kNoOutputOverlap;
         if (value == 1 || value == 0 || value == -1) {
           // No temp register required.
@@ -4631,10 +4736,11 @@
 
   switch (type) {
     case DataType::Type::kInt32: {
-      if (rem->InputAt(1)->IsConstant()) {
+      HInstruction* divisor = rem->InputAt(1);
+      if (divisor->IsConstant()) {
         locations->SetInAt(0, Location::RequiresRegister());
-        locations->SetInAt(1, Location::ConstantLocation(rem->InputAt(1)->AsConstant()));
-        int32_t value = Int32ConstantFrom(rem->InputAt(1));
+        locations->SetInAt(1, Location::ConstantLocation(divisor));
+        int32_t value = Int32ConstantFrom(divisor);
         Location::OutputOverlap out_overlaps = Location::kNoOutputOverlap;
         if (value == 1 || value == 0 || value == -1) {
           // No temp register required.
@@ -5187,17 +5293,18 @@
 void LocationsBuilderARMVIXL::VisitRor(HRor* ror) {
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(ror, LocationSummary::kNoCall);
+  HInstruction* shift = ror->InputAt(1);
   switch (ror->GetResultType()) {
     case DataType::Type::kInt32: {
       locations->SetInAt(0, Location::RequiresRegister());
-      locations->SetInAt(1, Location::RegisterOrConstant(ror->InputAt(1)));
+      locations->SetInAt(1, Location::RegisterOrConstant(shift));
       locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
       break;
     }
     case DataType::Type::kInt64: {
       locations->SetInAt(0, Location::RequiresRegister());
-      if (ror->InputAt(1)->IsConstant()) {
-        locations->SetInAt(1, Location::ConstantLocation(ror->InputAt(1)->AsConstant()));
+      if (shift->IsConstant()) {
+        locations->SetInAt(1, Location::ConstantLocation(shift));
       } else {
         locations->SetInAt(1, Location::RequiresRegister());
         locations->AddTemp(Location::RequiresRegister());
@@ -5234,11 +5341,12 @@
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(op, LocationSummary::kNoCall);
 
+  HInstruction* shift = op->InputAt(1);
   switch (op->GetResultType()) {
     case DataType::Type::kInt32: {
       locations->SetInAt(0, Location::RequiresRegister());
-      if (op->InputAt(1)->IsConstant()) {
-        locations->SetInAt(1, Location::ConstantLocation(op->InputAt(1)->AsConstant()));
+      if (shift->IsConstant()) {
+        locations->SetInAt(1, Location::ConstantLocation(shift));
         locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
       } else {
         locations->SetInAt(1, Location::RequiresRegister());
@@ -5250,8 +5358,8 @@
     }
     case DataType::Type::kInt64: {
       locations->SetInAt(0, Location::RequiresRegister());
-      if (op->InputAt(1)->IsConstant()) {
-        locations->SetInAt(1, Location::ConstantLocation(op->InputAt(1)->AsConstant()));
+      if (shift->IsConstant()) {
+        locations->SetInAt(1, Location::ConstantLocation(shift));
         // For simplicity, use kOutputOverlap even though we only require that low registers
         // don't clash with high registers which the register allocator currently guarantees.
         locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap);
@@ -5727,8 +5835,9 @@
   __ CompareAndBranchIfNonZero(temp1, &fail);
 }
 
-void LocationsBuilderARMVIXL::HandleFieldSet(
-    HInstruction* instruction, const FieldInfo& field_info) {
+void LocationsBuilderARMVIXL::HandleFieldSet(HInstruction* instruction,
+                                             const FieldInfo& field_info,
+                                             WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
 
   LocationSummary* locations =
@@ -5751,8 +5860,12 @@
   // Temporary registers for the write barrier.
   // TODO: consider renaming StoreNeedsWriteBarrier to StoreNeedsGCMark.
   if (needs_write_barrier) {
-    locations->AddTemp(Location::RequiresRegister());  // Possibly used for reference poisoning too.
-    locations->AddTemp(Location::RequiresRegister());
+    if (write_barrier_kind != WriteBarrierKind::kDontEmit) {
+      locations->AddTemp(Location::RequiresRegister());
+      locations->AddTemp(Location::RequiresRegister());
+    } else if (kPoisonHeapReferences) {
+      locations->AddTemp(Location::RequiresRegister());
+    }
   } else if (generate_volatile) {
     // ARM encoding have some additional constraints for ldrexd/strexd:
     // - registers need to be consecutive
@@ -5773,7 +5886,8 @@
 
 void InstructionCodeGeneratorARMVIXL::HandleFieldSet(HInstruction* instruction,
                                                      const FieldInfo& field_info,
-                                                     bool value_can_be_null) {
+                                                     bool value_can_be_null,
+                                                     WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
 
   LocationSummary* locations = instruction->GetLocations();
@@ -5889,10 +6003,16 @@
       UNREACHABLE();
   }
 
-  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) {
+  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1)) &&
+      write_barrier_kind != WriteBarrierKind::kDontEmit) {
     vixl32::Register temp = RegisterFrom(locations->GetTemp(0));
     vixl32::Register card = RegisterFrom(locations->GetTemp(1));
-    codegen_->MarkGCCard(temp, card, base, RegisterFrom(value), value_can_be_null);
+    codegen_->MarkGCCard(
+        temp,
+        card,
+        base,
+        RegisterFrom(value),
+        value_can_be_null && write_barrier_kind == WriteBarrierKind::kEmitWithNullCheck);
   }
 
   if (is_volatile) {
@@ -5911,7 +6031,7 @@
          instruction->IsPredicatedInstanceFieldGet());
 
   bool object_field_get_with_read_barrier =
-      kEmitCompilerReadBarrier && (field_info.GetFieldType() == DataType::Type::kReference);
+      gUseReadBarrier && (field_info.GetFieldType() == DataType::Type::kReference);
   bool is_predicated = instruction->IsPredicatedInstanceFieldGet();
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(instruction,
@@ -5975,7 +6095,7 @@
   DCHECK(DataType::IsFloatingPointType(input->GetType())) << input->GetType();
   if ((input->IsFloatConstant() && (input->AsFloatConstant()->IsArithmeticZero())) ||
       (input->IsDoubleConstant() && (input->AsDoubleConstant()->IsArithmeticZero()))) {
-    return Location::ConstantLocation(input->AsConstant());
+    return Location::ConstantLocation(input);
   } else {
     return Location::RequiresFpuRegister();
   }
@@ -5986,7 +6106,7 @@
   DCHECK(!DataType::IsFloatingPointType(constant->GetType()));
   if (constant->IsConstant() &&
       CanEncodeConstantAsImmediate(constant->AsConstant(), opcode)) {
-    return Location::ConstantLocation(constant->AsConstant());
+    return Location::ConstantLocation(constant);
   }
   return Location::RequiresRegister();
 }
@@ -6082,7 +6202,7 @@
 
     case DataType::Type::kReference: {
       // /* HeapReference<Object> */ out = *(base + offset)
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         Location maybe_temp = (locations->GetTempCount() != 0) ? locations->GetTemp(0) : Location();
         // Note that a potential implicit null check is handled in this
         // CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier call.
@@ -6165,11 +6285,14 @@
 }
 
 void LocationsBuilderARMVIXL::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo());
+  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetWriteBarrierKind());
 }
 
 void InstructionCodeGeneratorARMVIXL::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderARMVIXL::VisitInstanceFieldGet(HInstanceFieldGet* instruction) {
@@ -6202,11 +6325,14 @@
 }
 
 void LocationsBuilderARMVIXL::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo());
+  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetWriteBarrierKind());
 }
 
 void InstructionCodeGeneratorARMVIXL::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderARMVIXL::VisitStringBuilderAppend(HStringBuilderAppend* instruction) {
@@ -6386,7 +6512,7 @@
 
 void LocationsBuilderARMVIXL::VisitArrayGet(HArrayGet* instruction) {
   bool object_array_get_with_read_barrier =
-      kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+      gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(instruction,
                                                        object_array_get_with_read_barrier
@@ -6534,14 +6660,14 @@
       // The read barrier instrumentation of object ArrayGet
       // instructions does not support the HIntermediateAddress
       // instruction.
-      DCHECK(!(has_intermediate_address && kEmitCompilerReadBarrier));
+      DCHECK(!(has_intermediate_address && gUseReadBarrier));
 
       static_assert(
           sizeof(mirror::HeapReference<mirror::Object>) == sizeof(int32_t),
           "art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
       // /* HeapReference<Object> */ out =
       //     *(obj + data_offset + index * sizeof(HeapReference<Object>))
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         // Note that a potential implicit null check is handled in this
         // CodeGeneratorARMVIXL::GenerateArrayLoadWithBakerReadBarrier call.
         DCHECK(!instruction->CanDoImplicitNullCheckOn(instruction->InputAt(0)));
@@ -6688,8 +6814,10 @@
     locations->SetInAt(2, Location::RequiresRegister());
   }
   if (needs_write_barrier) {
-    // Temporary registers for the write barrier.
-    locations->AddTemp(Location::RequiresRegister());  // Possibly used for ref. poisoning too.
+    // Temporary registers for the write barrier or register poisoning.
+    // TODO(solanes): We could reduce the temp usage but it requires some non-trivial refactoring of
+    // InstructionCodeGeneratorARMVIXL::VisitArraySet.
+    locations->AddTemp(Location::RequiresRegister());
     locations->AddTemp(Location::RequiresRegister());
   }
 }
@@ -6841,7 +6969,11 @@
         }
       }
 
-      codegen_->MarkGCCard(temp1, temp2, array, value, /* value_can_be_null= */ false);
+      if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+        DCHECK_EQ(instruction->GetWriteBarrierKind(), WriteBarrierKind::kEmitNoNullCheck)
+            << " Already null checked so we shouldn't do it again.";
+        codegen_->MarkGCCard(temp1, temp2, array, value, /* emit_null_check= */ false);
+      }
 
       if (can_value_be_null) {
         DCHECK(do_store.IsReferenced());
@@ -7025,10 +7157,10 @@
   // locations.
   bool both_const = index->IsConstant() && length->IsConstant();
   locations->SetInAt(0, both_const
-      ? Location::ConstantLocation(index->AsConstant())
+      ? Location::ConstantLocation(index)
       : ArmEncodableConstantOrRegister(index, CMP));
   locations->SetInAt(1, both_const
-      ? Location::ConstantLocation(length->AsConstant())
+      ? Location::ConstantLocation(length)
       : ArmEncodableConstantOrRegister(length, CMP));
 }
 
@@ -7072,9 +7204,9 @@
                                       vixl32::Register card,
                                       vixl32::Register object,
                                       vixl32::Register value,
-                                      bool value_can_be_null) {
+                                      bool emit_null_check) {
   vixl32::Label is_null;
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ CompareAndBranchIfZero(value, &is_null, /* is_far_target=*/ false);
   }
   // Load the address of the card table into `card`.
@@ -7097,7 +7229,7 @@
   // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
   // (no need to explicitly load `kCardDirty` as an immediate value).
   __ Strb(card, MemOperand(card, temp));
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ Bind(&is_null);
   }
 }
@@ -7459,7 +7591,7 @@
             load_kind == HLoadClass::LoadKind::kBssEntryPublic ||
                 load_kind == HLoadClass::LoadKind::kBssEntryPackage);
 
-  const bool requires_read_barrier = kEmitCompilerReadBarrier && !cls->IsInBootImage();
+  const bool requires_read_barrier = gUseReadBarrier && !cls->IsInBootImage();
   LocationSummary::CallKind call_kind = (cls->NeedsEnvironment() || requires_read_barrier)
       ? LocationSummary::kCallOnSlowPath
       : LocationSummary::kNoCall;
@@ -7473,7 +7605,7 @@
   }
   locations->SetOut(Location::RequiresRegister());
   if (load_kind == HLoadClass::LoadKind::kBssEntry) {
-    if (!kUseReadBarrier || kUseBakerReadBarrier) {
+    if (!gUseReadBarrier || kUseBakerReadBarrier) {
       // Rely on the type resolution or initialization and marking to save everything we need.
       locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
     } else {
@@ -7501,7 +7633,7 @@
 
   const ReadBarrierOption read_barrier_option = cls->IsInBootImage()
       ? kWithoutReadBarrier
-      : kCompilerReadBarrierOption;
+      : gCompilerReadBarrierOption;
   bool generate_null_check = false;
   switch (load_kind) {
     case HLoadClass::LoadKind::kReferrersClass: {
@@ -7622,12 +7754,7 @@
     LoadClassSlowPathARMVIXL* slow_path, vixl32::Register class_reg) {
   UseScratchRegisterScope temps(GetVIXLAssembler());
   vixl32::Register temp = temps.Acquire();
-  constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
-  constexpr uint32_t shifted_visibly_initialized_value =
-      enum_cast<uint32_t>(ClassStatus::kVisiblyInitialized) << status_lsb_position;
-
-  const size_t status_offset = mirror::Class::StatusOffset().SizeValue();
-  GetAssembler()->LoadFromOffset(kLoadWord, temp, class_reg, status_offset);
+  __ Ldrb(temp, MemOperand(class_reg, status_byte_offset));
   __ Cmp(temp, shifted_visibly_initialized_value);
   __ B(lo, slow_path->GetEntryLabel());
   __ Bind(slow_path->GetExitLabel());
@@ -7721,7 +7848,7 @@
   } else {
     locations->SetOut(Location::RequiresRegister());
     if (load_kind == HLoadString::LoadKind::kBssEntry) {
-      if (!kUseReadBarrier || kUseBakerReadBarrier) {
+      if (!gUseReadBarrier || kUseBakerReadBarrier) {
         // Rely on the pResolveString and marking to save everything we need, including temps.
         locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
       } else {
@@ -7760,7 +7887,7 @@
       codegen_->EmitMovwMovtPlaceholder(labels, out);
       // All aligned loads are implicitly atomic consume operations on ARM.
       codegen_->GenerateGcRootFieldLoad(
-          load, out_loc, out, /*offset=*/ 0, kCompilerReadBarrierOption);
+          load, out_loc, out, /*offset=*/ 0, gCompilerReadBarrierOption);
       LoadStringSlowPathARMVIXL* slow_path =
           new (codegen_->GetScopedAllocator()) LoadStringSlowPathARMVIXL(load);
       codegen_->AddSlowPath(slow_path);
@@ -7781,7 +7908,7 @@
                                                         load->GetString()));
       // /* GcRoot<mirror::String> */ out = *out
       codegen_->GenerateGcRootFieldLoad(
-          load, out_loc, out, /*offset=*/ 0, kCompilerReadBarrierOption);
+          load, out_loc, out, /*offset=*/ 0, gCompilerReadBarrierOption);
       return;
     }
     default:
@@ -7838,7 +7965,7 @@
 
 // Temp is used for read barrier.
 static size_t NumberOfInstanceOfTemps(TypeCheckKind type_check_kind) {
-  if (kEmitCompilerReadBarrier &&
+  if (gUseReadBarrier &&
        (kUseBakerReadBarrier ||
           type_check_kind == TypeCheckKind::kAbstractClassCheck ||
           type_check_kind == TypeCheckKind::kClassHierarchyCheck ||
@@ -7888,9 +8015,9 @@
   }
   locations->SetInAt(0, Location::RequiresRegister());
   if (type_check_kind == TypeCheckKind::kBitstringCheck) {
-    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
-    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)->AsConstant()));
-    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)->AsConstant()));
+    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
+    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)));
+    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)));
   } else {
     locations->SetInAt(1, Location::RequiresRegister());
   }
@@ -8185,9 +8312,9 @@
       new (GetGraph()->GetAllocator()) LocationSummary(instruction, call_kind);
   locations->SetInAt(0, Location::RequiresRegister());
   if (type_check_kind == TypeCheckKind::kBitstringCheck) {
-    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
-    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)->AsConstant()));
-    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)->AsConstant()));
+    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
+    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)));
+    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)));
   } else {
     locations->SetInAt(1, Location::RequiresRegister());
   }
@@ -8773,7 +8900,7 @@
     ReadBarrierOption read_barrier_option) {
   vixl32::Register out_reg = RegisterFrom(out);
   if (read_barrier_option == kWithReadBarrier) {
-    CHECK(kEmitCompilerReadBarrier);
+    CHECK(gUseReadBarrier);
     DCHECK(maybe_temp.IsRegister()) << maybe_temp;
     if (kUseBakerReadBarrier) {
       // Load with fast path based Baker's read barrier.
@@ -8808,7 +8935,7 @@
   vixl32::Register out_reg = RegisterFrom(out);
   vixl32::Register obj_reg = RegisterFrom(obj);
   if (read_barrier_option == kWithReadBarrier) {
-    CHECK(kEmitCompilerReadBarrier);
+    CHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       DCHECK(maybe_temp.IsRegister()) << maybe_temp;
       // Load with fast path based Baker's read barrier.
@@ -8837,7 +8964,7 @@
     ReadBarrierOption read_barrier_option) {
   vixl32::Register root_reg = RegisterFrom(root);
   if (read_barrier_option == kWithReadBarrier) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       // Fast path implementation of art::ReadBarrier::BarrierForRoot when
       // Baker's read barrier are used.
@@ -8901,7 +9028,7 @@
 void CodeGeneratorARMVIXL::GenerateIntrinsicCasMoveWithBakerReadBarrier(
     vixl::aarch32::Register marked_old_value,
     vixl::aarch32::Register old_value) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   // Similar to the Baker RB path in GenerateGcRootFieldLoad(), with a MOV instead of LDR.
@@ -8935,7 +9062,7 @@
                                                                  vixl32::Register obj,
                                                                  const vixl32::MemOperand& src,
                                                                  bool needs_null_check) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
@@ -9028,7 +9155,7 @@
                                                                  Location index,
                                                                  Location temp,
                                                                  bool needs_null_check) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   static_assert(
@@ -9094,7 +9221,7 @@
 
 void CodeGeneratorARMVIXL::MaybeGenerateMarkingRegisterCheck(int code, Location temp_loc) {
   // The following condition is a compile-time one, so it does not have a run-time cost.
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier && kIsDebugBuild) {
+  if (kIsDebugBuild && gUseReadBarrier && kUseBakerReadBarrier) {
     // The following condition is a run-time one; it is executed after the
     // previous compile-time test, to avoid penalizing non-debug builds.
     if (GetCompilerOptions().EmitRunTimeChecksInDebugMode()) {
@@ -9124,7 +9251,7 @@
                                                    Location obj,
                                                    uint32_t offset,
                                                    Location index) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
 
   // Insert a slow path based read barrier *after* the reference load.
   //
@@ -9150,7 +9277,7 @@
                                                         Location obj,
                                                         uint32_t offset,
                                                         Location index) {
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     // Baker's read barriers shall be handled by the fast path
     // (CodeGeneratorARMVIXL::GenerateReferenceLoadWithBakerReadBarrier).
     DCHECK(!kUseBakerReadBarrier);
@@ -9165,7 +9292,7 @@
 void CodeGeneratorARMVIXL::GenerateReadBarrierForRootSlow(HInstruction* instruction,
                                                           Location out,
                                                           Location root) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
 
   // Insert a slow path based read barrier *after* the GC root load.
   //
diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h
index 790ad0f..f5abe69 100644
--- a/compiler/optimizing/code_generator_arm_vixl.h
+++ b/compiler/optimizing/code_generator_arm_vixl.h
@@ -18,6 +18,7 @@
 #define ART_COMPILER_OPTIMIZING_CODE_GENERATOR_ARM_VIXL_H_
 
 #include "base/enums.h"
+#include "base/macros.h"
 #include "class_root.h"
 #include "code_generator.h"
 #include "common_arm.h"
@@ -36,7 +37,7 @@
 #include "aarch32/macro-assembler-aarch32.h"
 #pragma GCC diagnostic pop
 
-namespace art {
+namespace art HIDDEN {
 
 namespace linker {
 class Thumb2RelativePatcherTest;
@@ -84,7 +85,7 @@
                                 vixl::aarch32::r6,
                                 vixl::aarch32::r7),
     // Do not consider r8 as a callee-save register with Baker read barriers.
-    ((kEmitCompilerReadBarrier && kUseBakerReadBarrier)
+    (kReserveMarkingRegister
          ? vixl::aarch32::RegisterList()
          : vixl::aarch32::RegisterList(vixl::aarch32::r8)),
     vixl::aarch32::RegisterList(vixl::aarch32::r10,
@@ -118,6 +119,65 @@
 using VIXLInt32Literal = vixl::aarch32::Literal<int32_t>;
 using VIXLUInt32Literal = vixl::aarch32::Literal<uint32_t>;
 
+#define UNIMPLEMENTED_INTRINSIC_LIST_ARM(V)                                \
+  V(MathRoundDouble) /* Could be done by changing rounding mode, maybe? */ \
+  V(UnsafeCASLong)   /* High register pressure */                          \
+  V(SystemArrayCopyChar)                                                   \
+  V(LongDivideUnsigned)                                                    \
+  V(CRC32Update)                                                           \
+  V(CRC32UpdateBytes)                                                      \
+  V(CRC32UpdateByteBuffer)                                                 \
+  V(FP16ToFloat)                                                           \
+  V(FP16ToHalf)                                                            \
+  V(FP16Floor)                                                             \
+  V(FP16Ceil)                                                              \
+  V(FP16Rint)                                                              \
+  V(FP16Greater)                                                           \
+  V(FP16GreaterEquals)                                                     \
+  V(FP16Less)                                                              \
+  V(FP16LessEquals)                                                        \
+  V(FP16Compare)                                                           \
+  V(FP16Min)                                                               \
+  V(FP16Max)                                                               \
+  V(MathMultiplyHigh)                                                      \
+  V(StringStringIndexOf)                                                   \
+  V(StringStringIndexOfAfter)                                              \
+  V(StringBufferAppend)                                                    \
+  V(StringBufferLength)                                                    \
+  V(StringBufferToString)                                                  \
+  V(StringBuilderAppendObject)                                             \
+  V(StringBuilderAppendString)                                             \
+  V(StringBuilderAppendCharSequence)                                       \
+  V(StringBuilderAppendCharArray)                                          \
+  V(StringBuilderAppendBoolean)                                            \
+  V(StringBuilderAppendChar)                                               \
+  V(StringBuilderAppendInt)                                                \
+  V(StringBuilderAppendLong)                                               \
+  V(StringBuilderAppendFloat)                                              \
+  V(StringBuilderAppendDouble)                                             \
+  V(StringBuilderLength)                                                   \
+  V(StringBuilderToString)                                                 \
+  V(SystemArrayCopyByte)                                                   \
+  V(SystemArrayCopyInt)                                                    \
+  /* 1.8 */                                                                \
+  V(MathFmaDouble)                                                         \
+  V(MathFmaFloat)                                                          \
+  V(UnsafeGetAndAddInt)                                                    \
+  V(UnsafeGetAndAddLong)                                                   \
+  V(UnsafeGetAndSetInt)                                                    \
+  V(UnsafeGetAndSetLong)                                                   \
+  V(UnsafeGetAndSetObject)                                                 \
+  V(MethodHandleInvokeExact)                                               \
+  V(MethodHandleInvoke)                                                    \
+  /* OpenJDK 11 */                                                         \
+  V(JdkUnsafeCASLong) /* High register pressure */                         \
+  V(JdkUnsafeGetAndAddInt)                                                 \
+  V(JdkUnsafeGetAndAddLong)                                                \
+  V(JdkUnsafeGetAndSetInt)                                                 \
+  V(JdkUnsafeGetAndSetLong)                                                \
+  V(JdkUnsafeGetAndSetObject)                                              \
+  V(JdkUnsafeCompareAndSetLong)
+
 class JumpTableARMVIXL : public DeletableArenaObject<kArenaAllocSwitchTable> {
  public:
   explicit JumpTableARMVIXL(HPackedSwitch* switch_instr)
@@ -309,7 +369,9 @@
   void HandleIntegerRotate(LocationSummary* locations);
   void HandleLongRotate(LocationSummary* locations);
   void HandleShift(HBinaryOperation* operation);
-  void HandleFieldSet(HInstruction* instruction, const FieldInfo& field_info);
+  void HandleFieldSet(HInstruction* instruction,
+                      const FieldInfo& field_info,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
 
   Location ArithmeticZeroOrFpuRegister(HInstruction* input);
@@ -378,7 +440,8 @@
 
   void HandleFieldSet(HInstruction* instruction,
                       const FieldInfo& field_info,
-                      bool value_can_be_null);
+                      bool value_can_be_null,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
 
   void GenerateMinMaxInt(LocationSummary* locations, bool is_min);
@@ -542,7 +605,7 @@
                   vixl::aarch32::Register card,
                   vixl::aarch32::Register object,
                   vixl::aarch32::Register value,
-                  bool value_can_be_null);
+                  bool emit_null_check);
 
   void GenerateMemoryBarrier(MemBarrierKind kind);
 
@@ -602,7 +665,6 @@
   struct PcRelativePatchInfo {
     PcRelativePatchInfo(const DexFile* dex_file, uint32_t off_or_idx)
         : target_dex_file(dex_file), offset_or_index(off_or_idx) { }
-    PcRelativePatchInfo(PcRelativePatchInfo&& other) = default;
 
     // Target dex file or null for .data.bmig.rel.ro patches.
     const DexFile* target_dex_file;
diff --git a/compiler/optimizing/code_generator_riscv64.h b/compiler/optimizing/code_generator_riscv64.h
new file mode 100644
index 0000000..405b39a
--- /dev/null
+++ b/compiler/optimizing/code_generator_riscv64.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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_COMPILER_OPTIMIZING_CODE_GENERATOR_RISCV64_H_
+#define ART_COMPILER_OPTIMIZING_CODE_GENERATOR_RISCV64_H_
+
+#include "code_generator.h"
+#include "driver/compiler_options.h"
+
+#endif  // ART_COMPILER_OPTIMIZING_CODE_GENERATOR_RISCV64_H_
diff --git a/compiler/optimizing/code_generator_utils.cc b/compiler/optimizing/code_generator_utils.cc
index abec2646..9980592 100644
--- a/compiler/optimizing/code_generator_utils.cc
+++ b/compiler/optimizing/code_generator_utils.cc
@@ -20,7 +20,7 @@
 
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 void CalculateMagicAndShiftForDivRem(int64_t divisor, bool is_long,
                                      int64_t* magic, int* shift) {
diff --git a/compiler/optimizing/code_generator_utils.h b/compiler/optimizing/code_generator_utils.h
index 64665ad..9d9ab2b 100644
--- a/compiler/optimizing/code_generator_utils.h
+++ b/compiler/optimizing/code_generator_utils.h
@@ -21,7 +21,9 @@
 #include <cstdlib>
 #include <limits>
 
-namespace art {
+#include "base/macros.h"
+
+namespace art HIDDEN {
 
 class HInstruction;
 
diff --git a/compiler/optimizing/code_generator_vector_arm64_neon.cc b/compiler/optimizing/code_generator_vector_arm64_neon.cc
index 0fe9898..6b6e25c 100644
--- a/compiler/optimizing/code_generator_vector_arm64_neon.cc
+++ b/compiler/optimizing/code_generator_vector_arm64_neon.cc
@@ -23,7 +23,7 @@
 
 using namespace vixl::aarch64;  // NOLINT(build/namespaces)
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 using helpers::DRegisterFrom;
@@ -65,7 +65,7 @@
                                                 HInstruction* instr) {
   if (constant->IsConstant()
       && NEONCanEncodeConstantAsImmediate(constant->AsConstant(), instr)) {
-    return Location::ConstantLocation(constant->AsConstant());
+    return Location::ConstantLocation(constant);
   }
 
   return Location::RequiresRegister();
@@ -94,7 +94,7 @@
     case DataType::Type::kFloat64:
       if (input->IsConstant() &&
           NEONCanEncodeConstantAsImmediate(input->AsConstant(), instruction)) {
-        locations->SetInAt(0, Location::ConstantLocation(input->AsConstant()));
+        locations->SetInAt(0, Location::ConstantLocation(input));
         locations->SetOut(Location::RequiresFpuRegister());
       } else {
         locations->SetInAt(0, Location::RequiresFpuRegister());
@@ -881,7 +881,7 @@
     case DataType::Type::kInt32:
     case DataType::Type::kInt64:
       locations->SetInAt(0, Location::RequiresFpuRegister());
-      locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
+      locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
       locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap);
       break;
     default:
@@ -1008,13 +1008,13 @@
     case DataType::Type::kInt16:
     case DataType::Type::kInt32:
     case DataType::Type::kInt64:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
     case DataType::Type::kFloat32:
     case DataType::Type::kFloat64:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresFpuRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
diff --git a/compiler/optimizing/code_generator_vector_arm64_sve.cc b/compiler/optimizing/code_generator_vector_arm64_sve.cc
index 824b6c9..fe15791 100644
--- a/compiler/optimizing/code_generator_vector_arm64_sve.cc
+++ b/compiler/optimizing/code_generator_vector_arm64_sve.cc
@@ -23,17 +23,14 @@
 
 using namespace vixl::aarch64;  // NOLINT(build/namespaces)
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 using helpers::DRegisterFrom;
-using helpers::HeapOperand;
 using helpers::InputRegisterAt;
 using helpers::Int64FromLocation;
 using helpers::LocationFrom;
 using helpers::OutputRegister;
-using helpers::QRegisterFrom;
-using helpers::StackOperandFrom;
 using helpers::SveStackOperandFrom;
 using helpers::VRegisterFrom;
 using helpers::ZRegisterFrom;
@@ -67,7 +64,7 @@
 inline Location SVEEncodableConstantOrRegister(HInstruction* constant, HInstruction* instr) {
   if (constant->IsConstant()
       && SVECanEncodeConstantAsImmediate(constant->AsConstant(), instr)) {
-    return Location::ConstantLocation(constant->AsConstant());
+    return Location::ConstantLocation(constant);
   }
 
   return Location::RequiresRegister();
@@ -96,7 +93,7 @@
     case DataType::Type::kFloat64:
       if (input->IsConstant() &&
           SVECanEncodeConstantAsImmediate(input->AsConstant(), instruction)) {
-        locations->SetInAt(0, Location::ConstantLocation(input->AsConstant()));
+        locations->SetInAt(0, Location::ConstantLocation(input));
         locations->SetOut(Location::RequiresFpuRegister());
       } else {
         locations->SetInAt(0, Location::RequiresFpuRegister());
@@ -754,7 +751,7 @@
     case DataType::Type::kInt32:
     case DataType::Type::kInt64:
       locations->SetInAt(0, Location::RequiresFpuRegister());
-      locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
+      locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
       locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap);
       break;
     default:
@@ -878,13 +875,13 @@
     case DataType::Type::kInt16:
     case DataType::Type::kInt32:
     case DataType::Type::kInt64:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
     case DataType::Type::kFloat32:
     case DataType::Type::kFloat64:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresFpuRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
diff --git a/compiler/optimizing/code_generator_vector_arm_vixl.cc b/compiler/optimizing/code_generator_vector_arm_vixl.cc
index c46f9b7..e8ecf28 100644
--- a/compiler/optimizing/code_generator_vector_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_vector_arm_vixl.cc
@@ -20,7 +20,7 @@
 namespace vixl32 = vixl::aarch32;
 using namespace vixl32;  // NOLINT(build/namespaces)
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 using helpers::DRegisterFrom;
@@ -640,7 +640,7 @@
     case DataType::Type::kInt16:
     case DataType::Type::kInt32:
       locations->SetInAt(0, Location::RequiresFpuRegister());
-      locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
+      locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
       locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap);
       break;
     default:
@@ -749,7 +749,7 @@
 
   switch (instruction->GetPackedType()) {
     case DataType::Type::kInt32:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
diff --git a/compiler/optimizing/code_generator_vector_x86.cc b/compiler/optimizing/code_generator_vector_x86.cc
index 9c837dd..343a6e1 100644
--- a/compiler/optimizing/code_generator_vector_x86.cc
+++ b/compiler/optimizing/code_generator_vector_x86.cc
@@ -19,7 +19,7 @@
 #include "mirror/array-inl.h"
 #include "mirror/string.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 // NOLINT on __ macro to suppress wrong warning/fix (misc-macro-parentheses) from clang-tidy.
@@ -42,13 +42,13 @@
     case DataType::Type::kUint16:
     case DataType::Type::kInt16:
     case DataType::Type::kInt32:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
     case DataType::Type::kFloat32:
     case DataType::Type::kFloat64:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresFpuRegister());
       locations->SetOut(is_zero ? Location::RequiresFpuRegister()
                                 : Location::SameAsFirstInput());
@@ -981,7 +981,7 @@
     case DataType::Type::kInt32:
     case DataType::Type::kInt64:
       locations->SetInAt(0, Location::RequiresFpuRegister());
-      locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
+      locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
       locations->SetOut(Location::SameAsFirstInput());
       break;
     default:
@@ -1094,13 +1094,13 @@
     case DataType::Type::kUint16:
     case DataType::Type::kInt16:
     case DataType::Type::kInt32:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
     case DataType::Type::kFloat32:
     case DataType::Type::kFloat64:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresFpuRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
diff --git a/compiler/optimizing/code_generator_vector_x86_64.cc b/compiler/optimizing/code_generator_vector_x86_64.cc
index 330bf76..fb6e4e7 100644
--- a/compiler/optimizing/code_generator_vector_x86_64.cc
+++ b/compiler/optimizing/code_generator_vector_x86_64.cc
@@ -19,7 +19,7 @@
 #include "mirror/array-inl.h"
 #include "mirror/string.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 // NOLINT on __ macro to suppress wrong warning/fix (misc-macro-parentheses) from clang-tidy.
@@ -37,13 +37,13 @@
     case DataType::Type::kInt16:
     case DataType::Type::kInt32:
     case DataType::Type::kInt64:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
     case DataType::Type::kFloat32:
     case DataType::Type::kFloat64:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresFpuRegister());
       locations->SetOut(is_zero ? Location::RequiresFpuRegister()
                                 : Location::SameAsFirstInput());
@@ -964,7 +964,7 @@
     case DataType::Type::kInt32:
     case DataType::Type::kInt64:
       locations->SetInAt(0, Location::RequiresFpuRegister());
-      locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
+      locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
       locations->SetOut(Location::SameAsFirstInput());
       break;
     default:
@@ -1072,13 +1072,13 @@
     case DataType::Type::kInt16:
     case DataType::Type::kInt32:
     case DataType::Type::kInt64:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
     case DataType::Type::kFloat32:
     case DataType::Type::kFloat64:
-      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input->AsConstant())
+      locations->SetInAt(0, is_zero ? Location::ConstantLocation(input)
                                     : Location::RequiresFpuRegister());
       locations->SetOut(Location::RequiresFpuRegister());
       break;
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 8c6b802..cb1cecc 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -20,7 +20,6 @@
 #include "art_method-inl.h"
 #include "class_table.h"
 #include "code_generator_utils.h"
-#include "compiled_method.h"
 #include "entrypoints/quick/quick_entrypoints.h"
 #include "entrypoints/quick/quick_entrypoints_enum.h"
 #include "gc/accounting/card_table.h"
@@ -36,6 +35,7 @@
 #include "mirror/array-inl.h"
 #include "mirror/class-inl.h"
 #include "mirror/var_handle.h"
+#include "optimizing/nodes.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
 #include "utils/assembler.h"
@@ -43,7 +43,7 @@
 #include "utils/x86/assembler_x86.h"
 #include "utils/x86/managed_register_x86.h"
 
-namespace art {
+namespace art HIDDEN {
 
 template<class MirrorType>
 class GcRoot;
@@ -503,7 +503,7 @@
       : SlowPathCode(instruction),
         ref_(ref),
         unpoison_ref_before_marking_(unpoison_ref_before_marking) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
   }
 
   const char* GetDescription() const override { return "ReadBarrierMarkSlowPathX86"; }
@@ -590,7 +590,7 @@
         field_addr_(field_addr),
         unpoison_ref_before_marking_(unpoison_ref_before_marking),
         temp_(temp) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
   }
 
   const char* GetDescription() const override { return "ReadBarrierMarkAndUpdateFieldSlowPathX86"; }
@@ -744,7 +744,7 @@
         obj_(obj),
         offset_(offset),
         index_(index) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     // If `obj` is equal to `out` or `ref`, it means the initial object
     // has been overwritten by (or after) the heap object reference load
     // to be instrumented, e.g.:
@@ -918,7 +918,7 @@
  public:
   ReadBarrierForRootSlowPathX86(HInstruction* instruction, Location out, Location root)
       : SlowPathCode(instruction), out_(out), root_(root) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
   }
 
   void EmitNativeCode(CodeGenerator* codegen) override {
@@ -967,6 +967,9 @@
         (instruction_->IsMethodEntryHook()) ? kQuickMethodEntryHook : kQuickMethodExitHook;
     __ Bind(GetEntryLabel());
     SaveLiveRegisters(codegen, locations);
+    if (instruction_->IsMethodExitHook()) {
+      __ movl(EBX, Immediate(codegen->GetFrameSize()));
+    }
     x86_codegen->InvokeRuntime(entry_point, instruction_, instruction_->GetDexPc(), this);
     RestoreLiveRegisters(codegen, locations);
     __ jmp(GetExitLabel());
@@ -1103,6 +1106,33 @@
   __ fs()->call(Address::Absolute(entry_point_offset));
 }
 
+namespace detail {
+// Mark which intrinsics we don't have handcrafted code for.
+template <Intrinsics T>
+struct IsUnimplemented {
+  bool is_unimplemented = false;
+};
+
+#define TRUE_OVERRIDE(Name)                     \
+  template <>                                   \
+  struct IsUnimplemented<Intrinsics::k##Name> { \
+    bool is_unimplemented = true;               \
+  };
+UNIMPLEMENTED_INTRINSIC_LIST_X86(TRUE_OVERRIDE)
+#undef TRUE_OVERRIDE
+
+#include "intrinsics_list.h"
+static constexpr bool kIsIntrinsicUnimplemented[] = {
+  false,  // kNone
+#define IS_UNIMPLEMENTED(Intrinsic, ...) \
+  IsUnimplemented<Intrinsics::k##Intrinsic>().is_unimplemented,
+  INTRINSICS_LIST(IS_UNIMPLEMENTED)
+#undef IS_UNIMPLEMENTED
+};
+#undef INTRINSICS_LIST
+
+}  // namespace detail
+
 CodeGeneratorX86::CodeGeneratorX86(HGraph* graph,
                                    const CompilerOptions& compiler_options,
                                    OptimizingCompilerStats* stats)
@@ -1115,7 +1145,8 @@
                         | (1 << kFakeReturnRegister),
                     0,
                     compiler_options,
-                    stats),
+                    stats,
+                    ArrayRef<const bool>(detail::kIsIntrinsicUnimplemented)),
       block_labels_(nullptr),
       location_builder_(graph, this),
       instruction_visitor_(graph, this),
@@ -1197,9 +1228,21 @@
       new (codegen_->GetScopedAllocator()) MethodEntryExitHooksSlowPathX86(instruction);
   codegen_->AddSlowPath(slow_path);
 
+  if (instruction->IsMethodExitHook()) {
+    // Check if we are required to check if the caller needs a deoptimization. Strictly speaking it
+    // would be sufficient to check if CheckCallerForDeopt bit is set. Though it is faster to check
+    // if it is just non-zero. kCHA bit isn't used in debuggable runtimes as cha optimization is
+    // disabled in debuggable runtime. The other bit is used when this method itself requires a
+    // deoptimization due to redefinition. So it is safe to just check for non-zero value here.
+    __ cmpl(Address(ESP, codegen_->GetStackOffsetOfShouldDeoptimizeFlag()), Immediate(0));
+    __ j(kNotEqual, slow_path->GetEntryLabel());
+  }
+
   uint64_t address = reinterpret_cast64<uint64_t>(Runtime::Current()->GetInstrumentation());
-  int offset = instrumentation::Instrumentation::NeedsEntryExitHooksOffset().Int32Value();
-  __ cmpb(Address::Absolute(address + offset), Immediate(0));
+  MemberOffset  offset = instruction->IsMethodExitHook() ?
+      instrumentation::Instrumentation::HaveMethodExitListenersOffset() :
+      instrumentation::Instrumentation::HaveMethodEntryListenersOffset();
+  __ cmpb(Address::Absolute(address + offset.Int32Value()), Immediate(0));
   __ j(kNotEqual, slow_path->GetEntryLabel());
   __ Bind(slow_path->GetExitLabel());
 }
@@ -1261,6 +1304,44 @@
 
 void CodeGeneratorX86::GenerateFrameEntry() {
   __ cfi().SetCurrentCFAOffset(kX86WordSize);  // return address
+
+  // Check if we need to generate the clinit check. We will jump to the
+  // resolution stub if the class is not initialized and the executing thread is
+  // not the thread initializing it.
+  // We do this before constructing the frame to get the correct stack trace if
+  // an exception is thrown.
+  if (GetCompilerOptions().ShouldCompileWithClinitCheck(GetGraph()->GetArtMethod())) {
+    NearLabel continue_execution, resolution;
+    // We'll use EBP as temporary.
+    __ pushl(EBP);
+    // Check if we're visibly initialized.
+
+    // We don't emit a read barrier here to save on code size. We rely on the
+    // resolution trampoline to do a suspend check before re-entering this code.
+    __ movl(EBP, Address(kMethodRegisterArgument, ArtMethod::DeclaringClassOffset().Int32Value()));
+    __ cmpb(Address(EBP,  status_byte_offset), Immediate(shifted_visibly_initialized_value));
+    __ j(kAboveEqual, &continue_execution);
+
+    // Check if we're initializing and the thread initializing is the one
+    // executing the code.
+    __ cmpb(Address(EBP,  status_byte_offset), Immediate(shifted_initializing_value));
+    __ j(kBelow, &resolution);
+
+    __ movl(EBP, Address(EBP, mirror::Class::ClinitThreadIdOffset().Int32Value()));
+    __ fs()->cmpl(EBP, Address::Absolute(Thread::TidOffset<kX86PointerSize>().Int32Value()));
+    __ j(kEqual, &continue_execution);
+    __ Bind(&resolution);
+
+    __ popl(EBP);
+    // Jump to the resolution stub.
+    ThreadOffset32 entrypoint_offset =
+        GetThreadOffset<kX86PointerSize>(kQuickQuickResolutionTrampoline);
+    __ fs()->jmp(Address::Absolute(entrypoint_offset));
+
+    __ Bind(&continue_execution);
+    __ popl(EBP);
+  }
+
   __ Bind(&frame_entry_label_);
   bool skip_overflow_check =
       IsLeafMethod() && !FrameNeedsStackCheck(GetFrameSize(), InstructionSet::kX86);
@@ -1619,7 +1700,7 @@
       __ movsd(dst.AsFpuRegister<XmmRegister>(), src);
       break;
     case DataType::Type::kReference:
-      DCHECK(!kEmitCompilerReadBarrier);
+      DCHECK(!gUseReadBarrier);
       __ movl(dst.AsRegister<Register>(), src);
       __ MaybeUnpoisonHeapReference(dst.AsRegister<Register>());
       break;
@@ -2230,12 +2311,12 @@
   }
 }
 
-void LocationsBuilderX86::VisitNativeDebugInfo(HNativeDebugInfo* info) {
-  new (GetGraph()->GetAllocator()) LocationSummary(info);
+void LocationsBuilderX86::VisitNop(HNop* nop) {
+  new (GetGraph()->GetAllocator()) LocationSummary(nop);
 }
 
-void InstructionCodeGeneratorX86::VisitNativeDebugInfo(HNativeDebugInfo*) {
-  // MaybeRecordNativeDebugInfo is already called implicitly in CodeGenerator::Compile.
+void InstructionCodeGeneratorX86::VisitNop(HNop*) {
+  // The environment recording already happened in CodeGenerator::Compile.
 }
 
 void CodeGeneratorX86::IncreaseFrame(size_t adjustment) {
@@ -2913,7 +2994,7 @@
         case DataType::Type::kInt64: {
           HInstruction* input = conversion->InputAt(0);
           Location input_location = input->IsConstant()
-              ? Location::ConstantLocation(input->AsConstant())
+              ? Location::ConstantLocation(input)
               : Location::RegisterPairLocation(EAX, EDX);
           locations->SetInAt(0, input_location);
           // Make the output overlap to please the register allocator. This greatly simplifies
@@ -5689,13 +5770,10 @@
   DCHECK_EQ(size, linker_patches->size());
 }
 
-void CodeGeneratorX86::MarkGCCard(Register temp,
-                                  Register card,
-                                  Register object,
-                                  Register value,
-                                  bool value_can_be_null) {
+void CodeGeneratorX86::MarkGCCard(
+    Register temp, Register card, Register object, Register value, bool emit_null_check) {
   NearLabel is_null;
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ testl(value, value);
     __ j(kEqual, &is_null);
   }
@@ -5720,7 +5798,7 @@
   // (no need to explicitly load `kCardDirty` as an immediate value).
   __ movb(Address(temp, card, TIMES_1, 0),
           X86ManagedRegister::FromCpuRegister(card).AsByteRegister());
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ Bind(&is_null);
   }
 }
@@ -5731,11 +5809,11 @@
          instruction->IsPredicatedInstanceFieldGet());
 
   bool object_field_get_with_read_barrier =
-      kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+      gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
   bool is_predicated = instruction->IsPredicatedInstanceFieldGet();
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(instruction,
-                                                       kEmitCompilerReadBarrier
+                                                       gUseReadBarrier
                                                            ? LocationSummary::kCallOnSlowPath
                                                            : LocationSummary::kNoCall);
   if (object_field_get_with_read_barrier && kUseBakerReadBarrier) {
@@ -5793,7 +5871,7 @@
 
   if (load_type == DataType::Type::kReference) {
     // /* HeapReference<Object> */ out = *(base + offset)
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       // Note that a potential implicit null check is handled in this
       // CodeGeneratorX86::GenerateFieldLoadWithBakerReadBarrier call.
       codegen_->GenerateFieldLoadWithBakerReadBarrier(
@@ -5824,7 +5902,9 @@
   }
 }
 
-void LocationsBuilderX86::HandleFieldSet(HInstruction* instruction, const FieldInfo& field_info) {
+void LocationsBuilderX86::HandleFieldSet(HInstruction* instruction,
+                                         const FieldInfo& field_info,
+                                         WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
 
   LocationSummary* locations =
@@ -5861,10 +5941,13 @@
     locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
 
     if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) {
-      // Temporary registers for the write barrier.
-      locations->AddTemp(Location::RequiresRegister());  // May be used for reference poisoning too.
-      // Ensure the card is in a byte register.
-      locations->AddTemp(Location::RegisterLocation(ECX));
+      if (write_barrier_kind != WriteBarrierKind::kDontEmit) {
+        locations->AddTemp(Location::RequiresRegister());
+        // Ensure the card is in a byte register.
+        locations->AddTemp(Location::RegisterLocation(ECX));
+      } else if (kPoisonHeapReferences) {
+        locations->AddTemp(Location::RequiresRegister());
+      }
     }
   }
 }
@@ -5875,7 +5958,8 @@
                                                  Address field_addr,
                                                  Register base,
                                                  bool is_volatile,
-                                                 bool value_can_be_null) {
+                                                 bool value_can_be_null,
+                                                 WriteBarrierKind write_barrier_kind) {
   LocationSummary* locations = instruction->GetLocations();
   Location value = locations->InAt(value_index);
   bool needs_write_barrier =
@@ -5988,10 +6072,15 @@
     codegen_->MaybeRecordImplicitNullCheck(instruction);
   }
 
-  if (needs_write_barrier) {
+  if (needs_write_barrier && write_barrier_kind != WriteBarrierKind::kDontEmit) {
     Register temp = locations->GetTemp(0).AsRegister<Register>();
     Register card = locations->GetTemp(1).AsRegister<Register>();
-    codegen_->MarkGCCard(temp, card, base, value.AsRegister<Register>(), value_can_be_null);
+    codegen_->MarkGCCard(
+        temp,
+        card,
+        base,
+        value.AsRegister<Register>(),
+        value_can_be_null && write_barrier_kind == WriteBarrierKind::kEmitWithNullCheck);
   }
 
   if (is_volatile) {
@@ -6001,7 +6090,8 @@
 
 void InstructionCodeGeneratorX86::HandleFieldSet(HInstruction* instruction,
                                                  const FieldInfo& field_info,
-                                                 bool value_can_be_null) {
+                                                 bool value_can_be_null,
+                                                 WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
 
   LocationSummary* locations = instruction->GetLocations();
@@ -6026,7 +6116,8 @@
                  field_addr,
                  base,
                  is_volatile,
-                 value_can_be_null);
+                 value_can_be_null,
+                 write_barrier_kind);
 
   if (is_predicated) {
     __ Bind(&pred_is_null);
@@ -6042,19 +6133,25 @@
 }
 
 void LocationsBuilderX86::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo());
+  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetWriteBarrierKind());
 }
 
 void InstructionCodeGeneratorX86::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderX86::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo());
+  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetWriteBarrierKind());
 }
 
 void InstructionCodeGeneratorX86::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderX86::VisitPredicatedInstanceFieldGet(
@@ -6202,7 +6299,7 @@
 
 void LocationsBuilderX86::VisitArrayGet(HArrayGet* instruction) {
   bool object_array_get_with_read_barrier =
-      kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+      gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(instruction,
                                                        object_array_get_with_read_barrier
@@ -6244,7 +6341,7 @@
         "art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
     // /* HeapReference<Object> */ out =
     //     *(obj + data_offset + index * sizeof(HeapReference<Object>))
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       // Note that a potential implicit null check is handled in this
       // CodeGeneratorX86::GenerateArrayLoadWithBakerReadBarrier call.
       codegen_->GenerateArrayLoadWithBakerReadBarrier(
@@ -6315,10 +6412,12 @@
     locations->SetInAt(2, Location::RegisterOrConstant(instruction->InputAt(2)));
   }
   if (needs_write_barrier) {
-    // Temporary registers for the write barrier.
-    locations->AddTemp(Location::RequiresRegister());  // Possibly used for ref. poisoning too.
-    // Ensure the card is in a byte register.
-    locations->AddTemp(Location::RegisterLocation(ECX));
+    // Used by reference poisoning or emitting write barrier.
+    locations->AddTemp(Location::RequiresRegister());
+    if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+      // Only used when emitting a write barrier. Ensure the card is in a byte register.
+      locations->AddTemp(Location::RegisterLocation(ECX));
+    }
   }
 }
 
@@ -6435,9 +6534,16 @@
         }
       }
 
-      Register card = locations->GetTemp(1).AsRegister<Register>();
-      codegen_->MarkGCCard(
-          temp, card, array, value.AsRegister<Register>(), /* value_can_be_null= */ false);
+      if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+        DCHECK_EQ(instruction->GetWriteBarrierKind(), WriteBarrierKind::kEmitNoNullCheck)
+            << " Already null checked so we shouldn't do it again.";
+        Register card = locations->GetTemp(1).AsRegister<Register>();
+        codegen_->MarkGCCard(temp,
+                             card,
+                             array,
+                             value.AsRegister<Register>(),
+                             /* emit_null_check= */ false);
+      }
 
       if (can_value_be_null) {
         DCHECK(do_store.IsLinked());
@@ -7057,7 +7163,7 @@
             load_kind == HLoadClass::LoadKind::kBssEntryPublic ||
                 load_kind == HLoadClass::LoadKind::kBssEntryPackage);
 
-  const bool requires_read_barrier = kEmitCompilerReadBarrier && !cls->IsInBootImage();
+  const bool requires_read_barrier = gUseReadBarrier && !cls->IsInBootImage();
   LocationSummary::CallKind call_kind = (cls->NeedsEnvironment() || requires_read_barrier)
       ? LocationSummary::kCallOnSlowPath
       : LocationSummary::kNoCall;
@@ -7071,7 +7177,7 @@
   }
   locations->SetOut(Location::RequiresRegister());
   if (call_kind == LocationSummary::kCallOnSlowPath && cls->HasPcRelativeLoadKind()) {
-    if (!kUseReadBarrier || kUseBakerReadBarrier) {
+    if (!gUseReadBarrier || kUseBakerReadBarrier) {
       // Rely on the type resolution and/or initialization to save everything.
       locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
     } else {
@@ -7109,7 +7215,7 @@
   bool generate_null_check = false;
   const ReadBarrierOption read_barrier_option = cls->IsInBootImage()
       ? kWithoutReadBarrier
-      : kCompilerReadBarrierOption;
+      : gCompilerReadBarrierOption;
   switch (load_kind) {
     case HLoadClass::LoadKind::kReferrersClass: {
       DCHECK(!cls->CanCallRuntime());
@@ -7233,12 +7339,6 @@
 
 void InstructionCodeGeneratorX86::GenerateClassInitializationCheck(
     SlowPathCode* slow_path, Register class_reg) {
-  constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
-  const size_t status_byte_offset =
-      mirror::Class::StatusOffset().SizeValue() + (status_lsb_position / kBitsPerByte);
-  constexpr uint32_t shifted_visibly_initialized_value =
-      enum_cast<uint32_t>(ClassStatus::kVisiblyInitialized) << (status_lsb_position % kBitsPerByte);
-
   __ cmpb(Address(class_reg,  status_byte_offset), Immediate(shifted_visibly_initialized_value));
   __ j(kBelow, slow_path->GetEntryLabel());
   __ Bind(slow_path->GetExitLabel());
@@ -7296,7 +7396,7 @@
   } else {
     locations->SetOut(Location::RequiresRegister());
     if (load_kind == HLoadString::LoadKind::kBssEntry) {
-      if (!kUseReadBarrier || kUseBakerReadBarrier) {
+      if (!gUseReadBarrier || kUseBakerReadBarrier) {
         // Rely on the pResolveString to save everything.
         locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
       } else {
@@ -7345,7 +7445,7 @@
       Address address = Address(method_address, CodeGeneratorX86::kPlaceholder32BitOffset);
       Label* fixup_label = codegen_->NewStringBssEntryPatch(load);
       // /* GcRoot<mirror::String> */ out = *address  /* PC-relative */
-      GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, kCompilerReadBarrierOption);
+      GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, gCompilerReadBarrierOption);
       // No need for memory fence, thanks to the x86 memory model.
       SlowPathCode* slow_path = new (codegen_->GetScopedAllocator()) LoadStringSlowPathX86(load);
       codegen_->AddSlowPath(slow_path);
@@ -7365,7 +7465,7 @@
       Label* fixup_label = codegen_->NewJitRootStringPatch(
           load->GetDexFile(), load->GetStringIndex(), load->GetString());
       // /* GcRoot<mirror::String> */ out = *address
-      GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, kCompilerReadBarrierOption);
+      GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, gCompilerReadBarrierOption);
       return;
     }
     default:
@@ -7416,7 +7516,7 @@
 
 // Temp is used for read barrier.
 static size_t NumberOfInstanceOfTemps(TypeCheckKind type_check_kind) {
-  if (kEmitCompilerReadBarrier &&
+  if (gUseReadBarrier &&
       !kUseBakerReadBarrier &&
       (type_check_kind == TypeCheckKind::kAbstractClassCheck ||
        type_check_kind == TypeCheckKind::kClassHierarchyCheck ||
@@ -7466,9 +7566,9 @@
   }
   locations->SetInAt(0, Location::RequiresRegister());
   if (type_check_kind == TypeCheckKind::kBitstringCheck) {
-    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
-    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)->AsConstant()));
-    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)->AsConstant()));
+    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
+    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)));
+    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)));
   } else {
     locations->SetInAt(1, Location::Any());
   }
@@ -7734,9 +7834,9 @@
     // a memory address.
     locations->SetInAt(1, Location::RequiresRegister());
   } else if (type_check_kind == TypeCheckKind::kBitstringCheck) {
-    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
-    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)->AsConstant()));
-    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)->AsConstant()));
+    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
+    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)));
+    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)));
   } else {
     locations->SetInAt(1, Location::Any());
   }
@@ -8188,7 +8288,7 @@
     ReadBarrierOption read_barrier_option) {
   Register out_reg = out.AsRegister<Register>();
   if (read_barrier_option == kWithReadBarrier) {
-    CHECK(kEmitCompilerReadBarrier);
+    CHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       // Load with fast path based Baker's read barrier.
       // /* HeapReference<Object> */ out = *(out + offset)
@@ -8222,7 +8322,7 @@
   Register out_reg = out.AsRegister<Register>();
   Register obj_reg = obj.AsRegister<Register>();
   if (read_barrier_option == kWithReadBarrier) {
-    CHECK(kEmitCompilerReadBarrier);
+    CHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       // Load with fast path based Baker's read barrier.
       // /* HeapReference<Object> */ out = *(obj + offset)
@@ -8250,7 +8350,7 @@
     ReadBarrierOption read_barrier_option) {
   Register root_reg = root.AsRegister<Register>();
   if (read_barrier_option == kWithReadBarrier) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       // Fast path implementation of art::ReadBarrier::BarrierForRoot when
       // Baker's read barrier are used:
@@ -8314,7 +8414,7 @@
                                                              Register obj,
                                                              uint32_t offset,
                                                              bool needs_null_check) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   // /* HeapReference<Object> */ ref = *(obj + offset)
@@ -8328,7 +8428,7 @@
                                                              uint32_t data_offset,
                                                              Location index,
                                                              bool needs_null_check) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   static_assert(
@@ -8347,7 +8447,7 @@
                                                                  bool needs_null_check,
                                                                  bool always_update_field,
                                                                  Register* temp) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   // In slow path based read barriers, the read barrier call is
@@ -8428,7 +8528,7 @@
                                                Location obj,
                                                uint32_t offset,
                                                Location index) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
 
   // Insert a slow path based read barrier *after* the reference load.
   //
@@ -8455,7 +8555,7 @@
                                                     Location obj,
                                                     uint32_t offset,
                                                     Location index) {
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     // Baker's read barriers shall be handled by the fast path
     // (CodeGeneratorX86::GenerateReferenceLoadWithBakerReadBarrier).
     DCHECK(!kUseBakerReadBarrier);
@@ -8470,7 +8570,7 @@
 void CodeGeneratorX86::GenerateReadBarrierForRootSlow(HInstruction* instruction,
                                                       Location out,
                                                       Location root) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
 
   // Insert a slow path based read barrier *after* the GC root load.
   //
diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h
index 75c5ceb..d27155f 100644
--- a/compiler/optimizing/code_generator_x86.h
+++ b/compiler/optimizing/code_generator_x86.h
@@ -19,6 +19,7 @@
 
 #include "arch/x86/instruction_set_features_x86.h"
 #include "base/enums.h"
+#include "base/macros.h"
 #include "code_generator.h"
 #include "dex/dex_file_types.h"
 #include "driver/compiler_options.h"
@@ -26,7 +27,7 @@
 #include "parallel_move_resolver.h"
 #include "utils/x86/assembler_x86.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 // Use a local definition to prevent copying mistakes.
@@ -47,6 +48,61 @@
 static constexpr size_t kRuntimeParameterFpuRegistersLength =
     arraysize(kRuntimeParameterFpuRegisters);
 
+#define UNIMPLEMENTED_INTRINSIC_LIST_X86(V) \
+  V(MathRoundDouble)                        \
+  V(FloatIsInfinite)                        \
+  V(DoubleIsInfinite)                       \
+  V(IntegerHighestOneBit)                   \
+  V(LongHighestOneBit)                      \
+  V(LongDivideUnsigned)                     \
+  V(CRC32Update)                            \
+  V(CRC32UpdateBytes)                       \
+  V(CRC32UpdateByteBuffer)                  \
+  V(FP16ToFloat)                            \
+  V(FP16ToHalf)                             \
+  V(FP16Floor)                              \
+  V(FP16Ceil)                               \
+  V(FP16Rint)                               \
+  V(FP16Greater)                            \
+  V(FP16GreaterEquals)                      \
+  V(FP16Less)                               \
+  V(FP16LessEquals)                         \
+  V(FP16Compare)                            \
+  V(FP16Min)                                \
+  V(FP16Max)                                \
+  V(MathMultiplyHigh)                       \
+  V(StringStringIndexOf)                    \
+  V(StringStringIndexOfAfter)               \
+  V(StringBufferAppend)                     \
+  V(StringBufferLength)                     \
+  V(StringBufferToString)                   \
+  V(StringBuilderAppendObject)              \
+  V(StringBuilderAppendString)              \
+  V(StringBuilderAppendCharSequence)        \
+  V(StringBuilderAppendCharArray)           \
+  V(StringBuilderAppendBoolean)             \
+  V(StringBuilderAppendChar)                \
+  V(StringBuilderAppendInt)                 \
+  V(StringBuilderAppendLong)                \
+  V(StringBuilderAppendFloat)               \
+  V(StringBuilderAppendDouble)              \
+  V(StringBuilderLength)                    \
+  V(StringBuilderToString)                  \
+  /* 1.8 */                                 \
+  V(UnsafeGetAndAddInt)                     \
+  V(UnsafeGetAndAddLong)                    \
+  V(UnsafeGetAndSetInt)                     \
+  V(UnsafeGetAndSetLong)                    \
+  V(UnsafeGetAndSetObject)                  \
+  V(MethodHandleInvokeExact)                \
+  V(MethodHandleInvoke)                     \
+  /* OpenJDK 11 */                          \
+  V(JdkUnsafeGetAndAddInt)                  \
+  V(JdkUnsafeGetAndAddLong)                 \
+  V(JdkUnsafeGetAndSetInt)                  \
+  V(JdkUnsafeGetAndSetLong)                 \
+  V(JdkUnsafeGetAndSetObject)
+
 class InvokeRuntimeCallingConvention : public CallingConvention<Register, XmmRegister> {
  public:
   InvokeRuntimeCallingConvention()
@@ -196,7 +252,9 @@
   void HandleInvoke(HInvoke* invoke);
   void HandleCondition(HCondition* condition);
   void HandleShift(HBinaryOperation* instruction);
-  void HandleFieldSet(HInstruction* instruction, const FieldInfo& field_info);
+  void HandleFieldSet(HInstruction* instruction,
+                      const FieldInfo& field_info,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
   bool CpuHasAvxFeatureFlag();
   bool CpuHasAvx2FeatureFlag();
@@ -249,7 +307,8 @@
                       Address field_addr,
                       Register base,
                       bool is_volatile,
-                      bool value_can_be_null);
+                      bool value_can_be_null,
+                      WriteBarrierKind write_barrier_kind);
 
  private:
   // Generate code for the given suspend check. If not null, `successor`
@@ -279,7 +338,8 @@
 
   void HandleFieldSet(HInstruction* instruction,
                       const FieldInfo& field_info,
-                      bool value_can_be_null);
+                      bool value_can_be_null,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
 
   // Generate a heap reference load using one register `out`:
@@ -519,11 +579,8 @@
   void EmitJitRootPatches(uint8_t* code, const uint8_t* roots_data) override;
 
   // Emit a write barrier.
-  void MarkGCCard(Register temp,
-                  Register card,
-                  Register object,
-                  Register value,
-                  bool value_can_be_null);
+  void MarkGCCard(
+      Register temp, Register card, Register object, Register value, bool emit_null_check);
 
   void GenerateMemoryBarrier(MemBarrierKind kind);
 
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 511917a..eea6b20 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -21,7 +21,6 @@
 #include "class_root-inl.h"
 #include "class_table.h"
 #include "code_generator_utils.h"
-#include "compiled_method.h"
 #include "entrypoints/quick/quick_entrypoints.h"
 #include "gc/accounting/card_table.h"
 #include "gc/space/image_space.h"
@@ -37,6 +36,7 @@
 #include "mirror/class-inl.h"
 #include "mirror/object_reference.h"
 #include "mirror/var_handle.h"
+#include "optimizing/nodes.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
 #include "utils/assembler.h"
@@ -45,7 +45,7 @@
 #include "utils/x86_64/constants_x86_64.h"
 #include "utils/x86_64/managed_register_x86_64.h"
 
-namespace art {
+namespace art HIDDEN {
 
 template<class MirrorType>
 class GcRoot;
@@ -510,7 +510,7 @@
       : SlowPathCode(instruction),
         ref_(ref),
         unpoison_ref_before_marking_(unpoison_ref_before_marking) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
   }
 
   const char* GetDescription() const override { return "ReadBarrierMarkSlowPathX86_64"; }
@@ -601,7 +601,7 @@
         unpoison_ref_before_marking_(unpoison_ref_before_marking),
         temp1_(temp1),
         temp2_(temp2) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
   }
 
   const char* GetDescription() const override {
@@ -761,7 +761,7 @@
         obj_(obj),
         offset_(offset),
         index_(index) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     // If `obj` is equal to `out` or `ref`, it means the initial
     // object has been overwritten by (or after) the heap object
     // reference load to be instrumented, e.g.:
@@ -937,7 +937,7 @@
  public:
   ReadBarrierForRootSlowPathX86_64(HInstruction* instruction, Location out, Location root)
       : SlowPathCode(instruction), out_(out), root_(root) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
   }
 
   void EmitNativeCode(CodeGenerator* codegen) override {
@@ -986,6 +986,10 @@
         (instruction_->IsMethodEntryHook()) ? kQuickMethodEntryHook : kQuickMethodExitHook;
     __ Bind(GetEntryLabel());
     SaveLiveRegisters(codegen, locations);
+    if (instruction_->IsMethodExitHook()) {
+      // Load FrameSize to pass to the exit hook.
+      __ movq(CpuRegister(R8), Immediate(codegen->GetFrameSize()));
+    }
     x86_64_codegen->InvokeRuntime(entry_point, instruction_, instruction_->GetDexPc(), this);
     RestoreLiveRegisters(codegen, locations);
     __ jmp(GetExitLabel());
@@ -1490,6 +1494,33 @@
   __ gs()->call(Address::Absolute(entry_point_offset, /* no_rip= */ true));
 }
 
+namespace detail {
+// Mark which intrinsics we don't have handcrafted code for.
+template <Intrinsics T>
+struct IsUnimplemented {
+  bool is_unimplemented = false;
+};
+
+#define TRUE_OVERRIDE(Name)                     \
+  template <>                                   \
+  struct IsUnimplemented<Intrinsics::k##Name> { \
+    bool is_unimplemented = true;               \
+  };
+UNIMPLEMENTED_INTRINSIC_LIST_X86_64(TRUE_OVERRIDE)
+#undef TRUE_OVERRIDE
+
+#include "intrinsics_list.h"
+static constexpr bool kIsIntrinsicUnimplemented[] = {
+  false,  // kNone
+#define IS_UNIMPLEMENTED(Intrinsic, ...) \
+  IsUnimplemented<Intrinsics::k##Intrinsic>().is_unimplemented,
+  INTRINSICS_LIST(IS_UNIMPLEMENTED)
+#undef IS_UNIMPLEMENTED
+};
+#undef INTRINSICS_LIST
+
+}  // namespace detail
+
 static constexpr int kNumberOfCpuRegisterPairs = 0;
 // Use a fake return address register to mimic Quick.
 static constexpr Register kFakeReturnRegister = Register(kLastCpuRegister + 1);
@@ -1506,7 +1537,8 @@
                     ComputeRegisterMask(reinterpret_cast<const int*>(kFpuCalleeSaves),
                                         arraysize(kFpuCalleeSaves)),
                     compiler_options,
-                    stats),
+                    stats,
+                    ArrayRef<const bool>(detail::kIsIntrinsicUnimplemented)),
       block_labels_(nullptr),
       location_builder_(graph, this),
       instruction_visitor_(graph, this),
@@ -1561,9 +1593,22 @@
       new (codegen_->GetScopedAllocator()) MethodEntryExitHooksSlowPathX86_64(instruction);
   codegen_->AddSlowPath(slow_path);
 
+  if (instruction->IsMethodExitHook()) {
+    // Check if we are required to check if the caller needs a deoptimization. Strictly speaking it
+    // would be sufficient to check if CheckCallerForDeopt bit is set. Though it is faster to check
+    // if it is just non-zero. kCHA bit isn't used in debuggable runtimes as cha optimization is
+    // disabled in debuggable runtime. The other bit is used when this method itself requires a
+    // deoptimization due to redefinition. So it is safe to just check for non-zero value here.
+    __ cmpl(Address(CpuRegister(RSP), codegen_->GetStackOffsetOfShouldDeoptimizeFlag()),
+            Immediate(0));
+    __ j(kNotEqual, slow_path->GetEntryLabel());
+  }
+
   uint64_t address = reinterpret_cast64<uint64_t>(Runtime::Current()->GetInstrumentation());
-  int offset = instrumentation::Instrumentation::NeedsEntryExitHooksOffset().Int32Value();
-  __ movq(CpuRegister(TMP), Immediate(address + offset));
+  MemberOffset  offset = instruction->IsMethodExitHook() ?
+      instrumentation::Instrumentation::HaveMethodExitListenersOffset()
+      : instrumentation::Instrumentation::HaveMethodEntryListenersOffset();
+  __ movq(CpuRegister(TMP), Immediate(address + offset.Int32Value()));
   __ cmpb(Address(CpuRegister(TMP), 0), Immediate(0));
   __ j(kNotEqual, slow_path->GetEntryLabel());
   __ Bind(slow_path->GetExitLabel());
@@ -1653,6 +1698,44 @@
 
 void CodeGeneratorX86_64::GenerateFrameEntry() {
   __ cfi().SetCurrentCFAOffset(kX86_64WordSize);  // return address
+
+  // Check if we need to generate the clinit check. We will jump to the
+  // resolution stub if the class is not initialized and the executing thread is
+  // not the thread initializing it.
+  // We do this before constructing the frame to get the correct stack trace if
+  // an exception is thrown.
+  if (GetCompilerOptions().ShouldCompileWithClinitCheck(GetGraph()->GetArtMethod())) {
+    NearLabel resolution;
+    // Check if we're visibly initialized.
+
+    // We don't emit a read barrier here to save on code size. We rely on the
+    // resolution trampoline to do a suspend check before re-entering this code.
+    __ movl(CpuRegister(TMP),
+            Address(CpuRegister(kMethodRegisterArgument),
+                    ArtMethod::DeclaringClassOffset().Int32Value()));
+    __ cmpb(Address(CpuRegister(TMP),  status_byte_offset),
+            Immediate(shifted_visibly_initialized_value));
+    __ j(kAboveEqual, &frame_entry_label_);
+
+    // Check if we're initializing and the thread initializing is the one
+    // executing the code.
+    __ cmpb(Address(CpuRegister(TMP),  status_byte_offset), Immediate(shifted_initializing_value));
+    __ j(kBelow, &resolution);
+
+    __ movl(CpuRegister(TMP),
+            Address(CpuRegister(TMP), mirror::Class::ClinitThreadIdOffset().Int32Value()));
+    __ gs()->cmpl(
+        CpuRegister(TMP),
+        Address::Absolute(Thread::TidOffset<kX86_64PointerSize>().Int32Value(), /*no_rip=*/ true));
+    __ j(kEqual, &frame_entry_label_);
+    __ Bind(&resolution);
+
+    // Jump to the resolution stub.
+    ThreadOffset64 entrypoint_offset =
+        GetThreadOffset<kX86_64PointerSize>(kQuickQuickResolutionTrampoline);
+    __ gs()->jmp(Address::Absolute(entrypoint_offset, /*no_rip=*/ true));
+  }
+
   __ Bind(&frame_entry_label_);
   bool skip_overflow_check = IsLeafMethod()
       && !FrameNeedsStackCheck(GetFrameSize(), InstructionSet::kX86_64);
@@ -2274,12 +2357,12 @@
   }
 }
 
-void LocationsBuilderX86_64::VisitNativeDebugInfo(HNativeDebugInfo* info) {
-  new (GetGraph()->GetAllocator()) LocationSummary(info);
+void LocationsBuilderX86_64::VisitNop(HNop* nop) {
+  new (GetGraph()->GetAllocator()) LocationSummary(nop);
 }
 
-void InstructionCodeGeneratorX86_64::VisitNativeDebugInfo(HNativeDebugInfo*) {
-  // MaybeRecordNativeDebugInfo is already called implicitly in CodeGenerator::Compile.
+void InstructionCodeGeneratorX86_64::VisitNop(HNop*) {
+  // The environment recording already happened in CodeGenerator::Compile.
 }
 
 void CodeGeneratorX86_64::IncreaseFrame(size_t adjustment) {
@@ -5013,7 +5096,7 @@
          instruction->IsPredicatedInstanceFieldGet());
 
   bool object_field_get_with_read_barrier =
-      kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+      gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
   bool is_predicated = instruction->IsPredicatedInstanceFieldGet();
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(instruction,
@@ -5064,7 +5147,7 @@
 
   if (load_type == DataType::Type::kReference) {
     // /* HeapReference<Object> */ out = *(base + offset)
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       // Note that a potential implicit null check is handled in this
       // CodeGeneratorX86_64::GenerateFieldLoadWithBakerReadBarrier call.
       codegen_->GenerateFieldLoadWithBakerReadBarrier(
@@ -5119,6 +5202,9 @@
       locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
     }
   }
+
+  // TODO(solanes): We could reduce the temp usage but it requires some non-trivial refactoring of
+  // InstructionCodeGeneratorX86_64::HandleFieldSet.
   if (needs_write_barrier) {
     // Temporary registers for the write barrier.
     locations->AddTemp(Location::RequiresRegister());
@@ -5180,7 +5266,8 @@
                                                     bool is_volatile,
                                                     bool is_atomic,
                                                     bool value_can_be_null,
-                                                    bool byte_swap) {
+                                                    bool byte_swap,
+                                                    WriteBarrierKind write_barrier_kind) {
   LocationSummary* locations = instruction->GetLocations();
   Location value = locations->InAt(value_index);
 
@@ -5298,10 +5385,16 @@
     codegen_->MaybeRecordImplicitNullCheck(instruction);
   }
 
-  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(value_index))) {
+  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(value_index)) &&
+      write_barrier_kind != WriteBarrierKind::kDontEmit) {
     CpuRegister temp = locations->GetTemp(0).AsRegister<CpuRegister>();
     CpuRegister card = locations->GetTemp(extra_temp_index).AsRegister<CpuRegister>();
-    codegen_->MarkGCCard(temp, card, base, value.AsRegister<CpuRegister>(), value_can_be_null);
+    codegen_->MarkGCCard(
+        temp,
+        card,
+        base,
+        value.AsRegister<CpuRegister>(),
+        value_can_be_null && write_barrier_kind == WriteBarrierKind::kEmitWithNullCheck);
   }
 
   if (is_volatile) {
@@ -5311,7 +5404,8 @@
 
 void InstructionCodeGeneratorX86_64::HandleFieldSet(HInstruction* instruction,
                                                     const FieldInfo& field_info,
-                                                    bool value_can_be_null) {
+                                                    bool value_can_be_null,
+                                                    WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
 
   LocationSummary* locations = instruction->GetLocations();
@@ -5336,7 +5430,9 @@
                  base,
                  is_volatile,
                  /*is_atomic=*/ false,
-                 value_can_be_null);
+                 value_can_be_null,
+                 /*byte_swap=*/ false,
+                 write_barrier_kind);
 
   if (is_predicated) {
     __ Bind(&pred_is_null);
@@ -5348,7 +5444,10 @@
 }
 
 void InstructionCodeGeneratorX86_64::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderX86_64::VisitPredicatedInstanceFieldGet(
@@ -5388,7 +5487,10 @@
 }
 
 void InstructionCodeGeneratorX86_64::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderX86_64::VisitStringBuilderAppend(HStringBuilderAppend* instruction) {
@@ -5513,7 +5615,7 @@
 
 void LocationsBuilderX86_64::VisitArrayGet(HArrayGet* instruction) {
   bool object_array_get_with_read_barrier =
-      kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+      gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(instruction,
                                                        object_array_get_with_read_barrier
@@ -5551,7 +5653,7 @@
         "art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
     // /* HeapReference<Object> */ out =
     //     *(obj + data_offset + index * sizeof(HeapReference<Object>))
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       // Note that a potential implicit null check is handled in this
       // CodeGeneratorX86_64::GenerateArrayLoadWithBakerReadBarrier call.
       codegen_->GenerateArrayLoadWithBakerReadBarrier(
@@ -5619,9 +5721,12 @@
   }
 
   if (needs_write_barrier) {
-    // Temporary registers for the write barrier.
-    locations->AddTemp(Location::RequiresRegister());  // Possibly used for ref. poisoning too.
+    // Used by reference poisoning or emitting write barrier.
     locations->AddTemp(Location::RequiresRegister());
+    if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+      // Only used when emitting a write barrier.
+      locations->AddTemp(Location::RequiresRegister());
+    }
   }
 }
 
@@ -5739,9 +5844,16 @@
         }
       }
 
-      CpuRegister card = locations->GetTemp(1).AsRegister<CpuRegister>();
-      codegen_->MarkGCCard(
-          temp, card, array, value.AsRegister<CpuRegister>(), /* value_can_be_null= */ false);
+      if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+        DCHECK_EQ(instruction->GetWriteBarrierKind(), WriteBarrierKind::kEmitNoNullCheck)
+            << " Already null checked so we shouldn't do it again.";
+        CpuRegister card = locations->GetTemp(1).AsRegister<CpuRegister>();
+        codegen_->MarkGCCard(temp,
+                             card,
+                             array,
+                             value.AsRegister<CpuRegister>(),
+                             /* emit_null_check= */ false);
+      }
 
       if (can_value_be_null) {
         DCHECK(do_store.IsLinked());
@@ -5940,9 +6052,9 @@
                                      CpuRegister card,
                                      CpuRegister object,
                                      CpuRegister value,
-                                     bool value_can_be_null) {
+                                     bool emit_null_check) {
   NearLabel is_null;
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ testl(value, value);
     __ j(kEqual, &is_null);
   }
@@ -5967,7 +6079,7 @@
   // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
   // (no need to explicitly load `kCardDirty` as an immediate value).
   __ movb(Address(temp, card, TIMES_1, 0), card);
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ Bind(&is_null);
   }
 }
@@ -6282,12 +6394,6 @@
 
 void InstructionCodeGeneratorX86_64::GenerateClassInitializationCheck(
     SlowPathCode* slow_path, CpuRegister class_reg) {
-  constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
-  const size_t status_byte_offset =
-      mirror::Class::StatusOffset().SizeValue() + (status_lsb_position / kBitsPerByte);
-  constexpr uint32_t shifted_visibly_initialized_value =
-      enum_cast<uint32_t>(ClassStatus::kVisiblyInitialized) << (status_lsb_position % kBitsPerByte);
-
   __ cmpb(Address(class_reg,  status_byte_offset), Immediate(shifted_visibly_initialized_value));
   __ j(kBelow, slow_path->GetEntryLabel());
   __ Bind(slow_path->GetExitLabel());
@@ -6352,7 +6458,7 @@
             load_kind == HLoadClass::LoadKind::kBssEntryPublic ||
                 load_kind == HLoadClass::LoadKind::kBssEntryPackage);
 
-  const bool requires_read_barrier = kEmitCompilerReadBarrier && !cls->IsInBootImage();
+  const bool requires_read_barrier = gUseReadBarrier && !cls->IsInBootImage();
   LocationSummary::CallKind call_kind = (cls->NeedsEnvironment() || requires_read_barrier)
       ? LocationSummary::kCallOnSlowPath
       : LocationSummary::kNoCall;
@@ -6366,7 +6472,7 @@
   }
   locations->SetOut(Location::RequiresRegister());
   if (load_kind == HLoadClass::LoadKind::kBssEntry) {
-    if (!kUseReadBarrier || kUseBakerReadBarrier) {
+    if (!gUseReadBarrier || kUseBakerReadBarrier) {
       // Rely on the type resolution and/or initialization to save everything.
       locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
     } else {
@@ -6403,7 +6509,7 @@
 
   const ReadBarrierOption read_barrier_option = cls->IsInBootImage()
       ? kWithoutReadBarrier
-      : kCompilerReadBarrierOption;
+      : gCompilerReadBarrierOption;
   bool generate_null_check = false;
   switch (load_kind) {
     case HLoadClass::LoadKind::kReferrersClass: {
@@ -6550,7 +6656,7 @@
   } else {
     locations->SetOut(Location::RequiresRegister());
     if (load->GetLoadKind() == HLoadString::LoadKind::kBssEntry) {
-      if (!kUseReadBarrier || kUseBakerReadBarrier) {
+      if (!gUseReadBarrier || kUseBakerReadBarrier) {
         // Rely on the pResolveString to save everything.
         locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
       } else {
@@ -6598,7 +6704,7 @@
                                           /* no_rip= */ false);
       Label* fixup_label = codegen_->NewStringBssEntryPatch(load);
       // /* GcRoot<mirror::Class> */ out = *address  /* PC-relative */
-      GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, kCompilerReadBarrierOption);
+      GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, gCompilerReadBarrierOption);
       // No need for memory fence, thanks to the x86-64 memory model.
       SlowPathCode* slow_path = new (codegen_->GetScopedAllocator()) LoadStringSlowPathX86_64(load);
       codegen_->AddSlowPath(slow_path);
@@ -6619,7 +6725,7 @@
       Label* fixup_label = codegen_->NewJitRootStringPatch(
           load->GetDexFile(), load->GetStringIndex(), load->GetString());
       // /* GcRoot<mirror::String> */ out = *address
-      GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, kCompilerReadBarrierOption);
+      GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, gCompilerReadBarrierOption);
       return;
     }
     default:
@@ -6672,7 +6778,7 @@
 
 // Temp is used for read barrier.
 static size_t NumberOfInstanceOfTemps(TypeCheckKind type_check_kind) {
-  if (kEmitCompilerReadBarrier &&
+  if (gUseReadBarrier &&
       !kUseBakerReadBarrier &&
       (type_check_kind == TypeCheckKind::kAbstractClassCheck ||
        type_check_kind == TypeCheckKind::kClassHierarchyCheck ||
@@ -6722,9 +6828,9 @@
   }
   locations->SetInAt(0, Location::RequiresRegister());
   if (type_check_kind == TypeCheckKind::kBitstringCheck) {
-    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
-    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)->AsConstant()));
-    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)->AsConstant()));
+    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
+    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)));
+    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)));
   } else {
     locations->SetInAt(1, Location::Any());
   }
@@ -7000,9 +7106,9 @@
     // a memory address.
     locations->SetInAt(1, Location::RequiresRegister());
   } else if (type_check_kind == TypeCheckKind::kBitstringCheck) {
-    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)->AsConstant()));
-    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)->AsConstant()));
-    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)->AsConstant()));
+    locations->SetInAt(1, Location::ConstantLocation(instruction->InputAt(1)));
+    locations->SetInAt(2, Location::ConstantLocation(instruction->InputAt(2)));
+    locations->SetInAt(3, Location::ConstantLocation(instruction->InputAt(3)));
   } else {
     locations->SetInAt(1, Location::Any());
   }
@@ -7426,7 +7532,7 @@
     ReadBarrierOption read_barrier_option) {
   CpuRegister out_reg = out.AsRegister<CpuRegister>();
   if (read_barrier_option == kWithReadBarrier) {
-    CHECK(kEmitCompilerReadBarrier);
+    CHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       // Load with fast path based Baker's read barrier.
       // /* HeapReference<Object> */ out = *(out + offset)
@@ -7460,7 +7566,7 @@
   CpuRegister out_reg = out.AsRegister<CpuRegister>();
   CpuRegister obj_reg = obj.AsRegister<CpuRegister>();
   if (read_barrier_option == kWithReadBarrier) {
-    CHECK(kEmitCompilerReadBarrier);
+    CHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       // Load with fast path based Baker's read barrier.
       // /* HeapReference<Object> */ out = *(obj + offset)
@@ -7488,7 +7594,7 @@
     ReadBarrierOption read_barrier_option) {
   CpuRegister root_reg = root.AsRegister<CpuRegister>();
   if (read_barrier_option == kWithReadBarrier) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     if (kUseBakerReadBarrier) {
       // Fast path implementation of art::ReadBarrier::BarrierForRoot when
       // Baker's read barrier are used:
@@ -7552,7 +7658,7 @@
                                                                 CpuRegister obj,
                                                                 uint32_t offset,
                                                                 bool needs_null_check) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   // /* HeapReference<Object> */ ref = *(obj + offset)
@@ -7566,7 +7672,7 @@
                                                                 uint32_t data_offset,
                                                                 Location index,
                                                                 bool needs_null_check) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   static_assert(
@@ -7586,7 +7692,7 @@
                                                                     bool always_update_field,
                                                                     CpuRegister* temp1,
                                                                     CpuRegister* temp2) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(kUseBakerReadBarrier);
 
   // In slow path based read barriers, the read barrier call is
@@ -7668,7 +7774,7 @@
                                                   Location obj,
                                                   uint32_t offset,
                                                   Location index) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
 
   // Insert a slow path based read barrier *after* the reference load.
   //
@@ -7695,7 +7801,7 @@
                                                        Location obj,
                                                        uint32_t offset,
                                                        Location index) {
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     // Baker's read barriers shall be handled by the fast path
     // (CodeGeneratorX86_64::GenerateReferenceLoadWithBakerReadBarrier).
     DCHECK(!kUseBakerReadBarrier);
@@ -7710,7 +7816,7 @@
 void CodeGeneratorX86_64::GenerateReadBarrierForRootSlow(HInstruction* instruction,
                                                          Location out,
                                                          Location root) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
 
   // Insert a slow path based read barrier *after* the GC root load.
   //
diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h
index 39a72d8..dff2e79 100644
--- a/compiler/optimizing/code_generator_x86_64.h
+++ b/compiler/optimizing/code_generator_x86_64.h
@@ -18,13 +18,14 @@
 #define ART_COMPILER_OPTIMIZING_CODE_GENERATOR_X86_64_H_
 
 #include "arch/x86_64/instruction_set_features_x86_64.h"
+#include "base/macros.h"
 #include "code_generator.h"
 #include "driver/compiler_options.h"
 #include "nodes.h"
 #include "parallel_move_resolver.h"
 #include "utils/x86_64/assembler_x86_64.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 // Use a local definition to prevent copying mistakes.
@@ -52,6 +53,53 @@
 // these are not clobbered by any direct call to native code (such as math intrinsics).
 static constexpr FloatRegister non_volatile_xmm_regs[] = { XMM12, XMM13, XMM14, XMM15 };
 
+#define UNIMPLEMENTED_INTRINSIC_LIST_X86_64(V) \
+  V(CRC32Update)                               \
+  V(CRC32UpdateBytes)                          \
+  V(CRC32UpdateByteBuffer)                     \
+  V(FP16ToFloat)                               \
+  V(FP16ToHalf)                                \
+  V(FP16Floor)                                 \
+  V(FP16Ceil)                                  \
+  V(FP16Rint)                                  \
+  V(FP16Greater)                               \
+  V(FP16GreaterEquals)                         \
+  V(FP16Less)                                  \
+  V(FP16LessEquals)                            \
+  V(FP16Compare)                               \
+  V(FP16Min)                                   \
+  V(FP16Max)                                   \
+  V(StringStringIndexOf)                       \
+  V(StringStringIndexOfAfter)                  \
+  V(StringBufferAppend)                        \
+  V(StringBufferLength)                        \
+  V(StringBufferToString)                      \
+  V(StringBuilderAppendObject)                 \
+  V(StringBuilderAppendString)                 \
+  V(StringBuilderAppendCharSequence)           \
+  V(StringBuilderAppendCharArray)              \
+  V(StringBuilderAppendBoolean)                \
+  V(StringBuilderAppendChar)                   \
+  V(StringBuilderAppendInt)                    \
+  V(StringBuilderAppendLong)                   \
+  V(StringBuilderAppendFloat)                  \
+  V(StringBuilderAppendDouble)                 \
+  V(StringBuilderLength)                       \
+  V(StringBuilderToString)                     \
+  /* 1.8 */                                    \
+  V(UnsafeGetAndAddInt)                        \
+  V(UnsafeGetAndAddLong)                       \
+  V(UnsafeGetAndSetInt)                        \
+  V(UnsafeGetAndSetLong)                       \
+  V(UnsafeGetAndSetObject)                     \
+  V(MethodHandleInvokeExact)                   \
+  V(MethodHandleInvoke)                        \
+  /* OpenJDK 11 */                             \
+  V(JdkUnsafeGetAndAddInt)                     \
+  V(JdkUnsafeGetAndAddLong)                    \
+  V(JdkUnsafeGetAndSetInt)                     \
+  V(JdkUnsafeGetAndSetLong)                    \
+  V(JdkUnsafeGetAndSetObject)
 
 class InvokeRuntimeCallingConvention : public CallingConvention<Register, FloatRegister> {
  public:
@@ -250,7 +298,8 @@
                       bool is_volatile,
                       bool is_atomic,
                       bool value_can_be_null,
-                      bool byte_swap = false);
+                      bool byte_swap,
+                      WriteBarrierKind write_barrier_kind);
 
   void Bswap(Location value, DataType::Type type, CpuRegister* temp = nullptr);
 
@@ -273,7 +322,8 @@
 
   void HandleFieldSet(HInstruction* instruction,
                       const FieldInfo& field_info,
-                      bool value_can_be_null);
+                      bool value_can_be_null,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
 
   void GenerateMinMaxInt(LocationSummary* locations, bool is_min, DataType::Type type);
@@ -435,7 +485,7 @@
                   CpuRegister card,
                   CpuRegister object,
                   CpuRegister value,
-                  bool value_can_be_null);
+                  bool emit_null_check);
 
   void GenerateMemoryBarrier(MemBarrierKind kind);
 
diff --git a/compiler/optimizing/code_sinking.cc b/compiler/optimizing/code_sinking.cc
index 766bb01..d759a16 100644
--- a/compiler/optimizing/code_sinking.cc
+++ b/compiler/optimizing/code_sinking.cc
@@ -19,30 +19,55 @@
 #include "base/arena_bit_vector.h"
 #include "base/array_ref.h"
 #include "base/bit_vector-inl.h"
+#include "base/globals.h"
 #include "base/logging.h"
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 #include "common_dominator.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 bool CodeSinking::Run() {
-  HBasicBlock* exit = graph_->GetExitBlock();
-  if (exit == nullptr) {
+  if (graph_->GetExitBlock() == nullptr) {
     // Infinite loop, just bail.
     return false;
   }
+
+  UncommonBranchSinking();
+  ReturnSinking();
+  return true;
+}
+
+void CodeSinking::UncommonBranchSinking() {
+  HBasicBlock* exit = graph_->GetExitBlock();
+  DCHECK(exit != nullptr);
   // TODO(ngeoffray): we do not profile branches yet, so use throw instructions
   // as an indicator of an uncommon branch.
   for (HBasicBlock* exit_predecessor : exit->GetPredecessors()) {
     HInstruction* last = exit_predecessor->GetLastInstruction();
+
+    // TryBoundary instructions are sometimes inserted between the last instruction (e.g. Throw,
+    // Return) and Exit. We don't want to use that instruction for our "uncommon branch" heuristic
+    // because they are not as good an indicator as throwing branches, so we skip them and fetch the
+    // actual last instruction.
+    if (last->IsTryBoundary()) {
+      // We have an exit try boundary. Fetch the previous instruction.
+      DCHECK(!last->AsTryBoundary()->IsEntry());
+      if (last->GetPrevious() == nullptr) {
+        DCHECK(exit_predecessor->IsSingleTryBoundary());
+        exit_predecessor = exit_predecessor->GetSinglePredecessor();
+        last = exit_predecessor->GetLastInstruction();
+      } else {
+        last = last->GetPrevious();
+      }
+    }
+
     // Any predecessor of the exit that does not return, throws an exception.
     if (!last->IsReturn() && !last->IsReturnVoid()) {
       SinkCodeToUncommonBranch(exit_predecessor);
     }
   }
-  return true;
 }
 
 static bool IsInterestingInstruction(HInstruction* instruction) {
@@ -88,7 +113,7 @@
 
   // We can only store on local allocations. Other heap references can
   // be escaping. Note that allocations can escape too, but we only move
-  // allocations if their users can move to, or are in the list of
+  // allocations if their users can move too, or are in the list of
   // post dominated blocks.
   if (instruction->IsInstanceFieldSet()) {
     if (!instruction->InputAt(0)->IsNewInstance()) {
@@ -102,7 +127,7 @@
     }
   }
 
-  // Heap accesses cannot go pass instructions that have memory side effects, which
+  // Heap accesses cannot go past instructions that have memory side effects, which
   // we are not tracking here. Note that the load/store elimination optimization
   // runs before this optimization, and should have removed interesting ones.
   // In theory, we could handle loads of local allocations, but this is currently
@@ -171,7 +196,6 @@
   return false;
 }
 
-
 // Find the ideal position for moving `instruction`. If `filter` is true,
 // we filter out store instructions to that instruction, which are processed
 // first in the step (3) of the sinking algorithm.
@@ -210,56 +234,52 @@
     return nullptr;
   }
 
-  // Move to the first dominator not in a loop, if we can.
-  while (target_block->IsInLoop()) {
+  // Move to the first dominator not in a loop, if we can. We only do this if we are trying to hoist
+  // `instruction` out of a loop it wasn't a part of.
+  const HLoopInformation* loop_info = instruction->GetBlock()->GetLoopInformation();
+  while (target_block->IsInLoop() && target_block->GetLoopInformation() != loop_info) {
     if (!post_dominated.IsBitSet(target_block->GetDominator()->GetBlockId())) {
       break;
     }
     target_block = target_block->GetDominator();
     DCHECK(target_block != nullptr);
   }
-  const bool was_in_loop = target_block->IsInLoop();
 
-  // For throwing instructions we can move them into:
-  //   * Blocks that are not part of a try
-  //     * Catch blocks are suitable as well, as long as they are not part of an outer try.
-  //   * Blocks that are part of the same try that the instrucion was already in.
-  //
-  // We cannot move an instruction that can throw into a try that said instruction is not a part of
-  // already, as that would mean it will throw into a different catch block. If we detect that
-  // `target_block` is not a valid block to move `instruction` to, we traverse up the dominator tree
-  // to find if we have a suitable block.
-  while (instruction->CanThrow() && target_block->GetTryCatchInformation() != nullptr) {
-    if (target_block->IsCatchBlock()) {
-      // If the catch block has an xhandler, it means it is inside of an outer try.
-      const bool inside_of_another_try_catch = target_block->GetSuccessors().size() != 1;
-      if (!inside_of_another_try_catch) {
-        // If we have a catch block, it's okay to sink as long as that catch is not inside of
-        // another try catch.
-        break;
+  if (instruction->CanThrow()) {
+    // Consistency check: We shouldn't land in a loop if we weren't in one before traversing up the
+    // dominator tree regarding try catches.
+    const bool was_in_loop = target_block->IsInLoop();
+
+    // We cannot move an instruction that can throw into a try that said instruction is not a part
+    // of already, as that would mean it will throw into a different catch block. In short, for
+    // throwing instructions:
+    // * If the throwing instruction is part of a try, they should only be sunk into that same try.
+    // * If the throwing instruction is not part of any try, they shouldn't be sunk to any try.
+    if (instruction->GetBlock()->IsTryBlock()) {
+      const HTryBoundary& try_entry =
+          instruction->GetBlock()->GetTryCatchInformation()->GetTryEntry();
+      while (!(target_block->IsTryBlock() &&
+               try_entry.HasSameExceptionHandlersAs(
+                   target_block->GetTryCatchInformation()->GetTryEntry()))) {
+        target_block = target_block->GetDominator();
+        if (!post_dominated.IsBitSet(target_block->GetBlockId())) {
+          // We couldn't find a suitable block.
+          return nullptr;
+        }
       }
     } else {
-      DCHECK(target_block->IsTryBlock());
-      if (instruction->GetBlock()->IsTryBlock() &&
-          instruction->GetBlock()->GetTryCatchInformation()->GetTryEntry().GetId() ==
-              target_block->GetTryCatchInformation()->GetTryEntry().GetId()) {
-        // Sink within the same try block is allowed.
-        break;
+      // Search for the first block also not in a try block
+      while (target_block->IsTryBlock()) {
+        target_block = target_block->GetDominator();
+        if (!post_dominated.IsBitSet(target_block->GetBlockId())) {
+          // We couldn't find a suitable block.
+          return nullptr;
+        }
       }
     }
-    // We are now in the case where we would be moving to a different try. Since we don't want
-    // that, traverse up the dominator tree to find a suitable block.
-    if (!post_dominated.IsBitSet(target_block->GetDominator()->GetBlockId())) {
-      // We couldn't find a suitable block.
-      return nullptr;
-    }
-    target_block = target_block->GetDominator();
-    DCHECK(target_block != nullptr);
-  }
 
-  // We shouldn't land in a loop if we weren't in one before traversing up the dominator tree
-  // regarding try catches.
-  DCHECK_IMPLIES(target_block->IsInLoop(), was_in_loop);
+    DCHECK_IMPLIES(target_block->IsInLoop(), was_in_loop);
+  }
 
   // Find insertion position. No need to filter anymore, as we have found a
   // target block.
@@ -271,10 +291,21 @@
     }
   }
   for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) {
-    HInstruction* user = use.GetUser()->GetHolder();
+    HEnvironment* env = use.GetUser();
+    HInstruction* user = env->GetHolder();
     if (user->GetBlock() == target_block &&
         (insert_pos == nullptr || user->StrictlyDominates(insert_pos))) {
-      insert_pos = user;
+      if (target_block->IsCatchBlock() && target_block->GetFirstInstruction() == user) {
+        // We can sink the instructions past the environment setting Nop. If we do that, we have to
+        // remove said instruction from the environment. Since we know that we will be sinking the
+        // instruction to this block and there are no more instructions to consider, we can safely
+        // remove it from the environment now.
+        DCHECK(target_block->GetFirstInstruction()->IsNop());
+        env->RemoveAsUserOfInput(use.GetIndex());
+        env->SetRawEnvAt(use.GetIndex(), /*instruction=*/ nullptr);
+      } else {
+        insert_pos = user;
+      }
     }
   }
   if (insert_pos == nullptr) {
@@ -310,8 +341,8 @@
   ScopedArenaVector<HInstruction*> move_in_order(allocator.Adapter(kArenaAllocMisc));
 
   // Step (1): Visit post order to get a subset of blocks post dominated by `end_block`.
-  // TODO(ngeoffray): Getting the full set of post-dominated shoud be done by
-  // computint the post dominator tree, but that could be too time consuming. Also,
+  // TODO(ngeoffray): Getting the full set of post-dominated should be done by
+  // computing the post dominator tree, but that could be too time consuming. Also,
   // we should start the analysis from blocks dominated by an uncommon branch, but we
   // don't profile branches yet.
   bool found_block = false;
@@ -321,45 +352,43 @@
       post_dominated.SetBit(block->GetBlockId());
     } else if (found_block) {
       bool is_post_dominated = true;
-      if (block->GetSuccessors().empty()) {
-        // We currently bail for loops.
-        is_post_dominated = false;
-      } else {
-        // BasicBlock that are try entries look like this:
-        //   BasicBlock i:
-        //     instr 1
-        //     ...
-        //     instr N
-        //     TryBoundary kind:entry ---Try begins here---
-        //
-        // Due to how our BasicBlocks are structured, BasicBlock i will have an xhandler successor
-        // since we are starting a try. If we use `GetSuccessors` for this case, we will check if
-        // the catch block is post_dominated.
-        //
-        // However, this catch block doesn't matter: when we sink the instruction into that
-        // BasicBlock i, we do it before the TryBoundary (i.e. outside of the try and outside the
-        // catch's domain). We can ignore catch blocks using `GetNormalSuccessors` to sink code
-        // right before the start of a try block.
-        //
-        // On the other side of the coin, BasicBlock that are try exits look like this:
-        //   BasicBlock j:
-        //     instr 1
-        //     ...
-        //     instr N
-        //     TryBoundary kind:exit ---Try ends here---
-        //
-        // If we sink to these basic blocks we would be sinking inside of the try so we would like
-        // to check the catch block for post dominance.
-        const bool ends_with_try_boundary_entry =
-            block->EndsWithTryBoundary() && block->GetLastInstruction()->AsTryBoundary()->IsEntry();
-        ArrayRef<HBasicBlock* const> successors =
-            ends_with_try_boundary_entry ? block->GetNormalSuccessors() :
-                                           ArrayRef<HBasicBlock* const>(block->GetSuccessors());
-        for (HBasicBlock* successor : successors) {
-          if (!post_dominated.IsBitSet(successor->GetBlockId())) {
-            is_post_dominated = false;
-            break;
-          }
+      DCHECK_NE(block, graph_->GetExitBlock())
+          << "We shouldn't encounter the exit block after `end_block`.";
+
+      // BasicBlock that are try entries look like this:
+      //   BasicBlock i:
+      //     instr 1
+      //     ...
+      //     instr N
+      //     TryBoundary kind:entry ---Try begins here---
+      //
+      // Due to how our BasicBlocks are structured, BasicBlock i will have an xhandler successor
+      // since we are starting a try. If we use `GetSuccessors` for this case, we will check if
+      // the catch block is post_dominated.
+      //
+      // However, this catch block doesn't matter: when we sink the instruction into that
+      // BasicBlock i, we do it before the TryBoundary (i.e. outside of the try and outside the
+      // catch's domain). We can ignore catch blocks using `GetNormalSuccessors` to sink code
+      // right before the start of a try block.
+      //
+      // On the other side of the coin, BasicBlock that are try exits look like this:
+      //   BasicBlock j:
+      //     instr 1
+      //     ...
+      //     instr N
+      //     TryBoundary kind:exit ---Try ends here---
+      //
+      // If we sink to these basic blocks we would be sinking inside of the try so we would like
+      // to check the catch block for post dominance.
+      const bool ends_with_try_boundary_entry =
+          block->EndsWithTryBoundary() && block->GetLastInstruction()->AsTryBoundary()->IsEntry();
+      ArrayRef<HBasicBlock* const> successors =
+          ends_with_try_boundary_entry ? block->GetNormalSuccessors() :
+                                         ArrayRef<HBasicBlock* const>(block->GetSuccessors());
+      for (HBasicBlock* successor : successors) {
+        if (!post_dominated.IsBitSet(successor->GetBlockId())) {
+          is_post_dominated = false;
+          break;
         }
       }
       if (is_post_dominated) {
@@ -509,4 +538,79 @@
   }
 }
 
+void CodeSinking::ReturnSinking() {
+  HBasicBlock* exit = graph_->GetExitBlock();
+  DCHECK(exit != nullptr);
+
+  int number_of_returns = 0;
+  bool saw_return = false;
+  for (HBasicBlock* pred : exit->GetPredecessors()) {
+    // TODO(solanes): We might have Return/ReturnVoid->TryBoundary->Exit. We can theoretically
+    // handle them and move them out of the TryBoundary. However, it is a border case and it adds
+    // codebase complexity.
+    if (pred->GetLastInstruction()->IsReturn() || pred->GetLastInstruction()->IsReturnVoid()) {
+      saw_return |= pred->GetLastInstruction()->IsReturn();
+      ++number_of_returns;
+    }
+  }
+
+  if (number_of_returns < 2) {
+    // Nothing to do.
+    return;
+  }
+
+  // `new_block` will coalesce the Return instructions into Phi+Return, or the ReturnVoid
+  // instructions into a ReturnVoid.
+  HBasicBlock* new_block = new (graph_->GetAllocator()) HBasicBlock(graph_, exit->GetDexPc());
+  if (saw_return) {
+    HPhi* new_phi = nullptr;
+    for (size_t i = 0; i < exit->GetPredecessors().size(); /*++i in loop*/) {
+      HBasicBlock* pred = exit->GetPredecessors()[i];
+      if (!pred->GetLastInstruction()->IsReturn()) {
+        ++i;
+        continue;
+      }
+
+      HReturn* ret = pred->GetLastInstruction()->AsReturn();
+      if (new_phi == nullptr) {
+        // Create the new_phi, if we haven't done so yet. We do it here since we need to know the
+        // type to assign to it.
+        new_phi = new (graph_->GetAllocator()) HPhi(graph_->GetAllocator(),
+                                                    kNoRegNumber,
+                                                    /*number_of_inputs=*/0,
+                                                    ret->InputAt(0)->GetType());
+        new_block->AddPhi(new_phi);
+      }
+      new_phi->AddInput(ret->InputAt(0));
+      pred->ReplaceAndRemoveInstructionWith(ret,
+                                            new (graph_->GetAllocator()) HGoto(ret->GetDexPc()));
+      pred->ReplaceSuccessor(exit, new_block);
+      // Since we are removing a predecessor, there's no need to increment `i`.
+    }
+    new_block->AddInstruction(new (graph_->GetAllocator()) HReturn(new_phi, exit->GetDexPc()));
+  } else {
+    for (size_t i = 0; i < exit->GetPredecessors().size(); /*++i in loop*/) {
+      HBasicBlock* pred = exit->GetPredecessors()[i];
+      if (!pred->GetLastInstruction()->IsReturnVoid()) {
+        ++i;
+        continue;
+      }
+
+      HReturnVoid* ret = pred->GetLastInstruction()->AsReturnVoid();
+      pred->ReplaceAndRemoveInstructionWith(ret,
+                                            new (graph_->GetAllocator()) HGoto(ret->GetDexPc()));
+      pred->ReplaceSuccessor(exit, new_block);
+      // Since we are removing a predecessor, there's no need to increment `i`.
+    }
+    new_block->AddInstruction(new (graph_->GetAllocator()) HReturnVoid(exit->GetDexPc()));
+  }
+
+  new_block->AddSuccessor(exit);
+  graph_->AddBlock(new_block);
+
+  // Recompute dominance since we added a new block.
+  graph_->ClearDominanceInformation();
+  graph_->ComputeDominanceInformation();
+}
+
 }  // namespace art
diff --git a/compiler/optimizing/code_sinking.h b/compiler/optimizing/code_sinking.h
index 8eb3a52..c743db4 100644
--- a/compiler/optimizing/code_sinking.h
+++ b/compiler/optimizing/code_sinking.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_CODE_SINKING_H_
 #define ART_COMPILER_OPTIMIZING_CODE_SINKING_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Optimization pass to move instructions into uncommon branches,
@@ -38,10 +39,16 @@
   static constexpr const char* kCodeSinkingPassName = "code_sinking";
 
  private:
-  // Try to move code only used by `end_block` and all its post-dominated / dominated
+  // Tries to sink code to uncommon branches.
+  void UncommonBranchSinking();
+  // Tries to move code only used by `end_block` and all its post-dominated / dominated
   // blocks, to these blocks.
   void SinkCodeToUncommonBranch(HBasicBlock* end_block);
 
+  // Coalesces the Return/ReturnVoid instructions into one, if we have two or more. We do this to
+  // avoid generating the exit frame code several times.
+  void ReturnSinking();
+
   DISALLOW_COPY_AND_ASSIGN(CodeSinking);
 };
 
diff --git a/compiler/optimizing/codegen_test.cc b/compiler/optimizing/codegen_test.cc
index c0441b0..2d9acc4 100644
--- a/compiler/optimizing/codegen_test.cc
+++ b/compiler/optimizing/codegen_test.cc
@@ -33,7 +33,7 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Return all combinations of ISA and code generator that are executable on
 // hardware, or on simulator, and that we'd like to test.
@@ -64,7 +64,7 @@
   return v;
 }
 
-class CodegenTest : public OptimizingUnitTest {
+class CodegenTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   void TestCode(const std::vector<uint16_t>& data, bool has_result = false, int32_t expected = 0);
   void TestCodeLong(const std::vector<uint16_t>& data, bool has_result, int64_t expected);
diff --git a/compiler/optimizing/codegen_test_utils.h b/compiler/optimizing/codegen_test_utils.h
index 397e601..7af9d0f 100644
--- a/compiler/optimizing/codegen_test_utils.h
+++ b/compiler/optimizing/codegen_test_utils.h
@@ -20,6 +20,7 @@
 #include "arch/arm/registers_arm.h"
 #include "arch/instruction_set.h"
 #include "arch/x86/registers_x86.h"
+#include "base/macros.h"
 #include "code_simulator.h"
 #include "code_simulator_container.h"
 #include "common_compiler_test.h"
@@ -35,6 +36,10 @@
 #include "code_generator_arm64.h"
 #endif
 
+#ifdef ART_ENABLE_CODEGEN_riscv64
+#include "code_generator_riscv64.h"
+#endif
+
 #ifdef ART_ENABLE_CODEGEN_x86
 #include "code_generator_x86.h"
 #endif
@@ -43,9 +48,9 @@
 #include "code_generator_x86_64.h"
 #endif
 
-namespace art {
+namespace art HIDDEN {
 
-typedef CodeGenerator* (*CreateCodegenFn)(HGraph*, const CompilerOptions&);
+using CreateCodegenFn = CodeGenerator* (*)(HGraph*, const CompilerOptions&);
 
 class CodegenTargetConfig {
  public:
@@ -254,15 +259,11 @@
     Runtime* GetRuntime() override { return nullptr; }
   };
   CodeHolder code_holder;
-  const void* code_ptr =
+  const void* method_code =
       code_holder.MakeExecutable(allocator.GetMemory(), ArrayRef<const uint8_t>(), target_isa);
 
-  typedef Expected (*fptr)();
-  fptr f = reinterpret_cast<fptr>(reinterpret_cast<uintptr_t>(code_ptr));
-  if (target_isa == InstructionSet::kThumb2) {
-    // For thumb we need the bottom bit set.
-    f = reinterpret_cast<fptr>(reinterpret_cast<uintptr_t>(f) + 1);
-  }
+  using fptr = Expected (*)();
+  fptr f = reinterpret_cast<fptr>(reinterpret_cast<uintptr_t>(method_code));
   VerifyGeneratedCode(target_isa, f, has_result, expected);
 }
 
@@ -332,6 +333,10 @@
 }
 #endif
 
+#ifdef ART_ENABLE_CODEGEN_riscv64
+inline CodeGenerator* create_codegen_riscv64(HGraph*, const CompilerOptions&) { return nullptr; }
+#endif
+
 #ifdef ART_ENABLE_CODEGEN_x86
 inline CodeGenerator* create_codegen_x86(HGraph* graph, const CompilerOptions& compiler_options) {
   return new (graph->GetAllocator()) TestCodeGeneratorX86(graph, compiler_options);
diff --git a/compiler/optimizing/common_arm.h b/compiler/optimizing/common_arm.h
index 320915e..5f71cb9 100644
--- a/compiler/optimizing/common_arm.h
+++ b/compiler/optimizing/common_arm.h
@@ -17,6 +17,7 @@
 #ifndef ART_COMPILER_OPTIMIZING_COMMON_ARM_H_
 #define ART_COMPILER_OPTIMIZING_COMMON_ARM_H_
 
+#include "base/macros.h"
 #include "instruction_simplifier_shared.h"
 #include "locations.h"
 #include "nodes.h"
@@ -28,7 +29,7 @@
 #include "aarch32/macro-assembler-aarch32.h"
 #pragma GCC diagnostic pop
 
-namespace art {
+namespace art HIDDEN {
 
 using helpers::HasShifterOperand;
 
diff --git a/compiler/optimizing/common_arm64.h b/compiler/optimizing/common_arm64.h
index 81c6561..20b0e38 100644
--- a/compiler/optimizing/common_arm64.h
+++ b/compiler/optimizing/common_arm64.h
@@ -17,6 +17,7 @@
 #ifndef ART_COMPILER_OPTIMIZING_COMMON_ARM64_H_
 #define ART_COMPILER_OPTIMIZING_COMMON_ARM64_H_
 
+#include "base/macros.h"
 #include "code_generator.h"
 #include "instruction_simplifier_shared.h"
 #include "locations.h"
@@ -31,7 +32,7 @@
 #include "aarch64/simulator-aarch64.h"
 #pragma GCC diagnostic pop
 
-namespace art {
+namespace art HIDDEN {
 
 using helpers::CanFitInShifterOperand;
 using helpers::HasShifterOperand;
@@ -153,7 +154,7 @@
                                                                      int index) {
   HInstruction* input = instr->InputAt(index);
   DataType::Type input_type = input->GetType();
-  if (input->IsConstant() && input->AsConstant()->IsZeroBitPattern()) {
+  if (IsZeroBitPattern(input)) {
     return (DataType::Size(input_type) >= vixl::aarch64::kXRegSizeInBytes)
         ? vixl::aarch64::Register(vixl::aarch64::xzr)
         : vixl::aarch64::Register(vixl::aarch64::wzr);
@@ -314,7 +315,7 @@
                                                  HInstruction* instr) {
   if (constant->IsConstant()
       && Arm64CanEncodeConstantAsImmediate(constant->AsConstant(), instr)) {
-    return Location::ConstantLocation(constant->AsConstant());
+    return Location::ConstantLocation(constant);
   }
 
   return Location::RequiresRegister();
@@ -380,10 +381,6 @@
   return instruction->IsAdd() || instruction->IsSub();
 }
 
-inline bool IsConstantZeroBitPattern(const HInstruction* instruction) {
-  return instruction->IsConstant() && instruction->AsConstant()->IsZeroBitPattern();
-}
-
 }  // namespace helpers
 }  // namespace arm64
 }  // namespace art
diff --git a/compiler/optimizing/common_dominator.h b/compiler/optimizing/common_dominator.h
index 9f012cf..f01270e 100644
--- a/compiler/optimizing/common_dominator.h
+++ b/compiler/optimizing/common_dominator.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_COMMON_DOMINATOR_H_
 #define ART_COMPILER_OPTIMIZING_COMMON_DOMINATOR_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Helper class for finding common dominators of two or more blocks in a graph.
 // The domination information of a graph must not be modified while there is
diff --git a/compiler/optimizing/constant_folding.cc b/compiler/optimizing/constant_folding.cc
index 2031707..06d19e3 100644
--- a/compiler/optimizing/constant_folding.cc
+++ b/compiler/optimizing/constant_folding.cc
@@ -16,14 +16,20 @@
 
 #include "constant_folding.h"
 
-namespace art {
+#include <algorithm>
+
+#include "dex/dex_file-inl.h"
+#include "optimizing/data_type.h"
+#include "optimizing/nodes.h"
+
+namespace art HIDDEN {
 
 // This visitor tries to simplify instructions that can be evaluated
 // as constants.
-class HConstantFoldingVisitor : public HGraphDelegateVisitor {
+class HConstantFoldingVisitor final : public HGraphDelegateVisitor {
  public:
-  explicit HConstantFoldingVisitor(HGraph* graph)
-      : HGraphDelegateVisitor(graph) {}
+  HConstantFoldingVisitor(HGraph* graph, OptimizingCompilerStats* stats, bool use_all_optimizations)
+      : HGraphDelegateVisitor(graph, stats), use_all_optimizations_(use_all_optimizations) {}
 
  private:
   void VisitBasicBlock(HBasicBlock* block) override;
@@ -31,8 +37,15 @@
   void VisitUnaryOperation(HUnaryOperation* inst) override;
   void VisitBinaryOperation(HBinaryOperation* inst) override;
 
-  void VisitTypeConversion(HTypeConversion* inst) override;
+  void VisitArrayLength(HArrayLength* inst) override;
   void VisitDivZeroCheck(HDivZeroCheck* inst) override;
+  void VisitIf(HIf* inst) override;
+  void VisitTypeConversion(HTypeConversion* inst) override;
+
+  void PropagateValue(HBasicBlock* starting_block, HInstruction* variable, HConstant* constant);
+
+  // Use all optimizations without restrictions.
+  bool use_all_optimizations_;
 
   DISALLOW_COPY_AND_ASSIGN(HConstantFoldingVisitor);
 };
@@ -55,6 +68,11 @@
   void VisitBelow(HBelow* instruction) override;
   void VisitBelowOrEqual(HBelowOrEqual* instruction) override;
 
+  void VisitGreaterThan(HGreaterThan* instruction) override;
+  void VisitGreaterThanOrEqual(HGreaterThanOrEqual* instruction) override;
+  void VisitLessThan(HLessThan* instruction) override;
+  void VisitLessThanOrEqual(HLessThanOrEqual* instruction) override;
+
   void VisitAnd(HAnd* instruction) override;
   void VisitCompare(HCompare* instruction) override;
   void VisitMul(HMul* instruction) override;
@@ -69,7 +87,7 @@
 
 
 bool HConstantFolding::Run() {
-  HConstantFoldingVisitor visitor(graph_);
+  HConstantFoldingVisitor visitor(graph_, stats_, use_all_optimizations_);
   // Process basic blocks in reverse post-order in the dominator tree,
   // so that an instruction turned into a constant, used as input of
   // another instruction, may possibly be used to turn that second
@@ -111,16 +129,6 @@
   }
 }
 
-void HConstantFoldingVisitor::VisitTypeConversion(HTypeConversion* inst) {
-  // Constant folding: replace `TypeConversion(a)' with a constant at
-  // compile time if `a' is a constant.
-  HConstant* constant = inst->TryStaticEvaluation();
-  if (constant != nullptr) {
-    inst->ReplaceWith(constant);
-    inst->GetBlock()->RemoveInstruction(inst);
-  }
-}
-
 void HConstantFoldingVisitor::VisitDivZeroCheck(HDivZeroCheck* inst) {
   // We can safely remove the check if the input is a non-null constant.
   HInstruction* check_input = inst->InputAt(0);
@@ -130,6 +138,169 @@
   }
 }
 
+void HConstantFoldingVisitor::PropagateValue(HBasicBlock* starting_block,
+                                             HInstruction* variable,
+                                             HConstant* constant) {
+  const bool recording_stats = stats_ != nullptr;
+  size_t uses_before = 0;
+  size_t uses_after = 0;
+  if (recording_stats) {
+    uses_before = variable->GetUses().SizeSlow();
+  }
+
+  if (variable->GetUses().HasExactlyOneElement()) {
+    // Nothing to do, since we only have the `if (variable)` use or the `condition` use.
+    return;
+  }
+
+  variable->ReplaceUsesDominatedBy(
+      starting_block->GetFirstInstruction(), constant, /* strictly_dominated= */ false);
+
+  if (recording_stats) {
+    uses_after = variable->GetUses().SizeSlow();
+    DCHECK_GE(uses_after, 1u) << "we must at least have the use in the if clause.";
+    DCHECK_GE(uses_before, uses_after);
+    MaybeRecordStat(stats_, MethodCompilationStat::kPropagatedIfValue, uses_before - uses_after);
+  }
+}
+
+void HConstantFoldingVisitor::VisitIf(HIf* inst) {
+  // This optimization can take a lot of compile time since we have a lot of If instructions in
+  // graphs.
+  if (!use_all_optimizations_) {
+    return;
+  }
+
+  // Consistency check: the true and false successors do not dominate each other.
+  DCHECK(!inst->IfTrueSuccessor()->Dominates(inst->IfFalseSuccessor()) &&
+         !inst->IfFalseSuccessor()->Dominates(inst->IfTrueSuccessor()));
+
+  HInstruction* if_input = inst->InputAt(0);
+
+  // Already a constant.
+  if (if_input->IsConstant()) {
+    return;
+  }
+
+  // if (variable) {
+  //   SSA `variable` guaranteed to be true
+  // } else {
+  //   and here false
+  // }
+  PropagateValue(inst->IfTrueSuccessor(), if_input, GetGraph()->GetIntConstant(1));
+  PropagateValue(inst->IfFalseSuccessor(), if_input, GetGraph()->GetIntConstant(0));
+
+  // If the input is a condition, we can propagate the information of the condition itself.
+  if (!if_input->IsCondition()) {
+    return;
+  }
+  HCondition* condition = if_input->AsCondition();
+
+  // We want either `==` or `!=`, since we cannot make assumptions for other conditions e.g. `>`
+  if (!condition->IsEqual() && !condition->IsNotEqual()) {
+    return;
+  }
+
+  HInstruction* left = condition->GetLeft();
+  HInstruction* right = condition->GetRight();
+
+  // We want one of them to be a constant and not the other.
+  if (left->IsConstant() == right->IsConstant()) {
+    return;
+  }
+
+  // At this point we have something like:
+  // if (variable == constant) {
+  //   SSA `variable` guaranteed to be equal to constant here
+  // } else {
+  //   No guarantees can be made here (usually, see boolean case below).
+  // }
+  // Similarly with variable != constant, except that we can make guarantees in the else case.
+
+  HConstant* constant = left->IsConstant() ? left->AsConstant() : right->AsConstant();
+  HInstruction* variable = left->IsConstant() ? right : left;
+
+  // Don't deal with floats/doubles since they bring a lot of edge cases e.g.
+  // if (f == 0.0f) {
+  //   // f is not really guaranteed to be 0.0f. It could be -0.0f, for example
+  // }
+  if (DataType::IsFloatingPointType(variable->GetType())) {
+    return;
+  }
+  DCHECK(!DataType::IsFloatingPointType(constant->GetType()));
+
+  // Sometimes we have an HCompare flowing into an Equals/NonEquals, which can act as a proxy. For
+  // example: `Equals(Compare(var, constant), 0)`. This is common for long, float, and double.
+  if (variable->IsCompare()) {
+    // We only care about equality comparisons so we skip if it is a less or greater comparison.
+    if (!constant->IsArithmeticZero()) {
+      return;
+    }
+
+    // Update left and right to be the ones from the HCompare.
+    left = variable->AsCompare()->GetLeft();
+    right = variable->AsCompare()->GetRight();
+
+    // Re-check that one of them to be a constant and not the other.
+    if (left->IsConstant() == right->IsConstant()) {
+      return;
+    }
+
+    constant = left->IsConstant() ? left->AsConstant() : right->AsConstant();
+    variable = left->IsConstant() ? right : left;
+
+    // Re-check floating point values.
+    if (DataType::IsFloatingPointType(variable->GetType())) {
+      return;
+    }
+    DCHECK(!DataType::IsFloatingPointType(constant->GetType()));
+  }
+
+  // From this block forward we want to replace the SSA value. We use `starting_block` and not the
+  // `if` block as we want to update one of the branches but not the other.
+  HBasicBlock* starting_block =
+      condition->IsEqual() ? inst->IfTrueSuccessor() : inst->IfFalseSuccessor();
+
+  PropagateValue(starting_block, variable, constant);
+
+  // Special case for booleans since they have only two values so we know what to propagate in the
+  // other branch. However, sometimes our boolean values are not compared to 0 or 1. In those cases
+  // we cannot make an assumption for the `else` branch.
+  if (variable->GetType() == DataType::Type::kBool &&
+      constant->IsIntConstant() &&
+      (constant->AsIntConstant()->IsTrue() || constant->AsIntConstant()->IsFalse())) {
+    HBasicBlock* other_starting_block =
+        condition->IsEqual() ? inst->IfFalseSuccessor() : inst->IfTrueSuccessor();
+    DCHECK_NE(other_starting_block, starting_block);
+
+    HConstant* other_constant = constant->AsIntConstant()->IsTrue() ?
+                                    GetGraph()->GetIntConstant(0) :
+                                    GetGraph()->GetIntConstant(1);
+    DCHECK_NE(other_constant, constant);
+    PropagateValue(other_starting_block, variable, other_constant);
+  }
+}
+
+void HConstantFoldingVisitor::VisitArrayLength(HArrayLength* inst) {
+  HInstruction* input = inst->InputAt(0);
+  if (input->IsLoadString()) {
+    DCHECK(inst->IsStringLength());
+    HLoadString* load_string = input->AsLoadString();
+    const DexFile& dex_file = load_string->GetDexFile();
+    const dex::StringId& string_id = dex_file.GetStringId(load_string->GetStringIndex());
+    inst->ReplaceWith(GetGraph()->GetIntConstant(dex_file.GetStringLength(string_id)));
+  }
+}
+
+void HConstantFoldingVisitor::VisitTypeConversion(HTypeConversion* inst) {
+  // Constant folding: replace `TypeConversion(a)' with a constant at
+  // compile time if `a' is a constant.
+  HConstant* constant = inst->TryStaticEvaluation();
+  if (constant != nullptr) {
+    inst->ReplaceWith(constant);
+    inst->GetBlock()->RemoveInstruction(inst);
+  }
+}
 
 void InstructionWithAbsorbingInputSimplifier::VisitShift(HBinaryOperation* instruction) {
   DCHECK(instruction->IsShl() || instruction->IsShr() || instruction->IsUShr());
@@ -145,8 +316,17 @@
 }
 
 void InstructionWithAbsorbingInputSimplifier::VisitEqual(HEqual* instruction) {
-  if ((instruction->GetLeft()->IsNullConstant() && !instruction->GetRight()->CanBeNull()) ||
-      (instruction->GetRight()->IsNullConstant() && !instruction->GetLeft()->CanBeNull())) {
+  if (instruction->GetLeft() == instruction->GetRight() &&
+      !DataType::IsFloatingPointType(instruction->GetLeft()->GetType())) {
+    // Replace code looking like
+    //    EQUAL lhs, lhs
+    //    CONSTANT true
+    // We don't perform this optimizations for FP types since Double.NaN != Double.NaN, which is the
+    // opposite value.
+    instruction->ReplaceWith(GetGraph()->GetConstant(DataType::Type::kBool, 1));
+    instruction->GetBlock()->RemoveInstruction(instruction);
+  } else if ((instruction->GetLeft()->IsNullConstant() && !instruction->GetRight()->CanBeNull()) ||
+             (instruction->GetRight()->IsNullConstant() && !instruction->GetLeft()->CanBeNull())) {
     // Replace code looking like
     //    EQUAL lhs, null
     // where lhs cannot be null with
@@ -157,8 +337,17 @@
 }
 
 void InstructionWithAbsorbingInputSimplifier::VisitNotEqual(HNotEqual* instruction) {
-  if ((instruction->GetLeft()->IsNullConstant() && !instruction->GetRight()->CanBeNull()) ||
-      (instruction->GetRight()->IsNullConstant() && !instruction->GetLeft()->CanBeNull())) {
+  if (instruction->GetLeft() == instruction->GetRight() &&
+      !DataType::IsFloatingPointType(instruction->GetLeft()->GetType())) {
+    // Replace code looking like
+    //    NOT_EQUAL lhs, lhs
+    //    CONSTANT false
+    // We don't perform this optimizations for FP types since Double.NaN != Double.NaN, which is the
+    // opposite value.
+    instruction->ReplaceWith(GetGraph()->GetConstant(DataType::Type::kBool, 0));
+    instruction->GetBlock()->RemoveInstruction(instruction);
+  } else if ((instruction->GetLeft()->IsNullConstant() && !instruction->GetRight()->CanBeNull()) ||
+             (instruction->GetRight()->IsNullConstant() && !instruction->GetLeft()->CanBeNull())) {
     // Replace code looking like
     //    NOT_EQUAL lhs, null
     // where lhs cannot be null with
@@ -169,8 +358,14 @@
 }
 
 void InstructionWithAbsorbingInputSimplifier::VisitAbove(HAbove* instruction) {
-  if (instruction->GetLeft()->IsConstant() &&
-      instruction->GetLeft()->AsConstant()->IsArithmeticZero()) {
+  if (instruction->GetLeft() == instruction->GetRight()) {
+    // Replace code looking like
+    //    ABOVE lhs, lhs
+    //    CONSTANT false
+    instruction->ReplaceWith(GetGraph()->GetConstant(DataType::Type::kBool, 0));
+    instruction->GetBlock()->RemoveInstruction(instruction);
+  } else if (instruction->GetLeft()->IsConstant() &&
+             instruction->GetLeft()->AsConstant()->IsArithmeticZero()) {
     // Replace code looking like
     //    ABOVE dst, 0, src  // unsigned 0 > src is always false
     // with
@@ -181,8 +376,14 @@
 }
 
 void InstructionWithAbsorbingInputSimplifier::VisitAboveOrEqual(HAboveOrEqual* instruction) {
-  if (instruction->GetRight()->IsConstant() &&
-      instruction->GetRight()->AsConstant()->IsArithmeticZero()) {
+  if (instruction->GetLeft() == instruction->GetRight()) {
+    // Replace code looking like
+    //    ABOVE_OR_EQUAL lhs, lhs
+    //    CONSTANT true
+    instruction->ReplaceWith(GetGraph()->GetConstant(DataType::Type::kBool, 1));
+    instruction->GetBlock()->RemoveInstruction(instruction);
+  } else if (instruction->GetRight()->IsConstant() &&
+             instruction->GetRight()->AsConstant()->IsArithmeticZero()) {
     // Replace code looking like
     //    ABOVE_OR_EQUAL dst, src, 0  // unsigned src >= 0 is always true
     // with
@@ -193,8 +394,14 @@
 }
 
 void InstructionWithAbsorbingInputSimplifier::VisitBelow(HBelow* instruction) {
-  if (instruction->GetRight()->IsConstant() &&
-      instruction->GetRight()->AsConstant()->IsArithmeticZero()) {
+  if (instruction->GetLeft() == instruction->GetRight()) {
+    // Replace code looking like
+    //    BELOW lhs, lhs
+    //    CONSTANT false
+    instruction->ReplaceWith(GetGraph()->GetConstant(DataType::Type::kBool, 0));
+    instruction->GetBlock()->RemoveInstruction(instruction);
+  } else if (instruction->GetRight()->IsConstant() &&
+             instruction->GetRight()->AsConstant()->IsArithmeticZero()) {
     // Replace code looking like
     //    BELOW dst, src, 0  // unsigned src < 0 is always false
     // with
@@ -205,8 +412,14 @@
 }
 
 void InstructionWithAbsorbingInputSimplifier::VisitBelowOrEqual(HBelowOrEqual* instruction) {
-  if (instruction->GetLeft()->IsConstant() &&
-      instruction->GetLeft()->AsConstant()->IsArithmeticZero()) {
+  if (instruction->GetLeft() == instruction->GetRight()) {
+    // Replace code looking like
+    //    BELOW_OR_EQUAL lhs, lhs
+    //    CONSTANT true
+    instruction->ReplaceWith(GetGraph()->GetConstant(DataType::Type::kBool, 1));
+    instruction->GetBlock()->RemoveInstruction(instruction);
+  } else if (instruction->GetLeft()->IsConstant() &&
+             instruction->GetLeft()->AsConstant()->IsArithmeticZero()) {
     // Replace code looking like
     //    BELOW_OR_EQUAL dst, 0, src  // unsigned 0 <= src is always true
     // with
@@ -216,6 +429,55 @@
   }
 }
 
+void InstructionWithAbsorbingInputSimplifier::VisitGreaterThan(HGreaterThan* instruction) {
+  if (instruction->GetLeft() == instruction->GetRight() &&
+      (!DataType::IsFloatingPointType(instruction->GetLeft()->GetType()) ||
+       instruction->IsLtBias())) {
+    // Replace code looking like
+    //    GREATER_THAN lhs, lhs
+    //    CONSTANT false
+    instruction->ReplaceWith(GetGraph()->GetConstant(DataType::Type::kBool, 0));
+    instruction->GetBlock()->RemoveInstruction(instruction);
+  }
+}
+
+void InstructionWithAbsorbingInputSimplifier::VisitGreaterThanOrEqual(
+    HGreaterThanOrEqual* instruction) {
+  if (instruction->GetLeft() == instruction->GetRight() &&
+      (!DataType::IsFloatingPointType(instruction->GetLeft()->GetType()) ||
+       instruction->IsGtBias())) {
+    // Replace code looking like
+    //    GREATER_THAN_OR_EQUAL lhs, lhs
+    //    CONSTANT true
+    instruction->ReplaceWith(GetGraph()->GetConstant(DataType::Type::kBool, 1));
+    instruction->GetBlock()->RemoveInstruction(instruction);
+  }
+}
+
+void InstructionWithAbsorbingInputSimplifier::VisitLessThan(HLessThan* instruction) {
+  if (instruction->GetLeft() == instruction->GetRight() &&
+      (!DataType::IsFloatingPointType(instruction->GetLeft()->GetType()) ||
+       instruction->IsGtBias())) {
+    // Replace code looking like
+    //    LESS_THAN lhs, lhs
+    //    CONSTANT false
+    instruction->ReplaceWith(GetGraph()->GetConstant(DataType::Type::kBool, 0));
+    instruction->GetBlock()->RemoveInstruction(instruction);
+  }
+}
+
+void InstructionWithAbsorbingInputSimplifier::VisitLessThanOrEqual(HLessThanOrEqual* instruction) {
+  if (instruction->GetLeft() == instruction->GetRight() &&
+      (!DataType::IsFloatingPointType(instruction->GetLeft()->GetType()) ||
+       instruction->IsLtBias())) {
+    // Replace code looking like
+    //    LESS_THAN_OR_EQUAL lhs, lhs
+    //    CONSTANT true
+    instruction->ReplaceWith(GetGraph()->GetConstant(DataType::Type::kBool, 1));
+    instruction->GetBlock()->RemoveInstruction(instruction);
+  }
+}
+
 void InstructionWithAbsorbingInputSimplifier::VisitAnd(HAnd* instruction) {
   DataType::Type type = instruction->GetType();
   HConstant* input_cst = instruction->GetConstantRight();
diff --git a/compiler/optimizing/constant_folding.h b/compiler/optimizing/constant_folding.h
index 72bd95b..29648e9 100644
--- a/compiler/optimizing/constant_folding.h
+++ b/compiler/optimizing/constant_folding.h
@@ -17,10 +17,12 @@
 #ifndef ART_COMPILER_OPTIMIZING_CONSTANT_FOLDING_H_
 #define ART_COMPILER_OPTIMIZING_CONSTANT_FOLDING_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
+#include "optimizing/optimizing_compiler_stats.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Optimization pass performing a simple constant-expression
@@ -39,13 +41,20 @@
  */
 class HConstantFolding : public HOptimization {
  public:
-  HConstantFolding(HGraph* graph, const char* name) : HOptimization(graph, name) {}
+  HConstantFolding(HGraph* graph,
+                   OptimizingCompilerStats* stats = nullptr,
+                   const char* name = kConstantFoldingPassName,
+                   bool use_all_optimizations = false)
+      : HOptimization(graph, name, stats), use_all_optimizations_(use_all_optimizations) {}
 
   bool Run() override;
 
   static constexpr const char* kConstantFoldingPassName = "constant_folding";
 
  private:
+  // Use all optimizations without restrictions.
+  bool use_all_optimizations_;
+
   DISALLOW_COPY_AND_ASSIGN(HConstantFolding);
 };
 
diff --git a/compiler/optimizing/constant_folding_test.cc b/compiler/optimizing/constant_folding_test.cc
index 74d9d3a..741fd3f 100644
--- a/compiler/optimizing/constant_folding_test.cc
+++ b/compiler/optimizing/constant_folding_test.cc
@@ -17,6 +17,8 @@
 #include <functional>
 
 #include "constant_folding.h"
+
+#include "base/macros.h"
 #include "dead_code_elimination.h"
 #include "driver/compiler_options.h"
 #include "graph_checker.h"
@@ -25,12 +27,12 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Fixture class for the constant folding and dce tests.
  */
-class ConstantFoldingTest : public OptimizingUnitTest {
+class ConstantFoldingTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  public:
   ConstantFoldingTest() : graph_(nullptr) { }
 
@@ -58,7 +60,9 @@
     std::string actual_before = printer_before.str();
     EXPECT_EQ(expected_before, actual_before);
 
-    HConstantFolding(graph_, "constant_folding").Run();
+    HConstantFolding constant_folding(
+        graph_, /* stats= */ nullptr, "constant_folding", /* use_all_optimizations= */ true);
+    constant_folding.Run();
     GraphChecker graph_checker_cf(graph_);
     graph_checker_cf.Run();
     ASSERT_TRUE(graph_checker_cf.IsValid());
diff --git a/compiler/optimizing/constructor_fence_redundancy_elimination.cc b/compiler/optimizing/constructor_fence_redundancy_elimination.cc
index 3a1a9e0..d9b7652 100644
--- a/compiler/optimizing/constructor_fence_redundancy_elimination.cc
+++ b/compiler/optimizing/constructor_fence_redundancy_elimination.cc
@@ -20,12 +20,12 @@
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static constexpr bool kCfreLogFenceInputCount = false;
 
 // TODO: refactor this code by reusing escape analysis.
-class CFREVisitor : public HGraphVisitor {
+class CFREVisitor final : public HGraphVisitor {
  public:
   CFREVisitor(HGraph* graph, OptimizingCompilerStats* stats)
       : HGraphVisitor(graph),
@@ -147,16 +147,6 @@
   void VisitAlias(HInstruction* aliasing_inst) {
     // An object is considered "published" if it becomes aliased by other instructions.
     if (HasInterestingPublishTargetAsInput(aliasing_inst))  {
-      // Note that constructing a "NullCheck" for new-instance, new-array,
-      // or a 'this' (receiver) reference is impossible.
-      //
-      // If by some reason we actually encounter such a NullCheck(FenceTarget),
-      // we LOG(WARNING).
-      if (UNLIKELY(aliasing_inst->IsNullCheck())) {
-        LOG(kIsDebugBuild ? FATAL : WARNING)
-            << "Unexpected instruction: NullCheck; should not be legal in graph";
-        // We then do a best-effort to handle this case.
-      }
       MergeCandidateFences();
     }
   }
diff --git a/compiler/optimizing/constructor_fence_redundancy_elimination.h b/compiler/optimizing/constructor_fence_redundancy_elimination.h
index 014b342..e04b986 100644
--- a/compiler/optimizing/constructor_fence_redundancy_elimination.h
+++ b/compiler/optimizing/constructor_fence_redundancy_elimination.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_CONSTRUCTOR_FENCE_REDUNDANCY_ELIMINATION_H_
 #define ART_COMPILER_OPTIMIZING_CONSTRUCTOR_FENCE_REDUNDANCY_ELIMINATION_H_
 
+#include "base/macros.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /*
  * Constructor Fence Redundancy Elimination (CFRE).
diff --git a/compiler/optimizing/critical_native_abi_fixup_arm.cc b/compiler/optimizing/critical_native_abi_fixup_arm.cc
index 3c4db4b..77e1566 100644
--- a/compiler/optimizing/critical_native_abi_fixup_arm.cc
+++ b/compiler/optimizing/critical_native_abi_fixup_arm.cc
@@ -23,7 +23,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "well_known_classes.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 // Fix up FP arguments passed in core registers for call to @CriticalNative by inserting fake calls
@@ -45,9 +45,9 @@
     if (DataType::IsFloatingPointType(input_type)) {
       bool is_double = (input_type == DataType::Type::kFloat64);
       DataType::Type converted_type = is_double ? DataType::Type::kInt64 : DataType::Type::kInt32;
-      jmethodID known_method = is_double ? WellKnownClasses::java_lang_Double_doubleToRawLongBits
-                                         : WellKnownClasses::java_lang_Float_floatToRawIntBits;
-      ArtMethod* resolved_method = jni::DecodeArtMethod(known_method);
+      ArtMethod* resolved_method = is_double
+          ? WellKnownClasses::java_lang_Double_doubleToRawLongBits
+          : WellKnownClasses::java_lang_Float_floatToRawIntBits;
       DCHECK(resolved_method != nullptr);
       DCHECK(resolved_method->IsIntrinsic());
       MethodReference target_method(nullptr, 0);
@@ -74,7 +74,8 @@
           dispatch_info,
           kStatic,
           target_method,
-          HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+          HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+          !block->GetGraph()->IsDebuggable());
       // The intrinsic has no side effects and does not need environment or dex cache on ARM.
       new_input->SetSideEffects(SideEffects::None());
       IntrinsicOptimizations opt(new_input);
diff --git a/compiler/optimizing/critical_native_abi_fixup_arm.h b/compiler/optimizing/critical_native_abi_fixup_arm.h
index faa3c7a..c2068f5 100644
--- a/compiler/optimizing/critical_native_abi_fixup_arm.h
+++ b/compiler/optimizing/critical_native_abi_fixup_arm.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_CRITICAL_NATIVE_ABI_FIXUP_ARM_H_
 #define ART_COMPILER_OPTIMIZING_CRITICAL_NATIVE_ABI_FIXUP_ARM_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 class CriticalNativeAbiFixupArm : public HOptimization {
diff --git a/compiler/optimizing/data_type-inl.h b/compiler/optimizing/data_type-inl.h
index 1b33b77..bbfe904 100644
--- a/compiler/optimizing/data_type-inl.h
+++ b/compiler/optimizing/data_type-inl.h
@@ -20,7 +20,7 @@
 #include "data_type.h"
 #include "dex/primitive.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Note: Not declared in data_type.h to avoid pulling in "primitive.h".
 constexpr DataType::Type DataTypeFromPrimitive(Primitive::Type type) {
diff --git a/compiler/optimizing/data_type.cc b/compiler/optimizing/data_type.cc
index cb354f4..183cf2c 100644
--- a/compiler/optimizing/data_type.cc
+++ b/compiler/optimizing/data_type.cc
@@ -16,7 +16,7 @@
 
 #include "data_type.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static const char* kTypeNames[] = {
     "Reference",
diff --git a/compiler/optimizing/data_type.h b/compiler/optimizing/data_type.h
index ec6ca7a..b6d9519 100644
--- a/compiler/optimizing/data_type.h
+++ b/compiler/optimizing/data_type.h
@@ -22,8 +22,9 @@
 #include <android-base/logging.h>
 
 #include "base/bit_utils.h"
+#include "base/macros.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class DataType {
  public:
diff --git a/compiler/optimizing/data_type_test.cc b/compiler/optimizing/data_type_test.cc
index 8fea22b..f6f614d 100644
--- a/compiler/optimizing/data_type_test.cc
+++ b/compiler/optimizing/data_type_test.cc
@@ -22,7 +22,7 @@
 #include "base/macros.h"
 #include "dex/primitive.h"
 
-namespace art {
+namespace art HIDDEN {
 
 template <DataType::Type data_type, Primitive::Type primitive_type>
 static void CheckConversion() {
diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc
index d808f2c..cf49e39 100644
--- a/compiler/optimizing/dead_code_elimination.cc
+++ b/compiler/optimizing/dead_code_elimination.cc
@@ -16,14 +16,17 @@
 
 #include "dead_code_elimination.h"
 
+#include "android-base/logging.h"
 #include "base/array_ref.h"
 #include "base/bit_vector-inl.h"
+#include "base/logging.h"
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 #include "base/stl_util.h"
+#include "optimizing/nodes.h"
 #include "ssa_phi_elimination.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static void MarkReachableBlocks(HGraph* graph, ArenaBitVector* visited) {
   // Use local allocator for allocating memory.
@@ -178,6 +181,13 @@
   } else if (!cond->InputAt(0)->IsNullConstant()) {
     return false;
   }
+
+  // We can't create a BoundType for an object with an invalid RTI.
+  const ReferenceTypeInfo ti = obj->GetReferenceTypeInfo();
+  if (!ti.IsValid()) {
+    return false;
+  }
+
   // Scan all uses of obj and find null check under control dependence.
   HBoundType* bound = nullptr;
   const HUseList<HInstruction*>& uses = obj->GetUses();
@@ -190,7 +200,6 @@
           user_block != throws &&
           block->Dominates(user_block)) {
         if (bound == nullptr) {
-          ReferenceTypeInfo ti = obj->GetReferenceTypeInfo();
           bound = new (obj->GetBlock()->GetGraph()->GetAllocator()) HBoundType(obj);
           bound->SetUpperBound(ti, /*can_be_null*/ false);
           bound->SetReferenceTypeInfo(ti);
@@ -213,6 +222,9 @@
 //          |   ...
 //          |   instr_n
 //          |   foo()  // always throws
+//          |   instr_n+2
+//          |   ...
+//          |   instr_n+m
 //          \   goto B2
 //           \ /
 //            B2
@@ -230,11 +242,14 @@
 //         B2  Exit
 //
 // Rationale:
-// Removal of the never taken edge to B2 may expose
-// other optimization opportunities, such as code sinking.
+// Removal of the never taken edge to B2 may expose other optimization opportunities, such as code
+// sinking.
+//
+// Note: The example above is a simple one that uses a `goto` but we could end the block with an If,
+// for example.
 bool HDeadCodeElimination::SimplifyAlwaysThrows() {
   HBasicBlock* exit = graph_->GetExitBlock();
-  if (exit == nullptr) {
+  if (!graph_->HasAlwaysThrowingInvokes() || exit == nullptr) {
     return false;
   }
 
@@ -242,55 +257,56 @@
 
   // Order does not matter, just pick one.
   for (HBasicBlock* block : graph_->GetReversePostOrder()) {
-    if (block->GetTryCatchInformation() != nullptr) {
+    if (block->IsTryBlock()) {
       // We don't want to perform the simplify always throws optimizations for throws inside of
-      // tries since those throws might not go to the exit block. We do that by checking the
-      // TryCatchInformation of the blocks.
-      //
-      // As a special case the `catch_block` is the first block of the catch and it has
-      // TryCatchInformation. Other blocks in the catch don't have try catch information (as long as
-      // they are not part of an outer try). Knowing if a `catch_block` is part of an outer try is
-      // possible by checking its successors, but other restrictions of the simplify always throws
-      // optimization will block `catch_block` nevertheless (e.g. only one predecessor) so it is not
-      // worth the effort.
-
-      // TODO(solanes): Maybe we can do a `goto catch` if inside of a try catch instead of going to
-      // the exit. If we do so, we have to take into account that we should go to the nearest valid
-      // catch i.e. one that would accept our exception type.
+      // tries since those throws might not go to the exit block.
       continue;
     }
 
-    HInstruction* last = block->GetLastInstruction();
-    HInstruction* prev = last->GetPrevious();
-    if (prev == nullptr) {
-      DCHECK_EQ(block->GetFirstInstruction(), block->GetLastInstruction());
-      continue;
-    }
-
-    if (prev->AlwaysThrows() &&
-        last->IsGoto() &&
-        block->GetPhis().IsEmpty() &&
-        block->GetPredecessors().size() == 1u) {
-      HBasicBlock* pred = block->GetSinglePredecessor();
-      HBasicBlock* succ = block->GetSingleSuccessor();
-      // Ensure no computations are merged through throwing block.
-      // This does not prevent the optimization per se, but would
-      // require an elaborate clean up of the SSA graph.
-      if (succ != exit &&
-          !block->Dominates(pred) &&
-          pred->Dominates(succ) &&
-          succ->GetPredecessors().size() > 1u &&
-          succ->GetPhis().IsEmpty()) {
-        block->ReplaceSuccessor(succ, exit);
-        rerun_dominance_and_loop_analysis = true;
-        MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyThrowingInvoke);
-        // Perform a quick follow up optimization on object != null control dependences
-        // that is much cheaper to perform now than in a later phase.
-        if (RemoveNonNullControlDependences(pred, block)) {
-          MaybeRecordStat(stats_, MethodCompilationStat::kRemovedNullCheck);
-        }
+    // We iterate to find the first instruction that always throws. If two instructions always
+    // throw, the first one will throw and the second one will never be reached.
+    HInstruction* throwing_invoke = nullptr;
+    for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
+      if (it.Current()->IsInvoke() && it.Current()->AsInvoke()->AlwaysThrows()) {
+        throwing_invoke = it.Current();
+        break;
       }
     }
+
+    if (throwing_invoke == nullptr) {
+      // No always-throwing instruction found. Continue with the rest of the blocks.
+      continue;
+    }
+
+    // If we are already pointing at the exit block we could still remove the instructions
+    // between the always throwing instruction, and the exit block. If we have no other
+    // instructions, just continue since there's nothing to do.
+    if (block->GetSuccessors().size() == 1 &&
+        block->GetSingleSuccessor() == exit &&
+        block->GetLastInstruction()->GetPrevious() == throwing_invoke) {
+      continue;
+    }
+
+    // We split the block at the throwing instruction, and the instructions after the throwing
+    // instructions will be disconnected from the graph after `block` points to the exit.
+    // `RemoveDeadBlocks` will take care of removing this new block and its instructions.
+    // Even though `SplitBefore` doesn't guarantee the graph to remain in SSA form, it is fine
+    // since we do not break it.
+    HBasicBlock* new_block = block->SplitBefore(throwing_invoke->GetNext(),
+                                                /* require_graph_not_in_ssa_form= */ false);
+    DCHECK_EQ(block->GetSingleSuccessor(), new_block);
+    block->ReplaceSuccessor(new_block, exit);
+
+    rerun_dominance_and_loop_analysis = true;
+    MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyThrowingInvoke);
+    // Perform a quick follow up optimization on object != null control dependences
+    // that is much cheaper to perform now than in a later phase.
+    // If there are multiple predecessors, none may end with a HIf as required in
+    // RemoveNonNullControlDependences because we split critical edges.
+    if (block->GetPredecessors().size() == 1u &&
+        RemoveNonNullControlDependences(block->GetSinglePredecessor(), block)) {
+      MaybeRecordStat(stats_, MethodCompilationStat::kRemovedNullCheck);
+    }
   }
 
   // We need to re-analyze the graph in order to run DCE afterwards.
@@ -303,54 +319,45 @@
   return false;
 }
 
-// Simplify the pattern:
-//
-//        B1    B2    ...
-//       goto  goto  goto
-//         \    |    /
-//          \   |   /
-//             B3
-//     i1 = phi(input, input)
-//     (i2 = condition on i1)
-//        if i1 (or i2)
-//          /     \
-//         /       \
-//        B4       B5
-//
-// Into:
-//
-//       B1      B2    ...
-//        |      |      |
-//       B4      B5    B?
-//
-// Note that individual edges can be redirected (for example B2->B3
-// can be redirected as B2->B5) without applying this optimization
-// to other incoming edges.
-//
-// This simplification cannot be applied to catch blocks, because
-// exception handler edges do not represent normal control flow.
-// Though in theory this could still apply to normal control flow
-// going directly to a catch block, we cannot support it at the
-// moment because the catch Phi's inputs do not correspond to the
-// catch block's predecessors, so we cannot identify which
-// predecessor corresponds to a given statically evaluated input.
-//
-// We do not apply this optimization to loop headers as this could
-// create irreducible loops. We rely on the suspend check in the
-// loop header to prevent the pattern match.
-//
-// Note that we rely on the dead code elimination to get rid of B3.
 bool HDeadCodeElimination::SimplifyIfs() {
   bool simplified_one_or_more_ifs = false;
   bool rerun_dominance_and_loop_analysis = false;
 
-  for (HBasicBlock* block : graph_->GetReversePostOrder()) {
+  // Iterating in PostOrder it's better for MaybeAddPhi as it can add a Phi for multiple If
+  // instructions in a chain without updating the dominator chain. The branch redirection itself can
+  // work in PostOrder or ReversePostOrder without issues.
+  for (HBasicBlock* block : graph_->GetPostOrder()) {
+    if (block->IsCatchBlock()) {
+      // This simplification cannot be applied to catch blocks, because exception handler edges do
+      // not represent normal control flow. Though in theory this could still apply to normal
+      // control flow going directly to a catch block, we cannot support it at the moment because
+      // the catch Phi's inputs do not correspond to the catch block's predecessors, so we cannot
+      // identify which predecessor corresponds to a given statically evaluated input.
+      continue;
+    }
+
     HInstruction* last = block->GetLastInstruction();
-    HInstruction* first = block->GetFirstInstruction();
-    if (!block->IsCatchBlock() &&
-        last->IsIf() &&
-        block->HasSinglePhi() &&
+    if (!last->IsIf()) {
+      continue;
+    }
+
+    if (block->IsLoopHeader()) {
+      // We do not apply this optimization to loop headers as this could create irreducible loops.
+      continue;
+    }
+
+    // We will add a Phi which allows the simplification to take place in cases where it wouldn't.
+    MaybeAddPhi(block);
+
+    // TODO(solanes): Investigate support for multiple phis in `block`. We can potentially "push
+    // downwards" existing Phis into the true/false branches. For example, let's say we have another
+    // Phi: Phi(x1,x2,x3,x4,x5,x6). This could turn into Phi(x1,x2) in the true branch, Phi(x3,x4)
+    // in the false branch, and remain as Phi(x5,x6) in `block` (for edges that we couldn't
+    // redirect). We might even be able to remove some phis altogether as they will have only one
+    // value.
+    if (block->HasSinglePhi() &&
         block->GetFirstPhi()->HasOnlyOneNonEnvironmentUse()) {
+      HInstruction* first = block->GetFirstInstruction();
       bool has_only_phi_and_if = (last == first) && (last->InputAt(0) == block->GetFirstPhi());
       bool has_only_phi_condition_and_if =
           !has_only_phi_and_if &&
@@ -361,7 +368,6 @@
           first->HasOnlyOneNonEnvironmentUse();
 
       if (has_only_phi_and_if || has_only_phi_condition_and_if) {
-        DCHECK(!block->IsLoopHeader());
         HPhi* phi = block->GetFirstPhi()->AsPhi();
         bool phi_input_is_left = (first->InputAt(0) == phi);
 
@@ -446,6 +452,125 @@
   return simplified_one_or_more_ifs;
 }
 
+void HDeadCodeElimination::MaybeAddPhi(HBasicBlock* block) {
+  DCHECK(block->GetLastInstruction()->IsIf());
+  HIf* if_instruction = block->GetLastInstruction()->AsIf();
+  if (if_instruction->InputAt(0)->IsConstant()) {
+    // Constant values are handled in RemoveDeadBlocks.
+    return;
+  }
+
+  if (block->GetNumberOfPredecessors() < 2u) {
+    // Nothing to redirect.
+    return;
+  }
+
+  if (!block->GetPhis().IsEmpty()) {
+    // SimplifyIf doesn't currently work with multiple phis. Adding a phi here won't help that
+    // optimization.
+    return;
+  }
+
+  HBasicBlock* dominator = block->GetDominator();
+  if (!dominator->EndsWithIf()) {
+    return;
+  }
+
+  HInstruction* input = if_instruction->InputAt(0);
+  HInstruction* dominator_input = dominator->GetLastInstruction()->AsIf()->InputAt(0);
+  const bool same_input = dominator_input == input;
+  if (!same_input) {
+    // Try to see if the dominator has the opposite input (e.g. if(cond) and if(!cond)). If that's
+    // the case, we can perform the optimization with the false and true branches reversed.
+    if (!dominator_input->IsCondition() || !input->IsCondition()) {
+      return;
+    }
+
+    HCondition* block_cond = input->AsCondition();
+    HCondition* dominator_cond = dominator_input->AsCondition();
+
+    if (block_cond->GetLeft() != dominator_cond->GetLeft() ||
+        block_cond->GetRight() != dominator_cond->GetRight() ||
+        block_cond->GetOppositeCondition() != dominator_cond->GetCondition()) {
+      return;
+    }
+  }
+
+  if (kIsDebugBuild) {
+    // `block`'s successors should have only one predecessor. Otherwise, we have a critical edge in
+    // the graph.
+    for (HBasicBlock* succ : block->GetSuccessors()) {
+      DCHECK_EQ(succ->GetNumberOfPredecessors(), 1u);
+    }
+  }
+
+  const size_t pred_size = block->GetNumberOfPredecessors();
+  HPhi* new_phi = new (graph_->GetAllocator())
+      HPhi(graph_->GetAllocator(), kNoRegNumber, pred_size, DataType::Type::kInt32);
+
+  for (size_t index = 0; index < pred_size; index++) {
+    HBasicBlock* pred = block->GetPredecessors()[index];
+    const bool dominated_by_true =
+        dominator->GetLastInstruction()->AsIf()->IfTrueSuccessor()->Dominates(pred);
+    const bool dominated_by_false =
+        dominator->GetLastInstruction()->AsIf()->IfFalseSuccessor()->Dominates(pred);
+    if (dominated_by_true == dominated_by_false) {
+      // In this case, we can't know if we are coming from the true branch, or the false branch. It
+      // happens in cases like:
+      //      1 (outer if)
+      //     / \
+      //    2   3 (inner if)
+      //    |  / \
+      //    | 4  5
+      //     \/  |
+      //      6  |
+      //       \ |
+      //         7 (has the same if(cond) as 1)
+      //         |
+      //         8
+      // `7` (which would be `block` in this example), and `6` will come from both the true path and
+      // the false path of `1`. We bumped into something similar in SelectGenerator. See
+      // HSelectGenerator::TryFixupDoubleDiamondPattern.
+      // TODO(solanes): Figure out if we can fix up the graph into a double diamond in a generic way
+      // so that DeadCodeElimination and SelectGenerator can take advantage of it.
+
+      if (!same_input) {
+        // `1` and `7` having the opposite condition is a case we are missing. We could potentially
+        // add a BooleanNot instruction to be able to add the Phi, but it seems like overkill since
+        // this case is not that common.
+        return;
+      }
+
+      // The Phi will have `0`, `1`, and `cond` as inputs. If SimplifyIf redirects 0s and 1s, we
+      // will end up with Phi(cond,...,cond) which will be replaced by `cond`. Effectively, we will
+      // redirect edges that we are able to redirect and the rest will remain as before (i.e. we
+      // won't have an extra Phi).
+      new_phi->SetRawInputAt(index, input);
+    } else {
+      // Redirect to either the true branch (1), or the false branch (0).
+      // Given that `dominated_by_true` is the exact opposite of `dominated_by_false`,
+      // `(same_input && dominated_by_true) || (!same_input && dominated_by_false)` is equivalent to
+      // `same_input == dominated_by_true`.
+      new_phi->SetRawInputAt(
+          index,
+          same_input == dominated_by_true ? graph_->GetIntConstant(1) : graph_->GetIntConstant(0));
+    }
+  }
+
+  block->AddPhi(new_phi);
+  if_instruction->ReplaceInput(new_phi, 0);
+
+  // Remove the old input now, if possible. This allows the branch redirection in SimplifyIf to
+  // work without waiting for another pass of DCE.
+  if (input->IsDeadAndRemovable()) {
+    DCHECK(!same_input)
+        << " if both blocks have the same condition, it shouldn't be dead and removable since the "
+        << "dominator block's If instruction would be using that condition.";
+    input->GetBlock()->RemoveInstruction(input);
+  }
+  MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyIfAddedPhi);
+}
+
 void HDeadCodeElimination::ConnectSuccessiveBlocks() {
   // Order does not matter. Skip the entry block by starting at index 1 in reverse post order.
   for (size_t i = 1u, size = graph_->GetReversePostOrder().size(); i != size; ++i) {
@@ -466,7 +591,192 @@
   }
 }
 
-bool HDeadCodeElimination::RemoveDeadBlocks() {
+struct HDeadCodeElimination::TryBelongingInformation {
+  explicit TryBelongingInformation(ScopedArenaAllocator* allocator)
+      : blocks_in_try(allocator->Adapter(kArenaAllocDCE)),
+        coalesced_try_entries(allocator->Adapter(kArenaAllocDCE)) {}
+
+  // Which blocks belong in the try.
+  ScopedArenaSet<HBasicBlock*> blocks_in_try;
+  // Which other try entries are referencing this same try.
+  ScopedArenaSet<HBasicBlock*> coalesced_try_entries;
+};
+
+bool HDeadCodeElimination::CanPerformTryRemoval(const TryBelongingInformation& try_belonging_info) {
+  for (HBasicBlock* block : try_belonging_info.blocks_in_try) {
+    for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
+      if (it.Current()->CanThrow()) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+void HDeadCodeElimination::DisconnectHandlersAndUpdateTryBoundary(
+    HBasicBlock* block,
+    /* out */ bool* any_block_in_loop) {
+  if (block->IsInLoop()) {
+    *any_block_in_loop = true;
+  }
+
+  // Disconnect the handlers.
+  while (block->GetSuccessors().size() > 1) {
+    HBasicBlock* handler = block->GetSuccessors()[1];
+    DCHECK(handler->IsCatchBlock());
+    block->RemoveSuccessor(handler);
+    handler->RemovePredecessor(block);
+    if (handler->IsInLoop()) {
+      *any_block_in_loop = true;
+    }
+  }
+
+  // Change TryBoundary to Goto.
+  DCHECK(block->EndsWithTryBoundary());
+  HInstruction* last = block->GetLastInstruction();
+  block->RemoveInstruction(last);
+  block->AddInstruction(new (graph_->GetAllocator()) HGoto(last->GetDexPc()));
+  DCHECK_EQ(block->GetSuccessors().size(), 1u);
+}
+
+void HDeadCodeElimination::RemoveTry(HBasicBlock* try_entry,
+                                     const TryBelongingInformation& try_belonging_info,
+                                     /* out */ bool* any_block_in_loop) {
+  // Update all try entries.
+  DCHECK(try_entry->EndsWithTryBoundary());
+  DCHECK(try_entry->GetLastInstruction()->AsTryBoundary()->IsEntry());
+  DisconnectHandlersAndUpdateTryBoundary(try_entry, any_block_in_loop);
+
+  for (HBasicBlock* other_try_entry : try_belonging_info.coalesced_try_entries) {
+    DCHECK(other_try_entry->EndsWithTryBoundary());
+    DCHECK(other_try_entry->GetLastInstruction()->AsTryBoundary()->IsEntry());
+    DisconnectHandlersAndUpdateTryBoundary(other_try_entry, any_block_in_loop);
+  }
+
+  // Update the blocks in the try.
+  for (HBasicBlock* block : try_belonging_info.blocks_in_try) {
+    // Update the try catch information since now the try doesn't exist.
+    block->SetTryCatchInformation(nullptr);
+    if (block->IsInLoop()) {
+      *any_block_in_loop = true;
+    }
+
+    if (block->EndsWithTryBoundary()) {
+      // Try exits.
+      DCHECK(!block->GetLastInstruction()->AsTryBoundary()->IsEntry());
+      DisconnectHandlersAndUpdateTryBoundary(block, any_block_in_loop);
+
+      if (block->GetSingleSuccessor()->IsExitBlock()) {
+        // `block` used to be a single exit TryBoundary that got turned into a Goto. It
+        // is now pointing to the exit which we don't allow. To fix it, we disconnect
+        // `block` from its predecessor and RemoveDeadBlocks will remove it from the
+        // graph.
+        DCHECK(block->IsSingleGoto());
+        HBasicBlock* predecessor = block->GetSinglePredecessor();
+        predecessor->ReplaceSuccessor(block, graph_->GetExitBlock());
+
+        if (!block->GetDominatedBlocks().empty()) {
+          // Update domination tree if `block` dominates a block to keep the graph consistent.
+          DCHECK_EQ(block->GetDominatedBlocks().size(), 1u);
+          DCHECK_EQ(graph_->GetExitBlock()->GetDominator(), block);
+          predecessor->AddDominatedBlock(graph_->GetExitBlock());
+          graph_->GetExitBlock()->SetDominator(predecessor);
+          block->RemoveDominatedBlock(graph_->GetExitBlock());
+        }
+      }
+    }
+  }
+}
+
+bool HDeadCodeElimination::RemoveUnneededTries() {
+  if (!graph_->HasTryCatch()) {
+    return false;
+  }
+
+  // Use local allocator for allocating memory.
+  ScopedArenaAllocator allocator(graph_->GetArenaStack());
+
+  // Collect which blocks are part of which try.
+  std::unordered_map<HBasicBlock*, TryBelongingInformation> tries;
+  for (HBasicBlock* block : graph_->GetReversePostOrderSkipEntryBlock()) {
+    if (block->IsTryBlock()) {
+      HBasicBlock* key = block->GetTryCatchInformation()->GetTryEntry().GetBlock();
+      auto it = tries.find(key);
+      if (it == tries.end()) {
+        it = tries.insert({key, TryBelongingInformation(&allocator)}).first;
+      }
+      it->second.blocks_in_try.insert(block);
+    }
+  }
+
+  // Deduplicate the tries which have different try entries but they are really the same try.
+  for (auto it = tries.begin(); it != tries.end(); it++) {
+    DCHECK(it->first->EndsWithTryBoundary());
+    HTryBoundary* try_boundary = it->first->GetLastInstruction()->AsTryBoundary();
+    for (auto other_it = next(it); other_it != tries.end(); /*other_it++ in the loop*/) {
+      DCHECK(other_it->first->EndsWithTryBoundary());
+      HTryBoundary* other_try_boundary = other_it->first->GetLastInstruction()->AsTryBoundary();
+      if (try_boundary->HasSameExceptionHandlersAs(*other_try_boundary)) {
+        // Merge the entries as they are really the same one.
+        // Block merging.
+        it->second.blocks_in_try.insert(other_it->second.blocks_in_try.begin(),
+                                        other_it->second.blocks_in_try.end());
+
+        // Add the coalesced try entry to update it too.
+        it->second.coalesced_try_entries.insert(other_it->first);
+
+        // Erase the other entry.
+        other_it = tries.erase(other_it);
+      } else {
+        other_it++;
+      }
+    }
+  }
+
+  size_t removed_tries = 0;
+  bool any_block_in_loop = false;
+
+  // Check which tries contain throwing instructions.
+  for (const auto& entry : tries) {
+    if (CanPerformTryRemoval(entry.second)) {
+      ++removed_tries;
+      RemoveTry(entry.first, entry.second, &any_block_in_loop);
+    }
+  }
+
+  if (removed_tries != 0) {
+    // We want to:
+    //   1) Update the dominance information
+    //   2) Remove catch block subtrees, if they are now unreachable.
+    // If we run the dominance recomputation without removing the code, those catch blocks will
+    // not be part of the post order and won't be removed. If we don't run the dominance
+    // recomputation, we risk RemoveDeadBlocks not running it and leaving the graph in an
+    // inconsistent state. So, what we can do is run RemoveDeadBlocks and force a recomputation.
+    // Note that we are not guaranteed to remove a catch block if we have nested try blocks:
+    //
+    //   try {
+    //     ... nothing can throw. TryBoundary A ...
+    //     try {
+    //       ... can throw. TryBoundary B...
+    //     } catch (Error e) {}
+    //   } catch (Exception e) {}
+    //
+    // In the example above, we can remove the TryBoundary A but the Exception catch cannot be
+    // removed as the TryBoundary B might still throw into that catch. TryBoundary A and B don't get
+    // coalesced since they have different catch handlers.
+
+    RemoveDeadBlocks(/* force_recomputation= */ true, any_block_in_loop);
+    MaybeRecordStat(stats_, MethodCompilationStat::kRemovedTry, removed_tries);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+bool HDeadCodeElimination::RemoveDeadBlocks(bool force_recomputation,
+                                            bool force_loop_recomputation) {
+  DCHECK_IMPLIES(force_loop_recomputation, force_recomputation);
+
   // Use local allocator for allocating memory.
   ScopedArenaAllocator allocator(graph_->GetArenaStack());
 
@@ -495,8 +805,8 @@
 
   // If we removed at least one block, we need to recompute the full
   // dominator tree and try block membership.
-  if (removed_one_or_more_blocks) {
-    if (rerun_dominance_and_loop_analysis) {
+  if (removed_one_or_more_blocks || force_recomputation) {
+    if (rerun_dominance_and_loop_analysis || force_loop_recomputation) {
       graph_->ClearLoopInformation();
       graph_->ClearDominanceInformation();
       graph_->BuildDominatorTree();
@@ -530,6 +840,33 @@
   }
 }
 
+void HDeadCodeElimination::UpdateGraphFlags() {
+  bool has_monitor_operations = false;
+  bool has_simd = false;
+  bool has_bounds_checks = false;
+  bool has_always_throwing_invokes = false;
+
+  for (HBasicBlock* block : graph_->GetReversePostOrder()) {
+    for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
+      HInstruction* instruction = it.Current();
+      if (instruction->IsMonitorOperation()) {
+        has_monitor_operations = true;
+      } else if (instruction->IsVecOperation()) {
+        has_simd = true;
+      } else if (instruction->IsBoundsCheck()) {
+        has_bounds_checks = true;
+      } else if (instruction->IsInvoke() && instruction->AsInvoke()->AlwaysThrows()) {
+        has_always_throwing_invokes = true;
+      }
+    }
+  }
+
+  graph_->SetHasMonitorOperations(has_monitor_operations);
+  graph_->SetHasSIMD(has_simd);
+  graph_->SetHasBoundsChecks(has_bounds_checks);
+  graph_->SetHasAlwaysThrowingInvokes(has_always_throwing_invokes);
+}
+
 bool HDeadCodeElimination::Run() {
   // Do not eliminate dead blocks if the graph has irreducible loops. We could
   // support it, but that would require changes in our loop representation to handle
@@ -541,6 +878,11 @@
     did_any_simplification |= SimplifyAlwaysThrows();
     did_any_simplification |= SimplifyIfs();
     did_any_simplification |= RemoveDeadBlocks();
+    // We call RemoveDeadBlocks before RemoveUnneededTries to remove the dead blocks from the
+    // previous optimizations. Otherwise, we might detect that a try has throwing instructions but
+    // they are actually dead code. RemoveUnneededTryBoundary will call RemoveDeadBlocks again if
+    // needed.
+    did_any_simplification |= RemoveUnneededTries();
     if (did_any_simplification) {
       // Connect successive blocks created by dead branches.
       ConnectSuccessiveBlocks();
@@ -548,6 +890,7 @@
   }
   SsaRedundantPhiElimination(graph_).Run();
   RemoveDeadInstructions();
+  UpdateGraphFlags();
   return true;
 }
 
diff --git a/compiler/optimizing/dead_code_elimination.h b/compiler/optimizing/dead_code_elimination.h
index 799721a..ddd01f7 100644
--- a/compiler/optimizing/dead_code_elimination.h
+++ b/compiler/optimizing/dead_code_elimination.h
@@ -17,11 +17,12 @@
 #ifndef ART_COMPILER_OPTIMIZING_DEAD_CODE_ELIMINATION_H_
 #define ART_COMPILER_OPTIMIZING_DEAD_CODE_ELIMINATION_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 #include "optimizing_compiler_stats.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Optimization pass performing dead code elimination (removal of
@@ -39,11 +40,87 @@
  private:
   void MaybeRecordDeadBlock(HBasicBlock* block);
   void MaybeRecordSimplifyIf();
-  bool RemoveDeadBlocks();
+  // If `force_recomputation` is true, we will recompute the dominance information even when we
+  // didn't delete any blocks. `force_loop_recomputation` is similar but it also forces the loop
+  // information recomputation.
+  bool RemoveDeadBlocks(bool force_recomputation = false, bool force_loop_recomputation = false);
   void RemoveDeadInstructions();
   bool SimplifyAlwaysThrows();
+  // Simplify the pattern:
+  //
+  //        B1    B2    ...
+  //       goto  goto  goto
+  //         \    |    /
+  //          \   |   /
+  //             B3
+  //     i1 = phi(input, input)
+  //     (i2 = condition on i1)
+  //        if i1 (or i2)
+  //          /     \
+  //         /       \
+  //        B4       B5
+  //
+  // Into:
+  //
+  //       B1      B2    ...
+  //        |      |      |
+  //       B4      B5    B?
+  //
+  // Note that individual edges can be redirected (for example B2->B3
+  // can be redirected as B2->B5) without applying this optimization
+  // to other incoming edges.
+  //
+  // Note that we rely on the dead code elimination to get rid of B3.
   bool SimplifyIfs();
   void ConnectSuccessiveBlocks();
+  // Updates the graph flags related to instructions (e.g. HasSIMD()) since we may have eliminated
+  // the relevant instructions. There's no need to update `SetHasTryCatch` since we do that in
+  // `ComputeTryBlockInformation`. Similarly with `HasLoops` and `HasIrreducibleLoops`: They are
+  // cleared in `ClearLoopInformation` and then set as true as part of `HLoopInformation::Populate`,
+  // if needed.
+  void UpdateGraphFlags();
+
+  // Helper struct to eliminate tries.
+  struct TryBelongingInformation;
+  // Disconnects `block`'s handlers and update its `TryBoundary` instruction to a `Goto`.
+  // Sets `any_block_in_loop` to true if any block is currently a loop to later update the loop
+  // information if needed.
+  void DisconnectHandlersAndUpdateTryBoundary(HBasicBlock* block,
+                                              /* out */ bool* any_block_in_loop);
+  // Returns true iff the try doesn't contain throwing instructions.
+  bool CanPerformTryRemoval(const TryBelongingInformation& try_belonging_info);
+  // Removes the try by disconnecting all try entries and exits from their handlers. Also updates
+  // the graph in the case that a `TryBoundary` instruction of kind `exit` has the Exit block as
+  // its successor.
+  void RemoveTry(HBasicBlock* try_entry,
+                 const TryBelongingInformation& try_belonging_info,
+                 bool* any_block_in_loop);
+  // Checks which tries (if any) are currently in the graph, coalesces the different try entries
+  // that are referencing the same try, and removes the tries which don't contain any throwing
+  // instructions.
+  bool RemoveUnneededTries();
+
+  // Adds a phi in `block`, if `block` and its dominator have the same (or opposite) condition.
+  // For example it turns:
+  // if(cond)
+  //   /  \
+  //  B1  B2
+  //   \ /
+  // if(cond)
+  //   /  \
+  //  B3  B4
+  //
+  // into:
+  // if(cond)
+  //   /  \
+  //  B1  B2
+  //   \ /
+  // if(Phi(1, 0))
+  //   /  \
+  //  B3  B4
+  //
+  // Following this, SimplifyIfs is able to connect B1->B3 and B2->B4 effectively skipping an if.
+  void MaybeAddPhi(HBasicBlock* block);
 
   DISALLOW_COPY_AND_ASSIGN(HDeadCodeElimination);
 };
diff --git a/compiler/optimizing/dead_code_elimination_test.cc b/compiler/optimizing/dead_code_elimination_test.cc
index f5cd4dc..b789434a 100644
--- a/compiler/optimizing/dead_code_elimination_test.cc
+++ b/compiler/optimizing/dead_code_elimination_test.cc
@@ -16,6 +16,7 @@
 
 #include "dead_code_elimination.h"
 
+#include "base/macros.h"
 #include "driver/compiler_options.h"
 #include "graph_checker.h"
 #include "optimizing_unit_test.h"
@@ -23,9 +24,9 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
-class DeadCodeEliminationTest : public OptimizingUnitTest {
+class DeadCodeEliminationTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   void TestCode(const std::vector<uint16_t>& data,
                 const std::string& expected_before,
diff --git a/compiler/optimizing/dominator_test.cc b/compiler/optimizing/dominator_test.cc
index 1d72ba1..5f366eb 100644
--- a/compiler/optimizing/dominator_test.cc
+++ b/compiler/optimizing/dominator_test.cc
@@ -15,6 +15,7 @@
  */
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "dex/dex_instruction.h"
 #include "nodes.h"
@@ -22,9 +23,9 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
-class OptimizerTest : public OptimizingUnitTest {
+class OptimizerTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   void TestCode(const std::vector<uint16_t>& data, const uint32_t* blocks, size_t blocks_length);
 };
diff --git a/compiler/optimizing/escape.cc b/compiler/optimizing/escape.cc
index 617833c..cebe94f 100644
--- a/compiler/optimizing/escape.cc
+++ b/compiler/optimizing/escape.cc
@@ -18,7 +18,7 @@
 
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 void VisitEscapes(HInstruction* reference, EscapeVisitor& escape_visitor) {
   // References not allocated in the method are intrinsically escaped.
diff --git a/compiler/optimizing/escape.h b/compiler/optimizing/escape.h
index 5402cb1..3b284fb 100644
--- a/compiler/optimizing/escape.h
+++ b/compiler/optimizing/escape.h
@@ -17,7 +17,9 @@
 #ifndef ART_COMPILER_OPTIMIZING_ESCAPE_H_
 #define ART_COMPILER_OPTIMIZING_ESCAPE_H_
 
-namespace art {
+#include "base/macros.h"
+
+namespace art HIDDEN {
 
 class HInstruction;
 
diff --git a/compiler/optimizing/execution_subgraph.cc b/compiler/optimizing/execution_subgraph.cc
index 66fdfcd..06aabbe 100644
--- a/compiler/optimizing/execution_subgraph.cc
+++ b/compiler/optimizing/execution_subgraph.cc
@@ -26,7 +26,7 @@
 #include "base/scoped_arena_allocator.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 ExecutionSubgraph::ExecutionSubgraph(HGraph* graph, ScopedArenaAllocator* allocator)
     : graph_(graph),
diff --git a/compiler/optimizing/execution_subgraph.h b/compiler/optimizing/execution_subgraph.h
index 7d2a660..5ddf17d 100644
--- a/compiler/optimizing/execution_subgraph.h
+++ b/compiler/optimizing/execution_subgraph.h
@@ -27,6 +27,7 @@
 #include "base/bit_vector-inl.h"
 #include "base/globals.h"
 #include "base/iteration_range.h"
+#include "base/macros.h"
 #include "base/mutex.h"
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
@@ -34,7 +35,7 @@
 #include "base/transform_iterator.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Helper for transforming blocks to block_ids.
 class BlockToBlockIdTransformer {
diff --git a/compiler/optimizing/execution_subgraph_test.cc b/compiler/optimizing/execution_subgraph_test.cc
index 74c243b..921ef05 100644
--- a/compiler/optimizing/execution_subgraph_test.cc
+++ b/compiler/optimizing/execution_subgraph_test.cc
@@ -37,7 +37,7 @@
 #include "optimizing_unit_test.h"
 #include "scoped_thread_state_change.h"
 
-namespace art {
+namespace art HIDDEN {
 
 using BlockSet = std::unordered_set<const HBasicBlock*>;
 
diff --git a/compiler/optimizing/execution_subgraph_test.h b/compiler/optimizing/execution_subgraph_test.h
index 13cb2bc..cee105a 100644
--- a/compiler/optimizing/execution_subgraph_test.h
+++ b/compiler/optimizing/execution_subgraph_test.h
@@ -19,7 +19,9 @@
 
 #include "android-base/macros.h"
 
-namespace art {
+#include "base/macros.h"
+
+namespace art HIDDEN {
 
 class HGraph;
 class ExecutionSubgraph;
diff --git a/compiler/optimizing/find_loops_test.cc b/compiler/optimizing/find_loops_test.cc
index 75b8e96..8857b2a 100644
--- a/compiler/optimizing/find_loops_test.cc
+++ b/compiler/optimizing/find_loops_test.cc
@@ -15,6 +15,7 @@
  */
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "dex/dex_file.h"
 #include "dex/dex_instruction.h"
@@ -25,9 +26,9 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
-class FindLoopsTest : public OptimizingUnitTest {};
+class FindLoopsTest : public CommonCompilerTest, public OptimizingUnitTestHelper {};
 
 TEST_F(FindLoopsTest, CFG1) {
   // Constant is not used.
diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc
index d1769ce..190b362 100644
--- a/compiler/optimizing/graph_checker.cc
+++ b/compiler/optimizing/graph_checker.cc
@@ -32,7 +32,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "subtype_check.h"
 
-namespace art {
+namespace art HIDDEN {
 
 using android::base::StringPrintf;
 
@@ -80,9 +80,91 @@
   // as the latter might visit dead blocks removed by the dominator
   // computation.
   VisitReversePostOrder();
+  CheckGraphFlags();
   return current_size;
 }
 
+void GraphChecker::VisitReversePostOrder() {
+  for (HBasicBlock* block : GetGraph()->GetReversePostOrder()) {
+    if (block->IsInLoop()) {
+      flag_info_.seen_loop = true;
+      if (block->GetLoopInformation()->IsIrreducible()) {
+        flag_info_.seen_irreducible_loop = true;
+      }
+    }
+
+    VisitBasicBlock(block);
+  }
+}
+
+static const char* StrBool(bool val) {
+  return val ? "true" : "false";
+}
+
+void GraphChecker::CheckGraphFlags() {
+  if (GetGraph()->HasMonitorOperations() != flag_info_.seen_monitor_operation) {
+    AddError(
+        StringPrintf("Flag mismatch: HasMonitorOperations() (%s) should be equal to "
+                     "flag_info_.seen_monitor_operation (%s)",
+                     StrBool(GetGraph()->HasMonitorOperations()),
+                     StrBool(flag_info_.seen_monitor_operation)));
+  }
+
+  if (GetGraph()->HasTryCatch() != flag_info_.seen_try_boundary) {
+    AddError(
+        StringPrintf("Flag mismatch: HasTryCatch() (%s) should be equal to "
+                     "flag_info_.seen_try_boundary (%s)",
+                     StrBool(GetGraph()->HasTryCatch()),
+                     StrBool(flag_info_.seen_try_boundary)));
+  }
+
+  if (GetGraph()->HasLoops() != flag_info_.seen_loop) {
+    AddError(
+        StringPrintf("Flag mismatch: HasLoops() (%s) should be equal to "
+                     "flag_info_.seen_loop (%s)",
+                     StrBool(GetGraph()->HasLoops()),
+                     StrBool(flag_info_.seen_loop)));
+  }
+
+  if (GetGraph()->HasIrreducibleLoops() && !GetGraph()->HasLoops()) {
+    AddError(StringPrintf("Flag mismatch: HasIrreducibleLoops() (%s) implies HasLoops() (%s)",
+                          StrBool(GetGraph()->HasIrreducibleLoops()),
+                          StrBool(GetGraph()->HasLoops())));
+  }
+
+  if (GetGraph()->HasIrreducibleLoops() != flag_info_.seen_irreducible_loop) {
+    AddError(
+        StringPrintf("Flag mismatch: HasIrreducibleLoops() (%s) should be equal to "
+                     "flag_info_.seen_irreducible_loop (%s)",
+                     StrBool(GetGraph()->HasIrreducibleLoops()),
+                     StrBool(flag_info_.seen_irreducible_loop)));
+  }
+
+  if (GetGraph()->HasSIMD() != flag_info_.seen_SIMD) {
+    AddError(
+        StringPrintf("Flag mismatch: HasSIMD() (%s) should be equal to "
+                     "flag_info_.seen_SIMD (%s)",
+                     StrBool(GetGraph()->HasSIMD()),
+                     StrBool(flag_info_.seen_SIMD)));
+  }
+
+  if (GetGraph()->HasBoundsChecks() != flag_info_.seen_bounds_checks) {
+    AddError(
+        StringPrintf("Flag mismatch: HasBoundsChecks() (%s) should be equal to "
+                     "flag_info_.seen_bounds_checks (%s)",
+                     StrBool(GetGraph()->HasBoundsChecks()),
+                     StrBool(flag_info_.seen_bounds_checks)));
+  }
+
+  if (GetGraph()->HasAlwaysThrowingInvokes() != flag_info_.seen_always_throwing_invokes) {
+    AddError(
+        StringPrintf("Flag mismatch: HasAlwaysThrowingInvokes() (%s) should be equal to "
+                     "flag_info_.seen_always_throwing_invokes (%s)",
+                     StrBool(GetGraph()->HasAlwaysThrowingInvokes()),
+                     StrBool(flag_info_.seen_always_throwing_invokes)));
+  }
+}
+
 void GraphChecker::VisitBasicBlock(HBasicBlock* block) {
   current_block_ = block;
 
@@ -159,6 +241,24 @@
     }
   }
 
+  // Make sure the first instruction of a catch block is always a Nop that emits an environment.
+  if (block->IsCatchBlock()) {
+    if (!block->GetFirstInstruction()->IsNop()) {
+      AddError(StringPrintf("Block %d doesn't have a Nop as its first instruction.",
+                            current_block_->GetBlockId()));
+    } else {
+      HNop* nop = block->GetFirstInstruction()->AsNop();
+      if (!nop->NeedsEnvironment()) {
+        AddError(
+            StringPrintf("%s:%d is a Nop and the first instruction of block %d, but it doesn't "
+                         "need an environment.",
+                         nop->DebugName(),
+                         nop->GetId(),
+                         current_block_->GetBlockId()));
+      }
+    }
+  }
+
   // Visit this block's list of phis.
   for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
     HInstruction* current = it.Current();
@@ -219,6 +319,12 @@
     }
   }
 
+  // Ensure all blocks have at least one successor, except the Exit block.
+  if (block->GetSuccessors().empty() && !block->IsExitBlock()) {
+    AddError(StringPrintf("Block %d has no successor and it is not the Exit block.",
+                          block->GetBlockId()));
+  }
+
   // Ensure there is no critical edge (i.e., an edge connecting a
   // block with multiple successors to a block with multiple
   // predecessors). Exceptional edges are synthesized and hence
@@ -291,27 +397,30 @@
 }
 
 void GraphChecker::VisitBoundsCheck(HBoundsCheck* check) {
+  VisitInstruction(check);
+
   if (!GetGraph()->HasBoundsChecks()) {
-    AddError(StringPrintf("Instruction %s:%d is a HBoundsCheck, "
-                          "but HasBoundsChecks() returns false",
-                          check->DebugName(),
-                          check->GetId()));
+    AddError(
+        StringPrintf("The graph doesn't have the HasBoundsChecks flag set but we saw "
+                     "%s:%d in block %d.",
+                     check->DebugName(),
+                     check->GetId(),
+                     check->GetBlock()->GetBlockId()));
   }
 
-  // Perform the instruction base checks too.
-  VisitInstruction(check);
+  flag_info_.seen_bounds_checks = true;
 }
 
 void GraphChecker::VisitDeoptimize(HDeoptimize* deopt) {
+  VisitInstruction(deopt);
   if (GetGraph()->IsCompilingOsr()) {
     AddError(StringPrintf("A graph compiled OSR cannot have a HDeoptimize instruction"));
   }
-
-  // Perform the instruction base checks too.
-  VisitInstruction(deopt);
 }
 
 void GraphChecker::VisitTryBoundary(HTryBoundary* try_boundary) {
+  VisitInstruction(try_boundary);
+
   ArrayRef<HBasicBlock* const> handlers = try_boundary->GetExceptionHandlers();
 
   // Ensure that all exception handlers are catch blocks.
@@ -338,24 +447,65 @@
     }
   }
 
-  VisitInstruction(try_boundary);
+  if (!GetGraph()->HasTryCatch()) {
+    AddError(
+        StringPrintf("The graph doesn't have the HasTryCatch flag set but we saw "
+                     "%s:%d in block %d.",
+                     try_boundary->DebugName(),
+                     try_boundary->GetId(),
+                     try_boundary->GetBlock()->GetBlockId()));
+  }
+
+  flag_info_.seen_try_boundary = true;
+}
+
+void GraphChecker::VisitLoadClass(HLoadClass* load) {
+  VisitInstruction(load);
+
+  if (load->GetLoadedClassRTI().IsValid() && !load->GetLoadedClassRTI().IsExact()) {
+    std::stringstream ssRTI;
+    ssRTI << load->GetLoadedClassRTI();
+    AddError(StringPrintf("%s:%d in block %d with RTI %s has valid but inexact RTI.",
+                          load->DebugName(),
+                          load->GetId(),
+                          load->GetBlock()->GetBlockId(),
+                          ssRTI.str().c_str()));
+  }
 }
 
 void GraphChecker::VisitLoadException(HLoadException* load) {
-  // Ensure that LoadException is the first instruction in a catch block.
+  VisitInstruction(load);
+
+  // Ensure that LoadException is the second instruction in a catch block. The first one should be a
+  // Nop (checked separately).
   if (!load->GetBlock()->IsCatchBlock()) {
     AddError(StringPrintf("%s:%d is in a non-catch block %d.",
                           load->DebugName(),
                           load->GetId(),
                           load->GetBlock()->GetBlockId()));
-  } else if (load->GetBlock()->GetFirstInstruction() != load) {
-    AddError(StringPrintf("%s:%d is not the first instruction in catch block %d.",
+  } else if (load->GetBlock()->GetFirstInstruction()->GetNext() != load) {
+    AddError(StringPrintf("%s:%d is not the second instruction in catch block %d.",
                           load->DebugName(),
                           load->GetId(),
                           load->GetBlock()->GetBlockId()));
   }
 }
 
+void GraphChecker::VisitMonitorOperation(HMonitorOperation* monitor_op) {
+  VisitInstruction(monitor_op);
+
+  if (!GetGraph()->HasMonitorOperations()) {
+    AddError(
+        StringPrintf("The graph doesn't have the HasMonitorOperations flag set but we saw "
+                     "%s:%d in block %d.",
+                     monitor_op->DebugName(),
+                     monitor_op->GetId(),
+                     monitor_op->GetBlock()->GetBlockId()));
+  }
+
+  flag_info_.seen_monitor_operation = true;
+}
+
 void GraphChecker::VisitInstruction(HInstruction* instruction) {
   if (seen_ids_.IsBitSet(instruction->GetId())) {
     AddError(StringPrintf("Instruction id %d is duplicate in graph.",
@@ -497,33 +647,16 @@
     }
   }
 
-  // Ensure that reference type instructions have reference type info.
-  if (check_reference_type_info_ && instruction->GetType() == DataType::Type::kReference) {
-    if (!instruction->GetReferenceTypeInfo().IsValid()) {
-      AddError(StringPrintf("Reference type instruction %s:%d does not have "
-                            "valid reference type information.",
-                            instruction->DebugName(),
-                            instruction->GetId()));
-    }
-  }
-
   if (instruction->CanThrow() && !instruction->HasEnvironment()) {
     AddError(StringPrintf("Throwing instruction %s:%d in block %d does not have an environment.",
                           instruction->DebugName(),
                           instruction->GetId(),
                           current_block_->GetBlockId()));
   } else if (instruction->CanThrowIntoCatchBlock()) {
-    // Find the top-level environment. This corresponds to the environment of
-    // the catch block since we do not inline methods with try/catch.
-    HEnvironment* environment = instruction->GetEnvironment();
-    while (environment->GetParent() != nullptr) {
-      environment = environment->GetParent();
-    }
-
-    // Find all catch blocks and test that `instruction` has an environment
-    // value for each one.
+    // Find all catch blocks and test that `instruction` has an environment value for each one.
     const HTryBoundary& entry = instruction->GetBlock()->GetTryCatchInformation()->GetTryEntry();
     for (HBasicBlock* catch_block : entry.GetExceptionHandlers()) {
+      const HEnvironment* environment = catch_block->GetFirstInstruction()->GetEnvironment();
       for (HInstructionIterator phi_it(catch_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
         HPhi* catch_phi = phi_it.Current()->AsPhi();
         if (environment->GetInstructionAt(catch_phi->GetRegNumber()) == nullptr) {
@@ -541,9 +674,26 @@
   }
 }
 
-void GraphChecker::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) {
+void GraphChecker::VisitInvoke(HInvoke* invoke) {
   VisitInstruction(invoke);
 
+  if (invoke->AlwaysThrows()) {
+    if (!GetGraph()->HasAlwaysThrowingInvokes()) {
+      AddError(
+          StringPrintf("The graph doesn't have the HasAlwaysThrowingInvokes flag set but we saw "
+                       "%s:%d in block %d and it always throws.",
+                       invoke->DebugName(),
+                       invoke->GetId(),
+                       invoke->GetBlock()->GetBlockId()));
+    }
+    flag_info_.seen_always_throwing_invokes = true;
+  }
+}
+
+void GraphChecker::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) {
+  // We call VisitInvoke and not VisitInstruction to de-duplicate the always throwing code check.
+  VisitInvoke(invoke);
+
   if (invoke->IsStaticWithExplicitClinitCheck()) {
     const HInstruction* last_input = invoke->GetInputs().back();
     if (last_input == nullptr) {
@@ -612,6 +762,17 @@
 
 void GraphChecker::HandleTypeCheckInstruction(HTypeCheckInstruction* check) {
   VisitInstruction(check);
+
+  if (check->GetTargetClassRTI().IsValid() && !check->GetTargetClassRTI().IsExact()) {
+    std::stringstream ssRTI;
+    ssRTI << check->GetTargetClassRTI();
+    AddError(StringPrintf("%s:%d in block %d with RTI %s has valid but inexact RTI.",
+                          check->DebugName(),
+                          check->GetId(),
+                          check->GetBlock()->GetBlockId(),
+                          ssRTI.str().c_str()));
+  }
+
   HInstruction* input = check->InputAt(1);
   if (check->GetTypeCheckKind() == TypeCheckKind::kBitstringCheck) {
     if (!input->IsNullConstant()) {
@@ -674,13 +835,14 @@
         loop_information->GetPreHeader()->GetSuccessors().size()));
   }
 
-  if (loop_information->GetSuspendCheck() == nullptr) {
-    AddError(StringPrintf(
-        "Loop with header %d does not have a suspend check.",
-        loop_header->GetBlockId()));
+  if (!GetGraph()->SuspendChecksAreAllowedToNoOp() &&
+      loop_information->GetSuspendCheck() == nullptr) {
+    AddError(StringPrintf("Loop with header %d does not have a suspend check.",
+                          loop_header->GetBlockId()));
   }
 
-  if (loop_information->GetSuspendCheck() != loop_header->GetFirstInstructionDisregardMoves()) {
+  if (!GetGraph()->SuspendChecksAreAllowedToNoOp() &&
+      loop_information->GetSuspendCheck() != loop_header->GetFirstInstructionDisregardMoves()) {
     AddError(StringPrintf(
         "Loop header %d does not have the loop suspend check as the first instruction.",
         loop_header->GetBlockId()));
@@ -1051,6 +1213,21 @@
   }
 }
 
+void GraphChecker::VisitArraySet(HArraySet* instruction) {
+  VisitInstruction(instruction);
+
+  if (instruction->NeedsTypeCheck() !=
+      instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC())) {
+    AddError(
+        StringPrintf("%s %d has a flag mismatch. An ArraySet instruction can trigger a GC iff it "
+                     "needs a type check. Needs type check: %s, Can trigger GC: %s",
+                     instruction->DebugName(),
+                     instruction->GetId(),
+                     StrBool(instruction->NeedsTypeCheck()),
+                     StrBool(instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()))));
+  }
+}
+
 void GraphChecker::VisitBinaryOperation(HBinaryOperation* op) {
   VisitInstruction(op);
   DataType::Type lhs_type = op->InputAt(0)->GetType();
@@ -1111,6 +1288,8 @@
 }
 
 void GraphChecker::VisitConstant(HConstant* instruction) {
+  VisitInstruction(instruction);
+
   HBasicBlock* block = instruction->GetBlock();
   if (!block->IsEntryBlock()) {
     AddError(StringPrintf(
@@ -1149,6 +1328,18 @@
 
 void GraphChecker::VisitVecOperation(HVecOperation* instruction) {
   VisitInstruction(instruction);
+
+  if (!GetGraph()->HasSIMD()) {
+    AddError(
+        StringPrintf("The graph doesn't have the HasSIMD flag set but we saw "
+                     "%s:%d in block %d.",
+                     instruction->DebugName(),
+                     instruction->GetId(),
+                     instruction->GetBlock()->GetBlockId()));
+  }
+
+  flag_info_.seen_SIMD = true;
+
   if (codegen_ == nullptr) {
     return;
   }
diff --git a/compiler/optimizing/graph_checker.h b/compiler/optimizing/graph_checker.h
index 04c8d21..d6644f3 100644
--- a/compiler/optimizing/graph_checker.h
+++ b/compiler/optimizing/graph_checker.h
@@ -21,10 +21,11 @@
 
 #include "base/arena_bit_vector.h"
 #include "base/bit_vector-inl.h"
+#include "base/macros.h"
 #include "base/scoped_arena_allocator.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 
@@ -54,6 +55,7 @@
   void VisitInstruction(HInstruction* instruction) override;
   void VisitPhi(HPhi* phi) override;
 
+  void VisitArraySet(HArraySet* instruction) override;
   void VisitBinaryOperation(HBinaryOperation* op) override;
   void VisitBooleanNot(HBooleanNot* instruction) override;
   void VisitBoundType(HBoundType* instruction) override;
@@ -64,8 +66,11 @@
   void VisitDeoptimize(HDeoptimize* instruction) override;
   void VisitIf(HIf* instruction) override;
   void VisitInstanceOf(HInstanceOf* check) override;
+  void VisitInvoke(HInvoke* invoke) override;
   void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) override;
+  void VisitLoadClass(HLoadClass* load) override;
   void VisitLoadException(HLoadException* load) override;
+  void VisitMonitorOperation(HMonitorOperation* monitor_operation) override;
   void VisitNeg(HNeg* instruction) override;
   void VisitPackedSwitch(HPackedSwitch* instruction) override;
   void VisitReturn(HReturn* ret) override;
@@ -102,15 +107,6 @@
     }
   }
 
-  // Enable/Disable the reference type info check.
-  //
-  // Return: the previous status of the check.
-  bool SetRefTypeInfoCheckEnabled(bool value = true) {
-    bool old_value = check_reference_type_info_;
-    check_reference_type_info_ = value;
-    return old_value;
-  }
-
  protected:
   // Report a new error.
   void AddError(const std::string& error) {
@@ -123,18 +119,30 @@
   ArenaVector<std::string> errors_;
 
  private:
+  void VisitReversePostOrder();
+
+  // Checks that the graph's flags are set correctly.
+  void CheckGraphFlags();
+
   // String displayed before dumped errors.
   const char* const dump_prefix_;
   ScopedArenaAllocator allocator_;
   ArenaBitVector seen_ids_;
-  // Whether to perform the reference type info check for instructions which use or produce
-  // object references, e.g. HNewInstance, HLoadClass.
-  // The default value is true.
-  bool check_reference_type_info_ = true;
 
   // Used to access target information.
   CodeGenerator* codegen_;
 
+  struct FlagInfo {
+    bool seen_try_boundary = false;
+    bool seen_monitor_operation = false;
+    bool seen_loop = false;
+    bool seen_irreducible_loop = false;
+    bool seen_SIMD = false;
+    bool seen_bounds_checks = false;
+    bool seen_always_throwing_invokes = false;
+  };
+  FlagInfo flag_info_;
+
   DISALLOW_COPY_AND_ASSIGN(GraphChecker);
 };
 
diff --git a/compiler/optimizing/graph_checker_test.cc b/compiler/optimizing/graph_checker_test.cc
index 08bfa5d..b256fbb 100644
--- a/compiler/optimizing/graph_checker_test.cc
+++ b/compiler/optimizing/graph_checker_test.cc
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
+#include "base/macros.h"
 #include "graph_checker.h"
 #include "optimizing_unit_test.h"
 
-namespace art {
+namespace art HIDDEN {
 
-class GraphCheckerTest : public OptimizingUnitTest {
+class GraphCheckerTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   HGraph* CreateSimpleCFG();
   void TestCode(const std::vector<uint16_t>& data);
diff --git a/compiler/optimizing/graph_test.cc b/compiler/optimizing/graph_test.cc
index 29af808..b5d7127 100644
--- a/compiler/optimizing/graph_test.cc
+++ b/compiler/optimizing/graph_test.cc
@@ -15,6 +15,7 @@
  */
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "nodes.h"
 #include "optimizing_unit_test.h"
@@ -22,7 +23,7 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class GraphTest : public OptimizingUnitTest {
  protected:
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index 4a6ee13..73bdd1e 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -43,7 +43,7 @@
 #include "ssa_liveness_analysis.h"
 #include "utils/assembler.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Unique pass-name to identify that the dump is for printing to log.
 constexpr const char* kDebugDumpName = "debug";
@@ -480,12 +480,20 @@
         << array_set->GetValueCanBeNull() << std::noboolalpha;
     StartAttributeStream("needs_type_check") << std::boolalpha
         << array_set->NeedsTypeCheck() << std::noboolalpha;
+    StartAttributeStream("can_trigger_gc")
+        << std::boolalpha << array_set->GetSideEffects().Includes(SideEffects::CanTriggerGC())
+        << std::noboolalpha;
+    StartAttributeStream("write_barrier_kind") << array_set->GetWriteBarrierKind();
   }
 
   void VisitCompare(HCompare* compare) override {
     StartAttributeStream("bias") << compare->GetBias();
   }
 
+  void VisitCondition(HCondition* condition) override {
+    StartAttributeStream("bias") << condition->GetBias();
+  }
+
   void VisitInvoke(HInvoke* invoke) override {
     StartAttributeStream("dex_file_index") << invoke->GetMethodReference().index;
     ArtMethod* method = invoke->GetResolvedMethod();
@@ -549,7 +557,9 @@
         iset->GetFieldInfo().GetDexFile().PrettyField(iset->GetFieldInfo().GetFieldIndex(),
                                                       /* with type */ false);
     StartAttributeStream("field_type") << iset->GetFieldType();
-    StartAttributeStream("predicated") << std::boolalpha << iset->GetIsPredicatedSet();
+    StartAttributeStream("predicated")
+        << std::boolalpha << iset->GetIsPredicatedSet() << std::noboolalpha;
+    StartAttributeStream("write_barrier_kind") << iset->GetWriteBarrierKind();
   }
 
   void VisitStaticFieldGet(HStaticFieldGet* sget) override {
@@ -564,6 +574,7 @@
         sset->GetFieldInfo().GetDexFile().PrettyField(sset->GetFieldInfo().GetFieldIndex(),
                                                       /* with type */ false);
     StartAttributeStream("field_type") << sset->GetFieldType();
+    StartAttributeStream("write_barrier_kind") << sset->GetWriteBarrierKind();
   }
 
   void VisitUnresolvedInstanceFieldGet(HUnresolvedInstanceFieldGet* field_access) override {
@@ -757,15 +768,7 @@
                  instruction->IsCheckCast()) {
         StartAttributeStream("klass") << "unresolved";
       } else {
-        // The NullConstant may be added to the graph during other passes that happen between
-        // ReferenceTypePropagation and Inliner (e.g. InstructionSimplifier). If the inliner
-        // doesn't run or doesn't inline anything, the NullConstant remains untyped.
-        // So we should check NullConstants for validity only after reference type propagation.
-        DCHECK(graph_in_bad_state_ ||
-               IsDebugDump() ||
-               (!is_after_pass_ && IsPass(HGraphBuilder::kBuilderPassName)))
-            << instruction->DebugName() << instruction->GetId() << " has invalid rti "
-            << (is_after_pass_ ? "after" : "before") << " pass " << pass_name_;
+        StartAttributeStream("klass") << "invalid";
       }
     }
     if (disasm_info_ != nullptr) {
@@ -904,6 +907,11 @@
 
     if (block->IsCatchBlock()) {
       PrintProperty("flags", "catch_block");
+    } else if (block->IsTryBlock()) {
+      std::stringstream flags_properties;
+      flags_properties << "try_start "
+                       << namer_.GetName(block->GetTryCatchInformation()->GetTryEntry().GetBlock());
+      PrintProperty("flags", flags_properties.str().c_str());
     } else if (!IsDebugDump()) {
       // Don't print useless information to logcat
       PrintEmptyProperty("flags");
diff --git a/compiler/optimizing/graph_visualizer.h b/compiler/optimizing/graph_visualizer.h
index 3429c11..9878917 100644
--- a/compiler/optimizing/graph_visualizer.h
+++ b/compiler/optimizing/graph_visualizer.h
@@ -22,10 +22,11 @@
 
 #include "arch/instruction_set.h"
 #include "base/arena_containers.h"
+#include "base/macros.h"
 #include "base/value_object.h"
 #include "block_namer.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 class DexCompilationUnit;
diff --git a/compiler/optimizing/gvn.cc b/compiler/optimizing/gvn.cc
index c7cd223..a6ca057 100644
--- a/compiler/optimizing/gvn.cc
+++ b/compiler/optimizing/gvn.cc
@@ -23,7 +23,7 @@
 #include "base/utils.h"
 #include "side_effects_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * A ValueSet holds instructions that can replace other instructions. It is updated
diff --git a/compiler/optimizing/gvn.h b/compiler/optimizing/gvn.h
index bbf2265..df4e3a8 100644
--- a/compiler/optimizing/gvn.h
+++ b/compiler/optimizing/gvn.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_GVN_H_
 #define ART_COMPILER_OPTIMIZING_GVN_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class SideEffectsAnalysis;
 
diff --git a/compiler/optimizing/gvn_test.cc b/compiler/optimizing/gvn_test.cc
index 3bf4cc3..1eb6307 100644
--- a/compiler/optimizing/gvn_test.cc
+++ b/compiler/optimizing/gvn_test.cc
@@ -17,12 +17,13 @@
 #include "gvn.h"
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "nodes.h"
 #include "optimizing_unit_test.h"
 #include "side_effects_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class GVNTest : public OptimizingUnitTest {};
 
diff --git a/compiler/optimizing/induction_var_analysis.cc b/compiler/optimizing/induction_var_analysis.cc
index 3b5a2f1..be6c268 100644
--- a/compiler/optimizing/induction_var_analysis.cc
+++ b/compiler/optimizing/induction_var_analysis.cc
@@ -16,9 +16,10 @@
 
 #include "induction_var_analysis.h"
 
+#include "base/scoped_arena_containers.h"
 #include "induction_var_range.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Returns true if the from/to types denote a narrowing, integral conversion (precision loss).
@@ -214,18 +215,25 @@
   size_t low_depth;
 };
 
-HInductionVarAnalysis::HInductionVarAnalysis(HGraph* graph, const char* name)
-    : HOptimization(graph, name),
+HInductionVarAnalysis::HInductionVarAnalysis(HGraph* graph,
+                                             OptimizingCompilerStats* stats,
+                                             const char* name)
+    : HOptimization(graph, name, stats),
       induction_(std::less<const HLoopInformation*>(),
                  graph->GetAllocator()->Adapter(kArenaAllocInductionVarAnalysis)),
-      cycles_(std::less<HPhi*>(),
-              graph->GetAllocator()->Adapter(kArenaAllocInductionVarAnalysis)) {
+      cycles_(std::less<HPhi*>(), graph->GetAllocator()->Adapter(kArenaAllocInductionVarAnalysis)) {
 }
 
 bool HInductionVarAnalysis::Run() {
   // Detects sequence variables (generalized induction variables) during an outer to inner
   // traversal of all loops using Gerlek's algorithm. The order is important to enable
   // range analysis on outer loop while visiting inner loops.
+
+  if (IsPathologicalCase()) {
+    MaybeRecordStat(stats_, MethodCompilationStat::kNotVarAnalyzedPathological);
+    return false;
+  }
+
   for (HBasicBlock* graph_block : graph_->GetReversePostOrder()) {
     // Don't analyze irreducible loops.
     if (graph_block->IsLoopHeader() && !graph_block->GetLoopInformation()->IsIrreducible()) {
@@ -1576,4 +1584,84 @@
   return "";
 }
 
+void HInductionVarAnalysis::CalculateLoopHeaderPhisInARow(
+    HPhi* initial_phi,
+    ScopedArenaSafeMap<HPhi*, int>& cached_values,
+    ScopedArenaAllocator& allocator) {
+  DCHECK(initial_phi->IsLoopHeaderPhi());
+  ScopedArenaQueue<HPhi*> worklist(allocator.Adapter(kArenaAllocInductionVarAnalysis));
+  worklist.push(initial_phi);
+  // Used to check which phis are in the current chain we are checking.
+  ScopedArenaSet<HPhi*> phis_in_chain(allocator.Adapter(kArenaAllocInductionVarAnalysis));
+  while (!worklist.empty()) {
+    HPhi* current_phi = worklist.front();
+    DCHECK(current_phi->IsLoopHeaderPhi());
+    if (cached_values.find(current_phi) != cached_values.end()) {
+      // Already processed.
+      worklist.pop();
+      continue;
+    }
+
+    phis_in_chain.insert(current_phi);
+    int max_value = 0;
+    bool pushed_other_phis = false;
+    for (size_t index = 0; index < current_phi->InputCount(); index++) {
+      // If the input is not a loop header phi, we only have 1 (current_phi).
+      int current_value = 1;
+      if (current_phi->InputAt(index)->IsLoopHeaderPhi()) {
+        HPhi* loop_header_phi = current_phi->InputAt(index)->AsPhi();
+        auto it = cached_values.find(loop_header_phi);
+        if (it != cached_values.end()) {
+          current_value += it->second;
+        } else if (phis_in_chain.find(current_phi) == phis_in_chain.end()) {
+          // Push phis which aren't in the chain already to be processed.
+          pushed_other_phis = true;
+          worklist.push(loop_header_phi);
+        }
+        // Phis in the chain will get processed later. We keep `current_value` as 1 to avoid
+        // double counting `loop_header_phi`.
+      }
+      max_value = std::max(max_value, current_value);
+    }
+
+    if (!pushed_other_phis) {
+      // Only finish processing after all inputs were processed.
+      worklist.pop();
+      phis_in_chain.erase(current_phi);
+      cached_values.FindOrAdd(current_phi, max_value);
+    }
+  }
+}
+
+bool HInductionVarAnalysis::IsPathologicalCase() {
+  ScopedArenaAllocator local_allocator(graph_->GetArenaStack());
+  ScopedArenaSafeMap<HPhi*, int> cached_values(
+      std::less<HPhi*>(), local_allocator.Adapter(kArenaAllocInductionVarAnalysis));
+
+  // Due to how our induction passes work, we will take a lot of time compiling if we have several
+  // loop header phis in a row. If we have more than 15 different loop header phis in a row, we
+  // don't perform the analysis.
+  constexpr int kMaximumLoopHeaderPhisInARow = 15;
+
+  for (HBasicBlock* block : graph_->GetReversePostOrder()) {
+    if (!block->IsLoopHeader()) {
+      continue;
+    }
+
+    for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
+      DCHECK(it.Current()->IsLoopHeaderPhi());
+      HPhi* phi = it.Current()->AsPhi();
+      CalculateLoopHeaderPhisInARow(phi, cached_values, local_allocator);
+      DCHECK(cached_values.find(phi) != cached_values.end())
+          << " we should have a value for Phi " << phi->GetId()
+          << " in block " << phi->GetBlock()->GetBlockId();
+      if (cached_values.find(phi)->second > kMaximumLoopHeaderPhisInARow) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
 }  // namespace art
diff --git a/compiler/optimizing/induction_var_analysis.h b/compiler/optimizing/induction_var_analysis.h
index 0941772..0509500 100644
--- a/compiler/optimizing/induction_var_analysis.h
+++ b/compiler/optimizing/induction_var_analysis.h
@@ -21,11 +21,12 @@
 
 #include "base/arena_containers.h"
 #include "base/array_ref.h"
+#include "base/macros.h"
 #include "base/scoped_arena_containers.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Induction variable analysis. This class does not have a direct public API.
@@ -38,7 +39,9 @@
  */
 class HInductionVarAnalysis : public HOptimization {
  public:
-  explicit HInductionVarAnalysis(HGraph* graph, const char* name = kInductionPassName);
+  explicit HInductionVarAnalysis(HGraph* graph,
+                                 OptimizingCompilerStats* stats = nullptr,
+                                 const char* name = kInductionPassName);
 
   bool Run() override;
 
@@ -307,6 +310,15 @@
   static std::string FetchToString(HInstruction* fetch);
   static std::string InductionToString(InductionInfo* info);
 
+  // Returns true if we have a pathological case we don't want to analyze.
+  bool IsPathologicalCase();
+  // Starting with initial_phi, it calculates how many loop header phis in a row we have. To do
+  // this, we count the loop header phi which are used as an input of other loop header phis. It
+  // uses `cached_values` to avoid recomputing results.
+  void CalculateLoopHeaderPhisInARow(HPhi* initial_phi,
+                                     ScopedArenaSafeMap<HPhi*, int>& cached_values,
+                                     ScopedArenaAllocator& allocator);
+
   /**
    * Maintains the results of the analysis as a mapping from loops to a mapping from instructions
    * to the induction information for that instruction in that loop.
diff --git a/compiler/optimizing/induction_var_analysis_test.cc b/compiler/optimizing/induction_var_analysis_test.cc
index 4c11ad4..80c1537 100644
--- a/compiler/optimizing/induction_var_analysis_test.cc
+++ b/compiler/optimizing/induction_var_analysis_test.cc
@@ -17,12 +17,13 @@
 #include <regex>
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "induction_var_analysis.h"
 #include "nodes.h"
 #include "optimizing_unit_test.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Fixture class for the InductionVarAnalysis tests.
diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc
index ad3d1a9..9b78699 100644
--- a/compiler/optimizing/induction_var_range.cc
+++ b/compiler/optimizing/induction_var_range.cc
@@ -17,8 +17,9 @@
 #include "induction_var_range.h"
 
 #include <limits>
+#include "optimizing/nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /** Returns true if 64-bit constant fits in 32-bit constant. */
 static bool CanLongValueFitIntoInt(int64_t c) {
@@ -1064,10 +1065,13 @@
       case HInductionVarAnalysis::kLinear:
         if (*stride_value > 0) {
           lower = nullptr;
+          return GenerateLastValueLinear(
+              context, loop, info, trip, graph, block, /*is_min=*/false, upper);
         } else {
           upper = nullptr;
+          return GenerateLastValueLinear(
+              context, loop, info, trip, graph, block, /*is_min=*/true, lower);
         }
-        break;
       case HInductionVarAnalysis::kPolynomial:
         return GenerateLastValuePolynomial(context, loop, info, trip, graph, block, lower);
       case HInductionVarAnalysis::kGeometric:
@@ -1113,6 +1117,54 @@
       GenerateCode(context, loop, info, trip, graph, block, /*is_min=*/ false, upper);
 }
 
+bool InductionVarRange::GenerateLastValueLinear(const HBasicBlock* context,
+                                                const HLoopInformation* loop,
+                                                HInductionVarAnalysis::InductionInfo* info,
+                                                HInductionVarAnalysis::InductionInfo* trip,
+                                                HGraph* graph,
+                                                HBasicBlock* block,
+                                                bool is_min,
+                                                /*out*/ HInstruction** result) const {
+  DataType::Type type = info->type;
+  // Avoid any narrowing linear induction or any type mismatch between the linear induction and the
+  // trip count expression.
+  if (HInductionVarAnalysis::IsNarrowingLinear(info) || trip->type != type) {
+    return false;
+  }
+
+  // Stride value must be a known constant that fits into int32.
+  int64_t stride_value = 0;
+  if (!IsConstant(context, loop, info->op_a, kExact, &stride_value) ||
+      !CanLongValueFitIntoInt(stride_value)) {
+    return false;
+  }
+
+  // We require `a` to be a constant value that didn't overflow.
+  const bool is_min_a = stride_value >= 0 ? is_min : !is_min;
+  Value val_a = GetVal(context, loop, trip, trip, is_min_a);
+  HInstruction* opb;
+  if (!IsConstantValue(val_a) ||
+      !GenerateCode(context, loop, info->op_b, trip, graph, block, is_min, &opb)) {
+    return false;
+  }
+
+  if (graph != nullptr) {
+    ArenaAllocator* allocator = graph->GetAllocator();
+    HInstruction* oper;
+    HInstruction* opa = graph->GetConstant(type, val_a.b_constant);
+    if (stride_value == 1) {
+      oper = new (allocator) HAdd(type, opa, opb);
+    } else if (stride_value == -1) {
+      oper = new (graph->GetAllocator()) HSub(type, opb, opa);
+    } else {
+      HInstruction* mul = new (allocator) HMul(type, graph->GetConstant(type, stride_value), opa);
+      oper = new (allocator) HAdd(type, Insert(block, mul), opb);
+    }
+    *result = Insert(block, oper);
+  }
+  return true;
+}
+
 bool InductionVarRange::GenerateLastValuePolynomial(const HBasicBlock* context,
                                                     const HLoopInformation* loop,
                                                     HInductionVarAnalysis::InductionInfo* info,
diff --git a/compiler/optimizing/induction_var_range.h b/compiler/optimizing/induction_var_range.h
index 552837c..3e1212b 100644
--- a/compiler/optimizing/induction_var_range.h
+++ b/compiler/optimizing/induction_var_range.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_INDUCTION_VAR_RANGE_H_
 #define ART_COMPILER_OPTIMIZING_INDUCTION_VAR_RANGE_H_
 
+#include "base/macros.h"
 #include "induction_var_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * This class implements range analysis on expressions within loops. It takes the results
@@ -317,6 +318,15 @@
                                 /*out*/ bool* needs_finite_test,
                                 /*out*/ bool* needs_taken_test) const;
 
+  bool GenerateLastValueLinear(const HBasicBlock* context,
+                               const HLoopInformation* loop,
+                               HInductionVarAnalysis::InductionInfo* info,
+                               HInductionVarAnalysis::InductionInfo* trip,
+                               HGraph* graph,
+                               HBasicBlock* block,
+                               bool is_min,
+                               /*out*/ HInstruction** result) const;
+
   bool GenerateLastValuePolynomial(const HBasicBlock* context,
                                    const HLoopInformation* loop,
                                    HInductionVarAnalysis::InductionInfo* info,
diff --git a/compiler/optimizing/induction_var_range_test.cc b/compiler/optimizing/induction_var_range_test.cc
index 962123d..d879897 100644
--- a/compiler/optimizing/induction_var_range_test.cc
+++ b/compiler/optimizing/induction_var_range_test.cc
@@ -17,12 +17,13 @@
 #include "induction_var_range.h"
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "induction_var_analysis.h"
 #include "nodes.h"
 #include "optimizing_unit_test.h"
 
-namespace art {
+namespace art HIDDEN {
 
 using Value = InductionVarRange::Value;
 
@@ -1064,10 +1065,6 @@
   HInstruction* last = range_.GenerateLastValue(phi, graph_, loop_preheader_);
   ASSERT_TRUE(last->IsSub());
   ExpectInt(1000, last->InputAt(0));
-  ASSERT_TRUE(last->InputAt(1)->IsNeg());
-  last = last->InputAt(1)->InputAt(0);
-  ASSERT_TRUE(last->IsSub());
-  ExpectInt(0, last->InputAt(0));
   ExpectInt(1000, last->InputAt(1));
 
   // Loop logic.
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index f73c0d3..5a4478d 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -46,7 +46,7 @@
 #include "thread.h"
 #include "verifier/verifier_compiler_binding.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Instruction limit to control memory.
 static constexpr size_t kMaximumNumberOfTotalInstructions = 1024;
@@ -72,6 +72,9 @@
 // Controls the use of inline caches in AOT mode.
 static constexpr bool kUseAOTInlineCaches = true;
 
+// Controls the use of inlining try catches.
+static constexpr bool kInlineTryCatches = true;
+
 // We check for line numbers to make sure the DepthString implementation
 // aligns the output nicely.
 #define LOG_INTERNAL(msg) \
@@ -141,7 +144,11 @@
   }
 
   bool did_inline = false;
-  bool did_set_always_throws = false;
+  // The inliner is the only phase that sets invokes as `always throwing`, and since we only run the
+  // inliner once per graph this value should always be false at the beginning of the inlining
+  // phase. This is important since we use `HasAlwaysThrowingInvokes` to know whether the inliner
+  // phase performed a relevant change in the graph.
+  DCHECK(!graph_->HasAlwaysThrowingInvokes());
 
   // Initialize the number of instructions for the method being compiled. Recursive calls
   // to HInliner::Run have already updated the instruction count.
@@ -175,14 +182,14 @@
       HInstruction* next = instruction->GetNext();
       HInvoke* call = instruction->AsInvoke();
       // As long as the call is not intrinsified, it is worth trying to inline.
-      if (call != nullptr && call->GetIntrinsic() == Intrinsics::kNone) {
+      if (call != nullptr && !codegen_->IsImplementedIntrinsic(call)) {
         if (honor_noinline_directives) {
           // Debugging case: directives in method names control or assert on inlining.
           std::string callee_name =
               call->GetMethodReference().PrettyMethod(/* with_signature= */ false);
           // Tests prevent inlining by having $noinline$ in their method names.
           if (callee_name.find("$noinline$") == std::string::npos) {
-            if (TryInline(call, &did_set_always_throws)) {
+            if (TryInline(call)) {
               did_inline = true;
             } else if (honor_inline_directives) {
               bool should_have_inlined = (callee_name.find("$inline$") != std::string::npos);
@@ -192,7 +199,7 @@
         } else {
           DCHECK(!honor_inline_directives);
           // Normal case: try to inline.
-          if (TryInline(call, &did_set_always_throws)) {
+          if (TryInline(call)) {
             did_inline = true;
           }
         }
@@ -201,7 +208,9 @@
     }
   }
 
-  return did_inline || did_set_always_throws;
+  // We return true if we either inlined at least one method, or we marked one of our methods as
+  // always throwing.
+  return did_inline || graph_->HasAlwaysThrowingInvokes();
 }
 
 static bool IsMethodOrDeclaringClassFinal(ArtMethod* method)
@@ -436,7 +445,7 @@
   return throw_seen;
 }
 
-bool HInliner::TryInline(HInvoke* invoke_instruction, /*inout*/ bool* did_set_always_throws) {
+bool HInliner::TryInline(HInvoke* invoke_instruction) {
   MaybeRecordStat(stats_, MethodCompilationStat::kTryInline);
 
   // Don't bother to move further if we know the method is unresolved or the invocation is
@@ -472,7 +481,8 @@
     bool result = TryInlineAndReplace(invoke_instruction,
                                       actual_method,
                                       ReferenceTypeInfo::CreateInvalid(),
-                                      /* do_rtp= */ true);
+                                      /* do_rtp= */ true,
+                                      /* is_speculative= */ false);
     if (result) {
       MaybeRecordStat(stats_, MethodCompilationStat::kInlinedInvokeVirtualOrInterface);
       if (outermost_graph_ == graph_) {
@@ -487,11 +497,10 @@
       } else {
         invoke_to_analyze = invoke_instruction;
       }
-      // Set always throws property for non-inlined method call with single
-      // target.
-      if (AlwaysThrows(actual_method)) {
-        invoke_to_analyze->SetAlwaysThrows(true);
-        *did_set_always_throws = true;
+      // Set always throws property for non-inlined method call with single target.
+      if (invoke_instruction->AlwaysThrows() || AlwaysThrows(actual_method)) {
+        invoke_to_analyze->SetAlwaysThrows(/* always_throws= */ true);
+        graph_->SetHasAlwaysThrowingInvokes(/* value= */ true);
       }
     }
     return result;
@@ -499,10 +508,27 @@
 
   DCHECK(!invoke_instruction->IsInvokeStaticOrDirect());
 
+  // No try catch inlining allowed here, or recursively. For try catch inlining we are banking on
+  // the fact that we have a unique dex pc list. We cannot guarantee that for some TryInline methods
+  // e.g. `TryInlinePolymorphicCall`.
+  // TODO(solanes): Setting `try_catch_inlining_allowed_` to false here covers all cases from
+  // `TryInlineFromCHA` and from `TryInlineFromInlineCache` as well (e.g.
+  // `TryInlinePolymorphicCall`). Reassess to see if we can inline inline catch blocks in
+  // `TryInlineFromCHA`, `TryInlineMonomorphicCall` and `TryInlinePolymorphicCallToSameTarget`.
+
+  // We store the value to restore it since we will use the same HInliner instance for other inlinee
+  // candidates.
+  const bool previous_value = try_catch_inlining_allowed_;
+  try_catch_inlining_allowed_ = false;
+
   if (TryInlineFromCHA(invoke_instruction)) {
+    try_catch_inlining_allowed_ = previous_value;
     return true;
   }
-  return TryInlineFromInlineCache(invoke_instruction);
+
+  const bool result = TryInlineFromInlineCache(invoke_instruction);
+  try_catch_inlining_allowed_ = previous_value;
+  return result;
 }
 
 bool HInliner::TryInlineFromCHA(HInvoke* invoke_instruction) {
@@ -518,7 +544,8 @@
   if (!TryInlineAndReplace(invoke_instruction,
                            method,
                            ReferenceTypeInfo::CreateInvalid(),
-                           /* do_rtp= */ true)) {
+                           /* do_rtp= */ true,
+                           /* is_speculative= */ true)) {
     return false;
   }
   AddCHAGuard(invoke_instruction, dex_pc, cursor, bb_cursor);
@@ -786,7 +813,8 @@
   if (!TryInlineAndReplace(invoke_instruction,
                            resolved_method,
                            ReferenceTypeInfo::Create(monomorphic_type, /* is_exact= */ true),
-                           /* do_rtp= */ false)) {
+                           /* do_rtp= */ false,
+                           /* is_speculative= */ true)) {
     return false;
   }
 
@@ -802,7 +830,6 @@
   // Run type propagation to get the guard typed, and eventually propagate the
   // type of the receiver.
   ReferenceTypePropagation rtp_fixup(graph_,
-                                     outer_compilation_unit_.GetClassLoader(),
                                      outer_compilation_unit_.GetDexCache(),
                                      /* is_first_run= */ false);
   rtp_fixup.Run();
@@ -982,7 +1009,8 @@
         !TryBuildAndInline(invoke_instruction,
                            method,
                            ReferenceTypeInfo::Create(handle, /* is_exact= */ true),
-                           &return_replacement)) {
+                           &return_replacement,
+                           /* is_speculative= */ true)) {
       all_targets_inlined = false;
     } else {
       one_target_inlined = true;
@@ -1024,7 +1052,6 @@
 
   // Run type propagation to get the guards typed.
   ReferenceTypePropagation rtp_fixup(graph_,
-                                     outer_compilation_unit_.GetClassLoader(),
                                      outer_compilation_unit_.GetDexCache(),
                                      /* is_first_run= */ false);
   rtp_fixup.Run();
@@ -1160,7 +1187,8 @@
   if (!TryBuildAndInline(invoke_instruction,
                          actual_method,
                          ReferenceTypeInfo::CreateInvalid(),
-                         &return_replacement)) {
+                         &return_replacement,
+                         /* is_speculative= */ true)) {
     return false;
   }
 
@@ -1215,7 +1243,6 @@
 
   // Run type propagation to get the guard typed.
   ReferenceTypePropagation rtp_fixup(graph_,
-                                     outer_compilation_unit_.GetClassLoader(),
                                      outer_compilation_unit_.GetDexCache(),
                                      /* is_first_run= */ false);
   rtp_fixup.Run();
@@ -1232,7 +1259,6 @@
     // Actual return value has a more specific type than the method's declared
     // return type. Run RTP again on the outer graph to propagate it.
     ReferenceTypePropagation(graph_,
-                             outer_compilation_unit_.GetClassLoader(),
                              outer_compilation_unit_.GetDexCache(),
                              /* is_first_run= */ false).Run();
   }
@@ -1246,6 +1272,13 @@
     return false;
   }
 
+  // Don't try to devirtualize intrinsics as it breaks pattern matching from later phases.
+  // TODO(solanes): This `if` could be removed if we update optimizations like
+  // TryReplaceStringBuilderAppend.
+  if (invoke_instruction->IsIntrinsic()) {
+    return false;
+  }
+
   // Don't bother trying to call directly a default conflict method. It
   // doesn't have a proper MethodReference, but also `GetCanonicalMethod`
   // will return an actual default implementation.
@@ -1288,7 +1321,8 @@
       dispatch_info,
       kDirect,
       MethodReference(method->GetDexFile(), method->GetDexMethodIndex()),
-      HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+      HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+      !graph_->IsDebuggable());
   HInputsRef inputs = invoke_instruction->GetInputs();
   DCHECK_EQ(inputs.size(), invoke_instruction->GetNumberOfArguments());
   for (size_t index = 0; index != inputs.size(); ++index) {
@@ -1301,7 +1335,7 @@
   invoke_instruction->GetBlock()->InsertInstructionBefore(new_invoke, invoke_instruction);
   new_invoke->CopyEnvironmentFrom(invoke_instruction->GetEnvironment());
   if (invoke_instruction->GetType() == DataType::Type::kReference) {
-    new_invoke->SetReferenceTypeInfo(invoke_instruction->GetReferenceTypeInfo());
+    new_invoke->SetReferenceTypeInfoIfValid(invoke_instruction->GetReferenceTypeInfo());
   }
   *replacement = new_invoke;
 
@@ -1316,11 +1350,13 @@
 bool HInliner::TryInlineAndReplace(HInvoke* invoke_instruction,
                                    ArtMethod* method,
                                    ReferenceTypeInfo receiver_type,
-                                   bool do_rtp) {
-  DCHECK(!invoke_instruction->IsIntrinsic());
+                                   bool do_rtp,
+                                   bool is_speculative) {
+  DCHECK(!codegen_->IsImplementedIntrinsic(invoke_instruction));
   HInstruction* return_replacement = nullptr;
 
-  if (!TryBuildAndInline(invoke_instruction, method, receiver_type, &return_replacement)) {
+  if (!TryBuildAndInline(
+          invoke_instruction, method, receiver_type, &return_replacement, is_speculative)) {
     return false;
   }
 
@@ -1378,6 +1414,15 @@
     return false;
   }
 
+  if (annotations::MethodIsNeverInline(*method->GetDexFile(),
+                                       method->GetClassDef(),
+                                       method->GetDexMethodIndex())) {
+    LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedNeverInlineAnnotation)
+        << "Method " << method->PrettyMethod()
+        << " has the @NeverInline annotation so it won't be inlined";
+    return false;
+  }
+
   return true;
 }
 
@@ -1397,9 +1442,25 @@
   }
 
   if (accessor.TriesSize() != 0) {
-    LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchCallee)
-        << "Method " << method->PrettyMethod() << " is not inlined because of try block";
-    return false;
+    if (!kInlineTryCatches) {
+      LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchDisabled)
+          << "Method " << method->PrettyMethod()
+          << " is not inlined because inlining try catches is disabled globally";
+      return false;
+    }
+    const bool disallowed_try_catch_inlining =
+        // Direct parent is a try block.
+        invoke_instruction->GetBlock()->IsTryBlock() ||
+        // Indirect parent disallows try catch inlining.
+        !try_catch_inlining_allowed_;
+    if (disallowed_try_catch_inlining) {
+      LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchCallee)
+          << "Method " << method->PrettyMethod()
+          << " is not inlined because it has a try catch and we are not supporting it for this"
+          << " particular call. This is could be because e.g. it would be inlined inside another"
+          << " try block, we arrived here from TryInlinePolymorphicCall, etc.";
+      return false;
+    }
   }
 
   if (invoke_instruction->IsInvokeStaticOrDirect() &&
@@ -1416,9 +1477,9 @@
   return true;
 }
 
-// Returns whether our resource limits allow inlining this method.
-bool HInliner::IsInliningBudgetAvailable(ArtMethod* method,
-                                         const CodeItemDataAccessor& accessor) const {
+bool HInliner::IsInliningEncouraged(const HInvoke* invoke_instruction,
+                                    ArtMethod* method,
+                                    const CodeItemDataAccessor& accessor) const {
   if (CountRecursiveCallsOf(method) > kMaximumNumberOfRecursiveCalls) {
     LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedRecursiveBudget)
         << "Method "
@@ -1438,13 +1499,21 @@
     return false;
   }
 
+  if (invoke_instruction->GetBlock()->GetLastInstruction()->IsThrow()) {
+    LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedEndsWithThrow)
+        << "Method " << method->PrettyMethod()
+        << " is not inlined because its block ends with a throw";
+    return false;
+  }
+
   return true;
 }
 
 bool HInliner::TryBuildAndInline(HInvoke* invoke_instruction,
                                  ArtMethod* method,
                                  ReferenceTypeInfo receiver_type,
-                                 HInstruction** return_replacement) {
+                                 HInstruction** return_replacement,
+                                 bool is_speculative) {
   // If invoke_instruction is devirtualized to a different method, give intrinsics
   // another chance before we try to inline it.
   if (invoke_instruction->GetResolvedMethod() != method && method->IsIntrinsic()) {
@@ -1459,7 +1528,8 @@
         invoke_instruction->GetMethodReference(),  // Use existing invoke's method's reference.
         method,
         MethodReference(method->GetDexFile(), method->GetDexMethodIndex()),
-        method->GetMethodIndex());
+        method->GetMethodIndex(),
+        !graph_->IsDebuggable());
     DCHECK_NE(new_invoke->GetIntrinsic(), Intrinsics::kNone);
     HInputsRef inputs = invoke_instruction->GetInputs();
     for (size_t index = 0; index != inputs.size(); ++index) {
@@ -1468,7 +1538,7 @@
     invoke_instruction->GetBlock()->InsertInstructionBefore(new_invoke, invoke_instruction);
     new_invoke->CopyEnvironmentFrom(invoke_instruction->GetEnvironment());
     if (invoke_instruction->GetType() == DataType::Type::kReference) {
-      new_invoke->SetReferenceTypeInfo(invoke_instruction->GetReferenceTypeInfo());
+      new_invoke->SetReferenceTypeInfoIfValid(invoke_instruction->GetReferenceTypeInfo());
     }
     *return_replacement = new_invoke;
     return true;
@@ -1503,12 +1573,12 @@
     return false;
   }
 
-  if (!IsInliningBudgetAvailable(method, accessor)) {
+  if (!IsInliningEncouraged(invoke_instruction, method, accessor)) {
     return false;
   }
 
   if (!TryBuildAndInlineHelper(
-          invoke_instruction, method, receiver_type, return_replacement)) {
+          invoke_instruction, method, receiver_type, return_replacement, is_speculative)) {
     return false;
   }
 
@@ -1627,7 +1697,7 @@
       bool needs_constructor_barrier = false;
       for (size_t i = 0; i != number_of_iputs; ++i) {
         HInstruction* value = GetInvokeInputForArgVRegIndex(invoke_instruction, iput_args[i]);
-        if (!value->IsConstant() || !value->AsConstant()->IsZeroBitPattern()) {
+        if (!IsZeroBitPattern(value)) {
           uint16_t field_index = iput_field_indexes[i];
           bool is_final;
           HInstanceFieldSet* iput =
@@ -1684,7 +1754,6 @@
     Handle<mirror::DexCache> dex_cache =
         graph_->GetHandleCache()->NewHandle(referrer->GetDexCache());
     ReferenceTypePropagation rtp(graph_,
-                                 outer_compilation_unit_.GetClassLoader(),
                                  dex_cache,
                                  /* is_first_run= */ false);
     rtp.Visit(iget);
@@ -1795,7 +1864,7 @@
           run_rtp = true;
           current->SetReferenceTypeInfo(receiver_type);
         } else {
-          current->SetReferenceTypeInfo(argument->GetReferenceTypeInfo());
+          current->SetReferenceTypeInfoIfValid(argument->GetReferenceTypeInfo());
         }
         current->AsParameterValue()->SetCanBeNull(argument->CanBeNull());
       }
@@ -1807,7 +1876,6 @@
   // are more specific than the declared ones, run RTP again on the inner graph.
   if (run_rtp || ArgumentTypesMoreSpecific(invoke_instruction, resolved_method)) {
     ReferenceTypePropagation(callee_graph,
-                             outer_compilation_unit_.GetClassLoader(),
                              dex_compilation_unit.GetDexCache(),
                              /* is_first_run= */ false).Run();
   }
@@ -1821,8 +1889,9 @@
 // If this function returns true, it will also set out_number_of_instructions to
 // the number of instructions in the inlined body.
 bool HInliner::CanInlineBody(const HGraph* callee_graph,
-                             const HBasicBlock* target_block,
-                             size_t* out_number_of_instructions) const {
+                             HInvoke* invoke,
+                             size_t* out_number_of_instructions,
+                             bool is_speculative) const {
   ArtMethod* const resolved_method = callee_graph->GetArtMethod();
 
   HBasicBlock* exit_block = callee_graph->GetExitBlock();
@@ -1835,15 +1904,30 @@
 
   bool has_one_return = false;
   for (HBasicBlock* predecessor : exit_block->GetPredecessors()) {
-    if (predecessor->GetLastInstruction()->IsThrow()) {
-      if (target_block->IsTryBlock()) {
-        // TODO(ngeoffray): Support adding HTryBoundary in Hgraph::InlineInto.
-        LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchCaller)
+    const HInstruction* last_instruction = predecessor->GetLastInstruction();
+    // On inlinees, we can have Return/ReturnVoid/Throw -> TryBoundary -> Exit. To check for the
+    // actual last instruction, we have to skip the TryBoundary instruction.
+    if (last_instruction->IsTryBoundary()) {
+      predecessor = predecessor->GetSinglePredecessor();
+      last_instruction = predecessor->GetLastInstruction();
+
+      // If the last instruction chain is Return/ReturnVoid -> TryBoundary -> Exit we will have to
+      // split a critical edge in InlineInto and might recompute loop information, which is
+      // unsupported for irreducible loops.
+      if (!last_instruction->IsThrow() && graph_->HasIrreducibleLoops()) {
+        DCHECK(last_instruction->IsReturn() || last_instruction->IsReturnVoid());
+        // TODO(ngeoffray): Support re-computing loop information to graphs with
+        // irreducible loops?
+        LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedIrreducibleLoopCaller)
             << "Method " << resolved_method->PrettyMethod()
-            << " could not be inlined because one branch always throws and"
-            << " caller is in a try/catch block";
+            << " could not be inlined because we will have to recompute the loop information and"
+            << " the caller has irreducible loops";
         return false;
-      } else if (graph_->GetExitBlock() == nullptr) {
+      }
+    }
+
+    if (last_instruction->IsThrow()) {
+      if (graph_->GetExitBlock() == nullptr) {
         // TODO(ngeoffray): Support adding HExit in the caller graph.
         LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedInfiniteLoop)
             << "Method " << resolved_method->PrettyMethod()
@@ -1853,9 +1937,10 @@
       } else if (graph_->HasIrreducibleLoops()) {
         // TODO(ngeoffray): Support re-computing loop information to graphs with
         // irreducible loops?
-        VLOG(compiler) << "Method " << resolved_method->PrettyMethod()
-                       << " could not be inlined because one branch always throws and"
-                       << " caller has irreducible loops";
+        LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedIrreducibleLoopCaller)
+            << "Method " << resolved_method->PrettyMethod()
+            << " could not be inlined because one branch always throws and"
+            << " the caller has irreducible loops";
         return false;
       }
     } else {
@@ -1864,6 +1949,15 @@
   }
 
   if (!has_one_return) {
+    if (!is_speculative) {
+      // If we know that the method always throws with the particular parameters, set it as such.
+      // This is better than using the dex instructions as we have more information about this
+      // particular call. We don't mark speculative inlines (e.g. the ones from the inline cache) as
+      // always throwing since they might not throw when executed.
+      invoke->SetAlwaysThrows(/* always_throws= */ true);
+      graph_->SetHasAlwaysThrowingInvokes(/* value= */ true);
+    }
+
     LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedAlwaysThrows)
         << "Method " << resolved_method->PrettyMethod()
         << " could not be inlined because it always throws";
@@ -1882,7 +1976,7 @@
       if (block->GetLoopInformation()->IsIrreducible()) {
         // Don't inline methods with irreducible loops, they could prevent some
         // optimizations to run.
-        LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedIrreducibleLoop)
+        LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedIrreducibleLoopCallee)
             << "Method " << resolved_method->PrettyMethod()
             << " could not be inlined because it contains an irreducible loop";
         return false;
@@ -1930,8 +2024,10 @@
       if (current->IsUnresolvedStaticFieldGet() ||
           current->IsUnresolvedInstanceFieldGet() ||
           current->IsUnresolvedStaticFieldSet() ||
-          current->IsUnresolvedInstanceFieldSet()) {
-        // Entrypoint for unresolved fields does not handle inlined frames.
+          current->IsUnresolvedInstanceFieldSet() ||
+          current->IsInvokeUnresolved()) {
+        // Unresolved invokes / field accesses are expensive at runtime when decoding inlining info,
+        // so don't inline methods that have them.
         LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedUnresolvedEntrypoint)
             << "Method " << resolved_method->PrettyMethod()
             << " could not be inlined because it is using an unresolved"
@@ -1964,7 +2060,8 @@
 bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction,
                                        ArtMethod* resolved_method,
                                        ReferenceTypeInfo receiver_type,
-                                       HInstruction** return_replacement) {
+                                       HInstruction** return_replacement,
+                                       bool is_speculative) {
   DCHECK(!(resolved_method->IsStatic() && receiver_type.IsValid()));
   const dex::CodeItem* code_item = resolved_method->GetCodeItem();
   const DexFile& callee_dex_file = *resolved_method->GetDexFile();
@@ -2057,10 +2154,18 @@
 
   SubstituteArguments(callee_graph, invoke_instruction, receiver_type, dex_compilation_unit);
 
-  RunOptimizations(callee_graph, code_item, dex_compilation_unit);
+  const bool try_catch_inlining_allowed_for_recursive_inline =
+      // It was allowed previously.
+      try_catch_inlining_allowed_ &&
+      // The current invoke is not a try block.
+      !invoke_instruction->GetBlock()->IsTryBlock();
+  RunOptimizations(callee_graph,
+                   code_item,
+                   dex_compilation_unit,
+                   try_catch_inlining_allowed_for_recursive_inline);
 
   size_t number_of_instructions = 0;
-  if (!CanInlineBody(callee_graph, invoke_instruction->GetBlock(), &number_of_instructions)) {
+  if (!CanInlineBody(callee_graph, invoke_instruction, &number_of_instructions, is_speculative)) {
     return false;
   }
 
@@ -2095,16 +2200,17 @@
 
 void HInliner::RunOptimizations(HGraph* callee_graph,
                                 const dex::CodeItem* code_item,
-                                const DexCompilationUnit& dex_compilation_unit) {
+                                const DexCompilationUnit& dex_compilation_unit,
+                                bool try_catch_inlining_allowed_for_recursive_inline) {
   // Note: if the outermost_graph_ is being compiled OSR, we should not run any
   // optimization that could lead to a HDeoptimize. The following optimizations do not.
   HDeadCodeElimination dce(callee_graph, inline_stats_, "dead_code_elimination$inliner");
-  HConstantFolding fold(callee_graph, "constant_folding$inliner");
+  HConstantFolding fold(callee_graph, inline_stats_, "constant_folding$inliner");
   InstructionSimplifier simplify(callee_graph, codegen_, inline_stats_);
 
   HOptimization* optimizations[] = {
-    &simplify,
     &fold,
+    &simplify,
     &dce,
   };
 
@@ -2141,7 +2247,8 @@
                    total_number_of_dex_registers_ + accessor.RegistersSize(),
                    total_number_of_instructions_ + number_of_instructions,
                    this,
-                   depth_ + 1);
+                   depth_ + 1,
+                   try_catch_inlining_allowed_for_recursive_inline);
   inliner.Run();
 }
 
@@ -2155,6 +2262,10 @@
   }
 
   ReferenceTypeInfo actual_rti = actual_obj->GetReferenceTypeInfo();
+  if (!actual_rti.IsValid()) {
+    return false;
+  }
+
   ObjPtr<mirror::Class> actual_class = actual_rti.GetTypeHandle().Get();
   return (actual_rti.IsExact() && !declared_is_exact) ||
          (declared_class != actual_class && declared_class->IsAssignableFrom(actual_class));
diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h
index a2c2085..af067da 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -17,13 +17,14 @@
 #ifndef ART_COMPILER_OPTIMIZING_INLINER_H_
 #define ART_COMPILER_OPTIMIZING_INLINER_H_
 
+#include "base/macros.h"
 #include "dex/dex_file_types.h"
 #include "dex/invoke_type.h"
 #include "jit/profiling_info.h"
 #include "optimization.h"
 #include "profile/profile_compilation_info.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 class DexCompilationUnit;
@@ -42,7 +43,8 @@
            size_t total_number_of_dex_registers,
            size_t total_number_of_instructions,
            HInliner* parent,
-           size_t depth = 0,
+           size_t depth,
+           bool try_catch_inlining_allowed,
            const char* name = kInlinerPassName)
       : HOptimization(outer_graph, name, stats),
         outermost_graph_(outermost_graph),
@@ -54,6 +56,7 @@
         parent_(parent),
         depth_(depth),
         inlining_budget_(0),
+        try_catch_inlining_allowed_(try_catch_inlining_allowed),
         inline_stats_(nullptr) {}
 
   bool Run() override;
@@ -70,9 +73,7 @@
     kInlineCacheMissingTypes = 5
   };
 
-  // We set `did_set_always_throws` as true if we analyzed `invoke_instruction` and it always
-  // throws.
-  bool TryInline(HInvoke* invoke_instruction, /*inout*/ bool* did_set_always_throws);
+  bool TryInline(HInvoke* invoke_instruction);
 
   // Try to inline `resolved_method` in place of `invoke_instruction`. `do_rtp` is whether
   // reference type propagation can run after the inlining. If the inlining is successful, this
@@ -80,19 +81,22 @@
   bool TryInlineAndReplace(HInvoke* invoke_instruction,
                            ArtMethod* resolved_method,
                            ReferenceTypeInfo receiver_type,
-                           bool do_rtp)
+                           bool do_rtp,
+                           bool is_speculative)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool TryBuildAndInline(HInvoke* invoke_instruction,
                          ArtMethod* resolved_method,
                          ReferenceTypeInfo receiver_type,
-                         HInstruction** return_replacement)
+                         HInstruction** return_replacement,
+                         bool is_speculative)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool TryBuildAndInlineHelper(HInvoke* invoke_instruction,
                                ArtMethod* resolved_method,
                                ReferenceTypeInfo receiver_type,
-                               HInstruction** return_replacement)
+                               HInstruction** return_replacement,
+                               bool is_speculative)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Substitutes parameters in the callee graph with their values from the caller.
@@ -105,8 +109,9 @@
   // Run simple optimizations on `callee_graph`.
   void RunOptimizations(HGraph* callee_graph,
                         const dex::CodeItem* code_item,
-                        const DexCompilationUnit& dex_compilation_unit)
-    REQUIRES_SHARED(Locks::mutator_lock_);
+                        const DexCompilationUnit& dex_compilation_unit,
+                        bool try_catch_inlining_allowed_for_recursive_inline)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Try to recognize known simple patterns and replace invoke call with appropriate instructions.
   bool TryPatternSubstitution(HInvoke* invoke_instruction,
@@ -129,12 +134,14 @@
                            const CodeItemDataAccessor& accessor) const
     REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Returns whether the inlining budget allows inlining method.
+  // Returns whether inlining is encouraged.
   //
   // For example, this checks whether the function has grown too large and
   // inlining should be prevented.
-  bool IsInliningBudgetAvailable(art::ArtMethod* method, const CodeItemDataAccessor& accessor) const
-    REQUIRES_SHARED(Locks::mutator_lock_);
+  bool IsInliningEncouraged(const HInvoke* invoke_instruction,
+                            art::ArtMethod* method,
+                            const CodeItemDataAccessor& accessor) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Inspects the body of a method (callee_graph) and returns whether it can be
   // inlined.
@@ -142,8 +149,9 @@
   // This checks for instructions and constructs that we do not support
   // inlining, such as inlining a throw instruction into a try block.
   bool CanInlineBody(const HGraph* callee_graph,
-                     const HBasicBlock* target_block,
-                     size_t* out_number_of_instructions) const
+                     HInvoke* invoke,
+                     size_t* out_number_of_instructions,
+                     bool is_speculative) const
     REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Create a new HInstanceFieldGet.
@@ -320,6 +328,9 @@
   // The budget left for inlining, in number of instructions.
   size_t inlining_budget_;
 
+  // States if we are allowing try catch inlining to occur at this particular instance of inlining.
+  bool try_catch_inlining_allowed_;
+
   // Used to record stats about optimizations on the inlined graph.
   // If the inlining is successful, these stats are merged to the caller graph's stats.
   OptimizingCompilerStats* inline_stats_;
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc
index e0bdd09..fee9091 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -42,7 +42,7 @@
 #include "ssa_builder.h"
 #include "well_known_classes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace {
 
@@ -343,6 +343,10 @@
     // Suspend checks were inserted into loop headers during building of dominator tree.
     DCHECK(block->GetFirstInstruction()->IsSuspendCheck());
     return block->GetFirstInstruction() != block->GetLastInstruction();
+  } else if (block->IsCatchBlock()) {
+    // Nops were inserted into the beginning of catch blocks.
+    DCHECK(block->GetFirstInstruction()->IsNop());
+    return block->GetFirstInstruction() != block->GetLastInstruction();
   } else {
     return !block->GetInstructions().IsEmpty();
   }
@@ -387,6 +391,11 @@
       // This is slightly odd because the loop header might not be empty (TryBoundary).
       // But we're still creating the environment with locals from the top of the block.
       InsertInstructionAtTop(suspend_check);
+    } else if (current_block_->IsCatchBlock()) {
+      // We add an environment emitting instruction at the beginning of each catch block, in order
+      // to support try catch inlining.
+      // This is slightly odd because the catch block might not be empty (TryBoundary).
+      InsertInstructionAtTop(new (allocator_) HNop(block_dex_pc, /* needs_environment= */ true));
     }
 
     if (block_dex_pc == kNoDexPc || current_block_ != block_builder_->GetBlockAt(block_dex_pc)) {
@@ -414,7 +423,7 @@
       }
 
       if (native_debuggable && native_debug_info_locations->IsBitSet(dex_pc)) {
-        AppendInstruction(new (allocator_) HNativeDebugInfo(dex_pc));
+        AppendInstruction(new (allocator_) HNop(dex_pc, /* needs_environment= */ true));
       }
 
       // Note: There may be no Thread for gtests.
@@ -460,6 +469,9 @@
   current_block_ = graph_->GetEntryBlock();
   InitializeBlockLocals();
   InitializeParameters();
+  if (graph_->IsDebuggable() && code_generator_->GetCompilerOptions().IsJitCompiler()) {
+    AppendInstruction(new (allocator_) HMethodEntryHook(0u));
+  }
   AppendInstruction(new (allocator_) HGoto(0u));
 
   // Fill the body.
@@ -495,14 +507,21 @@
         dispatch_info,
         invoke_type,
         target_method,
-        HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+        HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+        !graph_->IsDebuggable());
     HandleInvoke(invoke, operands, shorty, /* is_unresolved= */ false);
   }
 
   // Add the return instruction.
   if (return_type_ == DataType::Type::kVoid) {
+    if (graph_->IsDebuggable() && code_generator_->GetCompilerOptions().IsJitCompiler()) {
+      AppendInstruction(new (allocator_) HMethodExitHook(graph_->GetNullConstant(), kNoDexPc));
+    }
     AppendInstruction(new (allocator_) HReturnVoid());
   } else {
+    if (graph_->IsDebuggable() && code_generator_->GetCompilerOptions().IsJitCompiler()) {
+      AppendInstruction(new (allocator_) HMethodExitHook(latest_result_, kNoDexPc));
+    }
     AppendInstruction(new (allocator_) HReturn(latest_result_));
   }
 
@@ -972,11 +991,11 @@
     *imt_or_vtable_index = resolved_method->GetVtableIndex();
   } else if (*invoke_type == kInterface) {
     // For HInvokeInterface we need the IMT index.
-    *imt_or_vtable_index = ImTable::GetImtIndex(resolved_method);
+    *imt_or_vtable_index = resolved_method->GetImtIndex();
+    DCHECK_EQ(*imt_or_vtable_index, ImTable::GetImtIndex(resolved_method));
   }
 
-  *is_string_constructor =
-      resolved_method->IsConstructor() && resolved_method->GetDeclaringClass()->IsStringClass();
+  *is_string_constructor = resolved_method->IsStringConstructor();
 
   return resolved_method;
 }
@@ -1041,7 +1060,8 @@
         dispatch_info,
         invoke_type,
         resolved_method_reference,
-        HInvokeStaticOrDirect::ClinitCheckRequirement::kImplicit);
+        HInvokeStaticOrDirect::ClinitCheckRequirement::kImplicit,
+        !graph_->IsDebuggable());
     return HandleStringInit(invoke, operands, shorty);
   }
 
@@ -1054,7 +1074,7 @@
   }
 
   // Try to build an HIR replacement for the intrinsic.
-  if (UNLIKELY(resolved_method->IsIntrinsic())) {
+  if (UNLIKELY(resolved_method->IsIntrinsic()) && !graph_->IsDebuggable()) {
     // All intrinsics are in the primary boot image, so their class can always be referenced
     // and we do not need to rely on the implicit class initialization check. The class should
     // be initialized but we do not require that here.
@@ -1105,7 +1125,8 @@
                                                     dispatch_info,
                                                     invoke_type,
                                                     resolved_method_reference,
-                                                    clinit_check_requirement);
+                                                    clinit_check_requirement,
+                                                    !graph_->IsDebuggable());
     if (clinit_check != nullptr) {
       // Add the class initialization check as last input of `invoke`.
       DCHECK_EQ(clinit_check_requirement, HInvokeStaticOrDirect::ClinitCheckRequirement::kExplicit);
@@ -1121,7 +1142,8 @@
                                              method_reference,
                                              resolved_method,
                                              resolved_method_reference,
-                                             /*vtable_index=*/ imt_or_vtable_index);
+                                             /*vtable_index=*/ imt_or_vtable_index,
+                                             !graph_->IsDebuggable());
   } else {
     DCHECK_EQ(invoke_type, kInterface);
     if (kIsDebugBuild) {
@@ -1142,7 +1164,8 @@
                                                resolved_method,
                                                resolved_method_reference,
                                                /*imt_index=*/ imt_or_vtable_index,
-                                               load_kind);
+                                               load_kind,
+                                               !graph_->IsDebuggable());
   }
   return HandleInvoke(invoke, operands, shorty, /* is_unresolved= */ false);
 }
@@ -1341,12 +1364,14 @@
                                                         method_reference,
                                                         resolved_method,
                                                         resolved_method_reference,
-                                                        proto_idx);
+                                                        proto_idx,
+                                                        !graph_->IsDebuggable());
   if (!HandleInvoke(invoke, operands, shorty, /* is_unresolved= */ false)) {
     return false;
   }
 
-  if (invoke->GetIntrinsic() != Intrinsics::kMethodHandleInvoke &&
+  if (invoke->GetIntrinsic() != Intrinsics::kNone &&
+      invoke->GetIntrinsic() != Intrinsics::kMethodHandleInvoke &&
       invoke->GetIntrinsic() != Intrinsics::kMethodHandleInvokeExact &&
       VarHandleAccessorNeedsReturnTypeCheck(invoke, return_type)) {
     // Type check is needed because VarHandle intrinsics do not type check the retrieved reference.
@@ -1379,7 +1404,8 @@
                                                    call_site_idx,
                                                    return_type,
                                                    dex_pc,
-                                                   method_reference);
+                                                   method_reference,
+                                                   !graph_->IsDebuggable());
   return HandleInvoke(invoke, operands, shorty, /* is_unresolved= */ false);
 }
 
diff --git a/compiler/optimizing/instruction_builder.h b/compiler/optimizing/instruction_builder.h
index 817fbaa..3d65d8f 100644
--- a/compiler/optimizing/instruction_builder.h
+++ b/compiler/optimizing/instruction_builder.h
@@ -18,6 +18,7 @@
 #define ART_COMPILER_OPTIMIZING_INSTRUCTION_BUILDER_H_
 
 #include "base/array_ref.h"
+#include "base/macros.h"
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 #include "data_type.h"
@@ -27,7 +28,7 @@
 #include "handle.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArenaBitVector;
 class ArtField;
diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc
index 789f077..0c2fd5d 100644
--- a/compiler/optimizing/instruction_simplifier.cc
+++ b/compiler/optimizing/instruction_simplifier.cc
@@ -31,13 +31,13 @@
 #include "sharpening.h"
 #include "string_builder_append.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Whether to run an exhaustive test of individual HInstructions cloning when each instruction
 // is replaced with its copy if it is clonable.
 static constexpr bool kTestInstructionClonerExhaustively = false;
 
-class InstructionSimplifierVisitor : public HGraphDelegateVisitor {
+class InstructionSimplifierVisitor final : public HGraphDelegateVisitor {
  public:
   InstructionSimplifierVisitor(HGraph* graph,
                                CodeGenerator* codegen,
@@ -970,7 +970,7 @@
                           pred_get->GetFieldInfo().GetDexFile(),
                           pred_get->GetDexPc());
     if (pred_get->GetType() == DataType::Type::kReference) {
-      replace_with->SetReferenceTypeInfo(pred_get->GetReferenceTypeInfo());
+      replace_with->SetReferenceTypeInfoIfValid(pred_get->GetReferenceTypeInfo());
     }
     pred_get->GetBlock()->InsertInstructionBefore(replace_with, pred_get);
     pred_get->ReplaceWith(replace_with);
@@ -1117,6 +1117,10 @@
   }
 }
 
+// TODO(solanes): This optimization should be in ConstantFolding since we are folding to a constant.
+// However, we get code size regressions when we do that since we sometimes have a NullCheck between
+// HArrayLength and IsNewArray, and said NullCheck is eliminated in InstructionSimplifier. If we run
+// ConstantFolding and InstructionSimplifier in lockstep this wouldn't be an issue.
 void InstructionSimplifierVisitor::VisitArrayLength(HArrayLength* instruction) {
   HInstruction* input = instruction->InputAt(0);
   // If the array is a NewArray with constant size, replace the array length
@@ -1142,13 +1146,13 @@
   if (value->IsArrayGet()) {
     if (value->AsArrayGet()->GetArray() == instruction->GetArray()) {
       // If the code is just swapping elements in the array, no need for a type check.
-      instruction->ClearNeedsTypeCheck();
+      instruction->ClearTypeCheck();
       return;
     }
   }
 
   if (value->IsNullConstant()) {
-    instruction->ClearNeedsTypeCheck();
+    instruction->ClearTypeCheck();
     return;
   }
 
@@ -1160,13 +1164,13 @@
   }
 
   if (value_rti.IsValid() && array_rti.CanArrayHold(value_rti)) {
-    instruction->ClearNeedsTypeCheck();
+    instruction->ClearTypeCheck();
     return;
   }
 
   if (array_rti.IsObjectArray()) {
     if (array_rti.IsExact()) {
-      instruction->ClearNeedsTypeCheck();
+      instruction->ClearTypeCheck();
       return;
     }
     instruction->SetStaticTypeOfArrayIsObjectArray();
@@ -1860,13 +1864,16 @@
 
 // Search HDiv having the specified dividend and divisor which is in the specified basic block.
 // Return nullptr if nothing has been found.
-static HInstruction* FindDivWithInputsInBasicBlock(HInstruction* dividend,
-                                                   HInstruction* divisor,
-                                                   HBasicBlock* basic_block) {
+static HDiv* FindDivWithInputsInBasicBlock(HInstruction* dividend,
+                                           HInstruction* divisor,
+                                           HBasicBlock* basic_block) {
   for (const HUseListNode<HInstruction*>& use : dividend->GetUses()) {
     HInstruction* user = use.GetUser();
-    if (user->GetBlock() == basic_block && user->IsDiv() && user->InputAt(1) == divisor) {
-      return user;
+    if (user->GetBlock() == basic_block &&
+        user->IsDiv() &&
+        user->InputAt(0) == dividend &&
+        user->InputAt(1) == divisor) {
+      return user->AsDiv();
     }
   }
   return nullptr;
@@ -1900,7 +1907,7 @@
     }
   }
 
-  HInstruction* quotient = FindDivWithInputsInBasicBlock(dividend, divisor, basic_block);
+  HDiv* quotient = FindDivWithInputsInBasicBlock(dividend, divisor, basic_block);
   if (quotient == nullptr) {
     return;
   }
@@ -2458,7 +2465,7 @@
       DCHECK(method != nullptr);
       DCHECK(method->IsStatic());
       DCHECK(method->GetDeclaringClass() == system);
-      invoke->SetResolvedMethod(method);
+      invoke->SetResolvedMethod(method, !codegen_->GetGraph()->IsDebuggable());
       // Sharpen the new invoke. Note that we do not update the dex method index of
       // the invoke, as we would need to look it up in the current dex file, and it
       // is unlikely that it exists. The most usual situation for such typed
@@ -2647,15 +2654,13 @@
   // Collect args and check for unexpected uses.
   // We expect one call to a constructor with no arguments, one constructor fence (unless
   // eliminated), some number of append calls and one call to StringBuilder.toString().
-  bool constructor_inlined = false;
   bool seen_constructor = false;
   bool seen_constructor_fence = false;
   bool seen_to_string = false;
   uint32_t format = 0u;
   uint32_t num_args = 0u;
+  bool has_fp_args = false;
   HInstruction* args[StringBuilderAppend::kMaxArgs];  // Added in reverse order.
-  // When inlining, `maybe_new_array` tracks an environment use that we want to allow.
-  HInstruction* maybe_new_array = nullptr;
   for (HBackwardInstructionIterator iter(block->GetInstructions()); !iter.Done(); iter.Advance()) {
     HInstruction* user = iter.Current();
     // Instructions of interest apply to `sb`, skip those that do not involve `sb`.
@@ -2700,6 +2705,14 @@
         case Intrinsics::kStringBuilderAppendLong:
           arg = StringBuilderAppend::Argument::kLong;
           break;
+        case Intrinsics::kStringBuilderAppendFloat:
+          arg = StringBuilderAppend::Argument::kFloat;
+          has_fp_args = true;
+          break;
+        case Intrinsics::kStringBuilderAppendDouble:
+          arg = StringBuilderAppend::Argument::kDouble;
+          has_fp_args = true;
+          break;
         case Intrinsics::kStringBuilderAppendCharSequence: {
           ReferenceTypeInfo rti = user->AsInvokeVirtual()->InputAt(1)->GetReferenceTypeInfo();
           if (!rti.IsValid()) {
@@ -2719,10 +2732,6 @@
           }
           break;
         }
-        case Intrinsics::kStringBuilderAppendFloat:
-        case Intrinsics::kStringBuilderAppendDouble:
-          // TODO: Unimplemented, needs to call FloatingDecimal.getBinaryToASCIIConverter().
-          return false;
         default: {
           return false;
         }
@@ -2736,25 +2745,13 @@
       format = (format << StringBuilderAppend::kBitsPerArg) | static_cast<uint32_t>(arg);
       args[num_args] = as_invoke_virtual->InputAt(1u);
       ++num_args;
-    } else if (!seen_constructor) {
-      // At this point, we should see the constructor. However, we might have inlined it so we have
-      // to take care of both cases. We accept only the constructor with no extra arguments. This
-      // means that if we inline it, we have to check it is setting its field to a new array.
-      if (user->IsInvokeStaticOrDirect() &&
-          user->AsInvokeStaticOrDirect()->GetResolvedMethod() != nullptr &&
-          user->AsInvokeStaticOrDirect()->GetResolvedMethod()->IsConstructor() &&
-          user->AsInvokeStaticOrDirect()->GetNumberOfArguments() == 1u) {
-        constructor_inlined = false;
-      } else if (user->IsInstanceFieldSet() &&
-                 user->AsInstanceFieldSet()->GetFieldType() == DataType::Type::kReference &&
-                 user->AsInstanceFieldSet()->InputAt(0) == sb &&
-                 user->AsInstanceFieldSet()->GetValue()->IsNewArray()) {
-        maybe_new_array = user->AsInstanceFieldSet()->GetValue();
-        constructor_inlined = true;
-      } else {
-        // We were expecting a constructor but we haven't seen it. Abort optimization.
-        return false;
-      }
+    } else if (user->IsInvokeStaticOrDirect() &&
+               user->AsInvokeStaticOrDirect()->GetResolvedMethod() != nullptr &&
+               user->AsInvokeStaticOrDirect()->GetResolvedMethod()->IsConstructor() &&
+               user->AsInvokeStaticOrDirect()->GetNumberOfArguments() == 1u) {
+      // After arguments, we should see the constructor.
+      // We accept only the constructor with no extra arguments.
+      DCHECK(!seen_constructor);
       DCHECK(!seen_constructor_fence);
       seen_constructor = true;
     } else if (user->IsConstructorFence()) {
@@ -2780,17 +2777,6 @@
     // Accept only calls on the StringBuilder (which shall all be removed).
     // TODO: Carve-out for const-string? Or rely on environment pruning (to be implemented)?
     if (holder->InputCount() == 0 || holder->InputAt(0) != sb) {
-      // When inlining the constructor, we have a NewArray and may have a LoadClass as an
-      // environment use.
-      if (constructor_inlined) {
-        if (holder == maybe_new_array) {
-          continue;
-        }
-        if (holder == maybe_new_array->InputAt(0)) {
-          DCHECK(holder->IsLoadClass());
-          continue;
-        }
-      }
       return false;
     }
   }
@@ -2798,9 +2784,9 @@
   // Create replacement instruction.
   HIntConstant* fmt = block->GetGraph()->GetIntConstant(static_cast<int32_t>(format));
   ArenaAllocator* allocator = block->GetGraph()->GetAllocator();
-  HStringBuilderAppend* append =
-      new (allocator) HStringBuilderAppend(fmt, num_args, allocator, invoke->GetDexPc());
-  append->SetReferenceTypeInfo(invoke->GetReferenceTypeInfo());
+  HStringBuilderAppend* append = new (allocator) HStringBuilderAppend(
+      fmt, num_args, has_fp_args, allocator, invoke->GetDexPc());
+  append->SetReferenceTypeInfoIfValid(invoke->GetReferenceTypeInfo());
   for (size_t i = 0; i != num_args; ++i) {
     append->SetArgumentAt(i, args[num_args - 1u - i]);
   }
@@ -2824,33 +2810,6 @@
   while (sb->HasNonEnvironmentUses()) {
     block->RemoveInstruction(sb->GetUses().front().GetUser());
   }
-  if (constructor_inlined) {
-    // We need to remove the inlined constructor instructions,
-    // and all remaining environment uses (if any).
-    DCHECK(sb->HasEnvironmentUses());
-    DCHECK(maybe_new_array != nullptr);
-    DCHECK(maybe_new_array->IsNewArray());
-    DCHECK(maybe_new_array->HasNonEnvironmentUses());
-    HInstruction* fence = maybe_new_array->GetUses().front().GetUser();
-    DCHECK(fence->IsConstructorFence());
-    block->RemoveInstruction(fence);
-    block->RemoveInstruction(maybe_new_array);
-    if (sb->HasEnvironmentUses()) {
-      // We know the only remaining uses are from the LoadClass.
-      HInstruction* load_class = maybe_new_array->InputAt(0);
-      DCHECK(load_class->IsLoadClass());
-      for (HEnvironment* env = load_class->GetEnvironment();
-           env != nullptr;
-           env = env->GetParent()) {
-        for (size_t i = 0, size = env->Size(); i != size; ++i) {
-          if (env->GetInstructionAt(i) == sb) {
-            env->RemoveAsUserOfInput(i);
-            env->SetRawEnvAt(i, /*instruction=*/ nullptr);
-          }
-        }
-      }
-    }
-  }
   DCHECK(!sb->HasEnvironmentUses());
   block->RemoveInstruction(sb);
   return true;
diff --git a/compiler/optimizing/instruction_simplifier.h b/compiler/optimizing/instruction_simplifier.h
index feea771..98ebaaf 100644
--- a/compiler/optimizing/instruction_simplifier.h
+++ b/compiler/optimizing/instruction_simplifier.h
@@ -17,11 +17,12 @@
 #ifndef ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_H_
 #define ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 #include "optimizing_compiler_stats.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 
diff --git a/compiler/optimizing/instruction_simplifier_arm.cc b/compiler/optimizing/instruction_simplifier_arm.cc
index 1371ea7..05a518d 100644
--- a/compiler/optimizing/instruction_simplifier_arm.cc
+++ b/compiler/optimizing/instruction_simplifier_arm.cc
@@ -23,7 +23,7 @@
 #include "mirror/string.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 using helpers::CanFitInShifterOperand;
 using helpers::HasShifterOperand;
@@ -31,7 +31,7 @@
 
 namespace arm {
 
-class InstructionSimplifierArmVisitor : public HGraphVisitor {
+class InstructionSimplifierArmVisitor final : public HGraphVisitor {
  public:
   InstructionSimplifierArmVisitor(HGraph* graph, OptimizingCompilerStats* stats)
       : HGraphVisitor(graph), stats_(stats) {}
diff --git a/compiler/optimizing/instruction_simplifier_arm.h b/compiler/optimizing/instruction_simplifier_arm.h
index fca9341..0517e4f 100644
--- a/compiler/optimizing/instruction_simplifier_arm.h
+++ b/compiler/optimizing/instruction_simplifier_arm.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_ARM_H_
 #define ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_ARM_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 class InstructionSimplifierArm : public HOptimization {
diff --git a/compiler/optimizing/instruction_simplifier_arm64.cc b/compiler/optimizing/instruction_simplifier_arm64.cc
index a6ec020..671900b 100644
--- a/compiler/optimizing/instruction_simplifier_arm64.cc
+++ b/compiler/optimizing/instruction_simplifier_arm64.cc
@@ -21,7 +21,7 @@
 #include "mirror/array-inl.h"
 #include "mirror/string.h"
 
-namespace art {
+namespace art HIDDEN {
 
 using helpers::CanFitInShifterOperand;
 using helpers::HasShifterOperand;
@@ -31,7 +31,7 @@
 
 using helpers::ShifterOperandSupportsExtension;
 
-class InstructionSimplifierArm64Visitor : public HGraphVisitor {
+class InstructionSimplifierArm64Visitor final : public HGraphVisitor {
  public:
   InstructionSimplifierArm64Visitor(HGraph* graph, OptimizingCompilerStats* stats)
       : HGraphVisitor(graph), stats_(stats) {}
diff --git a/compiler/optimizing/instruction_simplifier_arm64.h b/compiler/optimizing/instruction_simplifier_arm64.h
index 8d93c01..374638a 100644
--- a/compiler/optimizing/instruction_simplifier_arm64.h
+++ b/compiler/optimizing/instruction_simplifier_arm64.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_ARM64_H_
 #define ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_ARM64_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 class InstructionSimplifierArm64 : public HOptimization {
diff --git a/compiler/optimizing/instruction_simplifier_shared.cc b/compiler/optimizing/instruction_simplifier_shared.cc
index dc60ba6..34daae2 100644
--- a/compiler/optimizing/instruction_simplifier_shared.cc
+++ b/compiler/optimizing/instruction_simplifier_shared.cc
@@ -18,7 +18,7 @@
 
 #include "mirror/array-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace {
 
@@ -244,7 +244,7 @@
     // The access may require a runtime call or the original array pointer.
     return false;
   }
-  if (kEmitCompilerReadBarrier &&
+  if (gUseReadBarrier &&
       !kUseBakerReadBarrier &&
       access->IsArrayGet() &&
       access->GetType() == DataType::Type::kReference) {
diff --git a/compiler/optimizing/instruction_simplifier_shared.h b/compiler/optimizing/instruction_simplifier_shared.h
index 876ed21..ddc3a86 100644
--- a/compiler/optimizing/instruction_simplifier_shared.h
+++ b/compiler/optimizing/instruction_simplifier_shared.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_SHARED_H_
 #define ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_SHARED_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace helpers {
 
diff --git a/compiler/optimizing/instruction_simplifier_test.cc b/compiler/optimizing/instruction_simplifier_test.cc
index c7c5b12..966f5b9 100644
--- a/compiler/optimizing/instruction_simplifier_test.cc
+++ b/compiler/optimizing/instruction_simplifier_test.cc
@@ -26,13 +26,15 @@
 #include "optimizing/data_type.h"
 #include "optimizing_unit_test.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace mirror {
 class ClassExt;
 class Throwable;
 }  // namespace mirror
 
+static constexpr bool kDebugSimplifierTests = false;
+
 template<typename SuperClass>
 class InstructionSimplifierTestBase : public SuperClass, public OptimizingUnitTestHelper {
  public:
@@ -49,6 +51,19 @@
     SuperClass::TearDown();
     gLogVerbosity.compiler = false;
   }
+
+  void PerformSimplification(const AdjacencyListGraph& blks) {
+    if (kDebugSimplifierTests) {
+      LOG(INFO) << "Pre simplification " << blks;
+    }
+    graph_->ClearDominanceInformation();
+    graph_->BuildDominatorTree();
+    InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
+    simp.Run();
+    if (kDebugSimplifierTests) {
+      LOG(INFO) << "Post simplify " << blks;
+    }
+  }
 };
 
 class InstructionSimplifierTest : public InstructionSimplifierTestBase<CommonCompilerTest> {};
@@ -197,13 +212,7 @@
 
   SetupExit(exit);
 
-  LOG(INFO) << "Pre simplification " << blks;
-  graph_->ClearDominanceInformation();
-  graph_->BuildDominatorTree();
-  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
-  simp.Run();
-
-  LOG(INFO) << "Post simplify " << blks;
+  PerformSimplification(blks);
 
   EXPECT_INS_RETAINED(read_end);
 
@@ -289,13 +298,7 @@
 
   SetupExit(exit);
 
-  LOG(INFO) << "Pre simplification " << blks;
-  graph_->ClearDominanceInformation();
-  graph_->BuildDominatorTree();
-  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
-  simp.Run();
-
-  LOG(INFO) << "Post simplify " << blks;
+  PerformSimplification(blks);
 
   EXPECT_FALSE(obj3->CanBeNull());
   EXPECT_INS_RETAINED(read_end);
@@ -373,13 +376,7 @@
 
   SetupExit(exit);
 
-  LOG(INFO) << "Pre simplification " << blks;
-  graph_->ClearDominanceInformation();
-  graph_->BuildDominatorTree();
-  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
-  simp.Run();
-
-  LOG(INFO) << "Post simplify " << blks;
+  PerformSimplification(blks);
 
   EXPECT_FALSE(obj1->CanBeNull());
   EXPECT_FALSE(obj2->CanBeNull());
@@ -464,16 +461,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-
-  LOG(INFO) << "Pre simplification " << blks;
-  graph_->ClearDominanceInformation();
-  graph_->BuildDominatorTree();
-  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
-  simp.Run();
-
-  LOG(INFO) << "Post simplify " << blks;
+  PerformSimplification(blks);
 
   if (!GetConstantResult() || GetParam() == InstanceOfKind::kSelf) {
     EXPECT_INS_RETAINED(target_klass);
@@ -532,16 +520,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-
-  LOG(INFO) << "Pre simplification " << blks;
-  graph_->ClearDominanceInformation();
-  graph_->BuildDominatorTree();
-  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
-  simp.Run();
-
-  LOG(INFO) << "Post simplify " << blks;
+  PerformSimplification(blks);
 
   if (!GetConstantResult() || GetParam() == InstanceOfKind::kSelf) {
     EXPECT_INS_RETAINED(target_klass);
diff --git a/compiler/optimizing/instruction_simplifier_x86.cc b/compiler/optimizing/instruction_simplifier_x86.cc
index 2d8f94a..5a4345d 100644
--- a/compiler/optimizing/instruction_simplifier_x86.cc
+++ b/compiler/optimizing/instruction_simplifier_x86.cc
@@ -17,11 +17,11 @@
 #include "instruction_simplifier_x86_shared.h"
 #include "code_generator_x86.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace x86 {
 
-class InstructionSimplifierX86Visitor : public HGraphVisitor {
+class InstructionSimplifierX86Visitor final : public HGraphVisitor {
  public:
   InstructionSimplifierX86Visitor(HGraph* graph,
                                   CodeGenerator* codegen,
diff --git a/compiler/optimizing/instruction_simplifier_x86.h b/compiler/optimizing/instruction_simplifier_x86.h
index 6f10006..25ebe20 100644
--- a/compiler/optimizing/instruction_simplifier_x86.h
+++ b/compiler/optimizing/instruction_simplifier_x86.h
@@ -16,10 +16,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_X86_H_
 #define ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_X86_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 namespace x86 {
diff --git a/compiler/optimizing/instruction_simplifier_x86_64.cc b/compiler/optimizing/instruction_simplifier_x86_64.cc
index 56c6b41..9ba1a8a 100644
--- a/compiler/optimizing/instruction_simplifier_x86_64.cc
+++ b/compiler/optimizing/instruction_simplifier_x86_64.cc
@@ -17,11 +17,11 @@
 #include "instruction_simplifier_x86_shared.h"
 #include "code_generator_x86_64.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace x86_64 {
 
-class InstructionSimplifierX86_64Visitor : public HGraphVisitor {
+class InstructionSimplifierX86_64Visitor final : public HGraphVisitor {
  public:
   InstructionSimplifierX86_64Visitor(HGraph* graph,
                                      CodeGenerator* codegen,
diff --git a/compiler/optimizing/instruction_simplifier_x86_64.h b/compiler/optimizing/instruction_simplifier_x86_64.h
index 6cae24d..1654dc4 100644
--- a/compiler/optimizing/instruction_simplifier_x86_64.h
+++ b/compiler/optimizing/instruction_simplifier_x86_64.h
@@ -16,10 +16,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_X86_64_H_
 #define ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_X86_64_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 
diff --git a/compiler/optimizing/instruction_simplifier_x86_shared.cc b/compiler/optimizing/instruction_simplifier_x86_shared.cc
index 2805abb..74c5ca2 100644
--- a/compiler/optimizing/instruction_simplifier_x86_shared.cc
+++ b/compiler/optimizing/instruction_simplifier_x86_shared.cc
@@ -14,9 +14,10 @@
  */
 
 #include "instruction_simplifier_x86_shared.h"
+
 #include "nodes_x86.h"
 
-namespace art {
+namespace art HIDDEN {
 
 bool TryCombineAndNot(HAnd* instruction) {
   DataType::Type type = instruction->GetType();
diff --git a/compiler/optimizing/instruction_simplifier_x86_shared.h b/compiler/optimizing/instruction_simplifier_x86_shared.h
index 7f94d7e..1a44d0f 100644
--- a/compiler/optimizing/instruction_simplifier_x86_shared.h
+++ b/compiler/optimizing/instruction_simplifier_x86_shared.h
@@ -16,13 +16,16 @@
 #ifndef ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_X86_SHARED_H_
 #define ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_X86_SHARED_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
+
 bool TryCombineAndNot(HAnd* instruction);
 bool TryGenerateResetLeastSetBit(HAnd* instruction);
 bool TryGenerateMaskUptoLeastSetBit(HXor* instruction);
 bool AreLeastSetBitInputs(HInstruction* to_test, HInstruction* other);
+
 }  // namespace art
 
 #endif  // ART_COMPILER_OPTIMIZING_INSTRUCTION_SIMPLIFIER_X86_SHARED_H_
diff --git a/compiler/optimizing/intrinsic_objects.cc b/compiler/optimizing/intrinsic_objects.cc
index 5f6f562..7e54211 100644
--- a/compiler/optimizing/intrinsic_objects.cc
+++ b/compiler/optimizing/intrinsic_objects.cc
@@ -22,7 +22,7 @@
 #include "image.h"
 #include "obj_ptr-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static constexpr size_t kIntrinsicObjectsOffset =
     enum_cast<size_t>(ImageHeader::kIntrinsicObjectsStart);
diff --git a/compiler/optimizing/intrinsic_objects.h b/compiler/optimizing/intrinsic_objects.h
index ed764bd..d750f29 100644
--- a/compiler/optimizing/intrinsic_objects.h
+++ b/compiler/optimizing/intrinsic_objects.h
@@ -19,9 +19,10 @@
 
 #include "base/bit_field.h"
 #include "base/bit_utils.h"
+#include "base/macros.h"
 #include "base/mutex.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ClassLinker;
 template <class MirrorType> class ObjPtr;
@@ -56,15 +57,15 @@
   }
 
   // Functions for retrieving data for Integer.valueOf().
-  static ObjPtr<mirror::ObjectArray<mirror::Object>> LookupIntegerCache(
+  EXPORT static ObjPtr<mirror::ObjectArray<mirror::Object>> LookupIntegerCache(
       Thread* self, ClassLinker* class_linker) REQUIRES_SHARED(Locks::mutator_lock_);
-  static ObjPtr<mirror::ObjectArray<mirror::Object>> GetIntegerValueOfCache(
+  EXPORT static ObjPtr<mirror::ObjectArray<mirror::Object>> GetIntegerValueOfCache(
       ObjPtr<mirror::ObjectArray<mirror::Object>> boot_image_live_objects)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  static ObjPtr<mirror::Object> GetIntegerValueOfObject(
+  EXPORT static ObjPtr<mirror::Object> GetIntegerValueOfObject(
       ObjPtr<mirror::ObjectArray<mirror::Object>> boot_image_live_objects,
       uint32_t index) REQUIRES_SHARED(Locks::mutator_lock_);
-  static MemberOffset GetIntegerValueOfArrayDataOffset(
+  EXPORT static MemberOffset GetIntegerValueOfArrayDataOffset(
       ObjPtr<mirror::ObjectArray<mirror::Object>> boot_image_live_objects)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/compiler/optimizing/intrinsics.cc b/compiler/optimizing/intrinsics.cc
index f2d2b45..774deec 100644
--- a/compiler/optimizing/intrinsics.cc
+++ b/compiler/optimizing/intrinsics.cc
@@ -32,7 +32,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "thread-current-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 std::ostream& operator<<(std::ostream& os, const Intrinsics& intrinsic) {
   switch (intrinsic) {
@@ -171,6 +171,7 @@
   if (!CanReferenceBootImageObjects(invoke, compiler_options)) {
     return;
   }
+  HInstruction* const input = invoke->InputAt(0);
   if (compiler_options.IsBootImage()) {
     if (!compiler_options.IsImageClass(kIntegerCacheDescriptor) ||
         !compiler_options.IsImageClass(kIntegerDescriptor)) {
@@ -207,8 +208,8 @@
         CHECK_EQ(value_field->GetInt(current_object), low + i);
       }
     }
-    if (invoke->InputAt(0)->IsIntConstant()) {
-      int32_t value = invoke->InputAt(0)->AsIntConstant()->GetValue();
+    if (input->IsIntConstant()) {
+      int32_t value = input->AsIntConstant()->GetValue();
       if (static_cast<uint32_t>(value) - static_cast<uint32_t>(low) <
           static_cast<uint32_t>(high - low + 1)) {
         // No call, we shall use direct pointer to the Integer object.
@@ -232,8 +233,8 @@
     } else {
       DCHECK(compiler_options.IsAotCompiler());
       DCHECK(CheckIntegerCache(self, runtime->GetClassLinker(), boot_image_live_objects, cache));
-      if (invoke->InputAt(0)->IsIntConstant()) {
-        int32_t value = invoke->InputAt(0)->AsIntConstant()->GetValue();
+      if (input->IsIntConstant()) {
+        int32_t value = input->AsIntConstant()->GetValue();
         // Retrieve the `value` from the lowest cached Integer.
         ObjPtr<mirror::Object> low_integer =
             IntrinsicObjects::GetIntegerValueOfObject(boot_image_live_objects, 0u);
@@ -255,11 +256,11 @@
   ArenaAllocator* allocator = codegen->GetGraph()->GetAllocator();
   LocationSummary* locations = new (allocator) LocationSummary(invoke, call_kind, kIntrinsified);
   if (call_kind == LocationSummary::kCallOnMainOnly) {
-    locations->SetInAt(0, Location::RegisterOrConstant(invoke->InputAt(0)));
+    locations->SetInAt(0, Location::RegisterOrConstant(input));
     locations->AddTemp(first_argument_location);
     locations->SetOut(return_location);
   } else {
-    locations->SetInAt(0, Location::ConstantLocation(invoke->InputAt(0)->AsConstant()));
+    locations->SetInAt(0, Location::ConstantLocation(input));
     locations->SetOut(Location::RequiresRegister());
   }
 }
@@ -392,7 +393,7 @@
 }
 
 void IntrinsicVisitor::CreateReferenceRefersToLocations(HInvoke* invoke) {
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     // Unimplemented for non-Baker read barrier.
     return;
   }
diff --git a/compiler/optimizing/intrinsics.h b/compiler/optimizing/intrinsics.h
index 5109882..893cd04 100644
--- a/compiler/optimizing/intrinsics.h
+++ b/compiler/optimizing/intrinsics.h
@@ -17,12 +17,13 @@
 #ifndef ART_COMPILER_OPTIMIZING_INTRINSICS_H_
 #define ART_COMPILER_OPTIMIZING_INTRINSICS_H_
 
+#include "base/macros.h"
 #include "code_generator.h"
 #include "nodes.h"
 #include "optimization.h"
 #include "parallel_move_resolver.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class DexFile;
 
diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc
index 646f4f2..d2dbaa3 100644
--- a/compiler/optimizing/intrinsics_arm64.cc
+++ b/compiler/optimizing/intrinsics_arm64.cc
@@ -46,7 +46,7 @@
 #include "aarch64/macro-assembler-aarch64.h"
 #pragma GCC diagnostic pop
 
-namespace art {
+namespace art HIDDEN {
 
 namespace arm64 {
 
@@ -55,7 +55,6 @@
 using helpers::HeapOperand;
 using helpers::LocationFrom;
 using helpers::InputCPURegisterOrZeroRegAt;
-using helpers::IsConstantZeroBitPattern;
 using helpers::OperandFrom;
 using helpers::RegisterFrom;
 using helpers::SRegisterFrom;
@@ -92,7 +91,7 @@
  public:
   ReadBarrierSystemArrayCopySlowPathARM64(HInstruction* instruction, Location tmp)
       : SlowPathCodeARM64(instruction), tmp_(tmp) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     DCHECK(kUseBakerReadBarrier);
   }
 
@@ -711,7 +710,7 @@
   Location trg_loc = locations->Out();
   Register trg = RegisterFrom(trg_loc, type);
 
-  if (type == DataType::Type::kReference && kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (type == DataType::Type::kReference && gUseReadBarrier && kUseBakerReadBarrier) {
     // UnsafeGetObject/UnsafeGetObjectVolatile with Baker's read barrier case.
     Register temp = WRegisterFrom(locations->GetTemp(0));
     MacroAssembler* masm = codegen->GetVIXLAssembler();
@@ -754,7 +753,7 @@
 }
 
 static void CreateIntIntIntToIntLocations(ArenaAllocator* allocator, HInvoke* invoke) {
-  bool can_call = kEmitCompilerReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
+  bool can_call = gUseReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
   LocationSummary* locations =
       new (allocator) LocationSummary(invoke,
                                       can_call
@@ -1096,7 +1095,7 @@
 }
 
 static void CreateUnsafeCASLocations(ArenaAllocator* allocator, HInvoke* invoke) {
-  const bool can_call = kEmitCompilerReadBarrier && IsUnsafeCASObject(invoke);
+  const bool can_call = gUseReadBarrier && IsUnsafeCASObject(invoke);
   LocationSummary* locations =
       new (allocator) LocationSummary(invoke,
                                       can_call
@@ -1448,7 +1447,7 @@
   vixl::aarch64::Label* exit_loop = &exit_loop_label;
   vixl::aarch64::Label* cmp_failure = &exit_loop_label;
 
-  if (kEmitCompilerReadBarrier && type == DataType::Type::kReference) {
+  if (gUseReadBarrier && type == DataType::Type::kReference) {
     // We need to store the `old_value` in a non-scratch register to make sure
     // the read barrier in the slow path does not clobber it.
     old_value = WRegisterFrom(locations->GetTemp(0));  // The old value from main path.
@@ -1523,12 +1522,12 @@
 }
 void IntrinsicLocationsBuilderARM64::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
   // The only supported read barrier implementation is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
   CreateUnsafeCASLocations(allocator_, invoke);
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     // We need two non-scratch temporary registers for read barrier.
     LocationSummary* locations = invoke->GetLocations();
     if (kUseBakerReadBarrier) {
@@ -1578,7 +1577,7 @@
 }
 void IntrinsicCodeGeneratorARM64::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
   // The only supported read barrier implementation is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   GenUnsafeCas(invoke, DataType::Type::kReference, codegen_);
 }
@@ -2576,9 +2575,9 @@
   __ Bind(&done);
 }
 
-// Mirrors ARRAYCOPY_SHORT_CHAR_ARRAY_THRESHOLD in libcore, so we can choose to use the native
-// implementation there for longer copy lengths.
-static constexpr int32_t kSystemArrayCopyCharThreshold = 32;
+// This value is greater than ARRAYCOPY_SHORT_CHAR_ARRAY_THRESHOLD in libcore,
+// so if we choose to jump to the slow path we will end up in the native implementation.
+static constexpr int32_t kSystemArrayCopyCharThreshold = 192;
 
 static void SetSystemArrayCopyLocationRequires(LocationSummary* locations,
                                                uint32_t at,
@@ -2710,11 +2709,13 @@
     __ Add(dst_base, dst_base, Operand(XRegisterFrom(dst_pos), LSL, element_size_shift));
   }
 
-  if (copy_length.IsConstant()) {
-    int32_t constant = copy_length.GetConstant()->AsIntConstant()->GetValue();
-    __ Add(src_end, src_base, element_size * constant);
-  } else {
-    __ Add(src_end, src_base, Operand(XRegisterFrom(copy_length), LSL, element_size_shift));
+  if (src_end.IsValid()) {
+    if (copy_length.IsConstant()) {
+      int32_t constant = copy_length.GetConstant()->AsIntConstant()->GetValue();
+      __ Add(src_end, src_base, element_size * constant);
+    } else {
+      __ Add(src_end, src_base, Operand(XRegisterFrom(copy_length), LSL, element_size_shift));
+    }
   }
 }
 
@@ -2745,13 +2746,14 @@
   if (!length.IsConstant()) {
     // Merge the following two comparisons into one:
     //   If the length is negative, bail out (delegate to libcore's native implementation).
-    //   If the length > 32 then (currently) prefer libcore's native implementation.
+    //   If the length > kSystemArrayCopyCharThreshold then (currently) prefer libcore's
+    //   native implementation.
     __ Cmp(WRegisterFrom(length), kSystemArrayCopyCharThreshold);
     __ B(slow_path->GetEntryLabel(), hi);
   } else {
     // We have already checked in the LocationsBuilder for the constant case.
     DCHECK_GE(length.GetConstant()->AsIntConstant()->GetValue(), 0);
-    DCHECK_LE(length.GetConstant()->AsIntConstant()->GetValue(), 32);
+    DCHECK_LE(length.GetConstant()->AsIntConstant()->GetValue(), kSystemArrayCopyCharThreshold);
   }
 
   Register src_curr_addr = WRegisterFrom(locations->GetTemp(0));
@@ -2787,21 +2789,102 @@
                               length,
                               src_curr_addr,
                               dst_curr_addr,
-                              src_stop_addr);
+                              Register());
 
   // Iterate over the arrays and do a raw copy of the chars.
   const int32_t char_size = DataType::Size(DataType::Type::kUint16);
   UseScratchRegisterScope temps(masm);
-  Register tmp = temps.AcquireW();
-  vixl::aarch64::Label loop, done;
-  __ Bind(&loop);
-  __ Cmp(src_curr_addr, src_stop_addr);
-  __ B(&done, eq);
-  __ Ldrh(tmp, MemOperand(src_curr_addr, char_size, PostIndex));
-  __ Strh(tmp, MemOperand(dst_curr_addr, char_size, PostIndex));
-  __ B(&loop);
-  __ Bind(&done);
 
+  // We split processing of the array in two parts: head and tail.
+  // A first loop handles the head by copying a block of characters per
+  // iteration (see: chars_per_block).
+  // A second loop handles the tail by copying the remaining characters.
+  // If the copy length is not constant, we copy them one-by-one.
+  // If the copy length is constant, we optimize by always unrolling the tail
+  // loop, and also unrolling the head loop when the copy length is small (see:
+  // unroll_threshold).
+  //
+  // Both loops are inverted for better performance, meaning they are
+  // implemented as conditional do-while loops.
+  // Here, the loop condition is first checked to determine if there are
+  // sufficient chars to run an iteration, then we enter the do-while: an
+  // iteration is performed followed by a conditional branch only if another
+  // iteration is necessary. As opposed to a standard while-loop, this inversion
+  // can save some branching (e.g. we don't branch back to the initial condition
+  // at the end of every iteration only to potentially immediately branch
+  // again).
+  //
+  // A full block of chars is subtracted and added before and after the head
+  // loop, respectively. This ensures that any remaining length after each
+  // head loop iteration means there is a full block remaining, reducing the
+  // number of conditional checks required on every iteration.
+  constexpr int32_t chars_per_block = 4;
+  constexpr int32_t unroll_threshold = 2 * chars_per_block;
+  vixl::aarch64::Label loop1, loop2, pre_loop2, done;
+
+  Register length_tmp = src_stop_addr.W();
+  Register tmp = temps.AcquireRegisterOfSize(char_size * chars_per_block * kBitsPerByte);
+
+  auto emitHeadLoop = [&]() {
+    __ Bind(&loop1);
+    __ Ldr(tmp, MemOperand(src_curr_addr, char_size * chars_per_block, PostIndex));
+    __ Subs(length_tmp, length_tmp, chars_per_block);
+    __ Str(tmp, MemOperand(dst_curr_addr, char_size * chars_per_block, PostIndex));
+    __ B(&loop1, ge);
+  };
+
+  auto emitTailLoop = [&]() {
+    __ Bind(&loop2);
+    __ Ldrh(tmp, MemOperand(src_curr_addr, char_size, PostIndex));
+    __ Subs(length_tmp, length_tmp, 1);
+    __ Strh(tmp, MemOperand(dst_curr_addr, char_size, PostIndex));
+    __ B(&loop2, gt);
+  };
+
+  auto emitUnrolledTailLoop = [&](const int32_t tail_length) {
+    DCHECK_LT(tail_length, 4);
+
+    // Don't use post-index addressing, and instead add a constant offset later.
+    if ((tail_length & 2) != 0) {
+      __ Ldr(tmp.W(), MemOperand(src_curr_addr));
+      __ Str(tmp.W(), MemOperand(dst_curr_addr));
+    }
+    if ((tail_length & 1) != 0) {
+      const int32_t offset = (tail_length & ~1) * char_size;
+      __ Ldrh(tmp, MemOperand(src_curr_addr, offset));
+      __ Strh(tmp, MemOperand(dst_curr_addr, offset));
+    }
+  };
+
+  if (length.IsConstant()) {
+    const int32_t constant_length = length.GetConstant()->AsIntConstant()->GetValue();
+    if (constant_length >= unroll_threshold) {
+      __ Mov(length_tmp, constant_length - chars_per_block);
+      emitHeadLoop();
+    } else {
+      static_assert(unroll_threshold == 8, "The unroll_threshold must be 8.");
+      // Fully unroll both the head and tail loops.
+      if ((constant_length & 4) != 0) {
+        __ Ldr(tmp, MemOperand(src_curr_addr, 4 * char_size, PostIndex));
+        __ Str(tmp, MemOperand(dst_curr_addr, 4 * char_size, PostIndex));
+      }
+    }
+    emitUnrolledTailLoop(constant_length % chars_per_block);
+  } else {
+    Register length_reg = WRegisterFrom(length);
+    __ Subs(length_tmp, length_reg, chars_per_block);
+    __ B(&pre_loop2, lt);
+
+    emitHeadLoop();
+
+    __ Bind(&pre_loop2);
+    __ Adds(length_tmp, length_tmp, chars_per_block);
+    __ B(&done, eq);
+
+    emitTailLoop();
+  }
+
+  __ Bind(&done);
   __ Bind(slow_path->GetExitLabel());
 }
 
@@ -2814,7 +2897,7 @@
 void IntrinsicLocationsBuilderARM64::VisitSystemArrayCopy(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // SystemArrayCopy intrinsic is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -2866,7 +2949,7 @@
 
   locations->AddTemp(Location::RequiresRegister());
   locations->AddTemp(Location::RequiresRegister());
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     // Temporary register IP0, obtained from the VIXL scratch register
     // pool, cannot be used in ReadBarrierSystemArrayCopySlowPathARM64
     // (because that register is clobbered by ReadBarrierMarkRegX
@@ -2884,7 +2967,7 @@
 void IntrinsicCodeGeneratorARM64::VisitSystemArrayCopy(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // SystemArrayCopy intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   MacroAssembler* masm = GetVIXLAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -2991,7 +3074,7 @@
     UseScratchRegisterScope temps(masm);
     Location temp3_loc;  // Used only for Baker read barrier.
     Register temp3;
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       temp3_loc = locations->GetTemp(2);
       temp3 = WRegisterFrom(temp3_loc);
     } else {
@@ -3004,7 +3087,7 @@
       // or the destination is Object[]. If none of these checks succeed, we go to the
       // slow path.
 
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         if (!optimizations.GetSourceIsNonPrimitiveArray()) {
           // /* HeapReference<Class> */ temp1 = src->klass_
           codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
@@ -3165,7 +3248,7 @@
     } else if (!optimizations.GetSourceIsNonPrimitiveArray()) {
       DCHECK(optimizations.GetDestinationIsNonPrimitiveArray());
       // Bail out if the source is not a non primitive array.
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         // /* HeapReference<Class> */ temp1 = src->klass_
         codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
                                                         temp1_loc,
@@ -3215,7 +3298,7 @@
         __ Cbz(WRegisterFrom(length), &done);
       }
 
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         // TODO: Also convert this intrinsic to the IsGcMarking strategy?
 
         // SystemArrayCopy implementation for Baker read barriers (see
@@ -3335,7 +3418,7 @@
   }
 
   // We only need one card marking on the destination array.
-  codegen_->MarkGCCard(dest.W(), Register(), /* value_can_be_null= */ false);
+  codegen_->MarkGCCard(dest.W(), Register(), /* emit_null_check= */ false);
 
   __ Bind(intrinsic_slow_path->GetExitLabel());
 }
@@ -3451,7 +3534,7 @@
 void IntrinsicLocationsBuilderARM64::VisitReferenceGetReferent(HInvoke* invoke) {
   IntrinsicVisitor::CreateReferenceGetReferentLocations(invoke, codegen_);
 
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier && invoke->GetLocations() != nullptr) {
+  if (gUseReadBarrier && kUseBakerReadBarrier && invoke->GetLocations() != nullptr) {
     invoke->GetLocations()->AddTemp(Location::RequiresRegister());
   }
 }
@@ -3466,7 +3549,7 @@
   SlowPathCodeARM64* slow_path = new (GetAllocator()) IntrinsicSlowPathARM64(invoke);
   codegen_->AddSlowPath(slow_path);
 
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     // Check self->GetWeakRefAccessEnabled().
     UseScratchRegisterScope temps(masm);
     Register temp = temps.AcquireW();
@@ -3493,7 +3576,7 @@
 
   // Load the value from the field.
   uint32_t referent_offset = mirror::Reference::ReferentOffset().Uint32Value();
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
                                                     out,
                                                     WRegisterFrom(obj),
@@ -3533,7 +3616,7 @@
 
   __ Cmp(tmp, other);
 
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     DCHECK(kUseBakerReadBarrier);
 
     vixl::aarch64::Label calculate_result;
@@ -4629,7 +4712,7 @@
                                          method.X(),
                                          ArtField::DeclaringClassOffset().Int32Value(),
                                          /*fixup_label=*/ nullptr,
-                                         kCompilerReadBarrierOption);
+                                         gCompilerReadBarrierOption);
       }
     }
   } else {
@@ -4673,8 +4756,8 @@
   uint32_t number_of_arguments = invoke->GetNumberOfArguments();
   for (size_t arg_index = arguments_start; arg_index != number_of_arguments; ++arg_index) {
     HInstruction* arg = invoke->InputAt(arg_index);
-    if (IsConstantZeroBitPattern(arg)) {
-      locations->SetInAt(arg_index, Location::ConstantLocation(arg->AsConstant()));
+    if (IsZeroBitPattern(arg)) {
+      locations->SetInAt(arg_index, Location::ConstantLocation(arg));
     } else if (DataType::IsFloatingPointType(arg->GetType())) {
       locations->SetInAt(arg_index, Location::RequiresFpuRegister());
     } else {
@@ -4683,7 +4766,7 @@
   }
 
   // Add a temporary for offset.
-  if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+  if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
       GetExpectedVarHandleCoordinatesCount(invoke) == 0u) {  // For static fields.
     // To preserve the offset value across the non-Baker read barrier slow path
     // for loading the declaring class, use a fixed callee-save register.
@@ -4706,7 +4789,7 @@
     return;
   }
 
-  if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+  if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
       invoke->GetType() == DataType::Type::kReference &&
       invoke->GetIntrinsic() != Intrinsics::kVarHandleGet &&
       invoke->GetIntrinsic() != Intrinsics::kVarHandleGetOpaque) {
@@ -4746,7 +4829,7 @@
   DCHECK(use_load_acquire || order == std::memory_order_relaxed);
 
   // Load the value from the target location.
-  if (type == DataType::Type::kReference && kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (type == DataType::Type::kReference && gUseReadBarrier && kUseBakerReadBarrier) {
     // Piggy-back on the field load path using introspection for the Baker read barrier.
     // The `target.offset` is a temporary, use it for field address.
     Register tmp_ptr = target.offset.X();
@@ -4898,7 +4981,7 @@
   }
 
   if (CodeGenerator::StoreNeedsWriteBarrier(value_type, invoke->InputAt(value_index))) {
-    codegen->MarkGCCard(target.object, Register(value), /*value_can_be_null=*/ true);
+    codegen->MarkGCCard(target.object, Register(value), /* emit_null_check= */ true);
   }
 
   if (slow_path != nullptr) {
@@ -4947,7 +5030,7 @@
 
   uint32_t number_of_arguments = invoke->GetNumberOfArguments();
   DataType::Type value_type = GetDataTypeFromShorty(invoke, number_of_arguments - 1u);
-  if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+  if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
       value_type == DataType::Type::kReference) {
     // Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores
     // the passed reference and reloads it from the field. This breaks the read barriers
@@ -4961,7 +5044,7 @@
 
   LocationSummary* locations = CreateVarHandleCommonLocations(invoke);
 
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     // We need callee-save registers for both the class object and offset instead of
     // the temporaries reserved in CreateVarHandleCommonLocations().
     static_assert(POPCOUNT(kArm64CalleeSaveRefSpills) >= 2u);
@@ -4985,16 +5068,16 @@
       // Add a temporary for old value and exclusive store result if floating point
       // `expected` and/or `new_value` take scratch registers.
       size_t available_scratch_registers =
-          (IsConstantZeroBitPattern(invoke->InputAt(number_of_arguments - 1u)) ? 1u : 0u) +
-          (IsConstantZeroBitPattern(invoke->InputAt(number_of_arguments - 2u)) ? 1u : 0u);
+          (IsZeroBitPattern(invoke->InputAt(number_of_arguments - 1u)) ? 1u : 0u) +
+          (IsZeroBitPattern(invoke->InputAt(number_of_arguments - 2u)) ? 1u : 0u);
       size_t temps_needed = /* pointer, old value, store result */ 3u - available_scratch_registers;
       // We can reuse the declaring class (if present) and offset temporary.
       if (temps_needed > old_temp_count) {
         locations->AddRegisterTemps(temps_needed - old_temp_count);
       }
     } else if ((value_type != DataType::Type::kReference && DataType::Size(value_type) != 1u) &&
-               !IsConstantZeroBitPattern(invoke->InputAt(number_of_arguments - 2u)) &&
-               !IsConstantZeroBitPattern(invoke->InputAt(number_of_arguments - 1u)) &&
+               !IsZeroBitPattern(invoke->InputAt(number_of_arguments - 2u)) &&
+               !IsZeroBitPattern(invoke->InputAt(number_of_arguments - 1u)) &&
                GetExpectedVarHandleCoordinatesCount(invoke) == 2u) {
       // Allocate a normal temporary for store result in the non-native byte order path
       // because scratch registers are used by the byte-swapped `expected` and `new_value`.
@@ -5002,7 +5085,7 @@
       locations->AddTemp(Location::RequiresRegister());
     }
   }
-  if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+  if (gUseReadBarrier && value_type == DataType::Type::kReference) {
     // Add a temporary for the `old_value_temp` in slow path.
     locations->AddTemp(Location::RequiresRegister());
   }
@@ -5068,7 +5151,7 @@
   // except for references that need the offset for the read barrier.
   UseScratchRegisterScope temps(masm);
   Register tmp_ptr = target.offset.X();
-  if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+  if (gUseReadBarrier && value_type == DataType::Type::kReference) {
     tmp_ptr = temps.AcquireX();
   }
   __ Add(tmp_ptr, target.object.X(), target.offset.X());
@@ -5151,7 +5234,7 @@
   vixl::aarch64::Label* exit_loop = &exit_loop_label;
   vixl::aarch64::Label* cmp_failure = &exit_loop_label;
 
-  if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+  if (gUseReadBarrier && value_type == DataType::Type::kReference) {
     // The `old_value_temp` is used first for the marked `old_value` and then for the unmarked
     // reloaded old value for subsequent CAS in the slow path. It cannot be a scratch register.
     size_t expected_coordinates_count = GetExpectedVarHandleCoordinatesCount(invoke);
@@ -5296,7 +5379,7 @@
     return;
   }
 
-  if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+  if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
       invoke->GetType() == DataType::Type::kReference) {
     // Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores
     // the passed reference and reloads it from the field, thus seeing the new value
@@ -5316,7 +5399,7 @@
       DCHECK(get_and_update_op == GetAndUpdateOp::kSet);
       // We can reuse the declaring class temporary if present.
       if (old_temp_count == 1u &&
-          !IsConstantZeroBitPattern(invoke->InputAt(invoke->GetNumberOfArguments() - 1u))) {
+          !IsZeroBitPattern(invoke->InputAt(invoke->GetNumberOfArguments() - 1u))) {
         // Add a temporary for `old_value` if floating point `new_value` takes a scratch register.
         locations->AddTemp(Location::RequiresRegister());
       }
@@ -5327,7 +5410,7 @@
   if (old_temp_count == 1u &&
       (get_and_update_op != GetAndUpdateOp::kSet && get_and_update_op != GetAndUpdateOp::kAdd) &&
       GetExpectedVarHandleCoordinatesCount(invoke) == 2u &&
-      !IsConstantZeroBitPattern(invoke->InputAt(invoke->GetNumberOfArguments() - 1u))) {
+      !IsZeroBitPattern(invoke->InputAt(invoke->GetNumberOfArguments() - 1u))) {
     DataType::Type value_type =
         GetVarHandleExpectedValueType(invoke, /*expected_coordinates_count=*/ 2u);
     if (value_type != DataType::Type::kReference && DataType::Size(value_type) != 1u) {
@@ -5372,7 +5455,7 @@
   // except for references that need the offset for the non-Baker read barrier.
   UseScratchRegisterScope temps(masm);
   Register tmp_ptr = target.offset.X();
-  if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+  if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
       value_type == DataType::Type::kReference) {
     tmp_ptr = temps.AcquireX();
   }
@@ -5402,7 +5485,7 @@
       // the new value unless it is zero bit pattern (+0.0f or +0.0) and need another one
       // in GenerateGetAndUpdate(). We have allocated a normal temporary to handle that.
       old_value = CPURegisterFrom(locations->GetTemp(1u), load_store_type);
-    } else if ((kEmitCompilerReadBarrier && kUseBakerReadBarrier) &&
+    } else if ((gUseReadBarrier && kUseBakerReadBarrier) &&
                value_type == DataType::Type::kReference) {
       // Load the old value initially to a scratch register.
       // We shall move it to `out` later with a read barrier.
@@ -5450,7 +5533,7 @@
     __ Sxtb(out.W(), old_value.W());
   } else if (value_type == DataType::Type::kInt16) {
     __ Sxth(out.W(), old_value.W());
-  } else if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+  } else if (gUseReadBarrier && value_type == DataType::Type::kReference) {
     if (kUseBakerReadBarrier) {
       codegen->GenerateIntrinsicCasMoveWithBakerReadBarrier(out.W(), old_value.W());
     } else {
@@ -5647,7 +5730,7 @@
 
     // Byte order check. For native byte order return to the main path.
     if (access_mode_template == mirror::VarHandle::AccessModeTemplate::kSet &&
-        IsConstantZeroBitPattern(invoke->InputAt(invoke->GetNumberOfArguments() - 1u))) {
+        IsZeroBitPattern(invoke->InputAt(invoke->GetNumberOfArguments() - 1u))) {
       // There is no reason to differentiate between native byte order and byte-swap
       // for setting a zero bit pattern. Just return to the main path.
       __ B(GetNativeByteOrderLabel());
@@ -5677,42 +5760,9 @@
   __ B(GetExitLabel());
 }
 
-UNIMPLEMENTED_INTRINSIC(ARM64, StringStringIndexOf);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringStringIndexOfAfter);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBufferAppend);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBufferLength);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBufferToString);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderAppendObject);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderAppendString);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderAppendCharSequence);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderAppendCharArray);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderAppendBoolean);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderAppendChar);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderAppendInt);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderAppendLong);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderAppendFloat);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderAppendDouble);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderLength);
-UNIMPLEMENTED_INTRINSIC(ARM64, StringBuilderToString);
-UNIMPLEMENTED_INTRINSIC(ARM64, SystemArrayCopyByte);
-UNIMPLEMENTED_INTRINSIC(ARM64, SystemArrayCopyInt);
-
-// 1.8.
-UNIMPLEMENTED_INTRINSIC(ARM64, UnsafeGetAndAddInt)
-UNIMPLEMENTED_INTRINSIC(ARM64, UnsafeGetAndAddLong)
-UNIMPLEMENTED_INTRINSIC(ARM64, UnsafeGetAndSetInt)
-UNIMPLEMENTED_INTRINSIC(ARM64, UnsafeGetAndSetLong)
-UNIMPLEMENTED_INTRINSIC(ARM64, UnsafeGetAndSetObject)
-
-UNIMPLEMENTED_INTRINSIC(ARM64, MethodHandleInvokeExact)
-UNIMPLEMENTED_INTRINSIC(ARM64, MethodHandleInvoke)
-
-// OpenJDK 11
-UNIMPLEMENTED_INTRINSIC(ARM64, JdkUnsafeGetAndAddInt)
-UNIMPLEMENTED_INTRINSIC(ARM64, JdkUnsafeGetAndAddLong)
-UNIMPLEMENTED_INTRINSIC(ARM64, JdkUnsafeGetAndSetInt)
-UNIMPLEMENTED_INTRINSIC(ARM64, JdkUnsafeGetAndSetLong)
-UNIMPLEMENTED_INTRINSIC(ARM64, JdkUnsafeGetAndSetObject)
+#define MARK_UNIMPLEMENTED(Name) UNIMPLEMENTED_INTRINSIC(ARM64, Name)
+UNIMPLEMENTED_INTRINSIC_LIST_ARM64(MARK_UNIMPLEMENTED);
+#undef MARK_UNIMPLEMENTED
 
 UNREACHABLE_INTRINSICS(ARM64)
 
diff --git a/compiler/optimizing/intrinsics_arm64.h b/compiler/optimizing/intrinsics_arm64.h
index 9c46efd..a0ccf87 100644
--- a/compiler/optimizing/intrinsics_arm64.h
+++ b/compiler/optimizing/intrinsics_arm64.h
@@ -17,6 +17,7 @@
 #ifndef ART_COMPILER_OPTIMIZING_INTRINSICS_ARM64_H_
 #define ART_COMPILER_OPTIMIZING_INTRINSICS_ARM64_H_
 
+#include "base/macros.h"
 #include "intrinsics.h"
 
 namespace vixl {
@@ -27,7 +28,7 @@
 }  // namespace aarch64
 }  // namespace vixl
 
-namespace art {
+namespace art HIDDEN {
 
 class ArenaAllocator;
 class HInvokeStaticOrDirect;
diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc
index d850cad..266b5bc 100644
--- a/compiler/optimizing/intrinsics_arm_vixl.cc
+++ b/compiler/optimizing/intrinsics_arm_vixl.cc
@@ -34,7 +34,7 @@
 
 #include "aarch32/constants-aarch32.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 #define __ assembler->GetVIXLAssembler()->
@@ -120,7 +120,7 @@
  public:
   explicit ReadBarrierSystemArrayCopySlowPathARMVIXL(HInstruction* instruction)
       : SlowPathCodeARMVIXL(instruction) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     DCHECK(kUseBakerReadBarrier);
   }
 
@@ -1242,7 +1242,7 @@
 void IntrinsicLocationsBuilderARMVIXL::VisitSystemArrayCopy(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // SystemArrayCopy intrinsic is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -1265,7 +1265,7 @@
   if (length != nullptr && !assembler_->ShifterOperandCanAlwaysHold(length->GetValue())) {
     locations->SetInAt(4, Location::RequiresRegister());
   }
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     // Temporary register IP cannot be used in
     // ReadBarrierSystemArrayCopySlowPathARM (because that register
     // is clobbered by ReadBarrierMarkRegX entry points). Get an extra
@@ -1339,7 +1339,7 @@
 void IntrinsicCodeGeneratorARMVIXL::VisitSystemArrayCopy(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // SystemArrayCopy intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   ArmVIXLAssembler* assembler = GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -1453,7 +1453,7 @@
     // or the destination is Object[]. If none of these checks succeed, we go to the
     // slow path.
 
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       if (!optimizations.GetSourceIsNonPrimitiveArray()) {
         // /* HeapReference<Class> */ temp1 = src->klass_
         codegen_->GenerateFieldLoadWithBakerReadBarrier(
@@ -1584,7 +1584,7 @@
   } else if (!optimizations.GetSourceIsNonPrimitiveArray()) {
     DCHECK(optimizations.GetDestinationIsNonPrimitiveArray());
     // Bail out if the source is not a non primitive array.
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       // /* HeapReference<Class> */ temp1 = src->klass_
       codegen_->GenerateFieldLoadWithBakerReadBarrier(
           invoke, temp1_loc, src, class_offset, temp2_loc, /* needs_null_check= */ false);
@@ -1621,7 +1621,7 @@
       __ CompareAndBranchIfZero(RegisterFrom(length), &done, /* is_far_target= */ false);
     }
 
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       // TODO: Also convert this intrinsic to the IsGcMarking strategy?
 
       // SystemArrayCopy implementation for Baker read barriers (see
@@ -1723,7 +1723,7 @@
   }
 
   // We only need one card marking on the destination array.
-  codegen_->MarkGCCard(temp1, temp2, dest, NoReg, /* value_can_be_null= */ false);
+  codegen_->MarkGCCard(temp1, temp2, dest, NoReg, /* emit_null_check= */ false);
 
   __ Bind(intrinsic_slow_path->GetExitLabel());
 }
@@ -2511,7 +2511,7 @@
   SlowPathCodeARMVIXL* slow_path = new (GetAllocator()) IntrinsicSlowPathARMVIXL(invoke);
   codegen_->AddSlowPath(slow_path);
 
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     // Check self->GetWeakRefAccessEnabled().
     UseScratchRegisterScope temps(assembler->GetVIXLAssembler());
     vixl32::Register temp = temps.Acquire();
@@ -2539,7 +2539,7 @@
 
   // Load the value from the field.
   uint32_t referent_offset = mirror::Reference::ReferentOffset().Uint32Value();
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
                                                     out,
                                                     RegisterFrom(obj),
@@ -2587,7 +2587,7 @@
   assembler->MaybeUnpoisonHeapReference(tmp);
   codegen_->GenerateMemoryBarrier(MemBarrierKind::kLoadAny);  // `referent` is volatile.
 
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     DCHECK(kUseBakerReadBarrier);
 
     vixl32::Label calculate_result;
@@ -2613,7 +2613,7 @@
 
     __ Bind(&calculate_result);
   } else {
-    DCHECK(!kEmitCompilerReadBarrier);
+    DCHECK(!gUseReadBarrier);
     __ Sub(out, tmp, other);
   }
 
@@ -2732,7 +2732,7 @@
       }
       break;
     case DataType::Type::kReference:
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         // Piggy-back on the field load path using introspection for the Baker read barrier.
         vixl32::Register temp = RegisterFrom(maybe_temp);
         __ Add(temp, base, offset);
@@ -2777,7 +2777,7 @@
     codegen->GenerateMemoryBarrier(
         seq_cst_barrier ? MemBarrierKind::kAnyAny : MemBarrierKind::kLoadAny);
   }
-  if (type == DataType::Type::kReference && !(kEmitCompilerReadBarrier && kUseBakerReadBarrier)) {
+  if (type == DataType::Type::kReference && !(gUseReadBarrier && kUseBakerReadBarrier)) {
     Location base_loc = LocationFrom(base);
     Location index_loc = LocationFrom(offset);
     codegen->MaybeGenerateReadBarrierSlow(invoke, out, out, base_loc, /* offset=*/ 0u, index_loc);
@@ -2802,7 +2802,7 @@
                                      CodeGeneratorARMVIXL* codegen,
                                      DataType::Type type,
                                      bool atomic) {
-  bool can_call = kEmitCompilerReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
+  bool can_call = gUseReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
   ArenaAllocator* allocator = invoke->GetBlock()->GetGraph()->GetAllocator();
   LocationSummary* locations =
       new (allocator) LocationSummary(invoke,
@@ -2818,7 +2818,7 @@
   locations->SetInAt(2, Location::RequiresRegister());
   locations->SetOut(Location::RequiresRegister(),
                     (can_call ? Location::kOutputOverlap : Location::kNoOutputOverlap));
-  if ((kEmitCompilerReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) ||
+  if ((gUseReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) ||
       (type == DataType::Type::kInt64 && Use64BitExclusiveLoadStore(atomic, codegen))) {
     // We need a temporary register for the read barrier marking slow
     // path in CodeGeneratorARMVIXL::GenerateReferenceLoadWithBakerReadBarrier,
@@ -2837,7 +2837,7 @@
   vixl32::Register offset = LowRegisterFrom(locations->InAt(2));  // Long offset, lo part only.
   Location out = locations->Out();
   Location maybe_temp = Location::NoLocation();
-  if ((kEmitCompilerReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) ||
+  if ((gUseReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) ||
       (type == DataType::Type::kInt64 && Use64BitExclusiveLoadStore(atomic, codegen))) {
     maybe_temp = locations->GetTemp(0);
   }
@@ -3470,7 +3470,7 @@
   // branch goes to the read barrier slow path that clobbers `success` anyway.
   bool init_failure_for_cmp =
       success.IsValid() &&
-      !(kEmitCompilerReadBarrier && type == DataType::Type::kReference && expected.IsRegister());
+      !(gUseReadBarrier && type == DataType::Type::kReference && expected.IsRegister());
   // Instruction scheduling: Loading a constant between LDREX* and using the loaded value
   // is essentially free, so prepare the failure value here if we can.
   bool init_failure_for_cmp_early =
@@ -3655,7 +3655,7 @@
 };
 
 static void CreateUnsafeCASLocations(ArenaAllocator* allocator, HInvoke* invoke) {
-  const bool can_call = kEmitCompilerReadBarrier && IsUnsafeCASObject(invoke);
+  const bool can_call = gUseReadBarrier && IsUnsafeCASObject(invoke);
   LocationSummary* locations =
       new (allocator) LocationSummary(invoke,
                                       can_call
@@ -3706,7 +3706,7 @@
   vixl32::Label* exit_loop = &exit_loop_label;
   vixl32::Label* cmp_failure = &exit_loop_label;
 
-  if (kEmitCompilerReadBarrier && type == DataType::Type::kReference) {
+  if (gUseReadBarrier && type == DataType::Type::kReference) {
     // If marking, check if the stored reference is a from-space reference to the same
     // object as the to-space reference `expected`. If so, perform a custom CAS loop.
     ReadBarrierCasSlowPathARMVIXL* slow_path =
@@ -3770,7 +3770,7 @@
 }
 void IntrinsicLocationsBuilderARMVIXL::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
   // The only supported read barrier implementation is the Baker-style read barriers (b/173104084).
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -3798,7 +3798,7 @@
 }
 void IntrinsicCodeGeneratorARMVIXL::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
   // The only supported read barrier implementation is the Baker-style read barriers (b/173104084).
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   GenUnsafeCas(invoke, DataType::Type::kReference, codegen_);
 }
@@ -4351,7 +4351,7 @@
                                          LocationFrom(target.object),
                                          method,
                                          ArtField::DeclaringClassOffset().Int32Value(),
-                                         kCompilerReadBarrierOption);
+                                         gCompilerReadBarrierOption);
       }
     }
   } else {
@@ -4403,7 +4403,7 @@
   }
 
   // Add a temporary for offset.
-  if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+  if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
       GetExpectedVarHandleCoordinatesCount(invoke) == 0u) {  // For static fields.
     // To preserve the offset value across the non-Baker read barrier slow path
     // for loading the declaring class, use a fixed callee-save register.
@@ -4428,7 +4428,7 @@
     return;
   }
 
-  if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+  if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
       invoke->GetType() == DataType::Type::kReference &&
       invoke->GetIntrinsic() != Intrinsics::kVarHandleGet &&
       invoke->GetIntrinsic() != Intrinsics::kVarHandleGetOpaque) {
@@ -4476,7 +4476,7 @@
   Location maybe_temp = Location::NoLocation();
   Location maybe_temp2 = Location::NoLocation();
   Location maybe_temp3 = Location::NoLocation();
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) {
+  if (gUseReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) {
     // Reuse the offset temporary.
     maybe_temp = LocationFrom(target.offset);
   } else if (DataType::Is64BitType(type) && Use64BitExclusiveLoadStore(atomic, codegen)) {
@@ -4590,7 +4590,7 @@
     HInstruction* arg = invoke->InputAt(number_of_arguments - 1u);
     bool has_reverse_bytes_slow_path =
         (expected_coordinates_count == 2u) &&
-        !(arg->IsConstant() && arg->AsConstant()->IsZeroBitPattern());
+        !IsZeroBitPattern(arg);
     if (Use64BitExclusiveLoadStore(atomic, codegen)) {
       // We need 4 temporaries in the byte array view slow path. Otherwise, we need
       // 2 or 3 temporaries for GenerateIntrinsicSet() depending on the value type.
@@ -4699,7 +4699,7 @@
     vixl32::Register temp = target.offset;
     vixl32::Register card = temps.Acquire();
     vixl32::Register value_reg = RegisterFrom(value);
-    codegen->MarkGCCard(temp, card, target.object, value_reg, /*value_can_be_null=*/ true);
+    codegen->MarkGCCard(temp, card, target.object, value_reg, /* emit_null_check= */ true);
   }
 
   if (slow_path != nullptr) {
@@ -4749,7 +4749,7 @@
 
   uint32_t number_of_arguments = invoke->GetNumberOfArguments();
   DataType::Type value_type = GetDataTypeFromShorty(invoke, number_of_arguments - 1u);
-  if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+  if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
       value_type == DataType::Type::kReference) {
     // Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores
     // the passed reference and reloads it from the field. This breaks the read barriers
@@ -4763,7 +4763,7 @@
 
   LocationSummary* locations = CreateVarHandleCommonLocations(invoke);
 
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     // We need callee-save registers for both the class object and offset instead of
     // the temporaries reserved in CreateVarHandleCommonLocations().
     static_assert(POPCOUNT(kArmCalleeSaveRefSpills) >= 2u);
@@ -4799,7 +4799,7 @@
       locations->AddRegisterTemps(2u);
     }
   }
-  if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+  if (gUseReadBarrier && value_type == DataType::Type::kReference) {
     // Add a temporary for store result, also used for the `old_value_temp` in slow path.
     locations->AddTemp(Location::RequiresRegister());
   }
@@ -4930,7 +4930,7 @@
   vixl32::Label* exit_loop = &exit_loop_label;
   vixl32::Label* cmp_failure = &exit_loop_label;
 
-  if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+  if (gUseReadBarrier && value_type == DataType::Type::kReference) {
     // The `old_value_temp` is used first for the marked `old_value` and then for the unmarked
     // reloaded old value for subsequent CAS in the slow path. This must not clobber `old_value`.
     vixl32::Register old_value_temp = return_success ? RegisterFrom(out) : store_result;
@@ -5086,7 +5086,7 @@
     return;
   }
 
-  if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+  if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
       invoke->GetType() == DataType::Type::kReference) {
     // Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores
     // the passed reference and reloads it from the field, thus seeing the new value
@@ -5107,7 +5107,7 @@
       // Add temps needed to do the GenerateGetAndUpdate() with core registers.
       size_t temps_needed = (value_type == DataType::Type::kFloat64) ? 5u : 3u;
       locations->AddRegisterTemps(temps_needed - locations->GetTempCount());
-    } else if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+    } else if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
                value_type == DataType::Type::kReference) {
       // We need to preserve the declaring class (if present) and offset for read barrier
       // slow paths, so we must use a separate temporary for the exclusive store result.
@@ -5213,7 +5213,7 @@
       if (byte_swap) {
         GenerateReverseBytes(assembler, DataType::Type::kInt32, arg, arg);
       }
-    } else if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+    } else if (gUseReadBarrier && value_type == DataType::Type::kReference) {
       if (kUseBakerReadBarrier) {
         // Load the old value initially to a temporary register.
         // We shall move it to `out` later with a read barrier.
@@ -5296,7 +5296,7 @@
     } else {
       __ Vmov(SRegisterFrom(out), RegisterFrom(old_value));
     }
-  } else if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+  } else if (gUseReadBarrier && value_type == DataType::Type::kReference) {
     if (kUseBakerReadBarrier) {
       codegen->GenerateIntrinsicCasMoveWithBakerReadBarrier(RegisterFrom(out),
                                                             RegisterFrom(old_value));
@@ -5517,7 +5517,7 @@
     // Byte order check. For native byte order return to the main path.
     if (access_mode_template == mirror::VarHandle::AccessModeTemplate::kSet) {
       HInstruction* arg = invoke->InputAt(invoke->GetNumberOfArguments() - 1u);
-      if (arg->IsConstant() && arg->AsConstant()->IsZeroBitPattern()) {
+      if (IsZeroBitPattern(arg)) {
         // There is no reason to differentiate between native byte order and byte-swap
         // for setting a zero bit pattern. Just return to the main path.
         __ B(GetNativeByteOrderLabel());
@@ -5549,69 +5549,9 @@
   __ B(GetExitLabel());
 }
 
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathRoundDouble)   // Could be done by changing rounding mode, maybe?
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, UnsafeCASLong)     // High register pressure.
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, SystemArrayCopyChar)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, LongDivideUnsigned)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, CRC32Update)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, CRC32UpdateBytes)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, CRC32UpdateByteBuffer)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16ToFloat)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16ToHalf)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16Floor)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16Ceil)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16Rint)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16Greater)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16GreaterEquals)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16Less)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16LessEquals)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16Compare)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16Min)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16Max)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMultiplyHigh)
-
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringStringIndexOf);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringStringIndexOfAfter);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBufferAppend);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBufferLength);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBufferToString);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderAppendObject);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderAppendString);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderAppendCharSequence);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderAppendCharArray);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderAppendBoolean);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderAppendChar);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderAppendInt);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderAppendLong);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderAppendFloat);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderAppendDouble);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderLength);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringBuilderToString);
-
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, SystemArrayCopyByte);
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, SystemArrayCopyInt);
-
-// 1.8.
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathFmaDouble)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathFmaFloat)
-
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, UnsafeGetAndAddInt)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, UnsafeGetAndAddLong)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, UnsafeGetAndSetInt)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, UnsafeGetAndSetLong)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, UnsafeGetAndSetObject)
-
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, MethodHandleInvokeExact)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, MethodHandleInvoke)
-
-// OpenJDK 11
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, JdkUnsafeCASLong)      // High register pressure.
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, JdkUnsafeGetAndAddInt)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, JdkUnsafeGetAndAddLong)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, JdkUnsafeGetAndSetInt)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, JdkUnsafeGetAndSetLong)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, JdkUnsafeGetAndSetObject)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, JdkUnsafeCompareAndSetLong)
+#define MARK_UNIMPLEMENTED(Name) UNIMPLEMENTED_INTRINSIC(ARMVIXL, Name)
+UNIMPLEMENTED_INTRINSIC_LIST_ARM(MARK_UNIMPLEMENTED);
+#undef MARK_UNIMPLEMENTED
 
 UNREACHABLE_INTRINSICS(ARMVIXL)
 
diff --git a/compiler/optimizing/intrinsics_arm_vixl.h b/compiler/optimizing/intrinsics_arm_vixl.h
index 3103cec..54475bc 100644
--- a/compiler/optimizing/intrinsics_arm_vixl.h
+++ b/compiler/optimizing/intrinsics_arm_vixl.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_INTRINSICS_ARM_VIXL_H_
 #define ART_COMPILER_OPTIMIZING_INTRINSICS_ARM_VIXL_H_
 
+#include "base/macros.h"
 #include "intrinsics.h"
 #include "utils/arm/assembler_arm_vixl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace arm {
 
diff --git a/compiler/optimizing/intrinsics_utils.h b/compiler/optimizing/intrinsics_utils.h
index 19f5e33..13cabda 100644
--- a/compiler/optimizing/intrinsics_utils.h
+++ b/compiler/optimizing/intrinsics_utils.h
@@ -29,7 +29,7 @@
 #include "utils/assembler.h"
 #include "utils/label.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Default slow-path for fallback (calling the managed code to handle the intrinsic) in an
 // intrinsified call. This will copy the arguments into the positions for a regular call.
diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc
index 7d90aae..d207220 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -38,7 +38,7 @@
 #include "utils/x86/assembler_x86.h"
 #include "utils/x86/constants_x86.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace x86 {
 
@@ -75,7 +75,7 @@
  public:
   explicit ReadBarrierSystemArrayCopySlowPathX86(HInstruction* instruction)
       : SlowPathCode(instruction) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     DCHECK(kUseBakerReadBarrier);
   }
 
@@ -1699,7 +1699,7 @@
 
     case DataType::Type::kReference: {
       Register output = output_loc.AsRegister<Register>();
-      if (kEmitCompilerReadBarrier) {
+      if (gUseReadBarrier) {
         if (kUseBakerReadBarrier) {
           Address src(base, offset, ScaleFactor::TIMES_1, 0);
           codegen->GenerateReferenceLoadWithBakerReadBarrier(
@@ -1757,7 +1757,7 @@
                                           HInvoke* invoke,
                                           DataType::Type type,
                                           bool is_volatile) {
-  bool can_call = kEmitCompilerReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
+  bool can_call = gUseReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
   LocationSummary* locations =
       new (allocator) LocationSummary(invoke,
                                       can_call
@@ -2103,7 +2103,7 @@
 static void CreateIntIntIntIntIntToInt(ArenaAllocator* allocator,
                                        DataType::Type type,
                                        HInvoke* invoke) {
-  const bool can_call = kEmitCompilerReadBarrier &&
+  const bool can_call = gUseReadBarrier &&
                         kUseBakerReadBarrier &&
                         IsUnsafeCASObject(invoke);
   LocationSummary* locations =
@@ -2175,7 +2175,7 @@
 
 void IntrinsicLocationsBuilderX86::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
   // The only supported read barrier implementation is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -2304,7 +2304,7 @@
   DCHECK_EQ(expected, EAX);
   DCHECK_NE(temp, temp2);
 
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     // Need to make sure the reference stored in the field is a to-space
     // one before attempting the CAS or the CAS could fail incorrectly.
     codegen->GenerateReferenceLoadWithBakerReadBarrier(
@@ -2391,7 +2391,7 @@
   if (type == DataType::Type::kReference) {
     // The only read barrier implementation supporting the
     // UnsafeCASObject intrinsic is the Baker-style read barriers.
-    DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+    DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
     Register temp = locations->GetTemp(0).AsRegister<Register>();
     Register temp2 = locations->GetTemp(1).AsRegister<Register>();
@@ -2413,7 +2413,7 @@
 void IntrinsicCodeGeneratorX86::VisitUnsafeCASObject(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // UnsafeCASObject intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   GenCAS(DataType::Type::kReference, invoke, codegen_);
 }
@@ -2443,7 +2443,7 @@
 
 void IntrinsicCodeGeneratorX86::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
   // The only supported read barrier implementation is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   GenCAS(DataType::Type::kReference, invoke, codegen_);
 }
@@ -2843,7 +2843,7 @@
 void IntrinsicLocationsBuilderX86::VisitSystemArrayCopy(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // SystemArrayCopy intrinsic is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -2875,7 +2875,7 @@
 void IntrinsicCodeGeneratorX86::VisitSystemArrayCopy(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // SystemArrayCopy intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86Assembler* assembler = GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -2995,7 +2995,7 @@
     // slow path.
 
     if (!optimizations.GetSourceIsNonPrimitiveArray()) {
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         // /* HeapReference<Class> */ temp1 = src->klass_
         codegen_->GenerateFieldLoadWithBakerReadBarrier(
             invoke, temp1_loc, src, class_offset, /* needs_null_check= */ false);
@@ -3022,7 +3022,7 @@
       __ j(kNotEqual, intrinsic_slow_path->GetEntryLabel());
     }
 
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       if (length.Equals(Location::RegisterLocation(temp3))) {
         // When Baker read barriers are enabled, register `temp3`,
         // which in the present case contains the `length` parameter,
@@ -3120,7 +3120,7 @@
   } else if (!optimizations.GetSourceIsNonPrimitiveArray()) {
     DCHECK(optimizations.GetDestinationIsNonPrimitiveArray());
     // Bail out if the source is not a non primitive array.
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       // /* HeapReference<Class> */ temp1 = src->klass_
       codegen_->GenerateFieldLoadWithBakerReadBarrier(
           invoke, temp1_loc, src, class_offset, /* needs_null_check= */ false);
@@ -3151,7 +3151,7 @@
   // Compute the base source address in `temp1`.
   GenSystemArrayCopyBaseAddress(GetAssembler(), type, src, src_pos, temp1);
 
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     // If it is needed (in the case of the fast-path loop), the base
     // destination address is computed later, as `temp2` is used for
     // intermediate computations.
@@ -3259,7 +3259,7 @@
   }
 
   // We only need one card marking on the destination array.
-  codegen_->MarkGCCard(temp1, temp2, dest, Register(kNoRegister), /* value_can_be_null= */ false);
+  codegen_->MarkGCCard(temp1, temp2, dest, Register(kNoRegister), /* emit_null_check= */ false);
 
   __ Bind(intrinsic_slow_path->GetExitLabel());
 }
@@ -3377,7 +3377,7 @@
   SlowPathCode* slow_path = new (GetAllocator()) IntrinsicSlowPathX86(invoke);
   codegen_->AddSlowPath(slow_path);
 
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     // Check self->GetWeakRefAccessEnabled().
     ThreadOffset32 offset = Thread::WeakRefAccessEnabledOffset<kX86PointerSize>();
     __ fs()->cmpl(Address::Absolute(offset),
@@ -3400,7 +3400,7 @@
 
   // Load the value from the field.
   uint32_t referent_offset = mirror::Reference::ReferentOffset().Uint32Value();
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
                                                     out,
                                                     obj.AsRegister<Register>(),
@@ -3442,7 +3442,7 @@
   NearLabel end, return_true, return_false;
   __ cmpl(out, other);
 
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     DCHECK(kUseBakerReadBarrier);
 
     __ j(kEqual, &return_true);
@@ -3781,7 +3781,7 @@
                                            Location::RegisterLocation(temp),
                                            Address(temp, declaring_class_offset),
                                            /* fixup_label= */ nullptr,
-                                           kCompilerReadBarrierOption);
+                                           gCompilerReadBarrierOption);
     return temp;
   }
 
@@ -3794,7 +3794,7 @@
 static void CreateVarHandleGetLocations(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -3836,7 +3836,7 @@
 static void GenerateVarHandleGet(HInvoke* invoke, CodeGeneratorX86* codegen) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86Assembler* assembler = codegen->GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -3860,7 +3860,7 @@
   Address field_addr(ref, offset, TIMES_1, 0);
 
   // Load the value from the field
-  if (type == DataType::Type::kReference && kCompilerReadBarrierOption == kWithReadBarrier) {
+  if (type == DataType::Type::kReference && gCompilerReadBarrierOption == kWithReadBarrier) {
     codegen->GenerateReferenceLoadWithBakerReadBarrier(
         invoke, out, ref, field_addr, /* needs_null_check= */ false);
   } else if (type == DataType::Type::kInt64 &&
@@ -3917,7 +3917,7 @@
 static void CreateVarHandleSetLocations(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -3963,7 +3963,7 @@
     case DataType::Type::kInt64:
       // We only handle constant non-atomic int64 values.
       DCHECK(value->IsConstant());
-      locations->SetInAt(value_index, Location::ConstantLocation(value->AsConstant()));
+      locations->SetInAt(value_index, Location::ConstantLocation(value));
       break;
     case DataType::Type::kReference:
       locations->SetInAt(value_index, Location::RequiresRegister());
@@ -3990,7 +3990,7 @@
 static void GenerateVarHandleSet(HInvoke* invoke, CodeGeneratorX86* codegen) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86Assembler* assembler = codegen->GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -4041,13 +4041,16 @@
   InstructionCodeGeneratorX86* instr_codegen =
         down_cast<InstructionCodeGeneratorX86*>(codegen->GetInstructionVisitor());
   // Store the value to the field
-  instr_codegen->HandleFieldSet(invoke,
-                                value_index,
-                                value_type,
-                                Address(reference, offset, TIMES_1, 0),
-                                reference,
-                                is_volatile,
-                                /* value_can_be_null */ true);
+  instr_codegen->HandleFieldSet(
+      invoke,
+      value_index,
+      value_type,
+      Address(reference, offset, TIMES_1, 0),
+      reference,
+      is_volatile,
+      /* value_can_be_null */ true,
+      // Value can be null, and this write barrier is not being relied on for other sets.
+      WriteBarrierKind::kEmitWithNullCheck);
 
   __ Bind(slow_path->GetExitLabel());
 }
@@ -4087,7 +4090,7 @@
 static void CreateVarHandleGetAndSetLocations(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -4135,7 +4138,7 @@
 static void GenerateVarHandleGetAndSet(HInvoke* invoke, CodeGeneratorX86* codegen) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86Assembler* assembler = codegen->GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -4194,7 +4197,7 @@
       __ movd(locations->Out().AsFpuRegister<XmmRegister>(), EAX);
       break;
     case DataType::Type::kReference: {
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         // Need to make sure the reference stored in the field is a to-space
         // one before attempting the CAS or the CAS could fail incorrectly.
         codegen->GenerateReferenceLoadWithBakerReadBarrier(
@@ -4208,7 +4211,7 @@
             &temp2);
       }
       codegen->MarkGCCard(
-          temp, temp2, reference, value.AsRegister<Register>(), /* value_can_be_null= */ false);
+          temp, temp2, reference, value.AsRegister<Register>(), /* emit_null_check= */ false);
       if (kPoisonHeapReferences) {
         __ movl(temp, value.AsRegister<Register>());
         __ PoisonHeapReference(temp);
@@ -4258,7 +4261,7 @@
 static void CreateVarHandleCompareAndSetOrExchangeLocations(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -4322,7 +4325,7 @@
 static void GenerateVarHandleCompareAndSetOrExchange(HInvoke* invoke, CodeGeneratorX86* codegen) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86Assembler* assembler = codegen->GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -4441,7 +4444,7 @@
 static void CreateVarHandleGetAndAddLocations(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -4490,7 +4493,7 @@
 static void GenerateVarHandleGetAndAdd(HInvoke* invoke, CodeGeneratorX86* codegen) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86Assembler* assembler = codegen->GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -4591,7 +4594,7 @@
 static void CreateVarHandleGetAndBitwiseOpLocations(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -4659,7 +4662,7 @@
 static void GenerateVarHandleGetAndBitwiseOp(HInvoke* invoke, CodeGeneratorX86* codegen) {
   // The only read barrier implementation supporting the
   // VarHandleGet intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86Assembler* assembler = codegen->GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -4829,64 +4832,9 @@
   }
 }
 
-UNIMPLEMENTED_INTRINSIC(X86, MathRoundDouble)
-UNIMPLEMENTED_INTRINSIC(X86, FloatIsInfinite)
-UNIMPLEMENTED_INTRINSIC(X86, DoubleIsInfinite)
-UNIMPLEMENTED_INTRINSIC(X86, IntegerHighestOneBit)
-UNIMPLEMENTED_INTRINSIC(X86, LongHighestOneBit)
-UNIMPLEMENTED_INTRINSIC(X86, LongDivideUnsigned)
-UNIMPLEMENTED_INTRINSIC(X86, CRC32Update)
-UNIMPLEMENTED_INTRINSIC(X86, CRC32UpdateBytes)
-UNIMPLEMENTED_INTRINSIC(X86, CRC32UpdateByteBuffer)
-UNIMPLEMENTED_INTRINSIC(X86, FP16ToFloat)
-UNIMPLEMENTED_INTRINSIC(X86, FP16ToHalf)
-UNIMPLEMENTED_INTRINSIC(X86, FP16Floor)
-UNIMPLEMENTED_INTRINSIC(X86, FP16Ceil)
-UNIMPLEMENTED_INTRINSIC(X86, FP16Rint)
-UNIMPLEMENTED_INTRINSIC(X86, FP16Greater)
-UNIMPLEMENTED_INTRINSIC(X86, FP16GreaterEquals)
-UNIMPLEMENTED_INTRINSIC(X86, FP16Less)
-UNIMPLEMENTED_INTRINSIC(X86, FP16LessEquals)
-UNIMPLEMENTED_INTRINSIC(X86, FP16Compare)
-UNIMPLEMENTED_INTRINSIC(X86, FP16Min)
-UNIMPLEMENTED_INTRINSIC(X86, FP16Max)
-UNIMPLEMENTED_INTRINSIC(X86, MathMultiplyHigh)
-
-UNIMPLEMENTED_INTRINSIC(X86, StringStringIndexOf);
-UNIMPLEMENTED_INTRINSIC(X86, StringStringIndexOfAfter);
-UNIMPLEMENTED_INTRINSIC(X86, StringBufferAppend);
-UNIMPLEMENTED_INTRINSIC(X86, StringBufferLength);
-UNIMPLEMENTED_INTRINSIC(X86, StringBufferToString);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderAppendObject);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderAppendString);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderAppendCharSequence);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderAppendCharArray);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderAppendBoolean);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderAppendChar);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderAppendInt);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderAppendLong);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderAppendFloat);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderAppendDouble);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderLength);
-UNIMPLEMENTED_INTRINSIC(X86, StringBuilderToString);
-
-// 1.8.
-
-UNIMPLEMENTED_INTRINSIC(X86, UnsafeGetAndAddInt)
-UNIMPLEMENTED_INTRINSIC(X86, UnsafeGetAndAddLong)
-UNIMPLEMENTED_INTRINSIC(X86, UnsafeGetAndSetInt)
-UNIMPLEMENTED_INTRINSIC(X86, UnsafeGetAndSetLong)
-UNIMPLEMENTED_INTRINSIC(X86, UnsafeGetAndSetObject)
-
-UNIMPLEMENTED_INTRINSIC(X86, MethodHandleInvokeExact)
-UNIMPLEMENTED_INTRINSIC(X86, MethodHandleInvoke)
-
-// OpenJDK 11
-UNIMPLEMENTED_INTRINSIC(X86, JdkUnsafeGetAndAddInt)
-UNIMPLEMENTED_INTRINSIC(X86, JdkUnsafeGetAndAddLong)
-UNIMPLEMENTED_INTRINSIC(X86, JdkUnsafeGetAndSetInt)
-UNIMPLEMENTED_INTRINSIC(X86, JdkUnsafeGetAndSetLong)
-UNIMPLEMENTED_INTRINSIC(X86, JdkUnsafeGetAndSetObject)
+#define MARK_UNIMPLEMENTED(Name) UNIMPLEMENTED_INTRINSIC(X86, Name)
+UNIMPLEMENTED_INTRINSIC_LIST_X86(MARK_UNIMPLEMENTED);
+#undef MARK_UNIMPLEMENTED
 
 UNREACHABLE_INTRINSICS(X86)
 
diff --git a/compiler/optimizing/intrinsics_x86.h b/compiler/optimizing/intrinsics_x86.h
index ae150da..77c236d 100644
--- a/compiler/optimizing/intrinsics_x86.h
+++ b/compiler/optimizing/intrinsics_x86.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_INTRINSICS_X86_H_
 #define ART_COMPILER_OPTIMIZING_INTRINSICS_X86_H_
 
+#include "base/macros.h"
 #include "intrinsics.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArenaAllocator;
 class HInvokeStaticOrDirect;
diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc
index 3c31374..9d0d5f1 100644
--- a/compiler/optimizing/intrinsics_x86_64.cc
+++ b/compiler/optimizing/intrinsics_x86_64.cc
@@ -36,7 +36,7 @@
 #include "utils/x86_64/assembler_x86_64.h"
 #include "utils/x86_64/constants_x86_64.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace x86_64 {
 
@@ -71,7 +71,7 @@
  public:
   explicit ReadBarrierSystemArrayCopySlowPathX86_64(HInstruction* instruction)
       : SlowPathCode(instruction) {
-    DCHECK(kEmitCompilerReadBarrier);
+    DCHECK(gUseReadBarrier);
     DCHECK(kUseBakerReadBarrier);
   }
 
@@ -836,7 +836,7 @@
 void IntrinsicLocationsBuilderX86_64::VisitSystemArrayCopy(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // SystemArrayCopy intrinsic is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -887,7 +887,7 @@
 void IntrinsicCodeGeneratorX86_64::VisitSystemArrayCopy(HInvoke* invoke) {
   // The only read barrier implementation supporting the
   // SystemArrayCopy intrinsic is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86_64Assembler* assembler = GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -1002,7 +1002,7 @@
     // slow path.
 
     bool did_unpoison = false;
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       // /* HeapReference<Class> */ temp1 = dest->klass_
       codegen_->GenerateFieldLoadWithBakerReadBarrier(
           invoke, temp1_loc, dest, class_offset, /* needs_null_check= */ false);
@@ -1034,7 +1034,7 @@
 
     if (!optimizations.GetDestinationIsNonPrimitiveArray()) {
       // Bail out if the destination is not a non primitive array.
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         // /* HeapReference<Class> */ TMP = temp1->component_type_
         codegen_->GenerateFieldLoadWithBakerReadBarrier(
             invoke, TMP_loc, temp1, component_offset, /* needs_null_check= */ false);
@@ -1055,7 +1055,7 @@
 
     if (!optimizations.GetSourceIsNonPrimitiveArray()) {
       // Bail out if the source is not a non primitive array.
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         // For the same reason given earlier, `temp1` is not trashed by the
         // read barrier emitted by GenerateFieldLoadWithBakerReadBarrier below.
         // /* HeapReference<Class> */ TMP = temp2->component_type_
@@ -1081,7 +1081,7 @@
     if (optimizations.GetDestinationIsTypedObjectArray()) {
       NearLabel do_copy;
       __ j(kEqual, &do_copy);
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         // /* HeapReference<Class> */ temp1 = temp1->component_type_
         codegen_->GenerateFieldLoadWithBakerReadBarrier(
             invoke, temp1_loc, temp1, component_offset, /* needs_null_check= */ false);
@@ -1109,7 +1109,7 @@
   } else if (!optimizations.GetSourceIsNonPrimitiveArray()) {
     DCHECK(optimizations.GetDestinationIsNonPrimitiveArray());
     // Bail out if the source is not a non primitive array.
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       // /* HeapReference<Class> */ temp1 = src->klass_
       codegen_->GenerateFieldLoadWithBakerReadBarrier(
           invoke, temp1_loc, src, class_offset, /* needs_null_check= */ false);
@@ -1141,7 +1141,7 @@
   GenSystemArrayCopyAddresses(
       GetAssembler(), type, src, src_pos, dest, dest_pos, length, temp1, temp2, temp3);
 
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     // SystemArrayCopy implementation for Baker read barriers (see
     // also CodeGeneratorX86_64::GenerateReferenceLoadWithBakerReadBarrier):
     //
@@ -1224,7 +1224,7 @@
   }
 
   // We only need one card marking on the destination array.
-  codegen_->MarkGCCard(temp1, temp2, dest, CpuRegister(kNoRegister), /* value_can_be_null= */ false);
+  codegen_->MarkGCCard(temp1, temp2, dest, CpuRegister(kNoRegister), /* emit_null_check= */ false);
 
   __ Bind(intrinsic_slow_path->GetExitLabel());
 }
@@ -1888,7 +1888,7 @@
       break;
 
     case DataType::Type::kReference: {
-      if (kEmitCompilerReadBarrier) {
+      if (gUseReadBarrier) {
         if (kUseBakerReadBarrier) {
           Address src(base, offset, ScaleFactor::TIMES_1, 0);
           codegen->GenerateReferenceLoadWithBakerReadBarrier(
@@ -1930,7 +1930,7 @@
 }
 
 static void CreateIntIntIntToIntLocations(ArenaAllocator* allocator, HInvoke* invoke) {
-  bool can_call = kEmitCompilerReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
+  bool can_call = gUseReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
   LocationSummary* locations =
       new (allocator) LocationSummary(invoke,
                                       can_call
@@ -2230,7 +2230,7 @@
 static void CreateUnsafeCASLocations(ArenaAllocator* allocator,
                                      DataType::Type type,
                                      HInvoke* invoke) {
-  const bool can_call = kEmitCompilerReadBarrier &&
+  const bool can_call = gUseReadBarrier &&
                         kUseBakerReadBarrier &&
                         IsUnsafeCASObject(invoke);
   LocationSummary* locations =
@@ -2253,7 +2253,7 @@
     // Need two temporaries for MarkGCCard.
     locations->AddTemp(Location::RequiresRegister());  // Possibly used for reference poisoning too.
     locations->AddTemp(Location::RequiresRegister());
-    if (kEmitCompilerReadBarrier) {
+    if (gUseReadBarrier) {
       // Need three temporaries for GenerateReferenceLoadWithBakerReadBarrier.
       DCHECK(kUseBakerReadBarrier);
       locations->AddTemp(Location::RequiresRegister());
@@ -2298,7 +2298,7 @@
 
 void IntrinsicLocationsBuilderX86_64::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
   // The only supported read barrier implementation is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return;
   }
 
@@ -2438,7 +2438,7 @@
                                           CpuRegister temp3,
                                           bool is_cmpxchg) {
   // The only supported read barrier implementation is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86_64Assembler* assembler = down_cast<X86_64Assembler*>(codegen->GetAssembler());
 
@@ -2447,7 +2447,7 @@
   codegen->MarkGCCard(temp1, temp2, base, value, value_can_be_null);
 
   Address field_addr(base, offset, TIMES_1, 0);
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     // Need to make sure the reference stored in the field is a to-space
     // one before attempting the CAS or the CAS could fail incorrectly.
     codegen->GenerateReferenceLoadWithBakerReadBarrier(
@@ -2556,7 +2556,7 @@
       CpuRegister new_value_reg = new_value.AsRegister<CpuRegister>();
       CpuRegister temp1 = locations->GetTemp(temp1_index).AsRegister<CpuRegister>();
       CpuRegister temp2 = locations->GetTemp(temp2_index).AsRegister<CpuRegister>();
-      CpuRegister temp3 = kEmitCompilerReadBarrier
+      CpuRegister temp3 = gUseReadBarrier
           ? locations->GetTemp(temp3_index).AsRegister<CpuRegister>()
           : CpuRegister(kNoRegister);
       DCHECK(RegsAreAllDifferent({base, offset, temp1, temp2, temp3}));
@@ -2624,7 +2624,7 @@
 
 void IntrinsicCodeGeneratorX86_64::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
   // The only supported read barrier implementation is the Baker-style read barriers.
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   GenCAS(DataType::Type::kReference, invoke, codegen_);
 }
@@ -3128,7 +3128,7 @@
   SlowPathCode* slow_path = new (GetAllocator()) IntrinsicSlowPathX86_64(invoke);
   codegen_->AddSlowPath(slow_path);
 
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     // Check self->GetWeakRefAccessEnabled().
     ThreadOffset64 offset = Thread::WeakRefAccessEnabledOffset<kX86_64PointerSize>();
     __ gs()->cmpl(Address::Absolute(offset, /* no_rip= */ true),
@@ -3150,7 +3150,7 @@
 
   // Load the value from the field.
   uint32_t referent_offset = mirror::Reference::ReferentOffset().Uint32Value();
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
                                                     out,
                                                     obj.AsRegister<CpuRegister>(),
@@ -3191,7 +3191,7 @@
 
   __ cmpl(out, other);
 
-  if (kEmitCompilerReadBarrier) {
+  if (gUseReadBarrier) {
     DCHECK(kUseBakerReadBarrier);
 
     NearLabel calculate_result;
@@ -3771,7 +3771,7 @@
                                                Location::RegisterLocation(target.object),
                                                Address(method, ArtField::DeclaringClassOffset()),
                                                /*fixup_label=*/ nullptr,
-                                               kCompilerReadBarrierOption);
+                                               gCompilerReadBarrierOption);
       }
     }
   } else {
@@ -3790,7 +3790,7 @@
 
 static bool HasVarHandleIntrinsicImplementation(HInvoke* invoke) {
   // The only supported read barrier implementation is the Baker-style read barriers.
-  if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     return false;
   }
 
@@ -3876,7 +3876,7 @@
   Location out = locations->Out();
 
   if (type == DataType::Type::kReference) {
-    if (kEmitCompilerReadBarrier) {
+    if (gUseReadBarrier) {
       DCHECK(kUseBakerReadBarrier);
       codegen->GenerateReferenceLoadWithBakerReadBarrier(
           invoke, out, CpuRegister(target.object), src, /* needs_null_check= */ false);
@@ -3985,16 +3985,19 @@
   Address dst(CpuRegister(target.object), CpuRegister(target.offset), TIMES_1, 0);
 
   // Store the value to the field.
-  codegen->GetInstructionCodegen()->HandleFieldSet(invoke,
-                                                   value_index,
-                                                   last_temp_index,
-                                                   value_type,
-                                                   dst,
-                                                   CpuRegister(target.object),
-                                                   is_volatile,
-                                                   is_atomic,
-                                                   /*value_can_be_null=*/ true,
-                                                   byte_swap);
+  codegen->GetInstructionCodegen()->HandleFieldSet(
+      invoke,
+      value_index,
+      last_temp_index,
+      value_type,
+      dst,
+      CpuRegister(target.object),
+      is_volatile,
+      is_atomic,
+      /*value_can_be_null=*/true,
+      byte_swap,
+      // Value can be null, and this write barrier is not being relied on for other sets.
+      WriteBarrierKind::kEmitWithNullCheck);
 
   // setVolatile needs kAnyAny barrier, but HandleFieldSet takes care of that.
 
@@ -4070,7 +4073,7 @@
       // Need two temporaries for MarkGCCard.
       locations->AddTemp(Location::RequiresRegister());
       locations->AddTemp(Location::RequiresRegister());
-      if (kEmitCompilerReadBarrier) {
+      if (gUseReadBarrier) {
         // Need three temporaries for GenerateReferenceLoadWithBakerReadBarrier.
         DCHECK(kUseBakerReadBarrier);
         locations->AddTemp(Location::RequiresRegister());
@@ -4085,7 +4088,7 @@
                                                      CodeGeneratorX86_64* codegen,
                                                      bool is_cmpxchg,
                                                      bool byte_swap = false) {
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86_64Assembler* assembler = codegen->GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -4218,7 +4221,7 @@
       // Need two temporaries for MarkGCCard.
       locations->AddTemp(Location::RequiresRegister());
       locations->AddTemp(Location::RequiresRegister());
-      if (kEmitCompilerReadBarrier) {
+      if (gUseReadBarrier) {
         // Need a third temporary for GenerateReferenceLoadWithBakerReadBarrier.
         DCHECK(kUseBakerReadBarrier);
         locations->AddTemp(Location::RequiresRegister());
@@ -4267,7 +4270,7 @@
     CpuRegister temp2 = locations->GetTemp(temp_count - 2).AsRegister<CpuRegister>();
     CpuRegister valreg = value.AsRegister<CpuRegister>();
 
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       codegen->GenerateReferenceLoadWithBakerReadBarrier(
           invoke,
           locations->GetTemp(temp_count - 3),
@@ -4278,7 +4281,7 @@
           &temp1,
           &temp2);
     }
-    codegen->MarkGCCard(temp1, temp2, ref, valreg, /*value_can_be_null=*/ false);
+    codegen->MarkGCCard(temp1, temp2, ref, valreg, /* emit_null_check= */ false);
 
     DCHECK_EQ(valreg, out.AsRegister<CpuRegister>());
     if (kPoisonHeapReferences) {
@@ -4647,7 +4650,7 @@
                                           bool need_any_store_barrier,
                                           bool need_any_any_barrier,
                                           bool byte_swap = false) {
-  DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+  DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
 
   X86_64Assembler* assembler = codegen->GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
@@ -4987,57 +4990,9 @@
   __ jmp(GetExitLabel());
 }
 
-UNIMPLEMENTED_INTRINSIC(X86_64, CRC32Update)
-UNIMPLEMENTED_INTRINSIC(X86_64, CRC32UpdateBytes)
-UNIMPLEMENTED_INTRINSIC(X86_64, CRC32UpdateByteBuffer)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16ToFloat)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16ToHalf)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16Floor)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16Ceil)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16Rint)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16Greater)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16GreaterEquals)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16Less)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16LessEquals)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16Compare)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16Min)
-UNIMPLEMENTED_INTRINSIC(X86_64, FP16Max)
-
-UNIMPLEMENTED_INTRINSIC(X86_64, StringStringIndexOf);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringStringIndexOfAfter);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBufferAppend);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBufferLength);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBufferToString);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderAppendObject);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderAppendString);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderAppendCharSequence);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderAppendCharArray);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderAppendBoolean);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderAppendChar);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderAppendInt);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderAppendLong);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderAppendFloat);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderAppendDouble);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderLength);
-UNIMPLEMENTED_INTRINSIC(X86_64, StringBuilderToString);
-
-// 1.8.
-
-UNIMPLEMENTED_INTRINSIC(X86_64, UnsafeGetAndAddInt)
-UNIMPLEMENTED_INTRINSIC(X86_64, UnsafeGetAndAddLong)
-UNIMPLEMENTED_INTRINSIC(X86_64, UnsafeGetAndSetInt)
-UNIMPLEMENTED_INTRINSIC(X86_64, UnsafeGetAndSetLong)
-UNIMPLEMENTED_INTRINSIC(X86_64, UnsafeGetAndSetObject)
-
-UNIMPLEMENTED_INTRINSIC(X86_64, MethodHandleInvokeExact)
-UNIMPLEMENTED_INTRINSIC(X86_64, MethodHandleInvoke)
-
-// OpenJDK 11
-UNIMPLEMENTED_INTRINSIC(X86_64, JdkUnsafeGetAndAddInt)
-UNIMPLEMENTED_INTRINSIC(X86_64, JdkUnsafeGetAndAddLong)
-UNIMPLEMENTED_INTRINSIC(X86_64, JdkUnsafeGetAndSetInt)
-UNIMPLEMENTED_INTRINSIC(X86_64, JdkUnsafeGetAndSetLong)
-UNIMPLEMENTED_INTRINSIC(X86_64, JdkUnsafeGetAndSetObject)
+#define MARK_UNIMPLEMENTED(Name) UNIMPLEMENTED_INTRINSIC(X86_64, Name)
+UNIMPLEMENTED_INTRINSIC_LIST_X86_64(MARK_UNIMPLEMENTED);
+#undef MARK_UNIMPLEMENTED
 
 UNREACHABLE_INTRINSICS(X86_64)
 
diff --git a/compiler/optimizing/intrinsics_x86_64.h b/compiler/optimizing/intrinsics_x86_64.h
index 199cfed..59fe815 100644
--- a/compiler/optimizing/intrinsics_x86_64.h
+++ b/compiler/optimizing/intrinsics_x86_64.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_INTRINSICS_X86_64_H_
 #define ART_COMPILER_OPTIMIZING_INTRINSICS_X86_64_H_
 
+#include "base/macros.h"
 #include "intrinsics.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArenaAllocator;
 class HInvokeStaticOrDirect;
diff --git a/compiler/optimizing/licm.cc b/compiler/optimizing/licm.cc
index 0edb23b..0c791b6 100644
--- a/compiler/optimizing/licm.cc
+++ b/compiler/optimizing/licm.cc
@@ -18,7 +18,7 @@
 
 #include "side_effects_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static bool IsPhiOf(HInstruction* instruction, HBasicBlock* block) {
   return instruction->IsPhi() && instruction->GetBlock() == block;
diff --git a/compiler/optimizing/licm.h b/compiler/optimizing/licm.h
index 9cafddb..1a86b6e 100644
--- a/compiler/optimizing/licm.h
+++ b/compiler/optimizing/licm.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_LICM_H_
 #define ART_COMPILER_OPTIMIZING_LICM_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class SideEffectsAnalysis;
 
diff --git a/compiler/optimizing/licm_test.cc b/compiler/optimizing/licm_test.cc
index adc3cab..f848109 100644
--- a/compiler/optimizing/licm_test.cc
+++ b/compiler/optimizing/licm_test.cc
@@ -17,12 +17,13 @@
 #include "licm.h"
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "nodes.h"
 #include "optimizing_unit_test.h"
 #include "side_effects_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Fixture class for the LICM tests.
diff --git a/compiler/optimizing/linear_order.cc b/compiler/optimizing/linear_order.cc
index 58e00a8..25ca866 100644
--- a/compiler/optimizing/linear_order.cc
+++ b/compiler/optimizing/linear_order.cc
@@ -19,7 +19,7 @@
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static bool InSameLoop(HLoopInformation* first_loop, HLoopInformation* second_loop) {
   return first_loop == second_loop;
diff --git a/compiler/optimizing/linear_order.h b/compiler/optimizing/linear_order.h
index 151db00..75e7504 100644
--- a/compiler/optimizing/linear_order.h
+++ b/compiler/optimizing/linear_order.h
@@ -19,9 +19,10 @@
 
 #include <type_traits>
 
+#include "base/macros.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 void LinearizeGraphInternal(const HGraph* graph, ArrayRef<HBasicBlock*> linear_order);
 
diff --git a/compiler/optimizing/linearize_test.cc b/compiler/optimizing/linearize_test.cc
index d56ae11..01daa23 100644
--- a/compiler/optimizing/linearize_test.cc
+++ b/compiler/optimizing/linearize_test.cc
@@ -17,6 +17,7 @@
 #include <fstream>
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "code_generator.h"
 #include "dex/dex_file.h"
@@ -28,9 +29,9 @@
 #include "pretty_printer.h"
 #include "ssa_liveness_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
-class LinearizeTest : public OptimizingUnitTest {
+class LinearizeTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   template <size_t number_of_blocks>
   void TestCode(const std::vector<uint16_t>& data,
diff --git a/compiler/optimizing/live_interval_test.cc b/compiler/optimizing/live_interval_test.cc
index c60386d..b5d1336 100644
--- a/compiler/optimizing/live_interval_test.cc
+++ b/compiler/optimizing/live_interval_test.cc
@@ -15,12 +15,13 @@
  */
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "optimizing_unit_test.h"
 #include "ssa_liveness_analysis.h"
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
 TEST(LiveInterval, GetStart) {
   ArenaPoolAndAllocator pool;
diff --git a/compiler/optimizing/live_ranges_test.cc b/compiler/optimizing/live_ranges_test.cc
index bb8a4dc..fb1a23e 100644
--- a/compiler/optimizing/live_ranges_test.cc
+++ b/compiler/optimizing/live_ranges_test.cc
@@ -15,6 +15,7 @@
  */
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "code_generator.h"
 #include "dex/dex_file.h"
@@ -25,9 +26,9 @@
 #include "prepare_for_register_allocation.h"
 #include "ssa_liveness_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
-class LiveRangesTest : public OptimizingUnitTest {
+class LiveRangesTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   HGraph* BuildGraph(const std::vector<uint16_t>& data);
 
diff --git a/compiler/optimizing/liveness_test.cc b/compiler/optimizing/liveness_test.cc
index ba3787e..0b421cf 100644
--- a/compiler/optimizing/liveness_test.cc
+++ b/compiler/optimizing/liveness_test.cc
@@ -15,6 +15,7 @@
  */
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "code_generator.h"
 #include "dex/dex_file.h"
@@ -25,9 +26,9 @@
 #include "prepare_for_register_allocation.h"
 #include "ssa_liveness_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
-class LivenessTest : public OptimizingUnitTest {
+class LivenessTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   void TestCode(const std::vector<uint16_t>& data, const char* expected);
 };
diff --git a/compiler/optimizing/load_store_analysis.cc b/compiler/optimizing/load_store_analysis.cc
index 3fe42af..f1c50ac 100644
--- a/compiler/optimizing/load_store_analysis.cc
+++ b/compiler/optimizing/load_store_analysis.cc
@@ -19,7 +19,7 @@
 #include "base/scoped_arena_allocator.h"
 #include "optimizing/escape.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // A cap for the number of heap locations to prevent pathological time/space consumption.
 // The number of heap locations for most of the methods stays below this threshold.
@@ -283,14 +283,6 @@
     heap_location_collector_.CleanUp();
     return false;
   }
-  if (heap_location_collector_.HasVolatile() || heap_location_collector_.HasMonitorOps()) {
-    // Don't do load/store elimination if the method has volatile field accesses or
-    // monitor operations, for now.
-    // TODO: do it right.
-    heap_location_collector_.CleanUp();
-    return false;
-  }
-
   heap_location_collector_.BuildAliasingMatrix();
   heap_location_collector_.DumpReferenceStats(stats_);
   return true;
diff --git a/compiler/optimizing/load_store_analysis.h b/compiler/optimizing/load_store_analysis.h
index 4975bae..c46a5b9 100644
--- a/compiler/optimizing/load_store_analysis.h
+++ b/compiler/optimizing/load_store_analysis.h
@@ -20,6 +20,7 @@
 #include "base/arena_allocator.h"
 #include "base/arena_bit_vector.h"
 #include "base/bit_vector-inl.h"
+#include "base/macros.h"
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 #include "base/stl_util.h"
@@ -28,7 +29,7 @@
 #include "nodes.h"
 #include "optimizing/optimizing_compiler_stats.h"
 
-namespace art {
+namespace art HIDDEN {
 
 enum class LoadStoreAnalysisType {
   kBasic,
@@ -170,14 +171,16 @@
                size_t offset,
                HInstruction* index,
                size_t vector_length,
-               int16_t declaring_class_def_index)
+               int16_t declaring_class_def_index,
+               bool is_vec_op)
       : ref_info_(ref_info),
         type_(DataType::ToSigned(type)),
         offset_(offset),
         index_(index),
         vector_length_(vector_length),
         declaring_class_def_index_(declaring_class_def_index),
-        has_aliased_locations_(false) {
+        has_aliased_locations_(false),
+        is_vec_op_(is_vec_op) {
     DCHECK(ref_info != nullptr);
     DCHECK((offset == kInvalidFieldOffset && index != nullptr) ||
            (offset != kInvalidFieldOffset && index == nullptr));
@@ -188,6 +191,7 @@
   size_t GetOffset() const { return offset_; }
   HInstruction* GetIndex() const { return index_; }
   size_t GetVectorLength() const { return vector_length_; }
+  bool IsVecOp() const { return is_vec_op_; }
 
   // Returns the definition of declaring class' dex index.
   // It's kDeclaringClassDefIndexForArrays for an array element.
@@ -226,11 +230,12 @@
   // Declaring class's def's dex index.
   // Invalid when this HeapLocation is not field access.
   const int16_t declaring_class_def_index_;
-
   // Has aliased heap locations in the method, due to either the
   // reference is aliased or the array element is aliased via different
   // index names.
   bool has_aliased_locations_;
+  // Whether this HeapLocation represents a vector operation.
+  bool is_vec_op_;
 
   DISALLOW_COPY_AND_ASSIGN(HeapLocation);
 };
@@ -253,8 +258,6 @@
         heap_locations_(allocator->Adapter(kArenaAllocLSA)),
         aliasing_matrix_(allocator, kInitialAliasingMatrixBitVectorSize, true, kArenaAllocLSA),
         has_heap_stores_(false),
-        has_volatile_(false),
-        has_monitor_operations_(false),
         lse_type_(lse_type) {
     aliasing_matrix_.ClearAllBits();
   }
@@ -319,7 +322,8 @@
                                  field->GetFieldOffset().SizeValue(),
                                  nullptr,
                                  HeapLocation::kScalar,
-                                 field->GetDeclaringClassDefIndex());
+                                 field->GetDeclaringClassDefIndex(),
+                                 /*is_vec_op=*/false);
   }
 
   size_t GetArrayHeapLocation(HInstruction* instruction) const {
@@ -328,10 +332,10 @@
     HInstruction* index = instruction->InputAt(1);
     DataType::Type type = instruction->GetType();
     size_t vector_length = HeapLocation::kScalar;
+    const bool is_vec_op = instruction->IsVecStore() || instruction->IsVecLoad();
     if (instruction->IsArraySet()) {
       type = instruction->AsArraySet()->GetComponentType();
-    } else if (instruction->IsVecStore() ||
-               instruction->IsVecLoad()) {
+    } else if (is_vec_op) {
       HVecOperation* vec_op = instruction->AsVecOperation();
       type = vec_op->GetPackedType();
       vector_length = vec_op->GetVectorLength();
@@ -343,21 +347,14 @@
                                  HeapLocation::kInvalidFieldOffset,
                                  index,
                                  vector_length,
-                                 HeapLocation::kDeclaringClassDefIndexForArrays);
+                                 HeapLocation::kDeclaringClassDefIndexForArrays,
+                                 is_vec_op);
   }
 
   bool HasHeapStores() const {
     return has_heap_stores_;
   }
 
-  bool HasVolatile() const {
-    return has_volatile_;
-  }
-
-  bool HasMonitorOps() const {
-    return has_monitor_operations_;
-  }
-
   // Find and return the heap location index in heap_locations_.
   // NOTE: When heap locations are created, potentially aliasing/overlapping
   // accesses are given different indexes. This find function also
@@ -373,7 +370,8 @@
                                size_t offset,
                                HInstruction* index,
                                size_t vector_length,
-                               int16_t declaring_class_def_index) const {
+                               int16_t declaring_class_def_index,
+                               bool is_vec_op) const {
     DataType::Type lookup_type = DataType::ToSigned(type);
     for (size_t i = 0; i < heap_locations_.size(); i++) {
       HeapLocation* loc = heap_locations_[i];
@@ -382,7 +380,8 @@
           loc->GetOffset() == offset &&
           loc->GetIndex() == index &&
           loc->GetVectorLength() == vector_length &&
-          loc->GetDeclaringClassDefIndex() == declaring_class_def_index) {
+          loc->GetDeclaringClassDefIndex() == declaring_class_def_index &&
+          loc->IsVecOp() == is_vec_op) {
         return i;
       }
     }
@@ -527,22 +526,20 @@
                                size_t offset,
                                HInstruction* index,
                                size_t vector_length,
-                               int16_t declaring_class_def_index) {
+                               int16_t declaring_class_def_index,
+                               bool is_vec_op) {
     HInstruction* original_ref = HuntForOriginalReference(ref);
     ReferenceInfo* ref_info = GetOrCreateReferenceInfo(original_ref);
     size_t heap_location_idx = FindHeapLocationIndex(
-        ref_info, type, offset, index, vector_length, declaring_class_def_index);
+        ref_info, type, offset, index, vector_length, declaring_class_def_index, is_vec_op);
     if (heap_location_idx == kHeapLocationNotFound) {
-      HeapLocation* heap_loc = new (allocator_)
-          HeapLocation(ref_info, type, offset, index, vector_length, declaring_class_def_index);
+      HeapLocation* heap_loc = new (allocator_) HeapLocation(
+          ref_info, type, offset, index, vector_length, declaring_class_def_index, is_vec_op);
       heap_locations_.push_back(heap_loc);
     }
   }
 
   void VisitFieldAccess(HInstruction* ref, const FieldInfo& field_info) {
-    if (field_info.IsVolatile()) {
-      has_volatile_ = true;
-    }
     DataType::Type type = field_info.GetFieldType();
     const uint16_t declaring_class_def_index = field_info.GetDeclaringClassDefIndex();
     const size_t offset = field_info.GetFieldOffset().SizeValue();
@@ -551,19 +548,22 @@
                             offset,
                             nullptr,
                             HeapLocation::kScalar,
-                            declaring_class_def_index);
+                            declaring_class_def_index,
+                            /*is_vec_op=*/false);
   }
 
   void VisitArrayAccess(HInstruction* array,
                         HInstruction* index,
                         DataType::Type type,
-                        size_t vector_length) {
+                        size_t vector_length,
+                        bool is_vec_op) {
     MaybeCreateHeapLocation(array,
                             type,
                             HeapLocation::kInvalidFieldOffset,
                             index,
                             vector_length,
-                            HeapLocation::kDeclaringClassDefIndexForArrays);
+                            HeapLocation::kDeclaringClassDefIndexForArrays,
+                            is_vec_op);
   }
 
   void VisitPredicatedInstanceFieldGet(HPredicatedInstanceFieldGet* instruction) override {
@@ -597,7 +597,7 @@
     HInstruction* array = instruction->InputAt(0);
     HInstruction* index = instruction->InputAt(1);
     DataType::Type type = instruction->GetType();
-    VisitArrayAccess(array, index, type, HeapLocation::kScalar);
+    VisitArrayAccess(array, index, type, HeapLocation::kScalar, /*is_vec_op=*/false);
     CreateReferenceInfoForReferenceType(instruction);
   }
 
@@ -605,7 +605,7 @@
     HInstruction* array = instruction->InputAt(0);
     HInstruction* index = instruction->InputAt(1);
     DataType::Type type = instruction->GetComponentType();
-    VisitArrayAccess(array, index, type, HeapLocation::kScalar);
+    VisitArrayAccess(array, index, type, HeapLocation::kScalar, /*is_vec_op=*/false);
     has_heap_stores_ = true;
   }
 
@@ -613,7 +613,7 @@
     HInstruction* array = instruction->InputAt(0);
     HInstruction* index = instruction->InputAt(1);
     DataType::Type type = instruction->GetPackedType();
-    VisitArrayAccess(array, index, type, instruction->GetVectorLength());
+    VisitArrayAccess(array, index, type, instruction->GetVectorLength(), /*is_vec_op=*/true);
     CreateReferenceInfoForReferenceType(instruction);
   }
 
@@ -621,7 +621,7 @@
     HInstruction* array = instruction->InputAt(0);
     HInstruction* index = instruction->InputAt(1);
     DataType::Type type = instruction->GetPackedType();
-    VisitArrayAccess(array, index, type, instruction->GetVectorLength());
+    VisitArrayAccess(array, index, type, instruction->GetVectorLength(), /*is_vec_op=*/true);
     has_heap_stores_ = true;
   }
 
@@ -637,18 +637,12 @@
     CreateReferenceInfoForReferenceType(instruction);
   }
 
-  void VisitMonitorOperation(HMonitorOperation* monitor ATTRIBUTE_UNUSED) override {
-    has_monitor_operations_ = true;
-  }
-
   ScopedArenaAllocator* allocator_;
   ScopedArenaVector<ReferenceInfo*> ref_info_array_;   // All references used for heap accesses.
   ScopedArenaVector<HeapLocation*> heap_locations_;    // All heap locations.
   ArenaBitVector aliasing_matrix_;    // aliasing info between each pair of locations.
   bool has_heap_stores_;    // If there is no heap stores, LSE acts as GVN with better
                             // alias analysis and won't be as effective.
-  bool has_volatile_;       // If there are volatile field accesses.
-  bool has_monitor_operations_;    // If there are monitor operations.
   LoadStoreAnalysisType lse_type_;
 
   DISALLOW_COPY_AND_ASSIGN(HeapLocationCollector);
diff --git a/compiler/optimizing/load_store_analysis_test.cc b/compiler/optimizing/load_store_analysis_test.cc
index 3c26c8d..865febb 100644
--- a/compiler/optimizing/load_store_analysis_test.cc
+++ b/compiler/optimizing/load_store_analysis_test.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "base/macros.h"
 #include "load_store_analysis.h"
 
 #include <array>
@@ -36,7 +37,7 @@
 #include "optimizing_unit_test.h"
 #include "scoped_thread_state_change.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class LoadStoreAnalysisTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  public:
@@ -117,12 +118,13 @@
   size_t field = HeapLocation::kInvalidFieldOffset;
   size_t vec = HeapLocation::kScalar;
   size_t class_def = HeapLocation::kDeclaringClassDefIndexForArrays;
+  const bool is_vec_op = false;
   size_t loc1 = heap_location_collector.FindHeapLocationIndex(
-      ref, type, field, c1, vec, class_def);
+      ref, type, field, c1, vec, class_def, is_vec_op);
   size_t loc2 = heap_location_collector.FindHeapLocationIndex(
-      ref, type, field, c2, vec, class_def);
+      ref, type, field, c2, vec, class_def, is_vec_op);
   size_t loc3 = heap_location_collector.FindHeapLocationIndex(
-      ref, type, field, index, vec, class_def);
+      ref, type, field, index, vec, class_def, is_vec_op);
   // must find this reference info for array in HeapLocationCollector.
   ASSERT_TRUE(ref != nullptr);
   // must find these heap locations;
@@ -142,7 +144,7 @@
   ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc3));
   ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc3));
 
-  EXPECT_TRUE(CheckGraph(graph_));
+  EXPECT_TRUE(CheckGraph());
 }
 
 TEST_F(LoadStoreAnalysisTest, FieldHeapLocations) {
@@ -223,15 +225,14 @@
   // accesses to different fields of the same object should not alias.
   ASSERT_FALSE(heap_location_collector.MayAlias(loc1, loc2));
 
-  EXPECT_TRUE(CheckGraph(graph_));
+  EXPECT_TRUE(CheckGraph());
 }
 
 TEST_F(LoadStoreAnalysisTest, ArrayIndexAliasingTest) {
   CreateGraph();
-  HBasicBlock* entry = new (GetAllocator()) HBasicBlock(graph_);
-  graph_->AddBlock(entry);
-  graph_->SetEntryBlock(entry);
-  graph_->BuildDominatorTree();
+  AdjacencyListGraph blks(
+      SetupFromAdjacencyList("entry", "exit", {{"entry", "body"}, {"body", "exit"}}));
+  HBasicBlock* body = blks.Get("body");
 
   HInstruction* array = new (GetAllocator()) HParameterValue(
       graph_->GetDexFile(), dex::TypeIndex(0), 0, DataType::Type::kReference);
@@ -261,23 +262,25 @@
   HInstruction* arr_set8 =
       new (GetAllocator()) HArraySet(array, sub_neg1, c0, DataType::Type::kInt32, 0);
 
-  entry->AddInstruction(array);
-  entry->AddInstruction(index);
-  entry->AddInstruction(add0);
-  entry->AddInstruction(add1);
-  entry->AddInstruction(sub0);
-  entry->AddInstruction(sub1);
-  entry->AddInstruction(sub_neg1);
-  entry->AddInstruction(rev_sub1);
+  body->AddInstruction(array);
+  body->AddInstruction(index);
+  body->AddInstruction(add0);
+  body->AddInstruction(add1);
+  body->AddInstruction(sub0);
+  body->AddInstruction(sub1);
+  body->AddInstruction(sub_neg1);
+  body->AddInstruction(rev_sub1);
 
-  entry->AddInstruction(arr_set1);  // array[0] = c0
-  entry->AddInstruction(arr_set2);  // array[1] = c0
-  entry->AddInstruction(arr_set3);  // array[i+0] = c0
-  entry->AddInstruction(arr_set4);  // array[i+1] = c0
-  entry->AddInstruction(arr_set5);  // array[i-0] = c0
-  entry->AddInstruction(arr_set6);  // array[i-1] = c0
-  entry->AddInstruction(arr_set7);  // array[1-i] = c0
-  entry->AddInstruction(arr_set8);  // array[i-(-1)] = c0
+  body->AddInstruction(arr_set1);  // array[0] = c0
+  body->AddInstruction(arr_set2);  // array[1] = c0
+  body->AddInstruction(arr_set3);  // array[i+0] = c0
+  body->AddInstruction(arr_set4);  // array[i+1] = c0
+  body->AddInstruction(arr_set5);  // array[i-0] = c0
+  body->AddInstruction(arr_set6);  // array[i-1] = c0
+  body->AddInstruction(arr_set7);  // array[1-i] = c0
+  body->AddInstruction(arr_set8);  // array[i-(-1)] = c0
+
+  body->AddInstruction(new (GetAllocator()) HReturnVoid());
 
   ScopedArenaAllocator allocator(graph_->GetArenaStack());
   LoadStoreAnalysis lsa(graph_, nullptr, &allocator, LoadStoreAnalysisType::kBasic);
@@ -317,7 +320,7 @@
   loc2 = heap_location_collector.GetArrayHeapLocation(arr_set8);
   ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2));
 
-  EXPECT_TRUE(CheckGraphSkipRefTypeInfoChecks(graph_));
+  EXPECT_TRUE(CheckGraph());
 }
 
 TEST_F(LoadStoreAnalysisTest, ArrayAliasingTest) {
@@ -891,7 +894,8 @@
                             {},
                             InvokeType::kStatic,
                             { nullptr, 0 },
-                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                            !graph_->IsDebuggable());
   HInstruction* goto_left = new (GetAllocator()) HGoto();
   call_left->AsInvoke()->SetRawInputAt(0, new_inst);
   left->AddInstruction(call_left);
@@ -1000,7 +1004,8 @@
                             {},
                             InvokeType::kStatic,
                             { nullptr, 0 },
-                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                            !graph_->IsDebuggable());
   HInstruction* goto_left = new (GetAllocator()) HGoto();
   call_left->AsInvoke()->SetRawInputAt(0, new_inst);
   left->AddInstruction(call_left);
@@ -1123,7 +1128,8 @@
                             {},
                             InvokeType::kStatic,
                             { nullptr, 0 },
-                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                            !graph_->IsDebuggable());
   HInstruction* goto_left = new (GetAllocator()) HGoto();
   call_left->AsInvoke()->SetRawInputAt(0, new_inst);
   left->AddInstruction(call_left);
@@ -1403,7 +1409,8 @@
                             {},
                             InvokeType::kStatic,
                             {nullptr, 0},
-                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                            !graph_->IsDebuggable());
   HInstruction* goto_left = new (GetAllocator()) HGoto();
   call_left->AsInvoke()->SetRawInputAt(0, new_inst);
   left->AddInstruction(call_left);
@@ -1504,7 +1511,8 @@
                             {},
                             InvokeType::kStatic,
                             { nullptr, 0 },
-                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                            !graph_->IsDebuggable());
   HInstruction* goto_left = new (GetAllocator()) HGoto();
   call_left->AsInvoke()->SetRawInputAt(0, new_inst);
   left->AddInstruction(call_left);
@@ -1615,7 +1623,8 @@
                             {},
                             InvokeType::kStatic,
                             { nullptr, 0 },
-                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                            !graph_->IsDebuggable());
   HInstruction* goto_left = new (GetAllocator()) HGoto();
   call_left->AsInvoke()->SetRawInputAt(0, new_inst);
   left->AddInstruction(call_left);
@@ -1631,7 +1640,8 @@
                             {},
                             InvokeType::kStatic,
                             { nullptr, 0 },
-                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                            !graph_->IsDebuggable());
   HInstruction* write_right = new (GetAllocator()) HInstanceFieldSet(new_inst,
                                                                      c0,
                                                                      nullptr,
@@ -1800,7 +1810,8 @@
                             {},
                             InvokeType::kStatic,
                             { nullptr, 0 },
-                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                            !graph_->IsDebuggable());
   HInstruction* goto_left = new (GetAllocator()) HGoto();
   call_left->AsInvoke()->SetRawInputAt(0, new_inst);
   high_left->AddInstruction(call_left);
@@ -1856,7 +1867,8 @@
                             {},
                             InvokeType::kStatic,
                             { nullptr, 0 },
-                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                            !graph_->IsDebuggable());
   HInstruction* goto_low_left = new (GetAllocator()) HGoto();
   call_low_left->AsInvoke()->SetRawInputAt(0, new_inst);
   low_left->AddInstruction(call_low_left);
@@ -2013,7 +2025,8 @@
                             {},
                             InvokeType::kStatic,
                             {nullptr, 0},
-                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                            HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                            !graph_->IsDebuggable());
   HInstruction* goto_left_merge = new (GetAllocator()) HGoto();
   left_phi->SetRawInputAt(0, obj_param);
   left_phi->SetRawInputAt(1, new_inst);
diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc
index 9b8f07e..9cabb12 100644
--- a/compiler/optimizing/load_store_elimination.cc
+++ b/compiler/optimizing/load_store_elimination.cc
@@ -319,7 +319,7 @@
  * a hash map to the HeapLocationCollector.
  */
 
-namespace art {
+namespace art HIDDEN {
 
 #define LSE_VLOG \
   if (::art::LoadStoreElimination::kVerboseLoggingMode && VLOG_IS_ON(compiler)) LOG(INFO)
@@ -855,25 +855,6 @@
     }
   }
 
-  // `instruction` is being removed. Try to see if the null check on it
-  // can be removed. This can happen if the same value is set in two branches
-  // but not in dominators. Such as:
-  //   int[] a = foo();
-  //   if () {
-  //     a[0] = 2;
-  //   } else {
-  //     a[0] = 2;
-  //   }
-  //   // a[0] can now be replaced with constant 2, and the null check on it can be removed.
-  void TryRemovingNullCheck(HInstruction* instruction) {
-    HInstruction* prev = instruction->GetPrevious();
-    if ((prev != nullptr) && prev->IsNullCheck() && (prev == instruction->InputAt(0))) {
-      // Previous instruction is a null check for this instruction. Remove the null check.
-      prev->ReplaceWith(prev->InputAt(0));
-      prev->GetBlock()->RemoveInstruction(prev);
-    }
-  }
-
   HInstruction* GetDefaultValue(DataType::Type type) {
     switch (type) {
       case DataType::Type::kReference:
@@ -993,13 +974,63 @@
                << " but LSE should be the only source of predicated-ifield-gets!";
   }
 
+  void HandleAcquireLoad(HInstruction* instruction) {
+    DCHECK((instruction->IsInstanceFieldGet() && instruction->AsInstanceFieldGet()->IsVolatile()) ||
+           (instruction->IsStaticFieldGet() && instruction->AsStaticFieldGet()->IsVolatile()) ||
+           (instruction->IsMonitorOperation() && instruction->AsMonitorOperation()->IsEnter()))
+        << "Unexpected instruction " << instruction->GetId() << ": " << instruction->DebugName();
+
+    // Acquire operations e.g. MONITOR_ENTER change the thread's view of the memory, so we must
+    // invalidate all current values.
+    ScopedArenaVector<ValueRecord>& heap_values =
+        heap_values_for_[instruction->GetBlock()->GetBlockId()];
+    for (size_t i = 0u, size = heap_values.size(); i != size; ++i) {
+      KeepStores(heap_values[i].stored_by);
+      heap_values[i].stored_by = Value::PureUnknown();
+      heap_values[i].value = Value::PartialUnknown(heap_values[i].value);
+    }
+
+    // Note that there's no need to record the load as subsequent acquire loads shouldn't be
+    // eliminated either.
+  }
+
+  void HandleReleaseStore(HInstruction* instruction) {
+    DCHECK((instruction->IsInstanceFieldSet() && instruction->AsInstanceFieldSet()->IsVolatile()) ||
+           (instruction->IsStaticFieldSet() && instruction->AsStaticFieldSet()->IsVolatile()) ||
+           (instruction->IsMonitorOperation() && !instruction->AsMonitorOperation()->IsEnter()))
+        << "Unexpected instruction " << instruction->GetId() << ": " << instruction->DebugName();
+
+    // Release operations e.g. MONITOR_EXIT do not affect this thread's view of the memory, but
+    // they will push the modifications for other threads to see. Therefore, we must keep the
+    // stores but there's no need to clobber the value.
+    ScopedArenaVector<ValueRecord>& heap_values =
+        heap_values_for_[instruction->GetBlock()->GetBlockId()];
+    for (size_t i = 0u, size = heap_values.size(); i != size; ++i) {
+      KeepStores(heap_values[i].stored_by);
+      heap_values[i].stored_by = Value::PureUnknown();
+    }
+
+    // Note that there's no need to record the store as subsequent release store shouldn't be
+    // eliminated either.
+  }
+
   void VisitInstanceFieldGet(HInstanceFieldGet* instruction) override {
+    if (instruction->IsVolatile()) {
+      HandleAcquireLoad(instruction);
+      return;
+    }
+
     HInstruction* object = instruction->InputAt(0);
     const FieldInfo& field = instruction->GetFieldInfo();
     VisitGetLocation(instruction, heap_location_collector_.GetFieldHeapLocation(object, &field));
   }
 
   void VisitInstanceFieldSet(HInstanceFieldSet* instruction) override {
+    if (instruction->IsVolatile()) {
+      HandleReleaseStore(instruction);
+      return;
+    }
+
     HInstruction* object = instruction->InputAt(0);
     const FieldInfo& field = instruction->GetFieldInfo();
     HInstruction* value = instruction->InputAt(1);
@@ -1008,12 +1039,22 @@
   }
 
   void VisitStaticFieldGet(HStaticFieldGet* instruction) override {
+    if (instruction->IsVolatile()) {
+      HandleAcquireLoad(instruction);
+      return;
+    }
+
     HInstruction* cls = instruction->InputAt(0);
     const FieldInfo& field = instruction->GetFieldInfo();
     VisitGetLocation(instruction, heap_location_collector_.GetFieldHeapLocation(cls, &field));
   }
 
   void VisitStaticFieldSet(HStaticFieldSet* instruction) override {
+    if (instruction->IsVolatile()) {
+      HandleReleaseStore(instruction);
+      return;
+    }
+
     HInstruction* cls = instruction->InputAt(0);
     const FieldInfo& field = instruction->GetFieldInfo();
     HInstruction* value = instruction->InputAt(1);
@@ -1021,6 +1062,14 @@
     VisitSetLocation(instruction, idx, value);
   }
 
+  void VisitMonitorOperation(HMonitorOperation* monitor_op) override {
+    if (monitor_op->IsEnter()) {
+      HandleAcquireLoad(monitor_op);
+    } else {
+      HandleReleaseStore(monitor_op);
+    }
+  }
+
   void VisitArrayGet(HArrayGet* instruction) override {
     VisitGetLocation(instruction, heap_location_collector_.GetArrayHeapLocation(instruction));
   }
@@ -1040,8 +1089,8 @@
   }
 
   void VisitDeoptimize(HDeoptimize* instruction) override {
-    // If we are in a try catch, even singletons are observable.
-    const bool in_try_catch = instruction->GetBlock()->GetTryCatchInformation() != nullptr;
+    // If we are in a try, even singletons are observable.
+    const bool inside_a_try = instruction->GetBlock()->IsTryBlock();
     HBasicBlock* block = instruction->GetBlock();
     ScopedArenaVector<ValueRecord>& heap_values = heap_values_for_[block->GetBlockId()];
     for (size_t i = 0u, size = heap_values.size(); i != size; ++i) {
@@ -1053,7 +1102,7 @@
       // for singletons that don't escape in the deoptimization environment.
       bool observable = true;
       ReferenceInfo* info = heap_location_collector_.GetHeapLocation(i)->GetReferenceInfo();
-      if (!in_try_catch && info->IsSingleton()) {
+      if (!inside_a_try && info->IsSingleton()) {
         HInstruction* reference = info->GetReference();
         // Finalizable objects always escape.
         const bool finalizable_object =
@@ -1099,10 +1148,8 @@
 
   void HandleThrowingInstruction(HInstruction* instruction) {
     DCHECK(instruction->CanThrow());
-    // If we are inside of a try catch, singletons can become visible since we may not exit the
-    // method.
-    HandleExit(instruction->GetBlock(),
-               instruction->GetBlock()->GetTryCatchInformation() != nullptr);
+    // If we are inside of a try, singletons can become visible since we may not exit the method.
+    HandleExit(instruction->GetBlock(), instruction->GetBlock()->IsTryBlock());
   }
 
   void VisitMethodEntryHook(HMethodEntryHook* method_entry) override {
@@ -1137,6 +1184,14 @@
     }
   }
 
+  void VisitLoadMethodHandle(HLoadMethodHandle* load_method_handle) override {
+    HandleThrowingInstruction(load_method_handle);
+  }
+
+  void VisitLoadMethodType(HLoadMethodType* load_method_type) override {
+    HandleThrowingInstruction(load_method_type);
+  }
+
   void VisitStringBuilderAppend(HStringBuilderAppend* sb_append) override {
     HandleThrowingInstruction(sb_append);
   }
@@ -1149,18 +1204,11 @@
     HandleThrowingInstruction(check_cast);
   }
 
-  void VisitMonitorOperation(HMonitorOperation* monitor_op) override {
-    if (monitor_op->CanThrow()) {
-      HandleThrowingInstruction(monitor_op);
-    }
-  }
-
   void HandleInvoke(HInstruction* instruction) {
     // If `instruction` can throw we have to presume all stores are visible.
     const bool can_throw = instruction->CanThrow();
-    // If we are in a try catch, even singletons are observable.
-    const bool can_throw_in_try_catch =
-        can_throw && instruction->GetBlock()->GetTryCatchInformation() != nullptr;
+    // If we are in a try, even singletons are observable.
+    const bool can_throw_inside_a_try = can_throw && instruction->GetBlock()->IsTryBlock();
     SideEffects side_effects = instruction->GetSideEffects();
     ScopedArenaVector<ValueRecord>& heap_values =
         heap_values_for_[instruction->GetBlock()->GetBlockId()];
@@ -1186,7 +1234,7 @@
                               return cohort.PrecedesBlock(blk);
                             });
       };
-      if (!can_throw_in_try_catch &&
+      if (!can_throw_inside_a_try &&
           (ref_info->IsSingleton() ||
            // partial and we aren't currently escaping and we haven't escaped yet.
            (ref_info->IsPartialSingleton() && partial_singleton_did_not_escape(ref_info, blk)))) {
@@ -1235,8 +1283,8 @@
   }
 
   void VisitNewInstance(HNewInstance* new_instance) override {
-    // If we are in a try catch, even singletons are observable.
-    const bool in_try_catch = new_instance->GetBlock()->GetTryCatchInformation() != nullptr;
+    // If we are in a try, even singletons are observable.
+    const bool inside_a_try = new_instance->GetBlock()->IsTryBlock();
     ReferenceInfo* ref_info = heap_location_collector_.FindReferenceInfoOf(new_instance);
     if (ref_info == nullptr) {
       // new_instance isn't used for field accesses. No need to process it.
@@ -1265,7 +1313,7 @@
           heap_values[i].value = Value::ForInstruction(new_instance->GetLoadClass());
           heap_values[i].stored_by = Value::PureUnknown();
         }
-      } else if (in_try_catch || IsEscapingObject(info, block, i)) {
+      } else if (inside_a_try || IsEscapingObject(info, block, i)) {
         // Since NewInstance can throw, we presume all previous stores could be visible.
         KeepStores(heap_values[i].stored_by);
         heap_values[i].stored_by = Value::PureUnknown();
@@ -1274,8 +1322,8 @@
   }
 
   void VisitNewArray(HNewArray* new_array) override {
-    // If we are in a try catch, even singletons are observable.
-    const bool in_try_catch = new_array->GetBlock()->GetTryCatchInformation() != nullptr;
+    // If we are in a try, even singletons are observable.
+    const bool inside_a_try = new_array->GetBlock()->IsTryBlock();
     ReferenceInfo* ref_info = heap_location_collector_.FindReferenceInfoOf(new_array);
     if (ref_info == nullptr) {
       // new_array isn't used for array accesses. No need to process it.
@@ -1300,7 +1348,7 @@
         // Array elements are set to default heap values.
         heap_values[i].value = Value::Default();
         heap_values[i].stored_by = Value::PureUnknown();
-      } else if (in_try_catch || IsEscapingObject(info, block, i)) {
+      } else if (inside_a_try || IsEscapingObject(info, block, i)) {
         // Since NewArray can throw, we presume all previous stores could be visible.
         KeepStores(heap_values[i].stored_by);
         heap_values[i].stored_by = Value::PureUnknown();
@@ -1704,8 +1752,7 @@
   ScopedArenaVector<ValueRecord>& heap_values = heap_values_for_[block->GetBlockId()];
   DCHECK(heap_values.empty());
   size_t num_heap_locations = heap_location_collector_.GetNumberOfHeapLocations();
-  if (block->GetPredecessors().empty() || (block->GetTryCatchInformation() != nullptr &&
-                                           block->GetTryCatchInformation()->IsCatchBlock())) {
+  if (block->GetPredecessors().empty() || block->IsCatchBlock()) {
     DCHECK_IMPLIES(block->GetPredecessors().empty(), block->IsEntryBlock());
     heap_values.resize(num_heap_locations,
                        {/*value=*/Value::PureUnknown(), /*stored_by=*/Value::PureUnknown()});
@@ -1764,7 +1811,6 @@
   if (type == DataType::Type::kReference) {
     // Update reference type information. Pass invalid handles, these are not used for Phis.
     ReferenceTypePropagation rtp_fixup(block->GetGraph(),
-                                       Handle<mirror::ClassLoader>(),
                                        Handle<mirror::DexCache>(),
                                        /* is_first_run= */ false);
     rtp_fixup.Visit(phi);
@@ -1877,7 +1923,6 @@
     }
     HInstruction* heap_value = FindSubstitute(record.value.GetInstruction());
     AddRemovedLoad(instruction, heap_value);
-    TryRemovingNullCheck(instruction);
   }
 }
 
@@ -2068,9 +2113,15 @@
   HInstruction* replacement = GetDefaultValue(type);
   for (uint32_t phi_placeholder_index : visited.Indexes()) {
     DCHECK(phi_placeholder_replacements_[phi_placeholder_index].IsInvalid());
-    phi_placeholder_replacements_[phi_placeholder_index] = Value::ForInstruction(replacement);
+    PhiPlaceholder curr = GetPhiPlaceholderAt(phi_placeholder_index);
+    HeapLocation* hl = heap_location_collector_.GetHeapLocation(curr.GetHeapLocation());
+    // We use both vector and non vector operations to analyze the information. However, we replace
+    // only non vector operations in this code path.
+    if (!hl->IsVecOp()) {
+      phi_placeholder_replacements_[phi_placeholder_index] = Value::ForInstruction(replacement);
+      phi_placeholders_to_materialize->ClearBit(phi_placeholder_index);
+    }
   }
-  phi_placeholders_to_materialize->Subtract(&visited);
   return true;
 }
 
@@ -2125,9 +2176,15 @@
   DCHECK(replacement != nullptr);
   for (uint32_t phi_placeholder_index : visited.Indexes()) {
     DCHECK(phi_placeholder_replacements_[phi_placeholder_index].IsInvalid());
-    phi_placeholder_replacements_[phi_placeholder_index] = Value::ForInstruction(replacement);
+    PhiPlaceholder curr = GetPhiPlaceholderAt(phi_placeholder_index);
+    HeapLocation* hl = heap_location_collector_.GetHeapLocation(curr.GetHeapLocation());
+    // We use both vector and non vector operations to analyze the information. However, we replace
+    // only vector operations in this code path.
+    if (hl->IsVecOp()) {
+      phi_placeholder_replacements_[phi_placeholder_index] = Value::ForInstruction(replacement);
+      phi_placeholders_to_materialize->ClearBit(phi_placeholder_index);
+    }
   }
-  phi_placeholders_to_materialize->Subtract(&visited);
   return true;
 }
 
@@ -2352,7 +2409,6 @@
     }
     // Update reference type information. Pass invalid handles, these are not used for Phis.
     ReferenceTypePropagation rtp_fixup(GetGraph(),
-                                       Handle<mirror::ClassLoader>(),
                                        Handle<mirror::DexCache>(),
                                        /* is_first_run= */ false);
     rtp_fixup.Visit(ArrayRef<HInstruction* const>(phis));
@@ -2639,7 +2695,6 @@
             record.value = local_heap_values[idx];
             HInstruction* heap_value = local_heap_values[idx].GetInstruction();
             AddRemovedLoad(load_or_store, heap_value);
-            TryRemovingNullCheck(load_or_store);
           }
         }
       }
@@ -2698,7 +2753,6 @@
       record.value = Replacement(record.value);
       HInstruction* heap_value = record.value.GetInstruction();
       AddRemovedLoad(load, heap_value);
-      TryRemovingNullCheck(load);
     }
   }
 }
@@ -3013,7 +3067,6 @@
       return;
     }
     ReferenceTypePropagation rtp_fixup(GetGraph(),
-                                       Handle<mirror::ClassLoader>(),
                                        Handle<mirror::DexCache>(),
                                        /* is_first_run= */ false);
     rtp_fixup.Visit(ArrayRef<HInstruction* const>(new_ref_phis_));
@@ -3333,7 +3386,7 @@
           ins->GetBlock()->InsertInstructionBefore(new_fget, ins);
           if (ins->GetType() == DataType::Type::kReference) {
             // Reference info is the same
-            new_fget->SetReferenceTypeInfo(ins->GetReferenceTypeInfo());
+            new_fget->SetReferenceTypeInfoIfValid(ins->GetReferenceTypeInfo());
           }
           // In this phase, substitute instructions are used only for the predicated get
           // default values which are used only if the partial singleton did not escape,
diff --git a/compiler/optimizing/load_store_elimination.h b/compiler/optimizing/load_store_elimination.h
index 6ad2eb2..42de803 100644
--- a/compiler/optimizing/load_store_elimination.h
+++ b/compiler/optimizing/load_store_elimination.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_LOAD_STORE_ELIMINATION_H_
 #define ART_COMPILER_OPTIMIZING_LOAD_STORE_ELIMINATION_H_
 
+#include "base/macros.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class SideEffectsAnalysis;
 
diff --git a/compiler/optimizing/load_store_elimination_test.cc b/compiler/optimizing/load_store_elimination_test.cc
index 02dc939..1ee1099 100644
--- a/compiler/optimizing/load_store_elimination_test.cc
+++ b/compiler/optimizing/load_store_elimination_test.cc
@@ -36,7 +36,9 @@
 #include "optimizing_unit_test.h"
 #include "scoped_thread_state_change.h"
 
-namespace art {
+namespace art HIDDEN {
+
+static constexpr bool kDebugLseTests = false;
 
 #define CHECK_SUBROUTINE_FAILURE() \
   do {                             \
@@ -54,12 +56,16 @@
 
   void SetUp() override {
     SuperTest::SetUp();
-    gLogVerbosity.compiler = true;
+    if (kDebugLseTests) {
+      gLogVerbosity.compiler = true;
+    }
   }
 
   void TearDown() override {
     SuperTest::TearDown();
-    gLogVerbosity.compiler = false;
+    if (kDebugLseTests) {
+      gLogVerbosity.compiler = false;
+    }
   }
 
   void PerformLSE(bool with_partial = true) {
@@ -67,15 +73,40 @@
     LoadStoreElimination lse(graph_, /*stats=*/nullptr);
     lse.Run(with_partial);
     std::ostringstream oss;
-    EXPECT_TRUE(CheckGraphSkipRefTypeInfoChecks(oss)) << oss.str();
+    EXPECT_TRUE(CheckGraph(oss)) << oss.str();
   }
 
-  void PerformLSEWithPartial() {
-    PerformLSE(true);
+  void PerformLSEWithPartial(const AdjacencyListGraph& blks) {
+    // PerformLSE expects this to be empty.
+    graph_->ClearDominanceInformation();
+    if (kDebugLseTests) {
+      LOG(INFO) << "Pre LSE " << blks;
+    }
+    PerformLSE(/*with_partial=*/ true);
+    if (kDebugLseTests) {
+      LOG(INFO) << "Post LSE " << blks;
+    }
   }
 
-  void PerformLSENoPartial() {
-    PerformLSE(false);
+  void PerformLSENoPartial(const AdjacencyListGraph& blks) {
+    // PerformLSE expects this to be empty.
+    graph_->ClearDominanceInformation();
+    if (kDebugLseTests) {
+      LOG(INFO) << "Pre LSE " << blks;
+    }
+    PerformLSE(/*with_partial=*/ false);
+    if (kDebugLseTests) {
+      LOG(INFO) << "Post LSE " << blks;
+    }
+  }
+
+  void PerformSimplifications(const AdjacencyListGraph& blks) {
+    InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
+    simp.Run();
+
+    if (kDebugLseTests) {
+      LOG(INFO) << "Post simplification " << blks;
+    }
   }
 
   // Create instructions shared among tests.
@@ -542,6 +573,7 @@
   AddVecStore(entry_block_, array_, j_);
   HInstruction* vstore = AddVecStore(entry_block_, array_, i_);
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_FALSE(IsRemoved(vstore));
@@ -557,6 +589,7 @@
   AddVecStore(entry_block_, array_, i_add1_);
   HInstruction* vstore = AddVecStore(entry_block_, array_, i_);
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_FALSE(IsRemoved(vstore));
@@ -601,6 +634,7 @@
   AddArraySet(entry_block_, array_, i_, c1);
   HInstruction* vload5 = AddVecLoad(entry_block_, array_, i_);
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_TRUE(IsRemoved(load1));
@@ -634,6 +668,7 @@
   // a[j] = 1;
   HInstruction* array_set = AddArraySet(return_block_, array_, j_, c1);
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_TRUE(IsRemoved(array_set));
@@ -671,6 +706,7 @@
   // a[j] = 0;
   HInstruction* a_set = AddArraySet(return_block_, array_, j_, c0);
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_TRUE(IsRemoved(vload));
@@ -709,6 +745,7 @@
   // x = a[j];
   HInstruction* load = AddArrayGet(return_block_, array_, j_);
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_TRUE(IsRemoved(vload));
@@ -749,6 +786,7 @@
   // down: a[i,... i + 3] = [1,...1]
   HInstruction* vstore4 = AddVecStore(down, array_, i_, vdata);
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_TRUE(IsRemoved(vstore2));
@@ -839,6 +877,7 @@
   HInstruction* vstore2 = AddVecStore(loop_, array_b, phi_, vload->AsVecLoad());
   HInstruction* vstore3 = AddVecStore(loop_, array_a, phi_, vstore1->InputAt(2));
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_FALSE(IsRemoved(vstore1));
@@ -894,7 +933,7 @@
   // loop:
   //   array2[i] = array[i]
   // array[0] = 2
-  HInstruction* store1 = AddArraySet(entry_block_, array_, c0, c2);
+  HInstruction* store1 = AddArraySet(pre_header_, array_, c0, c2);
 
   HInstruction* load = AddArrayGet(loop_, array_, phi_);
   HInstruction* store2 = AddArraySet(loop_, array2, phi_, load);
@@ -926,6 +965,7 @@
   HInstruction* vload = AddVecLoad(loop_, array_a, phi_);
   HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload->AsVecLoad());
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_FALSE(IsRemoved(vload));
@@ -949,6 +989,7 @@
   HInstruction* vload = AddVecLoad(pre_header_, array_a, c0);
   HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload->AsVecLoad());
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_FALSE(IsRemoved(vload));
@@ -1025,6 +1066,7 @@
   HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload->AsVecLoad());
   HInstruction* store = AddArraySet(return_block_, array_, c0, load);
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_FALSE(IsRemoved(vload));
@@ -1055,6 +1097,7 @@
   HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload->AsVecLoad());
   HInstruction* store = AddArraySet(return_block_, array_, c0, load);
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_FALSE(IsRemoved(vload));
@@ -1086,6 +1129,7 @@
   HInstruction* vstore1 = AddVecStore(return_block_, array_, c0, vload1->AsVecLoad());
   HInstruction* vstore2 = AddVecStore(return_block_, array_, c128, vload2->AsVecLoad());
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_FALSE(IsRemoved(vload1));
@@ -1116,6 +1160,7 @@
   HInstruction* vstore1 = AddVecStore(return_block_, array_, c0, vload1->AsVecLoad());
   HInstruction* vstore2 = AddVecStore(return_block_, array_, c128, vload2->AsVecLoad());
 
+  graph_->SetHasSIMD(true);
   PerformLSE();
 
   ASSERT_FALSE(IsRemoved(vload1));
@@ -2024,10 +2069,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSENoPartial();
+  PerformLSENoPartial(blks);
 
   EXPECT_INS_RETAINED(read_bottom);
   EXPECT_INS_RETAINED(write_c1);
@@ -2174,9 +2216,8 @@
   HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
   exit->AddInstruction(read_bottom);
   exit->AddInstruction(return_exit);
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  PerformLSENoPartial();
+
+  PerformLSENoPartial(blks);
 
   EXPECT_INS_RETAINED(read_bottom) << *read_bottom;
   EXPECT_INS_RETAINED(write_right) << *write_right;
@@ -2266,9 +2307,8 @@
   HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
   exit->AddInstruction(read_bottom);
   exit->AddInstruction(return_exit);
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  PerformLSENoPartial();
+
+  PerformLSENoPartial(blks);
 
   EXPECT_INS_RETAINED(read_bottom);
   EXPECT_INS_RETAINED(write_right_first);
@@ -2499,11 +2539,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HPredicatedInstanceFieldGet* pred_get =
       FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_);
@@ -2656,11 +2692,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_RETAINED(call_left_left);
   EXPECT_INS_REMOVED(read1);
@@ -2814,11 +2846,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HNewInstance* moved_new_inst1;
   HInstanceFieldSet* moved_set1;
@@ -2954,11 +2982,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(write_entry1);
   EXPECT_INS_REMOVED(write_entry2);
@@ -3115,11 +3139,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(new_inst1);
   EXPECT_INS_REMOVED(new_inst2);
@@ -3205,11 +3225,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HNewInstance* moved_new_inst = nullptr;
   HInstanceFieldSet* moved_set = nullptr;
@@ -3320,11 +3336,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HNewInstance* moved_new_inst = nullptr;
   HInstanceFieldSet* moved_set = nullptr;
@@ -3497,11 +3509,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HNewInstance* moved_new_inst = nullptr;
   HInstanceFieldSet* moved_set = nullptr;
@@ -3639,11 +3647,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HNewInstance* moved_new_inst;
   HInstanceFieldSet* moved_set;
@@ -3746,11 +3750,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   // Each escaping switch path gets its own materialization block.
   // Blocks:
@@ -3877,11 +3877,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(read_early);
   EXPECT_EQ(return_early->InputAt(0), c0);
@@ -4013,11 +4009,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   // Normal LSE can get rid of these two.
   EXPECT_INS_REMOVED(store_one);
@@ -4504,9 +4496,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  PerformLSENoPartial();
+  PerformLSENoPartial(blks);
 
   EXPECT_INS_RETAINED(write_left_pre) << *write_left_pre;
   EXPECT_INS_RETAINED(read_return) << *read_return;
@@ -4612,9 +4602,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  PerformLSENoPartial();
+  PerformLSENoPartial(blks);
 
   EXPECT_INS_RETAINED(read_return);
   EXPECT_INS_RETAINED(write_right);
@@ -4700,9 +4688,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  PerformLSENoPartial();
+  PerformLSENoPartial(blks);
 
   EXPECT_INS_RETAINED(read_bottom);
   EXPECT_INS_RETAINED(write_right);
@@ -4785,12 +4771,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSENoPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSENoPartial(blks);
 
   EXPECT_INS_REMOVED(read_bottom);
   EXPECT_INS_REMOVED(write_right);
@@ -4829,8 +4810,9 @@
   CreateGraph(/*handles=*/&vshs);
   AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                  "exit",
-                                                 {{"entry", "critical_break"},
-                                                  {"entry", "partial"},
+                                                 {{"entry", "first_block"},
+                                                  {"first_block", "critical_break"},
+                                                  {"first_block", "partial"},
                                                   {"partial", "merge"},
                                                   {"critical_break", "merge"},
                                                   {"merge", "left"},
@@ -4839,7 +4821,7 @@
                                                   {"right", "breturn"},
                                                   {"breturn", "exit"}}));
 #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
-  GET_BLOCK(entry);
+  GET_BLOCK(first_block);
   GET_BLOCK(merge);
   GET_BLOCK(partial);
   GET_BLOCK(critical_break);
@@ -4858,12 +4840,12 @@
   HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
   ComparisonInstructions cmp_instructions = GetComparisonInstructions(new_inst);
   HInstruction* if_inst = new (GetAllocator()) HIf(cmp_instructions.cmp_);
-  entry->AddInstruction(cls);
-  entry->AddInstruction(new_inst);
-  entry->AddInstruction(write_entry);
-  cmp_instructions.AddSetup(entry);
-  entry->AddInstruction(cmp_instructions.cmp_);
-  entry->AddInstruction(if_inst);
+  first_block->AddInstruction(cls);
+  first_block->AddInstruction(new_inst);
+  first_block->AddInstruction(write_entry);
+  cmp_instructions.AddSetup(first_block);
+  first_block->AddInstruction(cmp_instructions.cmp_);
+  first_block->AddInstruction(if_inst);
   ManuallyBuildEnvFor(cls, {});
   cmp_instructions.AddEnvironment(cls->GetEnvironment());
   new_inst->CopyEnvironmentFrom(cls->GetEnvironment());
@@ -4897,12 +4879,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   std::vector<HPhi*> merges;
   HPredicatedInstanceFieldGet* pred_get;
@@ -5026,11 +5003,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   std::vector<HPhi*> merges;
   HInstanceFieldSet* init_set =
@@ -5157,11 +5130,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   std::vector<HPhi*> merges;
   HInstanceFieldSet* init_set =
@@ -5290,12 +5259,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   std::vector<HPhi*> merges;
   std::vector<HInstanceFieldSet*> sets;
@@ -5424,12 +5388,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_RETAINED(write_bottom);
   EXPECT_TRUE(write_bottom->AsInstanceFieldSet()->GetIsPredicatedSet());
@@ -5539,11 +5498,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_RETAINED(write_bottom);
   EXPECT_TRUE(write_bottom->AsInstanceFieldSet()->GetIsPredicatedSet()) << *write_bottom;
@@ -5627,11 +5582,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(read_bottom);
   EXPECT_INS_REMOVED(write_right);
@@ -5748,11 +5699,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(read_bottom1);
   EXPECT_INS_REMOVED(read_bottom2);
@@ -5901,11 +5848,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(read_bottom1);
   EXPECT_INS_REMOVED(read_bottom2);
@@ -6078,11 +6021,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(early_exit_left_read);
   EXPECT_INS_REMOVED(early_exit_right_read);
@@ -6212,11 +6151,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(read_bottom);
   EXPECT_INS_REMOVED(read_right);
@@ -6334,11 +6269,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(read_bottom);
   EXPECT_INS_REMOVED(read_early_return);
@@ -6447,11 +6378,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(read_bottom);
   EXPECT_INS_REMOVED(write_right);
@@ -6585,11 +6512,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(read_bottom);
   EXPECT_INS_REMOVED(write_right);
@@ -6688,11 +6611,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_REMOVED(read_bottom);
   EXPECT_INS_RETAINED(write_left);
@@ -6861,11 +6780,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HPredicatedInstanceFieldGet* pred_get =
       FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
@@ -7045,11 +6960,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HPredicatedInstanceFieldGet* pred_get =
       FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
@@ -7196,11 +7107,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HPredicatedInstanceFieldGet* pred_get =
       FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
@@ -7344,11 +7251,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HPredicatedInstanceFieldGet* pred_get =
       FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
@@ -7492,11 +7395,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HPredicatedInstanceFieldGet* pred_get =
       FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
@@ -7657,11 +7556,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSEWithPartial();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   HPredicatedInstanceFieldGet* pred_get =
       FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
@@ -7757,17 +7652,10 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSE();
+  PerformLSEWithPartial(blks);
 
   // Run the code-simplifier too
-  LOG(INFO) << "Pre simplification " << blks;
-  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
-  simp.Run();
-
-  LOG(INFO) << "Post LSE " << blks;
+  PerformSimplifications(blks);
 
   EXPECT_INS_REMOVED(write_right);
   EXPECT_INS_REMOVED(write_start);
@@ -7851,17 +7739,10 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSE();
+  PerformLSEWithPartial(blks);
 
   // Run the code-simplifier too
-  LOG(INFO) << "Pre simplification " << blks;
-  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
-  simp.Run();
-
-  LOG(INFO) << "Post LSE " << blks;
+  PerformSimplifications(blks);
 
   EXPECT_INS_REMOVED(write_right);
   EXPECT_INS_REMOVED(write_start);
@@ -7961,17 +7842,10 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSE();
+  PerformLSEWithPartial(blks);
 
   // Run the code-simplifier too
-  LOG(INFO) << "Pre simplification " << blks;
-  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
-  simp.Run();
-
-  LOG(INFO) << "Post LSE " << blks;
+  PerformSimplifications(blks);
 
   EXPECT_INS_REMOVED(write_case2);
   EXPECT_INS_REMOVED(write_case3);
@@ -8069,17 +7943,10 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSE();
+  PerformLSEWithPartial(blks);
 
   // Run the code-simplifier too
-  LOG(INFO) << "Pre simplification " << blks;
-  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
-  simp.Run();
-
-  LOG(INFO) << "Post LSE " << blks;
+  PerformSimplifications(blks);
 
   EXPECT_INS_REMOVED(write_case2);
   EXPECT_INS_REMOVED(write_case3);
@@ -8225,11 +8092,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSE();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_TRUE(loop_header->IsLoopHeader());
   EXPECT_TRUE(loop_header->GetLoopInformation()->IsIrreducible());
@@ -8382,11 +8245,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSE();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_RETAINED(cls);
   EXPECT_INS_REMOVED(new_inst);
@@ -8544,11 +8403,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSE();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_RETAINED(cls);
   EXPECT_INS_REMOVED(new_inst);
@@ -8752,11 +8607,7 @@
 
   SetupExit(exit);
 
-  // PerformLSE expects this to be empty.
-  graph_->ClearDominanceInformation();
-  LOG(INFO) << "Pre LSE " << blks;
-  PerformLSE();
-  LOG(INFO) << "Post LSE " << blks;
+  PerformLSEWithPartial(blks);
 
   EXPECT_INS_RETAINED(cls);
   EXPECT_INS_REMOVED(new_inst);
diff --git a/compiler/optimizing/locations.cc b/compiler/optimizing/locations.cc
index 5879c6f..f40b7f4 100644
--- a/compiler/optimizing/locations.cc
+++ b/compiler/optimizing/locations.cc
@@ -21,7 +21,7 @@
 #include "code_generator.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Verify that Location is trivially copyable.
 static_assert(std::is_trivially_copyable<Location>::value, "Location should be trivially copyable");
@@ -57,7 +57,7 @@
 
 Location Location::RegisterOrConstant(HInstruction* instruction) {
   return instruction->IsConstant()
-      ? Location::ConstantLocation(instruction->AsConstant())
+      ? Location::ConstantLocation(instruction)
       : Location::RequiresRegister();
 }
 
@@ -85,16 +85,23 @@
 
 Location Location::ByteRegisterOrConstant(int reg, HInstruction* instruction) {
   return instruction->IsConstant()
-      ? Location::ConstantLocation(instruction->AsConstant())
+      ? Location::ConstantLocation(instruction)
       : Location::RegisterLocation(reg);
 }
 
 Location Location::FpuRegisterOrConstant(HInstruction* instruction) {
   return instruction->IsConstant()
-      ? Location::ConstantLocation(instruction->AsConstant())
+      ? Location::ConstantLocation(instruction)
       : Location::RequiresFpuRegister();
 }
 
+void Location::DCheckInstructionIsConstant(HInstruction* instruction) {
+  DCHECK(instruction != nullptr);
+  DCHECK(instruction->IsConstant());
+  DCHECK_EQ(reinterpret_cast<uintptr_t>(instruction),
+            reinterpret_cast<uintptr_t>(instruction->AsConstant()));
+}
+
 std::ostream& operator<<(std::ostream& os, const Location& location) {
   os << location.DebugString();
   if (location.IsRegister() || location.IsFpuRegister()) {
diff --git a/compiler/optimizing/locations.h b/compiler/optimizing/locations.h
index acaea71..7ee076f 100644
--- a/compiler/optimizing/locations.h
+++ b/compiler/optimizing/locations.h
@@ -22,9 +22,10 @@
 #include "base/bit_field.h"
 #include "base/bit_utils.h"
 #include "base/bit_vector.h"
+#include "base/macros.h"
 #include "base/value_object.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class HConstant;
 class HInstruction;
@@ -102,8 +103,12 @@
     return (value_ & kLocationConstantMask) == kConstant;
   }
 
-  static Location ConstantLocation(HConstant* constant) {
+  static Location ConstantLocation(HInstruction* constant) {
     DCHECK(constant != nullptr);
+    if (kIsDebugBuild) {
+      // Call out-of-line helper to avoid circular dependency with `nodes.h`.
+      DCheckInstructionIsConstant(constant);
+    }
     return Location(kConstant | reinterpret_cast<uintptr_t>(constant));
   }
 
@@ -425,6 +430,8 @@
     return PayloadField::Decode(value_);
   }
 
+  static void DCheckInstructionIsConstant(HInstruction* instruction);
+
   using KindField = BitField<Kind, 0, kBitsForKind>;
   using PayloadField = BitField<uintptr_t, kBitsForKind, kBitsForPayload>;
 
diff --git a/compiler/optimizing/loop_analysis.cc b/compiler/optimizing/loop_analysis.cc
index 76bd849..95e8153 100644
--- a/compiler/optimizing/loop_analysis.cc
+++ b/compiler/optimizing/loop_analysis.cc
@@ -20,7 +20,7 @@
 #include "code_generator.h"
 #include "induction_var_range.h"
 
-namespace art {
+namespace art HIDDEN {
 
 void LoopAnalysis::CalculateLoopBasicProperties(HLoopInformation* loop_info,
                                                 LoopAnalysisInfo* analysis_results,
diff --git a/compiler/optimizing/loop_analysis.h b/compiler/optimizing/loop_analysis.h
index fbf1516..cec00fe 100644
--- a/compiler/optimizing/loop_analysis.h
+++ b/compiler/optimizing/loop_analysis.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_LOOP_ANALYSIS_H_
 #define ART_COMPILER_OPTIMIZING_LOOP_ANALYSIS_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 class InductionVarRange;
diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc
index 2d7c208..7a52502 100644
--- a/compiler/optimizing/loop_optimization.cc
+++ b/compiler/optimizing/loop_optimization.cc
@@ -27,7 +27,7 @@
 #include "mirror/array-inl.h"
 #include "mirror/string.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Enables vectorization (SIMDization) in the loop optimizer.
 static constexpr bool kEnableVectorization = true;
@@ -507,9 +507,8 @@
     graph_->SetHasLoops(false);  // no more loops
   }
 
-  // Detach.
+  // Detach allocator.
   loop_allocator_ = nullptr;
-  last_loop_ = top_loop_ = nullptr;
 
   return did_loop_opt;
 }
@@ -530,11 +529,7 @@
       AddLoop(block->GetLoopInformation());
     }
   }
-
-  // TODO(solanes): How can `top_loop_` be null if `graph_->HasLoops()` is true?
-  if (top_loop_ == nullptr) {
-    return false;
-  }
+  DCHECK(top_loop_ != nullptr);
 
   // Traverse the loop hierarchy inner-to-outer and optimize. Traversal can use
   // temporary data structures using the phase-local allocator. All new HIR
@@ -681,6 +676,50 @@
 }
 
 //
+// This optimization applies to loops with plain simple operations
+// (I.e. no calls to java code or runtime) with a known small trip_count * instr_count
+// value.
+//
+bool HLoopOptimization::TryToRemoveSuspendCheckFromLoopHeader(LoopAnalysisInfo* analysis_info,
+                                                              bool generate_code) {
+  if (!graph_->SuspendChecksAreAllowedToNoOp()) {
+    return false;
+  }
+
+  int64_t trip_count = analysis_info->GetTripCount();
+
+  if (trip_count == LoopAnalysisInfo::kUnknownTripCount) {
+    return false;
+  }
+
+  int64_t instruction_count = analysis_info->GetNumberOfInstructions();
+  int64_t total_instruction_count = trip_count * instruction_count;
+
+  // The inclusion of the HasInstructionsPreventingScalarOpts() prevents this
+  // optimization from being applied to loops that have calls.
+  bool can_optimize =
+      total_instruction_count <= HLoopOptimization::kMaxTotalInstRemoveSuspendCheck &&
+      !analysis_info->HasInstructionsPreventingScalarOpts();
+
+  if (!can_optimize) {
+    return false;
+  }
+
+  // If we should do the optimization, disable codegen for the SuspendCheck.
+  if (generate_code) {
+    HLoopInformation* loop_info = analysis_info->GetLoopInfo();
+    HBasicBlock* header = loop_info->GetHeader();
+    HSuspendCheck* instruction = header->GetLoopInformation()->GetSuspendCheck();
+    // As other optimizations depend on SuspendCheck
+    // (e.g: CHAGuardVisitor::HoistGuard), disable its codegen instead of
+    // removing the SuspendCheck instruction.
+    instruction->SetIsNoOp(true);
+  }
+
+  return true;
+}
+
+//
 // Optimization.
 //
 
@@ -824,7 +863,7 @@
 }
 
 bool HLoopOptimization::OptimizeInnerLoop(LoopNode* node) {
-  return TryOptimizeInnerLoopFinite(node) || TryPeelingAndUnrolling(node);
+  return TryOptimizeInnerLoopFinite(node) || TryLoopScalarOpts(node);
 }
 
 //
@@ -928,7 +967,7 @@
   return true;
 }
 
-bool HLoopOptimization::TryPeelingAndUnrolling(LoopNode* node) {
+bool HLoopOptimization::TryLoopScalarOpts(LoopNode* node) {
   HLoopInformation* loop_info = node->loop_info;
   int64_t trip_count = LoopAnalysis::GetLoopTripCount(loop_info, &induction_range_);
   LoopAnalysisInfo analysis_info(loop_info);
@@ -941,10 +980,16 @@
 
   if (!TryFullUnrolling(&analysis_info, /*generate_code*/ false) &&
       !TryPeelingForLoopInvariantExitsElimination(&analysis_info, /*generate_code*/ false) &&
-      !TryUnrollingForBranchPenaltyReduction(&analysis_info, /*generate_code*/ false)) {
+      !TryUnrollingForBranchPenaltyReduction(&analysis_info, /*generate_code*/ false) &&
+      !TryToRemoveSuspendCheckFromLoopHeader(&analysis_info, /*generate_code*/ false)) {
     return false;
   }
 
+  // Try the suspend check removal even for non-clonable loops. Also this
+  // optimization doesn't interfere with other scalar loop optimizations so it can
+  // be done prior to them.
+  bool removed_suspend_check = TryToRemoveSuspendCheckFromLoopHeader(&analysis_info);
+
   // Run 'IsLoopClonable' the last as it might be time-consuming.
   if (!LoopClonerHelper::IsLoopClonable(loop_info)) {
     return false;
@@ -952,7 +997,7 @@
 
   return TryFullUnrolling(&analysis_info) ||
          TryPeelingForLoopInvariantExitsElimination(&analysis_info) ||
-         TryUnrollingForBranchPenaltyReduction(&analysis_info);
+         TryUnrollingForBranchPenaltyReduction(&analysis_info) || removed_suspend_check;
 }
 
 //
diff --git a/compiler/optimizing/loop_optimization.h b/compiler/optimizing/loop_optimization.h
index b178616..6dd778b 100644
--- a/compiler/optimizing/loop_optimization.h
+++ b/compiler/optimizing/loop_optimization.h
@@ -17,6 +17,7 @@
 #ifndef ART_COMPILER_OPTIMIZING_LOOP_OPTIMIZATION_H_
 #define ART_COMPILER_OPTIMIZING_LOOP_OPTIMIZATION_H_
 
+#include "base/macros.h"
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 #include "induction_var_range.h"
@@ -25,7 +26,7 @@
 #include "optimization.h"
 #include "superblock_cloner.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CompilerOptions;
 class ArchNoOptsLoopHelper;
@@ -47,6 +48,11 @@
 
   static constexpr const char* kLoopOptimizationPassName = "loop_optimization";
 
+  // The maximum number of total instructions (trip_count * instruction_count),
+  // where the optimization of removing SuspendChecks from the loop header could
+  // be performed.
+  static constexpr int64_t kMaxTotalInstRemoveSuspendCheck = 128;
+
  private:
   /**
    * A single loop inside the loop hierarchy representation.
@@ -179,8 +185,19 @@
   // should be actually applied.
   bool TryFullUnrolling(LoopAnalysisInfo* analysis_info, bool generate_code = true);
 
-  // Tries to apply scalar loop peeling and unrolling.
-  bool TryPeelingAndUnrolling(LoopNode* node);
+  // Tries to remove SuspendCheck for plain loops with a low trip count. The
+  // SuspendCheck in the codegen makes sure that the thread can be interrupted
+  // during execution for GC. Not being able to do so might decrease the
+  // responsiveness of GC when a very long loop or a long recursion is being
+  // executed. However, for plain loops with a small trip count, the removal of
+  // SuspendCheck should not affect the GC's responsiveness by a large margin.
+  // Consequently, since the thread won't be interrupted for plain loops, it is
+  // assumed that the performance might increase by removing SuspendCheck.
+  bool TryToRemoveSuspendCheckFromLoopHeader(LoopAnalysisInfo* analysis_info,
+                                             bool generate_code = true);
+
+  // Tries to apply scalar loop optimizations.
+  bool TryLoopScalarOpts(LoopNode* node);
 
   //
   // Vectorization analysis and synthesis.
diff --git a/compiler/optimizing/loop_optimization_test.cc b/compiler/optimizing/loop_optimization_test.cc
index bda2528..7f694fb 100644
--- a/compiler/optimizing/loop_optimization_test.cc
+++ b/compiler/optimizing/loop_optimization_test.cc
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
+#include "base/macros.h"
 #include "code_generator.h"
 #include "driver/compiler_options.h"
 #include "loop_optimization.h"
 #include "optimizing_unit_test.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Fixture class for the loop optimization tests. These unit tests focus
@@ -94,10 +95,7 @@
   void PerformAnalysis() {
     graph_->BuildDominatorTree();
     iva_->Run();
-    // Do not release the loop hierarchy.
-    ScopedArenaAllocator loop_allocator(GetArenaStack());
-    loop_opt_->loop_allocator_ = &loop_allocator;
-    loop_opt_->LocalRun();
+    loop_opt_->Run();
   }
 
   /** Constructs string representation of computed loop hierarchy. */
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index d35ed1c..3790058 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -40,7 +40,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "ssa_builder.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Enable floating-point static evaluation during constant folding
 // only if all floating-point operations and constants evaluate in the
@@ -150,30 +150,54 @@
   RemoveEnvironmentUses(instruction);
 }
 
-void HGraph::RemoveInstructionsAsUsersFromDeadBlocks(const ArenaBitVector& visited) const {
+void HGraph::RemoveDeadBlocksInstructionsAsUsersAndDisconnect(const ArenaBitVector& visited) const {
   for (size_t i = 0; i < blocks_.size(); ++i) {
     if (!visited.IsBitSet(i)) {
       HBasicBlock* block = blocks_[i];
       if (block == nullptr) continue;
+
+      // Remove as user.
       for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
         RemoveAsUser(it.Current());
       }
       for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
         RemoveAsUser(it.Current());
       }
+
+      // Remove non-catch phi uses, and disconnect the block.
+      block->DisconnectFromSuccessors(&visited);
+    }
+  }
+}
+
+// This method assumes `insn` has been removed from all users with the exception of catch
+// phis because of missing exceptional edges in the graph. It removes the
+// instruction from catch phi uses, together with inputs of other catch phis in
+// the catch block at the same index, as these must be dead too.
+static void RemoveCatchPhiUsesOfDeadInstruction(HInstruction* insn) {
+  DCHECK(!insn->HasEnvironmentUses());
+  while (insn->HasNonEnvironmentUses()) {
+    const HUseListNode<HInstruction*>& use = insn->GetUses().front();
+    size_t use_index = use.GetIndex();
+    HBasicBlock* user_block = use.GetUser()->GetBlock();
+    DCHECK(use.GetUser()->IsPhi());
+    DCHECK(user_block->IsCatchBlock());
+    for (HInstructionIterator phi_it(user_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
+      phi_it.Current()->AsPhi()->RemoveInputAt(use_index);
     }
   }
 }
 
 void HGraph::RemoveDeadBlocks(const ArenaBitVector& visited) {
+  DCHECK(reverse_post_order_.empty()) << "We shouldn't have dominance information.";
   for (size_t i = 0; i < blocks_.size(); ++i) {
     if (!visited.IsBitSet(i)) {
       HBasicBlock* block = blocks_[i];
       if (block == nullptr) continue;
-      // We only need to update the successor, which might be live.
-      for (HBasicBlock* successor : block->GetSuccessors()) {
-        successor->RemovePredecessor(block);
-      }
+
+      // Remove all remaining uses (which should be only catch phi uses), and the instructions.
+      block->RemoveCatchPhiUsesAndInstruction(/* building_dominator_tree = */ true);
+
       // Remove the block from the list of blocks, so that further analyses
       // never see it.
       blocks_[i] = nullptr;
@@ -200,7 +224,8 @@
   // (2) Remove instructions and phis from blocks not visited during
   //     the initial DFS as users from other instructions, so that
   //     users can be safely removed before uses later.
-  RemoveInstructionsAsUsersFromDeadBlocks(visited);
+  //     Also disconnect the block from its successors, updating the successor's phis if needed.
+  RemoveDeadBlocksInstructionsAsUsersAndDisconnect(visited);
 
   // (3) Remove blocks not visited during the initial DFS.
   //     Step (5) requires dead blocks to be removed from the
@@ -237,6 +262,7 @@
 }
 
 void HGraph::ClearLoopInformation() {
+  SetHasLoops(false);
   SetHasIrreducibleLoops(false);
   for (HBasicBlock* block : GetActiveBlocks()) {
     block->SetLoopInformation(nullptr);
@@ -544,6 +570,15 @@
   }
 }
 
+HBasicBlock* HGraph::SplitEdgeAndUpdateRPO(HBasicBlock* block, HBasicBlock* successor) {
+  HBasicBlock* new_block = SplitEdge(block, successor);
+  // In the RPO we have {... , block, ... , successor}. We want to insert `new_block` right after
+  // `block` to have a consistent RPO without recomputing the whole graph's RPO.
+  reverse_post_order_.insert(
+      reverse_post_order_.begin() + IndexOfElement(reverse_post_order_, block) + 1, new_block);
+  return new_block;
+}
+
 // Reorder phi inputs to match reordering of the block's predecessors.
 static void FixPhisAfterPredecessorsReodering(HBasicBlock* block, size_t first, size_t second) {
   for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
@@ -653,7 +688,7 @@
                                                     0,
                                                     header_phi->GetType());
     if (header_phi->GetType() == DataType::Type::kReference) {
-      preheader_phi->SetReferenceTypeInfo(header_phi->GetReferenceTypeInfo());
+      preheader_phi->SetReferenceTypeInfoIfValid(header_phi->GetReferenceTypeInfo());
     }
     preheader->AddPhi(preheader_phi);
 
@@ -708,6 +743,8 @@
 void HGraph::ComputeTryBlockInformation() {
   // Iterate in reverse post order to propagate try membership information from
   // predecessors to their successors.
+  bool graph_has_try_catch = false;
+
   for (HBasicBlock* block : GetReversePostOrder()) {
     if (block->IsEntryBlock() || block->IsCatchBlock()) {
       // Catch blocks after simplification have only exceptional predecessors
@@ -722,6 +759,7 @@
     DCHECK_IMPLIES(block->IsLoopHeader(),
                    !block->GetLoopInformation()->IsBackEdge(*first_predecessor));
     const HTryBoundary* try_entry = first_predecessor->ComputeTryEntryOfSuccessors();
+    graph_has_try_catch |= try_entry != nullptr;
     if (try_entry != nullptr &&
         (block->GetTryCatchInformation() == nullptr ||
          try_entry != &block->GetTryCatchInformation()->GetTryEntry())) {
@@ -730,6 +768,8 @@
       block->SetTryCatchInformation(new (allocator_) TryCatchInformation(*try_entry));
     }
   }
+
+  SetHasTryCatch(graph_has_try_catch);
 }
 
 void HGraph::SimplifyCFG() {
@@ -1459,6 +1499,10 @@
   UNREACHABLE();
 }
 
+bool HInstruction::Dominates(HInstruction* other_instruction) const {
+  return other_instruction == this || StrictlyDominates(other_instruction);
+}
+
 bool HInstruction::StrictlyDominates(HInstruction* other_instruction) const {
   if (other_instruction == this) {
     // An instruction does not strictly dominate itself.
@@ -1518,14 +1562,19 @@
   DCHECK(env_uses_.empty());
 }
 
-void HInstruction::ReplaceUsesDominatedBy(HInstruction* dominator, HInstruction* replacement) {
+void HInstruction::ReplaceUsesDominatedBy(HInstruction* dominator,
+                                          HInstruction* replacement,
+                                          bool strictly_dominated) {
   const HUseList<HInstruction*>& uses = GetUses();
   for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) {
     HInstruction* user = it->GetUser();
     size_t index = it->GetIndex();
     // Increment `it` now because `*it` may disappear thanks to user->ReplaceInput().
     ++it;
-    if (dominator->StrictlyDominates(user)) {
+    const bool dominated =
+        strictly_dominated ? dominator->StrictlyDominates(user) : dominator->Dominates(user);
+
+    if (dominated) {
       user->ReplaceInput(replacement, index);
     } else if (user->IsPhi() && !user->AsPhi()->IsCatchPhi()) {
       // If the input flows from a block dominated by `dominator`, we can replace it.
@@ -2108,8 +2157,9 @@
   MoveBefore(insert_pos);
 }
 
-HBasicBlock* HBasicBlock::SplitBefore(HInstruction* cursor) {
-  DCHECK(!graph_->IsInSsaForm()) << "Support for SSA form not implemented.";
+HBasicBlock* HBasicBlock::SplitBefore(HInstruction* cursor, bool require_graph_not_in_ssa_form) {
+  DCHECK_IMPLIES(require_graph_not_in_ssa_form, !graph_->IsInSsaForm())
+      << "Support for SSA form not implemented.";
   DCHECK_EQ(cursor->GetBlock(), this);
 
   HBasicBlock* new_block =
@@ -2376,24 +2426,6 @@
   }
 }
 
-// Should be called on instructions in a dead block in post order. This method
-// assumes `insn` has been removed from all users with the exception of catch
-// phis because of missing exceptional edges in the graph. It removes the
-// instruction from catch phi uses, together with inputs of other catch phis in
-// the catch block at the same index, as these must be dead too.
-static void RemoveUsesOfDeadInstruction(HInstruction* insn) {
-  DCHECK(!insn->HasEnvironmentUses());
-  while (insn->HasNonEnvironmentUses()) {
-    const HUseListNode<HInstruction*>& use = insn->GetUses().front();
-    size_t use_index = use.GetIndex();
-    HBasicBlock* user_block =  use.GetUser()->GetBlock();
-    DCHECK(use.GetUser()->IsPhi() && user_block->IsCatchBlock());
-    for (HInstructionIterator phi_it(user_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
-      phi_it.Current()->AsPhi()->RemoveInputAt(use_index);
-    }
-  }
-}
-
 void HBasicBlock::DisconnectAndDelete() {
   // Dominators must be removed after all the blocks they dominate. This way
   // a loop header is removed last, a requirement for correct loop information
@@ -2418,52 +2450,14 @@
   }
 
   // (2) Disconnect the block from its successors and update their phis.
-  for (HBasicBlock* successor : successors_) {
-    // Delete this block from the list of predecessors.
-    size_t this_index = successor->GetPredecessorIndexOf(this);
-    successor->predecessors_.erase(successor->predecessors_.begin() + this_index);
-
-    // Check that `successor` has other predecessors, otherwise `this` is the
-    // dominator of `successor` which violates the order DCHECKed at the top.
-    DCHECK(!successor->predecessors_.empty());
-
-    // Remove this block's entries in the successor's phis. Skip exceptional
-    // successors because catch phi inputs do not correspond to predecessor
-    // blocks but throwing instructions. The inputs of the catch phis will be
-    // updated in step (3).
-    if (!successor->IsCatchBlock()) {
-      if (successor->predecessors_.size() == 1u) {
-        // The successor has just one predecessor left. Replace phis with the only
-        // remaining input.
-        for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
-          HPhi* phi = phi_it.Current()->AsPhi();
-          phi->ReplaceWith(phi->InputAt(1 - this_index));
-          successor->RemovePhi(phi);
-        }
-      } else {
-        for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
-          phi_it.Current()->AsPhi()->RemoveInputAt(this_index);
-        }
-      }
-    }
-  }
-  successors_.clear();
+  DisconnectFromSuccessors();
 
   // (3) Remove instructions and phis. Instructions should have no remaining uses
   //     except in catch phis. If an instruction is used by a catch phi at `index`,
   //     remove `index`-th input of all phis in the catch block since they are
   //     guaranteed dead. Note that we may miss dead inputs this way but the
   //     graph will always remain consistent.
-  for (HBackwardInstructionIterator it(GetInstructions()); !it.Done(); it.Advance()) {
-    HInstruction* insn = it.Current();
-    RemoveUsesOfDeadInstruction(insn);
-    RemoveInstruction(insn);
-  }
-  for (HInstructionIterator it(GetPhis()); !it.Done(); it.Advance()) {
-    HPhi* insn = it.Current()->AsPhi();
-    RemoveUsesOfDeadInstruction(insn);
-    RemovePhi(insn);
-  }
+  RemoveCatchPhiUsesAndInstruction(/* building_dominator_tree = */ false);
 
   // (4) Disconnect the block from its predecessors and update their
   //     control-flow instructions.
@@ -2537,6 +2531,70 @@
   SetGraph(nullptr);
 }
 
+void HBasicBlock::DisconnectFromSuccessors(const ArenaBitVector* visited) {
+  for (HBasicBlock* successor : successors_) {
+    // Delete this block from the list of predecessors.
+    size_t this_index = successor->GetPredecessorIndexOf(this);
+    successor->predecessors_.erase(successor->predecessors_.begin() + this_index);
+
+    if (visited != nullptr && !visited->IsBitSet(successor->GetBlockId())) {
+      // `successor` itself is dead. Therefore, there is no need to update its phis.
+      continue;
+    }
+
+    DCHECK(!successor->predecessors_.empty());
+
+    // Remove this block's entries in the successor's phis. Skips exceptional
+    // successors because catch phi inputs do not correspond to predecessor
+    // blocks but throwing instructions. They are removed in `RemoveCatchPhiUses`.
+    if (!successor->IsCatchBlock()) {
+      if (successor->predecessors_.size() == 1u) {
+        // The successor has just one predecessor left. Replace phis with the only
+        // remaining input.
+        for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
+          HPhi* phi = phi_it.Current()->AsPhi();
+          phi->ReplaceWith(phi->InputAt(1 - this_index));
+          successor->RemovePhi(phi);
+        }
+      } else {
+        for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
+          phi_it.Current()->AsPhi()->RemoveInputAt(this_index);
+        }
+      }
+    }
+  }
+  successors_.clear();
+}
+
+void HBasicBlock::RemoveCatchPhiUsesAndInstruction(bool building_dominator_tree) {
+  for (HBackwardInstructionIterator it(GetInstructions()); !it.Done(); it.Advance()) {
+    HInstruction* insn = it.Current();
+    RemoveCatchPhiUsesOfDeadInstruction(insn);
+
+    // If we are building the dominator tree, we removed all input records previously.
+    // `RemoveInstruction` will try to remove them again but that's not something we support and we
+    // will crash. We check here since we won't be checking that in RemoveInstruction.
+    if (building_dominator_tree) {
+      DCHECK(insn->GetUses().empty());
+      DCHECK(insn->GetEnvUses().empty());
+    }
+    RemoveInstruction(insn, /* ensure_safety= */ !building_dominator_tree);
+  }
+  for (HInstructionIterator it(GetPhis()); !it.Done(); it.Advance()) {
+    HPhi* insn = it.Current()->AsPhi();
+    RemoveCatchPhiUsesOfDeadInstruction(insn);
+
+    // If we are building the dominator tree, we removed all input records previously.
+    // `RemovePhi` will try to remove them again but that's not something we support and we
+    // will crash. We check here since we won't be checking that in RemovePhi.
+    if (building_dominator_tree) {
+      DCHECK(insn->GetUses().empty());
+      DCHECK(insn->GetEnvUses().empty());
+    }
+    RemovePhi(insn, /* ensure_safety= */ !building_dominator_tree);
+  }
+}
+
 void HBasicBlock::MergeInstructionsWith(HBasicBlock* other) {
   DCHECK(EndsWithControlFlowInstruction());
   RemoveInstruction(GetLastInstruction());
@@ -2660,7 +2718,8 @@
 
 void HGraph::UpdateLoopAndTryInformationOfNewBlock(HBasicBlock* block,
                                                    HBasicBlock* reference,
-                                                   bool replace_if_back_edge) {
+                                                   bool replace_if_back_edge,
+                                                   bool has_more_specific_try_catch_info) {
   if (block->IsLoopHeader()) {
     // Clear the information of which blocks are contained in that loop. Since the
     // information is stored as a bit vector based on block ids, we have to update
@@ -2687,11 +2746,16 @@
     }
   }
 
-  // Copy TryCatchInformation if `reference` is a try block, not if it is a catch block.
-  TryCatchInformation* try_catch_info = reference->IsTryBlock()
-      ? reference->GetTryCatchInformation()
-      : nullptr;
-  block->SetTryCatchInformation(try_catch_info);
+  DCHECK_IMPLIES(has_more_specific_try_catch_info, !reference->IsTryBlock())
+      << "We don't allow to inline try catches inside of other try blocks.";
+
+  // Update the TryCatchInformation, if we are not inlining a try catch.
+  if (!has_more_specific_try_catch_info) {
+    // Copy TryCatchInformation if `reference` is a try block, not if it is a catch block.
+    TryCatchInformation* try_catch_info =
+        reference->IsTryBlock() ? reference->GetTryCatchInformation() : nullptr;
+    block->SetTryCatchInformation(try_catch_info);
+  }
 }
 
 HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) {
@@ -2730,9 +2794,15 @@
   if (HasTryCatch()) {
     outer_graph->SetHasTryCatch(true);
   }
+  if (HasMonitorOperations()) {
+    outer_graph->SetHasMonitorOperations(true);
+  }
   if (HasSIMD()) {
     outer_graph->SetHasSIMD(true);
   }
+  if (HasAlwaysThrowingInvokes()) {
+    outer_graph->SetHasAlwaysThrowingInvokes(true);
+  }
 
   HInstruction* return_value = nullptr;
   if (GetBlocks().size() == 3) {
@@ -2771,6 +2841,7 @@
 
     HBasicBlock* first = entry_block_->GetSuccessors()[0];
     DCHECK(!first->IsInLoop());
+    DCHECK(first->GetTryCatchInformation() == nullptr);
     at->MergeWithInlined(first);
     exit_block_->ReplaceWith(to);
 
@@ -2801,12 +2872,14 @@
     // and (4) to the blocks that apply.
     for (HBasicBlock* current : GetReversePostOrder()) {
       if (current != exit_block_ && current != entry_block_ && current != first) {
-        DCHECK(current->GetTryCatchInformation() == nullptr);
         DCHECK(current->GetGraph() == this);
         current->SetGraph(outer_graph);
         outer_graph->AddBlock(current);
         outer_graph->reverse_post_order_[++index_of_at] = current;
-        UpdateLoopAndTryInformationOfNewBlock(current, at,  /* replace_if_back_edge= */ false);
+        UpdateLoopAndTryInformationOfNewBlock(current,
+                                              at,
+                                              /* replace_if_back_edge= */ false,
+                                              current->GetTryCatchInformation() != nullptr);
       }
     }
 
@@ -2820,25 +2893,62 @@
 
     // Update all predecessors of the exit block (now the `to` block)
     // to not `HReturn` but `HGoto` instead. Special case throwing blocks
-    // to now get the outer graph exit block as successor. Note that the inliner
-    // currently doesn't support inlining methods with try/catch.
+    // to now get the outer graph exit block as successor.
     HPhi* return_value_phi = nullptr;
     bool rerun_dominance = false;
     bool rerun_loop_analysis = false;
     for (size_t pred = 0; pred < to->GetPredecessors().size(); ++pred) {
       HBasicBlock* predecessor = to->GetPredecessors()[pred];
       HInstruction* last = predecessor->GetLastInstruction();
+
+      // At this point we might either have:
+      // A) Return/ReturnVoid/Throw as the last instruction, or
+      // B) `Return/ReturnVoid/Throw->TryBoundary` as the last instruction chain
+
+      const bool saw_try_boundary = last->IsTryBoundary();
+      if (saw_try_boundary) {
+        DCHECK(predecessor->IsSingleTryBoundary());
+        DCHECK(!last->AsTryBoundary()->IsEntry());
+        predecessor = predecessor->GetSinglePredecessor();
+        last = predecessor->GetLastInstruction();
+      }
+
       if (last->IsThrow()) {
-        DCHECK(!at->IsTryBlock());
-        predecessor->ReplaceSuccessor(to, outer_graph->GetExitBlock());
+        if (at->IsTryBlock()) {
+          DCHECK(!saw_try_boundary) << "We don't support inlining of try blocks into try blocks.";
+          // Create a TryBoundary of kind:exit and point it to the Exit block.
+          HBasicBlock* new_block = outer_graph->SplitEdge(predecessor, to);
+          new_block->AddInstruction(
+              new (allocator) HTryBoundary(HTryBoundary::BoundaryKind::kExit, last->GetDexPc()));
+          new_block->ReplaceSuccessor(to, outer_graph->GetExitBlock());
+
+          // Copy information from the predecessor.
+          new_block->SetLoopInformation(predecessor->GetLoopInformation());
+          TryCatchInformation* try_catch_info = predecessor->GetTryCatchInformation();
+          new_block->SetTryCatchInformation(try_catch_info);
+          for (HBasicBlock* xhandler :
+               try_catch_info->GetTryEntry().GetBlock()->GetExceptionalSuccessors()) {
+            new_block->AddSuccessor(xhandler);
+          }
+          DCHECK(try_catch_info->GetTryEntry().HasSameExceptionHandlersAs(
+              *new_block->GetLastInstruction()->AsTryBoundary()));
+        } else {
+          // We either have `Throw->TryBoundary` or `Throw`. We want to point the whole chain to the
+          // exit, so we recompute `predecessor`
+          predecessor = to->GetPredecessors()[pred];
+          predecessor->ReplaceSuccessor(to, outer_graph->GetExitBlock());
+        }
+
         --pred;
         // We need to re-run dominance information, as the exit block now has
-        // a new dominator.
+        // a new predecessor and potential new dominator.
+        // TODO(solanes): See if it's worth it to hand-modify the domination chain instead of
+        // rerunning the dominance for the whole graph.
         rerun_dominance = true;
         if (predecessor->GetLoopInformation() != nullptr) {
-          // The exit block and blocks post dominated by the exit block do not belong
-          // to any loop. Because we do not compute the post dominators, we need to re-run
-          // loop analysis to get the loop information correct.
+          // The loop information might have changed e.g. `predecessor` might not be in a loop
+          // anymore. We only do this if `predecessor` has loop information as it is impossible for
+          // predecessor to end up in a loop if it wasn't in one before.
           rerun_loop_analysis = true;
         }
       } else {
@@ -2863,6 +2973,19 @@
         }
         predecessor->AddInstruction(new (allocator) HGoto(last->GetDexPc()));
         predecessor->RemoveInstruction(last);
+
+        if (saw_try_boundary) {
+          predecessor = to->GetPredecessors()[pred];
+          DCHECK(predecessor->EndsWithTryBoundary());
+          DCHECK_EQ(predecessor->GetNormalSuccessors().size(), 1u);
+          if (predecessor->GetSuccessors()[0]->GetPredecessors().size() > 1) {
+            outer_graph->SplitCriticalEdge(predecessor, to);
+            rerun_dominance = true;
+            if (predecessor->GetLoopInformation() != nullptr) {
+              rerun_loop_analysis = true;
+            }
+          }
+        }
       }
     }
     if (rerun_loop_analysis) {
@@ -3047,6 +3170,7 @@
   HSuspendCheck* suspend_check = new (allocator_) HSuspendCheck(header->GetDexPc());
   new_header->AddInstruction(suspend_check);
   new_body->AddInstruction(new (allocator_) HGoto());
+  DCHECK(loop->GetSuspendCheck() != nullptr);
   suspend_check->CopyEnvironmentFromWithLoopPhiAdjustment(
       loop->GetSuspendCheck()->GetEnvironment(), header);
 
@@ -3091,6 +3215,12 @@
   SetPackedFlag<kFlagReferenceTypeIsExact>(rti.IsExact());
 }
 
+void HInstruction::SetReferenceTypeInfoIfValid(ReferenceTypeInfo rti) {
+  if (rti.IsValid()) {
+    SetReferenceTypeInfo(rti);
+  }
+}
+
 bool HBoundType::InstructionDataEquals(const HInstruction* other) const {
   const HBoundType* other_bt = other->AsBoundType();
   ScopedObjectAccess soa(Thread::Current());
@@ -3441,8 +3571,8 @@
   return kCanThrow;
 }
 
-void HInvoke::SetResolvedMethod(ArtMethod* method) {
-  if (method != nullptr && method->IsIntrinsic()) {
+void HInvoke::SetResolvedMethod(ArtMethod* method, bool enable_intrinsic_opt) {
+  if (method != nullptr && method->IsIntrinsic() && enable_intrinsic_opt) {
     Intrinsics intrinsic = static_cast<Intrinsics>(method->GetIntrinsic());
     SetIntrinsic(intrinsic,
                  NeedsEnvironmentIntrinsic(intrinsic),
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 7a0059f..28112d1 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -29,6 +29,7 @@
 #include "base/array_ref.h"
 #include "base/intrusive_forward_list.h"
 #include "base/iteration_range.h"
+#include "base/macros.h"
 #include "base/mutex.h"
 #include "base/quasi_atomic.h"
 #include "base/stl_util.h"
@@ -51,7 +52,7 @@
 #include "mirror/method_type.h"
 #include "offsets.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArenaStack;
 class CodeGenerator;
@@ -406,6 +407,7 @@
         has_loops_(false),
         has_irreducible_loops_(false),
         has_direct_critical_native_call_(false),
+        has_always_throwing_invokes_(false),
         dead_reference_safe_(dead_reference_safe),
         debuggable_(debuggable),
         current_instruction_id_(start_instruction_id),
@@ -485,9 +487,11 @@
   // Update the loop and try membership of `block`, which was spawned from `reference`.
   // In case `reference` is a back edge, `replace_if_back_edge` notifies whether `block`
   // should be the new back edge.
+  // `has_more_specific_try_catch_info` will be set to true when inlining a try catch.
   void UpdateLoopAndTryInformationOfNewBlock(HBasicBlock* block,
                                              HBasicBlock* reference,
-                                             bool replace_if_back_edge);
+                                             bool replace_if_back_edge,
+                                             bool has_more_specific_try_catch_info = false);
 
   // Need to add a couple of blocks to test if the loop body is entered and
   // put deoptimization instructions, etc.
@@ -510,6 +514,11 @@
   HBasicBlock* SplitEdge(HBasicBlock* block, HBasicBlock* successor);
 
   void SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor);
+
+  // Splits the edge between `block` and `successor` and then updates the graph's RPO to keep
+  // consistency without recomputing the whole graph.
+  HBasicBlock* SplitEdgeAndUpdateRPO(HBasicBlock* block, HBasicBlock* successor);
+
   void OrderLoopHeaderPredecessors(HBasicBlock* header);
 
   // Transform a loop into a format with a single preheader.
@@ -678,6 +687,13 @@
     return cha_single_implementation_list_;
   }
 
+  // In case of OSR we intend to use SuspendChecks as an entry point to the
+  // function; for debuggable graphs we might deoptimize to interpreter from
+  // SuspendChecks. In these cases we should always generate code for them.
+  bool SuspendChecksAreAllowedToNoOp() const {
+    return !IsDebuggable() && !IsCompilingOsr();
+  }
+
   void AddCHASingleImplementationDependency(ArtMethod* method) {
     cha_single_implementation_list_.insert(method);
   }
@@ -704,6 +720,9 @@
   bool HasDirectCriticalNativeCall() const { return has_direct_critical_native_call_; }
   void SetHasDirectCriticalNativeCall(bool value) { has_direct_critical_native_call_ = value; }
 
+  bool HasAlwaysThrowingInvokes() const { return has_always_throwing_invokes_; }
+  void SetHasAlwaysThrowingInvokes(bool value) { has_always_throwing_invokes_ = value; }
+
   ArtMethod* GetArtMethod() const { return art_method_; }
   void SetArtMethod(ArtMethod* method) { art_method_ = method; }
 
@@ -719,12 +738,12 @@
     return ReferenceTypeInfo::Create(handle_cache_.GetObjectClassHandle(), /* is_exact= */ false);
   }
 
-  uint32_t GetNumberOfCHAGuards() { return number_of_cha_guards_; }
+  uint32_t GetNumberOfCHAGuards() const { return number_of_cha_guards_; }
   void SetNumberOfCHAGuards(uint32_t num) { number_of_cha_guards_ = num; }
   void IncrementNumberOfCHAGuards() { number_of_cha_guards_++; }
 
  private:
-  void RemoveInstructionsAsUsersFromDeadBlocks(const ArenaBitVector& visited) const;
+  void RemoveDeadBlocksInstructionsAsUsersAndDisconnect(const ArenaBitVector& visited) const;
   void RemoveDeadBlocks(const ArenaBitVector& visited);
 
   template <class InstructionType, typename ValueType>
@@ -792,14 +811,11 @@
   size_t temporaries_vreg_slots_;
 
   // Flag whether there are bounds checks in the graph. We can skip
-  // BCE if it's false. It's only best effort to keep it up to date in
-  // the presence of code elimination so there might be false positives.
+  // BCE if it's false.
   bool has_bounds_checks_;
 
   // Flag whether there are try/catch blocks in the graph. We will skip
-  // try/catch-related passes if it's false. It's only best effort to keep
-  // it up to date in the presence of code elimination so there might be
-  // false positives.
+  // try/catch-related passes if it's false.
   bool has_try_catch_;
 
   // Flag whether there are any HMonitorOperation in the graph. If yes this will mandate
@@ -812,20 +828,19 @@
   bool has_simd_;
 
   // Flag whether there are any loops in the graph. We can skip loop
-  // optimization if it's false. It's only best effort to keep it up
-  // to date in the presence of code elimination so there might be false
-  // positives.
+  // optimization if it's false.
   bool has_loops_;
 
-  // Flag whether there are any irreducible loops in the graph. It's only
-  // best effort to keep it up to date in the presence of code elimination
-  // so there might be false positives.
+  // Flag whether there are any irreducible loops in the graph.
   bool has_irreducible_loops_;
 
   // Flag whether there are any direct calls to native code registered
   // for @CriticalNative methods.
   bool has_direct_critical_native_call_;
 
+  // Flag whether the graph contains invokes that always throw.
+  bool has_always_throwing_invokes_;
+
   // Is the code known to be robust against eliminating dead references
   // and the effects of early finalization? If false, dead reference variables
   // are kept if they might be visible to the garbage collector.
@@ -1291,7 +1306,7 @@
   // graph, create a Goto at the end of the former block and will create an edge
   // between the blocks. It will not, however, update the reverse post order or
   // loop and try/catch information.
-  HBasicBlock* SplitBefore(HInstruction* cursor);
+  HBasicBlock* SplitBefore(HInstruction* cursor, bool require_graph_not_in_ssa_form = true);
 
   // Split the block into two blocks just before `cursor`. Returns the newly
   // created block. Note that this method just updates raw block information,
@@ -1332,6 +1347,20 @@
   // are safely updated.
   void DisconnectAndDelete();
 
+  // Disconnects `this` from all its successors and updates their phis, if the successors have them.
+  // If `visited` is provided, it will use the information to know if a successor is reachable and
+  // skip updating those phis.
+  void DisconnectFromSuccessors(const ArenaBitVector* visited = nullptr);
+
+  // Removes the catch phi uses of the instructions in `this`, and then remove the instruction
+  // itself. If `building_dominator_tree` is true, it will not remove the instruction as user, since
+  // we do it in a previous step. This is a special case for building up the dominator tree: we want
+  // to eliminate uses before inputs but we don't have domination information, so we remove all
+  // connections from input/uses first before removing any instruction.
+  // This method assumes the instructions have been removed from all users with the exception of
+  // catch phis because of missing exceptional edges in the graph.
+  void RemoveCatchPhiUsesAndInstruction(bool building_dominator_tree);
+
   void AddInstruction(HInstruction* instruction);
   // Insert `instruction` before/after an existing instruction `cursor`.
   void InsertInstructionBefore(HInstruction* instruction, HInstruction* cursor);
@@ -1540,10 +1569,10 @@
   M(Min, BinaryOperation)                                               \
   M(MonitorOperation, Instruction)                                      \
   M(Mul, BinaryOperation)                                               \
-  M(NativeDebugInfo, Instruction)                                       \
   M(Neg, UnaryOperation)                                                \
   M(NewArray, Instruction)                                              \
   M(NewInstance, Instruction)                                           \
+  M(Nop, Instruction)                                                   \
   M(Not, UnaryOperation)                                                \
   M(NotEqual, Condition)                                                \
   M(NullConstant, Instruction)                                          \
@@ -2348,7 +2377,10 @@
     return GetType() == DataType::Type::kReference;
   }
 
+  // Sets the ReferenceTypeInfo. The RTI must be valid.
   void SetReferenceTypeInfo(ReferenceTypeInfo rti);
+  // Same as above, but we only set it if it's valid. Otherwise, we don't change the current RTI.
+  void SetReferenceTypeInfoIfValid(ReferenceTypeInfo rti);
 
   ReferenceTypeInfo GetReferenceTypeInfo() const {
     DCHECK_EQ(GetType(), DataType::Type::kReference);
@@ -2408,7 +2440,7 @@
         !CanThrow() &&
         !IsSuspendCheck() &&
         !IsControlFlow() &&
-        !IsNativeDebugInfo() &&
+        !IsNop() &&
         !IsParameterValue() &&
         // If we added an explicit barrier then we should keep it.
         !IsMemoryBarrier() &&
@@ -2419,9 +2451,12 @@
     return IsRemovable() && !HasUses();
   }
 
-  // Does this instruction strictly dominate `other_instruction`?
-  // Returns false if this instruction and `other_instruction` are the same.
-  // Aborts if this instruction and `other_instruction` are both phis.
+  // Does this instruction dominate `other_instruction`?
+  // Aborts if this instruction and `other_instruction` are different phis.
+  bool Dominates(HInstruction* other_instruction) const;
+
+  // Same but with `strictly dominates` i.e. returns false if this instruction and
+  // `other_instruction` are the same.
   bool StrictlyDominates(HInstruction* other_instruction) const;
 
   int GetId() const { return id_; }
@@ -2486,7 +2521,9 @@
   void SetLocations(LocationSummary* locations) { locations_ = locations; }
 
   void ReplaceWith(HInstruction* instruction);
-  void ReplaceUsesDominatedBy(HInstruction* dominator, HInstruction* replacement);
+  void ReplaceUsesDominatedBy(HInstruction* dominator,
+                              HInstruction* replacement,
+                              bool strictly_dominated = true);
   void ReplaceEnvUsesDominatedBy(HInstruction* dominator, HInstruction* replacement);
   void ReplaceInput(HInstruction* replacement, size_t index);
 
@@ -3730,7 +3767,7 @@
   static constexpr size_t kNumberOfClassTableGetPackedBits = kFieldTableKind + kFieldTableKindSize;
   static_assert(kNumberOfClassTableGetPackedBits <= kMaxNumberOfPackedBits,
                 "Too many packed fields.");
-  using TableKindField = BitField<TableKind, kFieldTableKind, kFieldTableKind>;
+  using TableKindField = BitField<TableKind, kFieldTableKind, kFieldTableKindSize>;
 
   // The index of the ArtMethod in the table.
   const size_t index_;
@@ -4700,7 +4737,7 @@
 
   void SetAlwaysThrows(bool always_throws) { SetPackedFlag<kFlagAlwaysThrows>(always_throws); }
 
-  bool AlwaysThrows() const override { return GetPackedFlag<kFlagAlwaysThrows>(); }
+  bool AlwaysThrows() const override final { return GetPackedFlag<kFlagAlwaysThrows>(); }
 
   bool CanBeMoved() const override { return IsIntrinsic() && !DoesAnyWrite(); }
 
@@ -4719,7 +4756,7 @@
   bool IsIntrinsic() const { return intrinsic_ != Intrinsics::kNone; }
 
   ArtMethod* GetResolvedMethod() const { return resolved_method_; }
-  void SetResolvedMethod(ArtMethod* method);
+  void SetResolvedMethod(ArtMethod* method, bool enable_intrinsic_opt);
 
   MethodReference GetMethodReference() const { return method_reference_; }
 
@@ -4748,7 +4785,8 @@
           MethodReference method_reference,
           ArtMethod* resolved_method,
           MethodReference resolved_method_reference,
-          InvokeType invoke_type)
+          InvokeType invoke_type,
+          bool enable_intrinsic_opt)
     : HVariableInputSizeInstruction(
           kind,
           return_type,
@@ -4764,7 +4802,7 @@
       intrinsic_optimizations_(0) {
     SetPackedField<InvokeTypeField>(invoke_type);
     SetPackedFlag<kFlagCanThrow>(true);
-    SetResolvedMethod(resolved_method);
+    SetResolvedMethod(resolved_method, enable_intrinsic_opt);
   }
 
   DEFAULT_COPY_CONSTRUCTOR(Invoke);
@@ -4797,7 +4835,8 @@
                 method_reference,
                 nullptr,
                 MethodReference(nullptr, 0u),
-                invoke_type) {
+                invoke_type,
+                /* enable_intrinsic_opt= */ false) {
   }
 
   bool IsClonable() const override { return true; }
@@ -4820,7 +4859,8 @@
                      // to pass intrinsic information to the HInvokePolymorphic node.
                      ArtMethod* resolved_method,
                      MethodReference resolved_method_reference,
-                     dex::ProtoIndex proto_idx)
+                     dex::ProtoIndex proto_idx,
+                     bool enable_intrinsic_opt)
       : HInvoke(kInvokePolymorphic,
                 allocator,
                 number_of_arguments,
@@ -4830,7 +4870,8 @@
                 method_reference,
                 resolved_method,
                 resolved_method_reference,
-                kPolymorphic),
+                kPolymorphic,
+                enable_intrinsic_opt),
         proto_idx_(proto_idx) {
   }
 
@@ -4852,7 +4893,8 @@
                 uint32_t call_site_index,
                 DataType::Type return_type,
                 uint32_t dex_pc,
-                MethodReference method_reference)
+                MethodReference method_reference,
+                bool enable_intrinsic_opt)
       : HInvoke(kInvokeCustom,
                 allocator,
                 number_of_arguments,
@@ -4862,7 +4904,8 @@
                 method_reference,
                 /* resolved_method= */ nullptr,
                 MethodReference(nullptr, 0u),
-                kStatic),
+                kStatic,
+                enable_intrinsic_opt),
       call_site_index_(call_site_index) {
   }
 
@@ -4909,7 +4952,8 @@
                         DispatchInfo dispatch_info,
                         InvokeType invoke_type,
                         MethodReference resolved_method_reference,
-                        ClinitCheckRequirement clinit_check_requirement)
+                        ClinitCheckRequirement clinit_check_requirement,
+                        bool enable_intrinsic_opt)
       : HInvoke(kInvokeStaticOrDirect,
                 allocator,
                 number_of_arguments,
@@ -4922,7 +4966,8 @@
                 method_reference,
                 resolved_method,
                 resolved_method_reference,
-                invoke_type),
+                invoke_type,
+                enable_intrinsic_opt),
         dispatch_info_(dispatch_info) {
     SetPackedField<ClinitCheckRequirementField>(clinit_check_requirement);
   }
@@ -5134,7 +5179,8 @@
                  MethodReference method_reference,
                  ArtMethod* resolved_method,
                  MethodReference resolved_method_reference,
-                 uint32_t vtable_index)
+                 uint32_t vtable_index,
+                 bool enable_intrinsic_opt)
       : HInvoke(kInvokeVirtual,
                 allocator,
                 number_of_arguments,
@@ -5144,7 +5190,8 @@
                 method_reference,
                 resolved_method,
                 resolved_method_reference,
-                kVirtual),
+                kVirtual,
+                enable_intrinsic_opt),
         vtable_index_(vtable_index) {
   }
 
@@ -5196,7 +5243,8 @@
                    ArtMethod* resolved_method,
                    MethodReference resolved_method_reference,
                    uint32_t imt_index,
-                   MethodLoadKind load_kind)
+                   MethodLoadKind load_kind,
+                   bool enable_intrinsic_opt)
       : HInvoke(kInvokeInterface,
                 allocator,
                 number_of_arguments + (NeedsCurrentMethod(load_kind) ? 1 : 0),
@@ -5206,7 +5254,8 @@
                 method_reference,
                 resolved_method,
                 resolved_method_reference,
-                kInterface),
+                kInterface,
+                enable_intrinsic_opt),
         imt_index_(imt_index),
         hidden_argument_load_kind_(load_kind) {
   }
@@ -5321,7 +5370,7 @@
       kFieldComponentSizeShift + kFieldComponentSizeShiftSize;
   static_assert(kNumberOfNewArrayPackedBits <= kMaxNumberOfPackedBits, "Too many packed fields.");
   using ComponentSizeShiftField =
-      BitField<size_t, kFieldComponentSizeShift, kFieldComponentSizeShift>;
+      BitField<size_t, kFieldComponentSizeShift, kFieldComponentSizeShiftSize>;
 };
 
 class HAdd final : public HBinaryOperation {
@@ -6362,6 +6411,27 @@
   const FieldInfo field_info_;
 };
 
+enum class WriteBarrierKind {
+  // Emit the write barrier, with a runtime optimization which checks if the value that it is being
+  // set is null.
+  kEmitWithNullCheck,
+  // Emit the write barrier, without the runtime null check optimization. This could be set because:
+  //  A) It is a write barrier for an ArraySet (which does the optimization with the type check, so
+  //  it never does the optimization at the write barrier stage)
+  //  B) We know that the input can't be null
+  //  C) This write barrier is actually several write barriers coalesced into one. Potentially we
+  //  could ask if every value is null for a runtime optimization at the cost of compile time / code
+  //  size. At the time of writing it was deemed not worth the effort.
+  kEmitNoNullCheck,
+  // Skip emitting the write barrier. This could be set because:
+  //  A) The write barrier is not needed (e.g. it is not a reference, or the value is the null
+  //  constant)
+  //  B) This write barrier was coalesced into another one so there's no need to emit it.
+  kDontEmit,
+  kLast = kDontEmit
+};
+std::ostream& operator<<(std::ostream& os, WriteBarrierKind rhs);
+
 class HInstanceFieldSet final : public HExpression<2> {
  public:
   HInstanceFieldSet(HInstruction* object,
@@ -6386,6 +6456,7 @@
                     dex_file) {
     SetPackedFlag<kFlagValueCanBeNull>(true);
     SetPackedFlag<kFlagIsPredicatedSet>(false);
+    SetPackedField<WriteBarrierKindField>(WriteBarrierKind::kEmitWithNullCheck);
     SetRawInputAt(0, object);
     SetRawInputAt(1, value);
   }
@@ -6406,6 +6477,12 @@
   void ClearValueCanBeNull() { SetPackedFlag<kFlagValueCanBeNull>(false); }
   bool GetIsPredicatedSet() const { return GetPackedFlag<kFlagIsPredicatedSet>(); }
   void SetIsPredicatedSet(bool value = true) { SetPackedFlag<kFlagIsPredicatedSet>(value); }
+  WriteBarrierKind GetWriteBarrierKind() { return GetPackedField<WriteBarrierKindField>(); }
+  void SetWriteBarrierKind(WriteBarrierKind kind) {
+    DCHECK(kind != WriteBarrierKind::kEmitWithNullCheck)
+        << "We shouldn't go back to the original value.";
+    SetPackedField<WriteBarrierKindField>(kind);
+  }
 
   DECLARE_INSTRUCTION(InstanceFieldSet);
 
@@ -6415,11 +6492,17 @@
  private:
   static constexpr size_t kFlagValueCanBeNull = kNumberOfGenericPackedBits;
   static constexpr size_t kFlagIsPredicatedSet = kFlagValueCanBeNull + 1;
-  static constexpr size_t kNumberOfInstanceFieldSetPackedBits = kFlagIsPredicatedSet + 1;
+  static constexpr size_t kWriteBarrierKind = kFlagIsPredicatedSet + 1;
+  static constexpr size_t kWriteBarrierKindSize =
+      MinimumBitsToStore(static_cast<size_t>(WriteBarrierKind::kLast));
+  static constexpr size_t kNumberOfInstanceFieldSetPackedBits =
+      kWriteBarrierKind + kWriteBarrierKindSize;
   static_assert(kNumberOfInstanceFieldSetPackedBits <= kMaxNumberOfPackedBits,
                 "Too many packed fields.");
 
   const FieldInfo field_info_;
+  using WriteBarrierKindField =
+      BitField<WriteBarrierKind, kWriteBarrierKind, kWriteBarrierKindSize>;
 };
 
 class HArrayGet final : public HExpression<2> {
@@ -6540,6 +6623,8 @@
     SetPackedFlag<kFlagNeedsTypeCheck>(value->GetType() == DataType::Type::kReference);
     SetPackedFlag<kFlagValueCanBeNull>(true);
     SetPackedFlag<kFlagStaticTypeOfArrayIsObjectArray>(false);
+    // ArraySets never do the null check optimization at the write barrier stage.
+    SetPackedField<WriteBarrierKindField>(WriteBarrierKind::kEmitNoNullCheck);
     SetRawInputAt(0, array);
     SetRawInputAt(1, index);
     SetRawInputAt(2, value);
@@ -6560,8 +6645,10 @@
     return false;
   }
 
-  void ClearNeedsTypeCheck() {
+  void ClearTypeCheck() {
     SetPackedFlag<kFlagNeedsTypeCheck>(false);
+    // Clear the `CanTriggerGC` flag too as we can only trigger a GC when doing a type check.
+    SetSideEffects(GetSideEffects().Exclusion(SideEffects::CanTriggerGC()));
   }
 
   void ClearValueCanBeNull() {
@@ -6610,6 +6697,16 @@
                                                       : SideEffects::None();
   }
 
+  WriteBarrierKind GetWriteBarrierKind() { return GetPackedField<WriteBarrierKindField>(); }
+
+  void SetWriteBarrierKind(WriteBarrierKind kind) {
+    DCHECK(kind != WriteBarrierKind::kEmitNoNullCheck)
+        << "We shouldn't go back to the original value.";
+    DCHECK(kind != WriteBarrierKind::kEmitWithNullCheck)
+        << "We never do the null check optimization for ArraySets.";
+    SetPackedField<WriteBarrierKindField>(kind);
+  }
+
   DECLARE_INSTRUCTION(ArraySet);
 
  protected:
@@ -6625,11 +6722,16 @@
   // Cached information for the reference_type_info_ so that codegen
   // does not need to inspect the static type.
   static constexpr size_t kFlagStaticTypeOfArrayIsObjectArray = kFlagValueCanBeNull + 1;
-  static constexpr size_t kNumberOfArraySetPackedBits =
-      kFlagStaticTypeOfArrayIsObjectArray + 1;
+  static constexpr size_t kWriteBarrierKind = kFlagStaticTypeOfArrayIsObjectArray + 1;
+  static constexpr size_t kWriteBarrierKindSize =
+      MinimumBitsToStore(static_cast<size_t>(WriteBarrierKind::kLast));
+  static constexpr size_t kNumberOfArraySetPackedBits = kWriteBarrierKind + kWriteBarrierKindSize;
   static_assert(kNumberOfArraySetPackedBits <= kMaxNumberOfPackedBits, "Too many packed fields.");
   using ExpectedComponentTypeField =
       BitField<DataType::Type, kFieldExpectedComponentType, kFieldExpectedComponentTypeSize>;
+
+  using WriteBarrierKindField =
+      BitField<WriteBarrierKind, kWriteBarrierKind, kWriteBarrierKindSize>;
 };
 
 class HArrayLength final : public HExpression<1> {
@@ -6714,9 +6816,10 @@
 
 class HSuspendCheck final : public HExpression<0> {
  public:
-  explicit HSuspendCheck(uint32_t dex_pc = kNoDexPc)
+  explicit HSuspendCheck(uint32_t dex_pc = kNoDexPc, bool is_no_op = false)
       : HExpression(kSuspendCheck, SideEffects::CanTriggerGC(), dex_pc),
         slow_path_(nullptr) {
+    SetPackedFlag<kFlagIsNoOp>(is_no_op);
   }
 
   bool IsClonable() const override { return true; }
@@ -6725,6 +6828,10 @@
     return true;
   }
 
+  void SetIsNoOp(bool is_no_op) { SetPackedFlag<kFlagIsNoOp>(is_no_op); }
+  bool IsNoOp() const { return GetPackedFlag<kFlagIsNoOp>(); }
+
+
   void SetSlowPath(SlowPathCode* slow_path) { slow_path_ = slow_path; }
   SlowPathCode* GetSlowPath() const { return slow_path_; }
 
@@ -6733,28 +6840,42 @@
  protected:
   DEFAULT_COPY_CONSTRUCTOR(SuspendCheck);
 
+  // True if the HSuspendCheck should not emit any code during codegen. It is
+  // not possible to simply remove this instruction to disable codegen, as
+  // other optimizations (e.g: CHAGuardVisitor::HoistGuard) depend on
+  // HSuspendCheck being present in every loop.
+  static constexpr size_t kFlagIsNoOp = kNumberOfGenericPackedBits;
+  static constexpr size_t kNumberOfSuspendCheckPackedBits = kFlagIsNoOp + 1;
+  static_assert(kNumberOfSuspendCheckPackedBits <= HInstruction::kMaxNumberOfPackedBits,
+                "Too many packed fields.");
+
  private:
   // Only used for code generation, in order to share the same slow path between back edges
   // of a same loop.
   SlowPathCode* slow_path_;
 };
 
-// Pseudo-instruction which provides the native debugger with mapping information.
-// It ensures that we can generate line number and local variables at this point.
-class HNativeDebugInfo : public HExpression<0> {
+// Pseudo-instruction which doesn't generate any code.
+// If `emit_environment` is true, it can be used to generate an environment. It is used, for
+// example, to provide the native debugger with mapping information. It ensures that we can generate
+// line number and local variables at this point.
+class HNop : public HExpression<0> {
  public:
-  explicit HNativeDebugInfo(uint32_t dex_pc)
-      : HExpression<0>(kNativeDebugInfo, SideEffects::None(), dex_pc) {
+  explicit HNop(uint32_t dex_pc, bool needs_environment)
+      : HExpression<0>(kNop, SideEffects::None(), dex_pc), needs_environment_(needs_environment) {
   }
 
   bool NeedsEnvironment() const override {
-    return true;
+    return needs_environment_;
   }
 
-  DECLARE_INSTRUCTION(NativeDebugInfo);
+  DECLARE_INSTRUCTION(Nop);
 
  protected:
-  DEFAULT_COPY_CONSTRUCTOR(NativeDebugInfo);
+  DEFAULT_COPY_CONSTRUCTOR(Nop);
+
+ private:
+  bool needs_environment_;
 };
 
 /**
@@ -7222,6 +7343,10 @@
     return SideEffects::CanTriggerGC();
   }
 
+  bool CanThrow() const override { return true; }
+
+  bool NeedsEnvironment() const override { return true; }
+
   DECLARE_INSTRUCTION(LoadMethodHandle);
 
  protected:
@@ -7266,6 +7391,10 @@
     return SideEffects::CanTriggerGC();
   }
 
+  bool CanThrow() const override { return true; }
+
+  bool NeedsEnvironment() const override { return true; }
+
   DECLARE_INSTRUCTION(LoadMethodType);
 
  protected:
@@ -7400,6 +7529,7 @@
                     declaring_class_def_index,
                     dex_file) {
     SetPackedFlag<kFlagValueCanBeNull>(true);
+    SetPackedField<WriteBarrierKindField>(WriteBarrierKind::kEmitWithNullCheck);
     SetRawInputAt(0, cls);
     SetRawInputAt(1, value);
   }
@@ -7415,6 +7545,13 @@
   bool GetValueCanBeNull() const { return GetPackedFlag<kFlagValueCanBeNull>(); }
   void ClearValueCanBeNull() { SetPackedFlag<kFlagValueCanBeNull>(false); }
 
+  WriteBarrierKind GetWriteBarrierKind() { return GetPackedField<WriteBarrierKindField>(); }
+  void SetWriteBarrierKind(WriteBarrierKind kind) {
+    DCHECK(kind != WriteBarrierKind::kEmitWithNullCheck)
+        << "We shouldn't go back to the original value.";
+    SetPackedField<WriteBarrierKindField>(kind);
+  }
+
   DECLARE_INSTRUCTION(StaticFieldSet);
 
  protected:
@@ -7422,25 +7559,34 @@
 
  private:
   static constexpr size_t kFlagValueCanBeNull = kNumberOfGenericPackedBits;
-  static constexpr size_t kNumberOfStaticFieldSetPackedBits = kFlagValueCanBeNull + 1;
+  static constexpr size_t kWriteBarrierKind = kFlagValueCanBeNull + 1;
+  static constexpr size_t kWriteBarrierKindSize =
+      MinimumBitsToStore(static_cast<size_t>(WriteBarrierKind::kLast));
+  static constexpr size_t kNumberOfStaticFieldSetPackedBits =
+      kWriteBarrierKind + kWriteBarrierKindSize;
   static_assert(kNumberOfStaticFieldSetPackedBits <= kMaxNumberOfPackedBits,
                 "Too many packed fields.");
 
   const FieldInfo field_info_;
+  using WriteBarrierKindField =
+      BitField<WriteBarrierKind, kWriteBarrierKind, kWriteBarrierKindSize>;
 };
 
 class HStringBuilderAppend final : public HVariableInputSizeInstruction {
  public:
   HStringBuilderAppend(HIntConstant* format,
                        uint32_t number_of_arguments,
+                       bool has_fp_args,
                        ArenaAllocator* allocator,
                        uint32_t dex_pc)
       : HVariableInputSizeInstruction(
             kStringBuilderAppend,
             DataType::Type::kReference,
-            // The runtime call may read memory from inputs. It never writes outside
-            // of the newly allocated result object (or newly allocated helper objects).
-            SideEffects::AllReads().Union(SideEffects::CanTriggerGC()),
+            SideEffects::CanTriggerGC().Union(
+                // The runtime call may read memory from inputs. It never writes outside
+                // of the newly allocated result object or newly allocated helper objects,
+                // except for float/double arguments where we reuse thread-local helper objects.
+                has_fp_args ? SideEffects::AllWritesAndReads() : SideEffects::AllReads()),
             dex_pc,
             allocator,
             number_of_arguments + /* format */ 1u,
@@ -8393,7 +8539,7 @@
 #include "nodes_x86.h"
 #endif
 
-namespace art {
+namespace art HIDDEN {
 
 class OptimizingCompilerStats;
 
@@ -8457,7 +8603,7 @@
 // Create a clone for each clonable instructions/phis and replace the original with the clone.
 //
 // Used for testing individual instruction cloner.
-class CloneAndReplaceInstructionVisitor : public HGraphDelegateVisitor {
+class CloneAndReplaceInstructionVisitor final : public HGraphDelegateVisitor {
  public:
   explicit CloneAndReplaceInstructionVisitor(HGraph* graph)
       : HGraphDelegateVisitor(graph), instr_replaced_by_clones_count_(0) {}
diff --git a/compiler/optimizing/nodes_shared.cc b/compiler/optimizing/nodes_shared.cc
index eca97d7..b3a7ad9 100644
--- a/compiler/optimizing/nodes_shared.cc
+++ b/compiler/optimizing/nodes_shared.cc
@@ -23,7 +23,7 @@
 
 #include "instruction_simplifier_shared.h"
 
-namespace art {
+namespace art HIDDEN {
 
 using helpers::CanFitInShifterOperand;
 
diff --git a/compiler/optimizing/nodes_shared.h b/compiler/optimizing/nodes_shared.h
index 7dcac17..27e6103 100644
--- a/compiler/optimizing/nodes_shared.h
+++ b/compiler/optimizing/nodes_shared.h
@@ -22,7 +22,7 @@
 // (defining `HInstruction` and co).
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class HMultiplyAccumulate final : public HExpression<3> {
  public:
diff --git a/compiler/optimizing/nodes_test.cc b/compiler/optimizing/nodes_test.cc
index 34f0e9b..29210fe 100644
--- a/compiler/optimizing/nodes_test.cc
+++ b/compiler/optimizing/nodes_test.cc
@@ -17,11 +17,12 @@
 #include "nodes.h"
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "optimizing_unit_test.h"
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class NodeTest : public OptimizingUnitTest {};
 
diff --git a/compiler/optimizing/nodes_vector.h b/compiler/optimizing/nodes_vector.h
index a2cd86d..73f6c40 100644
--- a/compiler/optimizing/nodes_vector.h
+++ b/compiler/optimizing/nodes_vector.h
@@ -21,7 +21,7 @@
 // is included in the header file nodes.h itself. However it gives editing tools better context.
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Memory alignment, represented as an offset relative to a base, where 0 <= offset < base,
 // and base is a power of two. For example, the value Alignment(16, 0) means memory is
diff --git a/compiler/optimizing/nodes_vector_test.cc b/compiler/optimizing/nodes_vector_test.cc
index b0a665d..e0a48db 100644
--- a/compiler/optimizing/nodes_vector_test.cc
+++ b/compiler/optimizing/nodes_vector_test.cc
@@ -15,10 +15,11 @@
  */
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimizing_unit_test.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Fixture class for testing vector nodes.
diff --git a/compiler/optimizing/nodes_x86.h b/compiler/optimizing/nodes_x86.h
index 8e8fbc1..e246390 100644
--- a/compiler/optimizing/nodes_x86.h
+++ b/compiler/optimizing/nodes_x86.h
@@ -17,7 +17,7 @@
 #ifndef ART_COMPILER_OPTIMIZING_NODES_X86_H_
 #define ART_COMPILER_OPTIMIZING_NODES_X86_H_
 
-namespace art {
+namespace art HIDDEN {
 
 // Compute the address of the method for X86 Constant area support.
 class HX86ComputeBaseMethodAddress final : public HExpression<0> {
diff --git a/compiler/optimizing/optimization.cc b/compiler/optimizing/optimization.cc
index 2cac38b..12e9a10 100644
--- a/compiler/optimizing/optimization.cc
+++ b/compiler/optimizing/optimization.cc
@@ -55,10 +55,11 @@
 #include "select_generator.h"
 #include "sharpening.h"
 #include "side_effects_analysis.h"
+#include "write_barrier_elimination.h"
 
 // Decide between default or alternative pass name.
 
-namespace art {
+namespace art HIDDEN {
 
 const char* OptimizationPassName(OptimizationPass pass) {
   switch (pass) {
@@ -76,6 +77,7 @@
       return BoundsCheckElimination::kBoundsCheckEliminationPassName;
     case OptimizationPass::kLoadStoreElimination:
       return LoadStoreElimination::kLoadStoreEliminationPassName;
+    case OptimizationPass::kAggressiveConstantFolding:
     case OptimizationPass::kConstantFolding:
       return HConstantFolding::kConstantFoldingPassName;
     case OptimizationPass::kDeadCodeElimination:
@@ -95,6 +97,8 @@
       return ConstructorFenceRedundancyElimination::kCFREPassName;
     case OptimizationPass::kScheduling:
       return HInstructionScheduling::kInstructionSchedulingPassName;
+    case OptimizationPass::kWriteBarrierElimination:
+      return WriteBarrierElimination::kWBEPassName;
 #ifdef ART_ENABLE_CODEGEN_arm
     case OptimizationPass::kInstructionSimplifierArm:
       return arm::InstructionSimplifierArm::kInstructionSimplifierArmPassName;
@@ -194,7 +198,8 @@
         opt = most_recent_side_effects = new (allocator) SideEffectsAnalysis(graph, pass_name);
         break;
       case OptimizationPass::kInductionVarAnalysis:
-        opt = most_recent_induction = new (allocator) HInductionVarAnalysis(graph, pass_name);
+        opt = most_recent_induction =
+            new (allocator) HInductionVarAnalysis(graph, stats, pass_name);
         break;
       //
       // Passes that need prior analysis.
@@ -221,7 +226,11 @@
       // Regular passes.
       //
       case OptimizationPass::kConstantFolding:
-        opt = new (allocator) HConstantFolding(graph, pass_name);
+        opt = new (allocator) HConstantFolding(graph, stats, pass_name);
+        break;
+      case OptimizationPass::kAggressiveConstantFolding:
+        opt = new (allocator)
+            HConstantFolding(graph, stats, pass_name, /* use_all_optimizations_ = */ true);
         break;
       case OptimizationPass::kDeadCodeElimination:
         opt = new (allocator) HDeadCodeElimination(graph, stats, pass_name);
@@ -239,6 +248,7 @@
                                        /* total_number_of_instructions= */ 0,
                                        /* parent= */ nullptr,
                                        /* depth= */ 0,
+                                       /* try_catch_inlining_allowed= */ true,
                                        pass_name);
         break;
       }
@@ -267,6 +277,9 @@
       case OptimizationPass::kLoadStoreElimination:
         opt = new (allocator) LoadStoreElimination(graph, stats, pass_name);
         break;
+      case OptimizationPass::kWriteBarrierElimination:
+        opt = new (allocator) WriteBarrierElimination(graph, stats, pass_name);
+        break;
       case OptimizationPass::kScheduling:
         opt = new (allocator) HInstructionScheduling(
             graph, codegen->GetCompilerOptions().GetInstructionSet(), codegen, pass_name);
diff --git a/compiler/optimizing/optimization.h b/compiler/optimizing/optimization.h
index 2113df0..134e3cd 100644
--- a/compiler/optimizing/optimization.h
+++ b/compiler/optimizing/optimization.h
@@ -18,10 +18,11 @@
 #define ART_COMPILER_OPTIMIZING_OPTIMIZATION_H_
 
 #include "base/arena_object.h"
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimizing_compiler_stats.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 class DexCompilationUnit;
@@ -42,7 +43,7 @@
 
   // Return the name of the pass. Pass names for a single HOptimization should be of form
   // <optimization_name> or <optimization_name>$<pass_name> for common <optimization_name> prefix.
-  // Example: 'instruction_simplifier', 'instruction_simplifier$after_bce',
+  // Example: 'instruction_simplifier', 'instruction_simplifier$before_codegen',
   // 'instruction_simplifier$before_codegen'.
   const char* GetPassName() const { return pass_name_; }
 
@@ -66,6 +67,7 @@
 // field is preferred over a string lookup at places where performance matters.
 // TODO: generate this table and lookup methods below automatically?
 enum class OptimizationPass {
+  kAggressiveConstantFolding,
   kAggressiveInstructionSimplifier,
   kBoundsCheckElimination,
   kCHAGuardOptimization,
@@ -83,6 +85,7 @@
   kScheduling,
   kSelectGenerator,
   kSideEffectsAnalysis,
+  kWriteBarrierElimination,
 #ifdef ART_ENABLE_CODEGEN_arm
   kInstructionSimplifierArm,
   kCriticalNativeAbiFixupArm,
diff --git a/compiler/optimizing/optimizing_cfi_test.cc b/compiler/optimizing/optimizing_cfi_test.cc
index bad540e..f12e748 100644
--- a/compiler/optimizing/optimizing_cfi_test.cc
+++ b/compiler/optimizing/optimizing_cfi_test.cc
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "arch/instruction_set.h"
+#include "base/macros.h"
 #include "base/runtime_debug.h"
 #include "cfi_test.h"
 #include "driver/compiler_options.h"
@@ -32,7 +33,7 @@
 
 namespace vixl32 = vixl::aarch32;
 
-namespace art {
+namespace art HIDDEN {
 
 // Run the tests only on host.
 #ifndef ART_TARGET_ANDROID
@@ -167,9 +168,20 @@
 // barrier configuration, and as such is removed from the set of
 // callee-save registers in the ARM64 code generator of the Optimizing
 // compiler.
-#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
-TEST_ISA(kArm64)
-#endif
+//
+// We can't use compile-time macros for read-barrier as the introduction
+// of userfaultfd-GC has made it a runtime choice.
+TEST_F(OptimizingCFITest, kArm64) {
+  if (kUseBakerReadBarrier && gUseReadBarrier) {
+    std::vector<uint8_t> expected_asm(
+        expected_asm_kArm64,
+        expected_asm_kArm64 + arraysize(expected_asm_kArm64));
+    std::vector<uint8_t> expected_cfi(
+        expected_cfi_kArm64,
+        expected_cfi_kArm64 + arraysize(expected_cfi_kArm64));
+    TestImpl(InstructionSet::kArm64, "kArm64", expected_asm, expected_cfi);
+  }
+}
 #endif
 
 #ifdef ART_ENABLE_CODEGEN_x86
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 6eb3d01..00eb6e5 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -33,12 +33,11 @@
 #include "base/timing_logger.h"
 #include "builder.h"
 #include "code_generator.h"
-#include "compiled_method.h"
 #include "compiler.h"
 #include "debug/elf_debug_writer.h"
 #include "debug/method_debug_info.h"
 #include "dex/dex_file_types.h"
-#include "driver/compiled_method_storage.h"
+#include "driver/compiled_code_storage.h"
 #include "driver/compiler_options.h"
 #include "driver/dex_compilation_unit.h"
 #include "graph_checker.h"
@@ -52,6 +51,7 @@
 #include "linker/linker_patch.h"
 #include "nodes.h"
 #include "oat_quick_method_header.h"
+#include "optimizing/write_barrier_elimination.h"
 #include "prepare_for_register_allocation.h"
 #include "reference_type_propagation.h"
 #include "register_allocator_linear_scan.h"
@@ -62,7 +62,7 @@
 #include "stack_map_stream.h"
 #include "utils/assembler.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static constexpr size_t kArenaAllocatorMemoryReportThreshold = 8 * MB;
 
@@ -269,7 +269,7 @@
 class OptimizingCompiler final : public Compiler {
  public:
   explicit OptimizingCompiler(const CompilerOptions& compiler_options,
-                              CompiledMethodStorage* storage);
+                              CompiledCodeStorage* storage);
   ~OptimizingCompiler() override;
 
   bool CanCompileMethod(uint32_t method_idx, const DexFile& dex_file) const override;
@@ -359,11 +359,11 @@
                         const DexCompilationUnit& dex_compilation_unit,
                         PassObserver* pass_observer) const;
 
- private:
   // Create a 'CompiledMethod' for an optimized graph.
   CompiledMethod* Emit(ArenaAllocator* allocator,
                        CodeVectorAllocator* code_allocator,
                        CodeGenerator* codegen,
+                       bool is_intrinsic,
                        const dex::CodeItem* item) const;
 
   // Try compiling a method and return the code generator used for
@@ -413,7 +413,7 @@
 static const int kMaximumCompilationTimeBeforeWarning = 100; /* ms */
 
 OptimizingCompiler::OptimizingCompiler(const CompilerOptions& compiler_options,
-                                       CompiledMethodStorage* storage)
+                                       CompiledCodeStorage* storage)
     : Compiler(compiler_options, storage, kMaximumCompilationTimeBeforeWarning) {
   // Enable C1visualizer output.
   const std::string& cfg_file_name = compiler_options.GetDumpCfgFileName();
@@ -568,6 +568,9 @@
     }
 #endif
     default:
+      UNUSED(graph);
+      UNUSED(dex_compilation_unit);
+      UNUSED(pass_observer);
       return false;
   }
 }
@@ -653,7 +656,7 @@
     OptDef(OptimizationPass::kGlobalValueNumbering),
     // Simplification (TODO: only if GVN occurred).
     OptDef(OptimizationPass::kSelectGenerator),
-    OptDef(OptimizationPass::kConstantFolding,
+    OptDef(OptimizationPass::kAggressiveConstantFolding,
            "constant_folding$after_gvn"),
     OptDef(OptimizationPass::kInstructionSimplifier,
            "instruction_simplifier$after_gvn"),
@@ -668,20 +671,27 @@
     OptDef(OptimizationPass::kLoopOptimization),
     // Simplification.
     OptDef(OptimizationPass::kConstantFolding,
-           "constant_folding$after_bce"),
+           "constant_folding$after_loop_opt"),
     OptDef(OptimizationPass::kAggressiveInstructionSimplifier,
-           "instruction_simplifier$after_bce"),
+           "instruction_simplifier$after_loop_opt"),
+    OptDef(OptimizationPass::kDeadCodeElimination,
+           "dead_code_elimination$after_loop_opt"),
     // Other high-level optimizations.
     OptDef(OptimizationPass::kLoadStoreElimination),
     OptDef(OptimizationPass::kCHAGuardOptimization),
-    OptDef(OptimizationPass::kDeadCodeElimination,
-           "dead_code_elimination$final"),
     OptDef(OptimizationPass::kCodeSinking),
+    // Simplification.
+    OptDef(OptimizationPass::kConstantFolding,
+           "constant_folding$before_codegen"),
     // The codegen has a few assumptions that only the instruction simplifier
     // can satisfy. For example, the code generator does not expect to see a
     // HTypeConversion from a type to the same type.
     OptDef(OptimizationPass::kAggressiveInstructionSimplifier,
            "instruction_simplifier$before_codegen"),
+    // Simplification may result in dead code that should be removed prior to
+    // code generation.
+    OptDef(OptimizationPass::kDeadCodeElimination,
+           "dead_code_elimination$before_codegen"),
     // Eliminate constructor fences after code sinking to avoid
     // complicated sinking logic to split a fence with many inputs.
     OptDef(OptimizationPass::kConstructorFenceRedundancyElimination)
@@ -711,18 +721,19 @@
 CompiledMethod* OptimizingCompiler::Emit(ArenaAllocator* allocator,
                                          CodeVectorAllocator* code_allocator,
                                          CodeGenerator* codegen,
+                                         bool is_intrinsic,
                                          const dex::CodeItem* code_item_for_osr_check) const {
   ArenaVector<linker::LinkerPatch> linker_patches = EmitAndSortLinkerPatches(codegen);
   ScopedArenaVector<uint8_t> stack_map = codegen->BuildStackMaps(code_item_for_osr_check);
 
-  CompiledMethodStorage* storage = GetCompiledMethodStorage();
-  CompiledMethod* compiled_method = CompiledMethod::SwapAllocCompiledMethod(
-      storage,
+  CompiledCodeStorage* storage = GetCompiledCodeStorage();
+  CompiledMethod* compiled_method = storage->CreateCompiledMethod(
       codegen->GetInstructionSet(),
       code_allocator->GetMemory(),
       ArrayRef<const uint8_t>(stack_map),
       ArrayRef<const uint8_t>(*codegen->GetAssembler()->cfi().data()),
-      ArrayRef<const linker::LinkerPatch>(linker_patches));
+      ArrayRef<const linker::LinkerPatch>(linker_patches),
+      is_intrinsic);
 
   for (const linker::LinkerPatch& patch : linker_patches) {
     if (codegen->NeedsThunkCode(patch) && storage->GetThunkCode(patch).empty()) {
@@ -891,6 +902,8 @@
     RunBaselineOptimizations(graph, codegen.get(), dex_compilation_unit, &pass_observer);
   } else {
     RunOptimizations(graph, codegen.get(), dex_compilation_unit, &pass_observer);
+    PassScope scope(WriteBarrierElimination::kWBEPassName, &pass_observer);
+    WriteBarrierElimination(graph, compilation_stats_.get()).Run();
   }
 
   RegisterAllocator::Strategy regalloc_strategy =
@@ -984,6 +997,10 @@
                    optimizations);
 
   RunArchOptimizations(graph, codegen.get(), dex_compilation_unit, &pass_observer);
+  {
+    PassScope scope(WriteBarrierElimination::kWBEPassName, &pass_observer);
+    WriteBarrierElimination(graph, compilation_stats_.get()).Run();
+  }
 
   AllocateRegisters(graph,
                     codegen.get(),
@@ -1079,10 +1096,8 @@
     compiled_method = Emit(&allocator,
                            &code_allocator,
                            codegen.get(),
+                           compiled_intrinsic,
                            compiled_intrinsic ? nullptr : code_item);
-    if (compiled_intrinsic) {
-      compiled_method->MarkAsIntrinsic();
-    }
 
     if (kArenaAllocatorCountAllocations) {
       codegen.reset();  // Release codegen's ScopedArenaAllocator for memory accounting.
@@ -1115,17 +1130,18 @@
 
 static ScopedArenaVector<uint8_t> CreateJniStackMap(ScopedArenaAllocator* allocator,
                                                     const JniCompiledMethod& jni_compiled_method,
-                                                    size_t code_size) {
+                                                    size_t code_size,
+                                                    bool debuggable) {
   // 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(
       new (allocator) StackMapStream(allocator, jni_compiled_method.GetInstructionSet()));
-  stack_map_stream->BeginMethod(
-      jni_compiled_method.GetFrameSize(),
-      jni_compiled_method.GetCoreSpillMask(),
-      jni_compiled_method.GetFpSpillMask(),
-      /* num_dex_registers= */ 0,
-      /* baseline= */ false);
+  stack_map_stream->BeginMethod(jni_compiled_method.GetFrameSize(),
+                                jni_compiled_method.GetCoreSpillMask(),
+                                jni_compiled_method.GetFpSpillMask(),
+                                /* num_dex_registers= */ 0,
+                                /* baseline= */ false,
+                                debuggable);
   stack_map_stream->EndMethod(code_size);
   return stack_map_stream->Encode();
 }
@@ -1172,12 +1188,11 @@
                               method,
                               &handles));
       if (codegen != nullptr) {
-        CompiledMethod* compiled_method = Emit(&allocator,
-                                               &code_allocator,
-                                               codegen.get(),
-                                               /* item= */ nullptr);
-        compiled_method->MarkAsIntrinsic();
-        return compiled_method;
+        return Emit(&allocator,
+                    &code_allocator,
+                    codegen.get(),
+                    /*is_intrinsic=*/ true,
+                    /*item=*/ nullptr);
       }
     }
   }
@@ -1187,19 +1202,22 @@
   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, jni_compiled_method.GetCode().size());
-  return CompiledMethod::SwapAllocCompiledMethod(
-      GetCompiledMethodStorage(),
+  ScopedArenaVector<uint8_t> stack_map =
+      CreateJniStackMap(&stack_map_allocator,
+                        jni_compiled_method,
+                        jni_compiled_method.GetCode().size(),
+                        compiler_options.GetDebuggable() && compiler_options.IsJitCompiler());
+  return GetCompiledCodeStorage()->CreateCompiledMethod(
       jni_compiled_method.GetInstructionSet(),
       jni_compiled_method.GetCode(),
       ArrayRef<const uint8_t>(stack_map),
       jni_compiled_method.GetCfi(),
-      /* patches= */ ArrayRef<const linker::LinkerPatch>());
+      /*patches=*/ ArrayRef<const linker::LinkerPatch>(),
+      /*is_intrinsic=*/ false);
 }
 
 Compiler* CreateOptimizingCompiler(const CompilerOptions& compiler_options,
-                                   CompiledMethodStorage* storage) {
+                                   CompiledCodeStorage* storage) {
   return new OptimizingCompiler(compiler_options, storage);
 }
 
@@ -1233,6 +1251,19 @@
   ArenaAllocator allocator(runtime->GetJitArenaPool());
 
   if (UNLIKELY(method->IsNative())) {
+    // Use GenericJniTrampoline for critical native methods in debuggable runtimes. We don't
+    // support calling method entry / exit hooks for critical native methods yet.
+    // TODO(mythria): Add support for calling method entry / exit hooks in JITed stubs for critical
+    // native methods too.
+    if (compiler_options.GetDebuggable() && method->IsCriticalNative()) {
+      DCHECK(compiler_options.IsJitCompiler());
+      return false;
+    }
+    // Java debuggable runtimes should set compiler options to debuggable, so that we either
+    // generate method entry / exit hooks or skip JITing. For critical native methods we don't
+    // generate method entry / exit hooks so we shouldn't JIT them in debuggable runtimes.
+    DCHECK_IMPLIES(method->IsCriticalNative(), !runtime->IsJavaDebuggable());
+
     JniCompiledMethod jni_compiled_method = ArtQuickJniCompileMethod(
         compiler_options, access_flags, method_idx, *dex_file, &allocator);
     std::vector<Handle<mirror::Object>> roots;
@@ -1241,8 +1272,11 @@
     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, jni_compiled_method.GetCode().size());
+    ScopedArenaVector<uint8_t> stack_map =
+        CreateJniStackMap(&stack_map_allocator,
+                          jni_compiled_method,
+                          jni_compiled_method.GetCode().size(),
+                          compiler_options.GetDebuggable() && compiler_options.IsJitCompiler());
 
     ArrayRef<const uint8_t> reserved_code;
     ArrayRef<const uint8_t> reserved_data;
diff --git a/compiler/optimizing/optimizing_compiler.h b/compiler/optimizing/optimizing_compiler.h
index cd6d684..737ffd0 100644
--- a/compiler/optimizing/optimizing_compiler.h
+++ b/compiler/optimizing/optimizing_compiler.h
@@ -18,18 +18,19 @@
 #define ART_COMPILER_OPTIMIZING_OPTIMIZING_COMPILER_H_
 
 #include "base/globals.h"
+#include "base/macros.h"
 #include "base/mutex.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArtMethod;
+class CompiledCodeStorage;
 class Compiler;
-class CompiledMethodStorage;
 class CompilerOptions;
 class DexFile;
 
 Compiler* CreateOptimizingCompiler(const CompilerOptions& compiler_options,
-                                   CompiledMethodStorage* storage);
+                                   CompiledCodeStorage* storage);
 
 bool EncodeArtMethodInInlineInfo(ArtMethod* method);
 
diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h
index d458e42..a1d0a5a 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -26,8 +26,9 @@
 
 #include "base/atomic.h"
 #include "base/globals.h"
+#include "base/macros.h"
 
-namespace art {
+namespace art HIDDEN {
 
 enum class MethodCompilationStat {
   kAttemptBytecodeCompilation = 0,
@@ -46,6 +47,7 @@
   kUnresolvedFieldNotAFastAccess,
   kRemovedCheckedCast,
   kRemovedDeadInstruction,
+  kRemovedTry,
   kRemovedNullCheck,
   kNotCompiledSkipped,
   kNotCompiledInvalidBytecode,
@@ -59,6 +61,7 @@
   kNotCompiledSpaceFilter,
   kNotCompiledUnhandledInstruction,
   kNotCompiledUnsupportedIsa,
+  kNotCompiledInliningIrreducibleLoop,
   kNotCompiledIrreducibleLoopAndStringInit,
   kNotCompiledPhiEquivalentInOsr,
   kInlinedMonomorphicCall,
@@ -73,11 +76,13 @@
   kLoopVectorizedIdiom,
   kSelectGenerated,
   kRemovedInstanceOf,
+  kPropagatedIfValue,
   kInlinedInvokeVirtualOrInterface,
   kInlinedLastInvokeVirtualOrInterface,
   kImplicitNullCheckGenerated,
   kExplicitNullCheckGenerated,
   kSimplifyIf,
+  kSimplifyIfAddedPhi,
   kSimplifyThrowingInvoke,
   kInstructionSunk,
   kNotInlinedUnresolvedEntrypoint,
@@ -88,16 +93,19 @@
   kNotInlinedEnvironmentBudget,
   kNotInlinedInstructionBudget,
   kNotInlinedLoopWithoutExit,
-  kNotInlinedIrreducibleLoop,
+  kNotInlinedIrreducibleLoopCallee,
+  kNotInlinedIrreducibleLoopCaller,
   kNotInlinedAlwaysThrows,
   kNotInlinedInfiniteLoop,
-  kNotInlinedTryCatchCaller,
   kNotInlinedTryCatchCallee,
+  kNotInlinedTryCatchDisabled,
   kNotInlinedRegisterAllocator,
   kNotInlinedCannotBuild,
+  kNotInlinedNeverInlineAnnotation,
   kNotInlinedNotCompilable,
   kNotInlinedNotVerified,
   kNotInlinedCodeItem,
+  kNotInlinedEndsWithThrow,
   kNotInlinedWont,
   kNotInlinedRecursiveBudget,
   kNotInlinedPolymorphicRecursiveBudget,
@@ -105,12 +113,15 @@
   kNotInlinedUnresolved,
   kNotInlinedPolymorphic,
   kNotInlinedCustom,
+  kNotVarAnalyzedPathological,
   kTryInline,
   kConstructorFenceGeneratedNew,
   kConstructorFenceGeneratedFinal,
   kConstructorFenceRemovedLSE,
   kConstructorFenceRemovedPFRA,
   kConstructorFenceRemovedCFRE,
+  kPossibleWriteBarrier,
+  kRemovedWriteBarrier,
   kBitstringTypeCheck,
   kJitOutOfMemoryForCommit,
   kFullLSEAllocationRemoved,
diff --git a/compiler/optimizing/optimizing_unit_test.h b/compiler/optimizing/optimizing_unit_test.h
index e836880..2e05c41 100644
--- a/compiler/optimizing/optimizing_unit_test.h
+++ b/compiler/optimizing/optimizing_unit_test.h
@@ -25,6 +25,7 @@
 #include <vector>
 #include <variant>
 
+#include "base/macros.h"
 #include "base/indenter.h"
 #include "base/malloc_arena_pool.h"
 #include "base/scoped_arena_allocator.h"
@@ -46,7 +47,7 @@
 #include "ssa_builder.h"
 #include "ssa_liveness_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 #define NUM_INSTRUCTIONS(...)  \
   (sizeof((uint16_t[]) {__VA_ARGS__}) /sizeof(uint16_t))
@@ -240,13 +241,14 @@
 
     // Create the dex file based on the fake data. Call the constructor so that we can use virtual
     // functions. Don't use the arena for the StandardDexFile otherwise the dex location leaks.
-    dex_files_.emplace_back(new StandardDexFile(
-        dex_data,
-        sizeof(StandardDexFile::Header),
-        "no_location",
-        /*location_checksum*/ 0,
-        /*oat_dex_file*/ nullptr,
-        /*container*/ nullptr));
+    auto container =
+        std::make_shared<MemoryDexFileContainer>(dex_data, sizeof(StandardDexFile::Header));
+    dex_files_.emplace_back(new StandardDexFile(dex_data,
+                                                sizeof(StandardDexFile::Header),
+                                                "no_location",
+                                                /*location_checksum*/ 0,
+                                                /*oat_dex_file*/ nullptr,
+                                                std::move(container)));
 
     graph_ = new (allocator) HGraph(
         allocator,
@@ -260,9 +262,10 @@
 
   // Create a control-flow graph from Dex instructions.
   HGraph* CreateCFG(const std::vector<uint16_t>& data,
-                    DataType::Type return_type = DataType::Type::kInt32,
-                    VariableSizedHandleScope* handles = nullptr) {
-    HGraph* graph = CreateGraph(handles);
+                    DataType::Type return_type = DataType::Type::kInt32) {
+    ScopedObjectAccess soa(Thread::Current());
+    VariableSizedHandleScope handles(soa.Self());
+    HGraph* graph = CreateGraph(&handles);
 
     // The code item data might not aligned to 4 bytes, copy it to ensure that.
     const size_t code_item_size = data.size() * sizeof(data.front());
@@ -278,7 +281,7 @@
               /* class_linker= */ nullptr,
               graph->GetDexFile(),
               code_item,
-              /* class_def_index= */ DexFile::kDexNoIndex16,
+              /* class_def_idx= */ DexFile::kDexNoIndex16,
               /* method_idx= */ dex::kDexNoIndex,
               /* access_flags= */ 0u,
               /* verified_method= */ nullptr,
@@ -320,25 +323,10 @@
   // Run GraphChecker with all checks.
   //
   // Return: the status whether the run is successful.
-  bool CheckGraph(HGraph* graph, std::ostream& oss = std::cerr) {
-    return CheckGraph(graph, /*check_ref_type_info=*/true, oss);
-  }
-
   bool CheckGraph(std::ostream& oss = std::cerr) {
     return CheckGraph(graph_, oss);
   }
 
-  // Run GraphChecker with all checks except reference type information checks.
-  //
-  // Return: the status whether the run is successful.
-  bool CheckGraphSkipRefTypeInfoChecks(HGraph* graph, std::ostream& oss = std::cerr) {
-    return CheckGraph(graph, /*check_ref_type_info=*/false, oss);
-  }
-
-  bool CheckGraphSkipRefTypeInfoChecks(std::ostream& oss = std::cerr) {
-    return CheckGraphSkipRefTypeInfoChecks(graph_, oss);
-  }
-
   HEnvironment* ManuallyBuildEnvFor(HInstruction* instruction,
                                     ArenaVector<HInstruction*>* current_locals) {
     HEnvironment* environment = new (GetAllocator()) HEnvironment(
@@ -473,7 +461,8 @@
                               HInvokeStaticOrDirect::DispatchInfo{},
                               InvokeType::kStatic,
                               /* resolved_method_reference= */ method_reference,
-                              HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
+                              HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
+                              !graph_->IsDebuggable());
     for (auto [ins, idx] : ZipCount(MakeIterationRange(args))) {
       res->SetRawInputAt(idx, ins);
     }
@@ -531,9 +520,8 @@
   }
 
  protected:
-  bool CheckGraph(HGraph* graph, bool check_ref_type_info, std::ostream& oss) {
+  bool CheckGraph(HGraph* graph, std::ostream& oss) {
     GraphChecker checker(graph);
-    checker.SetRefTypeInfoCheckEnabled(check_ref_type_info);
     checker.Run();
     checker.Dump(oss);
     return checker.IsValid();
@@ -559,7 +547,7 @@
 class OptimizingUnitTest : public CommonArtTest, public OptimizingUnitTestHelper {};
 
 // Naive string diff data type.
-typedef std::list<std::pair<std::string, std::string>> diff_t;
+using diff_t = std::list<std::pair<std::string, std::string>>;
 
 // An alias for the empty string used to make it clear that a line is
 // removed in a diff.
@@ -586,7 +574,7 @@
   return alg.Dump(oss);
 }
 
-class PatternMatchGraphVisitor : public HGraphVisitor {
+class PatternMatchGraphVisitor final : public HGraphVisitor {
  private:
   struct HandlerWrapper {
    public:
diff --git a/compiler/optimizing/parallel_move_resolver.cc b/compiler/optimizing/parallel_move_resolver.cc
index 2036b4a..9fc4cc8 100644
--- a/compiler/optimizing/parallel_move_resolver.cc
+++ b/compiler/optimizing/parallel_move_resolver.cc
@@ -19,7 +19,7 @@
 #include "base/stl_util.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 void ParallelMoveResolver::BuildInitialMoveList(HParallelMove* parallel_move) {
   // Perform a linear sweep of the moves to add them to the initial list of
diff --git a/compiler/optimizing/parallel_move_resolver.h b/compiler/optimizing/parallel_move_resolver.h
index 5fadcab..17d5122 100644
--- a/compiler/optimizing/parallel_move_resolver.h
+++ b/compiler/optimizing/parallel_move_resolver.h
@@ -18,11 +18,12 @@
 #define ART_COMPILER_OPTIMIZING_PARALLEL_MOVE_RESOLVER_H_
 
 #include "base/arena_containers.h"
+#include "base/macros.h"
 #include "base/value_object.h"
 #include "data_type.h"
 #include "locations.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class HParallelMove;
 class MoveOperands;
diff --git a/compiler/optimizing/parallel_move_test.cc b/compiler/optimizing/parallel_move_test.cc
index a8ab6cd..a1c05e9 100644
--- a/compiler/optimizing/parallel_move_test.cc
+++ b/compiler/optimizing/parallel_move_test.cc
@@ -15,6 +15,7 @@
  */
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "base/malloc_arena_pool.h"
 #include "nodes.h"
 #include "parallel_move_resolver.h"
@@ -22,7 +23,7 @@
 #include "gtest/gtest-typed-test.h"
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
 constexpr int kScratchRegisterStartIndexForTest = 100;
 
diff --git a/compiler/optimizing/pc_relative_fixups_x86.cc b/compiler/optimizing/pc_relative_fixups_x86.cc
index 17f37f0..d3da3d3 100644
--- a/compiler/optimizing/pc_relative_fixups_x86.cc
+++ b/compiler/optimizing/pc_relative_fixups_x86.cc
@@ -18,13 +18,13 @@
 #include "code_generator_x86.h"
 #include "intrinsics_x86.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 /**
  * Finds instructions that need the constant area base as an input.
  */
-class PCRelativeHandlerVisitor : public HGraphVisitor {
+class PCRelativeHandlerVisitor final : public HGraphVisitor {
  public:
   PCRelativeHandlerVisitor(HGraph* graph, CodeGenerator* codegen)
       : HGraphVisitor(graph),
diff --git a/compiler/optimizing/pc_relative_fixups_x86.h b/compiler/optimizing/pc_relative_fixups_x86.h
index 3b470a6..45578d8 100644
--- a/compiler/optimizing/pc_relative_fixups_x86.h
+++ b/compiler/optimizing/pc_relative_fixups_x86.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_PC_RELATIVE_FIXUPS_X86_H_
 #define ART_COMPILER_OPTIMIZING_PC_RELATIVE_FIXUPS_X86_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 
diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc
index c2f3d0e..398b10a 100644
--- a/compiler/optimizing/prepare_for_register_allocation.cc
+++ b/compiler/optimizing/prepare_for_register_allocation.cc
@@ -22,7 +22,7 @@
 #include "optimizing_compiler_stats.h"
 #include "well_known_classes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 void PrepareForRegisterAllocation::Run() {
   // Order does not matter.
@@ -83,7 +83,7 @@
   if (check->IsStringCharAt()) {
     // Add a fake environment for String.charAt() inline info as we want the exception
     // to appear as being thrown from there. Skip if we're compiling String.charAt() itself.
-    ArtMethod* char_at_method = jni::DecodeArtMethod(WellKnownClasses::java_lang_String_charAt);
+    ArtMethod* char_at_method = WellKnownClasses::java_lang_String_charAt;
     if (GetGraph()->GetArtMethod() != char_at_method) {
       ArenaAllocator* allocator = GetGraph()->GetAllocator();
       HEnvironment* environment = new (allocator) HEnvironment(allocator,
@@ -109,7 +109,7 @@
   if (value->IsNullConstant()) {
     DCHECK_EQ(value->GetType(), DataType::Type::kReference);
     if (instruction->NeedsTypeCheck()) {
-      instruction->ClearNeedsTypeCheck();
+      instruction->ClearTypeCheck();
     }
   }
 }
@@ -295,15 +295,16 @@
     return false;
   }
 
-  // In debug mode, check that we have not inserted a throwing instruction
-  // or an instruction with side effects between input and user.
-  if (kIsDebugBuild) {
-    for (HInstruction* between = input->GetNext(); between != user; between = between->GetNext()) {
-      CHECK(between != nullptr);  // User must be after input in the same block.
-      CHECK(!between->CanThrow()) << *between << " User: " << *user;
-      CHECK(!between->HasSideEffects()) << *between << " User: " << *user;
+  // If there's a instruction between them that can throw or it has side effects, we cannot move the
+  // responsibility.
+  for (HInstruction* between = input->GetNext(); between != user; between = between->GetNext()) {
+    DCHECK(between != nullptr) << " User must be after input in the same block. input: " << *input
+                               << ", user: " << *user;
+    if (between->CanThrow() || between->HasSideEffects()) {
+      return false;
     }
   }
+
   return true;
 }
 
diff --git a/compiler/optimizing/prepare_for_register_allocation.h b/compiler/optimizing/prepare_for_register_allocation.h
index e0bb76e..0426f84 100644
--- a/compiler/optimizing/prepare_for_register_allocation.h
+++ b/compiler/optimizing/prepare_for_register_allocation.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_PREPARE_FOR_REGISTER_ALLOCATION_H_
 #define ART_COMPILER_OPTIMIZING_PREPARE_FOR_REGISTER_ALLOCATION_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CompilerOptions;
 class OptimizingCompilerStats;
diff --git a/compiler/optimizing/pretty_printer.h b/compiler/optimizing/pretty_printer.h
index 8ef9ce4..77ddb97 100644
--- a/compiler/optimizing/pretty_printer.h
+++ b/compiler/optimizing/pretty_printer.h
@@ -19,9 +19,10 @@
 
 #include "android-base/stringprintf.h"
 
+#include "base/macros.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class HPrettyPrinter : public HGraphVisitor {
  public:
diff --git a/compiler/optimizing/pretty_printer_test.cc b/compiler/optimizing/pretty_printer_test.cc
index 6ef386b..90d5f8f 100644
--- a/compiler/optimizing/pretty_printer_test.cc
+++ b/compiler/optimizing/pretty_printer_test.cc
@@ -17,6 +17,7 @@
 #include "pretty_printer.h"
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "dex/dex_file.h"
 #include "dex/dex_instruction.h"
@@ -25,9 +26,9 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
-class PrettyPrinterTest : public OptimizingUnitTest {
+class PrettyPrinterTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   void TestCode(const std::vector<uint16_t>& data, const char* expected);
 };
diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc
index e6024b0..91bae5f 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -29,7 +29,7 @@
 #include "mirror/dex_cache.h"
 #include "scoped_thread_state_change-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static inline ObjPtr<mirror::DexCache> FindDexCacheWithHint(
     Thread* self, const DexFile& dex_file, Handle<mirror::DexCache> hint_dex_cache)
@@ -41,18 +41,14 @@
   }
 }
 
-class ReferenceTypePropagation::RTPVisitor : public HGraphDelegateVisitor {
+class ReferenceTypePropagation::RTPVisitor final : public HGraphDelegateVisitor {
  public:
-  RTPVisitor(HGraph* graph,
-             Handle<mirror::ClassLoader> class_loader,
-             Handle<mirror::DexCache> hint_dex_cache,
-             bool is_first_run)
-    : HGraphDelegateVisitor(graph),
-      class_loader_(class_loader),
-      hint_dex_cache_(hint_dex_cache),
-      allocator_(graph->GetArenaStack()),
-      worklist_(allocator_.Adapter(kArenaAllocReferenceTypePropagation)),
-      is_first_run_(is_first_run) {
+  RTPVisitor(HGraph* graph, Handle<mirror::DexCache> hint_dex_cache, bool is_first_run)
+      : HGraphDelegateVisitor(graph),
+        hint_dex_cache_(hint_dex_cache),
+        allocator_(graph->GetArenaStack()),
+        worklist_(allocator_.Adapter(kArenaAllocReferenceTypePropagation)),
+        is_first_run_(is_first_run) {
     worklist_.reserve(kDefaultWorklistSize);
   }
 
@@ -110,7 +106,6 @@
 
   static constexpr size_t kDefaultWorklistSize = 8;
 
-  Handle<mirror::ClassLoader> class_loader_;
   Handle<mirror::DexCache> hint_dex_cache_;
 
   // Use local allocator for allocating memory.
@@ -122,63 +117,18 @@
 };
 
 ReferenceTypePropagation::ReferenceTypePropagation(HGraph* graph,
-                                                   Handle<mirror::ClassLoader> class_loader,
                                                    Handle<mirror::DexCache> hint_dex_cache,
                                                    bool is_first_run,
                                                    const char* name)
-    : HOptimization(graph, name),
-      class_loader_(class_loader),
-      hint_dex_cache_(hint_dex_cache),
-      is_first_run_(is_first_run) {
-}
-
-void ReferenceTypePropagation::ValidateTypes() {
-  // TODO: move this to the graph checker. Note: There may be no Thread for gtests.
-  if (kIsDebugBuild && Thread::Current() != nullptr) {
-    ScopedObjectAccess soa(Thread::Current());
-    for (HBasicBlock* block : graph_->GetReversePostOrder()) {
-      for (HInstructionIterator iti(block->GetInstructions()); !iti.Done(); iti.Advance()) {
-        HInstruction* instr = iti.Current();
-        if (instr->GetType() == DataType::Type::kReference) {
-          DCHECK(instr->GetReferenceTypeInfo().IsValid())
-              << "Invalid RTI for instruction: " << instr->DebugName();
-          if (instr->IsBoundType()) {
-            DCHECK(instr->AsBoundType()->GetUpperBound().IsValid());
-          } else if (instr->IsLoadClass()) {
-            HLoadClass* cls = instr->AsLoadClass();
-            DCHECK(cls->GetReferenceTypeInfo().IsExact());
-            DCHECK_IMPLIES(cls->GetLoadedClassRTI().IsValid(), cls->GetLoadedClassRTI().IsExact());
-          } else if (instr->IsNullCheck()) {
-            DCHECK(instr->GetReferenceTypeInfo().IsEqual(instr->InputAt(0)->GetReferenceTypeInfo()))
-                << "NullCheck " << instr->GetReferenceTypeInfo()
-                << "Input(0) " << instr->InputAt(0)->GetReferenceTypeInfo();
-          }
-        } else if (instr->IsInstanceOf()) {
-          HInstanceOf* iof = instr->AsInstanceOf();
-          DCHECK_IMPLIES(iof->GetTargetClassRTI().IsValid(), iof->GetTargetClassRTI().IsExact());
-        } else if (instr->IsCheckCast()) {
-          HCheckCast* check = instr->AsCheckCast();
-          DCHECK_IMPLIES(check->GetTargetClassRTI().IsValid(),
-                         check->GetTargetClassRTI().IsExact());
-        }
-      }
-    }
-  }
-}
+    : HOptimization(graph, name), hint_dex_cache_(hint_dex_cache), is_first_run_(is_first_run) {}
 
 void ReferenceTypePropagation::Visit(HInstruction* instruction) {
-  RTPVisitor visitor(graph_,
-                     class_loader_,
-                     hint_dex_cache_,
-                     is_first_run_);
+  RTPVisitor visitor(graph_, hint_dex_cache_, is_first_run_);
   instruction->Accept(&visitor);
 }
 
 void ReferenceTypePropagation::Visit(ArrayRef<HInstruction* const> instructions) {
-  RTPVisitor visitor(graph_,
-                     class_loader_,
-                     hint_dex_cache_,
-                     is_first_run_);
+  RTPVisitor visitor(graph_, hint_dex_cache_, is_first_run_);
   for (HInstruction* instruction : instructions) {
     if (instruction->IsPhi()) {
       // Need to force phis to recalculate null-ness.
@@ -349,7 +299,10 @@
 }
 
 bool ReferenceTypePropagation::Run() {
-  RTPVisitor visitor(graph_, class_loader_, hint_dex_cache_, is_first_run_);
+  DCHECK(Thread::Current() != nullptr)
+      << "ReferenceTypePropagation requires the use of Thread::Current(). Make sure you have a "
+      << "Runtime initialized before calling this optimization pass";
+  RTPVisitor visitor(graph_, hint_dex_cache_, is_first_run_);
 
   // To properly propagate type info we need to visit in the dominator-based order.
   // Reverse post order guarantees a node's dominators are visited first.
@@ -359,7 +312,6 @@
   }
 
   visitor.ProcessWorklist();
-  ValidateTypes();
   return true;
 }
 
@@ -446,10 +398,13 @@
         if (rhs->AsIntConstant()->IsTrue()) {
           // Case (1a)
           *trueBranch = ifInstruction->IfTrueSuccessor();
-        } else {
+        } else if (rhs->AsIntConstant()->IsFalse()) {
           // Case (2a)
-          DCHECK(rhs->AsIntConstant()->IsFalse()) << rhs->AsIntConstant()->GetValue();
           *trueBranch = ifInstruction->IfFalseSuccessor();
+        } else {
+          // Sometimes we see a comparison of instance-of with a constant which is neither 0 nor 1.
+          // In those cases, we cannot do the match if+instance-of.
+          return false;
         }
         *instanceOf = lhs->AsInstanceOf();
         return true;
@@ -463,10 +418,13 @@
         if (rhs->AsIntConstant()->IsFalse()) {
           // Case (1b)
           *trueBranch = ifInstruction->IfTrueSuccessor();
-        } else {
+        } else if (rhs->AsIntConstant()->IsTrue()) {
           // Case (2b)
-          DCHECK(rhs->AsIntConstant()->IsTrue()) << rhs->AsIntConstant()->GetValue();
           *trueBranch = ifInstruction->IfFalseSuccessor();
+        } else {
+          // Sometimes we see a comparison of instance-of with a constant which is neither 0 nor 1.
+          // In those cases, we cannot do the match if+instance-of.
+          return false;
         }
         *instanceOf = lhs->AsInstanceOf();
         return true;
@@ -583,7 +541,7 @@
   ScopedObjectAccess soa(Thread::Current());
   ObjPtr<mirror::DexCache> dex_cache = FindDexCacheWithHint(soa.Self(), dex_file, hint_dex_cache_);
   ObjPtr<mirror::Class> klass = Runtime::Current()->GetClassLinker()->LookupResolvedType(
-      type_idx, dex_cache, class_loader_.Get());
+      type_idx, dex_cache, dex_cache->GetClassLoader());
   SetClassAsTypeInfo(instr, klass, is_exact);
 }
 
diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h
index 889a846..655f62b 100644
--- a/compiler/optimizing/reference_type_propagation.h
+++ b/compiler/optimizing/reference_type_propagation.h
@@ -18,12 +18,13 @@
 #define ART_COMPILER_OPTIMIZING_REFERENCE_TYPE_PROPAGATION_H_
 
 #include "base/arena_containers.h"
+#include "base/macros.h"
 #include "mirror/class-inl.h"
 #include "nodes.h"
 #include "obj_ptr.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Propagates reference types to instructions.
@@ -31,7 +32,6 @@
 class ReferenceTypePropagation : public HOptimization {
  public:
   ReferenceTypePropagation(HGraph* graph,
-                           Handle<mirror::ClassLoader> class_loader,
                            Handle<mirror::DexCache> hint_dex_cache,
                            bool is_first_run,
                            const char* name = kReferenceTypePropagationPassName);
@@ -71,10 +71,6 @@
                                       HandleCache* handle_cache)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void ValidateTypes();
-
-  Handle<mirror::ClassLoader> class_loader_;
-
   // Note: hint_dex_cache_ is usually, but not necessarily, the dex cache associated with
   // graph_->GetDexFile(). Since we may look up also in other dex files, it's used only
   // as a hint, to reduce the number of calls to the costly ClassLinker::FindDexCache().
diff --git a/compiler/optimizing/reference_type_propagation_test.cc b/compiler/optimizing/reference_type_propagation_test.cc
index d1bcab0..2b012fc 100644
--- a/compiler/optimizing/reference_type_propagation_test.cc
+++ b/compiler/optimizing/reference_type_propagation_test.cc
@@ -19,6 +19,7 @@
 #include <random>
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "base/transform_array_ref.h"
 #include "base/transform_iterator.h"
 #include "builder.h"
@@ -26,7 +27,7 @@
 #include "object_lock.h"
 #include "optimizing_unit_test.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // TODO It would be good to use the following but there is a miniscule amount of
 // chance for flakiness so we'll just use a set seed instead.
@@ -47,11 +48,8 @@
 
   void SetupPropagation(VariableSizedHandleScope* handles) {
     graph_ = CreateGraph(handles);
-    propagation_ = new (GetAllocator()) ReferenceTypePropagation(graph_,
-                                                                 Handle<mirror::ClassLoader>(),
-                                                                 Handle<mirror::DexCache>(),
-                                                                 true,
-                                                                 "test_prop");
+    propagation_ = new (GetAllocator())
+        ReferenceTypePropagation(graph_, Handle<mirror::DexCache>(), true, "test_prop");
   }
 
   // Relay method to merge type in reference type propagation.
diff --git a/compiler/optimizing/register_allocation_resolver.cc b/compiler/optimizing/register_allocation_resolver.cc
index 875c633..53e11f2 100644
--- a/compiler/optimizing/register_allocation_resolver.cc
+++ b/compiler/optimizing/register_allocation_resolver.cc
@@ -21,7 +21,7 @@
 #include "linear_order.h"
 #include "ssa_liveness_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 RegisterAllocationResolver::RegisterAllocationResolver(CodeGenerator* codegen,
                                                        const SsaLivenessAnalysis& liveness)
diff --git a/compiler/optimizing/register_allocation_resolver.h b/compiler/optimizing/register_allocation_resolver.h
index 2783717..f4782eb 100644
--- a/compiler/optimizing/register_allocation_resolver.h
+++ b/compiler/optimizing/register_allocation_resolver.h
@@ -18,10 +18,11 @@
 #define ART_COMPILER_OPTIMIZING_REGISTER_ALLOCATION_RESOLVER_H_
 
 #include "base/array_ref.h"
+#include "base/macros.h"
 #include "base/value_object.h"
 #include "data_type.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArenaAllocator;
 class CodeGenerator;
diff --git a/compiler/optimizing/register_allocator.cc b/compiler/optimizing/register_allocator.cc
index a9c217f..e4c2d74 100644
--- a/compiler/optimizing/register_allocator.cc
+++ b/compiler/optimizing/register_allocator.cc
@@ -27,7 +27,7 @@
 #include "register_allocator_linear_scan.h"
 #include "ssa_liveness_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 RegisterAllocator::RegisterAllocator(ScopedArenaAllocator* allocator,
                                      CodeGenerator* codegen,
diff --git a/compiler/optimizing/register_allocator.h b/compiler/optimizing/register_allocator.h
index 4d22687..453e339 100644
--- a/compiler/optimizing/register_allocator.h
+++ b/compiler/optimizing/register_allocator.h
@@ -22,7 +22,7 @@
 #include "base/arena_object.h"
 #include "base/macros.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 class HBasicBlock;
diff --git a/compiler/optimizing/register_allocator_graph_color.cc b/compiler/optimizing/register_allocator_graph_color.cc
index 684aaf5..a7c891d 100644
--- a/compiler/optimizing/register_allocator_graph_color.cc
+++ b/compiler/optimizing/register_allocator_graph_color.cc
@@ -22,7 +22,7 @@
 #include "ssa_liveness_analysis.h"
 #include "thread-current-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Highest number of registers that we support for any platform. This can be used for std::bitset,
 // for example, which needs to know its size at compile time.
diff --git a/compiler/optimizing/register_allocator_graph_color.h b/compiler/optimizing/register_allocator_graph_color.h
index e5b86ea..0e10152 100644
--- a/compiler/optimizing/register_allocator_graph_color.h
+++ b/compiler/optimizing/register_allocator_graph_color.h
@@ -24,7 +24,7 @@
 #include "base/scoped_arena_containers.h"
 #include "register_allocator.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 class HBasicBlock;
diff --git a/compiler/optimizing/register_allocator_linear_scan.cc b/compiler/optimizing/register_allocator_linear_scan.cc
index 833c24d..fcdaa2d 100644
--- a/compiler/optimizing/register_allocator_linear_scan.cc
+++ b/compiler/optimizing/register_allocator_linear_scan.cc
@@ -26,7 +26,7 @@
 #include "register_allocation_resolver.h"
 #include "ssa_liveness_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static constexpr size_t kMaxLifetimePosition = -1;
 static constexpr size_t kDefaultNumberOfSpillSlots = 4;
diff --git a/compiler/optimizing/register_allocator_linear_scan.h b/compiler/optimizing/register_allocator_linear_scan.h
index 9a1e0d7..c71a9e9 100644
--- a/compiler/optimizing/register_allocator_linear_scan.h
+++ b/compiler/optimizing/register_allocator_linear_scan.h
@@ -22,7 +22,7 @@
 #include "base/scoped_arena_containers.h"
 #include "register_allocator.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 class HBasicBlock;
diff --git a/compiler/optimizing/register_allocator_test.cc b/compiler/optimizing/register_allocator_test.cc
index 6823155..d316aa5 100644
--- a/compiler/optimizing/register_allocator_test.cc
+++ b/compiler/optimizing/register_allocator_test.cc
@@ -18,6 +18,7 @@
 
 #include "arch/x86/instruction_set_features_x86.h"
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "code_generator.h"
 #include "code_generator_x86.h"
@@ -31,17 +32,17 @@
 #include "ssa_liveness_analysis.h"
 #include "ssa_phi_elimination.h"
 
-namespace art {
+namespace art HIDDEN {
 
 using Strategy = RegisterAllocator::Strategy;
 
 // Note: the register allocator tests rely on the fact that constants have live
 // intervals and registers get allocated to them.
 
-class RegisterAllocatorTest : public OptimizingUnitTest {
+class RegisterAllocatorTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   void SetUp() override {
-    OptimizingUnitTest::SetUp();
+    CommonCompilerTest::SetUp();
     // This test is using the x86 ISA.
     compiler_options_ = CommonCompilerTest::CreateCompilerOptions(InstructionSet::kX86, "default");
   }
diff --git a/compiler/optimizing/scheduler.cc b/compiler/optimizing/scheduler.cc
index 8f18ccf..116f526 100644
--- a/compiler/optimizing/scheduler.cc
+++ b/compiler/optimizing/scheduler.cc
@@ -32,7 +32,7 @@
 #include "scheduler_arm.h"
 #endif
 
-namespace art {
+namespace art HIDDEN {
 
 void SchedulingGraph::AddDependency(SchedulingNode* node,
                                     SchedulingNode* dependency,
@@ -718,9 +718,10 @@
   //    HLoadException
   //    HMemoryBarrier
   //    HMonitorOperation
-  //    HNativeDebugInfo
+  //    HNop
   //    HThrow
   //    HTryBoundary
+  //    All volatile field access e.g. HInstanceFieldGet
   // TODO: Some of the instructions above may be safe to schedule (maybe as
   // scheduling barriers).
   return instruction->IsArrayGet() ||
diff --git a/compiler/optimizing/scheduler.h b/compiler/optimizing/scheduler.h
index f7180a0..299fbc9 100644
--- a/compiler/optimizing/scheduler.h
+++ b/compiler/optimizing/scheduler.h
@@ -19,6 +19,7 @@
 
 #include <fstream>
 
+#include "base/macros.h"
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 #include "base/stl_util.h"
@@ -28,7 +29,7 @@
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // General description of instruction scheduling.
 //
diff --git a/compiler/optimizing/scheduler_arm.cc b/compiler/optimizing/scheduler_arm.cc
index 965e1bd..3f931c4 100644
--- a/compiler/optimizing/scheduler_arm.cc
+++ b/compiler/optimizing/scheduler_arm.cc
@@ -23,7 +23,7 @@
 #include "mirror/array-inl.h"
 #include "mirror/string.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 using helpers::Int32ConstantFrom;
@@ -669,7 +669,7 @@
     }
 
     case DataType::Type::kReference: {
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         last_visited_latency_ = kArmLoadWithBakerReadBarrierLatency;
       } else {
         if (index->IsConstant()) {
@@ -937,7 +937,7 @@
       break;
 
     case DataType::Type::kReference:
-      if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+      if (gUseReadBarrier && kUseBakerReadBarrier) {
         last_visited_internal_latency_ = kArmMemoryLoadLatency + kArmIntegerOpLatency;
         last_visited_latency_ = kArmMemoryLoadLatency;
       } else {
diff --git a/compiler/optimizing/scheduler_arm.h b/compiler/optimizing/scheduler_arm.h
index d11222d..0da21c1 100644
--- a/compiler/optimizing/scheduler_arm.h
+++ b/compiler/optimizing/scheduler_arm.h
@@ -17,14 +17,12 @@
 #ifndef ART_COMPILER_OPTIMIZING_SCHEDULER_ARM_H_
 #define ART_COMPILER_OPTIMIZING_SCHEDULER_ARM_H_
 
+#include "base/macros.h"
 #include "code_generator_arm_vixl.h"
 #include "scheduler.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
-// TODO: Replace CodeGeneratorARMType with CodeGeneratorARMVIXL everywhere?
-typedef CodeGeneratorARMVIXL CodeGeneratorARMType;
-
 // AArch32 instruction latencies.
 // We currently assume that all ARM CPUs share the same instruction latency list.
 // The following latencies were tuned based on performance experiments and
@@ -49,10 +47,10 @@
 static constexpr uint32_t kArmLoadWithBakerReadBarrierLatency = 18;
 static constexpr uint32_t kArmRuntimeTypeCheckLatency = 46;
 
-class SchedulingLatencyVisitorARM : public SchedulingLatencyVisitor {
+class SchedulingLatencyVisitorARM final : public SchedulingLatencyVisitor {
  public:
   explicit SchedulingLatencyVisitorARM(CodeGenerator* codegen)
-      : codegen_(down_cast<CodeGeneratorARMType*>(codegen)) {}
+      : codegen_(down_cast<CodeGeneratorARMVIXL*>(codegen)) {}
 
   // Default visitor for instructions not handled specifically below.
   void VisitInstruction(HInstruction* ATTRIBUTE_UNUSED) override {
@@ -133,7 +131,7 @@
 
   // The latency setting for each HInstruction depends on how CodeGenerator may generate code,
   // latency visitors may query CodeGenerator for such information for accurate latency settings.
-  CodeGeneratorARMType* codegen_;
+  CodeGeneratorARMVIXL* codegen_;
 };
 
 class HSchedulerARM : public HScheduler {
diff --git a/compiler/optimizing/scheduler_arm64.cc b/compiler/optimizing/scheduler_arm64.cc
index 4f504c2..3071afd 100644
--- a/compiler/optimizing/scheduler_arm64.cc
+++ b/compiler/optimizing/scheduler_arm64.cc
@@ -20,7 +20,7 @@
 #include "mirror/array-inl.h"
 #include "mirror/string.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 void SchedulingLatencyVisitorARM64::VisitBinaryOperation(HBinaryOperation* instr) {
diff --git a/compiler/optimizing/scheduler_arm64.h b/compiler/optimizing/scheduler_arm64.h
index ba5a743..ec41577 100644
--- a/compiler/optimizing/scheduler_arm64.h
+++ b/compiler/optimizing/scheduler_arm64.h
@@ -17,9 +17,10 @@
 #ifndef ART_COMPILER_OPTIMIZING_SCHEDULER_ARM64_H_
 #define ART_COMPILER_OPTIMIZING_SCHEDULER_ARM64_H_
 
+#include "base/macros.h"
 #include "scheduler.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 static constexpr uint32_t kArm64MemoryLoadLatency = 5;
@@ -55,7 +56,7 @@
 static constexpr uint32_t kArm64SIMDDivFloatLatency = 30;
 static constexpr uint32_t kArm64SIMDTypeConversionInt2FPLatency = 10;
 
-class SchedulingLatencyVisitorARM64 : public SchedulingLatencyVisitor {
+class SchedulingLatencyVisitorARM64 final : public SchedulingLatencyVisitor {
  public:
   // Default visitor for instructions not handled specifically below.
   void VisitInstruction(HInstruction* ATTRIBUTE_UNUSED) override {
diff --git a/compiler/optimizing/scheduler_test.cc b/compiler/optimizing/scheduler_test.cc
index a1cc202..165bfe3 100644
--- a/compiler/optimizing/scheduler_test.cc
+++ b/compiler/optimizing/scheduler_test.cc
@@ -17,6 +17,7 @@
 #include "scheduler.h"
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "codegen_test_utils.h"
 #include "common_compiler_test.h"
@@ -34,7 +35,7 @@
 #include "scheduler_arm.h"
 #endif
 
-namespace art {
+namespace art HIDDEN {
 
 // Return all combinations of ISA and code generator that are executable on
 // hardware, or on simulator, and that we'd like to test.
@@ -65,7 +66,7 @@
   return v;
 }
 
-class SchedulerTest : public OptimizingUnitTest {
+class SchedulerTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  public:
   SchedulerTest() : graph_(CreateGraph()) { }
 
diff --git a/compiler/optimizing/select_generator.cc b/compiler/optimizing/select_generator.cc
index 5405382..6a10440 100644
--- a/compiler/optimizing/select_generator.cc
+++ b/compiler/optimizing/select_generator.cc
@@ -16,10 +16,10 @@
 
 #include "select_generator.h"
 
-#include "base/scoped_arena_containers.h"
+#include "optimizing/nodes.h"
 #include "reference_type_propagation.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static constexpr size_t kMaxInstructionsInBranch = 1u;
 
@@ -69,156 +69,277 @@
   return block1->GetSingleSuccessor() == block2->GetSingleSuccessor();
 }
 
-// Returns nullptr if `block` has either no phis or there is more than one phi
-// with different inputs at `index1` and `index2`. Otherwise returns that phi.
-static HPhi* GetSingleChangedPhi(HBasicBlock* block, size_t index1, size_t index2) {
+// Returns nullptr if `block` has either no phis or there is more than one phi. Otherwise returns
+// that phi.
+static HPhi* GetSinglePhi(HBasicBlock* block, size_t index1, size_t index2) {
   DCHECK_NE(index1, index2);
 
   HPhi* select_phi = nullptr;
   for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
     HPhi* phi = it.Current()->AsPhi();
-    if (phi->InputAt(index1) != phi->InputAt(index2)) {
-      if (select_phi == nullptr) {
-        // First phi with different inputs for the two indices found.
-        select_phi = phi;
-      } else {
-        // More than one phis has different inputs for the two indices.
-        return nullptr;
-      }
+    if (select_phi == nullptr) {
+      // First phi found.
+      select_phi = phi;
+    } else {
+      // More than one phi found, return null.
+      return nullptr;
     }
   }
   return select_phi;
 }
 
+bool HSelectGenerator::TryGenerateSelectSimpleDiamondPattern(
+    HBasicBlock* block, ScopedArenaSafeMap<HInstruction*, HSelect*>* cache) {
+  DCHECK(block->GetLastInstruction()->IsIf());
+  HIf* if_instruction = block->GetLastInstruction()->AsIf();
+  HBasicBlock* true_block = if_instruction->IfTrueSuccessor();
+  HBasicBlock* false_block = if_instruction->IfFalseSuccessor();
+  DCHECK_NE(true_block, false_block);
+
+  if (!IsSimpleBlock(true_block) ||
+      !IsSimpleBlock(false_block) ||
+      !BlocksMergeTogether(true_block, false_block)) {
+    return false;
+  }
+  HBasicBlock* merge_block = true_block->GetSingleSuccessor();
+
+  // If the branches are not empty, move instructions in front of the If.
+  // TODO(dbrazdil): This puts an instruction between If and its condition.
+  //                 Implement moving of conditions to first users if possible.
+  while (!true_block->IsSingleGoto() && !true_block->IsSingleReturn()) {
+    HInstruction* instr = true_block->GetFirstInstruction();
+    DCHECK(!instr->CanThrow());
+    instr->MoveBefore(if_instruction);
+  }
+  while (!false_block->IsSingleGoto() && !false_block->IsSingleReturn()) {
+    HInstruction* instr = false_block->GetFirstInstruction();
+    DCHECK(!instr->CanThrow());
+    instr->MoveBefore(if_instruction);
+  }
+  DCHECK(true_block->IsSingleGoto() || true_block->IsSingleReturn());
+  DCHECK(false_block->IsSingleGoto() || false_block->IsSingleReturn());
+
+  // Find the resulting true/false values.
+  size_t predecessor_index_true = merge_block->GetPredecessorIndexOf(true_block);
+  size_t predecessor_index_false = merge_block->GetPredecessorIndexOf(false_block);
+  DCHECK_NE(predecessor_index_true, predecessor_index_false);
+
+  bool both_successors_return = true_block->IsSingleReturn() && false_block->IsSingleReturn();
+  // TODO(solanes): Extend to support multiple phis? e.g.
+  //   int a, b;
+  //   if (bool) {
+  //     a = 0; b = 1;
+  //   } else {
+  //     a = 1; b = 2;
+  //   }
+  //   // use a and b
+  HPhi* phi = GetSinglePhi(merge_block, predecessor_index_true, predecessor_index_false);
+
+  HInstruction* true_value = nullptr;
+  HInstruction* false_value = nullptr;
+  if (both_successors_return) {
+    true_value = true_block->GetFirstInstruction()->InputAt(0);
+    false_value = false_block->GetFirstInstruction()->InputAt(0);
+  } else if (phi != nullptr) {
+    true_value = phi->InputAt(predecessor_index_true);
+    false_value = phi->InputAt(predecessor_index_false);
+  } else {
+    return false;
+  }
+  DCHECK(both_successors_return || phi != nullptr);
+
+  // Create the Select instruction and insert it in front of the If.
+  HInstruction* condition = if_instruction->InputAt(0);
+  HSelect* select = new (graph_->GetAllocator()) HSelect(condition,
+                                                          true_value,
+                                                          false_value,
+                                                          if_instruction->GetDexPc());
+  if (both_successors_return) {
+    if (true_value->GetType() == DataType::Type::kReference) {
+      DCHECK(false_value->GetType() == DataType::Type::kReference);
+      ReferenceTypePropagation::FixUpInstructionType(select, graph_->GetHandleCache());
+    }
+  } else if (phi->GetType() == DataType::Type::kReference) {
+    select->SetReferenceTypeInfoIfValid(phi->GetReferenceTypeInfo());
+  }
+  block->InsertInstructionBefore(select, if_instruction);
+
+  // Remove the true branch which removes the corresponding Phi
+  // input if needed. If left only with the false branch, the Phi is
+  // automatically removed.
+  if (both_successors_return) {
+    false_block->GetFirstInstruction()->ReplaceInput(select, 0);
+  } else {
+    phi->ReplaceInput(select, predecessor_index_false);
+  }
+
+  bool only_two_predecessors = (merge_block->GetPredecessors().size() == 2u);
+  true_block->DisconnectAndDelete();
+
+  // Merge remaining blocks which are now connected with Goto.
+  DCHECK_EQ(block->GetSingleSuccessor(), false_block);
+  block->MergeWith(false_block);
+  if (!both_successors_return && only_two_predecessors) {
+    DCHECK_EQ(only_two_predecessors, phi->GetBlock() == nullptr);
+    DCHECK_EQ(block->GetSingleSuccessor(), merge_block);
+    block->MergeWith(merge_block);
+  }
+
+  MaybeRecordStat(stats_, MethodCompilationStat::kSelectGenerated);
+
+  // Very simple way of finding common subexpressions in the generated HSelect statements
+  // (since this runs after GVN). Lookup by condition, and reuse latest one if possible
+  // (due to post order, latest select is most likely replacement). If needed, we could
+  // improve this by e.g. using the operands in the map as well.
+  auto it = cache->find(condition);
+  if (it == cache->end()) {
+    cache->Put(condition, select);
+  } else {
+    // Found cached value. See if latest can replace cached in the HIR.
+    HSelect* cached_select = it->second;
+    DCHECK_EQ(cached_select->GetCondition(), select->GetCondition());
+    if (cached_select->GetTrueValue() == select->GetTrueValue() &&
+        cached_select->GetFalseValue() == select->GetFalseValue() &&
+        select->StrictlyDominates(cached_select)) {
+      cached_select->ReplaceWith(select);
+      cached_select->GetBlock()->RemoveInstruction(cached_select);
+    }
+    it->second = select;  // always cache latest
+  }
+
+  // No need to update dominance information, as we are simplifying
+  // a simple diamond shape, where the join block is merged with the
+  // entry block. Any following blocks would have had the join block
+  // as a dominator, and `MergeWith` handles changing that to the
+  // entry block
+  return true;
+}
+
+HBasicBlock* HSelectGenerator::TryFixupDoubleDiamondPattern(HBasicBlock* block) {
+  DCHECK(block->GetLastInstruction()->IsIf());
+  HIf* if_instruction = block->GetLastInstruction()->AsIf();
+  HBasicBlock* true_block = if_instruction->IfTrueSuccessor();
+  HBasicBlock* false_block = if_instruction->IfFalseSuccessor();
+  DCHECK_NE(true_block, false_block);
+
+  // One branch must be a single goto, and the other one the inner if.
+  if (true_block->IsSingleGoto() == false_block->IsSingleGoto()) {
+    return nullptr;
+  }
+
+  HBasicBlock* single_goto = true_block->IsSingleGoto() ? true_block : false_block;
+  HBasicBlock* inner_if_block = true_block->IsSingleGoto() ? false_block : true_block;
+
+  // The innner if branch has to be a block with just a comparison and an if.
+  if (!inner_if_block->EndsWithIf() ||
+      inner_if_block->GetLastInstruction()->AsIf()->InputAt(0) !=
+          inner_if_block->GetFirstInstruction() ||
+      inner_if_block->GetLastInstruction()->GetPrevious() !=
+          inner_if_block->GetFirstInstruction() ||
+      !inner_if_block->GetFirstInstruction()->IsCondition()) {
+    return nullptr;
+  }
+
+  HIf* inner_if_instruction = inner_if_block->GetLastInstruction()->AsIf();
+  HBasicBlock* inner_if_true_block = inner_if_instruction->IfTrueSuccessor();
+  HBasicBlock* inner_if_false_block = inner_if_instruction->IfFalseSuccessor();
+  if (!inner_if_true_block->IsSingleGoto() || !inner_if_false_block->IsSingleGoto()) {
+    return nullptr;
+  }
+
+  // One must merge into the outer condition and the other must not.
+  if (BlocksMergeTogether(single_goto, inner_if_true_block) ==
+      BlocksMergeTogether(single_goto, inner_if_false_block)) {
+    return nullptr;
+  }
+
+  // First merge merges the outer if with one of the inner if branches. The block must be a Phi and
+  // a Goto.
+  HBasicBlock* first_merge = single_goto->GetSingleSuccessor();
+  if (first_merge->GetNumberOfPredecessors() != 2 ||
+      first_merge->GetPhis().CountSize() != 1 ||
+      !first_merge->GetLastInstruction()->IsGoto() ||
+      first_merge->GetFirstInstruction() != first_merge->GetLastInstruction()) {
+    return nullptr;
+  }
+
+  HPhi* first_phi = first_merge->GetFirstPhi()->AsPhi();
+
+  // Second merge is first_merge and the remainder branch merging. It must be phi + goto, or phi +
+  // return. Depending on the first merge, we define the second merge.
+  HBasicBlock* merges_into_second_merge =
+    BlocksMergeTogether(single_goto, inner_if_true_block)
+      ? inner_if_false_block
+      : inner_if_true_block;
+  if (!BlocksMergeTogether(first_merge, merges_into_second_merge)) {
+    return nullptr;
+  }
+
+  HBasicBlock* second_merge = merges_into_second_merge->GetSingleSuccessor();
+  if (second_merge->GetNumberOfPredecessors() != 2 ||
+      second_merge->GetPhis().CountSize() != 1 ||
+      !(second_merge->GetLastInstruction()->IsGoto() ||
+        second_merge->GetLastInstruction()->IsReturn()) ||
+      second_merge->GetFirstInstruction() != second_merge->GetLastInstruction()) {
+    return nullptr;
+  }
+
+  size_t index = second_merge->GetPredecessorIndexOf(merges_into_second_merge);
+  HPhi* second_phi = second_merge->GetFirstPhi()->AsPhi();
+
+  // Merge the phis.
+  first_phi->AddInput(second_phi->InputAt(index));
+  merges_into_second_merge->ReplaceSuccessor(second_merge, first_merge);
+  second_phi->ReplaceWith(first_phi);
+  second_merge->RemovePhi(second_phi);
+
+  // Sort out the new domination before merging the blocks
+  DCHECK_EQ(second_merge->GetSinglePredecessor(), first_merge);
+  second_merge->GetDominator()->RemoveDominatedBlock(second_merge);
+  second_merge->SetDominator(first_merge);
+  first_merge->AddDominatedBlock(second_merge);
+  first_merge->MergeWith(second_merge);
+
+  // No need to update dominance information. There's a chance that `merges_into_second_merge`
+  // doesn't come before `first_merge` but we don't need to fix it since `merges_into_second_merge`
+  // will disappear from the graph altogether when doing the follow-up
+  // TryGenerateSelectSimpleDiamondPattern.
+
+  return inner_if_block;
+}
+
 bool HSelectGenerator::Run() {
-  bool didSelect = false;
+  bool did_select = false;
   // Select cache with local allocator.
   ScopedArenaAllocator allocator(graph_->GetArenaStack());
-  ScopedArenaSafeMap<HInstruction*, HSelect*> cache(
-      std::less<HInstruction*>(), allocator.Adapter(kArenaAllocSelectGenerator));
+  ScopedArenaSafeMap<HInstruction*, HSelect*> cache(std::less<HInstruction*>(),
+                                                    allocator.Adapter(kArenaAllocSelectGenerator));
 
   // Iterate in post order in the unlikely case that removing one occurrence of
   // the selection pattern empties a branch block of another occurrence.
   for (HBasicBlock* block : graph_->GetPostOrder()) {
-    if (!block->EndsWithIf()) continue;
-
-    // Find elements of the diamond pattern.
-    HIf* if_instruction = block->GetLastInstruction()->AsIf();
-    HBasicBlock* true_block = if_instruction->IfTrueSuccessor();
-    HBasicBlock* false_block = if_instruction->IfFalseSuccessor();
-    DCHECK_NE(true_block, false_block);
-
-    if (!IsSimpleBlock(true_block) ||
-        !IsSimpleBlock(false_block) ||
-        !BlocksMergeTogether(true_block, false_block)) {
+    if (!block->EndsWithIf()) {
       continue;
     }
-    HBasicBlock* merge_block = true_block->GetSingleSuccessor();
 
-    // If the branches are not empty, move instructions in front of the If.
-    // TODO(dbrazdil): This puts an instruction between If and its condition.
-    //                 Implement moving of conditions to first users if possible.
-    while (!true_block->IsSingleGoto() && !true_block->IsSingleReturn()) {
-      HInstruction* instr = true_block->GetFirstInstruction();
-      DCHECK(!instr->CanThrow());
-      instr->MoveBefore(if_instruction);
-    }
-    while (!false_block->IsSingleGoto() && !false_block->IsSingleReturn()) {
-      HInstruction* instr = false_block->GetFirstInstruction();
-      DCHECK(!instr->CanThrow());
-      instr->MoveBefore(if_instruction);
-    }
-    DCHECK(true_block->IsSingleGoto() || true_block->IsSingleReturn());
-    DCHECK(false_block->IsSingleGoto() || false_block->IsSingleReturn());
-
-    // Find the resulting true/false values.
-    size_t predecessor_index_true = merge_block->GetPredecessorIndexOf(true_block);
-    size_t predecessor_index_false = merge_block->GetPredecessorIndexOf(false_block);
-    DCHECK_NE(predecessor_index_true, predecessor_index_false);
-
-    bool both_successors_return = true_block->IsSingleReturn() && false_block->IsSingleReturn();
-    HPhi* phi = GetSingleChangedPhi(merge_block, predecessor_index_true, predecessor_index_false);
-
-    HInstruction* true_value = nullptr;
-    HInstruction* false_value = nullptr;
-    if (both_successors_return) {
-      true_value = true_block->GetFirstInstruction()->InputAt(0);
-      false_value = false_block->GetFirstInstruction()->InputAt(0);
-    } else if (phi != nullptr) {
-      true_value = phi->InputAt(predecessor_index_true);
-      false_value = phi->InputAt(predecessor_index_false);
+    if (TryGenerateSelectSimpleDiamondPattern(block, &cache)) {
+      did_select = true;
     } else {
-      continue;
-    }
-    DCHECK(both_successors_return || phi != nullptr);
-
-    // Create the Select instruction and insert it in front of the If.
-    HInstruction* condition = if_instruction->InputAt(0);
-    HSelect* select = new (graph_->GetAllocator()) HSelect(condition,
-                                                           true_value,
-                                                           false_value,
-                                                           if_instruction->GetDexPc());
-    if (both_successors_return) {
-      if (true_value->GetType() == DataType::Type::kReference) {
-        DCHECK(false_value->GetType() == DataType::Type::kReference);
-        ReferenceTypePropagation::FixUpInstructionType(select, graph_->GetHandleCache());
+      // Try to fix up the odd version of the double diamond pattern. If we could do it, it means
+      // that we can generate two selects.
+      HBasicBlock* inner_if_block = TryFixupDoubleDiamondPattern(block);
+      if (inner_if_block != nullptr) {
+        // Generate the selects now since `inner_if_block` should be after `block` in PostOrder.
+        bool result = TryGenerateSelectSimpleDiamondPattern(inner_if_block, &cache);
+        DCHECK(result);
+        result = TryGenerateSelectSimpleDiamondPattern(block, &cache);
+        DCHECK(result);
+        did_select = true;
       }
-    } else if (phi->GetType() == DataType::Type::kReference) {
-      select->SetReferenceTypeInfo(phi->GetReferenceTypeInfo());
     }
-    block->InsertInstructionBefore(select, if_instruction);
-
-    // Remove the true branch which removes the corresponding Phi
-    // input if needed. If left only with the false branch, the Phi is
-    // automatically removed.
-    if (both_successors_return) {
-      false_block->GetFirstInstruction()->ReplaceInput(select, 0);
-    } else {
-      phi->ReplaceInput(select, predecessor_index_false);
-    }
-
-    bool only_two_predecessors = (merge_block->GetPredecessors().size() == 2u);
-    true_block->DisconnectAndDelete();
-
-    // Merge remaining blocks which are now connected with Goto.
-    DCHECK_EQ(block->GetSingleSuccessor(), false_block);
-    block->MergeWith(false_block);
-    if (!both_successors_return && only_two_predecessors) {
-      DCHECK_EQ(only_two_predecessors, phi->GetBlock() == nullptr);
-      DCHECK_EQ(block->GetSingleSuccessor(), merge_block);
-      block->MergeWith(merge_block);
-    }
-
-    MaybeRecordStat(stats_, MethodCompilationStat::kSelectGenerated);
-
-    // Very simple way of finding common subexpressions in the generated HSelect statements
-    // (since this runs after GVN). Lookup by condition, and reuse latest one if possible
-    // (due to post order, latest select is most likely replacement). If needed, we could
-    // improve this by e.g. using the operands in the map as well.
-    auto it = cache.find(condition);
-    if (it == cache.end()) {
-      cache.Put(condition, select);
-    } else {
-      // Found cached value. See if latest can replace cached in the HIR.
-      HSelect* cached = it->second;
-      DCHECK_EQ(cached->GetCondition(), select->GetCondition());
-      if (cached->GetTrueValue() == select->GetTrueValue() &&
-          cached->GetFalseValue() == select->GetFalseValue() &&
-          select->StrictlyDominates(cached)) {
-       cached->ReplaceWith(select);
-       cached->GetBlock()->RemoveInstruction(cached);
-      }
-      it->second = select;  // always cache latest
-    }
-
-    // No need to update dominance information, as we are simplifying
-    // a simple diamond shape, where the join block is merged with the
-    // entry block. Any following blocks would have had the join block
-    // as a dominator, and `MergeWith` handles changing that to the
-    // entry block.
-    didSelect = true;
   }
-  return didSelect;
+
+  return did_select;
 }
 
 }  // namespace art
diff --git a/compiler/optimizing/select_generator.h b/compiler/optimizing/select_generator.h
index 30ac8a8..7aa0803 100644
--- a/compiler/optimizing/select_generator.h
+++ b/compiler/optimizing/select_generator.h
@@ -57,9 +57,12 @@
 #ifndef ART_COMPILER_OPTIMIZING_SELECT_GENERATOR_H_
 #define ART_COMPILER_OPTIMIZING_SELECT_GENERATOR_H_
 
+#include "base/macros.h"
+#include "base/scoped_arena_containers.h"
 #include "optimization.h"
+#include "optimizing/nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class HSelectGenerator : public HOptimization {
  public:
@@ -72,6 +75,43 @@
   static constexpr const char* kSelectGeneratorPassName = "select_generator";
 
  private:
+  bool TryGenerateSelectSimpleDiamondPattern(HBasicBlock* block,
+                                             ScopedArenaSafeMap<HInstruction*, HSelect*>* cache);
+
+  // When generating code for nested ternary operators (e.g. `return (x > 100) ? 100 : ((x < -100) ?
+  // -100 : x);`), a dexer can generate a double diamond pattern but it is not a clear cut one due
+  // to the merging of the blocks. `TryFixupDoubleDiamondPattern` recognizes that pattern and fixes
+  // up the graph to have a clean double diamond that `TryGenerateSelectSimpleDiamondPattern` can
+  // use to generate selects.
+  //
+  // In ASCII, it turns:
+  //
+  //      1 (outer if)
+  //     / \
+  //    2   3 (inner if)
+  //    |  / \
+  //    | 4  5
+  //     \/  |
+  //      6  |
+  //       \ |
+  //         7
+  //         |
+  //         8
+  // into:
+  //      1 (outer if)
+  //     / \
+  //    2   3 (inner if)
+  //    |  / \
+  //    | 4  5
+  //     \/ /
+  //      6
+  //      |
+  //      8
+  //
+  // In short, block 7 disappears and we merge 6 and 7. Now we have a diamond with {3,4,5,6}, and
+  // when that gets resolved we get another one with the outer if.
+  HBasicBlock* TryFixupDoubleDiamondPattern(HBasicBlock* block);
+
   DISALLOW_COPY_AND_ASSIGN(HSelectGenerator);
 };
 
diff --git a/compiler/optimizing/select_generator_test.cc b/compiler/optimizing/select_generator_test.cc
index b18d41a..fc9e150 100644
--- a/compiler/optimizing/select_generator_test.cc
+++ b/compiler/optimizing/select_generator_test.cc
@@ -17,12 +17,13 @@
 #include "select_generator.h"
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "nodes.h"
 #include "optimizing_unit_test.h"
 #include "side_effects_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class SelectGeneratorTest : public OptimizingUnitTest {
  protected:
diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc
index 17cf3d3..277edff 100644
--- a/compiler/optimizing/sharpening.cc
+++ b/compiler/optimizing/sharpening.cc
@@ -34,7 +34,7 @@
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 static bool IsInBootImage(ArtMethod* method) {
   gc::Heap* heap = Runtime::Current()->GetHeap();
@@ -63,9 +63,9 @@
     bool for_interface_call,
     CodeGenerator* codegen) {
   if (kIsDebugBuild) {
-    ScopedObjectAccess soa(Thread::Current());  // Required for GetDeclaringClass below.
+    ScopedObjectAccess soa(Thread::Current());  // Required for `IsStringConstructor()` below.
     DCHECK(callee != nullptr);
-    DCHECK(!(callee->IsConstructor() && callee->GetDeclaringClass()->IsStringClass()));
+    DCHECK(!callee->IsStringConstructor());
   }
 
   MethodLoadKind method_load_kind;
diff --git a/compiler/optimizing/sharpening.h b/compiler/optimizing/sharpening.h
index 9753669..6dfe904 100644
--- a/compiler/optimizing/sharpening.h
+++ b/compiler/optimizing/sharpening.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_SHARPENING_H_
 #define ART_COMPILER_OPTIMIZING_SHARPENING_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 class DexCompilationUnit;
diff --git a/compiler/optimizing/side_effects_analysis.cc b/compiler/optimizing/side_effects_analysis.cc
index ba97b43..56719b1 100644
--- a/compiler/optimizing/side_effects_analysis.cc
+++ b/compiler/optimizing/side_effects_analysis.cc
@@ -16,7 +16,7 @@
 
 #include "side_effects_analysis.h"
 
-namespace art {
+namespace art HIDDEN {
 
 bool SideEffectsAnalysis::Run() {
   // Inlining might have created more blocks, so we need to increase the size
diff --git a/compiler/optimizing/side_effects_analysis.h b/compiler/optimizing/side_effects_analysis.h
index 56a01e6..47fcdc5 100644
--- a/compiler/optimizing/side_effects_analysis.h
+++ b/compiler/optimizing/side_effects_analysis.h
@@ -18,10 +18,11 @@
 #define ART_COMPILER_OPTIMIZING_SIDE_EFFECTS_ANALYSIS_H_
 
 #include "base/arena_containers.h"
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class SideEffectsAnalysis : public HOptimization {
  public:
diff --git a/compiler/optimizing/side_effects_test.cc b/compiler/optimizing/side_effects_test.cc
index 268798c..f2b781d 100644
--- a/compiler/optimizing/side_effects_test.cc
+++ b/compiler/optimizing/side_effects_test.cc
@@ -16,10 +16,11 @@
 
 #include <gtest/gtest.h>
 
+#include "base/macros.h"
 #include "data_type.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Only runtime types other than void are allowed.
 static const DataType::Type kTestTypes[] = {
diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc
index 67ee83c..a658252 100644
--- a/compiler/optimizing/ssa_builder.cc
+++ b/compiler/optimizing/ssa_builder.cc
@@ -27,7 +27,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "ssa_phi_elimination.h"
 
-namespace art {
+namespace art HIDDEN {
 
 void SsaBuilder::FixNullConstantType() {
   // The order doesn't matter here.
@@ -538,7 +538,6 @@
   // Compute type of reference type instructions. The pass assumes that
   // NullConstant has been fixed up.
   ReferenceTypePropagation(graph_,
-                           class_loader_,
                            dex_cache_,
                            /* is_first_run= */ true).Run();
 
diff --git a/compiler/optimizing/ssa_builder.h b/compiler/optimizing/ssa_builder.h
index a7d4e0e..99a5469 100644
--- a/compiler/optimizing/ssa_builder.h
+++ b/compiler/optimizing/ssa_builder.h
@@ -17,12 +17,13 @@
 #ifndef ART_COMPILER_OPTIMIZING_SSA_BUILDER_H_
 #define ART_COMPILER_OPTIMIZING_SSA_BUILDER_H_
 
+#include "base/macros.h"
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Transforms a graph into SSA form. The liveness guarantees of
diff --git a/compiler/optimizing/ssa_liveness_analysis.cc b/compiler/optimizing/ssa_liveness_analysis.cc
index 18942a1..317e099 100644
--- a/compiler/optimizing/ssa_liveness_analysis.cc
+++ b/compiler/optimizing/ssa_liveness_analysis.cc
@@ -21,7 +21,7 @@
 #include "linear_order.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 void SsaLivenessAnalysis::Analyze() {
   // Compute the linear order directly in the graph's data structure
diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h
index 7f31585..cc2b49c 100644
--- a/compiler/optimizing/ssa_liveness_analysis.h
+++ b/compiler/optimizing/ssa_liveness_analysis.h
@@ -21,11 +21,12 @@
 
 #include "base/intrusive_forward_list.h"
 #include "base/iteration_range.h"
+#include "base/macros.h"
 #include "base/scoped_arena_allocator.h"
 #include "base/scoped_arena_containers.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 class SsaLivenessAnalysis;
diff --git a/compiler/optimizing/ssa_liveness_analysis_test.cc b/compiler/optimizing/ssa_liveness_analysis_test.cc
index a477893..2df0f34 100644
--- a/compiler/optimizing/ssa_liveness_analysis_test.cc
+++ b/compiler/optimizing/ssa_liveness_analysis_test.cc
@@ -20,12 +20,13 @@
 #include "arch/instruction_set_features.h"
 #include "base/arena_allocator.h"
 #include "base/arena_containers.h"
+#include "base/macros.h"
 #include "code_generator.h"
 #include "driver/compiler_options.h"
 #include "nodes.h"
 #include "optimizing_unit_test.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class SsaLivenessAnalysisTest : public OptimizingUnitTest {
  protected:
diff --git a/compiler/optimizing/ssa_phi_elimination.cc b/compiler/optimizing/ssa_phi_elimination.cc
index 8fd6962..ce343df 100644
--- a/compiler/optimizing/ssa_phi_elimination.cc
+++ b/compiler/optimizing/ssa_phi_elimination.cc
@@ -21,7 +21,7 @@
 #include "base/scoped_arena_containers.h"
 #include "base/bit_vector-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 bool SsaDeadPhiElimination::Run() {
   MarkDeadPhis();
diff --git a/compiler/optimizing/ssa_phi_elimination.h b/compiler/optimizing/ssa_phi_elimination.h
index c5cc752..f606f92 100644
--- a/compiler/optimizing/ssa_phi_elimination.h
+++ b/compiler/optimizing/ssa_phi_elimination.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_SSA_PHI_ELIMINATION_H_
 #define ART_COMPILER_OPTIMIZING_SSA_PHI_ELIMINATION_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Optimization phase that removes dead phis from the graph. Dead phis are unused
diff --git a/compiler/optimizing/ssa_test.cc b/compiler/optimizing/ssa_test.cc
index e679893..980493d 100644
--- a/compiler/optimizing/ssa_test.cc
+++ b/compiler/optimizing/ssa_test.cc
@@ -17,6 +17,7 @@
 #include "android-base/stringprintf.h"
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "builder.h"
 #include "dex/dex_file.h"
 #include "dex/dex_instruction.h"
@@ -27,9 +28,9 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
-class SsaTest : public OptimizingUnitTest {
+class SsaTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   void TestCode(const std::vector<uint16_t>& data, const char* expected);
 };
diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc
index f55bbee..1a368ed 100644
--- a/compiler/optimizing/stack_map_stream.cc
+++ b/compiler/optimizing/stack_map_stream.cc
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "art_method-inl.h"
+#include "base/globals.h"
 #include "base/stl_util.h"
 #include "class_linker.h"
 #include "dex/dex_file.h"
@@ -32,7 +33,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "stack_map.h"
 
-namespace art {
+namespace art HIDDEN {
 
 constexpr static bool kVerifyStackMaps = kIsDebugBuild;
 
@@ -49,7 +50,8 @@
                                  size_t core_spill_mask,
                                  size_t fp_spill_mask,
                                  uint32_t num_dex_registers,
-                                 bool baseline) {
+                                 bool baseline,
+                                 bool debuggable) {
   DCHECK(!in_method_) << "Mismatched Begin/End calls";
   in_method_ = true;
   DCHECK_EQ(packed_frame_size_, 0u) << "BeginMethod was already called";
@@ -60,6 +62,7 @@
   fp_spill_mask_ = fp_spill_mask;
   num_dex_registers_ = num_dex_registers;
   baseline_ = baseline;
+  debuggable_ = debuggable;
 
   if (kVerifyStackMaps) {
     dchecks_.emplace_back([=](const CodeInfo& code_info) {
@@ -99,16 +102,21 @@
   }
 }
 
-void StackMapStream::BeginStackMapEntry(uint32_t dex_pc,
-                                        uint32_t native_pc_offset,
-                                        uint32_t register_mask,
-                                        BitVector* stack_mask,
-                                        StackMap::Kind kind,
-                                        bool needs_vreg_info) {
+void StackMapStream::BeginStackMapEntry(
+    uint32_t dex_pc,
+    uint32_t native_pc_offset,
+    uint32_t register_mask,
+    BitVector* stack_mask,
+    StackMap::Kind kind,
+    bool needs_vreg_info,
+    const std::vector<uint32_t>& dex_pc_list_for_catch_verification) {
   DCHECK(in_method_) << "Call BeginMethod first";
   DCHECK(!in_stack_map_) << "Mismatched Begin/End calls";
   in_stack_map_ = true;
 
+  DCHECK_IMPLIES(!dex_pc_list_for_catch_verification.empty(), kind == StackMap::Kind::Catch);
+  DCHECK_IMPLIES(!dex_pc_list_for_catch_verification.empty(), kIsDebugBuild);
+
   current_stack_map_ = BitTableBuilder<StackMap>::Entry();
   current_stack_map_[StackMap::kKind] = static_cast<uint32_t>(kind);
   current_stack_map_[StackMap::kPackedNativePc] =
@@ -149,7 +157,8 @@
                                                                     instruction_set_);
         CHECK_EQ(stack_map.Row(), stack_map_index);
       } else if (kind == StackMap::Kind::Catch) {
-        StackMap stack_map = code_info.GetCatchStackMapForDexPc(dex_pc);
+        StackMap stack_map = code_info.GetCatchStackMapForDexPc(
+            ArrayRef<const uint32_t>(dex_pc_list_for_catch_verification));
         CHECK_EQ(stack_map.Row(), stack_map_index);
       }
       StackMap stack_map = code_info.GetStackMapAt(stack_map_index);
@@ -367,6 +376,7 @@
 
   uint32_t flags = (inline_infos_.size() > 0) ? CodeInfo::kHasInlineInfo : 0;
   flags |= baseline_ ? CodeInfo::kIsBaseline : 0;
+  flags |= debuggable_ ? CodeInfo::kIsDebuggable : 0;
   DCHECK_LE(flags, kVarintMax);  // Ensure flags can be read directly as byte.
   uint32_t bit_table_flags = 0;
   ForEachBitTable([&bit_table_flags](size_t i, auto bit_table) {
diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h
index 27145a1..643af2d 100644
--- a/compiler/optimizing/stack_map_stream.h
+++ b/compiler/optimizing/stack_map_stream.h
@@ -21,6 +21,7 @@
 #include "base/arena_bit_vector.h"
 #include "base/bit_table.h"
 #include "base/bit_vector-inl.h"
+#include "base/macros.h"
 #include "base/memory_region.h"
 #include "base/scoped_arena_containers.h"
 #include "base/value_object.h"
@@ -28,7 +29,7 @@
 #include "nodes.h"
 #include "stack_map.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class CodeGenerator;
 
@@ -64,15 +65,19 @@
                    size_t core_spill_mask,
                    size_t fp_spill_mask,
                    uint32_t num_dex_registers,
-                   bool baseline = false);
+                   bool baseline,
+                   bool debuggable);
   void EndMethod(size_t code_size);
 
-  void BeginStackMapEntry(uint32_t dex_pc,
-                          uint32_t native_pc_offset,
-                          uint32_t register_mask = 0,
-                          BitVector* sp_mask = nullptr,
-                          StackMap::Kind kind = StackMap::Kind::Default,
-                          bool needs_vreg_info = true);
+  void BeginStackMapEntry(
+      uint32_t dex_pc,
+      uint32_t native_pc_offset,
+      uint32_t register_mask = 0,
+      BitVector* sp_mask = nullptr,
+      StackMap::Kind kind = StackMap::Kind::Default,
+      bool needs_vreg_info = true,
+      const std::vector<uint32_t>& dex_pc_list_for_catch_verification = std::vector<uint32_t>());
+
   void EndStackMapEntry();
 
   void AddDexRegisterEntry(DexRegisterLocation::Kind kind, int32_t value) {
@@ -125,6 +130,7 @@
   uint32_t fp_spill_mask_ = 0;
   uint32_t num_dex_registers_ = 0;
   bool baseline_;
+  bool debuggable_;
   BitTableBuilder<StackMap> stack_maps_;
   BitTableBuilder<RegisterMask> register_masks_;
   BitmapTableBuilder stack_masks_;
diff --git a/compiler/optimizing/stack_map_test.cc b/compiler/optimizing/stack_map_test.cc
index f6a739e..a2c30e7 100644
--- a/compiler/optimizing/stack_map_test.cc
+++ b/compiler/optimizing/stack_map_test.cc
@@ -18,12 +18,13 @@
 
 #include "art_method.h"
 #include "base/arena_bit_vector.h"
+#include "base/macros.h"
 #include "base/malloc_arena_pool.h"
 #include "stack_map_stream.h"
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Check that the stack mask of given stack map is identical
 // to the given bit vector. Returns true if they are same.
@@ -52,7 +53,12 @@
   ArenaStack arena_stack(&pool);
   ScopedArenaAllocator allocator(&arena_stack);
   StackMapStream stream(&allocator, kRuntimeISA);
-  stream.BeginMethod(32, 0, 0, 2);
+  stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+                     /* core_spill_mask= */ 0,
+                     /* fp_spill_mask= */ 0,
+                     /* num_dex_registers= */ 2,
+                     /* baseline= */ false,
+                     /* debuggable= */ false);
 
   ArenaBitVector sp_mask(&allocator, 0, false);
   size_t number_of_dex_registers = 2;
@@ -106,7 +112,12 @@
   ArenaStack arena_stack(&pool);
   ScopedArenaAllocator allocator(&arena_stack);
   StackMapStream stream(&allocator, kRuntimeISA);
-  stream.BeginMethod(32, 0, 0, 2);
+  stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+                     /* core_spill_mask= */ 0,
+                     /* fp_spill_mask= */ 0,
+                     /* num_dex_registers= */ 2,
+                     /* baseline= */ false,
+                     /* debuggable= */ false);
   ArtMethod art_method;
 
   ArenaBitVector sp_mask1(&allocator, 0, true);
@@ -300,7 +311,12 @@
   ArenaStack arena_stack(&pool);
   ScopedArenaAllocator allocator(&arena_stack);
   StackMapStream stream(&allocator, kRuntimeISA);
-  stream.BeginMethod(32, 0, 0, 2);
+  stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+                     /* core_spill_mask= */ 0,
+                     /* fp_spill_mask= */ 0,
+                     /* num_dex_registers= */ 2,
+                     /* baseline= */ false,
+                     /* debuggable= */ false);
   ArtMethod art_method;
 
   ArenaBitVector sp_mask1(&allocator, 0, true);
@@ -363,7 +379,12 @@
   ArenaStack arena_stack(&pool);
   ScopedArenaAllocator allocator(&arena_stack);
   StackMapStream stream(&allocator, kRuntimeISA);
-  stream.BeginMethod(32, 0, 0, 2);
+  stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+                     /* core_spill_mask= */ 0,
+                     /* fp_spill_mask= */ 0,
+                     /* num_dex_registers= */ 2,
+                     /* baseline= */ false,
+                     /* debuggable= */ false);
 
   ArenaBitVector sp_mask(&allocator, 0, false);
   uint32_t number_of_dex_registers = 2;
@@ -411,7 +432,12 @@
   ArenaStack arena_stack(&pool);
   ScopedArenaAllocator allocator(&arena_stack);
   StackMapStream stream(&allocator, kRuntimeISA);
-  stream.BeginMethod(32, 0, 0, 2);
+  stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+                     /* core_spill_mask= */ 0,
+                     /* fp_spill_mask= */ 0,
+                     /* num_dex_registers= */ 2,
+                     /* baseline= */ false,
+                     /* debuggable= */ false);
 
   ArenaBitVector sp_mask(&allocator, 0, false);
   uint32_t number_of_dex_registers = 2;
@@ -467,7 +493,12 @@
   ArenaStack arena_stack(&pool);
   ScopedArenaAllocator allocator(&arena_stack);
   StackMapStream stream(&allocator, kRuntimeISA);
-  stream.BeginMethod(32, 0, 0, 1);
+  stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+                     /* core_spill_mask= */ 0,
+                     /* fp_spill_mask= */ 0,
+                     /* num_dex_registers= */ 1,
+                     /* baseline= */ false,
+                     /* debuggable= */ false);
 
   ArenaBitVector sp_mask(&allocator, 0, false);
   stream.BeginStackMapEntry(0, 64 * kPcAlign, 0x3, &sp_mask);
@@ -512,7 +543,12 @@
   ArenaStack arena_stack(&pool);
   ScopedArenaAllocator allocator(&arena_stack);
   StackMapStream stream(&allocator, kRuntimeISA);
-  stream.BeginMethod(32, 0, 0, 2);
+  stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+                     /* core_spill_mask= */ 0,
+                     /* fp_spill_mask= */ 0,
+                     /* num_dex_registers= */ 2,
+                     /* baseline= */ false,
+                     /* debuggable= */ false);
   ArtMethod art_method;
 
   ArenaBitVector sp_mask1(&allocator, 0, true);
@@ -702,7 +738,12 @@
   ArenaStack arena_stack(&pool);
   ScopedArenaAllocator allocator(&arena_stack);
   StackMapStream stream(&allocator, kRuntimeISA);
-  stream.BeginMethod(32, 0, 0, 0);
+  stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+                     /* core_spill_mask= */ 0,
+                     /* fp_spill_mask= */ 0,
+                     /* num_dex_registers= */ 0,
+                     /* baseline= */ false,
+                     /* debuggable= */ false);
 
   ArenaBitVector sp_mask(&allocator, 0, true);
   sp_mask.SetBit(1);
diff --git a/compiler/optimizing/superblock_cloner.cc b/compiler/optimizing/superblock_cloner.cc
index a5f919c..7c0097c 100644
--- a/compiler/optimizing/superblock_cloner.cc
+++ b/compiler/optimizing/superblock_cloner.cc
@@ -22,7 +22,7 @@
 
 #include <sstream>
 
-namespace art {
+namespace art HIDDEN {
 
 using HBasicBlockMap = SuperblockCloner::HBasicBlockMap;
 using HInstructionMap = SuperblockCloner::HInstructionMap;
@@ -633,7 +633,7 @@
     HPhi* phi = new (arena_) HPhi(arena_, kNoRegNumber, 0, value->GetType());
 
     if (value->GetType() == DataType::Type::kReference) {
-      phi->SetReferenceTypeInfo(value->GetReferenceTypeInfo());
+      phi->SetReferenceTypeInfoIfValid(value->GetReferenceTypeInfo());
     }
 
     exit_block->AddPhi(phi);
diff --git a/compiler/optimizing/superblock_cloner.h b/compiler/optimizing/superblock_cloner.h
index 1f6ee74..421701f 100644
--- a/compiler/optimizing/superblock_cloner.h
+++ b/compiler/optimizing/superblock_cloner.h
@@ -20,9 +20,10 @@
 #include "base/arena_bit_vector.h"
 #include "base/arena_containers.h"
 #include "base/bit_vector-inl.h"
+#include "base/macros.h"
 #include "nodes.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class InductionVarRange;
 
diff --git a/compiler/optimizing/superblock_cloner_test.cc b/compiler/optimizing/superblock_cloner_test.cc
index d8d68b7..ea2563e 100644
--- a/compiler/optimizing/superblock_cloner_test.cc
+++ b/compiler/optimizing/superblock_cloner_test.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "base/macros.h"
 #include "graph_checker.h"
 #include "nodes.h"
 #include "optimizing_unit_test.h"
@@ -21,7 +22,7 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
 using HBasicBlockMap = SuperblockCloner::HBasicBlockMap;
 using HInstructionMap = SuperblockCloner::HInstructionMap;
diff --git a/compiler/optimizing/suspend_check_test.cc b/compiler/optimizing/suspend_check_test.cc
index 33823e2..76e7e0c 100644
--- a/compiler/optimizing/suspend_check_test.cc
+++ b/compiler/optimizing/suspend_check_test.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "base/macros.h"
 #include "builder.h"
 #include "dex/dex_instruction.h"
 #include "nodes.h"
@@ -22,13 +23,13 @@
 
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 
 /**
  * Check that the HGraphBuilder adds suspend checks to backward branches.
  */
 
-class SuspendCheckTest : public OptimizingUnitTest {
+class SuspendCheckTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
  protected:
   void TestCode(const std::vector<uint16_t>& data);
 };
diff --git a/compiler/optimizing/write_barrier_elimination.cc b/compiler/optimizing/write_barrier_elimination.cc
new file mode 100644
index 0000000..eb70b67
--- /dev/null
+++ b/compiler/optimizing/write_barrier_elimination.cc
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "write_barrier_elimination.h"
+
+#include "base/arena_allocator.h"
+#include "base/scoped_arena_allocator.h"
+#include "base/scoped_arena_containers.h"
+#include "optimizing/nodes.h"
+
+namespace art HIDDEN {
+
+class WBEVisitor final : public HGraphVisitor {
+ public:
+  WBEVisitor(HGraph* graph, OptimizingCompilerStats* stats)
+      : HGraphVisitor(graph),
+        scoped_allocator_(graph->GetArenaStack()),
+        current_write_barriers_(scoped_allocator_.Adapter(kArenaAllocWBE)),
+        stats_(stats) {}
+
+  void VisitBasicBlock(HBasicBlock* block) override {
+    // We clear the map to perform this optimization only in the same block. Doing it across blocks
+    // would entail non-trivial merging of states.
+    current_write_barriers_.clear();
+    HGraphVisitor::VisitBasicBlock(block);
+  }
+
+  void VisitInstanceFieldSet(HInstanceFieldSet* instruction) override {
+    DCHECK(!instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()));
+
+    if (instruction->GetFieldType() != DataType::Type::kReference ||
+        instruction->GetValue()->IsNullConstant()) {
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      return;
+    }
+
+    MaybeRecordStat(stats_, MethodCompilationStat::kPossibleWriteBarrier);
+    HInstruction* obj = HuntForOriginalReference(instruction->InputAt(0));
+    auto it = current_write_barriers_.find(obj);
+    if (it != current_write_barriers_.end()) {
+      DCHECK(it->second->IsInstanceFieldSet());
+      DCHECK(it->second->AsInstanceFieldSet()->GetWriteBarrierKind() !=
+             WriteBarrierKind::kDontEmit);
+      DCHECK_EQ(it->second->GetBlock(), instruction->GetBlock());
+      it->second->AsInstanceFieldSet()->SetWriteBarrierKind(WriteBarrierKind::kEmitNoNullCheck);
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      MaybeRecordStat(stats_, MethodCompilationStat::kRemovedWriteBarrier);
+    } else {
+      const bool inserted = current_write_barriers_.insert({obj, instruction}).second;
+      DCHECK(inserted);
+      DCHECK(instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
+    }
+  }
+
+  void VisitStaticFieldSet(HStaticFieldSet* instruction) override {
+    DCHECK(!instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()));
+
+    if (instruction->GetFieldType() != DataType::Type::kReference ||
+        instruction->GetValue()->IsNullConstant()) {
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      return;
+    }
+
+    MaybeRecordStat(stats_, MethodCompilationStat::kPossibleWriteBarrier);
+    HInstruction* cls = HuntForOriginalReference(instruction->InputAt(0));
+    auto it = current_write_barriers_.find(cls);
+    if (it != current_write_barriers_.end()) {
+      DCHECK(it->second->IsStaticFieldSet());
+      DCHECK(it->second->AsStaticFieldSet()->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
+      DCHECK_EQ(it->second->GetBlock(), instruction->GetBlock());
+      it->second->AsStaticFieldSet()->SetWriteBarrierKind(WriteBarrierKind::kEmitNoNullCheck);
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      MaybeRecordStat(stats_, MethodCompilationStat::kRemovedWriteBarrier);
+    } else {
+      const bool inserted = current_write_barriers_.insert({cls, instruction}).second;
+      DCHECK(inserted);
+      DCHECK(instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
+    }
+  }
+
+  void VisitArraySet(HArraySet* instruction) override {
+    if (instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC())) {
+      ClearCurrentValues();
+    }
+
+    if (instruction->GetComponentType() != DataType::Type::kReference ||
+        instruction->GetValue()->IsNullConstant()) {
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      return;
+    }
+
+    HInstruction* arr = HuntForOriginalReference(instruction->InputAt(0));
+    MaybeRecordStat(stats_, MethodCompilationStat::kPossibleWriteBarrier);
+    auto it = current_write_barriers_.find(arr);
+    if (it != current_write_barriers_.end()) {
+      DCHECK(it->second->IsArraySet());
+      DCHECK(it->second->AsArraySet()->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
+      DCHECK_EQ(it->second->GetBlock(), instruction->GetBlock());
+      // We never skip the null check in ArraySets so that value is already set.
+      DCHECK(it->second->AsArraySet()->GetWriteBarrierKind() == WriteBarrierKind::kEmitNoNullCheck);
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      MaybeRecordStat(stats_, MethodCompilationStat::kRemovedWriteBarrier);
+    } else {
+      const bool inserted = current_write_barriers_.insert({arr, instruction}).second;
+      DCHECK(inserted);
+      DCHECK(instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
+    }
+  }
+
+  void VisitInstruction(HInstruction* instruction) override {
+    if (instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC())) {
+      ClearCurrentValues();
+    }
+  }
+
+ private:
+  void ClearCurrentValues() { current_write_barriers_.clear(); }
+
+  HInstruction* HuntForOriginalReference(HInstruction* ref) const {
+    // An original reference can be transformed by instructions like:
+    //   i0 NewArray
+    //   i1 HInstruction(i0)  <-- NullCheck, BoundType, IntermediateAddress.
+    //   i2 ArraySet(i1, index, value)
+    DCHECK(ref != nullptr);
+    while (ref->IsNullCheck() || ref->IsBoundType() || ref->IsIntermediateAddress()) {
+      ref = ref->InputAt(0);
+    }
+    return ref;
+  }
+
+  ScopedArenaAllocator scoped_allocator_;
+
+  // Stores a map of <Receiver, InstructionWhereTheWriteBarrierIs>.
+  // `InstructionWhereTheWriteBarrierIs` is used for DCHECKs only.
+  ScopedArenaHashMap<HInstruction*, HInstruction*> current_write_barriers_;
+
+  OptimizingCompilerStats* const stats_;
+
+  DISALLOW_COPY_AND_ASSIGN(WBEVisitor);
+};
+
+bool WriteBarrierElimination::Run() {
+  WBEVisitor wbe_visitor(graph_, stats_);
+  wbe_visitor.VisitReversePostOrder();
+  return true;
+}
+
+}  // namespace art
diff --git a/compiler/optimizing/write_barrier_elimination.h b/compiler/optimizing/write_barrier_elimination.h
new file mode 100644
index 0000000..a3769e7
--- /dev/null
+++ b/compiler/optimizing/write_barrier_elimination.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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_COMPILER_OPTIMIZING_WRITE_BARRIER_ELIMINATION_H_
+#define ART_COMPILER_OPTIMIZING_WRITE_BARRIER_ELIMINATION_H_
+
+#include "base/macros.h"
+#include "optimization.h"
+
+namespace art HIDDEN {
+
+// Eliminates unnecessary write barriers from InstanceFieldSet, StaticFieldSet, and ArraySet.
+//
+// We can eliminate redundant write barriers as we don't need several for the same receiver. For
+// example:
+//   MyObject o;
+//   o.inner_obj = io;
+//   o.inner_obj2 = io2;
+//   o.inner_obj3 = io3;
+// We can keep the write barrier for `inner_obj` and remove the other two.
+//
+// In order to do this, we set the WriteBarrierKind of the instruction. The instruction's kind are
+// set to kEmitNoNullCheck (if this write barrier coalesced other write barriers, we don't want to
+// perform the null check optimization), or to kDontEmit (if the write barrier as a whole is not
+// needed).
+class WriteBarrierElimination : public HOptimization {
+ public:
+  WriteBarrierElimination(HGraph* graph,
+                          OptimizingCompilerStats* stats,
+                          const char* name = kWBEPassName)
+      : HOptimization(graph, name, stats) {}
+
+  bool Run() override;
+
+  static constexpr const char* kWBEPassName = "write_barrier_elimination";
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(WriteBarrierElimination);
+};
+
+}  // namespace art
+
+#endif  // ART_COMPILER_OPTIMIZING_WRITE_BARRIER_ELIMINATION_H_
diff --git a/compiler/optimizing/x86_memory_gen.cc b/compiler/optimizing/x86_memory_gen.cc
index b1abcf6..e266618 100644
--- a/compiler/optimizing/x86_memory_gen.cc
+++ b/compiler/optimizing/x86_memory_gen.cc
@@ -18,13 +18,13 @@
 #include "code_generator.h"
 #include "driver/compiler_options.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 /**
  * Replace instructions with memory operand forms.
  */
-class MemoryOperandVisitor : public HGraphVisitor {
+class MemoryOperandVisitor final : public HGraphVisitor {
  public:
   MemoryOperandVisitor(HGraph* graph, bool do_implicit_null_checks)
       : HGraphVisitor(graph),
diff --git a/compiler/optimizing/x86_memory_gen.h b/compiler/optimizing/x86_memory_gen.h
index 3f4178d..1cae1a5 100644
--- a/compiler/optimizing/x86_memory_gen.h
+++ b/compiler/optimizing/x86_memory_gen.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_OPTIMIZING_X86_MEMORY_GEN_H_
 #define ART_COMPILER_OPTIMIZING_X86_MEMORY_GEN_H_
 
+#include "base/macros.h"
 #include "nodes.h"
 #include "optimization.h"
 
-namespace art {
+namespace art HIDDEN {
 class CodeGenerator;
 
 namespace x86 {
diff --git a/compiler/trampolines/trampoline_compiler.cc b/compiler/trampolines/trampoline_compiler.cc
index 0aaeaa5..a122d3c 100644
--- a/compiler/trampolines/trampoline_compiler.cc
+++ b/compiler/trampolines/trampoline_compiler.cc
@@ -38,7 +38,7 @@
 
 #define __ assembler.
 
-namespace art {
+namespace art HIDDEN {
 
 #ifdef ART_ENABLE_CODEGEN_arm
 namespace arm {
@@ -208,6 +208,8 @@
       return x86::CreateTrampoline(&allocator, offset);
 #endif
     default:
+      UNUSED(abi);
+      UNUSED(offset);
       LOG(FATAL) << "Unexpected InstructionSet: " << isa;
       UNREACHABLE();
   }
diff --git a/compiler/trampolines/trampoline_compiler.h b/compiler/trampolines/trampoline_compiler.h
index f0086b5..32e35ae 100644
--- a/compiler/trampolines/trampoline_compiler.h
+++ b/compiler/trampolines/trampoline_compiler.h
@@ -22,9 +22,10 @@
 #include <vector>
 
 #include "arch/instruction_set.h"
+#include "base/macros.h"
 #include "offsets.h"
 
-namespace art {
+namespace art HIDDEN {
 
 enum EntryPointCallingConvention {
   // ABI of invocations to a method's interpreter entry point.
@@ -36,12 +37,10 @@
 };
 
 // Create code that will invoke the function held in thread local storage.
-std::unique_ptr<const std::vector<uint8_t>> CreateTrampoline32(InstructionSet isa,
-                                                               EntryPointCallingConvention abi,
-                                                               ThreadOffset32 entry_point_offset);
-std::unique_ptr<const std::vector<uint8_t>> CreateTrampoline64(InstructionSet isa,
-                                                               EntryPointCallingConvention abi,
-                                                               ThreadOffset64 entry_point_offset);
+EXPORT std::unique_ptr<const std::vector<uint8_t>> CreateTrampoline32(
+    InstructionSet isa, EntryPointCallingConvention abi, ThreadOffset32 entry_point_offset);
+EXPORT std::unique_ptr<const std::vector<uint8_t>> CreateTrampoline64(
+    InstructionSet isa, EntryPointCallingConvention abi, ThreadOffset64 entry_point_offset);
 
 }  // namespace art
 
diff --git a/compiler/utils/arm/assembler_arm_shared.h b/compiler/utils/arm/assembler_arm_shared.h
deleted file mode 100644
index 7464052..0000000
--- a/compiler/utils/arm/assembler_arm_shared.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2016 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_COMPILER_UTILS_ARM_ASSEMBLER_ARM_SHARED_H_
-#define ART_COMPILER_UTILS_ARM_ASSEMBLER_ARM_SHARED_H_
-
-namespace art {
-namespace arm {
-
-enum LoadOperandType {
-  kLoadSignedByte,
-  kLoadUnsignedByte,
-  kLoadSignedHalfword,
-  kLoadUnsignedHalfword,
-  kLoadWord,
-  kLoadWordPair,
-  kLoadSWord,
-  kLoadDWord
-};
-
-enum StoreOperandType {
-  kStoreByte,
-  kStoreHalfword,
-  kStoreWord,
-  kStoreWordPair,
-  kStoreSWord,
-  kStoreDWord
-};
-
-}  // namespace arm
-}  // namespace art
-
-#endif  // ART_COMPILER_UTILS_ARM_ASSEMBLER_ARM_SHARED_H_
diff --git a/compiler/utils/arm/assembler_arm_vixl.cc b/compiler/utils/arm/assembler_arm_vixl.cc
index 77f5d70..c7ca003 100644
--- a/compiler/utils/arm/assembler_arm_vixl.cc
+++ b/compiler/utils/arm/assembler_arm_vixl.cc
@@ -26,7 +26,7 @@
 
 using namespace vixl::aarch32;  // NOLINT(build/namespaces)
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 #ifdef ___
@@ -81,9 +81,7 @@
 }
 
 void ArmVIXLAssembler::GenerateMarkingRegisterCheck(vixl32::Register temp, int code) {
-  // The Marking Register is only used in the Baker read barrier configuration.
-  DCHECK(kEmitCompilerReadBarrier);
-  DCHECK(kUseBakerReadBarrier);
+  DCHECK(kReserveMarkingRegister);
 
   vixl32::Label mr_is_ok;
 
diff --git a/compiler/utils/arm/assembler_arm_vixl.h b/compiler/utils/arm/assembler_arm_vixl.h
index 5bc8a70..741119d 100644
--- a/compiler/utils/arm/assembler_arm_vixl.h
+++ b/compiler/utils/arm/assembler_arm_vixl.h
@@ -19,15 +19,12 @@
 
 #include <android-base/logging.h>
 
-#include "base/arena_containers.h"
 #include "base/macros.h"
 #include "constants_arm.h"
 #include "dwarf/register.h"
 #include "offsets.h"
-#include "utils/arm/assembler_arm_shared.h"
 #include "utils/arm/managed_register_arm.h"
 #include "utils/assembler.h"
-#include "utils/jni_macro_assembler.h"
 
 // TODO(VIXL): Make VIXL compile with -Wshadow and remove pragmas.
 #pragma GCC diagnostic push
@@ -37,7 +34,7 @@
 
 namespace vixl32 = vixl::aarch32;
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 inline dwarf::Reg DWARFReg(vixl32::Register reg) {
@@ -48,6 +45,26 @@
   return dwarf::Reg::ArmFp(static_cast<int>(reg.GetCode()));
 }
 
+enum LoadOperandType {
+  kLoadSignedByte,
+  kLoadUnsignedByte,
+  kLoadSignedHalfword,
+  kLoadUnsignedHalfword,
+  kLoadWord,
+  kLoadWordPair,
+  kLoadSWord,
+  kLoadDWord
+};
+
+enum StoreOperandType {
+  kStoreByte,
+  kStoreHalfword,
+  kStoreWord,
+  kStoreWordPair,
+  kStoreSWord,
+  kStoreDWord
+};
+
 class ArmVIXLMacroAssembler final : public vixl32::MacroAssembler {
  public:
   // Most methods fit in a 1KB code buffer, which results in more optimal alloc/realloc and
diff --git a/compiler/utils/arm/constants_arm.cc b/compiler/utils/arm/constants_arm.cc
index b02b343..a927fc2 100644
--- a/compiler/utils/arm/constants_arm.cc
+++ b/compiler/utils/arm/constants_arm.cc
@@ -16,7 +16,7 @@
 
 #include "constants_arm.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 std::ostream& operator<<(std::ostream& os, const DRegister& rhs) {
diff --git a/compiler/utils/arm/constants_arm.h b/compiler/utils/arm/constants_arm.h
index f42fd97..ef6d48d 100644
--- a/compiler/utils/arm/constants_arm.h
+++ b/compiler/utils/arm/constants_arm.h
@@ -26,8 +26,9 @@
 #include "arch/arm/registers_arm.h"
 #include "base/casts.h"
 #include "base/globals.h"
+#include "base/macros.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 // Defines constants and accessor classes to assemble, disassemble and
diff --git a/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc b/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
index 6e6d40d..5487345 100644
--- a/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
+++ b/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
@@ -20,6 +20,7 @@
 #include <type_traits>
 
 #include "entrypoints/quick/quick_entrypoints.h"
+#include "indirect_reference_table.h"
 #include "lock_word.h"
 #include "thread.h"
 
@@ -27,9 +28,8 @@
 namespace vixl32 = vixl::aarch32;
 
 using vixl::ExactAssemblyScope;
-using vixl::CodeBufferCheckScope;
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 #ifdef ___
@@ -155,7 +155,7 @@
 
   // Pop LR to PC unless we need to emit some read barrier code just before returning.
   bool emit_code_before_return =
-      (kEmitCompilerReadBarrier && kUseBakerReadBarrier) &&
+      (gUseReadBarrier && kUseBakerReadBarrier) &&
       (may_suspend || (kIsDebugBuild && emit_run_time_checks_in_debug_mode_));
   if ((core_spill_mask & (1u << lr.GetCode())) != 0u && !emit_code_before_return) {
     DCHECK_EQ(core_spill_mask & (1u << pc.GetCode()), 0u);
@@ -215,7 +215,9 @@
     }
   }
 
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  // Emit marking register refresh even with all GCs as we are still using the
+  // register due to nterp's dependency.
+  if (kReserveMarkingRegister) {
     if (may_suspend) {
       // The method may be suspended; refresh the Marking Register.
       ___ Ldr(mr, MemOperand(tr, Thread::IsGcMarkingOffset<kArmPointerSize>().Int32Value()));
@@ -305,13 +307,6 @@
   }
 }
 
-void ArmVIXLJNIMacroAssembler::StoreRef(FrameOffset dest, ManagedRegister msrc) {
-  vixl::aarch32::Register src = AsVIXLRegister(msrc.AsArm());
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  temps.Exclude(src);
-  asm_.StoreToOffset(kStoreWord, src, sp, dest.Int32Value());
-}
-
 void ArmVIXLJNIMacroAssembler::StoreRawPtr(FrameOffset dest, ManagedRegister msrc) {
   vixl::aarch32::Register src = AsVIXLRegister(msrc.AsArm());
   UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
@@ -319,70 +314,6 @@
   asm_.StoreToOffset(kStoreWord, src, sp, dest.Int32Value());
 }
 
-void ArmVIXLJNIMacroAssembler::StoreSpanning(FrameOffset dest,
-                                             ManagedRegister msrc,
-                                             FrameOffset in_off) {
-  vixl::aarch32::Register src = AsVIXLRegister(msrc.AsArm());
-  asm_.StoreToOffset(kStoreWord, src, sp, dest.Int32Value());
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  vixl32::Register scratch = temps.Acquire();
-  asm_.LoadFromOffset(kLoadWord, scratch, sp, in_off.Int32Value());
-  asm_.StoreToOffset(kStoreWord, scratch, sp, dest.Int32Value() + 4);
-}
-
-void ArmVIXLJNIMacroAssembler::CopyRef(FrameOffset dest, FrameOffset src) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  vixl32::Register scratch = temps.Acquire();
-  asm_.LoadFromOffset(kLoadWord, scratch, sp, src.Int32Value());
-  asm_.StoreToOffset(kStoreWord, scratch, sp, dest.Int32Value());
-}
-
-void ArmVIXLJNIMacroAssembler::CopyRef(FrameOffset dest,
-                                       ManagedRegister base,
-                                       MemberOffset offs,
-                                       bool unpoison_reference) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  vixl32::Register scratch = temps.Acquire();
-  asm_.LoadFromOffset(kLoadWord, scratch, AsVIXLRegister(base.AsArm()), offs.Int32Value());
-  if (unpoison_reference) {
-    asm_.MaybeUnpoisonHeapReference(scratch);
-  }
-  asm_.StoreToOffset(kStoreWord, scratch, sp, dest.Int32Value());
-}
-
-void ArmVIXLJNIMacroAssembler::LoadRef(ManagedRegister mdest,
-                                       ManagedRegister mbase,
-                                       MemberOffset offs,
-                                       bool unpoison_reference) {
-  vixl::aarch32::Register dest = AsVIXLRegister(mdest.AsArm());
-  vixl::aarch32::Register base = AsVIXLRegister(mbase.AsArm());
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  temps.Exclude(dest, base);
-  asm_.LoadFromOffset(kLoadWord, dest, base, offs.Int32Value());
-
-  if (unpoison_reference) {
-    asm_.MaybeUnpoisonHeapReference(dest);
-  }
-}
-
-void ArmVIXLJNIMacroAssembler::LoadRef(ManagedRegister dest ATTRIBUTE_UNUSED,
-                                       FrameOffset src ATTRIBUTE_UNUSED) {
-  UNIMPLEMENTED(FATAL);
-}
-
-void ArmVIXLJNIMacroAssembler::LoadRawPtr(ManagedRegister dest ATTRIBUTE_UNUSED,
-                                          ManagedRegister base ATTRIBUTE_UNUSED,
-                                          Offset offs ATTRIBUTE_UNUSED) {
-  UNIMPLEMENTED(FATAL);
-}
-
-void ArmVIXLJNIMacroAssembler::StoreImmediateToFrame(FrameOffset dest, uint32_t imm) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  vixl32::Register scratch = temps.Acquire();
-  asm_.LoadImmediate(scratch, imm);
-  asm_.StoreToOffset(kStoreWord, scratch, sp, dest.Int32Value());
-}
-
 void ArmVIXLJNIMacroAssembler::Load(ManagedRegister m_dst, FrameOffset src, size_t size) {
   return Load(m_dst.AsArm(), sp, src.Int32Value(), size);
 }
@@ -394,11 +325,6 @@
   return Load(m_dst.AsArm(), AsVIXLRegister(m_base.AsArm()), offs.Int32Value(), size);
 }
 
-void ArmVIXLJNIMacroAssembler::LoadFromThread(ManagedRegister m_dst,
-                                              ThreadOffset32 src,
-                                              size_t size) {
-  return Load(m_dst.AsArm(), tr, src.Int32Value(), size);
-}
 
 void ArmVIXLJNIMacroAssembler::LoadRawPtrFromThread(ManagedRegister mdest, ThreadOffset32 offs) {
   vixl::aarch32::Register dest = AsVIXLRegister(mdest.AsArm());
@@ -407,29 +333,15 @@
   asm_.LoadFromOffset(kLoadWord, dest, tr, offs.Int32Value());
 }
 
-void ArmVIXLJNIMacroAssembler::CopyRawPtrFromThread(FrameOffset fr_offs, ThreadOffset32 thr_offs) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  vixl32::Register scratch = temps.Acquire();
-  asm_.LoadFromOffset(kLoadWord, scratch, tr, thr_offs.Int32Value());
-  asm_.StoreToOffset(kStoreWord, scratch, sp, fr_offs.Int32Value());
-}
-
-void ArmVIXLJNIMacroAssembler::CopyRawPtrToThread(ThreadOffset32 thr_offs ATTRIBUTE_UNUSED,
-                                                  FrameOffset fr_offs ATTRIBUTE_UNUSED,
-                                                  ManagedRegister mscratch ATTRIBUTE_UNUSED) {
-  UNIMPLEMENTED(FATAL);
-}
-
-void ArmVIXLJNIMacroAssembler::StoreStackOffsetToThread(ThreadOffset32 thr_offs,
-                                                        FrameOffset fr_offs) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  vixl32::Register scratch = temps.Acquire();
-  asm_.AddConstant(scratch, sp, fr_offs.Int32Value());
-  asm_.StoreToOffset(kStoreWord, scratch, tr, thr_offs.Int32Value());
-}
-
-void ArmVIXLJNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs) {
-  asm_.StoreToOffset(kStoreWord, sp, tr, thr_offs.Int32Value());
+void ArmVIXLJNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) {
+  if (tag_sp) {
+    UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
+    vixl32::Register reg = temps.Acquire();
+    ___ Orr(reg, sp, 0x2);
+    asm_.StoreToOffset(kStoreWord, reg, tr, thr_offs.Int32Value());
+  } else {
+    asm_.StoreToOffset(kStoreWord, sp, tr, thr_offs.Int32Value());
+  }
 }
 
 void ArmVIXLJNIMacroAssembler::SignExtend(ManagedRegister mreg ATTRIBUTE_UNUSED,
@@ -869,6 +781,11 @@
   }
 }
 
+void ArmVIXLJNIMacroAssembler::Move(ManagedRegister mdst, size_t value) {
+  ArmManagedRegister dst = mdst.AsArm();
+  ___ Mov(AsVIXLRegister(dst), static_cast<uint32_t>(value));
+}
+
 void ArmVIXLJNIMacroAssembler::Copy(FrameOffset dest, FrameOffset src, size_t size) {
   DCHECK(size == 4 || size == 8) << size;
   UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
@@ -884,48 +801,6 @@
   }
 }
 
-void ArmVIXLJNIMacroAssembler::Copy(FrameOffset dest ATTRIBUTE_UNUSED,
-                                    ManagedRegister src_base ATTRIBUTE_UNUSED,
-                                    Offset src_offset ATTRIBUTE_UNUSED,
-                                    ManagedRegister mscratch ATTRIBUTE_UNUSED,
-                                    size_t size ATTRIBUTE_UNUSED) {
-  UNIMPLEMENTED(FATAL);
-}
-
-void ArmVIXLJNIMacroAssembler::Copy(ManagedRegister dest_base ATTRIBUTE_UNUSED,
-                                    Offset dest_offset ATTRIBUTE_UNUSED,
-                                    FrameOffset src ATTRIBUTE_UNUSED,
-                                    ManagedRegister mscratch ATTRIBUTE_UNUSED,
-                                    size_t size ATTRIBUTE_UNUSED) {
-  UNIMPLEMENTED(FATAL);
-}
-
-void ArmVIXLJNIMacroAssembler::Copy(FrameOffset dst ATTRIBUTE_UNUSED,
-                                    FrameOffset src_base ATTRIBUTE_UNUSED,
-                                    Offset src_offset ATTRIBUTE_UNUSED,
-                                    ManagedRegister mscratch ATTRIBUTE_UNUSED,
-                                    size_t size ATTRIBUTE_UNUSED) {
-  UNIMPLEMENTED(FATAL);
-}
-
-void ArmVIXLJNIMacroAssembler::Copy(ManagedRegister dest ATTRIBUTE_UNUSED,
-                                    Offset dest_offset ATTRIBUTE_UNUSED,
-                                    ManagedRegister src ATTRIBUTE_UNUSED,
-                                    Offset src_offset ATTRIBUTE_UNUSED,
-                                    ManagedRegister mscratch ATTRIBUTE_UNUSED,
-                                    size_t size ATTRIBUTE_UNUSED) {
-  UNIMPLEMENTED(FATAL);
-}
-
-void ArmVIXLJNIMacroAssembler::Copy(FrameOffset dst ATTRIBUTE_UNUSED,
-                                    Offset dest_offset ATTRIBUTE_UNUSED,
-                                    FrameOffset src ATTRIBUTE_UNUSED,
-                                    Offset src_offset ATTRIBUTE_UNUSED,
-                                    ManagedRegister scratch ATTRIBUTE_UNUSED,
-                                    size_t size ATTRIBUTE_UNUSED) {
-  UNIMPLEMENTED(FATAL);
-}
-
 void ArmVIXLJNIMacroAssembler::CreateJObject(ManagedRegister mout_reg,
                                              FrameOffset spilled_reference_offset,
                                              ManagedRegister min_reg,
@@ -971,33 +846,19 @@
   }
 }
 
-void ArmVIXLJNIMacroAssembler::CreateJObject(FrameOffset out_off,
-                                             FrameOffset spilled_reference_offset,
-                                             bool null_allowed) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  vixl32::Register scratch = temps.Acquire();
-  if (null_allowed) {
-    asm_.LoadFromOffset(kLoadWord, scratch, sp, spilled_reference_offset.Int32Value());
-    // Null values get a jobject value null. Otherwise, the jobject is
-    // the address of the spilled reference.
-    // e.g. scratch = (scratch == 0) ? 0 : (SP+spilled_reference_offset)
-    ___ Cmp(scratch, 0);
-
-    // FIXME: Using 32-bit T32 instruction in IT-block is deprecated.
-    if (asm_.ShifterOperandCanHold(ADD, spilled_reference_offset.Int32Value())) {
-      ExactAssemblyScope guard(asm_.GetVIXLAssembler(),
-                               2 * vixl32::kMaxInstructionSizeInBytes,
-                               CodeBufferCheckScope::kMaximumSize);
-      ___ it(ne, 0x8);
-      asm_.AddConstantInIt(scratch, sp, spilled_reference_offset.Int32Value(), ne);
-    } else {
-      // TODO: Implement this (old arm assembler would have crashed here).
-      UNIMPLEMENTED(FATAL);
-    }
-  } else {
-    asm_.AddConstant(scratch, sp, spilled_reference_offset.Int32Value());
-  }
-  asm_.StoreToOffset(kStoreWord, scratch, sp, out_off.Int32Value());
+void ArmVIXLJNIMacroAssembler::DecodeJNITransitionOrLocalJObject(ManagedRegister mreg,
+                                                                 JNIMacroLabel* slow_path,
+                                                                 JNIMacroLabel* resume) {
+  constexpr uint32_t kGlobalOrWeakGlobalMask =
+      dchecked_integral_cast<uint32_t>(IndirectReferenceTable::GetGlobalOrWeakGlobalMask());
+  constexpr uint32_t kIndirectRefKindMask =
+      dchecked_integral_cast<uint32_t>(IndirectReferenceTable::GetIndirectRefKindMask());
+  vixl32::Register reg = AsVIXLRegister(mreg.AsArm());
+  ___ Tst(reg, kGlobalOrWeakGlobalMask);
+  ___ B(ne, ArmVIXLJNIMacroLabel::Cast(slow_path)->AsArm());
+  ___ Bics(reg, reg, kIndirectRefKindMask);
+  ___ B(eq, ArmVIXLJNIMacroLabel::Cast(resume)->AsArm());  // Skip load for null.
+  ___ Ldr(reg, MemOperand(reg));
 }
 
 void ArmVIXLJNIMacroAssembler::VerifyObject(ManagedRegister src ATTRIBUTE_UNUSED,
@@ -1165,7 +1026,7 @@
   UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
   vixl32::Register test_reg;
   DCHECK_EQ(Thread::IsGcMarkingSize(), 4u);
-  DCHECK(kUseReadBarrier);
+  DCHECK(gUseReadBarrier);
   if (kUseBakerReadBarrier) {
     // TestGcMarking() is used in the JNI stub entry when the marking register is up to date.
     if (kIsDebugBuild && emit_run_time_checks_in_debug_mode_) {
@@ -1213,15 +1074,19 @@
   }
 }
 
+void ArmVIXLJNIMacroAssembler::TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) {
+  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
+  vixl32::Register scratch = temps.Acquire();
+  ___ Mov(scratch, static_cast<uint32_t>(address));
+  ___ Ldrb(scratch, MemOperand(scratch, 0));
+  ___ CompareAndBranchIfNonZero(scratch, ArmVIXLJNIMacroLabel::Cast(label)->AsArm());
+}
+
 void ArmVIXLJNIMacroAssembler::Bind(JNIMacroLabel* label) {
   CHECK(label != nullptr);
   ___ Bind(ArmVIXLJNIMacroLabel::Cast(label)->AsArm());
 }
 
-void ArmVIXLJNIMacroAssembler::MemoryBarrier(ManagedRegister scratch ATTRIBUTE_UNUSED) {
-  UNIMPLEMENTED(FATAL);
-}
-
 void ArmVIXLJNIMacroAssembler::Load(ArmManagedRegister dest,
                                     vixl32::Register base,
                                     int32_t offset,
@@ -1243,6 +1108,8 @@
     }
   } else if (dest.IsRegisterPair()) {
     CHECK_EQ(8u, size) << dest;
+    // TODO: Use LDRD to improve stubs for @CriticalNative methods with parameters
+    // (long, long, ...). A single 32-bit LDRD is presumably faster than two 16-bit LDRs.
     ___ Ldr(AsVIXLRegisterPairLow(dest),  MemOperand(base, offset));
     ___ Ldr(AsVIXLRegisterPairHigh(dest), MemOperand(base, offset + 4));
   } else if (dest.IsSRegister()) {
diff --git a/compiler/utils/arm/jni_macro_assembler_arm_vixl.h b/compiler/utils/arm/jni_macro_assembler_arm_vixl.h
index ed453ae..f6df7f2 100644
--- a/compiler/utils/arm/jni_macro_assembler_arm_vixl.h
+++ b/compiler/utils/arm/jni_macro_assembler_arm_vixl.h
@@ -23,13 +23,12 @@
 #include "base/macros.h"
 #include "constants_arm.h"
 #include "offsets.h"
-#include "utils/arm/assembler_arm_shared.h"
 #include "utils/arm/assembler_arm_vixl.h"
 #include "utils/arm/managed_register_arm.h"
 #include "utils/assembler.h"
 #include "utils/jni_macro_assembler.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 class ArmVIXLJNIMacroAssembler final
@@ -63,34 +62,14 @@
   // Store routines.
   void Store(FrameOffset offs, ManagedRegister src, size_t size) override;
   void Store(ManagedRegister base, MemberOffset offs, ManagedRegister src, size_t size) override;
-  void StoreRef(FrameOffset dest, ManagedRegister src) override;
   void StoreRawPtr(FrameOffset dest, ManagedRegister src) override;
 
-  void StoreImmediateToFrame(FrameOffset dest, uint32_t imm) override;
-
-  void StoreStackOffsetToThread(ThreadOffset32 thr_offs, FrameOffset fr_offs) override;
-
-  void StoreStackPointerToThread(ThreadOffset32 thr_offs) override;
-
-  void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
+  void StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) override;
 
   // Load routines.
   void Load(ManagedRegister dest, FrameOffset src, size_t size) override;
   void Load(ManagedRegister dest, ManagedRegister base, MemberOffset offs, size_t size) override;
 
-  void LoadFromThread(ManagedRegister dest,
-                      ThreadOffset32 src,
-                      size_t size) override;
-
-  void LoadRef(ManagedRegister dest, FrameOffset src) override;
-
-  void LoadRef(ManagedRegister dest,
-               ManagedRegister base,
-               MemberOffset offs,
-               bool unpoison_reference) override;
-
-  void LoadRawPtr(ManagedRegister dest, ManagedRegister base, Offset offs) override;
-
   void LoadRawPtrFromThread(ManagedRegister dest, ThreadOffset32 offs) override;
 
   // Copying routines.
@@ -100,51 +79,7 @@
 
   void Move(ManagedRegister dest, ManagedRegister src, size_t size) override;
 
-  void CopyRawPtrFromThread(FrameOffset fr_offs, ThreadOffset32 thr_offs) override;
-
-  void CopyRawPtrToThread(ThreadOffset32 thr_offs,
-                          FrameOffset fr_offs,
-                          ManagedRegister scratch) override;
-
-  void CopyRef(FrameOffset dest, FrameOffset src) override;
-  void CopyRef(FrameOffset dest,
-               ManagedRegister base,
-               MemberOffset offs,
-               bool unpoison_reference) override;
-
-  void Copy(FrameOffset dest, FrameOffset src, size_t size) override;
-
-  void Copy(FrameOffset dest,
-            ManagedRegister src_base,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(ManagedRegister dest_base,
-            Offset dest_offset,
-            FrameOffset src,
-            ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(FrameOffset dest,
-            FrameOffset src_base,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(ManagedRegister dest,
-            Offset dest_offset,
-            ManagedRegister src,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(FrameOffset dest,
-            Offset dest_offset,
-            FrameOffset src,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
+  void Move(ManagedRegister dest, size_t value) override;
 
   // Sign extension.
   void SignExtend(ManagedRegister mreg, size_t size) override;
@@ -156,20 +91,10 @@
   void GetCurrentThread(ManagedRegister dest) override;
   void GetCurrentThread(FrameOffset dest_offset) override;
 
-  // Set up `out_reg` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
-  // or to be null if the value is null and `null_allowed`. `in_reg` holds a possibly
-  // stale reference that can be used to avoid loading the spilled value to
-  // see if the value is null.
-  void CreateJObject(ManagedRegister out_reg,
-                     FrameOffset spilled_reference_offset,
-                     ManagedRegister in_reg,
-                     bool null_allowed) override;
-
-  // Set up `out_off` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
-  // or to be null if the value is null and `null_allowed`.
-  void CreateJObject(FrameOffset out_off,
-                     FrameOffset spilled_reference_offset,
-                     bool null_allowed) override;
+  // Decode JNI transition or local `jobject`. For (weak) global `jobject`, jump to slow path.
+  void DecodeJNITransitionOrLocalJObject(ManagedRegister reg,
+                                         JNIMacroLabel* slow_path,
+                                         JNIMacroLabel* resume) override;
 
   // Heap::VerifyObject on src. In some cases (such as a reference to this) we
   // know that src may not be null.
@@ -213,17 +138,28 @@
   void TestGcMarking(JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
   // Emit a conditional jump to the label by applying a unary condition test to object's mark bit.
   void TestMarkBit(ManagedRegister ref, JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
+  // Emit a conditional jump to label if the loaded value from specified locations is not zero.
+  void TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) override;
   // Code at this offset will serve as the target for the Jump call.
   void Bind(JNIMacroLabel* label) override;
 
-  void MemoryBarrier(ManagedRegister scratch) override;
-
+ private:
+  void Copy(FrameOffset dest, FrameOffset src, size_t size);
   void Load(ArmManagedRegister dest, vixl32::Register base, int32_t offset, size_t size);
 
- private:
+  // Set up `out_reg` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
+  // or to be null if the value is null and `null_allowed`. `in_reg` holds a possibly
+  // stale reference that can be used to avoid loading the spilled value to
+  // see if the value is null.
+  void CreateJObject(ManagedRegister out_reg,
+                     FrameOffset spilled_reference_offset,
+                     ManagedRegister in_reg,
+                     bool null_allowed);
+
   // Used for testing.
-  friend class ArmVIXLAssemblerTest_VixlLoadFromOffset_Test;
-  friend class ArmVIXLAssemblerTest_VixlStoreToOffset_Test;
+  ART_FRIEND_TEST(ArmVIXLAssemblerTest, VixlJniHelpers);
+  ART_FRIEND_TEST(ArmVIXLAssemblerTest, VixlLoadFromOffset);
+  ART_FRIEND_TEST(ArmVIXLAssemblerTest, VixlStoreToOffset);
 };
 
 class ArmVIXLJNIMacroLabel final
diff --git a/compiler/utils/arm/managed_register_arm.cc b/compiler/utils/arm/managed_register_arm.cc
index deff658..07d50da 100644
--- a/compiler/utils/arm/managed_register_arm.cc
+++ b/compiler/utils/arm/managed_register_arm.cc
@@ -18,7 +18,7 @@
 
 #include "base/globals.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 // Returns true if this managed-register overlaps the other managed-register.
diff --git a/compiler/utils/arm/managed_register_arm.h b/compiler/utils/arm/managed_register_arm.h
index 6d942fa..b3d436c 100644
--- a/compiler/utils/arm/managed_register_arm.h
+++ b/compiler/utils/arm/managed_register_arm.h
@@ -19,10 +19,11 @@
 
 #include <android-base/logging.h>
 
+#include "base/macros.h"
 #include "constants_arm.h"
 #include "utils/managed_register.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 // Values for register pairs.
diff --git a/compiler/utils/arm/managed_register_arm_test.cc b/compiler/utils/arm/managed_register_arm_test.cc
index 6f440a7c..60f6090 100644
--- a/compiler/utils/arm/managed_register_arm_test.cc
+++ b/compiler/utils/arm/managed_register_arm_test.cc
@@ -16,9 +16,10 @@
 
 #include "managed_register_arm.h"
 #include "base/globals.h"
+#include "base/macros.h"
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 TEST(ArmManagedRegister, NoRegister) {
diff --git a/compiler/utils/arm64/assembler_arm64.cc b/compiler/utils/arm64/assembler_arm64.cc
index 6100ed9..26dce7c 100644
--- a/compiler/utils/arm64/assembler_arm64.cc
+++ b/compiler/utils/arm64/assembler_arm64.cc
@@ -16,7 +16,6 @@
 
 #include "arch/arm64/instruction_set_features_arm64.h"
 #include "assembler_arm64.h"
-#include "base/bit_utils_iterator.h"
 #include "entrypoints/quick/quick_entrypoints.h"
 #include "heap_poisoning.h"
 #include "offsets.h"
@@ -24,7 +23,7 @@
 
 using namespace vixl::aarch64;  // NOLINT(build/namespaces)
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 #ifdef ___
@@ -187,9 +186,7 @@
 }
 
 void Arm64Assembler::GenerateMarkingRegisterCheck(Register temp, int code) {
-  // The Marking Register is only used in the Baker read barrier configuration.
-  DCHECK(kEmitCompilerReadBarrier);
-  DCHECK(kUseBakerReadBarrier);
+  DCHECK(kReserveMarkingRegister);
 
   vixl::aarch64::Register mr = reg_x(MR);  // Marking Register.
   vixl::aarch64::Register tr = reg_x(TR);  // Thread Register.
diff --git a/compiler/utils/arm64/assembler_arm64.h b/compiler/utils/arm64/assembler_arm64.h
index b49a13a..f816890 100644
--- a/compiler/utils/arm64/assembler_arm64.h
+++ b/compiler/utils/arm64/assembler_arm64.h
@@ -23,7 +23,6 @@
 
 #include <android-base/logging.h>
 
-#include "base/arena_containers.h"
 #include "base/bit_utils_iterator.h"
 #include "base/macros.h"
 #include "dwarf/register.h"
@@ -38,7 +37,7 @@
 #include "aarch64/macro-assembler-aarch64.h"
 #pragma GCC diagnostic pop
 
-namespace art {
+namespace art HIDDEN {
 
 class Arm64InstructionSetFeatures;
 
diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.cc b/compiler/utils/arm64/jni_macro_assembler_arm64.cc
index 50ca468..9e9f122 100644
--- a/compiler/utils/arm64/jni_macro_assembler_arm64.cc
+++ b/compiler/utils/arm64/jni_macro_assembler_arm64.cc
@@ -17,6 +17,7 @@
 #include "jni_macro_assembler_arm64.h"
 
 #include "entrypoints/quick/quick_entrypoints.h"
+#include "indirect_reference_table.h"
 #include "lock_word.h"
 #include "managed_register_arm64.h"
 #include "offsets.h"
@@ -24,7 +25,7 @@
 
 using namespace vixl::aarch64;  // NOLINT(build/namespaces)
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 #ifdef ___
@@ -191,46 +192,22 @@
   }
 }
 
-void Arm64JNIMacroAssembler::StoreRef(FrameOffset offs, ManagedRegister m_src) {
-  Arm64ManagedRegister src = m_src.AsArm64();
-  CHECK(src.IsXRegister()) << src;
-  StoreWToOffset(kStoreWord, src.AsOverlappingWRegister(), SP,
-                 offs.Int32Value());
-}
-
 void Arm64JNIMacroAssembler::StoreRawPtr(FrameOffset offs, ManagedRegister m_src) {
   Arm64ManagedRegister src = m_src.AsArm64();
   CHECK(src.IsXRegister()) << src;
   StoreToOffset(src.AsXRegister(), SP, offs.Int32Value());
 }
 
-void Arm64JNIMacroAssembler::StoreImmediateToFrame(FrameOffset offs, uint32_t imm) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  Register scratch = temps.AcquireW();
-  ___ Mov(scratch, imm);
-  ___ Str(scratch, MEM_OP(reg_x(SP), offs.Int32Value()));
-}
-
-void Arm64JNIMacroAssembler::StoreStackOffsetToThread(ThreadOffset64 tr_offs, FrameOffset fr_offs) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  Register scratch = temps.AcquireX();
-  ___ Add(scratch, reg_x(SP), fr_offs.Int32Value());
-  ___ Str(scratch, MEM_OP(reg_x(TR), tr_offs.Int32Value()));
-}
-
-void Arm64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 tr_offs) {
+void Arm64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 tr_offs, bool tag_sp) {
   UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
   Register scratch = temps.AcquireX();
   ___ Mov(scratch, reg_x(SP));
+  if (tag_sp) {
+    ___ Orr(scratch, scratch, 0x2);
+  }
   ___ Str(scratch, MEM_OP(reg_x(TR), tr_offs.Int32Value()));
 }
 
-void Arm64JNIMacroAssembler::StoreSpanning(FrameOffset dest_off ATTRIBUTE_UNUSED,
-                                           ManagedRegister m_source ATTRIBUTE_UNUSED,
-                                           FrameOffset in_off ATTRIBUTE_UNUSED) {
-  UNIMPLEMENTED(FATAL);  // This case is not applicable to ARM64.
-}
-
 // Load routines.
 void Arm64JNIMacroAssembler::LoadImmediate(XRegister dest, int32_t value, Condition cond) {
   if ((cond == al) || (cond == nv)) {
@@ -329,45 +306,6 @@
   return Load(m_dst.AsArm64(), m_base.AsArm64().AsXRegister(), offs.Int32Value(), size);
 }
 
-void Arm64JNIMacroAssembler::LoadFromThread(ManagedRegister m_dst,
-                                            ThreadOffset64 src,
-                                            size_t size) {
-  return Load(m_dst.AsArm64(), TR, src.Int32Value(), size);
-}
-
-void Arm64JNIMacroAssembler::LoadRef(ManagedRegister m_dst, FrameOffset offs) {
-  Arm64ManagedRegister dst = m_dst.AsArm64();
-  CHECK(dst.IsXRegister()) << dst;
-  LoadWFromOffset(kLoadWord, dst.AsOverlappingWRegister(), SP, offs.Int32Value());
-}
-
-void Arm64JNIMacroAssembler::LoadRef(ManagedRegister m_dst,
-                                     ManagedRegister m_base,
-                                     MemberOffset offs,
-                                     bool unpoison_reference) {
-  Arm64ManagedRegister dst = m_dst.AsArm64();
-  Arm64ManagedRegister base = m_base.AsArm64();
-  CHECK(dst.IsXRegister() && base.IsXRegister());
-  LoadWFromOffset(kLoadWord, dst.AsOverlappingWRegister(), base.AsXRegister(),
-                  offs.Int32Value());
-  if (unpoison_reference) {
-    WRegister ref_reg = dst.AsOverlappingWRegister();
-    asm_.MaybeUnpoisonHeapReference(reg_w(ref_reg));
-  }
-}
-
-void Arm64JNIMacroAssembler::LoadRawPtr(ManagedRegister m_dst,
-                                        ManagedRegister m_base,
-                                        Offset offs) {
-  Arm64ManagedRegister dst = m_dst.AsArm64();
-  Arm64ManagedRegister base = m_base.AsArm64();
-  CHECK(dst.IsXRegister() && base.IsXRegister());
-  // Remove dst and base form the temp list - higher level API uses IP1, IP0.
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  temps.Exclude(reg_x(dst.AsXRegister()), reg_x(base.AsXRegister()));
-  ___ Ldr(reg_x(dst.AsXRegister()), MEM_OP(reg_x(base.AsXRegister()), offs.Int32Value()));
-}
-
 void Arm64JNIMacroAssembler::LoadRawPtrFromThread(ManagedRegister m_dst, ThreadOffset64 offs) {
   Arm64ManagedRegister dst = m_dst.AsArm64();
   CHECK(dst.IsXRegister()) << dst;
@@ -640,40 +578,10 @@
   }
 }
 
-void Arm64JNIMacroAssembler::CopyRawPtrFromThread(FrameOffset fr_offs, ThreadOffset64 tr_offs) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  Register scratch = temps.AcquireX();
-  ___ Ldr(scratch, MEM_OP(reg_x(TR), tr_offs.Int32Value()));
-  ___ Str(scratch, MEM_OP(sp, fr_offs.Int32Value()));
-}
-
-void Arm64JNIMacroAssembler::CopyRawPtrToThread(ThreadOffset64 tr_offs,
-                                                FrameOffset fr_offs,
-                                                ManagedRegister m_scratch) {
-  Arm64ManagedRegister scratch = m_scratch.AsArm64();
-  CHECK(scratch.IsXRegister()) << scratch;
-  LoadFromOffset(scratch.AsXRegister(), SP, fr_offs.Int32Value());
-  StoreToOffset(scratch.AsXRegister(), TR, tr_offs.Int32Value());
-}
-
-void Arm64JNIMacroAssembler::CopyRef(FrameOffset dest, FrameOffset src) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  Register scratch = temps.AcquireW();
-  ___ Ldr(scratch, MEM_OP(reg_x(SP), src.Int32Value()));
-  ___ Str(scratch, MEM_OP(reg_x(SP), dest.Int32Value()));
-}
-
-void Arm64JNIMacroAssembler::CopyRef(FrameOffset dest,
-                                     ManagedRegister base,
-                                     MemberOffset offs,
-                                     bool unpoison_reference) {
-  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
-  Register scratch = temps.AcquireW();
-  ___ Ldr(scratch, MEM_OP(reg_x(base.AsArm64().AsXRegister()), offs.Int32Value()));
-  if (unpoison_reference) {
-    asm_.MaybeUnpoisonHeapReference(scratch);
-  }
-  ___ Str(scratch, MEM_OP(reg_x(SP), dest.Int32Value()));
+void Arm64JNIMacroAssembler::Move(ManagedRegister m_dst, size_t value) {
+  Arm64ManagedRegister dst = m_dst.AsArm64();
+  DCHECK(dst.IsXRegister());
+  ___ Mov(reg_x(dst.AsXRegister()), value);
 }
 
 void Arm64JNIMacroAssembler::Copy(FrameOffset dest, FrameOffset src, size_t size) {
@@ -684,105 +592,6 @@
   ___ Str(scratch, MEM_OP(reg_x(SP), dest.Int32Value()));
 }
 
-void Arm64JNIMacroAssembler::Copy(FrameOffset dest,
-                                  ManagedRegister src_base,
-                                  Offset src_offset,
-                                  ManagedRegister m_scratch,
-                                  size_t size) {
-  Arm64ManagedRegister scratch = m_scratch.AsArm64();
-  Arm64ManagedRegister base = src_base.AsArm64();
-  CHECK(base.IsXRegister()) << base;
-  CHECK(scratch.IsXRegister() || scratch.IsWRegister()) << scratch;
-  CHECK(size == 4 || size == 8) << size;
-  if (size == 4) {
-    LoadWFromOffset(kLoadWord, scratch.AsWRegister(), base.AsXRegister(),
-                   src_offset.Int32Value());
-    StoreWToOffset(kStoreWord, scratch.AsWRegister(), SP, dest.Int32Value());
-  } else if (size == 8) {
-    LoadFromOffset(scratch.AsXRegister(), base.AsXRegister(), src_offset.Int32Value());
-    StoreToOffset(scratch.AsXRegister(), SP, dest.Int32Value());
-  } else {
-    UNIMPLEMENTED(FATAL) << "We only support Copy() of size 4 and 8";
-  }
-}
-
-void Arm64JNIMacroAssembler::Copy(ManagedRegister m_dest_base,
-                                  Offset dest_offs,
-                                  FrameOffset src,
-                                  ManagedRegister m_scratch,
-                                  size_t size) {
-  Arm64ManagedRegister scratch = m_scratch.AsArm64();
-  Arm64ManagedRegister base = m_dest_base.AsArm64();
-  CHECK(base.IsXRegister()) << base;
-  CHECK(scratch.IsXRegister() || scratch.IsWRegister()) << scratch;
-  CHECK(size == 4 || size == 8) << size;
-  if (size == 4) {
-    LoadWFromOffset(kLoadWord, scratch.AsWRegister(), SP, src.Int32Value());
-    StoreWToOffset(kStoreWord, scratch.AsWRegister(), base.AsXRegister(),
-                   dest_offs.Int32Value());
-  } else if (size == 8) {
-    LoadFromOffset(scratch.AsXRegister(), SP, src.Int32Value());
-    StoreToOffset(scratch.AsXRegister(), base.AsXRegister(), dest_offs.Int32Value());
-  } else {
-    UNIMPLEMENTED(FATAL) << "We only support Copy() of size 4 and 8";
-  }
-}
-
-void Arm64JNIMacroAssembler::Copy(FrameOffset /*dst*/,
-                                  FrameOffset /*src_base*/,
-                                  Offset /*src_offset*/,
-                                  ManagedRegister /*mscratch*/,
-                                  size_t /*size*/) {
-  UNIMPLEMENTED(FATAL) << "Unimplemented Copy() variant";
-}
-
-void Arm64JNIMacroAssembler::Copy(ManagedRegister m_dest,
-                                  Offset dest_offset,
-                                  ManagedRegister m_src,
-                                  Offset src_offset,
-                                  ManagedRegister m_scratch,
-                                  size_t size) {
-  Arm64ManagedRegister scratch = m_scratch.AsArm64();
-  Arm64ManagedRegister src = m_src.AsArm64();
-  Arm64ManagedRegister dest = m_dest.AsArm64();
-  CHECK(dest.IsXRegister()) << dest;
-  CHECK(src.IsXRegister()) << src;
-  CHECK(scratch.IsXRegister() || scratch.IsWRegister()) << scratch;
-  CHECK(size == 4 || size == 8) << size;
-  if (size == 4) {
-    if (scratch.IsWRegister()) {
-      LoadWFromOffset(kLoadWord, scratch.AsWRegister(), src.AsXRegister(),
-                    src_offset.Int32Value());
-      StoreWToOffset(kStoreWord, scratch.AsWRegister(), dest.AsXRegister(),
-                   dest_offset.Int32Value());
-    } else {
-      LoadWFromOffset(kLoadWord, scratch.AsOverlappingWRegister(), src.AsXRegister(),
-                    src_offset.Int32Value());
-      StoreWToOffset(kStoreWord, scratch.AsOverlappingWRegister(), dest.AsXRegister(),
-                   dest_offset.Int32Value());
-    }
-  } else if (size == 8) {
-    LoadFromOffset(scratch.AsXRegister(), src.AsXRegister(), src_offset.Int32Value());
-    StoreToOffset(scratch.AsXRegister(), dest.AsXRegister(), dest_offset.Int32Value());
-  } else {
-    UNIMPLEMENTED(FATAL) << "We only support Copy() of size 4 and 8";
-  }
-}
-
-void Arm64JNIMacroAssembler::Copy(FrameOffset /*dst*/,
-                                  Offset /*dest_offset*/,
-                                  FrameOffset /*src*/,
-                                  Offset /*src_offset*/,
-                                  ManagedRegister /*scratch*/,
-                                  size_t /*size*/) {
-  UNIMPLEMENTED(FATAL) << "Unimplemented Copy() variant";
-}
-
-void Arm64JNIMacroAssembler::MemoryBarrier(ManagedRegister m_scratch ATTRIBUTE_UNUSED) {
-  // TODO: Should we check that m_scratch is IP? - see arm.
-  ___ Dmb(InnerShareable, BarrierAll);
-}
-
 void Arm64JNIMacroAssembler::SignExtend(ManagedRegister mreg, size_t size) {
   Arm64ManagedRegister reg = mreg.AsArm64();
   CHECK(size == 1 || size == 2) << size;
@@ -882,6 +691,19 @@
   ___ Str(scratch, MEM_OP(reg_x(SP), out_off.Int32Value()));
 }
 
+void Arm64JNIMacroAssembler::DecodeJNITransitionOrLocalJObject(ManagedRegister m_reg,
+                                                               JNIMacroLabel* slow_path,
+                                                               JNIMacroLabel* resume) {
+  constexpr uint64_t kGlobalOrWeakGlobalMask = IndirectReferenceTable::GetGlobalOrWeakGlobalMask();
+  constexpr uint64_t kIndirectRefKindMask = IndirectReferenceTable::GetIndirectRefKindMask();
+  constexpr size_t kGlobalOrWeakGlobalBit = WhichPowerOf2(kGlobalOrWeakGlobalMask);
+  Register reg = reg_w(m_reg.AsArm64().AsWRegister());
+  ___ Tbnz(reg.X(), kGlobalOrWeakGlobalBit, Arm64JNIMacroLabel::Cast(slow_path)->AsArm64());
+  ___ And(reg.X(), reg.X(), ~kIndirectRefKindMask);
+  ___ Cbz(reg.X(), Arm64JNIMacroLabel::Cast(resume)->AsArm64());  // Skip load for null.
+  ___ Ldr(reg, MEM_OP(reg.X()));
+}
+
 void Arm64JNIMacroAssembler::TryToTransitionFromRunnableToNative(
     JNIMacroLabel* label, ArrayRef<const ManagedRegister> scratch_regs ATTRIBUTE_UNUSED) {
   constexpr uint32_t kNativeStateValue = Thread::StoredThreadStateValue(ThreadState::kNative);
@@ -989,7 +811,7 @@
   UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
   Register test_reg;
   DCHECK_EQ(Thread::IsGcMarkingSize(), 4u);
-  DCHECK(kUseReadBarrier);
+  DCHECK(gUseReadBarrier);
   if (kUseBakerReadBarrier) {
     // TestGcMarking() is used in the JNI stub entry when the marking register is up to date.
     if (kIsDebugBuild && emit_run_time_checks_in_debug_mode_) {
@@ -1037,6 +859,14 @@
   }
 }
 
+void Arm64JNIMacroAssembler::TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) {
+  UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
+  Register scratch = temps.AcquireX();
+  ___ Mov(scratch, address);
+  ___ Ldrb(scratch.W(), MEM_OP(scratch, 0));
+  ___ Cbnz(scratch.W(), Arm64JNIMacroLabel::Cast(label)->AsArm64());
+}
+
 void Arm64JNIMacroAssembler::Bind(JNIMacroLabel* label) {
   CHECK(label != nullptr);
   ___ Bind(Arm64JNIMacroLabel::Cast(label)->AsArm64());
@@ -1107,7 +937,9 @@
   asm_.UnspillRegisters(core_reg_list, frame_size - core_reg_size);
   asm_.UnspillRegisters(fp_reg_list, frame_size - core_reg_size - fp_reg_size);
 
-  if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+  // Emit marking register refresh even with all GCs as we are still using the
+  // register due to nterp's dependency.
+  if (kReserveMarkingRegister) {
     vixl::aarch64::Register mr = reg_x(MR);  // Marking Register.
     vixl::aarch64::Register tr = reg_x(TR);  // Thread Register.
 
diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.h b/compiler/utils/arm64/jni_macro_assembler_arm64.h
index 2c04184..2836e09 100644
--- a/compiler/utils/arm64/jni_macro_assembler_arm64.h
+++ b/compiler/utils/arm64/jni_macro_assembler_arm64.h
@@ -37,7 +37,7 @@
 #include "aarch64/macro-assembler-aarch64.h"
 #pragma GCC diagnostic pop
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 class Arm64JNIMacroAssembler final : public JNIMacroAssemblerFwd<Arm64Assembler, PointerSize::k64> {
@@ -68,23 +68,12 @@
   // Store routines.
   void Store(FrameOffset offs, ManagedRegister src, size_t size) override;
   void Store(ManagedRegister base, MemberOffset offs, ManagedRegister src, size_t size) override;
-  void StoreRef(FrameOffset dest, ManagedRegister src) override;
   void StoreRawPtr(FrameOffset dest, ManagedRegister src) override;
-  void StoreImmediateToFrame(FrameOffset dest, uint32_t imm) override;
-  void StoreStackOffsetToThread(ThreadOffset64 thr_offs, FrameOffset fr_offs) override;
-  void StoreStackPointerToThread(ThreadOffset64 thr_offs) override;
-  void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
+  void StoreStackPointerToThread(ThreadOffset64 thr_offs, bool tag_sp) override;
 
   // Load routines.
   void Load(ManagedRegister dest, FrameOffset src, size_t size) override;
   void Load(ManagedRegister dest, ManagedRegister base, MemberOffset offs, size_t size) override;
-  void LoadFromThread(ManagedRegister dest, ThreadOffset64 src, size_t size) override;
-  void LoadRef(ManagedRegister dest, FrameOffset src) override;
-  void LoadRef(ManagedRegister dest,
-               ManagedRegister base,
-               MemberOffset offs,
-               bool unpoison_reference) override;
-  void LoadRawPtr(ManagedRegister dest, ManagedRegister base, Offset offs) override;
   void LoadRawPtrFromThread(ManagedRegister dest, ThreadOffset64 offs) override;
 
   // Copying routines.
@@ -92,43 +81,7 @@
                      ArrayRef<ArgumentLocation> srcs,
                      ArrayRef<FrameOffset> refs) override;
   void Move(ManagedRegister dest, ManagedRegister src, size_t size) override;
-  void CopyRawPtrFromThread(FrameOffset fr_offs, ThreadOffset64 thr_offs) override;
-  void CopyRawPtrToThread(ThreadOffset64 thr_offs, FrameOffset fr_offs, ManagedRegister scratch)
-      override;
-  void CopyRef(FrameOffset dest, FrameOffset src) override;
-  void CopyRef(FrameOffset dest,
-               ManagedRegister base,
-               MemberOffset offs,
-               bool unpoison_reference) override;
-  void Copy(FrameOffset dest, FrameOffset src, size_t size) override;
-  void Copy(FrameOffset dest,
-            ManagedRegister src_base,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-  void Copy(ManagedRegister dest_base,
-            Offset dest_offset,
-            FrameOffset src,
-            ManagedRegister scratch,
-            size_t size) override;
-  void Copy(FrameOffset dest,
-            FrameOffset src_base,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-  void Copy(ManagedRegister dest,
-            Offset dest_offset,
-            ManagedRegister src,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-  void Copy(FrameOffset dest,
-            Offset dest_offset,
-            FrameOffset src,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-  void MemoryBarrier(ManagedRegister scratch) override;
+  void Move(ManagedRegister dest, size_t value) override;
 
   // Sign extension.
   void SignExtend(ManagedRegister mreg, size_t size) override;
@@ -140,20 +93,10 @@
   void GetCurrentThread(ManagedRegister dest) override;
   void GetCurrentThread(FrameOffset dest_offset) override;
 
-  // Set up `out_reg` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
-  // or to be null if the value is null and `null_allowed`. `in_reg` holds a possibly
-  // stale reference that can be used to avoid loading the spilled value to
-  // see if the value is null.
-  void CreateJObject(ManagedRegister out_reg,
-                     FrameOffset spilled_reference_offset,
-                     ManagedRegister in_reg,
-                     bool null_allowed) override;
-
-  // Set up `out_off` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
-  // or to be null if the value is null and `null_allowed`.
-  void CreateJObject(FrameOffset out_off,
-                     FrameOffset spilled_reference_offset,
-                     bool null_allowed) override;
+  // Decode JNI transition or local `jobject`. For (weak) global `jobject`, jump to slow path.
+  void DecodeJNITransitionOrLocalJObject(ManagedRegister reg,
+                                         JNIMacroLabel* slow_path,
+                                         JNIMacroLabel* resume) override;
 
   // Heap::VerifyObject on src. In some cases (such as a reference to this) we
   // know that src may not be null.
@@ -197,6 +140,8 @@
   void TestGcMarking(JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
   // Emit a conditional jump to the label by applying a unary condition test to object's mark bit.
   void TestMarkBit(ManagedRegister ref, JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
+  // Emit a conditional jump to label if the loaded value from specified locations is not zero.
+  void TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) override;
   // Code at this offset will serve as the target for the Jump call.
   void Bind(JNIMacroLabel* label) override;
 
@@ -220,6 +165,24 @@
   void LoadFromOffset(XRegister dest, XRegister base, int32_t offset);
   void LoadSFromOffset(SRegister dest, XRegister base, int32_t offset);
   void LoadDFromOffset(DRegister dest, XRegister base, int32_t offset);
+
+  void Copy(FrameOffset dest, FrameOffset src, size_t size);
+
+  // Set up `out_reg` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
+  // or to be null if the value is null and `null_allowed`. `in_reg` holds a possibly
+  // stale reference that can be used to avoid loading the spilled value to
+  // see if the value is null.
+  void CreateJObject(ManagedRegister out_reg,
+                     FrameOffset spilled_reference_offset,
+                     ManagedRegister in_reg,
+                     bool null_allowed);
+
+  // Set up `out_off` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
+  // or to be null if the value is null and `null_allowed`.
+  void CreateJObject(FrameOffset out_off,
+                     FrameOffset spilled_reference_offset,
+                     bool null_allowed);
+
   void AddConstant(XRegister rd,
                    int32_t value,
                    vixl::aarch64::Condition cond = vixl::aarch64::al);
diff --git a/compiler/utils/arm64/managed_register_arm64.cc b/compiler/utils/arm64/managed_register_arm64.cc
index 5632265..74a3545 100644
--- a/compiler/utils/arm64/managed_register_arm64.cc
+++ b/compiler/utils/arm64/managed_register_arm64.cc
@@ -17,7 +17,7 @@
 #include "managed_register_arm64.h"
 #include "base/globals.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 // TODO: Define convention
diff --git a/compiler/utils/arm64/managed_register_arm64.h b/compiler/utils/arm64/managed_register_arm64.h
index 8a06f63..7e8c976 100644
--- a/compiler/utils/arm64/managed_register_arm64.h
+++ b/compiler/utils/arm64/managed_register_arm64.h
@@ -20,9 +20,10 @@
 #include <android-base/logging.h>
 
 #include "arch/arm64/registers_arm64.h"
+#include "base/macros.h"
 #include "utils/managed_register.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 const int kNumberOfXRegIds = kNumberOfXRegisters;
diff --git a/compiler/utils/arm64/managed_register_arm64_test.cc b/compiler/utils/arm64/managed_register_arm64_test.cc
index d151ac9..f250360 100644
--- a/compiler/utils/arm64/managed_register_arm64_test.cc
+++ b/compiler/utils/arm64/managed_register_arm64_test.cc
@@ -18,9 +18,10 @@
 
 #include "assembler_arm64.h"
 #include "base/globals.h"
+#include "base/macros.h"
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm64 {
 
 TEST(Arm64ManagedRegister, NoRegister) {
diff --git a/compiler/utils/assembler.cc b/compiler/utils/assembler.cc
index d1d2a3d..b82f0dc 100644
--- a/compiler/utils/assembler.cc
+++ b/compiler/utils/assembler.cc
@@ -23,7 +23,7 @@
 #include "base/globals.h"
 #include "base/memory_region.h"
 
-namespace art {
+namespace art HIDDEN {
 
 AssemblerBuffer::AssemblerBuffer(ArenaAllocator* allocator)
     : allocator_(allocator) {
diff --git a/compiler/utils/assembler.h b/compiler/utils/assembler.h
index 4b4fb14..13a5d9f 100644
--- a/compiler/utils/assembler.h
+++ b/compiler/utils/assembler.h
@@ -37,7 +37,7 @@
 #include "x86/constants_x86.h"
 #include "x86_64/constants_x86_64.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class Assembler;
 class AssemblerBuffer;
diff --git a/compiler/utils/assembler_test.h b/compiler/utils/assembler_test.h
index bb22fe5..d03e5a7 100644
--- a/compiler/utils/assembler_test.h
+++ b/compiler/utils/assembler_test.h
@@ -26,11 +26,12 @@
 #include <fstream>
 #include <iterator>
 
+#include "base/macros.h"
 #include "base/malloc_arena_pool.h"
 #include "assembler_test_base.h"
 #include "common_runtime_test.h"  // For ScratchFile
 
-namespace art {
+namespace art HIDDEN {
 
 // Helper for a constexpr string length.
 constexpr size_t ConstexprStrLen(char const* str, size_t count = 0) {
@@ -59,7 +60,7 @@
     return assembler_.get();
   }
 
-  typedef std::string (*TestFn)(AssemblerTest* assembler_test, Ass* assembler);
+  using TestFn = std::string (*)(AssemblerTest *, Ass *);
 
   void DriverFn(TestFn f, const std::string& test_name) {
     DriverWrapper(f(this, assembler_.get()), test_name);
@@ -259,7 +260,7 @@
                                               std::string (AssemblerTest::*GetName1)(const Reg1&),
                                               std::string (AssemblerTest::*GetName2)(const Reg2&),
                                               std::string (AssemblerTest::*GetName3)(const Reg3&),
-                                              std::string fmt,
+                                              const std::string& fmt,
                                               int bias) {
     std::string str;
     std::vector<int64_t> imms = CreateImmediateValuesBits(abs(imm_bits), (imm_bits > 0));
diff --git a/compiler/utils/assembler_test_base.h b/compiler/utils/assembler_test_base.h
index bf73808..73f3657 100644
--- a/compiler/utils/assembler_test_base.h
+++ b/compiler/utils/assembler_test_base.h
@@ -26,6 +26,7 @@
 
 #include "android-base/strings.h"
 
+#include "base/macros.h"
 #include "base/os.h"
 #include "base/utils.h"
 #include "common_runtime_test.h"  // For ScratchDir.
@@ -34,7 +35,7 @@
 #include "exec_utils.h"
 #include "stream/file_output_stream.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // If you want to take a look at the differences between the ART assembler and clang,
 // set this flag to true. The disassembled files will then remain in the tmp directory.
@@ -59,7 +60,7 @@
 
   // This is intended to be run as a test.
   bool CheckTools() {
-    for (auto cmd : { GetAssemblerCommand()[0], GetDisassemblerCommand()[0] }) {
+    for (const std::string& cmd : { GetAssemblerCommand()[0], GetDisassemblerCommand()[0] }) {
       if (!OS::FileExists(cmd.c_str())) {
         LOG(ERROR) << "Could not find " << cmd;
         return false;
@@ -84,7 +85,7 @@
 
     // Assemble reference object file.
     std::string ref_obj_file = test_path(".ref.o");
-    ASSERT_TRUE(Assemble(ref_asm_file.c_str(), ref_obj_file.c_str()));
+    ASSERT_TRUE(Assemble(ref_asm_file, ref_obj_file));
 
     // Read the code produced by assembler from the ELF file.
     std::vector<uint8_t> ref_code;
@@ -153,9 +154,14 @@
   virtual std::vector<std::string> GetDisassemblerCommand() {
     switch (GetIsa()) {
       case InstructionSet::kThumb2:
-        return {FindTool("llvm-objdump"), "--disassemble", "--triple", "thumbv7a-linux-gnueabi"};
+        return {FindTool("llvm-objdump"),
+                "--disassemble",
+                "--no-print-imm-hex",
+                "--triple",
+                "thumbv7a-linux-gnueabi"};
       default:
-        return {FindTool("llvm-objdump"), "--disassemble", "--no-show-raw-insn"};
+        return {
+            FindTool("llvm-objdump"), "--disassemble", "--no-print-imm-hex", "--no-show-raw-insn"};
     }
   }
 
diff --git a/compiler/utils/assembler_thumb_test.cc b/compiler/utils/assembler_thumb_test.cc
index b2d4dcd..672cd3d 100644
--- a/compiler/utils/assembler_thumb_test.cc
+++ b/compiler/utils/assembler_thumb_test.cc
@@ -30,10 +30,11 @@
 #include "utils/assembler_test_base.h"
 
 #include "base/hex_dump.h"
+#include "base/macros.h"
 #include "base/malloc_arena_pool.h"
 #include "common_runtime_test.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace arm {
 
 // Include results file (generated manually)
@@ -143,7 +144,6 @@
   __ Load(scratch_register, FrameOffset(4092), 4);
   __ Load(scratch_register, FrameOffset(4096), 4);
   __ LoadRawPtrFromThread(scratch_register, ThreadOffset32(512));
-  __ LoadRef(method_register, scratch_register, MemberOffset(128), /* unpoison_reference= */ false);
 
   // Stores
   __ Store(FrameOffset(32), method_register, 4);
@@ -153,19 +153,67 @@
   __ Store(FrameOffset(1024), method_register, 4);
   __ Store(FrameOffset(4092), scratch_register, 4);
   __ Store(FrameOffset(4096), scratch_register, 4);
-  __ StoreImmediateToFrame(FrameOffset(48), 0xFF);
-  __ StoreImmediateToFrame(FrameOffset(48), 0xFFFFFF);
   __ StoreRawPtr(FrameOffset(48), scratch_register);
-  __ StoreRef(FrameOffset(48), scratch_register);
-  __ StoreSpanning(FrameOffset(48), method_register, FrameOffset(48));
-  __ StoreStackOffsetToThread(ThreadOffset32(512), FrameOffset(4096));
-  __ StoreStackPointerToThread(ThreadOffset32(512));
+  __ StoreStackPointerToThread(ThreadOffset32(512), false);
+  __ StoreStackPointerToThread(ThreadOffset32(512), true);
+
+  // MoveArguments
+  static constexpr FrameOffset kInvalidReferenceOffset =
+      JNIMacroAssembler<kArmPointerSize>::kInvalidReferenceOffset;
+  static constexpr size_t kNativePointerSize = static_cast<size_t>(kArmPointerSize);
+  // Normal or @FastNative with parameters (Object, long, long, int, Object).
+  // Note: This shall not spill the reference R1 to [sp, #36]. The JNI compiler spills
+  // references in an separate initial pass before moving arguments and creating `jobject`s.
+  ArgumentLocation move_dests1[] = {
+      ArgumentLocation(ArmManagedRegister::FromCoreRegister(R2), kNativePointerSize),
+      ArgumentLocation(FrameOffset(0), 2 * kVRegSize),
+      ArgumentLocation(FrameOffset(8), 2 * kVRegSize),
+      ArgumentLocation(FrameOffset(16), kVRegSize),
+      ArgumentLocation(FrameOffset(20), kNativePointerSize),
+  };
+  ArgumentLocation move_srcs1[] = {
+      ArgumentLocation(ArmManagedRegister::FromCoreRegister(R1), kVRegSize),
+      ArgumentLocation(ArmManagedRegister::FromRegisterPair(R2_R3), 2 * kVRegSize),
+      ArgumentLocation(FrameOffset(48), 2 * kVRegSize),
+      ArgumentLocation(FrameOffset(56), kVRegSize),
+      ArgumentLocation(FrameOffset(60), kVRegSize),
+  };
+  FrameOffset move_refs1[] {
+      FrameOffset(36),
+      FrameOffset(kInvalidReferenceOffset),
+      FrameOffset(kInvalidReferenceOffset),
+      FrameOffset(kInvalidReferenceOffset),
+      FrameOffset(60),
+  };
+  __ MoveArguments(ArrayRef<ArgumentLocation>(move_dests1),
+                   ArrayRef<ArgumentLocation>(move_srcs1),
+                   ArrayRef<FrameOffset>(move_refs1));
+  // @CriticalNative with parameters (long, long, long, int).
+  ArgumentLocation move_dests2[] = {
+      ArgumentLocation(ArmManagedRegister::FromRegisterPair(R0_R1), 2 * kVRegSize),
+      ArgumentLocation(ArmManagedRegister::FromRegisterPair(R2_R3), 2 * kVRegSize),
+      ArgumentLocation(FrameOffset(0), 2 * kVRegSize),
+      ArgumentLocation(FrameOffset(8), kVRegSize),
+  };
+  ArgumentLocation move_srcs2[] = {
+      ArgumentLocation(ArmManagedRegister::FromRegisterPair(R2_R3), 2 * kVRegSize),
+      ArgumentLocation(FrameOffset(28), kVRegSize),
+      ArgumentLocation(FrameOffset(32), 2 * kVRegSize),
+      ArgumentLocation(FrameOffset(40), kVRegSize),
+  };
+  FrameOffset move_refs2[] {
+      FrameOffset(kInvalidReferenceOffset),
+      FrameOffset(kInvalidReferenceOffset),
+      FrameOffset(kInvalidReferenceOffset),
+      FrameOffset(kInvalidReferenceOffset),
+  };
+  __ MoveArguments(ArrayRef<ArgumentLocation>(move_dests2),
+                   ArrayRef<ArgumentLocation>(move_srcs2),
+                   ArrayRef<FrameOffset>(move_refs2));
 
   // Other
   __ Call(method_register, FrameOffset(48));
   __ Copy(FrameOffset(48), FrameOffset(44), 4);
-  __ CopyRawPtrFromThread(FrameOffset(44), ThreadOffset32(512));
-  __ CopyRef(FrameOffset(48), FrameOffset(44));
   __ GetCurrentThread(method_register);
   __ GetCurrentThread(FrameOffset(48));
   __ Move(hidden_arg_register, method_register, 4);
@@ -176,7 +224,6 @@
   __ CreateJObject(high_register, FrameOffset(48), high_register, true);
   __ CreateJObject(high_register, FrameOffset(48), high_register, false);
   __ CreateJObject(method_register, FrameOffset(48), high_register, true);
-  __ CreateJObject(FrameOffset(48), FrameOffset(64), true);
   __ CreateJObject(method_register, FrameOffset(0), high_register, true);
   __ CreateJObject(method_register, FrameOffset(1028), high_register, true);
   __ CreateJObject(high_register, FrameOffset(1028), high_register, true);
diff --git a/compiler/utils/assembler_thumb_test_expected.cc.inc b/compiler/utils/assembler_thumb_test_expected.cc.inc
index b6c6025..aea7f14 100644
--- a/compiler/utils/assembler_thumb_test_expected.cc.inc
+++ b/compiler/utils/assembler_thumb_test_expected.cc.inc
@@ -1,258 +1,259 @@
 const char* const VixlJniHelpersResults = {
-  "       0: 2d e9 e0 4d   push.w {r5, r6, r7, r8, r10, r11, lr}\n"
-  "       4: 2d ed 10 8a   vpush {s16, s17, s18, s19, s20, s21, s22, s23, s24, s25, s26, s27, s28, s29, s30, s31}\n"
-  "       8: 81 b0         sub sp, #4\n"
-  "       a: 00 90         str r0, [sp]\n"
-  "       c: 19 91         str r1, [sp, #100]\n"
-  "       e: 8d ed 1a 0a   vstr s0, [sp, #104]\n"
-  "      12: 1b 92         str r2, [sp, #108]\n"
-  "      14: 1c 93         str r3, [sp, #112]\n"
-  "      16: 88 b0         sub sp, #32\n"
-  "      18: ad f5 80 5d   sub.w sp, sp, #4096\n"
-  "      1c: 08 98         ldr r0, [sp, #32]\n"
-  "      1e: 1f 98         ldr r0, [sp, #124]\n"
-  "      20: 21 98         ldr r0, [sp, #132]\n"
-  "      22: ff 98         ldr r0, [sp, #1020]\n"
-  "      24: dd f8 00 04   ldr.w r0, [sp, #1024]\n"
-  "      28: dd f8 fc cf   ldr.w r12, [sp, #4092]\n"
-  "      2c: 0d f5 80 5c   add.w r12, sp, #4096\n"
-  "      30: dc f8 00 c0   ldr.w r12, [r12]\n"
-  "      34: d9 f8 00 c2   ldr.w r12, [r9, #512]\n"
-  "      38: dc f8 80 00   ldr.w r0, [r12, #128]\n"
-  "      3c: 08 90         str r0, [sp, #32]\n"
-  "      3e: 1f 90         str r0, [sp, #124]\n"
-  "      40: 21 90         str r0, [sp, #132]\n"
-  "      42: ff 90         str r0, [sp, #1020]\n"
-  "      44: cd f8 00 04   str.w r0, [sp, #1024]\n"
-  "      48: cd f8 fc cf   str.w r12, [sp, #4092]\n"
-  "      4c: 4d f8 04 5d   str r5, [sp, #-4]!\n"
-  "      50: 0d f5 80 55   add.w r5, sp, #4096\n"
-  "      54: c5 f8 04 c0   str.w r12, [r5, #4]\n"
-  "      58: 5d f8 04 5b   ldr r5, [sp], #4\n"
-  "      5c: 4f f0 ff 0c   mov.w r12, #255\n"
-  "      60: cd f8 30 c0   str.w r12, [sp, #48]\n"
-  "      64: 6f f0 7f 4c   mvn r12, #4278190080\n"
-  "      68: cd f8 30 c0   str.w r12, [sp, #48]\n"
-  "      6c: cd f8 30 c0   str.w r12, [sp, #48]\n"
-  "      70: cd f8 30 c0   str.w r12, [sp, #48]\n"
-  "      74: 0c 90         str r0, [sp, #48]\n"
-  "      76: dd f8 30 c0   ldr.w r12, [sp, #48]\n"
-  "      7a: cd f8 34 c0   str.w r12, [sp, #52]\n"
-  "      7e: 0d f5 80 5c   add.w r12, sp, #4096\n"
-  "      82: c9 f8 00 c2   str.w r12, [r9, #512]\n"
-  "      86: c9 f8 00 d2   str.w sp, [r9, #512]\n"
-  "      8a: d0 f8 30 e0   ldr.w lr, [r0, #48]\n"
-  "      8e: f0 47         blx lr\n"
-  "      90: dd f8 2c c0   ldr.w r12, [sp, #44]\n"
-  "      94: cd f8 30 c0   str.w r12, [sp, #48]\n"
-  "      98: d9 f8 00 c2   ldr.w r12, [r9, #512]\n"
-  "      9c: cd f8 2c c0   str.w r12, [sp, #44]\n"
-  "      a0: dd f8 2c c0   ldr.w r12, [sp, #44]\n"
-  "      a4: cd f8 30 c0   str.w r12, [sp, #48]\n"
-  "      a8: 48 46         mov r0, r9\n"
-  "      aa: cd f8 30 90   str.w r9, [sp, #48]\n"
-  "      ae: 04 46         mov r4, r0\n"
-  "      b0: 0d f1 30 0c   add.w r12, sp, #48\n"
-  "      b4: bb f1 00 0f   cmp.w r11, #0\n"
-  "      b8: 18 bf         it ne\n"
-  "      ba: e3 46         movne r11, r12\n"
-  "      bc: 0d f1 30 0b   add.w r11, sp, #48\n"
-  "      c0: 5f ea 0b 00   movs.w r0, r11\n"
-  "      c4: 18 bf         it ne\n"
-  "      c6: 0c a8         addne r0, sp, #48\n"
-  "      c8: dd f8 40 c0   ldr.w r12, [sp, #64]\n"
-  "      cc: bc f1 00 0f   cmp.w r12, #0\n"
-  "      d0: 18 bf         it ne\n"
-  "      d2: 0d f1 40 0c   addne.w r12, sp, #64\n"
-  "      d6: cd f8 30 c0   str.w r12, [sp, #48]\n"
-  "      da: 5f ea 0b 00   movs.w r0, r11\n"
-  "      de: 18 bf         it ne\n"
-  "      e0: 00 a8         addne r0, sp, #0\n"
-  "      e2: 0d f2 04 40   addw r0, sp, #1028\n"
-  "      e6: bb f1 00 0f   cmp.w r11, #0\n"
-  "      ea: 08 bf         it eq\n"
-  "      ec: 58 46         moveq r0, r11\n"
-  "      ee: 0d f2 04 4c   addw r12, sp, #1028\n"
-  "      f2: bb f1 00 0f   cmp.w r11, #0\n"
-  "      f6: 18 bf         it ne\n"
-  "      f8: e3 46         movne r11, r12\n"
-  "      fa: d9 f8 94 c0   ldr.w r12, [r9, #148]\n"
-  "      fe: bc f1 00 0f   cmp.w r12, #0\n"
-  "     102: 71 d1         bne 0x1e8     @ imm = #226\n"
-  "     104: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     108: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     10c: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     110: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     114: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     118: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     11c: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     120: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     124: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     128: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     12c: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     130: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     134: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     138: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     13c: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     140: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     144: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     148: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     14c: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     150: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     154: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     158: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     15c: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     160: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     164: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     168: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     16c: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     170: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     174: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     178: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     17c: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     180: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     184: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     188: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     18c: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     190: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     194: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     198: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     19c: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1a0: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1a4: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1a8: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1ac: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1b0: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1b4: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1b8: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1bc: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1c0: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1c4: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1c8: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1cc: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1d0: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1d4: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1d8: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1dc: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1e0: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1e4: 00 f0 02 b8   b.w 0x1ec     @ imm = #4\n"
-  "     1e8: 00 f0 1b b8   b.w 0x222     @ imm = #54\n"
-  "     1ec: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1f0: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1f4: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1f8: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     1fc: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     200: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     204: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     208: cd f8 ff c7   str.w r12, [sp, #2047]\n"
-  "     20c: 0d f5 80 5d   add.w sp, sp, #4096\n"
-  "     210: 08 b0         add sp, #32\n"
-  "     212: 01 b0         add sp, #4\n"
-  "     214: bd ec 10 8a   vpop {s16, s17, s18, s19, s20, s21, s22, s23, s24, s25, s26, s27, s28, s29, s30, s31}\n"
-  "     218: bd e8 e0 4d   pop.w {r5, r6, r7, r8, r10, r11, lr}\n"
-  "     21c: d9 f8 24 80   ldr.w r8, [r9, #36]\n"
-  "     220: 70 47         bx lr\n"
-  "     222: d9 f8 94 00   ldr.w r0, [r9, #148]\n"
-  "     226: d9 f8 c8 e2   ldr.w lr, [r9, #712]\n"
-  "     22a: f0 47         blx lr\n"
+  "       0: e92d 4de0     push.w {r5, r6, r7, r8, r10, r11, lr}\n"
+  "       4: ed2d 8a10     vpush {s16, s17, s18, s19, s20, s21, s22, s23, s24, s25, s26, s27, s28, s29, s30, s31}\n"
+  "       8: b081          sub sp, #4\n"
+  "       a: 9000          str r0, [sp]\n"
+  "       c: 9119          str r1, [sp, #100]\n"
+  "       e: ed8d 0a1a     vstr s0, [sp, #104]\n"
+  "      12: 921b          str r2, [sp, #108]\n"
+  "      14: 931c          str r3, [sp, #112]\n"
+  "      16: b088          sub sp, #32\n"
+  "      18: f5ad 5d80     sub.w sp, sp, #4096\n"
+  "      1c: 9808          ldr r0, [sp, #32]\n"
+  "      1e: 981f          ldr r0, [sp, #124]\n"
+  "      20: 9821          ldr r0, [sp, #132]\n"
+  "      22: 98ff          ldr r0, [sp, #1020]\n"
+  "      24: f8dd 0400     ldr.w r0, [sp, #1024]\n"
+  "      28: f8dd cffc     ldr.w r12, [sp, #4092]\n"
+  "      2c: f50d 5c80     add.w r12, sp, #4096\n"
+  "      30: f8dc c000     ldr.w r12, [r12]\n"
+  "      34: f8d9 c200     ldr.w r12, [r9, #512]\n"
+  "      38: 9008          str r0, [sp, #32]\n"
+  "      3a: 901f          str r0, [sp, #124]\n"
+  "      3c: 9021          str r0, [sp, #132]\n"
+  "      3e: 90ff          str r0, [sp, #1020]\n"
+  "      40: f8cd 0400     str.w r0, [sp, #1024]\n"
+  "      44: f8cd cffc     str.w r12, [sp, #4092]\n"
+  "      48: f84d 5d04     str r5, [sp, #-4]!\n"
+  "      4c: f50d 5580     add.w r5, sp, #4096\n"
+  "      50: f8c5 c004     str.w r12, [r5, #4]\n"
+  "      54: f85d 5b04     ldr r5, [sp], #4\n"
+  "      58: f8cd c030     str.w r12, [sp, #48]\n"
+  "      5c: f8c9 d200     str.w sp, [r9, #512]\n"
+  "      60: f04d 0c02     orr r12, sp, #2\n"
+  "      64: f8c9 c200     str.w r12, [r9, #512]\n"
+  "      68: a909          add r1, sp, #36\n"
+  "      6a: e9cd 2300     strd r2, r3, [sp]\n"
+  "      6e: e9dd 020c     ldrd r0, r2, [sp, #48]\n"
+  "      72: e9cd 0202     strd r0, r2, [sp, #8]\n"
+  "      76: e9dd 020e     ldrd r0, r2, [sp, #56]\n"
+  "      7a: 2a00          cmp r2, #0\n"
+  "      7c: bf18          it ne\n"
+  "      7e: aa0f          addne r2, sp, #60\n"
+  "      80: e9cd 0204     strd r0, r2, [sp, #16]\n"
+  "      84: 460a          mov r2, r1\n"
+  "      86: e9dd 0108     ldrd r0, r1, [sp, #32]\n"
+  "      8a: e9cd 0100     strd r0, r1, [sp]\n"
+  "      8e: f8dd c028     ldr.w r12, [sp, #40]\n"
+  "      92: f8cd c008     str.w r12, [sp, #8]\n"
+  "      96: 4610          mov r0, r2\n"
+  "      98: 4619          mov r1, r3\n"
+  "      9a: 9a07          ldr r2, [sp, #28]\n"
+  "      9c: 9b08          ldr r3, [sp, #32]\n"
+  "      9e: f8d0 e030     ldr.w lr, [r0, #48]\n"
+  "      a2: 47f0          blx lr\n"
+  "      a4: f8dd c02c     ldr.w r12, [sp, #44]\n"
+  "      a8: f8cd c030     str.w r12, [sp, #48]\n"
+  "      ac: 4648          mov r0, r9\n"
+  "      ae: f8cd 9030     str.w r9, [sp, #48]\n"
+  "      b2: 4604          mov r4, r0\n"
+  "      b4: f10d 0c30     add.w r12, sp, #48\n"
+  "      b8: f1bb 0f00     cmp.w r11, #0\n"
+  "      bc: bf18          it ne\n"
+  "      be: 46e3          movne r11, r12\n"
+  "      c0: f10d 0b30     add.w r11, sp, #48\n"
+  "      c4: ea5f 000b     movs.w r0, r11\n"
+  "      c8: bf18          it ne\n"
+  "      ca: a80c          addne r0, sp, #48\n"
+  "      cc: ea5f 000b     movs.w r0, r11\n"
+  "      d0: bf18          it ne\n"
+  "      d2: a800          addne r0, sp, #0\n"
+  "      d4: f20d 4004     addw r0, sp, #1028\n"
+  "      d8: f1bb 0f00     cmp.w r11, #0\n"
+  "      dc: bf08          it eq\n"
+  "      de: 4658          moveq r0, r11\n"
+  "      e0: f20d 4c04     addw r12, sp, #1028\n"
+  "      e4: f1bb 0f00     cmp.w r11, #0\n"
+  "      e8: bf18          it ne\n"
+  "      ea: 46e3          movne r11, r12\n"
+  "      ec: f8d9 c09c     ldr.w r12, [r9, #156]\n"
+  "      f0: f1bc 0f00     cmp.w r12, #0\n"
+  "      f4: d16f          bne 0x1d6     @ imm = #222\n"
+  "      f6: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "      fa: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "      fe: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     102: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     106: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     10a: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     10e: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     112: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     116: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     11a: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     11e: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     122: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     126: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     12a: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     12e: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     132: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     136: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     13a: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     13e: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     142: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     146: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     14a: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     14e: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     152: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     156: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     15a: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     15e: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     162: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     166: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     16a: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     16e: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     172: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     176: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     17a: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     17e: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     182: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     186: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     18a: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     18e: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     192: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     196: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     19a: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     19e: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1a2: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1a6: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1aa: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1ae: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1b2: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1b6: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1ba: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1be: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1c2: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1c6: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1ca: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1ce: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1d2: f000 b803     b.w 0x1dc     @ imm = #6\n"
+  "     1d6: f000 b81e     b.w 0x216     @ imm = #60\n"
+  "     1da: 0000          movs r0, r0\n"
+  "     1dc: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1e0: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1e4: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1e8: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1ec: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1f0: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1f4: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1f8: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     1fc: f8cd c7ff     str.w r12, [sp, #2047]\n"
+  "     200: f50d 5d80     add.w sp, sp, #4096\n"
+  "     204: b008          add sp, #32\n"
+  "     206: b001          add sp, #4\n"
+  "     208: ecbd 8a10     vpop {s16, s17, s18, s19, s20, s21, s22, s23, s24, s25, s26, s27, s28, s29, s30, s31}\n"
+  "     20c: e8bd 4de0     pop.w {r5, r6, r7, r8, r10, r11, lr}\n"
+  "     210: f8d9 8024     ldr.w r8, [r9, #36]\n"
+  "     214: 4770          bx lr\n"
+  "     216: f8d9 009c     ldr.w r0, [r9, #156]\n"
+  "     21a: f8d9 e2d0     ldr.w lr, [r9, #720]\n"
+  "     21e: 47f0          blx lr\n"
 };
 
 const char* const VixlLoadFromOffsetResults = {
-  "       0: e2 68         ldr r2, [r4, #12]\n"
-  "       2: d4 f8 ff 2f   ldr.w r2, [r4, #4095]\n"
-  "       6: 04 f5 80 52   add.w r2, r4, #4096\n"
-  "       a: 12 68         ldr r2, [r2]\n"
-  "       c: 04 f5 80 12   add.w r2, r4, #1048576\n"
-  "      10: d2 f8 a4 20   ldr.w r2, [r2, #164]\n"
-  "      14: 4f f4 80 52   mov.w r2, #4096\n"
-  "      18: c0 f2 10 02   movt r2, #16\n"
-  "      1c: 22 44         add r2, r4\n"
-  "      1e: 12 68         ldr r2, [r2]\n"
-  "      20: 4f f4 80 5c   mov.w r12, #4096\n"
-  "      24: c0 f2 10 0c   movt r12, #16\n"
-  "      28: 64 44         add r4, r12\n"
-  "      2a: 24 68         ldr r4, [r4]\n"
-  "      2c: a2 89         ldrh r2, [r4, #12]\n"
-  "      2e: b4 f8 ff 2f   ldrh.w r2, [r4, #4095]\n"
-  "      32: 04 f5 80 52   add.w r2, r4, #4096\n"
-  "      36: 12 88         ldrh r2, [r2]\n"
-  "      38: 04 f5 80 12   add.w r2, r4, #1048576\n"
-  "      3c: b2 f8 a4 20   ldrh.w r2, [r2, #164]\n"
-  "      40: 4f f4 80 52   mov.w r2, #4096\n"
-  "      44: c0 f2 10 02   movt r2, #16\n"
-  "      48: 22 44         add r2, r4\n"
-  "      4a: 12 88         ldrh r2, [r2]\n"
-  "      4c: 4f f4 80 5c   mov.w r12, #4096\n"
-  "      50: c0 f2 10 0c   movt r12, #16\n"
-  "      54: 64 44         add r4, r12\n"
-  "      56: 24 88         ldrh r4, [r4]\n"
-  "      58: d4 e9 03 23   ldrd r2, r3, [r4, #12]\n"
-  "      5c: d4 e9 ff 23   ldrd r2, r3, [r4, #1020]\n"
-  "      60: 04 f5 80 62   add.w r2, r4, #1024\n"
-  "      64: d2 e9 00 23   ldrd r2, r3, [r2]\n"
-  "      68: 04 f5 80 22   add.w r2, r4, #262144\n"
-  "      6c: d2 e9 29 23   ldrd r2, r3, [r2, #164]\n"
-  "      70: 4f f4 80 62   mov.w r2, #1024\n"
-  "      74: c0 f2 04 02   movt r2, #4\n"
-  "      78: 22 44         add r2, r4\n"
-  "      7a: d2 e9 00 23   ldrd r2, r3, [r2]\n"
-  "      7e: 4f f4 80 6c   mov.w r12, #1024\n"
-  "      82: c0 f2 04 0c   movt r12, #4\n"
-  "      86: 64 44         add r4, r12\n"
-  "      88: d4 e9 00 45   ldrd r4, r5, [r4]\n"
-  "      8c: dc f8 0c 00   ldr.w r0, [r12, #12]\n"
-  "      90: a4 f5 80 12   sub.w r2, r4, #1048576\n"
-  "      94: d2 f8 a4 20   ldr.w r2, [r2, #164]\n"
-  "      98: 94 f9 0c 20   ldrsb.w r2, [r4, #12]\n"
-  "      9c: 22 7b         ldrb r2, [r4, #12]\n"
-  "      9e: b4 f9 0c 20   ldrsh.w r2, [r4, #12]\n"
+  "       0: 68e2          ldr r2, [r4, #12]\n"
+  "       2: f8d4 2fff     ldr.w r2, [r4, #4095]\n"
+  "       6: f504 5280     add.w r2, r4, #4096\n"
+  "       a: 6812          ldr r2, [r2]\n"
+  "       c: f504 1280     add.w r2, r4, #1048576\n"
+  "      10: f8d2 20a4     ldr.w r2, [r2, #164]\n"
+  "      14: f44f 5280     mov.w r2, #4096\n"
+  "      18: f2c0 0210     movt r2, #16\n"
+  "      1c: 4422          add r2, r4\n"
+  "      1e: 6812          ldr r2, [r2]\n"
+  "      20: f44f 5c80     mov.w r12, #4096\n"
+  "      24: f2c0 0c10     movt r12, #16\n"
+  "      28: 4464          add r4, r12\n"
+  "      2a: 6824          ldr r4, [r4]\n"
+  "      2c: 89a2          ldrh r2, [r4, #12]\n"
+  "      2e: f8b4 2fff     ldrh.w r2, [r4, #4095]\n"
+  "      32: f504 5280     add.w r2, r4, #4096\n"
+  "      36: 8812          ldrh r2, [r2]\n"
+  "      38: f504 1280     add.w r2, r4, #1048576\n"
+  "      3c: f8b2 20a4     ldrh.w r2, [r2, #164]\n"
+  "      40: f44f 5280     mov.w r2, #4096\n"
+  "      44: f2c0 0210     movt r2, #16\n"
+  "      48: 4422          add r2, r4\n"
+  "      4a: 8812          ldrh r2, [r2]\n"
+  "      4c: f44f 5c80     mov.w r12, #4096\n"
+  "      50: f2c0 0c10     movt r12, #16\n"
+  "      54: 4464          add r4, r12\n"
+  "      56: 8824          ldrh r4, [r4]\n"
+  "      58: e9d4 2303     ldrd r2, r3, [r4, #12]\n"
+  "      5c: e9d4 23ff     ldrd r2, r3, [r4, #1020]\n"
+  "      60: f504 6280     add.w r2, r4, #1024\n"
+  "      64: e9d2 2300     ldrd r2, r3, [r2]\n"
+  "      68: f504 2280     add.w r2, r4, #262144\n"
+  "      6c: e9d2 2329     ldrd r2, r3, [r2, #164]\n"
+  "      70: f44f 6280     mov.w r2, #1024\n"
+  "      74: f2c0 0204     movt r2, #4\n"
+  "      78: 4422          add r2, r4\n"
+  "      7a: e9d2 2300     ldrd r2, r3, [r2]\n"
+  "      7e: f44f 6c80     mov.w r12, #1024\n"
+  "      82: f2c0 0c04     movt r12, #4\n"
+  "      86: 4464          add r4, r12\n"
+  "      88: e9d4 4500     ldrd r4, r5, [r4]\n"
+  "      8c: f8dc 000c     ldr.w r0, [r12, #12]\n"
+  "      90: f5a4 1280     sub.w r2, r4, #1048576\n"
+  "      94: f8d2 20a4     ldr.w r2, [r2, #164]\n"
+  "      98: f994 200c     ldrsb.w r2, [r4, #12]\n"
+  "      9c: 7b22          ldrb r2, [r4, #12]\n"
+  "      9e: f9b4 200c     ldrsh.w r2, [r4, #12]\n"
 };
 
 const char* const VixlStoreToOffsetResults = {
-  "       0: e2 60         str r2, [r4, #12]\n"
-  "       2: c4 f8 ff 2f   str.w r2, [r4, #4095]\n"
-  "       6: 04 f5 80 5c   add.w r12, r4, #4096\n"
-  "       a: cc f8 00 20   str.w r2, [r12]\n"
-  "       e: 04 f5 80 1c   add.w r12, r4, #1048576\n"
-  "      12: cc f8 a4 20   str.w r2, [r12, #164]\n"
-  "      16: 4f f4 80 5c   mov.w r12, #4096\n"
-  "      1a: c0 f2 10 0c   movt r12, #16\n"
-  "      1e: a4 44         add r12, r4\n"
-  "      20: cc f8 00 20   str.w r2, [r12]\n"
-  "      24: 4f f4 80 5c   mov.w r12, #4096\n"
-  "      28: c0 f2 10 0c   movt r12, #16\n"
-  "      2c: a4 44         add r12, r4\n"
-  "      2e: cc f8 00 40   str.w r4, [r12]\n"
-  "      32: a2 81         strh r2, [r4, #12]\n"
-  "      34: a4 f8 ff 2f   strh.w r2, [r4, #4095]\n"
-  "      38: 04 f5 80 5c   add.w r12, r4, #4096\n"
-  "      3c: ac f8 00 20   strh.w r2, [r12]\n"
-  "      40: 04 f5 80 1c   add.w r12, r4, #1048576\n"
-  "      44: ac f8 a4 20   strh.w r2, [r12, #164]\n"
-  "      48: 4f f4 80 5c   mov.w r12, #4096\n"
-  "      4c: c0 f2 10 0c   movt r12, #16\n"
-  "      50: a4 44         add r12, r4\n"
-  "      52: ac f8 00 20   strh.w r2, [r12]\n"
-  "      56: 4f f4 80 5c   mov.w r12, #4096\n"
-  "      5a: c0 f2 10 0c   movt r12, #16\n"
-  "      5e: a4 44         add r12, r4\n"
-  "      60: ac f8 00 40   strh.w r4, [r12]\n"
-  "      64: c4 e9 03 23   strd r2, r3, [r4, #12]\n"
-  "      68: c4 e9 ff 23   strd r2, r3, [r4, #1020]\n"
-  "      6c: 04 f5 80 6c   add.w r12, r4, #1024\n"
-  "      70: cc e9 00 23   strd r2, r3, [r12]\n"
-  "      74: 04 f5 80 2c   add.w r12, r4, #262144\n"
-  "      78: cc e9 29 23   strd r2, r3, [r12, #164]\n"
-  "      7c: 4f f4 80 6c   mov.w r12, #1024\n"
-  "      80: c0 f2 04 0c   movt r12, #4\n"
-  "      84: a4 44         add r12, r4\n"
-  "      86: cc e9 00 23   strd r2, r3, [r12]\n"
-  "      8a: 4f f4 80 6c   mov.w r12, #1024\n"
-  "      8e: c0 f2 04 0c   movt r12, #4\n"
-  "      92: a4 44         add r12, r4\n"
-  "      94: cc e9 00 45   strd r4, r5, [r12]\n"
-  "      98: cc f8 0c 00   str.w r0, [r12, #12]\n"
-  "      9c: a4 f5 80 1c   sub.w r12, r4, #1048576\n"
-  "      a0: cc f8 a4 20   str.w r2, [r12, #164]\n"
-  "      a4: 22 73         strb r2, [r4, #12]\n"
+  "       0: 60e2          str r2, [r4, #12]\n"
+  "       2: f8c4 2fff     str.w r2, [r4, #4095]\n"
+  "       6: f504 5c80     add.w r12, r4, #4096\n"
+  "       a: f8cc 2000     str.w r2, [r12]\n"
+  "       e: f504 1c80     add.w r12, r4, #1048576\n"
+  "      12: f8cc 20a4     str.w r2, [r12, #164]\n"
+  "      16: f44f 5c80     mov.w r12, #4096\n"
+  "      1a: f2c0 0c10     movt r12, #16\n"
+  "      1e: 44a4          add r12, r4\n"
+  "      20: f8cc 2000     str.w r2, [r12]\n"
+  "      24: f44f 5c80     mov.w r12, #4096\n"
+  "      28: f2c0 0c10     movt r12, #16\n"
+  "      2c: 44a4          add r12, r4\n"
+  "      2e: f8cc 4000     str.w r4, [r12]\n"
+  "      32: 81a2          strh r2, [r4, #12]\n"
+  "      34: f8a4 2fff     strh.w r2, [r4, #4095]\n"
+  "      38: f504 5c80     add.w r12, r4, #4096\n"
+  "      3c: f8ac 2000     strh.w r2, [r12]\n"
+  "      40: f504 1c80     add.w r12, r4, #1048576\n"
+  "      44: f8ac 20a4     strh.w r2, [r12, #164]\n"
+  "      48: f44f 5c80     mov.w r12, #4096\n"
+  "      4c: f2c0 0c10     movt r12, #16\n"
+  "      50: 44a4          add r12, r4\n"
+  "      52: f8ac 2000     strh.w r2, [r12]\n"
+  "      56: f44f 5c80     mov.w r12, #4096\n"
+  "      5a: f2c0 0c10     movt r12, #16\n"
+  "      5e: 44a4          add r12, r4\n"
+  "      60: f8ac 4000     strh.w r4, [r12]\n"
+  "      64: e9c4 2303     strd r2, r3, [r4, #12]\n"
+  "      68: e9c4 23ff     strd r2, r3, [r4, #1020]\n"
+  "      6c: f504 6c80     add.w r12, r4, #1024\n"
+  "      70: e9cc 2300     strd r2, r3, [r12]\n"
+  "      74: f504 2c80     add.w r12, r4, #262144\n"
+  "      78: e9cc 2329     strd r2, r3, [r12, #164]\n"
+  "      7c: f44f 6c80     mov.w r12, #1024\n"
+  "      80: f2c0 0c04     movt r12, #4\n"
+  "      84: 44a4          add r12, r4\n"
+  "      86: e9cc 2300     strd r2, r3, [r12]\n"
+  "      8a: f44f 6c80     mov.w r12, #1024\n"
+  "      8e: f2c0 0c04     movt r12, #4\n"
+  "      92: 44a4          add r12, r4\n"
+  "      94: e9cc 4500     strd r4, r5, [r12]\n"
+  "      98: f8cc 000c     str.w r0, [r12, #12]\n"
+  "      9c: f5a4 1c80     sub.w r12, r4, #1048576\n"
+  "      a0: f8cc 20a4     str.w r2, [r12, #164]\n"
+  "      a4: 7322          strb r2, [r4, #12]\n"
 };
diff --git a/compiler/utils/atomic_dex_ref_map-inl.h b/compiler/utils/atomic_dex_ref_map-inl.h
index 377b7fe..5f68a7c 100644
--- a/compiler/utils/atomic_dex_ref_map-inl.h
+++ b/compiler/utils/atomic_dex_ref_map-inl.h
@@ -21,12 +21,13 @@
 
 #include <type_traits>
 
+#include "base/macros.h"
 #include "dex/class_reference.h"
 #include "dex/dex_file-inl.h"
 #include "dex/method_reference.h"
 #include "dex/type_reference.h"
 
-namespace art {
+namespace art HIDDEN {
 
 template <typename DexFileReferenceType, typename Value>
 inline size_t AtomicDexRefMap<DexFileReferenceType, Value>::NumberOfDexIndices(
diff --git a/compiler/utils/atomic_dex_ref_map.h b/compiler/utils/atomic_dex_ref_map.h
index a8c285f..b10fef5 100644
--- a/compiler/utils/atomic_dex_ref_map.h
+++ b/compiler/utils/atomic_dex_ref_map.h
@@ -19,10 +19,11 @@
 
 #include "base/atomic.h"
 #include "base/dchecked_vector.h"
+#include "base/macros.h"
 #include "base/safe_map.h"
 #include "dex/dex_file_reference.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class DexFile;
 
diff --git a/compiler/utils/atomic_dex_ref_map_test.cc b/compiler/utils/atomic_dex_ref_map_test.cc
index 864531e..329735b 100644
--- a/compiler/utils/atomic_dex_ref_map_test.cc
+++ b/compiler/utils/atomic_dex_ref_map_test.cc
@@ -18,12 +18,13 @@
 
 #include <memory>
 
+#include "base/macros.h"
 #include "common_runtime_test.h"
 #include "dex/dex_file-inl.h"
 #include "dex/method_reference.h"
 #include "scoped_thread_state_change-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class AtomicDexRefMapTest : public CommonRuntimeTest {};
 
diff --git a/compiler/utils/dedupe_set-inl.h b/compiler/utils/dedupe_set-inl.h
index d4a9cc8..db744c5 100644
--- a/compiler/utils/dedupe_set-inl.h
+++ b/compiler/utils/dedupe_set-inl.h
@@ -27,11 +27,12 @@
 #include "android-base/stringprintf.h"
 
 #include "base/hash_set.h"
+#include "base/macros.h"
 #include "base/mutex.h"
 #include "base/stl_util.h"
 #include "base/time_utils.h"
 
-namespace art {
+namespace art HIDDEN {
 
 template <typename InKey,
           typename StoreKey,
diff --git a/compiler/utils/dedupe_set.h b/compiler/utils/dedupe_set.h
index a1ba208..42db8e3 100644
--- a/compiler/utils/dedupe_set.h
+++ b/compiler/utils/dedupe_set.h
@@ -23,7 +23,7 @@
 
 #include "base/macros.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class Thread;
 
diff --git a/compiler/utils/dedupe_set_test.cc b/compiler/utils/dedupe_set_test.cc
index b390508..89385e7 100644
--- a/compiler/utils/dedupe_set_test.cc
+++ b/compiler/utils/dedupe_set_test.cc
@@ -21,11 +21,12 @@
 #include <vector>
 
 #include "base/array_ref.h"
+#include "base/macros.h"
 #include "dedupe_set-inl.h"
 #include "gtest/gtest.h"
 #include "thread-current-inl.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class DedupeSetTestHashFunc {
  public:
diff --git a/compiler/utils/jni_macro_assembler.cc b/compiler/utils/jni_macro_assembler.cc
index d6d49f8..8b47b38 100644
--- a/compiler/utils/jni_macro_assembler.cc
+++ b/compiler/utils/jni_macro_assembler.cc
@@ -35,7 +35,7 @@
 #include "base/globals.h"
 #include "base/memory_region.h"
 
-namespace art {
+namespace art HIDDEN {
 
 using MacroAsm32UniquePtr = std::unique_ptr<JNIMacroAssembler<PointerSize::k32>>;
 
@@ -58,6 +58,7 @@
       return MacroAsm32UniquePtr(new (allocator) x86::X86JNIMacroAssembler(allocator));
 #endif
     default:
+      UNUSED(allocator);
       LOG(FATAL) << "Unknown/unsupported 4B InstructionSet: " << instruction_set;
       UNREACHABLE();
   }
diff --git a/compiler/utils/jni_macro_assembler.h b/compiler/utils/jni_macro_assembler.h
index 7022e3d..0c72970 100644
--- a/compiler/utils/jni_macro_assembler.h
+++ b/compiler/utils/jni_macro_assembler.h
@@ -30,7 +30,7 @@
 #include "managed_register.h"
 #include "offsets.h"
 
-namespace art {
+namespace art HIDDEN {
 
 class ArenaAllocator;
 class DebugFrameOpCodeWriterForAssembler;
@@ -118,37 +118,18 @@
   // Store routines
   virtual void Store(FrameOffset offs, ManagedRegister src, size_t size) = 0;
   virtual void Store(ManagedRegister base, MemberOffset offs, ManagedRegister src, size_t size) = 0;
-  virtual void StoreRef(FrameOffset dest, ManagedRegister src) = 0;
   virtual void StoreRawPtr(FrameOffset dest, ManagedRegister src) = 0;
 
-  virtual void StoreImmediateToFrame(FrameOffset dest, uint32_t imm) = 0;
-
-  virtual void StoreStackOffsetToThread(ThreadOffset<kPointerSize> thr_offs,
-                                        FrameOffset fr_offs) = 0;
-
-  virtual void StoreStackPointerToThread(ThreadOffset<kPointerSize> thr_offs) = 0;
-
-  virtual void StoreSpanning(FrameOffset dest,
-                             ManagedRegister src,
-                             FrameOffset in_off) = 0;
+  // Stores stack pointer by tagging it if required so we can walk the stack. In debuggable runtimes
+  // we use tag to tell if we are using JITed code or AOT code. In non-debuggable runtimes we never
+  // use JITed code when AOT code is present. So checking for AOT code is sufficient to detect which
+  // code is being executed. We avoid tagging in non-debuggable runtimes to reduce instructions.
+  virtual void StoreStackPointerToThread(ThreadOffset<kPointerSize> thr_offs, bool tag_sp) = 0;
 
   // Load routines
   virtual void Load(ManagedRegister dest, FrameOffset src, size_t size) = 0;
   virtual void Load(ManagedRegister dest, ManagedRegister base, MemberOffset offs, size_t size) = 0;
 
-  virtual void LoadFromThread(ManagedRegister dest,
-                              ThreadOffset<kPointerSize> src,
-                              size_t size) = 0;
-
-  virtual void LoadRef(ManagedRegister dest, FrameOffset src) = 0;
-  // If unpoison_reference is true and kPoisonReference is true, then we negate the read reference.
-  virtual void LoadRef(ManagedRegister dest,
-                       ManagedRegister base,
-                       MemberOffset offs,
-                       bool unpoison_reference) = 0;
-
-  virtual void LoadRawPtr(ManagedRegister dest, ManagedRegister base, Offset offs) = 0;
-
   virtual void LoadRawPtrFromThread(ManagedRegister dest, ThreadOffset<kPointerSize> offs) = 0;
 
   // Copying routines
@@ -165,53 +146,7 @@
 
   virtual void Move(ManagedRegister dest, ManagedRegister src, size_t size) = 0;
 
-  virtual void CopyRawPtrFromThread(FrameOffset fr_offs, ThreadOffset<kPointerSize> thr_offs) = 0;
-
-  virtual void CopyRawPtrToThread(ThreadOffset<kPointerSize> thr_offs,
-                                  FrameOffset fr_offs,
-                                  ManagedRegister scratch) = 0;
-
-  virtual void CopyRef(FrameOffset dest, FrameOffset src) = 0;
-  virtual void CopyRef(FrameOffset dest,
-                       ManagedRegister base,
-                       MemberOffset offs,
-                       bool unpoison_reference) = 0;
-
-  virtual void Copy(FrameOffset dest, FrameOffset src, size_t size) = 0;
-
-  virtual void Copy(FrameOffset dest,
-                    ManagedRegister src_base,
-                    Offset src_offset,
-                    ManagedRegister scratch,
-                    size_t size) = 0;
-
-  virtual void Copy(ManagedRegister dest_base,
-                    Offset dest_offset,
-                    FrameOffset src,
-                    ManagedRegister scratch,
-                    size_t size) = 0;
-
-  virtual void Copy(FrameOffset dest,
-                    FrameOffset src_base,
-                    Offset src_offset,
-                    ManagedRegister scratch,
-                    size_t size) = 0;
-
-  virtual void Copy(ManagedRegister dest,
-                    Offset dest_offset,
-                    ManagedRegister src,
-                    Offset src_offset,
-                    ManagedRegister scratch,
-                    size_t size) = 0;
-
-  virtual void Copy(FrameOffset dest,
-                    Offset dest_offset,
-                    FrameOffset src,
-                    Offset src_offset,
-                    ManagedRegister scratch,
-                    size_t size) = 0;
-
-  virtual void MemoryBarrier(ManagedRegister scratch) = 0;
+  virtual void Move(ManagedRegister dst, size_t value) = 0;
 
   // Sign extension
   virtual void SignExtend(ManagedRegister mreg, size_t size) = 0;
@@ -223,20 +158,10 @@
   virtual void GetCurrentThread(ManagedRegister dest) = 0;
   virtual void GetCurrentThread(FrameOffset dest_offset) = 0;
 
-  // Set up `out_reg` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
-  // or to be null if the value is null and `null_allowed`. `in_reg` holds a possibly
-  // stale reference that can be used to avoid loading the spilled value to
-  // see if the value is null.
-  virtual void CreateJObject(ManagedRegister out_reg,
-                             FrameOffset spilled_reference_offset,
-                             ManagedRegister in_reg,
-                             bool null_allowed) = 0;
-
-  // Set up `out_off` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
-  // or to be null if the value is null and `null_allowed`.
-  virtual void CreateJObject(FrameOffset out_off,
-                             FrameOffset spilled_reference_offset,
-                             bool null_allowed) = 0;
+  // Decode JNI transition or local `jobject`. For (weak) global `jobject`, jump to slow path.
+  virtual void DecodeJNITransitionOrLocalJObject(ManagedRegister reg,
+                                                 JNIMacroLabel* slow_path,
+                                                 JNIMacroLabel* resume) = 0;
 
   // Heap::VerifyObject on src. In some cases (such as a reference to this) we
   // know that src may not be null.
@@ -282,6 +207,8 @@
   virtual void TestMarkBit(ManagedRegister ref,
                            JNIMacroLabel* label,
                            JNIMacroUnaryCondition cond) = 0;
+  // Emit a conditional jump to label if the loaded value from specified locations is not zero.
+  virtual void TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) = 0;
   // Code at this offset will serve as the target for the Jump call.
   virtual void Bind(JNIMacroLabel* label) = 0;
 
diff --git a/compiler/utils/jni_macro_assembler_test.h b/compiler/utils/jni_macro_assembler_test.h
index e77177e..ac8e7d3 100644
--- a/compiler/utils/jni_macro_assembler_test.h
+++ b/compiler/utils/jni_macro_assembler_test.h
@@ -20,6 +20,7 @@
 #include "jni_macro_assembler.h"
 
 #include "assembler_test_base.h"
+#include "base/macros.h"
 #include "base/malloc_arena_pool.h"
 #include "common_runtime_test.h"  // For ScratchFile
 
@@ -30,7 +31,7 @@
 #include <fstream>
 #include <iterator>
 
-namespace art {
+namespace art HIDDEN {
 
 template<typename Ass>
 class JNIMacroAssemblerTest : public AssemblerTestBase {
@@ -39,7 +40,7 @@
     return assembler_.get();
   }
 
-  typedef std::string (*TestFn)(JNIMacroAssemblerTest* assembler_test, Ass* assembler);
+  using TestFn = std::string (*)(JNIMacroAssemblerTest *, Ass *);
 
   void DriverFn(TestFn f, const std::string& test_name) {
     DriverWrapper(f(this, assembler_.get()), test_name);
diff --git a/compiler/utils/label.h b/compiler/utils/label.h
index 282500b..0368d90 100644
--- a/compiler/utils/label.h
+++ b/compiler/utils/label.h
@@ -20,7 +20,9 @@
 #include <android-base/logging.h>
 #include <android-base/macros.h>
 
-namespace art {
+#include "base/macros.h"
+
+namespace art HIDDEN {
 
 class Assembler;
 class AssemblerBuffer;
diff --git a/compiler/utils/managed_register.h b/compiler/utils/managed_register.h
index a3b33ba..ba6b46b 100644
--- a/compiler/utils/managed_register.h
+++ b/compiler/utils/managed_register.h
@@ -20,9 +20,10 @@
 #include <type_traits>
 #include <vector>
 
+#include "base/macros.h"
 #include "base/value_object.h"
 
-namespace art {
+namespace art HIDDEN {
 
 namespace arm {
 class ArmManagedRegister;
@@ -31,6 +32,10 @@
 class Arm64ManagedRegister;
 }  // namespace arm64
 
+namespace riscv64 {
+class Riscv64ManagedRegister;
+}  // namespace riscv64
+
 namespace x86 {
 class X86ManagedRegister;
 }  // namespace x86
@@ -50,6 +55,7 @@
 
   constexpr arm::ArmManagedRegister AsArm() const;
   constexpr arm64::Arm64ManagedRegister AsArm64() const;
+  constexpr riscv64::Riscv64ManagedRegister AsRiscv64() const;
   constexpr x86::X86ManagedRegister AsX86() const;
   constexpr x86_64::X86_64ManagedRegister AsX86_64() const;
 
diff --git a/compiler/utils/riscv64/managed_register_riscv64.cc b/compiler/utils/riscv64/managed_register_riscv64.cc
new file mode 100644
index 0000000..560019a
--- /dev/null
+++ b/compiler/utils/riscv64/managed_register_riscv64.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "managed_register_riscv64.h"
+
+#include "base/globals.h"
+
+namespace art {
+namespace riscv64 {
+
+bool Riscv64ManagedRegister::Overlaps(const Riscv64ManagedRegister& other) const {
+  if (IsNoRegister() || other.IsNoRegister()) {
+    return false;
+  }
+  CHECK(IsValidManagedRegister());
+  CHECK(other.IsValidManagedRegister());
+
+  return Equals(other);
+}
+
+void Riscv64ManagedRegister::Print(std::ostream& os) const {
+  if (!IsValidManagedRegister()) {
+    os << "No Register";
+  } else if (IsXRegister()) {
+    os << "XRegister: " << static_cast<int>(AsXRegister());
+  } else if (IsFRegister()) {
+    os << "FRegister: " << static_cast<int>(AsFRegister());
+  } else {
+    os << "??: " << RegId();
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, const Riscv64ManagedRegister& reg) {
+  reg.Print(os);
+  return os;
+}
+
+}  // namespace riscv64
+}  // namespace art
diff --git a/compiler/utils/riscv64/managed_register_riscv64.h b/compiler/utils/riscv64/managed_register_riscv64.h
new file mode 100644
index 0000000..8e02a9d
--- /dev/null
+++ b/compiler/utils/riscv64/managed_register_riscv64.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 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_COMPILER_UTILS_RISCV64_MANAGED_REGISTER_RISCV64_H_
+#define ART_COMPILER_UTILS_RISCV64_MANAGED_REGISTER_RISCV64_H_
+
+#include <android-base/logging.h>
+
+#include "arch/riscv64/registers_riscv64.h"
+#include "base/globals.h"
+#include "base/macros.h"
+#include "utils/managed_register.h"
+
+namespace art {
+namespace riscv64 {
+
+const int kNumberOfXRegIds = kNumberOfXRegisters;
+const int kNumberOfXAllocIds = kNumberOfXRegisters;
+
+const int kNumberOfFRegIds = kNumberOfFRegisters;
+const int kNumberOfFAllocIds = kNumberOfFRegisters;
+
+const int kNumberOfRegIds = kNumberOfXRegIds + kNumberOfFRegIds;
+const int kNumberOfAllocIds = kNumberOfXAllocIds + kNumberOfFAllocIds;
+
+// Register ids map:
+//   [0..R[  core registers (enum XRegister)
+//   [R..F[  floating-point registers (enum FRegister)
+// where
+//   R = kNumberOfXRegIds
+//   F = R + kNumberOfFRegIds
+
+// An instance of class 'ManagedRegister' represents a single Riscv64 register.
+// A register can be one of the following:
+//  * core register (enum XRegister)
+//  * floating-point register (enum FRegister)
+//
+// 'ManagedRegister::NoRegister()' provides an invalid register.
+// There is a one-to-one mapping between ManagedRegister and register id.
+class Riscv64ManagedRegister : public ManagedRegister {
+ public:
+  constexpr XRegister AsXRegister() const {
+    CHECK(IsXRegister());
+    return static_cast<XRegister>(id_);
+  }
+
+  constexpr FRegister AsFRegister() const {
+    CHECK(IsFRegister());
+    return static_cast<FRegister>(id_ - kNumberOfXRegIds);
+  }
+
+  constexpr bool IsXRegister() const {
+    CHECK(IsValidManagedRegister());
+    return (0 <= id_) && (id_ < kNumberOfXRegIds);
+  }
+
+  constexpr bool IsFRegister() const {
+    CHECK(IsValidManagedRegister());
+    const int test = id_ - kNumberOfXRegIds;
+    return (0 <= test) && (test < kNumberOfFRegIds);
+  }
+
+  void Print(std::ostream& os) const;
+
+  // Returns true if the two managed-registers ('this' and 'other') overlap.
+  // Either managed-register may be the NoRegister. If both are the NoRegister
+  // then false is returned.
+  bool Overlaps(const Riscv64ManagedRegister& other) const;
+
+  static constexpr Riscv64ManagedRegister FromXRegister(XRegister r) {
+    CHECK_NE(r, kNoXRegister);
+    return FromRegId(r);
+  }
+
+  static constexpr Riscv64ManagedRegister FromFRegister(FRegister r) {
+    CHECK_NE(r, kNoFRegister);
+    return FromRegId(r + kNumberOfXRegIds);
+  }
+
+ private:
+  constexpr bool IsValidManagedRegister() const { return (0 <= id_) && (id_ < kNumberOfRegIds); }
+
+  constexpr int RegId() const {
+    CHECK(!IsNoRegister());
+    return id_;
+  }
+
+  int AllocId() const {
+    CHECK(IsValidManagedRegister());
+    CHECK_LT(id_, kNumberOfAllocIds);
+    return id_;
+  }
+
+  int AllocIdLow() const;
+  int AllocIdHigh() const;
+
+  friend class ManagedRegister;
+
+  explicit constexpr Riscv64ManagedRegister(int reg_id) : ManagedRegister(reg_id) {}
+
+  static constexpr Riscv64ManagedRegister FromRegId(int reg_id) {
+    Riscv64ManagedRegister reg(reg_id);
+    CHECK(reg.IsValidManagedRegister());
+    return reg;
+  }
+};
+
+std::ostream& operator<<(std::ostream& os, const Riscv64ManagedRegister& reg);
+
+}  // namespace riscv64
+
+constexpr inline riscv64::Riscv64ManagedRegister ManagedRegister::AsRiscv64() const {
+  riscv64::Riscv64ManagedRegister reg(id_);
+  CHECK(reg.IsNoRegister() || reg.IsValidManagedRegister());
+  return reg;
+}
+
+}  // namespace art
+
+#endif  // ART_COMPILER_UTILS_RISCV64_MANAGED_REGISTER_RISCV64_H_
diff --git a/compiler/utils/riscv64/managed_register_riscv64_test.cc b/compiler/utils/riscv64/managed_register_riscv64_test.cc
new file mode 100644
index 0000000..c6ad2dc
--- /dev/null
+++ b/compiler/utils/riscv64/managed_register_riscv64_test.cc
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "managed_register_riscv64.h"
+
+#include "base/globals.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace riscv64 {
+
+TEST(Riscv64ManagedRegister, NoRegister) {
+  Riscv64ManagedRegister reg = ManagedRegister::NoRegister().AsRiscv64();
+  EXPECT_TRUE(reg.IsNoRegister());
+}
+
+TEST(Riscv64ManagedRegister, XRegister) {
+  Riscv64ManagedRegister reg = Riscv64ManagedRegister::FromXRegister(Zero);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(Zero, reg.AsXRegister());
+
+  reg = Riscv64ManagedRegister::FromXRegister(RA);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(RA, reg.AsXRegister());
+
+  reg = Riscv64ManagedRegister::FromXRegister(SP);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(SP, reg.AsXRegister());
+
+  reg = Riscv64ManagedRegister::FromXRegister(GP);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(GP, reg.AsXRegister());
+
+  reg = Riscv64ManagedRegister::FromXRegister(T0);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(T0, reg.AsXRegister());
+
+  reg = Riscv64ManagedRegister::FromXRegister(T2);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(T2, reg.AsXRegister());
+
+  reg = Riscv64ManagedRegister::FromXRegister(S0);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(S0, reg.AsXRegister());
+
+  reg = Riscv64ManagedRegister::FromXRegister(A0);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(A0, reg.AsXRegister());
+
+  reg = Riscv64ManagedRegister::FromXRegister(A7);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(A7, reg.AsXRegister());
+
+  reg = Riscv64ManagedRegister::FromXRegister(S2);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(S2, reg.AsXRegister());
+
+  reg = Riscv64ManagedRegister::FromXRegister(T3);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_TRUE(reg.IsXRegister());
+  EXPECT_FALSE(reg.IsFRegister());
+  EXPECT_EQ(T3, reg.AsXRegister());
+}
+
+TEST(Riscv64ManagedRegister, FRegister) {
+  Riscv64ManagedRegister reg = Riscv64ManagedRegister::FromFRegister(FT0);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_FALSE(reg.IsXRegister());
+  EXPECT_TRUE(reg.IsFRegister());
+  EXPECT_EQ(FT0, reg.AsFRegister());
+  EXPECT_TRUE(reg.Equals(Riscv64ManagedRegister::FromFRegister(FT0)));
+
+  reg = Riscv64ManagedRegister::FromFRegister(FT1);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_FALSE(reg.IsXRegister());
+  EXPECT_TRUE(reg.IsFRegister());
+  EXPECT_EQ(FT1, reg.AsFRegister());
+  EXPECT_TRUE(reg.Equals(Riscv64ManagedRegister::FromFRegister(FT1)));
+
+  reg = Riscv64ManagedRegister::FromFRegister(FS0);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_FALSE(reg.IsXRegister());
+  EXPECT_TRUE(reg.IsFRegister());
+  EXPECT_EQ(FS0, reg.AsFRegister());
+  EXPECT_TRUE(reg.Equals(Riscv64ManagedRegister::FromFRegister(FS0)));
+
+  reg = Riscv64ManagedRegister::FromFRegister(FA0);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_FALSE(reg.IsXRegister());
+  EXPECT_TRUE(reg.IsFRegister());
+  EXPECT_EQ(FA0, reg.AsFRegister());
+  EXPECT_TRUE(reg.Equals(Riscv64ManagedRegister::FromFRegister(FA0)));
+
+  reg = Riscv64ManagedRegister::FromFRegister(FA7);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_FALSE(reg.IsXRegister());
+  EXPECT_TRUE(reg.IsFRegister());
+  EXPECT_EQ(FA7, reg.AsFRegister());
+  EXPECT_TRUE(reg.Equals(Riscv64ManagedRegister::FromFRegister(FA7)));
+
+  reg = Riscv64ManagedRegister::FromFRegister(FS4);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_FALSE(reg.IsXRegister());
+  EXPECT_TRUE(reg.IsFRegister());
+  EXPECT_EQ(FS4, reg.AsFRegister());
+  EXPECT_TRUE(reg.Equals(Riscv64ManagedRegister::FromFRegister(FS4)));
+
+  reg = Riscv64ManagedRegister::FromFRegister(FT11);
+  EXPECT_FALSE(reg.IsNoRegister());
+  EXPECT_FALSE(reg.IsXRegister());
+  EXPECT_TRUE(reg.IsFRegister());
+  EXPECT_EQ(FT11, reg.AsFRegister());
+  EXPECT_TRUE(reg.Equals(Riscv64ManagedRegister::FromFRegister(FT11)));
+}
+
+TEST(Riscv64ManagedRegister, Equals) {
+  ManagedRegister no_reg = ManagedRegister::NoRegister();
+  EXPECT_TRUE(no_reg.Equals(Riscv64ManagedRegister::NoRegister()));
+  EXPECT_FALSE(no_reg.Equals(Riscv64ManagedRegister::FromXRegister(Zero)));
+  EXPECT_FALSE(no_reg.Equals(Riscv64ManagedRegister::FromXRegister(A1)));
+  EXPECT_FALSE(no_reg.Equals(Riscv64ManagedRegister::FromXRegister(S2)));
+  EXPECT_FALSE(no_reg.Equals(Riscv64ManagedRegister::FromFRegister(FT0)));
+  EXPECT_FALSE(no_reg.Equals(Riscv64ManagedRegister::FromFRegister(FT11)));
+
+  Riscv64ManagedRegister reg_Zero = Riscv64ManagedRegister::FromXRegister(Zero);
+  EXPECT_FALSE(reg_Zero.Equals(Riscv64ManagedRegister::NoRegister()));
+  EXPECT_TRUE(reg_Zero.Equals(Riscv64ManagedRegister::FromXRegister(Zero)));
+  EXPECT_FALSE(reg_Zero.Equals(Riscv64ManagedRegister::FromXRegister(A1)));
+  EXPECT_FALSE(reg_Zero.Equals(Riscv64ManagedRegister::FromXRegister(S2)));
+  EXPECT_FALSE(reg_Zero.Equals(Riscv64ManagedRegister::FromFRegister(FT0)));
+  EXPECT_FALSE(reg_Zero.Equals(Riscv64ManagedRegister::FromFRegister(FT11)));
+
+  Riscv64ManagedRegister reg_A1 = Riscv64ManagedRegister::FromXRegister(A1);
+  EXPECT_FALSE(reg_A1.Equals(Riscv64ManagedRegister::NoRegister()));
+  EXPECT_FALSE(reg_A1.Equals(Riscv64ManagedRegister::FromXRegister(Zero)));
+  EXPECT_FALSE(reg_A1.Equals(Riscv64ManagedRegister::FromXRegister(A0)));
+  EXPECT_TRUE(reg_A1.Equals(Riscv64ManagedRegister::FromXRegister(A1)));
+  EXPECT_FALSE(reg_A1.Equals(Riscv64ManagedRegister::FromXRegister(S2)));
+  EXPECT_FALSE(reg_A1.Equals(Riscv64ManagedRegister::FromFRegister(FT0)));
+  EXPECT_FALSE(reg_A1.Equals(Riscv64ManagedRegister::FromFRegister(FT11)));
+
+  Riscv64ManagedRegister reg_S2 = Riscv64ManagedRegister::FromXRegister(S2);
+  EXPECT_FALSE(reg_S2.Equals(Riscv64ManagedRegister::NoRegister()));
+  EXPECT_FALSE(reg_S2.Equals(Riscv64ManagedRegister::FromXRegister(Zero)));
+  EXPECT_FALSE(reg_S2.Equals(Riscv64ManagedRegister::FromXRegister(A1)));
+  EXPECT_FALSE(reg_S2.Equals(Riscv64ManagedRegister::FromXRegister(S1)));
+  EXPECT_TRUE(reg_S2.Equals(Riscv64ManagedRegister::FromXRegister(S2)));
+  EXPECT_FALSE(reg_S2.Equals(Riscv64ManagedRegister::FromFRegister(FT0)));
+  EXPECT_FALSE(reg_S2.Equals(Riscv64ManagedRegister::FromFRegister(FT11)));
+
+  Riscv64ManagedRegister reg_F0 = Riscv64ManagedRegister::FromFRegister(FT0);
+  EXPECT_FALSE(reg_F0.Equals(Riscv64ManagedRegister::NoRegister()));
+  EXPECT_FALSE(reg_F0.Equals(Riscv64ManagedRegister::FromXRegister(Zero)));
+  EXPECT_FALSE(reg_F0.Equals(Riscv64ManagedRegister::FromXRegister(A1)));
+  EXPECT_FALSE(reg_F0.Equals(Riscv64ManagedRegister::FromXRegister(S2)));
+  EXPECT_TRUE(reg_F0.Equals(Riscv64ManagedRegister::FromFRegister(FT0)));
+  EXPECT_FALSE(reg_F0.Equals(Riscv64ManagedRegister::FromFRegister(FT1)));
+  EXPECT_FALSE(reg_F0.Equals(Riscv64ManagedRegister::FromFRegister(FT11)));
+
+  Riscv64ManagedRegister reg_F31 = Riscv64ManagedRegister::FromFRegister(FT11);
+  EXPECT_FALSE(reg_F31.Equals(Riscv64ManagedRegister::NoRegister()));
+  EXPECT_FALSE(reg_F31.Equals(Riscv64ManagedRegister::FromXRegister(Zero)));
+  EXPECT_FALSE(reg_F31.Equals(Riscv64ManagedRegister::FromXRegister(A1)));
+  EXPECT_FALSE(reg_F31.Equals(Riscv64ManagedRegister::FromXRegister(S2)));
+  EXPECT_FALSE(reg_F31.Equals(Riscv64ManagedRegister::FromFRegister(FT0)));
+  EXPECT_FALSE(reg_F31.Equals(Riscv64ManagedRegister::FromFRegister(FT1)));
+  EXPECT_TRUE(reg_F31.Equals(Riscv64ManagedRegister::FromFRegister(FT11)));
+}
+
+}  // namespace riscv64
+}  // namespace art
diff --git a/compiler/utils/stack_checks.h b/compiler/utils/stack_checks.h
index c348f2c..d0fff73 100644
--- a/compiler/utils/stack_checks.h
+++ b/compiler/utils/stack_checks.h
@@ -18,8 +18,9 @@
 #define ART_COMPILER_UTILS_STACK_CHECKS_H_
 
 #include "arch/instruction_set.h"
+#include "base/macros.h"
 
-namespace art {
+namespace art HIDDEN {
 
 // Size of a frame that we definitely consider large. Anything larger than this should
 // definitely get a stack overflow check.
diff --git a/compiler/utils/swap_space.h b/compiler/utils/swap_space.h
deleted file mode 100644
index 827e9a6..0000000
--- a/compiler/utils/swap_space.h
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2014 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_COMPILER_UTILS_SWAP_SPACE_H_
-#define ART_COMPILER_UTILS_SWAP_SPACE_H_
-
-#include <stddef.h>
-#include <stdint.h>
-#include <cstdlib>
-#include <list>
-#include <set>
-#include <vector>
-
-#include <android-base/logging.h>
-
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/mutex.h"
-
-namespace art {
-
-// An arena pool that creates arenas backed by an mmaped file.
-class SwapSpace {
- public:
-  SwapSpace(int fd, size_t initial_size);
-  ~SwapSpace();
-  void* Alloc(size_t size) REQUIRES(!lock_);
-  void Free(void* ptr, size_t size) REQUIRES(!lock_);
-
-  size_t GetSize() {
-    return size_;
-  }
-
- private:
-  // Chunk of space.
-  struct SpaceChunk {
-    // We need mutable members as we keep these objects in a std::set<> (providing only const
-    // access) but we modify these members while carefully preserving the std::set<> ordering.
-    mutable uint8_t* ptr;
-    mutable size_t size;
-
-    uintptr_t Start() const {
-      return reinterpret_cast<uintptr_t>(ptr);
-    }
-    uintptr_t End() const {
-      return reinterpret_cast<uintptr_t>(ptr) + size;
-    }
-  };
-
-  class SortChunkByPtr {
-   public:
-    bool operator()(const SpaceChunk& a, const SpaceChunk& b) const {
-      return reinterpret_cast<uintptr_t>(a.ptr) < reinterpret_cast<uintptr_t>(b.ptr);
-    }
-  };
-
-  using FreeByStartSet = std::set<SpaceChunk, SortChunkByPtr>;
-
-  // Map size to an iterator to free_by_start_'s entry.
-  struct FreeBySizeEntry {
-    FreeBySizeEntry(size_t sz, FreeByStartSet::const_iterator entry)
-        : size(sz), free_by_start_entry(entry) { }
-
-    // We need mutable members as we keep these objects in a std::set<> (providing only const
-    // access) but we modify these members while carefully preserving the std::set<> ordering.
-    mutable size_t size;
-    mutable FreeByStartSet::const_iterator free_by_start_entry;
-  };
-  struct FreeBySizeComparator {
-    bool operator()(const FreeBySizeEntry& lhs, const FreeBySizeEntry& rhs) const {
-      if (lhs.size != rhs.size) {
-        return lhs.size < rhs.size;
-      } else {
-        return lhs.free_by_start_entry->Start() < rhs.free_by_start_entry->Start();
-      }
-    }
-  };
-  using FreeBySizeSet = std::set<FreeBySizeEntry, FreeBySizeComparator>;
-
-  SpaceChunk NewFileChunk(size_t min_size) REQUIRES(lock_);
-
-  void RemoveChunk(FreeBySizeSet::const_iterator free_by_size_pos) REQUIRES(lock_);
-  void InsertChunk(const SpaceChunk& chunk) REQUIRES(lock_);
-
-  int fd_;
-  size_t size_;
-
-  // NOTE: Boost.Bimap would be useful for the two following members.
-
-  // Map start of a free chunk to its size.
-  FreeByStartSet free_by_start_ GUARDED_BY(lock_);
-  // Free chunks ordered by size.
-  FreeBySizeSet free_by_size_ GUARDED_BY(lock_);
-
-  mutable Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
-  DISALLOW_COPY_AND_ASSIGN(SwapSpace);
-};
-
-template <typename T> class SwapAllocator;
-
-template <>
-class SwapAllocator<void> {
- public:
-  using value_type    = void;
-  using pointer       = void*;
-  using const_pointer = const void*;
-
-  template <typename U>
-  struct rebind {
-    using other = SwapAllocator<U>;
-  };
-
-  explicit SwapAllocator(SwapSpace* swap_space) : swap_space_(swap_space) {}
-
-  template <typename U>
-  SwapAllocator(const SwapAllocator<U>& other)
-      : swap_space_(other.swap_space_) {}
-
-  SwapAllocator(const SwapAllocator& other) = default;
-  SwapAllocator& operator=(const SwapAllocator& other) = default;
-  ~SwapAllocator() = default;
-
- private:
-  SwapSpace* swap_space_;
-
-  template <typename U>
-  friend class SwapAllocator;
-
-  template <typename U>
-  friend bool operator==(const SwapAllocator<U>& lhs, const SwapAllocator<U>& rhs);
-};
-
-template <typename T>
-class SwapAllocator {
- public:
-  using value_type      = T;
-  using pointer         = T*;
-  using reference       = T&;
-  using const_pointer   = const T*;
-  using const_reference = const T&;
-  using size_type       = size_t;
-  using difference_type = ptrdiff_t;
-
-  template <typename U>
-  struct rebind {
-    using other = SwapAllocator<U>;
-  };
-
-  explicit SwapAllocator(SwapSpace* swap_space) : swap_space_(swap_space) {}
-
-  template <typename U>
-  SwapAllocator(const SwapAllocator<U>& other)
-      : swap_space_(other.swap_space_) {}
-
-  SwapAllocator(const SwapAllocator& other) = default;
-  SwapAllocator& operator=(const SwapAllocator& other) = default;
-  ~SwapAllocator() = default;
-
-  size_type max_size() const {
-    return static_cast<size_type>(-1) / sizeof(T);
-  }
-
-  pointer address(reference x) const { return &x; }
-  const_pointer address(const_reference x) const { return &x; }
-
-  pointer allocate(size_type n, SwapAllocator<void>::pointer hint ATTRIBUTE_UNUSED = nullptr) {
-    DCHECK_LE(n, max_size());
-    if (swap_space_ == nullptr) {
-      T* result = reinterpret_cast<T*>(malloc(n * sizeof(T)));
-      CHECK_IMPLIES(result == nullptr, n == 0u);  // Abort if malloc() fails.
-      return result;
-    } else {
-      return reinterpret_cast<T*>(swap_space_->Alloc(n * sizeof(T)));
-    }
-  }
-  void deallocate(pointer p, size_type n) {
-    if (swap_space_ == nullptr) {
-      free(p);
-    } else {
-      swap_space_->Free(p, n * sizeof(T));
-    }
-  }
-
-  void construct(pointer p, const_reference val) {
-    new (static_cast<void*>(p)) value_type(val);
-  }
-  template <class U, class... Args>
-  void construct(U* p, Args&&... args) {
-    ::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
-  }
-  void destroy(pointer p) {
-    p->~value_type();
-  }
-
-  inline bool operator==(SwapAllocator const& other) {
-    return swap_space_ == other.swap_space_;
-  }
-  inline bool operator!=(SwapAllocator const& other) {
-    return !operator==(other);
-  }
-
- private:
-  SwapSpace* swap_space_;
-
-  template <typename U>
-  friend class SwapAllocator;
-
-  template <typename U>
-  friend bool operator==(const SwapAllocator<U>& lhs, const SwapAllocator<U>& rhs);
-};
-
-template <typename T>
-inline bool operator==(const SwapAllocator<T>& lhs, const SwapAllocator<T>& rhs) {
-  return lhs.swap_space_ == rhs.swap_space_;
-}
-
-template <typename T>
-inline bool operator!=(const SwapAllocator<T>& lhs, const SwapAllocator<T>& rhs) {
-  return !(lhs == rhs);
-}
-
-template <typename T>
-using SwapVector = std::vector<T, SwapAllocator<T>>;
-template <typename T, typename Comparator>
-using SwapSet = std::set<T, Comparator, SwapAllocator<T>>;
-
-}  // namespace art
-
-#endif  // ART_COMPILER_UTILS_SWAP_SPACE_H_
diff --git a/compiler/utils/swap_space_test.cc b/compiler/utils/swap_space_test.cc
deleted file mode 100644
index 1650080..0000000
--- a/compiler/utils/swap_space_test.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2014 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.
- */
-
-#include "utils/swap_space.h"
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <cstdio>
-
-#include "gtest/gtest.h"
-
-#include "base/os.h"
-#include "base/unix_file/fd_file.h"
-#include "common_runtime_test.h"
-
-namespace art {
-
-class SwapSpaceTest : public CommonRuntimeTest {
-};
-
-static void SwapTest(bool use_file) {
-  ScratchFile scratch;
-  int fd = scratch.GetFd();
-  unlink(scratch.GetFilename().c_str());
-
-  SwapSpace pool(fd, 1 * MB);
-  SwapAllocator<void> alloc(use_file ? &pool : nullptr);
-
-  SwapVector<int32_t> v(alloc);
-  v.reserve(1000000);
-  for (int32_t i = 0; i < 1000000; ++i) {
-    v.push_back(i);
-    EXPECT_EQ(i, v[i]);
-  }
-
-  SwapVector<int32_t> v2(alloc);
-  v2.reserve(1000000);
-  for (int32_t i = 0; i < 1000000; ++i) {
-    v2.push_back(i);
-    EXPECT_EQ(i, v2[i]);
-  }
-
-  SwapVector<int32_t> v3(alloc);
-  v3.reserve(500000);
-  for (int32_t i = 0; i < 1000000; ++i) {
-    v3.push_back(i);
-    EXPECT_EQ(i, v2[i]);
-  }
-
-  // Verify contents.
-  for (int32_t i = 0; i < 1000000; ++i) {
-    EXPECT_EQ(i, v[i]);
-    EXPECT_EQ(i, v2[i]);
-    EXPECT_EQ(i, v3[i]);
-  }
-
-  scratch.Close();
-}
-
-TEST_F(SwapSpaceTest, Memory) {
-  SwapTest(false);
-}
-
-TEST_F(SwapSpaceTest, Swap) {
-  SwapTest(true);
-}
-
-}  // namespace art
diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc
index 861b27e..a6b9011 100644
--- a/compiler/utils/x86/assembler_x86.cc
+++ b/compiler/utils/x86/assembler_x86.cc
@@ -21,7 +21,7 @@
 #include "entrypoints/quick/quick_entrypoints.h"
 #include "thread.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 std::ostream& operator<<(std::ostream& os, const XmmRegister& reg) {
diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h
index c346ba9..0f7854d 100644
--- a/compiler/utils/x86/assembler_x86.h
+++ b/compiler/utils/x86/assembler_x86.h
@@ -32,7 +32,7 @@
 #include "offsets.h"
 #include "utils/assembler.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 class Immediate : public ValueObject {
diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc
index 89c73c0..5da6f04 100644
--- a/compiler/utils/x86/assembler_x86_test.cc
+++ b/compiler/utils/x86/assembler_x86_test.cc
@@ -17,11 +17,12 @@
 #include "assembler_x86.h"
 
 #include "base/arena_allocator.h"
+#include "base/macros.h"
 #include "base/malloc_arena_pool.h"
 #include "base/stl_util.h"
 #include "utils/assembler_test.h"
 
-namespace art {
+namespace art HIDDEN {
 
 TEST(AssemblerX86, CreateBuffer) {
   MallocArenaPool pool;
diff --git a/compiler/utils/x86/constants_x86.h b/compiler/utils/x86/constants_x86.h
index 477b915..0c0a7d4 100644
--- a/compiler/utils/x86/constants_x86.h
+++ b/compiler/utils/x86/constants_x86.h
@@ -25,7 +25,7 @@
 #include "base/globals.h"
 #include "base/macros.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 enum ByteRegister {
diff --git a/compiler/utils/x86/jni_macro_assembler_x86.cc b/compiler/utils/x86/jni_macro_assembler_x86.cc
index 685f5f1..154e50b 100644
--- a/compiler/utils/x86/jni_macro_assembler_x86.cc
+++ b/compiler/utils/x86/jni_macro_assembler_x86.cc
@@ -18,11 +18,12 @@
 
 #include "base/casts.h"
 #include "entrypoints/quick/quick_entrypoints.h"
+#include "indirect_reference_table.h"
 #include "lock_word.h"
 #include "thread.h"
 #include "utils/assembler.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 static Register GetScratchRegister() {
@@ -165,36 +166,24 @@
   }
 }
 
-void X86JNIMacroAssembler::StoreRef(FrameOffset dest, ManagedRegister msrc) {
-  X86ManagedRegister src = msrc.AsX86();
-  CHECK(src.IsCpuRegister());
-  __ movl(Address(ESP, dest), src.AsCpuRegister());
-}
-
 void X86JNIMacroAssembler::StoreRawPtr(FrameOffset dest, ManagedRegister msrc) {
   X86ManagedRegister src = msrc.AsX86();
   CHECK(src.IsCpuRegister());
   __ movl(Address(ESP, dest), src.AsCpuRegister());
 }
 
-void X86JNIMacroAssembler::StoreImmediateToFrame(FrameOffset dest, uint32_t imm) {
-  __ movl(Address(ESP, dest), Immediate(imm));
-}
-
-void X86JNIMacroAssembler::StoreStackOffsetToThread(ThreadOffset32 thr_offs, FrameOffset fr_offs) {
-  Register scratch = GetScratchRegister();
-  __ leal(scratch, Address(ESP, fr_offs));
-  __ fs()->movl(Address::Absolute(thr_offs), scratch);
-}
-
-void X86JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs) {
-  __ fs()->movl(Address::Absolute(thr_offs), ESP);
-}
-
-void X86JNIMacroAssembler::StoreSpanning(FrameOffset /*dst*/,
-                                         ManagedRegister /*src*/,
-                                         FrameOffset /*in_off*/) {
-  UNIMPLEMENTED(FATAL);  // this case only currently exists for ARM
+void X86JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) {
+  if (tag_sp) {
+    // There is no free register, store contents onto stack and restore back later.
+    Register scratch = ECX;
+    __ movl(Address(ESP, -32), scratch);
+    __ movl(scratch, ESP);
+    __ orl(scratch, Immediate(0x2));
+    __ fs()->movl(Address::Absolute(thr_offs), scratch);
+    __ movl(scratch, Address(ESP, -32));
+  } else {
+    __ fs()->movl(Address::Absolute(thr_offs), ESP);
+  }
 }
 
 void X86JNIMacroAssembler::Load(ManagedRegister mdest, FrameOffset src, size_t size) {
@@ -233,61 +222,6 @@
   }
 }
 
-void X86JNIMacroAssembler::LoadFromThread(ManagedRegister mdest, ThreadOffset32 src, size_t size) {
-  X86ManagedRegister dest = mdest.AsX86();
-  if (dest.IsNoRegister()) {
-    CHECK_EQ(0u, size);
-  } else if (dest.IsCpuRegister()) {
-    if (size == 1u) {
-      __ fs()->movzxb(dest.AsCpuRegister(), Address::Absolute(src));
-    } else {
-      CHECK_EQ(4u, size);
-      __ fs()->movl(dest.AsCpuRegister(), Address::Absolute(src));
-    }
-  } else if (dest.IsRegisterPair()) {
-    CHECK_EQ(8u, size);
-    __ fs()->movl(dest.AsRegisterPairLow(), Address::Absolute(src));
-    __ fs()->movl(dest.AsRegisterPairHigh(), Address::Absolute(ThreadOffset32(src.Int32Value()+4)));
-  } else if (dest.IsX87Register()) {
-    if (size == 4) {
-      __ fs()->flds(Address::Absolute(src));
-    } else {
-      __ fs()->fldl(Address::Absolute(src));
-    }
-  } else {
-    CHECK(dest.IsXmmRegister());
-    if (size == 4) {
-      __ fs()->movss(dest.AsXmmRegister(), Address::Absolute(src));
-    } else {
-      __ fs()->movsd(dest.AsXmmRegister(), Address::Absolute(src));
-    }
-  }
-}
-
-void X86JNIMacroAssembler::LoadRef(ManagedRegister mdest, FrameOffset src) {
-  X86ManagedRegister dest = mdest.AsX86();
-  CHECK(dest.IsCpuRegister());
-  __ movl(dest.AsCpuRegister(), Address(ESP, src));
-}
-
-void X86JNIMacroAssembler::LoadRef(ManagedRegister mdest, ManagedRegister base, MemberOffset offs,
-                           bool unpoison_reference) {
-  X86ManagedRegister dest = mdest.AsX86();
-  CHECK(dest.IsCpuRegister() && dest.IsCpuRegister());
-  __ movl(dest.AsCpuRegister(), Address(base.AsX86().AsCpuRegister(), offs));
-  if (unpoison_reference) {
-    __ MaybeUnpoisonHeapReference(dest.AsCpuRegister());
-  }
-}
-
-void X86JNIMacroAssembler::LoadRawPtr(ManagedRegister mdest,
-                                      ManagedRegister base,
-                                      Offset offs) {
-  X86ManagedRegister dest = mdest.AsX86();
-  CHECK(dest.IsCpuRegister() && dest.IsCpuRegister());
-  __ movl(dest.AsCpuRegister(), Address(base.AsX86().AsCpuRegister(), offs));
-}
-
 void X86JNIMacroAssembler::LoadRawPtrFromThread(ManagedRegister mdest, ThreadOffset32 offs) {
   X86ManagedRegister dest = mdest.AsX86();
   CHECK(dest.IsCpuRegister());
@@ -402,37 +336,9 @@
   }
 }
 
-void X86JNIMacroAssembler::CopyRef(FrameOffset dest, FrameOffset src) {
-  Register scratch = GetScratchRegister();
-  __ movl(scratch, Address(ESP, src));
-  __ movl(Address(ESP, dest), scratch);
-}
-
-void X86JNIMacroAssembler::CopyRef(FrameOffset dest,
-                                   ManagedRegister base,
-                                   MemberOffset offs,
-                                   bool unpoison_reference) {
-  Register scratch = GetScratchRegister();
-  __ movl(scratch, Address(base.AsX86().AsCpuRegister(), offs));
-  if (unpoison_reference) {
-    __ MaybeUnpoisonHeapReference(scratch);
-  }
-  __ movl(Address(ESP, dest), scratch);
-}
-
-void X86JNIMacroAssembler::CopyRawPtrFromThread(FrameOffset fr_offs, ThreadOffset32 thr_offs) {
-  Register scratch = GetScratchRegister();
-  __ fs()->movl(scratch, Address::Absolute(thr_offs));
-  __ movl(Address(ESP, fr_offs), scratch);
-}
-
-void X86JNIMacroAssembler::CopyRawPtrToThread(ThreadOffset32 thr_offs,
-                                              FrameOffset fr_offs,
-                                              ManagedRegister mscratch) {
-  X86ManagedRegister scratch = mscratch.AsX86();
-  CHECK(scratch.IsCpuRegister());
-  Load(scratch, fr_offs, 4);
-  __ fs()->movl(Address::Absolute(thr_offs), scratch.AsCpuRegister());
+void X86JNIMacroAssembler::Move(ManagedRegister mdest, size_t value) {
+  X86ManagedRegister dest = mdest.AsX86();
+  __ movl(dest.AsCpuRegister(), Immediate(value));
 }
 
 void X86JNIMacroAssembler::Copy(FrameOffset dest, FrameOffset src, size_t size) {
@@ -446,67 +352,6 @@
   }
 }
 
-void X86JNIMacroAssembler::Copy(FrameOffset /*dst*/,
-                                ManagedRegister /*src_base*/,
-                                Offset /*src_offset*/,
-                                ManagedRegister /*scratch*/,
-                                size_t /*size*/) {
-  UNIMPLEMENTED(FATAL);
-}
-
-void X86JNIMacroAssembler::Copy(ManagedRegister dest_base,
-                                Offset dest_offset,
-                                FrameOffset src,
-                                ManagedRegister scratch,
-                                size_t size) {
-  CHECK(scratch.IsNoRegister());
-  CHECK_EQ(size, 4u);
-  __ pushl(Address(ESP, src));
-  __ popl(Address(dest_base.AsX86().AsCpuRegister(), dest_offset));
-}
-
-void X86JNIMacroAssembler::Copy(FrameOffset dest,
-                                FrameOffset src_base,
-                                Offset src_offset,
-                                ManagedRegister mscratch,
-                                size_t size) {
-  Register scratch = mscratch.AsX86().AsCpuRegister();
-  CHECK_EQ(size, 4u);
-  __ movl(scratch, Address(ESP, src_base));
-  __ movl(scratch, Address(scratch, src_offset));
-  __ movl(Address(ESP, dest), scratch);
-}
-
-void X86JNIMacroAssembler::Copy(ManagedRegister dest,
-                                Offset dest_offset,
-                                ManagedRegister src,
-                                Offset src_offset,
-                                ManagedRegister scratch,
-                                size_t size) {
-  CHECK_EQ(size, 4u);
-  CHECK(scratch.IsNoRegister());
-  __ pushl(Address(src.AsX86().AsCpuRegister(), src_offset));
-  __ popl(Address(dest.AsX86().AsCpuRegister(), dest_offset));
-}
-
-void X86JNIMacroAssembler::Copy(FrameOffset dest,
-                                Offset dest_offset,
-                                FrameOffset src,
-                                Offset src_offset,
-                                ManagedRegister mscratch,
-                                size_t size) {
-  Register scratch = mscratch.AsX86().AsCpuRegister();
-  CHECK_EQ(size, 4u);
-  CHECK_EQ(dest.Int32Value(), src.Int32Value());
-  __ movl(scratch, Address(ESP, src));
-  __ pushl(Address(scratch, src_offset));
-  __ popl(Address(scratch, dest_offset));
-}
-
-void X86JNIMacroAssembler::MemoryBarrier(ManagedRegister) {
-  __ mfence();
-}
-
 void X86JNIMacroAssembler::CreateJObject(ManagedRegister mout_reg,
                                          FrameOffset spilled_reference_offset,
                                          ManagedRegister min_reg,
@@ -547,6 +392,20 @@
   __ movl(Address(ESP, out_off), scratch);
 }
 
+void X86JNIMacroAssembler::DecodeJNITransitionOrLocalJObject(ManagedRegister reg,
+                                                             JNIMacroLabel* slow_path,
+                                                             JNIMacroLabel* resume) {
+  constexpr uint32_t kGlobalOrWeakGlobalMask =
+      dchecked_integral_cast<uint32_t>(IndirectReferenceTable::GetGlobalOrWeakGlobalMask());
+  constexpr uint32_t kIndirectRefKindMask =
+      dchecked_integral_cast<uint32_t>(IndirectReferenceTable::GetIndirectRefKindMask());
+  __ testl(reg.AsX86().AsCpuRegister(), Immediate(kGlobalOrWeakGlobalMask));
+  __ j(kNotZero, X86JNIMacroLabel::Cast(slow_path)->AsX86());
+  __ andl(reg.AsX86().AsCpuRegister(), Immediate(~kIndirectRefKindMask));
+  __ j(kZero, X86JNIMacroLabel::Cast(resume)->AsX86());  // Skip load for null.
+  __ movl(reg.AsX86().AsCpuRegister(), Address(reg.AsX86().AsCpuRegister(), /*disp=*/ 0));
+}
+
 void X86JNIMacroAssembler::VerifyObject(ManagedRegister /*src*/, bool /*could_be_null*/) {
   // TODO: not validating references
 }
@@ -724,6 +583,12 @@
   __ j(UnaryConditionToX86Condition(cond), X86JNIMacroLabel::Cast(label)->AsX86());
 }
 
+
+void X86JNIMacroAssembler::TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) {
+  __ cmpb(Address::Absolute(address), Immediate(0));
+  __ j(kNotZero, X86JNIMacroLabel::Cast(label)->AsX86());
+}
+
 void X86JNIMacroAssembler::Bind(JNIMacroLabel* label) {
   CHECK(label != nullptr);
   __ Bind(X86JNIMacroLabel::Cast(label)->AsX86());
diff --git a/compiler/utils/x86/jni_macro_assembler_x86.h b/compiler/utils/x86/jni_macro_assembler_x86.h
index 29fccfd..6b177f5 100644
--- a/compiler/utils/x86/jni_macro_assembler_x86.h
+++ b/compiler/utils/x86/jni_macro_assembler_x86.h
@@ -27,7 +27,7 @@
 #include "offsets.h"
 #include "utils/jni_macro_assembler.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 class X86JNIMacroLabel;
@@ -59,30 +59,14 @@
   // Store routines
   void Store(FrameOffset offs, ManagedRegister src, size_t size) override;
   void Store(ManagedRegister base, MemberOffset offs, ManagedRegister src, size_t size) override;
-  void StoreRef(FrameOffset dest, ManagedRegister src) override;
   void StoreRawPtr(FrameOffset dest, ManagedRegister src) override;
 
-  void StoreImmediateToFrame(FrameOffset dest, uint32_t imm) override;
-
-  void StoreStackOffsetToThread(ThreadOffset32 thr_offs, FrameOffset fr_offs) override;
-
-  void StoreStackPointerToThread(ThreadOffset32 thr_offs) override;
-
-  void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
+  void StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) override;
 
   // Load routines
   void Load(ManagedRegister dest, FrameOffset src, size_t size) override;
   void Load(ManagedRegister dest, ManagedRegister base, MemberOffset offs, size_t size) override;
 
-  void LoadFromThread(ManagedRegister dest, ThreadOffset32 src, size_t size) override;
-
-  void LoadRef(ManagedRegister dest, FrameOffset src) override;
-
-  void LoadRef(ManagedRegister dest, ManagedRegister base, MemberOffset offs,
-               bool unpoison_reference) override;
-
-  void LoadRawPtr(ManagedRegister dest, ManagedRegister base, Offset offs) override;
-
   void LoadRawPtrFromThread(ManagedRegister dest, ThreadOffset32 offs) override;
 
   // Copying routines
@@ -92,35 +76,7 @@
 
   void Move(ManagedRegister dest, ManagedRegister src, size_t size) override;
 
-  void CopyRawPtrFromThread(FrameOffset fr_offs, ThreadOffset32 thr_offs) override;
-
-  void CopyRawPtrToThread(ThreadOffset32 thr_offs, FrameOffset fr_offs, ManagedRegister scratch)
-      override;
-
-  void CopyRef(FrameOffset dest, FrameOffset src) override;
-  void CopyRef(FrameOffset dest,
-               ManagedRegister base,
-               MemberOffset offs,
-               bool unpoison_reference) override;
-
-  void Copy(FrameOffset dest, FrameOffset src, size_t size) override;
-
-  void Copy(FrameOffset dest, ManagedRegister src_base, Offset src_offset, ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(ManagedRegister dest_base, Offset dest_offset, FrameOffset src, ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(FrameOffset dest, FrameOffset src_base, Offset src_offset, ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(ManagedRegister dest, Offset dest_offset, ManagedRegister src, Offset src_offset,
-            ManagedRegister scratch, size_t size) override;
-
-  void Copy(FrameOffset dest, Offset dest_offset, FrameOffset src, Offset src_offset,
-            ManagedRegister scratch, size_t size) override;
-
-  void MemoryBarrier(ManagedRegister) override;
+  void Move(ManagedRegister dest, size_t value) override;
 
   // Sign extension
   void SignExtend(ManagedRegister mreg, size_t size) override;
@@ -132,20 +88,10 @@
   void GetCurrentThread(ManagedRegister dest) override;
   void GetCurrentThread(FrameOffset dest_offset) override;
 
-  // Set up `out_reg` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
-  // or to be null if the value is null and `null_allowed`. `in_reg` holds a possibly
-  // stale reference that can be used to avoid loading the spilled value to
-  // see if the value is null.
-  void CreateJObject(ManagedRegister out_reg,
-                     FrameOffset spilled_reference_offset,
-                     ManagedRegister in_reg,
-                     bool null_allowed) override;
-
-  // Set up `out_off` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
-  // or to be null if the value is null and `null_allowed`.
-  void CreateJObject(FrameOffset out_off,
-                     FrameOffset spilled_reference_offset,
-                     bool null_allowed) override;
+  // Decode JNI transition or local `jobject`. For (weak) global `jobject`, jump to slow path.
+  void DecodeJNITransitionOrLocalJObject(ManagedRegister reg,
+                                         JNIMacroLabel* slow_path,
+                                         JNIMacroLabel* resume) override;
 
   // Heap::VerifyObject on src. In some cases (such as a reference to this) we
   // know that src may not be null.
@@ -189,10 +135,29 @@
   void TestGcMarking(JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
   // Emit a conditional jump to the label by applying a unary condition test to object's mark bit.
   void TestMarkBit(ManagedRegister ref, JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
+  // Emit a conditional jump to label if the loaded value from specified locations is not zero.
+  void TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) override;
   // Code at this offset will serve as the target for the Jump call.
   void Bind(JNIMacroLabel* label) override;
 
  private:
+  void Copy(FrameOffset dest, FrameOffset src, size_t size);
+
+  // Set up `out_reg` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
+  // or to be null if the value is null and `null_allowed`. `in_reg` holds a possibly
+  // stale reference that can be used to avoid loading the spilled value to
+  // see if the value is null.
+  void CreateJObject(ManagedRegister out_reg,
+                     FrameOffset spilled_reference_offset,
+                     ManagedRegister in_reg,
+                     bool null_allowed);
+
+  // Set up `out_off` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
+  // or to be null if the value is null and `null_allowed`.
+  void CreateJObject(FrameOffset out_off,
+                     FrameOffset spilled_reference_offset,
+                     bool null_allowed);
+
   DISALLOW_COPY_AND_ASSIGN(X86JNIMacroAssembler);
 };
 
diff --git a/compiler/utils/x86/managed_register_x86.cc b/compiler/utils/x86/managed_register_x86.cc
index cc7cedf..bef9480 100644
--- a/compiler/utils/x86/managed_register_x86.cc
+++ b/compiler/utils/x86/managed_register_x86.cc
@@ -18,7 +18,7 @@
 
 #include "base/globals.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 // Define register pairs.
diff --git a/compiler/utils/x86/managed_register_x86.h b/compiler/utils/x86/managed_register_x86.h
index 27555bf..def4f68 100644
--- a/compiler/utils/x86/managed_register_x86.h
+++ b/compiler/utils/x86/managed_register_x86.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_UTILS_X86_MANAGED_REGISTER_X86_H_
 #define ART_COMPILER_UTILS_X86_MANAGED_REGISTER_X86_H_
 
+#include "base/macros.h"
 #include "constants_x86.h"
 #include "utils/managed_register.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 // Values for register pairs.
diff --git a/compiler/utils/x86/managed_register_x86_test.cc b/compiler/utils/x86/managed_register_x86_test.cc
index 28af531..9f5e197 100644
--- a/compiler/utils/x86/managed_register_x86_test.cc
+++ b/compiler/utils/x86/managed_register_x86_test.cc
@@ -17,9 +17,10 @@
 #include "managed_register_x86.h"
 
 #include "base/globals.h"
+#include "base/macros.h"
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86 {
 
 TEST(X86ManagedRegister, NoRegister) {
diff --git a/compiler/utils/x86_64/assembler_x86_64.cc b/compiler/utils/x86_64/assembler_x86_64.cc
index 21a4481..3fdf05b 100644
--- a/compiler/utils/x86_64/assembler_x86_64.cc
+++ b/compiler/utils/x86_64/assembler_x86_64.cc
@@ -21,7 +21,7 @@
 #include "entrypoints/quick/quick_entrypoints.h"
 #include "thread.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 std::ostream& operator<<(std::ostream& os, const CpuRegister& reg) {
diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h
index ea944c2..235ea03 100644
--- a/compiler/utils/x86_64/assembler_x86_64.h
+++ b/compiler/utils/x86_64/assembler_x86_64.h
@@ -30,9 +30,8 @@
 #include "managed_register_x86_64.h"
 #include "offsets.h"
 #include "utils/assembler.h"
-#include "utils/jni_macro_assembler.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 // Encodes an immediate value for operands.
diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc
index f7e890d..a7c206a 100644
--- a/compiler/utils/x86_64/assembler_x86_64_test.cc
+++ b/compiler/utils/x86_64/assembler_x86_64_test.cc
@@ -21,13 +21,14 @@
 #include <random>
 
 #include "base/bit_utils.h"
+#include "base/macros.h"
 #include "base/malloc_arena_pool.h"
 #include "base/stl_util.h"
 #include "jni_macro_assembler_x86_64.h"
 #include "utils/assembler_test.h"
 #include "utils/jni_macro_assembler_test.h"
 
-namespace art {
+namespace art HIDDEN {
 
 TEST(AssemblerX86_64, CreateBuffer) {
   MallocArenaPool pool;
diff --git a/compiler/utils/x86_64/constants_x86_64.h b/compiler/utils/x86_64/constants_x86_64.h
index 301c8fc..52ac987 100644
--- a/compiler/utils/x86_64/constants_x86_64.h
+++ b/compiler/utils/x86_64/constants_x86_64.h
@@ -25,7 +25,7 @@
 #include "base/globals.h"
 #include "base/macros.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 class CpuRegister {
diff --git a/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc b/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
index d5d1bba..3888457 100644
--- a/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
+++ b/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
@@ -19,10 +19,11 @@
 #include "base/casts.h"
 #include "base/memory_region.h"
 #include "entrypoints/quick/quick_entrypoints.h"
+#include "indirect_reference_table.h"
 #include "lock_word.h"
 #include "thread.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 static dwarf::Reg DWARFReg(Register reg) {
@@ -194,37 +195,21 @@
   }
 }
 
-void X86_64JNIMacroAssembler::StoreRef(FrameOffset dest, ManagedRegister msrc) {
-  X86_64ManagedRegister src = msrc.AsX86_64();
-  CHECK(src.IsCpuRegister());
-  __ movl(Address(CpuRegister(RSP), dest), src.AsCpuRegister());
-}
-
 void X86_64JNIMacroAssembler::StoreRawPtr(FrameOffset dest, ManagedRegister msrc) {
   X86_64ManagedRegister src = msrc.AsX86_64();
   CHECK(src.IsCpuRegister());
   __ movq(Address(CpuRegister(RSP), dest), src.AsCpuRegister());
 }
 
-void X86_64JNIMacroAssembler::StoreImmediateToFrame(FrameOffset dest, uint32_t imm) {
-  __ movl(Address(CpuRegister(RSP), dest), Immediate(imm));  // TODO(64) movq?
-}
-
-void X86_64JNIMacroAssembler::StoreStackOffsetToThread(ThreadOffset64 thr_offs,
-                                                       FrameOffset fr_offs) {
-  CpuRegister scratch = GetScratchRegister();
-  __ leaq(scratch, Address(CpuRegister(RSP), fr_offs));
-  __ gs()->movq(Address::Absolute(thr_offs, true), scratch);
-}
-
-void X86_64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 thr_offs) {
-  __ gs()->movq(Address::Absolute(thr_offs, true), CpuRegister(RSP));
-}
-
-void X86_64JNIMacroAssembler::StoreSpanning(FrameOffset /*dst*/,
-                                            ManagedRegister /*src*/,
-                                            FrameOffset /*in_off*/) {
-  UNIMPLEMENTED(FATAL);  // this case only currently exists for ARM
+void X86_64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 thr_offs, bool tag_sp) {
+  if (tag_sp) {
+    CpuRegister reg = GetScratchRegister();
+    __ movq(reg, CpuRegister(RSP));
+    __ orq(reg, Immediate(0x2));
+    __ gs()->movq(Address::Absolute(thr_offs, true), reg);
+  } else {
+    __ gs()->movq(Address::Absolute(thr_offs, true), CpuRegister(RSP));
+  }
 }
 
 void X86_64JNIMacroAssembler::Load(ManagedRegister mdest, FrameOffset src, size_t size) {
@@ -263,67 +248,6 @@
   }
 }
 
-void X86_64JNIMacroAssembler::LoadFromThread(ManagedRegister mdest,
-                                             ThreadOffset64 src, size_t size) {
-  X86_64ManagedRegister dest = mdest.AsX86_64();
-  if (dest.IsNoRegister()) {
-    CHECK_EQ(0u, size);
-  } else if (dest.IsCpuRegister()) {
-    if (size == 1u) {
-      __ gs()->movzxb(dest.AsCpuRegister(), Address::Absolute(src, true));
-    } else {
-      CHECK_EQ(4u, size);
-      __ gs()->movl(dest.AsCpuRegister(), Address::Absolute(src, true));
-    }
-  } else if (dest.IsRegisterPair()) {
-    CHECK_EQ(8u, size);
-    __ gs()->movq(dest.AsRegisterPairLow(), Address::Absolute(src, true));
-  } else if (dest.IsX87Register()) {
-    if (size == 4) {
-      __ gs()->flds(Address::Absolute(src, true));
-    } else {
-      __ gs()->fldl(Address::Absolute(src, true));
-    }
-  } else {
-    CHECK(dest.IsXmmRegister());
-    if (size == 4) {
-      __ gs()->movss(dest.AsXmmRegister(), Address::Absolute(src, true));
-    } else {
-      __ gs()->movsd(dest.AsXmmRegister(), Address::Absolute(src, true));
-    }
-  }
-}
-
-void X86_64JNIMacroAssembler::LoadRef(ManagedRegister mdest, FrameOffset src) {
-  X86_64ManagedRegister dest = mdest.AsX86_64();
-  CHECK(dest.IsCpuRegister());
-  __ movq(dest.AsCpuRegister(), Address(CpuRegister(RSP), src));
-}
-
-void X86_64JNIMacroAssembler::LoadRef(ManagedRegister mdest,
-                                      ManagedRegister mbase,
-                                      MemberOffset offs,
-                                      bool unpoison_reference) {
-  X86_64ManagedRegister base = mbase.AsX86_64();
-  X86_64ManagedRegister dest = mdest.AsX86_64();
-  CHECK(base.IsCpuRegister());
-  CHECK(dest.IsCpuRegister());
-  __ movl(dest.AsCpuRegister(), Address(base.AsCpuRegister(), offs));
-  if (unpoison_reference) {
-    __ MaybeUnpoisonHeapReference(dest.AsCpuRegister());
-  }
-}
-
-void X86_64JNIMacroAssembler::LoadRawPtr(ManagedRegister mdest,
-                                         ManagedRegister mbase,
-                                         Offset offs) {
-  X86_64ManagedRegister base = mbase.AsX86_64();
-  X86_64ManagedRegister dest = mdest.AsX86_64();
-  CHECK(base.IsCpuRegister());
-  CHECK(dest.IsCpuRegister());
-  __ movq(dest.AsCpuRegister(), Address(base.AsCpuRegister(), offs));
-}
-
 void X86_64JNIMacroAssembler::LoadRawPtrFromThread(ManagedRegister mdest, ThreadOffset64 offs) {
   X86_64ManagedRegister dest = mdest.AsX86_64();
   CHECK(dest.IsCpuRegister());
@@ -477,37 +401,10 @@
   }
 }
 
-void X86_64JNIMacroAssembler::CopyRef(FrameOffset dest, FrameOffset src) {
-  CpuRegister scratch = GetScratchRegister();
-  __ movl(scratch, Address(CpuRegister(RSP), src));
-  __ movl(Address(CpuRegister(RSP), dest), scratch);
-}
 
-void X86_64JNIMacroAssembler::CopyRef(FrameOffset dest,
-                                      ManagedRegister base,
-                                      MemberOffset offs,
-                                      bool unpoison_reference) {
-  CpuRegister scratch = GetScratchRegister();
-  __ movl(scratch, Address(base.AsX86_64().AsCpuRegister(), offs));
-  if (unpoison_reference) {
-    __ MaybeUnpoisonHeapReference(scratch);
-  }
-  __ movl(Address(CpuRegister(RSP), dest), scratch);
-}
-
-void X86_64JNIMacroAssembler::CopyRawPtrFromThread(FrameOffset fr_offs, ThreadOffset64 thr_offs) {
-  CpuRegister scratch = GetScratchRegister();
-  __ gs()->movq(scratch, Address::Absolute(thr_offs, true));
-  __ movq(Address(CpuRegister(RSP), fr_offs), scratch);
-}
-
-void X86_64JNIMacroAssembler::CopyRawPtrToThread(ThreadOffset64 thr_offs,
-                                                 FrameOffset fr_offs,
-                                                 ManagedRegister mscratch) {
-  X86_64ManagedRegister scratch = mscratch.AsX86_64();
-  CHECK(scratch.IsCpuRegister());
-  Load(scratch, fr_offs, 8);
-  __ gs()->movq(Address::Absolute(thr_offs, true), scratch.AsCpuRegister());
+void X86_64JNIMacroAssembler::Move(ManagedRegister mdest, size_t value) {
+  X86_64ManagedRegister dest = mdest.AsX86_64();
+  __ movq(dest.AsCpuRegister(), Immediate(value));
 }
 
 void X86_64JNIMacroAssembler::Copy(FrameOffset dest, FrameOffset src, size_t size) {
@@ -522,67 +419,6 @@
   }
 }
 
-void X86_64JNIMacroAssembler::Copy(FrameOffset /*dst*/,
-                                   ManagedRegister /*src_base*/,
-                                   Offset /*src_offset*/,
-                                   ManagedRegister /*scratch*/,
-                                   size_t /*size*/) {
-  UNIMPLEMENTED(FATAL);
-}
-
-void X86_64JNIMacroAssembler::Copy(ManagedRegister dest_base,
-                                   Offset dest_offset,
-                                   FrameOffset src,
-                                   ManagedRegister scratch,
-                                   size_t size) {
-  CHECK(scratch.IsNoRegister());
-  CHECK_EQ(size, 4u);
-  __ pushq(Address(CpuRegister(RSP), src));
-  __ popq(Address(dest_base.AsX86_64().AsCpuRegister(), dest_offset));
-}
-
-void X86_64JNIMacroAssembler::Copy(FrameOffset dest,
-                                   FrameOffset src_base,
-                                   Offset src_offset,
-                                   ManagedRegister mscratch,
-                                   size_t size) {
-  CpuRegister scratch = mscratch.AsX86_64().AsCpuRegister();
-  CHECK_EQ(size, 4u);
-  __ movq(scratch, Address(CpuRegister(RSP), src_base));
-  __ movq(scratch, Address(scratch, src_offset));
-  __ movq(Address(CpuRegister(RSP), dest), scratch);
-}
-
-void X86_64JNIMacroAssembler::Copy(ManagedRegister dest,
-                                   Offset dest_offset,
-                                   ManagedRegister src,
-                                   Offset src_offset,
-                                   ManagedRegister scratch,
-                                   size_t size) {
-  CHECK_EQ(size, 4u);
-  CHECK(scratch.IsNoRegister());
-  __ pushq(Address(src.AsX86_64().AsCpuRegister(), src_offset));
-  __ popq(Address(dest.AsX86_64().AsCpuRegister(), dest_offset));
-}
-
-void X86_64JNIMacroAssembler::Copy(FrameOffset dest,
-                                   Offset dest_offset,
-                                   FrameOffset src,
-                                   Offset src_offset,
-                                   ManagedRegister mscratch,
-                                   size_t size) {
-  CpuRegister scratch = mscratch.AsX86_64().AsCpuRegister();
-  CHECK_EQ(size, 4u);
-  CHECK_EQ(dest.Int32Value(), src.Int32Value());
-  __ movq(scratch, Address(CpuRegister(RSP), src));
-  __ pushq(Address(scratch, src_offset));
-  __ popq(Address(scratch, dest_offset));
-}
-
-void X86_64JNIMacroAssembler::MemoryBarrier(ManagedRegister) {
-  __ mfence();
-}
-
 void X86_64JNIMacroAssembler::CreateJObject(ManagedRegister mout_reg,
                                             FrameOffset spilled_reference_offset,
                                             ManagedRegister min_reg,
@@ -629,6 +465,19 @@
   __ movq(Address(CpuRegister(RSP), out_off), scratch);
 }
 
+void X86_64JNIMacroAssembler::DecodeJNITransitionOrLocalJObject(ManagedRegister reg,
+                                                                JNIMacroLabel* slow_path,
+                                                                JNIMacroLabel* resume) {
+  constexpr uint64_t kGlobalOrWeakGlobalMask = IndirectReferenceTable::GetGlobalOrWeakGlobalMask();
+  constexpr uint64_t kIndirectRefKindMask = IndirectReferenceTable::GetIndirectRefKindMask();
+  // TODO: Add `testq()` with `imm32` to assembler to avoid using 64-bit pointer as 32-bit value.
+  __ testl(reg.AsX86_64().AsCpuRegister(), Immediate(kGlobalOrWeakGlobalMask));
+  __ j(kNotZero, X86_64JNIMacroLabel::Cast(slow_path)->AsX86_64());
+  __ andq(reg.AsX86_64().AsCpuRegister(), Immediate(~kIndirectRefKindMask));
+  __ j(kZero, X86_64JNIMacroLabel::Cast(resume)->AsX86_64());  // Skip load for null.
+  __ movl(reg.AsX86_64().AsCpuRegister(), Address(reg.AsX86_64().AsCpuRegister(), /*disp=*/ 0));
+}
+
 void X86_64JNIMacroAssembler::VerifyObject(ManagedRegister /*src*/, bool /*could_be_null*/) {
   // TODO: not validating references
 }
@@ -803,6 +652,13 @@
   __ j(UnaryConditionToX86_64Condition(cond), X86_64JNIMacroLabel::Cast(label)->AsX86_64());
 }
 
+void X86_64JNIMacroAssembler::TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) {
+  CpuRegister scratch = GetScratchRegister();
+  __ movq(scratch, Immediate(address));
+  __ cmpb(Address(scratch, 0), Immediate(0));
+  __ j(kNotZero, X86_64JNIMacroLabel::Cast(label)->AsX86_64());
+}
+
 void X86_64JNIMacroAssembler::Bind(JNIMacroLabel* label) {
   CHECK(label != nullptr);
   __ Bind(X86_64JNIMacroLabel::Cast(label)->AsX86_64());
diff --git a/compiler/utils/x86_64/jni_macro_assembler_x86_64.h b/compiler/utils/x86_64/jni_macro_assembler_x86_64.h
index e080f0b..da0aef9 100644
--- a/compiler/utils/x86_64/jni_macro_assembler_x86_64.h
+++ b/compiler/utils/x86_64/jni_macro_assembler_x86_64.h
@@ -28,7 +28,7 @@
 #include "utils/assembler.h"
 #include "utils/jni_macro_assembler.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 class X86_64JNIMacroAssembler final : public JNIMacroAssemblerFwd<X86_64Assembler,
@@ -60,32 +60,14 @@
   // Store routines
   void Store(FrameOffset offs, ManagedRegister src, size_t size) override;
   void Store(ManagedRegister base, MemberOffset offs, ManagedRegister src, size_t size) override;
-  void StoreRef(FrameOffset dest, ManagedRegister src) override;
   void StoreRawPtr(FrameOffset dest, ManagedRegister src) override;
 
-  void StoreImmediateToFrame(FrameOffset dest, uint32_t imm) override;
-
-  void StoreStackOffsetToThread(ThreadOffset64 thr_offs, FrameOffset fr_offs) override;
-
-  void StoreStackPointerToThread(ThreadOffset64 thr_offs) override;
-
-  void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
+  void StoreStackPointerToThread(ThreadOffset64 thr_offs, bool tag_sp) override;
 
   // Load routines
   void Load(ManagedRegister dest, FrameOffset src, size_t size) override;
   void Load(ManagedRegister dest, ManagedRegister base, MemberOffset offs, size_t size) override;
 
-  void LoadFromThread(ManagedRegister dest, ThreadOffset64 src, size_t size) override;
-
-  void LoadRef(ManagedRegister dest, FrameOffset  src) override;
-
-  void LoadRef(ManagedRegister dest,
-               ManagedRegister base,
-               MemberOffset offs,
-               bool unpoison_reference) override;
-
-  void LoadRawPtr(ManagedRegister dest, ManagedRegister base, Offset offs) override;
-
   void LoadRawPtrFromThread(ManagedRegister dest, ThreadOffset64 offs) override;
 
   // Copying routines
@@ -95,52 +77,7 @@
 
   void Move(ManagedRegister dest, ManagedRegister src, size_t size) override;
 
-  void CopyRawPtrFromThread(FrameOffset fr_offs, ThreadOffset64 thr_offs) override;
-
-  void CopyRawPtrToThread(ThreadOffset64 thr_offs, FrameOffset fr_offs, ManagedRegister scratch)
-      override;
-
-  void CopyRef(FrameOffset dest, FrameOffset src) override;
-  void CopyRef(FrameOffset dest,
-               ManagedRegister base,
-               MemberOffset offs,
-               bool unpoison_reference) override;
-
-  void Copy(FrameOffset dest, FrameOffset src, size_t size) override;
-
-  void Copy(FrameOffset dest,
-            ManagedRegister src_base,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(ManagedRegister dest_base,
-            Offset dest_offset,
-            FrameOffset src,
-            ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(FrameOffset dest,
-            FrameOffset src_base,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(ManagedRegister dest,
-            Offset dest_offset,
-            ManagedRegister src,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-
-  void Copy(FrameOffset dest,
-            Offset dest_offset,
-            FrameOffset src,
-            Offset src_offset,
-            ManagedRegister scratch,
-            size_t size) override;
-
-  void MemoryBarrier(ManagedRegister) override;
+  void Move(ManagedRegister dest, size_t value) override;
 
   // Sign extension
   void SignExtend(ManagedRegister mreg, size_t size) override;
@@ -152,20 +89,10 @@
   void GetCurrentThread(ManagedRegister dest) override;
   void GetCurrentThread(FrameOffset dest_offset) override;
 
-  // Set up `out_reg` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
-  // or to be null if the value is null and `null_allowed`. `in_reg` holds a possibly
-  // stale reference that can be used to avoid loading the spilled value to
-  // see if the value is null.
-  void CreateJObject(ManagedRegister out_reg,
-                     FrameOffset spilled_reference_offset,
-                     ManagedRegister in_reg,
-                     bool null_allowed) override;
-
-  // Set up `out_off` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
-  // or to be null if the value is null and `null_allowed`.
-  void CreateJObject(FrameOffset out_off,
-                     FrameOffset spilled_reference_offset,
-                     bool null_allowed) override;
+  // Decode JNI transition or local `jobject`. For (weak) global `jobject`, jump to slow path.
+  void DecodeJNITransitionOrLocalJObject(ManagedRegister reg,
+                                         JNIMacroLabel* slow_path,
+                                         JNIMacroLabel* resume) override;
 
   // Heap::VerifyObject on src. In some cases (such as a reference to this) we
   // know that src may not be null.
@@ -209,10 +136,29 @@
   void TestGcMarking(JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
   // Emit a conditional jump to the label by applying a unary condition test to object's mark bit.
   void TestMarkBit(ManagedRegister ref, JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
+  // Emit a conditional jump to label if the loaded value from specified locations is not zero.
+  void TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) override;
   // Code at this offset will serve as the target for the Jump call.
   void Bind(JNIMacroLabel* label) override;
 
  private:
+  void Copy(FrameOffset dest, FrameOffset src, size_t size);
+
+  // Set up `out_reg` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
+  // or to be null if the value is null and `null_allowed`. `in_reg` holds a possibly
+  // stale reference that can be used to avoid loading the spilled value to
+  // see if the value is null.
+  void CreateJObject(ManagedRegister out_reg,
+                     FrameOffset spilled_reference_offset,
+                     ManagedRegister in_reg,
+                     bool null_allowed);
+
+  // Set up `out_off` to hold a `jobject` (`StackReference<Object>*` to a spilled value),
+  // or to be null if the value is null and `null_allowed`.
+  void CreateJObject(FrameOffset out_off,
+                     FrameOffset spilled_reference_offset,
+                     bool null_allowed);
+
   DISALLOW_COPY_AND_ASSIGN(X86_64JNIMacroAssembler);
 };
 
diff --git a/compiler/utils/x86_64/managed_register_x86_64.cc b/compiler/utils/x86_64/managed_register_x86_64.cc
index c0eec9d..75ff8aa 100644
--- a/compiler/utils/x86_64/managed_register_x86_64.cc
+++ b/compiler/utils/x86_64/managed_register_x86_64.cc
@@ -18,7 +18,7 @@
 
 #include "base/globals.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 // Define register pairs.
diff --git a/compiler/utils/x86_64/managed_register_x86_64.h b/compiler/utils/x86_64/managed_register_x86_64.h
index 62c0e37..7a1be0b 100644
--- a/compiler/utils/x86_64/managed_register_x86_64.h
+++ b/compiler/utils/x86_64/managed_register_x86_64.h
@@ -17,10 +17,11 @@
 #ifndef ART_COMPILER_UTILS_X86_64_MANAGED_REGISTER_X86_64_H_
 #define ART_COMPILER_UTILS_X86_64_MANAGED_REGISTER_X86_64_H_
 
+#include "base/macros.h"
 #include "constants_x86_64.h"
 #include "utils/managed_register.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 // Values for register pairs.
diff --git a/compiler/utils/x86_64/managed_register_x86_64_test.cc b/compiler/utils/x86_64/managed_register_x86_64_test.cc
index 46a405f..048268b 100644
--- a/compiler/utils/x86_64/managed_register_x86_64_test.cc
+++ b/compiler/utils/x86_64/managed_register_x86_64_test.cc
@@ -16,9 +16,10 @@
 
 #include "managed_register_x86_64.h"
 #include "base/globals.h"
+#include "base/macros.h"
 #include "gtest/gtest.h"
 
-namespace art {
+namespace art HIDDEN {
 namespace x86_64 {
 
 TEST(X86_64ManagedRegister, NoRegister) {
diff --git a/default_build b/default_build
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/default_build
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/dex2oat/Android.bp b/dex2oat/Android.bp
index 80ffa51..2938127 100644
--- a/dex2oat/Android.bp
+++ b/dex2oat/Android.bp
@@ -29,6 +29,9 @@
     host_supported: true,
     srcs: [
         "dex/quick_compiler_callbacks.cc",
+        "dex/verification_results.cc",
+        "driver/compiled_method.cc",
+        "driver/compiled_method_storage.cc",
         "driver/compiler_driver.cc",
         "linker/code_info_table_deduper.cc",
         "linker/elf_writer.cc",
@@ -37,6 +40,7 @@
         "linker/multi_oat_relative_patcher.cc",
         "linker/oat_writer.cc",
         "linker/relative_patcher.cc",
+        "utils/swap_space.cc",
     ],
 
     codegen: {
@@ -106,6 +110,9 @@
         "libartpalette",
         "libprofile",
     ],
+    static_libs: [
+        "libelffile",
+    ],
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
@@ -157,6 +164,9 @@
         "libartpalette",
         "libprofiled",
     ],
+    static_libs: [
+        "libelffiled",
+    ],
     apex_available: [
         "com.android.art.debug",
     ],
@@ -242,6 +252,12 @@
                 profile_file: "art/dex2oat_arm_arm64.profdata",
             },
         },
+        android_riscv64: {
+            pgo: {
+                enable_profile_use: false,
+                profile_file: "",
+            },
+        },
         android_x86_64: {
             pgo: {
                 profile_file: "art/dex2oat_x86_x86_64.profdata",
@@ -296,6 +312,7 @@
             ],
             static_libs: [
                 "libart-dex2oat",
+                "libelffile",
             ],
             lto: {
                 thin: true,
@@ -320,6 +337,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -355,6 +373,7 @@
             ],
             static_libs: [
                 "libartd-dex2oat",
+                "libelffiled",
             ],
         },
         host: {
@@ -459,6 +478,7 @@
     name: "art_dex2oat_tests_defaults",
     data: [
         ":art-gtest-jars-AbstractMethod",
+        ":art-gtest-jars-ArrayClassWithUnresolvedComponent",
         ":art-gtest-jars-DefaultMethods",
         ":art-gtest-jars-Dex2oatVdexPublicSdkDex",
         ":art-gtest-jars-Dex2oatVdexTestDex",
@@ -473,12 +493,14 @@
         ":art-gtest-jars-ManyMethods",
         ":art-gtest-jars-MultiDex",
         ":art-gtest-jars-MultiDexModifiedSecondary",
+        ":art-gtest-jars-MultiDexUncompressedAligned",
         ":art-gtest-jars-MyClassNatives",
         ":art-gtest-jars-Nested",
         ":art-gtest-jars-ProfileTestMultiDex",
         ":art-gtest-jars-StaticLeafMethods",
         ":art-gtest-jars-Statics",
         ":art-gtest-jars-StringLiterals",
+        ":art-gtest-jars-SuperWithAccessChecks",
         ":art-gtest-jars-VerifierDeps",
         ":art-gtest-jars-VerifierDepsMulti",
         ":art-gtest-jars-VerifySoftFailDuringClinit",
@@ -493,6 +515,7 @@
         "dex2oat_test.cc",
         "dex2oat_vdex_test.cc",
         "dex2oat_image_test.cc",
+        "driver/compiled_method_storage_test.cc",
         "driver/compiler_driver_test.cc",
         "linker/code_info_table_deduper_test.cc",
         "linker/elf_writer_test.cc",
@@ -502,6 +525,7 @@
         "linker/multi_oat_relative_patcher_test.cc",
         "linker/oat_writer_test.cc",
         "verifier_deps_test.cc",
+        "utils/swap_space_test.cc",
     ],
     target: {
         host: {
@@ -532,6 +556,9 @@
         },
     },
 
+    static_libs: [
+        "libziparchive",
+    ],
     shared_libs: [
         "libartpalette",
         "libbase",
@@ -539,7 +566,7 @@
         "liblz4", // libart(d)-dex2oat dependency; must be repeated here since it's a static lib.
         "liblog",
         "libsigchain",
-        "libziparchive",
+        "libz", // libziparchive dependency; must be repeated here since it's a static lib.
     ],
 }
 
@@ -553,13 +580,15 @@
     ],
     shared_libs: [
         "libartbased",
-        "libartd-compiler",
         "libartd-dexlayout",
+        "liblzma",
         "libprofiled",
     ],
     static_libs: [
-        "libartd-dex2oat-gtest",
+        "libartd-compiler",
         "libartd-dex2oat",
+        "libartd-dex2oat-gtest",
+        "libelffiled",
         "libvixld",
     ],
 }
@@ -573,14 +602,16 @@
     ],
     data: [":generate-boot-image"],
     shared_libs: [
-        "libartbase",
-        "libart-compiler",
         "libart-dexlayout",
+        "libartbase",
+        "liblzma",
         "libprofile",
     ],
     static_libs: [
-        "libart-dex2oat-gtest",
+        "libart-compiler",
         "libart-dex2oat",
+        "libart-dex2oat-gtest",
+        "libelffile",
         "libvixl",
     ],
     test_config: "art_standalone_dex2oat_tests.xml",
@@ -596,9 +627,16 @@
         ":art-gtest-jars-MainStripped",
         ":art-gtest-jars-MultiDex",
         ":art-gtest-jars-MultiDexModifiedSecondary",
+        ":art-gtest-jars-MultiDexUncompressedAligned",
         ":art-gtest-jars-Nested",
         ":generate-boot-image",
     ],
+    shared_libs: [
+        "libz", // libziparchive dependency; must be repeated here since it's a static lib.
+    ],
+    static_libs: [
+        "libziparchive",
+    ],
     test_config: "art_standalone_dex2oat_cts_tests.xml",
     test_suites: ["cts"],
 }
diff --git a/dex2oat/art_standalone_dex2oat_cts_tests.xml b/dex2oat/art_standalone_dex2oat_cts_tests.xml
index 0dabfda..a47febe 100644
--- a/dex2oat/art_standalone_dex2oat_cts_tests.xml
+++ b/dex2oat/art_standalone_dex2oat_cts_tests.xml
@@ -32,6 +32,7 @@
         <option name="push" value="art-gtest-jars-MainStripped.jar->/data/local/tmp/art_standalone_dex2oat_cts_tests/art-gtest-jars-MainStripped.jar" />
         <option name="push" value="art-gtest-jars-MultiDex.jar->/data/local/tmp/art_standalone_dex2oat_cts_tests/art-gtest-jars-MultiDex.jar" />
         <option name="push" value="art-gtest-jars-MultiDexModifiedSecondary.jar->/data/local/tmp/art_standalone_dex2oat_cts_tests/art-gtest-jars-MultiDexModifiedSecondary.jar" />
+        <option name="push" value="art-gtest-jars-MultiDexUncompressedAligned.jar->/data/local/tmp/art_standalone_dex2oat_cts_tests/art-gtest-jars-MultiDexUncompressedAligned.jar" />
         <option name="push" value="art-gtest-jars-Nested.jar->/data/local/tmp/art_standalone_dex2oat_cts_tests/art-gtest-jars-Nested.jar" />
     </target_preparer>
 
diff --git a/dex2oat/art_standalone_dex2oat_tests.xml b/dex2oat/art_standalone_dex2oat_tests.xml
index 32346f2..3a8c7b9 100644
--- a/dex2oat/art_standalone_dex2oat_tests.xml
+++ b/dex2oat/art_standalone_dex2oat_tests.xml
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 <configuration description="Runs art_standalone_dex2oat_tests.">
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
+
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="art_standalone_dex2oat_tests->/data/local/tmp/art_standalone_dex2oat_tests/art_standalone_dex2oat_tests" />
@@ -23,6 +25,8 @@
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="art-gtest-jars-AbstractMethod.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-AbstractMethod.jar" />
+        <option name="push" value="art-gtest-jars-ArrayClassWithUnresolvedComponent.dex->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-ArrayClassWithUnresolvedComponent.dex" />
+        <option name="push" value="art-gtest-jars-SuperWithAccessChecks.dex->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-SuperWithAccessChecks.dex" />
         <option name="push" value="art-gtest-jars-DefaultMethods.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-DefaultMethods.jar" />
         <option name="push" value="art-gtest-jars-Dex2oatVdexPublicSdkDex.dex->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-Dex2oatVdexPublicSdkDex.dex" />
         <option name="push" value="art-gtest-jars-Dex2oatVdexTestDex.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-Dex2oatVdexTestDex.jar" />
@@ -37,6 +41,7 @@
         <option name="push" value="art-gtest-jars-ManyMethods.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-ManyMethods.jar" />
         <option name="push" value="art-gtest-jars-MultiDex.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-MultiDex.jar" />
         <option name="push" value="art-gtest-jars-MultiDexModifiedSecondary.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-MultiDexModifiedSecondary.jar" />
+        <option name="push" value="art-gtest-jars-MultiDexUncompressedAligned.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-MultiDexUncompressedAligned.jar" />
         <option name="push" value="art-gtest-jars-MyClassNatives.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-MyClassNatives.jar" />
         <option name="push" value="art-gtest-jars-Nested.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-Nested.jar" />
         <option name="push" value="art-gtest-jars-ProfileTestMultiDex.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-ProfileTestMultiDex.jar" />
@@ -62,6 +67,9 @@
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-timeout" value="5m" />
+        <!-- Set this to the same as native-test-timeout to effectively disable per test case timeout. -->
+        <option name="test-case-timeout" value="5m" />
         <option name="native-test-device-path" value="/data/local/tmp/art_standalone_dex2oat_tests" />
         <option name="module-name" value="art_standalone_dex2oat_tests" />
         <option name="ld-library-path-32" value="/apex/com.android.art/lib" />
diff --git a/dex2oat/common_compiler_driver_test.cc b/dex2oat/common_compiler_driver_test.cc
index 7e71976..7a1d11c 100644
--- a/dex2oat/common_compiler_driver_test.cc
+++ b/dex2oat/common_compiler_driver_test.cc
@@ -21,6 +21,7 @@
 #include "base/casts.h"
 #include "base/timing_logger.h"
 #include "dex/quick_compiler_callbacks.h"
+#include "dex/verification_results.h"
 #include "driver/compiler_driver.h"
 #include "driver/compiler_options.h"
 #include "utils/atomic_dex_ref_map-inl.h"
@@ -46,9 +47,7 @@
   // Verification results in the `callback_` should not be used during compilation.
   down_cast<QuickCompilerCallbacks*>(callbacks_.get())->SetVerificationResults(
       reinterpret_cast<VerificationResults*>(inaccessible_page_));
-  compiler_options_->verification_results_ = verification_results_.get();
   compiler_driver_->CompileAll(class_loader, dex_files, timings);
-  compiler_options_->verification_results_ = nullptr;
   down_cast<QuickCompilerCallbacks*>(callbacks_.get())->SetVerificationResults(
       verification_results_.get());
 
@@ -89,6 +88,7 @@
   compiler_options_->image_classes_.swap(*GetImageClasses());
   compiler_options_->profile_compilation_info_ = GetProfileCompilationInfo();
   compiler_driver_.reset(new CompilerDriver(compiler_options_.get(),
+                                            verification_results_.get(),
                                             compiler_kind_,
                                             number_of_threads_,
                                             /* swap_fd= */ -1));
@@ -97,6 +97,7 @@
 void CommonCompilerDriverTest::SetUpRuntimeOptions(RuntimeOptions* options) {
   CommonCompilerTest::SetUpRuntimeOptions(options);
 
+  verification_results_.reset(new VerificationResults());
   QuickCompilerCallbacks* callbacks =
       new QuickCompilerCallbacks(CompilerCallbacks::CallbackMode::kCompileApp);
   callbacks->SetVerificationResults(verification_results_.get());
@@ -121,6 +122,7 @@
   }
   image_reservation_.Reset();
   compiler_driver_.reset();
+  verification_results_.reset();
 
   CommonCompilerTest::TearDown();
 }
diff --git a/dex2oat/common_compiler_driver_test.h b/dex2oat/common_compiler_driver_test.h
index 1ff88e5..b49d18f 100644
--- a/dex2oat/common_compiler_driver_test.h
+++ b/dex2oat/common_compiler_driver_test.h
@@ -31,6 +31,7 @@
 class DexFile;
 class ProfileCompilationInfo;
 class TimingLogger;
+class VerificationResults;
 
 class CommonCompilerDriverTest : public CommonCompilerTest {
  public:
@@ -62,6 +63,7 @@
 
   size_t number_of_threads_ = 2u;
 
+  std::unique_ptr<VerificationResults> verification_results_;
   std::unique_ptr<CompilerDriver> compiler_driver_;
 
  private:
diff --git a/dex2oat/dex/quick_compiler_callbacks.cc b/dex2oat/dex/quick_compiler_callbacks.cc
index 11223ff..7611374 100644
--- a/dex2oat/dex/quick_compiler_callbacks.cc
+++ b/dex2oat/dex/quick_compiler_callbacks.cc
@@ -28,6 +28,12 @@
   }
 }
 
+void QuickCompilerCallbacks::AddUncompilableClass(ClassReference ref) {
+  if (verification_results_ != nullptr) {
+    verification_results_->AddUncompilableClass(ref);
+  }
+}
+
 void QuickCompilerCallbacks::ClassRejected(ClassReference ref) {
   if (verification_results_ != nullptr) {
     verification_results_->AddRejectedClass(ref);
diff --git a/dex2oat/dex/quick_compiler_callbacks.h b/dex2oat/dex/quick_compiler_callbacks.h
index 4447e02..a7a482b 100644
--- a/dex2oat/dex/quick_compiler_callbacks.h
+++ b/dex2oat/dex/quick_compiler_callbacks.h
@@ -34,6 +34,7 @@
   ~QuickCompilerCallbacks() { }
 
   void AddUncompilableMethod(MethodReference ref) override;
+  void AddUncompilableClass(ClassReference ref) override;
 
   void ClassRejected(ClassReference ref) override;
 
diff --git a/dex2oat/dex/verification_results.cc b/dex2oat/dex/verification_results.cc
new file mode 100644
index 0000000..bc0329a
--- /dev/null
+++ b/dex2oat/dex/verification_results.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include "verification_results.h"
+
+#include <android-base/logging.h>
+
+#include "base/mutex-inl.h"
+#include "base/stl_util.h"
+#include "dex/class_accessor-inl.h"
+#include "runtime.h"
+#include "thread-current-inl.h"
+#include "thread.h"
+
+namespace art {
+
+VerificationResults::VerificationResults()
+    : uncompilable_methods_lock_("compiler uncompilable methods lock"),
+      rejected_classes_lock_("compiler rejected classes lock") {}
+
+// Non-inline version of the destructor, as it does some implicit work not worth
+// inlining.
+VerificationResults::~VerificationResults() {}
+
+void VerificationResults::AddRejectedClass(ClassReference ref) {
+  {
+    WriterMutexLock mu(Thread::Current(), rejected_classes_lock_);
+    rejected_classes_.insert(ref);
+  }
+  DCHECK(IsClassRejected(ref));
+}
+
+bool VerificationResults::IsClassRejected(ClassReference ref) const {
+  ReaderMutexLock mu(Thread::Current(), rejected_classes_lock_);
+  return rejected_classes_.find(ref) != rejected_classes_.end();
+}
+
+void VerificationResults::AddUncompilableMethod(MethodReference ref) {
+  {
+    WriterMutexLock mu(Thread::Current(), uncompilable_methods_lock_);
+    uncompilable_methods_.insert(ref);
+  }
+  DCHECK(IsUncompilableMethod(ref));
+}
+
+void VerificationResults::AddUncompilableClass(ClassReference ref) {
+  const DexFile& dex_file = *ref.dex_file;
+  const dex::ClassDef& class_def = dex_file.GetClassDef(ref.ClassDefIdx());
+  WriterMutexLock mu(Thread::Current(), uncompilable_methods_lock_);
+  ClassAccessor accessor(dex_file, class_def);
+  for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+    MethodReference method_ref(&dex_file, method.GetIndex());
+    uncompilable_methods_.insert(method_ref);
+  }
+}
+
+bool VerificationResults::IsUncompilableMethod(MethodReference ref) const {
+  ReaderMutexLock mu(Thread::Current(), uncompilable_methods_lock_);
+  return uncompilable_methods_.find(ref) != uncompilable_methods_.end();
+}
+
+
+}  // namespace art
diff --git a/dex2oat/dex/verification_results.h b/dex2oat/dex/verification_results.h
new file mode 100644
index 0000000..0a85e43
--- /dev/null
+++ b/dex2oat/dex/verification_results.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 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_DEX2OAT_DEX_VERIFICATION_RESULTS_H_
+#define ART_DEX2OAT_DEX_VERIFICATION_RESULTS_H_
+
+#include <set>
+
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "dex/class_reference.h"
+#include "dex/method_reference.h"
+
+namespace art {
+
+namespace verifier {
+class VerifierDepsTest;
+}  // namespace verifier
+
+// Used by CompilerCallbacks to track verification information from the Runtime.
+class VerificationResults {
+ public:
+  VerificationResults();
+  ~VerificationResults();
+
+  void AddRejectedClass(ClassReference ref) REQUIRES(!rejected_classes_lock_);
+  bool IsClassRejected(ClassReference ref) const REQUIRES(!rejected_classes_lock_);
+
+  void AddUncompilableClass(ClassReference ref) REQUIRES(!uncompilable_methods_lock_);
+  void AddUncompilableMethod(MethodReference ref) REQUIRES(!uncompilable_methods_lock_);
+  bool IsUncompilableMethod(MethodReference ref) const REQUIRES(!uncompilable_methods_lock_);
+
+ private:
+  // TODO: External locking during CompilerDriver::PreCompile(), no locking during compilation.
+  mutable ReaderWriterMutex uncompilable_methods_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+  std::set<MethodReference> uncompilable_methods_ GUARDED_BY(uncompilable_methods_lock_);
+
+  // Rejected classes.
+  // TODO: External locking during CompilerDriver::PreCompile(), no locking during compilation.
+  mutable ReaderWriterMutex rejected_classes_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+  std::set<ClassReference> rejected_classes_ GUARDED_BY(rejected_classes_lock_);
+
+  friend class verifier::VerifierDepsTest;
+};
+
+}  // namespace art
+
+#endif  // ART_DEX2OAT_DEX_VERIFICATION_RESULTS_H_
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 9e6103b..6c71372 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -20,6 +20,7 @@
 #include <stdlib.h>
 #include <sys/stat.h>
 
+#include <algorithm>
 #include <forward_list>
 #include <fstream>
 #include <iostream>
@@ -39,6 +40,7 @@
 #endif
 
 #include "android-base/parseint.h"
+#include "android-base/properties.h"
 #include "android-base/scopeguard.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
@@ -65,6 +67,7 @@
 #include "base/zip_archive.h"
 #include "class_linker.h"
 #include "class_loader_context.h"
+#include "class_root-inl.h"
 #include "cmdline_parser.h"
 #include "compiler.h"
 #include "compiler_callbacks.h"
@@ -108,7 +111,6 @@
 #include "stream/file_output_stream.h"
 #include "vdex_file.h"
 #include "verifier/verifier_deps.h"
-#include "well_known_classes.h"
 
 namespace art {
 
@@ -607,8 +609,13 @@
   }
 
   void ParseInstructionSetVariant(const std::string& option, ParserOptions* parser_options) {
-    compiler_options_->instruction_set_features_ = InstructionSetFeatures::FromVariant(
-        compiler_options_->instruction_set_, option, &parser_options->error_msg);
+    if (kIsTargetBuild) {
+      compiler_options_->instruction_set_features_ = InstructionSetFeatures::FromVariantAndHwcap(
+          compiler_options_->instruction_set_, option, &parser_options->error_msg);
+    } else {
+      compiler_options_->instruction_set_features_ = InstructionSetFeatures::FromVariant(
+          compiler_options_->instruction_set_, option, &parser_options->error_msg);
+    }
     if (compiler_options_->instruction_set_features_ == nullptr) {
       Usage("%s", parser_options->error_msg.c_str());
     }
@@ -718,8 +725,15 @@
 
     if (!IsBootImage() && boot_image_filename_.empty()) {
       DCHECK(!IsBootImageExtension());
-      boot_image_filename_ =
-          GetDefaultBootImageLocation(android_root_, /*deny_art_apex_data_files=*/false);
+      if (std::any_of(runtime_args_.begin(), runtime_args_.end(), [](const char* arg) {
+            return android::base::StartsWith(arg, "-Xbootclasspath:");
+          })) {
+        LOG(WARNING) << "--boot-image is not specified while -Xbootclasspath is specified. Running "
+                        "dex2oat in imageless mode";
+      } else {
+        boot_image_filename_ =
+            GetDefaultBootImageLocation(android_root_, /*deny_art_apex_data_files=*/false);
+      }
     }
 
     if (dex_filenames_.empty() && zip_fd_ == -1) {
@@ -774,9 +788,12 @@
       compiler_options_->multi_image_ = IsBootImage() || IsBootImageExtension();
     }
     // On target we support generating a single image for the primary boot image.
-    if (!kIsTargetBuild) {
+    if (!kIsTargetBuild && !force_allow_oj_inlines_) {
       if (IsBootImage() && !compiler_options_->multi_image_) {
-        Usage("--single-image specified for primary boot image on host");
+        Usage(
+            "--single-image specified for primary boot image on host. Please "
+            "use the flag --force-allow-oj-inlines and do not distribute "
+            "binaries.");
       }
     }
     if (IsAppImage() && compiler_options_->multi_image_) {
@@ -839,9 +856,7 @@
     // Set the compilation target's implicit checks options.
     switch (compiler_options_->GetInstructionSet()) {
       case InstructionSet::kArm64:
-        // TODO: Implicit suspend checks are currently disabled to facilitate search
-        // for unrelated memory use regressions. Bug: 213757852.
-        compiler_options_->implicit_suspend_checks_ = false;
+        compiler_options_->implicit_suspend_checks_ = true;
         FALLTHROUGH_INTENDED;
       case InstructionSet::kArm:
       case InstructionSet::kThumb2:
@@ -957,7 +972,7 @@
                           compiler_options_->GetNativeDebuggable());
     key_value_store_->Put(OatHeader::kCompilerFilter,
                           CompilerFilter::NameOfFilter(compiler_options_->GetCompilerFilter()));
-    key_value_store_->Put(OatHeader::kConcurrentCopying, kUseReadBarrier);
+    key_value_store_->Put(OatHeader::kConcurrentCopying, gUseReadBarrier);
     if (invocation_file_.get() != -1) {
       std::ostringstream oss;
       for (int i = 0; i < argc; ++i) {
@@ -1029,7 +1044,13 @@
     original_argv = argv;
 
     Locks::Init();
-    InitLogging(argv, Runtime::Abort);
+
+    // Microdroid doesn't support logd logging, so don't override there.
+    if (android::base::GetProperty("ro.hardware", "") == "microdroid") {
+      android::base::SetAborter(Runtime::Abort);
+    } else {
+      InitLogging(argv, Runtime::Abort);
+    }
 
     compiler_options_.reset(new CompilerOptions());
 
@@ -1095,6 +1116,19 @@
     AssignIfExists(args, M::PublicSdk, &public_sdk_);
     AssignIfExists(args, M::ApexVersions, &apex_versions_argument_);
 
+    // Check for phenotype flag to override compact_dex_level_, if it isn't "none" already.
+    // TODO(b/256664509): Clean this up.
+    if (compact_dex_level_ != CompactDexLevel::kCompactDexLevelNone) {
+      std::string ph_disable_compact_dex =
+          android::base::GetProperty(kPhDisableCompactDex, "false");
+      if (ph_disable_compact_dex == "true") {
+        LOG(WARNING)
+            << "Overriding --compact-dex-level due to "
+               "persist.device_config.runtime_native_boot.disable_compact_dex set to `true`";
+        compact_dex_level_ = CompactDexLevel::kCompactDexLevelNone;
+      }
+    }
+
     AssignIfExists(args, M::Backend, &compiler_kind_);
     parser_options->requested_specific_compiler = args.Exists(M::Backend);
 
@@ -1187,7 +1221,7 @@
       if (!parser_options->boot_image_filename.empty()) {
         Usage("Option --boot-image and --force-jit-zygote cannot be specified together");
       }
-      parser_options->boot_image_filename = "boot.art:/nonx/boot-framework.art";
+      parser_options->boot_image_filename = GetJitZygoteBootImageLocation();
     }
 
     // If we have a profile, change the default compiler filter to speed-profile
@@ -1400,24 +1434,36 @@
     }
   }
 
-  void LoadClassProfileDescriptors() {
+  void LoadImageClassDescriptors() {
     if (!IsImage()) {
       return;
     }
+    HashSet<std::string> image_classes;
     if (DoProfileGuidedOptimizations()) {
       // TODO: The following comment looks outdated or misplaced.
       // Filter out class path classes since we don't want to include these in the image.
-      HashSet<std::string> image_classes = profile_compilation_info_->GetClassDescriptors(
+      image_classes = profile_compilation_info_->GetClassDescriptors(
           compiler_options_->dex_files_for_oat_file_);
       VLOG(compiler) << "Loaded " << image_classes.size()
                      << " image class descriptors from profile";
-      if (VLOG_IS_ON(compiler)) {
-        for (const std::string& s : image_classes) {
-          LOG(INFO) << "Image class " << s;
+    } else if (compiler_options_->IsBootImage() || compiler_options_->IsBootImageExtension()) {
+      // If we are compiling a boot image but no profile is provided, include all classes in the
+      // image. This is to match pre-boot image extension work where we would load all boot image
+      // extension classes at startup.
+      for (const DexFile* dex_file : compiler_options_->dex_files_for_oat_file_) {
+        for (uint32_t i = 0; i < dex_file->NumClassDefs(); i++) {
+          const dex::ClassDef& class_def = dex_file->GetClassDef(i);
+          const char* descriptor = dex_file->GetClassDescriptor(class_def);
+          image_classes.insert(descriptor);
         }
       }
-      compiler_options_->image_classes_.swap(image_classes);
     }
+    if (VLOG_IS_ON(compiler)) {
+      for (const std::string& s : image_classes) {
+        LOG(INFO) << "Image class " << s;
+      }
+    }
+    compiler_options_->image_classes_ = std::move(image_classes);
   }
 
   // Set up the environment for compilation. Includes starting the runtime and loading/opening the
@@ -1469,16 +1515,12 @@
           return dex2oat::ReturnCode::kOther;
         }
         dex_files_per_oat_file_.push_back(MakeNonOwningPointerVector(opened_dex_files));
-        if (opened_dex_files_map.empty()) {
-          DCHECK(opened_dex_files.empty());
-        } else {
-          for (MemMap& map : opened_dex_files_map) {
-            opened_dex_files_maps_.push_back(std::move(map));
-          }
-          for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files) {
-            dex_file_oat_index_map_.insert(std::make_pair(dex_file.get(), i));
-            opened_dex_files_.push_back(std::move(dex_file));
-          }
+        for (MemMap& map : opened_dex_files_map) {
+          opened_dex_files_maps_.push_back(std::move(map));
+        }
+        for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files) {
+          dex_file_oat_index_map_.insert(std::make_pair(dex_file.get(), i));
+          opened_dex_files_.push_back(std::move(dex_file));
         }
       }
     }
@@ -1798,7 +1840,7 @@
     // 2. not verifying a vdex file, and
     // 3. using multidex, and
     // 4. not doing any AOT compilation.
-    // This means extract, no-vdex verify, and quicken, will use the individual compilation
+    // This means no-vdex verify will use the individual compilation
     // mode (to reduce RAM used by the compiler).
     return compile_individually_ &&
            (!IsImage() && !use_existing_vdex_ &&
@@ -1815,7 +1857,7 @@
   }
 
   // Set up and create the compiler driver and then invoke it to compile all the dex files.
-  jobject Compile() {
+  jobject Compile() REQUIRES(!Locks::mutator_lock_) {
     ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
 
     TimingLogger::ScopedTiming t("dex2oat Compile", timings_);
@@ -1864,7 +1906,7 @@
               }
             }
 
-            if (android::base::StartsWith(dex_location, filter.c_str())) {
+            if (android::base::StartsWith(dex_location, filter)) {
               VLOG(compiler) << "Disabling inlining from " << dex_file->GetLocation();
               no_inline_from_dex_files.push_back(dex_file);
               break;
@@ -1879,6 +1921,7 @@
     compiler_options_->profile_compilation_info_ = profile_compilation_info_.get();
 
     driver_.reset(new CompilerDriver(compiler_options_.get(),
+                                     verification_results_.get(),
                                      compiler_kind_,
                                      thread_count_,
                                      swap_fd_));
@@ -1891,9 +1934,7 @@
 
     const bool compile_individually = ShouldCompileDexFilesIndividually();
     if (compile_individually) {
-      // Set the compiler driver in the callbacks so that we can avoid re-verification. This not
-      // only helps performance but also prevents reverifying quickened bytecodes. Attempting
-      // verify quickened bytecode causes verification failures.
+      // Set the compiler driver in the callbacks so that we can avoid re-verification.
       // Only set the compiler filter if we are doing separate compilation since there is a bit
       // of overhead when checking if a class was previously verified.
       callbacks_->SetDoesClassUnloading(true, driver_.get());
@@ -1964,7 +2005,6 @@
                         timings_,
                         &compiler_options_->image_classes_);
     callbacks_->SetVerificationResults(nullptr);  // Should not be needed anymore.
-    compiler_options_->verification_results_ = verification_results_.get();
     driver_->CompileAll(class_loader, dex_files, timings_);
     driver_->FreeThreadPools();
     return class_loader;
@@ -2538,16 +2578,16 @@
   }
 
   bool PreparePreloadedClasses() {
-    preloaded_classes_ = std::make_unique<HashSet<std::string>>();
     if (!preloaded_classes_fds_.empty()) {
       for (int fd : preloaded_classes_fds_) {
-        if (!ReadCommentedInputFromFd(fd, nullptr, preloaded_classes_.get())) {
+        if (!ReadCommentedInputFromFd(fd, nullptr, &compiler_options_->preloaded_classes_)) {
           return false;
         }
       }
     } else {
       for (const std::string& file : preloaded_classes_files_) {
-        if (!ReadCommentedInputFromFile(file.c_str(), nullptr, preloaded_classes_.get())) {
+        if (!ReadCommentedInputFromFile(
+                file.c_str(), nullptr, &compiler_options_->preloaded_classes_)) {
           return false;
         }
       }
@@ -2633,6 +2673,7 @@
       bool do_oat_writer_layout = DoDexLayoutOptimizations() || DoOatLayoutOptimizations();
       oat_writers_.emplace_back(new linker::OatWriter(
           *compiler_options_,
+          verification_results_.get(),
           timings_,
           do_oat_writer_layout ? profile_compilation_info_.get() : nullptr,
           compact_dex_level_));
@@ -2731,19 +2772,14 @@
     interpreter::UnstartedRuntime::Initialize();
 
     Thread* self = Thread::Current();
+    runtime_->GetClassLinker()->RunEarlyRootClinits(self);
+    InitializeIntrinsics();
     runtime_->RunRootClinits(self);
 
     // Runtime::Create acquired the mutator_lock_ that is normally given away when we
     // Runtime::Start, give it away now so that we don't starve GC.
     self->TransitionFromRunnableToSuspended(ThreadState::kNative);
 
-    // Now that we are in native state, initialize well known classes and
-    // intrinsics if we don't have a boot image.
-    WellKnownClasses::Init(self->GetJniEnv());
-    if (IsBootImage() || runtime_->GetHeap()->GetBootImageSpaces().empty()) {
-      InitializeIntrinsics();
-    }
-
     WatchDog::SetRuntime(runtime_.get());
 
     return true;
@@ -2786,7 +2822,7 @@
   template <typename T>
   static bool ReadCommentedInputFromFile(
       const char* input_filename, std::function<std::string(const char*)>* process, T* output) {
-    auto input_file = std::unique_ptr<FILE, decltype(&fclose)>{fopen(input_filename, "r"), fclose};
+    auto input_file = std::unique_ptr<FILE, decltype(&fclose)>{fopen(input_filename, "re"), fclose};
     if (!input_file) {
       LOG(ERROR) << "Failed to open input file " << input_filename;
       return false;
@@ -2942,7 +2978,6 @@
   const char* dirty_image_objects_filename_;
   int dirty_image_objects_fd_;
   std::unique_ptr<HashSet<std::string>> dirty_image_objects_;
-  std::unique_ptr<HashSet<std::string>> preloaded_classes_;
   std::unique_ptr<std::vector<std::string>> passes_to_run_;
   bool is_host_;
   std::string android_root_;
@@ -3054,8 +3089,9 @@
   jobject obj_;
 };
 
-static dex2oat::ReturnCode DoCompilation(Dex2Oat& dex2oat) {
-  dex2oat.LoadClassProfileDescriptors();
+static dex2oat::ReturnCode DoCompilation(Dex2Oat& dex2oat) REQUIRES(!Locks::mutator_lock_) {
+  Locks::mutator_lock_->AssertNotHeld(Thread::Current());
+  dex2oat.LoadImageClassDescriptors();
   jobject class_loader = dex2oat.Compile();
   // Keep the class loader that was used for compilation live for the rest of the compilation
   // process.
diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc
index 2978fb4..4518905 100644
--- a/dex2oat/dex2oat_image_test.cc
+++ b/dex2oat/dex2oat_image_test.cc
@@ -431,6 +431,7 @@
                                                 relocate,
                                                 /*executable=*/ true,
                                                 /*extra_reservation_size=*/ 0u,
+                                                /*allow_in_memory_compilation=*/ true,
                                                 &boot_image_spaces,
                                                 &extra_reservation);
   };
diff --git a/dex2oat/dex2oat_options.cc b/dex2oat/dex2oat_options.cc
index 1f9138e..4da2198 100644
--- a/dex2oat/dex2oat_options.cc
+++ b/dex2oat/dex2oat_options.cc
@@ -55,6 +55,7 @@
 using Builder = Parser::Builder;
 
 static void AddInputMappings(Builder& builder) {
+  // clang-format off
   builder.
       Define("--dex-file=_")
           .WithType<std::vector<std::string>>().AppendValues()
@@ -102,9 +103,11 @@
                     "         class path components' paths)\n"
                     "Default: $ANDROID_ROOT/system/framework/boot.art")
           .IntoKey(M::BootImage);
+  // clang-format on
 }
 
 static void AddGeneratedArtifactMappings(Builder& builder) {
+  // clang-format off
   builder.
       Define("--input-vdex-fd=_")
           .WithType<int>()
@@ -156,9 +159,11 @@
                     "specified by --oat-fd.\n"
                     "Eg: --oat-location=/data/dalvik-cache/system@[email protected]")
           .IntoKey(M::OatLocation);
+  // clang-format on
 }
 
 static void AddImageMappings(Builder& builder) {
+  // clang-format off
   builder.
       Define("--image=_")
           .WithType<std::string>()
@@ -214,9 +219,11 @@
           .WithHelp("Which format to store the image Defaults to uncompressed. Eg:"
                     " --image-format=lz4")
           .IntoKey(M::ImageFormat);
+  // clang-format on
 }
 
 static void AddSwapMappings(Builder& builder) {
+  // clang-format off
   builder.
       Define("--swap-file=_")
           .WithType<std::string>()
@@ -234,9 +241,11 @@
           .WithType<unsigned int>()
           .WithHelp("specifies the minimum number of dex file to allow the use of swap.")
           .IntoKey(M::SwapDexCountThreshold);
+  // clang-format on
 }
 
 static void AddCompilerMappings(Builder& builder) {
+  // clang-format off
   builder.
       Define("--run-passes=_")
           .WithType<std::string>()
@@ -264,9 +273,11 @@
           .WithType<std::vector<int>>().AppendValues()
           .WithHelp("Specify files containing list of classes preloaded in the zygote.")
           .IntoKey(M::PreloadedClassesFds);
+  // clang-format on
 }
 
 static void AddTargetMappings(Builder& builder) {
+  // clang-format off
   builder.
       Define("--instruction-set=_")
           .WithType<InstructionSet>()
@@ -288,6 +299,7 @@
                     "Example: --instruction-set-features=div\n"
                     "Default: default")
           .IntoKey(M::TargetInstructionSetFeatures);
+  // clang-format on
 }
 
 Parser CreateDex2oatArgumentParser() {
@@ -300,6 +312,7 @@
   AddCompilerMappings(*parser_builder);
   AddTargetMappings(*parser_builder);
 
+  // clang-format off
   parser_builder->
       Define({"--watch-dog", "--no-watch-dog"})
           .WithHelp("Enable or disable the watchdog timer.")
@@ -448,7 +461,12 @@
           .IntoKey(M::ForceJitZygote)
       .Define("--force-palette-compilation-hooks")
           .WithHelp("Force PaletteNotify{Start,End}Dex2oatCompilation calls.")
-          .IntoKey(M::ForcePaletteCompilationHooks);
+          .IntoKey(M::ForcePaletteCompilationHooks)
+      .Ignore({
+        "--comments=_",
+        "--cache-info-fd=_",  // Handled in mark_compact.cc.
+      });
+  // clang-format on
 
   AddCompilerOptionsArgumentParserOptions<Dex2oatArgumentMap>(*parser_builder);
 
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index 9ee532a..ca76254 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -74,6 +74,7 @@
                                     bool use_fd = false) {
     std::unique_ptr<File> oat_file;
     std::vector<std::string> args;
+    args.reserve(dex_locations.size() + extra_args.size() + 6);
     // Add dex file args.
     for (const std::string& dex_location : dex_locations) {
       args.push_back("--dex-file=" + dex_location);
@@ -103,14 +104,13 @@
     return status;
   }
 
-  ::testing::AssertionResult GenerateOdexForTest(
-      const std::string& dex_location,
-      const std::string& odex_location,
-      CompilerFilter::Filter filter,
-      const std::vector<std::string>& extra_args = {},
-      bool expect_success = true,
-      bool use_fd = false,
-      bool use_zip_fd = false) WARN_UNUSED {
+  ::testing::AssertionResult GenerateOdexForTest(const std::string& dex_location,
+                                                 const std::string& odex_location,
+                                                 CompilerFilter::Filter filter,
+                                                 const std::vector<std::string>& extra_args = {},
+                                                 bool expect_success = true,
+                                                 bool use_fd = false,
+                                                 bool use_zip_fd = false) WARN_UNUSED {
     return GenerateOdexForTest(dex_location,
                                odex_location,
                                filter,
@@ -124,47 +124,42 @@
   bool test_accepts_odex_file_on_failure = false;
 
   template <typename T>
-  ::testing::AssertionResult GenerateOdexForTest(
-      const std::string& dex_location,
-      const std::string& odex_location,
-      CompilerFilter::Filter filter,
-      const std::vector<std::string>& extra_args,
-      bool expect_success,
-      bool use_fd,
-      bool use_zip_fd,
-      T check_oat) WARN_UNUSED {
+  ::testing::AssertionResult GenerateOdexForTest(const std::string& dex_location,
+                                                 const std::string& odex_location,
+                                                 CompilerFilter::Filter filter,
+                                                 const std::vector<std::string>& extra_args,
+                                                 bool expect_success,
+                                                 bool use_fd,
+                                                 bool use_zip_fd,
+                                                 T check_oat) WARN_UNUSED {
     std::vector<std::string> dex_locations;
     if (use_zip_fd) {
       std::string loc_arg = "--zip-location=" + dex_location;
-      CHECK(std::any_of(extra_args.begin(),
-                        extra_args.end(),
-                        [&](const std::string& s) { return s == loc_arg; }));
-      CHECK(std::any_of(extra_args.begin(),
-                        extra_args.end(),
-                        [](const std::string& s) { return StartsWith(s, "--zip-fd="); }));
+      CHECK(std::any_of(extra_args.begin(), extra_args.end(), [&](const std::string& s) {
+        return s == loc_arg;
+      }));
+      CHECK(std::any_of(extra_args.begin(), extra_args.end(), [](const std::string& s) {
+        return StartsWith(s, "--zip-fd=");
+      }));
     } else {
       dex_locations.push_back(dex_location);
     }
     std::string error_msg;
-    int status = GenerateOdexForTestWithStatus(dex_locations,
-                                               odex_location,
-                                               filter,
-                                               &error_msg,
-                                               extra_args,
-                                               use_fd);
+    int status = GenerateOdexForTestWithStatus(
+        dex_locations, odex_location, filter, &error_msg, extra_args, use_fd);
     bool success = (WIFEXITED(status) && WEXITSTATUS(status) == 0);
     if (expect_success) {
       if (!success) {
-        return ::testing::AssertionFailure()
-            << "Failed to compile odex: " << error_msg << std::endl << output_;
+        return ::testing::AssertionFailure() << "Failed to compile odex: " << error_msg << std::endl
+                                             << output_;
       }
 
       // Verify the odex file was generated as expected.
-      std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                       odex_location.c_str(),
-                                                       odex_location.c_str(),
-                                                       /*executable=*/ false,
-                                                       /*low_4gb=*/ false,
+      std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                       odex_location,
+                                                       odex_location,
+                                                       /*executable=*/false,
+                                                       /*low_4gb=*/false,
                                                        dex_location,
                                                        &error_msg));
       if (odex_file == nullptr) {
@@ -182,11 +177,11 @@
 
       if (!test_accepts_odex_file_on_failure) {
         // Verify there's no loadable odex file.
-        std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                         odex_location.c_str(),
-                                                         odex_location.c_str(),
-                                                         /*executable=*/ false,
-                                                         /*low_4gb=*/ false,
+        std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                         odex_location,
+                                                         odex_location,
+                                                         /*executable=*/false,
+                                                         /*low_4gb=*/false,
                                                          dex_location,
                                                          &error_msg));
         if (odex_file != nullptr) {
@@ -212,9 +207,8 @@
 // to what's already huge test methods).
 class Dex2oatWithExpectedFilterTest : public Dex2oatTest {
  protected:
-  void CheckFilter(
-        CompilerFilter::Filter expected ATTRIBUTE_UNUSED,
-        CompilerFilter::Filter actual) override {
+  void CheckFilter(CompilerFilter::Filter expected ATTRIBUTE_UNUSED,
+                   CompilerFilter::Filter actual) override {
     EXPECT_EQ(expected_filter_, actual);
   }
 
@@ -297,27 +291,27 @@
 };
 
 TEST_F(Dex2oatSwapTest, DoNotUseSwapDefaultSingleSmall) {
-  RunTest(/*use_fd=*/ false, /*expect_use=*/ false);
-  RunTest(/*use_fd=*/ true, /*expect_use=*/ false);
+  RunTest(/*use_fd=*/false, /*expect_use=*/false);
+  RunTest(/*use_fd=*/true, /*expect_use=*/false);
 }
 
 TEST_F(Dex2oatSwapTest, DoNotUseSwapSingle) {
-  RunTest(/*use_fd=*/ false, /*expect_use=*/ false, { "--swap-dex-size-threshold=0" });
-  RunTest(/*use_fd=*/ true, /*expect_use=*/ false, { "--swap-dex-size-threshold=0" });
+  RunTest(/*use_fd=*/false, /*expect_use=*/false, {"--swap-dex-size-threshold=0"});
+  RunTest(/*use_fd=*/true, /*expect_use=*/false, {"--swap-dex-size-threshold=0"});
 }
 
 TEST_F(Dex2oatSwapTest, DoNotUseSwapSmall) {
-  RunTest(/*use_fd=*/ false, /*expect_use=*/ false, { "--swap-dex-count-threshold=0" });
-  RunTest(/*use_fd=*/ true, /*expect_use=*/ false, { "--swap-dex-count-threshold=0" });
+  RunTest(/*use_fd=*/false, /*expect_use=*/false, {"--swap-dex-count-threshold=0"});
+  RunTest(/*use_fd=*/true, /*expect_use=*/false, {"--swap-dex-count-threshold=0"});
 }
 
 TEST_F(Dex2oatSwapTest, DoUseSwapSingleSmall) {
-  RunTest(/*use_fd=*/ false,
-          /*expect_use=*/ true,
-          { "--swap-dex-size-threshold=0", "--swap-dex-count-threshold=0" });
-  RunTest(/*use_fd=*/ true,
-          /*expect_use=*/ true,
-          { "--swap-dex-size-threshold=0", "--swap-dex-count-threshold=0" });
+  RunTest(/*use_fd=*/false,
+          /*expect_use=*/true,
+          {"--swap-dex-size-threshold=0", "--swap-dex-count-threshold=0"});
+  RunTest(/*use_fd=*/true,
+          /*expect_use=*/true,
+          {"--swap-dex-size-threshold=0", "--swap-dex-count-threshold=0"});
 }
 
 class Dex2oatSwapUseTest : public Dex2oatSwapTest {
@@ -342,7 +336,7 @@
   void GrabResult1() {
     if (!kIsTargetBuild) {
       native_alloc_1_ = ParseNativeAlloc();
-      swap_1_ = ParseSwap(/*expected=*/ false);
+      swap_1_ = ParseSwap(/*expected=*/false);
     } else {
       native_alloc_1_ = std::numeric_limits<size_t>::max();
       swap_1_ = 0;
@@ -352,7 +346,7 @@
   void GrabResult2() {
     if (!kIsTargetBuild) {
       native_alloc_2_ = ParseNativeAlloc();
-      swap_2_ = ParseSwap(/*expected=*/ true);
+      swap_2_ = ParseSwap(/*expected=*/true);
     } else {
       native_alloc_2_ = 0;
       swap_2_ = std::numeric_limits<size_t>::max();
@@ -423,16 +417,16 @@
   TEST_DISABLED_FOR_X86();
   TEST_DISABLED_FOR_X86_64();
 
-  RunTest(/*use_fd=*/ false,
-          /*expect_use=*/ false);
+  RunTest(/*use_fd=*/false,
+          /*expect_use=*/false);
   GrabResult1();
   std::string output_1 = output_;
 
   output_ = "";
 
-  RunTest(/*use_fd=*/ false,
-          /*expect_use=*/ true,
-          { "--swap-dex-size-threshold=0", "--swap-dex-count-threshold=0" });
+  RunTest(/*use_fd=*/false,
+          /*expect_use=*/true,
+          {"--swap-dex-size-threshold=0", "--swap-dex-count-threshold=0"});
   GrabResult2();
   std::string output_2 = output_;
 
@@ -478,7 +472,6 @@
     CheckResult(dex_location,
                 odex_location,
                 app_image_file,
-                filter,
                 expected_filter,
                 expect_large,
                 expect_downgrade);
@@ -487,7 +480,6 @@
   void CheckResult(const std::string& dex_location,
                    const std::string& odex_location,
                    const std::string& app_image_file,
-                   CompilerFilter::Filter filter,
                    CompilerFilter::Filter expected_filter,
                    bool expect_large,
                    bool expect_downgrade) {
@@ -496,11 +488,11 @@
     }
     // Host/target independent checks.
     std::string error_msg;
-    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                     odex_location.c_str(),
-                                                     odex_location.c_str(),
-                                                     /*executable=*/ false,
-                                                     /*low_4gb=*/ false,
+    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                     odex_location,
+                                                     odex_location,
+                                                     /*executable=*/false,
+                                                     /*low_4gb=*/false,
                                                      dex_location,
                                                      &error_msg));
     ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
@@ -525,9 +517,7 @@
       }
 
       // If the input filter was "below," it should have been used.
-      if (!CompilerFilter::IsAsGoodAs(CompilerFilter::kExtract, filter)) {
-        EXPECT_EQ(odex_file->GetCompilerFilter(), expected_filter);
-      }
+      EXPECT_EQ(odex_file->GetCompilerFilter(), expected_filter);
 
       // If expect large, make sure the app image isn't generated or is empty.
       if (file != nullptr) {
@@ -582,18 +572,15 @@
 
 TEST_F(Dex2oatVeryLargeTest, DontUseVeryLarge) {
   RunTest(CompilerFilter::kAssumeVerified, false, false);
-  RunTest(CompilerFilter::kExtract, false, false);
   RunTest(CompilerFilter::kSpeed, false, false);
 
-  RunTest(CompilerFilter::kAssumeVerified, false, false, { "--very-large-app-threshold=10000000" });
-  RunTest(CompilerFilter::kExtract, false, false, { "--very-large-app-threshold=10000000" });
-  RunTest(CompilerFilter::kSpeed, false, false, { "--very-large-app-threshold=10000000" });
+  RunTest(CompilerFilter::kAssumeVerified, false, false, {"--very-large-app-threshold=10000000"});
+  RunTest(CompilerFilter::kSpeed, false, false, {"--very-large-app-threshold=10000000"});
 }
 
 TEST_F(Dex2oatVeryLargeTest, UseVeryLarge) {
-  RunTest(CompilerFilter::kAssumeVerified, true, false, { "--very-large-app-threshold=100" });
-  RunTest(CompilerFilter::kExtract, true, false, { "--very-large-app-threshold=100" });
-  RunTest(CompilerFilter::kSpeed, true, true, { "--very-large-app-threshold=100" });
+  RunTest(CompilerFilter::kAssumeVerified, true, false, {"--very-large-app-threshold=100"});
+  RunTest(CompilerFilter::kSpeed, true, true, {"--very-large-app-threshold=100"});
 }
 
 // Regressin test for b/35665292.
@@ -618,19 +605,18 @@
     const char* location = dex_location.c_str();
     std::string error_msg;
     std::vector<std::unique_ptr<const DexFile>> dex_files;
-    const ArtDexFileLoader dex_file_loader;
+    ArtDexFileLoader dex_file_loader(location);
     ASSERT_TRUE(dex_file_loader.Open(
-        location, location, /*verify=*/true, /*verify_checksum=*/true, &error_msg, &dex_files));
+        /*verify=*/true, /*verify_checksum=*/true, &error_msg, &dex_files));
     EXPECT_EQ(dex_files.size(), 1U);
     std::unique_ptr<const DexFile>& dex_file = dex_files[0];
 
-    int profile_test_fd = open(test_profile.c_str(),
-                               O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC,
-                               0644);
+    int profile_test_fd =
+        open(test_profile.c_str(), O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0644);
     CHECK_GE(profile_test_fd, 0);
 
     ProfileCompilationInfo info;
-    std::vector<dex::TypeIndex> classes;;
+    std::vector<dex::TypeIndex> classes;
     for (size_t i = 0; i < num_classes; ++i) {
       classes.push_back(dex::TypeIndex(class_offset + 1 + i));
     }
@@ -661,12 +647,8 @@
         copy.push_back("--app-image-file=" + app_image_file_name);
       }
     }
-    ASSERT_TRUE(GenerateOdexForTest(dex_location,
-                                    odex_location,
-                                    CompilerFilter::kSpeedProfile,
-                                    copy,
-                                    expect_success,
-                                    use_fd));
+    ASSERT_TRUE(GenerateOdexForTest(
+        dex_location, odex_location, CompilerFilter::kSpeedProfile, copy, expect_success, use_fd));
     if (app_image_file != nullptr) {
       ASSERT_EQ(app_image_file->FlushCloseOrErase(), 0) << "Could not flush and close art file";
     }
@@ -707,7 +689,7 @@
   void RunTest(bool app_image) {
     std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
     std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex";
-    std::string app_image_file = app_image ? (GetOdexDir() + "/DexOdexNoOat.art"): "";
+    std::string app_image_file = app_image ? (GetOdexDir() + "/DexOdexNoOat.art") : "";
     Copy(GetDexSrc2(), dex_location);
 
     uint32_t image_file_empty_profile = 0;
@@ -715,8 +697,8 @@
       CompileProfileOdex(dex_location,
                          odex_location,
                          app_image_file,
-                         /*use_fd=*/ false,
-                         /*num_profile_classes=*/ 0);
+                         /*use_fd=*/false,
+                         /*num_profile_classes=*/0);
       CheckValidity();
       // Don't check the result since CheckResult relies on the class being in the profile.
       image_file_empty_profile = GetImageObjectSectionSize(app_image_file);
@@ -728,8 +710,8 @@
     CompileProfileOdex(dex_location,
                        odex_location,
                        app_image_file,
-                       /*use_fd=*/ false,
-                       /*num_profile_classes=*/ 1);
+                       /*use_fd=*/false,
+                       /*num_profile_classes=*/1);
     CheckValidity();
     CheckResult(dex_location, odex_location, app_image_file);
     CheckCompilerFilter(dex_location, odex_location, CompilerFilter::Filter::kSpeedProfile);
@@ -741,16 +723,15 @@
     }
   }
 
-  void CheckCompilerFilter(
-      const std::string& dex_location,
-      const std::string& odex_location,
-      CompilerFilter::Filter expected_filter) {
+  void CheckCompilerFilter(const std::string& dex_location,
+                           const std::string& odex_location,
+                           CompilerFilter::Filter expected_filter) {
     std::string error_msg;
-    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                     odex_location.c_str(),
-                                                     odex_location.c_str(),
-                                                     /*executable=*/ false,
-                                                     /*low_4gb=*/ false,
+    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                     odex_location,
+                                                     odex_location,
+                                                     /*executable=*/false,
+                                                     /*low_4gb=*/false,
                                                      dex_location,
                                                      &error_msg));
     EXPECT_EQ(odex_file->GetCompilerFilter(), expected_filter);
@@ -772,9 +753,9 @@
       CompileProfileOdex(dex_location,
                          odex_location,
                          app_image_file_name,
-                         /*use_fd=*/ true,
-                         /*num_profile_classes=*/ 1,
-                         { input_vdex, output_vdex });
+                         /*use_fd=*/true,
+                         /*num_profile_classes=*/1,
+                         {input_vdex, output_vdex});
       EXPECT_GT(vdex_file1->GetLength(), 0u);
     }
     {
@@ -784,10 +765,10 @@
       CompileProfileOdex(dex_location,
                          odex_location,
                          app_image_file_name,
-                         /*use_fd=*/ true,
-                         /*num_profile_classes=*/ 1,
-                         { input_vdex, output_vdex },
-                         /*expect_success=*/ true);
+                         /*use_fd=*/true,
+                         /*num_profile_classes=*/1,
+                         {input_vdex, output_vdex},
+                         /*expect_success=*/true);
       EXPECT_GT(vdex_file2.GetFile()->GetLength(), 0u);
     }
     ASSERT_EQ(vdex_file1->FlushCloseOrErase(), 0) << "Could not flush and close vdex file";
@@ -799,20 +780,20 @@
                    const std::string& app_image_file_name) {
     // Host/target independent checks.
     std::string error_msg;
-    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                     odex_location.c_str(),
-                                                     odex_location.c_str(),
-                                                     /*executable=*/ false,
-                                                     /*low_4gb=*/ false,
+    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                     odex_location,
+                                                     odex_location,
+                                                     /*executable=*/false,
+                                                     /*low_4gb=*/false,
                                                      dex_location,
                                                      &error_msg));
     ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
 
     const char* location = dex_location.c_str();
     std::vector<std::unique_ptr<const DexFile>> dex_files;
-    const ArtDexFileLoader dex_file_loader;
+    ArtDexFileLoader dex_file_loader(location);
     ASSERT_TRUE(dex_file_loader.Open(
-        location, location, /*verify=*/ true, /*verify_checksum=*/ true, &error_msg, &dex_files));
+        /*verify=*/true, /*verify_checksum=*/true, &error_msg, &dex_files));
     EXPECT_EQ(dex_files.size(), 1U);
     std::unique_ptr<const DexFile>& old_dex_file = dex_files[0];
 
@@ -864,13 +845,9 @@
   }
 };
 
-TEST_F(Dex2oatLayoutTest, TestLayout) {
-  RunTest(/*app_image=*/ false);
-}
+TEST_F(Dex2oatLayoutTest, TestLayout) { RunTest(/*app_image=*/false); }
 
-TEST_F(Dex2oatLayoutTest, TestLayoutAppImage) {
-  RunTest(/*app_image=*/ true);
-}
+TEST_F(Dex2oatLayoutTest, TestLayoutAppImage) { RunTest(/*app_image=*/true); }
 
 TEST_F(Dex2oatLayoutTest, TestLayoutAppImageMissingBootImage) {
   std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
@@ -881,18 +858,18 @@
   CompileProfileOdex(dex_location,
                      odex_location,
                      app_image_file,
-                     /*use_fd=*/ false,
-                     /*num_profile_classes=*/ 1,
-                     /*extra_args=*/ {"--boot-image=/nonx/boot.art"},
-                     /*expect_success=*/ true);
+                     /*use_fd=*/false,
+                     /*num_profile_classes=*/1,
+                     /*extra_args=*/{"--boot-image=/nonx/boot.art"},
+                     /*expect_success=*/true);
 
   // Verify the odex file does not require an image.
   std::string error_msg;
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   odex_location.c_str(),
-                                                   odex_location.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   odex_location,
+                                                   odex_location,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    dex_location,
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr) << "Could not open odex file: " << error_msg;
@@ -990,9 +967,7 @@
   EXPECT_EQ(image_size_wrong_checksum, image_size_empty);
 }
 
-TEST_F(Dex2oatLayoutTest, TestVdexLayout) {
-  RunTestVDex();
-}
+TEST_F(Dex2oatLayoutTest, TestVdexLayout) { RunTestVDex(); }
 
 class Dex2oatWatchdogTest : public Dex2oatTest {
  protected:
@@ -1007,16 +982,11 @@
     std::string swap_location = GetOdexDir() + "/Dex2OatSwapTest.odex.swap";
     copy.push_back("--swap-file=" + swap_location);
     copy.push_back("-j512");  // Excessive idle threads just slow down dex2oat.
-    ASSERT_TRUE(GenerateOdexForTest(dex_location,
-                                    odex_location,
-                                    CompilerFilter::kSpeed,
-                                    copy,
-                                    expect_success));
+    ASSERT_TRUE(GenerateOdexForTest(
+        dex_location, odex_location, CompilerFilter::kSpeed, copy, expect_success));
   }
 
-  std::string GetTestDexFileName() {
-    return GetDexSrc1();
-  }
+  std::string GetTestDexFileName() { return GetDexSrc1(); }
 };
 
 TEST_F(Dex2oatWatchdogTest, TestWatchdogOK) {
@@ -1024,7 +994,7 @@
   RunTest(true);
 
   // Check with ten minutes.
-  RunTest(true, { "--watchdog-timeout=600000" });
+  RunTest(true, {"--watchdog-timeout=600000"});
 }
 
 TEST_F(Dex2oatWatchdogTest, TestWatchdogTrigger) {
@@ -1038,7 +1008,7 @@
   test_accepts_odex_file_on_failure = true;
 
   // Check with ten milliseconds.
-  RunTest(false, { "--watchdog-timeout=10" });
+  RunTest(false, {"--watchdog-timeout=10"});
 }
 
 class Dex2oatReturnCodeTest : public Dex2oatTest {
@@ -1050,16 +1020,11 @@
     Copy(GetTestDexFileName(), dex_location);
 
     std::string error_msg;
-    return GenerateOdexForTestWithStatus({dex_location},
-                                         odex_location,
-                                         CompilerFilter::kSpeed,
-                                         &error_msg,
-                                         extra_args);
+    return GenerateOdexForTestWithStatus(
+        {dex_location}, odex_location, CompilerFilter::kSpeed, &error_msg, extra_args);
   }
 
-  std::string GetTestDexFileName() {
-    return GetDexSrc1();
-  }
+  std::string GetTestDexFileName() { return GetDexSrc1(); }
 };
 
 class Dex2oatClassLoaderContextTest : public Dex2oatTest {
@@ -1094,22 +1059,16 @@
                                     CompilerFilter::kVerify,
                                     extra_args,
                                     expected_success,
-                                    /*use_fd=*/ false,
-                                    /*use_zip_fd=*/ false,
+                                    /*use_fd=*/false,
+                                    /*use_zip_fd=*/false,
                                     check_oat));
   }
 
-  std::string GetUsedDexLocation() {
-    return GetScratchDir() + "/Context.jar";
-  }
+  std::string GetUsedDexLocation() { return GetScratchDir() + "/Context.jar"; }
 
-  std::string GetUsedOatLocation() {
-    return GetOdexDir() + "/Context.odex";
-  }
+  std::string GetUsedOatLocation() { return GetOdexDir() + "/Context.odex"; }
 
-  std::string GetUsedImageLocation() {
-    return GetOdexDir() + "/Context.art";
-  }
+  std::string GetUsedImageLocation() { return GetOdexDir() + "/Context.art"; }
 
   const char* kEmptyClassPathKey = "PCL[]";
 };
@@ -1131,8 +1090,8 @@
   std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Nested");
 
   std::string context = "PCL[" + dex_files[0]->GetLocation() + "]";
-  std::string expected_classpath_key = "PCL[" +
-      dex_files[0]->GetLocation() + "*" + std::to_string(dex_files[0]->GetLocationChecksum()) + "]";
+  std::string expected_classpath_key = "PCL[" + dex_files[0]->GetLocation() + "*" +
+                                       std::to_string(dex_files[0]->GetLocationChecksum()) + "]";
   RunTest(context.c_str(), expected_classpath_key.c_str(), true);
 }
 
@@ -1142,7 +1101,7 @@
 
   std::string context = "PCL[" + resource_only_classpath + "]";
   // Expect an empty context because resource only dex files cannot be open.
-  RunTest(context.c_str(), kEmptyClassPathKey , /*expected_success*/ true);
+  RunTest(context.c_str(), kEmptyClassPathKey, /*expected_success*/ true);
 }
 
 TEST_F(Dex2oatClassLoaderContextTest, ContextWithNotExistentDexFiles) {
@@ -1155,10 +1114,10 @@
   std::vector<std::unique_ptr<const DexFile>> dex_files1 = OpenTestDexFiles("Nested");
   std::vector<std::unique_ptr<const DexFile>> dex_files2 = OpenTestDexFiles("MultiDex");
 
-  std::string context = "PCL[" + GetTestDexFileName("Nested") + "];" +
-      "DLC[" + GetTestDexFileName("MultiDex") + "]";
+  std::string context =
+      "PCL[" + GetTestDexFileName("Nested") + "];" + "DLC[" + GetTestDexFileName("MultiDex") + "]";
   std::string expected_classpath_key = "PCL[" + CreateClassPathWithChecksums(dex_files1) + "];" +
-      "DLC[" + CreateClassPathWithChecksums(dex_files2) + "]";
+                                       "DLC[" + CreateClassPathWithChecksums(dex_files2) + "]";
 
   RunTest(context.c_str(), expected_classpath_key.c_str(), true);
 }
@@ -1167,10 +1126,10 @@
   std::vector<std::unique_ptr<const DexFile>> dex_files1 = OpenTestDexFiles("Nested");
   std::vector<std::unique_ptr<const DexFile>> dex_files2 = OpenTestDexFiles("MultiDex");
 
-  std::string context = "PCL[" + GetTestDexFileName("Nested") + "]" +
-      "{PCL[" + GetTestDexFileName("MultiDex") + "]}";
+  std::string context =
+      "PCL[" + GetTestDexFileName("Nested") + "]" + "{PCL[" + GetTestDexFileName("MultiDex") + "]}";
   std::string expected_classpath_key = "PCL[" + CreateClassPathWithChecksums(dex_files1) + "]" +
-      "{PCL[" + CreateClassPathWithChecksums(dex_files2) + "]}";
+                                       "{PCL[" + CreateClassPathWithChecksums(dex_files2) + "]}";
   RunTest(context.c_str(), expected_classpath_key.c_str(), true);
 }
 
@@ -1178,49 +1137,49 @@
   std::vector<std::unique_ptr<const DexFile>> dex_files1 = OpenTestDexFiles("Nested");
   std::vector<std::unique_ptr<const DexFile>> dex_files2 = OpenTestDexFiles("MultiDex");
 
-  std::string context = "PCL[" + GetTestDexFileName("Nested") + "]" +
-      "{PCL[" + GetTestDexFileName("MultiDex") + "]}";
+  std::string context =
+      "PCL[" + GetTestDexFileName("Nested") + "]" + "{PCL[" + GetTestDexFileName("MultiDex") + "]}";
   std::string expected_classpath_key = "PCL[" + CreateClassPathWithChecksums(dex_files1) + "]" +
-      "{PCL[" + CreateClassPathWithChecksums(dex_files2) + "]}";
+                                       "{PCL[" + CreateClassPathWithChecksums(dex_files2) + "]}";
   RunTest(context.c_str(),
           expected_classpath_key.c_str(),
-          /*expected_success=*/ true,
-          /*use_second_source=*/ false,
-          /*generate_image=*/ true);
+          /*expected_success=*/true,
+          /*use_second_source=*/false,
+          /*generate_image=*/true);
 }
 
 TEST_F(Dex2oatClassLoaderContextTest, ContextWithSameSharedLibrariesAndImage) {
   std::vector<std::unique_ptr<const DexFile>> dex_files1 = OpenTestDexFiles("Nested");
   std::vector<std::unique_ptr<const DexFile>> dex_files2 = OpenTestDexFiles("MultiDex");
 
-  std::string context = "PCL[" + GetTestDexFileName("Nested") + "]" +
-      "{PCL[" + GetTestDexFileName("MultiDex") + "]" +
-      "#PCL[" + GetTestDexFileName("MultiDex") + "]}";
+  std::string context = "PCL[" + GetTestDexFileName("Nested") + "]" + "{PCL[" +
+                        GetTestDexFileName("MultiDex") + "]" + "#PCL[" +
+                        GetTestDexFileName("MultiDex") + "]}";
   std::string expected_classpath_key = "PCL[" + CreateClassPathWithChecksums(dex_files1) + "]" +
-      "{PCL[" + CreateClassPathWithChecksums(dex_files2) + "]" +
-      "#PCL[" + CreateClassPathWithChecksums(dex_files2) + "]}";
+                                       "{PCL[" + CreateClassPathWithChecksums(dex_files2) + "]" +
+                                       "#PCL[" + CreateClassPathWithChecksums(dex_files2) + "]}";
   RunTest(context.c_str(),
           expected_classpath_key.c_str(),
-          /*expected_success=*/ true,
-          /*use_second_source=*/ false,
-          /*generate_image=*/ true);
+          /*expected_success=*/true,
+          /*use_second_source=*/false,
+          /*generate_image=*/true);
 }
 
 TEST_F(Dex2oatClassLoaderContextTest, ContextWithSharedLibrariesDependenciesAndImage) {
   std::vector<std::unique_ptr<const DexFile>> dex_files1 = OpenTestDexFiles("Nested");
   std::vector<std::unique_ptr<const DexFile>> dex_files2 = OpenTestDexFiles("MultiDex");
 
-  std::string context = "PCL[" + GetTestDexFileName("Nested") + "]" +
-      "{PCL[" + GetTestDexFileName("MultiDex") + "]" +
-      "{PCL[" + GetTestDexFileName("Nested") + "]}}";
+  std::string context = "PCL[" + GetTestDexFileName("Nested") + "]" + "{PCL[" +
+                        GetTestDexFileName("MultiDex") + "]" + "{PCL[" +
+                        GetTestDexFileName("Nested") + "]}}";
   std::string expected_classpath_key = "PCL[" + CreateClassPathWithChecksums(dex_files1) + "]" +
-      "{PCL[" + CreateClassPathWithChecksums(dex_files2) + "]" +
-      "{PCL[" + CreateClassPathWithChecksums(dex_files1) + "]}}";
+                                       "{PCL[" + CreateClassPathWithChecksums(dex_files2) + "]" +
+                                       "{PCL[" + CreateClassPathWithChecksums(dex_files1) + "]}}";
   RunTest(context.c_str(),
           expected_classpath_key.c_str(),
-          /*expected_success=*/ true,
-          /*use_second_source=*/ false,
-          /*generate_image=*/ true);
+          /*expected_success=*/true,
+          /*use_second_source=*/false,
+          /*generate_image=*/true);
 }
 
 class Dex2oatDeterminism : public Dex2oatTest {};
@@ -1239,12 +1198,12 @@
   ASSERT_GT(spaces.size(), 0u);
   const std::string image_location = spaces[0]->GetImageLocation();
   // Without passing in an app image, it will unload in between compilations.
-  const int res = GenerateOdexForTestWithStatus(
-      GetLibCoreDexFileNames(),
-      base_oat_name,
-      CompilerFilter::Filter::kVerify,
-      &error_msg,
-      {"--force-determinism", "--avoid-storing-invocation"});
+  const int res =
+      GenerateOdexForTestWithStatus(GetLibCoreDexFileNames(),
+                                    base_oat_name,
+                                    CompilerFilter::Filter::kVerify,
+                                    &error_msg,
+                                    {"--force-determinism", "--avoid-storing-invocation"});
   ASSERT_EQ(res, 0);
   Copy(base_oat_name, unload_oat_name);
   Copy(base_vdex_name, unload_vdex_name);
@@ -1309,21 +1268,14 @@
   std::vector<uint16_t> post_methods = {methods[0], methods[2], methods[6]};
   // Here, we build the profile from the method lists.
   ProfileCompilationInfo info;
+  info.AddMethodsForDex(static_cast<Hotness::Flag>(Hotness::kFlagHot | Hotness::kFlagStartup),
+                        dex.get(),
+                        hot_methods.begin(),
+                        hot_methods.end());
   info.AddMethodsForDex(
-      static_cast<Hotness::Flag>(Hotness::kFlagHot | Hotness::kFlagStartup),
-      dex.get(),
-      hot_methods.begin(),
-      hot_methods.end());
+      Hotness::kFlagStartup, dex.get(), startup_methods.begin(), startup_methods.end());
   info.AddMethodsForDex(
-      Hotness::kFlagStartup,
-      dex.get(),
-      startup_methods.begin(),
-      startup_methods.end());
-  info.AddMethodsForDex(
-      Hotness::kFlagPostStartup,
-      dex.get(),
-      post_methods.begin(),
-      post_methods.end());
+      Hotness::kFlagPostStartup, dex.get(), post_methods.begin(), post_methods.end());
   for (uint16_t id : hot_methods) {
     EXPECT_TRUE(info.GetMethodHotness(MethodReference(dex.get(), id)).IsHot());
     EXPECT_TRUE(info.GetMethodHotness(MethodReference(dex.get(), id)).IsStartup());
@@ -1346,16 +1298,15 @@
       oat_filename,
       CompilerFilter::Filter::kVerify,
       &error_msg,
-      {"--profile-file=" + profile_file.GetFilename(),
-       "--compact-dex-level=fast"});
+      {"--profile-file=" + profile_file.GetFilename(), "--compact-dex-level=fast"});
   EXPECT_EQ(res, 0);
 
   // Open our generated oat file.
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   oat_filename.c_str(),
-                                                   oat_filename.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   oat_filename,
+                                                   oat_filename,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    dex->GetLocation(),
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr);
@@ -1451,19 +1402,18 @@
   const std::string vdex_filename = dir + "/base.vdex";
   const std::string dex_location = GetTestDexFileName("MultiDex");
   std::string error_msg;
-  const int res = GenerateOdexForTestWithStatus(
-      { dex_location },
-      oat_filename,
-      CompilerFilter::Filter::kVerify,
-      &error_msg,
-      {"--compact-dex-level=fast"});
+  const int res = GenerateOdexForTestWithStatus({dex_location},
+                                                oat_filename,
+                                                CompilerFilter::Filter::kVerify,
+                                                &error_msg,
+                                                {"--compact-dex-level=fast"});
   EXPECT_EQ(res, 0);
   // Open our generated oat file.
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   oat_filename.c_str(),
-                                                   oat_filename.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   oat_filename,
+                                                   oat_filename,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    dex_location,
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr);
@@ -1511,20 +1461,18 @@
   std::string out_dir = GetScratchDir();
   const std::string base_oat_name = out_dir + "/base.oat";
   std::string error_msg;
-  const int res_fail = GenerateOdexForTestWithStatus(
-        {dex->GetLocation()},
-        base_oat_name,
-        CompilerFilter::Filter::kVerify,
-        &error_msg,
-        {"--abort-on-hard-verifier-error"});
+  const int res_fail = GenerateOdexForTestWithStatus({dex->GetLocation()},
+                                                     base_oat_name,
+                                                     CompilerFilter::Filter::kVerify,
+                                                     &error_msg,
+                                                     {"--abort-on-hard-verifier-error"});
   EXPECT_NE(0, res_fail);
 
-  const int res_no_fail = GenerateOdexForTestWithStatus(
-        {dex->GetLocation()},
-        base_oat_name,
-        CompilerFilter::Filter::kVerify,
-        &error_msg,
-        {"--no-abort-on-hard-verifier-error"});
+  const int res_no_fail = GenerateOdexForTestWithStatus({dex->GetLocation()},
+                                                        base_oat_name,
+                                                        CompilerFilter::Filter::kVerify,
+                                                        &error_msg,
+                                                        {"--no-abort-on-hard-verifier-error"});
   EXPECT_EQ(0, res_no_fail);
 }
 
@@ -1536,28 +1484,25 @@
   std::string out_dir = GetScratchDir();
   const std::string base_oat_name = out_dir + "/base.oat";
   size_t no_dedupe_size = 0;
-  ASSERT_TRUE(GenerateOdexForTest(dex->GetLocation(),
-                                  base_oat_name,
-                                  CompilerFilter::Filter::kSpeed,
-                                  { "--deduplicate-code=false" },
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false,
-                                  [&no_dedupe_size](const OatFile& o) {
-                                    no_dedupe_size = o.Size();
-                                  }));
+  ASSERT_TRUE(
+      GenerateOdexForTest(dex->GetLocation(),
+                          base_oat_name,
+                          CompilerFilter::Filter::kSpeed,
+                          {"--deduplicate-code=false"},
+                          /*expect_success=*/true,
+                          /*use_fd=*/false,
+                          /*use_zip_fd=*/false,
+                          [&no_dedupe_size](const OatFile& o) { no_dedupe_size = o.Size(); }));
 
   size_t dedupe_size = 0;
   ASSERT_TRUE(GenerateOdexForTest(dex->GetLocation(),
                                   base_oat_name,
                                   CompilerFilter::Filter::kSpeed,
-                                  { "--deduplicate-code=true" },
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false,
-                                  [&dedupe_size](const OatFile& o) {
-                                    dedupe_size = o.Size();
-                                  }));
+                                  {"--deduplicate-code=true"},
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/false,
+                                  [&dedupe_size](const OatFile& o) { dedupe_size = o.Size(); }));
 
   EXPECT_LT(dedupe_size, no_dedupe_size);
 }
@@ -1569,13 +1514,11 @@
   ASSERT_TRUE(GenerateOdexForTest(dex->GetLocation(),
                                   base_oat_name,
                                   CompilerFilter::Filter::kVerify,
-                                  { },
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false,
-                                  [](const OatFile& o) {
-                                    CHECK(!o.ContainsDexCode());
-                                  }));
+                                  {},
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/false,
+                                  [](const OatFile& o) { CHECK(!o.ContainsDexCode()); }));
 }
 
 TEST_F(Dex2oatTest, MissingBootImageTest) {
@@ -1594,13 +1537,12 @@
   std::string out_dir = GetScratchDir();
   const std::string base_oat_name = out_dir + "/base.oat";
   std::string error_msg;
-  int status = GenerateOdexForTestWithStatus(
-      { GetTestDexFileName("MainEmptyUncompressed") },
-      base_oat_name,
-      CompilerFilter::Filter::kVerify,
-      &error_msg,
-      { },
-      /*use_fd*/ false);
+  int status = GenerateOdexForTestWithStatus({GetTestDexFileName("MainEmptyUncompressed")},
+                                             base_oat_name,
+                                             CompilerFilter::Filter::kVerify,
+                                             &error_msg,
+                                             {},
+                                             /*use_fd*/ false);
   // Expect to fail with code 1 and not SIGSEGV or SIGABRT.
   ASSERT_TRUE(WIFEXITED(status));
   ASSERT_EQ(WEXITSTATUS(status), 1) << error_msg;
@@ -1610,13 +1552,12 @@
   std::string out_dir = GetScratchDir();
   const std::string base_oat_name = out_dir + "/base.oat";
   std::string error_msg;
-  int status = GenerateOdexForTestWithStatus(
-      { GetTestDexFileName("MainEmptyUncompressedAligned") },
-      base_oat_name,
-      CompilerFilter::Filter::kVerify,
-      &error_msg,
-      { },
-      /*use_fd*/ false);
+  int status = GenerateOdexForTestWithStatus({GetTestDexFileName("MainEmptyUncompressedAligned")},
+                                             base_oat_name,
+                                             CompilerFilter::Filter::kVerify,
+                                             &error_msg,
+                                             {},
+                                             /*use_fd*/ false);
   // Expect to fail with code 1 and not SIGSEGV or SIGABRT.
   ASSERT_TRUE(WIFEXITED(status));
   ASSERT_EQ(WEXITSTATUS(status), 1) << error_msg;
@@ -1702,20 +1643,18 @@
   ASSERT_TRUE(GenerateOdexForTest(temp_dex.GetFilename(),
                                   oat_filename,
                                   CompilerFilter::Filter::kVerify,
-                                  { },
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false,
-                                  [](const OatFile& o) {
-                                    CHECK(o.ContainsDexCode());
-                                  }));
+                                  {},
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/false,
+                                  [](const OatFile& o) { CHECK(o.ContainsDexCode()); }));
   // Open our generated oat file.
   std::string error_msg;
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   oat_filename.c_str(),
-                                                   oat_filename.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   oat_filename,
+                                                   oat_filename,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    temp_dex.GetFilename(),
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr);
@@ -1750,11 +1689,8 @@
   }
   const std::string& dex_location = apk_file.GetFilename();
   const std::string odex_location = GetOdexDir() + "/output.odex";
-  ASSERT_TRUE(GenerateOdexForTest(dex_location,
-                                  odex_location,
-                                  CompilerFilter::kVerify,
-                                  { "--compact-dex-level=fast" },
-                                  true));
+  ASSERT_TRUE(GenerateOdexForTest(
+      dex_location, odex_location, CompilerFilter::kVerify, {"--compact-dex-level=fast"}, true));
 }
 
 TEST_F(Dex2oatTest, StderrLoggerOutput) {
@@ -1767,7 +1703,7 @@
   ASSERT_TRUE(GenerateOdexForTest(dex_location,
                                   odex_location,
                                   CompilerFilter::kVerify,
-                                  { "--runtime-arg", "-Xuse-stderr-logger" },
+                                  {"--runtime-arg", "-Xuse-stderr-logger"},
                                   true));
   // Look for some random part of dex2oat logging. With the stderr logger this should be captured,
   // even on device.
@@ -1784,14 +1720,14 @@
   ASSERT_TRUE(GenerateOdexForTest(dex_location,
                                   odex_location,
                                   CompilerFilter::kVerify,
-                                  { "--compilation-reason=install" },
+                                  {"--compilation-reason=install"},
                                   true));
   std::string error_msg;
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   odex_location.c_str(),
-                                                   odex_location.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   odex_location,
+                                                   odex_location,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    dex_location,
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr);
@@ -1805,17 +1741,13 @@
   // Test file doesn't matter.
   Copy(GetDexSrc1(), dex_location);
 
-  ASSERT_TRUE(GenerateOdexForTest(dex_location,
-                                  odex_location,
-                                  CompilerFilter::kVerify,
-                                  {},
-                                  true));
+  ASSERT_TRUE(GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kVerify, {}, true));
   std::string error_msg;
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   odex_location.c_str(),
-                                                   odex_location.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   odex_location,
+                                                   odex_location,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    dex_location,
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr);
@@ -1832,25 +1764,25 @@
   ASSERT_TRUE(GenerateOdexForTest(dex_location,
                                   odex_location,
                                   CompilerFilter::Filter::kVerify,
-                                  { "--copy-dex-files=false" },
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false,
+                                  {"--copy-dex-files=false"},
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/false,
                                   [](const OatFile&) {}));
   {
     // Check the vdex doesn't have dex.
-    std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_location.c_str(),
-                                                  /*writable=*/ false,
-                                                  /*low_4gb=*/ false,
+    std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_location,
+                                                  /*writable=*/false,
+                                                  /*low_4gb=*/false,
                                                   &error_msg));
     ASSERT_TRUE(vdex != nullptr);
     EXPECT_FALSE(vdex->HasDexSection()) << output_;
   }
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   odex_location.c_str(),
-                                                   odex_location.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   odex_location,
+                                                   odex_location,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    dex_location,
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr) << dex_location;
@@ -1888,18 +1820,16 @@
     ASSERT_TRUE(GenerateOdexForTest(dex_location,
                                     odex_location,
                                     filter,
-                                    { "--dump-timings",
-                                      "--dm-file=" + dm_file.GetFilename(),
-                                      // Pass -Xuse-stderr-logger have dex2oat output in output_ on
-                                      // target.
-                                      "--runtime-arg",
-                                      "-Xuse-stderr-logger" },
-                                    /*expect_success=*/ true,
-                                    /*use_fd=*/ false,
-                                    /*use_zip_fd=*/ false,
-                                    [](const OatFile& o) {
-                                      CHECK(o.ContainsDexCode());
-                                    }));
+                                    {"--dump-timings",
+                                     "--dm-file=" + dm_file.GetFilename(),
+                                     // Pass -Xuse-stderr-logger have dex2oat output in output_ on
+                                     // target.
+                                     "--runtime-arg",
+                                     "-Xuse-stderr-logger"},
+                                    /*expect_success=*/true,
+                                    /*use_fd=*/false,
+                                    /*use_zip_fd=*/false,
+                                    [](const OatFile& o) { CHECK(o.ContainsDexCode()); }));
     // Check the output for "Fast verify", this is printed from --dump-timings.
     std::istringstream iss(output_);
     std::string line;
@@ -1939,12 +1869,11 @@
   const std::string& dex_location = invalid_dex.GetFilename();
   const std::string odex_location = GetOdexDir() + "/output.odex";
   std::string error_msg;
-  int status = GenerateOdexForTestWithStatus(
-      {dex_location},
-      odex_location,
-      CompilerFilter::kVerify,
-      &error_msg,
-      { "--compact-dex-level=fast" });
+  int status = GenerateOdexForTestWithStatus({dex_location},
+                                             odex_location,
+                                             CompilerFilter::kVerify,
+                                             &error_msg,
+                                             {"--compact-dex-level=fast"});
   ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) != 0) << status << " " << output_;
 }
 
@@ -1978,20 +1907,18 @@
   std::string error_msg;
   int status = 0u;
 
-  status = GenerateOdexForTestWithStatus(
-      { invalid_dex_zip.GetFilename() },
-      GetOdexDir() + "/output_apk.odex",
-      CompilerFilter::kVerify,
-      &error_msg,
-      { "--compact-dex-level=fast" });
+  status = GenerateOdexForTestWithStatus({invalid_dex_zip.GetFilename()},
+                                         GetOdexDir() + "/output_apk.odex",
+                                         CompilerFilter::kVerify,
+                                         &error_msg,
+                                         {"--compact-dex-level=fast"});
   ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) != 0) << status << " " << output_;
 
-  status = GenerateOdexForTestWithStatus(
-      { invalid_dex.GetFilename() },
-      GetOdexDir() + "/output.odex",
-      CompilerFilter::kVerify,
-      &error_msg,
-      { "--compact-dex-level=fast" });
+  status = GenerateOdexForTestWithStatus({invalid_dex.GetFilename()},
+                                         GetOdexDir() + "/output.odex",
+                                         CompilerFilter::kVerify,
+                                         &error_msg,
+                                         {"--compact-dex-level=fast"});
   ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) != 0) << status << " " << output_;
 }
 
@@ -2005,25 +1932,25 @@
   ASSERT_TRUE(GenerateOdexForTest(GetTestDexFileName("ManyMethods"),
                                   odex_location,
                                   CompilerFilter::Filter::kSpeedProfile,
-                                  { "--app-image-fd=" + std::to_string(app_image_file.GetFd()) },
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false,
+                                  {"--app-image-fd=" + std::to_string(app_image_file.GetFd())},
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/false,
                                   [](const OatFile&) {}));
   // Open our generated oat file.
   std::string error_msg;
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   odex_location.c_str(),
-                                                   odex_location.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   odex_location,
+                                                   odex_location,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr);
   ImageHeader header = {};
-  ASSERT_TRUE(app_image_file.GetFile()->PreadFully(
-      reinterpret_cast<void*>(&header),
-      sizeof(header),
-      /*offset*/ 0u)) << app_image_file.GetFile()->GetLength();
+  ASSERT_TRUE(app_image_file.GetFile()->PreadFully(reinterpret_cast<void*>(&header),
+                                                   sizeof(header),
+                                                   /*offset*/ 0u))
+      << app_image_file.GetFile()->GetLength();
   EXPECT_GT(header.GetImageSection(ImageHeader::kSectionObjects).Size(), 0u);
   EXPECT_EQ(header.GetImageSection(ImageHeader::kSectionArtMethods).Size(), 0u);
   EXPECT_EQ(header.GetImageSection(ImageHeader::kSectionArtFields).Size(), 0u);
@@ -2042,9 +1969,9 @@
                                   base_oat_name,
                                   CompilerFilter::Filter::kVerify,
                                   extra_args,
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ true));
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/true));
 }
 
 TEST_F(Dex2oatWithExpectedFilterTest, AppImageEmptyDex) {
@@ -2058,7 +1985,7 @@
   std::vector<uint16_t> methods;
   std::vector<dex::TypeIndex> classes;
   {
-    MutateDexFile(temp_dex.GetFile(), GetTestDexFileName("StringLiterals"), [&] (DexFile* dex) {
+    MutateDexFile(temp_dex.GetFile(), GetTestDexFileName("StringLiterals"), [&](DexFile* dex) {
       // Modify the header to make the dex file valid but empty.
       DexFile::Header* header = const_cast<DexFile::Header*>(&dex->GetHeader());
       header->string_ids_size_ = 0;
@@ -2096,20 +2023,20 @@
   ASSERT_TRUE(GenerateOdexForTest(dex_location,
                                   odex_location,
                                   CompilerFilter::Filter::kSpeedProfile,
-                                  { "--app-image-file=" + app_image_location,
-                                    "--resolve-startup-const-strings=true",
-                                    "--profile-file=" + profile_file.GetFilename()},
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false,
+                                  {"--app-image-file=" + app_image_location,
+                                   "--resolve-startup-const-strings=true",
+                                   "--profile-file=" + profile_file.GetFilename()},
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/false,
                                   [](const OatFile&) {}));
   // Open our generated oat file.
   std::string error_msg;
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   odex_location.c_str(),
-                                                   odex_location.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   odex_location,
+                                                   odex_location,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr);
 }
@@ -2144,9 +2071,9 @@
                                   base_oat_name,
                                   CompilerFilter::Filter::kVerify,
                                   extra_args,
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ true));
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/true));
 }
 
 TEST_F(Dex2oatTest, AppImageResolveStrings) {
@@ -2158,46 +2085,47 @@
   std::vector<uint16_t> methods;
   std::vector<dex::TypeIndex> classes;
   {
-    MutateDexFile(temp_dex.GetFile(), GetTestDexFileName("StringLiterals"), [&] (DexFile* dex) {
-      bool mutated_successfully = false;
-      // Change the dex instructions to make an opcode that spans past the end of the code item.
-      for (ClassAccessor accessor : dex->GetClasses()) {
-        if (accessor.GetDescriptor() == std::string("LStringLiterals$StartupClass;")) {
-          classes.push_back(accessor.GetClassIdx());
-        }
-        for (const ClassAccessor::Method& method : accessor.GetMethods()) {
-          std::string method_name(dex->GetMethodName(dex->GetMethodId(method.GetIndex())));
-          CodeItemInstructionAccessor instructions = method.GetInstructions();
-          if (method_name == "startUpMethod2") {
-            // Make an instruction that runs past the end of the code item and verify that it
-            // doesn't cause dex2oat to crash.
-            ASSERT_TRUE(instructions.begin() != instructions.end());
-            DexInstructionIterator last_instruction = instructions.begin();
-            for (auto dex_it = instructions.begin(); dex_it != instructions.end(); ++dex_it) {
-              last_instruction = dex_it;
+    MutateDexFile(
+        temp_dex.GetFile(), GetTestDexFileName("StringLiterals"), [&](DexFile* dex) {
+          bool mutated_successfully = false;
+          // Change the dex instructions to make an opcode that spans past the end of the code item.
+          for (ClassAccessor accessor : dex->GetClasses()) {
+            if (accessor.GetDescriptor() == std::string("LStringLiterals$StartupClass;")) {
+              classes.push_back(accessor.GetClassIdx());
             }
-            ASSERT_EQ(last_instruction->SizeInCodeUnits(), 1u);
-            // Set the opcode to something that will go past the end of the code item.
-            const_cast<Instruction&>(last_instruction.Inst()).SetOpcode(
-                Instruction::CONST_STRING_JUMBO);
-            mutated_successfully = true;
-            // Test that the safe iterator doesn't go past the end.
-            SafeDexInstructionIterator it2(instructions.begin(), instructions.end());
-            while (!it2.IsErrorState()) {
-              ++it2;
+            for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+              std::string method_name(dex->GetMethodName(dex->GetMethodId(method.GetIndex())));
+              CodeItemInstructionAccessor instructions = method.GetInstructions();
+              if (method_name == "startUpMethod2") {
+                // Make an instruction that runs past the end of the code item and verify that it
+                // doesn't cause dex2oat to crash.
+                ASSERT_TRUE(instructions.begin() != instructions.end());
+                DexInstructionIterator last_instruction = instructions.begin();
+                for (auto dex_it = instructions.begin(); dex_it != instructions.end(); ++dex_it) {
+                  last_instruction = dex_it;
+                }
+                ASSERT_EQ(last_instruction->SizeInCodeUnits(), 1u);
+                // Set the opcode to something that will go past the end of the code item.
+                const_cast<Instruction&>(last_instruction.Inst())
+                    .SetOpcode(Instruction::CONST_STRING_JUMBO);
+                mutated_successfully = true;
+                // Test that the safe iterator doesn't go past the end.
+                SafeDexInstructionIterator it2(instructions.begin(), instructions.end());
+                while (!it2.IsErrorState()) {
+                  ++it2;
+                }
+                EXPECT_TRUE(it2 == last_instruction);
+                EXPECT_TRUE(it2 < instructions.end());
+                methods.push_back(method.GetIndex());
+                mutated_successfully = true;
+              } else if (method_name == "startUpMethod") {
+                methods.push_back(method.GetIndex());
+              }
             }
-            EXPECT_TRUE(it2 == last_instruction);
-            EXPECT_TRUE(it2 < instructions.end());
-            methods.push_back(method.GetIndex());
-            mutated_successfully = true;
-          } else if (method_name == "startUpMethod") {
-            methods.push_back(method.GetIndex());
           }
-        }
-      }
-      CHECK(mutated_successfully)
-          << "Failed to find candidate code item with only one code unit in last instruction.";
-    });
+          CHECK(mutated_successfully)
+              << "Failed to find candidate code item with only one code unit in last instruction.";
+        });
   }
   std::unique_ptr<const DexFile> dex_file(OpenDexFile(temp_dex.GetFilename().c_str()));
   {
@@ -2216,38 +2144,36 @@
   ASSERT_TRUE(GenerateOdexForTest(dex_location,
                                   odex_location,
                                   CompilerFilter::Filter::kSpeedProfile,
-                                  { "--app-image-file=" + app_image_location,
-                                    "--resolve-startup-const-strings=true",
-                                    "--profile-file=" + profile_file.GetFilename()},
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false,
+                                  {"--app-image-file=" + app_image_location,
+                                   "--resolve-startup-const-strings=true",
+                                   "--profile-file=" + profile_file.GetFilename()},
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/false,
                                   [](const OatFile&) {}));
   // Open our generated oat file.
   std::string error_msg;
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   odex_location.c_str(),
-                                                   odex_location.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   odex_location,
+                                                   odex_location,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr);
   // Check the strings in the app image intern table only contain the "startup" strigs.
   {
     ScopedObjectAccess soa(Thread::Current());
-    std::unique_ptr<gc::space::ImageSpace> space =
-        gc::space::ImageSpace::CreateFromAppImage(app_image_location.c_str(),
-                                                  odex_file.get(),
-                                                  &error_msg);
+    std::unique_ptr<gc::space::ImageSpace> space = gc::space::ImageSpace::CreateFromAppImage(
+        app_image_location.c_str(), odex_file.get(), &error_msg);
     ASSERT_TRUE(space != nullptr) << error_msg;
     std::set<std::string> seen;
     InternTable intern_table;
-    intern_table.AddImageStringsToTable(space.get(), [&](InternTable::UnorderedSet& interns)
-        REQUIRES_SHARED(Locks::mutator_lock_) {
-      for (const GcRoot<mirror::String>& str : interns) {
-        seen.insert(str.Read()->ToModifiedUtf8());
-      }
-    });
+    intern_table.AddImageStringsToTable(
+        space.get(), [&](InternTable::UnorderedSet& interns) REQUIRES_SHARED(Locks::mutator_lock_) {
+          for (const GcRoot<mirror::String>& str : interns) {
+            seen.insert(str.Read()->ToModifiedUtf8());
+          }
+        });
     // Normal methods
     EXPECT_TRUE(seen.find("Loading ") != seen.end());
     EXPECT_TRUE(seen.find("Starting up") != seen.end());
@@ -2265,20 +2191,23 @@
     std::set<std::string> app_image_strings;
 
     MutexLock mu(Thread::Current(), *Locks::intern_table_lock_);
-    intern_table.VisitInterns([&](const GcRoot<mirror::String>& root)
-        REQUIRES_SHARED(Locks::mutator_lock_) {
-      boot_image_strings.insert(root.Read()->ToModifiedUtf8());
-    }, /*visit_boot_images=*/true, /*visit_non_boot_images=*/false);
-    intern_table.VisitInterns([&](const GcRoot<mirror::String>& root)
-        REQUIRES_SHARED(Locks::mutator_lock_) {
-      app_image_strings.insert(root.Read()->ToModifiedUtf8());
-    }, /*visit_boot_images=*/false, /*visit_non_boot_images=*/true);
+    intern_table.VisitInterns(
+        [&](const GcRoot<mirror::String>& root) REQUIRES_SHARED(Locks::mutator_lock_) {
+          boot_image_strings.insert(root.Read()->ToModifiedUtf8());
+        },
+        /*visit_boot_images=*/true,
+        /*visit_non_boot_images=*/false);
+    intern_table.VisitInterns(
+        [&](const GcRoot<mirror::String>& root) REQUIRES_SHARED(Locks::mutator_lock_) {
+          app_image_strings.insert(root.Read()->ToModifiedUtf8());
+        },
+        /*visit_boot_images=*/false,
+        /*visit_non_boot_images=*/true);
     EXPECT_EQ(boot_image_strings.size(), 0u);
     EXPECT_TRUE(app_image_strings == seen);
   }
 }
 
-
 TEST_F(Dex2oatClassLoaderContextTest, StoredClassLoaderContext) {
   std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("MultiDex");
   const std::string out_dir = GetScratchDir();
@@ -2299,31 +2228,34 @@
     expected_stored_context += "*" + std::to_string(dex_file->GetLocationChecksum());
     ++index;
   }
-  expected_stored_context +=    + "]";
+  expected_stored_context += "]";
   // The class path should not be valid and should fail being stored.
   EXPECT_TRUE(GenerateOdexForTest(GetTestDexFileName("ManyMethods"),
                                   odex_location,
                                   CompilerFilter::Filter::kVerify,
-                                  { "--class-loader-context=" + stored_context },
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false,
+                                  {"--class-loader-context=" + stored_context},
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/false,
                                   [&](const OatFile& oat_file) {
-    EXPECT_NE(oat_file.GetClassLoaderContext(), stored_context) << output_;
-    EXPECT_NE(oat_file.GetClassLoaderContext(), valid_context) << output_;
-  }));
+                                    EXPECT_NE(oat_file.GetClassLoaderContext(), stored_context)
+                                        << output_;
+                                    EXPECT_NE(oat_file.GetClassLoaderContext(), valid_context)
+                                        << output_;
+                                  }));
   // The stored context should match what we expect even though it's invalid.
-  EXPECT_TRUE(GenerateOdexForTest(GetTestDexFileName("ManyMethods"),
-                                  odex_location,
-                                  CompilerFilter::Filter::kVerify,
-                                  { "--class-loader-context=" + valid_context,
-                                    "--stored-class-loader-context=" + stored_context },
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false,
-                                  [&](const OatFile& oat_file) {
-    EXPECT_EQ(oat_file.GetClassLoaderContext(), expected_stored_context) << output_;
-  }));
+  EXPECT_TRUE(GenerateOdexForTest(
+      GetTestDexFileName("ManyMethods"),
+      odex_location,
+      CompilerFilter::Filter::kVerify,
+      {"--class-loader-context=" + valid_context,
+       "--stored-class-loader-context=" + stored_context},
+      /*expect_success=*/true,
+      /*use_fd=*/false,
+      /*use_zip_fd=*/false,
+      [&](const OatFile& oat_file) {
+        EXPECT_EQ(oat_file.GetClassLoaderContext(), expected_stored_context) << output_;
+      }));
 }
 
 class Dex2oatISAFeaturesRuntimeDetectionTest : public Dex2oatTest {
@@ -2334,15 +2266,11 @@
 
     Copy(GetTestDexFileName(), dex_location);
 
-    ASSERT_TRUE(GenerateOdexForTest(dex_location,
-                                    odex_location,
-                                    CompilerFilter::kSpeed,
-                                    extra_args));
+    ASSERT_TRUE(
+        GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, extra_args));
   }
 
-  std::string GetTestDexFileName() {
-    return GetDexSrc1();
-  }
+  std::string GetTestDexFileName() { return GetDexSrc1(); }
 };
 
 TEST_F(Dex2oatISAFeaturesRuntimeDetectionTest, TestCurrentRuntimeFeaturesAsDex2OatArguments) {
@@ -2368,20 +2296,19 @@
   std::string out_dir = GetScratchDir();
   const std::string base_oat_name = out_dir + "/base.oat";
   std::string error_msg;
-  const int res_fail = GenerateOdexForTestWithStatus(
-        {dex->GetLocation()},
-        base_oat_name,
-        CompilerFilter::Filter::kSpeed,
-        &error_msg,
-        {"--check-linkage-conditions", "--crash-on-linkage-violation"});
+  const int res_fail =
+      GenerateOdexForTestWithStatus({dex->GetLocation()},
+                                    base_oat_name,
+                                    CompilerFilter::Filter::kSpeed,
+                                    &error_msg,
+                                    {"--check-linkage-conditions", "--crash-on-linkage-violation"});
   EXPECT_NE(0, res_fail);
 
-  const int res_no_fail = GenerateOdexForTestWithStatus(
-        {dex->GetLocation()},
-        base_oat_name,
-        CompilerFilter::Filter::kSpeed,
-        &error_msg,
-        {"--check-linkage-conditions"});
+  const int res_no_fail = GenerateOdexForTestWithStatus({dex->GetLocation()},
+                                                        base_oat_name,
+                                                        CompilerFilter::Filter::kSpeed,
+                                                        &error_msg,
+                                                        {"--check-linkage-conditions"});
   EXPECT_EQ(0, res_no_fail);
 }
 
@@ -2393,19 +2320,19 @@
   ASSERT_TRUE(GenerateOdexForTest(dex->GetLocation(),
                                   base_oat_name,
                                   CompilerFilter::Filter::kSpeed,
-                                  { "--deduplicate-code=false" },
-                                  /*expect_success=*/ true,
-                                  /*use_fd=*/ false,
-                                  /*use_zip_fd=*/ false));
+                                  {"--deduplicate-code=false"},
+                                  /*expect_success=*/true,
+                                  /*use_fd=*/false,
+                                  /*use_zip_fd=*/false));
 
   // Check that we can open the oat file as executable.
   {
     std::string error_msg;
-    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                     base_oat_name.c_str(),
-                                                     base_oat_name.c_str(),
-                                                     /*executable=*/ true,
-                                                     /*low_4gb=*/ false,
+    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                     base_oat_name,
+                                                     base_oat_name,
+                                                     /*executable=*/true,
+                                                     /*low_4gb=*/false,
                                                      dex->GetLocation(),
                                                      &error_msg));
     ASSERT_TRUE(odex_file != nullptr) << error_msg;
@@ -2421,19 +2348,19 @@
     {
       std::string error_msg;
       std::unique_ptr<ElfFile> elf_file(ElfFile::Open(file.get(),
-                                                      /*writable=*/ false,
-                                                      /*program_header_only=*/ true,
-                                                      /*low_4gb=*/ false,
+                                                      /*writable=*/false,
+                                                      /*program_header_only=*/true,
+                                                      /*low_4gb=*/false,
                                                       &error_msg));
       ASSERT_TRUE(elf_file != nullptr) << error_msg;
       ASSERT_TRUE(elf_file->Load(file.get(),
-                                 /*executable=*/ false,
-                                 /*low_4gb=*/ false,
-                                 /*reservation=*/ nullptr,
-                                 &error_msg)) << error_msg;
-      const uint8_t* base_address = elf_file->Is64Bit()
-          ? elf_file->GetImpl64()->GetBaseAddress()
-          : elf_file->GetImpl32()->GetBaseAddress();
+                                 /*executable=*/false,
+                                 /*low_4gb=*/false,
+                                 /*reservation=*/nullptr,
+                                 &error_msg))
+          << error_msg;
+      const uint8_t* base_address = elf_file->Is64Bit() ? elf_file->GetImpl64()->GetBaseAddress() :
+                                                          elf_file->GetImpl32()->GetBaseAddress();
       const uint8_t* oatdata = elf_file->FindDynamicSymbolAddress("oatdata");
       ASSERT_TRUE(oatdata != nullptr);
       ASSERT_TRUE(oatdata > base_address);
@@ -2478,11 +2405,11 @@
   // Check that we reject the oat file without crashing.
   {
     std::string error_msg;
-    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                     base_oat_name.c_str(),
-                                                     base_oat_name.c_str(),
-                                                     /*executable=*/ true,
-                                                     /*low_4gb=*/ false,
+    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                     base_oat_name,
+                                                     base_oat_name,
+                                                     /*executable=*/true,
+                                                     /*low_4gb=*/false,
                                                      dex->GetLocation(),
                                                      &error_msg));
     ASSERT_FALSE(odex_file != nullptr);
diff --git a/dex2oat/dex2oat_vdex_test.cc b/dex2oat/dex2oat_vdex_test.cc
index 895e9c2..27fcc18 100644
--- a/dex2oat/dex2oat_vdex_test.cc
+++ b/dex2oat/dex2oat_vdex_test.cc
@@ -19,10 +19,8 @@
 
 #include "common_runtime_test.h"
 #include "dex2oat_environment_test.h"
-
 #include "vdex_file.h"
 #include "verifier/verifier_deps.h"
-#include "ziparchive/zip_writer.h"
 
 namespace art {
 
@@ -51,7 +49,7 @@
       args.push_back("--public-sdk=" + *public_sdk);
     }
     args.push_back("--compiler-filter=" +
-        CompilerFilter::NameOfFilter(CompilerFilter::Filter::kVerify));
+                   CompilerFilter::NameOfFilter(CompilerFilter::Filter::kVerify));
     args.push_back("--runtime-arg");
     args.push_back("-Xnorelocate");
     if (!copy_dex_files) {
@@ -67,12 +65,12 @@
     return Dex2Oat(args, &output_, &error_msg_) == 0;
   }
 
-  std::unique_ptr<VerifierDeps> GetVerifierDeps(
-        const std::string& vdex_location, const DexFile* dex_file) {
+  std::unique_ptr<VerifierDeps> GetVerifierDeps(const std::string& vdex_location,
+                                                const DexFile* dex_file) {
     // Verify the vdex file content: only the classes using public APIs should be verified.
-    std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_location.c_str(),
-                                                  /*writable=*/ false,
-                                                  /*low_4gb=*/ false,
+    std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_location,
+                                                  /*writable=*/false,
+                                                  /*low_4gb=*/false,
                                                   &error_msg_));
     // Check the vdex doesn't have dex.
     if (vdex->HasDexSection()) {
@@ -87,7 +85,7 @@
 
     std::vector<const DexFile*> dex_files;
     dex_files.push_back(dex_file);
-    std::unique_ptr<VerifierDeps> deps(new VerifierDeps(dex_files, /*output_only=*/ false));
+    std::unique_ptr<VerifierDeps> deps(new VerifierDeps(dex_files, /*output_only=*/false));
 
     if (!deps->ParseStoredData(dex_files, vdex->GetVerifierDepsData())) {
       ::testing::AssertionFailure() << error_msg_;
@@ -113,23 +111,6 @@
     return deps->GetVerifiedClasses(dex_file)[class_def_idx];
   }
 
-  void CreateDexMetadata(const std::string& vdex, const std::string& out_dm) {
-    // Read the vdex bytes.
-    std::unique_ptr<File> vdex_file(OS::OpenFileForReading(vdex.c_str()));
-    std::vector<uint8_t> data(vdex_file->GetLength());
-    ASSERT_TRUE(vdex_file->ReadFully(data.data(), data.size()));
-
-    // Zip the content.
-    FILE* file = fopen(out_dm.c_str(), "wb");
-    ZipWriter writer(file);
-    writer.StartEntry("primary.vdex", ZipWriter::kAlign32);
-    writer.WriteBytes(data.data(), data.size());
-    writer.FinishEntry();
-    writer.Finish();
-    fflush(file);
-    fclose(file);
-  }
-
   std::string GetFilename(const std::unique_ptr<const DexFile>& dex_file) {
     const std::string& str = dex_file->GetLocation();
     size_t idx = str.rfind('/');
@@ -219,11 +200,10 @@
   std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Dex2oatVdexTestDex"));
 
   // Compile the subject app using the predefined API-stubs
-  ASSERT_TRUE(RunDex2oat(
-      dex_file->GetLocation(),
-      GetOdex(dex_file),
-      /*public_sdk=*/ nullptr,
-      /*copy_dex_files=*/ true));
+  ASSERT_TRUE(RunDex2oat(dex_file->GetLocation(),
+                         GetOdex(dex_file),
+                         /*public_sdk=*/nullptr,
+                         /*copy_dex_files=*/true));
 
   // Create the .dm file with the output.
   std::string dm_file = GetScratchDir() + "/base.dm";
@@ -233,12 +213,11 @@
 
   // Recompile again with the .dm file which contains a vdex with code.
   // The compilation will pass, but dex2oat will not use the vdex file.
-  ASSERT_TRUE(RunDex2oat(
-      dex_file->GetLocation(),
-      GetOdex(dex_file, "v2"),
-      /*public_sdk=*/ nullptr,
-      /*copy_dex_files=*/ true,
-      extra_args));
+  ASSERT_TRUE(RunDex2oat(dex_file->GetLocation(),
+                         GetOdex(dex_file, "v2"),
+                         /*public_sdk=*/nullptr,
+                         /*copy_dex_files=*/true,
+                         extra_args));
 }
 
 // Check that corrupt vdex files from .dm archives are ignored.
@@ -257,12 +236,12 @@
   extra_args.push_back("--dm-file=" + dm_file);
 
   // Compile the dex file. Despite having a corrupt input .vdex, we should not crash.
-  ASSERT_TRUE(RunDex2oat(
-      dex_file->GetLocation(),
-      GetOdex(dex_file),
-      /*public_sdk=*/ nullptr,
-      /*copy_dex_files=*/ true,
-      extra_args)) << output_;
+  ASSERT_TRUE(RunDex2oat(dex_file->GetLocation(),
+                         GetOdex(dex_file),
+                         /*public_sdk=*/nullptr,
+                         /*copy_dex_files=*/true,
+                         extra_args))
+      << output_;
 }
 
 // Check that if the input dm a vdex with mismatching checksums the compilation fails
@@ -272,11 +251,10 @@
   // Generate a vdex file for Dex2oatVdexTestDex.
   std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Dex2oatVdexTestDex"));
 
-  ASSERT_TRUE(RunDex2oat(
-      dex_file->GetLocation(),
-      GetOdex(dex_file),
-      /*public_sdk=*/ nullptr,
-      /*copy_dex_files=*/ false));
+  ASSERT_TRUE(RunDex2oat(dex_file->GetLocation(),
+                         GetOdex(dex_file),
+                         /*public_sdk=*/nullptr,
+                         /*copy_dex_files=*/false));
 
   // Create the .dm file with the output.
   std::string dm_file = GetScratchDir() + "/base.dm";
@@ -287,12 +265,12 @@
   // Try to compile Main using an input dm which contains the vdex for
   // Dex2oatVdexTestDex. It should fail.
   std::unique_ptr<const DexFile> dex_file2(OpenTestDexFile("Main"));
-  ASSERT_FALSE(RunDex2oat(
-      dex_file2->GetLocation(),
-      GetOdex(dex_file2, "v2"),
-      /*public_sdk=*/ nullptr,
-      /*copy_dex_files=*/ false,
-      extra_args)) << output_;
+  ASSERT_FALSE(RunDex2oat(dex_file2->GetLocation(),
+                          GetOdex(dex_file2, "v2"),
+                          /*public_sdk=*/nullptr,
+                          /*copy_dex_files=*/false,
+                          extra_args))
+      << output_;
 }
 
 }  // namespace art
diff --git a/dex2oat/driver/compiled_method-inl.h b/dex2oat/driver/compiled_method-inl.h
new file mode 100644
index 0000000..77ac85c
--- /dev/null
+++ b/dex2oat/driver/compiled_method-inl.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_DEX2OAT_DRIVER_COMPILED_METHOD_INL_H_
+#define ART_DEX2OAT_DRIVER_COMPILED_METHOD_INL_H_
+
+#include "compiled_method.h"
+
+#include "base/array_ref.h"
+#include "base/length_prefixed_array.h"
+#include "linker/linker_patch.h"
+
+namespace art {
+
+inline ArrayRef<const uint8_t> CompiledCode::GetQuickCode() const {
+  return GetArray(quick_code_);
+}
+
+template <typename T>
+inline ArrayRef<const T> CompiledCode::GetArray(const LengthPrefixedArray<T>* array) {
+  if (array == nullptr) {
+    return ArrayRef<const T>();
+  }
+  DCHECK_NE(array->size(), 0u);
+  return ArrayRef<const T>(&array->At(0), array->size());
+}
+
+inline ArrayRef<const uint8_t> CompiledMethod::GetVmapTable() const {
+  return GetArray(vmap_table_);
+}
+
+inline ArrayRef<const uint8_t> CompiledMethod::GetCFIInfo() const {
+  return GetArray(cfi_info_);
+}
+
+inline ArrayRef<const linker::LinkerPatch> CompiledMethod::GetPatches() const {
+  return GetArray(patches_);
+}
+
+}  // namespace art
+
+#endif  // ART_DEX2OAT_DRIVER_COMPILED_METHOD_INL_H_
diff --git a/dex2oat/driver/compiled_method.cc b/dex2oat/driver/compiled_method.cc
new file mode 100644
index 0000000..0a0a005
--- /dev/null
+++ b/dex2oat/driver/compiled_method.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include "compiled_method.h"
+
+#include "driver/compiled_method_storage.h"
+#include "utils/swap_space.h"
+
+namespace art {
+
+CompiledCode::CompiledCode(CompiledMethodStorage* storage,
+                           InstructionSet instruction_set,
+                           const ArrayRef<const uint8_t>& quick_code)
+    : storage_(storage),
+      quick_code_(storage->DeduplicateCode(quick_code)),
+      packed_fields_(InstructionSetField::Encode(instruction_set)) {
+}
+
+CompiledCode::~CompiledCode() {
+  GetStorage()->ReleaseCode(quick_code_);
+}
+
+bool CompiledCode::operator==(const CompiledCode& rhs) const {
+  if (quick_code_ != nullptr) {
+    if (rhs.quick_code_ == nullptr) {
+      return false;
+    } else if (quick_code_->size() != rhs.quick_code_->size()) {
+      return false;
+    } else {
+      return std::equal(quick_code_->begin(), quick_code_->end(), rhs.quick_code_->begin());
+    }
+  }
+  return (rhs.quick_code_ == nullptr);
+}
+
+size_t CompiledCode::AlignCode(size_t offset) const {
+  return AlignCode(offset, GetInstructionSet());
+}
+
+size_t CompiledCode::AlignCode(size_t offset, InstructionSet instruction_set) {
+  return RoundUp(offset, GetInstructionSetCodeAlignment(instruction_set));
+}
+
+size_t CompiledCode::GetEntryPointAdjustment() const {
+  return GetInstructionSetEntryPointAdjustment(GetInstructionSet());
+}
+
+CompiledMethod::CompiledMethod(CompiledMethodStorage* storage,
+                               InstructionSet instruction_set,
+                               const ArrayRef<const uint8_t>& quick_code,
+                               const ArrayRef<const uint8_t>& vmap_table,
+                               const ArrayRef<const uint8_t>& cfi_info,
+                               const ArrayRef<const linker::LinkerPatch>& patches)
+    : CompiledCode(storage, instruction_set, quick_code),
+      vmap_table_(storage->DeduplicateVMapTable(vmap_table)),
+      cfi_info_(storage->DeduplicateCFIInfo(cfi_info)),
+      patches_(storage->DeduplicateLinkerPatches(patches)) {
+}
+
+CompiledMethod* CompiledMethod::SwapAllocCompiledMethod(
+    CompiledMethodStorage* storage,
+    InstructionSet instruction_set,
+    const ArrayRef<const uint8_t>& quick_code,
+    const ArrayRef<const uint8_t>& vmap_table,
+    const ArrayRef<const uint8_t>& cfi_info,
+    const ArrayRef<const linker::LinkerPatch>& patches) {
+  SwapAllocator<CompiledMethod> alloc(storage->GetSwapSpaceAllocator());
+  CompiledMethod* ret = alloc.allocate(1);
+  alloc.construct(ret,
+                  storage,
+                  instruction_set,
+                  quick_code,
+                  vmap_table,
+                  cfi_info, patches);
+  return ret;
+}
+
+void CompiledMethod::ReleaseSwapAllocatedCompiledMethod(CompiledMethodStorage* storage,
+                                                        CompiledMethod* m) {
+  SwapAllocator<CompiledMethod> alloc(storage->GetSwapSpaceAllocator());
+  alloc.destroy(m);
+  alloc.deallocate(m, 1);
+}
+
+CompiledMethod::~CompiledMethod() {
+  CompiledMethodStorage* storage = GetStorage();
+  storage->ReleaseLinkerPatches(patches_);
+  storage->ReleaseCFIInfo(cfi_info_);
+  storage->ReleaseVMapTable(vmap_table_);
+}
+
+}  // namespace art
diff --git a/dex2oat/driver/compiled_method.h b/dex2oat/driver/compiled_method.h
new file mode 100644
index 0000000..a92c757
--- /dev/null
+++ b/dex2oat/driver/compiled_method.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2011 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_DEX2OAT_DRIVER_COMPILED_METHOD_H_
+#define ART_DEX2OAT_DRIVER_COMPILED_METHOD_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "arch/instruction_set.h"
+#include "base/bit_field.h"
+#include "base/bit_utils.h"
+
+namespace art {
+
+template <typename T> class ArrayRef;
+class CompiledMethodStorage;
+template<typename T> class LengthPrefixedArray;
+
+namespace linker {
+class LinkerPatch;
+}  // namespace linker
+
+class CompiledCode {
+ public:
+  // For Quick to supply an code blob
+  CompiledCode(CompiledMethodStorage* storage,
+               InstructionSet instruction_set,
+               const ArrayRef<const uint8_t>& quick_code);
+
+  virtual ~CompiledCode();
+
+  InstructionSet GetInstructionSet() const {
+    return GetPackedField<InstructionSetField>();
+  }
+
+  ArrayRef<const uint8_t> GetQuickCode() const;
+
+  bool operator==(const CompiledCode& rhs) const;
+
+  // To align an offset from a page-aligned value to make it suitable
+  // for code storage. For example on ARM, to ensure that PC relative
+  // valu computations work out as expected.
+  size_t AlignCode(size_t offset) const;
+  static size_t AlignCode(size_t offset, InstructionSet instruction_set);
+
+  // Returns the difference between the code address and a usable PC.
+  // Mainly to cope with `kThumb2` where the lower bit must be set.
+  size_t GetEntryPointAdjustment() const;
+
+ protected:
+  static constexpr size_t kInstructionSetFieldSize =
+      MinimumBitsToStore(static_cast<size_t>(InstructionSet::kLast));
+  static constexpr size_t kNumberOfCompiledCodePackedBits = kInstructionSetFieldSize;
+  static constexpr size_t kMaxNumberOfPackedBits = sizeof(uint32_t) * kBitsPerByte;
+
+  template <typename T>
+  static ArrayRef<const T> GetArray(const LengthPrefixedArray<T>* array);
+
+  CompiledMethodStorage* GetStorage() {
+    return storage_;
+  }
+
+  template <typename BitFieldType>
+  typename BitFieldType::value_type GetPackedField() const {
+    return BitFieldType::Decode(packed_fields_);
+  }
+
+  template <typename BitFieldType>
+  void SetPackedField(typename BitFieldType::value_type value) {
+    DCHECK(IsUint<BitFieldType::size>(static_cast<uintptr_t>(value)));
+    packed_fields_ = BitFieldType::Update(value, packed_fields_);
+  }
+
+ private:
+  using InstructionSetField = BitField<InstructionSet, 0u, kInstructionSetFieldSize>;
+
+  CompiledMethodStorage* const storage_;
+
+  // Used to store the compiled code.
+  const LengthPrefixedArray<uint8_t>* const quick_code_;
+
+  uint32_t packed_fields_;
+};
+
+class CompiledMethod final : public CompiledCode {
+ public:
+  // Constructs a CompiledMethod.
+  // Note: Consider using the static allocation methods below that will allocate the CompiledMethod
+  //       in the swap space.
+  CompiledMethod(CompiledMethodStorage* storage,
+                 InstructionSet instruction_set,
+                 const ArrayRef<const uint8_t>& quick_code,
+                 const ArrayRef<const uint8_t>& vmap_table,
+                 const ArrayRef<const uint8_t>& cfi_info,
+                 const ArrayRef<const linker::LinkerPatch>& patches);
+
+  virtual ~CompiledMethod();
+
+  static CompiledMethod* SwapAllocCompiledMethod(
+      CompiledMethodStorage* storage,
+      InstructionSet instruction_set,
+      const ArrayRef<const uint8_t>& quick_code,
+      const ArrayRef<const uint8_t>& vmap_table,
+      const ArrayRef<const uint8_t>& cfi_info,
+      const ArrayRef<const linker::LinkerPatch>& patches);
+
+  static void ReleaseSwapAllocatedCompiledMethod(CompiledMethodStorage* storage, CompiledMethod* m);
+
+  bool IsIntrinsic() const {
+    return GetPackedField<IsIntrinsicField>();
+  }
+
+  // Marks the compiled method as being generated using an intrinsic codegen.
+  // Such methods have no relationships to their code items.
+  // This affects debug information generated at link time.
+  void MarkAsIntrinsic() {
+    DCHECK(!IsIntrinsic());
+    SetPackedField<IsIntrinsicField>(/* value= */ true);
+  }
+
+  ArrayRef<const uint8_t> GetVmapTable() const;
+
+  ArrayRef<const uint8_t> GetCFIInfo() const;
+
+  ArrayRef<const linker::LinkerPatch> GetPatches() const;
+
+ private:
+  static constexpr size_t kIsIntrinsicLsb = kNumberOfCompiledCodePackedBits;
+  static constexpr size_t kIsIntrinsicSize = 1u;
+  static constexpr size_t kNumberOfCompiledMethodPackedBits = kIsIntrinsicLsb + kIsIntrinsicSize;
+  static_assert(kNumberOfCompiledMethodPackedBits <= CompiledCode::kMaxNumberOfPackedBits,
+                "Too many packed fields.");
+
+  using IsIntrinsicField = BitField<bool, kIsIntrinsicLsb, kIsIntrinsicSize>;
+
+  // For quick code, holds code infos which contain stack maps, inline information, and etc.
+  const LengthPrefixedArray<uint8_t>* const vmap_table_;
+  // For quick code, a FDE entry for the debug_frame section.
+  const LengthPrefixedArray<uint8_t>* const cfi_info_;
+  // For quick code, linker patches needed by the method.
+  const LengthPrefixedArray<linker::LinkerPatch>* const patches_;
+};
+
+}  // namespace art
+
+#endif  // ART_DEX2OAT_DRIVER_COMPILED_METHOD_H_
diff --git a/dex2oat/driver/compiled_method_storage.cc b/dex2oat/driver/compiled_method_storage.cc
new file mode 100644
index 0000000..0e46f4e
--- /dev/null
+++ b/dex2oat/driver/compiled_method_storage.cc
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include <algorithm>
+#include <ostream>
+
+#include "compiled_method_storage.h"
+
+#include <android-base/logging.h>
+
+#include "base/data_hash.h"
+#include "base/utils.h"
+#include "compiled_method.h"
+#include "linker/linker_patch.h"
+#include "thread-current-inl.h"
+#include "utils/dedupe_set-inl.h"
+#include "utils/swap_space.h"
+
+namespace art {
+
+namespace {  // anonymous namespace
+
+template <typename T>
+const LengthPrefixedArray<T>* CopyArray(SwapSpace* swap_space, const ArrayRef<const T>& array) {
+  DCHECK(!array.empty());
+  SwapAllocator<uint8_t> allocator(swap_space);
+  void* storage = allocator.allocate(LengthPrefixedArray<T>::ComputeSize(array.size()));
+  LengthPrefixedArray<T>* array_copy = new(storage) LengthPrefixedArray<T>(array.size());
+  std::copy(array.begin(), array.end(), array_copy->begin());
+  return array_copy;
+}
+
+template <typename T>
+void ReleaseArray(SwapSpace* swap_space, const LengthPrefixedArray<T>* array) {
+  SwapAllocator<uint8_t> allocator(swap_space);
+  size_t size = LengthPrefixedArray<T>::ComputeSize(array->size());
+  array->~LengthPrefixedArray<T>();
+  allocator.deallocate(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(array)), size);
+}
+
+}  // anonymous namespace
+
+template <typename T, typename DedupeSetType>
+inline const LengthPrefixedArray<T>* CompiledMethodStorage::AllocateOrDeduplicateArray(
+    const ArrayRef<const T>& data,
+    DedupeSetType* dedupe_set) {
+  if (data.empty()) {
+    return nullptr;
+  } else if (!DedupeEnabled()) {
+    return CopyArray(swap_space_.get(), data);
+  } else {
+    return dedupe_set->Add(Thread::Current(), data);
+  }
+}
+
+template <typename T>
+inline void CompiledMethodStorage::ReleaseArrayIfNotDeduplicated(
+    const LengthPrefixedArray<T>* array) {
+  if (array != nullptr && !DedupeEnabled()) {
+    ReleaseArray(swap_space_.get(), array);
+  }
+}
+
+template <typename ContentType>
+class CompiledMethodStorage::DedupeHashFunc {
+ private:
+  static constexpr bool kUseMurmur3Hash = true;
+
+ public:
+  size_t operator()(const ArrayRef<ContentType>& array) const {
+    return DataHash()(array);
+  }
+};
+
+template <typename T>
+class CompiledMethodStorage::LengthPrefixedArrayAlloc {
+ public:
+  explicit LengthPrefixedArrayAlloc(SwapSpace* swap_space)
+      : swap_space_(swap_space) {
+  }
+
+  const LengthPrefixedArray<T>* Copy(const ArrayRef<const T>& array) {
+    return CopyArray(swap_space_, array);
+  }
+
+  void Destroy(const LengthPrefixedArray<T>* array) {
+    ReleaseArray(swap_space_, array);
+  }
+
+ private:
+  SwapSpace* const swap_space_;
+};
+
+class CompiledMethodStorage::ThunkMapKey {
+ public:
+  ThunkMapKey(linker::LinkerPatch::Type type, uint32_t custom_value1, uint32_t custom_value2)
+      : type_(type), custom_value1_(custom_value1), custom_value2_(custom_value2) {}
+
+  bool operator<(const ThunkMapKey& other) const {
+    if (custom_value1_ != other.custom_value1_) {
+      return custom_value1_ < other.custom_value1_;
+    }
+    if (custom_value2_ != other.custom_value2_) {
+      return custom_value2_ < other.custom_value2_;
+    }
+    return type_ < other.type_;
+  }
+
+ private:
+  linker::LinkerPatch::Type type_;
+  uint32_t custom_value1_;
+  uint32_t custom_value2_;
+};
+
+class CompiledMethodStorage::ThunkMapValue {
+ public:
+  ThunkMapValue(std::vector<uint8_t, SwapAllocator<uint8_t>>&& code,
+                const std::string& debug_name)
+      : code_(std::move(code)), debug_name_(debug_name) {}
+
+  ArrayRef<const uint8_t> GetCode() const {
+    return ArrayRef<const uint8_t>(code_);
+  }
+
+  const std::string& GetDebugName() const {
+    return debug_name_;
+  }
+
+ private:
+  std::vector<uint8_t, SwapAllocator<uint8_t>> code_;
+  std::string debug_name_;
+};
+
+CompiledMethodStorage::CompiledMethodStorage(int swap_fd)
+    : swap_space_(swap_fd == -1 ? nullptr : new SwapSpace(swap_fd, 10 * MB)),
+      dedupe_enabled_(true),
+      dedupe_code_("dedupe code", LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())),
+      dedupe_vmap_table_("dedupe vmap table",
+                         LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())),
+      dedupe_cfi_info_("dedupe cfi info", LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())),
+      dedupe_linker_patches_("dedupe cfi info",
+                             LengthPrefixedArrayAlloc<linker::LinkerPatch>(swap_space_.get())),
+      thunk_map_lock_("thunk_map_lock"),
+      thunk_map_(std::less<ThunkMapKey>(), SwapAllocator<ThunkMapValueType>(swap_space_.get())) {
+}
+
+CompiledMethodStorage::~CompiledMethodStorage() {
+  // All done by member destructors.
+}
+
+void CompiledMethodStorage::DumpMemoryUsage(std::ostream& os, bool extended) const {
+  if (swap_space_.get() != nullptr) {
+    const size_t swap_size = swap_space_->GetSize();
+    os << " swap=" << PrettySize(swap_size) << " (" << swap_size << "B)";
+  }
+  if (extended) {
+    Thread* self = Thread::Current();
+    os << "\nCode dedupe: " << dedupe_code_.DumpStats(self);
+    os << "\nVmap table dedupe: " << dedupe_vmap_table_.DumpStats(self);
+    os << "\nCFI info dedupe: " << dedupe_cfi_info_.DumpStats(self);
+  }
+}
+
+const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateCode(
+    const ArrayRef<const uint8_t>& code) {
+  return AllocateOrDeduplicateArray(code, &dedupe_code_);
+}
+
+void CompiledMethodStorage::ReleaseCode(const LengthPrefixedArray<uint8_t>* code) {
+  ReleaseArrayIfNotDeduplicated(code);
+}
+
+size_t CompiledMethodStorage::UniqueCodeEntries() const {
+  DCHECK(DedupeEnabled());
+  return dedupe_code_.Size(Thread::Current());
+}
+
+const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateVMapTable(
+    const ArrayRef<const uint8_t>& table) {
+  return AllocateOrDeduplicateArray(table, &dedupe_vmap_table_);
+}
+
+void CompiledMethodStorage::ReleaseVMapTable(const LengthPrefixedArray<uint8_t>* table) {
+  ReleaseArrayIfNotDeduplicated(table);
+}
+
+size_t CompiledMethodStorage::UniqueVMapTableEntries() const {
+  DCHECK(DedupeEnabled());
+  return dedupe_vmap_table_.Size(Thread::Current());
+}
+
+const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateCFIInfo(
+    const ArrayRef<const uint8_t>& cfi_info) {
+  return AllocateOrDeduplicateArray(cfi_info, &dedupe_cfi_info_);
+}
+
+void CompiledMethodStorage::ReleaseCFIInfo(const LengthPrefixedArray<uint8_t>* cfi_info) {
+  ReleaseArrayIfNotDeduplicated(cfi_info);
+}
+
+size_t CompiledMethodStorage::UniqueCFIInfoEntries() const {
+  DCHECK(DedupeEnabled());
+  return dedupe_cfi_info_.Size(Thread::Current());
+}
+
+const LengthPrefixedArray<linker::LinkerPatch>* CompiledMethodStorage::DeduplicateLinkerPatches(
+    const ArrayRef<const linker::LinkerPatch>& linker_patches) {
+  return AllocateOrDeduplicateArray(linker_patches, &dedupe_linker_patches_);
+}
+
+void CompiledMethodStorage::ReleaseLinkerPatches(
+    const LengthPrefixedArray<linker::LinkerPatch>* linker_patches) {
+  ReleaseArrayIfNotDeduplicated(linker_patches);
+}
+
+size_t CompiledMethodStorage::UniqueLinkerPatchesEntries() const {
+  DCHECK(DedupeEnabled());
+  return dedupe_linker_patches_.Size(Thread::Current());
+}
+
+CompiledMethodStorage::ThunkMapKey CompiledMethodStorage::GetThunkMapKey(
+    const linker::LinkerPatch& linker_patch) {
+  uint32_t custom_value1 = 0u;
+  uint32_t custom_value2 = 0u;
+  switch (linker_patch.GetType()) {
+    case linker::LinkerPatch::Type::kCallEntrypoint:
+      custom_value1 = linker_patch.EntrypointOffset();
+      break;
+    case linker::LinkerPatch::Type::kBakerReadBarrierBranch:
+      custom_value1 = linker_patch.GetBakerCustomValue1();
+      custom_value2 = linker_patch.GetBakerCustomValue2();
+      break;
+    case linker::LinkerPatch::Type::kCallRelative:
+      // No custom values.
+      break;
+    default:
+      LOG(FATAL) << "Unexpected patch type: " << linker_patch.GetType();
+      UNREACHABLE();
+  }
+  return ThunkMapKey(linker_patch.GetType(), custom_value1, custom_value2);
+}
+
+CompiledMethod* CompiledMethodStorage::CreateCompiledMethod(
+    InstructionSet instruction_set,
+    ArrayRef<const uint8_t> code,
+    ArrayRef<const uint8_t> stack_map,
+    ArrayRef<const uint8_t> cfi,
+    ArrayRef<const linker::LinkerPatch> patches,
+    bool is_intrinsic) {
+  CompiledMethod* compiled_method = CompiledMethod::SwapAllocCompiledMethod(
+      this, instruction_set, code, stack_map, cfi, patches);
+  if (is_intrinsic) {
+    compiled_method->MarkAsIntrinsic();
+  }
+  return compiled_method;
+}
+
+ArrayRef<const uint8_t> CompiledMethodStorage::GetThunkCode(const linker::LinkerPatch& linker_patch,
+                                                            /*out*/ std::string* debug_name) {
+  ThunkMapKey key = GetThunkMapKey(linker_patch);
+  MutexLock lock(Thread::Current(), thunk_map_lock_);
+  auto it = thunk_map_.find(key);
+  if (it != thunk_map_.end()) {
+    const ThunkMapValue& value = it->second;
+    if (debug_name != nullptr) {
+      *debug_name = value.GetDebugName();
+    }
+    return value.GetCode();
+  } else {
+    if (debug_name != nullptr) {
+      *debug_name = std::string();
+    }
+    return ArrayRef<const uint8_t>();
+  }
+}
+
+void CompiledMethodStorage::SetThunkCode(const linker::LinkerPatch& linker_patch,
+                                         ArrayRef<const uint8_t> code,
+                                         const std::string& debug_name) {
+  DCHECK(!code.empty());
+  ThunkMapKey key = GetThunkMapKey(linker_patch);
+  std::vector<uint8_t, SwapAllocator<uint8_t>> code_copy(
+      code.begin(), code.end(), SwapAllocator<uint8_t>(swap_space_.get()));
+  ThunkMapValue value(std::move(code_copy), debug_name);
+  MutexLock lock(Thread::Current(), thunk_map_lock_);
+  // Note: Multiple threads can try and compile the same thunk, so this may not create a new entry.
+  thunk_map_.emplace(key, std::move(value));
+}
+
+}  // namespace art
diff --git a/dex2oat/driver/compiled_method_storage.h b/dex2oat/driver/compiled_method_storage.h
new file mode 100644
index 0000000..3b0304e
--- /dev/null
+++ b/dex2oat/driver/compiled_method_storage.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015 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_DEX2OAT_DRIVER_COMPILED_METHOD_STORAGE_H_
+#define ART_DEX2OAT_DRIVER_COMPILED_METHOD_STORAGE_H_
+
+#include <iosfwd>
+#include <map>
+#include <memory>
+
+#include "base/array_ref.h"
+#include "base/length_prefixed_array.h"
+#include "base/macros.h"
+#include "driver/compiled_code_storage.h"
+#include "utils/dedupe_set.h"
+#include "utils/swap_space.h"
+
+namespace art {
+
+namespace linker {
+class LinkerPatch;
+}  // namespace linker
+
+// TODO: Find a better name. This stores both method and non-method (thunks) code.
+class CompiledMethodStorage final : public CompiledCodeStorage {
+ public:
+  explicit CompiledMethodStorage(int swap_fd);
+  ~CompiledMethodStorage();
+
+  void DumpMemoryUsage(std::ostream& os, bool extended) const;
+
+  void SetDedupeEnabled(bool dedupe_enabled) {
+    dedupe_enabled_ = dedupe_enabled;
+  }
+  bool DedupeEnabled() const {
+    return dedupe_enabled_;
+  }
+
+  SwapAllocator<void> GetSwapSpaceAllocator() {
+    return SwapAllocator<void>(swap_space_.get());
+  }
+
+  const LengthPrefixedArray<uint8_t>* DeduplicateCode(const ArrayRef<const uint8_t>& code);
+  void ReleaseCode(const LengthPrefixedArray<uint8_t>* code);
+  size_t UniqueCodeEntries() const;
+
+  const LengthPrefixedArray<uint8_t>* DeduplicateVMapTable(const ArrayRef<const uint8_t>& table);
+  void ReleaseVMapTable(const LengthPrefixedArray<uint8_t>* table);
+  size_t UniqueVMapTableEntries() const;
+
+  const LengthPrefixedArray<uint8_t>* DeduplicateCFIInfo(const ArrayRef<const uint8_t>& cfi_info);
+  void ReleaseCFIInfo(const LengthPrefixedArray<uint8_t>* cfi_info);
+  size_t UniqueCFIInfoEntries() const;
+
+  const LengthPrefixedArray<linker::LinkerPatch>* DeduplicateLinkerPatches(
+      const ArrayRef<const linker::LinkerPatch>& linker_patches);
+  void ReleaseLinkerPatches(const LengthPrefixedArray<linker::LinkerPatch>* linker_patches);
+  size_t UniqueLinkerPatchesEntries() const;
+
+  CompiledMethod* CreateCompiledMethod(InstructionSet instruction_set,
+                                       ArrayRef<const uint8_t> code,
+                                       ArrayRef<const uint8_t> stack_map,
+                                       ArrayRef<const uint8_t> cfi,
+                                       ArrayRef<const linker::LinkerPatch> patches,
+                                       bool is_intrinsic) override;
+
+  // Returns the code associated with the given patch.
+  // If the code has not been set, returns empty data.
+  // If `debug_name` is not null, stores the associated debug name in `*debug_name`.
+  ArrayRef<const uint8_t> GetThunkCode(const linker::LinkerPatch& linker_patch,
+                                       /*out*/ std::string* debug_name = nullptr) override;
+
+  // Sets the code and debug name associated with the given patch.
+  void SetThunkCode(const linker::LinkerPatch& linker_patch,
+                    ArrayRef<const uint8_t> code,
+                    const std::string& debug_name) override;
+
+ private:
+  class ThunkMapKey;
+  class ThunkMapValue;
+  using ThunkMapValueType = std::pair<const ThunkMapKey, ThunkMapValue>;
+  using ThunkMap = std::map<ThunkMapKey,
+                            ThunkMapValue,
+                            std::less<ThunkMapKey>,
+                            SwapAllocator<ThunkMapValueType>>;
+  static_assert(std::is_same<ThunkMapValueType, ThunkMap::value_type>::value, "Value type check.");
+
+  static ThunkMapKey GetThunkMapKey(const linker::LinkerPatch& linker_patch);
+
+  template <typename T, typename DedupeSetType>
+  const LengthPrefixedArray<T>* AllocateOrDeduplicateArray(const ArrayRef<const T>& data,
+                                                           DedupeSetType* dedupe_set);
+
+  template <typename T>
+  void ReleaseArrayIfNotDeduplicated(const LengthPrefixedArray<T>* array);
+
+  // DeDuplication data structures.
+  template <typename ContentType>
+  class DedupeHashFunc;
+
+  template <typename T>
+  class LengthPrefixedArrayAlloc;
+
+  template <typename T>
+  using ArrayDedupeSet = DedupeSet<ArrayRef<const T>,
+                                   LengthPrefixedArray<T>,
+                                   LengthPrefixedArrayAlloc<T>,
+                                   size_t,
+                                   DedupeHashFunc<const T>,
+                                   4>;
+
+  // Swap pool and allocator used for native allocations. May be file-backed. Needs to be first
+  // as other fields rely on this.
+  std::unique_ptr<SwapSpace> swap_space_;
+
+  bool dedupe_enabled_;
+
+  ArrayDedupeSet<uint8_t> dedupe_code_;
+  ArrayDedupeSet<uint8_t> dedupe_vmap_table_;
+  ArrayDedupeSet<uint8_t> dedupe_cfi_info_;
+  ArrayDedupeSet<linker::LinkerPatch> dedupe_linker_patches_;
+
+  Mutex thunk_map_lock_;
+  ThunkMap thunk_map_ GUARDED_BY(thunk_map_lock_);
+
+  DISALLOW_COPY_AND_ASSIGN(CompiledMethodStorage);
+};
+
+}  // namespace art
+
+#endif  // ART_DEX2OAT_DRIVER_COMPILED_METHOD_STORAGE_H_
diff --git a/compiler/driver/compiled_method_storage_test.cc b/dex2oat/driver/compiled_method_storage_test.cc
similarity index 100%
rename from compiler/driver/compiled_method_storage_test.cc
rename to dex2oat/driver/compiled_method_storage_test.cc
diff --git a/dex2oat/driver/compiler_driver.cc b/dex2oat/driver/compiler_driver.cc
index d9509b0..23bbe14 100644
--- a/dex2oat/driver/compiler_driver.cc
+++ b/dex2oat/driver/compiler_driver.cc
@@ -87,6 +87,7 @@
 #include "verifier/class_verifier.h"
 #include "verifier/verifier_deps.h"
 #include "verifier/verifier_enums.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -252,10 +253,12 @@
 
 CompilerDriver::CompilerDriver(
     const CompilerOptions* compiler_options,
+    const VerificationResults* verification_results,
     Compiler::Kind compiler_kind,
     size_t thread_count,
     int swap_fd)
     : compiler_options_(compiler_options),
+      verification_results_(verification_results),
       compiler_(),
       compiler_kind_(compiler_kind),
       number_of_soft_verifier_failures_(0),
@@ -493,7 +496,7 @@
       // Method is annotated with @NeverCompile and should not be compiled.
     } else {
       const CompilerOptions& compiler_options = driver->GetCompilerOptions();
-      const VerificationResults* results = compiler_options.GetVerificationResults();
+      const VerificationResults* results = driver->GetVerificationResults();
       DCHECK(results != nullptr);
       MethodReference method_ref(&dex_file, method_idx);
       // Don't compile class initializers unless kEverything.
@@ -1008,13 +1011,68 @@
   HashSet<std::string>* const image_classes_;
 };
 
-// Add classes which contain intrinsics methods to the list of image classes.
-static void AddClassesContainingIntrinsics(/* out */ HashSet<std::string>* image_classes) {
-#define ADD_INTRINSIC_OWNER_CLASS(_, __, ___, ____, _____, ClassName, ______, _______) \
-  image_classes->insert(ClassName);
+// Verify that classes which contain intrinsics methods are in the list of image classes.
+static void VerifyClassesContainingIntrinsicsAreImageClasses(HashSet<std::string>* image_classes) {
+#define CHECK_INTRINSIC_OWNER_CLASS(_, __, ___, ____, _____, ClassName, ______, _______) \
+  CHECK(image_classes->find(std::string_view(ClassName)) != image_classes->end());
 
-  INTRINSICS_LIST(ADD_INTRINSIC_OWNER_CLASS)
-#undef ADD_INTRINSIC_OWNER_CLASS
+  INTRINSICS_LIST(CHECK_INTRINSIC_OWNER_CLASS)
+#undef CHECK_INTRINSIC_OWNER_CLASS
+}
+
+// We need to put classes required by app class loaders to the boot image,
+// otherwise we would not be able to store app class loaders in app images.
+static void AddClassLoaderClasses(/* out */ HashSet<std::string>* image_classes) {
+  ScopedObjectAccess soa(Thread::Current());
+  // Well known classes have been loaded and shall be added to image classes
+  // by the `RecordImageClassesVisitor`. However, there are fields with array
+  // types which we need to add to the image classes explicitly.
+  ArtField* class_loader_array_fields[] = {
+      WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders,
+      // BaseDexClassLoader.sharedLibraryLoadersAfter has the same array type as above.
+      WellKnownClasses::dalvik_system_DexPathList_dexElements,
+  };
+  for (ArtField* field : class_loader_array_fields) {
+    const char* field_type_descriptor = field->GetTypeDescriptor();
+    DCHECK_EQ(field_type_descriptor[0], '[');
+    image_classes->insert(field_type_descriptor);
+  }
+}
+
+static void VerifyClassLoaderClassesAreImageClasses(/* out */ HashSet<std::string>* image_classes) {
+  ScopedObjectAccess soa(Thread::Current());
+  ScopedAssertNoThreadSuspension sants(__FUNCTION__);
+  ObjPtr<mirror::Class> class_loader_classes[] = {
+      WellKnownClasses::dalvik_system_BaseDexClassLoader.Get(),
+      WellKnownClasses::dalvik_system_DelegateLastClassLoader.Get(),
+      WellKnownClasses::dalvik_system_DexClassLoader.Get(),
+      WellKnownClasses::dalvik_system_DexFile.Get(),
+      WellKnownClasses::dalvik_system_DexPathList.Get(),
+      WellKnownClasses::dalvik_system_DexPathList__Element.Get(),
+      WellKnownClasses::dalvik_system_InMemoryDexClassLoader.Get(),
+      WellKnownClasses::dalvik_system_PathClassLoader.Get(),
+      WellKnownClasses::java_lang_BootClassLoader.Get(),
+      WellKnownClasses::java_lang_ClassLoader.Get(),
+  };
+  for (ObjPtr<mirror::Class> klass : class_loader_classes) {
+    std::string temp;
+    std::string_view descriptor = klass->GetDescriptor(&temp);
+    CHECK(image_classes->find(descriptor) != image_classes->end());
+  }
+  ArtField* class_loader_fields[] = {
+      WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList,
+      WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders,
+      WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter,
+      WellKnownClasses::dalvik_system_DexFile_cookie,
+      WellKnownClasses::dalvik_system_DexFile_fileName,
+      WellKnownClasses::dalvik_system_DexPathList_dexElements,
+      WellKnownClasses::dalvik_system_DexPathList__Element_dexFile,
+      WellKnownClasses::java_lang_ClassLoader_parent,
+  };
+  for (ArtField* field : class_loader_fields) {
+    std::string_view field_type_descriptor = field->GetTypeDescriptor();
+    CHECK(image_classes->find(field_type_descriptor) != image_classes->end());
+  }
 }
 
 // Make a list of descriptors for classes to include in the image
@@ -1028,7 +1086,10 @@
   TimingLogger::ScopedTiming t("LoadImageClasses", timings);
 
   if (GetCompilerOptions().IsBootImage()) {
-    AddClassesContainingIntrinsics(image_classes);
+    // Image classes of intrinsics are loaded and shall be added
+    // to image classes by the `RecordImageClassesVisitor`.
+    // Add classes needed for storing class loaders in app images.
+    AddClassLoaderClasses(image_classes);
   }
 
   // Make a first pass to load all classes explicitly listed in the file
@@ -1099,6 +1160,11 @@
   RecordImageClassesVisitor visitor(image_classes);
   class_linker->VisitClasses(&visitor);
 
+  if (kIsDebugBuild && GetCompilerOptions().IsBootImage()) {
+    VerifyClassesContainingIntrinsicsAreImageClasses(image_classes);
+    VerifyClassLoaderClassesAreImageClasses(image_classes);
+  }
+
   if (GetCompilerOptions().IsBootImage()) {
     CHECK(!image_classes->empty());
   }
@@ -1109,6 +1175,7 @@
                                    HashSet<std::string>* image_classes)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK_EQ(self, Thread::Current());
+  DCHECK(klass->IsResolved());
   Runtime* runtime = Runtime::Current();
   gc::Heap* heap = runtime->GetHeap();
   if (heap->ObjectIsInBootImageSpace(klass)) {
@@ -1218,12 +1285,16 @@
           data_->image_class_descriptors_->erase(it);
         }
       } else if (can_include_in_image) {
-        // Check whether it is initialized and has a clinit. They must be kept, too.
-        if (klass->IsInitialized() && klass->FindClassInitializer(
-            Runtime::Current()->GetClassLinker()->GetImagePointerSize()) != nullptr) {
-          DCHECK(!Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(klass->GetDexCache()))
-              << klass->PrettyDescriptor();
-          data_->image_classes_.push_back(data_->hs_.NewHandle(klass));
+        // Check whether the class is initialized and has a clinit or static fields.
+        // Such classes must be kept too.
+        if (klass->IsInitialized()) {
+          PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+          if (klass->FindClassInitializer(pointer_size) != nullptr ||
+              klass->NumStaticFields() != 0) {
+            DCHECK(!Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(klass->GetDexCache()))
+                << klass->PrettyDescriptor();
+            data_->image_classes_.push_back(data_->hs_.NewHandle(klass));
+          }
         }
       }
       return true;
@@ -1536,8 +1607,7 @@
       mirror::Throwable* exception = soa.Self()->GetException();
       DCHECK(exception != nullptr);
       VLOG(compiler) << "Exception during type resolution: " << exception->Dump();
-      if (exception->GetClass() ==
-              soa.Decode<mirror::Class>(WellKnownClasses::java_lang_OutOfMemoryError)) {
+      if (exception->GetClass() == WellKnownClasses::java_lang_OutOfMemoryError.Get()) {
         // There's little point continuing compilation if the heap is exhausted.
         // Trying to do so would also introduce non-deterministic compilation results.
         LOG(FATAL) << "Out of memory during type resolution for compilation";
@@ -1653,8 +1723,8 @@
 bool CompilerDriver::FastVerify(jobject jclass_loader,
                                 const std::vector<const DexFile*>& dex_files,
                                 TimingLogger* timings) {
-  verifier::VerifierDeps* verifier_deps =
-      Runtime::Current()->GetCompilerCallbacks()->GetVerifierDeps();
+  CompilerCallbacks* callbacks = Runtime::Current()->GetCompilerCallbacks();
+  verifier::VerifierDeps* verifier_deps = callbacks->GetVerifierDeps();
   // If there exist VerifierDeps that aren't the ones we just created to output, use them to verify.
   if (verifier_deps == nullptr || verifier_deps->OutputOnly()) {
     return false;
@@ -1672,6 +1742,9 @@
       class_loader,
       dex_files,
       &error_msg)) {
+    // Clear the information we have as we are going to re-verify and we do not
+    // want to keep that a class is verified.
+    verifier_deps->ClearData(dex_files);
     LOG(WARNING) << "Fast verification failed: " << error_msg;
     return false;
   }
@@ -1690,28 +1763,34 @@
     const std::vector<bool>& verified_classes = verifier_deps->GetVerifiedClasses(*dex_file);
     DCHECK_EQ(verified_classes.size(), dex_file->NumClassDefs());
     for (ClassAccessor accessor : dex_file->GetClasses()) {
-      if (verified_classes[accessor.GetClassDefIndex()]) {
-        if (compiler_only_verifies) {
-          // Just update the compiled_classes_ map. The compiler doesn't need to resolve
-          // the type.
-          ClassReference ref(dex_file, accessor.GetClassDefIndex());
-          const ClassStatus existing = ClassStatus::kNotReady;
-          ClassStateTable::InsertResult result =
-             compiled_classes_.Insert(ref, existing, ClassStatus::kVerifiedNeedsAccessChecks);
-          CHECK_EQ(result, ClassStateTable::kInsertResultSuccess) << ref.dex_file->GetLocation();
-        } else {
-          // Update the class status, so later compilation stages know they don't need to verify
-          // the class.
-          LoadAndUpdateStatus(
-              accessor, ClassStatus::kVerifiedNeedsAccessChecks, class_loader, soa.Self());
-        }
-      } else if (!compiler_only_verifies) {
-        // Make sure later compilation stages know they should not try to verify
-        // this class again.
-        LoadAndUpdateStatus(accessor,
-                            ClassStatus::kRetryVerificationAtRuntime,
-                            class_loader,
-                            soa.Self());
+      ClassStatus status = verified_classes[accessor.GetClassDefIndex()]
+          ? ClassStatus::kVerifiedNeedsAccessChecks
+          : ClassStatus::kRetryVerificationAtRuntime;
+      if (compiler_only_verifies) {
+        // Just update the compiled_classes_ map. The compiler doesn't need to resolve
+        // the type.
+        ClassReference ref(dex_file, accessor.GetClassDefIndex());
+        const ClassStatus existing = ClassStatus::kNotReady;
+        // Note: when dex files are compiled inidividually, the class may have
+        // been verified in a previous stage. This means this insertion can
+        // fail, but that's OK.
+        compiled_classes_.Insert(ref, existing, status);
+      } else {
+        // Update the class status, so later compilation stages know they don't need to verify
+        // the class.
+        LoadAndUpdateStatus(accessor, status, class_loader, soa.Self());
+      }
+
+      // Vdex marks class as unverified for two reasons only:
+      // 1. It has a hard failure, or
+      // 2. Once of its method needs lock counting.
+      //
+      // The optimizing compiler expects a method to not have a hard failure before
+      // compiling it, so for simplicity just disable any compilation of methods
+      // of these classes.
+      if (status == ClassStatus::kRetryVerificationAtRuntime) {
+        ClassReference ref(dex_file, accessor.GetClassDefIndex());
+        callbacks->AddUncompilableClass(ref);
       }
     }
   }
@@ -2100,13 +2179,16 @@
         bool too_many_encoded_fields = (!is_boot_image && !is_boot_image_extension) &&
             klass->NumStaticFields() > kMaxEncodedFields;
 
+        bool have_profile = (compiler_options.GetProfileCompilationInfo() != nullptr) &&
+            !compiler_options.GetProfileCompilationInfo()->IsEmpty();
         // If the class was not initialized, we can proceed to see if we can initialize static
         // fields. Limit the max number of encoded fields.
         if (!klass->IsInitialized() &&
             (is_app_image || is_boot_image || is_boot_image_extension) &&
-            try_initialize_with_superclasses &&
-            !too_many_encoded_fields &&
-            compiler_options.IsImageClass(descriptor)) {
+            try_initialize_with_superclasses && !too_many_encoded_fields &&
+            compiler_options.IsImageClass(descriptor) &&
+            // TODO(b/274077782): remove this test.
+            (have_profile || !is_boot_image_extension)) {
           bool can_init_static_fields = false;
           if (is_boot_image || is_boot_image_extension) {
             // We need to initialize static fields, we only do this for image classes that aren't
@@ -2232,6 +2314,19 @@
       // Make sure the class initialization did not leave any local references.
       self->GetJniEnv()->AssertLocalsEmpty();
     }
+
+    if (!klass->IsVisiblyInitialized() &&
+        (is_boot_image || is_boot_image_extension) &&
+        !compiler_options.IsPreloadedClass(PrettyDescriptor(descriptor).c_str())) {
+      klass->SetInBootImageAndNotInPreloadedClasses();
+    }
+
+    if (compiler_options.CompileArtTest()) {
+      // For stress testing and unit-testing the clinit check in compiled code feature.
+      if (kIsDebugBuild || EndsWith(std::string_view(descriptor), "$NoPreloadHolder;")) {
+        klass->SetInBootImageAndNotInPreloadedClasses();
+      }
+    }
   }
 
  private:
@@ -2493,7 +2588,8 @@
     ClassAccessor accessor(dex_file, class_def_index);
     CompilerDriver* const driver = context.GetCompiler();
     // Skip compiling classes with generic verifier failures since they will still fail at runtime
-    if (driver->GetCompilerOptions().GetVerificationResults()->IsClassRejected(ref)) {
+    DCHECK(driver->GetVerificationResults() != nullptr);
+    if (driver->GetVerificationResults()->IsClassRejected(ref)) {
       return;
     }
     // Use a scoped object access to perform to the quick SkipClass check.
diff --git a/dex2oat/driver/compiler_driver.h b/dex2oat/driver/compiler_driver.h
index ed8fc2f..7985771 100644
--- a/dex2oat/driver/compiler_driver.h
+++ b/dex2oat/driver/compiler_driver.h
@@ -85,6 +85,7 @@
   // can assume will be in the image, with null implying all available
   // classes.
   CompilerDriver(const CompilerOptions* compiler_options,
+                 const VerificationResults* verification_results,
                  Compiler::Kind compiler_kind,
                  size_t thread_count,
                  int swap_fd);
@@ -115,6 +116,10 @@
     return *compiler_options_;
   }
 
+  const VerificationResults* GetVerificationResults() const {
+    return verification_results_;
+  }
+
   Compiler* GetCompiler() const {
     return compiler_.get();
   }
@@ -295,6 +300,7 @@
                            /*inout*/ TimingLogger* timings);
 
   const CompilerOptions* const compiler_options_;
+  const VerificationResults* const verification_results_;
 
   std::unique_ptr<Compiler> compiler_;
   Compiler::Kind compiler_kind_;
diff --git a/dex2oat/driver/compiler_driver_test.cc b/dex2oat/driver/compiler_driver_test.cc
index 65aa888..759426a 100644
--- a/dex2oat/driver/compiler_driver_test.cc
+++ b/dex2oat/driver/compiler_driver_test.cc
@@ -25,6 +25,7 @@
 #include "base/casts.h"
 #include "class_linker-inl.h"
 #include "common_compiler_driver_test.h"
+#include "compiled_method-inl.h"
 #include "compiler_callbacks.h"
 #include "dex/dex_file.h"
 #include "dex/dex_file_types.h"
@@ -80,14 +81,19 @@
   void MakeExecutable(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
     CHECK(method != nullptr);
 
-    const CompiledMethod* compiled_method = nullptr;
+    const void* method_code = nullptr;
     if (!method->IsAbstract()) {
-      const DexFile& dex_file = *method->GetDexFile();
-      compiled_method =
-          compiler_driver_->GetCompiledMethod(MethodReference(&dex_file,
-                                                              method->GetDexMethodIndex()));
+      MethodReference method_ref(method->GetDexFile(), method->GetDexMethodIndex());
+      const CompiledMethod* compiled_method = compiler_driver_->GetCompiledMethod(method_ref);
+      // If the code size is 0 it means the method was skipped due to profile guided compilation.
+      if (compiled_method != nullptr && compiled_method->GetQuickCode().size() != 0u) {
+        method_code = CommonCompilerTest::MakeExecutable(compiled_method->GetQuickCode(),
+                                                         compiled_method->GetVmapTable(),
+                                                         compiled_method->GetInstructionSet());
+        LOG(INFO) << "MakeExecutable " << method->PrettyMethod() << " code=" << method_code;
+      }
     }
-    CommonCompilerTest::MakeExecutable(method, compiled_method);
+    runtime_->GetInstrumentation()->InitializeMethodsCode(method, /*aot_code=*/ method_code);
   }
 
   void MakeDexFileExecutable(jobject class_loader, const DexFile& dex_file) {
@@ -124,19 +130,15 @@
   ASSERT_TRUE(java_lang_dex_file_ != nullptr);
   const DexFile& dex = *java_lang_dex_file_;
   ObjPtr<mirror::DexCache> dex_cache = class_linker_->FindDexCache(soa.Self(), dex);
-  EXPECT_EQ(dex.NumStringIds(), dex_cache->NumStrings());
   for (size_t i = 0; i < dex_cache->NumStrings(); i++) {
     const ObjPtr<mirror::String> string = dex_cache->GetResolvedString(dex::StringIndex(i));
     EXPECT_TRUE(string != nullptr) << "string_idx=" << i;
   }
-  EXPECT_EQ(dex.NumTypeIds(), dex_cache->NumResolvedTypes());
   for (size_t i = 0; i < dex_cache->NumResolvedTypes(); i++) {
     const ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(dex::TypeIndex(i));
     EXPECT_TRUE(type != nullptr)
         << "type_idx=" << i << " " << dex.GetTypeDescriptor(dex.GetTypeId(dex::TypeIndex(i)));
   }
-  EXPECT_TRUE(dex_cache->StaticMethodSize() == dex_cache->NumResolvedMethods()
-      || dex.NumMethodIds() ==  dex_cache->NumResolvedMethods());
   for (size_t i = 0; i < dex_cache->NumResolvedMethods(); i++) {
     // FIXME: This is outdated for hash-based method array.
     ArtMethod* method = dex_cache->GetResolvedMethod(i);
@@ -147,8 +149,6 @@
         << " " << dex.GetMethodDeclaringClassDescriptor(dex.GetMethodId(i)) << " "
         << dex.GetMethodName(dex.GetMethodId(i));
   }
-  EXPECT_TRUE(dex_cache->StaticArtFieldSize() == dex_cache->NumResolvedFields()
-      || dex.NumFieldIds() ==  dex_cache->NumResolvedFields());
   for (size_t i = 0; i < dex_cache->NumResolvedFields(); i++) {
     // FIXME: This is outdated for hash-based field array.
     ArtField* field = dex_cache->GetResolvedField(i);
diff --git a/dex2oat/linker/arm/relative_patcher_arm_base.cc b/dex2oat/linker/arm/relative_patcher_arm_base.cc
index 35e799a..1cb72f6 100644
--- a/dex2oat/linker/arm/relative_patcher_arm_base.cc
+++ b/dex2oat/linker/arm/relative_patcher_arm_base.cc
@@ -17,9 +17,9 @@
 #include "linker/arm/relative_patcher_arm_base.h"
 
 #include "base/stl_util.h"
-#include "compiled_method-inl.h"
 #include "debug/method_debug_info.h"
 #include "dex/dex_file_types.h"
+#include "driver/compiled_method-inl.h"
 #include "linker/linker_patch.h"
 #include "oat.h"
 #include "oat_quick_method_header.h"
@@ -462,7 +462,7 @@
   }
   unreserved_thunks_.insert(unreserved_thunks_.begin() + index, data);
   // We may need to update the max next offset(s) if the thunk code would not fit.
-  size_t alignment = GetInstructionSetAlignment(instruction_set_);
+  size_t alignment = GetInstructionSetCodeAlignment(instruction_set_);
   if (index + 1u != unreserved_thunks_.size()) {
     // Note: Ignore the return value as we need to process previous thunks regardless.
     data->MakeSpaceBefore(*unreserved_thunks_[index + 1u], alignment);
@@ -501,7 +501,8 @@
         if (!result.first) {
           break;
         }
-        uint32_t target_offset = result.second - CompiledCode::CodeDelta(instruction_set_);
+        uint32_t target_offset =
+            result.second - GetInstructionSetEntryPointAdjustment(instruction_set_);
         if (target_offset >= patch_offset) {
           DCHECK_LE(target_offset - patch_offset, max_positive_displacement);
         } else if (patch_offset - target_offset > max_negative_displacement) {
@@ -535,7 +536,7 @@
 inline uint32_t ArmBaseRelativePatcher::CalculateMaxNextOffset(uint32_t patch_offset,
                                                                const ThunkKey& key) {
   return RoundDown(patch_offset + MaxPositiveDisplacement(key),
-                   GetInstructionSetAlignment(instruction_set_));
+                   GetInstructionSetCodeAlignment(instruction_set_));
 }
 
 inline ArmBaseRelativePatcher::ThunkData ArmBaseRelativePatcher::ThunkDataForPatch(
diff --git a/dex2oat/linker/arm/relative_patcher_thumb2.cc b/dex2oat/linker/arm/relative_patcher_thumb2.cc
index 99728cf..45a4e8b 100644
--- a/dex2oat/linker/arm/relative_patcher_thumb2.cc
+++ b/dex2oat/linker/arm/relative_patcher_thumb2.cc
@@ -22,7 +22,7 @@
 #include "art_method.h"
 #include "base/bit_utils.h"
 #include "base/malloc_arena_pool.h"
-#include "compiled_method.h"
+#include "driver/compiled_method.h"
 #include "entrypoints/quick/quick_entrypoints_enum.h"
 #include "linker/linker_patch.h"
 #include "lock_word.h"
diff --git a/dex2oat/linker/arm/relative_patcher_thumb2_test.cc b/dex2oat/linker/arm/relative_patcher_thumb2_test.cc
index 296bf61..f7abd6b 100644
--- a/dex2oat/linker/arm/relative_patcher_thumb2_test.cc
+++ b/dex2oat/linker/arm/relative_patcher_thumb2_test.cc
@@ -145,7 +145,7 @@
                                  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);
+    CHECK_EQ(distance_without_thunks % kArmCodeAlignment, 0u);
     uint32_t method1_offset =
         kTrampolineSize + CodeAlignmentSize(kTrampolineSize) + sizeof(OatQuickMethodHeader);
     AddCompiledMethod(MethodRef(1u), method1_code, method1_patches);
@@ -153,7 +153,7 @@
 
     // 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);
+    CHECK_ALIGNED(last_method_offset, kArmCodeAlignment);
     const uint32_t gap_end = last_method_offset - sizeof(OatQuickMethodHeader);
 
     // Fill the gap with intermediate methods in chunks of 2MiB and the first in [2MiB, 4MiB).
@@ -562,24 +562,25 @@
       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
+  if (sizeof(OatQuickMethodHeader) < kArmCodeAlignment) {
+    // The thunk needs to start on a kArmCodeAlignment-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
+    // is at least kArmCodeAlignment, 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 method_after_thunk_offset = GetMethodOffset(method_after_thunk_idx);
-  ASSERT_TRUE(IsAligned<kArmAlignment>(method_after_thunk_offset));
+  ASSERT_TRUE(IsAligned<kArmCodeAlignment>(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(method_after_thunk_header_offset - thunk_size, kArmAlignment);
+  uint32_t thunk_offset =
+      RoundDown(method_after_thunk_header_offset - thunk_size, kArmCodeAlignment);
   DCHECK_EQ(thunk_offset + thunk_size + CodeAlignmentSize(thunk_offset + thunk_size),
             method_after_thunk_header_offset);
-  ASSERT_TRUE(IsAligned<kArmAlignment>(thunk_offset));
+  ASSERT_TRUE(IsAligned<kArmCodeAlignment>(thunk_offset));
   uint32_t diff = thunk_offset - (method1_offset + bl_offset_in_method1 + 4u /* PC adjustment */);
   ASSERT_TRUE(IsAligned<2u>(diff));
   ASSERT_GE(diff, 16 * MB - (1u << 22));  // Simple encoding, unknown bits fit into imm10:imm11:0.
@@ -725,7 +726,7 @@
   Link();
 
   // All thunks are at the end.
-  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArmAlignment);
+  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArmCodeAlignment);
   method_idx = 0u;
   for (uint32_t base_reg : kBakerValidRegs) {
     for (uint32_t holder_reg : kBakerValidRegs) {
@@ -791,7 +792,7 @@
       // Do not check the rest of the implementation.
 
       // The next thunk follows on the next aligned offset.
-      thunk_offset += RoundUp(expected_thunk.size(), kArmAlignment);
+      thunk_offset += RoundUp(expected_thunk.size(), kArmCodeAlignment);
     }
   }
 }
@@ -823,7 +824,7 @@
   Link();
 
   // All thunks are at the end.
-  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArmAlignment);
+  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArmCodeAlignment);
   method_idx = 0u;
   for (uint32_t base_reg : kBakerValidRegs) {
     if (base_reg >= 8u) {
@@ -892,7 +893,7 @@
       // Do not check the rest of the implementation.
 
       // The next thunk follows on the next aligned offset.
-      thunk_offset += RoundUp(expected_thunk.size(), kArmAlignment);
+      thunk_offset += RoundUp(expected_thunk.size(), kArmCodeAlignment);
     }
   }
 }
@@ -945,9 +946,10 @@
 
   constexpr uint32_t expected_thunk_offset =
       kLiteralOffset1 + kPcAdjustment + /* kMaxBcondPositiveDisplacement */ ((1 << 20) - 2u);
-  static_assert(IsAligned<kArmAlignment>(expected_thunk_offset), "Target offset must be aligned.");
+  static_assert(IsAligned<kArmCodeAlignment>(expected_thunk_offset),
+                "Target offset must be aligned.");
   size_t filler1_size = expected_thunk_offset -
-                        RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArmAlignment);
+                        RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArmCodeAlignment);
   std::vector<uint8_t> raw_filler1_code = GenNops(filler1_size / 2u);
   ArrayRef<const uint8_t> filler1_code(raw_filler1_code);
   AddCompiledMethod(MethodRef(2u), filler1_code);
@@ -956,7 +958,7 @@
   AddCompiledMethod(MethodRef(3u), kNopCode);
 
   constexpr uint32_t kLiteralOffset2 = 4;
-  static_assert(IsAligned<kArmAlignment>(kLiteralOffset2 + kPcAdjustment),
+  static_assert(IsAligned<kArmCodeAlignment>(kLiteralOffset2 + kPcAdjustment),
                 "PC for BNE must be aligned.");
 
   // Allow reaching the thunk from the very beginning of a method almost 1MiB away. Backward branch
@@ -968,8 +970,8 @@
       CompileBakerOffsetThunk(/* base_reg */ 0, /* holder_reg */ 0, /* narrow */ false).size();
   size_t filler2_size =
       1 * MB - (kLiteralOffset2 + kPcAdjustment)
-             - RoundUp(thunk_size + sizeof(OatQuickMethodHeader), kArmAlignment)
-             - RoundUp(kNopCode.size() + sizeof(OatQuickMethodHeader), kArmAlignment)
+             - RoundUp(thunk_size + sizeof(OatQuickMethodHeader), kArmCodeAlignment)
+             - RoundUp(kNopCode.size() + sizeof(OatQuickMethodHeader), kArmCodeAlignment)
              - sizeof(OatQuickMethodHeader);
   std::vector<uint8_t> raw_filler2_code = GenNops(filler2_size / 2u);
   ArrayRef<const uint8_t> filler2_code(raw_filler2_code);
@@ -1013,16 +1015,18 @@
 
   constexpr uint32_t expected_thunk_offset =
       kLiteralOffset1 + kPcAdjustment + /* kMaxBcondPositiveDisplacement + 2 */ (1u << 20);
-  static_assert(IsAligned<kArmAlignment>(expected_thunk_offset), "Target offset must be aligned.");
+  static_assert(IsAligned<kArmCodeAlignment>(expected_thunk_offset),
+                "Target offset must be aligned.");
   size_t filler1_size = expected_thunk_offset -
-                        RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArmAlignment);
+                        RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArmCodeAlignment);
   std::vector<uint8_t> raw_filler1_code = GenNops(filler1_size / 2u);
   ArrayRef<const uint8_t> filler1_code(raw_filler1_code);
   AddCompiledMethod(MethodRef(2u), filler1_code);
 
   Link();
 
-  const uint32_t bne = BneWWithOffset(kLiteralOffset1, RoundUp(raw_code1.size(), kArmAlignment));
+  const uint32_t bne =
+      BneWWithOffset(kLiteralOffset1, RoundUp(raw_code1.size(), kArmCodeAlignment));
   const std::vector<uint8_t> expected_code1 = RawCode({kNopWInsn, bne, kLdrWInsn, kNopInsn});
   ASSERT_TRUE(CheckLinkedMethod(MethodRef(1), ArrayRef<const uint8_t>(expected_code1)));
 }
@@ -1043,9 +1047,10 @@
 
   constexpr uint32_t expected_thunk_offset =
       kLiteralOffset1 + kPcAdjustment + /* kMaxBcondPositiveDisplacement */ ((1 << 20) - 2u);
-  static_assert(IsAligned<kArmAlignment>(expected_thunk_offset), "Target offset must be aligned.");
+  static_assert(IsAligned<kArmCodeAlignment>(expected_thunk_offset),
+                "Target offset must be aligned.");
   size_t filler1_size = expected_thunk_offset -
-                        RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArmAlignment);
+                        RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArmCodeAlignment);
   std::vector<uint8_t> raw_filler1_code = GenNops(filler1_size / 2u);
   ArrayRef<const uint8_t> filler1_code(raw_filler1_code);
   AddCompiledMethod(MethodRef(2u), filler1_code);
@@ -1055,7 +1060,7 @@
 
   constexpr uint32_t kReachableFromOffset2 = 4;
   constexpr uint32_t kLiteralOffset2 = kReachableFromOffset2 + 2;
-  static_assert(IsAligned<kArmAlignment>(kReachableFromOffset2 + kPcAdjustment),
+  static_assert(IsAligned<kArmCodeAlignment>(kReachableFromOffset2 + kPcAdjustment),
                 "PC for BNE must be aligned.");
 
   // If not for the extra NOP, this would allow reaching the thunk from the BNE
@@ -1068,8 +1073,8 @@
       CompileBakerOffsetThunk(/* base_reg */ 0, /* holder_reg */ 0, /* narrow */ false).size();
   size_t filler2_size =
       1 * MB - (kReachableFromOffset2 + kPcAdjustment)
-             - RoundUp(thunk_size + sizeof(OatQuickMethodHeader), kArmAlignment)
-             - RoundUp(kNopCode.size() + sizeof(OatQuickMethodHeader), kArmAlignment)
+             - RoundUp(thunk_size + sizeof(OatQuickMethodHeader), kArmCodeAlignment)
+             - RoundUp(kNopCode.size() + sizeof(OatQuickMethodHeader), kArmCodeAlignment)
              - sizeof(OatQuickMethodHeader);
   std::vector<uint8_t> raw_filler2_code = GenNops(filler2_size / 2u);
   ArrayRef<const uint8_t> filler2_code(raw_filler2_code);
@@ -1091,7 +1096,7 @@
 
   const uint32_t bne_max_forward = kBneWPlus0 | 0x003f2fff;
   const uint32_t bne_last =
-      BneWWithOffset(kLiteralOffset2, RoundUp(raw_code2.size(), kArmAlignment));
+      BneWWithOffset(kLiteralOffset2, RoundUp(raw_code2.size(), kArmCodeAlignment));
   const std::vector<uint8_t> expected_code1 =
       RawCode({kNopWInsn, kNopInsn, bne_max_forward, kLdrWInsn});
   const std::vector<uint8_t> expected_code2 =
@@ -1123,7 +1128,7 @@
   Link();
 
   // All thunks are at the end.
-  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArmAlignment);
+  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArmCodeAlignment);
   method_idx = 0u;
   for (uint32_t base_reg : kBakerValidRegs) {
     ++method_idx;
@@ -1177,7 +1182,7 @@
     // Do not check the rest of the implementation.
 
     // The next thunk follows on the next aligned offset.
-    thunk_offset += RoundUp(expected_thunk.size(), kArmAlignment);
+    thunk_offset += RoundUp(expected_thunk.size(), kArmCodeAlignment);
   }
 }
 
@@ -1200,7 +1205,7 @@
   Link();
 
   // All thunks are at the end.
-  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArmAlignment);
+  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArmCodeAlignment);
   method_idx = 0u;
   for (uint32_t root_reg : kBakerValidRegs) {
     ++method_idx;
@@ -1232,7 +1237,7 @@
     // Do not check the rest of the implementation.
 
     // The next thunk follows on the next aligned offset.
-    thunk_offset += RoundUp(expected_thunk.size(), kArmAlignment);
+    thunk_offset += RoundUp(expected_thunk.size(), kArmCodeAlignment);
   }
 }
 
@@ -1255,7 +1260,7 @@
   Link();
 
   // All thunks are at the end.
-  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArmAlignment);
+  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArmCodeAlignment);
   method_idx = 0u;
   for (uint32_t root_reg : kBakerValidRegsNarrow) {
     ++method_idx;
@@ -1281,7 +1286,7 @@
     // Do not check the rest of the implementation.
 
     // The next thunk follows on the next aligned offset.
-    thunk_offset += RoundUp(expected_thunk.size(), kArmAlignment);
+    thunk_offset += RoundUp(expected_thunk.size(), kArmCodeAlignment);
   }
 }
 
@@ -1309,7 +1314,7 @@
   Link();
 
   // The thunk is right after the method code.
-  DCHECK_ALIGNED(1 * MB, kArmAlignment);
+  DCHECK_ALIGNED(1 * MB, kArmCodeAlignment);
   std::vector<uint8_t> expected_code;
   for (size_t i = 0; i != num_patches; ++i) {
     PushBackInsn(&expected_code, ldr);
@@ -1343,7 +1348,7 @@
   // Add a method with the right size that the method code for the next one starts 1MiB
   // after code for method 1.
   size_t filler_size =
-      1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArmAlignment)
+      1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArmCodeAlignment)
              - sizeof(OatQuickMethodHeader);
   std::vector<uint8_t> filler_code = GenNops(filler_size / 2u);
   ++method_idx;
@@ -1358,16 +1363,16 @@
   }
 
   // Add 2 Baker GC root patches to the last method, one that would allow the thunk at
-  // 1MiB + kArmAlignment, i.e. kArmAlignment after the method call thunk, and the
-  // second that needs it kArmAlignment after that. Given the size of the GC root thunk
-  // is more than the space required by the method call thunk plus kArmAlignment,
+  // 1MiB + kArmCodeAlignment, i.e. kArmCodeAlignment after the method call thunk, and the
+  // second that needs it kArmCodeAlignment after that. Given the size of the GC root thunk
+  // is more than the space required by the method call thunk plus kArmCodeAlignment,
   // this pushes the first GC root thunk's pending MaxNextOffset() before the method call
   // thunk's pending MaxNextOffset() which needs to be adjusted.
-  ASSERT_LT(RoundUp(CompileMethodCallThunk().size(), kArmAlignment) + kArmAlignment,
+  ASSERT_LT(RoundUp(CompileMethodCallThunk().size(), kArmCodeAlignment) + kArmCodeAlignment,
             CompileBakerGcRootThunk(/* root_reg */ 0, /* narrow */ false).size());
-  static_assert(kArmAlignment == 8, "Code below assumes kArmAlignment == 8");
-  constexpr size_t kBakerLiteralOffset1 = kArmAlignment + 2u - kPcAdjustment;
-  constexpr size_t kBakerLiteralOffset2 = kBakerLiteralOffset1 + kArmAlignment;
+  static_assert(kArmCodeAlignment == 8, "Code below assumes kArmCodeAlignment == 8");
+  constexpr size_t kBakerLiteralOffset1 = kArmCodeAlignment + 2u - kPcAdjustment;
+  constexpr size_t kBakerLiteralOffset2 = kBakerLiteralOffset1 + kArmCodeAlignment;
   // Use offset = 0, base_reg = 0, the LDR is simply `kLdrWInsn | (root_reg << 12)`.
   const uint32_t ldr1 = kLdrWInsn | (/* root_reg */ 1 << 12);
   const uint32_t ldr2 = kLdrWInsn | (/* root_reg */ 2 << 12);
diff --git a/dex2oat/linker/arm64/relative_patcher_arm64.cc b/dex2oat/linker/arm64/relative_patcher_arm64.cc
index 4028f75..6b84472 100644
--- a/dex2oat/linker/arm64/relative_patcher_arm64.cc
+++ b/dex2oat/linker/arm64/relative_patcher_arm64.cc
@@ -21,7 +21,7 @@
 #include "art_method.h"
 #include "base/bit_utils.h"
 #include "base/malloc_arena_pool.h"
-#include "compiled_method-inl.h"
+#include "driver/compiled_method-inl.h"
 #include "driver/compiler_driver.h"
 #include "entrypoints/quick/quick_entrypoints_enum.h"
 #include "heap_poisoning.h"
@@ -251,7 +251,7 @@
   } else {
     if ((insn & 0xfffffc00) == 0x91000000) {
       // ADD immediate, 64-bit with imm12 == 0 (unset).
-      if (!kEmitCompilerReadBarrier) {
+      if (!gUseReadBarrier) {
         DCHECK(patch.GetType() == LinkerPatch::Type::kIntrinsicReference ||
                patch.GetType() == LinkerPatch::Type::kMethodRelative ||
                patch.GetType() == LinkerPatch::Type::kTypeRelative ||
diff --git a/dex2oat/linker/arm64/relative_patcher_arm64_test.cc b/dex2oat/linker/arm64/relative_patcher_arm64_test.cc
index 8bae5d4..ce61f43 100644
--- a/dex2oat/linker/arm64/relative_patcher_arm64_test.cc
+++ b/dex2oat/linker/arm64/relative_patcher_arm64_test.cc
@@ -112,7 +112,7 @@
                                  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 % kArm64Alignment, 0u);
+    CHECK_EQ(distance_without_thunks % kArm64CodeAlignment, 0u);
     uint32_t method1_offset =
         kTrampolineSize + CodeAlignmentSize(kTrampolineSize) + sizeof(OatQuickMethodHeader);
     AddCompiledMethod(MethodRef(1u), method1_code, method1_patches);
@@ -120,7 +120,7 @@
 
     // 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);
+    CHECK_ALIGNED(last_method_offset, kArm64CodeAlignment);
     const uint32_t gap_end = last_method_offset - sizeof(OatQuickMethodHeader);
 
     // Fill the gap with intermediate methods in chunks of 2MiB and the first in [2MiB, 4MiB).
@@ -733,24 +733,26 @@
       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) < kArm64Alignment) {
-    // The thunk needs to start on a kArm64Alignment-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 kArm64Alignment, the thunk start shall fit between the previous filler method
-    // and that address. Otherwise, it shall be inserted before that filler method.
+  if (sizeof(OatQuickMethodHeader) < kArm64CodeAlignment) {
+    // The thunk needs to start on a kArm64CodeAlignment-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 kArm64CodeAlignment, 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 method_after_thunk_offset = GetMethodOffset(method_after_thunk_idx);
-  ASSERT_TRUE(IsAligned<kArm64Alignment>(method_after_thunk_offset));
+  ASSERT_TRUE(IsAligned<kArm64CodeAlignment>(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(method_after_thunk_header_offset - thunk_size, kArm64Alignment);
+  uint32_t thunk_offset = RoundDown(
+            method_after_thunk_header_offset - thunk_size, kArm64CodeAlignment);
   DCHECK_EQ(thunk_offset + thunk_size + CodeAlignmentSize(thunk_offset + thunk_size),
             method_after_thunk_header_offset);
-  ASSERT_TRUE(IsAligned<kArm64Alignment>(thunk_offset));
+  ASSERT_TRUE(IsAligned<kArm64CodeAlignment>(thunk_offset));
   uint32_t diff = thunk_offset - (method1_offset + bl_offset_in_method1);
   ASSERT_TRUE(IsAligned<4u>(diff));
   ASSERT_LT(diff, 128 * MB);
@@ -1065,7 +1067,8 @@
   Link();
 
   // All thunks are at the end.
-  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArm64Alignment);
+  uint32_t thunk_offset =
+      GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArm64CodeAlignment);
   method_idx = 0u;
   for (uint32_t base_reg : valid_regs) {
     for (uint32_t holder_reg : valid_regs) {
@@ -1118,7 +1121,7 @@
       // Do not check the rest of the implementation.
 
       // The next thunk follows on the next aligned offset.
-      thunk_offset += RoundUp(expected_thunk.size(), kArm64Alignment);
+      thunk_offset += RoundUp(expected_thunk.size(), kArm64CodeAlignment);
     }
   }
 }
@@ -1155,7 +1158,7 @@
   // Allow thunk at 1MiB offset from the start of the method above. Literal offset being 4
   // allows the branch to reach that thunk.
   size_t filler1_size =
-      1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64Alignment);
+      1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment);
   std::vector<uint8_t> raw_filler1_code = GenNops(filler1_size / 4u);
   ArrayRef<const uint8_t> filler1_code(raw_filler1_code);
   AddCompiledMethod(MethodRef(2u), filler1_code);
@@ -1170,8 +1173,8 @@
   //   - method 4 header (let there be no padding between method 4 code and method 5 pre-header).
   size_t thunk_size = CompileBakerOffsetThunk(/* base_reg */ 0, /* holder_reg */ 0).size();
   size_t filler2_size =
-      1 * MB - RoundUp(thunk_size + sizeof(OatQuickMethodHeader), kArm64Alignment)
-             - RoundUp(kNopCode.size() + sizeof(OatQuickMethodHeader), kArm64Alignment)
+      1 * MB - RoundUp(thunk_size + sizeof(OatQuickMethodHeader), kArm64CodeAlignment)
+             - RoundUp(kNopCode.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment)
              - sizeof(OatQuickMethodHeader);
   std::vector<uint8_t> raw_filler2_code = GenNops(filler2_size / 4u);
   ArrayRef<const uint8_t> filler2_code(raw_filler2_code);
@@ -1215,14 +1218,14 @@
   // Allow thunk at 1MiB offset from the start of the method above. Literal offset being 4
   // allows the branch to reach that thunk.
   size_t filler1_size =
-      1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64Alignment);
+      1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment);
   std::vector<uint8_t> raw_filler1_code = GenNops(filler1_size / 4u);
   ArrayRef<const uint8_t> filler1_code(raw_filler1_code);
   AddCompiledMethod(MethodRef(2u), filler1_code);
 
   Link();
 
-  const uint32_t cbnz_offset = RoundUp(raw_code1.size(), kArm64Alignment) - kLiteralOffset1;
+  const uint32_t cbnz_offset = RoundUp(raw_code1.size(), kArm64CodeAlignment) - kLiteralOffset1;
   const uint32_t cbnz = kCbnzIP1Plus0Insn | (cbnz_offset << (5 - 2));
   const std::vector<uint8_t> expected_code1 = RawCode({cbnz, kLdrWInsn, kNopInsn});
   ASSERT_TRUE(CheckLinkedMethod(MethodRef(1), ArrayRef<const uint8_t>(expected_code1)));
@@ -1244,7 +1247,7 @@
   // Allow thunk at 1MiB offset from the start of the method above. Literal offset being 4
   // allows the branch to reach that thunk.
   size_t filler1_size =
-      1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64Alignment);
+      1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment);
   std::vector<uint8_t> raw_filler1_code = GenNops(filler1_size / 4u);
   ArrayRef<const uint8_t> filler1_code(raw_filler1_code);
   AddCompiledMethod(MethodRef(2u), filler1_code);
@@ -1259,8 +1262,8 @@
   //   - method 4 header (let there be no padding between method 4 code and method 5 pre-header).
   size_t thunk_size = CompileBakerOffsetThunk(/* base_reg */ 0, /* holder_reg */ 0).size();
   size_t filler2_size =
-      1 * MB - RoundUp(thunk_size + sizeof(OatQuickMethodHeader), kArm64Alignment)
-             - RoundUp(kNopCode.size() + sizeof(OatQuickMethodHeader), kArm64Alignment)
+      1 * MB - RoundUp(thunk_size + sizeof(OatQuickMethodHeader), kArm64CodeAlignment)
+             - RoundUp(kNopCode.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment)
              - sizeof(OatQuickMethodHeader);
   std::vector<uint8_t> raw_filler2_code = GenNops(filler2_size / 4u);
   ArrayRef<const uint8_t> filler2_code(raw_filler2_code);
@@ -1278,7 +1281,8 @@
   Link();
 
   const uint32_t cbnz_max_forward = kCbnzIP1Plus0Insn | 0x007fffe0;
-  const uint32_t cbnz_last_offset = RoundUp(raw_code2.size(), kArm64Alignment) - kLiteralOffset2;
+  const uint32_t cbnz_last_offset =
+      RoundUp(raw_code2.size(), kArm64CodeAlignment) - kLiteralOffset2;
   const uint32_t cbnz_last = kCbnzIP1Plus0Insn | (cbnz_last_offset << (5 - 2));
   const std::vector<uint8_t> expected_code1 = RawCode({kNopInsn, cbnz_max_forward, kLdrWInsn});
   const std::vector<uint8_t> expected_code2 = RawCode({kNopInsn, cbnz_last, kLdrWInsn});
@@ -1315,7 +1319,8 @@
   Link();
 
   // All thunks are at the end.
-  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArm64Alignment);
+  uint32_t thunk_offset =
+      GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArm64CodeAlignment);
   method_idx = 0u;
   for (uint32_t base_reg : valid_regs) {
     ++method_idx;
@@ -1363,7 +1368,7 @@
     // Do not check the rest of the implementation.
 
     // The next thunk follows on the next aligned offset.
-    thunk_offset += RoundUp(expected_thunk.size(), kArm64Alignment);
+    thunk_offset += RoundUp(expected_thunk.size(), kArm64CodeAlignment);
   }
 }
 
@@ -1392,7 +1397,8 @@
   Link();
 
   // All thunks are at the end.
-  uint32_t thunk_offset = GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArm64Alignment);
+  uint32_t thunk_offset =
+      GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArm64CodeAlignment);
   method_idx = 0u;
   for (uint32_t root_reg : valid_regs) {
     ++method_idx;
@@ -1419,7 +1425,7 @@
     // Do not check the rest of the implementation.
 
     // The next thunk follows on the next aligned offset.
-    thunk_offset += RoundUp(expected_thunk.size(), kArm64Alignment);
+    thunk_offset += RoundUp(expected_thunk.size(), kArm64CodeAlignment);
   }
 }
 
@@ -1447,7 +1453,7 @@
   // Add a method with the right size that the method code for the next one starts 1MiB
   // after code for method 1.
   size_t filler_size =
-      1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64Alignment)
+      1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment)
              - sizeof(OatQuickMethodHeader);
   std::vector<uint8_t> filler_code = GenNops(filler_size / 4u);
   ++method_idx;
@@ -1462,16 +1468,16 @@
   }
 
   // Add 2 Baker GC root patches to the last method, one that would allow the thunk at
-  // 1MiB + kArm64Alignment, i.e. kArm64Alignment after the method call thunk, and the
-  // second that needs it kArm64Alignment after that. Given the size of the GC root thunk
-  // is more than the space required by the method call thunk plus kArm64Alignment,
+  // 1MiB + kArm64CodeAlignment, i.e. kArm64CodeAlignment after the method call thunk, and the
+  // second that needs it kArm64CodeAlignment after that. Given the size of the GC root thunk
+  // is more than the space required by the method call thunk plus kArm64CodeAlignment,
   // this pushes the first GC root thunk's pending MaxNextOffset() before the method call
   // thunk's pending MaxNextOffset() which needs to be adjusted.
-  ASSERT_LT(RoundUp(CompileMethodCallThunk().size(), kArm64Alignment) + kArm64Alignment,
+  ASSERT_LT(RoundUp(CompileMethodCallThunk().size(), kArm64CodeAlignment) + kArm64CodeAlignment,
             CompileBakerGcRootThunk(/* root_reg */ 0).size());
-  static_assert(kArm64Alignment == 16, "Code below assumes kArm64Alignment == 16");
-  constexpr size_t kBakerLiteralOffset1 = 4u + kArm64Alignment;
-  constexpr size_t kBakerLiteralOffset2 = 4u + 2 * kArm64Alignment;
+  static_assert(kArm64CodeAlignment == 16, "Code below assumes kArm64CodeAlignment == 16");
+  constexpr size_t kBakerLiteralOffset1 = 4u + kArm64CodeAlignment;
+  constexpr size_t kBakerLiteralOffset2 = 4u + 2 * kArm64CodeAlignment;
   // Use offset = 0, base_reg = 0, the LDR is simply `kLdrWInsn | root_reg`.
   const uint32_t ldr1 = kLdrWInsn | /* root_reg */ 1;
   const uint32_t ldr2 = kLdrWInsn | /* root_reg */ 2;
diff --git a/dex2oat/linker/code_info_table_deduper_test.cc b/dex2oat/linker/code_info_table_deduper_test.cc
index 8913b07..54b7dd5 100644
--- a/dex2oat/linker/code_info_table_deduper_test.cc
+++ b/dex2oat/linker/code_info_table_deduper_test.cc
@@ -35,7 +35,12 @@
   ArenaStack arena_stack(&pool);
   ScopedArenaAllocator allocator(&arena_stack);
   StackMapStream stream(&allocator, kRuntimeISA);
-  stream.BeginMethod(32, 0, 0, 2);
+  stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+                     /* core_spill_mask= */ 0,
+                     /* fp_spill_mask= */ 0,
+                     /* num_dex_registers= */ 2,
+                     /* baseline= */ false,
+                     /* debuggable= */ false);
 
   stream.BeginStackMapEntry(0, 64 * kPcAlign);
   stream.AddDexRegisterEntry(Kind::kInStack, 0);
diff --git a/dex2oat/linker/elf_writer_quick.cc b/dex2oat/linker/elf_writer_quick.cc
index 424c252..61e5783 100644
--- a/dex2oat/linker/elf_writer_quick.cc
+++ b/dex2oat/linker/elf_writer_quick.cc
@@ -25,7 +25,6 @@
 #include "base/globals.h"
 #include "base/leb128.h"
 #include "base/utils.h"
-#include "compiled_method.h"
 #include "debug/elf_debug_writer.h"
 #include "debug/method_debug_info.h"
 #include "driver/compiler_options.h"
diff --git a/dex2oat/linker/elf_writer_test.cc b/dex2oat/linker/elf_writer_test.cc
index c1ff8f2..6fa5f66 100644
--- a/dex2oat/linker/elf_writer_test.cc
+++ b/dex2oat/linker/elf_writer_test.cc
@@ -16,8 +16,6 @@
 
 #include <sys/mman.h>  // For the PROT_NONE constant.
 
-#include "elf_file.h"
-
 #include "base/file_utils.h"
 #include "base/mem_map.h"
 #include "base/unix_file/fd_file.h"
diff --git a/dex2oat/linker/image_test.cc b/dex2oat/linker/image_test.cc
index 33d122b..82c87f5 100644
--- a/dex2oat/linker/image_test.cc
+++ b/dex2oat/linker/image_test.cc
@@ -176,5 +176,37 @@
           /*image_classes_failing_aot_clinit=*/ {"LClassToInitialize;"});
 }
 
+TEST_F(ImageTest, TestImageClassWithArrayClassWithUnresolvedComponent) {
+  CompilationHelper helper;
+  Compile(ImageHeader::kStorageModeUncompressed,
+          /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
+          helper,
+          "ArrayClassWithUnresolvedComponent",
+          /*image_classes=*/ {"LClassWithStatic;",
+                              "LClassWithStaticConst;",
+                              "[LClassWithMissingInterface;",
+                              "[[LClassWithMissingInterface;",
+                              "[LClassWithMissingSuper",
+                              "[[LClassWithMissingSuper"},
+          /*image_classes_failing_aot_clinit=*/ {
+                              "LClassWithStatic;",
+                              "LClassWithStaticConst;"},
+          /*image_classes_failing_resolution=*/ {
+                              "[LClassWithMissingInterface;",
+                              "[[LClassWithMissingInterface;",
+                              "[LClassWithMissingSuper",
+                              "[[LClassWithMissingSuper"});
+}
+
+TEST_F(ImageTest, TestSuperWithAccessChecks) {
+  CompilationHelper helper;
+  Compile(ImageHeader::kStorageModeUncompressed,
+          /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
+          helper,
+          "SuperWithAccessChecks",
+          /*image_classes=*/ {"LSubClass;", "LImplementsClass;"},
+          /*image_classes_failing_aot_clinit=*/ {"LSubClass;", "LImplementsClass;"});
+}
+
 }  // namespace linker
 }  // namespace art
diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h
index 5c2d84c..8dea9a6 100644
--- a/dex2oat/linker/image_test.h
+++ b/dex2oat/linker/image_test.h
@@ -50,6 +50,7 @@
 #include "mirror/object-inl.h"
 #include "oat.h"
 #include "oat_writer.h"
+#include "read_barrier_config.h"
 #include "scoped_thread_state_change-inl.h"
 #include "signal_catcher.h"
 #include "stream/buffered_output_stream.h"
@@ -63,6 +64,7 @@
 struct CompilationHelper {
   std::vector<std::string> dex_file_locations;
   std::vector<ScratchFile> image_locations;
+  std::string extra_dex;
   std::vector<std::unique_ptr<const DexFile>> extra_dex_files;
   std::vector<ScratchFile> image_files;
   std::vector<ScratchFile> oat_files;
@@ -78,7 +80,7 @@
  protected:
   void SetUp() override {
     ReserveImageSpace();
-    CommonCompilerTest::SetUp();
+    CommonCompilerDriverTest::SetUp();
   }
 
   void Compile(ImageHeader::StorageMode storage_mode,
@@ -86,10 +88,11 @@
                /*out*/ CompilationHelper& out_helper,
                const std::string& extra_dex = "",
                const std::initializer_list<std::string>& image_classes = {},
-               const std::initializer_list<std::string>& image_classes_failing_aot_clinit = {});
+               const std::initializer_list<std::string>& image_classes_failing_aot_clinit = {},
+               const std::initializer_list<std::string>& image_classes_failing_resolution = {});
 
   void SetUpRuntimeOptions(RuntimeOptions* options) override {
-    CommonCompilerTest::SetUpRuntimeOptions(options);
+    CommonCompilerDriverTest::SetUpRuntimeOptions(options);
     QuickCompilerCallbacks* new_callbacks =
         new QuickCompilerCallbacks(CompilerCallbacks::CallbackMode::kCompileBootImage);
     new_callbacks->SetVerificationResults(verification_results_.get());
@@ -149,18 +152,11 @@
 inline void ImageTest::DoCompile(ImageHeader::StorageMode storage_mode,
                                  /*out*/ CompilationHelper& out_helper) {
   CompilerDriver* driver = compiler_driver_.get();
+  Runtime::Current()->AppendToBootClassPath(
+      out_helper.extra_dex, out_helper.extra_dex, out_helper.extra_dex_files);
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   std::vector<const DexFile*> class_path = class_linker->GetBootClassPath();
 
-  for (const std::unique_ptr<const DexFile>& dex_file : out_helper.extra_dex_files) {
-    {
-      ScopedObjectAccess soa(Thread::Current());
-      // Inject in boot class path so that the compiler driver can see it.
-      class_linker->AppendToBootClassPath(soa.Self(), dex_file.get());
-    }
-    class_path.push_back(dex_file.get());
-  }
-
   // Enable write for dex2dex.
   for (const DexFile* dex_file : class_path) {
     out_helper.dex_file_locations.push_back(dex_file->GetLocation());
@@ -228,6 +224,8 @@
       key_value_store.Put(OatHeader::kBootClassPathKey,
                           android::base::Join(out_helper.dex_file_locations, ':'));
       key_value_store.Put(OatHeader::kApexVersionsKey, Runtime::Current()->GetApexVersions());
+      key_value_store.Put(OatHeader::kConcurrentCopying,
+                          gUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);
 
       std::vector<std::unique_ptr<ElfWriter>> elf_writers;
       std::vector<std::unique_ptr<OatWriter>> oat_writers;
@@ -235,6 +233,7 @@
         elf_writers.emplace_back(CreateElfWriterQuick(*compiler_options_, oat_file.GetFile()));
         elf_writers.back()->Start();
         oat_writers.emplace_back(new OatWriter(*compiler_options_,
+                                               verification_results_.get(),
                                                &timings,
                                                /*profile_compilation_info*/nullptr,
                                                CompactDexLevel::kCompactDexLevelNone));
@@ -352,7 +351,8 @@
     CompilationHelper& helper,
     const std::string& extra_dex,
     const std::initializer_list<std::string>& image_classes,
-    const std::initializer_list<std::string>& image_classes_failing_aot_clinit) {
+    const std::initializer_list<std::string>& image_classes_failing_aot_clinit,
+    const std::initializer_list<std::string>& image_classes_failing_resolution) {
   for (const std::string& image_class : image_classes_failing_aot_clinit) {
     ASSERT_TRUE(ContainsElement(image_classes, image_class));
   }
@@ -366,6 +366,7 @@
   compiler_options_->SetMaxImageBlockSize(max_image_block_size);
   image_classes_.clear();
   if (!extra_dex.empty()) {
+    helper.extra_dex = extra_dex;
     helper.extra_dex_files = OpenTestDexFiles(extra_dex.c_str());
   }
   DoCompile(storage_mode, helper);
@@ -375,12 +376,14 @@
     ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
     for (const std::string& image_class : image_classes) {
       ObjPtr<mirror::Class> klass =
-          class_linker->FindSystemClass(Thread::Current(), image_class.c_str());
-      EXPECT_TRUE(klass != nullptr);
-      EXPECT_TRUE(klass->IsResolved());
-      if (ContainsElement(image_classes_failing_aot_clinit, image_class)) {
+          class_linker->LookupClass(Thread::Current(), image_class.c_str(), nullptr);
+      if (ContainsElement(image_classes_failing_resolution, image_class)) {
+        EXPECT_TRUE(klass == nullptr || klass->IsErroneousUnresolved());
+      } else  if (ContainsElement(image_classes_failing_aot_clinit, image_class)) {
+        ASSERT_TRUE(klass != nullptr) << image_class;
         EXPECT_FALSE(klass->IsInitialized());
       } else {
+        ASSERT_TRUE(klass != nullptr) << image_class;
         EXPECT_TRUE(klass->IsInitialized());
       }
     }
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 56fbe90..63ede1b 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -21,10 +21,12 @@
 #include <sys/stat.h>
 #include <zlib.h>
 
+#include <charconv>
 #include <memory>
 #include <numeric>
 #include <vector>
 
+#include "android-base/strings.h"
 #include "art_field-inl.h"
 #include "art_method-inl.h"
 #include "base/callee_save_type.h"
@@ -35,7 +37,6 @@
 #include "base/unix_file/fd_file.h"
 #include "class_linker-inl.h"
 #include "class_root-inl.h"
-#include "compiled_method.h"
 #include "dex/dex_file-inl.h"
 #include "dex/dex_file_types.h"
 #include "driver/compiler_options.h"
@@ -83,7 +84,7 @@
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "subtype_check.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 using ::art::mirror::Class;
 using ::art::mirror::DexCache;
@@ -101,7 +102,7 @@
 // to make them full. We never insert additional elements to them, so we do not want to waste
 // extra memory. And unlike runtime class tables, we do not want this to depend on runtime
 // properties (see `Runtime::GetHashTableMaxLoadFactor()` checking for low memory mode).
-constexpr double kImageClassTableMaxLoadFactor = 0.7;
+constexpr double kImageClassTableMaxLoadFactor = 0.6;
 
 // The actual value of `kImageInternTableMinLoadFactor` is irrelevant because image intern tables
 // are never resized, but we still need to pass a reasonable value to the constructor.
@@ -110,61 +111,7 @@
 // to make them full. We never insert additional elements to them, so we do not want to waste
 // extra memory. And unlike runtime intern tables, we do not want this to depend on runtime
 // properties (see `Runtime::GetHashTableMaxLoadFactor()` checking for low memory mode).
-constexpr double kImageInternTableMaxLoadFactor = 0.7;
-
-static ArrayRef<const uint8_t> MaybeCompressData(ArrayRef<const uint8_t> source,
-                                                 ImageHeader::StorageMode image_storage_mode,
-                                                 /*out*/ dchecked_vector<uint8_t>* storage) {
-  const uint64_t compress_start_time = NanoTime();
-
-  switch (image_storage_mode) {
-    case ImageHeader::kStorageModeLZ4: {
-      storage->resize(LZ4_compressBound(source.size()));
-      size_t data_size = LZ4_compress_default(
-          reinterpret_cast<char*>(const_cast<uint8_t*>(source.data())),
-          reinterpret_cast<char*>(storage->data()),
-          source.size(),
-          storage->size());
-      storage->resize(data_size);
-      break;
-    }
-    case ImageHeader::kStorageModeLZ4HC: {
-      // Bound is same as non HC.
-      storage->resize(LZ4_compressBound(source.size()));
-      size_t data_size = LZ4_compress_HC(
-          reinterpret_cast<const char*>(const_cast<uint8_t*>(source.data())),
-          reinterpret_cast<char*>(storage->data()),
-          source.size(),
-          storage->size(),
-          LZ4HC_CLEVEL_MAX);
-      storage->resize(data_size);
-      break;
-    }
-    case ImageHeader::kStorageModeUncompressed: {
-      return source;
-    }
-    default: {
-      LOG(FATAL) << "Unsupported";
-      UNREACHABLE();
-    }
-  }
-
-  DCHECK(image_storage_mode == ImageHeader::kStorageModeLZ4 ||
-         image_storage_mode == ImageHeader::kStorageModeLZ4HC);
-  VLOG(compiler) << "Compressed from " << source.size() << " to " << storage->size() << " in "
-                 << PrettyDuration(NanoTime() - compress_start_time);
-  if (kIsDebugBuild) {
-    dchecked_vector<uint8_t> decompressed(source.size());
-    const size_t decompressed_size = LZ4_decompress_safe(
-        reinterpret_cast<char*>(storage->data()),
-        reinterpret_cast<char*>(decompressed.data()),
-        storage->size(),
-        decompressed.size());
-    CHECK_EQ(decompressed_size, decompressed.size());
-    CHECK_EQ(memcmp(source.data(), decompressed.data(), source.size()), 0) << image_storage_mode;
-  }
-  return ArrayRef<const uint8_t>(*storage);
-}
+constexpr double kImageInternTableMaxLoadFactor = 0.6;
 
 // Separate objects into multiple bins to optimize dirty memory use.
 static constexpr bool kBinObjects = true;
@@ -265,8 +212,8 @@
   auto visitor = [](Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
     DCHECK(obj != nullptr);
     Class* klass = obj->GetClass();
-    if (klass == WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_DexFile)) {
-      ArtField* field = jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
+    if (klass == WellKnownClasses::dalvik_system_DexFile) {
+      ArtField* field = WellKnownClasses::dalvik_system_DexFile_cookie;
       // Null out the cookie to enable determinism. b/34090128
       field->SetObject</*kTransactionActive*/false>(obj, nullptr);
     }
@@ -327,6 +274,13 @@
     TimingLogger::ScopedTiming t("CalculateNewObjectOffsets", timings);
     ScopedObjectAccess soa(self);
     CalculateNewObjectOffsets();
+
+    // If dirty_image_objects_ is present - try optimizing object layout.
+    // It can only be done after the first CalculateNewObjectOffsets,
+    // because calculated offsets are used to match dirty objects between imgdiag and dex2oat.
+    if (compiler_options_.IsBootImage() && dirty_image_objects_ != nullptr) {
+      TryRecalculateOffsetsWithDirtyObjects();
+    }
   }
 
   // This needs to happen after CalculateNewObjectOffsets since it relies on intern_table_bytes_ and
@@ -375,58 +329,6 @@
          IsStronglyInternedString(referred_obj->AsString());
 }
 
-// Helper class that erases the image file if it isn't properly flushed and closed.
-class ImageWriter::ImageFileGuard {
- public:
-  ImageFileGuard() noexcept = default;
-  ImageFileGuard(ImageFileGuard&& other) noexcept = default;
-  ImageFileGuard& operator=(ImageFileGuard&& other) noexcept = default;
-
-  ~ImageFileGuard() {
-    if (image_file_ != nullptr) {
-      // Failure, erase the image file.
-      image_file_->Erase();
-    }
-  }
-
-  void reset(File* image_file) {
-    image_file_.reset(image_file);
-  }
-
-  bool operator==(std::nullptr_t) {
-    return image_file_ == nullptr;
-  }
-
-  bool operator!=(std::nullptr_t) {
-    return image_file_ != nullptr;
-  }
-
-  File* operator->() const {
-    return image_file_.get();
-  }
-
-  bool WriteHeaderAndClose(const std::string& image_filename, const ImageHeader* image_header) {
-    // The header is uncompressed since it contains whether the image is compressed or not.
-    if (!image_file_->PwriteFully(image_header, sizeof(ImageHeader), 0)) {
-      PLOG(ERROR) << "Failed to write image file header " << image_filename;
-      return false;
-    }
-
-    // FlushCloseOrErase() takes care of erasing, so the destructor does not need
-    // to do that whether the FlushCloseOrErase() succeeds or fails.
-    std::unique_ptr<File> image_file = std::move(image_file_);
-    if (image_file->FlushCloseOrErase() != 0) {
-      PLOG(ERROR) << "Failed to flush and close image file " << image_filename;
-      return false;
-    }
-
-    return true;
-  }
-
- private:
-  std::unique_ptr<File> image_file_;
-};
-
 bool ImageWriter::Write(int image_fd,
                         const std::vector<std::string>& image_filenames,
                         size_t component_count) {
@@ -497,139 +399,37 @@
 
     // Image data size excludes the bitmap and the header.
     ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_info.image_.Begin());
-
-    // Block sources (from the image).
-    const bool is_compressed = image_storage_mode_ != ImageHeader::kStorageModeUncompressed;
-    dchecked_vector<std::pair<uint32_t, uint32_t>> block_sources;
-    dchecked_vector<ImageHeader::Block> blocks;
-
-    // Add a set of solid blocks such that no block is larger than the maximum size. A solid block
-    // is a block that must be decompressed all at once.
-    auto add_blocks = [&](uint32_t offset, uint32_t size) {
-      while (size != 0u) {
-        const uint32_t cur_size = std::min(size, compiler_options_.MaxImageBlockSize());
-        block_sources.emplace_back(offset, cur_size);
-        offset += cur_size;
-        size -= cur_size;
-      }
-    };
-
-    add_blocks(sizeof(ImageHeader), image_header->GetImageSize() - sizeof(ImageHeader));
-
-    // Checksum of compressed image data and header.
-    uint32_t image_checksum = adler32(0L, Z_NULL, 0);
-    image_checksum = adler32(image_checksum,
-                             reinterpret_cast<const uint8_t*>(image_header),
-                             sizeof(ImageHeader));
-    // Copy and compress blocks.
-    size_t out_offset = sizeof(ImageHeader);
-    for (const std::pair<uint32_t, uint32_t> block : block_sources) {
-      ArrayRef<const uint8_t> raw_image_data(image_info.image_.Begin() + block.first,
-                                             block.second);
-      dchecked_vector<uint8_t> compressed_data;
-      ArrayRef<const uint8_t> image_data =
-          MaybeCompressData(raw_image_data, image_storage_mode_, &compressed_data);
-
-      if (!is_compressed) {
-        // For uncompressed, preserve alignment since the image will be directly mapped.
-        out_offset = block.first;
-      }
-
-      // Fill in the compressed location of the block.
-      blocks.emplace_back(ImageHeader::Block(
-          image_storage_mode_,
-          /*data_offset=*/ out_offset,
-          /*data_size=*/ image_data.size(),
-          /*image_offset=*/ block.first,
-          /*image_size=*/ block.second));
-
-      // Write out the image + fields + methods.
-      if (!image_file->PwriteFully(image_data.data(), image_data.size(), out_offset)) {
-        PLOG(ERROR) << "Failed to write image file data " << image_filename;
-        image_file->Erase();
-        return false;
-      }
-      out_offset += image_data.size();
-      image_checksum = adler32(image_checksum, image_data.data(), image_data.size());
-    }
-
-    // Write the block metadata directly after the image sections.
-    // Note: This is not part of the mapped image and is not preserved after decompressing, it's
-    // only used for image loading. For this reason, only write it out for compressed images.
-    if (is_compressed) {
-      // Align up since the compressed data is not necessarily aligned.
-      out_offset = RoundUp(out_offset, alignof(ImageHeader::Block));
-      CHECK(!blocks.empty());
-      const size_t blocks_bytes = blocks.size() * sizeof(blocks[0]);
-      if (!image_file->PwriteFully(&blocks[0], blocks_bytes, out_offset)) {
-        PLOG(ERROR) << "Failed to write image blocks " << image_filename;
-        image_file->Erase();
-        return false;
-      }
-      image_header->blocks_offset_ = out_offset;
-      image_header->blocks_count_ = blocks.size();
-      out_offset += blocks_bytes;
-    }
-
-    // Data size includes everything except the bitmap.
-    image_header->data_size_ = out_offset - sizeof(ImageHeader);
-
-    // Update and write the bitmap section. Note that the bitmap section is relative to the
-    // possibly compressed image.
-    ImageSection& bitmap_section = image_header->GetImageSection(ImageHeader::kSectionImageBitmap);
-    // Align up since data size may be unaligned if the image is compressed.
-    out_offset = RoundUp(out_offset, kPageSize);
-    bitmap_section = ImageSection(out_offset, bitmap_section.Size());
-
-    if (!image_file->PwriteFully(image_info.image_bitmap_.Begin(),
-                                 bitmap_section.Size(),
-                                 bitmap_section.Offset())) {
-      PLOG(ERROR) << "Failed to write image file bitmap " << image_filename;
+    std::string error_msg;
+    if (!image_header->WriteData(image_file,
+                                 image_info.image_.Begin(),
+                                 reinterpret_cast<const uint8_t*>(image_info.image_bitmap_.Begin()),
+                                 image_storage_mode_,
+                                 compiler_options_.MaxImageBlockSize(),
+                                 /* update_checksum= */ true,
+                                 &error_msg)) {
+      LOG(ERROR) << error_msg;
       return false;
     }
 
-    int err = image_file->Flush();
-    if (err < 0) {
-      PLOG(ERROR) << "Failed to flush image file " << image_filename << " with result " << err;
-      return false;
-    }
-
-    // Calculate the image checksum of the remaining data.
-    image_checksum = adler32(image_checksum,
-                             reinterpret_cast<const uint8_t*>(image_info.image_bitmap_.Begin()),
-                             bitmap_section.Size());
-    image_header->SetImageChecksum(image_checksum);
-
-    if (VLOG_IS_ON(compiler)) {
-      const size_t separately_written_section_size = bitmap_section.Size();
-      const size_t total_uncompressed_size = image_info.image_size_ +
-          separately_written_section_size;
-      const size_t total_compressed_size = out_offset + separately_written_section_size;
-
-      VLOG(compiler) << "Dex2Oat:uncompressedImageSize = " << total_uncompressed_size;
-      if (total_uncompressed_size != total_compressed_size) {
-        VLOG(compiler) << "Dex2Oat:compressedImageSize = " << total_compressed_size;
-      }
-    }
-
-    CHECK_EQ(bitmap_section.End(), static_cast<size_t>(image_file->GetLength()))
-        << "Bitmap should be at the end of the file";
-
     // Write header last in case the compiler gets killed in the middle of image writing.
     // We do not want to have a corrupted image with a valid header.
     // Delay the writing of the primary image header until after writing secondary images.
     if (i == 0u) {
       primary_image_file = std::move(image_file);
     } else {
-      if (!image_file.WriteHeaderAndClose(image_filename, image_header)) {
+      if (!image_file.WriteHeaderAndClose(image_filename, image_header, &error_msg)) {
+        LOG(ERROR) << error_msg;
         return false;
       }
       // Update the primary image checksum with the secondary image checksum.
-      primary_header->SetImageChecksum(primary_header->GetImageChecksum() ^ image_checksum);
+      primary_header->SetImageChecksum(
+          primary_header->GetImageChecksum() ^ image_header->GetImageChecksum());
     }
   }
   DCHECK(primary_image_file != nullptr);
-  if (!primary_image_file.WriteHeaderAndClose(image_filenames[0], primary_header)) {
+  std::string error_msg;
+  if (!primary_image_file.WriteHeaderAndClose(image_filenames[0], primary_header, &error_msg)) {
+    LOG(ERROR) << error_msg;
     return false;
   }
 
@@ -722,7 +522,13 @@
     // so packing them together will not result in a noticeably tighter dirty-to-clean ratio.
     //
     ObjPtr<mirror::Class> klass = object->GetClass<kVerifyNone, kWithoutReadBarrier>();
-    if (klass->IsClassClass()) {
+    if (klass->IsStringClass<kVerifyNone>()) {
+      // Assign strings to their bin before checking dirty objects, because
+      // string intern processing expects strings to be in Bin::kString.
+      bin = Bin::kString;  // Strings are almost always immutable (except for object header).
+    } else if (dirty_objects_.find(object) != dirty_objects_.end()) {
+      bin = Bin::kKnownDirty;
+    } else if (klass->IsClassClass()) {
       bin = Bin::kClassVerified;
       ObjPtr<mirror::Class> as_klass = object->AsClass<kVerifyNone>();
 
@@ -759,8 +565,6 @@
           }
         }
       }
-    } else if (klass->IsStringClass<kVerifyNone>()) {
-      bin = Bin::kString;  // Strings are almost always immutable (except for object header).
     } else if (!klass->HasSuperClass()) {
       // Only `j.l.Object` and primitive classes lack the superclass and
       // there are no instances of primitive classes.
@@ -1121,7 +925,8 @@
       last_class_set.erase(it);
       DCHECK(std::none_of(class_table->classes_.begin(),
                           class_table->classes_.end(),
-                          [klass, hash](ClassTable::ClassSet& class_set) {
+                          [klass, hash](ClassTable::ClassSet& class_set)
+                              REQUIRES_SHARED(Locks::mutator_lock_) {
                             ClassTable::TableSlot slot(klass, hash);
                             return class_set.FindWithHash(slot, hash) != class_set.end();
                           }));
@@ -1538,6 +1343,9 @@
   void ProcessDexFileObjects(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
   void ProcessRoots(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
   void FinalizeInternTables() REQUIRES_SHARED(Locks::mutator_lock_);
+  // Recreate dirty object offsets (kKnownDirty bin) with objects sorted by sort_key.
+  void SortDirtyObjects(const HashMap<mirror::Object*, uint32_t>& dirty_objects, size_t oat_index)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   void VerifyImageBinSlotsAssigned() REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -2166,6 +1974,49 @@
   }
 }
 
+void ImageWriter::LayoutHelper::SortDirtyObjects(
+    const HashMap<mirror::Object*, uint32_t>& dirty_objects, size_t oat_index) {
+  constexpr Bin bin = Bin::kKnownDirty;
+  ImageInfo& image_info = image_writer_->GetImageInfo(oat_index);
+
+  dchecked_vector<mirror::Object*>& known_dirty = bin_objects_[oat_index][enum_cast<size_t>(bin)];
+  if (known_dirty.empty()) {
+    return;
+  }
+
+  // Collect objects and their combined sort_keys.
+  // Combined key contains sort_key and original offset to ensure deterministic sorting.
+  using CombinedKey = std::pair<uint32_t, uint32_t>;
+  using ObjSortPair = std::pair<mirror::Object*, CombinedKey>;
+  dchecked_vector<ObjSortPair> objects;
+  objects.reserve(known_dirty.size());
+  for (mirror::Object* obj : known_dirty) {
+    const BinSlot bin_slot = image_writer_->GetImageBinSlot(obj, oat_index);
+    const uint32_t original_offset = bin_slot.GetOffset();
+    const auto it = dirty_objects.find(obj);
+    const uint32_t sort_key = (it != dirty_objects.end()) ? it->second : 0;
+    objects.emplace_back(obj, std::make_pair(sort_key, original_offset));
+  }
+  // Sort by combined sort_key.
+  std::sort(std::begin(objects), std::end(objects), [&](ObjSortPair& lhs, ObjSortPair& rhs) {
+    return lhs.second < rhs.second;
+  });
+
+  // Fill known_dirty objects in sorted order, update bin offsets.
+  known_dirty.clear();
+  size_t offset = 0;
+  for (const ObjSortPair& entry : objects) {
+    mirror::Object* obj = entry.first;
+
+    known_dirty.push_back(obj);
+    image_writer_->UpdateImageBinSlotOffset(obj, oat_index, offset);
+
+    const size_t aligned_object_size = RoundUp(obj->SizeOf<kVerifyNone>(), kObjectAlignment);
+    offset += aligned_object_size;
+  }
+  DCHECK_EQ(offset, image_info.GetBinSlotSize(bin));
+}
+
 void ImageWriter::LayoutHelper::VerifyImageBinSlotsAssigned() {
   dchecked_vector<mirror::Object*> carveout;
   JavaVMExt* vm = nullptr;
@@ -2217,12 +2068,11 @@
           CHECK(ref != nullptr);
           CHECK(image_writer_->IsImageBinSlotAssigned(ref.Ptr()));
           ObjPtr<mirror::Class> ref_klass = ref->GetClass<kVerifyNone, kWithoutReadBarrier>();
-          CHECK(ref_klass ==
-                DecodeGlobalWithoutRB<mirror::Class>(vm, WellKnownClasses::dalvik_system_DexFile));
+          CHECK(ref_klass == WellKnownClasses::dalvik_system_DexFile.Get<kWithoutReadBarrier>());
           // Note: The app class loader is used only for checking against the runtime
           // class loader, the dex file cookie is cleared and therefore we do not need
           // to run the finalizer even if we implement app image objects collection.
-          ArtField* field = jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
+          ArtField* field = WellKnownClasses::dalvik_system_DexFile_cookie;
           CHECK(field->GetObject<kWithoutReadBarrier>(ref) == nullptr);
           return;
         }
@@ -2489,6 +2339,13 @@
   layout_helper.ProcessRoots(self);
   layout_helper.FinalizeInternTables();
 
+  // Sort objects in dirty bin.
+  if (!dirty_objects_.empty()) {
+    for (size_t oat_index = 0; oat_index < image_infos_.size(); ++oat_index) {
+      layout_helper.SortDirtyObjects(dirty_objects_, oat_index);
+    }
+  }
+
   // Verify that all objects have assigned image bin slots.
   layout_helper.VerifyImageBinSlotsAssigned();
 
@@ -2527,6 +2384,149 @@
   }
 }
 
+std::optional<HashMap<mirror::Object*, uint32_t>> ImageWriter::MatchDirtyObjectOffsets(
+    const HashMap<uint32_t, DirtyEntry>& dirty_entries) REQUIRES_SHARED(Locks::mutator_lock_) {
+  HashMap<mirror::Object*, uint32_t> dirty_objects;
+  bool mismatch_found = false;
+
+  auto visitor = [&](Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK(obj != nullptr);
+    if (mismatch_found) {
+      return;
+    }
+    if (!IsImageBinSlotAssigned(obj)) {
+      return;
+    }
+
+    uint8_t* image_address = reinterpret_cast<uint8_t*>(GetImageAddress(obj));
+    uint32_t offset = static_cast<uint32_t>(image_address - global_image_begin_);
+
+    auto entry_it = dirty_entries.find(offset);
+    if (entry_it == dirty_entries.end()) {
+      return;
+    }
+
+    const DirtyEntry& entry = entry_it->second;
+
+    const bool is_class = obj->IsClass();
+    const uint32_t descriptor_hash =
+        is_class ? obj->AsClass()->DescriptorHash() : obj->GetClass()->DescriptorHash();
+
+    if (is_class != entry.is_class || descriptor_hash != entry.descriptor_hash) {
+      LOG(WARNING) << "Dirty image objects offset mismatch (outdated file?)";
+      mismatch_found = true;
+      return;
+    }
+
+    dirty_objects.insert(std::make_pair(obj, entry.sort_key));
+  };
+  Runtime::Current()->GetHeap()->VisitObjects(visitor);
+
+  // A single mismatch indicates that dirty-image-objects layout differs from
+  // current ImageWriter layout. In this case any "valid" matches are likely to be accidental,
+  // so there's no point in optimizing the layout with such data.
+  if (mismatch_found) {
+    return {};
+  }
+  if (dirty_objects.size() != dirty_entries.size()) {
+    LOG(WARNING) << "Dirty image objects missing offsets (outdated file?)";
+    return {};
+  }
+  return dirty_objects;
+}
+
+void ImageWriter::ResetObjectOffsets() {
+  const size_t image_infos_size = image_infos_.size();
+  image_infos_.clear();
+  image_infos_.resize(image_infos_size);
+
+  native_object_relocations_.clear();
+
+  // CalculateNewObjectOffsets stores image offsets of the objects in lock words,
+  // while original lock words are preserved in saved_hashcode_map.
+  // Restore original lock words.
+  auto visitor = [&](Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK(obj != nullptr);
+    const auto it = saved_hashcode_map_.find(obj);
+    obj->SetLockWord(it != saved_hashcode_map_.end() ? LockWord::FromHashCode(it->second, 0u) :
+                                                       LockWord::Default(),
+                     false);
+  };
+  Runtime::Current()->GetHeap()->VisitObjects(visitor);
+
+  saved_hashcode_map_.clear();
+}
+
+void ImageWriter::TryRecalculateOffsetsWithDirtyObjects() {
+  const std::optional<HashMap<uint32_t, ImageWriter::DirtyEntry>> dirty_entries =
+      ParseDirtyObjectOffsets(*dirty_image_objects_);
+  if (!dirty_entries || dirty_entries->empty()) {
+    return;
+  }
+
+  std::optional<HashMap<mirror::Object*, uint32_t>> dirty_objects =
+      MatchDirtyObjectOffsets(*dirty_entries);
+  if (!dirty_objects || dirty_objects->empty()) {
+    return;
+  }
+  // Calculate offsets again, now with dirty object offsets.
+  LOG(INFO) << "Recalculating object offsets using dirty-image-objects";
+  dirty_objects_ = std::move(*dirty_objects);
+  ResetObjectOffsets();
+  CalculateNewObjectOffsets();
+}
+
+std::optional<HashMap<uint32_t, ImageWriter::DirtyEntry>> ImageWriter::ParseDirtyObjectOffsets(
+    const HashSet<std::string>& dirty_image_objects) REQUIRES_SHARED(Locks::mutator_lock_) {
+  HashMap<uint32_t, DirtyEntry> dirty_entries;
+
+  // Go through each dirty-image-object line, parse only lines of the format:
+  // "dirty_obj: <offset> <type> <descriptor_hash> <sort_key>"
+  // <offset> -- decimal uint32.
+  // <type> -- "class" or "instance" (defines if descriptor is referring to a class or an instance).
+  // <descriptor_hash> -- decimal uint32 (from DescriptorHash() method).
+  // <sort_key> -- decimal uint32 (defines order of the object inside the dirty bin).
+  const std::string prefix = "dirty_obj:";
+  for (const std::string& entry_str : dirty_image_objects) {
+    // Skip the lines of old dirty-image-object format.
+    if (std::strncmp(entry_str.data(), prefix.data(), prefix.size()) != 0) {
+      continue;
+    }
+
+    const std::vector<std::string> tokens = android::base::Split(entry_str, " ");
+    if (tokens.size() != 5) {
+      LOG(WARNING) << "Invalid dirty image objects format: \"" << entry_str << "\"";
+      return {};
+    }
+
+    uint32_t offset = 0;
+    std::from_chars_result res =
+        std::from_chars(tokens[1].data(), tokens[1].data() + tokens[1].size(), offset);
+    if (res.ec != std::errc()) {
+      LOG(WARNING) << "Couldn't parse dirty object offset: \"" << entry_str << "\"";
+      return {};
+    }
+
+    DirtyEntry entry;
+    entry.is_class = (tokens[2] == "class");
+    res = std::from_chars(
+        tokens[3].data(), tokens[3].data() + tokens[3].size(), entry.descriptor_hash);
+    if (res.ec != std::errc()) {
+      LOG(WARNING) << "Couldn't parse dirty object descriptor hash: \"" << entry_str << "\"";
+      return {};
+    }
+    res = std::from_chars(tokens[4].data(), tokens[4].data() + tokens[4].size(), entry.sort_key);
+    if (res.ec != std::errc()) {
+      LOG(WARNING) << "Couldn't parse dirty object marker: \"" << entry_str << "\"";
+      return {};
+    }
+
+    dirty_entries.insert(std::make_pair(offset, entry));
+  }
+
+  return dirty_entries;
+}
+
 std::pair<size_t, dchecked_vector<ImageSection>>
 ImageWriter::ImageInfo::CreateImageSections() const {
   dchecked_vector<ImageSection> sections(ImageHeader::kSectionCount);
@@ -2610,6 +2610,15 @@
           ImageSection(cur_pos, sizeof(string_reference_offsets_[0]) * num_string_references_);
 
   /*
+   * DexCache arrays section
+   */
+
+  // Round up to the alignment dex caches arrays expects.
+  cur_pos = RoundUp(sections[ImageHeader::kSectionStringReferenceOffsets].End(), sizeof(uint32_t));
+  // We don't generate dex cache arrays in an image generated by dex2oat.
+  sections[ImageHeader::kSectionDexCacheArrays] = ImageSection(cur_pos, 0u);
+
+  /*
    * Metadata section.
    */
 
@@ -3288,25 +3297,7 @@
     const OatFile* oat_file = image_spaces[0]->GetOatFile();
     CHECK(oat_file != nullptr);
     const OatHeader& header = oat_file->GetOatHeader();
-    switch (type) {
-      // TODO: We could maybe clean this up if we stored them in an array in the oat header.
-      case StubType::kQuickGenericJNITrampoline:
-        return static_cast<const uint8_t*>(header.GetQuickGenericJniTrampoline());
-      case StubType::kJNIDlsymLookupTrampoline:
-        return static_cast<const uint8_t*>(header.GetJniDlsymLookupTrampoline());
-      case StubType::kJNIDlsymLookupCriticalTrampoline:
-        return static_cast<const uint8_t*>(header.GetJniDlsymLookupCriticalTrampoline());
-      case StubType::kQuickIMTConflictTrampoline:
-        return static_cast<const uint8_t*>(header.GetQuickImtConflictTrampoline());
-      case StubType::kQuickResolutionTrampoline:
-        return static_cast<const uint8_t*>(header.GetQuickResolutionTrampoline());
-      case StubType::kQuickToInterpreterBridge:
-        return static_cast<const uint8_t*>(header.GetQuickToInterpreterBridge());
-      case StubType::kNterpTrampoline:
-        return static_cast<const uint8_t*>(header.GetNterpTrampoline());
-      default:
-        UNREACHABLE();
-    }
+    return header.GetOatAddress(type);
   }
   const ImageInfo& primary_image_info = GetImageInfo(0);
   return GetOatAddressForOffset(primary_image_info.GetStubOffset(type), primary_image_info);
@@ -3336,8 +3327,7 @@
     quick_code = GetOatAddressForOffset(quick_oat_code_offset, image_info);
   }
 
-  bool needs_clinit_check = NeedsClinitCheckBeforeCall(method) &&
-      !method->GetDeclaringClass<kWithoutReadBarrier>()->IsVisiblyInitialized();
+  bool still_needs_clinit_check = method->StillNeedsClinitCheck<kWithoutReadBarrier>();
 
   if (quick_code == nullptr) {
     // If we don't have code, use generic jni / interpreter.
@@ -3347,7 +3337,7 @@
     } else if (CanMethodUseNterp(method, compiler_options_.GetInstructionSet())) {
       // The nterp trampoline doesn't do initialization checks, so install the
       // resolution stub if needed.
-      if (needs_clinit_check) {
+      if (still_needs_clinit_check) {
         quick_code = GetOatAddress(StubType::kQuickResolutionTrampoline);
       } else {
         quick_code = GetOatAddress(StubType::kNterpTrampoline);
@@ -3356,7 +3346,7 @@
       // The interpreter brige performs class initialization check if needed.
       quick_code = GetOatAddress(StubType::kQuickToInterpreterBridge);
     }
-  } else if (needs_clinit_check) {
+  } else if (still_needs_clinit_check && !compiler_options_.ShouldCompileWithClinitCheck(method)) {
     // If we do have code but the method needs a class initialization check before calling
     // that code, install the resolution stub that will perform the check.
     quick_code = GetOatAddress(StubType::kQuickResolutionTrampoline);
diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h
index e5eeacc..3f6aa28 100644
--- a/dex2oat/linker/image_writer.h
+++ b/dex2oat/linker/image_writer.h
@@ -45,6 +45,7 @@
 #include "intern_table.h"
 #include "lock_word.h"
 #include "mirror/dex_cache.h"
+#include "oat.h"
 #include "oat_file.h"
 #include "obj_ptr.h"
 
@@ -229,18 +230,6 @@
   };
   friend std::ostream& operator<<(std::ostream& stream, NativeObjectRelocationType type);
 
-  enum class StubType {
-    kJNIDlsymLookupTrampoline,
-    kJNIDlsymLookupCriticalTrampoline,
-    kQuickGenericJNITrampoline,
-    kQuickIMTConflictTrampoline,
-    kQuickResolutionTrampoline,
-    kQuickToInterpreterBridge,
-    kNterpTrampoline,
-    kLast = kNterpTrampoline,
-  };
-  friend std::ostream& operator<<(std::ostream& stream, StubType stub_type);
-
   static constexpr size_t kBinBits =
       MinimumBitsToStore<uint32_t>(static_cast<size_t>(Bin::kMirrorCount) - 1);
   // uint32 = typeof(lockword_)
@@ -462,6 +451,25 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
   void CalculateObjectBinSlots(mirror::Object* obj)
       REQUIRES_SHARED(Locks::mutator_lock_);
+  // Undo the changes of CalculateNewObjectOffsets.
+  void ResetObjectOffsets() REQUIRES_SHARED(Locks::mutator_lock_);
+  // Reset and calculate new offsets with dirty objects optimization.
+  // Does nothing if dirty object offsets don't match with current offsets.
+  void TryRecalculateOffsetsWithDirtyObjects() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Dirty object data from dirty-image-objects.
+  struct DirtyEntry {
+    uint32_t descriptor_hash = 0;
+    bool is_class = false;
+    uint32_t sort_key = 0;
+  };
+  // Parse dirty-image-objects into (offset->entry) map. Returns nullopt on parse error.
+  static std::optional<HashMap<uint32_t, DirtyEntry>> ParseDirtyObjectOffsets(
+      const HashSet<std::string>& dirty_image_objects) REQUIRES_SHARED(Locks::mutator_lock_);
+  // Get all objects that match dirty_entries by offset. Returns nullopt if there is a mismatch.
+  // Map values are sort_keys from DirtyEntry.
+  std::optional<HashMap<mirror::Object*, uint32_t>> MatchDirtyObjectOffsets(
+      const HashMap<uint32_t, DirtyEntry>& dirty_entries) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Creates the contiguous image in memory and adjusts pointers.
   void CopyAndFixupNativeData(size_t oat_index) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -685,9 +693,14 @@
   // Map of dex files to the indexes of oat files that they were compiled into.
   const HashMap<const DexFile*, size_t>& dex_file_oat_index_map_;
 
-  // Set of objects known to be dirty in the image. Can be nullptr if there are none.
+  // Set of classes/objects known to be dirty in the image. Can be nullptr if there are none.
+  // For old dirty-image-objects format this set contains descriptors of dirty classes.
+  // For new format -- a set of dirty object offsets and descriptor hashes.
   const HashSet<std::string>* dirty_image_objects_;
 
+  // Dirty object instances and their sort keys parsed from dirty_image_object_
+  HashMap<mirror::Object*, uint32_t> dirty_objects_;
+
   // Objects are guaranteed to not cross the region size boundary.
   size_t region_size_ = 0u;
 
@@ -697,7 +710,6 @@
   class FixupClassVisitor;
   class FixupRootVisitor;
   class FixupVisitor;
-  class ImageFileGuard;
   class LayoutHelper;
   class NativeLocationVisitor;
   class PruneClassesVisitor;
@@ -712,7 +724,6 @@
 
 std::ostream& operator<<(std::ostream& stream, ImageWriter::Bin bin);
 std::ostream& operator<<(std::ostream& stream, ImageWriter::NativeObjectRelocationType type);
-std::ostream& operator<<(std::ostream& stream, ImageWriter::StubType stub_type);
 
 }  // namespace linker
 }  // namespace art
diff --git a/dex2oat/linker/multi_oat_relative_patcher_test.cc b/dex2oat/linker/multi_oat_relative_patcher_test.cc
index 2a05816..a393eb8 100644
--- a/dex2oat/linker/multi_oat_relative_patcher_test.cc
+++ b/dex2oat/linker/multi_oat_relative_patcher_test.cc
@@ -16,8 +16,8 @@
 
 #include "multi_oat_relative_patcher.h"
 
-#include "compiled_method.h"
 #include "debug/method_debug_info.h"
+#include "driver/compiled_method.h"
 #include "gtest/gtest.h"
 #include "linker/linker_patch.h"
 #include "stream/vector_output_stream.h"
diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc
index 19e77de..63d2a42 100644
--- a/dex2oat/linker/oat_writer.cc
+++ b/dex2oat/linker/oat_writer.cc
@@ -16,10 +16,13 @@
 
 #include "oat_writer.h"
 
-#include <algorithm>
 #include <unistd.h>
 #include <zlib.h>
 
+#include <algorithm>
+#include <memory>
+#include <vector>
+
 #include "arch/arm64/instruction_set_features_arm64.h"
 #include "art_method-inl.h"
 #include "base/allocator.h"
@@ -37,18 +40,19 @@
 #include "class_linker.h"
 #include "class_table-inl.h"
 #include "code_info_table_deduper.h"
-#include "compiled_method-inl.h"
 #include "debug/method_debug_info.h"
 #include "dex/art_dex_file_loader.h"
 #include "dex/class_accessor-inl.h"
 #include "dex/dex_file-inl.h"
 #include "dex/dex_file_loader.h"
 #include "dex/dex_file_types.h"
+#include "dex/dex_file_verifier.h"
 #include "dex/standard_dex_file.h"
 #include "dex/type_lookup_table.h"
 #include "dex/verification_results.h"
 #include "dex_container.h"
 #include "dexlayout.h"
+#include "driver/compiled_method-inl.h"
 #include "driver/compiler_driver-inl.h"
 #include "driver/compiler_options.h"
 #include "gc/space/image_space.h"
@@ -138,75 +142,6 @@
   OatWriter* const writer_;
 };
 
-// Defines the location of the raw dex file to write.
-class OatWriter::DexFileSource {
- public:
-  enum Type {
-    kNone,
-    kZipEntry,
-    kRawFile,
-    kRawData,
-  };
-
-  explicit DexFileSource(ZipEntry* zip_entry)
-      : type_(kZipEntry), source_(zip_entry) {
-    DCHECK(source_ != nullptr);
-  }
-
-  explicit DexFileSource(File* raw_file)
-      : type_(kRawFile), source_(raw_file) {
-    DCHECK(source_ != nullptr);
-  }
-
-  explicit DexFileSource(const uint8_t* dex_file)
-      : type_(kRawData), source_(dex_file) {
-    DCHECK(source_ != nullptr);
-  }
-
-  Type GetType() const { return type_; }
-  bool IsZipEntry() const { return type_ == kZipEntry; }
-  bool IsRawFile() const { return type_ == kRawFile; }
-  bool IsRawData() const { return type_ == kRawData; }
-
-  ZipEntry* GetZipEntry() const {
-    DCHECK(IsZipEntry());
-    DCHECK(source_ != nullptr);
-    return static_cast<ZipEntry*>(const_cast<void*>(source_));
-  }
-
-  File* GetRawFile() const {
-    DCHECK(IsRawFile());
-    DCHECK(source_ != nullptr);
-    return static_cast<File*>(const_cast<void*>(source_));
-  }
-
-  const uint8_t* GetRawData() const {
-    DCHECK(IsRawData());
-    DCHECK(source_ != nullptr);
-    return static_cast<const uint8_t*>(source_);
-  }
-
-  void SetDexLayoutData(std::vector<uint8_t>&& dexlayout_data) {
-    DCHECK_GE(dexlayout_data.size(), sizeof(DexFile::Header));
-    dexlayout_data_ = std::move(dexlayout_data);
-    type_ = kRawData;
-    source_ = dexlayout_data_.data();
-  }
-
-  void Clear() {
-    type_ = kNone;
-    source_ = nullptr;
-    // Release the memory held by `dexlayout_data_`.
-    std::vector<uint8_t> temp;
-    temp.swap(dexlayout_data_);
-  }
-
- private:
-  Type type_;
-  const void* source_;
-  std::vector<uint8_t> dexlayout_data_;
-};
-
 // OatClassHeader is the header only part of the oat class that is required even when compilation
 // is not enabled.
 class OatWriter::OatClassHeader {
@@ -307,12 +242,11 @@
 
 class OatWriter::OatDexFile {
  public:
-  OatDexFile(const char* dex_file_location,
-             DexFileSource source,
-             uint32_t dex_file_location_checksun,
-             size_t dex_file_size);
+  explicit OatDexFile(std::unique_ptr<const DexFile> dex_file);
   OatDexFile(OatDexFile&& src) = default;
 
+  const DexFile* GetDexFile() const { return dex_file_.get(); }
+
   const char* GetLocation() const {
     return dex_file_location_data_;
   }
@@ -325,8 +259,10 @@
     return class_offsets_.size() * sizeof(class_offsets_[0]);
   }
 
-  // The source of the dex file.
-  DexFileSource source_;
+  std::unique_ptr<const DexFile> dex_file_;
+  std::unique_ptr<std::string> dex_file_location_;
+
+  std::vector<uint8_t> cdex_main_section_;
 
   // Dex file size. Passed in the constructor, but could be
   // overwritten by LayoutDexFile.
@@ -384,115 +320,112 @@
     << "file_offset=" << file_offset << " offset_=" << offset_
 
 OatWriter::OatWriter(const CompilerOptions& compiler_options,
+                     const VerificationResults* verification_results,
                      TimingLogger* timings,
                      ProfileCompilationInfo* info,
                      CompactDexLevel compact_dex_level)
-  : write_state_(WriteState::kAddingDexFileSources),
-    timings_(timings),
-    raw_dex_files_(),
-    zip_archives_(),
-    zipped_dex_files_(),
-    zipped_dex_file_locations_(),
-    compiler_driver_(nullptr),
-    compiler_options_(compiler_options),
-    image_writer_(nullptr),
-    extract_dex_files_into_vdex_(true),
-    vdex_begin_(nullptr),
-    dex_files_(nullptr),
-    primary_oat_file_(false),
-    vdex_size_(0u),
-    vdex_dex_files_offset_(0u),
-    vdex_dex_shared_data_offset_(0u),
-    vdex_verifier_deps_offset_(0u),
-    vdex_quickening_info_offset_(0u),
-    vdex_lookup_tables_offset_(0u),
-    oat_checksum_(adler32(0L, Z_NULL, 0)),
-    code_size_(0u),
-    oat_size_(0u),
-    data_bimg_rel_ro_start_(0u),
-    data_bimg_rel_ro_size_(0u),
-    bss_start_(0u),
-    bss_size_(0u),
-    bss_methods_offset_(0u),
-    bss_roots_offset_(0u),
-    data_bimg_rel_ro_entries_(),
-    bss_method_entry_references_(),
-    bss_method_entries_(),
-    bss_type_entries_(),
-    bss_public_type_entries_(),
-    bss_package_type_entries_(),
-    bss_string_entries_(),
-    oat_data_offset_(0u),
-    oat_header_(nullptr),
-    size_vdex_header_(0),
-    size_vdex_checksums_(0),
-    size_dex_file_alignment_(0),
-    size_quickening_table_offset_(0),
-    size_executable_offset_alignment_(0),
-    size_oat_header_(0),
-    size_oat_header_key_value_store_(0),
-    size_dex_file_(0),
-    size_verifier_deps_(0),
-    size_verifier_deps_alignment_(0),
-    size_quickening_info_(0),
-    size_quickening_info_alignment_(0),
-    size_vdex_lookup_table_alignment_(0),
-    size_vdex_lookup_table_(0),
-    size_interpreter_to_interpreter_bridge_(0),
-    size_interpreter_to_compiled_code_bridge_(0),
-    size_jni_dlsym_lookup_trampoline_(0),
-    size_jni_dlsym_lookup_critical_trampoline_(0),
-    size_quick_generic_jni_trampoline_(0),
-    size_quick_imt_conflict_trampoline_(0),
-    size_quick_resolution_trampoline_(0),
-    size_quick_to_interpreter_bridge_(0),
-    size_nterp_trampoline_(0),
-    size_trampoline_alignment_(0),
-    size_method_header_(0),
-    size_code_(0),
-    size_code_alignment_(0),
-    size_data_bimg_rel_ro_(0),
-    size_data_bimg_rel_ro_alignment_(0),
-    size_relative_call_thunks_(0),
-    size_misc_thunks_(0),
-    size_vmap_table_(0),
-    size_method_info_(0),
-    size_oat_dex_file_location_size_(0),
-    size_oat_dex_file_location_data_(0),
-    size_oat_dex_file_location_checksum_(0),
-    size_oat_dex_file_offset_(0),
-    size_oat_dex_file_class_offsets_offset_(0),
-    size_oat_dex_file_lookup_table_offset_(0),
-    size_oat_dex_file_dex_layout_sections_offset_(0),
-    size_oat_dex_file_dex_layout_sections_(0),
-    size_oat_dex_file_dex_layout_sections_alignment_(0),
-    size_oat_dex_file_method_bss_mapping_offset_(0),
-    size_oat_dex_file_type_bss_mapping_offset_(0),
-    size_oat_dex_file_public_type_bss_mapping_offset_(0),
-    size_oat_dex_file_package_type_bss_mapping_offset_(0),
-    size_oat_dex_file_string_bss_mapping_offset_(0),
-    size_bcp_bss_info_size_(0),
-    size_bcp_bss_info_method_bss_mapping_offset_(0),
-    size_bcp_bss_info_type_bss_mapping_offset_(0),
-    size_bcp_bss_info_public_type_bss_mapping_offset_(0),
-    size_bcp_bss_info_package_type_bss_mapping_offset_(0),
-    size_bcp_bss_info_string_bss_mapping_offset_(0),
-    size_oat_class_offsets_alignment_(0),
-    size_oat_class_offsets_(0),
-    size_oat_class_type_(0),
-    size_oat_class_status_(0),
-    size_oat_class_num_methods_(0),
-    size_oat_class_method_bitmaps_(0),
-    size_oat_class_method_offsets_(0),
-    size_method_bss_mappings_(0u),
-    size_type_bss_mappings_(0u),
-    size_public_type_bss_mappings_(0u),
-    size_package_type_bss_mappings_(0u),
-    size_string_bss_mappings_(0u),
-    relative_patcher_(nullptr),
-    profile_compilation_info_(info),
-    compact_dex_level_(compact_dex_level) {
-}
+    : write_state_(WriteState::kAddingDexFileSources),
+      timings_(timings),
+      compiler_driver_(nullptr),
+      compiler_options_(compiler_options),
+      verification_results_(verification_results),
+      image_writer_(nullptr),
+      extract_dex_files_into_vdex_(true),
+      vdex_begin_(nullptr),
+      dex_files_(nullptr),
+      primary_oat_file_(false),
+      vdex_size_(0u),
+      vdex_dex_files_offset_(0u),
+      vdex_dex_shared_data_offset_(0u),
+      vdex_verifier_deps_offset_(0u),
+      vdex_quickening_info_offset_(0u),
+      vdex_lookup_tables_offset_(0u),
+      oat_checksum_(adler32(0L, Z_NULL, 0)),
+      code_size_(0u),
+      oat_size_(0u),
+      data_bimg_rel_ro_start_(0u),
+      data_bimg_rel_ro_size_(0u),
+      bss_start_(0u),
+      bss_size_(0u),
+      bss_methods_offset_(0u),
+      bss_roots_offset_(0u),
+      data_bimg_rel_ro_entries_(),
+      bss_method_entry_references_(),
+      bss_method_entries_(),
+      bss_type_entries_(),
+      bss_public_type_entries_(),
+      bss_package_type_entries_(),
+      bss_string_entries_(),
+      oat_data_offset_(0u),
+      oat_header_(nullptr),
+      size_vdex_header_(0),
+      size_vdex_checksums_(0),
+      size_dex_file_alignment_(0),
+      size_quickening_table_offset_(0),
+      size_executable_offset_alignment_(0),
+      size_oat_header_(0),
+      size_oat_header_key_value_store_(0),
+      size_dex_file_(0),
+      size_verifier_deps_(0),
+      size_verifier_deps_alignment_(0),
+      size_quickening_info_(0),
+      size_quickening_info_alignment_(0),
+      size_vdex_lookup_table_alignment_(0),
+      size_vdex_lookup_table_(0),
+      size_interpreter_to_interpreter_bridge_(0),
+      size_interpreter_to_compiled_code_bridge_(0),
+      size_jni_dlsym_lookup_trampoline_(0),
+      size_jni_dlsym_lookup_critical_trampoline_(0),
+      size_quick_generic_jni_trampoline_(0),
+      size_quick_imt_conflict_trampoline_(0),
+      size_quick_resolution_trampoline_(0),
+      size_quick_to_interpreter_bridge_(0),
+      size_nterp_trampoline_(0),
+      size_trampoline_alignment_(0),
+      size_method_header_(0),
+      size_code_(0),
+      size_code_alignment_(0),
+      size_data_bimg_rel_ro_(0),
+      size_data_bimg_rel_ro_alignment_(0),
+      size_relative_call_thunks_(0),
+      size_misc_thunks_(0),
+      size_vmap_table_(0),
+      size_method_info_(0),
+      size_oat_dex_file_location_size_(0),
+      size_oat_dex_file_location_data_(0),
+      size_oat_dex_file_location_checksum_(0),
+      size_oat_dex_file_offset_(0),
+      size_oat_dex_file_class_offsets_offset_(0),
+      size_oat_dex_file_lookup_table_offset_(0),
+      size_oat_dex_file_dex_layout_sections_offset_(0),
+      size_oat_dex_file_dex_layout_sections_(0),
+      size_oat_dex_file_dex_layout_sections_alignment_(0),
+      size_oat_dex_file_method_bss_mapping_offset_(0),
+      size_oat_dex_file_type_bss_mapping_offset_(0),
+      size_oat_dex_file_public_type_bss_mapping_offset_(0),
+      size_oat_dex_file_package_type_bss_mapping_offset_(0),
+      size_oat_dex_file_string_bss_mapping_offset_(0),
+      size_bcp_bss_info_size_(0),
+      size_bcp_bss_info_method_bss_mapping_offset_(0),
+      size_bcp_bss_info_type_bss_mapping_offset_(0),
+      size_bcp_bss_info_public_type_bss_mapping_offset_(0),
+      size_bcp_bss_info_package_type_bss_mapping_offset_(0),
+      size_bcp_bss_info_string_bss_mapping_offset_(0),
+      size_oat_class_offsets_alignment_(0),
+      size_oat_class_offsets_(0),
+      size_oat_class_type_(0),
+      size_oat_class_status_(0),
+      size_oat_class_num_methods_(0),
+      size_oat_class_method_bitmaps_(0),
+      size_oat_class_method_offsets_(0),
+      size_method_bss_mappings_(0u),
+      size_type_bss_mappings_(0u),
+      size_public_type_bss_mappings_(0u),
+      size_package_type_bss_mappings_(0u),
+      size_string_bss_mappings_(0u),
+      relative_patcher_(nullptr),
+      profile_compilation_info_(info),
+      compact_dex_level_(compact_dex_level) {}
 
 static bool ValidateDexFileHeader(const uint8_t* raw_header, const char* location) {
   const bool valid_standard_dex_magic = DexFileLoader::IsMagicValid(raw_header);
@@ -513,22 +446,6 @@
   return true;
 }
 
-static const UnalignedDexFileHeader* GetDexFileHeader(File* file,
-                                                      uint8_t* raw_header,
-                                                      const char* location) {
-  // Read the dex file header and perform minimal verification.
-  if (!file->ReadFully(raw_header, sizeof(DexFile::Header))) {
-    PLOG(ERROR) << "Failed to read dex file header. Actual: "
-                << " File: " << location << " Output: " << file->GetPath();
-    return nullptr;
-  }
-  if (!ValidateDexFileHeader(raw_header, location)) {
-    return nullptr;
-  }
-
-  return AsUnalignedDexFileHeader(raw_header);
-}
-
 bool OatWriter::AddDexFileSource(const char* filename, const char* location) {
   DCHECK(write_state_ == WriteState::kAddingDexFileSources);
   File fd(filename, O_RDONLY, /* check_usage= */ false);
@@ -545,58 +462,21 @@
 bool OatWriter::AddDexFileSource(File&& dex_file_fd, const char* location) {
   DCHECK(write_state_ == WriteState::kAddingDexFileSources);
   std::string error_msg;
-  uint32_t magic;
-  if (!ReadMagicAndReset(dex_file_fd.Fd(), &magic, &error_msg)) {
-    LOG(ERROR) << "Failed to read magic number from dex file '" << location << "': " << error_msg;
+  ArtDexFileLoader loader(dex_file_fd.Release(), location);
+  std::vector<std::unique_ptr<const DexFile>> dex_files;
+  if (!loader.Open(/*verify=*/false,
+                   /*verify_checksum=*/false,
+                   &error_msg,
+                   &dex_files)) {
+    LOG(ERROR) << "Failed to open dex file '" << location << "': " << error_msg;
     return false;
   }
-  if (DexFileLoader::IsMagicValid(magic)) {
-    uint8_t raw_header[sizeof(DexFile::Header)];
-    const UnalignedDexFileHeader* header = GetDexFileHeader(&dex_file_fd, raw_header, location);
-    if (header == nullptr) {
-      LOG(ERROR) << "Failed to get DexFileHeader from file descriptor for '"
-          << location << "': " << error_msg;
+  for (auto& dex_file : dex_files) {
+    if (dex_file->IsCompactDexFile()) {
+      LOG(ERROR) << "Compact dex is only supported from vdex: " << location;
       return false;
     }
-    // The file is open for reading, not writing, so it's OK to let the File destructor
-    // close it without checking for explicit Close(), so pass checkUsage = false.
-    raw_dex_files_.emplace_back(new File(dex_file_fd.Release(), location, /* checkUsage */ false));
-    oat_dex_files_.emplace_back(/* OatDexFile */
-        location,
-        DexFileSource(raw_dex_files_.back().get()),
-        header->checksum_,
-        header->file_size_);
-  } else if (IsZipMagic(magic)) {
-    zip_archives_.emplace_back(ZipArchive::OpenFromFd(dex_file_fd.Release(), location, &error_msg));
-    ZipArchive* zip_archive = zip_archives_.back().get();
-    if (zip_archive == nullptr) {
-      LOG(ERROR) << "Failed to open zip from file descriptor for '" << location << "': "
-          << error_msg;
-      return false;
-    }
-    for (size_t i = 0; ; ++i) {
-      std::string entry_name = DexFileLoader::GetMultiDexClassesDexName(i);
-      std::unique_ptr<ZipEntry> entry(zip_archive->Find(entry_name.c_str(), &error_msg));
-      if (entry == nullptr) {
-        break;
-      }
-      zipped_dex_files_.push_back(std::move(entry));
-      zipped_dex_file_locations_.push_back(DexFileLoader::GetMultiDexLocation(i, location));
-      const char* full_location = zipped_dex_file_locations_.back().c_str();
-      // We override the checksum from header with the CRC from ZIP entry.
-      oat_dex_files_.emplace_back(/* OatDexFile */
-          full_location,
-          DexFileSource(zipped_dex_files_.back().get()),
-          zipped_dex_files_.back()->GetCrc32(),
-          zipped_dex_files_.back()->GetUncompressedLength());
-    }
-    if (zipped_dex_file_locations_.empty()) {
-      LOG(ERROR) << "No dex files in zip file '" << location << "': " << error_msg;
-      return false;
-    }
-  } else {
-    LOG(ERROR) << "Expected valid zip or dex file: '" << location << "'";
-    return false;
+    oat_dex_files_.emplace_back(std::move(dex_file));
   }
   return true;
 }
@@ -619,14 +499,13 @@
       return false;
     }
     // We used `zipped_dex_file_locations_` to keep the strings in memory.
-    zipped_dex_file_locations_.push_back(DexFileLoader::GetMultiDexLocation(i, location));
-    const char* full_location = zipped_dex_file_locations_.back().c_str();
+    std::string multidex_location = DexFileLoader::GetMultiDexLocation(i, location);
     const UnalignedDexFileHeader* header = AsUnalignedDexFileHeader(current_dex_data);
-    oat_dex_files_.emplace_back(/* OatDexFile */
-        full_location,
-        DexFileSource(current_dex_data),
-        vdex_file.GetLocationChecksum(i),
-        header->file_size_);
+    if (!AddRawDexFileSource({current_dex_data, header->file_size_},
+                             multidex_location.c_str(),
+                             vdex_file.GetLocationChecksum(i))) {
+      return false;
+    }
   }
 
   if (vdex_file.GetNextDexFileData(current_dex_data, i) != nullptr) {
@@ -646,26 +525,18 @@
                                     const char* location,
                                     uint32_t location_checksum) {
   DCHECK(write_state_ == WriteState::kAddingDexFileSources);
-  if (data.size() < sizeof(DexFile::Header)) {
-    LOG(ERROR) << "Provided data is shorter than dex file header. size: "
-               << data.size() << " File: " << location;
+  std::string error_msg;
+  ArtDexFileLoader loader(data.data(), data.size(), location);
+  auto dex_file = loader.Open(location_checksum,
+                              nullptr,
+                              /*verify=*/false,
+                              /*verify_checksum=*/false,
+                              &error_msg);
+  if (dex_file == nullptr) {
+    LOG(ERROR) << "Failed to open dex file '" << location << "': " << error_msg;
     return false;
   }
-  if (!ValidateDexFileHeader(data.data(), location)) {
-    return false;
-  }
-  const UnalignedDexFileHeader* header = AsUnalignedDexFileHeader(data.data());
-  if (data.size() < header->file_size_) {
-    LOG(ERROR) << "Truncated dex file data. Data size: " << data.size()
-               << " file size from header: " << header->file_size_ << " File: " << location;
-    return false;
-  }
-
-  oat_dex_files_.emplace_back(/* OatDexFile */
-      location,
-      DexFileSource(data.data()),
-      location_checksum,
-      header->file_size_);
+  oat_dex_files_.emplace_back(std::move(dex_file));
   return true;
 }
 
@@ -700,8 +571,8 @@
   // Write DEX files into VDEX, mmap and open them.
   std::vector<MemMap> dex_files_map;
   std::vector<std::unique_ptr<const DexFile>> dex_files;
-  if (!WriteDexFiles(vdex_file, use_existing_vdex, copy_dex_files, &dex_files_map) ||
-      !OpenDexFiles(vdex_file, verify, &dex_files_map, &dex_files)) {
+  if (!WriteDexFiles(vdex_file, verify, use_existing_vdex, copy_dex_files, &dex_files_map) ||
+      !OpenDexFiles(vdex_file, &dex_files_map, &dex_files)) {
     return false;
   }
 
@@ -1013,7 +884,7 @@
     ClassStatus status;
     bool found = writer_->compiler_driver_->GetCompiledClass(class_ref, &status);
     if (!found) {
-      const VerificationResults* results = writer_->compiler_options_.GetVerificationResults();
+      const VerificationResults* results = writer_->verification_results_;
       if (results != nullptr && results->IsClassRejected(class_ref)) {
         // The oat class status is used only for verification of resolved classes,
         // so use ClassStatus::kErrorResolved whether the class was resolved or unresolved
@@ -1351,7 +1222,7 @@
 
     ArrayRef<const uint8_t> quick_code = compiled_method->GetQuickCode();
     uint32_t code_size = quick_code.size() * sizeof(uint8_t);
-    uint32_t thumb_offset = compiled_method->CodeDelta();
+    uint32_t thumb_offset = compiled_method->GetEntryPointAdjustment();
 
     // Deduplicate code arrays if we are not producing debuggable code.
     bool deduped = true;
@@ -1486,7 +1357,7 @@
     offset_ = relative_patcher_->ReserveSpace(offset_, compiled_method, method_ref);
     offset_ += CodeAlignmentSize(offset_, *compiled_method);
     DCHECK_ALIGNED_PARAM(offset_ + sizeof(OatQuickMethodHeader),
-                         GetInstructionSetAlignment(compiled_method->GetInstructionSet()));
+                         GetInstructionSetCodeAlignment(compiled_method->GetInstructionSet()));
     return offset_ + sizeof(OatQuickMethodHeader) + thumb_offset;
   }
 
@@ -1797,9 +1668,10 @@
         DCHECK_OFFSET_();
       }
       DCHECK_ALIGNED_PARAM(offset_ + sizeof(OatQuickMethodHeader),
-                           GetInstructionSetAlignment(compiled_method->GetInstructionSet()));
-      DCHECK_EQ(method_offsets.code_offset_,
-                offset_ + sizeof(OatQuickMethodHeader) + compiled_method->CodeDelta())
+                           GetInstructionSetCodeAlignment(compiled_method->GetInstructionSet()));
+      DCHECK_EQ(
+          method_offsets.code_offset_,
+          offset_ + sizeof(OatQuickMethodHeader) + compiled_method->GetEntryPointAdjustment())
           << dex_file_->PrettyMethod(method_ref.index);
       const OatQuickMethodHeader& method_header =
           oat_class->method_headers_[method_offsets_index];
@@ -2391,27 +2263,29 @@
   offset = RoundUp(offset, kPageSize);
   oat_header_->SetExecutableOffset(offset);
   size_executable_offset_alignment_ = offset - old_offset;
-  if (GetCompilerOptions().IsBootImage() && primary_oat_file_) {
-    InstructionSet instruction_set = compiler_options_.GetInstructionSet();
+  InstructionSet instruction_set = compiler_options_.GetInstructionSet();
+  if (GetCompilerOptions().IsBootImage() && primary_oat_file_ &&
+      // TODO(riscv64): remove this when we have compiler support for RISC-V.
+      instruction_set != InstructionSet::kRiscv64) {
     const bool generate_debug_info = GetCompilerOptions().GenerateAnyDebugInfo();
     size_t adjusted_offset = offset;
 
-    #define DO_TRAMPOLINE(field, fn_name)                                   \
-      /* Pad with at least four 0xFFs so we can do DCHECKs in OatQuickMethodHeader */ \
-      offset = CompiledCode::AlignCode(offset + 4, instruction_set);        \
-      adjusted_offset = offset + CompiledCode::CodeDelta(instruction_set);  \
-      oat_header_->Set ## fn_name ## Offset(adjusted_offset);               \
-      (field) = compiler_driver_->Create ## fn_name();                      \
-      if (generate_debug_info) {                                            \
-        debug::MethodDebugInfo info = {};                                   \
-        info.custom_name = #fn_name;                                        \
-        info.isa = instruction_set;                                         \
-        info.is_code_address_text_relative = true;                          \
-        /* Use the code offset rather than the `adjusted_offset`. */        \
-        info.code_address = offset - oat_header_->GetExecutableOffset();    \
-        info.code_size = (field)->size();                                   \
-        method_info_.push_back(std::move(info));                            \
-      }                                                                     \
+    #define DO_TRAMPOLINE(field, fn_name)                                                 \
+      /* Pad with at least four 0xFFs so we can do DCHECKs in OatQuickMethodHeader */     \
+      offset = CompiledCode::AlignCode(offset + 4, instruction_set);                      \
+      adjusted_offset = offset + GetInstructionSetEntryPointAdjustment(instruction_set);  \
+      oat_header_->Set ## fn_name ## Offset(adjusted_offset);                             \
+      (field) = compiler_driver_->Create ## fn_name();                                    \
+      if (generate_debug_info) {                                                          \
+        debug::MethodDebugInfo info = {};                                                 \
+        info.custom_name = #fn_name;                                                      \
+        info.isa = instruction_set;                                                       \
+        info.is_code_address_text_relative = true;                                        \
+        /* Use the code offset rather than the `adjusted_offset`. */                      \
+        info.code_address = offset - oat_header_->GetExecutableOffset();                  \
+        info.code_size = (field)->size();                                                 \
+        method_info_.push_back(std::move(info));                                          \
+      }                                                                                   \
       offset += (field)->size();
 
     DO_TRAMPOLINE(jni_dlsym_lookup_trampoline_, JniDlsymLookupTrampoline);
@@ -3197,9 +3071,10 @@
 }
 
 size_t OatWriter::WriteCode(OutputStream* out, size_t file_offset, size_t relative_offset) {
-  if (GetCompilerOptions().IsBootImage() && primary_oat_file_) {
-    InstructionSet instruction_set = compiler_options_.GetInstructionSet();
-
+  InstructionSet instruction_set = compiler_options_.GetInstructionSet();
+  if (GetCompilerOptions().IsBootImage() && primary_oat_file_ &&
+      // TODO(riscv64): remove this when we have compiler support for RISC-V.
+      instruction_set != InstructionSet::kRiscv64) {
     #define DO_TRAMPOLINE(field) \
       do { \
         /* Pad with at least four 0xFFs so we can do DCHECKs in OatQuickMethodHeader */ \
@@ -3304,6 +3179,7 @@
 }
 
 bool OatWriter::WriteDexFiles(File* file,
+                              bool verify,
                               bool use_existing_vdex,
                               CopyOption copy_dex_files,
                               /*out*/ std::vector<MemMap>* opened_dex_files_map) {
@@ -3313,12 +3189,8 @@
   if (copy_dex_files == CopyOption::kOnlyIfCompressed) {
     extract_dex_files_into_vdex_ = false;
     for (OatDexFile& oat_dex_file : oat_dex_files_) {
-      if (!oat_dex_file.source_.IsZipEntry()) {
-        extract_dex_files_into_vdex_ = true;
-        break;
-      }
-      ZipEntry* entry = oat_dex_file.source_.GetZipEntry();
-      if (!entry->IsUncompressed() || !entry->IsAlignedTo(alignof(DexFile::Header))) {
+      const DexFileContainer* container = oat_dex_file.GetDexFile()->GetContainer();
+      if (!(container->IsZip() && container->IsFileMap())) {
         extract_dex_files_into_vdex_ = true;
         break;
       }
@@ -3330,6 +3202,24 @@
     extract_dex_files_into_vdex_ = false;
   }
 
+  if (verify) {
+    TimingLogger::ScopedTiming split2("Verify input Dex files", timings_);
+    for (OatDexFile& oat_dex_file : oat_dex_files_) {
+      const DexFile* dex_file = oat_dex_file.GetDexFile();
+      if (dex_file->IsCompactDexFile()) {
+        continue;  // Compact dex files can not be verified.
+      }
+      std::string error_msg;
+      if (!dex::Verify(dex_file,
+                       dex_file->GetLocation().c_str(),
+                       /*verify_checksum=*/true,
+                       &error_msg)) {
+        LOG(ERROR) << "Failed to verify " << dex_file->GetLocation() << ": " << error_msg;
+        return false;
+      }
+    }
+  }
+
   if (extract_dex_files_into_vdex_) {
     vdex_dex_files_offset_ = vdex_size_;
 
@@ -3370,16 +3260,13 @@
       // Dex files from input vdex are represented as raw dex files and they can be
       // compact dex files. These need to specify the same shared data section if any.
       for (const OatDexFile& oat_dex_file : oat_dex_files_) {
-        if (!oat_dex_file.source_.IsRawData()) {
-          continue;
-        }
-        const uint8_t* raw_data = oat_dex_file.source_.GetRawData();
-        const UnalignedDexFileHeader& header = *AsUnalignedDexFileHeader(raw_data);
-        if (!CompactDexFile::IsMagicValid(header.magic_) || header.data_size_ == 0u) {
+        const DexFile* dex_file = oat_dex_file.GetDexFile();
+        auto& header = dex_file->GetHeader();
+        if (!dex_file->IsCompactDexFile() || header.data_size_ == 0u) {
           // Non compact dex does not have shared data section.
           continue;
         }
-        const uint8_t* cur_data_begin = raw_data + header.data_off_;
+        const uint8_t* cur_data_begin = dex_file->Begin() + header.data_off_;
         if (raw_dex_file_shared_data_begin == nullptr) {
           raw_dex_file_shared_data_begin = cur_data_begin;
         } else if (raw_dex_file_shared_data_begin != cur_data_begin) {
@@ -3434,9 +3321,28 @@
       vdex_size_ = RoundUp(vdex_size_, 4u);
       size_dex_file_alignment_ += vdex_size_ - old_vdex_size;
       // Write the actual dex file.
-      if (!WriteDexFile(file, &oat_dex_file, use_existing_vdex)) {
-        return false;
+      DCHECK_EQ(vdex_size_, oat_dex_file.dex_file_offset_);
+      uint8_t* out = vdex_begin_ + oat_dex_file.dex_file_offset_;
+      const std::vector<uint8_t>& cdex_data = oat_dex_file.cdex_main_section_;
+      if (!cdex_data.empty()) {
+        CHECK(!use_existing_vdex);
+        // Use the compact dex version instead of the original dex file.
+        DCHECK_EQ(oat_dex_file.dex_file_size_, cdex_data.size());
+        memcpy(out, cdex_data.data(), cdex_data.size());
+      } else {
+        const DexFile* dex_file = oat_dex_file.GetDexFile();
+        DCHECK_EQ(oat_dex_file.dex_file_size_, dex_file->Size());
+        if (use_existing_vdex) {
+          // The vdex already contains the data.
+          DCHECK_EQ(memcmp(out, dex_file->Begin(), dex_file->Size()), 0);
+        } else {
+          memcpy(out, dex_file->Begin(), dex_file->Size());
+        }
       }
+
+      // Update current size and account for the written data.
+      vdex_size_ += oat_dex_file.dex_file_size_;
+      size_dex_file_ += oat_dex_file.dex_file_size_;
     }
 
     // Write shared dex file data section and fix up the dex file headers.
@@ -3496,102 +3402,15 @@
 
 void OatWriter::CloseSources() {
   for (OatDexFile& oat_dex_file : oat_dex_files_) {
-    oat_dex_file.source_.Clear();  // Get rid of the reference, it's about to be invalidated.
+    oat_dex_file.dex_file_.reset();
   }
-  zipped_dex_files_.clear();
-  zip_archives_.clear();
-  raw_dex_files_.clear();
-}
-
-bool OatWriter::WriteDexFile(File* file,
-                             OatDexFile* oat_dex_file,
-                             bool use_existing_vdex) {
-  DCHECK_EQ(vdex_size_, oat_dex_file->dex_file_offset_);
-  if (oat_dex_file->source_.IsZipEntry()) {
-    DCHECK(!use_existing_vdex);
-    if (!WriteDexFile(file, oat_dex_file, oat_dex_file->source_.GetZipEntry())) {
-      return false;
-    }
-  } else if (oat_dex_file->source_.IsRawFile()) {
-    DCHECK(!use_existing_vdex);
-    if (!WriteDexFile(file, oat_dex_file, oat_dex_file->source_.GetRawFile())) {
-      return false;
-    }
-  } else {
-    DCHECK(oat_dex_file->source_.IsRawData());
-    const uint8_t* raw_data = oat_dex_file->source_.GetRawData();
-    if (!WriteDexFile(oat_dex_file, raw_data, use_existing_vdex)) {
-      return false;
-    }
-  }
-
-  // Update current size and account for the written data.
-  vdex_size_ += oat_dex_file->dex_file_size_;
-  size_dex_file_ += oat_dex_file->dex_file_size_;
-  return true;
 }
 
 bool OatWriter::LayoutDexFile(OatDexFile* oat_dex_file) {
   TimingLogger::ScopedTiming split("Dex Layout", timings_);
   std::string error_msg;
   std::string location(oat_dex_file->GetLocation());
-  std::unique_ptr<const DexFile> dex_file;
-  const ArtDexFileLoader dex_file_loader;
-  if (oat_dex_file->source_.IsZipEntry()) {
-    ZipEntry* zip_entry = oat_dex_file->source_.GetZipEntry();
-    MemMap mem_map;
-    {
-      TimingLogger::ScopedTiming extract("Unzip", timings_);
-      mem_map = zip_entry->ExtractToMemMap(location.c_str(), "classes.dex", &error_msg);
-    }
-    if (!mem_map.IsValid()) {
-      LOG(ERROR) << "Failed to extract dex file to mem map for layout: " << error_msg;
-      return false;
-    }
-    TimingLogger::ScopedTiming extract("Open", timings_);
-    dex_file = dex_file_loader.Open(location,
-                                    zip_entry->GetCrc32(),
-                                    std::move(mem_map),
-                                    /*verify=*/ true,
-                                    /*verify_checksum=*/ true,
-                                    &error_msg);
-  } else if (oat_dex_file->source_.IsRawFile()) {
-    File* raw_file = oat_dex_file->source_.GetRawFile();
-    int dup_fd = DupCloexec(raw_file->Fd());
-    if (dup_fd < 0) {
-      PLOG(ERROR) << "Failed to dup dex file descriptor (" << raw_file->Fd() << ") at " << location;
-      return false;
-    }
-    TimingLogger::ScopedTiming extract("Open", timings_);
-    dex_file = dex_file_loader.OpenDex(dup_fd, location,
-                                       /*verify=*/ true,
-                                       /*verify_checksum=*/ true,
-                                       /*mmap_shared=*/ false,
-                                       &error_msg);
-  } else {
-    // The source data is a vdex file.
-    CHECK(oat_dex_file->source_.IsRawData())
-        << static_cast<size_t>(oat_dex_file->source_.GetType());
-    const uint8_t* raw_dex_file = oat_dex_file->source_.GetRawData();
-    // Note: The raw data has already been checked to contain the header
-    // and all the data that the header specifies as the file size.
-    DCHECK(raw_dex_file != nullptr);
-    DCHECK(ValidateDexFileHeader(raw_dex_file, oat_dex_file->GetLocation()));
-    const UnalignedDexFileHeader* header = AsUnalignedDexFileHeader(raw_dex_file);
-    // Since the source may have had its layout changed, or may be quickened, don't verify it.
-    dex_file = dex_file_loader.Open(raw_dex_file,
-                                    header->file_size_,
-                                    location,
-                                    oat_dex_file->dex_file_location_checksum_,
-                                    nullptr,
-                                    /*verify=*/ false,
-                                    /*verify_checksum=*/ false,
-                                    &error_msg);
-  }
-  if (dex_file == nullptr) {
-    LOG(ERROR) << "Failed to open dex file for layout: " << error_msg;
-    return false;
-  }
+  std::unique_ptr<const DexFile>& dex_file = oat_dex_file->dex_file_;
   Options options;
   options.compact_dex_level_ = compact_dex_level_;
   options.update_checksum_ = true;
@@ -3604,11 +3423,11 @@
                                   &dex_container_,
                                   &error_msg)) {
       oat_dex_file->dex_sections_layout_ = dex_layout.GetSections();
-      oat_dex_file->source_.SetDexLayoutData(dex_container_->GetMainSection()->ReleaseData());
+      oat_dex_file->cdex_main_section_ = dex_container_->GetMainSection()->ReleaseData();
       // Dex layout can affect the size of the dex file, so we update here what we have set
       // when adding the dex file as a source.
       const UnalignedDexFileHeader* header =
-          AsUnalignedDexFileHeader(oat_dex_file->source_.GetRawData());
+          AsUnalignedDexFileHeader(oat_dex_file->cdex_main_section_.data());
       oat_dex_file->dex_file_size_ = header->file_size_;
     } else {
       LOG(WARNING) << "Failed to run dex layout, reason:" << error_msg;
@@ -3623,57 +3442,8 @@
   return true;
 }
 
-bool OatWriter::WriteDexFile(File* file,
-                             OatDexFile* oat_dex_file,
-                             ZipEntry* dex_file) {
-  uint8_t* raw_output = vdex_begin_ + oat_dex_file->dex_file_offset_;
-
-  // Extract the dex file.
-  std::string error_msg;
-  if (!dex_file->ExtractToMemory(raw_output, &error_msg)) {
-    LOG(ERROR) << "Failed to extract dex file from ZIP entry: " << error_msg
-               << " File: " << oat_dex_file->GetLocation() << " Output: " << file->GetPath();
-    return false;
-  }
-
-  return true;
-}
-
-bool OatWriter::WriteDexFile(File* file,
-                             OatDexFile* oat_dex_file,
-                             File* dex_file) {
-  uint8_t* raw_output = vdex_begin_ + oat_dex_file->dex_file_offset_;
-
-  if (!dex_file->PreadFully(raw_output, oat_dex_file->dex_file_size_, /*offset=*/ 0u)) {
-    PLOG(ERROR) << "Failed to copy dex file to vdex file."
-                << " File: " << oat_dex_file->GetLocation() << " Output: " << file->GetPath();
-    return false;
-  }
-
-  return true;
-}
-
-bool OatWriter::WriteDexFile(OatDexFile* oat_dex_file,
-                             const uint8_t* dex_file,
-                             bool use_existing_vdex) {
-  // Note: The raw data has already been checked to contain the header
-  // and all the data that the header specifies as the file size.
-  DCHECK(dex_file != nullptr);
-  DCHECK(ValidateDexFileHeader(dex_file, oat_dex_file->GetLocation()));
-  DCHECK_EQ(oat_dex_file->dex_file_size_, AsUnalignedDexFileHeader(dex_file)->file_size_);
-
-  if (use_existing_vdex) {
-    // The vdex already contains the dex code, no need to write it again.
-  } else {
-    uint8_t* raw_output = vdex_begin_ + oat_dex_file->dex_file_offset_;
-    memcpy(raw_output, dex_file, oat_dex_file->dex_file_size_);
-  }
-  return true;
-}
-
 bool OatWriter::OpenDexFiles(
     File* file,
-    bool verify,
     /*inout*/ std::vector<MemMap>* opened_dex_files_map,
     /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files) {
   TimingLogger::ScopedTiming split("OpenDexFiles", timings_);
@@ -3686,37 +3456,11 @@
   if (!extract_dex_files_into_vdex_) {
     DCHECK_EQ(opened_dex_files_map->size(), 0u);
     std::vector<std::unique_ptr<const DexFile>> dex_files;
-    std::vector<MemMap> maps;
     for (OatDexFile& oat_dex_file : oat_dex_files_) {
-      std::string error_msg;
-      maps.emplace_back(oat_dex_file.source_.GetZipEntry()->MapDirectlyOrExtract(
-          oat_dex_file.dex_file_location_data_,
-          "zipped dex",
-          &error_msg,
-          alignof(DexFile::Header)));
-      MemMap* map = &maps.back();
-      if (!map->IsValid()) {
-        LOG(ERROR) << error_msg;
-        return false;
-      }
-      // Now, open the dex file.
-      const ArtDexFileLoader dex_file_loader;
-      dex_files.emplace_back(dex_file_loader.Open(map->Begin(),
-                                                  map->Size(),
-                                                  oat_dex_file.GetLocation(),
-                                                  oat_dex_file.dex_file_location_checksum_,
-                                                  /* oat_dex_file */ nullptr,
-                                                  verify,
-                                                  verify,
-                                                  &error_msg));
-      if (dex_files.back() == nullptr) {
-        LOG(ERROR) << "Failed to open dex file from oat file. File: " << oat_dex_file.GetLocation()
-                   << " Error: " << error_msg;
-        return false;
-      }
+      // The dex file is already open, release the reference.
+      dex_files.emplace_back(std::move(oat_dex_file.dex_file_));
       oat_dex_file.class_offsets_.resize(dex_files.back()->GetHeader().class_defs_size_);
     }
-    *opened_dex_files_map = std::move(maps);
     *opened_dex_files = std::move(dex_files);
     CloseSources();
     return true;
@@ -3727,7 +3471,6 @@
 
   DCHECK_EQ(opened_dex_files_map->size(), 1u);
   DCHECK(vdex_begin_ == opened_dex_files_map->front().Begin());
-  const ArtDexFileLoader dex_file_loader;
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   for (OatDexFile& oat_dex_file : oat_dex_files_) {
     const uint8_t* raw_dex_file = vdex_begin_ + oat_dex_file.dex_file_offset_;
@@ -3749,13 +3492,13 @@
 
     // Now, open the dex file.
     std::string error_msg;
-    dex_files.emplace_back(dex_file_loader.Open(raw_dex_file,
-                                                oat_dex_file.dex_file_size_,
-                                                oat_dex_file.GetLocation(),
-                                                oat_dex_file.dex_file_location_checksum_,
-                                                /* oat_dex_file */ nullptr,
-                                                verify,
-                                                verify,
+    ArtDexFileLoader dex_file_loader(
+        raw_dex_file, oat_dex_file.dex_file_size_, oat_dex_file.GetLocation());
+    // All dex files have been already verified in WriteDexFiles before we copied them.
+    dex_files.emplace_back(dex_file_loader.Open(oat_dex_file.dex_file_location_checksum_,
+                                                /*oat_dex_file=*/nullptr,
+                                                /*verify=*/false,
+                                                /*verify_checksum=*/false,
                                                 &error_msg));
     if (dex_files.back() == nullptr) {
       LOG(ERROR) << "Failed to open dex file from oat file. File: " << oat_dex_file.GetLocation()
@@ -4066,16 +3809,14 @@
   }
 }
 
-OatWriter::OatDexFile::OatDexFile(const char* dex_file_location,
-                                  DexFileSource source,
-                                  uint32_t dex_file_location_checksum,
-                                  size_t dex_file_size)
-    : source_(std::move(source)),
-      dex_file_size_(dex_file_size),
+OatWriter::OatDexFile::OatDexFile(std::unique_ptr<const DexFile> dex_file)
+    : dex_file_(std::move(dex_file)),
+      dex_file_location_(std::make_unique<std::string>(dex_file_->GetLocation())),
+      dex_file_size_(dex_file_->Size()),
       offset_(0),
-      dex_file_location_size_(strlen(dex_file_location)),
-      dex_file_location_data_(dex_file_location),
-      dex_file_location_checksum_(dex_file_location_checksum),
+      dex_file_location_size_(strlen(dex_file_location_->c_str())),
+      dex_file_location_data_(dex_file_location_->c_str()),
+      dex_file_location_checksum_(dex_file_->GetLocationChecksum()),
       dex_file_offset_(0u),
       lookup_table_offset_(0u),
       class_offsets_offset_(0u),
@@ -4085,8 +3826,7 @@
       package_type_bss_mapping_offset_(0u),
       string_bss_mapping_offset_(0u),
       dex_sections_layout_offset_(0u),
-      class_offsets_() {
-}
+      class_offsets_() {}
 
 size_t OatWriter::OatDexFile::SizeOf() const {
   return sizeof(dex_file_location_size_)
diff --git a/dex2oat/linker/oat_writer.h b/dex2oat/linker/oat_writer.h
index ebff7a1..79ec47e 100644
--- a/dex2oat/linker/oat_writer.h
+++ b/dex2oat/linker/oat_writer.h
@@ -49,6 +49,7 @@
 class TimingLogger;
 class TypeLookupTable;
 class VdexFile;
+class VerificationResults;
 class ZipEntry;
 
 namespace debug {
@@ -115,6 +116,7 @@
 class OatWriter {
  public:
   OatWriter(const CompilerOptions& compiler_options,
+            const VerificationResults* verification_results,
             TimingLogger* timings,
             ProfileCompilationInfo* info,
             CompactDexLevel compact_dex_level);
@@ -246,7 +248,6 @@
  private:
   struct BssMappingInfo;
   class ChecksumUpdatingOutputStream;
-  class DexFileSource;
   class OatClassHeader;
   class OatClass;
   class OatDexFile;
@@ -277,24 +278,12 @@
   // If `update_input_vdex` is true, then this method won't actually write the dex files,
   // and the compiler will just re-use the existing vdex file.
   bool WriteDexFiles(File* file,
+                     bool verify,
                      bool use_existing_vdex,
                      CopyOption copy_dex_files,
                      /*out*/ std::vector<MemMap>* opened_dex_files_map);
-  bool WriteDexFile(File* file,
-                    OatDexFile* oat_dex_file,
-                    bool use_existing_vdex);
   bool LayoutDexFile(OatDexFile* oat_dex_file);
-  bool WriteDexFile(File* file,
-                    OatDexFile* oat_dex_file,
-                    ZipEntry* dex_file);
-  bool WriteDexFile(File* file,
-                    OatDexFile* oat_dex_file,
-                    File* dex_file);
-  bool WriteDexFile(OatDexFile* oat_dex_file,
-                    const uint8_t* dex_file,
-                    bool use_existing_vdex);
   bool OpenDexFiles(File* file,
-                    bool verify,
                     /*inout*/ std::vector<MemMap>* opened_dex_files_map,
                     /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files);
   void WriteQuickeningInfo(/*out*/std::vector<uint8_t>* buffer);
@@ -377,20 +366,13 @@
   WriteState write_state_;
   TimingLogger* timings_;
 
-  std::vector<std::unique_ptr<File>> raw_dex_files_;
-  std::vector<std::unique_ptr<ZipArchive>> zip_archives_;
-  std::vector<std::unique_ptr<ZipEntry>> zipped_dex_files_;
-
-  // Using std::list<> which doesn't move elements around on push/emplace_back().
-  // We need this because we keep plain pointers to the strings' c_str().
-  std::list<std::string> zipped_dex_file_locations_;
-
   dchecked_vector<debug::MethodDebugInfo> method_info_;
 
   std::vector<uint8_t> code_info_data_;
 
   const CompilerDriver* compiler_driver_;
   const CompilerOptions& compiler_options_;
+  const VerificationResults* const verification_results_;
   ImageWriter* image_writer_;
   // Whether the dex files being compiled are going to be extracted to the vdex.
   bool extract_dex_files_into_vdex_;
diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc
index 6b2198d..6742cf77 100644
--- a/dex2oat/linker/oat_writer_test.cc
+++ b/dex2oat/linker/oat_writer_test.cc
@@ -24,7 +24,6 @@
 #include "base/unix_file/fd_file.h"
 #include "class_linker.h"
 #include "common_compiler_driver_test.h"
-#include "compiled_method-inl.h"
 #include "compiler.h"
 #include "debug/method_debug_info.h"
 #include "dex/class_accessor-inl.h"
@@ -32,6 +31,7 @@
 #include "dex/quick_compiler_callbacks.h"
 #include "dex/test_dex_file_builder.h"
 #include "dex/verification_results.h"
+#include "driver/compiled_method-inl.h"
 #include "driver/compiler_driver.h"
 #include "driver/compiler_options.h"
 #include "entrypoints/quick/quick_entrypoints.h"
@@ -107,6 +107,7 @@
     TimingLogger timings("WriteElf", false, false);
     ClearBootImageOption();
     OatWriter oat_writer(*compiler_options_,
+                         verification_results_.get(),
                          &timings,
                          /*profile_compilation_info*/nullptr,
                          CompactDexLevel::kCompactDexLevelNone);
@@ -134,6 +135,7 @@
     TimingLogger timings("WriteElf", false, false);
     ClearBootImageOption();
     OatWriter oat_writer(*compiler_options_,
+                         verification_results_.get(),
                          &timings,
                          profile_compilation_info,
                          CompactDexLevel::kCompactDexLevelNone);
@@ -156,6 +158,7 @@
     TimingLogger timings("WriteElf", false, false);
     ClearBootImageOption();
     OatWriter oat_writer(*compiler_options_,
+                         verification_results_.get(),
                          &timings,
                          profile_compilation_info,
                          CompactDexLevel::kCompactDexLevelNone);
@@ -181,7 +184,7 @@
     if (!oat_writer.WriteAndOpenDexFiles(
         vdex_file,
         verify,
-        /*update_input_vdex=*/ false,
+        /*use_existing_vdex=*/ false,
         copy,
         &opened_dex_files_maps,
         &opened_dex_files)) {
@@ -505,7 +508,7 @@
   EXPECT_EQ(68U, sizeof(OatHeader));
   EXPECT_EQ(4U, sizeof(OatMethodOffsets));
   EXPECT_EQ(4U, sizeof(OatQuickMethodHeader));
-  EXPECT_EQ(167 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)),
+  EXPECT_EQ(170 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)),
             sizeof(QuickEntryPoints));
 }
 
diff --git a/dex2oat/linker/relative_patcher_test.h b/dex2oat/linker/relative_patcher_test.h
index 56e0dc1..2900523 100644
--- a/dex2oat/linker/relative_patcher_test.h
+++ b/dex2oat/linker/relative_patcher_test.h
@@ -24,9 +24,9 @@
 #include "base/array_ref.h"
 #include "base/globals.h"
 #include "base/macros.h"
-#include "compiled_method-inl.h"
 #include "dex/method_reference.h"
 #include "dex/string_reference.h"
+#include "driver/compiled_method-inl.h"
 #include "driver/compiled_method_storage.h"
 #include "linker/relative_patcher.h"
 #include "oat_quick_method_header.h"
@@ -132,7 +132,7 @@
       offset += alignment_size;
 
       offset += sizeof(OatQuickMethodHeader);
-      uint32_t quick_code_offset = offset + compiled_method->CodeDelta();
+      uint32_t quick_code_offset = offset + compiled_method->GetEntryPointAdjustment();
       const auto code = compiled_method->GetQuickCode();
       offset += code.size();
 
@@ -172,7 +172,8 @@
           if (patch.GetType() == LinkerPatch::Type::kCallRelative) {
             auto result = method_offset_map_.FindMethodOffset(patch.TargetMethod());
             uint32_t target_offset =
-                result.first ? result.second : kTrampolineOffset + compiled_method->CodeDelta();
+                result.first ? result.second
+                             : kTrampolineOffset + compiled_method->GetEntryPointAdjustment();
             patcher_->PatchCall(&patched_code_,
                                 patch.LiteralOffset(),
                                 offset + patch.LiteralOffset(),
@@ -227,7 +228,7 @@
 
     auto result = method_offset_map_.FindMethodOffset(method_ref);
     CHECK(result.first);  // Must have been linked.
-    size_t offset = result.second - compiled_methods_[idx]->CodeDelta();
+    size_t offset = result.second - compiled_methods_[idx]->GetEntryPointAdjustment();
     CHECK_LT(offset, output_.size());
     CHECK_LE(offset + expected_code.size(), output_.size());
     ArrayRef<const uint8_t> linked_code(&output_[offset], expected_code.size());
diff --git a/dex2oat/linker/x86/relative_patcher_x86.cc b/dex2oat/linker/x86/relative_patcher_x86.cc
index e3b94b0..a444446 100644
--- a/dex2oat/linker/x86/relative_patcher_x86.cc
+++ b/dex2oat/linker/x86/relative_patcher_x86.cc
@@ -16,7 +16,6 @@
 
 #include "linker/x86/relative_patcher_x86.h"
 
-#include "compiled_method.h"
 #include "linker/linker_patch.h"
 
 namespace art {
diff --git a/dex2oat/linker/x86_64/relative_patcher_x86_64.cc b/dex2oat/linker/x86_64/relative_patcher_x86_64.cc
index 0b9d07e..629affc 100644
--- a/dex2oat/linker/x86_64/relative_patcher_x86_64.cc
+++ b/dex2oat/linker/x86_64/relative_patcher_x86_64.cc
@@ -16,7 +16,6 @@
 
 #include "linker/x86_64/relative_patcher_x86_64.h"
 
-#include "compiled_method.h"
 #include "linker/linker_patch.h"
 
 namespace art {
diff --git a/compiler/utils/swap_space.cc b/dex2oat/utils/swap_space.cc
similarity index 100%
rename from compiler/utils/swap_space.cc
rename to dex2oat/utils/swap_space.cc
diff --git a/dex2oat/utils/swap_space.h b/dex2oat/utils/swap_space.h
new file mode 100644
index 0000000..aba6485
--- /dev/null
+++ b/dex2oat/utils/swap_space.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2014 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_DEX2OAT_UTILS_SWAP_SPACE_H_
+#define ART_DEX2OAT_UTILS_SWAP_SPACE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <cstdlib>
+#include <list>
+#include <set>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/mutex.h"
+
+namespace art {
+
+// An arena pool that creates arenas backed by an mmaped file.
+class SwapSpace {
+ public:
+  SwapSpace(int fd, size_t initial_size);
+  ~SwapSpace();
+  void* Alloc(size_t size) REQUIRES(!lock_);
+  void Free(void* ptr, size_t size) REQUIRES(!lock_);
+
+  size_t GetSize() {
+    return size_;
+  }
+
+ private:
+  // Chunk of space.
+  struct SpaceChunk {
+    // We need mutable members as we keep these objects in a std::set<> (providing only const
+    // access) but we modify these members while carefully preserving the std::set<> ordering.
+    mutable uint8_t* ptr;
+    mutable size_t size;
+
+    uintptr_t Start() const {
+      return reinterpret_cast<uintptr_t>(ptr);
+    }
+    uintptr_t End() const {
+      return reinterpret_cast<uintptr_t>(ptr) + size;
+    }
+  };
+
+  class SortChunkByPtr {
+   public:
+    bool operator()(const SpaceChunk& a, const SpaceChunk& b) const {
+      return reinterpret_cast<uintptr_t>(a.ptr) < reinterpret_cast<uintptr_t>(b.ptr);
+    }
+  };
+
+  using FreeByStartSet = std::set<SpaceChunk, SortChunkByPtr>;
+
+  // Map size to an iterator to free_by_start_'s entry.
+  struct FreeBySizeEntry {
+    FreeBySizeEntry(size_t sz, FreeByStartSet::const_iterator entry)
+        : size(sz), free_by_start_entry(entry) { }
+
+    // We need mutable members as we keep these objects in a std::set<> (providing only const
+    // access) but we modify these members while carefully preserving the std::set<> ordering.
+    mutable size_t size;
+    mutable FreeByStartSet::const_iterator free_by_start_entry;
+  };
+  struct FreeBySizeComparator {
+    bool operator()(const FreeBySizeEntry& lhs, const FreeBySizeEntry& rhs) const {
+      if (lhs.size != rhs.size) {
+        return lhs.size < rhs.size;
+      } else {
+        return lhs.free_by_start_entry->Start() < rhs.free_by_start_entry->Start();
+      }
+    }
+  };
+  using FreeBySizeSet = std::set<FreeBySizeEntry, FreeBySizeComparator>;
+
+  SpaceChunk NewFileChunk(size_t min_size) REQUIRES(lock_);
+
+  void RemoveChunk(FreeBySizeSet::const_iterator free_by_size_pos) REQUIRES(lock_);
+  void InsertChunk(const SpaceChunk& chunk) REQUIRES(lock_);
+
+  int fd_;
+  size_t size_;
+
+  // NOTE: Boost.Bimap would be useful for the two following members.
+
+  // Map start of a free chunk to its size.
+  FreeByStartSet free_by_start_ GUARDED_BY(lock_);
+  // Free chunks ordered by size.
+  FreeBySizeSet free_by_size_ GUARDED_BY(lock_);
+
+  mutable Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+  DISALLOW_COPY_AND_ASSIGN(SwapSpace);
+};
+
+template <typename T> class SwapAllocator;
+
+template <>
+class SwapAllocator<void> {
+ public:
+  using value_type    = void;
+  using pointer       = void*;
+  using const_pointer = const void*;
+
+  template <typename U>
+  struct rebind {
+    using other = SwapAllocator<U>;
+  };
+
+  explicit SwapAllocator(SwapSpace* swap_space) : swap_space_(swap_space) {}
+
+  template <typename U>
+  SwapAllocator(const SwapAllocator<U>& other)
+      : swap_space_(other.swap_space_) {}
+
+  SwapAllocator(const SwapAllocator& other) = default;
+  SwapAllocator& operator=(const SwapAllocator& other) = default;
+  ~SwapAllocator() = default;
+
+ private:
+  SwapSpace* swap_space_;
+
+  template <typename U>
+  friend class SwapAllocator;
+
+  template <typename U>
+  friend bool operator==(const SwapAllocator<U>& lhs, const SwapAllocator<U>& rhs);
+};
+
+template <typename T>
+class SwapAllocator {
+ public:
+  using value_type      = T;
+  using pointer         = T*;
+  using reference       = T&;
+  using const_pointer   = const T*;
+  using const_reference = const T&;
+  using size_type       = size_t;
+  using difference_type = ptrdiff_t;
+
+  template <typename U>
+  struct rebind {
+    using other = SwapAllocator<U>;
+  };
+
+  explicit SwapAllocator(SwapSpace* swap_space) : swap_space_(swap_space) {}
+
+  template <typename U>
+  SwapAllocator(const SwapAllocator<U>& other)
+      : swap_space_(other.swap_space_) {}
+
+  SwapAllocator(const SwapAllocator& other) = default;
+  SwapAllocator& operator=(const SwapAllocator& other) = default;
+  ~SwapAllocator() = default;
+
+  size_type max_size() const {
+    return static_cast<size_type>(-1) / sizeof(T);
+  }
+
+  pointer address(reference x) const { return &x; }
+  const_pointer address(const_reference x) const { return &x; }
+
+  pointer allocate(size_type n, SwapAllocator<void>::pointer hint ATTRIBUTE_UNUSED = nullptr) {
+    DCHECK_LE(n, max_size());
+    if (swap_space_ == nullptr) {
+      T* result = reinterpret_cast<T*>(malloc(n * sizeof(T)));
+      CHECK_IMPLIES(result == nullptr, n == 0u);  // Abort if malloc() fails.
+      return result;
+    } else {
+      return reinterpret_cast<T*>(swap_space_->Alloc(n * sizeof(T)));
+    }
+  }
+  void deallocate(pointer p, size_type n) {
+    if (swap_space_ == nullptr) {
+      free(p);
+    } else {
+      swap_space_->Free(p, n * sizeof(T));
+    }
+  }
+
+  void construct(pointer p, const_reference val) {
+    new (static_cast<void*>(p)) value_type(val);
+  }
+  template <class U, class... Args>
+  void construct(U* p, Args&&... args) {
+    ::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
+  }
+  void destroy(pointer p) {
+    p->~value_type();
+  }
+
+  inline bool operator==(SwapAllocator const& other) {
+    return swap_space_ == other.swap_space_;
+  }
+  inline bool operator!=(SwapAllocator const& other) {
+    return !operator==(other);
+  }
+
+ private:
+  SwapSpace* swap_space_;
+
+  template <typename U>
+  friend class SwapAllocator;
+
+  template <typename U>
+  friend bool operator==(const SwapAllocator<U>& lhs, const SwapAllocator<U>& rhs);
+};
+
+template <typename T>
+inline bool operator==(const SwapAllocator<T>& lhs, const SwapAllocator<T>& rhs) {
+  return lhs.swap_space_ == rhs.swap_space_;
+}
+
+template <typename T>
+inline bool operator!=(const SwapAllocator<T>& lhs, const SwapAllocator<T>& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename T>
+using SwapVector = std::vector<T, SwapAllocator<T>>;
+template <typename T, typename Comparator>
+using SwapSet = std::set<T, Comparator, SwapAllocator<T>>;
+
+}  // namespace art
+
+#endif  // ART_DEX2OAT_UTILS_SWAP_SPACE_H_
diff --git a/dex2oat/utils/swap_space_test.cc b/dex2oat/utils/swap_space_test.cc
new file mode 100644
index 0000000..eb79cfd
--- /dev/null
+++ b/dex2oat/utils/swap_space_test.cc
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include "utils/swap_space.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cstdio>
+
+#include "gtest/gtest.h"
+
+#include "base/common_art_test.h"
+#include "base/os.h"
+#include "base/unix_file/fd_file.h"
+
+namespace art {
+
+class SwapSpaceTest : public CommonArtTest {};
+
+static void SwapTest(bool use_file) {
+  ScratchFile scratch;
+  int fd = scratch.GetFd();
+  unlink(scratch.GetFilename().c_str());
+
+  SwapSpace pool(fd, 1 * MB);
+  SwapAllocator<void> alloc(use_file ? &pool : nullptr);
+
+  SwapVector<int32_t> v(alloc);
+  v.reserve(1000000);
+  for (int32_t i = 0; i < 1000000; ++i) {
+    v.push_back(i);
+    EXPECT_EQ(i, v[i]);
+  }
+
+  SwapVector<int32_t> v2(alloc);
+  v2.reserve(1000000);
+  for (int32_t i = 0; i < 1000000; ++i) {
+    v2.push_back(i);
+    EXPECT_EQ(i, v2[i]);
+  }
+
+  SwapVector<int32_t> v3(alloc);
+  v3.reserve(500000);
+  for (int32_t i = 0; i < 1000000; ++i) {
+    v3.push_back(i);
+    EXPECT_EQ(i, v2[i]);
+  }
+
+  // Verify contents.
+  for (int32_t i = 0; i < 1000000; ++i) {
+    EXPECT_EQ(i, v[i]);
+    EXPECT_EQ(i, v2[i]);
+    EXPECT_EQ(i, v3[i]);
+  }
+
+  scratch.Close();
+}
+
+TEST_F(SwapSpaceTest, Memory) {
+  SwapTest(false);
+}
+
+TEST_F(SwapSpaceTest, Swap) {
+  SwapTest(true);
+}
+
+}  // namespace art
diff --git a/dex2oat/verifier_deps_test.cc b/dex2oat/verifier_deps_test.cc
index 708af04..00593f5 100644
--- a/dex2oat/verifier_deps_test.cc
+++ b/dex2oat/verifier_deps_test.cc
@@ -47,6 +47,7 @@
         deps_(nullptr) {}
 
   void AddUncompilableMethod(MethodReference ref ATTRIBUTE_UNUSED) override {}
+  void AddUncompilableClass(ClassReference ref ATTRIBUTE_UNUSED) override {}
   void ClassRejected(ClassReference ref ATTRIBUTE_UNUSED) override {}
 
   verifier::VerifierDeps* GetVerifierDeps() const override { return deps_; }
@@ -501,6 +502,7 @@
   std::vector<std::unique_ptr<const DexFile>> first_dex_files = OpenTestDexFiles("VerifierDeps");
   std::vector<std::unique_ptr<const DexFile>> second_dex_files = OpenTestDexFiles("MultiDex");
   std::vector<const DexFile*> dex_files;
+  dex_files.reserve(first_dex_files.size() + second_dex_files.size());
   for (auto& dex_file : first_dex_files) {
     dex_files.push_back(dex_file.get());
   }
@@ -630,12 +632,5 @@
   ASSERT_FALSE(buffer.empty());
 }
 
-TEST_F(VerifierDepsTest, Assignable_Arrays) {
-  ASSERT_TRUE(TestAssignabilityRecording(/* dst= */ "[LIface;",
-                                         /* src= */ "[LMyClassExtendingInterface;"));
-  ASSERT_FALSE(HasAssignable(
-      "LIface;", "LMyClassExtendingInterface;"));
-}
-
 }  // namespace verifier
 }  // namespace art
diff --git a/dexdump/Android.bp b/dexdump/Android.bp
index 1bc6334..da43fbe 100644
--- a/dexdump/Android.bp
+++ b/dexdump/Android.bp
@@ -69,6 +69,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
diff --git a/dexdump/dexdump.cc b/dexdump/dexdump.cc
index 2ab98ec..7e4c1a6 100644
--- a/dexdump/dexdump.cc
+++ b/dexdump/dexdump.cc
@@ -1962,18 +1962,12 @@
     LOG(ERROR) << "ReadFileToString failed";
     return -1;
   }
-  const DexFileLoader dex_file_loader;
   DexFileLoaderErrorCode error_code;
   std::string error_msg;
   std::vector<std::unique_ptr<const DexFile>> dex_files;
-  if (!dex_file_loader.OpenAll(reinterpret_cast<const uint8_t*>(content.data()),
-                               content.size(),
-                               fileName,
-                               kVerify,
-                               kVerifyChecksum,
-                               &error_code,
-                               &error_msg,
-                               &dex_files)) {
+  DexFileLoader dex_file_loader(
+      reinterpret_cast<const uint8_t*>(content.data()), content.size(), fileName);
+  if (!dex_file_loader.Open(kVerify, kVerifyChecksum, &error_code, &error_msg, &dex_files)) {
     // Display returned error message to user. Note that this error behavior
     // differs from the error messages shown by the original Dalvik dexdump.
     LOG(ERROR) << error_msg;
diff --git a/dexdump/dexdump_main.cc b/dexdump/dexdump_main.cc
index 3afee0d..fed2ba7 100644
--- a/dexdump/dexdump_main.cc
+++ b/dexdump/dexdump_main.cc
@@ -22,13 +22,13 @@
  * Also, ODEX files are no longer supported.
  */
 
-#include "dexdump.h"
-
+#include <android-base/logging.h>
+#include <base/mem_map.h>
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
 
-#include <android-base/logging.h>
+#include "dexdump.h"
 
 namespace art {
 
@@ -137,7 +137,7 @@
 
   // Open alternative output file.
   if (gOptions.outputFileName) {
-    gOutFile = fopen(gOptions.outputFileName, "w");
+    gOutFile = fopen(gOptions.outputFileName, "we");
     if (!gOutFile) {
       PLOG(ERROR) << "Can't open " << gOptions.outputFileName;
       return 1;
@@ -157,6 +157,7 @@
 int main(int argc, char** argv) {
   // Output all logging to stderr.
   android::base::SetLogger(android::base::StderrLogger);
+  art::MemMap::Init();
 
   return art::dexdumpDriver(argc, argv);
 }
diff --git a/dexlayout/Android.bp b/dexlayout/Android.bp
index 922e134..ec7fdf6 100644
--- a/dexlayout/Android.bp
+++ b/dexlayout/Android.bp
@@ -106,6 +106,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -191,6 +192,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
diff --git a/dexlayout/dex_ir.h b/dexlayout/dex_ir.h
index 66ca84b..c819c67 100644
--- a/dexlayout/dex_ir.h
+++ b/dexlayout/dex_ir.h
@@ -20,7 +20,7 @@
 #define ART_DEXLAYOUT_DEX_IR_H_
 
 #include <stdint.h>
-
+#include <unordered_map>
 #include <vector>
 
 #include "base/iteration_range.h"
@@ -262,13 +262,21 @@
   // Sort the vector by copying pointers over.
   template <typename MapType>
   void SortByMapOrder(const MapType& map) {
-    auto it = map.begin();
     CHECK_EQ(map.size(), Size());
+
+    // Move all pointers to a temporary map owning the pointers.
+    std::unordered_map<T*, ElementType> pointers_map;
+    pointers_map.reserve(Size());
+    for (std::unique_ptr<T>& element : collection_) {
+      pointers_map[element.get()] = std::move(element);
+    }
+
+    // Move back the pointers to the original vector according to the map order.
+    auto it = map.begin();
     for (size_t i = 0; i < Size(); ++i) {
-      // There are times when the array will temporarily contain the same pointer twice, doing the
-      // release here sure there is no double free errors.
-      collection_[i].release();
-      collection_[i].reset(it->second);
+      auto element_it = pointers_map.find(it->second);
+      DCHECK(element_it != pointers_map.end());
+      collection_[i] = std::move(element_it->second);
       ++it;
     }
   }
diff --git a/dexlayout/dex_writer.cc b/dexlayout/dex_writer.cc
index 7f05ae8..e7473c0 100644
--- a/dexlayout/dex_writer.cc
+++ b/dexlayout/dex_writer.cc
@@ -30,8 +30,6 @@
 
 namespace art {
 
-constexpr uint32_t DexWriter::kDataSectionAlignment;
-
 static size_t EncodeIntValue(int32_t value, uint8_t* buffer) {
   size_t length = 0;
   if (value >= 0) {
diff --git a/dexlayout/dexdiag_test.cc b/dexlayout/dexdiag_test.cc
index 27ac402..3cd80b4 100644
--- a/dexlayout/dexdiag_test.cc
+++ b/dexlayout/dexdiag_test.cc
@@ -33,9 +33,7 @@
 
 class DexDiagTest : public CommonArtTest {
  protected:
-  void SetUp() override {
-    CommonArtTest::SetUp();
-  }
+  void SetUp() override { CommonArtTest::SetUp(); }
 
   // Path to the dexdiag(d?)[32|64] binary.
   std::string GetDexDiagFilePath() {
@@ -63,11 +61,11 @@
     EXPECT_TRUE(!oat_location.empty());
     std::cout << "==" << oat_location << std::endl;
     std::string error_msg;
-    std::unique_ptr<OatFile> oat(OatFile::Open(/*zip_fd=*/ -1,
-                                               oat_location.c_str(),
-                                               oat_location.c_str(),
-                                               /*executable=*/ false,
-                                               /*low_4gb=*/ false,
+    std::unique_ptr<OatFile> oat(OatFile::Open(/*zip_fd=*/-1,
+                                               oat_location,
+                                               oat_location,
+                                               /*executable=*/false,
+                                               /*low_4gb=*/false,
                                                &error_msg));
     EXPECT_TRUE(oat != nullptr) << error_msg;
     return oat;
@@ -82,8 +80,8 @@
 
     // Build the command line "dexdiag <args> this_pid".
     std::string executable_path = GetDexDiagFilePath();
-    EXPECT_TRUE(OS::FileExists(executable_path.c_str())) << executable_path
-                                                         << " should be a valid file path";
+    EXPECT_TRUE(OS::FileExists(executable_path.c_str()))
+        << executable_path << " should be a valid file path";
     exec_argv.push_back(executable_path);
     for (const auto& arg : args) {
       exec_argv.push_back(arg);
@@ -102,11 +100,11 @@
 TEST_F(DexDiagTest, DexDiagHelpTest) {
   // TODO: test the resulting output.
   std::string error_msg;
-  ASSERT_TRUE(Exec(getpid(), { kDexDiagHelp }, &error_msg)) << "Failed to execute -- because: "
-                                                            << error_msg;
+  ASSERT_TRUE(Exec(getpid(), {kDexDiagHelp}, &error_msg))
+      << "Failed to execute -- because: " << error_msg;
 }
 
-#if defined (ART_TARGET)
+#if defined(ART_TARGET)
 TEST_F(DexDiagTest, DexDiagContainsTest) {
 #else
 TEST_F(DexDiagTest, DISABLED_DexDiagContainsTest) {
@@ -114,11 +112,11 @@
   std::unique_ptr<OatFile> oat = OpenOatAndVdexFiles();
   // TODO: test the resulting output.
   std::string error_msg;
-  ASSERT_TRUE(Exec(getpid(), { kDexDiagContains }, &error_msg)) << "Failed to execute -- because: "
-                                                                << error_msg;
+  ASSERT_TRUE(Exec(getpid(), {kDexDiagContains}, &error_msg))
+      << "Failed to execute -- because: " << error_msg;
 }
 
-#if defined (ART_TARGET)
+#if defined(ART_TARGET)
 TEST_F(DexDiagTest, DexDiagContainsFailsTest) {
 #else
 TEST_F(DexDiagTest, DISABLED_DexDiagContainsFailsTest) {
@@ -126,12 +124,11 @@
   std::unique_ptr<OatFile> oat = OpenOatAndVdexFiles();
   // TODO: test the resulting output.
   std::string error_msg;
-  ASSERT_FALSE(Exec(getpid(), { kDexDiagContainsFails }, &error_msg))
-      << "Failed to execute -- because: "
-      << error_msg;
+  ASSERT_FALSE(Exec(getpid(), {kDexDiagContainsFails}, &error_msg))
+      << "Failed to execute -- because: " << error_msg;
 }
 
-#if defined (ART_TARGET)
+#if defined(ART_TARGET)
 TEST_F(DexDiagTest, DexDiagVerboseTest) {
 #else
 TEST_F(DexDiagTest, DISABLED_DexDiagVerboseTest) {
@@ -139,8 +136,8 @@
   // TODO: test the resulting output.
   std::unique_ptr<OatFile> oat = OpenOatAndVdexFiles();
   std::string error_msg;
-  ASSERT_TRUE(Exec(getpid(), { kDexDiagVerbose }, &error_msg)) << "Failed to execute -- because: "
-                                                               << error_msg;
+  ASSERT_TRUE(Exec(getpid(), {kDexDiagVerbose}, &error_msg))
+      << "Failed to execute -- because: " << error_msg;
 }
 
 }  // namespace art
diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc
index 648c52d..9152c18 100644
--- a/dexlayout/dexlayout.cc
+++ b/dexlayout/dexlayout.cc
@@ -25,16 +25,17 @@
 #include <inttypes.h>
 #include <stdio.h>
 
+#include <cstdint>
 #include <iostream>
+#include <iterator>
 #include <memory>
 #include <sstream>
 #include <unordered_set>
 #include <vector>
 
 #include "android-base/stringprintf.h"
-
-#include "base/logging.h"  // For VLOG_IS_ON.
 #include "base/hiddenapi_flags.h"
+#include "base/logging.h"  // For VLOG_IS_ON.
 #include "base/mem_map.h"
 #include "base/mman.h"  // For the PROT_* and MAP_* constants.
 #include "base/os.h"
@@ -47,6 +48,7 @@
 #include "dex/dex_file_types.h"
 #include "dex/dex_file_verifier.h"
 #include "dex/dex_instruction-inl.h"
+#include "dex_ir.h"
 #include "dex_ir_builder.h"
 #include "dex_verify.h"
 #include "dex_visualize.h"
@@ -533,6 +535,9 @@
                          method.c_str(), proto.c_str(), width, index, width, secondary_index);
     }
     break;
+    case Instruction::kIndexCallSiteRef:
+      outSize = snprintf(buf.get(), buf_size, "call_site@%0*x", width, index);
+      break;
     // SOME NOT SUPPORTED:
     // case Instruction::kIndexVaries:
     // case Instruction::kIndexInlineMethod:
@@ -1607,6 +1612,226 @@
   free(access_str);
 }
 
+void DexLayout::DumpMethodHandle(int idx) {
+  const dex_ir::MethodHandleItem* mh = header_->MethodHandleItems()[idx];
+  const char* type = nullptr;
+  bool is_instance = false;
+  bool is_invoke = false;
+
+  switch (mh->GetMethodHandleType()) {
+    case DexFile::MethodHandleType::kStaticPut:
+      type = "put-static";
+      is_instance = false;
+      is_invoke = false;
+      break;
+    case DexFile::MethodHandleType::kStaticGet:
+      type = "get-static";
+      is_instance = false;
+      is_invoke = false;
+      break;
+    case DexFile::MethodHandleType::kInstancePut:
+      type = "put-instance";
+      is_instance = true;
+      is_invoke = false;
+      break;
+    case DexFile::MethodHandleType::kInstanceGet:
+      type = "get-instance";
+      is_instance = true;
+      is_invoke = false;
+      break;
+    case DexFile::MethodHandleType::kInvokeStatic:
+      type = "invoke-static";
+      is_instance = false;
+      is_invoke = true;
+      break;
+    case DexFile::MethodHandleType::kInvokeInstance:
+      type = "invoke-instance";
+      is_instance = true;
+      is_invoke = true;
+      break;
+    case DexFile::MethodHandleType::kInvokeConstructor:
+      type = "invoke-constructor";
+      is_instance = true;
+      is_invoke = true;
+      break;
+    case DexFile::MethodHandleType::kInvokeDirect:
+      type = "invoke-direct";
+      is_instance = true;
+      is_invoke = true;
+      break;
+    case DexFile::MethodHandleType::kInvokeInterface:
+      type = "invoke-interface";
+      is_instance = true;
+      is_invoke = true;
+      break;
+    default:
+      type = "????";
+      break;
+  }  // switch
+
+  const char* declaring_class;
+  const char* member;
+  std::string member_type;
+  if (type != nullptr) {
+    if (is_invoke) {
+      auto method_id = static_cast<dex_ir::MethodId*>(mh->GetFieldOrMethodId());
+      declaring_class = method_id->Class()->GetStringId()->Data();
+      member = method_id->Name()->Data();
+      auto proto_id = method_id->Proto();
+      member_type = GetSignatureForProtoId(proto_id);
+    } else {
+      auto field_id = static_cast<dex_ir::FieldId*>(mh->GetFieldOrMethodId());
+      declaring_class = field_id->Class()->GetStringId()->Data();
+      member = field_id->Name()->Data();
+      member_type = field_id->Type()->GetStringId()->Data();
+    }
+    if (is_instance) {
+      member_type = android::base::StringPrintf("(%s%s", declaring_class, member_type.c_str() + 1);
+    }
+  } else {
+    type = "?";
+    declaring_class = "?";
+    member = "?";
+    member_type = "?";
+  }
+
+  if (options_.output_format_ == kOutputPlain) {
+    fprintf(out_file_, "Method handle #%u:\n", idx);
+    fprintf(out_file_, "  type        : %s\n", type);
+    fprintf(out_file_, "  target      : %s %s\n", declaring_class, member);
+    fprintf(out_file_, "  target_type : %s\n", member_type.c_str());
+  }
+}
+
+void DexLayout::DumpCallSite(int idx) {
+  const dex_ir::CallSiteId* call_site_id = header_->CallSiteIds()[idx];
+  auto call_site_items = call_site_id->CallSiteItem()->GetEncodedValues();
+  if (call_site_items->size() < 3) {
+    LOG(ERROR) << "ERROR: Call site " << idx << " has too few values.";
+    return;
+  }
+  uint32_t offset = call_site_id->CallSiteItem()->GetOffset();
+
+  auto it = call_site_items->begin();
+  if ((*it)->Type() != EncodedArrayValueIterator::ValueType::kMethodHandle) {
+    LOG(ERROR) << "ERROR: Call site " << idx << " needs to have a MethodHandle as its first item."
+               << " Found " << (*it)->Type();
+    return;
+  }
+  auto method_handle = (*it)->GetMethodHandle();
+  uint32_t method_handle_idx = method_handle->GetIndex();
+
+  it++;
+  if ((*it)->Type() != EncodedArrayValueIterator::ValueType::kString) {
+    LOG(ERROR) << "ERROR: Call site " << idx << " needs to have a String for method name "
+               << "as its second item."
+               << " Found " << (*it)->Type();
+    return;
+  }
+  const char* method_name = (*it)->GetStringId()->Data();
+
+  it++;
+  if ((*it)->Type() != EncodedArrayValueIterator::ValueType::kMethodType) {
+    LOG(ERROR) << "ERROR: Call site " << idx << " needs to have a Prototype as its third item."
+               << " Found " << (*it)->Type();
+    return;
+  }
+  auto proto_id = (*it)->GetProtoId();
+  std::string method_type = GetSignatureForProtoId(proto_id);
+
+  it++;
+  if (options_.output_format_ == kOutputPlain) {
+    fprintf(out_file_, "Call site #%u: // offset %u\n", idx, offset);
+    fprintf(out_file_, "  link_argument[0] : %u (MethodHandle)\n", method_handle_idx);
+    fprintf(out_file_, "  link_argument[1] : %s (String)\n", method_name);
+    fprintf(out_file_, "  link_argument[2] : %s (MethodType)\n", method_type.c_str());
+  }
+
+  size_t argument = 3;
+
+  while (it != call_site_items->end()) {
+    const char* type;
+    std::string value;
+    switch ((*it)->Type()) {
+      case EncodedArrayValueIterator::ValueType::kByte:
+        type = "byte";
+        value = android::base::StringPrintf("%u", (*it)->GetByte());
+        break;
+      case EncodedArrayValueIterator::ValueType::kShort:
+        type = "short";
+        value = android::base::StringPrintf("%d", (*it)->GetShort());
+        break;
+      case EncodedArrayValueIterator::ValueType::kChar:
+        type = "char";
+        value = android::base::StringPrintf("%u", (*it)->GetChar());
+        break;
+      case EncodedArrayValueIterator::ValueType::kInt:
+        type = "int";
+        value = android::base::StringPrintf("%d", (*it)->GetInt());
+        break;
+      case EncodedArrayValueIterator::ValueType::kLong:
+        type = "long";
+        value = android::base::StringPrintf("%" PRId64, (*it)->GetLong());
+        break;
+      case EncodedArrayValueIterator::ValueType::kFloat:
+        type = "float";
+        value = android::base::StringPrintf("%g", (*it)->GetFloat());
+        break;
+      case EncodedArrayValueIterator::ValueType::kDouble:
+        type = "double";
+        value = android::base::StringPrintf("%g", (*it)->GetDouble());
+        break;
+      case EncodedArrayValueIterator::ValueType::kMethodType: {
+        type = "MethodType";
+        auto proto_id_item = (*it)->GetProtoId();
+        value = GetSignatureForProtoId(proto_id_item);
+        break;
+      }
+      case EncodedArrayValueIterator::ValueType::kMethodHandle: {
+        type = "MethodHandle";
+        auto method_handle_item = (*it)->GetMethodHandle();
+        value = android::base::StringPrintf("%d", method_handle_item->GetIndex());
+        break;
+      }
+      case EncodedArrayValueIterator::ValueType::kString: {
+        type = "String";
+        auto string_id = (*it)->GetStringId();
+        value = string_id->Data();
+        break;
+      }
+      case EncodedArrayValueIterator::ValueType::kType: {
+        type = "Class";
+        auto type_id = (*it)->GetTypeId();
+        value = type_id->GetStringId()->Data();
+        break;
+      }
+      case EncodedArrayValueIterator::ValueType::kField:
+      case EncodedArrayValueIterator::ValueType::kMethod:
+      case EncodedArrayValueIterator::ValueType::kEnum:
+      case EncodedArrayValueIterator::ValueType::kArray:
+      case EncodedArrayValueIterator::ValueType::kAnnotation:
+        // Unreachable based on current EncodedArrayValueIterator::Next().
+        UNIMPLEMENTED(FATAL) << " type " << (*it)->Type();
+        UNREACHABLE();
+      case EncodedArrayValueIterator::ValueType::kNull:
+        type = "Null";
+        value = "null";
+        break;
+      case EncodedArrayValueIterator::ValueType::kBoolean:
+        type = "boolean";
+        value = (*it)->GetBoolean() ? "true" : "false";
+        break;
+    }
+
+    if (options_.output_format_ == kOutputPlain) {
+      fprintf(out_file_, "  link_argument[%zu] : %s (%s)\n", argument, value.c_str(), type);
+    }
+
+    it++;
+    argument++;
+  }
+}
+
 void DexLayout::DumpDexFile() {
   // Headers.
   if (options_.show_file_headers_) {
@@ -1625,6 +1850,16 @@
     DumpClass(i, &package);
   }  // for
 
+  const uint32_t mh_items_size = header_->MethodHandleItems().Size();
+  for (uint32_t i = 0; i < mh_items_size; i++) {
+    DumpMethodHandle(i);
+  }
+
+  const uint32_t call_sites_size = header_->CallSiteIds().Size();
+  for (uint32_t i = 0; i < call_sites_size; i++) {
+    DumpCallSite(i);
+  }
+
   // Free the last package allocated.
   if (package != nullptr) {
     fprintf(out_file_, "</package>\n");
@@ -2009,23 +2244,19 @@
       std::string location = "memory mapped file for " + std::string(file_name);
       // Dex file verifier cannot handle compact dex.
       bool verify = options_.compact_dex_level_ == CompactDexLevel::kCompactDexLevelNone;
-      const ArtDexFileLoader dex_file_loader;
       DexContainer::Section* const main_section = (*dex_container)->GetMainSection();
       DexContainer::Section* const data_section = (*dex_container)->GetDataSection();
       DCHECK_EQ(file_size, main_section->Size())
           << main_section->Size() << " " << data_section->Size();
+      auto container = std::make_unique<DexLoaderContainer>(
+          main_section->Begin(), main_section->End(), data_section->Begin(), data_section->End());
+      ArtDexFileLoader dex_file_loader(std::move(container), location);
       std::unique_ptr<const DexFile> output_dex_file(
-          dex_file_loader.OpenWithDataSection(
-              main_section->Begin(),
-              main_section->Size(),
-              data_section->Begin(),
-              data_section->Size(),
-              location,
-              /* location_checksum= */ 0,
-              /*oat_dex_file=*/ nullptr,
-              verify,
-              /*verify_checksum=*/ false,
-              error_msg));
+          dex_file_loader.Open(/* location_checksum= */ 0,
+                               /*oat_dex_file=*/nullptr,
+                               verify,
+                               /*verify_checksum=*/false,
+                               error_msg));
       CHECK(output_dex_file != nullptr) << "Failed to re-open output file:" << *error_msg;
 
       // Do IR-level comparison between input and output. This check ignores potential differences
@@ -2059,10 +2290,10 @@
   // all of which are Zip archives with "classes.dex" inside.
   const bool verify_checksum = !options_.ignore_bad_checksum_;
   std::string error_msg;
-  const ArtDexFileLoader dex_file_loader;
+  ArtDexFileLoader dex_file_loader(file_name);
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   if (!dex_file_loader.Open(
-        file_name, file_name, /* verify= */ true, verify_checksum, &error_msg, &dex_files)) {
+          /* verify= */ true, verify_checksum, &error_msg, &dex_files)) {
     // Display returned error message to user. Note that this error behavior
     // differs from the error messages shown by the original Dalvik dexdump.
     LOG(ERROR) << error_msg;
diff --git a/dexlayout/dexlayout.h b/dexlayout/dexlayout.h
index bdc7863..c6f76e5 100644
--- a/dexlayout/dexlayout.h
+++ b/dexlayout/dexlayout.h
@@ -140,6 +140,8 @@
   void DumpBytecodes(uint32_t idx, const dex_ir::CodeItem* code, uint32_t code_offset);
   void DumpCatches(const dex_ir::CodeItem* code);
   void DumpClass(int idx, char** last_package);
+  void DumpMethodHandle(int idx);
+  void DumpCallSite(int idx);
   void DumpClassAnnotations(int idx);
   void DumpClassDef(int idx);
   void DumpCode(uint32_t idx,
@@ -199,6 +201,19 @@
   DISALLOW_COPY_AND_ASSIGN(DexLayout);
 };
 
+class DexLoaderContainer : public MemoryDexFileContainer {
+ public:
+  DexLoaderContainer(const uint8_t* begin,
+                     const uint8_t* end,
+                     const uint8_t* data_begin,
+                     const uint8_t* data_end)
+      : MemoryDexFileContainer(begin, end), data_(data_begin, data_end - data_begin) {}
+  ArrayRef<const uint8_t> Data() const override { return data_; }
+
+ private:
+  ArrayRef<const uint8_t> data_;
+};
+
 }  // namespace art
 
 #endif  // ART_DEXLAYOUT_DEXLAYOUT_H_
diff --git a/dexlayout/dexlayout_main.cc b/dexlayout/dexlayout_main.cc
index 12674f5..6c37b1d 100644
--- a/dexlayout/dexlayout_main.cc
+++ b/dexlayout/dexlayout_main.cc
@@ -180,7 +180,7 @@
   // Open alternative output file.
   FILE* out_file = stdout;
   if (options.output_file_name_) {
-    out_file = fopen(options.output_file_name_, "w");
+    out_file = fopen(options.output_file_name_, "we");
     if (!out_file) {
       PLOG(ERROR) << "Can't open " << options.output_file_name_;
       return 1;
diff --git a/dexlayout/dexlayout_test.cc b/dexlayout/dexlayout_test.cc
index 3c37e6d..03df258 100644
--- a/dexlayout/dexlayout_test.cc
+++ b/dexlayout/dexlayout_test.cc
@@ -14,13 +14,16 @@
  * limitations under the License.
  */
 
-#include <sstream>
-#include <string>
-#include <vector>
+#include "dexlayout.h"
 
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
 #include "base/common_art_test.h"
 #include "base/unix_file/fd_file.h"
 #include "base/utils.h"
@@ -30,7 +33,6 @@
 #include "dex/code_item_accessors-inl.h"
 #include "dex/dex_file-inl.h"
 #include "dex/dex_file_loader.h"
-#include "dexlayout.h"
 #include "exec_utils.h"
 #include "profile/profile_compilation_info.h"
 
@@ -328,11 +330,9 @@
                      const std::string& out_profile) {
     std::vector<std::unique_ptr<const DexFile>> dex_files;
     std::string error_msg;
-    const ArtDexFileLoader dex_file_loader;
-    bool result = dex_file_loader.Open(input_dex.c_str(),
-                                       input_dex,
-                                       /*verify=*/ true,
-                                       /*verify_checksum=*/ false,
+    ArtDexFileLoader dex_file_loader(input_dex);
+    bool result = dex_file_loader.Open(/*verify=*/true,
+                                       /*verify_checksum=*/false,
                                        &error_msg,
                                        &dex_files);
 
@@ -772,14 +772,15 @@
 TEST_F(DexLayoutTest, ClassFilter) {
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   std::string error_msg;
-  const ArtDexFileLoader dex_file_loader;
   const std::string input_jar = GetTestDexFileName("ManyMethods");
-  CHECK(dex_file_loader.Open(input_jar.c_str(),
-                             input_jar.c_str(),
-                             /*verify=*/ true,
-                             /*verify_checksum=*/ true,
-                             &error_msg,
-                             &dex_files)) << error_msg;
+  {
+    ArtDexFileLoader dex_file_loader(input_jar);
+    CHECK(dex_file_loader.Open(/*verify=*/true,
+                               /*verify_checksum=*/true,
+                               &error_msg,
+                               &dex_files))
+        << error_msg;
+  }
   ASSERT_EQ(dex_files.size(), 1u);
   for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
     EXPECT_GT(dex_file->NumClassDefs(), 1u);
@@ -802,18 +803,16 @@
         &out,
         &error_msg);
     ASSERT_TRUE(result) << "Failed to run dexlayout " << error_msg;
-    std::unique_ptr<const DexFile> output_dex_file(
-        dex_file_loader.OpenWithDataSection(
-            out->GetMainSection()->Begin(),
-            out->GetMainSection()->Size(),
-            out->GetDataSection()->Begin(),
-            out->GetDataSection()->Size(),
-            dex_file->GetLocation().c_str(),
-            /* location_checksum= */ 0,
-            /*oat_dex_file=*/ nullptr,
-            /* verify= */ true,
-            /*verify_checksum=*/ false,
-            &error_msg));
+    auto container = std::make_unique<DexLoaderContainer>(out->GetMainSection()->Begin(),
+                                                          out->GetMainSection()->End(),
+                                                          out->GetDataSection()->Begin(),
+                                                          out->GetDataSection()->End());
+    ArtDexFileLoader dex_file_loader(std::move(container), dex_file->GetLocation());
+    std::unique_ptr<const DexFile> output_dex_file(dex_file_loader.Open(/* location_checksum= */ 0,
+                                                                        /*oat_dex_file=*/nullptr,
+                                                                        /* verify= */ true,
+                                                                        /*verify_checksum=*/false,
+                                                                        &error_msg));
     ASSERT_TRUE(output_dex_file != nullptr);
 
     ASSERT_EQ(output_dex_file->NumClassDefs(), options.class_filter_.size());
diff --git a/dexlist/Android.bp b/dexlist/Android.bp
index 6a65ef4..918fc68 100644
--- a/dexlist/Android.bp
+++ b/dexlist/Android.bp
@@ -34,6 +34,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
diff --git a/dexlist/dexlist.cc b/dexlist/dexlist.cc
index 9ef9ae9..3603675 100644
--- a/dexlist/dexlist.cc
+++ b/dexlist/dexlist.cc
@@ -30,6 +30,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 
+#include "base/mem_map.h"
 #include "dex/class_accessor-inl.h"
 #include "dex/code_item_accessors-inl.h"
 #include "dex/dex_file-inl.h"
@@ -168,15 +169,10 @@
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   DexFileLoaderErrorCode error_code;
   std::string error_msg;
-  const DexFileLoader dex_file_loader;
-  if (!dex_file_loader.OpenAll(reinterpret_cast<const uint8_t*>(content.data()),
-                               content.size(),
-                               fileName,
-                               /*verify=*/ true,
-                               kVerifyChecksum,
-                               &error_code,
-                               &error_msg,
-                               &dex_files)) {
+  DexFileLoader dex_file_loader(
+      reinterpret_cast<const uint8_t*>(content.data()), content.size(), fileName);
+  if (!dex_file_loader.Open(
+          /*verify=*/true, kVerifyChecksum, &error_code, &error_msg, &dex_files)) {
     LOG(ERROR) << error_msg;
     return -1;
   }
@@ -281,6 +277,7 @@
 int main(int argc, char** argv) {
   // Output all logging to stderr.
   android::base::SetLogger(android::base::StderrLogger);
+  art::MemMap::Init();
 
   return art::dexlistDriver(argc, argv);
 }
diff --git a/dexoptanalyzer/Android.bp b/dexoptanalyzer/Android.bp
index 1e37b8d..82f0c1a 100644
--- a/dexoptanalyzer/Android.bp
+++ b/dexoptanalyzer/Android.bp
@@ -83,8 +83,12 @@
 
 art_cc_defaults {
     name: "art_dexoptanalyzer_tests_defaults",
+    static_libs: [
+        "libziparchive",
+    ],
     shared_libs: [
-        "libbacktrace",
+        "libunwindstack",
+        "libz", // libziparchive dependency; must be repeated here since it's a static lib.
     ],
     data: [
         ":art-gtest-jars-LinkageTest",
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 0cc2cdb..b182f68 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -122,9 +122,6 @@
   UsageError("      print a colon-separated list of its dex files to standard output. Dexopt");
   UsageError("      needed analysis is not performed when this option is set.");
   UsageError("");
-  UsageError("  --validate-bcp: validates the boot class path files (.art, .oat, .vdex).");
-  UsageError("      Requires --isa and --image options to locate artifacts.");
-  UsageError("");
   UsageError("Return code:");
   UsageError("  To make it easier to integrate with the internal tools this command will make");
   UsageError("    available its result (dexoptNeeded) as the exit/return code. i.e. it will not");
@@ -147,10 +144,7 @@
 
 class DexoptAnalyzer final {
  public:
-  DexoptAnalyzer() :
-      only_flatten_context_(false),
-      only_validate_bcp_(false),
-      downgrade_(false) {}
+  DexoptAnalyzer() : only_flatten_context_(false), downgrade_(false) {}
 
   void ParseArgs(int argc, char **argv) {
     original_argc = argc;
@@ -236,8 +230,6 @@
         }
       } else if (option == "--flatten-class-loader-context") {
         only_flatten_context_ = true;
-      } else if (option == "--validate-bcp") {
-        only_validate_bcp_ = true;
       } else {
         Usage("Unknown argument '%s'", raw_option);
       }
@@ -247,11 +239,16 @@
       // If we don't receive the image, try to use the default one.
       // Tests may specify a different image (e.g. core image).
       std::string error_msg;
-      image_ = GetDefaultBootImageLocation(&error_msg);
-
+      std::string android_root = GetAndroidRootSafe(&error_msg);
+      if (android_root.empty()) {
+        LOG(ERROR) << error_msg;
+        Usage("--image unspecified and ANDROID_ROOT not set.");
+      }
+      image_ = GetDefaultBootImageLocationSafe(
+          android_root, /*deny_art_apex_data_files=*/false, &error_msg);
       if (image_.empty()) {
         LOG(ERROR) << error_msg;
-        Usage("--image unspecified and ANDROID_ROOT not set or image file does not exist.");
+        Usage("--image unspecified and failed to get default boot image location.");
       }
     }
   }
@@ -324,6 +321,7 @@
                                                             class_loader_context.get(),
                                                             /*load_executable=*/ false,
                                                             /*only_load_trusted_executable=*/ false,
+                                                            /*runtime_options=*/ nullptr,
                                                             vdex_fd_,
                                                             oat_fd_,
                                                             zip_fd_);
@@ -362,82 +360,6 @@
     }
   }
 
-  // Validates the boot classpath and boot classpath extensions by checking the image checksums,
-  // the oat files and the vdex files.
-  //
-  // Returns `ReturnCode::kNoDexOptNeeded` when all the files are up-to-date,
-  // `ReturnCode::kDex2OatFromScratch` if any of the files are missing or out-of-date, and
-  // `ReturnCode::kErrorCannotCreateRuntime` if the files could not be tested due to problem
-  // creating a runtime.
-  ReturnCode ValidateBcp() const {
-    using ImageSpace = gc::space::ImageSpace;
-
-    if (!CreateRuntime()) {
-      return ReturnCode::kErrorCannotCreateRuntime;
-    }
-    std::unique_ptr<Runtime> runtime(Runtime::Current());
-
-    auto dex_files = ArrayRef<const DexFile* const>(runtime->GetClassLinker()->GetBootClassPath());
-    auto boot_image_spaces = ArrayRef<ImageSpace* const>(runtime->GetHeap()->GetBootImageSpaces());
-    const std::string checksums = ImageSpace::GetBootClassPathChecksums(boot_image_spaces,
-                                                                        dex_files);
-
-    std::string error_msg;
-    const std::vector<std::string>& bcp = runtime->GetBootClassPath();
-    const std::vector<std::string>& bcp_locations = runtime->GetBootClassPathLocations();
-    const std::vector<int>& bcp_fds = runtime->GetBootClassPathFds();
-    const std::vector<std::string>& image_locations = runtime->GetImageLocations();
-    const std::string bcp_locations_path = android::base::Join(bcp_locations, ':');
-    if (!ImageSpace::VerifyBootClassPathChecksums(checksums,
-                                                  bcp_locations_path,
-                                                  ArrayRef<const std::string>(image_locations),
-                                                  ArrayRef<const std::string>(bcp_locations),
-                                                  ArrayRef<const std::string>(bcp),
-                                                  ArrayRef<const int>(bcp_fds),
-                                                  runtime->GetInstructionSet(),
-                                                  &error_msg)) {
-      LOG(INFO) << "Failed to verify boot class path checksums: " << error_msg;
-      return ReturnCode::kDex2OatFromScratch;
-    }
-
-    const auto& image_spaces = runtime->GetHeap()->GetBootImageSpaces();
-    size_t bcp_component_count = 0;
-    for (const auto& image_space : image_spaces) {
-      if (!image_space->GetImageHeader().IsValid()) {
-        LOG(INFO) << "Image header is not valid: " << image_space->GetImageFilename();
-        return ReturnCode::kDex2OatFromScratch;
-      }
-      const OatFile* oat_file = image_space->GetOatFile();
-      if (oat_file == nullptr) {
-        const std::string oat_path = ReplaceFileExtension(image_space->GetImageFilename(), "oat");
-        LOG(INFO) << "Oat file missing: " << oat_path;
-        return ReturnCode::kDex2OatFromScratch;
-      }
-      if (!oat_file->GetOatHeader().IsValid() ||
-          !ImageSpace::ValidateOatFile(*oat_file, &error_msg)) {
-        LOG(INFO) << "Oat file is not valid: " << oat_file->GetLocation() << " " << error_msg;
-        return ReturnCode::kDex2OatFromScratch;
-      }
-      const VdexFile* vdex_file = oat_file->GetVdexFile();
-      if (vdex_file == nullptr || !vdex_file->IsValid()) {
-        LOG(INFO) << "Vdex file is not valid : " << oat_file->GetLocation();
-        return ReturnCode::kDex2OatFromScratch;
-      }
-      bcp_component_count += image_space->GetComponentCount();
-    }
-
-    // If the number of components encountered in the image spaces does not match the number
-    // of components expected from the boot classpath locations then something is missing.
-    if (bcp_component_count != bcp_locations.size()) {
-      for (size_t i = bcp_component_count; i < bcp_locations.size(); ++i) {
-        LOG(INFO) << "Missing image file for " << bcp_locations[i];
-      }
-      return ReturnCode::kDex2OatFromScratch;
-    }
-
-    return ReturnCode::kNoDexOptNeeded;
-  }
-
   ReturnCode FlattenClassLoaderContext() const {
     DCHECK(only_flatten_context_);
     if (context_str_.empty()) {
@@ -449,15 +371,13 @@
       Usage("Invalid --class-loader-context '%s'", context_str_.c_str());
     }
 
-    std::cout << context->FlattenDexPaths() << std::flush;
+    std::cout << android::base::Join(context->FlattenDexPaths(), ':') << std::flush;
     return ReturnCode::kFlattenClassLoaderContextSuccess;
   }
 
   ReturnCode Run() const {
     if (only_flatten_context_) {
       return FlattenClassLoaderContext();
-    } else if (only_validate_bcp_) {
-      return ValidateBcp();
     } else {
       return GetDexOptNeeded();
     }
@@ -469,7 +389,6 @@
   CompilerFilter::Filter compiler_filter_;
   std::string context_str_;
   bool only_flatten_context_;
-  bool only_validate_bcp_;
   ProfileAnalysisResult profile_analysis_result_;
   bool downgrade_;
   std::string image_;
diff --git a/dexoptanalyzer/dexoptanalyzer_test.cc b/dexoptanalyzer/dexoptanalyzer_test.cc
index 7abd4ba..15f71e8 100644
--- a/dexoptanalyzer/dexoptanalyzer_test.cc
+++ b/dexoptanalyzer/dexoptanalyzer_test.cc
@@ -118,7 +118,6 @@
   Copy(GetDexSrc1(), dex_location);
 
   Verify(dex_location, CompilerFilter::kSpeed);
-  Verify(dex_location, CompilerFilter::kExtract);
   Verify(dex_location, CompilerFilter::kVerify);
   Verify(dex_location, CompilerFilter::kSpeedProfile);
   Verify(dex_location, CompilerFilter::kSpeed,
@@ -130,11 +129,10 @@
   std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
   std::string odex_location = GetOdexDir() + "/OatUpToDate.odex";
   Copy(GetDexSrc1(), dex_location);
-  GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kSpeed);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
 
   Verify(dex_location, CompilerFilter::kSpeed);
   Verify(dex_location, CompilerFilter::kVerify);
-  Verify(dex_location, CompilerFilter::kExtract);
   Verify(dex_location, CompilerFilter::kEverything);
   Verify(dex_location, CompilerFilter::kSpeed,
       ProfileAnalysisResult::kDontOptimizeSmallDelta, false, nullptr);
@@ -145,7 +143,7 @@
   std::string dex_location = GetScratchDir() + "/ProfileOatUpToDate.jar";
   std::string odex_location = GetOdexDir() + "/ProfileOatUpToDate.odex";
   Copy(GetDexSrc1(), dex_location);
-  GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kSpeedProfile);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile);
 
   Verify(dex_location, CompilerFilter::kSpeedProfile,
       ProfileAnalysisResult::kDontOptimizeSmallDelta);
@@ -161,7 +159,7 @@
   std::string odex_location = GetOdexDir() + "/VerifyAndEmptyProfiles.odex";
   Copy(GetDexSrc1(), dex_location);
 
-  GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kVerify);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kVerify);
 
   // If we want to speed-profile something that was verified, do it even if
   // the profile analysis returns kDontOptimizeSmallDelta (it means that we do have profile data,
@@ -188,14 +186,12 @@
   std::string dex_location = GetScratchDir() + "/Downgrade.jar";
   std::string odex_location = GetOdexDir() + "/Downgrade.odex";
   Copy(GetDexSrc1(), dex_location);
-  GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kVerify);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kVerify);
 
   Verify(dex_location, CompilerFilter::kSpeedProfile,
       ProfileAnalysisResult::kDontOptimizeSmallDelta, true);
   Verify(dex_location, CompilerFilter::kVerify,
       ProfileAnalysisResult::kDontOptimizeSmallDelta, true);
-  Verify(dex_location, CompilerFilter::kExtract,
-      ProfileAnalysisResult::kDontOptimizeSmallDelta, true);
 }
 
 // Case: We have a MultiDEX file and up-to-date ODEX file for it.
@@ -204,7 +200,7 @@
   std::string odex_location = GetOdexDir() + "/MultiDexOatUpToDate.odex";
 
   Copy(GetMultiDexSrc1(), dex_location);
-  GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kSpeed);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
 
   Verify(dex_location, CompilerFilter::kSpeed, ProfileAnalysisResult::kDontOptimizeSmallDelta);
 }
@@ -216,7 +212,7 @@
 
   // Compile code for GetMultiDexSrc1.
   Copy(GetMultiDexSrc1(), dex_location);
-  GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kSpeed);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
 
   // Now overwrite the dex file with GetMultiDexSrc2 so the secondary checksum
   // is out of date.
@@ -234,10 +230,9 @@
   // We create a dex, generate an oat for it, then overwrite the dex with a
   // different dex to make the oat out of date.
   Copy(GetDexSrc1(), dex_location);
-  GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kSpeed);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
   Copy(GetDexSrc2(), dex_location);
 
-  Verify(dex_location, CompilerFilter::kExtract);
   Verify(dex_location, CompilerFilter::kSpeed);
 }
 
@@ -248,34 +243,15 @@
   std::string odex_location = GetOdexDir() + "/OatImageOutOfDate.odex";
 
   Copy(GetDexSrc1(), dex_location);
-  GenerateOatForTest(dex_location.c_str(),
-                     odex_location.c_str(),
+  GenerateOatForTest(dex_location,
+                     odex_location,
                      CompilerFilter::kSpeed,
                      /*with_alternate_image=*/true);
 
-  Verify(dex_location, CompilerFilter::kExtract);
   Verify(dex_location, CompilerFilter::kVerify);
   Verify(dex_location, CompilerFilter::kSpeed);
 }
 
-// Case: We have a DEX file and a verify-at-runtime OAT file out of date with
-// respect to the boot image.
-// It shouldn't matter that the OAT file is out of date, because it is
-// verify-at-runtime.
-TEST_F(DexoptAnalyzerTest, OatVerifyAtRuntimeImageOutOfDate) {
-  std::string dex_location = GetScratchDir() + "/OatVerifyAtRuntimeImageOutOfDate.jar";
-  std::string odex_location = GetOdexDir() + "/OatVerifyAtRuntimeImageOutOfDate.odex";
-
-  Copy(GetDexSrc1(), dex_location);
-  GenerateOatForTest(dex_location.c_str(),
-                     odex_location.c_str(),
-                     CompilerFilter::kExtract,
-                     /*with_alternate_image=*/true);
-
-  Verify(dex_location, CompilerFilter::kExtract);
-  Verify(dex_location, CompilerFilter::kVerify);
-}
-
 // Case: We have a DEX file and an ODEX file, but no OAT file.
 TEST_F(DexoptAnalyzerTest, DexOdexNoOat) {
   std::string dex_location = GetScratchDir() + "/DexOdexNoOat.jar";
@@ -284,7 +260,6 @@
   Copy(GetDexSrc1(), dex_location);
   GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
 
-  Verify(dex_location, CompilerFilter::kExtract);
   Verify(dex_location, CompilerFilter::kSpeed);
   Verify(dex_location, CompilerFilter::kEverything);
 }
@@ -297,7 +272,6 @@
   Copy(GetResourceOnlySrc1(), dex_location);
 
   Verify(dex_location, CompilerFilter::kSpeed);
-  Verify(dex_location, CompilerFilter::kExtract);
   Verify(dex_location, CompilerFilter::kVerify);
 }
 
@@ -317,18 +291,6 @@
   Verify(dex_location, CompilerFilter::kSpeed);
 }
 
-// Case: We have a DEX file and a VerifyAtRuntime ODEX file, but no OAT file..
-TEST_F(DexoptAnalyzerTest, DexVerifyAtRuntimeOdexNoOat) {
-  std::string dex_location = GetScratchDir() + "/DexVerifyAtRuntimeOdexNoOat.jar";
-  std::string odex_location = GetOdexDir() + "/DexVerifyAtRuntimeOdexNoOat.odex";
-
-  Copy(GetDexSrc1(), dex_location);
-  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kExtract);
-
-  Verify(dex_location, CompilerFilter::kExtract);
-  Verify(dex_location, CompilerFilter::kSpeed);
-}
-
 // Case: Non-standard extension for dex file.
 TEST_F(DexoptAnalyzerTest, LongDexExtension) {
   std::string dex_location = GetScratchDir() + "/LongDexExtension.jarx";
diff --git a/disassembler/Android.bp b/disassembler/Android.bp
index 4b55673..b7f758f 100644
--- a/disassembler/Android.bp
+++ b/disassembler/Android.bp
@@ -104,7 +104,6 @@
             ],
         },
     },
-
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
@@ -132,3 +131,38 @@
         "com.android.art",
     ],
 }
+
+art_cc_defaults {
+    name: "art_disassembler_tests_defaults",
+    codegen: {
+        arm64: {
+            srcs: ["disassembler_arm64_test.cc"],
+        },
+    },
+}
+
+// Version of ART gtest `art_disassembler_tests` bundled with the ART APEX on target.
+// TODO(b/192274705): Remove this module when the migration to standalone ART gtests is complete.
+art_cc_test {
+    name: "art_disassembler_tests",
+    defaults: [
+        "art_gtest_defaults",
+        "art_disassembler_tests_defaults",
+    ],
+    static_libs: [
+        "libvixld",
+    ],
+}
+
+// Standalone version of ART gtest `art_disassembler_tests`,
+// not bundled with the ART APEX on target.
+art_cc_test {
+    name: "art_standalone_disassembler_tests",
+    defaults: [
+        "art_standalone_gtest_defaults",
+        "art_disassembler_tests_defaults",
+    ],
+    static_libs: [
+        "libvixl",
+    ],
+}
diff --git a/disassembler/disassembler.cc b/disassembler/disassembler.cc
index 53461ce..a05183a 100644
--- a/disassembler/disassembler.cc
+++ b/disassembler/disassembler.cc
@@ -62,6 +62,7 @@
       return new x86::DisassemblerX86(options, /* supports_rex= */ true);
 #endif
     default:
+      UNUSED(options);
       UNIMPLEMENTED(FATAL) << static_cast<uint32_t>(instruction_set);
       UNREACHABLE();
   }
diff --git a/disassembler/disassembler_arm64.cc b/disassembler/disassembler_arm64.cc
index 0d51cfd..23472a8 100644
--- a/disassembler/disassembler_arm64.cc
+++ b/disassembler/disassembler_arm64.cc
@@ -18,6 +18,8 @@
 
 #include <inttypes.h>
 
+#include <regex>
+
 #include <sstream>
 
 #include "android-base/logging.h"
@@ -58,9 +60,34 @@
   Disassembler::AppendRegisterNameToOutput(instr, reg);
 }
 
-void CustomDisassembler::VisitLoadLiteral(const Instruction* instr) {
-  Disassembler::VisitLoadLiteral(instr);
+void CustomDisassembler::Visit(vixl::aarch64::Metadata* metadata, const Instruction* instr) {
+  vixl::aarch64::Disassembler::Visit(metadata, instr);
+  const std::string& form = (*metadata)["form"];
 
+  // These regexs are long, but it is an attempt to match the mapping entry keys in the
+  // #define DEFAULT_FORM_TO_VISITOR_MAP(VISITORCLASS) in the file
+  // external/vixl/src/aarch64/decoder-visitor-map-aarch64.h
+  // for the ::VisitLoadLiteralInstr, ::VisitLoadStoreUnsignedOffset or ::VisitUnconditionalBranch
+  // function addresess key values.
+  // N.B. the mapping are many to one.
+  if (std::regex_match(form, std::regex("(ldrsw|ldr|prfm)_(32|64|d|b|h|q|s)_loadlit"))) {
+    VisitLoadLiteralInstr(instr);
+    return;
+  }
+
+  if (std::regex_match(form, std::regex(
+      "(ldrb|ldrh|ldrsb|ldrsh|ldrsw|ldr|prfm|strb|strh|str)_(32|64|d|b|h|q|s)_ldst_pos"))) {
+    VisitLoadStoreUnsignedOffsetInstr(instr);
+    return;
+  }
+
+  if (std::regex_match(form, std::regex("(bl|b)_only_branch_imm"))) {
+    VisitUnconditionalBranchInstr(instr);
+    return;
+  }
+}
+
+void CustomDisassembler::VisitLoadLiteralInstr(const Instruction* instr) {
   if (!read_literals_) {
     return;
   }
@@ -69,6 +96,7 @@
   // avoid trying to fetch invalid literals (we can encounter this when
   // interpreting raw data as instructions).
   void* data_address = instr->GetLiteralAddress<void*>();
+
   if (data_address < base_address_ || data_address >= end_address_) {
     AppendToOutput(" (?)");
     return;
@@ -97,17 +125,13 @@
   }
 }
 
-void CustomDisassembler::VisitLoadStoreUnsignedOffset(const Instruction* instr) {
-  Disassembler::VisitLoadStoreUnsignedOffset(instr);
-
+void CustomDisassembler::VisitLoadStoreUnsignedOffsetInstr(const Instruction* instr) {
   if (instr->GetRn() == TR) {
     AppendThreadOfsetName(instr);
   }
 }
 
-void CustomDisassembler::VisitUnconditionalBranch(const Instruction* instr) {
-  Disassembler::VisitUnconditionalBranch(instr);
-
+void CustomDisassembler::VisitUnconditionalBranchInstr(const Instruction* instr) {
   if (instr->Mask(UnconditionalBranchMask) == BL) {
     const Instruction* target = instr->GetImmPCOffsetTarget();
     if (target >= base_address_ &&
diff --git a/disassembler/disassembler_arm64.h b/disassembler/disassembler_arm64.h
index a895dfe..d0443d2 100644
--- a/disassembler/disassembler_arm64.h
+++ b/disassembler/disassembler_arm64.h
@@ -47,16 +47,20 @@
   void AppendRegisterNameToOutput(const vixl::aarch64::Instruction* instr,
                                   const vixl::aarch64::CPURegister& reg) override;
 
-  // Improve the disassembly of literal load instructions.
-  void VisitLoadLiteral(const vixl::aarch64::Instruction* instr) override;
-
-  // Improve the disassembly of thread offset.
-  void VisitLoadStoreUnsignedOffset(const vixl::aarch64::Instruction* instr) override;
-
-  // Improve the disassembly of branch to thunk jumping to pointer from thread entrypoint.
-  void VisitUnconditionalBranch(const vixl::aarch64::Instruction* instr) override;
+  // Intercepts the instruction flow captured by the parent method,
+  // to specially instrument for particular instruction types.
+  void Visit(vixl::aarch64::Metadata* metadata, const vixl::aarch64::Instruction* instr) override;
 
  private:
+  // Improve the disassembly of literal load instructions.
+  void VisitLoadLiteralInstr(const vixl::aarch64::Instruction* instr);
+
+  // Improve the disassembly of thread offset.
+  void VisitLoadStoreUnsignedOffsetInstr(const vixl::aarch64::Instruction* instr);
+
+  // Improve the disassembly of branch to thunk jumping to pointer from thread entrypoint.
+  void VisitUnconditionalBranchInstr(const vixl::aarch64::Instruction* instr);
+
   void AppendThreadOfsetName(const vixl::aarch64::Instruction* instr);
 
   // Indicate if the disassembler should read data loaded from literal pools.
diff --git a/disassembler/disassembler_arm64_test.cc b/disassembler/disassembler_arm64_test.cc
new file mode 100644
index 0000000..81c067a
--- /dev/null
+++ b/disassembler/disassembler_arm64_test.cc
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <inttypes.h>
+
+#include <regex>
+
+#include <sstream>
+
+#include "base/common_art_test.h"
+#include "disassembler_arm64.h"
+#include "thread.h"
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#include "aarch64/disasm-aarch64.h"
+#include "aarch64/macro-assembler-aarch64.h"
+#pragma GCC diagnostic pop
+
+
+using namespace vixl::aarch64;  // NOLINT(build/namespaces)
+
+namespace art {
+namespace arm64 {
+
+/**
+ * Fixture class for the ArtDisassemblerTest tests.
+ */
+class ArtDisassemblerTest : public CommonArtTest {
+ public:
+  ArtDisassemblerTest() {
+  }
+
+  void SetupAssembly(uint64_t end_address) {
+    masm.GetCPUFeatures()->Combine(vixl::CPUFeatures::All());
+
+    disamOptions.reset(new DisassemblerOptions(/* absolute_addresses= */ true,
+                                               reinterpret_cast<uint8_t*>(0x0),
+                                               reinterpret_cast<uint8_t*>(end_address),
+                                               /* can_read_literals_= */ true,
+                                               &Thread::DumpThreadOffset<PointerSize::k64>));
+    disasm.reset(new CustomDisassembler(&*disamOptions));
+    decoder.AppendVisitor(disasm.get());
+    masm.SetGenerateSimulatorCode(false);
+  }
+
+  static constexpr size_t kMaxSizeGenerated = 1024;
+
+  template <typename LamdaType>
+  void ImplantInstruction(LamdaType fn) {
+    vixl::ExactAssemblyScope guard(&masm,
+                                   kMaxSizeGenerated,
+                                   vixl::ExactAssemblyScope::kMaximumSize);
+    fn();
+  }
+
+  // Appends an instruction to the existing buffer and then
+  // attempts to match the output of that instructions disassembly
+  // against a regex expression. Fails if no match is found.
+  template <typename LamdaType>
+  void CompareInstruction(LamdaType fn, const char* EXP) {
+    ImplantInstruction(fn);
+    masm.FinalizeCode();
+
+    // This gets the last instruction in the buffer.
+    // The end address of the buffer is at the end of the last instruction.
+    // sizeof(Instruction) is 1 byte as it in an empty class.
+    // Therefore we need to go back kInstructionSize * sizeof(Instruction) bytes
+    // in order to get to the start of the last instruction.
+    const Instruction* targetInstruction =
+        masm.GetBuffer()->GetEndAddress<Instruction*>()->
+            GetInstructionAtOffset(-static_cast<signed>(kInstructionSize));
+
+    decoder.Decode(targetInstruction);
+
+    const char* disassembly = disasm->GetOutput();
+
+    if (!std::regex_match(disassembly, std::regex(EXP))) {
+      const uint32_t encoding = static_cast<uint32_t>(targetInstruction->GetInstructionBits());
+
+      printf("\nEncoding: %08" PRIx32 "\nExpected: %s\nFound:    %s\n",
+             encoding,
+             EXP,
+             disassembly);
+
+      ADD_FAILURE();
+    }
+    printf("----\n%s\n", disassembly);
+  }
+
+  std::unique_ptr<CustomDisassembler> disasm;
+  std::unique_ptr<DisassemblerOptions> disamOptions;
+  Decoder decoder;
+  MacroAssembler masm;
+};
+
+#define IMPLANT(fn)                                                          \
+  do {                                                                       \
+    ImplantInstruction([&]() { this->masm.fn; });                            \
+  } while (0)
+
+#define COMPARE(fn, output)                                                  \
+  do {                                                                       \
+    CompareInstruction([&]() { this->masm.fn; }, (output));                  \
+  } while (0)
+
+// These tests map onto the named per instruction instrumentation functions in:
+// ART/art/disassembler/disassembler_arm.cc
+// Context can be found in the logic conditional on incoming instruction types and sequences in the
+// ART disassembler. As of writing the functionality we are testing for that of additional
+// diagnostic info being appended to the end of the ART disassembly output.
+TEST_F(ArtDisassemblerTest, LoadLiteralVisitBadAddress) {
+  SetupAssembly(0xffffff);
+
+  // Check we append an erroneous hint "(?)" for literal load instructions with
+  // out of scope literal pool value addresses.
+  COMPARE(ldr(x0, vixl::aarch64::Assembler::ImmLLiteral(1000)),
+      "ldr x0, pc\\+128000 \\(addr -?0x[0-9a-fA-F]+\\) \\(\\?\\)");
+}
+
+TEST_F(ArtDisassemblerTest, LoadLiteralVisit) {
+  SetupAssembly(0xffffffffffffffff);
+
+  // Test that we do not append anything for ineligible instruction.
+  COMPARE(ldr(x0, MemOperand(x18, 0)), "ldr x0, \\[x18\\]$");
+
+  // Check we do append some extra info in the right text format for valid literal load instruction.
+  COMPARE(ldr(w0, vixl::aarch64::Assembler::ImmLLiteral(0)),
+      "ldr w0, pc\\+0 \\(addr -?0x[0-9a-f]+\\) \\(0x18000000 / 402653184\\)");
+  // We don't compare with exact value even though it's a known literal (the encoding of the
+  // instruction itself) since the precision of printed floating point values could change.
+  COMPARE(ldr(s0, vixl::aarch64::Assembler::ImmLLiteral(0)),
+      "ldr s0, pc\\+0 \\(addr -?0x[0-9a-f]+\\) \\([0-9]+.[0-9]+e(\\+|-)[0-9]+\\)");
+}
+
+TEST_F(ArtDisassemblerTest, LoadStoreUnsignedOffsetVisit) {
+  SetupAssembly(0xffffffffffffffff);
+
+  // Test that we do not append anything for ineligible instruction.
+  COMPARE(ldr(x0, MemOperand(x18, 8)), "ldr x0, \\[x18, #8\\]$");
+  // Test that we do append the function name if the instruction is a load from the address
+  // stored in the TR register.
+  COMPARE(ldr(x0, MemOperand(x19, 8)), "ldr x0, \\[tr, #8\\] ; thin_lock_thread_id");
+}
+
+TEST_F(ArtDisassemblerTest, UnconditionalBranchNoAppendVisit) {
+  SetupAssembly(0xffffffffffffffff);
+
+  vixl::aarch64::Label destination;
+  masm.Bind(&destination);
+
+  IMPLANT(ldr(x16, MemOperand(x18, 0)));
+
+  // Test that we do not append anything for ineligible instruction.
+  COMPARE(bl(&destination),
+      "bl #-0x4 \\(addr -?0x[0-9a-f]+\\)$");
+}
+
+TEST_F(ArtDisassemblerTest, UnconditionalBranchVisit) {
+  SetupAssembly(0xffffffffffffffff);
+
+  vixl::aarch64::Label destination;
+  masm.Bind(&destination);
+
+  IMPLANT(ldr(x16, MemOperand(x19, 0)));
+  IMPLANT(br(x16));
+
+  // Test that we do append the function name if the instruction is a branch
+  // to a load that reads data from the address in the TR register, into the IPO register
+  // followed by a BR branching using the IPO register.
+  COMPARE(bl(&destination),
+      "bl #-0x8 \\(addr -?0x[0-9a-f]+\\) ; state_and_flags");
+}
+
+
+}  // namespace arm64
+}  // namespace art
diff --git a/dt_fd_forward/Android.bp b/dt_fd_forward/Android.bp
index 772b55e..e0507d5 100644
--- a/dt_fd_forward/Android.bp
+++ b/dt_fd_forward/Android.bp
@@ -39,10 +39,10 @@
     visibility: [":__subpackages__"],
     license_kinds: [
         "SPDX-license-identifier-Apache-2.0",
-        "SPDX-license-identifier-GPL-with-classpath-exception",
+        "SPDX-license-identifier-GPL-2.0-with-classpath-exception",
     ],
     license_text: [
-        "NOTICE",
+        "LICENSE",
     ],
 }
 
diff --git a/dt_fd_forward/NOTICE b/dt_fd_forward/LICENSE
similarity index 100%
rename from dt_fd_forward/NOTICE
rename to dt_fd_forward/LICENSE
diff --git a/imgdiag/Android.bp b/imgdiag/Android.bp
index afd86a0..a0354ab 100644
--- a/imgdiag/Android.bp
+++ b/imgdiag/Android.bp
@@ -84,6 +84,7 @@
         "libartd",
         "libartbased",
         "libartd-compiler",
+        "libdexfiled",
     ],
     apex_available: [
         "com.android.art.debug",
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index 745475e..e3310e9 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -17,11 +17,10 @@
 #include <stdio.h>
 #include <stdlib.h>
 
-#include <fstream>
 #include <functional>
-#include <iostream>
 #include <map>
 #include <optional>
+#include <ostream>
 #include <set>
 #include <string>
 #include <unordered_set>
@@ -73,6 +72,16 @@
   kImageAndZygote
 };
 
+std::ostream& operator<<(std::ostream& os, RemoteProcesses remotes) {
+  switch (remotes) {
+    case RemoteProcesses::kImageOnly: os << "ImageOnly"; break;
+    case RemoteProcesses::kZygoteOnly: os << "ZygoteOnly"; break;
+    case RemoteProcesses::kImageAndZygote: os << "ImageAndZygote"; break;
+    default: UNREACHABLE();
+  }
+  return os;
+}
+
 struct MappingData {
   // The count of pages that are considered dirty by the OS.
   size_t dirty_pages = 0;
@@ -90,6 +99,8 @@
   size_t false_dirty_pages = 0;
   // Set of the local virtual page indices that are dirty.
   std::set<size_t> dirty_page_set;
+  // Private dirty page counts for each section of the image
+  std::array<size_t, ImageHeader::kSectionCount> private_dirty_pages_for_section = {};
 };
 
 static std::string GetClassDescriptor(mirror::Class* klass)
@@ -158,7 +169,7 @@
   // Store value->key so that we can use the default sort from pair which
   // sorts by value first and then key
   std::vector<std::pair<V, K>> value_key_vector;
-
+  value_key_vector.reserve(map.size());
   for (const auto& kv_pair : map) {
     value_key_vector.push_back(std::make_pair(value_mapper(kv_pair.second), kv_pair.first));
   }
@@ -209,14 +220,24 @@
   return reinterpret_cast<T*>(const_cast<uint8_t*>(local_ptr));
 }
 
-template <typename T> size_t EntrySize(T* entry);
-template<> size_t EntrySize(mirror::Object* object) REQUIRES_SHARED(Locks::mutator_lock_) {
+size_t EntrySize(mirror::Object* object) REQUIRES_SHARED(Locks::mutator_lock_) {
   return object->SizeOf();
 }
-template<> size_t EntrySize(ArtMethod* art_method) REQUIRES_SHARED(Locks::mutator_lock_) {
+size_t EntrySize(ArtMethod* art_method) REQUIRES_SHARED(Locks::mutator_lock_) {
   return sizeof(*art_method);
 }
 
+// Print all pages the entry belongs to
+void PrintEntryPages(uintptr_t entry_address, size_t entry_size, std::ostream& os) {
+    const char* tabs = "    ";
+    const uintptr_t first_page_idx = entry_address / kPageSize;
+    const uintptr_t last_page_idx = RoundUp(entry_address + entry_size,
+                                            kObjectAlignment) / kPageSize;
+    for (uintptr_t page_idx = first_page_idx; page_idx <= last_page_idx; ++page_idx) {
+      os << tabs << "page_idx=" << page_idx << "\n";
+    }
+}
+
 // entry1 and entry2 might be relocated, this means we must use the runtime image's entry
 // (image_entry) to avoid crashes.
 template <typename T>
@@ -268,7 +289,6 @@
   size_t GetDirtyEntryBytes() const { return dirty_entry_bytes_; }
   size_t GetFalseDirtyEntryCount() const { return false_dirty_entries_.size(); }
   size_t GetFalseDirtyEntryBytes() const { return false_dirty_entry_bytes_; }
-  size_t GetZygoteDirtyEntryCount() const { return zygote_dirty_entries_.size(); }
 
  protected:
   bool IsEntryOnDirtyPage(T* entry, const std::set<size_t>& dirty_pages) const
@@ -289,10 +309,6 @@
     return false;
   }
 
-  void AddZygoteDirtyEntry(T* entry) REQUIRES_SHARED(Locks::mutator_lock_) {
-    zygote_dirty_entries_.insert(entry);
-  }
-
   void AddImageDirtyEntry(T* entry) REQUIRES_SHARED(Locks::mutator_lock_) {
     image_dirty_entries_.insert(entry);
   }
@@ -327,11 +343,6 @@
   // If zygote_pid_only_ == false, these are private dirty entries in the application.
   std::set<T*> image_dirty_entries_;
 
-  // Zygote dirty entries (probably private dirty).
-  // We only add entries here if they differed in both the image and the zygote, so
-  // they are probably private dirty.
-  std::set<T*> zygote_dirty_entries_;
-
   std::map<off_t /* field offset */, size_t /* count */> field_dirty_count_;
 
  private:
@@ -439,18 +450,24 @@
   void DiffEntryContents(mirror::Object* entry,
                          uint8_t* remote_bytes,
                          const uint8_t* base_ptr,
-                         bool log_dirty_objects)
-      REQUIRES_SHARED(Locks::mutator_lock_) {
+                         bool log_dirty_objects,
+                         size_t entry_offset) REQUIRES_SHARED(Locks::mutator_lock_) {
     const char* tabs = "    ";
     // Attempt to find fields for all dirty bytes.
     mirror::Class* klass = entry->GetClass();
+    std::string temp;
     if (entry->IsClass()) {
       os_ << tabs
           << "Class " << mirror::Class::PrettyClass(entry->AsClass()) << " " << entry << "\n";
+      os_ << tabs << "dirty_obj: " << entry_offset << " class "
+          << entry->AsClass()->DescriptorHash() << "\n";
     } else {
       os_ << tabs
           << "Instance of " << mirror::Class::PrettyClass(klass) << " " << entry << "\n";
+      os_ << tabs << "dirty_obj: " << entry_offset << " instance " << klass->DescriptorHash()
+          << "\n";
     }
+    PrintEntryPages(reinterpret_cast<uintptr_t>(entry), EntrySize(entry), os_);
 
     std::unordered_set<ArtField*> dirty_instance_fields;
     std::unordered_set<ArtField*> dirty_static_fields;
@@ -764,10 +781,12 @@
   void DiffEntryContents(ArtMethod* method,
                          uint8_t* remote_bytes,
                          const uint8_t* base_ptr,
-                         bool log_dirty_objects ATTRIBUTE_UNUSED)
+                         bool log_dirty_objects ATTRIBUTE_UNUSED,
+                         size_t entry_offset ATTRIBUTE_UNUSED)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     const char* tabs = "    ";
     os_ << tabs << "ArtMethod " << ArtMethod::PrettyMethod(method) << "\n";
+    PrintEntryPages(reinterpret_cast<uintptr_t>(method), EntrySize(method), os_);
 
     std::unordered_set<size_t> dirty_members;
     // Examine the members comprising the ArtMethod, computing which members are dirty.
@@ -1009,24 +1028,11 @@
         os_ << "  Application dirty entries (unknown whether private or shared dirty): ";
         break;
     }
-    DiffDirtyEntries(ProcessType::kRemote,
+    DiffDirtyEntries(RegionCommon<T>::image_dirty_entries_,
                      begin_image_ptr,
                      RegionCommon<T>::remote_contents_,
                      base_ptr,
                      /*log_dirty_objects=*/true);
-    // Print shared dirty after since it's less important.
-    if (RegionCommon<T>::GetZygoteDirtyEntryCount() != 0) {
-      // We only reach this point if both pids were specified.  Furthermore,
-      // entries are only displayed here if they differed in both the image
-      // and the zygote, so they are probably private dirty.
-      CHECK(remotes == RemoteProcesses::kImageAndZygote);
-      os_ << "\n" << "  Zygote dirty entries (probably shared dirty): ";
-      DiffDirtyEntries(ProcessType::kZygote,
-                       begin_image_ptr,
-                       RegionCommon<T>::zygote_contents_,
-                       begin_image_ptr,
-                       /*log_dirty_objects=*/false);
-    }
     RegionSpecializedBase<T>::DumpDirtyObjects();
     RegionSpecializedBase<T>::DumpDirtyEntries();
     RegionSpecializedBase<T>::DumpFalseDirtyEntries();
@@ -1036,25 +1042,19 @@
  private:
   std::ostream& os_;
 
-  void DiffDirtyEntries(ProcessType process_type,
+  void DiffDirtyEntries(const std::set<T*>& entries,
                         const uint8_t* begin_image_ptr,
                         ArrayRef<uint8_t> contents,
                         const uint8_t* base_ptr,
                         bool log_dirty_objects)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     os_ << RegionCommon<T>::dirty_entries_.size() << "\n";
-    const std::set<T*>& entries =
-        (process_type == ProcessType::kZygote) ?
-            RegionCommon<T>::zygote_dirty_entries_:
-            RegionCommon<T>::image_dirty_entries_;
     for (T* entry : entries) {
       uint8_t* entry_bytes = reinterpret_cast<uint8_t*>(entry);
       ptrdiff_t offset = entry_bytes - begin_image_ptr;
       uint8_t* remote_bytes = &contents[offset];
-      RegionSpecializedBase<T>::DiffEntryContents(entry,
-                                                  remote_bytes,
-                                                  &base_ptr[offset],
-                                                  log_dirty_objects);
+      RegionSpecializedBase<T>::DiffEntryContents(
+          entry, remote_bytes, &base_ptr[offset], log_dirty_objects, static_cast<size_t>(offset));
     }
   }
 
@@ -1077,17 +1077,11 @@
     // Test private dirty first.
     bool is_dirty = false;
     if (have_zygote) {
-      bool private_dirty = EntriesDiffer(entry, entry_zygote, entry_remote);
-      if (private_dirty) {
+      if (EntriesDiffer(entry, entry_zygote, entry_remote)) {
         // Private dirty, app vs zygote.
         is_dirty = true;
         RegionCommon<T>::AddImageDirtyEntry(entry);
       }
-      if (EntriesDiffer(entry, entry_zygote, entry)) {
-        // Shared dirty, zygote vs image.
-        is_dirty = true;
-        RegionCommon<T>::AddZygoteDirtyEntry(entry);
-      }
     } else if (EntriesDiffer(entry, entry_remote, entry)) {
       // Shared or private dirty, app vs image.
       is_dirty = true;
@@ -1127,8 +1121,10 @@
   bool Init() {
     std::ostream& os = *os_;
 
-    if (image_diff_pid_ < 0 && zygote_diff_pid_ < 0) {
-      os << "Either --image-diff-pid or --zygote-diff-pid (or both) must be specified.\n";
+    if (image_diff_pid_ < 0 || zygote_diff_pid_ < 0) {
+      // TODO: ComputeDirtyBytes must be modified
+      // to support single app/zygote to bootimage comparison
+      os << "Both --image-diff-pid and --zygote-diff-pid must be specified.\n";
       return false;
     }
 
@@ -1203,69 +1199,14 @@
       }
     }
 
-    std::unique_ptr<File> clean_pagemap_file;
     std::unique_ptr<File> kpageflags_file;
     std::unique_ptr<File> kpagecount_file;
-    if (!open_file("/proc/self/pagemap", &clean_pagemap_file) ||
-        !open_file("/proc/kpageflags", &kpageflags_file) ||
+    if (!open_file("/proc/kpageflags", &kpageflags_file) ||
         !open_file("/proc/kpagecount", &kpagecount_file)) {
       return false;
     }
 
-    // Note: the boot image is not really clean but close enough.
-    // For now, log pages found to be dirty.
     // TODO: Rewrite imgdiag to load boot image without creating a runtime.
-    // FIXME: The following does not reliably detect dirty pages.
-    Runtime* runtime = Runtime::Current();
-    CHECK(!runtime->ShouldRelocate());
-    size_t total_dirty_pages = 0u;
-    for (gc::space::ImageSpace* space : runtime->GetHeap()->GetBootImageSpaces()) {
-      const ImageHeader& image_header = space->GetImageHeader();
-      const uint8_t* image_begin = image_header.GetImageBegin();
-      const uint8_t* image_end = AlignUp(image_begin + image_header.GetImageSize(), kPageSize);
-      size_t virtual_page_idx_begin = reinterpret_cast<uintptr_t>(image_begin) / kPageSize;
-      size_t virtual_page_idx_end = reinterpret_cast<uintptr_t>(image_end) / kPageSize;
-      size_t num_virtual_pages = virtual_page_idx_end - virtual_page_idx_begin;
-
-      std::string error_msg;
-      std::vector<uint64_t> page_frame_numbers(num_virtual_pages);
-      if (!GetPageFrameNumbers(clean_pagemap_file.get(),
-                               virtual_page_idx_begin,
-                               ArrayRef<uint64_t>(page_frame_numbers),
-                               &error_msg)) {
-        os << "Failed to get page frame numbers for image space " << space->GetImageLocation()
-           << ", error: " << error_msg;
-        return false;
-      }
-
-      std::vector<uint64_t> page_flags(num_virtual_pages);
-      if (!GetPageFlagsOrCounts(kpageflags_file.get(),
-                                ArrayRef<const uint64_t>(page_frame_numbers),
-                                ArrayRef<uint64_t>(page_flags),
-                                &error_msg)) {
-        os << "Failed to get page flags for image space " << space->GetImageLocation()
-           << ", error: " << error_msg;
-        return false;
-      }
-
-      size_t num_dirty_pages = 0u;
-      std::optional<size_t> first_dirty_page;
-      for (size_t i = 0u, size = page_flags.size(); i != size; ++i) {
-        if (UNLIKELY((page_flags[i] & kPageFlagsDirtyMask) != 0u)) {
-          ++num_dirty_pages;
-          if (!first_dirty_page.has_value()) {
-            first_dirty_page = i;
-          }
-        }
-      }
-      if (num_dirty_pages != 0u) {
-        DCHECK(first_dirty_page.has_value());
-        os << "Found " << num_dirty_pages << " dirty pages for " << space->GetImageLocation()
-           << ", first dirty page: " << first_dirty_page.value_or(0u);
-        total_dirty_pages += num_dirty_pages;
-      }
-    }
-    os << "Found " << total_dirty_pages << " dirty pages in total ";
 
     // Commit the mappings and files.
     image_proc_maps_ = std::move(image_proc_maps);
@@ -1276,7 +1217,6 @@
       zygote_mem_file_ = std::move(*zygote_mem_file);
       zygote_pagemap_file_ = std::move(*zygote_pagemap_file);
     }
-    clean_pagemap_file_ = std::move(*clean_pagemap_file);
     kpageflags_file_ = std::move(*kpageflags_file);
     kpagecount_file_ = std::move(*kpagecount_file);
 
@@ -1313,128 +1253,110 @@
   }
 
   bool ComputeDirtyBytes(const ImageHeader& image_header,
-                         const uint8_t* image_begin,
                          const android::procinfo::MapInfo& boot_map,
                          ArrayRef<uint8_t> remote_contents,
-                         MappingData* mapping_data /*out*/) {
-    std::ostream& os = *os_;
-
-    size_t virtual_page_idx = 0;   // Virtual page number (for an absolute memory address)
-    size_t page_idx = 0;           // Page index relative to 0
-    size_t previous_page_idx = 0;  // Previous page index relative to 0
-
-
+                         ArrayRef<uint8_t> zygote_contents,
+                         MappingData* mapping_data /*out*/,
+                         std::string* error_msg /*out*/) {
     // Iterate through one page at a time. Boot map begin/end already implicitly aligned.
     for (uintptr_t begin = boot_map.start; begin != boot_map.end; begin += kPageSize) {
-      ptrdiff_t offset = begin - boot_map.start;
+      const ptrdiff_t offset = begin - boot_map.start;
 
       // We treat the image header as part of the memory map for now
       // If we wanted to change this, we could pass base=start+sizeof(ImageHeader)
       // But it might still be interesting to see if any of the ImageHeader data mutated
-      const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&image_header) + offset;
+      const uint8_t* zygote_ptr = &zygote_contents[offset];
       const uint8_t* remote_ptr = &remote_contents[offset];
 
-      if (memcmp(local_ptr, remote_ptr, kPageSize) != 0) {
+      if (memcmp(zygote_ptr, remote_ptr, kPageSize) != 0) {
         mapping_data->different_pages++;
 
         // Count the number of 32-bit integers that are different.
         for (size_t i = 0; i < kPageSize / sizeof(uint32_t); ++i) {
           const uint32_t* remote_ptr_int32 = reinterpret_cast<const uint32_t*>(remote_ptr);
-          const uint32_t* local_ptr_int32 = reinterpret_cast<const uint32_t*>(local_ptr);
+          const uint32_t* zygote_ptr_int32 = reinterpret_cast<const uint32_t*>(zygote_ptr);
 
-          if (remote_ptr_int32[i] != local_ptr_int32[i]) {
+          if (remote_ptr_int32[i] != zygote_ptr_int32[i]) {
             mapping_data->different_int32s++;
           }
         }
+        // Count the number of bytes that are different.
+        for (size_t i = 0; i < kPageSize; ++i) {
+          if (remote_ptr[i] != zygote_ptr[i]) {
+            mapping_data->different_bytes++;
+          }
+        }
       }
     }
 
-    std::vector<size_t> private_dirty_pages_for_section(ImageHeader::kSectionCount, 0u);
-
-    // Iterate through one byte at a time.
-    ptrdiff_t page_off_begin = image_header.GetImageBegin() - image_begin;
-    for (uintptr_t begin = boot_map.start; begin != boot_map.end; ++begin) {
-      previous_page_idx = page_idx;
+    for (uintptr_t begin = boot_map.start; begin != boot_map.end; begin += kPageSize) {
       ptrdiff_t offset = begin - boot_map.start;
 
-      // We treat the image header as part of the memory map for now
-      // If we wanted to change this, we could pass base=start+sizeof(ImageHeader)
-      // But it might still be interesting to see if any of the ImageHeader data mutated
-      const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&image_header) + offset;
-      const uint8_t* remote_ptr = &remote_contents[offset];
+      // Virtual page number (for an absolute memory address)
+      size_t virtual_page_idx = begin / kPageSize;
 
-      virtual_page_idx = reinterpret_cast<uintptr_t>(local_ptr) / kPageSize;
-
-      // Calculate the page index, relative to the 0th page where the image begins
-      page_idx = (offset + page_off_begin) / kPageSize;
-      if (*local_ptr != *remote_ptr) {
-        // Track number of bytes that are different
-        mapping_data->different_bytes++;
+      uint64_t page_count = 0xC0FFEE;
+      // TODO: virtual_page_idx needs to be from the same process
+      int dirtiness = (IsPageDirty(&image_pagemap_file_,     // Image-diff-pid procmap
+                                   &zygote_pagemap_file_,    // Zygote procmap
+                                   &kpageflags_file_,
+                                   &kpagecount_file_,
+                                   virtual_page_idx,  // compare same page in image
+                                   virtual_page_idx,  // and zygote
+                                   &page_count,
+                                   error_msg));
+      if (dirtiness < 0) {
+        return false;
+      } else if (dirtiness > 0) {
+        mapping_data->dirty_pages++;
+        mapping_data->dirty_page_set.insert(mapping_data->dirty_page_set.end(), virtual_page_idx);
       }
 
-      // Independently count the # of dirty pages on the remote side
-      size_t remote_virtual_page_idx = begin / kPageSize;
-      if (previous_page_idx != page_idx) {
-        uint64_t page_count = 0xC0FFEE;
-        // TODO: virtual_page_idx needs to be from the same process
-        std::string error_msg;
-        int dirtiness = (IsPageDirty(&image_pagemap_file_,     // Image-diff-pid procmap
-                                     &clean_pagemap_file_,     // Self procmap
-                                     &kpageflags_file_,
-                                     &kpagecount_file_,
-                                     remote_virtual_page_idx,  // potentially "dirty" page
-                                     virtual_page_idx,         // true "clean" page
-                                     &page_count,
-                                     &error_msg));
-        if (dirtiness < 0) {
-          os << error_msg;
-          return false;
-        } else if (dirtiness > 0) {
-          mapping_data->dirty_pages++;
-          mapping_data->dirty_page_set.insert(mapping_data->dirty_page_set.end(), virtual_page_idx);
-        }
+      const bool is_dirty = dirtiness > 0;
+      const bool is_private = page_count == 1;
 
-        bool is_dirty = dirtiness > 0;
-        bool is_private = page_count == 1;
+      if (is_private) {
+        mapping_data->private_pages++;
+      }
 
-        if (page_count == 1) {
-          mapping_data->private_pages++;
-        }
-
-        if (is_dirty && is_private) {
-          mapping_data->private_dirty_pages++;
-          for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) {
-            const ImageHeader::ImageSections section = static_cast<ImageHeader::ImageSections>(i);
-            if (image_header.GetImageSection(section).Contains(offset)) {
-              ++private_dirty_pages_for_section[i];
-            }
+      if (is_dirty && is_private) {
+        mapping_data->private_dirty_pages++;
+        for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) {
+          const ImageHeader::ImageSections section = static_cast<ImageHeader::ImageSections>(i);
+          if (image_header.GetImageSection(section).Contains(offset)) {
+            mapping_data->private_dirty_pages_for_section[i] += 1;
           }
         }
       }
     }
     mapping_data->false_dirty_pages = mapping_data->dirty_pages - mapping_data->different_pages;
+
+    return true;
+  }
+
+  void PrintMappingData(const MappingData& mapping_data, const ImageHeader& image_header) {
+    std::ostream& os = *os_;
     // Print low-level (bytes, int32s, pages) statistics.
-    os << mapping_data->different_bytes << " differing bytes,\n  "
-       << mapping_data->different_int32s << " differing int32s,\n  "
-       << mapping_data->different_pages << " differing pages,\n  "
-       << mapping_data->dirty_pages << " pages are dirty;\n  "
-       << mapping_data->false_dirty_pages << " pages are false dirty;\n  "
-       << mapping_data->private_pages << " pages are private;\n  "
-       << mapping_data->private_dirty_pages << " pages are Private_Dirty\n  "
+    os << mapping_data.different_bytes << " differing bytes,\n  "
+       << mapping_data.different_int32s << " differing int32s,\n  "
+       << mapping_data.different_pages << " differing pages,\n  "
+       << mapping_data.dirty_pages << " pages are dirty;\n  "
+       << mapping_data.false_dirty_pages << " pages are false dirty;\n  "
+       << mapping_data.private_pages << " pages are private;\n  "
+       << mapping_data.private_dirty_pages << " pages are Private_Dirty\n  "
        << "\n";
 
-    size_t total_private_dirty_pages = std::accumulate(private_dirty_pages_for_section.begin(),
-                                                       private_dirty_pages_for_section.end(),
-                                                       0u);
+    size_t total_private_dirty_pages = std::accumulate(
+      mapping_data.private_dirty_pages_for_section.begin(),
+      mapping_data.private_dirty_pages_for_section.end(),
+      0u);
     os << "Image sections (total private dirty pages " << total_private_dirty_pages << ")\n";
     for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) {
       const ImageHeader::ImageSections section = static_cast<ImageHeader::ImageSections>(i);
       os << section << " " << image_header.GetImageSection(section)
-         << " private dirty pages=" << private_dirty_pages_for_section[i] << "\n";
+         << " private dirty pages=" << mapping_data.private_dirty_pages_for_section[i] << "\n";
     }
     os << "\n";
-
-    return true;
   }
 
   // Look at /proc/$pid/mem and only diff the things from there
@@ -1575,13 +1497,7 @@
       // For more validation should also check the ImageHeader from the file
     }
 
-    MappingData mapping_data;
 
-    os << "Mapping at [" << reinterpret_cast<void*>(boot_map.start) << ", "
-       << reinterpret_cast<void*>(boot_map.end) << ") had:\n  ";
-    if (!ComputeDirtyBytes(image_header, image_begin, boot_map, remote_contents, &mapping_data)) {
-      return false;
-    }
     RemoteProcesses remotes;
     if (zygote_pid_only_) {
       remotes = RemoteProcesses::kZygoteOnly;
@@ -1591,6 +1507,23 @@
       remotes = RemoteProcesses::kImageOnly;
     }
 
+    // Only app vs zygote is supported at the moment
+    CHECK_EQ(remotes, RemoteProcesses::kImageAndZygote);
+
+    MappingData mapping_data;
+    if (!ComputeDirtyBytes(image_header,
+                           boot_map,
+                           remote_contents,
+                           zygote_contents,
+                           &mapping_data,
+                           &error_msg)) {
+      os << error_msg;
+      return false;
+    }
+    os << "Mapping at [" << reinterpret_cast<void*>(boot_map.start) << ", "
+       << reinterpret_cast<void*>(boot_map.end) << ") had:\n  ";
+    PrintMappingData(mapping_data, image_header);
+
     // Check all the mirror::Object entries in the image.
     RegionData<mirror::Object> object_region_data(os_,
                                                   remote_contents,
@@ -1815,8 +1748,6 @@
   // A File for reading /proc/<zygote_diff_pid_>/pagemap.
   File zygote_pagemap_file_;
 
-  // A File for reading /proc/self/pagemap.
-  File clean_pagemap_file_;
   // A File for reading /proc/kpageflags.
   File kpageflags_file_;
   // A File for reading /proc/kpagecount.
diff --git a/libartbase/Android.bp b/libartbase/Android.bp
index f937523..951599d 100644
--- a/libartbase/Android.bp
+++ b/libartbase/Android.bp
@@ -39,7 +39,6 @@
         "base/file_utils.cc",
         "base/flags.cc",
         "base/hex_dump.cc",
-        "base/hiddenapi_flags.cc",
         "base/logging.cc",
         "base/malloc_arena_pool.cc",
         "base/membarrier.cc",
@@ -66,10 +65,16 @@
                 "base/globals_unix.cc",
                 "base/mem_map_unix.cc",
             ],
+            static: {
+                cflags: ["-DART_STATIC_LIBARTBASE"],
+            },
             static_libs: [
+                "libcap",
+                "libmodules-utils-build",
                 // ZipArchive support, the order matters here to get all symbols.
                 "libziparchive",
             ],
+            whole_static_libs: ["libtinyxml2"],
             shared_libs: [
                 "libz",
                 "liblog",
@@ -79,6 +84,9 @@
                 "libbase",
             ],
             export_shared_lib_headers: ["libbase"], // ART's macros.h depends on libbase's macros.h.
+            export_static_lib_headers: [
+                "libcap",
+            ],
         },
         not_windows: {
             srcs: [
@@ -88,6 +96,7 @@
             static: {
                 cflags: ["-DART_STATIC_LIBARTBASE"],
             },
+            whole_static_libs: ["libtinyxml2"],
             shared_libs: [
                 "libziparchive",
                 "libz",
@@ -99,6 +108,14 @@
             ],
             export_shared_lib_headers: ["libbase"], // ART's macros.h depends on libbase's macros.h.
         },
+        linux_glibc: {
+            static_libs: [
+                "libcap",
+            ],
+            export_static_lib_headers: [
+                "libcap",
+            ],
+        },
         windows: {
             srcs: [
                 "base/mem_map_windows.cc",
@@ -112,6 +129,7 @@
                 // For common macros.
                 "libbase",
             ],
+            whole_static_libs: ["libtinyxml2"],
             export_static_lib_headers: ["libbase"], // ART's macros.h depends on libbase's macros.h.
 
             cflags: ["-Wno-thread-safety"],
@@ -134,6 +152,18 @@
         "libz",
         "libziparchive",
     ],
+    target: {
+        android: {
+            whole_static_libs: [
+                "libcap",
+            ],
+        },
+        linux_glibc: {
+            whole_static_libs: [
+                "libcap",
+            ],
+        },
+    },
 }
 
 cc_defaults {
@@ -181,13 +211,11 @@
         "com.android.art.debug",
     ],
 
-    shared_libs: [
-        "libbase",
-        "libziparchive",
-    ],
-    export_shared_lib_headers: ["libbase"],
     target: {
         windows: {
+            // Control the enabled property here rather than in
+            // libartbase_defaults, to ensure it overrides properties inherited
+            // from other defaults.
             enabled: true,
             shared: {
                 enabled: false,
@@ -206,13 +234,13 @@
     apex_available: [
         "com.android.art.debug",
     ],
-    shared_libs: [
-        "libbase",
-        "libziparchive",
-    ],
-    export_shared_lib_headers: ["libbase"],
+
     target: {
         windows: {
+            // Control the enabled property here rather than in
+            // libartbase_defaults, to ensure it overrides properties inherited
+            // from other defaults, in particular any inherited via
+            // art_debug_defaults.
             enabled: true,
             shared: {
                 enabled: false,
@@ -228,7 +256,7 @@
     ],
     shared_libs: [
         "libbase",
-        "libbacktrace",
+        "libunwindstack",
     ],
     header_libs: [
         "libnativehelper_header_only",
@@ -236,11 +264,13 @@
     static: {
         whole_static_libs: [
             "libc++fs",
+            "libcap",
         ],
     },
     shared: {
         static_libs: [
             "libc++fs",
+            "libcap",
         ],
     },
 }
@@ -391,6 +421,8 @@
     shared_libs: ["libbase"],
     export_shared_lib_headers: ["libbase"],
 
+    whole_static_libs: ["libtinyxml2"],
+
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
diff --git a/libartbase/arch/instruction_set.cc b/libartbase/arch/instruction_set.cc
index 9ec66fe..e0de4e8 100644
--- a/libartbase/arch/instruction_set.cc
+++ b/libartbase/arch/instruction_set.cc
@@ -17,6 +17,8 @@
 #include "instruction_set.h"
 
 #include "android-base/logging.h"
+#include "android-base/properties.h"
+#include "android-base/stringprintf.h"
 #include "base/bit_utils.h"
 #include "base/globals.h"
 
@@ -27,6 +29,7 @@
     case InstructionSet::kArm:
     case InstructionSet::kThumb2:
     case InstructionSet::kArm64:
+    case InstructionSet::kRiscv64:
     case InstructionSet::kX86:
     case InstructionSet::kX86_64:
     case InstructionSet::kNone:
@@ -44,6 +47,8 @@
       return "arm";
     case InstructionSet::kArm64:
       return "arm64";
+    case InstructionSet::kRiscv64:
+      return "riscv64";
     case InstructionSet::kX86:
       return "x86";
     case InstructionSet::kX86_64:
@@ -62,6 +67,8 @@
     return InstructionSet::kArm;
   } else if (strcmp("arm64", isa_str) == 0) {
     return InstructionSet::kArm64;
+  } else if (strcmp("riscv64", isa_str) == 0) {
+    return InstructionSet::kRiscv64;
   } else if (strcmp("x86", isa_str) == 0) {
     return InstructionSet::kX86;
   } else if (strcmp("x86_64", isa_str) == 0) {
@@ -71,30 +78,53 @@
   return InstructionSet::kNone;
 }
 
-size_t GetInstructionSetAlignment(InstructionSet isa) {
-  switch (isa) {
-    case InstructionSet::kArm:
-      // Fall-through.
-    case InstructionSet::kThumb2:
-      return kArmAlignment;
-    case InstructionSet::kArm64:
-      return kArm64Alignment;
-    case InstructionSet::kX86:
-      // Fall-through.
-    case InstructionSet::kX86_64:
-      return kX86Alignment;
-    case InstructionSet::kNone:
-      LOG(FATAL) << "ISA kNone does not have alignment.";
-      UNREACHABLE();
+std::vector<InstructionSet> GetSupportedInstructionSets(std::string* error_msg) {
+  std::string zygote_kinds = android::base::GetProperty("ro.zygote", {});
+  if (zygote_kinds.empty()) {
+    *error_msg = "Unable to get Zygote kinds";
+    return {};
   }
-  LOG(FATAL) << "Unknown ISA " << isa;
-  UNREACHABLE();
+
+  switch (kRuntimeISA) {
+    case InstructionSet::kArm:
+    case InstructionSet::kArm64:
+      if (zygote_kinds == "zygote64_32" || zygote_kinds == "zygote32_64") {
+        return {InstructionSet::kArm64, InstructionSet::kArm};
+      } else if (zygote_kinds == "zygote64") {
+        return {InstructionSet::kArm64};
+      } else if (zygote_kinds == "zygote32") {
+        return {InstructionSet::kArm};
+      } else {
+        *error_msg = android::base::StringPrintf("Unknown Zygote kinds '%s'", zygote_kinds.c_str());
+        return {};
+      }
+    case InstructionSet::kRiscv64:
+      return {InstructionSet::kRiscv64};
+    case InstructionSet::kX86:
+    case InstructionSet::kX86_64:
+      if (zygote_kinds == "zygote64_32" || zygote_kinds == "zygote32_64") {
+        return {InstructionSet::kX86_64, InstructionSet::kX86};
+      } else if (zygote_kinds == "zygote64") {
+        return {InstructionSet::kX86_64};
+      } else if (zygote_kinds == "zygote32") {
+        return {InstructionSet::kX86};
+      } else {
+        *error_msg = android::base::StringPrintf("Unknown Zygote kinds '%s'", zygote_kinds.c_str());
+        return {};
+      }
+    default:
+      *error_msg = android::base::StringPrintf("Unknown runtime ISA '%s'",
+                                               GetInstructionSetString(kRuntimeISA));
+      return {};
+  }
 }
 
 namespace instruction_set_details {
 
 static_assert(IsAligned<kPageSize>(kArmStackOverflowReservedBytes), "ARM gap not page aligned");
 static_assert(IsAligned<kPageSize>(kArm64StackOverflowReservedBytes), "ARM64 gap not page aligned");
+static_assert(IsAligned<kPageSize>(kRiscv64StackOverflowReservedBytes),
+              "RISCV64 gap not page aligned");
 static_assert(IsAligned<kPageSize>(kX86StackOverflowReservedBytes), "X86 gap not page aligned");
 static_assert(IsAligned<kPageSize>(kX86_64StackOverflowReservedBytes),
               "X86_64 gap not page aligned");
@@ -107,6 +137,8 @@
 static_assert(ART_FRAME_SIZE_LIMIT < kArmStackOverflowReservedBytes, "Frame size limit too large");
 static_assert(ART_FRAME_SIZE_LIMIT < kArm64StackOverflowReservedBytes,
               "Frame size limit too large");
+static_assert(ART_FRAME_SIZE_LIMIT < kRiscv64StackOverflowReservedBytes,
+              "Frame size limit too large");
 static_assert(ART_FRAME_SIZE_LIMIT < kX86StackOverflowReservedBytes,
               "Frame size limit too large");
 static_assert(ART_FRAME_SIZE_LIMIT < kX86_64StackOverflowReservedBytes,
diff --git a/libartbase/arch/instruction_set.h b/libartbase/arch/instruction_set.h
index faf881d..0e9ebf0 100644
--- a/libartbase/arch/instruction_set.h
+++ b/libartbase/arch/instruction_set.h
@@ -19,6 +19,7 @@
 
 #include <iosfwd>
 #include <string>
+#include <vector>
 
 #include "base/enums.h"
 #include "base/macros.h"
@@ -30,6 +31,7 @@
   kArm,
   kArm64,
   kThumb2,
+  kRiscv64,
   kX86,
   kX86_64,
   kLast = kX86_64
@@ -40,6 +42,8 @@
 static constexpr InstructionSet kRuntimeISA = InstructionSet::kArm;
 #elif defined(__aarch64__)
 static constexpr InstructionSet kRuntimeISA = InstructionSet::kArm64;
+#elif defined (__riscv)
+static constexpr InstructionSet kRuntimeISA = InstructionSet::kRiscv64;
 #elif defined(__i386__)
 static constexpr InstructionSet kRuntimeISA = InstructionSet::kX86;
 #elif defined(__x86_64__)
@@ -51,25 +55,29 @@
 // Architecture-specific pointer sizes
 static constexpr PointerSize kArmPointerSize = PointerSize::k32;
 static constexpr PointerSize kArm64PointerSize = PointerSize::k64;
+static constexpr PointerSize kRiscv64PointerSize = PointerSize::k64;
 static constexpr PointerSize kX86PointerSize = PointerSize::k32;
 static constexpr PointerSize kX86_64PointerSize = PointerSize::k64;
 
-// ARM instruction alignment. ARM processors require code to be 4-byte aligned,
-// but ARM ELF requires 8..
-static constexpr size_t kArmAlignment = 8;
-
-// ARM64 instruction alignment. This is the recommended alignment for maximum performance.
-static constexpr size_t kArm64Alignment = 16;
-
 // ARM64 default SVE vector length.
 static constexpr size_t kArm64DefaultSVEVectorLength = 256;
 
-// X86 instruction alignment. This is the recommended alignment for maximum performance.
-static constexpr size_t kX86Alignment = 16;
+// Code alignment (used for the first instruction of a subroutine, such as an entrypoint).
+// This is the recommended alignment for maximum performance.
+// ARM processors require code to be 4-byte aligned, but ARM ELF requires 8.
+static constexpr size_t kArmCodeAlignment = 8;
+static constexpr size_t kArm64CodeAlignment = 16;
+static constexpr size_t kRiscv64CodeAlignment = 16;
+static constexpr size_t kX86CodeAlignment = 16;
 
-// Different than code alignment since code alignment is only first instruction of method.
+// Instruction alignment (every instruction must be aligned at this boundary). This differs from
+// code alignment, which applies only to the first instruction of a subroutine.
+// Android requires the RISC-V compressed instruction extension, and that allows
+// *all* instructions (not just compressed ones) to be 2-byte aligned rather
+// than the usual 4-byte alignment requirement.
 static constexpr size_t kThumb2InstructionAlignment = 2;
 static constexpr size_t kArm64InstructionAlignment = 4;
+static constexpr size_t kRiscv64InstructionAlignment = 2;
 static constexpr size_t kX86InstructionAlignment = 1;
 static constexpr size_t kX86_64InstructionAlignment = 1;
 
@@ -89,6 +97,8 @@
       return kArmPointerSize;
     case InstructionSet::kArm64:
       return kArm64PointerSize;
+    case InstructionSet::kRiscv64:
+      return kRiscv64PointerSize;
     case InstructionSet::kX86:
       return kX86PointerSize;
     case InstructionSet::kX86_64:
@@ -100,30 +110,12 @@
   InstructionSetAbort(isa);
 }
 
-constexpr size_t GetInstructionSetInstructionAlignment(InstructionSet isa) {
-  switch (isa) {
-    case InstructionSet::kArm:
-      // Fall-through.
-    case InstructionSet::kThumb2:
-      return kThumb2InstructionAlignment;
-    case InstructionSet::kArm64:
-      return kArm64InstructionAlignment;
-    case InstructionSet::kX86:
-      return kX86InstructionAlignment;
-    case InstructionSet::kX86_64:
-      return kX86_64InstructionAlignment;
-
-    case InstructionSet::kNone:
-      break;
-  }
-  InstructionSetAbort(isa);
-}
-
 constexpr bool IsValidInstructionSet(InstructionSet isa) {
   switch (isa) {
     case InstructionSet::kArm:
     case InstructionSet::kThumb2:
     case InstructionSet::kArm64:
+    case InstructionSet::kRiscv64:
     case InstructionSet::kX86:
     case InstructionSet::kX86_64:
       return true;
@@ -134,7 +126,68 @@
   return false;
 }
 
-size_t GetInstructionSetAlignment(InstructionSet isa);
+constexpr size_t GetInstructionSetInstructionAlignment(InstructionSet isa) {
+  switch (isa) {
+    case InstructionSet::kArm:
+      // Fall-through.
+    case InstructionSet::kThumb2:
+      return kThumb2InstructionAlignment;
+    case InstructionSet::kArm64:
+      return kArm64InstructionAlignment;
+    case InstructionSet::kRiscv64:
+      return kRiscv64InstructionAlignment;
+    case InstructionSet::kX86:
+      return kX86InstructionAlignment;
+    case InstructionSet::kX86_64:
+      return kX86_64InstructionAlignment;
+
+    case InstructionSet::kNone:
+      break;
+  }
+  InstructionSetAbort(isa);
+}
+
+constexpr size_t GetInstructionSetCodeAlignment(InstructionSet isa) {
+  switch (isa) {
+    case InstructionSet::kArm:
+      // Fall-through.
+    case InstructionSet::kThumb2:
+      return kArmCodeAlignment;
+    case InstructionSet::kArm64:
+      return kArm64CodeAlignment;
+    case InstructionSet::kRiscv64:
+      return kRiscv64CodeAlignment;
+    case InstructionSet::kX86:
+      // Fall-through.
+    case InstructionSet::kX86_64:
+      return kX86CodeAlignment;
+
+    case InstructionSet::kNone:
+      break;
+  }
+  InstructionSetAbort(isa);
+}
+
+// Returns the difference between the code address and a usable PC.
+// Mainly to cope with `kThumb2` where the lower bit must be set.
+constexpr size_t GetInstructionSetEntryPointAdjustment(InstructionSet isa) {
+  switch (isa) {
+    case InstructionSet::kArm:
+    case InstructionSet::kArm64:
+    case InstructionSet::kRiscv64:
+    case InstructionSet::kX86:
+    case InstructionSet::kX86_64:
+      return 0;
+    case InstructionSet::kThumb2: {
+      // +1 to set the low-order bit so a BLX will switch to Thumb mode
+      return 1;
+    }
+
+    case InstructionSet::kNone:
+      break;
+  }
+  InstructionSetAbort(isa);
+}
 
 constexpr bool Is64BitInstructionSet(InstructionSet isa) {
   switch (isa) {
@@ -144,6 +197,7 @@
       return false;
 
     case InstructionSet::kArm64:
+    case InstructionSet::kRiscv64:
     case InstructionSet::kX86_64:
       return true;
 
@@ -165,6 +219,8 @@
       return 4;
     case InstructionSet::kArm64:
       return 8;
+    case InstructionSet::kRiscv64:
+      return 8;
     case InstructionSet::kX86:
       return 4;
     case InstructionSet::kX86_64:
@@ -184,6 +240,8 @@
       return 4;
     case InstructionSet::kArm64:
       return 8;
+    case InstructionSet::kRiscv64:
+      return 8;
     case InstructionSet::kX86:
       return 8;
     case InstructionSet::kX86_64:
@@ -195,17 +253,22 @@
   InstructionSetAbort(isa);
 }
 
+// Returns the instruction sets supported by the device, or an empty list on failure.
+std::vector<InstructionSet> GetSupportedInstructionSets(std::string* error_msg);
+
 namespace instruction_set_details {
 
 #if !defined(ART_STACK_OVERFLOW_GAP_arm) || !defined(ART_STACK_OVERFLOW_GAP_arm64) || \
+    !defined(ART_STACK_OVERFLOW_GAP_riscv64) || \
     !defined(ART_STACK_OVERFLOW_GAP_x86) || !defined(ART_STACK_OVERFLOW_GAP_x86_64)
 #error "Missing defines for stack overflow gap"
 #endif
 
-static constexpr size_t kArmStackOverflowReservedBytes    = ART_STACK_OVERFLOW_GAP_arm;
-static constexpr size_t kArm64StackOverflowReservedBytes  = ART_STACK_OVERFLOW_GAP_arm64;
-static constexpr size_t kX86StackOverflowReservedBytes    = ART_STACK_OVERFLOW_GAP_x86;
-static constexpr size_t kX86_64StackOverflowReservedBytes = ART_STACK_OVERFLOW_GAP_x86_64;
+static constexpr size_t kArmStackOverflowReservedBytes     = ART_STACK_OVERFLOW_GAP_arm;
+static constexpr size_t kArm64StackOverflowReservedBytes   = ART_STACK_OVERFLOW_GAP_arm64;
+static constexpr size_t kRiscv64StackOverflowReservedBytes = ART_STACK_OVERFLOW_GAP_riscv64;
+static constexpr size_t kX86StackOverflowReservedBytes     = ART_STACK_OVERFLOW_GAP_x86;
+static constexpr size_t kX86_64StackOverflowReservedBytes  = ART_STACK_OVERFLOW_GAP_x86_64;
 
 NO_RETURN void GetStackOverflowReservedBytesFailure(const char* error_msg);
 
@@ -221,6 +284,9 @@
     case InstructionSet::kArm64:
       return instruction_set_details::kArm64StackOverflowReservedBytes;
 
+    case InstructionSet::kRiscv64:
+      return instruction_set_details::kRiscv64StackOverflowReservedBytes;
+
     case InstructionSet::kX86:
       return instruction_set_details::kX86StackOverflowReservedBytes;
 
@@ -273,7 +339,7 @@
   return ((hi64 << 32) | lo32);
 }
 
-#elif defined(__x86_64__) || defined(__aarch64__)
+#elif defined(__x86_64__) || defined(__aarch64__) || defined(__riscv)
 
 // Note: TwoWordReturn can't be constexpr for 64-bit targets. We'd need a constexpr constructor,
 //       which would violate C-linkage in the entrypoint functions.
diff --git a/libartbase/base/arena_allocator.cc b/libartbase/base/arena_allocator.cc
index 76f2883..d38a64e 100644
--- a/libartbase/base/arena_allocator.cc
+++ b/libartbase/base/arena_allocator.cc
@@ -28,8 +28,6 @@
 
 namespace art {
 
-constexpr size_t kMemoryToolRedZoneBytes = 8;
-
 template <bool kCount>
 const char* const ArenaAllocatorStatsImpl<kCount>::kAllocNames[] = {
   // Every name should have the same width and end with a space. Abbreviate if necessary:
@@ -75,6 +73,7 @@
   "LSE          ",
   "CFRE         ",
   "LICM         ",
+  "WBE          ",
   "LoopOpt      ",
   "SsaLiveness  ",
   "SsaPhiElim   ",
@@ -187,9 +186,6 @@
   MEMORY_TOOL_MAKE_NOACCESS(ptr, size);
 }
 
-Arena::Arena() : bytes_allocated_(0), memory_(nullptr), size_(0), next_(nullptr) {
-}
-
 size_t ArenaAllocator::BytesAllocated() const {
   return ArenaAllocatorStats::BytesAllocated();
 }
@@ -247,7 +243,7 @@
   size_t rounded_bytes = bytes + kMemoryToolRedZoneBytes;
   DCHECK_ALIGNED(rounded_bytes, 8);  // `bytes` is 16-byte aligned, red zone is 8-byte aligned.
   uintptr_t padding =
-      ((reinterpret_cast<uintptr_t>(ptr_) + 15u) & 15u) - reinterpret_cast<uintptr_t>(ptr_);
+      RoundUp(reinterpret_cast<uintptr_t>(ptr_), 16) - reinterpret_cast<uintptr_t>(ptr_);
   ArenaAllocatorStats::RecordAlloc(rounded_bytes, kind);
   uint8_t* ret;
   if (UNLIKELY(padding + rounded_bytes > static_cast<size_t>(end_ - ptr_))) {
@@ -270,6 +266,13 @@
   pool_->FreeArenaChain(arena_head_);
 }
 
+void ArenaAllocator::ResetCurrentArena() {
+  UpdateBytesAllocated();
+  begin_ = nullptr;
+  ptr_ = nullptr;
+  end_ = nullptr;
+}
+
 uint8_t* ArenaAllocator::AllocFromNewArena(size_t bytes) {
   Arena* new_arena = pool_->AllocArena(std::max(arena_allocator::kArenaDefaultSize, bytes));
   DCHECK(new_arena != nullptr);
diff --git a/libartbase/base/arena_allocator.h b/libartbase/base/arena_allocator.h
index 12a44d5..c4f713a 100644
--- a/libartbase/base/arena_allocator.h
+++ b/libartbase/base/arena_allocator.h
@@ -84,6 +84,7 @@
   kArenaAllocLSE,
   kArenaAllocCFRE,
   kArenaAllocLICM,
+  kArenaAllocWBE,
   kArenaAllocLoopOptimization,
   kArenaAllocSsaLiveness,
   kArenaAllocSsaPhiElimination,
@@ -152,7 +153,7 @@
 
 class ArenaAllocatorMemoryTool {
  public:
-  bool IsRunningOnMemoryTool() { return kMemoryToolIsAvailable; }
+  static constexpr bool IsRunningOnMemoryTool() { return kMemoryToolIsAvailable; }
 
   void MakeDefined(void* ptr, size_t size) {
     if (UNLIKELY(IsRunningOnMemoryTool())) {
@@ -178,19 +179,18 @@
 
 class Arena {
  public:
-  Arena();
+  Arena() : bytes_allocated_(0), memory_(nullptr), size_(0), next_(nullptr) {}
+
   virtual ~Arena() { }
   // Reset is for pre-use and uses memset for performance.
   void Reset();
   // Release is used inbetween uses and uses madvise for memory usage.
   virtual void Release() { }
-  uint8_t* Begin() {
+  uint8_t* Begin() const {
     return memory_;
   }
 
-  uint8_t* End() {
-    return memory_ + size_;
-  }
+  uint8_t* End() const { return memory_ + size_; }
 
   size_t Size() const {
     return size_;
@@ -205,9 +205,9 @@
   }
 
   // Return true if ptr is contained in the arena.
-  bool Contains(const void* ptr) const {
-    return memory_ <= ptr && ptr < memory_ + bytes_allocated_;
-  }
+  bool Contains(const void* ptr) const { return memory_ <= ptr && ptr < memory_ + size_; }
+
+  Arena* Next() const { return next_; }
 
  protected:
   size_t bytes_allocated_;
@@ -289,7 +289,7 @@
       return AllocWithMemoryToolAlign16(bytes, kind);
     }
     uintptr_t padding =
-        ((reinterpret_cast<uintptr_t>(ptr_) + 15u) & 15u) - reinterpret_cast<uintptr_t>(ptr_);
+        RoundUp(reinterpret_cast<uintptr_t>(ptr_), 16) - reinterpret_cast<uintptr_t>(ptr_);
     ArenaAllocatorStats::RecordAlloc(bytes, kind);
     if (UNLIKELY(padding + bytes > static_cast<size_t>(end_ - ptr_))) {
       static_assert(kArenaAlignment >= 16, "Expecting sufficient alignment for new Arena.");
@@ -355,6 +355,22 @@
     return pool_;
   }
 
+  Arena* GetHeadArena() const {
+    return arena_head_;
+  }
+
+  uint8_t* CurrentPtr() const {
+    return ptr_;
+  }
+
+  size_t CurrentArenaUnusedBytes() const {
+    DCHECK_LE(ptr_, end_);
+    return end_ - ptr_;
+  }
+  // Resets the current arena in use, which will force us to get a new arena
+  // on next allocation.
+  void ResetCurrentArena();
+
   bool Contains(const void* ptr) const;
 
   // The alignment guaranteed for individual allocations.
@@ -363,6 +379,9 @@
   // The alignment required for the whole Arena rather than individual allocations.
   static constexpr size_t kArenaAlignment = 16u;
 
+  // Extra bytes required by the memory tool.
+  static constexpr size_t kMemoryToolRedZoneBytes = 8u;
+
  private:
   void* AllocWithMemoryTool(size_t bytes, ArenaAllocKind kind);
   void* AllocWithMemoryToolAlign16(size_t bytes, ArenaAllocKind kind);
diff --git a/libartbase/base/array_slice.h b/libartbase/base/array_slice.h
index a58ff44..067d9f2 100644
--- a/libartbase/base/array_slice.h
+++ b/libartbase/base/array_slice.h
@@ -65,9 +65,9 @@
             lpa != nullptr ? lpa->size() : 0,
             element_size) {}
   ArraySlice(const ArraySlice<T>&) = default;
-  ArraySlice(ArraySlice<T>&&) = default;
+  ArraySlice(ArraySlice<T>&&) noexcept = default;
   ArraySlice<T>& operator=(const ArraySlice<T>&) = default;
-  ArraySlice<T>& operator=(ArraySlice<T>&&) = default;
+  ArraySlice<T>& operator=(ArraySlice<T>&&) noexcept = default;
 
   // Iterators.
   iterator begin() { return iterator(&AtUnchecked(0), element_size_); }
diff --git a/libartbase/base/bit_memory_region.h b/libartbase/base/bit_memory_region.h
index c5224a5..baac2f5 100644
--- a/libartbase/base/bit_memory_region.h
+++ b/libartbase/base/bit_memory_region.h
@@ -324,8 +324,37 @@
   size_t bit_size_ = 0;
 };
 
-constexpr uint32_t kVarintBits = 4;  // Minimum number of bits used for varint.
-constexpr uint32_t kVarintMax = 11;  // Maximum value which is stored "inline".
+// Minimum number of bits used for varint. A varint represents either a value stored "inline" or
+// the number of bytes that are required to encode the value.
+constexpr uint32_t kVarintBits = 4;
+// Maximum value which is stored "inline". We use the rest of the values to encode the number of
+// bytes required to encode the value when the value is greater than kVarintMax.
+// We encode any value less than or equal to 11 inline. We use 12, 13, 14 and 15
+// to represent that the value is encoded in 1, 2, 3 and 4 bytes respectively.
+//
+// For example if we want to encode 1, 15, 16, 7, 11, 256:
+//
+// Low numbers (1, 7, 11) are encoded inline. 15 and 12 are set with 12 to show
+// we need to load one byte for each to have their real values (15 and 12), and
+// 256 is set with 13 to show we need to load two bytes. This is done to
+// compress the values in the bit array and keep the size down. Where the actual value
+// is read from depends on the use case.
+//
+// Values greater than kVarintMax could be encoded as a separate list referred
+// to as InterleavedVarints (see ReadInterleavedVarints / WriteInterleavedVarints).
+// This is used when there are fixed number of fields like CodeInfo headers.
+// In our example the interleaved encoding looks like below:
+//
+// Meaning: 1--- 15-- 12-- 7--- 11-- 256- 15------- 12------- 256----------------
+// Bits:    0001 1100 1100 0111 1011 1101 0000 1111 0000 1100 0000 0001 0000 0000
+//
+// In other cases the value is recorded just following the size encoding. This is
+// referred as consecutive encoding (See ReadVarint / WriteVarint). In our
+// example the consecutively encoded varints looks like below:
+//
+// Meaning: 1--- 15-- 15------- 12-- 12------- 7--- 11-- 256- 256----------------
+// Bits:    0001 1100 0000 1100 1100 0000 1100 0111 1011 1101 0000 0001 0000 0000
+constexpr uint32_t kVarintMax = 11;
 
 class BitMemoryReader {
  public:
diff --git a/libartbase/base/casts.h b/libartbase/base/casts.h
index c88f589..70a7035 100644
--- a/libartbase/base/casts.h
+++ b/libartbase/base/casts.h
@@ -57,17 +57,7 @@
 // type Foo to type SubclassOfFoo), static_cast<> isn't safe, because
 // how do you know the pointer is really of type SubclassOfFoo?  It
 // could be a bare Foo, or of type DifferentSubclassOfFoo.  Thus,
-// when you downcast, you should use this macro.  In debug mode, we
-// use dynamic_cast<> to double-check the downcast is legal (we die
-// if it's not).  In normal mode, we do the efficient static_cast<>
-// instead.  Thus, it's important to test in debug mode to make sure
-// the cast is legal!
-//    This is the only place in the code we should use dynamic_cast<>.
-// In particular, you SHOULDN'T be using dynamic_cast<> in order to
-// do RTTI (eg code like this:
-//    if (dynamic_cast<Subclass1>(foo)) HandleASubclass1Object(foo);
-//    if (dynamic_cast<Subclass2>(foo)) HandleASubclass2Object(foo);
-// You should design the code some other way not to need this.
+// when you downcast, you should use this macro.
 
 template<typename To, typename From>     // use like this: down_cast<T*>(foo);
 inline To down_cast(From* f) {                   // so we only accept pointers
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index c74f3de..db0e1c1 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -22,14 +22,17 @@
 #include <ftw.h>
 #include <libgen.h>
 #include <stdlib.h>
+#include <sys/capability.h>
 #include <unistd.h>
 
 #include <cstdio>
 #include <filesystem>
+#include <functional>
 
 #include "android-base/file.h"
 #include "android-base/logging.h"
 #include "android-base/process.h"
+#include "android-base/scopeguard.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "android-base/unique_fd.h"
@@ -41,6 +44,7 @@
 #include "base/mutex.h"
 #include "base/os.h"
 #include "base/runtime_debug.h"
+#include "base/scoped_cap.h"
 #include "base/stl_util.h"
 #include "base/string_view_cpp20.h"
 #include "base/testing.h"
@@ -69,15 +73,7 @@
 
 ScratchDir::~ScratchDir() {
   if (!keep_files_) {
-    // Recursively delete the directory and all its content.
-    nftw(path_.c_str(), [](const char* name, const struct stat*, int type, struct FTW *) {
-      if (type == FTW_F) {
-        unlink(name);
-      } else if (type == FTW_DP) {
-        rmdir(name);
-      }
-      return 0;
-    }, 256 /* max open file descriptors */, FTW_DEPTH);
+    std::filesystem::remove_all(path_);
   }
 }
 
@@ -145,6 +141,29 @@
   CHECK_EQ(0, unlink_result);
 }
 
+// Temporarily drops all root capabilities when the test is run as root. This is a noop otherwise.
+android::base::ScopeGuard<std::function<void()>> ScopedUnroot() {
+  ScopedCap old_cap(cap_get_proc());
+  CHECK_NE(old_cap.Get(), nullptr);
+  ScopedCap new_cap(cap_dup(old_cap.Get()));
+  CHECK_NE(new_cap.Get(), nullptr);
+  CHECK_EQ(cap_clear_flag(new_cap.Get(), CAP_EFFECTIVE), 0);
+  CHECK_EQ(cap_set_proc(new_cap.Get()), 0);
+  // `old_cap` is actually not shared with anyone else, but we have to wrap it with a `shared_ptr`
+  // because `std::function` requires captures to be copyable.
+  return android::base::make_scope_guard(
+      [old_cap = std::make_shared<ScopedCap>(std::move(old_cap))]() {
+        CHECK_EQ(cap_set_proc(old_cap->Get()), 0);
+      });
+}
+
+// Temporarily drops write permission on a file/directory.
+android::base::ScopeGuard<std::function<void()>> ScopedInaccessible(const std::string& path) {
+  std::filesystem::perms old_perms = std::filesystem::status(path).permissions();
+  std::filesystem::permissions(path, std::filesystem::perms::none);
+  return android::base::make_scope_guard([=]() { std::filesystem::permissions(path, old_perms); });
+}
+
 std::string CommonArtTestImpl::GetAndroidBuildTop() {
   CHECK(IsHost());
   std::string android_build_top;
@@ -247,7 +266,7 @@
     const char* android_i18n_root_from_env = getenv("ANDROID_I18N_ROOT");
     if (android_i18n_root_from_env == nullptr) {
       // Use ${ANDROID_I18N_OUT}/com.android.i18n for ANDROID_I18N_ROOT.
-      std::string android_i18n_root = android_host_out.c_str();
+      std::string android_i18n_root = android_host_out;
       android_i18n_root += "/com.android.i18n";
       setenv("ANDROID_I18N_ROOT", android_i18n_root.c_str(), 1);
     }
@@ -258,7 +277,7 @@
     const char* android_art_root_from_env = getenv("ANDROID_ART_ROOT");
     if (android_art_root_from_env == nullptr) {
       // Use ${ANDROID_HOST_OUT}/com.android.art for ANDROID_ART_ROOT.
-      std::string android_art_root = android_host_out.c_str();
+      std::string android_art_root = android_host_out;
       android_art_root += "/com.android.art";
       setenv("ANDROID_ART_ROOT", android_art_root.c_str(), 1);
     }
@@ -269,7 +288,7 @@
     const char* android_tzdata_root_from_env = getenv("ANDROID_TZDATA_ROOT");
     if (android_tzdata_root_from_env == nullptr) {
       // Use ${ANDROID_HOST_OUT}/com.android.tzdata for ANDROID_TZDATA_ROOT.
-      std::string android_tzdata_root = android_host_out.c_str();
+      std::string android_tzdata_root = android_host_out;
       android_tzdata_root += "/com.android.tzdata";
       setenv("ANDROID_TZDATA_ROOT", android_tzdata_root.c_str(), 1);
     }
@@ -309,17 +328,17 @@
   SetUpAndroidDataDir(android_data_);
 
   // Re-use the data temporary directory for /system_ext tests
-  android_system_ext_.append(android_data_.c_str());
+  android_system_ext_.append(android_data_);
   android_system_ext_.append("/system_ext");
   int mkdir_result = mkdir(android_system_ext_.c_str(), 0700);
   ASSERT_EQ(mkdir_result, 0);
-  setenv("ANDROID_SYSTEM_EXT", android_system_ext_.c_str(), 1);
+  setenv("SYSTEM_EXT_ROOT", android_system_ext_.c_str(), 1);
 
   std::string system_ext_framework = android_system_ext_ + "/framework";
   mkdir_result = mkdir(system_ext_framework.c_str(), 0700);
   ASSERT_EQ(mkdir_result, 0);
 
-  dalvik_cache_.append(android_data_.c_str());
+  dalvik_cache_.append(android_data_);
   dalvik_cache_.append("/dalvik-cache");
   mkdir_result = mkdir(dalvik_cache_.c_str(), 0700);
   ASSERT_EQ(mkdir_result, 0);
@@ -370,14 +389,9 @@
   std::string error_msg;
   MemMap::Init();
   static constexpr bool kVerifyChecksum = true;
-  const ArtDexFileLoader dex_file_loader;
   std::string filename(IsHost() ? GetAndroidBuildTop() + location : location);
-  if (!dex_file_loader.Open(filename.c_str(),
-                            std::string(location),
-                            /* verify= */ true,
-                            kVerifyChecksum,
-                            &error_msg,
-                            &dex_files)) {
+  ArtDexFileLoader dex_file_loader(filename.c_str(), std::string(location));
+  if (!dex_file_loader.Open(/* verify= */ true, kVerifyChecksum, &error_msg, &dex_files)) {
     LOG(FATAL) << "Could not open .dex file '" << filename << "': " << error_msg << "\n";
     UNREACHABLE();
   }
@@ -497,17 +511,11 @@
   static constexpr bool kVerify = true;
   static constexpr bool kVerifyChecksum = true;
   std::string error_msg;
-  const ArtDexFileLoader dex_file_loader;
+  ArtDexFileLoader dex_file_loader(filename);
   std::vector<std::unique_ptr<const DexFile>> dex_files;
-  bool success = dex_file_loader.Open(filename,
-                                      filename,
-                                      kVerify,
-                                      kVerifyChecksum,
-                                      &error_msg,
-                                      &dex_files);
+  bool success = dex_file_loader.Open(kVerify, kVerifyChecksum, &error_msg, &dex_files);
   CHECK(success) << "Failed to open '" << filename << "': " << error_msg;
   for (auto& dex_file : dex_files) {
-    CHECK_EQ(PROT_READ, dex_file->GetPermissions());
     CHECK(dex_file->IsReadOnly());
   }
   return dex_files;
@@ -592,6 +600,7 @@
   result.stage = ForkAndExecResult::kLink;
 
   std::vector<const char*> c_args;
+  c_args.reserve(argv.size() + 1);
   for (const std::string& str : argv) {
     c_args.push_back(str.c_str());
   }
diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h
index bd06043..d7711f2 100644
--- a/libartbase/base/common_art_test.h
+++ b/libartbase/base/common_art_test.h
@@ -25,6 +25,7 @@
 #include <vector>
 
 #include "android-base/logging.h"
+#include "android-base/scopeguard.h"
 #include "base/file_utils.h"
 #include "base/globals.h"
 #include "base/memory_tool.h"
@@ -122,6 +123,12 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedUnsetEnvironmentVariable);
 };
 
+// Temporarily drops all root capabilities when the test is run as root. This is a noop otherwise.
+android::base::ScopeGuard<std::function<void()>> ScopedUnroot();
+
+// Temporarily drops all permissions on a file/directory.
+android::base::ScopeGuard<std::function<void()>> ScopedInaccessible(const std::string& path);
+
 class CommonArtTestImpl {
  public:
   CommonArtTestImpl() = default;
@@ -166,13 +173,12 @@
   bool MutateDexFile(File* output_dex, const std::string& input_jar, const Mutator& mutator) {
     std::vector<std::unique_ptr<const DexFile>> dex_files;
     std::string error_msg;
-    const ArtDexFileLoader dex_file_loader;
-    CHECK(dex_file_loader.Open(input_jar.c_str(),
-                               input_jar.c_str(),
-                               /*verify*/ true,
+    ArtDexFileLoader dex_file_loader(input_jar);
+    CHECK(dex_file_loader.Open(/*verify*/ true,
                                /*verify_checksum*/ true,
                                &error_msg,
-                               &dex_files)) << error_msg;
+                               &dex_files))
+        << error_msg;
     EXPECT_EQ(dex_files.size(), 1u) << "Only one input dex is supported";
     const std::unique_ptr<const DexFile>& dex = dex_files[0];
     CHECK(dex->EnableWrite()) << "Failed to enable write";
diff --git a/libartbase/base/compiler_filter.cc b/libartbase/base/compiler_filter.cc
index a6d1c80..b4f924d 100644
--- a/libartbase/base/compiler_filter.cc
+++ b/libartbase/base/compiler_filter.cc
@@ -25,7 +25,6 @@
 bool CompilerFilter::IsAotCompilationEnabled(Filter filter) {
   switch (filter) {
     case CompilerFilter::kAssumeVerified:
-    case CompilerFilter::kExtract:
     case CompilerFilter::kVerify: return false;
 
     case CompilerFilter::kSpaceProfile:
@@ -41,7 +40,6 @@
 bool CompilerFilter::IsJniCompilationEnabled(Filter filter) {
   switch (filter) {
     case CompilerFilter::kAssumeVerified:
-    case CompilerFilter::kExtract:
     case CompilerFilter::kVerify: return false;
 
     case CompilerFilter::kSpaceProfile:
@@ -60,8 +58,7 @@
 
 bool CompilerFilter::IsVerificationEnabled(Filter filter) {
   switch (filter) {
-    case CompilerFilter::kAssumeVerified:
-    case CompilerFilter::kExtract: return false;
+    case CompilerFilter::kAssumeVerified: return false;
 
     case CompilerFilter::kVerify:
     case CompilerFilter::kSpaceProfile:
@@ -83,7 +80,6 @@
 bool CompilerFilter::DependsOnProfile(Filter filter) {
   switch (filter) {
     case CompilerFilter::kAssumeVerified:
-    case CompilerFilter::kExtract:
     case CompilerFilter::kVerify:
     case CompilerFilter::kSpace:
     case CompilerFilter::kSpeed:
@@ -99,7 +95,6 @@
 CompilerFilter::Filter CompilerFilter::GetNonProfileDependentFilterFrom(Filter filter) {
   switch (filter) {
     case CompilerFilter::kAssumeVerified:
-    case CompilerFilter::kExtract:
     case CompilerFilter::kVerify:
     case CompilerFilter::kSpace:
     case CompilerFilter::kSpeed:
@@ -123,7 +118,6 @@
   // code.
   switch (filter) {
     case CompilerFilter::kAssumeVerified:
-    case CompilerFilter::kExtract:
     case CompilerFilter::kVerify:
       return filter;
 
@@ -149,7 +143,6 @@
 std::string CompilerFilter::NameOfFilter(Filter filter) {
   switch (filter) {
     case CompilerFilter::kAssumeVerified: return "assume-verified";
-    case CompilerFilter::kExtract: return "extract";
     case CompilerFilter::kVerify: return "verify";
     case CompilerFilter::kSpaceProfile: return "space-profile";
     case CompilerFilter::kSpace: return "space";
@@ -178,8 +171,8 @@
     *filter = kVerify;
   } else if (strcmp(option, "verify-at-runtime") == 0) {
     LOG(WARNING) << "'verify-at-runtime' is an obsolete compiler filter name that will be "
-                 << "removed in future releases, please use 'extract' instead.";
-    *filter = kExtract;
+                 << "removed in future releases, please use 'verify' instead.";
+    *filter = kVerify;
   } else if (strcmp(option, "balanced") == 0) {
     LOG(WARNING) << "'balanced' is an obsolete compiler filter name that will be "
                  << "removed in future releases, please use 'speed' instead.";
@@ -188,14 +181,17 @@
     LOG(WARNING) << "'time' is an obsolete compiler filter name that will be "
                  << "removed in future releases, please use 'space' instead.";
     *filter = kSpace;
-  } else if (strcmp(option, "assume-verified") == 0) {
-    *filter = kAssumeVerified;
   } else if (strcmp(option, "extract") == 0) {
-    *filter = kExtract;
-  } else if (strcmp(option, "verify") == 0) {
+    LOG(WARNING) << "'extract' is an obsolete compiler filter name that will be "
+                 << "removed in future releases, please use 'verify' instead.";
     *filter = kVerify;
   } else if (strcmp(option, "quicken") == 0) {
-    // b/170086509 'quicken' becomes an alias to 'verify.
+    LOG(WARNING) << "'quicken' is an obsolete compiler filter name that will be "
+                 << "removed in future releases, please use 'verify' instead.";
+    *filter = kVerify;
+  } else if (strcmp(option, "assume-verified") == 0) {
+    *filter = kAssumeVerified;
+  } else if (strcmp(option, "verify") == 0) {
     *filter = kVerify;
   } else if (strcmp(option, "space") == 0) {
     *filter = kSpace;
@@ -216,7 +212,7 @@
 }
 
 const char* CompilerFilter::DescribeOptions() {
-  return "assume-verified|extract|verify|quicken|space{,-profile}|speed{,-profile}|"
+  return "assume-verified|verify|space{,-profile}|speed{,-profile}|"
           "everything{,-profile}";
 }
 
diff --git a/libartbase/base/compiler_filter.h b/libartbase/base/compiler_filter.h
index 4ca3c76..0a7b1bc 100644
--- a/libartbase/base/compiler_filter.h
+++ b/libartbase/base/compiler_filter.h
@@ -29,9 +29,10 @@
  public:
   // Note: Order here matters. Later filter choices are considered "as good
   // as" earlier filter choices.
+  // Keep supported filters in sync with `ArtShellCommand.printHelp` in
+  // art/libartservice/service/java/com/android/server/art/ArtShellCommand.java.
   enum Filter {
     kAssumeVerified,      // Skip verification but mark all classes as verified anyway.
-    kExtract,             // Delay verication to runtime, do not compile anything.
     kVerify,              // Only verify classes.
     kSpaceProfile,        // Maximize space savings based on profile.
     kSpace,               // Maximize space savings.
diff --git a/libartbase/base/compiler_filter_test.cc b/libartbase/base/compiler_filter_test.cc
index df7c8e7..3677afa 100644
--- a/libartbase/base/compiler_filter_test.cc
+++ b/libartbase/base/compiler_filter_test.cc
@@ -41,7 +41,6 @@
   CompilerFilter::Filter filter;
 
   TestCompilerFilterName(CompilerFilter::kAssumeVerified, "assume-verified");
-  TestCompilerFilterName(CompilerFilter::kExtract, "extract");
   TestCompilerFilterName(CompilerFilter::kVerify, "verify");
   TestCompilerFilterName(CompilerFilter::kSpaceProfile, "space-profile");
   TestCompilerFilterName(CompilerFilter::kSpace, "space");
@@ -55,7 +54,6 @@
 
 TEST(CompilerFilterTest, SafeModeFilter) {
   TestSafeModeFilter(CompilerFilter::kAssumeVerified, "assume-verified");
-  TestSafeModeFilter(CompilerFilter::kExtract, "extract");
   TestSafeModeFilter(CompilerFilter::kVerify, "verify");
   TestSafeModeFilter(CompilerFilter::kVerify, "space-profile");
   TestSafeModeFilter(CompilerFilter::kVerify, "space");
diff --git a/libartbase/base/data_hash.h b/libartbase/base/data_hash.h
index 3399899..ccb8736 100644
--- a/libartbase/base/data_hash.h
+++ b/libartbase/base/data_hash.h
@@ -44,7 +44,7 @@
       uint32_t hash = Murmur3Start();
 
       const size_t nblocks = length_in_bytes / 4;
-      typedef __attribute__((__aligned__(1))) uint32_t unaligned_uint32_t;
+      using unaligned_uint32_t __attribute__((__aligned__(1))) = uint32_t;
       const unaligned_uint32_t* blocks = reinterpret_cast<const unaligned_uint32_t*>(data);
       for (size_t i = 0; i != nblocks; ++i) {
         hash = Murmur3Update(hash, blocks[i]);
diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc
index 0b670e7..faf5d9d 100644
--- a/libartbase/base/file_utils.cc
+++ b/libartbase/base/file_utils.cc
@@ -40,6 +40,7 @@
 
 #include <memory>
 #include <sstream>
+#include <vector>
 
 #include "android-base/file.h"
 #include "android-base/logging.h"
@@ -50,6 +51,7 @@
 #include "base/os.h"
 #include "base/stl_util.h"
 #include "base/unix_file/fd_file.h"
+#include "base/utils.h"
 
 #if defined(__APPLE__)
 #include <crt_externs.h>
@@ -62,6 +64,10 @@
 #include <linux/unistd.h>
 #endif
 
+#ifdef ART_TARGET_ANDROID
+#include "android-modules-utils/sdk_level.h"
+#endif
+
 namespace art {
 
 using android::base::StringPrintf;
@@ -69,15 +75,18 @@
 static constexpr const char* kClassesDex = "classes.dex";
 static constexpr const char* kAndroidRootEnvVar = "ANDROID_ROOT";
 static constexpr const char* kAndroidRootDefaultPath = "/system";
-static constexpr const char* kAndroidSystemExtRootEnvVar = "ANDROID_SYSTEM_EXT";
+static constexpr const char* kAndroidSystemExtRootEnvVar = "SYSTEM_EXT_ROOT";
 static constexpr const char* kAndroidSystemExtRootDefaultPath = "/system_ext";
 static constexpr const char* kAndroidDataEnvVar = "ANDROID_DATA";
 static constexpr const char* kAndroidDataDefaultPath = "/data";
+static constexpr const char* kAndroidExpandEnvVar = "ANDROID_EXPAND";
+static constexpr const char* kAndroidExpandDefaultPath = "/mnt/expand";
 static constexpr const char* kAndroidArtRootEnvVar = "ANDROID_ART_ROOT";
 static constexpr const char* kAndroidConscryptRootEnvVar = "ANDROID_CONSCRYPT_ROOT";
 static constexpr const char* kAndroidI18nRootEnvVar = "ANDROID_I18N_ROOT";
 static constexpr const char* kApexDefaultPath = "/apex/";
 static constexpr const char* kArtApexDataEnvVar = "ART_APEX_DATA";
+static constexpr const char* kBootImageStem = "boot";
 
 // Get the "root" directory containing the "lib" directory where this instance
 // of the libartbase library (which contains `GetRootContainingLibartbase`) is
@@ -106,56 +115,6 @@
   return "";
 }
 
-std::string GetAndroidRootSafe(std::string* error_msg) {
-#ifdef _WIN32
-  UNUSED(kAndroidRootEnvVar, kAndroidRootDefaultPath, GetRootContainingLibartbase);
-  *error_msg = "GetAndroidRootSafe unsupported for Windows.";
-  return "";
-#else
-  // Prefer ANDROID_ROOT if it's set.
-  const char* android_root_from_env = getenv(kAndroidRootEnvVar);
-  if (android_root_from_env != nullptr) {
-    if (!OS::DirectoryExists(android_root_from_env)) {
-      *error_msg =
-          StringPrintf("Failed to find %s directory %s", kAndroidRootEnvVar, android_root_from_env);
-      return "";
-    }
-    return android_root_from_env;
-  }
-
-  // On host, libartbase is currently installed in "$ANDROID_ROOT/lib"
-  // (e.g. something like "$ANDROID_BUILD_TOP/out/host/linux-x86/lib". Use this
-  // information to infer the location of the Android Root (on host only).
-  //
-  // Note that this could change in the future, if we decided to install ART
-  // artifacts in a different location, e.g. within an "ART APEX" directory.
-  if (!kIsTargetBuild) {
-    std::string root_containing_libartbase = GetRootContainingLibartbase();
-    if (!root_containing_libartbase.empty()) {
-      return root_containing_libartbase;
-    }
-  }
-
-  // Try the default path.
-  if (!OS::DirectoryExists(kAndroidRootDefaultPath)) {
-    *error_msg =
-        StringPrintf("Failed to find default Android Root directory %s", kAndroidRootDefaultPath);
-    return "";
-  }
-  return kAndroidRootDefaultPath;
-#endif
-}
-
-std::string GetAndroidRoot() {
-  std::string error_msg;
-  std::string ret = GetAndroidRootSafe(&error_msg);
-  if (ret.empty()) {
-    LOG(FATAL) << error_msg;
-    UNREACHABLE();
-  }
-  return ret;
-}
-
 static const char* GetAndroidDirSafe(const char* env_var,
                                      const char* default_dir,
                                      bool must_exist,
@@ -189,6 +148,62 @@
   }
 }
 
+std::string GetAndroidRootSafe(std::string* error_msg) {
+#ifdef _WIN32
+  UNUSED(kAndroidRootEnvVar, kAndroidRootDefaultPath, GetRootContainingLibartbase);
+  *error_msg = "GetAndroidRootSafe unsupported for Windows.";
+  return "";
+#else
+  std::string local_error_msg;
+  const char* dir = GetAndroidDirSafe(kAndroidRootEnvVar, kAndroidRootDefaultPath,
+      /*must_exist=*/ true, &local_error_msg);
+  if (dir == nullptr) {
+    // On host, libartbase is currently installed in "$ANDROID_ROOT/lib"
+    // (e.g. something like "$ANDROID_BUILD_TOP/out/host/linux-x86/lib". Use this
+    // information to infer the location of the Android Root (on host only).
+    //
+    // Note that this could change in the future, if we decided to install ART
+    // artifacts in a different location, e.g. within an "ART APEX" directory.
+    if (!kIsTargetBuild) {
+      std::string root_containing_libartbase = GetRootContainingLibartbase();
+      if (!root_containing_libartbase.empty()) {
+        return root_containing_libartbase;
+      }
+    }
+    *error_msg = std::move(local_error_msg);
+    return "";
+  }
+
+  return dir;
+#endif
+}
+
+std::string GetAndroidRoot() {
+  std::string error_msg;
+  std::string ret = GetAndroidRootSafe(&error_msg);
+  CHECK(!ret.empty()) << error_msg;
+  return ret;
+}
+
+std::string GetSystemExtRootSafe(std::string* error_msg) {
+#ifdef _WIN32
+  UNUSED(kAndroidSystemExtRootEnvVar, kAndroidSystemExtRootDefaultPath);
+  *error_msg = "GetSystemExtRootSafe unsupported for Windows.";
+  return "";
+#else
+  const char* dir = GetAndroidDirSafe(kAndroidSystemExtRootEnvVar, kAndroidSystemExtRootDefaultPath,
+      /*must_exist=*/ true, error_msg);
+  return dir ? dir : "";
+#endif
+}
+
+std::string GetSystemExtRoot() {
+  std::string error_msg;
+  std::string ret = GetSystemExtRootSafe(&error_msg);
+  CHECK(!ret.empty()) << error_msg;
+  return ret;
+}
+
 static std::string GetArtRootSafe(bool must_exist, /*out*/ std::string* error_msg) {
 #ifdef _WIN32
   UNUSED(kAndroidArtRootEnvVar, kAndroidArtApexDefaultPath, GetRootContainingLibartbase);
@@ -276,6 +291,18 @@
 
 std::string GetAndroidData() { return GetAndroidDir(kAndroidDataEnvVar, kAndroidDataDefaultPath); }
 
+std::string GetAndroidExpandSafe(std::string* error_msg) {
+  const char* android_dir = GetAndroidDirSafe(kAndroidExpandEnvVar,
+                                              kAndroidExpandDefaultPath,
+                                              /*must_exist=*/true,
+                                              error_msg);
+  return (android_dir != nullptr) ? android_dir : "";
+}
+
+std::string GetAndroidExpand() {
+  return GetAndroidDir(kAndroidExpandEnvVar, kAndroidExpandDefaultPath);
+}
+
 std::string GetArtApexData() {
   return GetAndroidDir(kArtApexDataEnvVar, kArtApexDataDefaultPath, /*must_exist=*/false);
 }
@@ -292,10 +319,102 @@
   return GetPrebuiltPrimaryBootImageDir(android_root);
 }
 
-std::string GetDefaultBootImageLocation(const std::string& android_root,
-                                        bool deny_art_apex_data_files) {
+std::string GetFirstMainlineFrameworkLibraryFilename(std::string* error_msg) {
+  const char* env_bcp = getenv("BOOTCLASSPATH");
+  const char* env_dex2oat_bcp = getenv("DEX2OATBOOTCLASSPATH");
+  if (env_bcp == nullptr || env_dex2oat_bcp == nullptr) {
+    *error_msg = "BOOTCLASSPATH and DEX2OATBOOTCLASSPATH must not be empty";
+    return "";
+  }
+
+  // DEX2OATBOOTCLASSPATH contains core libraries and framework libraries. We used to only compile
+  // those libraries. Now we compile mainline framework libraries as well, and we have repurposed
+  // DEX2OATBOOTCLASSPATH to indicate the separation between mainline framework libraries and other
+  // libraries.
+  std::string_view mainline_bcp(env_bcp);
+  if (!android::base::ConsumePrefix(&mainline_bcp, env_dex2oat_bcp)) {
+    *error_msg = "DEX2OATBOOTCLASSPATH must be a prefix of BOOTCLASSPATH";
+    return "";
+  }
+
+  std::vector<std::string_view> mainline_bcp_jars;
+  Split(mainline_bcp, ':', &mainline_bcp_jars);
+  if (mainline_bcp_jars.empty()) {
+    *error_msg = "No mainline framework library found";
+    return "";
+  }
+
+  return std::string(mainline_bcp_jars[0]);
+}
+
+static std::string GetFirstMainlineFrameworkLibraryName(std::string* error_msg) {
+  std::string filename = GetFirstMainlineFrameworkLibraryFilename(error_msg);
+  if (filename.empty()) {
+    return "";
+  }
+
+  std::string jar_name = android::base::Basename(filename);
+
+  std::string_view library_name(jar_name);
+  if (!android::base::ConsumeSuffix(&library_name, ".jar")) {
+    *error_msg = "Invalid mainline framework jar: " + jar_name;
+    return "";
+  }
+
+  return std::string(library_name);
+}
+
+// Returns true when no error occurs, even if the extension doesn't exist.
+static bool MaybeAppendBootImageMainlineExtension(const std::string& android_root,
+                                                  bool deny_system_files,
+                                                  bool deny_art_apex_data_files,
+                                                  /*inout*/ std::string* location,
+                                                  /*out*/ std::string* error_msg) {
+  if (!kIsTargetAndroid) {
+    return true;
+  }
+  // Due to how the runtime determines the mapping between boot images and bootclasspath jars, the
+  // name of the boot image extension must be in the format of
+  // `<primary-boot-image-stem>-<first-library-name>.art`.
+  std::string library_name = GetFirstMainlineFrameworkLibraryName(error_msg);
+  if (library_name.empty()) {
+    return false;
+  }
+
+  if (!deny_art_apex_data_files) {
+    std::string mainline_extension_location =
+        StringPrintf("%s/%s-%s.art",
+                     GetApexDataDalvikCacheDirectory(InstructionSet::kNone).c_str(),
+                     kBootImageStem,
+                     library_name.c_str());
+    std::string mainline_extension_path =
+        GetSystemImageFilename(mainline_extension_location.c_str(), kRuntimeISA);
+    if (OS::FileExists(mainline_extension_path.c_str(), /*check_file_type=*/true)) {
+      *location += ":" + mainline_extension_location;
+      return true;
+    }
+  }
+
+  if (!deny_system_files) {
+    std::string mainline_extension_location = StringPrintf(
+        "%s/framework/%s-%s.art", android_root.c_str(), kBootImageStem, library_name.c_str());
+    std::string mainline_extension_path =
+        GetSystemImageFilename(mainline_extension_location.c_str(), kRuntimeISA);
+    // It is expected that the file doesn't exist when the ART module is preloaded on an old source
+    // tree that doesn't dexpreopt mainline BCP jars, so it shouldn't be considered as an error.
+    if (OS::FileExists(mainline_extension_path.c_str(), /*check_file_type=*/true)) {
+      *location += ":" + mainline_extension_location;
+      return true;
+    }
+  }
+
+  return true;
+}
+
+std::string GetDefaultBootImageLocationSafe(const std::string& android_root,
+                                            bool deny_art_apex_data_files,
+                                            std::string* error_msg) {
   constexpr static const char* kEtcBootImageProf = "etc/boot-image.prof";
-  constexpr static const char* kBootImageStem = "boot";
   constexpr static const char* kMinimalBootImageStem = "boot_minimal";
 
   // If an update for the ART module has been been installed, a single boot image for the entire
@@ -305,14 +424,27 @@
         GetApexDataDalvikCacheDirectory(InstructionSet::kNone) + "/" + kBootImageStem + ".art";
     const std::string boot_image_filename = GetSystemImageFilename(boot_image.c_str(), kRuntimeISA);
     if (OS::FileExists(boot_image_filename.c_str(), /*check_file_type=*/true)) {
-      // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot.art!/apex/com.android.art
-      // /etc/boot-image.prof!/system/etc/boot-image.prof".
-      return StringPrintf("%s!%s/%s!%s/%s",
-                          boot_image.c_str(),
-                          kAndroidArtApexDefaultPath,
-                          kEtcBootImageProf,
-                          android_root.c_str(),
-                          kEtcBootImageProf);
+      // Boot image consists of two parts:
+      //  - the primary boot image (contains the Core Libraries and framework libraries)
+      //  - the boot image mainline extension (contains mainline framework libraries)
+      // Typically
+      // "/data/misc/apexdata/com.android.art/dalvik-cache/boot.art!/apex/com.android.art
+      // /etc/boot-image.prof!/system/etc/boot-image.prof:
+      // /data/misc/apexdata/com.android.art/dalvik-cache/boot-framework-adservices.art".
+      std::string location = StringPrintf("%s!%s/%s!%s/%s",
+                                          boot_image.c_str(),
+                                          kAndroidArtApexDefaultPath,
+                                          kEtcBootImageProf,
+                                          android_root.c_str(),
+                                          kEtcBootImageProf);
+      if (!MaybeAppendBootImageMainlineExtension(android_root,
+                                                 /*deny_system_files=*/true,
+                                                 deny_art_apex_data_files,
+                                                 &location,
+                                                 error_msg)) {
+        return "";
+      }
+      return location;
     } else if (errno == EACCES) {
       // Additional warning for potential SELinux misconfiguration.
       PLOG(ERROR) << "Default boot image check failed, could not stat: " << boot_image_filename;
@@ -341,28 +473,60 @@
       PLOG(ERROR) << "Minimal boot image check failed, could not stat: " << boot_image_filename;
     }
   }
-  // Boot image consists of two parts:
-  //  - the primary boot image (contains the Core Libraries)
-  //  - the boot image extensions (contains framework libraries)
-  // Typically "/apex/com.android.art/javalib/boot.art!/apex/com.android.art/etc/boot-image.prof:
-  // /system/framework/boot-framework.art!/system/etc/boot-image.prof".
-  return StringPrintf("%s/%s.art!%s/%s:%s/framework/%s-framework.art!%s/%s",
-                      GetPrebuiltPrimaryBootImageDir(android_root).c_str(),
-                      kBootImageStem,
-                      kAndroidArtApexDefaultPath,
-                      kEtcBootImageProf,
-                      android_root.c_str(),
-                      kBootImageStem,
-                      android_root.c_str(),
-                      kEtcBootImageProf);
-}
 
-std::string GetDefaultBootImageLocation(std::string* error_msg) {
-  std::string android_root = GetAndroidRootSafe(error_msg);
-  if (android_root.empty()) {
+  // Boot image consists of two parts:
+  //  - the primary boot image (contains the Core Libraries and framework libraries)
+  //  - the boot image mainline extension (contains mainline framework libraries)
+  // Typically "/system/framework/boot.art
+  // !/apex/com.android.art/etc/boot-image.prof!/system/etc/boot-image.prof:
+  // /system/framework/boot-framework-adservices.art".
+
+  std::string location = StringPrintf("%s/%s.art!%s/%s!%s/%s",
+                                      GetPrebuiltPrimaryBootImageDir(android_root).c_str(),
+                                      kBootImageStem,
+                                      kAndroidArtApexDefaultPath,
+                                      kEtcBootImageProf,
+                                      android_root.c_str(),
+                                      kEtcBootImageProf);
+
+#ifdef ART_TARGET_ANDROID
+  // Prior to U, there was a framework extension.
+  if (!android::modules::sdklevel::IsAtLeastU()) {
+    location = StringPrintf("%s/%s.art!%s/%s:%s/framework/%s-framework.art!%s/%s",
+                            GetPrebuiltPrimaryBootImageDir(android_root).c_str(),
+                            kBootImageStem,
+                            kAndroidArtApexDefaultPath,
+                            kEtcBootImageProf,
+                            android_root.c_str(),
+                            kBootImageStem,
+                            android_root.c_str(),
+                            kEtcBootImageProf);
+  }
+#endif
+
+  if (!MaybeAppendBootImageMainlineExtension(android_root,
+                                             /*deny_system_files=*/false,
+                                             deny_art_apex_data_files,
+                                             &location,
+                                             error_msg)) {
     return "";
   }
-  return GetDefaultBootImageLocation(android_root, /*deny_art_apex_data_files=*/false);
+  return location;
+}
+
+std::string GetDefaultBootImageLocation(const std::string& android_root,
+                                        bool deny_art_apex_data_files) {
+  std::string error_msg;
+  std::string location =
+      GetDefaultBootImageLocationSafe(android_root, deny_art_apex_data_files, &error_msg);
+  CHECK(!location.empty()) << error_msg;
+  return location;
+}
+
+std::string GetJitZygoteBootImageLocation() {
+  // Intentionally use a non-existing location so that the runtime will fail to find the boot image
+  // and JIT bootclasspath with the given profiles.
+  return "/nonx/boot.art!/apex/com.android.art/etc/boot-image.prof!/system/etc/boot-image.prof";
 }
 
 static /*constinit*/ std::string_view dalvik_cache_sub_dir = "dalvik-cache";
@@ -675,12 +839,31 @@
   LOG(FATAL) << "LocationIsOnSystem is unsupported on Windows.";
   return false;
 #else
-  return android::base::StartsWith(location, GetAndroidRoot().c_str());
+  return android::base::StartsWith(location, GetAndroidRoot());
+#endif
+}
+
+bool LocationIsOnSystemExt(const std::string& location) {
+#ifdef _WIN32
+  UNUSED(location);
+  LOG(FATAL) << "LocationIsOnSystemExt is unsupported on Windows.";
+  return false;
+#else
+  return IsLocationOn(location,
+                      kAndroidSystemExtRootEnvVar,
+                      kAndroidSystemExtRootDefaultPath) ||
+         // When the 'system_ext' partition is not present, builds will create
+         // '/system/system_ext' instead.
+         IsLocationOn(location,
+                      kAndroidRootEnvVar,
+                      kAndroidRootDefaultPath,
+                      /* subdir= */ "system_ext/");
 #endif
 }
 
 bool LocationIsTrusted(const std::string& location, bool trust_art_apex_data_files) {
-  if (LocationIsOnSystem(location) || LocationIsOnArtModule(location)) {
+  if (LocationIsOnSystem(location) || LocationIsOnSystemExt(location)
+        || LocationIsOnArtModule(location)) {
     return true;
   }
   return LocationIsOnArtApexData(location) & trust_art_apex_data_files;
@@ -704,7 +887,7 @@
 #if defined(__linux__)
   return fcntl(fd, F_DUPFD_CLOEXEC, 0);
 #else
-  return dup(fd);
+  return dup(fd); // NOLINT
 #endif
 }
 
diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h
index c1c45bc..cff6a92 100644
--- a/libartbase/base/file_utils.h
+++ b/libartbase/base/file_utils.h
@@ -47,6 +47,11 @@
 // Find $ANDROID_ROOT, /system, or return an empty string.
 std::string GetAndroidRootSafe(/*out*/ std::string* error_msg);
 
+// Find $SYSTEM_EXT_ROOT, /system_ext, or abort.
+std::string GetSystemExtRoot();
+// Find $SYSTEM_EXT_ROOT, /system_ext, or return an empty string.
+std::string GetSystemExtRootSafe(/*out*/ std::string* error_msg);
+
 // These methods return the ART Root, which is the location of the (activated)
 // ART APEX module. On target, this is normally "/apex/com.android.art". On
 // host, this is usually a subdirectory of the Android Root, e.g.
@@ -66,6 +71,11 @@
 // Find $ANDROID_DATA, /data, or return an empty string.
 std::string GetAndroidDataSafe(/*out*/ std::string* error_msg);
 
+// Find $ANDROID_EXPAND, /mnt/expand, or abort.
+std::string GetAndroidExpand();
+// Find $ANDROID_EXPAND, /mnt/expand, or return an empty string.
+std::string GetAndroidExpandSafe(/*out*/ std::string* error_msg);
+
 // Find $ART_APEX_DATA, /data/misc/apexdata/com.android.art, or abort.
 std::string GetArtApexData();
 
@@ -73,14 +83,24 @@
 // generated at build time).
 std::string GetPrebuiltPrimaryBootImageDir();
 
-// Returns the default boot image location (ANDROID_ROOT/framework/boot.art).
-// Returns an empty string if ANDROID_ROOT is not set.
-std::string GetDefaultBootImageLocation(std::string* error_msg);
+// Returns the filename of the first mainline framework library.
+std::string GetFirstMainlineFrameworkLibraryFilename(std::string* error_msg);
 
 // Returns the default boot image location, based on the passed `android_root`.
+// Returns an empty string if an error occurs.
+// The default boot image location can only be used with the default bootclasspath (the value of the
+// BOOTCLASSPATH environment variable).
+std::string GetDefaultBootImageLocationSafe(const std::string& android_root,
+                                            bool deny_art_apex_data_files,
+                                            std::string* error_msg);
+
+// Same as above, but fails if an error occurs.
 std::string GetDefaultBootImageLocation(const std::string& android_root,
                                         bool deny_art_apex_data_files);
 
+// Returns the boot image location that forces the runtime to run in JIT Zygote mode.
+std::string GetJitZygoteBootImageLocation();
+
 // Allows the name to be used for the dalvik cache directory (normally "dalvik-cache") to be
 // overridden with a new value.
 void OverrideDalvikCacheSubDirectory(std::string sub_dir);
@@ -160,6 +180,9 @@
 // Return whether the location is on system (i.e. android root).
 bool LocationIsOnSystem(const std::string& location);
 
+// Return whether the location is on system_ext
+bool LocationIsOnSystemExt(const std::string& location);
+
 // Return whether the location is on system/framework (i.e. $ANDROID_ROOT/framework).
 bool LocationIsOnSystemFramework(std::string_view location);
 
diff --git a/libartbase/base/file_utils_test.cc b/libartbase/base/file_utils_test.cc
index dff4478..e660f35 100644
--- a/libartbase/base/file_utils_test.cc
+++ b/libartbase/base/file_utils_test.cc
@@ -36,9 +36,8 @@
     OverrideDalvikCacheSubDirectory(override);
   }
 
-  ~ScopedOverrideDalvikCacheSubDirectory() {
-    OverrideDalvikCacheSubDirectory("dalvik-cache");
-  }
+  ~ScopedOverrideDalvikCacheSubDirectory() { OverrideDalvikCacheSubDirectory("dalvik-cache"); }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ScopedOverrideDalvikCacheSubDirectory);
 };
@@ -104,7 +103,7 @@
     // broken. On non-bionic. On bionic we can be running with a different libartbase that lives
     // outside of ANDROID_ROOT.
     UniqueCPtr<char> real_root3(realpath(android_root3.c_str(), nullptr));
-#if !defined(__BIONIC__ ) || defined(__ANDROID__)
+#if !defined(__BIONIC__) || defined(__ANDROID__)
     UniqueCPtr<char> real_root(realpath(android_root.c_str(), nullptr));
     EXPECT_STREQ(real_root.get(), real_root3.get()) << error_msg;
 #else
@@ -158,7 +157,7 @@
       // the test setup is broken. On non-bionic. On bionic we can be running
       // with a different libartbase that lives outside of ANDROID_ART_ROOT.
       UniqueCPtr<char> real_root3(realpath(android_art_root3.c_str(), nullptr));
-#if !defined(__BIONIC__ ) || defined(__ANDROID__)
+#if !defined(__BIONIC__) || defined(__ANDROID__)
       UniqueCPtr<char> real_root(realpath(android_art_root.c_str(), nullptr));
       EXPECT_STREQ(real_root.get(), real_root3.get()) << error_msg;
 #else
@@ -195,11 +194,10 @@
             GetApexDataOatFilename("/product/javalib/beep.jar", InstructionSet::kArm));
 
   const std::string art_apex_jar = std::string {kAndroidArtApexDefaultPath} + "/javalib/some.jar";
-  EXPECT_EQ(std::string {}, GetApexDataOatFilename(art_apex_jar.c_str(), InstructionSet::kArm));
+  EXPECT_EQ(std::string{}, GetApexDataOatFilename(art_apex_jar, InstructionSet::kArm));
 
-  const std::string i18n_jar =
-      std::string {kAndroidI18nApexDefaultPath} + "/javalib/core-icu4j.jar";
-  EXPECT_EQ(std::string {}, GetApexDataOatFilename(i18n_jar, InstructionSet::kArm));
+  const std::string i18n_jar = std::string{kAndroidI18nApexDefaultPath} + "/javalib/core-icu4j.jar";
+  EXPECT_EQ(std::string{}, GetApexDataOatFilename(i18n_jar, InstructionSet::kArm));
 
   const std::string system_jar_apexdata_oat = GetArtApexData() + "/dalvik-cache/x86/boot-lace.oat";
   EXPECT_EQ(system_jar_apexdata_oat,
@@ -216,13 +214,12 @@
   const std::string art_apex_jar = std::string {kAndroidArtApexDefaultPath} + "/javalib/some.jar";
   EXPECT_EQ(
       GetArtApexData() + "/dalvik-cache/arm/[email protected]@[email protected]@classes.odex",
-      GetApexDataOdexFilename(art_apex_jar.c_str(), InstructionSet::kArm));
+      GetApexDataOdexFilename(art_apex_jar, InstructionSet::kArm));
 
-  const std::string i18n_jar =
-      std::string {kAndroidI18nApexDefaultPath} + "/javalib/core-icu4j.jar";
+  const std::string i18n_jar = std::string{kAndroidI18nApexDefaultPath} + "/javalib/core-icu4j.jar";
   EXPECT_EQ(GetArtApexData() +
                 "/dalvik-cache/arm/[email protected]@[email protected]@classes.odex",
-            GetApexDataOdexFilename(i18n_jar.c_str(), InstructionSet::kArm));
+            GetApexDataOdexFilename(i18n_jar, InstructionSet::kArm));
 
   const std::string system_jar_apexdata_odex =
       GetArtApexData() + "/dalvik-cache/x86/system@[email protected]@classes.odex";
@@ -234,20 +231,20 @@
   ScopedUnsetEnvironmentVariable android_root("ANDROID_ROOT");
   ScopedUnsetEnvironmentVariable art_apex_data("ART_APEX_DATA");
 
-  EXPECT_EQ(std::string {},
-            GetApexDataBootImage(std::string {kAndroidI18nApexDefaultPath} + "/javalib/bar.jar"));
+  EXPECT_EQ(std::string{},
+            GetApexDataBootImage(std::string{kAndroidI18nApexDefaultPath} + "/javalib/bar.jar"));
 
   // Check image location has the prefix "boot-" in front of the basename of dex location and
   // that image suffix is .art.
   const std::string system_jar = "/system/framework/disk.jar";
-  const std::string boot_image = GetApexDataBootImage(system_jar.c_str());
+  const std::string boot_image = GetApexDataBootImage(system_jar);
   EXPECT_EQ(GetArtApexData() + "/dalvik-cache/boot-disk.art", boot_image);
 
   // Check the image filename corresponds to the oat file for the same system jar.
   const InstructionSet isa = InstructionSet::kArm64;
   const std::string boot_image_filename = GetSystemImageFilename(boot_image.c_str(), isa);
   const std::string accompanying_oat_file = ReplaceFileExtension(boot_image_filename, "oat");
-  EXPECT_EQ(accompanying_oat_file, GetApexDataOatFilename(system_jar.c_str(), isa));
+  EXPECT_EQ(accompanying_oat_file, GetApexDataOatFilename(system_jar, isa));
 }
 
 TEST_F(FileUtilsTest, GetApexDataImage) {
@@ -308,7 +305,7 @@
             GetApexDataOdexFilename("/data/some/code.dex", InstructionSet::kArm));
 
   const std::string system_jar = "/system/framework/disk.jar";
-  const std::string boot_image = GetApexDataBootImage(system_jar.c_str());
+  const std::string boot_image = GetApexDataBootImage(system_jar);
   EXPECT_EQ(GetArtApexData() + "/overridden-cache/boot-disk.art", boot_image);
 
   EXPECT_EQ(
@@ -327,7 +324,7 @@
   const std::string apex_jar = std::string {kAndroidArtApexDefaultPath} + "/javalib/some.jar";
   EXPECT_EQ(
       GetAndroidRoot() + "/framework/oat/arm/[email protected]@[email protected]@classes.odex",
-      GetSystemOdexFilenameForApex(apex_jar.c_str(), InstructionSet::kArm));
+      GetSystemOdexFilenameForApex(apex_jar, InstructionSet::kArm));
 }
 
 TEST_F(FileUtilsTest, ApexNameFromLocation) {
diff --git a/libartbase/base/flags.h b/libartbase/base/flags.h
index d1e1ca6..4734a60 100644
--- a/libartbase/base/flags.h
+++ b/libartbase/base/flags.h
@@ -236,8 +236,8 @@
 //
 //     Flag<int> WriteMetricsToLog{"my-feature-test.flag", 42, FlagType::kDeviceConfig};
 //
-// This creates a boolean flag that can be read through gFlags.WriteMetricsToLog(). The default
-// value is false. Note that the default value can be left unspecified, in which the value of the
+// This creates an integer flag that can be read through gFlags.WriteMetricsToLog(). The default
+// value is 42. Note that the default value can be left unspecified, in which case the value of the
 // type's default constructor will be used.
 //
 // The flag can be set through the following generated means:
@@ -263,22 +263,23 @@
 
   // The reporting spec for regular apps. An example of valid value is "S,1,2,4,*".
   // See metrics::ReportingPeriodSpec for complete docs.
-  Flag<std::string> MetricsReportingSpec{"metrics.reporting-spec", "", FlagType::kDeviceConfig};
+  Flag<std::string> MetricsReportingSpec{
+      "metrics.reporting-spec", "1,5,30,60,600", FlagType::kDeviceConfig};
 
   // The reporting spec for the system server. See MetricsReportingSpec as well.
-  Flag<std::string> MetricsReportingSpecSystemServer{"metrics.reporting-spec-server", "",
-      FlagType::kDeviceConfig};
+  Flag<std::string> MetricsReportingSpecSystemServer{
+      "metrics.reporting-spec-server", "1,10,60,3600,*", FlagType::kDeviceConfig};
 
   // The mods that should report metrics. Together with MetricsReportingNumMods, they
   // dictate what percentage of the runtime execution will report metrics.
   // If the `session_id (a random number) % MetricsReportingNumMods < MetricsReportingMods`
   // then the runtime session will report metrics.
   //
-  // By default, the mods are 0, which means the reporting is disabled.
-  Flag<uint32_t> MetricsReportingMods{"metrics.reporting-mods", 0,
-      FlagType::kDeviceConfig};
-  Flag<uint32_t> MetricsReportingModsServer{"metrics.reporting-mods-server", 0,
-      FlagType::kDeviceConfig};
+  // By default, the mods are 2, which means that 2 out of #{reporting-num-mods} of Android sessions
+  // will be reported (with the default values this is 2/100 = 2%).
+  Flag<uint32_t> MetricsReportingMods{"metrics.reporting-mods", 2, FlagType::kDeviceConfig};
+  Flag<uint32_t> MetricsReportingModsServer{
+      "metrics.reporting-mods-server", 2, FlagType::kDeviceConfig};
 
   // See MetricsReportingMods docs.
   //
@@ -293,7 +294,7 @@
   // Whether or not we should write metrics to statsd.
   // Note that the actual write is still controlled by
   // MetricsReportingMods and MetricsReportingNumMods.
-  Flag<bool> MetricsWriteToStatsd{ "metrics.write-to-statsd", false, FlagType::kDeviceConfig};
+  Flag<bool> MetricsWriteToStatsd{"metrics.write-to-statsd", true, FlagType::kDeviceConfig};
 
   // Whether or not we should write metrics to logcat.
   // Note that the actual write is still controlled by
@@ -304,6 +305,12 @@
   // Note that the actual write is still controlled by
   // MetricsReportingMods and MetricsReportingNumMods.
   Flag<std::string> MetricsWriteToFile{"metrics.write-to-file", "", FlagType::kCmdlineOnly};
+
+  // The output format for metrics. This is only used
+  // when writing metrics to a file; metrics written
+  // to logcat will be in human-readable text format.
+  // Supported values are "text" and "xml".
+  Flag<std::string> MetricsFormat{"metrics.format", "text", FlagType::kCmdlineOnly};
 };
 
 // This is the actual instance of all the flags.
diff --git a/libartbase/base/globals.h b/libartbase/base/globals.h
index 8d37b8a..4103154 100644
--- a/libartbase/base/globals.h
+++ b/libartbase/base/globals.h
@@ -38,6 +38,17 @@
 // compile-time constant so the compiler can generate better code.
 static constexpr size_t kPageSize = 4096;
 
+// TODO: Kernels for arm and x86 in both, 32-bit and 64-bit modes use 512 entries per page-table
+// page. Find a way to confirm that in userspace.
+// Address range covered by 1 Page Middle Directory (PMD) entry in the page table
+static constexpr size_t kPMDSize = (kPageSize / sizeof(uint64_t)) * kPageSize;
+// Address range covered by 1 Page Upper Directory (PUD) entry in the page table
+static constexpr size_t kPUDSize = (kPageSize / sizeof(uint64_t)) * kPMDSize;
+// Returns the ideal alignment corresponding to page-table levels for the
+// given size.
+static constexpr size_t BestPageTableAlignment(size_t size) {
+  return size < kPUDSize ? kPMDSize : kPUDSize;
+}
 // Clion, clang analyzer, etc can falsely believe that "if (kIsDebugBuild)" always
 // returns the same value. By wrapping into a call to another constexpr function, we force it
 // to realize that is not actually always evaluating to the same value.
@@ -107,6 +118,12 @@
 static constexpr bool kHostStaticBuildEnabled = false;
 #endif
 
+// System property for phenotype flag to test disabling compact dex and in
+// particular dexlayout.
+// TODO(b/256664509): Clean this up.
+static constexpr char kPhDisableCompactDex[] =
+    "persist.device_config.runtime_native_boot.disable_compact_dex";
+
 }  // namespace art
 
 #endif  // ART_LIBARTBASE_BASE_GLOBALS_H_
diff --git a/libartbase/base/hash_set.h b/libartbase/base/hash_set.h
index c4af1b6..3f3c8f2 100644
--- a/libartbase/base/hash_set.h
+++ b/libartbase/base/hash_set.h
@@ -139,6 +139,17 @@
   }
 };
 
+template <>
+class DefaultEmptyFn<std::string> {
+ public:
+  void MakeEmpty(std::string& item) const {
+    item = std::string();
+  }
+  bool IsEmpty(const std::string& item) const {
+    return item.empty();
+  }
+};
+
 template <class T>
 using DefaultHashFn = std::conditional_t<std::is_same_v<T, std::string>, DataHash, std::hash<T>>;
 
diff --git a/libartbase/base/hiddenapi_flags.cc b/libartbase/base/hiddenapi_flags.cc
deleted file mode 100644
index ea57cb7..0000000
--- a/libartbase/base/hiddenapi_flags.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-#include "hiddenapi_flags.h"
-
-namespace art {
-namespace hiddenapi {
-
-constexpr const char* ApiList::kValueNames[ApiList::kValueCount];
-constexpr const char* ApiList::kDomainApiNames[ApiList::kDomainApiCount];
-constexpr SdkVersion ApiList::kMaxSdkVersions[ApiList::kValueCount];
-
-}  // namespace hiddenapi
-}  // namespace art
diff --git a/libartbase/base/logging.h b/libartbase/base/logging.h
index 7a421a4..ef03894 100644
--- a/libartbase/base/logging.h
+++ b/libartbase/base/logging.h
@@ -56,7 +56,7 @@
   bool verifier;
   bool verifier_debug;   // Only works in debug builds.
   bool image;
-  bool systrace_lock_logging;  // Enabled with "-verbose:sys-locks".
+  bool systrace_lock_logging;  // Enabled with "-verbose:systrace-locks".
   bool agents;
   bool dex;  // Some dex access output etc.
   bool plugin;  // Used by some plugins.
diff --git a/libartbase/base/macros.h b/libartbase/base/macros.h
index eec73cb..13e87d7 100644
--- a/libartbase/base/macros.h
+++ b/libartbase/base/macros.h
@@ -75,6 +75,8 @@
 #define FLATTEN  __attribute__ ((flatten))
 #endif
 
+#define NO_STACK_PROTECTOR __attribute__ ((no_stack_protector))
+
 // clang doesn't like attributes on lambda functions. It would be nice to say:
 //   #define ALWAYS_INLINE_LAMBDA ALWAYS_INLINE
 #define ALWAYS_INLINE_LAMBDA
diff --git a/libartbase/base/mem_map.cc b/libartbase/base/mem_map.cc
index aa07f1c..04c11ed 100644
--- a/libartbase/base/mem_map.cc
+++ b/libartbase/base/mem_map.cc
@@ -352,10 +352,7 @@
 
   if (actual == MAP_FAILED) {
     if (error_msg != nullptr) {
-      if (kIsDebugBuild || VLOG_IS_ON(oat)) {
-        PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
-      }
-
+      PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
       *error_msg = StringPrintf("Failed anonymous mmap(%p, %zd, 0x%x, 0x%x, %d, 0): %s. "
                                     "See process maps in the log.",
                                 addr,
@@ -389,6 +386,32 @@
                 reuse);
 }
 
+MemMap MemMap::MapAnonymousAligned(const char* name,
+                                   size_t byte_count,
+                                   int prot,
+                                   bool low_4gb,
+                                   size_t alignment,
+                                   /*out=*/std::string* error_msg) {
+  DCHECK(IsPowerOfTwo(alignment));
+  DCHECK_GT(alignment, kPageSize);
+  // Allocate extra 'alignment - kPageSize' bytes so that the mapping can be aligned.
+  MemMap ret = MapAnonymous(name,
+                            /*addr=*/nullptr,
+                            byte_count + alignment - kPageSize,
+                            prot,
+                            low_4gb,
+                            /*reuse=*/false,
+                            /*reservation=*/nullptr,
+                            error_msg);
+  if (LIKELY(ret.IsValid())) {
+    ret.AlignBy(alignment, /*align_both_ends=*/false);
+    ret.SetSize(byte_count);
+    DCHECK_EQ(ret.Size(), byte_count);
+    DCHECK_ALIGNED_PARAM(ret.Begin(), alignment);
+  }
+  return ret;
+}
+
 MemMap MemMap::MapPlaceholder(const char* name, uint8_t* addr, size_t byte_count) {
   if (byte_count == 0) {
     return Invalid();
@@ -777,11 +800,11 @@
   return MemMap(tail_name, actual, tail_size, actual, tail_base_size, tail_prot, false);
 }
 
-MemMap MemMap::TakeReservedMemory(size_t byte_count) {
+MemMap MemMap::TakeReservedMemory(size_t byte_count, bool reuse) {
   uint8_t* begin = Begin();
   ReleaseReservedMemory(byte_count);  // Performs necessary DCHECK()s on this reservation.
   size_t base_size = RoundUp(byte_count, kPageSize);
-  return MemMap(name_, begin, byte_count, begin, base_size, prot_, /* reuse= */ false);
+  return MemMap(name_, begin, byte_count, begin, base_size, prot_, reuse);
 }
 
 void MemMap::ReleaseReservedMemory(size_t byte_count) {
@@ -998,6 +1021,8 @@
   TargetMMapInit();
 }
 
+bool MemMap::IsInitialized() { return mem_maps_lock_ != nullptr; }
+
 void MemMap::Shutdown() {
   if (mem_maps_lock_ == nullptr) {
     // If MemMap::Shutdown is called more than once, there is no effect.
@@ -1247,40 +1272,46 @@
   }
 }
 
-void MemMap::AlignBy(size_t size) {
+void MemMap::AlignBy(size_t alignment, bool align_both_ends) {
   CHECK_EQ(begin_, base_begin_) << "Unsupported";
   CHECK_EQ(size_, base_size_) << "Unsupported";
-  CHECK_GT(size, static_cast<size_t>(kPageSize));
-  CHECK_ALIGNED(size, kPageSize);
+  CHECK_GT(alignment, static_cast<size_t>(kPageSize));
+  CHECK_ALIGNED(alignment, kPageSize);
   CHECK(!reuse_);
-  if (IsAlignedParam(reinterpret_cast<uintptr_t>(base_begin_), size) &&
-      IsAlignedParam(base_size_, size)) {
+  if (IsAlignedParam(reinterpret_cast<uintptr_t>(base_begin_), alignment) &&
+      (!align_both_ends || IsAlignedParam(base_size_, alignment))) {
     // Already aligned.
     return;
   }
   uint8_t* base_begin = reinterpret_cast<uint8_t*>(base_begin_);
-  uint8_t* base_end = base_begin + base_size_;
-  uint8_t* aligned_base_begin = AlignUp(base_begin, size);
-  uint8_t* aligned_base_end = AlignDown(base_end, size);
+  uint8_t* aligned_base_begin = AlignUp(base_begin, alignment);
   CHECK_LE(base_begin, aligned_base_begin);
-  CHECK_LE(aligned_base_end, base_end);
-  size_t aligned_base_size = aligned_base_end - aligned_base_begin;
-  CHECK_LT(aligned_base_begin, aligned_base_end)
-      << "base_begin = " << reinterpret_cast<void*>(base_begin)
-      << " base_end = " << reinterpret_cast<void*>(base_end);
-  CHECK_GE(aligned_base_size, size);
-  // Unmap the unaligned parts.
   if (base_begin < aligned_base_begin) {
     MEMORY_TOOL_MAKE_UNDEFINED(base_begin, aligned_base_begin - base_begin);
     CHECK_EQ(TargetMUnmap(base_begin, aligned_base_begin - base_begin), 0)
         << "base_begin=" << reinterpret_cast<void*>(base_begin)
         << " aligned_base_begin=" << reinterpret_cast<void*>(aligned_base_begin);
   }
-  if (aligned_base_end < base_end) {
-    MEMORY_TOOL_MAKE_UNDEFINED(aligned_base_end, base_end - aligned_base_end);
-    CHECK_EQ(TargetMUnmap(aligned_base_end, base_end - aligned_base_end), 0)
-        << "base_end=" << reinterpret_cast<void*>(base_end)
-        << " aligned_base_end=" << reinterpret_cast<void*>(aligned_base_end);
+  uint8_t* base_end = base_begin + base_size_;
+  size_t aligned_base_size;
+  if (align_both_ends) {
+    uint8_t* aligned_base_end = AlignDown(base_end, alignment);
+    CHECK_LE(aligned_base_end, base_end);
+    CHECK_LT(aligned_base_begin, aligned_base_end)
+        << "base_begin = " << reinterpret_cast<void*>(base_begin)
+        << " base_end = " << reinterpret_cast<void*>(base_end);
+    aligned_base_size = aligned_base_end - aligned_base_begin;
+    CHECK_GE(aligned_base_size, alignment);
+    if (aligned_base_end < base_end) {
+      MEMORY_TOOL_MAKE_UNDEFINED(aligned_base_end, base_end - aligned_base_end);
+      CHECK_EQ(TargetMUnmap(aligned_base_end, base_end - aligned_base_end), 0)
+          << "base_end=" << reinterpret_cast<void*>(base_end)
+          << " aligned_base_end=" << reinterpret_cast<void*>(aligned_base_end);
+    }
+  } else {
+    CHECK_LT(aligned_base_begin, base_end)
+        << "base_begin = " << reinterpret_cast<void*>(base_begin);
+    aligned_base_size = base_end - aligned_base_begin;
   }
   std::lock_guard<std::mutex> mu(*mem_maps_lock_);
   if (base_begin < aligned_base_begin) {
diff --git a/libartbase/base/mem_map.h b/libartbase/base/mem_map.h
index 4c41388..98fb69d 100644
--- a/libartbase/base/mem_map.h
+++ b/libartbase/base/mem_map.h
@@ -29,7 +29,8 @@
 
 namespace art {
 
-#if defined(__LP64__) && !defined(__Fuchsia__) && (defined(__aarch64__) || defined(__APPLE__))
+#if defined(__LP64__) && !defined(__Fuchsia__) && \
+    (defined(__aarch64__) || defined(__riscv) || defined(__APPLE__))
 #define USE_ART_LOW_4G_ALLOCATOR 1
 #else
 #if defined(__LP64__) && !defined(__Fuchsia__) && !defined(__x86_64__)
@@ -127,7 +128,7 @@
   // 'name' will be used -- on systems that support it -- to give the mapping
   // a name.
   //
-  // On success, returns returns a valid MemMap.  On failure, returns an invalid MemMap.
+  // On success, returns a valid MemMap. On failure, returns an invalid MemMap.
   static MemMap MapAnonymous(const char* name,
                              uint8_t* addr,
                              size_t byte_count,
@@ -137,6 +138,17 @@
                              /*inout*/MemMap* reservation,
                              /*out*/std::string* error_msg,
                              bool use_debug_name = true);
+
+  // Request an aligned anonymous region. We can't directly ask for a MAP_SHARED (anonymous or
+  // otherwise) mapping to be aligned as in that case file offset is involved and could make
+  // the starting offset to be out of sync with another mapping of the same file.
+  static MemMap MapAnonymousAligned(const char* name,
+                                    size_t byte_count,
+                                    int prot,
+                                    bool low_4gb,
+                                    size_t alignment,
+                                    /*out=*/std::string* error_msg);
+
   static MemMap MapAnonymous(const char* name,
                              size_t byte_count,
                              int prot,
@@ -173,10 +185,10 @@
   // The region is not considered to be owned and will not be unmmaped.
   static MemMap MapPlaceholder(const char* name, uint8_t* addr, size_t byte_count);
 
-  // Map part of a file, taking care of non-page aligned offsets.  The
+  // Map part of a file, taking care of non-page aligned offsets. The
   // "start" offset is absolute, not relative.
   //
-  // On success, returns returns a valid MemMap.  On failure, returns an invalid MemMap.
+  // On success, returns a valid MemMap. On failure, returns an invalid MemMap.
   static MemMap MapFile(size_t byte_count,
                         int prot,
                         int flags,
@@ -198,7 +210,7 @@
                             error_msg);
   }
 
-  // Map part of a file, taking care of non-page aligned offsets.  The "start" offset is absolute,
+  // Map part of a file, taking care of non-page aligned offsets. The "start" offset is absolute,
   // not relative. This version allows requesting a specific address for the base of the mapping.
   //
   // `reuse` allows re-mapping an address range from an existing mapping which retains the
@@ -209,7 +221,7 @@
   // This helps improve performance of the fail case since reading and printing /proc/maps takes
   // several milliseconds in the worst case.
   //
-  // On success, returns returns a valid MemMap.  On failure, returns an invalid MemMap.
+  // On success, returns a valid MemMap. On failure, returns an invalid MemMap.
   static MemMap MapFileAtAddress(uint8_t* addr,
                                  size_t byte_count,
                                  int prot,
@@ -290,8 +302,9 @@
   // exceed the size of this reservation.
   //
   // Returns a mapping owning `byte_count` bytes rounded up to entire pages
-  // with size set to the passed `byte_count`.
-  MemMap TakeReservedMemory(size_t byte_count);
+  // with size set to the passed `byte_count`. If 'reuse' is true then the caller
+  // is responsible for unmapping the taken pages.
+  MemMap TakeReservedMemory(size_t byte_count, bool reuse = false);
 
   static bool CheckNoGaps(MemMap& begin_map, MemMap& end_map)
       REQUIRES(!MemMap::mem_maps_lock_);
@@ -303,14 +316,16 @@
   // time after the first call to Init and before the first call to Shutodwn.
   static void Init() REQUIRES(!MemMap::mem_maps_lock_);
   static void Shutdown() REQUIRES(!MemMap::mem_maps_lock_);
+  static bool IsInitialized();
 
   // If the map is PROT_READ, try to read each page of the map to check it is in fact readable (not
   // faulting). This is used to diagnose a bug b/19894268 where mprotect doesn't seem to be working
   // intermittently.
   void TryReadable();
 
-  // Align the map by unmapping the unaligned parts at the lower and the higher ends.
-  void AlignBy(size_t size);
+  // Align the map by unmapping the unaligned part at the lower end and if 'align_both_ends' is
+  // true, then the higher end as well.
+  void AlignBy(size_t alignment, bool align_both_ends = true);
 
   // For annotation reasons.
   static std::mutex* GetMemMapsLock() RETURN_CAPABILITY(mem_maps_lock_) {
@@ -321,6 +336,9 @@
   // in the parent process.
   void ResetInForkedProcess();
 
+  // 'redzone_size_ == 0' indicates that we are not using memory-tool on this mapping.
+  size_t GetRedzoneSize() const { return redzone_size_; }
+
  private:
   MemMap(const std::string& name,
          uint8_t* begin,
diff --git a/libartbase/base/mem_map_windows.cc b/libartbase/base/mem_map_windows.cc
index 84e14ea..dfa75a1 100644
--- a/libartbase/base/mem_map_windows.cc
+++ b/libartbase/base/mem_map_windows.cc
@@ -31,7 +31,6 @@
 
 namespace art {
 
-using android::base::MappedFile;
 using android::base::StringPrintf;
 
 static off_t allocation_granularity;
diff --git a/libartbase/base/metrics/metrics.h b/libartbase/base/metrics/metrics.h
index d6f2463..40db63d 100644
--- a/libartbase/base/metrics/metrics.h
+++ b/libartbase/base/metrics/metrics.h
@@ -29,33 +29,69 @@
 
 #include "android-base/logging.h"
 #include "base/bit_utils.h"
+#include "base/macros.h"
 #include "base/time_utils.h"
+#include "tinyxml2.h"
 
 #pragma clang diagnostic push
 #pragma clang diagnostic error "-Wconversion"
 
 // See README.md in this directory for how to define metrics.
-#define ART_METRICS(METRIC)                                             \
-  METRIC(ClassLoadingTotalTime, MetricsCounter)                         \
-  METRIC(ClassVerificationTotalTime, MetricsCounter)                    \
-  METRIC(ClassVerificationCount, MetricsCounter)                        \
-  METRIC(WorldStopTimeDuringGCAvg, MetricsAverage)                      \
-  METRIC(YoungGcCount, MetricsCounter)                                  \
-  METRIC(FullGcCount, MetricsCounter)                                   \
-  METRIC(TotalBytesAllocated, MetricsCounter)                           \
-  METRIC(TotalGcCollectionTime, MetricsCounter)                         \
-  METRIC(YoungGcThroughputAvg, MetricsAverage)                          \
-  METRIC(FullGcThroughputAvg, MetricsAverage)                           \
-  METRIC(YoungGcTracingThroughputAvg, MetricsAverage)                   \
-  METRIC(FullGcTracingThroughputAvg, MetricsAverage)                    \
-  METRIC(JitMethodCompileTotalTime, MetricsCounter)                     \
-  METRIC(JitMethodCompileCount, MetricsCounter)                         \
-  METRIC(YoungGcCollectionTime, MetricsHistogram, 15, 0, 60'000)        \
-  METRIC(FullGcCollectionTime, MetricsHistogram, 15, 0, 60'000)         \
-  METRIC(YoungGcThroughput, MetricsHistogram, 15, 0, 10'000)            \
-  METRIC(FullGcThroughput, MetricsHistogram, 15, 0, 10'000)             \
-  METRIC(YoungGcTracingThroughput, MetricsHistogram, 15, 0, 10'000)     \
-  METRIC(FullGcTracingThroughput, MetricsHistogram, 15, 0, 10'000)
+
+// Metrics reported as Event Metrics.
+#define ART_EVENT_METRICS(METRIC)                                   \
+  METRIC(ClassLoadingTotalTime, MetricsCounter)                     \
+  METRIC(ClassVerificationTotalTime, MetricsCounter)                \
+  METRIC(ClassVerificationCount, MetricsCounter)                    \
+  METRIC(WorldStopTimeDuringGCAvg, MetricsAverage)                  \
+  METRIC(YoungGcCount, MetricsCounter)                              \
+  METRIC(FullGcCount, MetricsCounter)                               \
+  METRIC(TotalBytesAllocated, MetricsCounter)                       \
+  METRIC(TotalGcCollectionTime, MetricsCounter)                     \
+  METRIC(YoungGcThroughputAvg, MetricsAverage)                      \
+  METRIC(FullGcThroughputAvg, MetricsAverage)                       \
+  METRIC(YoungGcTracingThroughputAvg, MetricsAverage)               \
+  METRIC(FullGcTracingThroughputAvg, MetricsAverage)                \
+  METRIC(JitMethodCompileTotalTime, MetricsCounter)                 \
+  METRIC(JitMethodCompileCount, MetricsCounter)                     \
+  METRIC(YoungGcCollectionTime, MetricsHistogram, 15, 0, 60'000)    \
+  METRIC(FullGcCollectionTime, MetricsHistogram, 15, 0, 60'000)     \
+  METRIC(YoungGcThroughput, MetricsHistogram, 15, 0, 10'000)        \
+  METRIC(FullGcThroughput, MetricsHistogram, 15, 0, 10'000)         \
+  METRIC(YoungGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) \
+  METRIC(FullGcTracingThroughput, MetricsHistogram, 15, 0, 10'000)  \
+  METRIC(GcWorldStopTime, MetricsCounter)                           \
+  METRIC(GcWorldStopCount, MetricsCounter)                          \
+  METRIC(YoungGcScannedBytes, MetricsCounter)                       \
+  METRIC(YoungGcFreedBytes, MetricsCounter)                         \
+  METRIC(YoungGcDuration, MetricsCounter)                           \
+  METRIC(FullGcScannedBytes, MetricsCounter)                        \
+  METRIC(FullGcFreedBytes, MetricsCounter)                          \
+  METRIC(FullGcDuration, MetricsCounter)
+
+// Increasing counter metrics, reported as Value Metrics in delta increments.
+#define ART_VALUE_METRICS(METRIC)                              \
+  METRIC(GcWorldStopTimeDelta, MetricsDeltaCounter)            \
+  METRIC(GcWorldStopCountDelta, MetricsDeltaCounter)           \
+  METRIC(YoungGcScannedBytesDelta, MetricsDeltaCounter)        \
+  METRIC(YoungGcFreedBytesDelta, MetricsDeltaCounter)          \
+  METRIC(YoungGcDurationDelta, MetricsDeltaCounter)            \
+  METRIC(FullGcScannedBytesDelta, MetricsDeltaCounter)         \
+  METRIC(FullGcFreedBytesDelta, MetricsDeltaCounter)           \
+  METRIC(FullGcDurationDelta, MetricsDeltaCounter)             \
+  METRIC(JitMethodCompileTotalTimeDelta, MetricsDeltaCounter)  \
+  METRIC(JitMethodCompileCountDelta, MetricsDeltaCounter)      \
+  METRIC(ClassVerificationTotalTimeDelta, MetricsDeltaCounter) \
+  METRIC(ClassVerificationCountDelta, MetricsDeltaCounter)     \
+  METRIC(ClassLoadingTotalTimeDelta, MetricsDeltaCounter)      \
+  METRIC(TotalBytesAllocatedDelta, MetricsDeltaCounter)        \
+  METRIC(TotalGcCollectionTimeDelta, MetricsDeltaCounter)      \
+  METRIC(YoungGcCountDelta, MetricsDeltaCounter)               \
+  METRIC(FullGcCountDelta, MetricsDeltaCounter)
+
+#define ART_METRICS(METRIC) \
+  ART_EVENT_METRICS(METRIC) \
+  ART_VALUE_METRICS(METRIC)
 
 // A lot of the metrics implementation code is generated by passing one-off macros into ART_COUNTERS
 // and ART_HISTOGRAMS. This means metrics.h and metrics.cc are very #define-heavy, which can be
@@ -71,6 +107,15 @@
 struct RuntimeArgumentMap;
 
 namespace metrics {
+template <typename value_t>
+class MetricsBase;
+}  // namespace metrics
+
+namespace gc {
+class HeapTest_GCMetrics_Test;
+}  // namespace gc
+
+namespace metrics {
 
 /**
  * An enumeration of all ART counters and histograms.
@@ -82,26 +127,27 @@
 };
 
 // Names come from PackageManagerServiceCompilerMapping.java
-#define REASON_NAME_LIST(V) \
-  V(kError, "error") \
-  V(kUnknown, "unknown") \
-  V(kFirstBoot, "first-boot") \
-  V(kBootAfterOTA, "boot-after-ota") \
-  V(kPostBoot, "post-boot") \
-  V(kInstall, "install") \
-  V(kInstallFast, "install-fast") \
-  V(kInstallBulk, "install-bulk") \
-  V(kInstallBulkSecondary, "install-bulk-secondary") \
-  V(kInstallBulkDowngraded, "install-bulk-downgraded") \
+#define REASON_NAME_LIST(V)                                               \
+  V(kError, "error")                                                      \
+  V(kUnknown, "unknown")                                                  \
+  V(kFirstBoot, "first-boot")                                             \
+  V(kBootAfterOTA, "boot-after-ota")                                      \
+  V(kPostBoot, "post-boot")                                               \
+  V(kInstall, "install")                                                  \
+  V(kInstallFast, "install-fast")                                         \
+  V(kInstallBulk, "install-bulk")                                         \
+  V(kInstallBulkSecondary, "install-bulk-secondary")                      \
+  V(kInstallBulkDowngraded, "install-bulk-downgraded")                    \
   V(kInstallBulkSecondaryDowngraded, "install-bulk-secondary-downgraded") \
-  V(kBgDexopt, "bg-dexopt") \
-  V(kABOTA, "ab-ota") \
-  V(kInactive, "inactive") \
-  V(kShared, "shared") \
-  V(kInstallWithDexMetadata, "install-with-dex-metadata") \
-  V(kPrebuilt, "prebuilt") \
-  V(kCmdLine, "cmdline") \
-  V(kVdex, "vdex")
+  V(kBgDexopt, "bg-dexopt")                                               \
+  V(kABOTA, "ab-ota")                                                     \
+  V(kInactive, "inactive")                                                \
+  V(kShared, "shared")                                                    \
+  V(kInstallWithDexMetadata, "install-with-dex-metadata")                 \
+  V(kPrebuilt, "prebuilt")                                                \
+  V(kCmdLine, "cmdline")                                                  \
+  V(kVdex, "vdex")                                                        \
+  V(kBootAfterMainlineUpdate, "boot-after-mainline-update")
 
 // We log compilation reasons as part of the metadata we report. Since elsewhere compilation reasons
 // are specified as a string, we define them as an enum here which indicates the reasons that we
@@ -115,7 +161,7 @@
 #define REASON_NAME(kind, kind_name) \
     case CompilationReason::kind: return kind_name;
 #define REASON_FROM_NAME(kind, kind_name) \
-    if (name == kind_name) { return CompilationReason::kind; }
+    if (name == (kind_name)) { return CompilationReason::kind; }
 
 constexpr const char* CompilationReasonName(CompilationReason reason) {
   switch (reason) {
@@ -129,7 +175,7 @@
 }
 
 #undef REASON_NAME
-#undef ReasonFromName
+#undef REASON_FROM_NAME
 
 #define COMPILER_FILTER_REPORTING_LIST(V) \
   V(kError, "error") /* Error (invalid value) condition */ \
@@ -156,7 +202,7 @@
 #define FILTER_NAME(kind, kind_name) \
     case CompilerFilterReporting::kind: return kind_name;
 #define FILTER_FROM_NAME(kind, kind_name) \
-    if (name == kind_name) { return CompilerFilterReporting::kind; }
+    if (name == (kind_name)) { return CompilerFilterReporting::kind; }
 
 constexpr const char* CompilerFilterReportingName(CompilerFilterReporting filter) {
   switch (filter) {
@@ -234,6 +280,8 @@
 
   template <DatumId counter_type, typename T>
   friend class MetricsCounter;
+  template <DatumId counter_type, typename T>
+  friend class MetricsDeltaCounter;
   template <DatumId histogram_type, size_t num_buckets, int64_t low_value, int64_t high_value>
   friend class MetricsHistogram;
   template <DatumId datum_id, typename T, const T& AccumulatorFunction(const T&, const T&)>
@@ -248,6 +296,13 @@
  public:
   virtual void Add(value_t value) = 0;
   virtual ~MetricsBase() { }
+
+ private:
+  // Is the metric "null", i.e. never updated or freshly reset?
+  // Used for testing purpose only.
+  virtual bool IsNull() const = 0;
+
+  ART_FRIEND_TEST(gc::HeapTest, GCMetrics);
 };
 
 template <DatumId counter_type, typename T = uint64_t>
@@ -263,18 +318,23 @@
   }
 
   void AddOne() { Add(1u); }
-  void Add(value_t value) { value_.fetch_add(value, std::memory_order::memory_order_relaxed); }
-
-  void Report(MetricsBackend* backend) const { backend->ReportCounter(counter_type, Value()); }
-
- protected:
-  void Reset() {
-    value_ = 0;
+  void Add(value_t value) override {
+    value_.fetch_add(value, std::memory_order::memory_order_relaxed);
   }
 
+  void Report(const std::vector<MetricsBackend*>& backends) const {
+    for (MetricsBackend* backend : backends) {
+      backend->ReportCounter(counter_type, Value());
+    }
+  }
+
+ protected:
+  void Reset() { value_ = 0; }
   value_t Value() const { return value_.load(std::memory_order::memory_order_relaxed); }
 
  private:
+  bool IsNull() const override { return Value() == 0; }
+
   std::atomic<value_t> value_;
   static_assert(std::atomic<value_t>::is_always_lock_free);
 
@@ -303,16 +363,19 @@
   // 1. The metric eventually becomes consistent.
   // 2. For sufficiently large count_, a few data points which are off shouldn't
   // make a huge difference to the reporter.
-  void Add(value_t value) {
+  void Add(value_t value) override {
     MetricsCounter<datum_id, value_t>::Add(value);
     count_.fetch_add(1, std::memory_order::memory_order_release);
   }
 
-  void Report(MetricsBackend* backend) const {
+  void Report(const std::vector<MetricsBackend*>& backends) const {
+    count_t value = MetricsCounter<datum_id, value_t>::Value();
     count_t count = count_.load(std::memory_order::memory_order_acquire);
-    backend->ReportCounter(datum_id,
-                           // Avoid divide-by-0.
-                           count != 0 ? MetricsCounter<datum_id, value_t>::Value() / count : 0);
+    // Avoid divide-by-0.
+    count_t average_value = count != 0 ? value / count : 0;
+    for (MetricsBackend* backend : backends) {
+      backend->ReportCounter(datum_id, average_value);
+    }
   }
 
  protected:
@@ -322,12 +385,54 @@
   }
 
  private:
+  count_t Count() const { return count_.load(std::memory_order::memory_order_relaxed); }
+
+  bool IsNull() const override { return Count() == 0; }
+
   std::atomic<count_t> count_;
   static_assert(std::atomic<count_t>::is_always_lock_free);
 
   friend class ArtMetrics;
 };
 
+template <DatumId datum_id, typename T = uint64_t>
+class MetricsDeltaCounter : public MetricsBase<T> {
+ public:
+  using value_t = T;
+
+  explicit constexpr MetricsDeltaCounter(uint64_t value = 0) : value_{value} {
+    // Ensure we do not have any unnecessary data in this class.
+    // Adding intptr_t to accommodate vtable, and rounding up to incorporate
+    // padding.
+    static_assert(RoundUp(sizeof(*this), sizeof(uint64_t)) ==
+                  RoundUp(sizeof(intptr_t) + sizeof(value_t), sizeof(uint64_t)));
+  }
+
+  void Add(value_t value) override {
+    value_.fetch_add(value, std::memory_order::memory_order_relaxed);
+  }
+  void AddOne() { Add(1u); }
+
+  void ReportAndReset(const std::vector<MetricsBackend*>& backends) {
+    value_t value = value_.exchange(0, std::memory_order::memory_order_relaxed);
+    for (MetricsBackend* backend : backends) {
+      backend->ReportCounter(datum_id, value);
+    }
+  }
+
+  void Reset() { value_ = 0; }
+
+ private:
+  value_t Value() const { return value_.load(std::memory_order::memory_order_relaxed); }
+
+  bool IsNull() const override { return Value() == 0; }
+
+  std::atomic<value_t> value_;
+  static_assert(std::atomic<value_t>::is_always_lock_free);
+
+  friend class ArtMetrics;
+};
+
 template <DatumId histogram_type_,
           size_t num_buckets_,
           int64_t minimum_value_,
@@ -347,13 +452,15 @@
                   == RoundUp(sizeof(intptr_t) + sizeof(value_t) * num_buckets_, sizeof(uint64_t)));
   }
 
-  void Add(int64_t value) {
+  void Add(int64_t value) override {
     const size_t i = FindBucketId(value);
     buckets_[i].fetch_add(1u, std::memory_order::memory_order_relaxed);
   }
 
-  void Report(MetricsBackend* backend) const {
-    backend->ReportHistogram(histogram_type_, minimum_value_, maximum_value_, GetBuckets());
+  void Report(const std::vector<MetricsBackend*>& backends) const {
+    for (MetricsBackend* backend : backends) {
+      backend->ReportHistogram(histogram_type_, minimum_value_, maximum_value_, GetBuckets());
+    }
   }
 
  protected:
@@ -385,6 +492,11 @@
     return std::vector<value_t>{buckets_.begin(), buckets_.end()};
   }
 
+  bool IsNull() const override {
+    std::vector<value_t> buckets = GetBuckets();
+    return std::all_of(buckets.cbegin(), buckets.cend(), [](value_t i) { return i == 0; });
+  }
+
   std::array<std::atomic<value_t>, num_buckets_> buckets_;
   static_assert(std::atomic<value_t>::is_always_lock_free);
 
@@ -402,7 +514,7 @@
                   RoundUp(sizeof(intptr_t) + sizeof(T), sizeof(uint64_t)));
   }
 
-  void Add(T value) {
+  void Add(T value) override {
     T current = value_.load(std::memory_order::memory_order_relaxed);
     T new_value;
     do {
@@ -428,17 +540,87 @@
  private:
   T Value() const { return value_.load(std::memory_order::memory_order_relaxed); }
 
+  bool IsNull() const override { return Value() == 0; }
+
   std::atomic<T> value_;
 
   friend class ArtMetrics;
 };
 
-// A backend that writes metrics in a human-readable format to a string.
+// Base class for formatting metrics into different formats
+// (human-readable text, JSON, etc.)
+class MetricsFormatter {
+ public:
+  virtual ~MetricsFormatter() = default;
+
+  virtual void FormatBeginReport(uint64_t timestamp_since_start_ms,
+                                 const std::optional<SessionData>& session_data) = 0;
+  virtual void FormatEndReport() = 0;
+  virtual void FormatReportCounter(DatumId counter_type, uint64_t value) = 0;
+  virtual void FormatReportHistogram(DatumId histogram_type,
+                                     int64_t low_value,
+                                     int64_t high_value,
+                                     const std::vector<uint32_t>& buckets) = 0;
+  virtual std::string GetAndResetBuffer() = 0;
+
+ protected:
+  const std::string version = "1.0";
+};
+
+// Formatter outputting metrics in human-readable text format
+class TextFormatter : public MetricsFormatter {
+ public:
+  TextFormatter() = default;
+
+  void FormatBeginReport(uint64_t timestamp_millis,
+                         const std::optional<SessionData>& session_data) override;
+
+  void FormatReportCounter(DatumId counter_type, uint64_t value) override;
+
+  void FormatReportHistogram(DatumId histogram_type,
+                             int64_t low_value,
+                             int64_t high_value,
+                             const std::vector<uint32_t>& buckets) override;
+
+  void FormatEndReport() override;
+
+  std::string GetAndResetBuffer() override;
+
+ private:
+  std::ostringstream os_;
+};
+
+// Formatter outputting metrics in XML format
+class XmlFormatter : public MetricsFormatter {
+ public:
+  XmlFormatter() = default;
+
+  void FormatBeginReport(uint64_t timestamp_millis,
+                         const std::optional<SessionData>& session_data) override;
+
+  void FormatReportCounter(DatumId counter_type, uint64_t value) override;
+
+  void FormatReportHistogram(DatumId histogram_type,
+                             int64_t low_value,
+                             int64_t high_value,
+                             const std::vector<uint32_t>& buckets) override;
+
+  void FormatEndReport() override;
+
+  std::string GetAndResetBuffer() override;
+
+ private:
+  tinyxml2::XMLDocument document_;
+};
+
+// A backend that writes metrics to a string.
+// The format of the metrics' output is delegated
+// to the MetricsFormatter class.
 //
 // This is used as a base for LogBackend and FileBackend.
 class StringBackend : public MetricsBackend {
  public:
-  StringBackend();
+  explicit StringBackend(std::unique_ptr<MetricsFormatter> formatter);
 
   void BeginOrUpdateSession(const SessionData& session_data) override;
 
@@ -456,14 +638,15 @@
   std::string GetAndResetBuffer();
 
  private:
-  std::ostringstream os_;
+  std::unique_ptr<MetricsFormatter> formatter_;
   std::optional<SessionData> session_data_;
 };
 
 // A backend that writes metrics in human-readable format to the log (i.e. logcat).
 class LogBackend : public StringBackend {
  public:
-  explicit LogBackend(android::base::LogSeverity level);
+  explicit LogBackend(std::unique_ptr<MetricsFormatter> formatter,
+                      android::base::LogSeverity level);
 
   void BeginReport(uint64_t timestamp_millis) override;
   void EndReport() override;
@@ -473,12 +656,10 @@
 };
 
 // A backend that writes metrics to a file.
-//
-// These are currently written in the same human-readable format used by StringBackend and
-// LogBackend, but we will probably want a more machine-readable format in the future.
 class FileBackend : public StringBackend {
  public:
-  explicit FileBackend(const std::string& filename);
+  explicit FileBackend(std::unique_ptr<MetricsFormatter> formatter,
+                       const std::string& filename);
 
   void BeginReport(uint64_t timestamp_millis) override;
   void EndReport() override;
@@ -563,8 +744,8 @@
  public:
   ArtMetrics();
 
-  void ReportAllMetrics(MetricsBackend* backend) const;
-  void DumpForSigQuit(std::ostream& os) const;
+  void ReportAllMetricsAndResetValueMetrics(const std::vector<MetricsBackend*>& backends);
+  void DumpForSigQuit(std::ostream& os);
 
   // Resets all metrics to their initial value. This is intended to be used after forking from the
   // zygote so we don't attribute parent values to the child process.
diff --git a/libartbase/base/metrics/metrics_common.cc b/libartbase/base/metrics/metrics_common.cc
index f09987b..6c4aa95 100644
--- a/libartbase/base/metrics/metrics_common.cc
+++ b/libartbase/base/metrics/metrics_common.cc
@@ -65,36 +65,43 @@
 {
 }
 
-void ArtMetrics::ReportAllMetrics(MetricsBackend* backend) const {
-  backend->BeginReport(MilliTime() - beginning_timestamp_);
+void ArtMetrics::ReportAllMetricsAndResetValueMetrics(
+    const std::vector<MetricsBackend*>& backends) {
+  for (auto& backend : backends) {
+    backend->BeginReport(MilliTime() - beginning_timestamp_);
+  }
 
-#define ART_METRIC(name, Kind, ...) name()->Report(backend);
-  ART_METRICS(ART_METRIC)
-#undef ART_METRIC
+#define REPORT_METRIC(name, Kind, ...) name()->Report(backends);
+  ART_EVENT_METRICS(REPORT_METRIC)
+#undef REPORT_METRIC
 
-  backend->EndReport();
+#define REPORT_METRIC(name, Kind, ...) name()->ReportAndReset(backends);
+  ART_VALUE_METRICS(REPORT_METRIC)
+#undef REPORT_METRIC
+
+  for (auto& backend : backends) {
+    backend->EndReport();
+  }
 }
 
-void ArtMetrics::DumpForSigQuit(std::ostream& os) const {
-  StringBackend backend;
-  ReportAllMetrics(&backend);
+void ArtMetrics::DumpForSigQuit(std::ostream& os) {
+  StringBackend backend(std::make_unique<TextFormatter>());
+  ReportAllMetricsAndResetValueMetrics({&backend});
   os << backend.GetAndResetBuffer();
 }
 
 void ArtMetrics::Reset() {
   beginning_timestamp_ = MilliTime();
-#define ART_METRIC(name, kind, ...) name##_.Reset();
-  ART_METRICS(ART_METRIC);
-#undef ART_METRIC
+#define RESET_METRIC(name, ...) name##_.Reset();
+  ART_METRICS(RESET_METRIC)
+#undef RESET_METRIC
 }
 
-StringBackend::StringBackend() {}
+StringBackend::StringBackend(std::unique_ptr<MetricsFormatter> formatter)
+    : formatter_(std::move(formatter)) {}
 
 std::string StringBackend::GetAndResetBuffer() {
-  std::string result = os_.str();
-  os_.clear();
-  os_.str("");
-  return result;
+  return formatter_->GetAndResetBuffer();
 }
 
 void StringBackend::BeginOrUpdateSession(const SessionData& session_data) {
@@ -102,32 +109,51 @@
 }
 
 void StringBackend::BeginReport(uint64_t timestamp_since_start_ms) {
-  os_ << "\n*** ART internal metrics ***\n";
-  os_ << "  Metadata:\n";
-  os_ << "    timestamp_since_start_ms: " << timestamp_since_start_ms << "\n";
-  if (session_data_.has_value()) {
-    os_ << "    session_id: " << session_data_->session_id << "\n";
-    os_ << "    uid: " << session_data_->uid << "\n";
-    os_ << "    compilation_reason: " << CompilationReasonName(session_data_->compilation_reason)
-        << "\n";
-    os_ << "    compiler_filter: " << CompilerFilterReportingName(session_data_->compiler_filter)
-        << "\n";
-  }
-  os_ << "  Metrics:\n";
+  formatter_->FormatBeginReport(timestamp_since_start_ms, session_data_);
 }
 
-void StringBackend::EndReport() { os_ << "*** Done dumping ART internal metrics ***\n"; }
+void StringBackend::EndReport() {
+  formatter_->FormatEndReport();
+}
 
 void StringBackend::ReportCounter(DatumId counter_type, uint64_t value) {
-  os_ << "    " << DatumName(counter_type) << ": count = " << value << "\n";
+  formatter_->FormatReportCounter(counter_type, value);
 }
 
 void StringBackend::ReportHistogram(DatumId histogram_type,
                                     int64_t minimum_value_,
                                     int64_t maximum_value_,
                                     const std::vector<uint32_t>& buckets) {
-  os_ << "    " << DatumName(histogram_type) << ": range = " << minimum_value_ << "..." << maximum_value_;
-  if (buckets.size() > 0) {
+  formatter_->FormatReportHistogram(histogram_type, minimum_value_, maximum_value_, buckets);
+}
+
+void TextFormatter::FormatBeginReport(uint64_t timestamp_since_start_ms,
+                                      const std::optional<SessionData>& session_data) {
+  os_ << "\n*** ART internal metrics ***\n";
+  os_ << "  Metadata:\n";
+  os_ << "    timestamp_since_start_ms: " << timestamp_since_start_ms << "\n";
+  if (session_data.has_value()) {
+    os_ << "    session_id: " << session_data->session_id << "\n";
+    os_ << "    uid: " << session_data->uid << "\n";
+    os_ << "    compilation_reason: " << CompilationReasonName(session_data->compilation_reason)
+        << "\n";
+    os_ << "    compiler_filter: " << CompilerFilterReportingName(session_data->compiler_filter)
+        << "\n";
+  }
+  os_ << "  Metrics:\n";
+}
+
+void TextFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) {
+  os_ << "    " << DatumName(counter_type) << ": count = " << value << "\n";
+}
+
+void TextFormatter::FormatReportHistogram(DatumId histogram_type,
+                                          int64_t minimum_value_,
+                                          int64_t maximum_value_,
+                                          const std::vector<uint32_t>& buckets) {
+  os_ << "    " << DatumName(histogram_type) << ": range = "
+      << minimum_value_ << "..." << maximum_value_;
+  if (!buckets.empty()) {
     os_ << ", buckets: ";
     bool first = true;
     for (const auto& count : buckets) {
@@ -143,22 +169,100 @@
   }
 }
 
-LogBackend::LogBackend(android::base::LogSeverity level) : level_{level} {}
+void TextFormatter::FormatEndReport() {
+  os_ << "*** Done dumping ART internal metrics ***\n";
+}
+
+std::string TextFormatter::GetAndResetBuffer() {
+  std::string result = os_.str();
+  os_.clear();
+  os_.str("");
+  return result;
+}
+
+void XmlFormatter::FormatBeginReport(uint64_t timestamp_millis,
+                                     const std::optional<SessionData>& session_data) {
+  tinyxml2::XMLElement* art_runtime_metrics = document_.NewElement("art_runtime_metrics");
+  document_.InsertEndChild(art_runtime_metrics);
+
+  art_runtime_metrics->InsertNewChildElement("version")->SetText(version.data());
+
+  tinyxml2::XMLElement* metadata = art_runtime_metrics->InsertNewChildElement("metadata");
+  metadata->InsertNewChildElement("timestamp_since_start_ms")->SetText(timestamp_millis);
+
+  if (session_data.has_value()) {
+    metadata->InsertNewChildElement("session_id")->SetText(session_data->session_id);
+    metadata->InsertNewChildElement("uid")->SetText(session_data->uid);
+    metadata
+        ->InsertNewChildElement("compilation_reason")
+        ->SetText(CompilationReasonName(session_data->compilation_reason));
+    metadata
+        ->InsertNewChildElement("compiler_filter")
+        ->SetText(CompilerFilterReportingName(session_data->compiler_filter));
+  }
+
+  art_runtime_metrics->InsertNewChildElement("metrics");
+}
+
+void XmlFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) {
+  tinyxml2::XMLElement* metrics = document_.RootElement()->FirstChildElement("metrics");
+
+  tinyxml2::XMLElement* counter = metrics->InsertNewChildElement(DatumName(counter_type).data());
+  counter->InsertNewChildElement("counter_type")->SetText("count");
+  counter->InsertNewChildElement("value")->SetText(value);
+}
+
+void XmlFormatter::FormatReportHistogram(DatumId histogram_type,
+                                         int64_t low_value,
+                                         int64_t high_value,
+                                         const std::vector<uint32_t>& buckets) {
+  tinyxml2::XMLElement* metrics = document_.RootElement()->FirstChildElement("metrics");
+
+  tinyxml2::XMLElement* histogram =
+      metrics->InsertNewChildElement(DatumName(histogram_type).data());
+  histogram->InsertNewChildElement("counter_type")->SetText("histogram");
+  histogram->InsertNewChildElement("minimum_value")->SetText(low_value);
+  histogram->InsertNewChildElement("maximum_value")->SetText(high_value);
+
+  tinyxml2::XMLElement* buckets_element = histogram->InsertNewChildElement("buckets");
+  for (const auto& count : buckets) {
+    buckets_element->InsertNewChildElement("bucket")->SetText(count);
+  }
+}
+
+void XmlFormatter::FormatEndReport() {}
+
+std::string XmlFormatter::GetAndResetBuffer() {
+  tinyxml2::XMLPrinter printer(/*file=*/nullptr, /*compact=*/true);
+  document_.Print(&printer);
+  std::string result = printer.CStr();
+  document_.Clear();
+
+  return result;
+}
+
+LogBackend::LogBackend(std::unique_ptr<MetricsFormatter> formatter,
+                       android::base::LogSeverity level)
+  : StringBackend{std::move(formatter)}, level_{level}
+{}
 
 void LogBackend::BeginReport(uint64_t timestamp_since_start_ms) {
-  GetAndResetBuffer();
+  StringBackend::GetAndResetBuffer();
   StringBackend::BeginReport(timestamp_since_start_ms);
 }
 
 void LogBackend::EndReport() {
   StringBackend::EndReport();
-  LOG_STREAM(level_) << GetAndResetBuffer();
+  LOG_STREAM(level_) << StringBackend::GetAndResetBuffer();
 }
 
-FileBackend::FileBackend(const std::string& filename) : filename_{filename} {}
+FileBackend::FileBackend(std::unique_ptr<MetricsFormatter> formatter,
+                         const std::string& filename)
+  : StringBackend{std::move(formatter)}, filename_{filename}
+{}
 
 void FileBackend::BeginReport(uint64_t timestamp_since_start_ms) {
-  GetAndResetBuffer();
+  StringBackend::GetAndResetBuffer();
   StringBackend::BeginReport(timestamp_since_start_ms);
 }
 
@@ -170,7 +274,7 @@
   if (file.get() == nullptr) {
     LOG(WARNING) << "Could open metrics file '" << filename_ << "': " << error_message;
   } else {
-    if (!android::base::WriteStringToFd(GetAndResetBuffer(), file.get()->Fd())) {
+    if (!android::base::WriteStringToFd(StringBackend::GetAndResetBuffer(), file.get()->Fd())) {
       PLOG(WARNING) << "Error writing metrics to file";
     }
   }
@@ -217,6 +321,11 @@
               CompilationReason::kPrebuilt);
 static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kCmdLine)) ==
               CompilationReason::kCmdLine);
+static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kVdex)) ==
+              CompilationReason::kVdex);
+static_assert(
+    CompilationReasonFromName(CompilationReasonName(CompilationReason::kBootAfterMainlineUpdate)) ==
+    CompilationReason::kBootAfterMainlineUpdate);
 
 }  // namespace metrics
 }  // namespace art
diff --git a/libartbase/base/metrics/metrics_test.cc b/libartbase/base/metrics/metrics_test.cc
index ed6f88d..2d69c95 100644
--- a/libartbase/base/metrics/metrics_test.cc
+++ b/libartbase/base/metrics/metrics_test.cc
@@ -16,6 +16,8 @@
 
 #include "metrics.h"
 
+#include "base/macros.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "metrics_test.h"
 
@@ -231,7 +233,7 @@
     bool found_histogram_{false};
   } backend;
 
-  metrics.ReportAllMetrics(&backend);
+  metrics.ReportAllMetricsAndResetValueMetrics({&backend});
 }
 
 TEST_F(MetricsTest, HistogramTimer) {
@@ -248,9 +250,9 @@
 // Makes sure all defined metrics are included when dumping through StreamBackend.
 TEST_F(MetricsTest, StreamBackendDumpAllMetrics) {
   ArtMetrics metrics;
-  StringBackend backend;
+  StringBackend backend(std::make_unique<TextFormatter>());
 
-  metrics.ReportAllMetrics(&backend);
+  metrics.ReportAllMetricsAndResetValueMetrics({&backend});
 
   // Make sure the resulting string lists all the metrics.
   const std::string result = backend.GetAndResetBuffer();
@@ -270,11 +272,14 @@
 
   class NonZeroBackend : public TestBackendBase {
    public:
-    void ReportCounter(DatumId, uint64_t value) override {
+    void ReportCounter(DatumId counter_type [[maybe_unused]], uint64_t value) override {
       EXPECT_NE(value, 0u);
     }
 
-    void ReportHistogram(DatumId, int64_t, int64_t, const std::vector<uint32_t>& buckets) override {
+    void ReportHistogram(DatumId histogram_type [[maybe_unused]],
+                         int64_t minimum_value [[maybe_unused]],
+                         int64_t maximum_value [[maybe_unused]],
+                         const std::vector<uint32_t>& buckets) override {
       bool nonzero = false;
       for (const auto value : buckets) {
         nonzero |= (value != 0u);
@@ -284,25 +289,357 @@
   } non_zero_backend;
 
   // Make sure the metrics all have a nonzero value.
-  metrics.ReportAllMetrics(&non_zero_backend);
+  metrics.ReportAllMetricsAndResetValueMetrics({&non_zero_backend});
 
   // Reset the metrics and make sure they are all zero again
   metrics.Reset();
 
   class ZeroBackend : public TestBackendBase {
    public:
-    void ReportCounter(DatumId, uint64_t value) override {
+    void ReportCounter(DatumId counter_type [[maybe_unused]], uint64_t value) override {
       EXPECT_EQ(value, 0u);
     }
 
-    void ReportHistogram(DatumId, int64_t, int64_t, const std::vector<uint32_t>& buckets) override {
+    void ReportHistogram(DatumId histogram_type [[maybe_unused]],
+                         int64_t minimum_value [[maybe_unused]],
+                         int64_t maximum_value [[maybe_unused]],
+                         const std::vector<uint32_t>& buckets) override {
       for (const auto value : buckets) {
         EXPECT_EQ(value, 0u);
       }
     }
   } zero_backend;
 
-  metrics.ReportAllMetrics(&zero_backend);
+  metrics.ReportAllMetricsAndResetValueMetrics({&zero_backend});
+}
+
+TEST_F(MetricsTest, KeepEventMetricsResetValueMetricsAfterReporting) {
+  ArtMetrics metrics;
+
+  // Add something to each of the metrics.
+#define METRIC(name, type, ...) metrics.name()->Add(42);
+  ART_METRICS(METRIC)
+#undef METRIC
+
+  class FirstBackend : public TestBackendBase {
+   public:
+    void ReportCounter(DatumId counter_type [[maybe_unused]], uint64_t value) override {
+      EXPECT_NE(value, 0u);
+    }
+
+    void ReportHistogram(DatumId histogram_type [[maybe_unused]],
+                         int64_t minimum_value [[maybe_unused]],
+                         int64_t maximum_value [[maybe_unused]],
+                         const std::vector<uint32_t>& buckets) override {
+      EXPECT_NE(buckets[0], 0u) << "Bucket 0 should have a non-zero value";
+      for (size_t i = 1; i < buckets.size(); i++) {
+        EXPECT_EQ(buckets[i], 0u) << "Bucket " << i << " should have a zero value";
+      }
+    }
+  } first_backend;
+
+  // Make sure the metrics all have a nonzero value, and they are not reset between backends.
+  metrics.ReportAllMetricsAndResetValueMetrics({&first_backend, &first_backend});
+
+  // After reporting, the Value Metrics should have been reset.
+  class SecondBackend : public TestBackendBase {
+   public:
+    void ReportCounter(DatumId datum_id, uint64_t value) override {
+      switch (datum_id) {
+        // Value metrics - expected to have been reset
+#define CHECK_METRIC(name, ...) case DatumId::k##name:
+        ART_VALUE_METRICS(CHECK_METRIC)
+#undef CHECK_METRIC
+        EXPECT_EQ(value, 0u);
+        return;
+
+        // Event metrics - expected to have retained their previous value
+#define CHECK_METRIC(name, ...) case DatumId::k##name:
+        ART_EVENT_METRICS(CHECK_METRIC)
+#undef CHECK_METRIC
+        EXPECT_NE(value, 0u);
+        return;
+
+        default:
+          // unknown metric - it should not be possible to reach this path
+          FAIL();
+          UNREACHABLE();
+      }
+    }
+
+    // All histograms are event metrics.
+    void ReportHistogram(DatumId histogram_type [[maybe_unused]],
+                         int64_t minimum_value [[maybe_unused]],
+                         int64_t maximum_value [[maybe_unused]],
+                         const std::vector<uint32_t>& buckets) override {
+      EXPECT_NE(buckets[0], 0u) << "Bucket 0 should have a non-zero value";
+      for (size_t i = 1; i < buckets.size(); i++) {
+        EXPECT_EQ(buckets[i], 0u) << "Bucket " << i << " should have a zero value";
+      }
+    }
+  } second_backend;
+
+  metrics.ReportAllMetricsAndResetValueMetrics({&second_backend});
+}
+
+TEST(TextFormatterTest, ReportMetrics_WithBuckets) {
+  TextFormatter text_formatter;
+  SessionData session_data {
+      .session_id = 1000,
+      .uid = 50,
+      .compilation_reason = CompilationReason::kInstall,
+      .compiler_filter = CompilerFilterReporting::kSpeed,
+  };
+
+  text_formatter.FormatBeginReport(200, session_data);
+  text_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u);
+  text_formatter.FormatReportHistogram(DatumId::kFullGcCollectionTime,
+                                       50,
+                                       200,
+                                       {2, 4, 7, 1});
+  text_formatter.FormatEndReport();
+
+  const std::string result = text_formatter.GetAndResetBuffer();
+  ASSERT_EQ(result,
+            "\n*** ART internal metrics ***\n"
+            "  Metadata:\n"
+            "    timestamp_since_start_ms: 200\n"
+            "    session_id: 1000\n"
+            "    uid: 50\n"
+            "    compilation_reason: install\n"
+            "    compiler_filter: speed\n"
+            "  Metrics:\n"
+            "    FullGcCount: count = 1\n"
+            "    FullGcCollectionTime: range = 50...200, buckets: 2,4,7,1\n"
+            "*** Done dumping ART internal metrics ***\n");
+}
+
+TEST(TextFormatterTest, ReportMetrics_NoBuckets) {
+  TextFormatter text_formatter;
+  SessionData session_data {
+      .session_id = 500,
+      .uid = 15,
+      .compilation_reason = CompilationReason::kCmdLine,
+      .compiler_filter = CompilerFilterReporting::kExtract,
+  };
+
+  text_formatter.FormatBeginReport(400, session_data);
+  text_formatter.FormatReportHistogram(DatumId::kFullGcCollectionTime, 10, 20, {});
+  text_formatter.FormatEndReport();
+
+  std::string result = text_formatter.GetAndResetBuffer();
+  ASSERT_EQ(result,
+            "\n*** ART internal metrics ***\n"
+            "  Metadata:\n"
+            "    timestamp_since_start_ms: 400\n"
+            "    session_id: 500\n"
+            "    uid: 15\n"
+            "    compilation_reason: cmdline\n"
+            "    compiler_filter: extract\n"
+            "  Metrics:\n"
+            "    FullGcCollectionTime: range = 10...20, no buckets\n"
+            "*** Done dumping ART internal metrics ***\n");
+}
+
+TEST(TextFormatterTest, BeginReport_NoSessionData) {
+  TextFormatter text_formatter;
+  std::optional<SessionData> empty_session_data;
+
+  text_formatter.FormatBeginReport(100, empty_session_data);
+  text_formatter.FormatEndReport();
+
+  std::string result = text_formatter.GetAndResetBuffer();
+  ASSERT_EQ(result,
+            "\n*** ART internal metrics ***\n"
+            "  Metadata:\n"
+            "    timestamp_since_start_ms: 100\n"
+            "  Metrics:\n"
+            "*** Done dumping ART internal metrics ***\n");
+}
+
+TEST(TextFormatterTest, GetAndResetBuffer_ActuallyResetsBuffer) {
+  TextFormatter text_formatter;
+  std::optional<SessionData> empty_session_data;
+
+  text_formatter.FormatBeginReport(200, empty_session_data);
+  text_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u);
+  text_formatter.FormatEndReport();
+
+  std::string result = text_formatter.GetAndResetBuffer();
+  ASSERT_EQ(result,
+            "\n*** ART internal metrics ***\n"
+            "  Metadata:\n"
+            "    timestamp_since_start_ms: 200\n"
+            "  Metrics:\n"
+            "    FullGcCount: count = 1\n"
+            "*** Done dumping ART internal metrics ***\n");
+
+  text_formatter.FormatBeginReport(300, empty_session_data);
+  text_formatter.FormatReportCounter(DatumId::kFullGcCount, 5u);
+  text_formatter.FormatEndReport();
+
+  result = text_formatter.GetAndResetBuffer();
+  ASSERT_EQ(result,
+            "\n*** ART internal metrics ***\n"
+            "  Metadata:\n"
+            "    timestamp_since_start_ms: 300\n"
+            "  Metrics:\n"
+            "    FullGcCount: count = 5\n"
+            "*** Done dumping ART internal metrics ***\n");
+}
+
+TEST(XmlFormatterTest, ReportMetrics_WithBuckets) {
+  XmlFormatter xml_formatter;
+  SessionData session_data {
+      .session_id = 123,
+      .uid = 456,
+      .compilation_reason = CompilationReason::kFirstBoot,
+      .compiler_filter = CompilerFilterReporting::kSpace,
+  };
+
+  xml_formatter.FormatBeginReport(250, session_data);
+  xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 3u);
+  xml_formatter.FormatReportHistogram(DatumId::kYoungGcCollectionTime,
+                                      300,
+                                      600,
+                                      {1, 5, 3});
+  xml_formatter.FormatEndReport();
+
+  const std::string result = xml_formatter.GetAndResetBuffer();
+  ASSERT_EQ(result,
+            "<art_runtime_metrics>"
+              "<version>1.0</version>"
+              "<metadata>"
+                "<timestamp_since_start_ms>250</timestamp_since_start_ms>"
+                "<session_id>123</session_id>"
+                "<uid>456</uid>"
+                "<compilation_reason>first-boot</compilation_reason>"
+                "<compiler_filter>space</compiler_filter>"
+              "</metadata>"
+              "<metrics>"
+                "<YoungGcCount>"
+                  "<counter_type>count</counter_type>"
+                  "<value>3</value>"
+                "</YoungGcCount>"
+                "<YoungGcCollectionTime>"
+                  "<counter_type>histogram</counter_type>"
+                  "<minimum_value>300</minimum_value>"
+                  "<maximum_value>600</maximum_value>"
+                  "<buckets>"
+                    "<bucket>1</bucket>"
+                    "<bucket>5</bucket>"
+                    "<bucket>3</bucket>"
+                  "</buckets>"
+                "</YoungGcCollectionTime>"
+              "</metrics>"
+            "</art_runtime_metrics>");
+}
+
+TEST(XmlFormatterTest, ReportMetrics_NoBuckets) {
+  XmlFormatter xml_formatter;
+  SessionData session_data {
+      .session_id = 234,
+      .uid = 345,
+      .compilation_reason = CompilationReason::kFirstBoot,
+      .compiler_filter = CompilerFilterReporting::kSpace,
+  };
+
+  xml_formatter.FormatBeginReport(160, session_data);
+  xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 4u);
+  xml_formatter.FormatReportHistogram(DatumId::kYoungGcCollectionTime, 20, 40, {});
+  xml_formatter.FormatEndReport();
+
+  const std::string result = xml_formatter.GetAndResetBuffer();
+  ASSERT_EQ(result,
+            "<art_runtime_metrics>"
+              "<version>1.0</version>"
+              "<metadata>"
+                "<timestamp_since_start_ms>160</timestamp_since_start_ms>"
+                "<session_id>234</session_id>"
+                "<uid>345</uid>"
+                "<compilation_reason>first-boot</compilation_reason>"
+                "<compiler_filter>space</compiler_filter>"
+              "</metadata>"
+              "<metrics>"
+                "<YoungGcCount>"
+                  "<counter_type>count</counter_type>"
+                  "<value>4</value>"
+                "</YoungGcCount>"
+                "<YoungGcCollectionTime>"
+                  "<counter_type>histogram</counter_type>"
+                  "<minimum_value>20</minimum_value>"
+                  "<maximum_value>40</maximum_value>"
+                  "<buckets/>"
+                "</YoungGcCollectionTime>"
+              "</metrics>"
+            "</art_runtime_metrics>");
+}
+
+TEST(XmlFormatterTest, BeginReport_NoSessionData) {
+  XmlFormatter xml_formatter;
+  std::optional<SessionData> empty_session_data;
+
+  xml_formatter.FormatBeginReport(100, empty_session_data);
+  xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 3u);
+  xml_formatter.FormatEndReport();
+
+  std::string result = xml_formatter.GetAndResetBuffer();
+  ASSERT_EQ(result,
+            "<art_runtime_metrics>"
+              "<version>1.0</version>"
+              "<metadata>"
+                "<timestamp_since_start_ms>100</timestamp_since_start_ms>"
+              "</metadata>"
+              "<metrics>"
+                "<YoungGcCount>"
+                  "<counter_type>count</counter_type>"
+                  "<value>3</value>"
+                "</YoungGcCount>"
+              "</metrics>"
+            "</art_runtime_metrics>");
+}
+
+TEST(XmlFormatterTest, GetAndResetBuffer_ActuallyResetsBuffer) {
+  XmlFormatter xml_formatter;
+  std::optional<SessionData> empty_session_data;
+
+  xml_formatter.FormatBeginReport(200, empty_session_data);
+  xml_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u);
+  xml_formatter.FormatEndReport();
+
+  std::string result = xml_formatter.GetAndResetBuffer();
+  ASSERT_EQ(result,
+            "<art_runtime_metrics>"
+              "<version>1.0</version>"
+              "<metadata>"
+                "<timestamp_since_start_ms>200</timestamp_since_start_ms>"
+              "</metadata>"
+              "<metrics>"
+                "<FullGcCount>"
+                  "<counter_type>count</counter_type>"
+                  "<value>1</value>"
+                "</FullGcCount>"
+              "</metrics>"
+            "</art_runtime_metrics>");
+
+  xml_formatter.FormatBeginReport(300, empty_session_data);
+  xml_formatter.FormatReportCounter(DatumId::kFullGcCount, 5u);
+  xml_formatter.FormatEndReport();
+
+  result = xml_formatter.GetAndResetBuffer();
+  ASSERT_EQ(result,
+            "<art_runtime_metrics>"
+              "<version>1.0</version>"
+              "<metadata>"
+                "<timestamp_since_start_ms>300</timestamp_since_start_ms>"
+              "</metadata>"
+              "<metrics>"
+                "<FullGcCount>"
+                  "<counter_type>count</counter_type>"
+                  "<value>5</value>"
+                "</FullGcCount>"
+              "</metrics>"
+            "</art_runtime_metrics>");
 }
 
 TEST(CompilerFilterReportingTest, FromName) {
@@ -400,6 +737,10 @@
             CompilationReason::kCmdLine);
   ASSERT_EQ(CompilationReasonFromName("error"),
             CompilationReason::kError);
+  ASSERT_EQ(CompilationReasonFromName("vdex"),
+            CompilationReason::kVdex);
+  ASSERT_EQ(CompilationReasonFromName("boot-after-mainline-update"),
+            CompilationReason::kBootAfterMainlineUpdate);
 }
 
 TEST(CompilerReason, Name) {
@@ -439,6 +780,10 @@
             "cmdline");
   ASSERT_EQ(CompilationReasonName(CompilationReason::kError),
             "error");
+  ASSERT_EQ(CompilationReasonName(CompilationReason::kVdex),
+            "vdex");
+  ASSERT_EQ(CompilationReasonName(CompilationReason::kBootAfterMainlineUpdate),
+            "boot-after-mainline-update");
 }
 }  // namespace metrics
 }  // namespace art
diff --git a/libartbase/base/metrics/metrics_test.h b/libartbase/base/metrics/metrics_test.h
index 3e8b42a..07b4e9d 100644
--- a/libartbase/base/metrics/metrics_test.h
+++ b/libartbase/base/metrics/metrics_test.h
@@ -58,7 +58,7 @@
 
     uint64_t* counter_value_;
   } backend{&counter_value};
-  counter.Report(&backend);
+  counter.Report({&backend});
   return counter_value;
 }
 
@@ -75,7 +75,7 @@
 
     std::vector<uint32_t>* buckets_;
   } backend{&buckets};
-  histogram.Report(&backend);
+  histogram.Report({&backend});
   return buckets;
 }
 
diff --git a/libartbase/base/safe_copy.cc b/libartbase/base/safe_copy.cc
index ad75aa7..7b0b895 100644
--- a/libartbase/base/safe_copy.cc
+++ b/libartbase/base/safe_copy.cc
@@ -56,10 +56,10 @@
     }
 
     src_iovs[iovecs_used].iov_base = const_cast<char*>(cur);
-    if (!IsAlignedParam(cur, PAGE_SIZE)) {
-      src_iovs[iovecs_used].iov_len = AlignUp(cur, PAGE_SIZE) - cur;
+    if (!IsAlignedParam(cur, kPageSize)) {
+      src_iovs[iovecs_used].iov_len = AlignUp(cur, kPageSize) - cur;
     } else {
-      src_iovs[iovecs_used].iov_len = PAGE_SIZE;
+      src_iovs[iovecs_used].iov_len = kPageSize;
     }
 
     src_iovs[iovecs_used].iov_len = std::min(src_iovs[iovecs_used].iov_len, len);
diff --git a/libartbase/base/safe_copy_test.cc b/libartbase/base/safe_copy_test.cc
index 9f7d409..01ed7cd 100644
--- a/libartbase/base/safe_copy_test.cc
+++ b/libartbase/base/safe_copy_test.cc
@@ -31,7 +31,7 @@
 #if defined(__linux__)
 
 TEST(SafeCopyTest, smoke) {
-  DCHECK_EQ(kPageSize, static_cast<decltype(kPageSize)>(PAGE_SIZE));
+  DCHECK_EQ(kPageSize, static_cast<size_t>(sysconf(_SC_PAGE_SIZE)));
 
   // Map four pages, mark the second one as PROT_NONE, unmap the last one.
   void* map = mmap(nullptr, kPageSize * 4, PROT_READ | PROT_WRITE,
@@ -79,7 +79,7 @@
 }
 
 TEST(SafeCopyTest, alignment) {
-  DCHECK_EQ(kPageSize, static_cast<decltype(kPageSize)>(PAGE_SIZE));
+  DCHECK_EQ(kPageSize, static_cast<size_t>(sysconf(_SC_PAGE_SIZE)));
 
   // Copy the middle of a mapping to the end of another one.
   void* src_map = mmap(nullptr, kPageSize * 3, PROT_READ | PROT_WRITE,
diff --git a/libartbase/base/safe_map.h b/libartbase/base/safe_map.h
index 7ae85d4..fa13fe0 100644
--- a/libartbase/base/safe_map.h
+++ b/libartbase/base/safe_map.h
@@ -49,8 +49,9 @@
   SafeMap() = default;
   SafeMap(const SafeMap&) = default;
   SafeMap(SafeMap&&) noexcept = default;
+  explicit SafeMap(const allocator_type& allocator) : map_(allocator) {}
   explicit SafeMap(const key_compare& cmp, const allocator_type& allocator = allocator_type())
-    : map_(cmp, allocator) {
+      : map_(cmp, allocator) {
   }
 
   Self& operator=(const Self& rhs) {
@@ -149,7 +150,7 @@
 
   template <typename CreateFn>
   V& GetOrCreate(const K& k, CreateFn create) {
-    static_assert(std::is_same_v<V, std::result_of_t<CreateFn()>>,
+    static_assert(std::is_same_v<V, std::invoke_result_t<CreateFn>>,
                   "Argument `create` should return a value of type V.");
     auto lb = lower_bound(k);
     if (lb != end() && !key_comp()(k, lb->first)) {
diff --git a/libartbase/base/scoped_cap.h b/libartbase/base/scoped_cap.h
new file mode 100644
index 0000000..c369821
--- /dev/null
+++ b/libartbase/base/scoped_cap.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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_SCOPED_CAP_H_
+#define ART_LIBARTBASE_BASE_SCOPED_CAP_H_
+
+#include <sys/capability.h>
+
+#include <utility>
+
+#include "android-base/logging.h"
+
+namespace art {
+
+// A wrapper of `cap_t` that automatically calls `cap_free`.
+class ScopedCap {
+ public:
+  explicit ScopedCap(cap_t cap) : cap_(cap) {}
+
+  ScopedCap(const ScopedCap&) = delete;
+  ScopedCap& operator=(const ScopedCap&) = delete;
+  ScopedCap(ScopedCap&& other) noexcept : cap_(std::exchange(other.cap_, nullptr)) {}
+
+  ~ScopedCap() {
+    if (cap_ != nullptr) {
+      if (cap_free(cap_) != 0) {
+        PLOG(ERROR) << "Failed to call cap_free";
+      }
+    }
+  }
+
+  cap_t Get() const { return cap_; }
+
+ private:
+  cap_t cap_;
+};
+
+}  // namespace art
+
+#endif  // ART_LIBARTBASE_BASE_SCOPED_CAP_H_
diff --git a/libartbase/base/scoped_flock.cc b/libartbase/base/scoped_flock.cc
index b16a45a..3f44e25 100644
--- a/libartbase/base/scoped_flock.cc
+++ b/libartbase/base/scoped_flock.cc
@@ -27,7 +27,7 @@
 
 namespace art {
 
-using android::base::StringPrintf;
+using android::base::StringPrintf;  // NOLINT - StringPrintf is actually used
 
 /* static */ ScopedFlock LockedFile::Open(const char* filename, std::string* error_msg) {
   return Open(filename, O_CREAT | O_RDWR, true, error_msg);
diff --git a/libartbase/base/sdk_version.h b/libartbase/base/sdk_version.h
index 07c3c2c..d39aa95 100644
--- a/libartbase/base/sdk_version.h
+++ b/libartbase/base/sdk_version.h
@@ -36,7 +36,8 @@
   kQ     = 29u,
   kR     = 30u,
   kS     = 31u,
-  kT     = 32u,
+  kS_V2  = 32u,
+  kT     = 33u,
   kMax   = std::numeric_limits<uint32_t>::max(),
 };
 
diff --git a/libartbase/base/stl_util.h b/libartbase/base/stl_util.h
index 0ae4fd2..2c9547f 100644
--- a/libartbase/base/stl_util.h
+++ b/libartbase/base/stl_util.h
@@ -278,11 +278,10 @@
   std::optional<RealIter> end_;
 };
 
-template <typename Iter, typename Filter>
-static inline IterationRange<FilterIterator<Iter, Filter>> Filter(
-    IterationRange<Iter> it, Filter cond) {
-  auto end = it.end();
-  auto start = std::find_if(it.begin(), end, cond);
+template <typename BaseRange, typename Filter>
+static inline auto Filter(BaseRange&& range, Filter cond) {
+  auto end = range.end();
+  auto start = std::find_if(range.begin(), end, cond);
   return MakeIterationRange(FilterIterator(start, cond, std::make_optional(end)),
                             FilterIterator(end, cond, std::make_optional(end)));
 }
diff --git a/libartbase/base/stride_iterator.h b/libartbase/base/stride_iterator.h
index 67c0d38..6a7e4be 100644
--- a/libartbase/base/stride_iterator.h
+++ b/libartbase/base/stride_iterator.h
@@ -30,9 +30,9 @@
       typename std::iterator<std::random_access_iterator_tag, T>::difference_type;
 
   StrideIterator(const StrideIterator&) = default;
-  StrideIterator(StrideIterator&&) = default;
+  StrideIterator(StrideIterator&&) noexcept = default;
   StrideIterator& operator=(const StrideIterator&) = default;
-  StrideIterator& operator=(StrideIterator&&) = default;
+  StrideIterator& operator=(StrideIterator&&) noexcept = default;
 
   StrideIterator(T* ptr, size_t stride)
       : ptr_(reinterpret_cast<uintptr_t>(ptr)),
diff --git a/libartbase/base/string_view_cpp20.h b/libartbase/base/string_view_cpp20.h
index 2c11a2f..9bd29d5 100644
--- a/libartbase/base/string_view_cpp20.h
+++ b/libartbase/base/string_view_cpp20.h
@@ -21,18 +21,23 @@
 
 namespace art {
 
-// Replacement functions for std::string_view::starts_with(), ends_with()
-// which shall be available in C++20.
-#if __cplusplus >= 202000L
-#error "When upgrading to C++20, remove this error and file a bug to remove this workaround."
-#endif
+// When this code is only compiled on C++20+, these wrappers can be removed and
+// calling code changed to call the string_view methods directly.
 
 inline bool StartsWith(std::string_view sv, std::string_view prefix) {
+#if !defined(__cpp_lib_starts_ends_with) || __cpp_lib_starts_ends_with < 201711L
   return sv.substr(0u, prefix.size()) == prefix;
+#else
+  return sv.starts_with(prefix);
+#endif
 }
 
 inline bool EndsWith(std::string_view sv, std::string_view suffix) {
+#if !defined(__cpp_lib_starts_ends_with) || __cpp_lib_starts_ends_with < 201711L
   return sv.size() >= suffix.size() && sv.substr(sv.size() - suffix.size()) == suffix;
+#else
+  return sv.ends_with(suffix);
+#endif
 }
 
 }  // namespace art
diff --git a/libartbase/base/systrace.h b/libartbase/base/systrace.h
index 42975d7..6e5e0e0 100644
--- a/libartbase/base/systrace.h
+++ b/libartbase/base/systrace.h
@@ -60,6 +60,7 @@
   }
 
   explicit ScopedTrace(const std::string& name) : ScopedTrace(name.c_str()) {}
+  ScopedTrace(ScopedTrace&&) = default;
 
   ~ScopedTrace() {
     ATraceEnd();
diff --git a/libartbase/base/transform_iterator.h b/libartbase/base/transform_iterator.h
index 062c88b..552f31f 100644
--- a/libartbase/base/transform_iterator.h
+++ b/libartbase/base/transform_iterator.h
@@ -160,7 +160,7 @@
 }
 
 template <typename BaseRange, typename Function>
-auto MakeTransformRange(BaseRange& range, Function f) {
+auto MakeTransformRange(BaseRange&& range, Function f) {
   return MakeIterationRange(MakeTransformIterator(range.begin(), f),
                             MakeTransformIterator(range.end(), f));
 }
diff --git a/libartbase/base/utils.cc b/libartbase/base/utils.cc
index ba62f30..e114e4d 100644
--- a/libartbase/base/utils.cc
+++ b/libartbase/base/utils.cc
@@ -37,6 +37,7 @@
 
 #if defined(__APPLE__)
 #include <crt_externs.h>
+// NOLINTNEXTLINE - inclusion of syscall is dependent on arch
 #include <sys/syscall.h>
 #include "AvailabilityMacros.h"  // For MAC_OS_X_VERSION_MAX_ALLOWED
 #endif
@@ -44,13 +45,14 @@
 #if defined(__BIONIC__)
 // membarrier(2) is only supported for target builds (b/111199492).
 #include <linux/membarrier.h>
+// NOLINTNEXTLINE - inclusion of syscall is dependent on arch
 #include <sys/syscall.h>
 #endif
 
 #if defined(__linux__)
 #include <linux/unistd.h>
+// NOLINTNEXTLINE - inclusion of syscall is dependent on arch
 #include <sys/syscall.h>
-#include <sys/utsname.h>
 #endif
 
 #if defined(_WIN32)
@@ -64,7 +66,7 @@
 
 namespace art {
 
-using android::base::ReadFileToString;
+using android::base::ReadFileToString;  // NOLINT - ReadFileToString is actually used
 using android::base::StringPrintf;
 
 #if defined(__arm__)
@@ -91,7 +93,7 @@
   CHECK_LT(start, limit);
   CHECK_EQ(RoundDown(start, kPageSize), RoundDown(limit - 1, kPageSize)) << "range spans pages";
   // Declare a volatile variable so the compiler does not elide reads from the page being touched.
-  volatile uint8_t v = 0;
+  [[maybe_unused]] volatile uint8_t v = 0;
   for (size_t i = 0; i < attempts; ++i) {
     // Touch page to maximize chance page is resident.
     v = *reinterpret_cast<uint8_t*>(start);
@@ -158,6 +160,17 @@
 
 #endif
 
+#if defined(__linux__)
+bool IsKernelVersionAtLeast(int reqd_major, int reqd_minor) {
+  struct utsname uts;
+  int major, minor;
+  CHECK_EQ(uname(&uts), 0);
+  CHECK_EQ(strcmp(uts.sysname, "Linux"), 0);
+  CHECK_EQ(sscanf(uts.release, "%d.%d:", &major, &minor), 2);
+  return major > reqd_major || (major == reqd_major && minor >= reqd_minor);
+}
+#endif
+
 bool CacheOperationsMaySegFault() {
 #if defined(__linux__) && defined(__aarch64__)
   // Avoid issue on older ARM64 kernels where data cache operations could be classified as writes
@@ -167,18 +180,10 @@
   //
   // This behaviour means we should avoid the dual view JIT on the device. This is just
   // an issue when running tests on devices that have an old kernel.
-  static constexpr int kRequiredMajor = 3;
-  static constexpr int kRequiredMinor = 12;
-  struct utsname uts;
-  int major, minor;
-  if (uname(&uts) != 0 ||
-      strcmp(uts.sysname, "Linux") != 0 ||
-      sscanf(uts.release, "%d.%d", &major, &minor) != 2 ||
-      (major < kRequiredMajor || (major == kRequiredMajor && minor < kRequiredMinor))) {
-    return true;
-  }
-#endif
+  return !IsKernelVersionAtLeast(3, 12);
+#else
   return false;
+#endif
 }
 
 uint32_t GetTid() {
@@ -249,6 +254,9 @@
 template void Split(const std::string_view& s,
                     char separator,
                     std::vector<std::string_view>* out_result);
+template void Split(const std::string_view& s,
+                    char separator,
+                    std::vector<std::string>* out_result);
 
 template <typename Str>
 void Split(const Str& s, char separator, size_t len, Str* out_result) {
diff --git a/libartbase/base/utils.h b/libartbase/base/utils.h
index 0e8231a..f311f09 100644
--- a/libartbase/base/utils.h
+++ b/libartbase/base/utils.h
@@ -31,6 +31,10 @@
 #include "globals.h"
 #include "macros.h"
 
+#if defined(__linux__)
+#include <sys/utsname.h>
+#endif
+
 namespace art {
 
 static inline uint32_t PointerToLowMemUInt32(const void* p) {
@@ -125,6 +129,10 @@
 // Flush CPU caches. Returns true on success, false if flush failed.
 WARN_UNUSED bool FlushCpuCaches(void* begin, void* end);
 
+#if defined(__linux__)
+bool IsKernelVersionAtLeast(int reqd_major, int reqd_minor);
+#endif
+
 // On some old kernels, a cache operation may segfault.
 WARN_UNUSED bool CacheOperationsMaySegFault();
 
@@ -158,6 +166,13 @@
   }
 }
 
+// Forces the compiler to emit a load instruction, but discards the value.
+// Useful when dealing with memory paging.
+template <typename T>
+inline void ForceRead(const T* pointer) {
+  static_cast<void>(*const_cast<volatile T*>(pointer));
+}
+
 // Lookup value for a given key in /proc/self/status. Keys and values are separated by a ':' in
 // the status file. Returns value found on success and "<unknown>" if the key is not found or
 // there is an I/O error.
diff --git a/libartbase/base/zip_archive.cc b/libartbase/base/zip_archive.cc
index 70778e7..ba44096 100644
--- a/libartbase/base/zip_archive.cc
+++ b/libartbase/base/zip_archive.cc
@@ -253,6 +253,24 @@
   return OpenFromFdInternal(fd, /*assume_ownership=*/false, filename, error_msg);
 }
 
+ZipArchive* ZipArchive::OpenFromMemory(const uint8_t* data,
+                                       size_t size,
+                                       const char* filename,
+                                       std::string* error_msg) {
+  DCHECK(filename != nullptr);
+  DCHECK(data != nullptr);
+
+  ZipArchiveHandle handle;
+  const int32_t error = OpenArchiveFromMemory(data, size, filename, &handle);
+  if (error != 0) {
+    *error_msg = std::string(ErrorCodeString(error));
+    CloseArchive(handle);
+    return nullptr;
+  }
+
+  return new ZipArchive(handle);
+}
+
 ZipArchive* ZipArchive::OpenFromFdInternal(int fd,
                                            bool assume_ownership,
                                            const char* filename,
diff --git a/libartbase/base/zip_archive.h b/libartbase/base/zip_archive.h
index 084bfd0..e740c9f 100644
--- a/libartbase/base/zip_archive.h
+++ b/libartbase/base/zip_archive.h
@@ -93,6 +93,10 @@
   static ZipArchive* Open(const char* filename, std::string* error_msg);
   static ZipArchive* OpenFromFd(int fd, const char* filename, std::string* error_msg);
   static ZipArchive* OpenFromOwnedFd(int fd, const char* filename, std::string* error_msg);
+  static ZipArchive* OpenFromMemory(const uint8_t* data,
+                                    size_t size,
+                                    const char* filename,
+                                    std::string* error_msg);
 
   ZipEntry* Find(const char* name, std::string* error_msg) const;
 
diff --git a/libartpalette/Android.bp b/libartpalette/Android.bp
index 773e7c1..2faa926 100644
--- a/libartpalette/Android.bp
+++ b/libartpalette/Android.bp
@@ -58,6 +58,9 @@
         "libbase_headers",
         "jni_headers",
     ],
+    export_header_lib_headers: [
+        "jni_headers",
+    ],
     target: {
         // Targets supporting dlopen build the client library which loads
         // and binds the methods in the libartpalette-system library.
@@ -66,7 +69,7 @@
             runtime_libs: ["libartpalette-system"],
             srcs: ["apex/palette.cc"],
             shared_libs: ["liblog"],
-            version_script: "libartpalette.map.txt",
+            version_script: "libartpalette.map",
         },
         host_linux: {
             header_libs: ["libbase_headers"],
@@ -77,7 +80,7 @@
                     "liblog",
                 ],
             },
-            version_script: "libartpalette.map.txt",
+            version_script: "libartpalette.map",
         },
         // Targets without support for dlopen just use the sources for
         // the system library which actually implements functionality.
@@ -111,6 +114,11 @@
 art_cc_defaults {
     name: "art_libartpalette_tests_defaults",
     srcs: ["apex/palette_test.cc"],
+    target: {
+        android: {
+            static_libs: ["libmodules-utils-build"],
+        },
+    },
 }
 
 // Version of ART gtest `art_libartpalette_tests` bundled with the ART APEX on target.
@@ -138,6 +146,7 @@
     ],
     static_libs: [
         "libartpalette",
+        "libc++fs",
         // libnativehelper_lazy has the glue we need to dlsym the platform-only
         // APIs we need, like JniInvocationInit.
         "libnativehelper_lazy",
diff --git a/libartpalette/apex/palette.cc b/libartpalette/apex/palette.cc
index 75a3878..dadb470 100644
--- a/libartpalette/apex/palette.cc
+++ b/libartpalette/apex/palette.cc
@@ -110,6 +110,8 @@
 
 extern "C" {
 
+// Methods in version 1 API, corresponding to SDK level 31.
+
 palette_status_t PaletteSchedSetPriority(int32_t tid, int32_t java_priority) {
   PaletteSchedSetPriorityMethod m = PaletteLoader::Instance().GetPaletteSchedSetPriorityMethod();
   return m(tid, java_priority);
@@ -218,6 +220,8 @@
   return m(env);
 }
 
+// Methods in version 2 API, corresponding to SDK level 33.
+
 palette_status_t PaletteReportLockContention(JNIEnv* env,
                                              int32_t wait_ms,
                                              const char* filename,
@@ -242,4 +246,13 @@
            thread_name);
 }
 
+// Methods in version 3 API, corresponding to SDK level 34.
+
+palette_status_t PaletteSetTaskProfiles(int32_t tid,
+                                        const char* const profiles[],
+                                        size_t profiles_len) {
+  PaletteSetTaskProfilesMethod m = PaletteLoader::Instance().GetPaletteSetTaskProfilesMethod();
+  return m(tid, profiles, profiles_len);
+}
+
 }  // extern "C"
diff --git a/libartpalette/apex/palette_test.cc b/libartpalette/apex/palette_test.cc
index 16f2838..f2b4858 100644
--- a/libartpalette/apex/palette_test.cc
+++ b/libartpalette/apex/palette_test.cc
@@ -21,6 +21,13 @@
 #include <sys/syscall.h>
 #include <unistd.h>
 
+#include <filesystem>
+
+#ifdef ART_TARGET_ANDROID
+#include "android-modules-utils/sdk_level.h"
+#include "android/api-level.h"
+#endif
+
 #include "gtest/gtest.h"
 #include "nativehelper/JniInvocation.h"                                 \
 
@@ -34,6 +41,17 @@
 #endif  // __BIONIC__
 }
 
+#ifdef ART_TARGET_ANDROID
+bool PaletteSetTaskProfilesIsSupported(palette_status_t res) {
+  if (android::modules::sdklevel::IsAtLeastU()) {
+    return true;
+  }
+  EXPECT_EQ(PALETTE_STATUS_NOT_SUPPORTED, res)
+      << "Device API level: " << android_get_device_api_level();
+  return false;
+}
+#endif
+
 }  // namespace
 
 class PaletteClientTest : public testing::Test {};
@@ -104,3 +122,49 @@
   EXPECT_EQ(JNI_OK, jvm->DestroyJavaVM());
 #endif
 }
+
+TEST_F(PaletteClientTest, SetTaskProfiles) {
+#ifndef ART_TARGET_ANDROID
+  GTEST_SKIP() << "SetTaskProfiles is only supported on Android";
+#else
+  if (!std::filesystem::exists("/sys/fs/cgroup/cgroup.controllers")) {
+    // This is intended to detect ART chroot setups, where SetTaskProfiles won't work.
+    GTEST_SKIP() << "Kernel cgroup support missing";
+  }
+
+  const char* profiles[] = {"ProcessCapacityHigh", "TimerSlackNormal"};
+  palette_status_t res = PaletteSetTaskProfiles(GetTid(), &profiles[0], 2);
+  if (PaletteSetTaskProfilesIsSupported(res)) {
+    // SetTaskProfiles will only work fully if we run as root. Otherwise it'll
+    // return false which is mapped to PALETTE_STATUS_FAILED_CHECK_LOG.
+    if (getuid() == 0) {
+      EXPECT_EQ(PALETTE_STATUS_OK, res);
+    } else {
+      EXPECT_EQ(PALETTE_STATUS_FAILED_CHECK_LOG, res);
+    }
+  }
+#endif
+}
+
+TEST_F(PaletteClientTest, SetTaskProfilesCpp) {
+#ifndef ART_TARGET_ANDROID
+  GTEST_SKIP() << "SetTaskProfiles is only supported on Android";
+#else
+  if (!std::filesystem::exists("/sys/fs/cgroup/cgroup.controllers")) {
+    // This is intended to detect ART chroot setups, where SetTaskProfiles won't work.
+    GTEST_SKIP() << "Kernel cgroup support missing";
+  }
+
+  std::vector<std::string> profiles = {"ProcessCapacityHigh", "TimerSlackNormal"};
+  palette_status_t res = PaletteSetTaskProfiles(GetTid(), profiles);
+  if (PaletteSetTaskProfilesIsSupported(res)) {
+    // SetTaskProfiles will only work fully if we run as root. Otherwise it'll
+    // return false which is mapped to PALETTE_STATUS_FAILED_CHECK_LOG.
+    if (getuid() == 0) {
+      EXPECT_EQ(PALETTE_STATUS_OK, res);
+    } else {
+      EXPECT_EQ(PALETTE_STATUS_FAILED_CHECK_LOG, res);
+    }
+  }
+#endif
+}
diff --git a/libartpalette/include/palette/palette.h b/libartpalette/include/palette/palette.h
index 3e12b14..e9e47e9 100644
--- a/libartpalette/include/palette/palette.h
+++ b/libartpalette/include/palette/palette.h
@@ -17,11 +17,11 @@
 #ifndef ART_LIBARTPALETTE_INCLUDE_PALETTE_PALETTE_H_
 #define ART_LIBARTPALETTE_INCLUDE_PALETTE_PALETTE_H_
 
+#include <sys/cdefs.h>
+
 #include "palette_types.h"
 
-#ifdef __cplusplus
-extern "C" {
-#endif  // __cplusplus
+__BEGIN_DECLS
 
 // Palette method signatures are defined in palette_method_list.h.
 
@@ -31,8 +31,25 @@
 PALETTE_METHOD_LIST(PALETTE_METHOD_DECLARATION)
 #undef PALETTE_METHOD_DECLARATION
 
+__END_DECLS
+
+// C++ wrappers
+
 #ifdef __cplusplus
+
+#include <string>
+#include <vector>
+
+static inline palette_status_t PaletteSetTaskProfiles(int tid,
+                                                      const std::vector<std::string>& profiles) {
+  std::vector<const char*> profile_c_strs;
+  profile_c_strs.reserve(profiles.size());
+  for (const std::string& p : profiles) {
+    profile_c_strs.push_back(p.c_str());
+  }
+  return PaletteSetTaskProfiles(tid, profile_c_strs.data(), profile_c_strs.size());
 }
+
 #endif  // __cplusplus
 
 #endif  // ART_LIBARTPALETTE_INCLUDE_PALETTE_PALETTE_H_
diff --git a/libartpalette/include/palette/palette_method_list.h b/libartpalette/include/palette/palette_method_list.h
index 066f24f..e22e828 100644
--- a/libartpalette/include/palette/palette_method_list.h
+++ b/libartpalette/include/palette/palette_method_list.h
@@ -23,45 +23,58 @@
 
 #include "jni.h"
 
-// Methods in version 1 API
-#define PALETTE_METHOD_LIST(M)                                              \
-  M(PaletteSchedSetPriority, int32_t tid, int32_t java_priority)            \
-  M(PaletteSchedGetPriority, int32_t tid, /*out*/int32_t* java_priority)    \
-  M(PaletteWriteCrashThreadStacks, const char* stacks, size_t stacks_len)   \
-  M(PaletteTraceEnabled, /*out*/bool* enabled)                              \
-  M(PaletteTraceBegin, const char* name)                                    \
-  M(PaletteTraceEnd)                                                        \
-  M(PaletteTraceIntegerValue, const char* name, int32_t value)              \
-  M(PaletteAshmemCreateRegion, const char* name, size_t size, int* fd)      \
-  M(PaletteAshmemSetProtRegion, int, int)                                   \
-  /* Create the staging directory for on-device signing.                 */ \
-  /* `staging_dir` is updated to point to a constant string in the       */ \
-  /* palette implementation.                                             */ \
-  /* This method is not thread-safe.                                     */ \
-  M(PaletteCreateOdrefreshStagingDirectory, /*out*/const char** staging_dir)\
-  M(PaletteShouldReportDex2oatCompilation, bool*)                           \
-  M(PaletteNotifyStartDex2oatCompilation, int source_fd,                    \
-                                          int art_fd,                       \
-                                          int oat_fd,                       \
-                                          int vdex_fd)                      \
-  M(PaletteNotifyEndDex2oatCompilation, int source_fd,                      \
-                                        int art_fd,                         \
-                                        int oat_fd,                         \
-                                        int vdex_fd)                        \
-  M(PaletteNotifyDexFileLoaded, const char* path)                           \
-  M(PaletteNotifyOatFileLoaded, const char* path)                           \
-  M(PaletteShouldReportJniInvocations, bool*)                               \
-  M(PaletteNotifyBeginJniInvocation, JNIEnv* env)                           \
-  M(PaletteNotifyEndJniInvocation, JNIEnv* env)                             \
-  M(PaletteReportLockContention, JNIEnv* env,                               \
-                                 int32_t wait_ms,                           \
-                                 const char* filename,                      \
-                                 int32_t line_number,                       \
-                                 const char* method_name,                   \
-                                 const char* owner_filename,                \
-                                 int32_t owner_line_number,                 \
-                                 const char* owner_method_name,             \
-                                 const char* proc_name,                     \
-                                 const char* thread_name)                   \
+#define PALETTE_METHOD_LIST(M)                                                                \
+  /* Methods in version 1 API, corresponding to SDK level 31. */                              \
+  M(PaletteSchedSetPriority, int32_t tid, int32_t java_priority)                              \
+  M(PaletteSchedGetPriority, int32_t tid, /*out*/ int32_t* java_priority)                     \
+  M(PaletteWriteCrashThreadStacks, const char* stacks, size_t stacks_len)                     \
+  M(PaletteTraceEnabled, /*out*/ bool* enabled)                                               \
+  M(PaletteTraceBegin, const char* name)                                                      \
+  M(PaletteTraceEnd)                                                                          \
+  M(PaletteTraceIntegerValue, const char* name, int32_t value)                                \
+  M(PaletteAshmemCreateRegion, const char* name, size_t size, int* fd)                        \
+  M(PaletteAshmemSetProtRegion, int, int)                                                     \
+  /* Create the staging directory for on-device signing.           */                         \
+  /* `staging_dir` is updated to point to a constant string in the */                         \
+  /* palette implementation.                                       */                         \
+  /* This method is not thread-safe.                               */                         \
+  M(PaletteCreateOdrefreshStagingDirectory, /*out*/ const char** staging_dir)                 \
+  M(PaletteShouldReportDex2oatCompilation, bool*)                                             \
+  M(PaletteNotifyStartDex2oatCompilation, int source_fd, int art_fd, int oat_fd, int vdex_fd) \
+  M(PaletteNotifyEndDex2oatCompilation, int source_fd, int art_fd, int oat_fd, int vdex_fd)   \
+  M(PaletteNotifyDexFileLoaded, const char* path)                                             \
+  M(PaletteNotifyOatFileLoaded, const char* path)                                             \
+  M(PaletteShouldReportJniInvocations, bool*)                                                 \
+  M(PaletteNotifyBeginJniInvocation, JNIEnv* env)                                             \
+  M(PaletteNotifyEndJniInvocation, JNIEnv* env)                                               \
+                                                                                              \
+  /* Methods in version 2 API, corresponding to SDK level 33. */                              \
+  M(PaletteReportLockContention,                                                              \
+    JNIEnv* env,                                                                              \
+    int32_t wait_ms,                                                                          \
+    const char* filename,                                                                     \
+    int32_t line_number,                                                                      \
+    const char* method_name,                                                                  \
+    const char* owner_filename,                                                               \
+    int32_t owner_line_number,                                                                \
+    const char* owner_method_name,                                                            \
+    const char* proc_name,                                                                    \
+    const char* thread_name)                                                                  \
+                                                                                              \
+  /* Methods in version 3 API, corresponding to SDK level 34. */                              \
+                                                                                              \
+  /* Calls through to SetTaskProfiles in libprocessgroup to set the */                        \
+  /* [task profile](https:/-/source.android.com/docs/core/perf/cgroups#task-profiles-file) */ \
+  /* for the given thread id. */                                                              \
+  /* */                                                                                       \
+  /* @param tid The thread id. */                                                             \
+  /* @param profiles An array of pointers to C strings that list the task profiles to set. */ \
+  /* @param profiles_len The number of elements in profiles. */                               \
+  /* @return PALETTE_STATUS_OK if the call succeeded. */                                      \
+  /*         PALETTE_STATUS_FAILED_CHECK_LOG if it failed. */                                 \
+  /*         PALETTE_STATUS_NOT_SUPPORTED if the implementation no longer supports this */    \
+  /*         call. This can happen at any future SDK level since this function wraps an */    \
+  /*         internal unstable API. */                                                        \
+  M(PaletteSetTaskProfiles, int32_t tid, const char* const profiles[], size_t profiles_len)
 
 #endif  // ART_LIBARTPALETTE_INCLUDE_PALETTE_PALETTE_METHOD_LIST_H_
diff --git a/libartpalette/include/palette/palette_types.h b/libartpalette/include/palette/palette_types.h
index 905a341..3c02544 100644
--- a/libartpalette/include/palette/palette_types.h
+++ b/libartpalette/include/palette/palette_types.h
@@ -23,7 +23,7 @@
 extern "C" {
 #endif  // __cplusplus
 
-typedef int32_t palette_status_t;
+using palette_status_t = int32_t;
 
 // Palette function return value when the function completed successfully.
 #define PALETTE_STATUS_OK                 ((palette_status_t) 0)
diff --git a/libartpalette/libartpalette.map b/libartpalette/libartpalette.map
new file mode 100644
index 0000000..11824cb
--- /dev/null
+++ b/libartpalette/libartpalette.map
@@ -0,0 +1,53 @@
+#
+# Copyright (C) 2019 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.
+#
+
+LIBARTPALETTE_1 { # introduced=31
+  global:
+    # --- VERSION 01 API ---
+    PaletteSchedSetPriority; # apex
+    PaletteSchedGetPriority; # apex
+    PaletteWriteCrashThreadStacks; # apex
+    PaletteTraceEnabled; # apex
+    PaletteTraceBegin; # apex
+    PaletteTraceEnd; # apex
+    PaletteTraceIntegerValue; # apex
+    PaletteAshmemCreateRegion; # apex
+    PaletteAshmemSetProtRegion; # apex
+    PaletteCreateOdrefreshStagingDirectory; # apex
+    PaletteShouldReportDex2oatCompilation; # apex
+    PaletteNotifyStartDex2oatCompilation; # apex
+    PaletteNotifyEndDex2oatCompilation; # apex
+    PaletteNotifyDexFileLoaded; # apex
+    PaletteNotifyOatFileLoaded; # apex
+    PaletteShouldReportJniInvocations; # apex
+    PaletteNotifyBeginJniInvocation; # apex
+    PaletteNotifyEndJniInvocation; # apex
+
+  local:
+    *;
+};
+
+LIBARTPALETTE_2 { # introduced=33
+  global:
+    # --- VERSION 02 API ---
+    PaletteReportLockContention; # apex
+} LIBARTPALETTE_1;
+
+LIBARTPALETTE_3 { # introduced=UpsideDownCake
+  global:
+    # --- VERSION 03 API ---
+    PaletteSetTaskProfiles; # apex
+} LIBARTPALETTE_2;
diff --git a/libartpalette/libartpalette.map.txt b/libartpalette/libartpalette.map.txt
deleted file mode 100644
index 6401010..0000000
--- a/libartpalette/libartpalette.map.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# Copyright (C) 2019 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.
-#
-
-LIBARTPALETTE_1 {
-  global:
-    # --- VERSION 01 API ---
-    PaletteSchedSetPriority; # apex
-    PaletteSchedGetPriority; # apex
-    PaletteWriteCrashThreadStacks; #apex
-    PaletteTraceEnabled; # apex
-    PaletteTraceBegin; # apex
-    PaletteTraceEnd; # apex
-    PaletteTraceIntegerValue; # apex
-    PaletteAshmemCreateRegion; # apex
-    PaletteAshmemSetProtRegion; # apex
-    PaletteCreateOdrefreshStagingDirectory; # apex
-    PaletteShouldReportDex2oatCompilation; #apex
-    PaletteNotifyStartDex2oatCompilation; #apex
-    PaletteNotifyEndDex2oatCompilation; #apex
-    PaletteNotifyDexFileLoaded; #apex
-    PaletteNotifyOatFileLoaded; #apex
-    PaletteShouldReportJniInvocations; #apex
-    PaletteNotifyBeginJniInvocation; #apex
-    PaletteNotifyEndJniInvocation; #apex
-    PaletteReportLockContention; #apex
-
-  local:
-    *;
-};
diff --git a/libartpalette/system/palette_fake.cc b/libartpalette/system/palette_fake.cc
index bbf8f2d..743a4db 100644
--- a/libartpalette/system/palette_fake.cc
+++ b/libartpalette/system/palette_fake.cc
@@ -25,6 +25,8 @@
 
 #include "palette_system.h"
 
+// Methods in version 1 API, corresponding to SDK level 31.
+
 // Cached thread priority for testing. No thread priorities are ever affected.
 static std::mutex g_tid_priority_map_mutex;
 static std::map<int32_t, int32_t> g_tid_priority_map;
@@ -129,6 +131,8 @@
   return PALETTE_STATUS_OK;
 }
 
+// Methods in version 2 API, corresponding to SDK level 33.
+
 palette_status_t PaletteReportLockContention(JNIEnv* env ATTRIBUTE_UNUSED,
                                              int32_t wait_ms ATTRIBUTE_UNUSED,
                                              const char* filename ATTRIBUTE_UNUSED,
@@ -141,3 +145,11 @@
                                              const char* thread_name ATTRIBUTE_UNUSED) {
   return PALETTE_STATUS_OK;
 }
+
+// Methods in version 3 API, corresponding to SDK level 34.
+
+palette_status_t PaletteSetTaskProfiles(int32_t tid ATTRIBUTE_UNUSED,
+                                        const char* const profiles[] ATTRIBUTE_UNUSED,
+                                        size_t profiles_len ATTRIBUTE_UNUSED) {
+  return PALETTE_STATUS_OK;
+}
diff --git a/libartservice/Android.bp b/libartservice/Android.bp
deleted file mode 100644
index 985a2eb..0000000
--- a/libartservice/Android.bp
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (C) 2021 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.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "art_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["art_license"],
-}
-
-cc_library {
-    // This native library contains JNI support code for the ART Service Java
-    // Language library.
-
-    name: "libartservice",
-    defaults: ["art_defaults"],
-    host_supported: true,
-    srcs: [
-        "service/native/service.cc",
-    ],
-    export_include_dirs: ["."],
-    apex_available: [
-        "com.android.art",
-        "com.android.art.debug",
-    ],
-    shared_libs: [
-        "libbase",
-    ],
-    export_shared_lib_headers: ["libbase"],
-}
-
-// Provides the API and implementation of the ART Service class that will be
-// loaded by the System Server.
-java_sdk_library {
-    // This target is named 'service-art' to conform to the naming conventions
-    // for JAR files in the System Server.
-    name: "service-art",
-    defaults: ["framework-system-server-module-defaults"],
-
-    permitted_packages: ["com.android.server.art"],
-
-    visibility: [
-        "//art:__subpackages__",
-        "//frameworks/base/services/core",
-    ],
-
-    impl_library_visibility: [
-        "//art/libartservice/tests",
-    ],
-
-    stubs_library_visibility: ["//visibility:public"],
-    stubs_source_visibility: ["//visibility:private"],
-
-    apex_available: [
-        "com.android.art",
-        "com.android.art.debug",
-    ],
-    sdk_version: "core_platform",
-    min_sdk_version: "31",
-
-    public: {
-        // Override the setting of "module_current" from the defaults as that is
-        // not available in all manifests where this needs to be built.
-        sdk_version: "core_current",
-    },
-
-    system_server: {
-        // Override the setting of "module_current" from the defaults as that is
-        // not available in all manifests where this needs to be built.
-        sdk_version: "core_current",
-    },
-
-    // The API elements are the ones annotated with
-    //   libcore.api.CorePlatformApi(status=libcore.api.CorePlatformApi.Status.STABLE)
-    droiddoc_options: [
-        "--show-single-annotation libcore.api.CorePlatformApi\\(status=libcore.api.CorePlatformApi.Status.STABLE\\)",
-    ],
-
-    // Temporarily disable compatibility with previous released APIs.
-    // TODO - remove once prototype has stabilized
-    //   running "m update-api" will give instructions on what to do next
-    unsafe_ignore_missing_latest_api: true,
-
-    // This cannot be accessed by apps using <uses-library> in their manifest.
-    shared_library: false,
-    // TODO(b/188773212): force dex compilation for inclusion in bootclasspath_fragment.
-    compile_dex: true,
-
-    srcs: [
-        "service/java/com/android/server/art/ArtManagerLocal.java",
-    ],
-
-    libs: [
-        "art.module.api.annotations.for.system.modules",
-    ],
-
-    plugins: ["java_api_finder"],
-    dist_group: "android",
-}
-
-art_cc_defaults {
-    name: "art_libartservice_tests_defaults",
-    srcs: [
-        "service/native/service_test.cc",
-    ],
-    shared_libs: [
-        "libbase",
-        "libartservice",
-    ],
-}
-
-// Version of ART gtest `art_libartservice_tests` bundled with the ART APEX on target.
-// TODO(b/192274705): Remove this module when the migration to standalone ART gtests is complete.
-art_cc_test {
-    name: "art_libartservice_tests",
-    defaults: [
-        "art_gtest_defaults",
-        "art_libartservice_tests_defaults",
-    ],
-}
-
-// Standalone version of ART gtest `art_libartservice_tests`, not bundled with the ART APEX on
-// target.
-art_cc_test {
-    name: "art_standalone_libartservice_tests",
-    defaults: [
-        "art_standalone_gtest_defaults",
-        "art_libartservice_tests_defaults",
-    ],
-}
diff --git a/libartservice/api/current.txt b/libartservice/api/current.txt
deleted file mode 100644
index c7844e0..0000000
--- a/libartservice/api/current.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-// Signature format: 2.0
-package com.android.server.art {
-
-  public final class ArtManagerLocal {
-    ctor public ArtManagerLocal();
-  }
-
-}
-
diff --git a/libartservice/api/system-server-current.txt b/libartservice/api/system-server-current.txt
deleted file mode 100644
index c7844e0..0000000
--- a/libartservice/api/system-server-current.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-// Signature format: 2.0
-package com.android.server.art {
-
-  public final class ArtManagerLocal {
-    ctor public ArtManagerLocal();
-  }
-
-}
-
diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp
new file mode 100644
index 0000000..ad2033a
--- /dev/null
+++ b/libartservice/service/Android.bp
@@ -0,0 +1,239 @@
+// Copyright (C) 2021 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.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// This native library contains JNI support code for the ART Service Java
+// Language library.
+cc_defaults {
+    name: "libartservice_defaults",
+    defaults: ["art_defaults"],
+    host_supported: true,
+    srcs: [
+        "native/service.cc",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+}
+
+cc_library {
+    name: "libartservice",
+    defaults: ["libartservice_defaults"],
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+    shared_libs: [
+    ],
+}
+
+cc_library {
+    name: "libartserviced",
+    defaults: [
+        "libartservice_defaults",
+        "art_debug_defaults",
+    ],
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+    shared_libs: [
+    ],
+}
+
+java_defaults {
+    name: "service-art-defaults",
+    defaults: [
+        "framework-system-server-module-defaults",
+    ],
+    sdk_version: "system_server_current",
+    min_sdk_version: "31",
+    srcs: [
+        "java/**/*.java",
+    ],
+    libs: [
+        "androidx.annotation_annotation",
+        "auto_value_annotations",
+        "sdk_module-lib_current_framework-permission-s",
+        // TODO(b/256866172): Transitive dependency, for r8 only.
+        "framework-statsd.stubs.module_lib",
+        // TODO(b/256866172): Transitive dependency, for r8 only. This module
+        // always refers to the jar in prebuilts/sdk. We can't use
+        // "framework-connectivity.stubs.module_lib" here because it's not
+        // available on master-art.
+        "sdk_module-lib_current_framework-connectivity",
+    ],
+    static_libs: [
+        "art-statslog-art-java",
+        "artd-aidl-java",
+        "modules-utils-package-state",
+        "modules-utils-shell-command-handler",
+        "service-art-proto-java",
+    ],
+    plugins: [
+        "auto_value_plugin",
+    ],
+}
+
+// Used by tests to allow tests to mock the right classes.
+java_library {
+    name: "service-art-pre-jarjar",
+    defaults: ["service-art-defaults"],
+    installable: false,
+    visibility: [
+        "//visibility:override",
+        "//visibility:private",
+    ],
+}
+
+// Provides the API and implementation of the ART Service class that will be
+// loaded by the System Server.
+java_sdk_library {
+    // This target is named 'service-art' to conform to the naming conventions
+    // for JAR files in the System Server.
+    name: "service-art",
+    defaults: [
+        "service-art-defaults",
+        "framework-system-server-module-optimize-defaults",
+    ],
+    permitted_packages: ["com.android.server.art"],
+    visibility: [
+        "//art:__subpackages__",
+        "//frameworks/base/services/core",
+    ],
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+    jarjar_rules: "jarjar-rules.txt",
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
+}
+
+java_library {
+    name: "service-art-proto-java",
+    proto: {
+        type: "lite",
+    },
+    srcs: [
+        "proto/**/*.proto",
+    ],
+    sdk_version: "system_server_current",
+    min_sdk_version: "31",
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
+java_library {
+    name: "art-statslog-art-java",
+    srcs: [
+        ":art-statslog-art-java-gen",
+    ],
+    libs: [
+        "framework-statsd.stubs.module_lib",
+    ],
+    sdk_version: "system_server_current",
+    min_sdk_version: "31",
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
+genrule {
+    name: "art-statslog-art-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module art --javaPackage com.android.server.art --javaClass ArtStatsLog",
+    out: ["java/com/android/server/art/ArtStatsLog.java"],
+}
+
+art_cc_defaults {
+    name: "art_libartservice_tests_defaults",
+    defaults: ["libartservice_defaults"],
+    srcs: [
+        "native/service_test.cc",
+    ],
+}
+
+// Version of ART gtest `art_libartservice_tests` bundled with the ART APEX on target.
+// TODO(b/192274705): Remove this module when the migration to standalone ART gtests is complete.
+art_cc_test {
+    name: "art_libartservice_tests",
+    defaults: [
+        "art_gtest_defaults",
+        "art_libartservice_tests_defaults",
+    ],
+}
+
+// Standalone version of ART gtest `art_libartservice_tests`, not bundled with the ART APEX on
+// target.
+art_cc_test {
+    name: "art_standalone_libartservice_tests",
+    defaults: [
+        "art_standalone_gtest_defaults",
+        "art_libartservice_tests_defaults",
+    ],
+}
+
+android_test {
+    name: "ArtServiceTests",
+
+    // Include all test java files.
+    srcs: [
+        "javatests/**/*.java",
+    ],
+
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+        "androidx.test.runner",
+        "artd-aidl-java",
+        "framework-annotations-lib",
+        // We need ExtendedMockito to mock static methods.
+        "mockito-target-extended-minus-junit4",
+        "modules-utils-package-state",
+        "service-art-pre-jarjar",
+        // Statically link against system server to allow us to mock system
+        // server APIs. This won't work on master-art, but it's fine because we
+        // don't run this test on master-art.
+        "services.core",
+    ],
+
+    jni_libs: [
+        // The two libraries below are required by ExtendedMockito.
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    compile_multilib: "both",
+
+    // TODO: This module should move to sdk_version: "system_server_current" when possible,
+    //   as this will restrict the APIs available to just that expected system API. For now,
+    //   a compileOnly / runtimeOnly split for dependencies doesn't exist in the build system
+    //   and so it's not possible to enforce.
+    min_sdk_version: "31",
+
+    test_suites: ["general-tests"],
+    test_config: "ArtServiceTests.xml",
+}
diff --git a/libartservice/service/AndroidManifest.xml b/libartservice/service/AndroidManifest.xml
new file mode 100644
index 0000000..1c13fc6
--- /dev/null
+++ b/libartservice/service/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.art.tests">
+
+    <!-- android:debuggable is required by ExtendedMockito. -->
+    <application android:label="ArtServiceTests" android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.art.tests"
+        android:label="Tests for ART Serices" />
+</manifest>
diff --git a/libartservice/service/ArtServiceTests.xml b/libartservice/service/ArtServiceTests.xml
new file mode 100644
index 0000000..7a47ca3
--- /dev/null
+++ b/libartservice/service/ArtServiceTests.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<configuration description="Config for ART Services test cases">
+    <option name="test-suite-tag" value="apct" />
+
+    <!-- This test needs access to system APIs for mainline modules. -->
+    <option name="hidden-api-checks" value="false"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="ArtServiceTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.server.art.tests"/>
+    </test>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <!-- TODO(jiakaiz): Change this to U once `ro.build.version.sdk` is bumped. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/libartservice/service/README.md b/libartservice/service/README.md
new file mode 100644
index 0000000..028f5f5
--- /dev/null
+++ b/libartservice/service/README.md
@@ -0,0 +1,104 @@
+## ART Service
+
+Warning: The contents in this doc can become stale while the code evolves.
+
+ART Service manages dexopt artifacts of apps. With ART Service, you can dexopt
+apps, query their dexopt status (the compiler filter, the compilation reason,
+whether the dexopt artifacts are up-to-date, etc.), and delete dexopt artifacts.
+
+Note: ART Service is introduced since Android U. Prior to ART Service, dexopt
+artifacts were managed by Package Manager with a legacy implementation. ART
+Service is the default on Android U, while partners are allowed to opt-out and
+use the legacy implementation instead. The legacy implementation will be removed
+since Android V. This doc only describes ART Service, not the legacy
+implementation.
+
+### Primary dex vs. secondary dex
+
+ART Service dexopts both primary dex files and secondary dex files of an app.
+
+A primary dex file refers to the base APK or a split APK of an app. It's
+installed by Package Manager or shipped as a part of the system image, and it's
+loaded by Framework on app startup.
+
+A secondary dex file refers to an APK or JAR file that an app adds to its own
+data directory and loads dynamically.
+
+Note: Strictly speaking, an APK/JAR file is not a DEX file. It is a ZIP file
+that contain one or more DEX files. However, it is called a *dex file*
+conventionally.
+
+### Compiler filters
+
+See
+[Compilation options](https://source.android.com/docs/core/runtime/configure#compilation_options).
+
+### Dexopt scenarios
+
+At a high level, ART Service dexopts apps in the following senarios:
+
+-   the device is on the very first boot
+-   the device is on the first boot after an OTA update
+-   the device is on the first boot after a mainline update
+-   an app is being installed
+-   the device is idle and charging
+
+Tip: The compilation reason, stored in the header of the OAT file, shows the
+senario, in which the app is dexopted.
+
+The sections below describe the default behavior in each senario. Note that the
+list of apps to dexopt and the compiler filter, as well as other options, can be
+customized by partners through a callback.
+
+#### On the very first boot / the first boot after an OTA update
+
+On the very first boot / the first boot after an OTA update, ART Service only
+dexopts primary dex files of all apps with the "verify" compiler filter.
+
+Note: It doesn't dexopt secondary dex files or use the "speed-profile" filter
+because doing so may block the boot for too long.
+
+Note: In practice, ART Service does nothing for most of the apps because the
+apps on the system partitions have dexopt artifacts generated by dexpreopt, and
+the apps on the data partition still have VDEX files usable even though their
+dependencies (bootclasspath and class loader context) have probably changed. In
+this senario, ART Service mostly dexopt apps in APEXes because they are not
+supported by dexpreopt.
+
+#### On the first boot after a mainline update
+
+On the first boot after a mainline update, ART Service dexopts the primary dex
+files of the system UI and the launcher. It uses the "speed" compiler filter for
+the system UI, and uses the "speed-profile" compiler filter for the launcher.
+
+Note: It only dexopts those two apps because they are important to user
+experience.
+
+Note: ART Service cannot use the "speed-profile" compiler filter for the system
+UI because the system UI is dexpreopted using the "speed" compiler filter and
+therefore it's never JITed and as a result there is no profile collected on the
+device to use. This may change in the future.
+
+#### During app installation
+
+During app installation, ART Service dexopts the primary dex files of the app.
+If the app is installed along with a DM file that contains a profile (known as a
+*cloud profile*), it uses the "speed-profile" compiler filter. Otherwise, it
+uses the "verify" compiler filter.
+
+Note: If the APK is uncompressed and aligned, and it is installed along with a
+DM file that only contains a VDEX file (but not a profile), no dexopt will be
+performed because the compiler filter will be "verify" and the VDEX file is
+satisfactory.
+
+Note: There is no secondary dex file present during installation.
+
+#### When the device is idle and charging
+
+ART Service has a job called *background dexopt job* managed by Job Scheduler.
+It is triggered when the device is idle and charging. During the job execution,
+it dexopts primary dex files and secondary dex files of all apps with the
+"speed-profile" compiler filter.
+
+The job is cancellable. When the device is no longer idle or charging, Job
+Scheduler cancels the job.
diff --git a/libartservice/api/removed.txt b/libartservice/service/api/current.txt
similarity index 100%
copy from libartservice/api/removed.txt
copy to libartservice/service/api/current.txt
diff --git a/libartservice/api/removed.txt b/libartservice/service/api/removed.txt
similarity index 100%
rename from libartservice/api/removed.txt
rename to libartservice/service/api/removed.txt
diff --git a/libartservice/service/api/system-server-current.txt b/libartservice/service/api/system-server-current.txt
new file mode 100644
index 0000000..a9cd65a
--- /dev/null
+++ b/libartservice/service/api/system-server-current.txt
@@ -0,0 +1,185 @@
+// Signature format: 2.0
+package com.android.server.art {
+
+  public final class ArtManagerLocal {
+    ctor @Deprecated public ArtManagerLocal();
+    ctor public ArtManagerLocal(@NonNull android.content.Context);
+    method public void addDexoptDoneCallback(boolean, @NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.DexoptDoneCallback);
+    method public void cancelBackgroundDexoptJob();
+    method @NonNull public void clearAppProfiles(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
+    method public void clearBatchDexoptStartCallback();
+    method public void clearScheduleBackgroundDexoptJobCallback();
+    method @NonNull public com.android.server.art.model.DeleteResult deleteDexoptArtifacts(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
+    method @NonNull public com.android.server.art.model.DexoptResult dexoptPackage(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull com.android.server.art.model.DexoptParams);
+    method @NonNull public com.android.server.art.model.DexoptResult dexoptPackage(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull com.android.server.art.model.DexoptParams, @NonNull android.os.CancellationSignal);
+    method public void dump(@NonNull java.io.PrintWriter, @NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot);
+    method public void dumpPackage(@NonNull java.io.PrintWriter, @NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
+    method @NonNull public com.android.server.art.model.DexoptStatus getDexoptStatus(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
+    method @NonNull public com.android.server.art.model.DexoptStatus getDexoptStatus(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, int);
+    method public int handleShellCommand(@NonNull android.os.Binder, @NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull String[]);
+    method public void onBoot(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<com.android.server.art.model.OperationProgress>);
+    method public void printShellCommandHelp(@NonNull java.io.PrintWriter);
+    method public void removeDexoptDoneCallback(@NonNull com.android.server.art.ArtManagerLocal.DexoptDoneCallback);
+    method public int scheduleBackgroundDexoptJob();
+    method public void setBatchDexoptStartCallback(@NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.BatchDexoptStartCallback);
+    method public void setScheduleBackgroundDexoptJobCallback(@NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.ScheduleBackgroundDexoptJobCallback);
+    method @NonNull public android.os.ParcelFileDescriptor snapshotAppProfile(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @Nullable String) throws com.android.server.art.ArtManagerLocal.SnapshotProfileException;
+    method @NonNull public android.os.ParcelFileDescriptor snapshotBootImageProfile(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot) throws com.android.server.art.ArtManagerLocal.SnapshotProfileException;
+    method public void startBackgroundDexoptJob();
+    method public void unscheduleBackgroundDexoptJob();
+  }
+
+  public static interface ArtManagerLocal.BatchDexoptStartCallback {
+    method public void onBatchDexoptStart(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull com.android.server.art.model.BatchDexoptParams.Builder, @NonNull android.os.CancellationSignal);
+  }
+
+  public static interface ArtManagerLocal.DexoptDoneCallback {
+    method public void onDexoptDone(@NonNull com.android.server.art.model.DexoptResult);
+  }
+
+  public static interface ArtManagerLocal.ScheduleBackgroundDexoptJobCallback {
+    method public void onOverrideJobInfo(@NonNull android.app.job.JobInfo.Builder);
+  }
+
+  public static class ArtManagerLocal.SnapshotProfileException extends java.lang.Exception {
+  }
+
+  public class ArtModuleServiceInitializer {
+    method public static void setArtModuleServiceManager(@NonNull android.os.ArtModuleServiceManager);
+  }
+
+  public class DexUseManagerLocal {
+    method @NonNull public static com.android.server.art.DexUseManagerLocal createInstance(@NonNull android.content.Context);
+    method @NonNull public java.util.List<com.android.server.art.model.DexContainerFileUseInfo> getSecondaryDexContainerFileUseInfo(@NonNull String);
+    method public void notifyDexContainersLoaded(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull java.util.Map<java.lang.String,java.lang.String>);
+    method public void systemReady();
+  }
+
+  public class ReasonMapping {
+    field public static final String REASON_BG_DEXOPT = "bg-dexopt";
+    field public static final String REASON_BOOT_AFTER_MAINLINE_UPDATE = "boot-after-mainline-update";
+    field public static final String REASON_BOOT_AFTER_OTA = "boot-after-ota";
+    field public static final String REASON_CMDLINE = "cmdline";
+    field public static final String REASON_FIRST_BOOT = "first-boot";
+    field public static final String REASON_INACTIVE = "inactive";
+    field public static final String REASON_INSTALL = "install";
+    field public static final String REASON_INSTALL_BULK = "install-bulk";
+    field public static final String REASON_INSTALL_BULK_DOWNGRADED = "install-bulk-downgraded";
+    field public static final String REASON_INSTALL_BULK_SECONDARY = "install-bulk-secondary";
+    field public static final String REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = "install-bulk-secondary-downgraded";
+    field public static final String REASON_INSTALL_FAST = "install-fast";
+  }
+
+}
+
+package com.android.server.art.model {
+
+  public class ArtFlags {
+    method public static int defaultGetStatusFlags();
+    field public static final int FLAG_FORCE = 16; // 0x10
+    field public static final int FLAG_FOR_PRIMARY_DEX = 1; // 0x1
+    field public static final int FLAG_FOR_SECONDARY_DEX = 2; // 0x2
+    field public static final int FLAG_FOR_SINGLE_SPLIT = 32; // 0x20
+    field public static final int FLAG_SHOULD_DOWNGRADE = 8; // 0x8
+    field public static final int FLAG_SHOULD_INCLUDE_DEPENDENCIES = 4; // 0x4
+    field public static final int FLAG_SKIP_IF_STORAGE_LOW = 64; // 0x40
+    field public static final int PRIORITY_BACKGROUND = 40; // 0x28
+    field public static final int PRIORITY_BOOT = 100; // 0x64
+    field public static final int PRIORITY_INTERACTIVE = 60; // 0x3c
+    field public static final int PRIORITY_INTERACTIVE_FAST = 80; // 0x50
+    field public static final int SCHEDULE_DISABLED_BY_SYSPROP = 2; // 0x2
+    field public static final int SCHEDULE_JOB_SCHEDULER_FAILURE = 1; // 0x1
+    field public static final int SCHEDULE_SUCCESS = 0; // 0x0
+  }
+
+  public abstract class BatchDexoptParams {
+    method @NonNull public abstract com.android.server.art.model.DexoptParams getDexoptParams();
+    method @NonNull public abstract java.util.List<java.lang.String> getPackages();
+  }
+
+  public static final class BatchDexoptParams.Builder {
+    method @NonNull public com.android.server.art.model.BatchDexoptParams build();
+    method @NonNull public com.android.server.art.model.BatchDexoptParams.Builder setDexoptParams(@NonNull com.android.server.art.model.DexoptParams);
+    method @NonNull public com.android.server.art.model.BatchDexoptParams.Builder setPackages(@NonNull java.util.List<java.lang.String>);
+  }
+
+  public abstract class DeleteResult {
+    method public abstract long getFreedBytes();
+  }
+
+  public abstract class DexContainerFileUseInfo {
+    method @NonNull public abstract String getDexContainerFile();
+    method @NonNull public abstract java.util.Set<java.lang.String> getLoadingPackages();
+    method @NonNull public abstract android.os.UserHandle getUserHandle();
+  }
+
+  public class DexoptParams {
+    method @NonNull public String getCompilerFilter();
+    method public int getFlags();
+    method public int getPriorityClass();
+    method @NonNull public String getReason();
+    method @Nullable public String getSplitName();
+    field public static final String COMPILER_FILTER_NOOP = "skip";
+  }
+
+  public static final class DexoptParams.Builder {
+    ctor public DexoptParams.Builder(@NonNull String);
+    ctor public DexoptParams.Builder(@NonNull String, int);
+    method @NonNull public com.android.server.art.model.DexoptParams build();
+    method @NonNull public com.android.server.art.model.DexoptParams.Builder setCompilerFilter(@NonNull String);
+    method @NonNull public com.android.server.art.model.DexoptParams.Builder setFlags(int);
+    method @NonNull public com.android.server.art.model.DexoptParams.Builder setFlags(int, int);
+    method @NonNull public com.android.server.art.model.DexoptParams.Builder setPriorityClass(int);
+    method @NonNull public com.android.server.art.model.DexoptParams.Builder setSplitName(@Nullable String);
+  }
+
+  public abstract class DexoptResult {
+    method public int getFinalStatus();
+    method @NonNull public abstract java.util.List<com.android.server.art.model.DexoptResult.PackageDexoptResult> getPackageDexoptResults();
+    method @NonNull public abstract String getReason();
+    method @NonNull public abstract String getRequestedCompilerFilter();
+    field public static final int DEXOPT_CANCELLED = 40; // 0x28
+    field public static final int DEXOPT_FAILED = 30; // 0x1e
+    field public static final int DEXOPT_PERFORMED = 20; // 0x14
+    field public static final int DEXOPT_SKIPPED = 10; // 0xa
+  }
+
+  public abstract static class DexoptResult.DexContainerFileDexoptResult {
+    method @NonNull public abstract String getAbi();
+    method @NonNull public abstract String getActualCompilerFilter();
+    method public abstract long getDex2oatCpuTimeMillis();
+    method public abstract long getDex2oatWallTimeMillis();
+    method @NonNull public abstract String getDexContainerFile();
+    method public abstract long getSizeBeforeBytes();
+    method public abstract long getSizeBytes();
+    method public abstract int getStatus();
+    method public abstract boolean isPrimaryAbi();
+  }
+
+  public abstract static class DexoptResult.PackageDexoptResult {
+    method @NonNull public abstract java.util.List<com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult> getDexContainerFileDexoptResults();
+    method @NonNull public abstract String getPackageName();
+    method public int getStatus();
+    method public boolean hasUpdatedArtifacts();
+  }
+
+  public abstract class DexoptStatus {
+    method @NonNull public abstract java.util.List<com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus> getDexContainerFileDexoptStatuses();
+  }
+
+  public abstract static class DexoptStatus.DexContainerFileDexoptStatus {
+    method @NonNull public abstract String getAbi();
+    method @NonNull public abstract String getCompilationReason();
+    method @NonNull public abstract String getCompilerFilter();
+    method @NonNull public abstract String getDexContainerFile();
+    method @NonNull public abstract String getLocationDebugString();
+    method public abstract boolean isPrimaryAbi();
+    method public abstract boolean isPrimaryDex();
+  }
+
+  public abstract class OperationProgress {
+    method public int getPercentage();
+  }
+
+}
+
diff --git a/libartservice/api/system-server-removed.txt b/libartservice/service/api/system-server-removed.txt
similarity index 100%
rename from libartservice/api/system-server-removed.txt
rename to libartservice/service/api/system-server-removed.txt
diff --git a/libartservice/service/jarjar-rules.txt b/libartservice/service/jarjar-rules.txt
new file mode 100644
index 0000000..54ff0a1
--- /dev/null
+++ b/libartservice/service/jarjar-rules.txt
@@ -0,0 +1,3 @@
+# Repackages static libraries to make them private to ART Services.
+rule com.android.modules.utils.** com.android.server.art.jarjar.@0
+rule com.google.protobuf.** com.android.server.art.jarjar.@0
diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java
new file mode 100644
index 0000000..4445ecd
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/AidlUtils.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.ProfilePath.PrebuiltProfilePath;
+import static com.android.server.art.ProfilePath.PrimaryCurProfilePath;
+import static com.android.server.art.ProfilePath.PrimaryRefProfilePath;
+import static com.android.server.art.ProfilePath.SecondaryCurProfilePath;
+import static com.android.server.art.ProfilePath.SecondaryRefProfilePath;
+import static com.android.server.art.ProfilePath.TmpProfilePath;
+import static com.android.server.art.ProfilePath.WritableProfilePath;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/** @hide */
+public final class AidlUtils {
+    private AidlUtils() {}
+
+    @NonNull
+    public static ArtifactsPath buildArtifactsPath(
+            @NonNull String dexPath, @NonNull String isa, boolean isInDalvikCache) {
+        var artifactsPath = new ArtifactsPath();
+        artifactsPath.dexPath = dexPath;
+        artifactsPath.isa = isa;
+        artifactsPath.isInDalvikCache = isInDalvikCache;
+        return artifactsPath;
+    }
+
+    @NonNull
+    public static FsPermission buildFsPermission(
+            int uid, int gid, boolean isOtherReadable, boolean isOtherExecutable) {
+        var fsPermission = new FsPermission();
+        fsPermission.uid = uid;
+        fsPermission.gid = gid;
+        fsPermission.isOtherReadable = isOtherReadable;
+        fsPermission.isOtherExecutable = isOtherExecutable;
+        return fsPermission;
+    }
+
+    @NonNull
+    public static FsPermission buildFsPermission(int uid, int gid, boolean isOtherReadable) {
+        return buildFsPermission(uid, gid, isOtherReadable, false /* isOtherExecutable */);
+    }
+
+    @NonNull
+    public static DexMetadataPath buildDexMetadataPath(@NonNull String dexPath) {
+        var dexMetadataPath = new DexMetadataPath();
+        dexMetadataPath.dexPath = dexPath;
+        return dexMetadataPath;
+    }
+
+    @NonNull
+    public static PermissionSettings buildPermissionSettings(@NonNull FsPermission dirFsPermission,
+            @NonNull FsPermission fileFsPermission, @Nullable SeContext seContext) {
+        var permissionSettings = new PermissionSettings();
+        permissionSettings.dirFsPermission = dirFsPermission;
+        permissionSettings.fileFsPermission = fileFsPermission;
+        permissionSettings.seContext = seContext;
+        return permissionSettings;
+    }
+
+    @NonNull
+    public static OutputArtifacts buildOutputArtifacts(@NonNull String dexPath, @NonNull String isa,
+            boolean isInDalvikCache, @NonNull PermissionSettings permissionSettings) {
+        var outputArtifacts = new OutputArtifacts();
+        outputArtifacts.artifactsPath = buildArtifactsPath(dexPath, isa, isInDalvikCache);
+        outputArtifacts.permissionSettings = permissionSettings;
+        return outputArtifacts;
+    }
+
+    @NonNull
+    public static PrimaryRefProfilePath buildPrimaryRefProfilePath(
+            @NonNull String packageName, @NonNull String profileName) {
+        var primaryRefProfilePath = new PrimaryRefProfilePath();
+        primaryRefProfilePath.packageName = packageName;
+        primaryRefProfilePath.profileName = profileName;
+        return primaryRefProfilePath;
+    }
+
+    @NonNull
+    public static SecondaryRefProfilePath buildSecondaryRefProfilePath(@NonNull String dexPath) {
+        var secondaryRefProfilePath = new SecondaryRefProfilePath();
+        secondaryRefProfilePath.dexPath = dexPath;
+        return secondaryRefProfilePath;
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForPrimaryRef(
+            @NonNull String packageName, @NonNull String profileName) {
+        return ProfilePath.primaryRefProfilePath(
+                buildPrimaryRefProfilePath(packageName, profileName));
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForPrebuilt(@NonNull String dexPath) {
+        var prebuiltProfilePath = new PrebuiltProfilePath();
+        prebuiltProfilePath.dexPath = dexPath;
+        return ProfilePath.prebuiltProfilePath(prebuiltProfilePath);
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForDm(@NonNull String dexPath) {
+        return ProfilePath.dexMetadataPath(buildDexMetadataPath(dexPath));
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForPrimaryCur(
+            int userId, @NonNull String packageName, @NonNull String profileName) {
+        var primaryCurProfilePath = new PrimaryCurProfilePath();
+        primaryCurProfilePath.userId = userId;
+        primaryCurProfilePath.packageName = packageName;
+        primaryCurProfilePath.profileName = profileName;
+        return ProfilePath.primaryCurProfilePath(primaryCurProfilePath);
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForSecondaryRef(@NonNull String dexPath) {
+        return ProfilePath.secondaryRefProfilePath(buildSecondaryRefProfilePath(dexPath));
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForSecondaryCur(@NonNull String dexPath) {
+        var secondaryCurProfilePath = new SecondaryCurProfilePath();
+        secondaryCurProfilePath.dexPath = dexPath;
+        return ProfilePath.secondaryCurProfilePath(secondaryCurProfilePath);
+    }
+
+    @NonNull
+    private static OutputProfile buildOutputProfile(
+            @NonNull WritableProfilePath finalPath, int uid, int gid, boolean isPublic) {
+        var outputProfile = new OutputProfile();
+        outputProfile.profilePath = new TmpProfilePath();
+        outputProfile.profilePath.finalPath = finalPath;
+        outputProfile.profilePath.id = ""; // Will be filled by artd.
+        outputProfile.profilePath.tmpPath = ""; // Will be filled by artd.
+        outputProfile.fsPermission = buildFsPermission(uid, gid, isPublic);
+        return outputProfile;
+    }
+
+    @NonNull
+    public static OutputProfile buildOutputProfileForPrimary(@NonNull String packageName,
+            @NonNull String profileName, int uid, int gid, boolean isPublic) {
+        return buildOutputProfile(WritableProfilePath.forPrimary(
+                                          buildPrimaryRefProfilePath(packageName, profileName)),
+                uid, gid, isPublic);
+    }
+
+    @NonNull
+    public static OutputProfile buildOutputProfileForSecondary(
+            @NonNull String dexPath, int uid, int gid, boolean isPublic) {
+        return buildOutputProfile(
+                WritableProfilePath.forSecondary(buildSecondaryRefProfilePath(dexPath)), uid, gid,
+                isPublic);
+    }
+
+    @NonNull
+    public static SeContext buildSeContext(@NonNull String seInfo, int uid) {
+        var seContext = new SeContext();
+        seContext.seInfo = seInfo;
+        seContext.uid = uid;
+        return seContext;
+    }
+
+    @NonNull
+    public static String toString(@NonNull PrimaryRefProfilePath profile) {
+        return String.format("PrimaryRefProfilePath[packageName = %s, profileName = %s]",
+                profile.packageName, profile.profileName);
+    }
+
+    @NonNull
+    public static String toString(@NonNull SecondaryRefProfilePath profile) {
+        return String.format("SecondaryRefProfilePath[dexPath = %s]", profile.dexPath);
+    }
+
+    @NonNull
+    public static String toString(@NonNull PrebuiltProfilePath profile) {
+        return String.format("PrebuiltProfilePath[dexPath = %s]", profile.dexPath);
+    }
+
+    @NonNull
+    public static String toString(@NonNull DexMetadataPath profile) {
+        return String.format("DexMetadataPath[dexPath = %s]", profile.dexPath);
+    }
+
+    @NonNull
+    public static String toString(@NonNull WritableProfilePath profile) {
+        switch (profile.getTag()) {
+            case WritableProfilePath.forPrimary:
+                return toString(profile.getForPrimary());
+            case WritableProfilePath.forSecondary:
+                return toString(profile.getForSecondary());
+            default:
+                throw new IllegalStateException(
+                        "Unknown WritableProfilePath tag " + profile.getTag());
+        }
+    }
+
+    @NonNull
+    public static String toString(@NonNull ProfilePath profile) {
+        switch (profile.getTag()) {
+            case ProfilePath.primaryRefProfilePath:
+                return toString(profile.getPrimaryRefProfilePath());
+            case ProfilePath.secondaryRefProfilePath:
+                return toString(profile.getSecondaryRefProfilePath());
+            case ProfilePath.prebuiltProfilePath:
+                return toString(profile.getPrebuiltProfilePath());
+            case ProfilePath.dexMetadataPath:
+                return toString(profile.getDexMetadataPath());
+            default:
+                throw new UnsupportedOperationException(
+                        "Only a subset of profile paths are supported to be converted to string, "
+                        + "got " + profile.getTag());
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index aac4b25..378bfa4 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -16,15 +16,1338 @@
 
 package com.android.server.art;
 
-import libcore.api.CorePlatformApi;
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
+import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
+import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
+import static com.android.server.art.ReasonMapping.BatchDexoptReason;
+import static com.android.server.art.ReasonMapping.BootReason;
+import static com.android.server.art.Utils.Abi;
+import static com.android.server.art.model.ArtFlags.GetStatusFlags;
+import static com.android.server.art.model.ArtFlags.ScheduleStatus;
+import static com.android.server.art.model.Config.Callback;
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.job.JobInfo;
+import android.apphibernation.AppHibernationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.BatchDexoptParams;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.model.OperationProgress;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.DexFile;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
- * This class provides a system API for functionality provided by the ART
- * module.
+ * This class provides a system API for functionality provided by the ART module.
+ *
+ * Note: Although this class is the entry point of ART services, this class is not a {@link
+ * SystemService}, and it does not publish a binder. Instead, it is a module loaded by the
+ * system_server process, registered in {@link LocalManagerRegistry}. {@link LocalManagerRegistry}
+ * specifies that in-process module interfaces should be named with the suffix {@code ManagerLocal}
+ * for consistency.
+ *
+ * @hide
  */
[email protected](status = libcore.api.CorePlatformApi.Status.STABLE)
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public final class ArtManagerLocal {
-    static final String LOG_TAG = "ArtService";
+    /** @hide */
+    public static final String TAG = "ArtService";
 
-    public ArtManagerLocal() {}
+    private static final String[] CLASSPATHS_FOR_BOOT_IMAGE_PROFILE = {
+            "BOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"};
+
+    /** @hide */
+    @VisibleForTesting public static final long DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES = 500_000_000;
+
+    @NonNull private final Injector mInjector;
+
+    @Deprecated
+    public ArtManagerLocal() {
+        mInjector = new Injector(this, null /* context */);
+    }
+
+    /**
+     * Creates an instance.
+     *
+     * Only {@code SystemServer} should create an instance and register it in {@link
+     * LocalManagerRegistry}. Other API users should obtain the instance from {@link
+     * LocalManagerRegistry}.
+     *
+     * @param context the system server context
+     * @throws NullPointerException if required dependencies are missing
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public ArtManagerLocal(@NonNull Context context) {
+        mInjector = new Injector(this, context);
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public ArtManagerLocal(@NonNull Injector injector) {
+        mInjector = injector;
+    }
+
+    /**
+     * Handles ART Service commands, which is a subset of `cmd package` commands.
+     *
+     * Note: This method is not an override of {@link Binder#handleShellCommand} because ART
+     * services does not publish a binder. Instead, it handles the commands forwarded by the
+     * `package` service. The semantics of the parameters are the same as {@link
+     * Binder#handleShellCommand}.
+     *
+     * @return zero on success, non-zero on internal error (e.g., I/O error)
+     * @throws SecurityException if the caller is not root
+     * @throws IllegalArgumentException if the arguments are illegal
+     * @see ArtShellCommand#printHelp(PrintWriter)
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public int handleShellCommand(@NonNull Binder target, @NonNull ParcelFileDescriptor in,
+            @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+            @NonNull String[] args) {
+        return new ArtShellCommand(this, mInjector.getPackageManagerLocal())
+                .exec(target, in.getFileDescriptor(), out.getFileDescriptor(),
+                        err.getFileDescriptor(), args);
+    }
+
+    /** Prints ART Service shell command help. */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void printShellCommandHelp(@NonNull PrintWriter pw) {
+        ArtShellCommand.printHelp(pw);
+    }
+
+    /**
+     * Deletes dexopt artifacts of a package, including the artifacts for primary dex files and the
+     * ones for secondary dex files. This includes VDEX, ODEX, and ART files.
+     *
+     * @throws IllegalArgumentException if the package is not found or the flags are illegal
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public DeleteResult deleteDexoptArtifacts(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+        try {
+            long freedBytes = 0;
+
+            boolean isInDalvikCache = Utils.isInDalvikCache(pkgState, mInjector.getArtd());
+            for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
+                if (!dexInfo.hasCode()) {
+                    continue;
+                }
+                for (Abi abi : Utils.getAllAbis(pkgState)) {
+                    freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath(
+                            dexInfo.dexPath(), abi.isa(), isInDalvikCache));
+                }
+            }
+
+            for (SecondaryDexInfo dexInfo :
+                    mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) {
+                for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
+                    freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath(
+                            dexInfo.dexPath(), abi.isa(), false /* isInDalvikCache */));
+                }
+            }
+
+            return DeleteResult.create(freedBytes);
+        } catch (RemoteException e) {
+            Utils.logArtdException(e);
+            return DeleteResult.create(0 /* freedBytes */);
+        }
+    }
+
+    /**
+     * Returns the dexopt status of a package.
+     *
+     * Uses the default flags ({@link ArtFlags#defaultGetStatusFlags()}).
+     *
+     * @throws IllegalArgumentException if the package is not found or the flags are illegal
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public DexoptStatus getDexoptStatus(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        return getDexoptStatus(snapshot, packageName, ArtFlags.defaultGetStatusFlags());
+    }
+
+    /**
+     * Same as above, but allows to specify flags.
+     *
+     * @see #getDexoptStatus(PackageManagerLocal.FilteredSnapshot, String)
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public DexoptStatus getDexoptStatus(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String packageName, @GetStatusFlags int flags) {
+        if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
+                && (flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) == 0) {
+            throw new IllegalArgumentException("Nothing to check");
+        }
+
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+        List<Pair<DetailedDexInfo, Abi>> dexAndAbis = new ArrayList<>();
+
+        if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+            for (DetailedPrimaryDexInfo dexInfo :
+                    PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
+                if (!dexInfo.hasCode()) {
+                    continue;
+                }
+                for (Abi abi : Utils.getAllAbis(pkgState)) {
+                    dexAndAbis.add(Pair.create(dexInfo, abi));
+                }
+            }
+        }
+
+        if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+            for (SecondaryDexInfo dexInfo :
+                    mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) {
+                for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
+                    dexAndAbis.add(Pair.create(dexInfo, abi));
+                }
+            }
+        }
+
+        try {
+            List<DexContainerFileDexoptStatus> statuses = new ArrayList<>();
+
+            for (Pair<DetailedDexInfo, Abi> pair : dexAndAbis) {
+                DetailedDexInfo dexInfo = pair.first;
+                Abi abi = pair.second;
+                try {
+                    GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus(
+                            dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext());
+                    statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
+                            dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(),
+                            abi.name(), result.compilerFilter, result.compilationReason,
+                            result.locationDebugString));
+                } catch (ServiceSpecificException e) {
+                    statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
+                            dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(),
+                            abi.name(), "error", "error", e.getMessage()));
+                }
+            }
+
+            return DexoptStatus.create(statuses);
+        } catch (RemoteException e) {
+            Utils.logArtdException(e);
+            List<DexContainerFileDexoptStatus> statuses = new ArrayList<>();
+            for (Pair<DetailedDexInfo, Abi> pair : dexAndAbis) {
+                DetailedDexInfo dexInfo = pair.first;
+                Abi abi = pair.second;
+                statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
+                        dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(), abi.name(),
+                        "error", "error", e.getMessage()));
+            }
+            return DexoptStatus.create(statuses);
+        }
+    }
+
+    /**
+     * Clear the profiles that are collected locally for the given package, including the profiles
+     * for primary and secondary dex files. More specifically, it clears reference profiles and
+     * current profiles. External profiles (e.g., cloud profiles) will be kept.
+     *
+     * @throws IllegalArgumentException if the package is not found or the flags are illegal
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public void clearAppProfiles(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+        try {
+            for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
+                if (!dexInfo.hasCode()) {
+                    continue;
+                }
+                mInjector.getArtd().deleteProfile(
+                        PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
+                for (ProfilePath profile : PrimaryDexUtils.getCurProfiles(
+                             mInjector.getUserManager(), pkgState, dexInfo)) {
+                    mInjector.getArtd().deleteProfile(profile);
+                }
+            }
+
+            // This only deletes the profiles of known secondary dex files. If there are unknown
+            // secondary dex files, their profiles will be deleted by `cleanup`.
+            for (SecondaryDexInfo dexInfo :
+                    mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) {
+                mInjector.getArtd().deleteProfile(
+                        AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath()));
+                mInjector.getArtd().deleteProfile(
+                        AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
+            }
+        } catch (RemoteException e) {
+            Utils.logArtdException(e);
+        }
+    }
+
+    /**
+     * Dexopts a package. The time this operation takes ranges from a few milliseconds to several
+     * minutes, depending on the params and the code size of the package.
+     *
+     * When this operation ends (either completed or cancelled), callbacks added by {@link
+     * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} are called.
+     *
+     * @throws IllegalArgumentException if the package is not found or the params are illegal
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public DexoptResult dexoptPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String packageName, @NonNull DexoptParams params) {
+        var cancellationSignal = new CancellationSignal();
+        return dexoptPackage(snapshot, packageName, params, cancellationSignal);
+    }
+
+    /**
+     * Same as above, but supports cancellation.
+     *
+     * @see #dexoptPackage(PackageManagerLocal.FilteredSnapshot, String, DexoptParams)
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public DexoptResult dexoptPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String packageName, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        return mInjector.getDexoptHelper().dexopt(
+                snapshot, List.of(packageName), params, cancellationSignal, Runnable::run);
+    }
+
+    /**
+     * Resets the dexopt state of the package as if the package is newly installed.
+     *
+     * More specifically, it clears reference profiles, current profiles, and any code compiled from
+     * those local profiles. If there is an external profile (e.g., a cloud profile), the code
+     * compiled from that profile will be kept.
+     *
+     * For secondary dex files, it also clears all dexopt artifacts.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public DexoptResult resetDexoptStatus(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String packageName, @NonNull CancellationSignal cancellationSignal) {
+        // We must delete the artifacts for primary dex files beforehand rather than relying on
+        // `dexoptPackage` to replace them because:
+        // - If dexopt is not needed after the deletion, then we shouldn't run dexopt at all. For
+        //   example, when we have a DM file that contains a VDEX file but doesn't contain a cloud
+        //   profile, this happens. Note that this is more about correctness rather than
+        //   performance.
+        // - We don't want the existing artifacts to affect dexopt. For example, the existing VDEX
+        //   file should not be an input VDEX.
+        //
+        // We delete the artifacts for secondary dex files and `dexoptPackage` won't re-generate
+        // them because `dexoptPackage` for `REASON_INSTALL` is for primary dex only. This is
+        // intentional because secondary dex files are supposed to be unknown at install time.
+        deleteDexoptArtifacts(snapshot, packageName);
+        clearAppProfiles(snapshot, packageName);
+
+        // Re-generate artifacts for primary dex files if needed.
+        return dexoptPackage(snapshot, packageName,
+                new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build(), cancellationSignal);
+    }
+
+    /**
+     * Runs batch dexopt for the given reason.
+     *
+     * This is called by ART Service automatically during boot / background dexopt.
+     *
+     * The list of packages and options are determined by {@code reason}, and can be overridden by
+     * {@link #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)}.
+     *
+     * The dexopt is done in a thread pool. The number of packages being dexopted
+     * simultaneously can be configured by system property {@code pm.dexopt.<reason>.concurrency}
+     * (e.g., {@code pm.dexopt.bg-dexopt.concurrency=4}), and the number of threads for each {@code
+     * dex2oat} invocation can be configured by system property {@code dalvik.vm.*dex2oat-threads}
+     * (e.g., {@code dalvik.vm.background-dex2oat-threads=4}). I.e., the maximum number of
+     * concurrent threads is the product of the two system properties. Note that the physical core
+     * usage is always bound by {@code dalvik.vm.*dex2oat-cpu-set} regardless of the number of
+     * threads.
+     *
+     * When this operation ends (either completed or cancelled), callbacks added by {@link
+     * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} are called.
+     *
+     * If the storage is nearly low, and {@code reason} is {@link ReasonMapping#REASON_BG_DEXOPT},
+     * it may also downgrade some inactive packages to a less optimized compiler filter, specified
+     * by the system property {@code pm.dexopt.inactive} (typically "verify"), to free up some
+     * space. This feature is only enabled when the system property {@code
+     * pm.dexopt.downgrade_after_inactive_days} is set. The space threshold to trigger this feature
+     * is the Storage Manager's low space threshold plus {@link
+     * #DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES}. The concurrency can be configured by system property
+     * {@code pm.dexopt.bg-dexopt.concurrency}. The packages in the list provided by
+     * {@link BatchDexoptStartCallback} for {@link ReasonMapping#REASON_BG_DEXOPT} are never
+     * downgraded.
+     *
+     * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
+     * @param reason determines the default list of packages and options
+     * @param cancellationSignal provides the ability to cancel this operation
+     * @param processCallbackExecutor the executor to call {@code progressCallback}
+     * @param progressCallback called repeatedly whenever there is an update on the progress
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error), or the callback set by {@link
+     *         #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)} provides invalid
+     *         params.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public DexoptResult dexoptPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull @BatchDexoptReason String reason,
+            @NonNull CancellationSignal cancellationSignal,
+            @Nullable @CallbackExecutor Executor progressCallbackExecutor,
+            @Nullable Consumer<OperationProgress> progressCallback) {
+        List<String> defaultPackages =
+                Collections.unmodifiableList(getDefaultPackages(snapshot, reason));
+        DexoptParams defaultDexoptParams = new DexoptParams.Builder(reason).build();
+        var builder = new BatchDexoptParams.Builder(defaultPackages, defaultDexoptParams);
+        Callback<BatchDexoptStartCallback, Void> callback =
+                mInjector.getConfig().getBatchDexoptStartCallback();
+        if (callback != null) {
+            Utils.executeAndWait(callback.executor(), () -> {
+                callback.get().onBatchDexoptStart(
+                        snapshot, reason, defaultPackages, builder, cancellationSignal);
+            });
+        }
+        BatchDexoptParams params = builder.build();
+        Utils.check(params.getDexoptParams().getReason().equals(reason));
+
+        ExecutorService dexoptExecutor =
+                Executors.newFixedThreadPool(ReasonMapping.getConcurrencyForReason(reason));
+        try {
+            if (reason.equals(ReasonMapping.REASON_BG_DEXOPT)) {
+                maybeDowngradePackages(snapshot,
+                        new HashSet<>(params.getPackages()) /* excludedPackages */,
+                        cancellationSignal, dexoptExecutor);
+            }
+            Log.i(TAG, "Dexopting packages");
+            return mInjector.getDexoptHelper().dexopt(snapshot, params.getPackages(),
+                    params.getDexoptParams(), cancellationSignal, dexoptExecutor,
+                    progressCallbackExecutor, progressCallback);
+        } finally {
+            dexoptExecutor.shutdown();
+        }
+    }
+
+    /**
+     * Overrides the default params for {@link #dexoptPackages}. This method is thread-safe.
+     *
+     * This method gives users the opportunity to change the behavior of {@link #dexoptPackages},
+     * which is called by ART Service automatically during boot / background dexopt.
+     *
+     * If this method is not called, the default list of packages and options determined by {@code
+     * reason} will be used.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void setBatchDexoptStartCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull BatchDexoptStartCallback callback) {
+        mInjector.getConfig().setBatchDexoptStartCallback(executor, callback);
+    }
+
+    /**
+     * Clears the callback set by {@link
+     * #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)}. This method is
+     * thread-safe.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void clearBatchDexoptStartCallback() {
+        mInjector.getConfig().clearBatchDexoptStartCallback();
+    }
+
+    /**
+     * Schedules a background dexopt job. Does nothing if the job is already scheduled.
+     *
+     * Use this method if you want the system to automatically determine the best time to run
+     * dexopt.
+     *
+     * The job will be run by the job scheduler. The job scheduling configuration can be overridden
+     * by {@link
+     * #setScheduleBackgroundDexoptJobCallback(Executor, ScheduleBackgroundDexoptJobCallback)}. By
+     * default, it runs periodically (at most once a day) when all the following constraints are
+     * meet.
+     *
+     * <ul>
+     *   <li>The device is idling. (see {@link JobInfo.Builder#setRequiresDeviceIdle(boolean)})
+     *   <li>The device is charging. (see {@link JobInfo.Builder#setRequiresCharging(boolean)})
+     *   <li>The battery level is not low.
+     *     (see {@link JobInfo.Builder#setRequiresBatteryNotLow(boolean)})
+     * </ul>
+     *
+     * When the job is running, it may be cancelled by the job scheduler immediately whenever one of
+     * the constraints above is no longer met or cancelled by the {@link
+     * #cancelBackgroundDexoptJob()} API. The job scheduler retries it in the next <i>maintenance
+     * window</i>. For information about <i>maintenance window</i>, see
+     * https://developer.android.com/training/monitoring-device-state/doze-standby.
+     *
+     * See {@link #dexoptPackages} for how to customize the behavior of the job.
+     *
+     * When the job ends (either completed or cancelled), the result is sent to the callbacks added
+     * by {@link #addDexoptDoneCallback(Executor, DexoptDoneCallback)} with the
+     * reason {@link ReasonMapping#REASON_BG_DEXOPT}.
+     *
+     * @throws RuntimeException if called during boot before the job scheduler service has started.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public @ScheduleStatus int scheduleBackgroundDexoptJob() {
+        return mInjector.getBackgroundDexoptJob().schedule();
+    }
+
+    /**
+     * Unschedules the background dexopt job scheduled by {@link #scheduleBackgroundDexoptJob()}.
+     * Does nothing if the job is not scheduled.
+     *
+     * Use this method if you no longer want the system to automatically run dexopt.
+     *
+     * If the job is already started by the job scheduler and is running, it will be cancelled
+     * immediately, and the result sent to the callbacks added by {@link
+     * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} will contain {@link
+     * DexoptResult#DEXOPT_CANCELLED}. Note that a job started by {@link
+     * #startBackgroundDexoptJob()} will not be cancelled by this method.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void unscheduleBackgroundDexoptJob() {
+        mInjector.getBackgroundDexoptJob().unschedule();
+    }
+
+    /**
+     * Overrides the configuration of the background dexopt job. This method is thread-safe.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void setScheduleBackgroundDexoptJobCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull ScheduleBackgroundDexoptJobCallback callback) {
+        mInjector.getConfig().setScheduleBackgroundDexoptJobCallback(executor, callback);
+    }
+
+    /**
+     * Clears the callback set by {@link
+     * #setScheduleBackgroundDexoptJobCallback(Executor, ScheduleBackgroundDexoptJobCallback)}. This
+     * method is thread-safe.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void clearScheduleBackgroundDexoptJobCallback() {
+        mInjector.getConfig().clearScheduleBackgroundDexoptJobCallback();
+    }
+
+    /**
+     * Manually starts a background dexopt job. Does nothing if a job is already started by this
+     * method or by the job scheduler. This method is not blocking.
+     *
+     * Unlike the job started by job scheduler, the job started by this method does not respect
+     * constraints described in {@link #scheduleBackgroundDexoptJob()}, and hence will not be
+     * cancelled when they aren't met.
+     *
+     * See {@link #dexoptPackages} for how to customize the behavior of the job.
+     *
+     * When the job ends (either completed or cancelled), the result is sent to the callbacks added
+     * by {@link #addDexoptDoneCallback(Executor, DexoptDoneCallback)} with the
+     * reason {@link ReasonMapping#REASON_BG_DEXOPT}.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void startBackgroundDexoptJob() {
+        mInjector.getBackgroundDexoptJob().start();
+    }
+
+    /**
+     * Same as above, but also returns a {@link CompletableFuture}.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public CompletableFuture<BackgroundDexoptJob.Result> startBackgroundDexoptJobAndReturnFuture() {
+        return mInjector.getBackgroundDexoptJob().start();
+    }
+
+    /**
+     * Returns the running background dexopt job, or null of no background dexopt job is running.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Nullable
+    public CompletableFuture<BackgroundDexoptJob.Result> getRunningBackgroundDexoptJob() {
+        return mInjector.getBackgroundDexoptJob().get();
+    }
+
+    /**
+     * Cancels the running background dexopt job started by the job scheduler or by {@link
+     * #startBackgroundDexoptJob()}. Does nothing if the job is not running. This method is not
+     * blocking.
+     *
+     * The result sent to the callbacks added by {@link
+     * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} will contain {@link
+     * DexoptResult#DEXOPT_CANCELLED}.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void cancelBackgroundDexoptJob() {
+        mInjector.getBackgroundDexoptJob().cancel();
+    }
+
+    /**
+     * Adds a global listener that listens to any result of dexopting package(s), no matter run
+     * manually or automatically. Calling this method multiple times with different callbacks is
+     * allowed. Callbacks are executed in the same order as the one in which they were added. This
+     * method is thread-safe.
+     *
+     * @param onlyIncludeUpdates if true, the results passed to the callback will only contain
+     *         packages that have any update, and the callback won't be called with results that
+     *         don't have any update.
+     * @throws IllegalStateException if the same callback instance is already added
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void addDexoptDoneCallback(boolean onlyIncludeUpdates,
+            @NonNull @CallbackExecutor Executor executor, @NonNull DexoptDoneCallback callback) {
+        mInjector.getConfig().addDexoptDoneCallback(onlyIncludeUpdates, executor, callback);
+    }
+
+    /**
+     * Removes the listener added by {@link
+     * #addDexoptDoneCallback(Executor, DexoptDoneCallback)}. Does nothing if the
+     * callback was not added. This method is thread-safe.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void removeDexoptDoneCallback(@NonNull DexoptDoneCallback callback) {
+        mInjector.getConfig().removeDexoptDoneCallback(callback);
+    }
+
+    /**
+     * Snapshots the profile of the given app split. The profile snapshot is the aggregation of all
+     * existing profiles of the app split (all current user profiles and the reference profile).
+     *
+     * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
+     * @param packageName the name of the app that owns the profile
+     * @param splitName see {@link AndroidPackageSplit#getName()}
+     * @return the file descriptor of the snapshot. It doesn't have any path associated with it. The
+     *         caller is responsible for closing it. Note that the content may be empty.
+     * @throws IllegalArgumentException if the package or the split is not found
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     * @throws SnapshotProfileException if the operation encounters an error that the caller should
+     *         handle (e.g., an I/O error, a sub-process crash).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public ParcelFileDescriptor snapshotAppProfile(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @Nullable String splitName) throws SnapshotProfileException {
+        var options = new MergeProfileOptions();
+        options.forceMerge = true;
+        return snapshotOrDumpAppProfile(snapshot, packageName, splitName, options);
+    }
+
+    /**
+     * Same as above, but outputs in text format.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public ParcelFileDescriptor dumpAppProfile(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @Nullable String splitName, boolean dumpClassesAndMethods)
+            throws SnapshotProfileException {
+        var options = new MergeProfileOptions();
+        options.dumpOnly = !dumpClassesAndMethods;
+        options.dumpClassesAndMethods = dumpClassesAndMethods;
+        return snapshotOrDumpAppProfile(snapshot, packageName, splitName, options);
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    private ParcelFileDescriptor snapshotOrDumpAppProfile(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @Nullable String splitName, @NonNull MergeProfileOptions options)
+            throws SnapshotProfileException {
+        try {
+            PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+            AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+            PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfoBySplitName(pkg, splitName);
+
+            List<ProfilePath> profiles = new ArrayList<>();
+
+            Pair<ProfilePath, Boolean> pair = Utils.getOrInitReferenceProfile(mInjector.getArtd(),
+                    dexInfo.dexPath(), PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo),
+                    PrimaryDexUtils.getExternalProfiles(dexInfo),
+                    PrimaryDexUtils.buildOutputProfile(pkgState, dexInfo, Process.SYSTEM_UID,
+                            Process.SYSTEM_UID, false /* isPublic */));
+            ProfilePath refProfile = pair != null ? pair.first : null;
+
+            if (refProfile != null) {
+                profiles.add(refProfile);
+            }
+
+            profiles.addAll(
+                    PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), pkgState, dexInfo));
+
+            OutputProfile output = PrimaryDexUtils.buildOutputProfile(pkgState, dexInfo,
+                    Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */);
+
+            try {
+                return mergeProfilesAndGetFd(profiles, output, List.of(dexInfo.dexPath()), options);
+            } finally {
+                if (refProfile != null && refProfile.getTag() == ProfilePath.tmpProfilePath) {
+                    mInjector.getArtd().deleteProfile(refProfile);
+                }
+            }
+        } catch (RemoteException e) {
+            throw new SnapshotProfileException(e);
+        }
+    }
+
+    /**
+     * Snapshots the boot image profile
+     * (https://source.android.com/docs/core/bootloader/boot-image-profiles). The profile snapshot
+     * is the aggregation of all existing profiles on the device (all current user profiles and
+     * reference profiles) of all apps and the system server filtered by applicable classpaths.
+     *
+     * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
+     * @return the file descriptor of the snapshot. It doesn't have any path associated with it. The
+     *         caller is responsible for closing it. Note that the content may be empty.
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     * @throws SnapshotProfileException if the operation encounters an error that the caller should
+     *         handle (e.g., an I/O error, a sub-process crash).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public ParcelFileDescriptor snapshotBootImageProfile(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
+            throws SnapshotProfileException {
+        if (!Constants.isBootImageProfilingEnabled()) {
+            throw new SnapshotProfileException("Boot image profiling not enabled");
+        }
+
+        List<ProfilePath> profiles = new ArrayList<>();
+
+        // System server profiles.
+        profiles.add(AidlUtils.buildProfilePathForPrimaryRef(
+                Utils.PLATFORM_PACKAGE_NAME, PrimaryDexUtils.PROFILE_PRIMARY));
+        for (UserHandle handle :
+                mInjector.getUserManager().getUserHandles(true /* excludeDying */)) {
+            profiles.add(AidlUtils.buildProfilePathForPrimaryCur(handle.getIdentifier(),
+                    Utils.PLATFORM_PACKAGE_NAME, PrimaryDexUtils.PROFILE_PRIMARY));
+        }
+
+        // App profiles.
+        snapshot.getPackageStates().forEach((packageName, appPkgState) -> {
+            // Hibernating apps can still provide useful profile contents, so skip the hibernation
+            // check.
+            if (Utils.canDexoptPackage(appPkgState, null /* appHibernationManager */)) {
+                AndroidPackage appPkg = Utils.getPackageOrThrow(appPkgState);
+                for (PrimaryDexInfo appDexInfo : PrimaryDexUtils.getDexInfo(appPkg)) {
+                    if (!appDexInfo.hasCode()) {
+                        continue;
+                    }
+                    profiles.add(PrimaryDexUtils.buildRefProfilePath(appPkgState, appDexInfo));
+                    profiles.addAll(PrimaryDexUtils.getCurProfiles(
+                            mInjector.getUserManager(), appPkgState, appDexInfo));
+                }
+            }
+        });
+
+        OutputProfile output = AidlUtils.buildOutputProfileForPrimary(Utils.PLATFORM_PACKAGE_NAME,
+                PrimaryDexUtils.PROFILE_PRIMARY, Process.SYSTEM_UID, Process.SYSTEM_UID,
+                false /* isPublic */);
+
+        List<String> dexPaths = Arrays.stream(CLASSPATHS_FOR_BOOT_IMAGE_PROFILE)
+                                        .map(envVar -> Constants.getenv(envVar))
+                                        .filter(classpath -> !TextUtils.isEmpty(classpath))
+                                        .flatMap(classpath -> Arrays.stream(classpath.split(":")))
+                                        .collect(Collectors.toList());
+
+        var options = new MergeProfileOptions();
+        options.forceMerge = true;
+        options.forBootImage = true;
+        return mergeProfilesAndGetFd(profiles, output, dexPaths, options);
+    }
+
+    /**
+     * Notifies ART Service that this is a boot that falls into one of the categories listed in
+     * {@link BootReason}. The current behavior is that ART Service goes through all recently used
+     * packages and dexopts those that are not dexopted. This might change in the future.
+     *
+     * This method is blocking. It takes about 30 seconds to a few minutes. During execution, {@code
+     * progressCallback} is repeatedly called whenever there is an update on the progress.
+     *
+     * See {@link #dexoptPackages} for how to customize the behavior.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void onBoot(@NonNull @BootReason String bootReason,
+            @Nullable @CallbackExecutor Executor progressCallbackExecutor,
+            @Nullable Consumer<OperationProgress> progressCallback) {
+        try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) {
+            dexoptPackages(snapshot, bootReason, new CancellationSignal(), progressCallbackExecutor,
+                    progressCallback);
+        }
+    }
+
+    /**
+     * Dumps the dexopt state of all packages in text format for debugging purposes.
+     *
+     * There are no stability guarantees for the output format.
+     *
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void dump(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        new DumpHelper(this).dump(pw, snapshot);
+    }
+
+    /**
+     * Dumps the dexopt state of the given package in text format for debugging purposes.
+     *
+     * There are no stability guarantees for the output format.
+     *
+     * @throws IllegalArgumentException if the package is not found
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void dumpPackage(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        new DumpHelper(this).dumpPackage(
+                pw, snapshot, Utils.getPackageStateOrThrow(snapshot, packageName));
+    }
+
+    /**
+     * Cleans up obsolete profiles and artifacts.
+     *
+     * This is done in a mark-and-sweep approach.
+     *
+     * @return The amount of the disk space freed by the cleanup, in bytes.
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public long cleanup(@NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        mInjector.getDexUseManager().cleanup();
+
+        try {
+            // For every primary dex container file or secondary dex container file of every app, if
+            // it has code, we keep the following types of files:
+            // - The reference profile and the current profiles, regardless of the hibernation state
+            //   of the app.
+            // - The dexopt artifacts, if they are up-to-date and the app is not hibernating.
+            // - Only the VDEX part of the dexopt artifacts, if the dexopt artifacts are outdated
+            //   but the VDEX part is still usable and the app is not hibernating.
+            List<ProfilePath> profilesToKeep = new ArrayList<>();
+            List<ArtifactsPath> artifactsToKeep = new ArrayList<>();
+            List<VdexPath> vdexFilesToKeep = new ArrayList<>();
+
+            for (PackageState pkgState : snapshot.getPackageStates().values()) {
+                if (!Utils.canDexoptPackage(pkgState, null /* appHibernationManager */)) {
+                    continue;
+                }
+                AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+                boolean isInDalvikCache = Utils.isInDalvikCache(pkgState, mInjector.getArtd());
+                boolean keepArtifacts = !Utils.shouldSkipDexoptDueToHibernation(
+                        pkgState, mInjector.getAppHibernationManager());
+                for (DetailedPrimaryDexInfo dexInfo :
+                        PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
+                    if (!dexInfo.hasCode()) {
+                        continue;
+                    }
+                    profilesToKeep.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
+                    profilesToKeep.addAll(PrimaryDexUtils.getCurProfiles(
+                            mInjector.getUserManager(), pkgState, dexInfo));
+                    if (keepArtifacts) {
+                        for (Abi abi : Utils.getAllAbis(pkgState)) {
+                            maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, pkgState, dexInfo,
+                                    abi, isInDalvikCache);
+                        }
+                    }
+                }
+                for (DetailedSecondaryDexInfo dexInfo :
+                        mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo(
+                                pkgState.getPackageName())) {
+                    profilesToKeep.add(
+                            AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath()));
+                    profilesToKeep.add(
+                            AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
+                    if (keepArtifacts) {
+                        for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
+                            maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, pkgState, dexInfo,
+                                    abi, false /* isInDalvikCache */);
+                        }
+                    }
+                }
+            }
+            return mInjector.getArtd().cleanup(profilesToKeep, artifactsToKeep, vdexFilesToKeep);
+        } catch (RemoteException e) {
+            Utils.logArtdException(e);
+            return 0;
+        }
+    }
+
+    /**
+     * Checks if the artifacts are up-to-date, and maybe adds them to {@code artifactsToKeep} or
+     * {@code vdexFilesToKeep} based on the result.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private void maybeKeepArtifacts(@NonNull List<ArtifactsPath> artifactsToKeep,
+            @NonNull List<VdexPath> vdexFilesToKeep, @NonNull PackageState pkgState,
+            @NonNull DetailedDexInfo dexInfo, @NonNull Abi abi, boolean isInDalvikCache)
+            throws RemoteException {
+        try {
+            GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus(
+                    dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext());
+            if (DexFile.isValidCompilerFilter(result.compilerFilter)) {
+                // TODO(b/263579377): This is a bit inaccurate. We may be keeping the artifacts in
+                // dalvik-cache while OatFileAssistant actually picks the ones not in dalvik-cache.
+                // However, this isn't a big problem because it is an edge case and it only causes
+                // us to delete less rather than deleting more.
+                ArtifactsPath artifacts =
+                        AidlUtils.buildArtifactsPath(dexInfo.dexPath(), abi.isa(), isInDalvikCache);
+                if (result.compilationReason.equals(ArtConstants.REASON_VDEX)) {
+                    // Only the VDEX file is usable.
+                    vdexFilesToKeep.add(VdexPath.artifactsPath(artifacts));
+                } else {
+                    artifactsToKeep.add(artifacts);
+                }
+            }
+        } catch (ServiceSpecificException e) {
+            // Don't add the artifacts to the lists. They should be cleaned up.
+            Log.e(TAG,
+                    String.format("Failed to get dexopt status [packageName = %s, dexPath = %s, "
+                                    + "isa = %s, classLoaderContext = %s]",
+                            pkgState.getPackageName(), dexInfo.dexPath(), abi.isa(),
+                            dexInfo.classLoaderContext()),
+                    e);
+        }
+    }
+
+    /**
+     * Should be used by {@link BackgroundDexoptJobService} ONLY.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    BackgroundDexoptJob getBackgroundDexoptJob() {
+        return mInjector.getBackgroundDexoptJob();
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private void maybeDowngradePackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull Set<String> excludedPackages, @NonNull CancellationSignal cancellationSignal,
+            @NonNull Executor executor) {
+        if (shouldDowngrade()) {
+            List<String> packages = getDefaultPackages(snapshot, ReasonMapping.REASON_INACTIVE)
+                                            .stream()
+                                            .filter(pkg -> !excludedPackages.contains(pkg))
+                                            .collect(Collectors.toList());
+            if (!packages.isEmpty()) {
+                Log.i(TAG, "Storage is low. Downgrading inactive packages");
+                DexoptParams params =
+                        new DexoptParams.Builder(ReasonMapping.REASON_INACTIVE).build();
+                mInjector.getDexoptHelper().dexopt(snapshot, packages, params, cancellationSignal,
+                        executor, null /* processCallbackExecutor */, null /* progressCallback */);
+            } else {
+                Log.i(TAG,
+                        "Storage is low, but downgrading is disabled or there's nothing to "
+                                + "downgrade");
+            }
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private boolean shouldDowngrade() {
+        try {
+            return mInjector.getStorageManager().getAllocatableBytes(StorageManager.UUID_DEFAULT)
+                    < DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES;
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to check storage. Assuming storage not low", e);
+            return false;
+        }
+    }
+
+    /** Returns the list of packages to process for the given reason. */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    private List<String> getDefaultPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull /* @BatchDexoptReason|REASON_INACTIVE */ String reason) {
+        var appHibernationManager = mInjector.getAppHibernationManager();
+
+        // Filter out hibernating packages even if the reason is REASON_INACTIVE. This is because
+        // artifacts for hibernating packages are already deleted.
+        Stream<PackageState> packages = snapshot.getPackageStates().values().stream().filter(
+                pkgState -> Utils.canDexoptPackage(pkgState, appHibernationManager));
+
+        switch (reason) {
+            case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE:
+                packages = packages.filter(pkgState
+                        -> mInjector.isSystemUiPackage(pkgState.getPackageName())
+                                || mInjector.isLauncherPackage(pkgState.getPackageName()));
+                break;
+            case ReasonMapping.REASON_INACTIVE:
+                packages = filterAndSortByLastActiveTime(
+                        packages, false /* keepRecent */, false /* descending */);
+                break;
+            default:
+                // Actually, the sorting is only needed for background dexopt, but we do it for all
+                // cases for simplicity.
+                packages = filterAndSortByLastActiveTime(
+                        packages, true /* keepRecent */, true /* descending */);
+        }
+
+        return packages.map(PackageState::getPackageName).collect(Collectors.toList());
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    private Stream<PackageState> filterAndSortByLastActiveTime(
+            @NonNull Stream<PackageState> packages, boolean keepRecent, boolean descending) {
+        // "pm.dexopt.downgrade_after_inactive_days" is repurposed to also determine whether to
+        // dexopt a package.
+        long inactiveMs = TimeUnit.DAYS.toMillis(SystemProperties.getInt(
+                "pm.dexopt.downgrade_after_inactive_days", Integer.MAX_VALUE /* def */));
+        long currentTimeMs = mInjector.getCurrentTimeMillis();
+        long thresholdTimeMs = currentTimeMs - inactiveMs;
+        return packages
+                .map(pkgState
+                        -> Pair.create(pkgState,
+                                Utils.getPackageLastActiveTime(pkgState,
+                                        mInjector.getDexUseManager(), mInjector.getUserManager())))
+                .filter(keepRecent ? (pair -> pair.second > thresholdTimeMs)
+                                   : (pair -> pair.second <= thresholdTimeMs))
+                .sorted(descending ? Comparator.comparingLong(pair -> - pair.second)
+                                   : Comparator.comparingLong(pair -> pair.second))
+                .map(pair -> pair.first);
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    private ParcelFileDescriptor mergeProfilesAndGetFd(@NonNull List<ProfilePath> profiles,
+            @NonNull OutputProfile output, @NonNull List<String> dexPaths,
+            @NonNull MergeProfileOptions options) throws SnapshotProfileException {
+        try {
+            boolean hasContent = false;
+            try {
+                hasContent = mInjector.getArtd().mergeProfiles(
+                        profiles, null /* referenceProfile */, output, dexPaths, options);
+            } catch (ServiceSpecificException e) {
+                throw new SnapshotProfileException(e);
+            }
+
+            String path;
+            Path emptyFile = null;
+            if (hasContent) {
+                path = output.profilePath.tmpPath;
+            } else {
+                // We cannot use /dev/null because `ParcelFileDescriptor` have an API `getStatSize`,
+                // which expects the file to be a regular file or a link, and apps may call that
+                // API.
+                emptyFile =
+                        Files.createTempFile(Paths.get(mInjector.getTempDir()), "empty", ".tmp");
+                path = emptyFile.toString();
+            }
+            ParcelFileDescriptor fd;
+            try {
+                fd = ParcelFileDescriptor.open(new File(path), ParcelFileDescriptor.MODE_READ_ONLY);
+            } catch (FileNotFoundException e) {
+                throw new IllegalStateException(
+                        String.format("Failed to open profile snapshot '%s'", path), e);
+            }
+
+            // The deletion is done on the open file so that only the FD keeps a reference to the
+            // file.
+            if (hasContent) {
+                mInjector.getArtd().deleteProfile(ProfilePath.tmpProfilePath(output.profilePath));
+            } else {
+                Files.delete(emptyFile);
+            }
+
+            return fd;
+        } catch (IOException | RemoteException e) {
+            throw new SnapshotProfileException(e);
+        }
+    }
+
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public interface BatchDexoptStartCallback {
+        /**
+         * Mutates {@code builder} to override the default params for {@link #dexoptPackages}. It
+         * must ignore unknown reasons because more reasons may be added in the future.
+         *
+         * This is called before the start of any automatic package dexopt (i.e., not
+         * including package dexopt initiated by the {@link #dexoptPackage} API call).
+         *
+         * If {@code builder.setPackages} is not called, {@code defaultPackages} will be used as the
+         * list of packages to dexopt.
+         *
+         * If {@code builder.setDexoptParams} is not called, the default params built from {@code
+         * new DexoptParams.Builder(reason)} will to used as the params for dexopting each
+         * package.
+         *
+         * Additionally, {@code cancellationSignal.cancel()} can be called to cancel this operation.
+         * If this operation is initiated by the job scheduler and the {@code reason} is {@link
+         * ReasonMapping#REASON_BG_DEXOPT}, the job will be retried in the next <i>maintenance
+         * window</i>. For information about <i>maintenance window</i>, see
+         * https://developer.android.com/training/monitoring-device-state/doze-standby.
+         *
+         * Changing the reason is not allowed. Doing so will result in {@link IllegalStateException}
+         * when {@link #dexoptPackages} is called.
+         */
+        void onBatchDexoptStart(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+                @NonNull @BatchDexoptReason String reason, @NonNull List<String> defaultPackages,
+                @NonNull BatchDexoptParams.Builder builder,
+                @NonNull CancellationSignal cancellationSignal);
+    }
+
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public interface ScheduleBackgroundDexoptJobCallback {
+        /**
+         * Mutates {@code builder} to override the configuration of the background dexopt job.
+         *
+         * The default configuration described in {@link
+         * ArtManagerLocal#scheduleBackgroundDexoptJob()} is passed to the callback as the {@code
+         * builder} argument.
+         *
+         * Setting {@link JobInfo.Builder#setRequiresStorageNotLow(boolean)} is not allowed. Doing
+         * so will result in {@link IllegalStateException} when {@link
+         * #scheduleBackgroundDexoptJob()} is called. ART Service has its own storage check, which
+         * skips package dexopt when the storage is low. The storage check is enabled by
+         * default for background dexopt jobs. {@link
+         * #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)} can be used to disable
+         * the storage check by clearing the {@link ArtFlags#FLAG_SKIP_IF_STORAGE_LOW} flag.
+         */
+        void onOverrideJobInfo(@NonNull JobInfo.Builder builder);
+    }
+
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public interface DexoptDoneCallback {
+        void onDexoptDone(@NonNull DexoptResult result);
+    }
+
+    /**
+     * Represents an error that happens when snapshotting profiles.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static class SnapshotProfileException extends Exception {
+        /** @hide */
+        public SnapshotProfileException(@NonNull Throwable cause) {
+            super(cause);
+        }
+
+        /** @hide */
+        public SnapshotProfileException(@NonNull String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Injector {
+        @Nullable private final ArtManagerLocal mArtManagerLocal;
+        @Nullable private final Context mContext;
+        @Nullable private final PackageManagerLocal mPackageManagerLocal;
+        @Nullable private final Config mConfig;
+        @Nullable private BackgroundDexoptJob mBgDexoptJob = null;
+
+        // TODO(jiakaiz): Remove @SuppressLint and check `Build.VERSION.SDK_INT >=
+        // Build.VERSION_CODES.UPSIDE_DOWN_CAKE` once the SDK is finalized.
+        @SuppressLint("NewApi")
+        Injector(@NonNull ArtManagerLocal artManagerLocal, @Nullable Context context) {
+            mArtManagerLocal = artManagerLocal;
+            mContext = context;
+            if (context != null) {
+                // We only need them on Android U and above, where a context is passed.
+                mPackageManagerLocal = Objects.requireNonNull(
+                        LocalManagerRegistry.getManager(PackageManagerLocal.class));
+                mConfig = new Config();
+
+                // Call the getters for the dependencies that aren't optional, to ensure correct
+                // initialization order.
+                getDexoptHelper();
+                getUserManager();
+                getDexUseManager();
+                getStorageManager();
+                ArtModuleServiceInitializer.getArtModuleServiceManager();
+            } else {
+                mPackageManagerLocal = null;
+                mConfig = null;
+            }
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public Context getContext() {
+            return Objects.requireNonNull(mContext);
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public PackageManagerLocal getPackageManagerLocal() {
+            return Objects.requireNonNull(mPackageManagerLocal);
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public IArtd getArtd() {
+            return Utils.getArtd();
+        }
+
+        /** Returns a new {@link DexoptHelper} instance. */
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public DexoptHelper getDexoptHelper() {
+            return new DexoptHelper(getContext(), getConfig());
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public Config getConfig() {
+            return mConfig;
+        }
+
+        /** Returns the registered {@link AppHibernationManager} instance. */
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public AppHibernationManager getAppHibernationManager() {
+            return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class));
+        }
+
+        /**
+         * Returns the {@link BackgroundDexoptJob} instance.
+         *
+         * @throws RuntimeException if called during boot before the job scheduler service has
+         *         started.
+         */
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public synchronized BackgroundDexoptJob getBackgroundDexoptJob() {
+            if (mBgDexoptJob == null) {
+                mBgDexoptJob = new BackgroundDexoptJob(mContext, mArtManagerLocal, mConfig);
+            }
+            return mBgDexoptJob;
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public UserManager getUserManager() {
+            return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public DexUseManagerLocal getDexUseManager() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(DexUseManagerLocal.class));
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        public boolean isSystemUiPackage(@NonNull String packageName) {
+            return Utils.isSystemUiPackage(mContext, packageName);
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        public boolean isLauncherPackage(@NonNull String packageName) {
+            return Utils.isLauncherPackage(mContext, packageName);
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        public long getCurrentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public StorageManager getStorageManager() {
+            return Objects.requireNonNull(mContext.getSystemService(StorageManager.class));
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public String getTempDir() {
+            // This is a path that system_server is known to have full access to.
+            return "/data/system";
+        }
+    }
 }
diff --git a/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java b/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java
new file mode 100644
index 0000000..c9295c1
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.ArtModuleServiceManager;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * Class for performing registration for the ART mainline module.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class ArtModuleServiceInitializer {
+    private ArtModuleServiceInitializer() {}
+
+    @NonNull private static Object sLock = new Object();
+    @GuardedBy("sLock") @Nullable private static ArtModuleServiceManager sArtModuleServiceManager;
+
+    /**
+     * Sets an instance of {@link ArtModuleServiceManager} that allows the ART mainline module to
+     * obtain ART binder services. This is called by the platform during the system server
+     * initialization.
+     */
+    public static void setArtModuleServiceManager(
+            @NonNull ArtModuleServiceManager artModuleServiceManager) {
+        synchronized (sLock) {
+            if (sArtModuleServiceManager != null) {
+                throw new IllegalStateException("ArtModuleServiceManager is already set");
+            }
+            sArtModuleServiceManager = artModuleServiceManager;
+        }
+    }
+
+    /** @hide */
+    @NonNull
+    public static ArtModuleServiceManager getArtModuleServiceManager() {
+        synchronized (sLock) {
+            return Objects.requireNonNull(sArtModuleServiceManager);
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
new file mode 100644
index 0000000..b83f4ab
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -0,0 +1,938 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
+
+import static com.android.server.art.ArtManagerLocal.SnapshotProfileException;
+import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
+import static com.android.server.art.model.ArtFlags.DexoptFlags;
+import static com.android.server.art.model.ArtFlags.PriorityClassApi;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.model.DexoptResult.DexoptResultStatus;
+import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.model.OperationProgress;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import libcore.io.Streams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+/**
+ * This class handles ART shell commands.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public final class ArtShellCommand extends BasicShellCommandHandler {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    /** The default location for profile dumps. */
+    private final static String PROFILE_DEBUG_LOCATION = "/data/misc/profman";
+
+    private final ArtManagerLocal mArtManagerLocal;
+    private final PackageManagerLocal mPackageManagerLocal;
+
+    @GuardedBy("sCancellationSignalMap")
+    @NonNull
+    private static final Map<String, CancellationSignal> sCancellationSignalMap = new HashMap<>();
+
+    public ArtShellCommand(@NonNull ArtManagerLocal artManagerLocal,
+            @NonNull PackageManagerLocal packageManagerLocal) {
+        mArtManagerLocal = artManagerLocal;
+        mPackageManagerLocal = packageManagerLocal;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        // Apps shouldn't call ART Service shell commands, not even for dexopting themselves.
+        enforceRootOrShell();
+        PrintWriter pw = getOutPrintWriter();
+        try (var snapshot = mPackageManagerLocal.withFilteredSnapshot()) {
+            switch (cmd) {
+                case "compile":
+                    return handleCompile(pw, snapshot);
+                case "reconcile-secondary-dex-files":
+                    pw.println("Warning: 'pm reconcile-secondary-dex-files' is deprecated. It is "
+                            + "now doing nothing");
+                    return 0;
+                case "force-dex-opt":
+                    return handleForceDexopt(pw, snapshot);
+                case "bg-dexopt-job":
+                    return handleBgDexoptJob(pw, snapshot);
+                case "cancel-bg-dexopt-job":
+                    pw.println("Warning: 'pm cancel-bg-dexopt-job' is deprecated. It is now an "
+                            + "alias of 'pm bg-dexopt-job --cancel'");
+                    return handleCancelBgDexoptJob(pw);
+                case "delete-dexopt":
+                    return handleDeleteDexopt(pw, snapshot);
+                case "dump-profiles":
+                    return handleDumpProfile(pw, snapshot);
+                case "snapshot-profile":
+                    return handleSnapshotProfile(pw, snapshot);
+                case "art":
+                    return handleArtCommand(pw, snapshot);
+                default:
+                    // Can't happen. Only supported commands are forwarded to ART Service.
+                    throw new IllegalArgumentException(
+                            String.format("Unexpected command '%s' forwarded to ART Service", cmd));
+            }
+        } catch (IllegalArgumentException | SnapshotProfileException e) {
+            pw.println("Error: " + e.getMessage());
+            return 1;
+        }
+    }
+
+    private int handleArtCommand(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        String subcmd = getNextArgRequired();
+        switch (subcmd) {
+            case "dexopt-packages": {
+                return handleBatchDexopt(pw, snapshot);
+            }
+            case "cancel": {
+                String jobId = getNextArgRequired();
+                CancellationSignal signal;
+                synchronized (sCancellationSignalMap) {
+                    signal = sCancellationSignalMap.getOrDefault(jobId, null);
+                }
+                if (signal == null) {
+                    pw.println("Job not found");
+                    return 1;
+                }
+                signal.cancel();
+                pw.println("Job cancelled");
+                return 0;
+            }
+            case "dump": {
+                String packageName = getNextArg();
+                if (packageName != null) {
+                    mArtManagerLocal.dumpPackage(pw, snapshot, packageName);
+                } else {
+                    mArtManagerLocal.dump(pw, snapshot);
+                }
+                return 0;
+            }
+            case "cleanup": {
+                return handleCleanup(pw, snapshot);
+            }
+            case "clear-app-profiles": {
+                mArtManagerLocal.clearAppProfiles(snapshot, getNextArgRequired());
+                pw.println("Profiles cleared");
+                return 0;
+            }
+            default:
+                pw.printf("Error: Unknown 'art' sub-command '%s'\n", subcmd);
+                pw.println("See 'pm help' for help");
+                return 1;
+        }
+    }
+
+    private int handleCompile(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        @DexoptFlags int scopeFlags = 0;
+        String reason = null;
+        String compilerFilter = null;
+        @PriorityClassApi int priorityClass = ArtFlags.PRIORITY_NONE;
+        String splitArg = null;
+        boolean force = false;
+        boolean reset = false;
+        boolean forAllPackages = false;
+        boolean legacyClearProfile = false;
+        boolean verbose = false;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-a":
+                    forAllPackages = true;
+                    break;
+                case "-r":
+                    reason = getNextArgRequired();
+                    break;
+                case "-m":
+                    compilerFilter = getNextArgRequired();
+                    break;
+                case "-p":
+                    priorityClass = parsePriorityClass(getNextArgRequired());
+                    break;
+                case "-f":
+                    force = true;
+                    break;
+                case "--primary-dex":
+                    scopeFlags |= ArtFlags.FLAG_FOR_PRIMARY_DEX;
+                    break;
+                case "--secondary-dex":
+                    scopeFlags |= ArtFlags.FLAG_FOR_SECONDARY_DEX;
+                    break;
+                case "--include-dependencies":
+                    scopeFlags |= ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+                    break;
+                case "--full":
+                    scopeFlags |= ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                            | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+                    break;
+                case "--split":
+                    splitArg = getNextArgRequired();
+                    break;
+                case "--reset":
+                    reset = true;
+                    break;
+                case "-c":
+                    pw.println("Warning: Flag '-c' is deprecated and usually produces undesired "
+                            + "results. Please use one of the following commands instead.");
+                    pw.println("- To clear the local profiles only, use "
+                            + "'pm art clear-app-profiles PACKAGE_NAME'. (The existing dexopt "
+                            + "artifacts will be kept, even if they are derived from the "
+                            + "profiles.)");
+                    pw.println("- To clear the local profiles and also clear the dexopt artifacts "
+                            + "that are derived from them, use 'pm compile --reset PACKAGE_NAME'. "
+                            + "(The package will be reset to the initial state as if it's newly "
+                            + "installed, which means the package will be re-dexopted if "
+                            + "necessary, and cloud profiles will be used if exist.)");
+                    pw.println("- To re-dexopt the package with no profile, use "
+                            + "'pm compile -m verify -f PACKAGE_NAME'. (The local profiles "
+                            + "will be kept but not used during the dexopt. The dexopt artifacts "
+                            + "are guaranteed to have no compiled code.)");
+                    legacyClearProfile = true;
+                    break;
+                case "--check-prof":
+                    getNextArgRequired();
+                    pw.println("Warning: Ignoring obsolete flag '--check-prof'. It is "
+                            + "unconditionally enabled now");
+                    break;
+                case "-v":
+                    verbose = true;
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        List<String> packageNames = forAllPackages
+                ? List.copyOf(snapshot.getPackageStates().keySet())
+                : List.of(getNextArgRequired());
+
+        var paramsBuilder = new DexoptParams.Builder(ReasonMapping.REASON_CMDLINE);
+        if (reason != null) {
+            if (reason.equals(ReasonMapping.REASON_INACTIVE)) {
+                pw.println("Warning: '-r inactive' produces undesired results.");
+            }
+            if (compilerFilter == null) {
+                paramsBuilder.setCompilerFilter(ReasonMapping.getCompilerFilterForReason(reason));
+            }
+            if (priorityClass == ArtFlags.PRIORITY_NONE) {
+                paramsBuilder.setPriorityClass(ReasonMapping.getPriorityClassForReason(reason));
+            }
+        }
+        if (compilerFilter != null) {
+            paramsBuilder.setCompilerFilter(compilerFilter);
+        }
+        if (priorityClass != ArtFlags.PRIORITY_NONE) {
+            paramsBuilder.setPriorityClass(priorityClass);
+        }
+        if (force) {
+            paramsBuilder.setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE);
+        }
+        if (splitArg != null) {
+            if (scopeFlags != 0) {
+                pw.println("Error: '--primary-dex', '--secondary-dex', "
+                        + "'--include-dependencies', or '--full' must not be set when '--split' "
+                        + "is set.");
+                return 1;
+            }
+            if (forAllPackages) {
+                pw.println("Error:  '-a' cannot be specified together with '--split'");
+                return 1;
+            }
+            scopeFlags = ArtFlags.FLAG_FOR_PRIMARY_DEX;
+            paramsBuilder.setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT, ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                    .setSplitName(getSplitName(pw, snapshot, packageNames.get(0), splitArg));
+        }
+        if (scopeFlags != 0) {
+            paramsBuilder.setFlags(scopeFlags,
+                    ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                            | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
+        } else {
+            paramsBuilder.setFlags(
+                    ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+                    ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                            | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
+        }
+        if (forAllPackages) {
+            // We'll iterate over all packages anyway.
+            paramsBuilder.setFlags(0, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
+        }
+
+        if (reset) {
+            return resetPackages(pw, snapshot, packageNames, verbose);
+        } else {
+            if (legacyClearProfile) {
+                // For compat only. Combining this with dexopt usually produces in undesired
+                // results.
+                for (String packageName : packageNames) {
+                    mArtManagerLocal.clearAppProfiles(snapshot, packageName);
+                }
+            }
+            return dexoptPackages(pw, snapshot, packageNames, paramsBuilder.build(), verbose);
+        }
+    }
+
+    private int handleForceDexopt(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        pw.println("Warning: 'pm force-dex-opt' is deprecated. Please use 'pm compile "
+                + "-f PACKAGE_NAME' instead");
+        return dexoptPackages(pw, snapshot, List.of(getNextArgRequired()),
+                new DexoptParams.Builder(ReasonMapping.REASON_CMDLINE)
+                        .setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE)
+                        .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX
+                                        | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+                                ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                        | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                        .build(),
+                false /* verbose */);
+    }
+
+    private int handleBgDexoptJob(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        String opt = getNextOption();
+        if (opt == null) {
+            List<String> packageNames = new ArrayList<>();
+            String arg;
+            while ((arg = getNextArg()) != null) {
+                packageNames.add(arg);
+            }
+            if (!packageNames.isEmpty()) {
+                pw.println("Warning: Running 'pm bg-dexopt-job' with package names is deprecated. "
+                        + "Please use 'pm compile -r bg-dexopt PACKAGE_NAME' instead");
+                return dexoptPackages(pw, snapshot, packageNames,
+                        new DexoptParams.Builder(ReasonMapping.REASON_BG_DEXOPT).build(),
+                        false /* verbose */);
+            }
+
+            CompletableFuture<BackgroundDexoptJob.Result> runningJob =
+                    mArtManagerLocal.getRunningBackgroundDexoptJob();
+            if (runningJob != null) {
+                pw.println("Another job already running. Waiting for it to finish... To cancel it, "
+                        + "run 'pm bg-dexopt-job --cancel'. in a separate shell.");
+                pw.flush();
+                Utils.getFuture(runningJob);
+            }
+            CompletableFuture<BackgroundDexoptJob.Result> future =
+                    mArtManagerLocal.startBackgroundDexoptJobAndReturnFuture();
+            pw.println("Job running...  To cancel it, run 'pm bg-dexopt-job --cancel'. in a "
+                    + "separate shell.");
+            pw.flush();
+            BackgroundDexoptJob.Result result = Utils.getFuture(future);
+            if (result instanceof BackgroundDexoptJob.CompletedResult) {
+                var completedResult = (BackgroundDexoptJob.CompletedResult) result;
+                if (completedResult.dexoptResult().getFinalStatus()
+                        == DexoptResult.DEXOPT_CANCELLED) {
+                    pw.println("Job cancelled. See logs for details");
+                } else {
+                    pw.println("Job finished. See logs for details");
+                }
+            } else if (result instanceof BackgroundDexoptJob.FatalErrorResult) {
+                // Never expected.
+                pw.println("Job encountered a fatal error");
+            }
+            return 0;
+        }
+        switch (opt) {
+            case "--cancel": {
+                return handleCancelBgDexoptJob(pw);
+            }
+            case "--enable": {
+                // This operation requires the uid to be "system" (1000).
+                long identityToken = Binder.clearCallingIdentity();
+                try {
+                    mArtManagerLocal.scheduleBackgroundDexoptJob();
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+                pw.println("Background dexopt job enabled");
+                return 0;
+            }
+            case "--disable": {
+                // This operation requires the uid to be "system" (1000).
+                long identityToken = Binder.clearCallingIdentity();
+                try {
+                    mArtManagerLocal.unscheduleBackgroundDexoptJob();
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+                pw.println("Background dexopt job disabled");
+                return 0;
+            }
+            default:
+                pw.println("Error: Unknown option: " + opt);
+                return 1;
+        }
+    }
+
+    private int handleCancelBgDexoptJob(@NonNull PrintWriter pw) {
+        mArtManagerLocal.cancelBackgroundDexoptJob();
+        pw.println("Background dexopt job cancelled");
+        return 0;
+    }
+
+    private int handleCleanup(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        long freedBytes = mArtManagerLocal.cleanup(snapshot);
+        pw.printf("Freed %d bytes\n", freedBytes);
+        return 0;
+    }
+
+    private int handleDeleteDexopt(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        DeleteResult result =
+                mArtManagerLocal.deleteDexoptArtifacts(snapshot, getNextArgRequired());
+        pw.printf("Freed %d bytes\n", result.getFreedBytes());
+        return 0;
+    }
+
+    private int handleSnapshotProfile(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
+            throws SnapshotProfileException {
+        String splitName = null;
+        String codePath = null;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--split":
+                    splitName = getNextArgRequired();
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        String packageName = getNextArgRequired();
+
+        if ("--code-path".equals(getNextOption())) {
+            pw.println("Warning: Specifying a split using '--code-path' is deprecated. Please use "
+                    + "'--split SPLIT_NAME' instead");
+            pw.println("Tip: '--split SPLIT_NAME' must be passed before the package name");
+            codePath = getNextArgRequired();
+        }
+
+        if (splitName != null && codePath != null) {
+            pw.println("Error: '--split' and '--code-path' cannot be both specified");
+            return 1;
+        }
+
+        if (packageName.equals(Utils.PLATFORM_PACKAGE_NAME)) {
+            if (splitName != null) {
+                pw.println("Error: '--split' must not be specified for boot image profile");
+                return 1;
+            }
+            if (codePath != null) {
+                pw.println("Error: '--code-path' must not be specified for boot image profile");
+                return 1;
+            }
+            return handleSnapshotBootProfile(pw, snapshot);
+        }
+
+        if (splitName != null && splitName.isEmpty()) {
+            splitName = null;
+        }
+        if (codePath != null) {
+            splitName = getSplitNameByFullPath(snapshot, packageName, codePath);
+        }
+
+        return handleSnapshotAppProfile(pw, snapshot, packageName, splitName);
+    }
+
+    private int handleSnapshotBootProfile(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
+            throws SnapshotProfileException {
+        String outputRelativePath = "android.prof";
+        ParcelFileDescriptor fd = mArtManagerLocal.snapshotBootImageProfile(snapshot);
+        writeProfileFdContentsToFile(pw, fd, outputRelativePath);
+        return 0;
+    }
+
+    private int handleSnapshotAppProfile(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @Nullable String splitName) throws SnapshotProfileException {
+        String outputRelativePath = String.format("%s%s.prof", packageName,
+                splitName != null ? String.format("-split_%s.apk", splitName) : "");
+        ParcelFileDescriptor fd =
+                mArtManagerLocal.snapshotAppProfile(snapshot, packageName, splitName);
+        writeProfileFdContentsToFile(pw, fd, outputRelativePath);
+        return 0;
+    }
+
+    private int handleDumpProfile(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
+            throws SnapshotProfileException {
+        boolean dumpClassesAndMethods = false;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--dump-classes-and-methods": {
+                    dumpClassesAndMethods = true;
+                    break;
+                }
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        String packageName = getNextArgRequired();
+
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+        try (var tracing = new Utils.Tracing("dump profiles")) {
+            for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
+                if (!dexInfo.hasCode()) {
+                    continue;
+                }
+                String profileName = PrimaryDexUtils.getProfileName(dexInfo.splitName());
+                // The path is intentionally inconsistent with the one for "snapshot-profile". This
+                // is to match the behavior of the legacy PM shell command.
+                String outputRelativePath =
+                        String.format("%s-%s.prof.txt", packageName, profileName);
+                ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile(
+                        snapshot, packageName, dexInfo.splitName(), dumpClassesAndMethods);
+                writeProfileFdContentsToFile(pw, fd, outputRelativePath);
+            }
+        }
+        return 0;
+    }
+
+    private int handleBatchDexopt(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        String reason = null;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-r":
+                    reason = getNextArgRequired();
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+        if (reason == null) {
+            pw.println("Error: '-r REASON' is required");
+            return 1;
+        }
+        if (!ReasonMapping.BATCH_DEXOPT_REASONS.contains(reason)) {
+            pw.printf("Error: Invalid batch dexopt reason '%s'. Valid values are: %s\n", reason,
+                    ReasonMapping.BATCH_DEXOPT_REASONS);
+            return 1;
+        }
+        DexoptResult result;
+        ExecutorService progressCallbackExecutor = Executors.newSingleThreadExecutor();
+        try (var signal = new WithCancellationSignal(pw, true /* verbose */)) {
+            result = mArtManagerLocal.dexoptPackages(
+                    snapshot, reason, signal.get(), progressCallbackExecutor, progress -> {
+                        pw.println(String.format("Dexopting apps: %d%%", progress.getPercentage()));
+                        pw.flush();
+                    });
+            Utils.executeAndWait(progressCallbackExecutor, () -> {
+                printDexoptResult(pw, result, true /* verbose */, true /* multiPackage */);
+            });
+        } finally {
+            progressCallbackExecutor.shutdown();
+        }
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        // No one should call this. The help text should be printed by the `onHelp` handler of `cmd
+        // package`.
+        throw new UnsupportedOperationException("Unexpected call to 'onHelp'");
+    }
+
+    public static void printHelp(@NonNull PrintWriter pw) {
+        pw.println("compile [-r COMPILATION_REASON] [-m COMPILER_FILTER] [-p PRIORITY] [-f]");
+        pw.println("    [--primary-dex] [--secondary-dex] [--include-dependencies] [--full]");
+        pw.println("    [--split SPLIT_NAME] [--reset] [-a | PACKAGE_NAME]");
+        pw.println("  Dexopt a package or all packages.");
+        pw.println("  Options:");
+        pw.println("    -a Dexopt all packages");
+        pw.println("    -r Set the compiler filter and the priority based on the given");
+        pw.println("       compilation reason.");
+        pw.println("       Available options: 'first-boot', 'boot-after-ota',");
+        pw.println("       'boot-after-mainline-update', 'install', 'bg-dexopt', 'cmdline'.");
+        pw.println("    -m Set the target compiler filter. The filter actually used may be");
+        pw.println("       different, e.g. 'speed-profile' without profiles present may result in");
+        pw.println("       'verify' being used instead. If not specified, this defaults to the");
+        pw.println("       value given by -r, or the system property 'pm.dexopt.cmdline'.");
+        pw.println("       Available options (in descending order): 'speed', 'speed-profile',");
+        pw.println("       'verify'.");
+        pw.println("    -p Set the priority of the operation, which determines the resource usage");
+        pw.println("       and the process priority. If not specified, this defaults to");
+        pw.println("       the value given by -r, or 'PRIORITY_INTERACTIVE'.");
+        pw.println("       Available options (in descending order): 'PRIORITY_BOOT',");
+        pw.println("       'PRIORITY_INTERACTIVE_FAST', 'PRIORITY_INTERACTIVE',");
+        pw.println("       'PRIORITY_BACKGROUND'.");
+        pw.println("    -f Force dexopt, also when the compiler filter being applied is not");
+        pw.println("       better than that of the current dexopt artifacts for a package.");
+        pw.println("    --reset Reset the dexopt state of the package as if the package is newly");
+        pw.println("       installed.");
+        pw.println("       More specifically, it clears reference profiles, current profiles, and");
+        pw.println("       any code compiled from those local profiles. If there is an external");
+        pw.println("       profile (e.g., a cloud profile), the code compiled from that profile");
+        pw.println("       will be kept.");
+        pw.println("       For secondary dex files, it also clears all dexopt artifacts.");
+        pw.println("       When this flag is set, all the other flags are ignored.");
+        pw.println("    -v Verbose mode. This mode prints detailed results.");
+        pw.println("  Scope options:");
+        pw.println("    --primary-dex Dexopt primary dex files only (all APKs that are installed");
+        pw.println("      as part of the package, including the base APK and all other split");
+        pw.println("      APKs).");
+        pw.println("    --secondary-dex Dexopt secondary dex files only (APKs/JARs that the app");
+        pw.println("      puts in its own data directory at runtime and loads with custom");
+        pw.println("      classloaders).");
+        pw.println("    --include-dependencies Include dependency packages (dependencies that are");
+        pw.println("      declared by the app with <uses-library> tags and transitive");
+        pw.println("      dependencies). This option can only be used together with");
+        pw.println("      '--primary-dex' or '--secondary-dex'.");
+        pw.println("    --full Dexopt all above. (Recommended)");
+        pw.println("    --split SPLIT_NAME Only dexopt the given split. If SPLIT_NAME is an empty");
+        pw.println("      string, only dexopt the base APK.");
+        pw.println("      Tip: To pass an empty string, use a pair of quotes (\"\").");
+        pw.println("      When this option is set, '--primary-dex', '--secondary-dex',");
+        pw.println("      '--include-dependencies', '--full', and '-a' must not be set.");
+        pw.println("    Note: If none of the scope options above are set, the scope defaults to");
+        pw.println("    '--primary-dex --include-dependencies'.");
+        pw.println();
+        pw.println("delete-dexopt PACKAGE_NAME");
+        pw.println("  Delete the dexopt artifacts of both primary dex files and secondary dex");
+        pw.println("  files of a package.");
+        pw.println();
+        pw.println("bg-dexopt-job [--cancel | --disable | --enable]");
+        pw.println("  Control the background dexopt job.");
+        pw.println("  Without flags, it starts a background dexopt job immediately and waits for");
+        pw.println("    it to finish. If a job is already started either automatically by the");
+        pw.println("    system or through this command, it will wait for the running job to");
+        pw.println("    finish and then start a new one.");
+        pw.println("  Different from 'pm compile -r bg-dexopt -a', the behavior of this command");
+        pw.println("  is the same as a real background dexopt job. Specifically,");
+        pw.println("    - It only dexopts a subset of apps determined by either the system's");
+        pw.println("      default logic based on app usage data or the custom logic specified by");
+        pw.println("      the 'ArtManagerLocal.setBatchDexoptStartCallback' Java API.");
+        pw.println("    - It runs dexopt in parallel, where the concurrency setting is specified");
+        pw.println("      by the system property 'pm.dexopt.bg-dexopt.concurrency'.");
+        pw.println("    - If the storage is low, it also downgrades unused apps.");
+        pw.println("    - It also cleans up obsolete files.");
+        pw.println("  Options:");
+        pw.println("    --cancel Cancel any currently running background dexopt job immediately.");
+        pw.println("      This cancels jobs started either automatically by the system or through");
+        pw.println("      this command. This command is not blocking.");
+        pw.println("    --disable: Disable the background dexopt job from being started by the");
+        pw.println("      job scheduler. If a job is already started by the job scheduler and is");
+        pw.println("      running, it will be cancelled immediately. Does not affect jobs started");
+        pw.println("      through this command or by the system in other ways.");
+        pw.println("      This state will be lost when the system_server process exits.");
+        pw.println("    --enable: Enable the background dexopt job to be started by the job");
+        pw.println("      scheduler again, if previously disabled by --disable.");
+        pw.println("  When a list of package names is passed, this command does NOT start a real");
+        pw.println("  background dexopt job. Instead, it dexopts the given packages sequentially.");
+        pw.println("  This usage is deprecated. Please use 'pm compile -r bg-dexopt PACKAGE_NAME'");
+        pw.println("  instead.");
+        pw.println();
+        pw.println("snapshot-profile [android | [--split SPLIT_NAME] PACKAGE_NAME]");
+        pw.println("  Snapshot the boot image profile or the app profile and save it to");
+        pw.println("  '" + PROFILE_DEBUG_LOCATION + "'.");
+        pw.println("  If 'android' is passed, the command snapshots the boot image profile, and");
+        pw.println("  the output filename is 'android.prof'.");
+        pw.println("  If a package name is passed, the command snapshots the app profile.");
+        pw.println("  Options:");
+        pw.println("    --split SPLIT_NAME If specified, the command snapshots the profile of the");
+        pw.println("      given split, and the output filename is");
+        pw.println("      'PACKAGE_NAME-split_SPLIT_NAME.apk.prof'.");
+        pw.println("      If not specified, the command snapshots the profile of the base APK,");
+        pw.println("      and the output filename is 'PACKAGE_NAME.prof'");
+        pw.println();
+        pw.println("dump-profiles [--dump-classes-and-methods] PACKAGE_NAME");
+        pw.println("  Dump the profiles of the given app in text format and save the outputs to");
+        pw.println("  '" + PROFILE_DEBUG_LOCATION + "'.");
+        pw.println("  The profile of the base APK is dumped to 'PACKAGE_NAME-primary.prof.txt'");
+        pw.println("  The profile of a split APK is dumped to");
+        pw.println("  'PACKAGE_NAME-SPLIT_NAME.split.prof.txt'");
+        pw.println();
+        pw.println("art SUB_COMMAND [ARGS]...");
+        pw.println("  Run ART Service commands");
+        pw.println();
+        pw.println("  Supported sub-commands:");
+        pw.println();
+        pw.println("  cancel JOB_ID");
+        pw.println("    Cancel a job started by a shell command. This doesn't apply to background");
+        pw.println("    jobs.");
+        pw.println();
+        pw.println("  clear-app-profiles PACKAGE_NAME");
+        pw.println("    Clear the profiles that are collected locally for the given package,");
+        pw.println("    including the profiles for primary and secondary dex files. More");
+        pw.println("    specifically, this command clears reference profiles and current");
+        pw.println("    profiles. External profiles (e.g., cloud profiles) will be kept.");
+        pw.println();
+        pw.println("  cleanup");
+        pw.println("    Cleanup obsolete files, such as dexopt artifacts that are outdated or");
+        pw.println("    correspond to dex container files that no longer exist.");
+        pw.println();
+        pw.println("  dump [PACKAGE_NAME]");
+        pw.println("    Dumps the dexopt state in text format to stdout.");
+        pw.println("    If PACKAGE_NAME is empty, the command is for all packages. Otherwise, it");
+        pw.println("    is for the given package.");
+        pw.println();
+        pw.println("  dexopt-packages -r REASON");
+        pw.println("    Run batch dexopt for the given reason.");
+        pw.println("    Valid values for REASON: 'first-boot', 'boot-after-ota',");
+        pw.println("    'boot-after-mainline-update', 'bg-dexopt'");
+        pw.println("    This command is different from 'pm compile -r REASON -a'. For example, it");
+        pw.println("    only dexopts a subset of apps, and it runs dexopt in parallel. See the");
+        pw.println("    API documentation for 'ArtManagerLocal.dexoptPackages' for details.");
+    }
+
+    private void enforceRootOrShell() {
+        final int uid = Binder.getCallingUid();
+        if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
+            throw new SecurityException("ART service shell commands need root or shell access");
+        }
+    }
+
+    @PriorityClassApi
+    int parsePriorityClass(@NonNull String priorityClass) {
+        switch (priorityClass) {
+            case "PRIORITY_BOOT":
+                return ArtFlags.PRIORITY_BOOT;
+            case "PRIORITY_INTERACTIVE_FAST":
+                return ArtFlags.PRIORITY_INTERACTIVE_FAST;
+            case "PRIORITY_INTERACTIVE":
+                return ArtFlags.PRIORITY_INTERACTIVE;
+            case "PRIORITY_BACKGROUND":
+                return ArtFlags.PRIORITY_BACKGROUND;
+            default:
+                throw new IllegalArgumentException("Unknown priority " + priorityClass);
+        }
+    }
+
+    @Nullable
+    private String getSplitName(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @NonNull String splitArg) {
+        if (splitArg.isEmpty()) {
+            return null; // Base APK.
+        }
+
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+        List<PrimaryDexInfo> dexInfoList = PrimaryDexUtils.getDexInfo(pkg);
+
+        for (PrimaryDexInfo dexInfo : dexInfoList) {
+            if (splitArg.equals(dexInfo.splitName())) {
+                return splitArg;
+            }
+        }
+
+        for (PrimaryDexInfo dexInfo : dexInfoList) {
+            if (splitArg.equals(new File(dexInfo.dexPath()).getName())) {
+                pw.println("Warning: Specifying a split using a filename is deprecated. Please "
+                        + "use a split name (or an empty string for the base APK) instead");
+                return dexInfo.splitName();
+            }
+        }
+
+        throw new IllegalArgumentException(String.format("Split '%s' not found", splitArg));
+    }
+
+    @Nullable
+    private String getSplitNameByFullPath(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String packageName, @NonNull String fullPath) {
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+        List<PrimaryDexInfo> dexInfoList = PrimaryDexUtils.getDexInfo(pkg);
+
+        for (PrimaryDexInfo dexInfo : dexInfoList) {
+            if (fullPath.equals(dexInfo.dexPath())) {
+                return dexInfo.splitName();
+            }
+        }
+
+        throw new IllegalArgumentException(String.format("Code path '%s' not found", fullPath));
+    }
+
+    private int resetPackages(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull List<String> packageNames, boolean verbose) {
+        try (var signal = new WithCancellationSignal(pw, verbose)) {
+            for (String packageName : packageNames) {
+                DexoptResult result =
+                        mArtManagerLocal.resetDexoptStatus(snapshot, packageName, signal.get());
+                printDexoptResult(pw, result, verbose, packageNames.size() > 1);
+            }
+        }
+        return 0;
+    }
+
+    private int dexoptPackages(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull List<String> packageNames, @NonNull DexoptParams params, boolean verbose) {
+        try (var signal = new WithCancellationSignal(pw, verbose)) {
+            for (String packageName : packageNames) {
+                DexoptResult result =
+                        mArtManagerLocal.dexoptPackage(snapshot, packageName, params, signal.get());
+                printDexoptResult(pw, result, verbose, packageNames.size() > 1);
+            }
+        }
+        return 0;
+    }
+
+    @NonNull
+    private String dexoptResultStatusToSimpleString(@DexoptResultStatus int status) {
+        return (status == DexoptResult.DEXOPT_SKIPPED || status == DexoptResult.DEXOPT_PERFORMED)
+                ? "Success"
+                : "Failure";
+    }
+
+    private void printDexoptResult(@NonNull PrintWriter pw, @NonNull DexoptResult result,
+            boolean verbose, boolean multiPackage) {
+        for (PackageDexoptResult packageResult : result.getPackageDexoptResults()) {
+            if (verbose) {
+                pw.printf("[%s]\n", packageResult.getPackageName());
+                for (DexContainerFileDexoptResult fileResult :
+                        packageResult.getDexContainerFileDexoptResults()) {
+                    pw.println(fileResult);
+                }
+            } else if (multiPackage) {
+                pw.printf("[%s] %s\n", packageResult.getPackageName(),
+                        dexoptResultStatusToSimpleString(packageResult.getStatus()));
+            }
+        }
+
+        if (verbose) {
+            pw.println("Final Status: "
+                    + DexoptResult.dexoptResultStatusToString(result.getFinalStatus()));
+        } else if (!multiPackage) {
+            // Multi-package result is printed by the loop above.
+            pw.println(dexoptResultStatusToSimpleString(result.getFinalStatus()));
+        }
+
+        pw.flush();
+    }
+
+    private void writeProfileFdContentsToFile(@NonNull PrintWriter pw,
+            @NonNull ParcelFileDescriptor fd, @NonNull String outputRelativePath) {
+        try {
+            StructStat st = Os.stat(PROFILE_DEBUG_LOCATION);
+            if (st.st_uid != Process.SYSTEM_UID || st.st_gid != Process.SHELL_UID
+                    || (st.st_mode & 0007) != 0) {
+                throw new RuntimeException(
+                        String.format("%s has wrong permissions: uid=%d, gid=%d, mode=%o",
+                                PROFILE_DEBUG_LOCATION, st.st_uid, st.st_gid, st.st_mode));
+            }
+        } catch (ErrnoException e) {
+            throw new RuntimeException("Unable to stat " + PROFILE_DEBUG_LOCATION, e);
+        }
+        Path outputPath = Paths.get(PROFILE_DEBUG_LOCATION, outputRelativePath);
+        try (InputStream inputStream = new AutoCloseInputStream(fd);
+                FileOutputStream outputStream = new FileOutputStream(outputPath.toFile())) {
+            // The system server doesn't have the permission to chown the file to "shell", so we
+            // make it readable by everyone and put it in a directory that is only accessible by
+            // "shell", which is created by system/core/rootdir/init.rc. The permissions are
+            // verified by the code above.
+            Os.fchmod(outputStream.getFD(), 0644);
+            Streams.copy(inputStream, outputStream);
+            pw.printf("Profile saved to '%s'\n", outputPath);
+        } catch (IOException | ErrnoException e) {
+            Utils.deleteIfExistsSafe(outputPath);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static class WithCancellationSignal implements AutoCloseable {
+        @NonNull private final CancellationSignal mSignal = new CancellationSignal();
+        @NonNull private final String mJobId;
+
+        public WithCancellationSignal(@NonNull PrintWriter pw, boolean verbose) {
+            mJobId = UUID.randomUUID().toString();
+            if (verbose) {
+                pw.printf(
+                        "Job running. To cancel it, run 'pm art cancel %s' in a separate shell.\n",
+                        mJobId);
+                pw.flush();
+            }
+
+            synchronized (sCancellationSignalMap) {
+                sCancellationSignalMap.put(mJobId, mSignal);
+            }
+        }
+
+        @NonNull
+        public CancellationSignal get() {
+            return mSignal;
+        }
+
+        public void close() {
+            synchronized (sCancellationSignalMap) {
+                sCancellationSignalMap.remove(mJobId);
+            }
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java b/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java
new file mode 100644
index 0000000..b297326
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.ArtManagerLocal.ScheduleBackgroundDexoptJobCallback;
+import static com.android.server.art.model.ArtFlags.ScheduleStatus;
+import static com.android.server.art.model.Config.Callback;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.pm.PackageManagerLocal;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/** @hide */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class BackgroundDexoptJob {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    /**
+     * "android" is the package name for a <service> declared in
+     * frameworks/base/core/res/AndroidManifest.xml
+     */
+    private static final String JOB_PKG_NAME = Utils.PLATFORM_PACKAGE_NAME;
+    /** An arbitrary number. Must be unique among all jobs owned by the system uid. */
+    private static final int JOB_ID = 27873780;
+
+    @VisibleForTesting public static final long JOB_INTERVAL_MS = TimeUnit.DAYS.toMillis(1);
+
+    @NonNull private final Injector mInjector;
+
+    @GuardedBy("this") @Nullable private CompletableFuture<Result> mRunningJob = null;
+    @GuardedBy("this") @Nullable private CancellationSignal mCancellationSignal = null;
+    @GuardedBy("this") @NonNull private Optional<Integer> mLastStopReason = Optional.empty();
+
+    public BackgroundDexoptJob(@NonNull Context context, @NonNull ArtManagerLocal artManagerLocal,
+            @NonNull Config config) {
+        this(new Injector(context, artManagerLocal, config));
+    }
+
+    @VisibleForTesting
+    public BackgroundDexoptJob(@NonNull Injector injector) {
+        mInjector = injector;
+    }
+
+    /** Handles {@link BackgroundDexoptJobService#onStartJob(JobParameters)}. */
+    public boolean onStartJob(
+            @NonNull BackgroundDexoptJobService jobService, @NonNull JobParameters params) {
+        start().thenAcceptAsync(result -> {
+            writeStats(result);
+            // This is a periodic job, where the interval is specified in the `JobInfo`. "true"
+            // means to execute again during a future idle maintenance window in the same
+            // interval, while "false" means not to execute again during a future idle maintenance
+            // window in the same interval but to execute again in the next interval.
+            // This call will be ignored if `onStopJob` is called.
+            boolean wantsReschedule = result instanceof CompletedResult
+                    && ((CompletedResult) result).dexoptResult().getFinalStatus()
+                            == DexoptResult.DEXOPT_CANCELLED;
+            jobService.jobFinished(params, wantsReschedule);
+        });
+        // "true" means the job will continue running until `jobFinished` is called.
+        return true;
+    }
+
+    /** Handles {@link BackgroundDexoptJobService#onStopJob(JobParameters)}. */
+    public boolean onStopJob(@NonNull JobParameters params) {
+        synchronized (this) {
+            mLastStopReason = Optional.of(params.getStopReason());
+        }
+        cancel();
+        // "true" means to execute again during a future idle maintenance window in the same
+        // interval.
+        return true;
+    }
+
+    /** Handles {@link ArtManagerLocal#scheduleBackgroundDexoptJob()}. */
+    public @ScheduleStatus int schedule() {
+        if (this != BackgroundDexoptJobService.getJob()) {
+            throw new IllegalStateException("This job cannot be scheduled");
+        }
+
+        if (SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt", false /* def */)) {
+            Log.i(TAG, "Job is disabled by system property 'pm.dexopt.disable_bg_dexopt'");
+            return ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP;
+        }
+
+        JobInfo.Builder builder =
+                new JobInfo
+                        .Builder(JOB_ID,
+                                new ComponentName(
+                                        JOB_PKG_NAME, BackgroundDexoptJobService.class.getName()))
+                        .setPeriodic(JOB_INTERVAL_MS)
+                        .setRequiresDeviceIdle(true)
+                        .setRequiresCharging(true)
+                        .setRequiresBatteryNotLow(true);
+
+        Callback<ScheduleBackgroundDexoptJobCallback, Void> callback =
+                mInjector.getConfig().getScheduleBackgroundDexoptJobCallback();
+        if (callback != null) {
+            Utils.executeAndWait(
+                    callback.executor(), () -> { callback.get().onOverrideJobInfo(builder); });
+        }
+
+        JobInfo info = builder.build();
+        if (info.isRequireStorageNotLow()) {
+            // See the javadoc of
+            // `ArtManagerLocal.ScheduleBackgroundDexoptJobCallback.onOverrideJobInfo` for details.
+            throw new IllegalStateException("'setRequiresStorageNotLow' must not be set");
+        }
+
+        return mInjector.getJobScheduler().schedule(info) == JobScheduler.RESULT_SUCCESS
+                ? ArtFlags.SCHEDULE_SUCCESS
+                : ArtFlags.SCHEDULE_JOB_SCHEDULER_FAILURE;
+    }
+
+    /** Handles {@link ArtManagerLocal#unscheduleBackgroundDexoptJob()}. */
+    public void unschedule() {
+        if (this != BackgroundDexoptJobService.getJob()) {
+            throw new IllegalStateException("This job cannot be unscheduled");
+        }
+
+        mInjector.getJobScheduler().cancel(JOB_ID);
+    }
+
+    @NonNull
+    public synchronized CompletableFuture<Result> start() {
+        if (mRunningJob != null) {
+            Log.i(TAG, "Job is already running");
+            return mRunningJob;
+        }
+
+        mCancellationSignal = new CancellationSignal();
+        mLastStopReason = Optional.empty();
+        mRunningJob = new CompletableFuture().supplyAsync(() -> {
+            try (var tracing = new Utils.TracingWithTimingLogging(TAG, "jobExecution")) {
+                return run(mCancellationSignal);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Fatal error", e);
+                return new FatalErrorResult();
+            } finally {
+                synchronized (this) {
+                    mRunningJob = null;
+                    mCancellationSignal = null;
+                }
+            }
+        });
+        return mRunningJob;
+    }
+
+    public synchronized void cancel() {
+        if (mRunningJob == null) {
+            Log.i(TAG, "Job is not running");
+            return;
+        }
+
+        mCancellationSignal.cancel();
+        Log.i(TAG, "Job cancelled");
+    }
+
+    @Nullable
+    public synchronized CompletableFuture<Result> get() {
+        return mRunningJob;
+    }
+
+    @NonNull
+    private CompletedResult run(@NonNull CancellationSignal cancellationSignal) {
+        long startTimeMs = SystemClock.uptimeMillis();
+        DexoptResult dexoptResult;
+        try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) {
+            dexoptResult = mInjector.getArtManagerLocal().dexoptPackages(snapshot,
+                    ReasonMapping.REASON_BG_DEXOPT, cancellationSignal,
+                    null /* processCallbackExecutor */, null /* processCallback */);
+
+            // For simplicity, we don't support cancelling the following operation in the middle.
+            // This is fine because it typically takes only a few seconds.
+            if (!cancellationSignal.isCanceled()) {
+                // We do the cleanup after dexopt so that it doesn't affect the `getSizeBeforeBytes`
+                // field in the result that we send to callbacks. Admittedly, this will cause us to
+                // lose some chance to dexopt when the storage is very low, but it's fine because we
+                // can still dexopt in the next run.
+                long freedBytes = mInjector.getArtManagerLocal().cleanup(snapshot);
+                Log.i(TAG, String.format("Freed %d bytes", freedBytes));
+            }
+        }
+        return CompletedResult.create(dexoptResult, SystemClock.uptimeMillis() - startTimeMs);
+    }
+
+    private void writeStats(@NonNull Result result) {
+        Optional<Integer> stopReason;
+        synchronized (this) {
+            stopReason = mLastStopReason;
+        }
+        if (result instanceof CompletedResult) {
+            var completedResult = (CompletedResult) result;
+            ArtStatsLog.write(ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
+                    getStatusForStats(completedResult, stopReason),
+                    stopReason.orElse(JobParameters.STOP_REASON_UNDEFINED),
+                    completedResult.durationMs(), 0 /* deprecated */);
+        } else if (result instanceof FatalErrorResult) {
+            ArtStatsLog.write(ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
+                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_FATAL_ERROR,
+                    JobParameters.STOP_REASON_UNDEFINED, 0 /* durationMs */, 0 /* deprecated */);
+        }
+    }
+
+    private int getStatusForStats(@NonNull CompletedResult result, Optional<Integer> stopReason) {
+        if (result.dexoptResult().getFinalStatus() == DexoptResult.DEXOPT_CANCELLED) {
+            if (stopReason.isPresent()) {
+                return ArtStatsLog
+                        .BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_CANCELLATION;
+            } else {
+                return ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_API;
+            }
+        }
+
+        boolean isSkippedDueToStorageLow =
+                result.dexoptResult()
+                        .getPackageDexoptResults()
+                        .stream()
+                        .flatMap(packageResult
+                                -> packageResult.getDexContainerFileDexoptResults().stream())
+                        .anyMatch(fileResult -> fileResult.isSkippedDueToStorageLow());
+        if (isSkippedDueToStorageLow) {
+            return ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_NO_SPACE_LEFT;
+        }
+
+        return ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED;
+    }
+
+    static abstract class Result {}
+    static class FatalErrorResult extends Result {}
+
+    @AutoValue
+    static abstract class CompletedResult extends Result {
+        abstract @NonNull DexoptResult dexoptResult();
+        abstract long durationMs();
+
+        @NonNull
+        static CompletedResult create(@NonNull DexoptResult dexoptResult, long durationMs) {
+            return new AutoValue_BackgroundDexoptJob_CompletedResult(dexoptResult, durationMs);
+        }
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Injector {
+        @NonNull private final Context mContext;
+        @NonNull private final ArtManagerLocal mArtManagerLocal;
+        @NonNull private final Config mConfig;
+
+        Injector(@NonNull Context context, @NonNull ArtManagerLocal artManagerLocal,
+                @NonNull Config config) {
+            mContext = context;
+            mArtManagerLocal = artManagerLocal;
+            mConfig = config;
+
+            // Call the getters for various dependencies, to ensure correct initialization order.
+            getPackageManagerLocal();
+            getJobScheduler();
+        }
+
+        @NonNull
+        public ArtManagerLocal getArtManagerLocal() {
+            return mArtManagerLocal;
+        }
+
+        @NonNull
+        public PackageManagerLocal getPackageManagerLocal() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(PackageManagerLocal.class));
+        }
+
+        @NonNull
+        public Config getConfig() {
+            return mConfig;
+        }
+
+        @NonNull
+        public JobScheduler getJobScheduler() {
+            return Objects.requireNonNull(mContext.getSystemService(JobScheduler.class));
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/BackgroundDexoptJobService.java b/libartservice/service/java/com/android/server/art/BackgroundDexoptJobService.java
new file mode 100644
index 0000000..41425ee
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/BackgroundDexoptJobService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.server.LocalManagerRegistry;
+
+/**
+ * Entry point for the callback from the job scheduler. This class is instantiated by the system
+ * automatically.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class BackgroundDexoptJobService extends JobService {
+    @Override
+    public boolean onStartJob(@NonNull JobParameters params) {
+        return getJob().onStartJob(this, params);
+    }
+
+    @Override
+    public boolean onStopJob(@NonNull JobParameters params) {
+        return getJob().onStopJob(params);
+    }
+
+    @NonNull
+    static BackgroundDexoptJob getJob() {
+        return LocalManagerRegistry.getManager(ArtManagerLocal.class).getBackgroundDexoptJob();
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/Constants.java b/libartservice/service/java/com/android/server/art/Constants.java
new file mode 100644
index 0000000..f4c6e07
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Constants.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.system.Os;
+
+/**
+ * A mockable wrapper class for device-specific constants.
+ *
+ * @hide
+ */
+public class Constants {
+    private Constants() {}
+
+    /** Returns the ABI that the device prefers. */
+    @NonNull
+    public static String getPreferredAbi() {
+        return Build.SUPPORTED_ABIS[0];
+    }
+
+    /** Returns the 64 bit ABI that is native to the device. */
+    @Nullable
+    public static String getNative64BitAbi() {
+        // The value comes from "ro.product.cpu.abilist64" and we assume that the first element is
+        // the native one.
+        return Build.SUPPORTED_64_BIT_ABIS.length > 0 ? Build.SUPPORTED_64_BIT_ABIS[0] : null;
+    }
+
+    /** Returns the 32 bit ABI that is native to the device. */
+    @Nullable
+    public static String getNative32BitAbi() {
+        // The value comes from "ro.product.cpu.abilist32" and we assume that the first element is
+        // the native one.
+        return Build.SUPPORTED_32_BIT_ABIS.length > 0 ? Build.SUPPORTED_32_BIT_ABIS[0] : null;
+    }
+
+    @Nullable
+    public static String getenv(@NonNull String name) {
+        return Os.getenv(name);
+    }
+
+    public static boolean isBootImageProfilingEnabled() {
+        boolean profileBootClassPath = SystemProperties.getBoolean(
+                "persist.device_config.runtime_native_boot.profilebootclasspath",
+                SystemProperties.getBoolean("dalvik.vm.profilebootclasspath", false /* def */));
+        return Build.isDebuggable() && profileBootClassPath;
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/Debouncer.java b/libartservice/service/java/com/android/server/art/Debouncer.java
new file mode 100644
index 0000000..61aea81
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Debouncer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * A class that executes commands with a minimum interval.
+ *
+ * @hide
+ */
+public class Debouncer {
+    @NonNull private Supplier<ScheduledExecutorService> mScheduledExecutorFactory;
+    private final long mIntervalMs;
+    @Nullable private ScheduledFuture<?> mCurrentTask = null;
+
+    public Debouncer(
+            long intervalMs, @NonNull Supplier<ScheduledExecutorService> scheduledExecutorFactory) {
+        mScheduledExecutorFactory = scheduledExecutorFactory;
+        mIntervalMs = intervalMs;
+    }
+
+    /**
+     * Runs the given command after the interval has passed. If another command comes in during
+     * this interval, the previous one will never run.
+     */
+    synchronized public void maybeRunAsync(@NonNull Runnable command) {
+        if (mCurrentTask != null) {
+            mCurrentTask.cancel(false /* mayInterruptIfRunning */);
+        }
+        ScheduledExecutorService executor = mScheduledExecutorFactory.get();
+        mCurrentTask = executor.schedule(command, mIntervalMs, TimeUnit.MILLISECONDS);
+        executor.shutdown();
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
new file mode 100644
index 0000000..153e83b
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
@@ -0,0 +1,1162 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.Immutable;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.art.model.DexContainerFileUseInfo;
+import com.android.server.art.proto.DexUseProto;
+import com.android.server.art.proto.Int32Value;
+import com.android.server.art.proto.PackageDexUseProto;
+import com.android.server.art.proto.PrimaryDexUseProto;
+import com.android.server.art.proto.PrimaryDexUseRecordProto;
+import com.android.server.art.proto.SecondaryDexUseProto;
+import com.android.server.art.proto.SecondaryDexUseRecordProto;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+
+import com.google.auto.value.AutoValue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * A singleton class that maintains the information about dex uses. This class is thread-safe.
+ *
+ * This class collects data sent directly by apps, and hence the data should be trusted as little as
+ * possible.
+ *
+ * To avoid overwriting data, {@link #load()} must be called exactly once, during initialization.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class DexUseManagerLocal {
+    private static final String TAG = ArtManagerLocal.TAG;
+    private static final String FILENAME = "/data/system/package-dex-usage.pb";
+
+    /**
+     * The minimum interval between disk writes.
+     *
+     * In practice, the interval will be much longer because we use a debouncer to postpone the disk
+     * write to the end of a series of changes. Note that in theory we could postpone the disk write
+     * indefinitely, and therefore we could lose data if the device isn't shut down in the normal
+     * way, but that's fine because the data isn't crucial and is recoverable.
+     *
+     * @hide
+     */
+    @VisibleForTesting public static final long INTERVAL_MS = 15_000;
+
+    private static final Object sLock = new Object();
+    @GuardedBy("sLock") @Nullable private static DexUseManagerLocal sInstance = null;
+
+    @NonNull private final Injector mInjector;
+    @NonNull private final Debouncer mDebouncer;
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock") @NonNull private DexUse mDexUse; // Initialized by `load`.
+    @GuardedBy("mLock") private int mRevision = 0;
+    @GuardedBy("mLock") private int mLastCommittedRevision = 0;
+    @GuardedBy("mLock")
+    @NonNull
+    private SecondaryDexLocationManager mSecondaryDexLocationManager =
+            new SecondaryDexLocationManager();
+
+    /**
+     * Creates the singleton instance.
+     *
+     * Only {@code SystemServer} should create the instance and register it in {@link
+     * LocalManagerRegistry}. Other API users should obtain the instance from {@link
+     * LocalManagerRegistry}.
+     *
+     * In practice, it must be created and registered in {@link LocalManagerRegistry} before {@code
+     * PackageManagerService} starts because {@code PackageManagerService} needs it as soon as it
+     * starts. It's safe to create an instance early because it doesn't depend on anything else.
+     *
+     * @param context the system server context
+     * @throws IllegalStateException if the instance is already created
+     * @throws NullPointerException if required dependencies are missing
+     */
+    @NonNull
+    public static DexUseManagerLocal createInstance(@NonNull Context context) {
+        synchronized (sLock) {
+            if (sInstance != null) {
+                throw new IllegalStateException("DexUseManagerLocal is already created");
+            }
+            sInstance = new DexUseManagerLocal(context);
+            return sInstance;
+        }
+    }
+
+    private DexUseManagerLocal(@NonNull Context context) {
+        this(new Injector(context));
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public DexUseManagerLocal(@NonNull Injector injector) {
+        mInjector = injector;
+        mDebouncer = new Debouncer(INTERVAL_MS, mInjector::createScheduledExecutor);
+        load();
+    }
+
+    /** Notifies dex use manager that {@link Context#registerReceiver} is ready for use. */
+    public void systemReady() {
+        // Save the data when the device is being shut down. The receiver is blocking, with a
+        // 10s timeout.
+        mInjector.getContext().registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                context.unregisterReceiver(this);
+                save();
+            }
+        }, new IntentFilter(Intent.ACTION_SHUTDOWN));
+    }
+
+    /**
+     * Returns the information about the use of all secondary dex files owned by the given package,
+     * or an empty list if the package does not own any secondary dex file or it does not exist.
+     */
+    @NonNull
+    public List<DexContainerFileUseInfo> getSecondaryDexContainerFileUseInfo(
+            @NonNull String packageName) {
+        return getSecondaryDexInfo(packageName)
+                .stream()
+                .map(info
+                        -> DexContainerFileUseInfo.create(info.dexPath(), info.userHandle(),
+                                info.loaders()
+                                        .stream()
+                                        .map(loader -> loader.loadingPackageName())
+                                        .collect(Collectors.toSet())))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Returns all entities that load the given primary dex file owned by the given package.
+     *
+     * @hide
+     */
+    @NonNull
+    public Set<DexLoader> getPrimaryDexLoaders(
+            @NonNull String packageName, @NonNull String dexPath) {
+        synchronized (mLock) {
+            PackageDexUse packageDexUse =
+                    mDexUse.mPackageDexUseByOwningPackageName.get(packageName);
+            if (packageDexUse == null) {
+                return Set.of();
+            }
+            PrimaryDexUse primaryDexUse = packageDexUse.mPrimaryDexUseByDexFile.get(dexPath);
+            if (primaryDexUse == null) {
+                return Set.of();
+            }
+            return Set.copyOf(primaryDexUse.mRecordByLoader.keySet());
+        }
+    }
+
+    /**
+     * Returns whether a primary dex file owned by the given package is used by other apps.
+     *
+     * @hide
+     */
+    public boolean isPrimaryDexUsedByOtherApps(
+            @NonNull String packageName, @NonNull String dexPath) {
+        return isUsedByOtherApps(getPrimaryDexLoaders(packageName, dexPath), packageName);
+    }
+
+    /**
+     * Returns the basic information about all secondary dex files owned by the given package. This
+     * method doesn't take dex file visibility into account, so it can only be used for debugging
+     * purpose, such as dumpsys.
+     *
+     * @see #getFilteredDetailedSecondaryDexInfo(String)
+     * @hide
+     */
+    public @NonNull List<? extends SecondaryDexInfo> getSecondaryDexInfo(
+            @NonNull String packageName) {
+        return getSecondaryDexInfoImpl(packageName, false /* checkDexFile */);
+    }
+
+    /**
+     * Same as above, but requires disk IO, and returns the detailed information, including dex file
+     * visibility, filtered by dex file existence and visibility.
+     *
+     * @hide
+     */
+    public @NonNull List<DetailedSecondaryDexInfo> getFilteredDetailedSecondaryDexInfo(
+            @NonNull String packageName) {
+        return getSecondaryDexInfoImpl(packageName, true /* checkDexFile */);
+    }
+
+    /**
+     * Returns the last time the package was used, or 0 if the package has never been used.
+     *
+     * @hide
+     */
+    public long getPackageLastUsedAtMs(@NonNull String packageName) {
+        synchronized (mLock) {
+            PackageDexUse packageDexUse =
+                    mDexUse.mPackageDexUseByOwningPackageName.get(packageName);
+            if (packageDexUse == null) {
+                return 0;
+            }
+            long primaryLastUsedAtMs =
+                    packageDexUse.mPrimaryDexUseByDexFile.values()
+                            .stream()
+                            .flatMap(primaryDexUse
+                                    -> primaryDexUse.mRecordByLoader.values().stream())
+                            .map(record -> record.mLastUsedAtMs)
+                            .max(Long::compare)
+                            .orElse(0l);
+            long secondaryLastUsedAtMs =
+                    packageDexUse.mSecondaryDexUseByDexFile.values()
+                            .stream()
+                            .flatMap(secondaryDexUse
+                                    -> secondaryDexUse.mRecordByLoader.values().stream())
+                            .map(record -> record.mLastUsedAtMs)
+                            .max(Long::compare)
+                            .orElse(0l);
+            return Math.max(primaryLastUsedAtMs, secondaryLastUsedAtMs);
+        }
+    }
+
+    /**
+     * @param checkDexFile if true, check the existence and visibility of the dex files, and filter
+     *         the results accordingly. Note that the value of the {@link
+     *         DetailedSecondaryDexInfo#isDexFilePublic()} field is undefined if this argument is
+     *         false.
+     */
+    private @NonNull List<DetailedSecondaryDexInfo> getSecondaryDexInfoImpl(
+            @NonNull String packageName, boolean checkDexFile) {
+        synchronized (mLock) {
+            PackageDexUse packageDexUse =
+                    mDexUse.mPackageDexUseByOwningPackageName.get(packageName);
+            if (packageDexUse == null) {
+                return List.of();
+            }
+            var results = new ArrayList<DetailedSecondaryDexInfo>();
+            for (var entry : packageDexUse.mSecondaryDexUseByDexFile.entrySet()) {
+                String dexPath = entry.getKey();
+                SecondaryDexUse secondaryDexUse = entry.getValue();
+
+                @FileVisibility
+                int visibility = checkDexFile ? getDexFileVisibility(dexPath)
+                                              : FileVisibility.OTHER_READABLE;
+                if (visibility == FileVisibility.NOT_FOUND) {
+                    continue;
+                }
+
+                Map<DexLoader, SecondaryDexUseRecord> filteredRecordByLoader;
+                if (visibility == FileVisibility.OTHER_READABLE) {
+                    filteredRecordByLoader = secondaryDexUse.mRecordByLoader;
+                } else {
+                    // Only keep the entry that belongs to the same app.
+                    DexLoader sameApp = DexLoader.create(packageName, false /* isolatedProcess */);
+                    SecondaryDexUseRecord record = secondaryDexUse.mRecordByLoader.get(sameApp);
+                    filteredRecordByLoader = record != null ? Map.of(sameApp, record) : Map.of();
+                }
+                if (filteredRecordByLoader.isEmpty()) {
+                    continue;
+                }
+                List<String> distinctClcList =
+                        filteredRecordByLoader.values()
+                                .stream()
+                                .map(record -> Utils.assertNonEmpty(record.mClassLoaderContext))
+                                .filter(clc
+                                        -> !clc.equals(
+                                                SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT))
+                                .distinct()
+                                .collect(Collectors.toList());
+                String clc;
+                if (distinctClcList.size() == 0) {
+                    clc = SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT;
+                } else if (distinctClcList.size() == 1) {
+                    clc = distinctClcList.get(0);
+                } else {
+                    // If there are more than one class loader contexts, we can't dexopt the dex
+                    // file.
+                    clc = SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS;
+                }
+                // Although we filter out unsupported CLCs above, `distinctAbiNames` and `loaders`
+                // still need to take apps with unsupported CLCs into account because the vdex file
+                // is still usable to them.
+                Set<String> distinctAbiNames =
+                        filteredRecordByLoader.values()
+                                .stream()
+                                .map(record -> Utils.assertNonEmpty(record.mAbiName))
+                                .collect(Collectors.toSet());
+                Set<DexLoader> loaders = Set.copyOf(filteredRecordByLoader.keySet());
+                results.add(DetailedSecondaryDexInfo.create(dexPath,
+                        Objects.requireNonNull(secondaryDexUse.mUserHandle), clc, distinctAbiNames,
+                        loaders, isUsedByOtherApps(loaders, packageName),
+                        visibility == FileVisibility.OTHER_READABLE));
+            }
+            return Collections.unmodifiableList(results);
+        }
+    }
+
+    /**
+     * Notifies ART Service that a list of dex container files have been loaded.
+     *
+     * ART Service uses this information to:
+     * <ul>
+     *   <li>Determine whether an app is used by another app
+     *   <li>Record which secondary dex container files to dexopt and how to dexopt them
+     * </ul>
+     *
+     * @param loadingPackageName the name of the package who performs the load. ART Service assumes
+     *         that this argument has been validated that it exists in the snapshot and matches the
+     *         calling UID
+     * @param classLoaderContextByDexContainerFile a map from dex container files' absolute paths to
+     *         the string representations of the class loader contexts used to load them
+     * @throws IllegalArgumentException if {@code classLoaderContextByDexContainerFile} contains
+     *         invalid entries
+     */
+    public void notifyDexContainersLoaded(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String loadingPackageName,
+            @NonNull Map<String, String> classLoaderContextByDexContainerFile) {
+        // "android" comes from `SystemServerDexLoadReporter`. ART Services doesn't need to handle
+        // this case because it doesn't compile system server and system server isn't allowed to
+        // load artifacts produced by ART Services.
+        if (loadingPackageName.equals(Utils.PLATFORM_PACKAGE_NAME)) {
+            return;
+        }
+
+        validateInputs(snapshot, loadingPackageName, classLoaderContextByDexContainerFile);
+
+        // TODO(jiakaiz): Investigate whether it should also be considered as isolated process if
+        // `Process.isSdkSandboxUid` returns true.
+        boolean isolatedProcess = Process.isIsolatedUid(Binder.getCallingUid());
+        long lastUsedAtMs = mInjector.getCurrentTimeMillis();
+
+        for (var entry : classLoaderContextByDexContainerFile.entrySet()) {
+            String dexPath = Utils.assertNonEmpty(entry.getKey());
+            String classLoaderContext = Utils.assertNonEmpty(entry.getValue());
+            String owningPackageName = findOwningPackage(snapshot, loadingPackageName,
+                    (pkgState) -> isOwningPackageForPrimaryDex(pkgState, dexPath));
+            if (owningPackageName != null) {
+                addPrimaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess,
+                        lastUsedAtMs);
+                continue;
+            }
+            Path path = Paths.get(dexPath);
+            synchronized (mLock) {
+                owningPackageName = findOwningPackage(snapshot, loadingPackageName,
+                        (pkgState) -> isOwningPackageForSecondaryDexLocked(pkgState, path));
+            }
+            if (owningPackageName != null) {
+                PackageState loadingPkgState =
+                        Utils.getPackageStateOrThrow(snapshot, loadingPackageName);
+                // An app is always launched with its primary ABI.
+                Utils.Abi abi = Utils.getPrimaryAbi(loadingPkgState);
+                addSecondaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess,
+                        classLoaderContext, abi.name(), lastUsedAtMs);
+                continue;
+            }
+            // It is expected that a dex file isn't owned by any package. For example, the dex
+            // file could be a shared library jar.
+        }
+    }
+
+    @Nullable
+    private static String findOwningPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String loadingPackageName,
+            @NonNull Function<PackageState, Boolean> predicate) {
+        // Most likely, the package is loading its own dex file, so we check this first as an
+        // optimization.
+        PackageState loadingPkgState = Utils.getPackageStateOrThrow(snapshot, loadingPackageName);
+        if (predicate.apply(loadingPkgState)) {
+            return loadingPkgState.getPackageName();
+        }
+
+        for (PackageState pkgState : snapshot.getPackageStates().values()) {
+            if (predicate.apply(pkgState)) {
+                return pkgState.getPackageName();
+            }
+        }
+
+        return null;
+    }
+
+    private static boolean isOwningPackageForPrimaryDex(
+            @NonNull PackageState pkgState, @NonNull String dexPath) {
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+        List<AndroidPackageSplit> splits = pkg.getSplits();
+        for (int i = 0; i < splits.size(); i++) {
+            if (splits.get(i).getPath().equals(dexPath)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @GuardedBy("mLock")
+    private boolean isOwningPackageForSecondaryDexLocked(
+            @NonNull PackageState pkgState, @NonNull Path dexPath) {
+        UserHandle userHandle = Binder.getCallingUserHandle();
+        List<Path> locations = mSecondaryDexLocationManager.getLocations(pkgState, userHandle);
+        for (int i = 0; i < locations.size(); i++) {
+            if (dexPath.startsWith(locations.get(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void addPrimaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath,
+            @NonNull String loadingPackageName, boolean isolatedProcess, long lastUsedAtMs) {
+        synchronized (mLock) {
+            PrimaryDexUseRecord record =
+                    mDexUse.mPackageDexUseByOwningPackageName
+                            .computeIfAbsent(owningPackageName, k -> new PackageDexUse())
+                            .mPrimaryDexUseByDexFile
+                            .computeIfAbsent(dexPath, k -> new PrimaryDexUse())
+                            .mRecordByLoader.computeIfAbsent(
+                                    DexLoader.create(loadingPackageName, isolatedProcess),
+                                    k -> new PrimaryDexUseRecord());
+            record.mLastUsedAtMs = lastUsedAtMs;
+            mRevision++;
+        }
+        maybeSaveAsync();
+    }
+
+    private void addSecondaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath,
+            @NonNull String loadingPackageName, boolean isolatedProcess,
+            @NonNull String classLoaderContext, @NonNull String abiName, long lastUsedAtMs) {
+        synchronized (mLock) {
+            SecondaryDexUse secondaryDexUse =
+                    mDexUse.mPackageDexUseByOwningPackageName
+                            .computeIfAbsent(owningPackageName, k -> new PackageDexUse())
+                            .mSecondaryDexUseByDexFile.computeIfAbsent(
+                                    dexPath, k -> new SecondaryDexUse());
+            secondaryDexUse.mUserHandle = Binder.getCallingUserHandle();
+            SecondaryDexUseRecord record = secondaryDexUse.mRecordByLoader.computeIfAbsent(
+                    DexLoader.create(loadingPackageName, isolatedProcess),
+                    k -> new SecondaryDexUseRecord());
+            record.mClassLoaderContext = classLoaderContext;
+            record.mAbiName = abiName;
+            record.mLastUsedAtMs = lastUsedAtMs;
+            mRevision++;
+        }
+        maybeSaveAsync();
+    }
+
+    /** @hide */
+    public @NonNull String dump() {
+        var builder = DexUseProto.newBuilder();
+        synchronized (mLock) {
+            mDexUse.toProto(builder);
+        }
+        return builder.build().toString();
+    }
+
+    private void save() {
+        var builder = DexUseProto.newBuilder();
+        int thisRevision;
+        synchronized (mLock) {
+            if (mRevision <= mLastCommittedRevision) {
+                return;
+            }
+            mDexUse.toProto(builder);
+            thisRevision = mRevision;
+        }
+        var file = new File(mInjector.getFilename());
+        File tempFile = null;
+        try {
+            tempFile = File.createTempFile(file.getName(), null /* suffix */, file.getParentFile());
+            try (OutputStream out = new FileOutputStream(tempFile.getPath())) {
+                builder.build().writeTo(out);
+            }
+            synchronized (mLock) {
+                // Check revision again in case `mLastCommittedRevision` has changed since the check
+                // above, to avoid ABA race.
+                if (thisRevision > mLastCommittedRevision) {
+                    Files.move(tempFile.toPath(), file.toPath(),
+                            StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+                    mLastCommittedRevision = thisRevision;
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to save dex use data", e);
+        } finally {
+            Utils.deleteIfExistsSafe(tempFile);
+        }
+    }
+
+    private void maybeSaveAsync() {
+        mDebouncer.maybeRunAsync(this::save);
+    }
+
+    /** This should only be called during initialization. */
+    private void load() {
+        DexUseProto proto = null;
+        try (InputStream in = new FileInputStream(mInjector.getFilename())) {
+            proto = DexUseProto.parseFrom(in);
+        } catch (IOException e) {
+            // Nothing else we can do but to start from scratch.
+            Log.e(TAG, "Failed to load dex use data", e);
+        }
+        synchronized (mLock) {
+            if (mDexUse != null) {
+                throw new IllegalStateException("Load has already been attempted");
+            }
+            mDexUse = new DexUse();
+            if (proto != null) {
+                mDexUse.fromProto(proto);
+            }
+        }
+    }
+
+    private static boolean isUsedByOtherApps(
+            @NonNull Set<DexLoader> loaders, @NonNull String owningPackageName) {
+        return loaders.stream().anyMatch(loader -> isLoaderOtherApp(loader, owningPackageName));
+    }
+
+    /**
+     * Returns true if {@code loader} is considered as "other app" (i.e., its process UID is
+     * different from the UID of the package represented by {@code owningPackageName}).
+     *
+     * @hide
+     */
+    public static boolean isLoaderOtherApp(
+            @NonNull DexLoader loader, @NonNull String owningPackageName) {
+        // If the dex file is loaded by an isolated process of the same app, it can also be
+        // considered as "used by other apps" because isolated processes are sandboxed and can only
+        // read world readable files, so they need the dexopt artifacts to be world readable. An
+        // example of such a package is webview.
+        return !loader.loadingPackageName().equals(owningPackageName) || loader.isolatedProcess();
+    }
+
+    private static void validateInputs(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String loadingPackageName,
+            @NonNull Map<String, String> classLoaderContextByDexContainerFile) {
+        if (classLoaderContextByDexContainerFile.isEmpty()) {
+            throw new IllegalArgumentException("Nothing to record");
+        }
+
+        for (var entry : classLoaderContextByDexContainerFile.entrySet()) {
+            Utils.assertNonEmpty(entry.getKey());
+            if (!Paths.get(entry.getKey()).isAbsolute()) {
+                throw new IllegalArgumentException(String.format(
+                        "Dex container file path must be absolute, got '%s'", entry.getKey()));
+            }
+            Utils.assertNonEmpty(entry.getValue());
+        }
+
+        // TODO(b/253570365): Make the validation more strict.
+    }
+
+    private @FileVisibility int getDexFileVisibility(@NonNull String dexPath) {
+        try {
+            return mInjector.getArtd().getDexFileVisibility(dexPath);
+        } catch (ServiceSpecificException | RemoteException e) {
+            Log.e(TAG, "Failed to get visibility of " + dexPath, e);
+            return FileVisibility.NOT_FOUND;
+        }
+    }
+
+    /** @hide */
+    @Nullable
+    public String getSecondaryClassLoaderContext(
+            @NonNull String owningPackageName, @NonNull String dexFile, @NonNull DexLoader loader) {
+        synchronized (mLock) {
+            return Optional
+                    .ofNullable(mDexUse.mPackageDexUseByOwningPackageName.get(owningPackageName))
+                    .map(packageDexUse -> packageDexUse.mSecondaryDexUseByDexFile.get(dexFile))
+                    .map(secondaryDexUse -> secondaryDexUse.mRecordByLoader.get(loader))
+                    .map(record -> record.mClassLoaderContext)
+                    .orElse(null);
+        }
+    }
+
+    /**
+     * Cleans up obsolete information about dex files and packages that no longer exist.
+     *
+     * @hide
+     */
+    public void cleanup() {
+        Set<String> packageNames = mInjector.getAllPackageNames();
+        Map<String, Integer> dexFileVisibilityByName = new HashMap<>();
+
+        // Scan the data in two passes to avoid holding the lock during I/O.
+        synchronized (mLock) {
+            for (PackageDexUse packageDexUse : mDexUse.mPackageDexUseByOwningPackageName.values()) {
+                for (String dexFile : packageDexUse.mPrimaryDexUseByDexFile.keySet()) {
+                    dexFileVisibilityByName.put(dexFile, FileVisibility.NOT_FOUND);
+                }
+                for (String dexFile : packageDexUse.mSecondaryDexUseByDexFile.keySet()) {
+                    dexFileVisibilityByName.put(dexFile, FileVisibility.NOT_FOUND);
+                }
+            }
+        }
+
+        for (var entry : dexFileVisibilityByName.entrySet()) {
+            entry.setValue(getDexFileVisibility(entry.getKey()));
+        }
+
+        synchronized (mLock) {
+            for (var it = mDexUse.mPackageDexUseByOwningPackageName.entrySet().iterator();
+                    it.hasNext();) {
+                Map.Entry<String, PackageDexUse> entry = it.next();
+                String owningPackageName = entry.getKey();
+                PackageDexUse packageDexUse = entry.getValue();
+
+                if (!packageNames.contains(owningPackageName)) {
+                    // Remove information about the non-existing owning package.
+                    it.remove();
+                    mRevision++;
+                    continue;
+                }
+
+                cleanupPrimaryDexUsesLocked(packageDexUse.mPrimaryDexUseByDexFile, packageNames,
+                        dexFileVisibilityByName, owningPackageName);
+
+                cleanupSecondaryDexUsesLocked(packageDexUse.mSecondaryDexUseByDexFile, packageNames,
+                        dexFileVisibilityByName, owningPackageName);
+
+                if (packageDexUse.mPrimaryDexUseByDexFile.isEmpty()
+                        && packageDexUse.mSecondaryDexUseByDexFile.isEmpty()) {
+                    it.remove();
+                    mRevision++;
+                }
+            }
+        }
+
+        maybeSaveAsync();
+    }
+
+    @GuardedBy("mLock")
+    private void cleanupPrimaryDexUsesLocked(@NonNull Map<String, PrimaryDexUse> primaryDexUses,
+            @NonNull Set<String> packageNames,
+            @NonNull Map<String, Integer> dexFileVisibilityByName,
+            @NonNull String owningPackageName) {
+        for (var it = primaryDexUses.entrySet().iterator(); it.hasNext();) {
+            Map.Entry<String, PrimaryDexUse> entry = it.next();
+            String dexFile = entry.getKey();
+            PrimaryDexUse primaryDexUse = entry.getValue();
+
+            if (!dexFileVisibilityByName.containsKey(dexFile)) {
+                // This can only happen when the file is added after the first pass. We can just
+                // keep it as-is and check it in the next `cleanup` run.
+                continue;
+            }
+
+            @FileVisibility int visibility = dexFileVisibilityByName.get(dexFile);
+
+            if (visibility == FileVisibility.NOT_FOUND) {
+                // Remove information about the non-existing dex files.
+                it.remove();
+                mRevision++;
+                continue;
+            }
+
+            cleanupRecordsLocked(
+                    primaryDexUse.mRecordByLoader, packageNames, visibility, owningPackageName);
+
+            if (primaryDexUse.mRecordByLoader.isEmpty()) {
+                it.remove();
+                mRevision++;
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void cleanupSecondaryDexUsesLocked(
+            @NonNull Map<String, SecondaryDexUse> secondaryDexUses,
+            @NonNull Set<String> packageNames,
+            @NonNull Map<String, Integer> dexFileVisibilityByName,
+            @NonNull String owningPackageName) {
+        for (var it = secondaryDexUses.entrySet().iterator(); it.hasNext();) {
+            Map.Entry<String, SecondaryDexUse> entry = it.next();
+            String dexFile = entry.getKey();
+            SecondaryDexUse secondaryDexUse = entry.getValue();
+
+            if (!dexFileVisibilityByName.containsKey(dexFile)) {
+                // This can only happen when the file is added after the first pass. We can just
+                // keep it as-is and check it in the next `cleanup` run.
+                continue;
+            }
+
+            @FileVisibility int visibility = dexFileVisibilityByName.get(dexFile);
+
+            // Remove information about non-existing dex files.
+            if (visibility == FileVisibility.NOT_FOUND) {
+                it.remove();
+                mRevision++;
+                continue;
+            }
+
+            cleanupRecordsLocked(
+                    secondaryDexUse.mRecordByLoader, packageNames, visibility, owningPackageName);
+
+            if (secondaryDexUse.mRecordByLoader.isEmpty()) {
+                it.remove();
+                mRevision++;
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void cleanupRecordsLocked(@NonNull Map<DexLoader, ?> records,
+            @NonNull Set<String> packageNames, @FileVisibility int visibility,
+            @NonNull String owningPackageName) {
+        for (var it = records.entrySet().iterator(); it.hasNext();) {
+            Map.Entry<DexLoader, ?> entry = it.next();
+            DexLoader loader = entry.getKey();
+
+            if (!packageNames.contains(loader.loadingPackageName())) {
+                // Remove information about the non-existing loading package.
+                it.remove();
+                mRevision++;
+                continue;
+            }
+
+            if (visibility == FileVisibility.NOT_OTHER_READABLE
+                    && isLoaderOtherApp(loader, owningPackageName)) {
+                // The visibility must have changed since the last load. The loader cannot load this
+                // dex file anymore.
+                it.remove();
+                mRevision++;
+                continue;
+            }
+        }
+    }
+
+    /**
+     * Basic information about a secondary dex file (an APK or JAR file that an app adds to its
+     * own data directory and loads dynamically).
+     *
+     * @hide
+     */
+    @Immutable
+    public abstract static class SecondaryDexInfo implements DetailedDexInfo {
+        // Special encoding used to denote a foreign ClassLoader was found when trying to encode
+        // class loader contexts for each classpath element in a ClassLoader.
+        // Must be in sync with `kUnsupportedClassLoaderContextEncoding` in
+        // `art/runtime/class_loader_context.h`.
+        public static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
+                "=UnsupportedClassLoaderContext=";
+
+        // Special encoding used to denote that a dex file is loaded by different packages with
+        // different ClassLoader's. Only for display purpose (e.g., in dumpsys). This value is not
+        // written to the file, and so far only used here.
+        @VisibleForTesting
+        public static final String VARYING_CLASS_LOADER_CONTEXTS = "=VaryingClassLoaderContexts=";
+
+        /** The absolute path to the dex file within the user's app data directory. */
+        public abstract @NonNull String dexPath();
+
+        /**
+         * The {@link UserHandle} that represents the human user who owns and loads the dex file. A
+         * secondary dex file belongs to a specific human user, and only that user can load it.
+         */
+        public abstract @NonNull UserHandle userHandle();
+
+        /**
+         * A string describing the structure of the class loader that the dex file is loaded with,
+         * or {@link #UNSUPPORTED_CLASS_LOADER_CONTEXT} or {@link #VARYING_CLASS_LOADER_CONTEXTS}.
+         */
+        public abstract @NonNull String displayClassLoaderContext();
+
+        /**
+         * A string describing the structure of the class loader that the dex file is loaded with,
+         * or null if the class loader context is invalid.
+         */
+        public @Nullable String classLoaderContext() {
+            return !displayClassLoaderContext().equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)
+                            && !displayClassLoaderContext().equals(VARYING_CLASS_LOADER_CONTEXTS)
+                    ? displayClassLoaderContext()
+                    : null;
+        }
+
+        /** The set of ABIs of the dex file is loaded with. Guaranteed to be non-empty. */
+        public abstract @NonNull Set<String> abiNames();
+
+        /** The set of entities that load the dex file. Guaranteed to be non-empty. */
+        public abstract @NonNull Set<DexLoader> loaders();
+
+        /** Returns whether the dex file is used by apps other than the app that owns it. */
+        public abstract boolean isUsedByOtherApps();
+    }
+
+    /**
+     * Detailed information about a secondary dex file (an APK or JAR file that an app adds to its
+     * own data directory and loads dynamically). It contains the visibility of the dex file in
+     * addition to what is in {@link SecondaryDexInfo}, but producing it requires disk IO.
+     *
+     * @hide
+     */
+    @Immutable
+    @AutoValue
+    public abstract static class DetailedSecondaryDexInfo
+            extends SecondaryDexInfo implements DetailedDexInfo {
+        static DetailedSecondaryDexInfo create(@NonNull String dexPath,
+                @NonNull UserHandle userHandle, @NonNull String displayClassLoaderContext,
+                @NonNull Set<String> abiNames, @NonNull Set<DexLoader> loaders,
+                boolean isUsedByOtherApps, boolean isDexFilePublic) {
+            return new AutoValue_DexUseManagerLocal_DetailedSecondaryDexInfo(dexPath, userHandle,
+                    displayClassLoaderContext, Collections.unmodifiableSet(abiNames),
+                    Collections.unmodifiableSet(loaders), isUsedByOtherApps, isDexFilePublic);
+        }
+
+        /**
+         * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
+         * (S_IROTH).
+         */
+        public abstract boolean isDexFilePublic();
+    }
+
+    private static class DexUse {
+        @NonNull Map<String, PackageDexUse> mPackageDexUseByOwningPackageName = new HashMap<>();
+
+        void toProto(@NonNull DexUseProto.Builder builder) {
+            for (var entry : mPackageDexUseByOwningPackageName.entrySet()) {
+                var packageBuilder =
+                        PackageDexUseProto.newBuilder().setOwningPackageName(entry.getKey());
+                entry.getValue().toProto(packageBuilder);
+                builder.addPackageDexUse(packageBuilder);
+            }
+        }
+
+        void fromProto(@NonNull DexUseProto proto) {
+            for (PackageDexUseProto packageProto : proto.getPackageDexUseList()) {
+                var packageDexUse = new PackageDexUse();
+                packageDexUse.fromProto(packageProto);
+                mPackageDexUseByOwningPackageName.put(
+                        Utils.assertNonEmpty(packageProto.getOwningPackageName()), packageDexUse);
+            }
+        }
+    }
+
+    private static class PackageDexUse {
+        /**
+         * The keys are absolute paths to primary dex files of the owning package (the base APK and
+         * split APKs).
+         */
+        @NonNull Map<String, PrimaryDexUse> mPrimaryDexUseByDexFile = new HashMap<>();
+
+        /**
+         * The keys are absolute paths to secondary dex files of the owning package (the APKs and
+         * JARs in CE and DE directories).
+         */
+        @NonNull Map<String, SecondaryDexUse> mSecondaryDexUseByDexFile = new HashMap<>();
+
+        void toProto(@NonNull PackageDexUseProto.Builder builder) {
+            for (var entry : mPrimaryDexUseByDexFile.entrySet()) {
+                var primaryBuilder = PrimaryDexUseProto.newBuilder().setDexFile(entry.getKey());
+                entry.getValue().toProto(primaryBuilder);
+                builder.addPrimaryDexUse(primaryBuilder);
+            }
+            for (var entry : mSecondaryDexUseByDexFile.entrySet()) {
+                var secondaryBuilder = SecondaryDexUseProto.newBuilder().setDexFile(entry.getKey());
+                entry.getValue().toProto(secondaryBuilder);
+                builder.addSecondaryDexUse(secondaryBuilder);
+            }
+        }
+
+        void fromProto(@NonNull PackageDexUseProto proto) {
+            for (PrimaryDexUseProto primaryProto : proto.getPrimaryDexUseList()) {
+                var primaryDexUse = new PrimaryDexUse();
+                primaryDexUse.fromProto(primaryProto);
+                mPrimaryDexUseByDexFile.put(
+                        Utils.assertNonEmpty(primaryProto.getDexFile()), primaryDexUse);
+            }
+            for (SecondaryDexUseProto secondaryProto : proto.getSecondaryDexUseList()) {
+                var secondaryDexUse = new SecondaryDexUse();
+                secondaryDexUse.fromProto(secondaryProto);
+                mSecondaryDexUseByDexFile.put(
+                        Utils.assertNonEmpty(secondaryProto.getDexFile()), secondaryDexUse);
+            }
+        }
+    }
+
+    private static class PrimaryDexUse {
+        @NonNull Map<DexLoader, PrimaryDexUseRecord> mRecordByLoader = new HashMap<>();
+
+        void toProto(@NonNull PrimaryDexUseProto.Builder builder) {
+            for (var entry : mRecordByLoader.entrySet()) {
+                var recordBuilder =
+                        PrimaryDexUseRecordProto.newBuilder()
+                                .setLoadingPackageName(entry.getKey().loadingPackageName())
+                                .setIsolatedProcess(entry.getKey().isolatedProcess());
+                entry.getValue().toProto(recordBuilder);
+                builder.addRecord(recordBuilder);
+            }
+        }
+
+        void fromProto(@NonNull PrimaryDexUseProto proto) {
+            for (PrimaryDexUseRecordProto recordProto : proto.getRecordList()) {
+                var record = new PrimaryDexUseRecord();
+                record.fromProto(recordProto);
+                mRecordByLoader.put(
+                        DexLoader.create(Utils.assertNonEmpty(recordProto.getLoadingPackageName()),
+                                recordProto.getIsolatedProcess()),
+                        record);
+            }
+        }
+    }
+
+    private static class SecondaryDexUse {
+        @Nullable UserHandle mUserHandle = null;
+        @NonNull Map<DexLoader, SecondaryDexUseRecord> mRecordByLoader = new HashMap<>();
+
+        void toProto(@NonNull SecondaryDexUseProto.Builder builder) {
+            builder.setUserId(Int32Value.newBuilder().setValue(mUserHandle.getIdentifier()));
+            for (var entry : mRecordByLoader.entrySet()) {
+                var recordBuilder =
+                        SecondaryDexUseRecordProto.newBuilder()
+                                .setLoadingPackageName(entry.getKey().loadingPackageName())
+                                .setIsolatedProcess(entry.getKey().isolatedProcess());
+                entry.getValue().toProto(recordBuilder);
+                builder.addRecord(recordBuilder);
+            }
+        }
+
+        void fromProto(@NonNull SecondaryDexUseProto proto) {
+            Utils.check(proto.hasUserId());
+            mUserHandle = UserHandle.of(proto.getUserId().getValue());
+            for (SecondaryDexUseRecordProto recordProto : proto.getRecordList()) {
+                var record = new SecondaryDexUseRecord();
+                record.fromProto(recordProto);
+                mRecordByLoader.put(
+                        DexLoader.create(Utils.assertNonEmpty(recordProto.getLoadingPackageName()),
+                                recordProto.getIsolatedProcess()),
+                        record);
+            }
+        }
+    }
+
+    /**
+     * Represents an entity that loads a dex file.
+     *
+     * @hide
+     */
+    @Immutable
+    @AutoValue
+    public abstract static class DexLoader implements Comparable<DexLoader> {
+        static DexLoader create(@NonNull String loadingPackageName, boolean isolatedProcess) {
+            return new AutoValue_DexUseManagerLocal_DexLoader(loadingPackageName, isolatedProcess);
+        }
+
+        abstract @NonNull String loadingPackageName();
+
+        /** @see Process#isIsolatedUid(int) */
+        abstract boolean isolatedProcess();
+
+        @Override
+        @NonNull
+        public String toString() {
+            return loadingPackageName() + (isolatedProcess() ? " (isolated)" : "");
+        }
+
+        @Override
+        public int compareTo(DexLoader o) {
+            return Comparator.comparing(DexLoader::loadingPackageName)
+                    .thenComparing(DexLoader::isolatedProcess)
+                    .compare(this, o);
+        }
+    }
+
+    private static class PrimaryDexUseRecord {
+        @Nullable long mLastUsedAtMs = 0;
+
+        void toProto(@NonNull PrimaryDexUseRecordProto.Builder builder) {
+            builder.setLastUsedAtMs(mLastUsedAtMs);
+        }
+
+        void fromProto(@NonNull PrimaryDexUseRecordProto proto) {
+            mLastUsedAtMs = proto.getLastUsedAtMs();
+            Utils.check(mLastUsedAtMs > 0);
+        }
+    }
+
+    private static class SecondaryDexUseRecord {
+        // An app constructs their own class loader to load a secondary dex file, so only itself
+        // knows the class loader context. Therefore, we need to record the class loader context
+        // reported by the app.
+        @Nullable String mClassLoaderContext = null;
+        @Nullable String mAbiName = null;
+        @Nullable long mLastUsedAtMs = 0;
+
+        void toProto(@NonNull SecondaryDexUseRecordProto.Builder builder) {
+            builder.setClassLoaderContext(mClassLoaderContext)
+                    .setAbiName(mAbiName)
+                    .setLastUsedAtMs(mLastUsedAtMs);
+        }
+
+        void fromProto(@NonNull SecondaryDexUseRecordProto proto) {
+            mClassLoaderContext = Utils.assertNonEmpty(proto.getClassLoaderContext());
+            mAbiName = Utils.assertNonEmpty(proto.getAbiName());
+            mLastUsedAtMs = proto.getLastUsedAtMs();
+            Utils.check(mLastUsedAtMs > 0);
+        }
+    }
+
+    // TODO(b/278697552): Consider removing the cache or moving it to `Environment`.
+    static class SecondaryDexLocationManager {
+        private @NonNull Map<CacheKey, CacheValue> mCache = new HashMap<>();
+
+        public @NonNull List<Path> getLocations(
+                @NonNull PackageState pkgState, @NonNull UserHandle userHandle) {
+            AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+            UUID storageUuid = pkg.getStorageUuid();
+            String packageName = pkgState.getPackageName();
+
+            CacheKey cacheKey = CacheKey.create(packageName, userHandle);
+            CacheValue cacheValue = mCache.get(cacheKey);
+            if (cacheValue != null && cacheValue.storageUuid().equals(storageUuid)) {
+                return cacheValue.locations();
+            }
+
+            File ceDir = Environment.getDataCePackageDirectoryForUser(
+                    storageUuid, userHandle, packageName);
+            File deDir = Environment.getDataDePackageDirectoryForUser(
+                    storageUuid, userHandle, packageName);
+            List<Path> locations = List.of(ceDir.toPath(), deDir.toPath());
+            mCache.put(cacheKey, CacheValue.create(locations, storageUuid));
+            return locations;
+        }
+
+        @Immutable
+        @AutoValue
+        abstract static class CacheKey {
+            static CacheKey create(@NonNull String packageName, @NonNull UserHandle userHandle) {
+                return new AutoValue_DexUseManagerLocal_SecondaryDexLocationManager_CacheKey(
+                        packageName, userHandle);
+            }
+
+            abstract @NonNull String packageName();
+
+            abstract @NonNull UserHandle userHandle();
+        }
+
+        @Immutable
+        @AutoValue
+        abstract static class CacheValue {
+            static CacheValue create(@NonNull List<Path> locations, @NonNull UUID storageUuid) {
+                return new AutoValue_DexUseManagerLocal_SecondaryDexLocationManager_CacheValue(
+                        locations, storageUuid);
+            }
+
+            abstract @NonNull List<Path> locations();
+
+            abstract @NonNull UUID storageUuid();
+        }
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Injector {
+        @NonNull private final Context mContext;
+
+        Injector(@NonNull Context context) {
+            mContext = context;
+
+            // Call the getters for various dependencies, to ensure correct initialization order.
+            ArtModuleServiceInitializer.getArtModuleServiceManager();
+            getPackageManagerLocal();
+        }
+
+        @NonNull
+        public IArtd getArtd() {
+            return Utils.getArtd();
+        }
+
+        public long getCurrentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        @NonNull
+        public String getFilename() {
+            return FILENAME;
+        }
+
+        @NonNull
+        public ScheduledExecutorService createScheduledExecutor() {
+            return Executors.newSingleThreadScheduledExecutor();
+        }
+
+        @NonNull
+        public Context getContext() {
+            return mContext;
+        }
+
+        @NonNull
+        public Set<String> getAllPackageNames() {
+            try (PackageManagerLocal.UnfilteredSnapshot snapshot =
+                            getPackageManagerLocal().withUnfilteredSnapshot()) {
+                return new HashSet<>(snapshot.getPackageStates().keySet());
+            }
+        }
+
+        @NonNull
+        private PackageManagerLocal getPackageManagerLocal() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(PackageManagerLocal.class));
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/DexoptHelper.java b/libartservice/service/java/com/android/server/art/DexoptHelper.java
new file mode 100644
index 0000000..c04a981
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexoptHelper.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.ArtManagerLocal.DexoptDoneCallback;
+import static com.android.server.art.model.Config.Callback;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.apphibernation.AppHibernationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.WorkSource;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.OperationProgress;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedLibrary;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * A helper class to handle dexopt.
+ *
+ * It talks to other components (e.g., PowerManager) and dispatches tasks to dexopters.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class DexoptHelper {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    /**
+     * Timeout of the wake lock. This is required by AndroidLint, but we set it to a very large
+     * value so that it should normally never triggered.
+     */
+    private static final long WAKE_LOCK_TIMEOUT_MS = TimeUnit.DAYS.toMillis(1);
+
+    @NonNull private final Injector mInjector;
+
+    public DexoptHelper(@NonNull Context context, @NonNull Config config) {
+        this(new Injector(context, config));
+    }
+
+    @VisibleForTesting
+    public DexoptHelper(@NonNull Injector injector) {
+        mInjector = injector;
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link
+     * ArtManagerLocal#dexoptPackages}.
+     */
+    @NonNull
+    public DexoptResult dexopt(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull List<String> packageNames, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor) {
+        return dexopt(snapshot, packageNames, params, cancellationSignal, dexoptExecutor,
+                null /* progressCallbackExecutor */, null /* progressCallback */);
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link
+     * ArtManagerLocal#dexoptPackages}.
+     */
+    @NonNull
+    public DexoptResult dexopt(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull List<String> packageNames, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor,
+            @Nullable Executor progressCallbackExecutor,
+            @Nullable Consumer<OperationProgress> progressCallback) {
+        return dexoptPackages(
+                getPackageStates(snapshot, packageNames,
+                        (params.getFlags() & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0),
+                params, cancellationSignal, dexoptExecutor, progressCallbackExecutor,
+                progressCallback);
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link
+     * ArtManagerLocal#dexoptPackages}.
+     */
+    @NonNull
+    private DexoptResult dexoptPackages(@NonNull List<PackageState> pkgStates,
+            @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal,
+            @NonNull Executor dexoptExecutor, @Nullable Executor progressCallbackExecutor,
+            @Nullable Consumer<OperationProgress> progressCallback) {
+        int callingUid = Binder.getCallingUid();
+        long identityToken = Binder.clearCallingIdentity();
+        PowerManager.WakeLock wakeLock = null;
+
+        try {
+            // Acquire a wake lock.
+            PowerManager powerManager = mInjector.getPowerManager();
+            wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+            wakeLock.setWorkSource(new WorkSource(callingUid));
+            wakeLock.acquire(WAKE_LOCK_TIMEOUT_MS);
+
+            List<CompletableFuture<PackageDexoptResult>> futures = new ArrayList<>();
+
+            // Child threads will set their own listeners on the cancellation signal, so we must
+            // create a separate cancellation signal for each of them so that the listeners don't
+            // overwrite each other.
+            List<CancellationSignal> childCancellationSignals =
+                    pkgStates.stream()
+                            .map(pkgState -> new CancellationSignal())
+                            .collect(Collectors.toList());
+            cancellationSignal.setOnCancelListener(() -> {
+                for (CancellationSignal childCancellationSignal : childCancellationSignals) {
+                    childCancellationSignal.cancel();
+                }
+            });
+
+            for (int i = 0; i < pkgStates.size(); i++) {
+                PackageState pkgState = pkgStates.get(i);
+                CancellationSignal childCancellationSignal = childCancellationSignals.get(i);
+                futures.add(CompletableFuture.supplyAsync(() -> {
+                    return dexoptPackage(pkgState, params, childCancellationSignal);
+                }, dexoptExecutor));
+            }
+
+            if (progressCallback != null) {
+                CompletableFuture.runAsync(() -> {
+                    progressCallback.accept(
+                            OperationProgress.create(0 /* current */, futures.size()));
+                }, progressCallbackExecutor);
+                AtomicInteger current = new AtomicInteger(0);
+                for (CompletableFuture<PackageDexoptResult> future : futures) {
+                    future.thenRunAsync(() -> {
+                        progressCallback.accept(OperationProgress.create(
+                                current.incrementAndGet(), futures.size()));
+                    }, progressCallbackExecutor);
+                }
+            }
+
+            List<PackageDexoptResult> results =
+                    futures.stream().map(Utils::getFuture).collect(Collectors.toList());
+
+            var result =
+                    DexoptResult.create(params.getCompilerFilter(), params.getReason(), results);
+
+            for (Callback<DexoptDoneCallback, Boolean> doneCallback :
+                    mInjector.getConfig().getDexoptDoneCallbacks()) {
+                boolean onlyIncludeUpdates = doneCallback.extra();
+                if (onlyIncludeUpdates) {
+                    List<PackageDexoptResult> filteredResults =
+                            results.stream()
+                                    .filter(PackageDexoptResult::hasUpdatedArtifacts)
+                                    .collect(Collectors.toList());
+                    if (!filteredResults.isEmpty()) {
+                        var resultForCallback = DexoptResult.create(
+                                params.getCompilerFilter(), params.getReason(), filteredResults);
+                        CompletableFuture.runAsync(() -> {
+                            doneCallback.get().onDexoptDone(resultForCallback);
+                        }, doneCallback.executor());
+                    }
+                } else {
+                    CompletableFuture.runAsync(() -> {
+                        doneCallback.get().onDexoptDone(result);
+                    }, doneCallback.executor());
+                }
+            }
+
+            return result;
+        } finally {
+            if (wakeLock != null) {
+                wakeLock.release();
+            }
+            Binder.restoreCallingIdentity(identityToken);
+            // Make sure nothing leaks even if the caller holds `cancellationSignal` forever.
+            cancellationSignal.setOnCancelListener(null);
+        }
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link
+     * ArtManagerLocal#dexoptPackages}.
+     */
+    @NonNull
+    private PackageDexoptResult dexoptPackage(@NonNull PackageState pkgState,
+            @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal) {
+        List<DexContainerFileDexoptResult> results = new ArrayList<>();
+        Function<Integer, PackageDexoptResult> createResult = (packageLevelStatus)
+                -> PackageDexoptResult.create(
+                        pkgState.getPackageName(), results, packageLevelStatus);
+
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+        if (!canDexoptPackage(pkgState)) {
+            return createResult.apply(null /* packageLevelStatus */);
+        }
+
+        if ((params.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
+            // Throws if the split is not found.
+            PrimaryDexUtils.getDexInfoBySplitName(pkg, params.getSplitName());
+        }
+
+        try (var tracing = new Utils.Tracing("dexopt")) {
+            if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+                if (cancellationSignal.isCanceled()) {
+                    return createResult.apply(DexoptResult.DEXOPT_CANCELLED);
+                }
+
+                results.addAll(
+                        mInjector.getPrimaryDexopter(pkgState, pkg, params, cancellationSignal)
+                                .dexopt());
+            }
+
+            if ((params.getFlags() & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+                if (cancellationSignal.isCanceled()) {
+                    return createResult.apply(DexoptResult.DEXOPT_CANCELLED);
+                }
+
+                results.addAll(
+                        mInjector.getSecondaryDexopter(pkgState, pkg, params, cancellationSignal)
+                                .dexopt());
+            }
+        } catch (RemoteException e) {
+            Utils.logArtdException(e);
+            return createResult.apply(DexoptResult.DEXOPT_FAILED);
+        }
+
+        return createResult.apply(null /* packageLevelStatus */);
+    }
+
+    private boolean canDexoptPackage(@NonNull PackageState pkgState) {
+        // getAppHibernationManager may return null here during boot time compilation, which will
+        // make this function return true incorrectly for packages that shouldn't be dexopted due to
+        // hibernation. Further discussion in comments in ArtManagerLocal.getDefaultPackages.
+        return Utils.canDexoptPackage(pkgState, mInjector.getAppHibernationManager());
+    }
+
+    @NonNull
+    private List<PackageState> getPackageStates(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull List<String> packageNames, boolean includeDependencies) {
+        var pkgStates = new LinkedHashMap<String, PackageState>();
+        Set<String> visitedLibraries = new HashSet<>();
+        Queue<SharedLibrary> queue = new LinkedList<>();
+
+        Consumer<SharedLibrary> maybeEnqueue = library -> {
+            // The package name is not null if the library is an APK.
+            // TODO(jiakaiz): Support JAR libraries.
+            if (library.getPackageName() != null && !library.isNative()
+                    && !visitedLibraries.contains(library.getName())) {
+                visitedLibraries.add(library.getName());
+                queue.add(library);
+            }
+        };
+
+        for (String packageName : packageNames) {
+            PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+            Utils.getPackageOrThrow(pkgState);
+            pkgStates.put(packageName, pkgState);
+            if (includeDependencies && canDexoptPackage(pkgState)) {
+                for (SharedLibrary library : pkgState.getSharedLibraryDependencies()) {
+                    maybeEnqueue.accept(library);
+                }
+            }
+        }
+
+        SharedLibrary library;
+        while ((library = queue.poll()) != null) {
+            String packageName = library.getPackageName();
+            PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+            if (canDexoptPackage(pkgState)) {
+                pkgStates.put(packageName, pkgState);
+
+                // Note that `library.getDependencies()` is different from
+                // `pkgState.getUsesLibraries()`. Different libraries can belong to the same
+                // package. `pkgState.getUsesLibraries()` returns a union of dependencies of
+                // libraries that belong to the same package, which is not what we want here.
+                // Therefore, this loop cannot be unified with the one above.
+                for (SharedLibrary dep : library.getDependencies()) {
+                    maybeEnqueue.accept(dep);
+                }
+            }
+        }
+
+        // `LinkedHashMap` guarantees deterministic order.
+        return new ArrayList<>(pkgStates.values());
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Injector {
+        @NonNull private final Context mContext;
+        @NonNull private final Config mConfig;
+
+        Injector(@NonNull Context context, @NonNull Config config) {
+            mContext = context;
+            mConfig = config;
+
+            // Call the getters for the dependencies that aren't optional, to ensure correct
+            // initialization order.
+            getAppHibernationManager();
+            getPowerManager();
+        }
+
+        @NonNull
+        PrimaryDexopter getPrimaryDexopter(@NonNull PackageState pkgState,
+                @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+                @NonNull CancellationSignal cancellationSignal) {
+            return new PrimaryDexopter(mContext, pkgState, pkg, params, cancellationSignal);
+        }
+
+        @NonNull
+        SecondaryDexopter getSecondaryDexopter(@NonNull PackageState pkgState,
+                @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+                @NonNull CancellationSignal cancellationSignal) {
+            return new SecondaryDexopter(mContext, pkgState, pkg, params, cancellationSignal);
+        }
+
+        @NonNull
+        public AppHibernationManager getAppHibernationManager() {
+            return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class));
+        }
+
+        @NonNull
+        public PowerManager getPowerManager() {
+            return Objects.requireNonNull(mContext.getSystemService(PowerManager.class));
+        }
+
+        @NonNull
+        public Config getConfig() {
+            return mConfig;
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java
new file mode 100644
index 0000000..446d948
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Dexopter.java
@@ -0,0 +1,713 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.ProfilePath.TmpProfilePath;
+import static com.android.server.art.Utils.Abi;
+import static com.android.server.art.model.ArtFlags.DexoptFlags;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+
+import android.R;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.DexFile;
+
+import com.google.auto.value.AutoValue;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/** @hide */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
+    private static final String TAG = ArtManagerLocal.TAG;
+    private static final List<String> ART_PACKAGE_NAMES =
+            List.of("com.google.android.art", "com.android.art", "com.google.android.go.art");
+
+    @NonNull protected final Injector mInjector;
+    @NonNull protected final PackageState mPkgState;
+    /** This is always {@code mPkgState.getAndroidPackage()} and guaranteed to be non-null. */
+    @NonNull protected final AndroidPackage mPkg;
+    @NonNull protected final DexoptParams mParams;
+    @NonNull protected final CancellationSignal mCancellationSignal;
+
+    protected Dexopter(@NonNull Injector injector, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        mInjector = injector;
+        mPkgState = pkgState;
+        mPkg = pkg;
+        mParams = params;
+        mCancellationSignal = cancellationSignal;
+        if (pkgState.getAppId() < 0) {
+            throw new IllegalStateException(
+                    "Package '" + pkgState.getPackageName() + "' has invalid app ID");
+        }
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link
+     * ArtManagerLocal#dexoptPackage(PackageManagerLocal.FilteredSnapshot, String,
+     * DexoptParams)}.
+     */
+    @NonNull
+    public final List<DexContainerFileDexoptResult> dexopt() throws RemoteException {
+        List<DexContainerFileDexoptResult> results = new ArrayList<>();
+
+        boolean isInDalvikCache = isInDalvikCache();
+
+        for (DexInfoType dexInfo : getDexInfoList()) {
+            ProfilePath profile = null;
+            boolean succeeded = true;
+            try {
+                if (!isDexoptable(dexInfo)) {
+                    continue;
+                }
+
+                String compilerFilter = adjustCompilerFilter(mParams.getCompilerFilter(), dexInfo);
+                if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) {
+                    continue;
+                }
+
+                boolean needsToBeShared = needsToBeShared(dexInfo);
+                boolean isOtherReadable = true;
+                // If true, implies that the profile has changed since the last compilation.
+                boolean profileMerged = false;
+                if (DexFile.isProfileGuidedCompilerFilter(compilerFilter)) {
+                    if (needsToBeShared) {
+                        profile = initReferenceProfile(dexInfo);
+                    } else {
+                        Pair<ProfilePath, Boolean> pair = getOrInitReferenceProfile(dexInfo);
+                        if (pair != null) {
+                            profile = pair.first;
+                            isOtherReadable = pair.second;
+                        }
+                        ProfilePath mergedProfile = mergeProfiles(dexInfo, profile);
+                        if (mergedProfile != null) {
+                            if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
+                                mInjector.getArtd().deleteProfile(profile);
+                            }
+                            profile = mergedProfile;
+                            isOtherReadable = false;
+                            profileMerged = true;
+                        }
+                    }
+                    if (profile == null) {
+                        // A profile guided dexopt with no profile is essentially 'verify',
+                        // and dex2oat already makes this transformation. However, we need to
+                        // explicitly make this transformation here to guide the later decisions
+                        // such as whether the artifacts can be public and whether dexopt is needed.
+                        compilerFilter = needsToBeShared
+                                ? ReasonMapping.getCompilerFilterForShared()
+                                : "verify";
+                    }
+                }
+                boolean isProfileGuidedCompilerFilter =
+                        DexFile.isProfileGuidedCompilerFilter(compilerFilter);
+                Utils.check(isProfileGuidedCompilerFilter == (profile != null));
+
+                boolean canBePublic = (!isProfileGuidedCompilerFilter || isOtherReadable)
+                        && isDexFilePublic(dexInfo);
+                Utils.check(Utils.implies(needsToBeShared, canBePublic));
+                PermissionSettings permissionSettings = getPermissionSettings(dexInfo, canBePublic);
+
+                DexoptOptions dexoptOptions =
+                        getDexoptOptions(dexInfo, isProfileGuidedCompilerFilter);
+
+                for (Abi abi : getAllAbis(dexInfo)) {
+                    @DexoptResult.DexoptResultStatus int status = DexoptResult.DEXOPT_SKIPPED;
+                    long wallTimeMs = 0;
+                    long cpuTimeMs = 0;
+                    long sizeBytes = 0;
+                    long sizeBeforeBytes = 0;
+                    boolean isSkippedDueToStorageLow = false;
+                    try {
+                        var target = DexoptTarget.<DexInfoType>builder()
+                                             .setDexInfo(dexInfo)
+                                             .setIsa(abi.isa())
+                                             .setIsInDalvikCache(isInDalvikCache)
+                                             .setCompilerFilter(compilerFilter)
+                                             .build();
+                        var options = GetDexoptNeededOptions.builder()
+                                              .setProfileMerged(profileMerged)
+                                              .setFlags(mParams.getFlags())
+                                              .setNeedsToBePublic(needsToBeShared)
+                                              .build();
+
+                        GetDexoptNeededResult getDexoptNeededResult =
+                                getDexoptNeeded(target, options);
+
+                        if (!getDexoptNeededResult.isDexoptNeeded) {
+                            continue;
+                        }
+
+                        try {
+                            // `StorageManager.getAllocatableBytes` returns (free space + space used
+                            // by clearable cache - low storage threshold). Since we only compare
+                            // the result with 0, the clearable cache doesn't make a difference.
+                            // When the free space is below the threshold, there should be no
+                            // clearable cache left because system cleans up cache every minute.
+                            if ((mParams.getFlags() & ArtFlags.FLAG_SKIP_IF_STORAGE_LOW) != 0
+                                    && mInjector.getStorageManager().getAllocatableBytes(
+                                               mPkg.getStorageUuid())
+                                            <= 0) {
+                                isSkippedDueToStorageLow = true;
+                                continue;
+                            }
+                        } catch (IOException e) {
+                            Log.e(TAG, "Failed to check storage. Assuming storage not low", e);
+                        }
+
+                        IArtdCancellationSignal artdCancellationSignal =
+                                mInjector.getArtd().createCancellationSignal();
+                        mCancellationSignal.setOnCancelListener(() -> {
+                            try {
+                                artdCancellationSignal.cancel();
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "An error occurred when sending a cancellation signal",
+                                        e);
+                            }
+                        });
+
+                        ArtdDexoptResult dexoptResult = dexoptFile(target, profile,
+                                getDexoptNeededResult, permissionSettings,
+                                mParams.getPriorityClass(), dexoptOptions, artdCancellationSignal);
+                        status = dexoptResult.cancelled ? DexoptResult.DEXOPT_CANCELLED
+                                                        : DexoptResult.DEXOPT_PERFORMED;
+                        wallTimeMs = dexoptResult.wallTimeMs;
+                        cpuTimeMs = dexoptResult.cpuTimeMs;
+                        sizeBytes = dexoptResult.sizeBytes;
+                        sizeBeforeBytes = dexoptResult.sizeBeforeBytes;
+
+                        if (status == DexoptResult.DEXOPT_CANCELLED) {
+                            return results;
+                        }
+                    } catch (ServiceSpecificException e) {
+                        // Log the error and continue.
+                        Log.e(TAG,
+                                String.format("Failed to dexopt [packageName = %s, dexPath = %s, "
+                                                + "isa = %s, classLoaderContext = %s]",
+                                        mPkgState.getPackageName(), dexInfo.dexPath(), abi.isa(),
+                                        dexInfo.classLoaderContext()),
+                                e);
+                        status = DexoptResult.DEXOPT_FAILED;
+                    } finally {
+                        var result = DexContainerFileDexoptResult.create(dexInfo.dexPath(),
+                                abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
+                                cpuTimeMs, sizeBytes, sizeBeforeBytes, isSkippedDueToStorageLow);
+                        Log.i(TAG,
+                                String.format("Dexopt result: [packageName = %s] %s",
+                                        mPkgState.getPackageName(), result));
+                        results.add(result);
+                        if (status != DexoptResult.DEXOPT_SKIPPED
+                                && status != DexoptResult.DEXOPT_PERFORMED) {
+                            succeeded = false;
+                        }
+                        // Make sure artd does not leak even if the caller holds
+                        // `mCancellationSignal` forever.
+                        mCancellationSignal.setOnCancelListener(null);
+                    }
+                }
+
+                if (profile != null && succeeded) {
+                    if (profile.getTag() == ProfilePath.tmpProfilePath) {
+                        // Commit the profile only if dexopt succeeds.
+                        if (commitProfileChanges(profile.getTmpProfilePath())) {
+                            profile = null;
+                        }
+                    }
+                    if (profileMerged) {
+                        // Note that this is just an optimization, to reduce the amount of data that
+                        // the runtime writes on every profile save. The profile merge result on the
+                        // next run won't change regardless of whether the cleanup is done or not
+                        // because profman only looks at the diff.
+                        // A caveat is that it may delete more than what has been merged, if the
+                        // runtime writes additional entries between the merge and the cleanup, but
+                        // this is fine because the runtime writes all JITed classes and methods on
+                        // every save and the additional entries will likely be written back on the
+                        // next save.
+                        cleanupCurProfiles(dexInfo);
+                    }
+                }
+            } finally {
+                if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
+                    mInjector.getArtd().deleteProfile(profile);
+                }
+            }
+        }
+
+        return results;
+    }
+
+    @NonNull
+    private String adjustCompilerFilter(
+            @NonNull String targetCompilerFilter, @NonNull DexInfoType dexInfo) {
+        if (mInjector.isSystemUiPackage(mPkgState.getPackageName())) {
+            String systemUiCompilerFilter = getSystemUiCompilerFilter();
+            if (!systemUiCompilerFilter.isEmpty()) {
+                return systemUiCompilerFilter;
+            }
+        }
+
+        if (mInjector.isLauncherPackage(mPkgState.getPackageName())) {
+            return "speed-profile";
+        }
+
+        // We force vmSafeMode on debuggable apps as well:
+        //  - the runtime ignores their compiled code
+        //  - they generally have lots of methods that could make the compiler used run out of
+        //    memory (b/130828957)
+        // Note that forcing the compiler filter here applies to all compilations (even if they
+        // are done via adb shell commands). This is okay because the runtime will ignore the
+        // compiled code anyway.
+        if (mPkg.isVmSafeMode() || mPkg.isDebuggable()) {
+            return DexFile.getSafeModeCompilerFilter(targetCompilerFilter);
+        }
+
+        // We cannot do AOT compilation if we don't have a valid class loader context.
+        if (dexInfo.classLoaderContext() == null) {
+            return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
+                                                                           : targetCompilerFilter;
+        }
+
+        // This application wants to use the embedded dex in the APK, rather than extracted or
+        // locally compiled variants, so we only verify it.
+        // "verify" does not prevent dex2oat from extracting the dex code, but in practice, dex2oat
+        // won't extract the dex code because the APK is uncompressed, and the assumption is that
+        // such applications always use uncompressed APKs.
+        if (mPkg.isUseEmbeddedDex()) {
+            return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
+                                                                           : targetCompilerFilter;
+        }
+
+        return targetCompilerFilter;
+    }
+
+    @NonNull
+    private String getSystemUiCompilerFilter() {
+        String compilerFilter = SystemProperties.get("dalvik.vm.systemuicompilerfilter");
+        if (!compilerFilter.isEmpty() && !Utils.isValidArtServiceCompilerFilter(compilerFilter)) {
+            throw new IllegalStateException(
+                    "Got invalid compiler filter '" + compilerFilter + "' for System UI");
+        }
+        return compilerFilter;
+    }
+
+    /** @see Utils#getOrInitReferenceProfile */
+    @Nullable
+    private Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull DexInfoType dexInfo)
+            throws RemoteException {
+        return Utils.getOrInitReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
+                buildRefProfilePath(dexInfo), getExternalProfiles(dexInfo),
+                buildOutputProfile(dexInfo, true /* isPublic */));
+    }
+
+    @Nullable
+    private ProfilePath initReferenceProfile(@NonNull DexInfoType dexInfo) throws RemoteException {
+        return Utils.initReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
+                getExternalProfiles(dexInfo), buildOutputProfile(dexInfo, true /* isPublic */));
+    }
+
+    @NonNull
+    private DexoptOptions getDexoptOptions(
+            @NonNull DexInfoType dexInfo, boolean isProfileGuidedFilter) {
+        DexoptOptions dexoptOptions = new DexoptOptions();
+        dexoptOptions.compilationReason = mParams.getReason();
+        dexoptOptions.targetSdkVersion = mPkg.getTargetSdkVersion();
+        dexoptOptions.debuggable = mPkg.isDebuggable() || isAlwaysDebuggable();
+        // Generating a meaningful app image needs a profile to determine what to include in the
+        // image. Otherwise, the app image will be nearly empty.
+        dexoptOptions.generateAppImage =
+                isProfileGuidedFilter && isAppImageAllowed(dexInfo) && isAppImageEnabled();
+        dexoptOptions.hiddenApiPolicyEnabled = isHiddenApiPolicyEnabled();
+        dexoptOptions.comments = String.format(
+                "app-version-name:%s,app-version-code:%d,art-version:%d", mPkg.getVersionName(),
+                mPkg.getLongVersionCode(), mInjector.getArtVersion());
+        return dexoptOptions;
+    }
+
+    private boolean isAlwaysDebuggable() {
+        return SystemProperties.getBoolean("dalvik.vm.always_debuggable", false /* def */);
+    }
+
+    private boolean isAppImageEnabled() {
+        return !SystemProperties.get("dalvik.vm.appimageformat").isEmpty();
+    }
+
+    private boolean isHiddenApiPolicyEnabled() {
+        return mPkgState.getHiddenApiEnforcementPolicy()
+                != ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
+    }
+
+    @NonNull
+    GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget<DexInfoType> target,
+            @NonNull GetDexoptNeededOptions options) throws RemoteException {
+        int dexoptTrigger = getDexoptTrigger(target, options);
+
+        // The result should come from artd even if all the bits of `dexoptTrigger` are set
+        // because the result also contains information about the usable VDEX file.
+        // Note that the class loader context can be null. In that case, we intentionally pass the
+        // null value down to lower levels to indicate that the class loader context check should be
+        // skipped because we are only going to verify the dex code (see `adjustCompilerFilter`).
+        GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded(
+                target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
+                target.compilerFilter(), dexoptTrigger);
+
+        return result;
+    }
+
+    int getDexoptTrigger(@NonNull DexoptTarget<DexInfoType> target,
+            @NonNull GetDexoptNeededOptions options) throws RemoteException {
+        if ((options.flags() & ArtFlags.FLAG_FORCE) != 0) {
+            return DexoptTrigger.COMPILER_FILTER_IS_BETTER | DexoptTrigger.COMPILER_FILTER_IS_SAME
+                    | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+                    | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE
+                    | DexoptTrigger.NEED_EXTRACTION;
+        }
+
+        if ((options.flags() & ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0) {
+            return DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+        }
+
+        int dexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+                | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
+        if (options.profileMerged()) {
+            dexoptTrigger |= DexoptTrigger.COMPILER_FILTER_IS_SAME;
+        }
+
+        ArtifactsPath existingArtifactsPath = AidlUtils.buildArtifactsPath(
+                target.dexInfo().dexPath(), target.isa(), target.isInDalvikCache());
+
+        if (options.needsToBePublic()
+                && mInjector.getArtd().getArtifactsVisibility(existingArtifactsPath)
+                        == FileVisibility.NOT_OTHER_READABLE) {
+            // Typically, this happens after an app starts being used by other apps.
+            // This case should be the same as force as we have no choice but to trigger a new
+            // dexopt.
+            dexoptTrigger |=
+                    DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+        }
+
+        return dexoptTrigger;
+    }
+
+    private ArtdDexoptResult dexoptFile(@NonNull DexoptTarget<DexInfoType> target,
+            @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult,
+            @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass,
+            @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal)
+            throws RemoteException {
+        OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(target.dexInfo().dexPath(),
+                target.isa(), target.isInDalvikCache(), permissionSettings);
+
+        VdexPath inputVdex =
+                getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa());
+
+        DexMetadataPath dmFile = getDmFile(target.dexInfo());
+        if (dmFile != null
+                && ReasonMapping.REASONS_FOR_INSTALL.contains(dexoptOptions.compilationReason)) {
+            // If the DM file is passed to dex2oat, then add the "-dm" suffix to the reason (e.g.,
+            // "install-dm").
+            // Note that this only applies to reasons for app install because the goal is to give
+            // Play a signal that a DM file is downloaded at install time. We actually pass the DM
+            // file regardless of the compilation reason, but we don't append a suffix when the
+            // compilation reason is not a reason for app install.
+            // Also note that the "-dm" suffix does NOT imply anything in the DM file being used by
+            // dex2oat. dex2oat may ignore some contents of the DM file when appropriate. The
+            // compilation reason can still be "install-dm" even if dex2oat left all contents of the
+            // DM file unused or an empty DM file is passed to dex2oat.
+            dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm";
+        }
+
+        return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
+                target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
+                dmFile, priorityClass, dexoptOptions, artdCancellationSignal);
+    }
+
+    @Nullable
+    private VdexPath getInputVdex(@NonNull GetDexoptNeededResult getDexoptNeededResult,
+            @NonNull String dexPath, @NonNull String isa) {
+        if (!getDexoptNeededResult.isVdexUsable) {
+            return null;
+        }
+        switch (getDexoptNeededResult.artifactsLocation) {
+            case ArtifactsLocation.DALVIK_CACHE:
+                return VdexPath.artifactsPath(
+                        AidlUtils.buildArtifactsPath(dexPath, isa, true /* isInDalvikCache */));
+            case ArtifactsLocation.NEXT_TO_DEX:
+                return VdexPath.artifactsPath(
+                        AidlUtils.buildArtifactsPath(dexPath, isa, false /* isInDalvikCache */));
+            case ArtifactsLocation.DM:
+                // The DM file is passed to dex2oat as a separate flag whenever it exists.
+                return null;
+            default:
+                // This should never happen as the value is got from artd.
+                throw new IllegalStateException(
+                        "Unknown artifacts location " + getDexoptNeededResult.artifactsLocation);
+        }
+    }
+
+    @Nullable
+    private DexMetadataPath getDmFile(@NonNull DexInfoType dexInfo) throws RemoteException {
+        DexMetadataPath path = buildDmPath(dexInfo);
+        if (path == null) {
+            return null;
+        }
+        try {
+            if (mInjector.getArtd().getDmFileVisibility(path) != FileVisibility.NOT_FOUND) {
+                return path;
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG, "Failed to check DM file for " + dexInfo.dexPath(), e);
+        }
+        return null;
+    }
+
+    private boolean commitProfileChanges(@NonNull TmpProfilePath profile) throws RemoteException {
+        try {
+            mInjector.getArtd().commitTmpProfile(profile);
+            return true;
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG, "Failed to commit profile changes " + AidlUtils.toString(profile.finalPath),
+                    e);
+            return false;
+        }
+    }
+
+    @Nullable
+    private ProfilePath mergeProfiles(@NonNull DexInfoType dexInfo,
+            @Nullable ProfilePath referenceProfile) throws RemoteException {
+        OutputProfile output = buildOutputProfile(dexInfo, false /* isPublic */);
+
+        try {
+            if (mInjector.getArtd().mergeProfiles(getCurProfiles(dexInfo), referenceProfile, output,
+                        List.of(dexInfo.dexPath()), new MergeProfileOptions())) {
+                return ProfilePath.tmpProfilePath(output.profilePath);
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG,
+                    "Failed to merge profiles " + AidlUtils.toString(output.profilePath.finalPath),
+                    e);
+        }
+
+        return null;
+    }
+
+    private void cleanupCurProfiles(@NonNull DexInfoType dexInfo) throws RemoteException {
+        for (ProfilePath profile : getCurProfiles(dexInfo)) {
+            mInjector.getArtd().deleteProfile(profile);
+        }
+    }
+
+    // Methods to be implemented by child classes.
+
+    /** Returns true if the artifacts should be written to the global dalvik-cache directory. */
+    protected abstract boolean isInDalvikCache() throws RemoteException;
+
+    /** Returns information about all dex files. */
+    @NonNull protected abstract List<DexInfoType> getDexInfoList();
+
+    /** Returns true if the given dex file should be dexopted. */
+    protected abstract boolean isDexoptable(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns true if the artifacts should be shared with other apps. Note that this must imply
+     * {@link #isDexFilePublic(DexInfoType)}.
+     */
+    protected abstract boolean needsToBeShared(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
+     * (S_IROTH).
+     */
+    protected abstract boolean isDexFilePublic(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns a list of external profiles (e.g., a DM profile) that the reference profile can be
+     * initialized from, in the order of preference.
+     */
+    @NonNull protected abstract List<ProfilePath> getExternalProfiles(@NonNull DexInfoType dexInfo);
+
+    /** Returns the permission settings to use for the artifacts of the given dex file. */
+    @NonNull
+    protected abstract PermissionSettings getPermissionSettings(
+            @NonNull DexInfoType dexInfo, boolean canBePublic);
+
+    /** Returns all ABIs that the given dex file should be compiled for. */
+    @NonNull protected abstract List<Abi> getAllAbis(@NonNull DexInfoType dexInfo);
+
+    /** Returns the path to the reference profile of the given dex file. */
+    @NonNull protected abstract ProfilePath buildRefProfilePath(@NonNull DexInfoType dexInfo);
+
+    /** Returns true if app image (--app-image-fd) is allowed. */
+    protected abstract boolean isAppImageAllowed(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns the data structure that represents the temporary profile to use during processing.
+     */
+    @NonNull
+    protected abstract OutputProfile buildOutputProfile(
+            @NonNull DexInfoType dexInfo, boolean isPublic);
+
+    /** Returns the paths to the current profiles of the given dex file. */
+    @NonNull protected abstract List<ProfilePath> getCurProfiles(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns the path to the DM file that should be passed to dex2oat, or null if no DM file
+     * should be passed.
+     */
+    @Nullable protected abstract DexMetadataPath buildDmPath(@NonNull DexInfoType dexInfo);
+
+    @AutoValue
+    abstract static class DexoptTarget<DexInfoType extends DetailedDexInfo> {
+        abstract @NonNull DexInfoType dexInfo();
+        abstract @NonNull String isa();
+        abstract boolean isInDalvikCache();
+        abstract @NonNull String compilerFilter();
+
+        static <DexInfoType extends DetailedDexInfo> Builder<DexInfoType> builder() {
+            return new AutoValue_Dexopter_DexoptTarget.Builder<DexInfoType>();
+        }
+
+        @AutoValue.Builder
+        abstract static class Builder<DexInfoType extends DetailedDexInfo> {
+            abstract Builder setDexInfo(@NonNull DexInfoType value);
+            abstract Builder setIsa(@NonNull String value);
+            abstract Builder setIsInDalvikCache(boolean value);
+            abstract Builder setCompilerFilter(@NonNull String value);
+            abstract DexoptTarget<DexInfoType> build();
+        }
+    }
+
+    @AutoValue
+    abstract static class GetDexoptNeededOptions {
+        abstract @DexoptFlags int flags();
+        abstract boolean profileMerged();
+        abstract boolean needsToBePublic();
+
+        static Builder builder() {
+            return new AutoValue_Dexopter_GetDexoptNeededOptions.Builder();
+        }
+
+        @AutoValue.Builder
+        abstract static class Builder {
+            abstract Builder setFlags(@DexoptFlags int value);
+            abstract Builder setProfileMerged(boolean value);
+            abstract Builder setNeedsToBePublic(boolean value);
+            abstract GetDexoptNeededOptions build();
+        }
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public static class Injector {
+        @NonNull private final Context mContext;
+
+        public Injector(@NonNull Context context) {
+            mContext = context;
+
+            // Call the getters for various dependencies, to ensure correct initialization order.
+            getUserManager();
+            getDexUseManager();
+            getStorageManager();
+            ArtModuleServiceInitializer.getArtModuleServiceManager();
+        }
+
+        public boolean isSystemUiPackage(@NonNull String packageName) {
+            return Utils.isSystemUiPackage(mContext, packageName);
+        }
+
+        public boolean isLauncherPackage(@NonNull String packageName) {
+            return Utils.isLauncherPackage(mContext, packageName);
+        }
+
+        @NonNull
+        public UserManager getUserManager() {
+            return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
+        }
+
+        @NonNull
+        public DexUseManagerLocal getDexUseManager() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(DexUseManagerLocal.class));
+        }
+
+        @NonNull
+        public IArtd getArtd() {
+            return Utils.getArtd();
+        }
+
+        @NonNull
+        public StorageManager getStorageManager() {
+            return Objects.requireNonNull(mContext.getSystemService(StorageManager.class));
+        }
+
+        @NonNull
+        private PackageManagerLocal getPackageManagerLocal() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(PackageManagerLocal.class));
+        }
+
+        public long getArtVersion() {
+            try (var snapshot = getPackageManagerLocal().withUnfilteredSnapshot()) {
+                Map<String, PackageState> packageStates = snapshot.getPackageStates();
+                for (String artPackageName : ART_PACKAGE_NAMES) {
+                    PackageState pkgState = packageStates.get(artPackageName);
+                    if (pkgState != null) {
+                        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+                        return pkg.getLongVersionCode();
+                    }
+                }
+            }
+            return -1;
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/DumpHelper.java b/libartservice/service/java/com/android/server/art/DumpHelper.java
new file mode 100644
index 0000000..2a640ec
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DumpHelper.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DexLoader;
+import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+
+import android.annotation.NonNull;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.VMRuntime;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * A helper class to handle dump.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class DumpHelper {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    @NonNull private final Injector mInjector;
+
+    public DumpHelper(@NonNull ArtManagerLocal artManagerLocal) {
+        this(new Injector(artManagerLocal));
+    }
+
+    @VisibleForTesting
+    public DumpHelper(@NonNull Injector injector) {
+        mInjector = injector;
+    }
+
+    /** Handles {@link ArtManagerLocal#dump(PrintWriter, PackageManagerLocal.FilteredSnapshot)}. */
+    public void dump(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        snapshot.getPackageStates()
+                .values()
+                .stream()
+                .sorted(Comparator.comparing(PackageState::getPackageName))
+                .forEach(pkgState -> dumpPackage(pw, snapshot, pkgState));
+    }
+
+    /**
+     * Handles {@link
+     * ArtManagerLocal#dumpPackage(PrintWriter, PackageManagerLocal.FilteredSnapshot, String)}.
+     */
+    public void dumpPackage(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull PackageState pkgState) {
+        // An APEX has a uid of -1.
+        // TODO(b/256637152): Consider using `isApex` instead.
+        if (pkgState.getAppId() <= 0 || pkgState.getAndroidPackage() == null) {
+            return;
+        }
+
+        var ipw = new IndentingPrintWriter(pw);
+
+        String packageName = pkgState.getPackageName();
+        ipw.printf("[%s]\n", packageName);
+
+        List<DexContainerFileDexoptStatus> statuses =
+                mInjector.getArtManagerLocal()
+                        .getDexoptStatus(snapshot, packageName)
+                        .getDexContainerFileDexoptStatuses();
+        Map<String, SecondaryDexInfo> secondaryDexInfoByDexPath =
+                mInjector.getDexUseManager()
+                        .getSecondaryDexInfo(packageName)
+                        .stream()
+                        .collect(Collectors.toMap(SecondaryDexInfo::dexPath, Function.identity()));
+
+        // Use LinkedHashMap to keep the order. They are ordered by their split indexes.
+        var primaryStatusesByDexPath =
+                new LinkedHashMap<String, List<DexContainerFileDexoptStatus>>();
+        // Use TreeMap to force lexicographical order.
+        var secondaryStatusesByDexPath = new TreeMap<String, List<DexContainerFileDexoptStatus>>();
+        for (DexContainerFileDexoptStatus fileStatus : statuses) {
+            if (fileStatus.isPrimaryDex()) {
+                primaryStatusesByDexPath
+                        .computeIfAbsent(fileStatus.getDexContainerFile(), k -> new ArrayList<>())
+                        .add(fileStatus);
+            } else if (secondaryDexInfoByDexPath.containsKey(fileStatus.getDexContainerFile())) {
+                // The condition above is false only if a change occurs between
+                // `getDexoptStatus` and `getSecondaryDexInfo`, which is an edge case.
+                secondaryStatusesByDexPath
+                        .computeIfAbsent(fileStatus.getDexContainerFile(), k -> new ArrayList<>())
+                        .add(fileStatus);
+            }
+        }
+
+        ipw.increaseIndent();
+        for (List<DexContainerFileDexoptStatus> fileStatuses : primaryStatusesByDexPath.values()) {
+            dumpPrimaryDex(ipw, snapshot, fileStatuses, packageName);
+        }
+        if (!secondaryStatusesByDexPath.isEmpty()) {
+            ipw.println("known secondary dex files:");
+            ipw.increaseIndent();
+            for (Map.Entry<String, List<DexContainerFileDexoptStatus>> entry :
+                    secondaryStatusesByDexPath.entrySet()) {
+                dumpSecondaryDex(ipw, snapshot, entry.getValue(), packageName,
+                        secondaryDexInfoByDexPath.get(entry.getKey()));
+            }
+            ipw.decreaseIndent();
+        }
+        ipw.decreaseIndent();
+    }
+
+    private void dumpPrimaryDex(@NonNull IndentingPrintWriter ipw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            List<DexContainerFileDexoptStatus> fileStatuses, @NonNull String packageName) {
+        String dexPath = fileStatuses.get(0).getDexContainerFile();
+        ipw.printf("path: %s\n", dexPath);
+        ipw.increaseIndent();
+        dumpFileStatuses(ipw, fileStatuses);
+        dumpUsedByOtherApps(ipw, snapshot,
+                mInjector.getDexUseManager().getPrimaryDexLoaders(packageName, dexPath),
+                packageName);
+        ipw.decreaseIndent();
+    }
+
+    private void dumpSecondaryDex(@NonNull IndentingPrintWriter ipw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            List<DexContainerFileDexoptStatus> fileStatuses, @NonNull String packageName,
+            @NonNull SecondaryDexInfo info) {
+        String dexPath = fileStatuses.get(0).getDexContainerFile();
+        @FileVisibility int visibility = getDexFileVisibility(dexPath);
+        ipw.println(dexPath
+                + (visibility == FileVisibility.NOT_FOUND
+                                ? " (removed)"
+                                : (visibility == FileVisibility.OTHER_READABLE ? " (public)"
+                                                                               : "")));
+        ipw.increaseIndent();
+        dumpFileStatuses(ipw, fileStatuses);
+        ipw.printf("class loader context: %s\n", info.displayClassLoaderContext());
+        TreeMap<DexLoader, String> classLoaderContexts =
+                info.loaders().stream().collect(Collectors.toMap(loader
+                        -> loader,
+                        loader
+                        -> mInjector.getDexUseManager().getSecondaryClassLoaderContext(
+                                packageName, dexPath, loader),
+                        (a, b) -> a, TreeMap::new));
+        // We should print all class loader contexts even if `info.displayClassLoaderContext()` is
+        // not `VARYING_CLASS_LOADER_CONTEXTS`. This is because `info.displayClassLoaderContext()`
+        // may show the only supported class loader context while other apps have unsupported ones.
+        if (classLoaderContexts.values().stream().distinct().count() >= 2) {
+            ipw.increaseIndent();
+            for (var entry : classLoaderContexts.entrySet()) {
+                // entry.getValue() may be null due to a race, but it's an edge case.
+                ipw.printf("%s: %s\n", entry.getKey(), entry.getValue());
+            }
+            ipw.decreaseIndent();
+        }
+        dumpUsedByOtherApps(ipw, snapshot, info.loaders(), packageName);
+        ipw.decreaseIndent();
+    }
+
+    private void dumpFileStatuses(
+            @NonNull IndentingPrintWriter ipw, List<DexContainerFileDexoptStatus> fileStatuses) {
+        for (DexContainerFileDexoptStatus fileStatus : fileStatuses) {
+            ipw.printf("%s: [status=%s] [reason=%s]%s\n",
+                    VMRuntime.getInstructionSet(fileStatus.getAbi()),
+                    fileStatus.getCompilerFilter(), fileStatus.getCompilationReason(),
+                    fileStatus.isPrimaryAbi() ? " [primary-abi]" : "");
+            ipw.increaseIndent();
+            ipw.printf("[location is %s]\n", fileStatus.getLocationDebugString());
+            ipw.decreaseIndent();
+        }
+    }
+
+    private void dumpUsedByOtherApps(@NonNull IndentingPrintWriter ipw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull Set<DexLoader> dexLoaders, @NonNull String packageName) {
+        List<DexLoader> otherApps =
+                dexLoaders.stream()
+                        .filter(loader -> DexUseManagerLocal.isLoaderOtherApp(loader, packageName))
+                        .collect(Collectors.toList());
+        if (!otherApps.isEmpty()) {
+            ipw.printf("used by other apps: [%s]\n",
+                    otherApps.stream()
+                            .sorted()
+                            .map(loader -> getLoaderState(snapshot, loader))
+                            .collect(Collectors.joining(", ")));
+        }
+    }
+
+    @NonNull
+    private String getLoaderState(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull DexLoader loader) {
+        var result = new StringBuilder(loader.toString());
+        PackageState loadingPkgState = snapshot.getPackageState(loader.loadingPackageName());
+        if (loadingPkgState == null) {
+            // This can happen because the information held by DexUseManagerLocal can be outdated.
+            // We don't want to clean up the entry at this point because we don't want the dump
+            // operation to have an side effect.
+            result.append(" (removed)");
+            return result.toString();
+        }
+        Utils.Abi abi = Utils.getPrimaryAbi(loadingPkgState);
+        result.append(String.format(" (isa=%s)", abi.isa()));
+        return result.toString();
+    }
+
+    private @FileVisibility int getDexFileVisibility(@NonNull String dexPath) {
+        try {
+            return mInjector.getArtd().getDexFileVisibility(dexPath);
+        } catch (ServiceSpecificException | RemoteException e) {
+            Log.e(TAG, "Failed to get visibility of " + dexPath, e);
+            return FileVisibility.NOT_FOUND;
+        }
+    }
+
+    /** Injector pattern for testing purpose. */
+    @VisibleForTesting
+    public static class Injector {
+        @NonNull private final ArtManagerLocal mArtManagerLocal;
+
+        Injector(@NonNull ArtManagerLocal artManagerLocal) {
+            mArtManagerLocal = artManagerLocal;
+        }
+
+        @NonNull
+        public ArtManagerLocal getArtManagerLocal() {
+            return mArtManagerLocal;
+        }
+
+        @NonNull
+        public DexUseManagerLocal getDexUseManager() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(DexUseManagerLocal.class));
+        }
+
+        @NonNull
+        public IArtd getArtd() {
+            return Utils.getArtd();
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/IndentingPrintWriter.java b/libartservice/service/java/com/android/server/art/IndentingPrintWriter.java
new file mode 100644
index 0000000..b717786
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/IndentingPrintWriter.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+
+/**
+ * A minimal copy of the hidden {@link android.util.IndentingPrintWriter}.
+ *
+ * TODO(b/264968147): Avoid the duplication.
+ *
+ * @hide
+ */
+public class IndentingPrintWriter extends PrintWriter {
+    private final String mSingleIndent;
+
+    /** Mutable version of current indent */
+    private StringBuilder mIndentBuilder = new StringBuilder();
+    /** Cache of current {@link #mIndentBuilder} value */
+    private char[] mCurrentIndent;
+    /** Length of current line being built, excluding any indent */
+    private int mCurrentLength;
+
+    /**
+     * Flag indicating if we're currently sitting on an empty line, and that
+     * next write should be prefixed with the current indent.
+     */
+    private boolean mEmptyLine = true;
+
+    private char[] mSingleChar = new char[1];
+
+    public IndentingPrintWriter(@NonNull Writer writer) {
+        super(writer);
+        mSingleIndent = "  ";
+    }
+
+    /**
+     * Increases the indent starting with the next printed line.
+     */
+    @NonNull
+    public IndentingPrintWriter increaseIndent() {
+        mIndentBuilder.append(mSingleIndent);
+        mCurrentIndent = null;
+        return this;
+    }
+
+    /**
+     * Decreases the indent starting with the next printed line.
+     */
+    @NonNull
+    public IndentingPrintWriter decreaseIndent() {
+        mIndentBuilder.delete(0, mSingleIndent.length());
+        mCurrentIndent = null;
+        return this;
+    }
+
+    @Override
+    public void println() {
+        write('\n');
+    }
+
+    @Override
+    public void write(int c) {
+        mSingleChar[0] = (char) c;
+        write(mSingleChar, 0, 1);
+    }
+
+    @Override
+    public void write(@NonNull String s, int off, int len) {
+        final char[] buf = new char[len];
+        s.getChars(off, len - off, buf, 0);
+        write(buf, 0, len);
+    }
+
+    @Override
+    public void write(@NonNull char[] buf, int offset, int count) {
+        final int indentLength = mIndentBuilder.length();
+        final int bufferEnd = offset + count;
+        int lineStart = offset;
+        int lineEnd = offset;
+
+        // March through incoming buffer looking for newlines
+        while (lineEnd < bufferEnd) {
+            char ch = buf[lineEnd++];
+            mCurrentLength++;
+            if (ch == '\n') {
+                maybeWriteIndent();
+                super.write(buf, lineStart, lineEnd - lineStart);
+                lineStart = lineEnd;
+                mEmptyLine = true;
+                mCurrentLength = 0;
+            }
+        }
+
+        if (lineStart != lineEnd) {
+            maybeWriteIndent();
+            super.write(buf, lineStart, lineEnd - lineStart);
+        }
+    }
+
+    private void maybeWriteIndent() {
+        if (mEmptyLine) {
+            mEmptyLine = false;
+            if (mIndentBuilder.length() != 0) {
+                if (mCurrentIndent == null) {
+                    mCurrentIndent = mIndentBuilder.toString().toCharArray();
+                }
+                super.write(mCurrentIndent, 0, mCurrentIndent.length);
+            }
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
new file mode 100644
index 0000000..64f9bc5
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.Immutable;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.SharedLibrary;
+
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.DexClassLoader;
+import dalvik.system.PathClassLoader;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/** @hide */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class PrimaryDexUtils {
+    public static final String PROFILE_PRIMARY = "primary";
+    private static final String SHARED_LIBRARY_LOADER_TYPE = PathClassLoader.class.getName();
+
+    /**
+     * Returns the basic information about all primary dex files belonging to the package. The
+     * return value is a list where the entry at index 0 is the information about the base APK, and
+     * the entry at index i is the information about the (i-1)-th split APK.
+     */
+    @NonNull
+    public static List<PrimaryDexInfo> getDexInfo(@NonNull AndroidPackage pkg) {
+        return getDexInfoImpl(pkg)
+                .stream()
+                .map(builder -> builder.build())
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Same as above, but requires {@link PackageState} in addition, and returns the detailed
+     * information, including the class loader context.
+     */
+    @NonNull
+    public static List<DetailedPrimaryDexInfo> getDetailedDexInfo(
+            @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) {
+        return getDetailedDexInfoImpl(pkgState, pkg)
+                .stream()
+                .map(builder -> builder.buildDetailed())
+                .collect(Collectors.toList());
+    }
+
+    /** Returns the basic information about a dex file specified by {@code splitName}. */
+    @NonNull
+    public static PrimaryDexInfo getDexInfoBySplitName(
+            @NonNull AndroidPackage pkg, @Nullable String splitName) {
+        if (splitName == null) {
+            return getDexInfo(pkg).get(0);
+        } else {
+            return getDexInfo(pkg)
+                    .stream()
+                    .filter(info -> splitName.equals(info.splitName()))
+                    .findFirst()
+                    .orElseThrow(() -> {
+                        return new IllegalArgumentException(
+                                String.format("Split '%s' not found", splitName));
+                    });
+        }
+    }
+
+    @NonNull
+    private static List<PrimaryDexInfoBuilder> getDexInfoImpl(@NonNull AndroidPackage pkg) {
+        List<PrimaryDexInfoBuilder> dexInfos = new ArrayList<>();
+
+        for (var split : pkg.getSplits()) {
+            dexInfos.add(new PrimaryDexInfoBuilder(split));
+        }
+
+        return dexInfos;
+    }
+
+    @NonNull
+    private static List<PrimaryDexInfoBuilder> getDetailedDexInfoImpl(
+            @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) {
+        List<PrimaryDexInfoBuilder> dexInfos = getDexInfoImpl(pkg);
+
+        PrimaryDexInfoBuilder baseApk = dexInfos.get(0);
+        baseApk.mClassLoaderName = baseApk.mSplit.getClassLoaderName();
+        File baseDexFile = new File(baseApk.mSplit.getPath());
+        baseApk.mRelativeDexPath = baseDexFile.getName();
+
+        // Shared libraries are the dependencies of the base APK.
+        baseApk.mSharedLibrariesContext =
+                encodeSharedLibraries(pkgState.getSharedLibraryDependencies());
+
+        boolean isIsolatedSplitLoading = isIsolatedSplitLoading(pkg);
+
+        for (int i = 1; i < dexInfos.size(); i++) {
+            var dexInfoBuilder = dexInfos.get(i);
+            File splitDexFile = new File(dexInfoBuilder.mSplit.getPath());
+            if (!splitDexFile.getParent().equals(baseDexFile.getParent())) {
+                throw new IllegalStateException(
+                        "Split APK and base APK are in different directories: "
+                        + splitDexFile.getParent() + " != " + baseDexFile.getParent());
+            }
+            dexInfoBuilder.mRelativeDexPath = splitDexFile.getName();
+            if (isIsolatedSplitLoading && dexInfoBuilder.mSplit.isHasCode()) {
+                dexInfoBuilder.mClassLoaderName = dexInfoBuilder.mSplit.getClassLoaderName();
+
+                List<AndroidPackageSplit> dependencies = dexInfoBuilder.mSplit.getDependencies();
+                if (!Utils.isEmpty(dependencies)) {
+                    // We only care about the first dependency because it is the parent split. The
+                    // rest are configuration splits, which we don't care.
+                    AndroidPackageSplit dependency = dependencies.get(0);
+                    for (var dexInfo : dexInfos) {
+                        if (Objects.equals(dexInfo.mSplit, dependency)) {
+                            dexInfoBuilder.mSplitDependency = dexInfo;
+                            break;
+                        }
+                    }
+
+                    if (dexInfoBuilder.mSplitDependency == null) {
+                        throw new IllegalStateException(
+                                "Split dependency not found for " + splitDexFile);
+                    }
+                }
+            }
+        }
+
+        if (isIsolatedSplitLoading) {
+            computeClassLoaderContextsIsolated(dexInfos);
+        } else {
+            computeClassLoaderContexts(dexInfos);
+        }
+
+        return dexInfos;
+    }
+
+    /**
+     * Computes class loader context for an app that didn't request isolated split loading. Stores
+     * the results in {@link PrimaryDexInfoBuilder#mClassLoaderContext}.
+     *
+     * In this case, all the splits will be loaded in the base apk class loader (in the order of
+     * their definition).
+     *
+     * The CLC for the base APK is `CLN[]{shared-libraries}`; the CLC for the n-th split APK is
+     * `CLN[base.apk, split_0.apk, ..., split_n-1.apk]{shared-libraries}`; where `CLN` is the
+     * class loader name for the base APK.
+     */
+    private static void computeClassLoaderContexts(@NonNull List<PrimaryDexInfoBuilder> dexInfos) {
+        String baseClassLoaderName = dexInfos.get(0).mClassLoaderName;
+        String sharedLibrariesContext = dexInfos.get(0).mSharedLibrariesContext;
+        List<String> classpath = new ArrayList<>();
+        for (PrimaryDexInfoBuilder dexInfo : dexInfos) {
+            if (dexInfo.mSplit.isHasCode()) {
+                dexInfo.mClassLoaderContext = encodeClassLoader(baseClassLoaderName, classpath,
+                        null /* parentContext */, sharedLibrariesContext);
+            }
+            // Note that the splits with no code are not removed from the classpath computation.
+            // I.e., split_n might get the split_n-1 in its classpath dependency even if split_n-1
+            // has no code.
+            // The splits with no code do not matter for the runtime which ignores APKs without code
+            // when doing the classpath checks. As such we could actually filter them but we don't
+            // do it in order to keep consistency with how the apps are loaded.
+            classpath.add(dexInfo.mRelativeDexPath);
+        }
+    }
+
+    /**
+     * Computes class loader context for an app that requested for isolated split loading. Stores
+     * the results in {@link PrimaryDexInfoBuilder#mClassLoaderContext}.
+     *
+     * In this case, each split will be loaded with a separate class loader, whose context is a
+     * chain formed from inter-split dependencies.
+     *
+     * The CLC for the base APK is `CLN[]{shared-libraries}`; the CLC for the n-th split APK that
+     * depends on the base APK is `CLN_n[];CLN[base.apk]{shared-libraries}`; the CLC for the n-th
+     * split APK that depends on the m-th split APK is
+     * `CLN_n[];CLN_m[split_m.apk];...;CLN[base.apk]{shared-libraries}`; where `CLN` is the base
+     * class loader name for the base APK, `CLN_i` is the class loader name for the i-th split APK,
+     * and `...` represents the ancestors along the dependency chain.
+     *
+     * Specially, if a split does not have any dependency, the CLC for it is `CLN_n[]`.
+     */
+    private static void computeClassLoaderContextsIsolated(
+            @NonNull List<PrimaryDexInfoBuilder> dexInfos) {
+        for (PrimaryDexInfoBuilder dexInfo : dexInfos) {
+            if (dexInfo.mSplit.isHasCode()) {
+                dexInfo.mClassLoaderContext = encodeClassLoader(dexInfo.mClassLoaderName,
+                        null /* classpath */, getParentContextRecursive(dexInfo),
+                        dexInfo.mSharedLibrariesContext);
+            }
+        }
+    }
+
+    /**
+     * Computes the parent class loader context, recursively. Caches results in {@link
+     * PrimaryDexInfoBuilder#mContextForChildren}.
+     */
+    @Nullable
+    private static String getParentContextRecursive(@NonNull PrimaryDexInfoBuilder dexInfo) {
+        if (dexInfo.mSplitDependency == null) {
+            return null;
+        }
+        PrimaryDexInfoBuilder parent = dexInfo.mSplitDependency;
+        if (parent.mContextForChildren == null) {
+            parent.mContextForChildren =
+                    encodeClassLoader(parent.mClassLoaderName, List.of(parent.mRelativeDexPath),
+                            getParentContextRecursive(parent), parent.mSharedLibrariesContext);
+        }
+        return parent.mContextForChildren;
+    }
+
+    /**
+     * Returns class loader context in the format of
+     * `CLN[classpath...]{share-libraries};parent-context`, where `CLN` is the class loader name.
+     */
+    @NonNull
+    private static String encodeClassLoader(@Nullable String classLoaderName,
+            @Nullable List<String> classpath, @Nullable String parentContext,
+            @Nullable String sharedLibrariesContext) {
+        StringBuilder classLoaderContext = new StringBuilder();
+
+        classLoaderContext.append(encodeClassLoaderName(classLoaderName));
+
+        classLoaderContext.append(
+                "[" + (classpath != null ? String.join(":", classpath) : "") + "]");
+
+        if (!TextUtils.isEmpty(sharedLibrariesContext)) {
+            classLoaderContext.append(sharedLibrariesContext);
+        }
+
+        if (!TextUtils.isEmpty(parentContext)) {
+            classLoaderContext.append(";" + parentContext);
+        }
+
+        return classLoaderContext.toString();
+    }
+
+    @NonNull
+    private static String encodeClassLoaderName(@Nullable String classLoaderName) {
+        // `PathClassLoader` and `DexClassLoader` are grouped together because they have the same
+        // behavior. For null values we default to "PCL". This covers the case where a package does
+        // not specify any value for its class loader.
+        if (classLoaderName == null || PathClassLoader.class.getName().equals(classLoaderName)
+                || DexClassLoader.class.getName().equals(classLoaderName)) {
+            return "PCL";
+        } else if (DelegateLastClassLoader.class.getName().equals(classLoaderName)) {
+            return "DLC";
+        } else {
+            throw new IllegalStateException("Unsupported classLoaderName: " + classLoaderName);
+        }
+    }
+
+    /**
+     * Returns shared libraries context in the format of
+     * `{PCL[library_1_dex_1.jar:library_1_dex_2.jar:...]{library_1-dependencies}#PCL[
+     *     library_1_dex_2.jar:library_2_dex_2.jar:...]{library_2-dependencies}#...}`.
+     */
+    @Nullable
+    private static String encodeSharedLibraries(@Nullable List<SharedLibrary> sharedLibraries) {
+        if (Utils.isEmpty(sharedLibraries)) {
+            return null;
+        }
+        return sharedLibraries.stream()
+                .filter(library -> !library.isNative())
+                .map(library
+                        -> encodeClassLoader(SHARED_LIBRARY_LOADER_TYPE, library.getAllCodePaths(),
+                                null /* parentContext */,
+                                encodeSharedLibraries(library.getDependencies())))
+                .collect(Collectors.joining("#", "{", "}"));
+    }
+
+    public static boolean isIsolatedSplitLoading(@NonNull AndroidPackage pkg) {
+        return pkg.isIsolatedSplitLoading() && pkg.getSplits().size() > 1;
+    }
+
+    @NonNull
+    public static ProfilePath buildRefProfilePath(
+            @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo) {
+        String profileName = getProfileName(dexInfo.splitName());
+        return AidlUtils.buildProfilePathForPrimaryRef(pkgState.getPackageName(), profileName);
+    }
+
+    @NonNull
+    public static OutputProfile buildOutputProfile(@NonNull PackageState pkgState,
+            @NonNull PrimaryDexInfo dexInfo, int uid, int gid, boolean isPublic) {
+        String profileName = getProfileName(dexInfo.splitName());
+        return AidlUtils.buildOutputProfileForPrimary(
+                pkgState.getPackageName(), profileName, uid, gid, isPublic);
+    }
+
+    @NonNull
+    public static List<ProfilePath> getCurProfiles(@NonNull UserManager userManager,
+            @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo) {
+        List<ProfilePath> profiles = new ArrayList<>();
+        for (UserHandle handle : userManager.getUserHandles(true /* excludeDying */)) {
+            int userId = handle.getIdentifier();
+            PackageUserState userState = pkgState.getStateForUser(handle);
+            if (userState.isInstalled()) {
+                profiles.add(AidlUtils.buildProfilePathForPrimaryCur(
+                        userId, pkgState.getPackageName(), getProfileName(dexInfo.splitName())));
+            }
+        }
+        return profiles;
+    }
+
+    @NonNull
+    public static String getProfileName(@Nullable String splitName) {
+        return splitName == null ? PROFILE_PRIMARY : splitName + ".split";
+    }
+
+    @NonNull
+    public static List<ProfilePath> getExternalProfiles(@NonNull PrimaryDexInfo dexInfo) {
+        return List.of(AidlUtils.buildProfilePathForPrebuilt(dexInfo.dexPath()),
+                AidlUtils.buildProfilePathForDm(dexInfo.dexPath()));
+    }
+
+    /** Basic information about a primary dex file (either the base APK or a split APK). */
+    @Immutable
+    public static class PrimaryDexInfo {
+        private final @NonNull AndroidPackageSplit mSplit;
+
+        PrimaryDexInfo(@NonNull AndroidPackageSplit split) {
+            mSplit = split;
+        }
+
+        /** The path to the dex file. */
+        public @NonNull String dexPath() {
+            return mSplit.getPath();
+        }
+
+        /** True if the dex file has code. */
+        public boolean hasCode() {
+            return mSplit.isHasCode();
+        }
+
+        /** The name of the split, or null for base APK. */
+        public @Nullable String splitName() {
+            return mSplit.getName();
+        }
+    }
+
+    /**
+     * Detailed information about a primary dex file (either the base APK or a split APK). It
+     * contains the class loader context in addition to what is in {@link PrimaryDexInfo}, but
+     * producing it requires {@link PackageState}.
+     */
+    @Immutable
+    public static class DetailedPrimaryDexInfo extends PrimaryDexInfo implements DetailedDexInfo {
+        private final @Nullable String mClassLoaderContext;
+
+        DetailedPrimaryDexInfo(
+                @NonNull AndroidPackageSplit split, @Nullable String classLoaderContext) {
+            super(split);
+            mClassLoaderContext = classLoaderContext;
+        }
+
+        /**
+         * A string describing the structure of the class loader that the dex file is loaded with.
+         */
+        public @Nullable String classLoaderContext() {
+            return mClassLoaderContext;
+        }
+    }
+
+    private static class PrimaryDexInfoBuilder {
+        @NonNull AndroidPackageSplit mSplit;
+        @Nullable String mRelativeDexPath = null;
+        @Nullable String mClassLoaderContext = null;
+        @Nullable String mClassLoaderName = null;
+        @Nullable PrimaryDexInfoBuilder mSplitDependency = null;
+        /** The class loader context of the shared libraries. Only applicable for the base APK. */
+        @Nullable String mSharedLibrariesContext = null;
+        /** The class loader context for children to use when this dex file is used as a parent. */
+        @Nullable String mContextForChildren = null;
+
+        PrimaryDexInfoBuilder(@NonNull AndroidPackageSplit split) {
+            mSplit = split;
+        }
+
+        PrimaryDexInfo build() {
+            return new PrimaryDexInfo(mSplit);
+        }
+
+        DetailedPrimaryDexInfo buildDetailed() {
+            return new DetailedPrimaryDexInfo(mSplit, mClassLoaderContext);
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexopter.java b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java
new file mode 100644
index 0000000..a5481d9
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
+import static com.android.server.art.Utils.Abi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.pm.PackageStateModulesUtils;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.DexFile;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** @hide */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class PrimaryDexopter extends Dexopter<DetailedPrimaryDexInfo> {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    private final int mSharedGid;
+
+    public PrimaryDexopter(@NonNull Context context, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        this(new Injector(context), pkgState, pkg, params, cancellationSignal);
+    }
+
+    @VisibleForTesting
+    public PrimaryDexopter(@NonNull Injector injector, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        super(injector, pkgState, pkg, params, cancellationSignal);
+
+        mSharedGid = UserHandle.getSharedAppGid(pkgState.getAppId());
+        if (mSharedGid < 0) {
+            throw new IllegalStateException(
+                    String.format("Unable to get shared gid for package '%s' (app ID: %d)",
+                            pkgState.getPackageName(), pkgState.getAppId()));
+        }
+    }
+
+    @Override
+    protected boolean isInDalvikCache() throws RemoteException {
+        return Utils.isInDalvikCache(mPkgState, mInjector.getArtd());
+    }
+
+    @Override
+    @NonNull
+    protected List<DetailedPrimaryDexInfo> getDexInfoList() {
+        return PrimaryDexUtils.getDetailedDexInfo(mPkgState, mPkg);
+    }
+
+    @Override
+    protected boolean isDexoptable(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        if (!dexInfo.hasCode()) {
+            return false;
+        }
+        if ((mParams.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
+            return Objects.equals(mParams.getSplitName(), dexInfo.splitName());
+        }
+        return true;
+    }
+
+    @Override
+    protected boolean needsToBeShared(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return isSharedLibrary()
+                || mInjector.getDexUseManager().isPrimaryDexUsedByOtherApps(
+                        mPkgState.getPackageName(), dexInfo.dexPath());
+    }
+
+    @Override
+    protected boolean isDexFilePublic(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        // The filesystem permission of a primary dex file always has the S_IROTH bit. In practice,
+        // the accessibility is enforced by Application Sandbox, not filesystem permission.
+        return true;
+    }
+
+    @Override
+    @NonNull
+    protected List<ProfilePath> getExternalProfiles(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return PrimaryDexUtils.getExternalProfiles(dexInfo);
+    }
+
+    @Override
+    @NonNull
+    protected PermissionSettings getPermissionSettings(
+            @NonNull DetailedPrimaryDexInfo dexInfo, boolean canBePublic) {
+        // The files and directories should belong to the system so that Package Manager can manage
+        // them (e.g., move them around).
+        // We don't need the "read" bit for "others" on the directories because others only need to
+        // access the files in the directories, but they don't need to "ls" the directories.
+        FsPermission dirFsPermission = AidlUtils.buildFsPermission(Process.SYSTEM_UID /* uid */,
+                Process.SYSTEM_UID /* gid */, false /* isOtherReadable */,
+                true /* isOtherExecutable */);
+        FsPermission fileFsPermission = AidlUtils.buildFsPermission(
+                Process.SYSTEM_UID /* uid */, mSharedGid /* gid */, canBePublic);
+        // For primary dex, we can use the default SELinux context.
+        SeContext seContext = null;
+        return AidlUtils.buildPermissionSettings(dirFsPermission, fileFsPermission, seContext);
+    }
+
+    @Override
+    @NonNull
+    protected List<Abi> getAllAbis(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return Utils.getAllAbis(mPkgState);
+    }
+
+    @Override
+    @NonNull
+    protected ProfilePath buildRefProfilePath(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return PrimaryDexUtils.buildRefProfilePath(mPkgState, dexInfo);
+    }
+
+    @Override
+    protected boolean isAppImageAllowed(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        // Only allow app image for the base APK because having multiple app images is not
+        // supported.
+        // Additionally, disable app images if the app requests for the splits to be loaded in
+        // isolation because app images are unsupported for multiple class loaders (b/72696798).
+        // TODO(jiakaiz): Investigate whether this is still the best choice today.
+        return dexInfo.splitName() == null && !PrimaryDexUtils.isIsolatedSplitLoading(mPkg);
+    }
+
+    @Override
+    @NonNull
+    protected OutputProfile buildOutputProfile(
+            @NonNull DetailedPrimaryDexInfo dexInfo, boolean isPublic) {
+        return PrimaryDexUtils.buildOutputProfile(
+                mPkgState, dexInfo, Process.SYSTEM_UID, mSharedGid, isPublic);
+    }
+
+    @Override
+    @NonNull
+    protected List<ProfilePath> getCurProfiles(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), mPkgState, dexInfo);
+    }
+
+    @Override
+    @Nullable
+    protected DexMetadataPath buildDmPath(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return AidlUtils.buildDexMetadataPath(dexInfo.dexPath());
+    }
+
+    private boolean isSharedLibrary() {
+        return PackageStateModulesUtils.isLoadableInOtherProcesses(mPkgState, true /* codeOnly */);
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/ReasonMapping.java b/libartservice/service/java/com/android/server/art/ReasonMapping.java
new file mode 100644
index 0000000..ac08856
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ReasonMapping.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.model.ArtFlags.PriorityClassApi;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.annotation.SystemApi;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.pm.PackageManagerLocal;
+
+import dalvik.system.DexFile;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Maps a compilation reason to a compiler filter and a priority class.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class ReasonMapping {
+    private ReasonMapping() {}
+
+    // Keep this in sync with `ArtShellCommand.printHelp` except for 'inactive'.
+
+    /** Dexopting apps on the first boot after flashing or factory resetting the device. */
+    public static final String REASON_FIRST_BOOT = "first-boot";
+    /** Dexopting apps on the next boot after an OTA. */
+    public static final String REASON_BOOT_AFTER_OTA = "boot-after-ota";
+    /** Dexopting apps on the next boot after a mainline update. */
+    public static final String REASON_BOOT_AFTER_MAINLINE_UPDATE = "boot-after-mainline-update";
+    /** Installing an app after user presses the "install"/"update" button. */
+    public static final String REASON_INSTALL = "install";
+    /** Dexopting apps in the background. */
+    public static final String REASON_BG_DEXOPT = "bg-dexopt";
+    /** Invoked by cmdline. */
+    public static final String REASON_CMDLINE = "cmdline";
+    /** Downgrading the compiler filter when an app is not used for a long time. */
+    public static final String REASON_INACTIVE = "inactive";
+
+    // Reasons for Play Install Hints (go/install-hints).
+    public static final String REASON_INSTALL_FAST = "install-fast";
+    public static final String REASON_INSTALL_BULK = "install-bulk";
+    public static final String REASON_INSTALL_BULK_SECONDARY = "install-bulk-secondary";
+    public static final String REASON_INSTALL_BULK_DOWNGRADED = "install-bulk-downgraded";
+    public static final String REASON_INSTALL_BULK_SECONDARY_DOWNGRADED =
+            "install-bulk-secondary-downgraded";
+
+    /** @hide */
+    public static final Set<String> REASONS_FOR_INSTALL = Set.of(REASON_INSTALL,
+            REASON_INSTALL_FAST, REASON_INSTALL_BULK, REASON_INSTALL_BULK_SECONDARY,
+            REASON_INSTALL_BULK_DOWNGRADED, REASON_INSTALL_BULK_SECONDARY_DOWNGRADED);
+
+    // Keep this in sync with `ArtShellCommand.printHelp`.
+    /** @hide */
+    public static final Set<String> BATCH_DEXOPT_REASONS = Set.of(REASON_FIRST_BOOT,
+            REASON_BOOT_AFTER_OTA, REASON_BOOT_AFTER_MAINLINE_UPDATE, REASON_BG_DEXOPT);
+
+    /**
+     * Reasons for {@link ArtManagerLocal#dexoptPackages}.
+     *
+     * @hide
+     */
+    // clang-format off
+    @StringDef(prefix = "REASON_", value = {
+        REASON_FIRST_BOOT,
+        REASON_BOOT_AFTER_OTA,
+        REASON_BOOT_AFTER_MAINLINE_UPDATE,
+        REASON_BG_DEXOPT,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BatchDexoptReason {}
+
+    /**
+     * Reasons for {@link ArtManagerLocal#onBoot(String, Executor, Consumer<OperationProgress>)}.
+     *
+     * @hide
+     */
+    // clang-format off
+    @StringDef(prefix = "REASON_", value = {
+        REASON_FIRST_BOOT,
+        REASON_BOOT_AFTER_OTA,
+        REASON_BOOT_AFTER_MAINLINE_UPDATE,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BootReason {}
+
+    /**
+     * Loads the compiler filter from the system property for the given reason and checks for
+     * validity.
+     *
+     * @throws IllegalArgumentException if the reason is invalid
+     * @throws IllegalStateException if the system property value is invalid
+     *
+     * @hide
+     */
+    @NonNull
+    public static String getCompilerFilterForReason(@NonNull String reason) {
+        String value = SystemProperties.get("pm.dexopt." + reason);
+        if (TextUtils.isEmpty(value)) {
+            throw new IllegalArgumentException("No compiler filter for reason '" + reason + "'");
+        }
+        if (!Utils.isValidArtServiceCompilerFilter(value)) {
+            throw new IllegalStateException(
+                    "Got invalid compiler filter '" + value + "' for reason '" + reason + "'");
+        }
+        return value;
+    }
+
+    /**
+     * Loads the compiler filter from the system property for:
+     * - shared libraries
+     * - apps used by other apps without a dex metadata file
+     *
+     * @throws IllegalStateException if the system property value is invalid
+     *
+     * @hide
+     */
+    @NonNull
+    public static String getCompilerFilterForShared() {
+        // "shared" is technically not a compilation reason, but the compiler filter is defined as a
+        // system property as if "shared" is a reason.
+        String value = getCompilerFilterForReason("shared");
+        if (DexFile.isProfileGuidedCompilerFilter(value)) {
+            throw new IllegalStateException(
+                    "Compiler filter for 'shared' must not be profile guided, got '" + value + "'");
+        }
+        return value;
+    }
+
+    /**
+     * Returns the priority for the given reason.
+     *
+     * @throws IllegalArgumentException if the reason is invalid
+     * @see PriorityClassApi
+     *
+     * @hide
+     */
+    public static @PriorityClassApi byte getPriorityClassForReason(@NonNull String reason) {
+        switch (reason) {
+            case REASON_FIRST_BOOT:
+            case REASON_BOOT_AFTER_OTA:
+            case REASON_BOOT_AFTER_MAINLINE_UPDATE:
+                return ArtFlags.PRIORITY_BOOT;
+            case REASON_INSTALL_FAST:
+                return ArtFlags.PRIORITY_INTERACTIVE_FAST;
+            case REASON_INSTALL:
+            case REASON_CMDLINE:
+                return ArtFlags.PRIORITY_INTERACTIVE;
+            case REASON_BG_DEXOPT:
+            case REASON_INACTIVE:
+            case REASON_INSTALL_BULK:
+            case REASON_INSTALL_BULK_SECONDARY:
+            case REASON_INSTALL_BULK_DOWNGRADED:
+            case REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
+                return ArtFlags.PRIORITY_BACKGROUND;
+            default:
+                throw new IllegalArgumentException("No priority class for reason '" + reason + "'");
+        }
+    }
+
+    /**
+     * Loads the concurrency from the system property, for batch dexopt ({@link
+     * ArtManagerLocal#dexoptPackages}), or 1 if the system property is not found or cannot be
+     * parsed.
+     *
+     * @hide
+     */
+    public static int getConcurrencyForReason(@NonNull @BatchDexoptReason String reason) {
+        return SystemProperties.getInt("pm.dexopt." + reason + ".concurrency", 1 /* def */);
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/SecondaryDexopter.java b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java
new file mode 100644
index 0000000..c8c63db
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.Utils.Abi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Build;
+import android.os.CancellationSignal;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import java.util.List;
+
+/** @hide */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class SecondaryDexopter extends Dexopter<DetailedSecondaryDexInfo> {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    public SecondaryDexopter(@NonNull Context context, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        this(new Injector(context), pkgState, pkg, params, cancellationSignal);
+    }
+
+    @VisibleForTesting
+    public SecondaryDexopter(@NonNull Injector injector, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        super(injector, pkgState, pkg, params, cancellationSignal);
+    }
+
+    @Override
+    protected boolean isInDalvikCache() {
+        // A secondary dex file is added by the app, so it's always in a writable location and hence
+        // never uses dalvik-cache.
+        return false;
+    }
+
+    @Override
+    @NonNull
+    protected List<DetailedSecondaryDexInfo> getDexInfoList() {
+        return mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo(
+                mPkgState.getPackageName());
+    }
+
+    @Override
+    protected boolean isDexoptable(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return true;
+    }
+
+    @Override
+    protected boolean needsToBeShared(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return dexInfo.isUsedByOtherApps();
+    }
+
+    @Override
+    protected boolean isDexFilePublic(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return dexInfo.isDexFilePublic();
+    }
+
+    @Override
+    @NonNull
+    protected List<ProfilePath> getExternalProfiles(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        // A secondary dex file doesn't have any external profile to use.
+        return List.of();
+    }
+
+    @Override
+    @NonNull
+    protected PermissionSettings getPermissionSettings(
+            @NonNull DetailedSecondaryDexInfo dexInfo, boolean canBePublic) {
+        int uid = getUid(dexInfo);
+        // We need the "execute" bit for "others" even though `canBePublic` is false because the
+        // directory can contain other artifacts that needs to be public.
+        // We don't need the "read" bit for "others" on the directories because others only need to
+        // access the files in the directories, but they don't need to "ls" the directories.
+        FsPermission dirFsPermission = AidlUtils.buildFsPermission(uid /* uid */, uid /* gid */,
+                false /* isOtherReadable */, true /* isOtherExecutable */);
+        FsPermission fileFsPermission =
+                AidlUtils.buildFsPermission(uid /* uid */, uid /* gid */, canBePublic);
+        SeContext seContext = AidlUtils.buildSeContext(mPkgState.getSeInfo(), uid);
+        return AidlUtils.buildPermissionSettings(dirFsPermission, fileFsPermission, seContext);
+    }
+
+    @Override
+    @NonNull
+    protected List<Abi> getAllAbis(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return Utils.getAllAbisForNames(dexInfo.abiNames(), mPkgState);
+    }
+
+    @Override
+    @NonNull
+    protected ProfilePath buildRefProfilePath(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath());
+    }
+
+    @Override
+    protected boolean isAppImageAllowed(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        // The runtime can only load the app image of the base APK.
+        return false;
+    }
+
+    @Override
+    @NonNull
+    protected OutputProfile buildOutputProfile(
+            @NonNull DetailedSecondaryDexInfo dexInfo, boolean isPublic) {
+        int uid = getUid(dexInfo);
+        return AidlUtils.buildOutputProfileForSecondary(dexInfo.dexPath(), uid, uid, isPublic);
+    }
+
+    @Override
+    @NonNull
+    protected List<ProfilePath> getCurProfiles(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        // A secondary dex file can only be loaded by one user, so there is only one profile.
+        return List.of(AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
+    }
+
+    @Override
+    @Nullable
+    protected DexMetadataPath buildDmPath(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return null;
+    }
+
+    private int getUid(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return dexInfo.userHandle().getUid(mPkgState.getAppId());
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
new file mode 100644
index 0000000..fc94d64
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.ProfilePath.TmpProfilePath;
+
+import android.R;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.role.RoleManager;
+import android.apphibernation.AppHibernationManager;
+import android.content.Context;
+import android.os.Build;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.modules.utils.pm.PackageStateModulesUtils;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.DexFile;
+import dalvik.system.VMRuntime;
+
+import com.google.auto.value.AutoValue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.stream.Collectors;
+
+/** @hide */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public final class Utils {
+    public static final String TAG = ArtManagerLocal.TAG;
+    public static final String PLATFORM_PACKAGE_NAME = "android";
+
+    /** A copy of {@link android.os.Trace.TRACE_TAG_DALVIK}. */
+    private static final long TRACE_TAG_DALVIK = 1L << 14;
+
+    private Utils() {}
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static <T> boolean isEmpty(@Nullable Collection<T> array) {
+        return array == null || array.isEmpty();
+    }
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static <T> boolean isEmpty(@Nullable SparseArray<T> array) {
+        return array == null || array.size() == 0;
+    }
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static boolean isEmpty(@Nullable int[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /** Returns the ABI information for the package. The primary ABI comes first. */
+    @NonNull
+    public static List<Abi> getAllAbis(@NonNull PackageState pkgState) {
+        List<Abi> abis = new ArrayList<>();
+        abis.add(getPrimaryAbi(pkgState));
+        String pkgPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
+        String pkgSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
+        if (pkgSecondaryCpuAbi != null) {
+            Utils.check(pkgState.getPrimaryCpuAbi() != null);
+            String isa = getTranslatedIsa(VMRuntime.getInstructionSet(pkgSecondaryCpuAbi));
+            abis.add(Abi.create(nativeIsaToAbi(isa), isa, false /* isPrimaryAbi */));
+        }
+        // Primary and secondary ABIs should be guaranteed to have different ISAs.
+        if (abis.size() == 2 && abis.get(0).isa().equals(abis.get(1).isa())) {
+            throw new IllegalStateException(String.format(
+                    "Duplicate ISA: primary ABI '%s' ('%s'), secondary ABI '%s' ('%s')",
+                    pkgPrimaryCpuAbi, abis.get(0).name(), pkgSecondaryCpuAbi, abis.get(1).name()));
+        }
+        return abis;
+    }
+
+    /**
+     * Returns the ABI information for the ABIs with the given names. The primary ABI comes first,
+     * if given.
+     */
+    @NonNull
+    public static List<Abi> getAllAbisForNames(
+            @NonNull Set<String> abiNames, @NonNull PackageState pkgState) {
+        Utils.check(abiNames.stream().allMatch(Utils::isNativeAbi));
+        Abi pkgPrimaryAbi = getPrimaryAbi(pkgState);
+        return abiNames.stream()
+                .map(name
+                        -> Abi.create(name, VMRuntime.getInstructionSet(name),
+                                name.equals(pkgPrimaryAbi.name())))
+                .sorted(Comparator.comparing(Abi::isPrimaryAbi).reversed())
+                .collect(Collectors.toList());
+    }
+
+    @NonNull
+    public static Abi getPrimaryAbi(@NonNull PackageState pkgState) {
+        String primaryCpuAbi = pkgState.getPrimaryCpuAbi();
+        if (primaryCpuAbi != null) {
+            String isa = getTranslatedIsa(VMRuntime.getInstructionSet(primaryCpuAbi));
+            return Abi.create(nativeIsaToAbi(isa), isa, true /* isPrimaryAbi */);
+        }
+        // This is the most common case. The package manager can't infer the ABIs, probably because
+        // the package doesn't contain any native library. The app is launched with the device's
+        // preferred ABI.
+        String preferredAbi = Constants.getPreferredAbi();
+        Utils.check(isNativeAbi(preferredAbi));
+        return Abi.create(
+                preferredAbi, VMRuntime.getInstructionSet(preferredAbi), true /* isPrimaryAbi */);
+    }
+
+    /**
+     * If the given ISA isn't native to the device, returns the ISA that the native bridge
+     * translates it to. Otherwise, returns the ISA as is. This is the ISA that the app is actually
+     * launched with and therefore the ISA that should be used to compile the app.
+     */
+    @NonNull
+    private static String getTranslatedIsa(@NonNull String isa) {
+        String abi64 = Constants.getNative64BitAbi();
+        String abi32 = Constants.getNative32BitAbi();
+        if ((abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64)))
+                || (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32)))) {
+            return isa;
+        }
+        String translatedIsa = SystemProperties.get("ro.dalvik.vm.isa." + isa);
+        if (TextUtils.isEmpty(translatedIsa)) {
+            throw new IllegalStateException(String.format("Unsupported isa '%s'", isa));
+        }
+        return translatedIsa;
+    }
+
+    @NonNull
+    private static String nativeIsaToAbi(@NonNull String isa) {
+        String abi64 = Constants.getNative64BitAbi();
+        if (abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64))) {
+            return abi64;
+        }
+        String abi32 = Constants.getNative32BitAbi();
+        if (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32))) {
+            return abi32;
+        }
+        throw new IllegalStateException(String.format("Non-native isa '%s'", isa));
+    }
+
+    private static boolean isNativeAbi(@NonNull String abiName) {
+        return abiName.equals(Constants.getNative64BitAbi())
+                || abiName.equals(Constants.getNative32BitAbi());
+    }
+
+    /**
+     * Returns whether the artifacts of the primary dex files should be in the global dalvik-cache
+     * directory.
+     *
+     * This method is not needed for secondary dex files because they are always in writable
+     * locations.
+     */
+    @NonNull
+    public static boolean isInDalvikCache(@NonNull PackageState pkgState, @NonNull IArtd artd)
+            throws RemoteException {
+        // The artifacts should be in the global dalvik-cache directory if:
+        // (1). the package is on a system partition, even if the partition is remounted read-write,
+        //      or
+        // (2). the package is in any other readonly location. (At the time of writing, this only
+        //      include Incremental FS.)
+        //
+        // Right now, we are using some heuristics to determine this. For (1), we can potentially
+        // use "libfstab" instead as a general solution, but for (2), unfortunately, we have to
+        // stick with heuristics.
+        //
+        // We cannot rely on access(2) because:
+        // - It doesn't take effective capabilities into account, from which artd gets root access
+        //   to the filesystem.
+        // - The `faccessat` variant with the `AT_EACCESS` flag, which takes effective capabilities
+        //   into account, is not supported by bionic.
+        //
+        // We cannot rely on `f_flags` returned by statfs(2) because:
+        // - Incremental FS is tagged as read-write while it's actually not.
+        return (pkgState.isSystem() && !pkgState.isUpdatedSystemApp())
+                || artd.isIncrementalFsPath(
+                        pkgState.getAndroidPackage().getSplits().get(0).getPath());
+    }
+
+    /** Returns true if the given string is a valid compiler filter. */
+    public static boolean isValidArtServiceCompilerFilter(@NonNull String compilerFilter) {
+        if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) {
+            return true;
+        }
+        return DexFile.isValidCompilerFilter(compilerFilter);
+    }
+
+    @NonNull
+    public static IArtd getArtd() {
+        IArtd artd = IArtd.Stub.asInterface(ArtModuleServiceInitializer.getArtModuleServiceManager()
+                                                    .getArtdServiceRegisterer()
+                                                    .waitForService());
+        if (artd == null) {
+            throw new IllegalStateException("Unable to connect to artd");
+        }
+        return artd;
+    }
+
+    public static boolean implies(boolean cond1, boolean cond2) {
+        return cond1 ? cond2 : true;
+    }
+
+    public static void check(boolean cond) {
+        // This cannot be replaced with `assert` because `assert` is not enabled in Android.
+        if (!cond) {
+            throw new IllegalStateException("Check failed");
+        }
+    }
+
+    @NonNull
+    public static PackageState getPackageStateOrThrow(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        PackageState pkgState = snapshot.getPackageState(packageName);
+        if (pkgState == null) {
+            throw new IllegalArgumentException("Package not found: " + packageName);
+        }
+        return pkgState;
+    }
+
+    @NonNull
+    public static AndroidPackage getPackageOrThrow(@NonNull PackageState pkgState) {
+        AndroidPackage pkg = pkgState.getAndroidPackage();
+        if (pkg == null) {
+            throw new IllegalArgumentException(
+                    "Unable to get package " + pkgState.getPackageName());
+        }
+        return pkg;
+    }
+
+    @NonNull
+    public static String assertNonEmpty(@Nullable String str) {
+        if (TextUtils.isEmpty(str)) {
+            throw new IllegalArgumentException();
+        }
+        return str;
+    }
+
+    public static void executeAndWait(@NonNull Executor executor, @NonNull Runnable runnable) {
+        getFuture(CompletableFuture.runAsync(runnable, executor));
+    }
+
+    public static <T> T getFuture(Future<T> future) {
+        try {
+            return future.get();
+        } catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof RuntimeException) {
+                throw (RuntimeException) cause;
+            }
+            throw new RuntimeException(cause);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Returns true if the given package is dexoptable.
+     *
+     * @param appHibernationManager the {@link AppHibernationManager} instance for checking
+     *         hibernation status, or null to skip the check
+     */
+    public static boolean canDexoptPackage(
+            @NonNull PackageState pkgState, @Nullable AppHibernationManager appHibernationManager) {
+        if (!PackageStateModulesUtils.isDexoptable(pkgState)) {
+            return false;
+        }
+
+        // We do not dexopt unused packages.
+        // If `appHibernationManager` is null, the caller's intention is to skip the check.
+        if (appHibernationManager != null
+                && shouldSkipDexoptDueToHibernation(pkgState, appHibernationManager)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public static boolean shouldSkipDexoptDueToHibernation(
+            @NonNull PackageState pkgState, @NonNull AppHibernationManager appHibernationManager) {
+        return appHibernationManager.isHibernatingGlobally(pkgState.getPackageName())
+                && appHibernationManager.isOatArtifactDeletionEnabled();
+    }
+
+    public static long getPackageLastActiveTime(@NonNull PackageState pkgState,
+            @NonNull DexUseManagerLocal dexUseManager, @NonNull UserManager userManager) {
+        long lastUsedAtMs = dexUseManager.getPackageLastUsedAtMs(pkgState.getPackageName());
+        // The time where the last user installed the package the first time.
+        long lastFirstInstallTimeMs =
+                userManager.getUserHandles(true /* excludeDying */)
+                        .stream()
+                        .map(handle -> pkgState.getStateForUser(handle))
+                        .map(userState -> userState.getFirstInstallTimeMillis())
+                        .max(Long::compare)
+                        .orElse(0l);
+        return Math.max(lastUsedAtMs, lastFirstInstallTimeMs);
+    }
+
+    public static void deleteIfExistsSafe(@Nullable File file) {
+        if (file != null) {
+            deleteIfExistsSafe(file.toPath());
+        }
+    }
+
+    public static void deleteIfExistsSafe(@NonNull Path path) {
+        try {
+            Files.deleteIfExists(path);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to delete file '" + path + "'", e);
+        }
+    }
+
+    public static boolean isSystemUiPackage(@NonNull Context context, @NonNull String packageName) {
+        return packageName.equals(context.getString(R.string.config_systemUi));
+    }
+
+    public static boolean isLauncherPackage(@NonNull Context context, @NonNull String packageName) {
+        RoleManager roleManager = context.getSystemService(RoleManager.class);
+        return roleManager.getRoleHolders(RoleManager.ROLE_HOME).contains(packageName);
+    }
+
+    /**
+     * Gets the existing reference profile if one exists, or initializes a reference profile from an
+     * external profile.
+     *
+     * If the reference profile is initialized from an external profile, the returned profile path
+     * will be a {@link TmpProfilePath}. It's the callers responsibility to either commit it to the
+     * final location by calling {@link IArtd#commitTmpProfile} or clean it up by calling {@link
+     * IArtd#deleteProfile}.
+     *
+     * @param dexPath the path to the dex file that the profile is checked against
+     * @param refProfile the path where an existing reference profile would be found, if present
+     * @param externalProfiles a list of external profiles to initialize the reference profile from,
+     *         in the order of preference
+     * @param initOutput the final location to initialize the reference profile to
+     *
+     * @return a pair where the first element is the found or initialized profile, and the second
+     *         element is true if the profile is readable by others. Returns null if there is no
+     *         reference profile or external profile to use
+     */
+    @Nullable
+    public static Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull IArtd artd,
+            @NonNull String dexPath, @NonNull ProfilePath refProfile,
+            @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile initOutput)
+            throws RemoteException {
+        try {
+            if (artd.isProfileUsable(refProfile, dexPath)) {
+                boolean isOtherReadable =
+                        artd.getProfileVisibility(refProfile) == FileVisibility.OTHER_READABLE;
+                return Pair.create(refProfile, isOtherReadable);
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG,
+                    "Failed to use the existing reference profile "
+                            + AidlUtils.toString(refProfile),
+                    e);
+        }
+
+        ProfilePath initializedProfile =
+                initReferenceProfile(artd, dexPath, externalProfiles, initOutput);
+        return initializedProfile != null ? Pair.create(initializedProfile, true) : null;
+    }
+
+    /**
+     * Similar to above, but never uses an existing profile.
+     *
+     * Unlike the one above, this method doesn't return a boolean flag to indicate if the profile is
+     * readable by others. The profile returned by this method is initialized form an external
+     * profile, meaning it has no user data, so it's always readable by others.
+     */
+    @Nullable
+    public static ProfilePath initReferenceProfile(@NonNull IArtd artd, @NonNull String dexPath,
+            @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile output)
+            throws RemoteException {
+        for (ProfilePath profile : externalProfiles) {
+            try {
+                // If the profile path is a PrebuiltProfilePath, and the APK is really a prebuilt
+                // one, rewriting the profile is unnecessary because the dex location is known at
+                // build time and is correctly set in the profile header. However, the APK can also
+                // be an installed one, in which case partners may place a profile file next to the
+                // APK at install time. Rewriting the profile in the latter case is necessary.
+                if (artd.copyAndRewriteProfile(profile, output, dexPath)) {
+                    return ProfilePath.tmpProfilePath(output.profilePath);
+                }
+            } catch (ServiceSpecificException e) {
+                Log.e(TAG, "Failed to initialize profile from " + AidlUtils.toString(profile), e);
+            }
+        }
+
+        return null;
+    }
+
+    public static void logArtdException(@NonNull RemoteException e) {
+        String message = "An error occurred when calling artd";
+        if (e instanceof DeadObjectException) {
+            // We assume that `DeadObjectException` only happens in two cases:
+            // 1. artd crashed, in which case a native stack trace was logged.
+            // 2. artd was killed before system server during device shutdown, in which case the
+            //    exception is expected.
+            // In either case, we don't need to surface the exception from here.
+            // The Java stack trace is intentionally omitted because it's not helpful.
+            Log.e(TAG, message);
+        } else {
+            // Not expected. Log wtf to surface it.
+            Slog.wtf(TAG, message, e);
+        }
+    }
+
+    @AutoValue
+    public abstract static class Abi {
+        static @NonNull Abi create(
+                @NonNull String name, @NonNull String isa, boolean isPrimaryAbi) {
+            return new AutoValue_Utils_Abi(name, isa, isPrimaryAbi);
+        }
+
+        // The ABI name. E.g., "arm64-v8a".
+        abstract @NonNull String name();
+
+        // The instruction set name. E.g., "arm64".
+        abstract @NonNull String isa();
+
+        abstract boolean isPrimaryAbi();
+    }
+
+    public static class Tracing implements AutoCloseable {
+        public Tracing(@NonNull String methodName) {
+            Trace.traceBegin(TRACE_TAG_DALVIK, methodName);
+        }
+
+        @Override
+        public void close() {
+            Trace.traceEnd(TRACE_TAG_DALVIK);
+        }
+    }
+
+    public static class TracingWithTimingLogging extends Tracing {
+        @NonNull private final String mTag;
+        @NonNull private final String mMethodName;
+        @NonNull private final long mStartTimeMs;
+
+        public TracingWithTimingLogging(@NonNull String tag, @NonNull String methodName) {
+            super(methodName);
+            mTag = tag;
+            mMethodName = methodName;
+            mStartTimeMs = SystemClock.elapsedRealtime();
+            Log.d(tag, methodName);
+        }
+
+        @Override
+        public void close() {
+            Log.d(mTag,
+                    mMethodName + " took to complete: "
+                            + (SystemClock.elapsedRealtime() - mStartTimeMs) + "ms");
+            super.close();
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/ArtFlags.java b/libartservice/service/java/com/android/server/art/model/ArtFlags.java
new file mode 100644
index 0000000..cc4f826
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/ArtFlags.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.job.JobScheduler;
+
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.PriorityClass;
+import com.android.server.art.ReasonMapping;
+import com.android.server.pm.PackageManagerLocal;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class ArtFlags {
+    // Common flags.
+
+    /**
+     * Whether the operation is applied for primary dex'es (all APKs that are installed as part of
+     * the package, including the base APK and all other split APKs).
+     */
+    public static final int FLAG_FOR_PRIMARY_DEX = 1 << 0;
+    /**
+     * Whether the operation is applied for secondary dex'es (APKs/JARs that the app puts in its
+     * own data directory at runtime and loads with custom classloaders).
+     */
+    public static final int FLAG_FOR_SECONDARY_DEX = 1 << 1;
+
+    // Flags specific to `dexoptPackage`.
+
+    /**
+     * Whether to dexopt dependency libraries as well (dependencies that are declared by the app
+     * with <uses-library> tags and transitive dependencies).
+     */
+    public static final int FLAG_SHOULD_INCLUDE_DEPENDENCIES = 1 << 2;
+    /**
+     * Whether the intention is to downgrade the compiler filter. If true, the dexopt will
+     * be skipped if the target compiler filter is better than or equal to the compiler filter
+     * of the existing dexopt artifacts, or dexopt artifacts do not exist.
+     */
+    public static final int FLAG_SHOULD_DOWNGRADE = 1 << 3;
+    /**
+     * Whether to force dexopt. If true, the dexopt will be performed regardless of
+     * any existing dexopt artifacts.
+     */
+    public static final int FLAG_FORCE = 1 << 4;
+    /**
+     * If set, the dexopt will be performed for a single split. Otherwise, the dexopt
+     * will be performed for all splits. {@link DexoptParams.Builder#setSplitName()} can be used
+     * to specify the split to dexopt.
+     *
+     * When this flag is set, {@link #FLAG_FOR_PRIMARY_DEX} must be set, and {@link
+     * #FLAG_FOR_SECONDARY_DEX} and {@link #FLAG_SHOULD_INCLUDE_DEPENDENCIES} must not be set.
+     */
+    public static final int FLAG_FOR_SINGLE_SPLIT = 1 << 5;
+    /**
+     * If set, skips the dexopt if the remaining storage space is low. The threshold is
+     * controlled by the global settings {@code sys_storage_threshold_percentage} and {@code
+     * sys_storage_threshold_max_bytes}.
+     */
+    public static final int FLAG_SKIP_IF_STORAGE_LOW = 1 << 6;
+
+    /**
+     * Flags for {@link
+     * ArtManagerLocal#getDexoptStatus(PackageManagerLocal.FilteredSnapshot, String, int)}.
+     *
+     * @hide
+     */
+    // clang-format off
+    @IntDef(flag = true, prefix = "FLAG_", value = {
+        FLAG_FOR_PRIMARY_DEX,
+        FLAG_FOR_SECONDARY_DEX,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GetStatusFlags {}
+
+    /**
+     * Default flags that are used when {@link
+     * ArtManagerLocal#getDexoptStatus(PackageManagerLocal.FilteredSnapshot, String)} is
+     * called.
+     * Value: {@link #FLAG_FOR_PRIMARY_DEX}, {@link #FLAG_FOR_SECONDARY_DEX}.
+     */
+    public static @GetStatusFlags int defaultGetStatusFlags() {
+        return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX;
+    }
+
+    /**
+     * Flags for {@link DexoptParams}.
+     *
+     * @hide
+     */
+    // clang-format off
+    @IntDef(flag = true, prefix = "FLAG_", value = {
+        FLAG_FOR_PRIMARY_DEX,
+        FLAG_FOR_SECONDARY_DEX,
+        FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+        FLAG_SHOULD_DOWNGRADE,
+        FLAG_FORCE,
+        FLAG_FOR_SINGLE_SPLIT,
+        FLAG_SKIP_IF_STORAGE_LOW,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DexoptFlags {}
+
+    /**
+     * Default flags that are used when
+     * {@link DexoptParams.Builder#Builder(String)} is called.
+     *
+     * @hide
+     */
+    public static @DexoptFlags int defaultDexoptFlags(@NonNull String reason) {
+        switch (reason) {
+            case ReasonMapping.REASON_INSTALL:
+            case ReasonMapping.REASON_INSTALL_FAST:
+            case ReasonMapping.REASON_INSTALL_BULK:
+            case ReasonMapping.REASON_INSTALL_BULK_SECONDARY:
+            case ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED:
+            case ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
+                return FLAG_FOR_PRIMARY_DEX;
+            case ReasonMapping.REASON_INACTIVE:
+                return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX | FLAG_SHOULD_DOWNGRADE;
+            case ReasonMapping.REASON_FIRST_BOOT:
+            case ReasonMapping.REASON_BOOT_AFTER_OTA:
+            case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE:
+                return FLAG_FOR_PRIMARY_DEX | FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+            case ReasonMapping.REASON_BG_DEXOPT:
+                return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX
+                        | FLAG_SHOULD_INCLUDE_DEPENDENCIES | FLAG_SKIP_IF_STORAGE_LOW;
+            case ReasonMapping.REASON_CMDLINE:
+            default:
+                return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX
+                        | FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+        }
+    }
+
+    // Keep in sync with `PriorityClass` except for `PRIORITY_NONE`.
+    // Keep this in sync with `ArtShellCommand.printHelp` except for 'PRIORITY_NONE'.
+
+    /**
+     * Initial value. Not expected.
+     *
+     * @hide
+     */
+    public static final int PRIORITY_NONE = -1;
+    /** Indicates that the operation blocks boot. */
+    public static final int PRIORITY_BOOT = PriorityClass.BOOT;
+    /**
+     * Indicates that a human is waiting on the result and the operation is more latency sensitive
+     * than usual.
+     */
+    public static final int PRIORITY_INTERACTIVE_FAST = PriorityClass.INTERACTIVE_FAST;
+    /** Indicates that a human is waiting on the result. */
+    public static final int PRIORITY_INTERACTIVE = PriorityClass.INTERACTIVE;
+    /** Indicates that the operation runs in background. */
+    public static final int PRIORITY_BACKGROUND = PriorityClass.BACKGROUND;
+
+    /**
+     * Indicates the priority of an operation. The value affects the resource usage and the process
+     * priority. A higher value may result in faster execution but may consume more resources and
+     * compete for resources with other processes.
+     *
+     * @hide
+     */
+    // clang-format off
+    @IntDef(prefix = "PRIORITY_", value = {
+        PRIORITY_NONE,
+        PRIORITY_BOOT,
+        PRIORITY_INTERACTIVE_FAST,
+        PRIORITY_INTERACTIVE,
+        PRIORITY_BACKGROUND,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PriorityClassApi {}
+
+    /** The job has been successfully scheduled. */
+    public static final int SCHEDULE_SUCCESS = 0;
+
+    /** @see JobScheduler#RESULT_FAILURE */
+    public static final int SCHEDULE_JOB_SCHEDULER_FAILURE = 1;
+
+    /** The job is disabled by the system property {@code pm.dexopt.disable_bg_dexopt}. */
+    public static final int SCHEDULE_DISABLED_BY_SYSPROP = 2;
+
+    /**
+     * Indicates the result of scheduling a background dexopt job.
+     *
+     * @hide
+     */
+    // clang-format off
+    @IntDef(prefix = "SCHEDULE_", value = {
+        SCHEDULE_SUCCESS,
+        SCHEDULE_JOB_SCHEDULER_FAILURE,
+        SCHEDULE_DISABLED_BY_SYSPROP,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScheduleStatus {}
+
+    private ArtFlags() {}
+}
diff --git a/libartservice/service/java/com/android/server/art/model/BatchDexoptParams.java b/libartservice/service/java/com/android/server/art/model/BatchDexoptParams.java
new file mode 100644
index 0000000..ffe5500
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/BatchDexoptParams.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class BatchDexoptParams {
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public static final class Builder {
+        private @NonNull List<String> mPackageNames; // This is assumed immutable.
+        private @NonNull DexoptParams mDexoptParams;
+
+        /** @hide */
+        public Builder(
+                @NonNull List<String> defaultPackages, @NonNull DexoptParams defaultDexoptParams) {
+            mPackageNames = defaultPackages; // The argument is assumed immutable.
+            mDexoptParams = defaultDexoptParams;
+        }
+
+        /**
+         * Sets the list of packages to dexopt. The dexopt will be scheduled in the given
+         * order.
+         *
+         * If not called, the default list will be used.
+         */
+        @NonNull
+        public Builder setPackages(@NonNull List<String> packageNames) {
+            mPackageNames = Collections.unmodifiableList(new ArrayList<>(packageNames));
+            return this;
+        }
+
+        /**
+         * Sets the params for dexopting each package.
+         *
+         * If not called, the default params built from {@link DexoptParams#Builder(String)} will
+         * be used.
+         */
+        @NonNull
+        public Builder setDexoptParams(@NonNull DexoptParams dexoptParams) {
+            mDexoptParams = dexoptParams;
+            return this;
+        }
+
+        /** Returns the built object. */
+        @NonNull
+        public BatchDexoptParams build() {
+            return new AutoValue_BatchDexoptParams(mPackageNames, mDexoptParams);
+        }
+    }
+
+    /** @hide */
+    protected BatchDexoptParams() {}
+
+    /** The ordered list of packages to dexopt. */
+    public abstract @NonNull List<String> getPackages();
+
+    /** The params for dexopting each package. */
+    public abstract @NonNull DexoptParams getDexoptParams();
+}
diff --git a/libartservice/service/java/com/android/server/art/model/Config.java b/libartservice/service/java/com/android/server/art/model/Config.java
new file mode 100644
index 0000000..49bc930
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/Config.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import static com.android.server.art.ArtManagerLocal.BatchDexoptStartCallback;
+import static com.android.server.art.ArtManagerLocal.DexoptDoneCallback;
+import static com.android.server.art.ArtManagerLocal.ScheduleBackgroundDexoptJobCallback;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.art.ArtManagerLocal;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * A class that stores the configurations set by the consumer of ART Service at runtime. This class
+ * is thread-safe.
+ *
+ * @hide
+ */
+public class Config {
+    /** @see ArtManagerLocal#setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback) */
+    @GuardedBy("this")
+    @Nullable
+    private Callback<BatchDexoptStartCallback, Void> mBatchDexoptStartCallback = null;
+
+    /**
+     * @see ArtManagerLocal#setScheduleBackgroundDexoptJobCallback(Executor,
+     *         ScheduleBackgroundDexoptJobCallback)
+     */
+    @GuardedBy("this")
+    @Nullable
+    private Callback<ScheduleBackgroundDexoptJobCallback, Void>
+            mScheduleBackgroundDexoptJobCallback = null;
+
+    /**
+     * @see ArtManagerLocal#addDexoptDoneCallback(Executor, DexoptDoneCallback)
+     */
+    @GuardedBy("this")
+    @NonNull
+    private LinkedHashMap<DexoptDoneCallback, Callback<DexoptDoneCallback, Boolean>>
+            mDexoptDoneCallbacks = new LinkedHashMap<>();
+
+    public synchronized void setBatchDexoptStartCallback(
+            @NonNull Executor executor, @NonNull BatchDexoptStartCallback callback) {
+        mBatchDexoptStartCallback = Callback.create(callback, executor);
+    }
+
+    public synchronized void clearBatchDexoptStartCallback() {
+        mBatchDexoptStartCallback = null;
+    }
+
+    @Nullable
+    public synchronized Callback<BatchDexoptStartCallback, Void> getBatchDexoptStartCallback() {
+        return mBatchDexoptStartCallback;
+    }
+
+    public synchronized void setScheduleBackgroundDexoptJobCallback(
+            @NonNull Executor executor, @NonNull ScheduleBackgroundDexoptJobCallback callback) {
+        mScheduleBackgroundDexoptJobCallback = Callback.create(callback, executor);
+    }
+
+    public synchronized void clearScheduleBackgroundDexoptJobCallback() {
+        mScheduleBackgroundDexoptJobCallback = null;
+    }
+
+    @Nullable
+    public synchronized Callback<ScheduleBackgroundDexoptJobCallback, Void>
+    getScheduleBackgroundDexoptJobCallback() {
+        return mScheduleBackgroundDexoptJobCallback;
+    }
+
+    public synchronized void addDexoptDoneCallback(boolean onlyIncludeUpdates,
+            @NonNull Executor executor, @NonNull DexoptDoneCallback callback) {
+        if (mDexoptDoneCallbacks.putIfAbsent(
+                    callback, Callback.create(callback, executor, onlyIncludeUpdates))
+                != null) {
+            throw new IllegalStateException("callback already added");
+        }
+    }
+
+    public synchronized void removeDexoptDoneCallback(@NonNull DexoptDoneCallback callback) {
+        mDexoptDoneCallbacks.remove(callback);
+    }
+
+    @NonNull
+    public synchronized List<Callback<DexoptDoneCallback, Boolean>> getDexoptDoneCallbacks() {
+        return new ArrayList<>(mDexoptDoneCallbacks.values());
+    }
+
+    @AutoValue
+    public static abstract class Callback<CallbackType, ExtraType> {
+        public abstract @NonNull CallbackType get();
+        public abstract @NonNull Executor executor();
+        public abstract @Nullable ExtraType extra();
+        static <CallbackType, ExtraType> @NonNull Callback<CallbackType, ExtraType> create(
+                @NonNull CallbackType callback, @NonNull Executor executor,
+                @Nullable ExtraType extra) {
+            return new AutoValue_Config_Callback<CallbackType, ExtraType>(
+                    callback, executor, extra);
+        }
+        static <CallbackType> @NonNull Callback<CallbackType, Void> create(
+                @NonNull CallbackType callback, @NonNull Executor executor) {
+            return new AutoValue_Config_Callback<CallbackType, Void>(
+                    callback, executor, null /* extra */);
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DeleteResult.java b/libartservice/service/java/com/android/server/art/model/DeleteResult.java
new file mode 100644
index 0000000..e8e1520
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DeleteResult.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class DeleteResult {
+    /** @hide */
+    protected DeleteResult() {}
+
+    /** @hide */
+    public static @NonNull DeleteResult create(long freedBytes) {
+        return new AutoValue_DeleteResult(freedBytes);
+    }
+
+    /** The amount of the disk space freed by the deletion, in bytes. */
+    public abstract long getFreedBytes();
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java b/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java
new file mode 100644
index 0000000..932813f
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.Immutable;
+
+/**
+ * Detailed information about a dex file.
+ *
+ * @hide
+ */
+@Immutable
+public interface DetailedDexInfo {
+    /** The path to the dex file. */
+    @NonNull String dexPath();
+
+    /**
+     * A string describing the structure of the class loader that the dex file is loaded with, or
+     * null if the class loader context is invalid.
+     */
+    @Nullable String classLoaderContext();
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DexContainerFileUseInfo.java b/libartservice/service/java/com/android/server/art/model/DexContainerFileUseInfo.java
new file mode 100644
index 0000000..1f4c013
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DexContainerFileUseInfo.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The information about the use of a dex container file.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class DexContainerFileUseInfo {
+    /** @hide */
+    protected DexContainerFileUseInfo() {}
+
+    /** @hide */
+    public static @NonNull DexContainerFileUseInfo create(@NonNull String dexContainerFile,
+            @NonNull UserHandle userHandle, @NonNull Set<String> loadingPackages) {
+        return new AutoValue_DexContainerFileUseInfo(dexContainerFile, userHandle, loadingPackages);
+    }
+
+    /** The absolute path to the dex container file. */
+    public abstract @NonNull String getDexContainerFile();
+
+    /** The {@link UserHandle} that represents the human user who loads the dex file. */
+    public abstract @NonNull UserHandle getUserHandle();
+
+    /** The names of packages that load the dex file. Guaranteed to be non-empty. */
+    public abstract @NonNull Set<String> getLoadingPackages();
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DexoptParams.java b/libartservice/service/java/com/android/server/art/model/DexoptParams.java
new file mode 100644
index 0000000..cf86ad6
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DexoptParams.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import static com.android.server.art.model.ArtFlags.DexoptFlags;
+import static com.android.server.art.model.ArtFlags.PriorityClassApi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.Immutable;
+import com.android.server.art.ArtConstants;
+import com.android.server.art.ReasonMapping;
+import com.android.server.art.Utils;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+@Immutable
+public class DexoptParams {
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public static final class Builder {
+        private DexoptParams mParams = new DexoptParams();
+
+        /**
+         * Creates a builder.
+         *
+         * Uses default flags ({@link ArtFlags#defaultDexoptFlags()}).
+         *
+         * @param reason Compilation reason. Can be a string defined in {@link ReasonMapping} or a
+         *         custom string. If the value is a string defined in {@link ReasonMapping}, it
+         *         determines the compiler filter and/or the priority class, if those values are not
+         *         explicitly set. If the value is a custom string, the priority class and the
+         *         compiler filter must be explicitly set.
+         */
+        public Builder(@NonNull String reason) {
+            this(reason, ArtFlags.defaultDexoptFlags(reason));
+        }
+
+        /**
+         * Same as above, but allows to specify flags.
+         */
+        public Builder(@NonNull String reason, @DexoptFlags int flags) {
+            mParams.mReason = reason;
+            setFlags(flags);
+        }
+
+        /** Replaces all flags with the given value. */
+        @NonNull
+        public Builder setFlags(@DexoptFlags int value) {
+            mParams.mFlags = value;
+            return this;
+        }
+
+        /** Replaces the flags specified by the mask with the given value. */
+        @NonNull
+        public Builder setFlags(@DexoptFlags int value, @DexoptFlags int mask) {
+            mParams.mFlags = (mParams.mFlags & ~mask) | (value & mask);
+            return this;
+        }
+
+        /**
+         * The target compiler filter, passed as the {@code --compiler-filer} option to dex2oat.
+         * Supported values are listed in
+         * https://source.android.com/docs/core/dalvik/configure#compilation_options.
+         *
+         * Note that the compiler filter might be adjusted before the execution based on factors
+         * like whether the profile is available or whether the app is used by other apps. If not
+         * set, the default compiler filter for the given reason will be used.
+         */
+        @NonNull
+        public Builder setCompilerFilter(@NonNull String value) {
+            mParams.mCompilerFilter = value;
+            return this;
+        }
+
+        /**
+         * The priority of the operation. If not set, the default priority class for the given
+         * reason will be used.
+         *
+         * @see PriorityClassApi
+         */
+        @NonNull
+        public Builder setPriorityClass(@PriorityClassApi int value) {
+            mParams.mPriorityClass = value;
+            return this;
+        }
+
+        /**
+         * The name of the split to dexopt, or null for the base split. This option is only
+         * available when {@link ArtFlags#FLAG_FOR_SINGLE_SPLIT} is set.
+         */
+        @NonNull
+        public Builder setSplitName(@Nullable String value) {
+            mParams.mSplitName = value;
+            return this;
+        }
+
+        /**
+         * Returns the built object.
+         *
+         * @throws IllegalArgumentException if the built options would be invalid
+         */
+        @NonNull
+        public DexoptParams build() {
+            if (mParams.mReason.isEmpty()) {
+                throw new IllegalArgumentException("Reason must not be empty");
+            }
+            if (mParams.mReason.equals(ArtConstants.REASON_VDEX)) {
+                throw new IllegalArgumentException(
+                        "Reason must not be '" + ArtConstants.REASON_VDEX + "'");
+            }
+
+            if (mParams.mCompilerFilter.isEmpty()) {
+                mParams.mCompilerFilter = ReasonMapping.getCompilerFilterForReason(mParams.mReason);
+            } else if (!Utils.isValidArtServiceCompilerFilter(mParams.mCompilerFilter)) {
+                throw new IllegalArgumentException(
+                        "Invalid compiler filter '" + mParams.mCompilerFilter + "'");
+            }
+
+            if (mParams.mPriorityClass == ArtFlags.PRIORITY_NONE) {
+                mParams.mPriorityClass = ReasonMapping.getPriorityClassForReason(mParams.mReason);
+            } else if (mParams.mPriorityClass < 0 || mParams.mPriorityClass > 100) {
+                throw new IllegalArgumentException("Invalid priority class "
+                        + mParams.mPriorityClass + ". Must be between 0 and 100");
+            }
+
+            if ((mParams.mFlags & (ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX))
+                    == 0) {
+                throw new IllegalArgumentException("Nothing to dexopt");
+            }
+
+            if ((mParams.mFlags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
+                    && (mParams.mFlags & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0) {
+                throw new IllegalArgumentException(
+                        "FLAG_SHOULD_INCLUDE_DEPENDENCIES must not set if FLAG_FOR_PRIMARY_DEX is "
+                        + "not set.");
+            }
+
+            if ((mParams.mFlags & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
+                if ((mParams.mFlags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0) {
+                    throw new IllegalArgumentException(
+                            "FLAG_FOR_PRIMARY_DEX must be set when FLAG_FOR_SINGLE_SPLIT is set");
+                }
+                if ((mParams.mFlags
+                            & (ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                    | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES))
+                        != 0) {
+                    throw new IllegalArgumentException(
+                            "FLAG_FOR_SECONDARY_DEX and FLAG_SHOULD_INCLUDE_DEPENDENCIES must "
+                            + "not be set when FLAG_FOR_SINGLE_SPLIT is set");
+                }
+            } else {
+                if (mParams.mSplitName != null) {
+                    throw new IllegalArgumentException(
+                            "Split name must not be set when FLAG_FOR_SINGLE_SPLIT is not set");
+                }
+            }
+
+            return mParams;
+        }
+    }
+
+    /**
+     * A value indicating that dexopt shouldn't be run. This value is consumed by ART Services and
+     * is not propagated to dex2oat.
+     */
+    public static final String COMPILER_FILTER_NOOP = "skip";
+
+    private @DexoptFlags int mFlags = 0;
+    private @NonNull String mCompilerFilter = "";
+    private @PriorityClassApi int mPriorityClass = ArtFlags.PRIORITY_NONE;
+    private @NonNull String mReason = "";
+    private @Nullable String mSplitName = null;
+
+    private DexoptParams() {}
+
+    /** Returns all flags. */
+    public @DexoptFlags int getFlags() {
+        return mFlags;
+    }
+
+    /** The target compiler filter. */
+    public @NonNull String getCompilerFilter() {
+        return mCompilerFilter;
+    }
+
+    /** The priority class. */
+    public @PriorityClassApi int getPriorityClass() {
+        return mPriorityClass;
+    }
+
+    /**
+     * The compilation reason.
+     *
+     * DO NOT directly use the string value to determine the resource usage and the process
+     * priority. Use {@link #getPriorityClass}.
+     */
+    public @NonNull String getReason() {
+        return mReason;
+    }
+
+    /** The name of the split to dexopt, or null for the base split. */
+    public @Nullable String getSplitName() {
+        return mSplitName;
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DexoptResult.java b/libartservice/service/java/com/android/server/art/model/DexoptResult.java
new file mode 100644
index 0000000..79f9b5f
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DexoptResult.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class DexoptResult {
+    // Possible values of {@link #DexoptResultStatus}.
+    // A larger number means a higher priority. If multiple dex container files are processed, the
+    // final status will be the one with the highest priority.
+    /** Dexopt is skipped because there is no need to do it. */
+    public static final int DEXOPT_SKIPPED = 10;
+    /** Dexopt is performed successfully. */
+    public static final int DEXOPT_PERFORMED = 20;
+    /** Dexopt is failed. */
+    public static final int DEXOPT_FAILED = 30;
+    /** Dexopt is cancelled. */
+    public static final int DEXOPT_CANCELLED = 40;
+
+    /** @hide */
+    // clang-format off
+    @IntDef(prefix = {"DEXOPT_"}, value = {
+        DEXOPT_SKIPPED,
+        DEXOPT_FAILED,
+        DEXOPT_PERFORMED,
+        DEXOPT_CANCELLED,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DexoptResultStatus {}
+
+    /** @hide */
+    protected DexoptResult() {}
+
+    /** @hide */
+    public static @NonNull DexoptResult create(@NonNull String requestedCompilerFilter,
+            @NonNull String reason, @NonNull List<PackageDexoptResult> packageDexoptResult) {
+        return new AutoValue_DexoptResult(requestedCompilerFilter, reason, packageDexoptResult);
+    }
+
+    /**
+     * The requested compiler filter. Note that the compiler filter might be adjusted before the
+     * execution based on factors like whether the profile is available or whether the app is
+     * used by other apps.
+     *
+     * @see DexoptParams.Builder#setCompilerFilter(String)
+     * @see DexContainerFileDexoptResult#getActualCompilerFilter()
+     */
+    public abstract @NonNull String getRequestedCompilerFilter();
+
+    /** The compilation reason. */
+    public abstract @NonNull String getReason();
+
+    /**
+     * The result of each individual package.
+     *
+     * If the request is to dexopt a single package without dexopting dependencies, the only
+     * element is the result of the requested package.
+     *
+     * If the request is to dexopt a single package with {@link
+     * ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES} set, the first element is the result of the
+     * requested package, and the rest are the results of the dependency packages.
+     *
+     * If the request is to dexopt multiple packages, the list contains the results of all the
+     * requested packages. The results of their dependency packages are also included if {@link
+     * ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES} is set.
+     *
+     * If the request is a batch dexopt operation that got cancelled, the list still has an entry
+     * for every package that was requested to be optimized.
+     */
+    public abstract @NonNull List<PackageDexoptResult> getPackageDexoptResults();
+
+    /** The final status. */
+    public @DexoptResultStatus int getFinalStatus() {
+        return getPackageDexoptResults()
+                .stream()
+                .mapToInt(result -> result.getStatus())
+                .max()
+                .orElse(DEXOPT_SKIPPED);
+    }
+
+    /** @hide */
+    @NonNull
+    public static String dexoptResultStatusToString(@DexoptResultStatus int status) {
+        switch (status) {
+            case DexoptResult.DEXOPT_SKIPPED:
+                return "SKIPPED";
+            case DexoptResult.DEXOPT_PERFORMED:
+                return "PERFORMED";
+            case DexoptResult.DEXOPT_FAILED:
+                return "FAILED";
+            case DexoptResult.DEXOPT_CANCELLED:
+                return "CANCELLED";
+        }
+        throw new IllegalArgumentException("Unknown dexopt status " + status);
+    }
+
+    /**
+     * Describes the result of a package.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @Immutable
+    @AutoValue
+    public static abstract class PackageDexoptResult {
+        /** @hide */
+        protected PackageDexoptResult() {}
+
+        /** @hide */
+        public static @NonNull PackageDexoptResult create(@NonNull String packageName,
+                @NonNull List<DexContainerFileDexoptResult> dexContainerFileDexoptResults,
+                @Nullable @DexoptResultStatus Integer packageLevelStatus) {
+            return new AutoValue_DexoptResult_PackageDexoptResult(
+                    packageName, dexContainerFileDexoptResults, packageLevelStatus);
+        }
+
+        /** The package name. */
+        public abstract @NonNull String getPackageName();
+
+        /**
+         * The results of dexopting dex container files. Note that there can be multiple entries
+         * for the same dex container file, but for different ABIs.
+         */
+        public abstract @NonNull List<DexContainerFileDexoptResult>
+        getDexContainerFileDexoptResults();
+
+        /** @hide */
+        @Nullable @DexoptResultStatus public abstract Integer getPackageLevelStatus();
+
+        /** The overall status of the package. */
+        public @DexoptResultStatus int getStatus() {
+            return getPackageLevelStatus() != null ? getPackageLevelStatus()
+                                                   : getDexContainerFileDexoptResults()
+                                                             .stream()
+                                                             .mapToInt(result -> result.getStatus())
+                                                             .max()
+                                                             .orElse(DEXOPT_SKIPPED);
+        }
+
+        /** True if the package has any artifacts updated by this operation. */
+        public boolean hasUpdatedArtifacts() {
+            return getDexContainerFileDexoptResults().stream().anyMatch(
+                    result -> result.getStatus() == DEXOPT_PERFORMED);
+        }
+    }
+
+    /**
+     * Describes the result of dexopting a dex container file.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @Immutable
+    @AutoValue
+    public static abstract class DexContainerFileDexoptResult {
+        /** @hide */
+        protected DexContainerFileDexoptResult() {}
+
+        /** @hide */
+        public static @NonNull DexContainerFileDexoptResult create(@NonNull String dexContainerFile,
+                boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter,
+                @DexoptResultStatus int status, long dex2oatWallTimeMillis,
+                long dex2oatCpuTimeMillis, long sizeBytes, long sizeBeforeBytes,
+                boolean isSkippedDueToStorageLow) {
+            return new AutoValue_DexoptResult_DexContainerFileDexoptResult(dexContainerFile,
+                    isPrimaryAbi, abi, compilerFilter, status, dex2oatWallTimeMillis,
+                    dex2oatCpuTimeMillis, sizeBytes, sizeBeforeBytes, isSkippedDueToStorageLow);
+        }
+
+        /** The absolute path to the dex container file. */
+        public abstract @NonNull String getDexContainerFile();
+
+        /**
+         * If true, the dexopt is for the primary ABI of the package (the ABI that the
+         * application is launched with). Otherwise, the dexopt is for an ABI that other
+         * applications might be launched with when using this application's code.
+         */
+        public abstract boolean isPrimaryAbi();
+
+        /**
+         * Returns the ABI that the dexopt is for. Possible values are documented at
+         * https://developer.android.com/ndk/guides/abis#sa.
+         */
+        public abstract @NonNull String getAbi();
+
+        /**
+         * The actual compiler filter.
+         *
+         * @see DexoptParams.Builder#setCompilerFilter(String)
+         */
+        public abstract @NonNull String getActualCompilerFilter();
+
+        /** The status of dexopting this dex container file. */
+        public abstract @DexoptResultStatus int getStatus();
+
+        /**
+         * The wall time of the dex2oat invocation, in milliseconds, if dex2oat succeeded or was
+         * cancelled. Returns 0 if dex2oat failed or was not run, or if failed to get the value.
+         */
+        public abstract @DurationMillisLong long getDex2oatWallTimeMillis();
+
+        /**
+         * The CPU time of the dex2oat invocation, in milliseconds, if dex2oat succeeded or was
+         * cancelled. Returns 0 if dex2oat failed or was not run, or if failed to get the value.
+         */
+        public abstract @DurationMillisLong long getDex2oatCpuTimeMillis();
+
+        /**
+         * The total size, in bytes, of the dexopt artifacts. Returns 0 if {@link #getStatus()}
+         * is not {@link #DEXOPT_PERFORMED}.
+         */
+        public abstract long getSizeBytes();
+
+        /**
+         * The total size, in bytes, of the previous dexopt artifacts that has been replaced.
+         * Returns 0 if there were no previous dexopt artifacts or {@link #getStatus()} is not
+         * {@link #DEXOPT_PERFORMED}.
+         */
+        public abstract long getSizeBeforeBytes();
+
+        /** @hide */
+        public abstract boolean isSkippedDueToStorageLow();
+
+        @Override
+        @NonNull
+        public String toString() {
+            return String.format("DexContainerFileDexoptResult{"
+                            + "dexContainerFile=%s, "
+                            + "primaryAbi=%b, "
+                            + "abi=%s, "
+                            + "actualCompilerFilter=%s, "
+                            + "status=%s, "
+                            + "dex2oatWallTimeMillis=%d, "
+                            + "dex2oatCpuTimeMillis=%d, "
+                            + "sizeBytes=%d, "
+                            + "sizeBeforeBytes=%d}",
+                    getDexContainerFile(), isPrimaryAbi(), getAbi(), getActualCompilerFilter(),
+                    DexoptResult.dexoptResultStatusToString(getStatus()),
+                    getDex2oatWallTimeMillis(), getDex2oatCpuTimeMillis(), getSizeBytes(),
+                    getSizeBeforeBytes());
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DexoptStatus.java b/libartservice/service/java/com/android/server/art/model/DexoptStatus.java
new file mode 100644
index 0000000..40130ea
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DexoptStatus.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.List;
+
+/**
+ * Describes the dexopt status of a package.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class DexoptStatus {
+    /** @hide */
+    protected DexoptStatus() {}
+
+    /** @hide */
+    public static @NonNull DexoptStatus create(
+            @NonNull List<DexContainerFileDexoptStatus> dexContainerFileDexoptStatuses) {
+        return new AutoValue_DexoptStatus(dexContainerFileDexoptStatuses);
+    }
+
+    /**
+     * The statuses of the dex container file dexopts. Note that there can be multiple entries
+     * for the same dex container file, but for different ABIs.
+     */
+    @NonNull public abstract List<DexContainerFileDexoptStatus> getDexContainerFileDexoptStatuses();
+
+    /**
+     * Describes the dexopt status of a dex container file.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @Immutable
+    @AutoValue
+    public abstract static class DexContainerFileDexoptStatus {
+        /** @hide */
+        protected DexContainerFileDexoptStatus() {}
+
+        /** @hide */
+        public static @NonNull DexContainerFileDexoptStatus create(@NonNull String dexContainerFile,
+                boolean isPrimaryDex, boolean isPrimaryAbi, @NonNull String abi,
+                @NonNull String compilerFilter, @NonNull String compilationReason,
+                @NonNull String locationDebugString) {
+            return new AutoValue_DexoptStatus_DexContainerFileDexoptStatus(dexContainerFile,
+                    isPrimaryDex, isPrimaryAbi, abi, compilerFilter, compilationReason,
+                    locationDebugString);
+        }
+
+        /** The absolute path to the dex container file. */
+        public abstract @NonNull String getDexContainerFile();
+
+        /**
+         * If true, the dex container file is a primary dex (the base APK or a split APK).
+         * Otherwise, it's a secondary dex (a APK or a JAR that the package sideloaded into its data
+         * directory).
+         */
+        public abstract boolean isPrimaryDex();
+
+        /**
+         * If true, the dexopt is for the primary ABI of the package (the ABI that the
+         * application is launched with). Otherwise, the dexopt is for an ABI that other
+         * applications might be launched with when using this application's code.
+         */
+        public abstract boolean isPrimaryAbi();
+
+        /**
+         * Returns the ABI that the dexopt is for. Possible values are documented at
+         * https://developer.android.com/ndk/guides/abis#sa.
+         */
+        public abstract @NonNull String getAbi();
+
+        /**
+         * A human-readable string that describes the compiler filter.
+         *
+         * Possible values are:
+         * <ul>
+         *   <li>A valid value of the {@code --compiler-filer} option passed to {@code dex2oat}, if
+         *     the dexopt artifacts are valid. See
+         *     https://source.android.com/docs/core/dalvik/configure#compilation_options.
+         *   <li>{@code "run-from-apk"}, if the dexopt artifacts do not exist.
+         *   <li>{@code "run-from-apk-fallback"}, if the dexopt artifacts exist but are invalid
+         *     because the dex container file has changed.
+         *   <li>{@code "error"}, if an unexpected error occurs.
+         * </ul>
+         */
+        public abstract @NonNull String getCompilerFilter();
+
+        /**
+         * A string that describes the compilation reason.
+         *
+         * Possible values are:
+         * <ul>
+         *   <li>The compilation reason, in text format, passed to {@code dex2oat}.
+         *   <li>{@code "unknown"}: if the reason is empty or the dexopt artifacts do not exist.
+         *   <li>{@code "error"}: if an unexpected error occurs.
+         * </ul>
+         *
+         * Note that this value can differ from the requested compilation reason passed to {@link
+         * DexoptParams.Builder}. Specifically, if the requested reason is for app install (e.g.,
+         * "install"), and a DM file is passed to {@code dex2oat}, a "-dm" suffix will be appended
+         * to the actual reason (e.g., "install-dm"). Other compilation reasons remain unchanged
+         * even if a DM file is passed to {@code dex2oat}.
+         *
+         * Also note that the "-dm" suffix does <b>not</b> imply anything in the DM file being used
+         * by {@code dex2oat}. The compilation reason can still be "install-dm" even if {@code
+         * dex2oat} left all contents of the DM file unused or an empty DM file is passed to
+         * {@code dex2oat}.
+         */
+        public abstract @NonNull String getCompilationReason();
+
+        /**
+         * A human-readable string that describes the location of the dexopt artifacts.
+         *
+         * Note that this string is for debugging purposes only. There is no stability guarantees
+         * for the format of the string. DO NOT use it programmatically.
+         */
+        public abstract @NonNull String getLocationDebugString();
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/OperationProgress.java b/libartservice/service/java/com/android/server/art/model/OperationProgress.java
new file mode 100644
index 0000000..a47a556
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/OperationProgress.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class OperationProgress {
+    /** @hide */
+    protected OperationProgress() {}
+
+    /** @hide */
+    public static @NonNull OperationProgress create(int current, int total) {
+        return new AutoValue_OperationProgress(current, total);
+    }
+
+    /** The overall progress, in the range of [0, 100]. */
+    public int getPercentage() {
+        return 100 * getCurrent() / getTotal();
+    }
+
+    /**
+     * The number of processed items. Can be 0, which means the operation was just started.
+     *
+     * Currently, this is the number of packages, for which dexopt has been done, regardless
+     * of the results (performed, failed, skipped, etc.).
+     *
+     * @hide
+     */
+    public abstract int getCurrent();
+
+    /**
+     * The total number of items. Stays constant during the operation.
+     *
+     * Currently, this is the total number of packages to dexopt.
+     *
+     * @hide
+     */
+    public abstract int getTotal();
+}
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
new file mode 100644
index 0000000..2e3bd5f
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -0,0 +1,1065 @@
+/*
+ * Copyright (C) 2021 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
+ */
+
+package com.android.server.art;
+
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
+
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+import static com.android.server.art.testing.TestingUtils.inAnyOrder;
+import static com.android.server.art.testing.TestingUtils.inAnyOrderDeepEquals;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.apphibernation.AppHibernationManager;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.pm.PackageStateModulesUtils;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.art.testing.TestingUtils;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageUserState;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.Mock;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class ArtManagerLocalTest {
+    private static final String PKG_NAME_1 = "com.example.foo";
+    private static final String PKG_NAME_2 = "com.android.bar";
+    private static final String PKG_NAME_HIBERNATING = "com.example.hibernating";
+    private static final int INACTIVE_DAYS = 1;
+    private static final long CURRENT_TIME_MS = 10000000000l;
+    private static final long RECENT_TIME_MS =
+            CURRENT_TIME_MS - TimeUnit.DAYS.toMillis(INACTIVE_DAYS) + 1;
+    private static final long NOT_RECENT_TIME_MS =
+            CURRENT_TIME_MS - TimeUnit.DAYS.toMillis(INACTIVE_DAYS) - 1;
+
+    @Rule
+    public StaticMockitoRule mockitoRule = new StaticMockitoRule(
+            SystemProperties.class, Constants.class, PackageStateModulesUtils.class);
+
+    @Mock private ArtManagerLocal.Injector mInjector;
+    @Mock private PackageManagerLocal mPackageManagerLocal;
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+    @Mock private IArtd mArtd;
+    @Mock private DexoptHelper mDexoptHelper;
+    @Mock private AppHibernationManager mAppHibernationManager;
+    @Mock private UserManager mUserManager;
+    @Mock private DexUseManagerLocal mDexUseManager;
+    @Mock private StorageManager mStorageManager;
+    private PackageState mPkgState1;
+    private AndroidPackage mPkg1;
+    private List<DetailedSecondaryDexInfo> mSecondaryDexInfo1;
+    private Config mConfig;
+
+    // True if the primary dex'es are in a system partition.
+    @Parameter(0) public boolean mIsInSystemPartition;
+    // True if the primary dex'es are in Incremental FS.
+    @Parameter(1) public boolean mIsInIncrementalFs;
+    // True if the artifacts should be in dalvik-cache.
+    @Parameter(2) public boolean mExpectedIsInDalvikCache;
+
+    private ArtManagerLocal mArtManagerLocal;
+
+    @Parameters(name = "isInReadonlyPartition={0},isInIncrementalFs={1},"
+                    + "expectedIsInDalvikCache={2}")
+    public static Iterable<Object[]>
+    data() {
+        return List.of(new Object[] {true, false, true}, new Object[] {false, true, true},
+                new Object[] {false, false, false});
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mConfig = new Config();
+
+        // Use `lenient()` to suppress `UnnecessaryStubbingException` thrown by the strict stubs.
+        // These are the default test setups. They may or may not be used depending on the code path
+        // that each test case examines.
+        lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal);
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+        lenient().when(mInjector.getDexoptHelper()).thenReturn(mDexoptHelper);
+        lenient().when(mInjector.getConfig()).thenReturn(mConfig);
+        lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAppHibernationManager);
+        lenient().when(mInjector.getUserManager()).thenReturn(mUserManager);
+        lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
+        lenient().when(mInjector.isLauncherPackage(any())).thenReturn(false);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+        lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(CURRENT_TIME_MS);
+        lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager);
+
+        Path tempDir = Files.createTempDirectory("temp");
+        tempDir.toFile().deleteOnExit();
+        lenient().when(mInjector.getTempDir()).thenReturn(tempDir.toString());
+
+        lenient().when(SystemProperties.get(eq("pm.dexopt.install"))).thenReturn("speed-profile");
+        lenient().when(SystemProperties.get(eq("pm.dexopt.bg-dexopt"))).thenReturn("speed-profile");
+        lenient().when(SystemProperties.get(eq("pm.dexopt.first-boot"))).thenReturn("verify");
+        lenient()
+                .when(SystemProperties.get(eq("pm.dexopt.boot-after-mainline-update")))
+                .thenReturn("verify");
+        lenient().when(SystemProperties.get(eq("pm.dexopt.inactive"))).thenReturn("verify");
+        lenient()
+                .when(SystemProperties.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt()))
+                .thenReturn(3);
+        lenient()
+                .when(SystemProperties.getInt(
+                        eq("pm.dexopt.boot-after-mainline-update.concurrency"), anyInt()))
+                .thenReturn(3);
+        lenient()
+                .when(SystemProperties.getInt(eq("pm.dexopt.inactive.concurrency"), anyInt()))
+                .thenReturn(3);
+        lenient()
+                .when(SystemProperties.getInt(
+                        eq("pm.dexopt.downgrade_after_inactive_days"), anyInt()))
+                .thenReturn(INACTIVE_DAYS);
+
+        // No ISA translation.
+        lenient()
+                .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+                .thenReturn("");
+
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+        lenient().when(Constants.isBootImageProfilingEnabled()).thenReturn(true);
+
+        lenient().when(mAppHibernationManager.isHibernatingGlobally(any())).thenReturn(false);
+        lenient().when(mAppHibernationManager.isOatArtifactDeletionEnabled()).thenReturn(true);
+
+        lenient()
+                .when(mUserManager.getUserHandles(anyBoolean()))
+                .thenReturn(List.of(UserHandle.of(0), UserHandle.of(1)));
+
+        // All packages are by default recently used.
+        lenient().when(mDexUseManager.getPackageLastUsedAtMs(any())).thenReturn(RECENT_TIME_MS);
+        mSecondaryDexInfo1 = createSecondaryDexInfo();
+        lenient()
+                .doReturn(mSecondaryDexInfo1)
+                .when(mDexUseManager)
+                .getSecondaryDexInfo(eq(PKG_NAME_1));
+        lenient()
+                .doReturn(mSecondaryDexInfo1)
+                .when(mDexUseManager)
+                .getFilteredDetailedSecondaryDexInfo(eq(PKG_NAME_1));
+
+        simulateStorageNotLow();
+
+        lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot);
+        List<PackageState> pkgStates = createPackageStates();
+        for (PackageState pkgState : pkgStates) {
+            lenient()
+                    .when(mSnapshot.getPackageState(pkgState.getPackageName()))
+                    .thenReturn(pkgState);
+        }
+        var packageStateMap = pkgStates.stream().collect(
+                Collectors.toMap(PackageState::getPackageName, it -> it));
+        lenient().when(mSnapshot.getPackageStates()).thenReturn(packageStateMap);
+        mPkgState1 = mSnapshot.getPackageState(PKG_NAME_1);
+        mPkg1 = mPkgState1.getAndroidPackage();
+
+        lenient().when(mArtd.isIncrementalFsPath(any())).thenReturn(mIsInIncrementalFs);
+
+        // By default, none of the profiles are usable.
+        lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(false);
+        lenient().when(mArtd.copyAndRewriteProfile(any(), any(), any())).thenReturn(false);
+
+        mArtManagerLocal = new ArtManagerLocal(mInjector);
+    }
+
+    @Test
+    public void testdeleteDexoptArtifacts() throws Exception {
+        when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+
+        DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
+        assertThat(result.getFreedBytes()).isEqualTo(5);
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
+
+        // Verify that there are no more calls than the ones above.
+        verify(mArtd, times(5)).deleteArtifacts(any());
+    }
+
+    @Test
+    public void testdeleteDexoptArtifactsTranslatedIsas() throws Exception {
+        lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm64")).thenReturn("x86_64");
+        lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm")).thenReturn("x86");
+        lenient().when(Constants.getPreferredAbi()).thenReturn("x86_64");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("x86_64");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("x86");
+        when(mSecondaryDexInfo1.get(0).abiNames()).thenReturn(Set.of("x86_64"));
+
+        when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+
+        DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
+        assertThat(result.getFreedBytes()).isEqualTo(5);
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "x86_64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "x86", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "x86_64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "x86", mExpectedIsInDalvikCache)));
+        // We assume that the ISA got from `DexUseManagerLocal` is already the translated one.
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/user/0/foo/1.apk", "x86_64", false /* isInDalvikCache */)));
+
+        // Verify that there are no more calls than the ones above.
+        verify(mArtd, times(5)).deleteArtifacts(any());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testdeleteDexoptArtifactsPackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+        mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testdeleteDexoptArtifactsNoPackage() throws Exception {
+        when(mPkgState1.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
+    }
+
+    @Test
+    public void testGetDexoptStatus() throws Exception {
+        doReturn(createGetDexoptStatusResult(
+                         "speed", "compilation-reason-0", "location-debug-string-0"))
+                .when(mArtd)
+                .getDexoptStatus("/data/app/foo/base.apk", "arm64", "PCL[]");
+        doReturn(createGetDexoptStatusResult(
+                         "speed-profile", "compilation-reason-1", "location-debug-string-1"))
+                .when(mArtd)
+                .getDexoptStatus("/data/app/foo/base.apk", "arm", "PCL[]");
+        doReturn(createGetDexoptStatusResult(
+                         "verify", "compilation-reason-2", "location-debug-string-2"))
+                .when(mArtd)
+                .getDexoptStatus("/data/app/foo/split_0.apk", "arm64", "PCL[base.apk]");
+        doReturn(createGetDexoptStatusResult(
+                         "extract", "compilation-reason-3", "location-debug-string-3"))
+                .when(mArtd)
+                .getDexoptStatus("/data/app/foo/split_0.apk", "arm", "PCL[base.apk]");
+        doReturn(createGetDexoptStatusResult("run-from-apk", "unknown", "unknown"))
+                .when(mArtd)
+                .getDexoptStatus("/data/user/0/foo/1.apk", "arm64", "CLC");
+
+        DexoptStatus result = mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1);
+
+        assertThat(result.getDexContainerFileDexoptStatuses())
+                .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptStatus>deepEquality())
+                .containsExactly(
+                        DexContainerFileDexoptStatus.create("/data/app/foo/base.apk",
+                                true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                                "speed", "compilation-reason-0", "location-debug-string-0"),
+                        DexContainerFileDexoptStatus.create("/data/app/foo/base.apk",
+                                true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
+                                "speed-profile", "compilation-reason-1", "location-debug-string-1"),
+                        DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk",
+                                true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                                "verify", "compilation-reason-2", "location-debug-string-2"),
+                        DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk",
+                                true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
+                                "extract", "compilation-reason-3", "location-debug-string-3"),
+                        DexContainerFileDexoptStatus.create("/data/user/0/foo/1.apk",
+                                false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                                "run-from-apk", "unknown", "unknown"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetDexoptStatusPackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+        mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetDexoptStatusNoPackage() throws Exception {
+        when(mPkgState1.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1);
+    }
+
+    @Test
+    public void testGetDexoptStatusNonFatalError() throws Exception {
+        when(mArtd.getDexoptStatus(any(), any(), any()))
+                .thenThrow(new ServiceSpecificException(1 /* errorCode */, "some error message"));
+
+        DexoptStatus result = mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1);
+
+        List<DexContainerFileDexoptStatus> statuses = result.getDexContainerFileDexoptStatuses();
+        assertThat(statuses.size()).isEqualTo(5);
+
+        for (DexContainerFileDexoptStatus status : statuses) {
+            assertThat(status.getCompilerFilter()).isEqualTo("error");
+            assertThat(status.getCompilationReason()).isEqualTo("error");
+            assertThat(status.getLocationDebugString()).isEqualTo("some error message");
+        }
+    }
+
+    @Test
+    public void testClearAppProfiles() throws Exception {
+        mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME_1);
+
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME_1, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME_1, "primary")));
+
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk")));
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testClearAppProfilesPackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+        mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME_1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testClearAppProfilesNoPackage() throws Exception {
+        when(mPkgState1.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME_1);
+    }
+
+    @Test
+    public void testDexoptPackage() throws Exception {
+        var params = new DexoptParams.Builder("install").build();
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        when(mDexoptHelper.dexopt(any(), deepEq(List.of(PKG_NAME_1)), same(params),
+                     same(cancellationSignal), any()))
+                .thenReturn(result);
+
+        assertThat(
+                mArtManagerLocal.dexoptPackage(mSnapshot, PKG_NAME_1, params, cancellationSignal))
+                .isSameInstanceAs(result);
+    }
+
+    @Test
+    public void testResetDexoptStatus() throws Exception {
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        when(mDexoptHelper.dexopt(
+                     any(), deepEq(List.of(PKG_NAME_1)), any(), same(cancellationSignal), any()))
+                .thenReturn(result);
+
+        assertThat(mArtManagerLocal.resetDexoptStatus(mSnapshot, PKG_NAME_1, cancellationSignal))
+                .isSameInstanceAs(result);
+
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME_1, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME_1, "primary")));
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm", mExpectedIsInDalvikCache)));
+
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk")));
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")));
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
+    }
+
+    @Test
+    public void testDexoptPackages() throws Exception {
+        var dexoptResult = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_2)).thenReturn(CURRENT_TIME_MS);
+        simulateStorageLow();
+
+        // It should use the default package list and params. The list is sorted by last active
+        // time in descending order.
+        doReturn(dexoptResult)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_2, PKG_NAME_1)),
+                        argThat(params -> params.getReason().equals("bg-dexopt")),
+                        same(cancellationSignal), any(), any(), any());
+
+        assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                           null /* processCallbackExecutor */, null /* processCallback */))
+                .isSameInstanceAs(dexoptResult);
+
+        // Nothing to downgrade.
+        verify(mDexoptHelper, never())
+                .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+                        any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDexoptPackagesRecentlyInstalled() throws Exception {
+        // The package is recently installed but hasn't been used.
+        PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(0l);
+        simulateStorageLow();
+
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        // PKG_NAME_1 should be dexopted.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2),
+                        argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+                        any(), any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // PKG_NAME_1 should not be downgraded.
+        verify(mDexoptHelper, never())
+                .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+                        any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDexoptPackagesInactive() throws Exception {
+        // PKG_NAME_1 is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS);
+        simulateStorageLow();
+
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        // PKG_NAME_1 should not be dexopted.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_2)),
+                        argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+                        any(), any());
+
+        // PKG_NAME_1 should be downgraded.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_1)),
+                        argThat(params -> params.getReason().equals("inactive")), any(), any(),
+                        any(), any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+    }
+
+    @Test
+    public void testDexoptPackagesInactiveStorageNotLow() throws Exception {
+        // PKG_NAME_1 is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS);
+
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        // PKG_NAME_1 should not be dexopted.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_2)),
+                        argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+                        any(), any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // PKG_NAME_1 should not be downgraded because the storage is not low.
+        verify(mDexoptHelper, never())
+                .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+                        any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDexoptPackagesBootAfterMainlineUpdate() throws Exception {
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        lenient().when(mInjector.isSystemUiPackage(PKG_NAME_1)).thenReturn(true);
+        lenient().when(mInjector.isLauncherPackage(PKG_NAME_2)).thenReturn(true);
+
+        // It should dexopt the system UI and the launcher.
+        when(mDexoptHelper.dexopt(
+                     any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2), any(), any(), any(), any(), any()))
+                .thenReturn(result);
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "boot-after-mainline-update", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+    }
+
+    @Test
+    public void testDexoptPackagesBootAfterMainlineUpdatePackagesNotFound() throws Exception {
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+        // PKG_NAME_1 is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
+        lenient().when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        lenient()
+                .when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1))
+                .thenReturn(NOT_RECENT_TIME_MS);
+        simulateStorageLow();
+
+        // It should dexopt the system UI and the launcher, but they are not found.
+        when(mDexoptHelper.dexopt(any(), deepEq(List.of()), any(), any(), any(), any(), any()))
+                .thenReturn(result);
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "boot-after-mainline-update", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // It should never downgrade apps, even if the storage is low.
+        verify(mDexoptHelper, never())
+                .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+                        any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDexoptPackagesOverride() throws Exception {
+        // PKG_NAME_1 is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS);
+        simulateStorageLow();
+
+        var params = new DexoptParams.Builder("bg-dexopt").build();
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+                (snapshot, reason, defaultPackages, builder, passedSignal) -> {
+                    assertThat(reason).isEqualTo("bg-dexopt");
+                    assertThat(defaultPackages).containsExactly(PKG_NAME_2);
+                    assertThat(passedSignal).isSameInstanceAs(cancellationSignal);
+                    builder.setPackages(List.of(PKG_NAME_1)).setDexoptParams(params);
+                });
+
+        // It should use the overridden package list and params.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_1)), same(params), any(), any(), any(),
+                        any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // It should not downgrade PKG_NAME_1 because it's in the overridden package list. It should
+        // not downgrade PKG_NAME_2 either because it's not an inactive package.
+        verify(mDexoptHelper, never())
+                .dexopt(any(), any(), argThat(params2 -> params2.getReason().equals("inactive")),
+                        any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDexoptPackagesOverrideCleared() throws Exception {
+        var params = new DexoptParams.Builder("bg-dexopt").build();
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+                (snapshot, reason, defaultPackages, builder, passedSignal) -> {
+                    builder.setPackages(List.of(PKG_NAME_1)).setDexoptParams(params);
+                });
+        mArtManagerLocal.clearBatchDexoptStartCallback();
+
+        // It should use the default package list and params.
+        when(mDexoptHelper.dexopt(any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2), not(same(params)),
+                     same(cancellationSignal), any(), any(), any()))
+                .thenReturn(result);
+
+        assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                           null /* processCallbackExecutor */, null /* processCallback */))
+                .isSameInstanceAs(result);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testDexoptPackagesOverrideReasonChanged() throws Exception {
+        var params = new DexoptParams.Builder("first-boot").build();
+        var cancellationSignal = new CancellationSignal();
+
+        mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+                (snapshot, reason, defaultPackages, builder, passedSignal) -> {
+                    builder.setDexoptParams(params);
+                });
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+    }
+
+    @Test
+    public void testSnapshotAppProfile() throws Exception {
+        var options = new MergeProfileOptions();
+        options.forceMerge = true;
+        options.forBootImage = false;
+
+        File tempFile = File.createTempFile("primary", ".prof");
+        tempFile.deleteOnExit();
+
+        ProfilePath refProfile = AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary");
+        String dexPath = "/data/app/foo/base.apk";
+
+        when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(true);
+
+        when(mArtd.mergeProfiles(deepEq(List.of(refProfile,
+                                         AidlUtils.buildProfilePathForPrimaryCur(
+                                                 0 /* userId */, PKG_NAME_1, "primary"),
+                                         AidlUtils.buildProfilePathForPrimaryCur(
+                                                 1 /* userId */, PKG_NAME_1, "primary"))),
+                     isNull(),
+                     deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME_1, "primary",
+                             Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+                     deepEq(List.of(dexPath)), deepEq(options)))
+                .thenAnswer(invocation -> {
+                    try (var writer = new FileWriter(tempFile)) {
+                        writer.write("snapshot");
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                    var output = invocation.<OutputProfile>getArgument(2);
+                    output.profilePath.tmpPath = tempFile.getPath();
+                    return true;
+                });
+
+        ParcelFileDescriptor fd =
+                mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
+
+        verify(mArtd).deleteProfile(
+                argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempFile.getPath())));
+
+        assertThat(fd.getStatSize()).isGreaterThan(0);
+        try (InputStream inputStream = new AutoCloseInputStream(fd)) {
+            String contents = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
+            assertThat(contents).isEqualTo("snapshot");
+        }
+    }
+
+    @Test
+    public void testSnapshotAppProfileFromDm() throws Exception {
+        String tempPathForRef = "/temp/path/for/ref";
+        File tempFileForSnapshot = File.createTempFile("primary", ".prof");
+        tempFileForSnapshot.deleteOnExit();
+
+        ProfilePath refProfile = AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary");
+        String dexPath = "/data/app/foo/base.apk";
+
+        // Simulate that the reference profile doesn't exist.
+        when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(false);
+
+        // The DM file is usable.
+        when(mArtd.copyAndRewriteProfile(
+                     deepEq(AidlUtils.buildProfilePathForDm(dexPath)), any(), eq(dexPath)))
+                .thenAnswer(invocation -> {
+                    var output = invocation.<OutputProfile>getArgument(1);
+                    output.profilePath.tmpPath = tempPathForRef;
+                    return true;
+                });
+
+        // Verify that the reference file initialized from the DM file is used.
+        when(mArtd.mergeProfiles(
+                     argThat(profiles
+                             -> profiles.stream().anyMatch(profile
+                                     -> profile.getTag() == ProfilePath.tmpProfilePath
+                                             && profile.getTmpProfilePath().tmpPath.equals(
+                                                     tempPathForRef))),
+                     isNull(), any(), deepEq(List.of(dexPath)), any()))
+                .thenAnswer(invocation -> {
+                    var output = invocation.<OutputProfile>getArgument(2);
+                    output.profilePath.tmpPath = tempFileForSnapshot.getPath();
+                    return true;
+                });
+
+        ParcelFileDescriptor fd =
+                mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
+
+        verify(mArtd).deleteProfile(argThat(profile
+                -> profile.getTmpProfilePath().tmpPath.equals(tempFileForSnapshot.getPath())));
+        verify(mArtd).deleteProfile(
+                argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempPathForRef)));
+    }
+
+    @Test
+    public void testSnapshotAppProfileSplit() throws Exception {
+        ProfilePath refProfile =
+                AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split");
+        String dexPath = "/data/app/foo/split_0.apk";
+
+        when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(true);
+
+        when(mArtd.mergeProfiles(deepEq(List.of(refProfile,
+                                         AidlUtils.buildProfilePathForPrimaryCur(
+                                                 0 /* userId */, PKG_NAME_1, "split_0.split"),
+                                         AidlUtils.buildProfilePathForPrimaryCur(
+                                                 1 /* userId */, PKG_NAME_1, "split_0.split"))),
+                     isNull(),
+                     deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME_1, "split_0.split",
+                             Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+                     deepEq(List.of(dexPath)), any()))
+                .thenReturn(false);
+
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, "split_0");
+    }
+
+    @Test
+    public void testSnapshotAppProfileEmpty() throws Exception {
+        when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
+
+        ParcelFileDescriptor fd =
+                mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
+
+        verify(mArtd, never()).deleteProfile(any());
+
+        assertThat(fd.getStatSize()).isEqualTo(0);
+        try (InputStream inputStream = new AutoCloseInputStream(fd)) {
+            assertThat(inputStream.readAllBytes()).isEmpty();
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSnapshotAppProfilePackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSnapshotAppProfileNoPackage() throws Exception {
+        when(mPkgState1.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSnapshotAppProfileSplitNotFound() throws Exception {
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, "non-existent-split");
+    }
+
+    @Test
+    public void testDumpAppProfile() throws Exception {
+        var options = new MergeProfileOptions();
+        options.dumpOnly = true;
+
+        when(mArtd.mergeProfiles(any(), isNull(), any(), any(), deepEq(options)))
+                .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
+
+        ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile(
+                mSnapshot, PKG_NAME_1, null /* splitName */, false /* dumpClassesAndMethods */);
+    }
+
+    @Test
+    public void testDumpAppProfileDumpClassesAndMethods() throws Exception {
+        var options = new MergeProfileOptions();
+        options.dumpClassesAndMethods = true;
+
+        when(mArtd.mergeProfiles(any(), isNull(), any(), any(), deepEq(options)))
+                .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
+
+        ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile(
+                mSnapshot, PKG_NAME_1, null /* splitName */, true /* dumpClassesAndMethods */);
+    }
+
+    @Test
+    public void testSnapshotBootImageProfile() throws Exception {
+        // `lenient()` is required to allow mocking the same method multiple times.
+        lenient().when(Constants.getenv("BOOTCLASSPATH")).thenReturn("bcp0:bcp1");
+        lenient().when(Constants.getenv("SYSTEMSERVERCLASSPATH")).thenReturn("sscp0:sscp1");
+        lenient().when(Constants.getenv("STANDALONE_SYSTEMSERVER_JARS")).thenReturn("sssj0:sssj1");
+
+        var options = new MergeProfileOptions();
+        options.forceMerge = true;
+        options.forBootImage = true;
+
+        when(mArtd.mergeProfiles(
+                     inAnyOrderDeepEquals(
+                             AidlUtils.buildProfilePathForPrimaryRef("android", "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, "android", "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, "android", "primary"),
+                             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME_1, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME_1, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME_1, "split_0.split"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME_1, "split_0.split"),
+                             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_2, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME_2, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME_2, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryRef(
+                                     PKG_NAME_HIBERNATING, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME_HIBERNATING, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME_HIBERNATING, "primary")),
+                     isNull(),
+                     deepEq(AidlUtils.buildOutputProfileForPrimary("android", "primary",
+                             Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+                     deepEq(List.of("bcp0", "bcp1", "sscp0", "sscp1", "sssj0", "sssj1")),
+                     deepEq(options)))
+                .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
+
+        mArtManagerLocal.snapshotBootImageProfile(mSnapshot);
+    }
+
+    @Test
+    public void testCleanup() throws Exception {
+        // It should keep all artifacts.
+        doReturn(createGetDexoptStatusResult("speed-profile", "bg-dexopt", "location"))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/base.apk"), eq("arm64"), any());
+        doReturn(createGetDexoptStatusResult("verify", "cmdline", "location"))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any());
+
+        // It should only keep VDEX files.
+        doReturn(createGetDexoptStatusResult("verify", "vdex", "location"))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/split_0.apk"), eq("arm64"), any());
+        doReturn(createGetDexoptStatusResult("verify", "vdex", "location"))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/split_0.apk"), eq("arm"), any());
+
+        // It should not keep any artifacts.
+        doReturn(createGetDexoptStatusResult("run-from-apk", "unknown", "unknown"))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/base.apk"), eq("arm"), any());
+
+        when(mSnapshot.getPackageStates()).thenReturn(Map.of(PKG_NAME_1, mPkgState1));
+        mArtManagerLocal.cleanup(mSnapshot);
+
+        verify(mArtd).cleanup(
+                inAnyOrderDeepEquals(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                0 /* userId */, PKG_NAME_1, "primary"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                1 /* userId */, PKG_NAME_1, "primary"),
+                        AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                0 /* userId */, PKG_NAME_1, "split_0.split"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                1 /* userId */, PKG_NAME_1, "split_0.split"),
+                        AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk"),
+                        AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")),
+                inAnyOrderDeepEquals(AidlUtils.buildArtifactsPath("/data/app/foo/base.apk", "arm64",
+                                             mExpectedIsInDalvikCache),
+                        AidlUtils.buildArtifactsPath(
+                                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)),
+                inAnyOrderDeepEquals(
+                        VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                "/data/app/foo/split_0.apk", "arm64", mExpectedIsInDalvikCache)),
+                        VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                "/data/app/foo/split_0.apk", "arm", mExpectedIsInDalvikCache))));
+    }
+
+    private AndroidPackage createPackage(boolean multiSplit) {
+        AndroidPackage pkg = mock(AndroidPackage.class);
+
+        var baseSplit = mock(AndroidPackageSplit.class);
+        lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk");
+        lenient().when(baseSplit.isHasCode()).thenReturn(true);
+
+        if (multiSplit) {
+            // split_0 has code while split_1 doesn't.
+            var split0 = mock(AndroidPackageSplit.class);
+            lenient().when(split0.getName()).thenReturn("split_0");
+            lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
+            lenient().when(split0.isHasCode()).thenReturn(true);
+            var split1 = mock(AndroidPackageSplit.class);
+            lenient().when(split1.getName()).thenReturn("split_1");
+            lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
+            lenient().when(split1.isHasCode()).thenReturn(false);
+
+            lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0, split1));
+        } else {
+            lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit));
+        }
+
+        return pkg;
+    }
+
+    private PackageUserState createPackageUserState() {
+        PackageUserState pkgUserState = mock(PackageUserState.class);
+        lenient().when(pkgUserState.isInstalled()).thenReturn(true);
+        // All packages are by default pre-installed.
+        lenient().when(pkgUserState.getFirstInstallTimeMillis()).thenReturn(0l);
+        return pkgUserState;
+    }
+
+    private PackageState createPackageState(
+            String packageName, boolean isDexoptable, boolean multiSplit) {
+        PackageState pkgState = mock(PackageState.class);
+
+        lenient().when(pkgState.getPackageName()).thenReturn(packageName);
+        lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
+        lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
+        lenient().when(pkgState.isSystem()).thenReturn(mIsInSystemPartition);
+        lenient().when(pkgState.isUpdatedSystemApp()).thenReturn(false);
+
+        AndroidPackage pkg = createPackage(multiSplit);
+        lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+
+        PackageUserState pkgUserState0 = createPackageUserState();
+        lenient().when(pkgState.getStateForUser(UserHandle.of(0))).thenReturn(pkgUserState0);
+        PackageUserState pkgUserState1 = createPackageUserState();
+        lenient().when(pkgState.getStateForUser(UserHandle.of(1))).thenReturn(pkgUserState1);
+
+        lenient().when(PackageStateModulesUtils.isDexoptable(pkgState)).thenReturn(isDexoptable);
+
+        return pkgState;
+    }
+
+    private List<PackageState> createPackageStates() {
+        PackageState pkgState1 =
+                createPackageState(PKG_NAME_1, true /* isDexoptable */, true /* multiSplit */);
+
+        PackageState pkgState2 =
+                createPackageState(PKG_NAME_2, true /* isDexoptable */, false /* multiSplit */);
+
+        // This should not be dexopted because it's hibernating. However, it should be included
+        // when snapshotting boot image profile.
+        PackageState pkgHibernatingState = createPackageState(
+                PKG_NAME_HIBERNATING, true /* isDexoptable */, false /* multiSplit */);
+        lenient()
+                .when(mAppHibernationManager.isHibernatingGlobally(PKG_NAME_HIBERNATING))
+                .thenReturn(true);
+
+        // This should not be dexopted because it's not dexoptable.
+        PackageState nonDexoptablePkgState = createPackageState(
+                "com.example.non-dexoptable", false /* isDexoptable */, false /* multiSplit */);
+
+        return List.of(pkgState1, pkgState2, pkgHibernatingState, nonDexoptablePkgState);
+    }
+
+    private GetDexoptStatusResult createGetDexoptStatusResult(
+            String compilerFilter, String compilationReason, String locationDebugString) {
+        var getDexoptStatusResult = new GetDexoptStatusResult();
+        getDexoptStatusResult.compilerFilter = compilerFilter;
+        getDexoptStatusResult.compilationReason = compilationReason;
+        getDexoptStatusResult.locationDebugString = locationDebugString;
+        return getDexoptStatusResult;
+    }
+
+    private List<DetailedSecondaryDexInfo> createSecondaryDexInfo() throws Exception {
+        var dexInfo = mock(DetailedSecondaryDexInfo.class);
+        lenient().when(dexInfo.dexPath()).thenReturn("/data/user/0/foo/1.apk");
+        lenient().when(dexInfo.abiNames()).thenReturn(Set.of("arm64-v8a"));
+        lenient().when(dexInfo.classLoaderContext()).thenReturn("CLC");
+        return List.of(dexInfo);
+    }
+
+    private void simulateStorageLow() throws Exception {
+        lenient()
+                .when(mStorageManager.getAllocatableBytes(any()))
+                .thenReturn(ArtManagerLocal.DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES - 1);
+    }
+
+    private void simulateStorageNotLow() throws Exception {
+        lenient()
+                .when(mStorageManager.getAllocatableBytes(any()))
+                .thenReturn(ArtManagerLocal.DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES);
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java b/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java
new file mode 100644
index 0000000..3528caf
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2022 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
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.model.Config.Callback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.os.CancellationSignal;
+import android.os.SystemProperties;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.BackgroundDexoptJob.CompletedResult;
+import com.android.server.art.BackgroundDexoptJob.FatalErrorResult;
+import com.android.server.art.BackgroundDexoptJob.Result;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.PackageManagerLocal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BackgroundDexoptJobTest {
+    private static final long TIMEOUT_SEC = 10;
+
+    @Rule
+    public StaticMockitoRule mockitoRule =
+            new StaticMockitoRule(SystemProperties.class, BackgroundDexoptJobService.class);
+
+    @Mock private BackgroundDexoptJob.Injector mInjector;
+    @Mock private ArtManagerLocal mArtManagerLocal;
+    @Mock private PackageManagerLocal mPackageManagerLocal;
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+    @Mock private JobScheduler mJobScheduler;
+    @Mock private DexoptResult mDexoptResult;
+    @Mock private BackgroundDexoptJobService mJobService;
+    @Mock private JobParameters mJobParameters;
+    private Config mConfig;
+    private BackgroundDexoptJob mBackgroundDexoptJob;
+    private Semaphore mJobFinishedCalled = new Semaphore(0);
+
+    @Before
+    public void setUp() throws Exception {
+        lenient()
+                .when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean()))
+                .thenReturn(false);
+
+        lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot);
+
+        mConfig = new Config();
+
+        lenient().when(mInjector.getArtManagerLocal()).thenReturn(mArtManagerLocal);
+        lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal);
+        lenient().when(mInjector.getConfig()).thenReturn(mConfig);
+        lenient().when(mInjector.getJobScheduler()).thenReturn(mJobScheduler);
+
+        mBackgroundDexoptJob = new BackgroundDexoptJob(mInjector);
+        lenient().when(BackgroundDexoptJobService.getJob()).thenReturn(mBackgroundDexoptJob);
+
+        lenient()
+                .doAnswer(invocation -> {
+                    mJobFinishedCalled.release();
+                    return null;
+                })
+                .when(mJobService)
+                .jobFinished(any(), anyBoolean());
+
+        lenient()
+                .when(mJobParameters.getStopReason())
+                .thenReturn(JobParameters.STOP_REASON_UNDEFINED);
+    }
+
+    @Test
+    public void testStart() {
+        when(mArtManagerLocal.dexoptPackages(
+                     same(mSnapshot), eq(ReasonMapping.REASON_BG_DEXOPT), any(), any(), any()))
+                .thenReturn(mDexoptResult);
+
+        Result result = Utils.getFuture(mBackgroundDexoptJob.start());
+        assertThat(result).isInstanceOf(CompletedResult.class);
+        assertThat(((CompletedResult) result).dexoptResult()).isSameInstanceAs(mDexoptResult);
+
+        verify(mArtManagerLocal).cleanup(same(mSnapshot));
+    }
+
+    @Test
+    public void testStartAlreadyRunning() {
+        Semaphore dexoptDone = new Semaphore(0);
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenAnswer(invocation -> {
+                    assertThat(dexoptDone.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+                    return mDexoptResult;
+                });
+
+        Future<Result> future1 = mBackgroundDexoptJob.start();
+        Future<Result> future2 = mBackgroundDexoptJob.start();
+        assertThat(future1).isSameInstanceAs(future2);
+
+        dexoptDone.release();
+        Utils.getFuture(future1);
+
+        verify(mArtManagerLocal, times(1)).dexoptPackages(any(), any(), any(), any(), any());
+    }
+
+    @Test
+    public void testStartAnother() {
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenReturn(mDexoptResult);
+
+        Future<Result> future1 = mBackgroundDexoptJob.start();
+        Utils.getFuture(future1);
+        Future<Result> future2 = mBackgroundDexoptJob.start();
+        Utils.getFuture(future2);
+        assertThat(future1).isNotSameInstanceAs(future2);
+    }
+
+    @Test
+    public void testStartFatalError() {
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenThrow(IllegalStateException.class);
+
+        Result result = Utils.getFuture(mBackgroundDexoptJob.start());
+        assertThat(result).isInstanceOf(FatalErrorResult.class);
+    }
+
+    @Test
+    public void testStartIgnoreDisabled() {
+        lenient()
+                .when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean()))
+                .thenReturn(true);
+
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenReturn(mDexoptResult);
+
+        // The `start` method should ignore the system property. The system property is for
+        // `schedule`.
+        Utils.getFuture(mBackgroundDexoptJob.start());
+    }
+
+    @Test
+    public void testCancel() {
+        Semaphore dexoptCancelled = new Semaphore(0);
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenAnswer(invocation -> {
+                    assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+                    var cancellationSignal = invocation.<CancellationSignal>getArgument(2);
+                    assertThat(cancellationSignal.isCanceled()).isTrue();
+                    return mDexoptResult;
+                });
+
+        Future<Result> future = mBackgroundDexoptJob.start();
+        mBackgroundDexoptJob.cancel();
+        dexoptCancelled.release();
+        Utils.getFuture(future);
+    }
+
+    @Test
+    public void testSchedule() {
+        var captor = ArgumentCaptor.forClass(JobInfo.class);
+        when(mJobScheduler.schedule(captor.capture())).thenReturn(JobScheduler.RESULT_SUCCESS);
+
+        assertThat(mBackgroundDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS);
+
+        JobInfo jobInfo = captor.getValue();
+        assertThat(jobInfo.getIntervalMillis()).isEqualTo(BackgroundDexoptJob.JOB_INTERVAL_MS);
+        assertThat(jobInfo.isRequireDeviceIdle()).isTrue();
+        assertThat(jobInfo.isRequireCharging()).isTrue();
+        assertThat(jobInfo.isRequireBatteryNotLow()).isTrue();
+        assertThat(jobInfo.isRequireStorageNotLow()).isFalse();
+    }
+
+    @Test
+    public void testScheduleDisabled() {
+        when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean()))
+                .thenReturn(true);
+
+        assertThat(mBackgroundDexoptJob.schedule())
+                .isEqualTo(ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP);
+
+        verify(mJobScheduler, never()).schedule(any());
+    }
+
+    @Test
+    public void testScheduleOverride() {
+        mConfig.setScheduleBackgroundDexoptJobCallback(Runnable::run, builder -> {
+            builder.setRequiresBatteryNotLow(false);
+            builder.setPriority(JobInfo.PRIORITY_LOW);
+        });
+
+        var captor = ArgumentCaptor.forClass(JobInfo.class);
+        when(mJobScheduler.schedule(captor.capture())).thenReturn(JobScheduler.RESULT_SUCCESS);
+
+        assertThat(mBackgroundDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS);
+
+        JobInfo jobInfo = captor.getValue();
+        assertThat(jobInfo.getIntervalMillis()).isEqualTo(BackgroundDexoptJob.JOB_INTERVAL_MS);
+        assertThat(jobInfo.isRequireDeviceIdle()).isTrue();
+        assertThat(jobInfo.isRequireCharging()).isTrue();
+        assertThat(jobInfo.isRequireBatteryNotLow()).isFalse();
+        assertThat(jobInfo.getPriority()).isEqualTo(JobInfo.PRIORITY_LOW);
+    }
+
+    @Test
+    public void testScheduleOverrideCleared() {
+        mConfig.setScheduleBackgroundDexoptJobCallback(
+                Runnable::run, builder -> { builder.setRequiresBatteryNotLow(false); });
+        mConfig.clearScheduleBackgroundDexoptJobCallback();
+
+        var captor = ArgumentCaptor.forClass(JobInfo.class);
+        when(mJobScheduler.schedule(captor.capture())).thenReturn(JobScheduler.RESULT_SUCCESS);
+
+        assertThat(mBackgroundDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS);
+
+        JobInfo jobInfo = captor.getValue();
+        assertThat(jobInfo.isRequireBatteryNotLow()).isTrue();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testScheduleOverrideStorageNotLow() {
+        mConfig.setScheduleBackgroundDexoptJobCallback(
+                Runnable::run, builder -> { builder.setRequiresStorageNotLow(true); });
+
+        mBackgroundDexoptJob.schedule();
+    }
+
+    @Test
+    public void testUnschedule() {
+        mBackgroundDexoptJob.unschedule();
+        verify(mJobScheduler).cancel(anyInt());
+    }
+
+    @Test
+    public void testWantsRescheduleFalsePerformed() throws Exception {
+        when(mDexoptResult.getFinalStatus()).thenReturn(DexoptResult.DEXOPT_PERFORMED);
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenReturn(mDexoptResult);
+
+        mBackgroundDexoptJob.onStartJob(mJobService, mJobParameters);
+        assertThat(mJobFinishedCalled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+        verify(mJobService).jobFinished(any(), eq(false) /* wantsReschedule */);
+    }
+
+    @Test
+    public void testWantsRescheduleFalseFatalError() throws Exception {
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenThrow(RuntimeException.class);
+
+        mBackgroundDexoptJob.onStartJob(mJobService, mJobParameters);
+        assertThat(mJobFinishedCalled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+        verify(mJobService).jobFinished(any(), eq(false) /* wantsReschedule */);
+    }
+
+    @Test
+    public void testWantsRescheduleTrue() throws Exception {
+        when(mDexoptResult.getFinalStatus()).thenReturn(DexoptResult.DEXOPT_CANCELLED);
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenReturn(mDexoptResult);
+
+        mBackgroundDexoptJob.onStartJob(mJobService, mJobParameters);
+        assertThat(mJobFinishedCalled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+        verify(mJobService).jobFinished(any(), eq(true) /* wantsReschedule */);
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/DebouncerTest.java b/libartservice/service/javatests/com/android/server/art/DebouncerTest.java
new file mode 100644
index 0000000..bf0bc70
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DebouncerTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.lenient;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.art.testing.MockClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class DebouncerTest {
+    private MockClock mMockClock;
+    private Debouncer mDebouncer;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockClock = new MockClock();
+        mDebouncer =
+                new Debouncer(100 /* intervalMs */, () -> mMockClock.createScheduledExecutor());
+    }
+
+    @Test
+    public void test() throws Exception {
+        List<Integer> list = new ArrayList<>();
+
+        mDebouncer.maybeRunAsync(() -> list.add(1));
+        mDebouncer.maybeRunAsync(() -> list.add(2));
+        mMockClock.advanceTime(100);
+        mDebouncer.maybeRunAsync(() -> list.add(3));
+        mMockClock.advanceTime(99);
+        mDebouncer.maybeRunAsync(() -> list.add(4));
+        mMockClock.advanceTime(99);
+        mDebouncer.maybeRunAsync(() -> list.add(5));
+        mMockClock.advanceTime(1000);
+
+        assertThat(list).containsExactly(2, 5).inOrder();
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
new file mode 100644
index 0000000..5850e61
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
@@ -0,0 +1,768 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+import static com.android.server.art.DexUseManagerLocal.DexLoader;
+import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.DexContainerFileUseInfo;
+import com.android.server.art.proto.DexUseProto;
+import com.android.server.art.testing.MockClock;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.PathClassLoader;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DexUseManagerTest {
+    private static final String LOADING_PKG_NAME = "com.example.loadingpackage";
+    private static final String OWNING_PKG_NAME = "com.example.owningpackage";
+    private static final String BASE_APK = "/data/app/" + OWNING_PKG_NAME + "/base.apk";
+    private static final String SPLIT_APK = "/data/app/" + OWNING_PKG_NAME + "/split_0.apk";
+
+    @Rule
+    public StaticMockitoRule mockitoRule =
+            new StaticMockitoRule(SystemProperties.class, Constants.class, Process.class);
+
+    private final UserHandle mUserHandle = Binder.getCallingUserHandle();
+
+    /**
+     * The default value of `isDexFilePublic` returned by `getSecondaryDexInfo`. The value doesn't
+     * matter because it's undefined, but it's needed for deep equality check, to make the test
+     * simpler.
+     */
+    private final boolean mDefaultIsDexFilePublic = true;
+
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+    @Mock private DexUseManagerLocal.Injector mInjector;
+    @Mock private IArtd mArtd;
+    @Mock private Context mContext;
+    private DexUseManagerLocal mDexUseManager;
+    private String mCeDir;
+    private String mDeDir;
+    private MockClock mMockClock;
+    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+    private File mTempFile;
+    private Map<String, PackageState> mPackageStates;
+
+    @Before
+    public void setUp() throws Exception {
+        // No ISA translation.
+        lenient()
+                .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+                .thenReturn("");
+
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+        lenient().when(Process.isIsolatedUid(anyInt())).thenReturn(false);
+
+        mPackageStates = new HashMap<>();
+
+        PackageState loadingPkgState = createPackageState(LOADING_PKG_NAME, "armeabi-v7a");
+        addPackage(LOADING_PKG_NAME, loadingPkgState);
+        PackageState owningPkgState = createPackageState(OWNING_PKG_NAME, "arm64-v8a");
+        addPackage(OWNING_PKG_NAME, owningPkgState);
+        PackageState platformPkgState =
+                createPackageState(Utils.PLATFORM_PACKAGE_NAME, "arm64-v8a");
+        addPackage(Utils.PLATFORM_PACKAGE_NAME, platformPkgState);
+
+        lenient().when(mSnapshot.getPackageStates()).thenReturn(mPackageStates);
+
+        mBroadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+        lenient()
+                .when(mContext.registerReceiver(mBroadcastReceiverCaptor.capture(), any()))
+                .thenReturn(mock(Intent.class));
+
+        mCeDir = Environment
+                         .getDataCePackageDirectoryForUser(StorageManager.UUID_DEFAULT,
+                                 Binder.getCallingUserHandle(), OWNING_PKG_NAME)
+                         .toString();
+        mDeDir = Environment
+                         .getDataDePackageDirectoryForUser(StorageManager.UUID_DEFAULT,
+                                 Binder.getCallingUserHandle(), OWNING_PKG_NAME)
+                         .toString();
+        mMockClock = new MockClock();
+
+        mTempFile = File.createTempFile("package-dex-usage", ".pb");
+        mTempFile.deleteOnExit();
+
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+        lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(0l);
+        lenient().when(mInjector.getFilename()).thenReturn(mTempFile.getPath());
+        lenient()
+                .when(mInjector.createScheduledExecutor())
+                .thenAnswer(invocation -> mMockClock.createScheduledExecutor());
+        lenient().when(mInjector.getContext()).thenReturn(mContext);
+        lenient().when(mInjector.getAllPackageNames()).thenReturn(mPackageStates.keySet());
+
+        mDexUseManager = new DexUseManagerLocal(mInjector);
+        mDexUseManager.systemReady();
+    }
+
+    @Test
+    public void testPrimaryDexOwned() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK))
+                .containsExactly(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */));
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isFalse();
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)).isEmpty();
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK))
+                .isFalse();
+    }
+
+    @Test
+    public void testPrimaryDexOwnedIsolated() {
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK))
+                .containsExactly(DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */));
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isTrue();
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)).isEmpty();
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK))
+                .isFalse();
+    }
+
+    @Test
+    public void testPrimaryDexOwnedSplitIsolated() {
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(SPLIT_APK, "CLC"));
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK)).isEmpty();
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isFalse();
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK))
+                .containsExactly(DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */));
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK)).isTrue();
+    }
+
+    @Test
+    public void testPrimaryDexOthers() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK))
+                .containsExactly(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */));
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isTrue();
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)).isEmpty();
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK))
+                .isFalse();
+    }
+
+    /** Checks that it ignores and dedups things correctly. */
+    @Test
+    public void testPrimaryDexMultipleEntries() throws Exception {
+        verifyPrimaryDexMultipleEntries(
+                false /* saveAndLoad */, false /* shutdown */, false /* cleanup */);
+    }
+
+    /** Checks that it saves data after some time has passed and loads data correctly. */
+    @Test
+    public void testPrimaryDexMultipleEntriesPersisted() throws Exception {
+        verifyPrimaryDexMultipleEntries(
+                true /*saveAndLoad */, false /* shutdown */, false /* cleanup */);
+    }
+
+    /** Checks that it saves data when the device is being shutdown and loads data correctly. */
+    @Test
+    public void testPrimaryDexMultipleEntriesPersistedDueToShutdown() throws Exception {
+        verifyPrimaryDexMultipleEntries(
+                true /*saveAndLoad */, true /* shutdown */, false /* cleanup */);
+    }
+
+    /** Checks that it doesn't accidentally cleanup any entry that is needed. */
+    @Test
+    public void testPrimaryDexMultipleEntriesSurviveCleanup() throws Exception {
+        verifyPrimaryDexMultipleEntries(
+                false /*saveAndLoad */, false /* shutdown */, true /* cleanup */);
+    }
+
+    private void verifyPrimaryDexMultipleEntries(
+            boolean saveAndLoad, boolean shutdown, boolean cleanup) throws Exception {
+        when(mInjector.getCurrentTimeMillis()).thenReturn(1000l);
+
+        lenient()
+                .when(mArtd.getDexFileVisibility(BASE_APK))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        lenient()
+                .when(mArtd.getDexFileVisibility(SPLIT_APK))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        // These should be ignored.
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, Utils.PLATFORM_PACKAGE_NAME, Map.of(BASE_APK, "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+                Map.of("/data/app/" + OWNING_PKG_NAME + "/non-existing.apk", "CLC"));
+
+        // Some of these should be deduped.
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC", SPLIT_APK, "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC", SPLIT_APK, "CLC"));
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+        when(mInjector.getCurrentTimeMillis()).thenReturn(2000l);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+        if (saveAndLoad) {
+            if (shutdown) {
+                mBroadcastReceiverCaptor.getValue().onReceive(mContext, mock(Intent.class));
+            } else {
+                // MockClock runs tasks synchronously.
+                mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS);
+            }
+            mDexUseManager = new DexUseManagerLocal(mInjector);
+        }
+
+        if (cleanup) {
+            // Nothing should be cleaned up.
+            mDexUseManager.cleanup();
+        }
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK))
+                .containsExactly(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */),
+                        DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */),
+                        DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */));
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK))
+                .containsExactly(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */));
+
+        assertThat(mDexUseManager.getPackageLastUsedAtMs(OWNING_PKG_NAME)).isEqualTo(2000l);
+    }
+
+    @Test
+    public void testSecondaryDexOwned() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("arm64-v8a"),
+                        Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)),
+                        false /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+        assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC");
+    }
+
+    @Test
+    public void testSecondaryDexOwnedIsolated() {
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mDeDir + "/foo.apk", "CLC"));
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mDeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("arm64-v8a"),
+                        Set.of(DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+        assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC");
+    }
+
+    @Test
+    public void testSecondaryDexOthers() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("armeabi-v7a"),
+                        Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+        assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC");
+    }
+
+    @Test
+    public void testSecondaryDexUnsupportedClc() {
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME,
+                Map.of(mCeDir + "/foo.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT));
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT, Set.of("armeabi-v7a"),
+                        Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+        assertThat(dexInfoList.get(0).classLoaderContext()).isNull();
+    }
+
+    @Test
+    public void testSecondaryDexVariableClc() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC2"));
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS,
+                        Set.of("arm64-v8a", "armeabi-v7a"),
+                        Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */),
+                                DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+        assertThat(dexInfoList.get(0).classLoaderContext()).isNull();
+    }
+
+    /** Checks that it ignores and dedups things correctly. */
+    @Test
+    public void testSecondaryDexMultipleEntries() throws Exception {
+        verifySecondaryDexMultipleEntries(
+                false /*saveAndLoad */, false /* shutdown */, false /* cleanup */);
+    }
+
+    /** Checks that it saves data after some time has passed and loads data correctly. */
+    @Test
+    public void testSecondaryDexMultipleEntriesPersisted() throws Exception {
+        verifySecondaryDexMultipleEntries(
+                true /*saveAndLoad */, false /* shutdown */, false /* cleanup */);
+    }
+
+    /** Checks that it saves data when the device is being shutdown and loads data correctly. */
+    @Test
+    public void testSecondaryDexMultipleEntriesPersistedDueToShutdown() throws Exception {
+        verifySecondaryDexMultipleEntries(
+                true /*saveAndLoad */, true /* shutdown */, false /* cleanup */);
+    }
+
+    /** Checks that it doesn't accidentally cleanup any entry that is needed. */
+    @Test
+    public void testSecondaryDexMultipleEntriesSurviveCleanup() throws Exception {
+        verifySecondaryDexMultipleEntries(
+                false /*saveAndLoad */, false /* shutdown */, true /* cleanup */);
+    }
+
+    private void verifySecondaryDexMultipleEntries(
+            boolean saveAndLoad, boolean shutdown, boolean cleanup) throws Exception {
+        when(mInjector.getCurrentTimeMillis()).thenReturn(1000l);
+
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/bar.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/baz.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        // These should be ignored.
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, Utils.PLATFORM_PACKAGE_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of("/some/non-existing.apk", "CLC"));
+
+        // Some of these should be deduped.
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+                Map.of(mCeDir + "/foo.apk", "CLC", mCeDir + "/bar.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+                Map.of(mCeDir + "/foo.apk", "UpdatedCLC", mCeDir + "/bar.apk", "UpdatedCLC"));
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "UpdatedCLC"));
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/bar.apk", "DifferentCLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/bar.apk", "UpdatedDifferentCLC"));
+
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+                Map.of(mCeDir + "/baz.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT));
+
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        when(mInjector.getCurrentTimeMillis()).thenReturn(2000l);
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+                Map.of(mCeDir + "/foo.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT));
+
+        if (saveAndLoad) {
+            if (shutdown) {
+                mBroadcastReceiverCaptor.getValue().onReceive(mContext, mock(Intent.class));
+            } else {
+                // MockClock runs tasks synchronously.
+                mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS);
+            }
+            mDexUseManager = new DexUseManagerLocal(mInjector);
+        }
+
+        if (cleanup) {
+            // Nothing should be cleaned up.
+            mDexUseManager.cleanup();
+        }
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                                         "UpdatedCLC", Set.of("arm64-v8a", "armeabi-v7a"),
+                                         Set.of(DexLoader.create(OWNING_PKG_NAME,
+                                                        false /* isolatedProcess */),
+                                                 DexLoader.create(OWNING_PKG_NAME,
+                                                         true /* isolatedProcess */),
+                                                 DexLoader.create(LOADING_PKG_NAME,
+                                                         false /* isolatedProcess */)),
+                                         true /* isUsedByOtherApps */, mDefaultIsDexFilePublic),
+                        DetailedSecondaryDexInfo.create(mCeDir + "/bar.apk", mUserHandle,
+                                SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS,
+                                Set.of("arm64-v8a", "armeabi-v7a"),
+                                Set.of(DexLoader.create(
+                                               OWNING_PKG_NAME, false /* isolatedProcess */),
+                                        DexLoader.create(
+                                                LOADING_PKG_NAME, false /* isolatedProcess */)),
+                                true /* isUsedByOtherApps */, mDefaultIsDexFilePublic),
+                        DetailedSecondaryDexInfo.create(mCeDir + "/baz.apk", mUserHandle,
+                                SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT,
+                                Set.of("arm64-v8a"),
+                                Set.of(DexLoader.create(
+                                        OWNING_PKG_NAME, false /* isolatedProcess */)),
+                                false /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+
+        assertThat(mDexUseManager.getSecondaryDexContainerFileUseInfo(OWNING_PKG_NAME))
+                .containsExactly(DexContainerFileUseInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                                         Set.of(OWNING_PKG_NAME, LOADING_PKG_NAME)),
+                        DexContainerFileUseInfo.create(mCeDir + "/bar.apk", mUserHandle,
+                                Set.of(OWNING_PKG_NAME, LOADING_PKG_NAME)),
+                        DexContainerFileUseInfo.create(
+                                mCeDir + "/baz.apk", mUserHandle, Set.of(OWNING_PKG_NAME)));
+
+        assertThat(mDexUseManager.getPackageLastUsedAtMs(OWNING_PKG_NAME)).isEqualTo(2000l);
+    }
+
+    @Test
+    public void testFilteredDetailedSecondaryDexPublic() throws Exception {
+        when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME))
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("arm64-v8a", "armeabi-v7a"),
+                        Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */),
+                                DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, true /* isDexFilePublic */));
+    }
+
+    @Test
+    public void testFilteredDetailedSecondaryDexPrivate() throws Exception {
+        when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME))
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("arm64-v8a"),
+                        Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)),
+                        false /* isUsedByOtherApps */, false /* isDexFilePublic */));
+    }
+
+    @Test
+    public void testFilteredDetailedSecondaryDexFilteredDueToVisibility() throws Exception {
+        when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)).isEmpty();
+    }
+
+    @Test
+    public void testFilteredDetailedSecondaryDexFilteredDueToNotFound() throws Exception {
+        when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")).thenReturn(FileVisibility.NOT_FOUND);
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)).isEmpty();
+    }
+
+    @Test
+    public void testCleanup() throws Exception {
+        PackageState pkgState = createPackageState("com.example.deletedpackage", "arm64-v8a");
+        addPackage("com.example.deletedpackage", pkgState);
+        lenient()
+                .when(mArtd.getDexFileVisibility("/data/app/com.example.deletedpackage/base.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        lenient()
+                .when(mArtd.getDexFileVisibility(BASE_APK))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        // Simulate that a package loads its own dex file and another package's dex file.
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, "com.example.deletedpackage",
+                Map.of("/data/app/com.example.deletedpackage/base.apk", "CLC", BASE_APK, "CLC"));
+        // Simulate that another package loads this package's dex file.
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME,
+                Map.of("/data/app/com.example.deletedpackage/base.apk", "CLC"));
+        // Simulate that the package is then deleted.
+        removePackage("com.example.deletedpackage");
+
+        // Simulate that a primary dex file is loaded and then deleted.
+        lenient()
+                .when(mArtd.getDexFileVisibility(SPLIT_APK))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(SPLIT_APK, "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(SPLIT_APK, "CLC"));
+        lenient().when(mArtd.getDexFileVisibility(SPLIT_APK)).thenReturn(FileVisibility.NOT_FOUND);
+
+        // Simulate that a secondary dex file is loaded and then deleted.
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.NOT_FOUND);
+
+        // Create an entry that should be kept.
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/bar.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/bar.apk", "CLC"));
+
+        // Simulate that a secondary dex file is loaded by another package and then made private.
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/baz.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/baz.apk", "CLC"));
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/baz.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        // Simulate that all the files of a package are deleted. The whole container entry of the
+        // package should be cleaned up, though the package still exists.
+        lenient()
+                .when(mArtd.getDexFileVisibility("/data/app/" + LOADING_PKG_NAME + "/base.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME,
+                Map.of("/data/app/" + LOADING_PKG_NAME + "/base.apk", "CLC"));
+        lenient()
+                .when(mArtd.getDexFileVisibility("/data/app/" + LOADING_PKG_NAME + "/base.apk"))
+                .thenReturn(FileVisibility.NOT_FOUND);
+
+        // Run cleanup.
+        mDexUseManager.cleanup();
+
+        // Save.
+        mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS);
+
+        // Check that the entries are removed from the proto. Normally, we should check the return
+        // values of the public get methods instead of checking the raw proto. However, here we want
+        // to make sure that the container entries are cleaned up when they are empty so that they
+        // don't cost extra memory or storage.
+        // Note that every repeated field must not contain more than one entry, to keep the
+        // textproto deterministic.
+        DexUseProto proto;
+        try (InputStream in = new FileInputStream(mTempFile.getPath())) {
+            proto = DexUseProto.parseFrom(in);
+        }
+        String textproto = proto.toString();
+        // Remove the first line, which is an auto-generated comment.
+        textproto = textproto.substring(textproto.indexOf('\n') + 1).trim();
+        assertThat(textproto).isEqualTo("package_dex_use {\n"
+                + "  owning_package_name: \"com.example.owningpackage\"\n"
+                + "  secondary_dex_use {\n"
+                + "    dex_file: \"/data/user/0/com.example.owningpackage/bar.apk\"\n"
+                + "    record {\n"
+                + "      abi_name: \"arm64-v8a\"\n"
+                + "      class_loader_context: \"CLC\"\n"
+                + "      last_used_at_ms: 0\n"
+                + "      loading_package_name: \"com.example.owningpackage\"\n"
+                + "    }\n"
+                + "    user_id {\n"
+                + "    }\n"
+                + "  }\n"
+                + "}");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testUnknownPackage() {
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, "bogus", Map.of(BASE_APK, "CLC"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testEmptyMap() {
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, Map.of());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNullKey() {
+        var map = new HashMap<String, String>();
+        map.put(null, "CLC");
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, map);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNonAbsoluteKey() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of("a/b.jar", "CLC"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNullValue() {
+        var map = new HashMap<String, String>();
+        map.put(mCeDir + "/foo.apk", null);
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, map);
+    }
+
+    @Test
+    public void testFileNotFound() {
+        // It should fail to load the file.
+        when(mInjector.getFilename()).thenReturn("/nonexisting/file");
+        mDexUseManager = new DexUseManagerLocal(mInjector);
+
+        // Add some arbitrary data to see if it works fine after a failed load.
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        assertThat(mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME))
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("armeabi-v7a"),
+                        Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+    }
+
+    private AndroidPackage createPackage(String packageName) {
+        AndroidPackage pkg = mock(AndroidPackage.class);
+        lenient().when(pkg.getStorageUuid()).thenReturn(StorageManager.UUID_DEFAULT);
+
+        var baseSplit = mock(AndroidPackageSplit.class);
+        lenient().when(baseSplit.getPath()).thenReturn("/data/app/" + packageName + "/base.apk");
+        lenient().when(baseSplit.isHasCode()).thenReturn(true);
+        lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+
+        var split0 = mock(AndroidPackageSplit.class);
+        lenient().when(split0.getName()).thenReturn("split_0");
+        lenient().when(split0.getPath()).thenReturn("/data/app/" + packageName + "/split_0.apk");
+        lenient().when(split0.isHasCode()).thenReturn(true);
+
+        lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0));
+
+        return pkg;
+    }
+
+    private PackageState createPackageState(String packageName, String primaryAbi) {
+        PackageState pkgState = mock(PackageState.class);
+        lenient().when(pkgState.getPackageName()).thenReturn(packageName);
+        AndroidPackage pkg = createPackage(packageName);
+        lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+        lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn(primaryAbi);
+        return pkgState;
+    }
+
+    private void addPackage(String packageName, PackageState pkgState) {
+        lenient().when(mSnapshot.getPackageState(packageName)).thenReturn(pkgState);
+        mPackageStates.put(packageName, pkgState);
+    }
+
+    private void removePackage(String packageName) {
+        lenient().when(mSnapshot.getPackageState(packageName)).thenReturn(null);
+        mPackageStates.remove(packageName);
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
new file mode 100644
index 0000000..f08035d
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
@@ -0,0 +1,859 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.ArtManagerLocal.DexoptDoneCallback;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.model.DexoptResult.DexoptResultStatus;
+import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.apphibernation.AppHibernationManager;
+import android.os.CancellationSignal;
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.modules.utils.pm.PackageStateModulesUtils;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.OperationProgress;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedLibrary;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DexoptHelperTest {
+    private static final String PKG_NAME_FOO = "com.example.foo";
+    private static final String PKG_NAME_BAR = "com.example.bar";
+    private static final String PKG_NAME_LIB1 = "com.example.lib1";
+    private static final String PKG_NAME_LIB2 = "com.example.lib2";
+    private static final String PKG_NAME_LIB3 = "com.example.lib3";
+    private static final String PKG_NAME_LIB4 = "com.example.lib4";
+    private static final String PKG_NAME_LIBBAZ = "com.example.libbaz";
+
+    @Rule
+    public StaticMockitoRule mockitoRule = new StaticMockitoRule(PackageStateModulesUtils.class);
+
+    @Mock private DexoptHelper.Injector mInjector;
+    @Mock private PrimaryDexopter mPrimaryDexopter;
+    @Mock private SecondaryDexopter mSecondaryDexopter;
+    @Mock private AppHibernationManager mAhm;
+    @Mock private PowerManager mPowerManager;
+    @Mock private PowerManager.WakeLock mWakeLock;
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+    private PackageState mPkgStateFoo;
+    private PackageState mPkgStateBar;
+    private PackageState mPkgStateLib1;
+    private PackageState mPkgStateLib2;
+    private PackageState mPkgStateLib4;
+    private PackageState mPkgStateLibbaz;
+    private AndroidPackage mPkgFoo;
+    private AndroidPackage mPkgBar;
+    private AndroidPackage mPkgLib1;
+    private AndroidPackage mPkgLib2;
+    private AndroidPackage mPkgLib4;
+    private AndroidPackage mPkgLibbaz;
+    private CancellationSignal mCancellationSignal;
+    private ExecutorService mExecutor;
+    private List<DexContainerFileDexoptResult> mPrimaryResults;
+    private List<DexContainerFileDexoptResult> mSecondaryResults;
+    private Config mConfig;
+    private DexoptParams mParams;
+    private List<String> mRequestedPackages;
+    private DexoptHelper mDexoptHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        lenient()
+                .when(mPowerManager.newWakeLock(eq(PowerManager.PARTIAL_WAKE_LOCK), any()))
+                .thenReturn(mWakeLock);
+
+        lenient().when(mAhm.isHibernatingGlobally(any())).thenReturn(false);
+        lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(true);
+
+        mCancellationSignal = new CancellationSignal();
+        mExecutor = Executors.newSingleThreadExecutor();
+        mConfig = new Config();
+
+        preparePackagesAndLibraries();
+
+        mPrimaryResults =
+                createResults("/data/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */,
+                        DexoptResult.DEXOPT_PERFORMED /* status2 */);
+        mSecondaryResults = createResults("/data/user_de/0/foo/foo.apk",
+                DexoptResult.DEXOPT_PERFORMED /* status1 */,
+                DexoptResult.DEXOPT_PERFORMED /* status2 */);
+
+        lenient()
+                .when(mInjector.getPrimaryDexopter(any(), any(), any(), any()))
+                .thenReturn(mPrimaryDexopter);
+        lenient().when(mPrimaryDexopter.dexopt()).thenReturn(mPrimaryResults);
+
+        lenient()
+                .when(mInjector.getSecondaryDexopter(any(), any(), any(), any()))
+                .thenReturn(mSecondaryDexopter);
+        lenient().when(mSecondaryDexopter.dexopt()).thenReturn(mSecondaryResults);
+
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAhm);
+        lenient().when(mInjector.getPowerManager()).thenReturn(mPowerManager);
+        lenient().when(mInjector.getConfig()).thenReturn(mConfig);
+
+        mDexoptHelper = new DexoptHelper(mInjector);
+    }
+
+    @After
+    public void tearDown() {
+        mExecutor.shutdown();
+    }
+
+    @Test
+    public void testDexopt() throws Exception {
+        // Only package libbaz fails.
+        var failingPrimaryDexopter = mock(PrimaryDexopter.class);
+        List<DexContainerFileDexoptResult> partialFailureResults =
+                createResults("/data/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */,
+                        DexoptResult.DEXOPT_FAILED /* status2 */);
+        lenient().when(failingPrimaryDexopter.dexopt()).thenReturn(partialFailureResults);
+        when(mInjector.getPrimaryDexopter(same(mPkgStateLibbaz), any(), any(), any()))
+                .thenReturn(failingPrimaryDexopter);
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getRequestedCompilerFilter()).isEqualTo("speed-profile");
+        assertThat(result.getReason()).isEqualTo("install");
+        assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_FAILED);
+
+        // The requested packages must come first.
+        assertThat(result.getPackageDexoptResults()).hasSize(6);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_FAILED,
+                List.of(partialFailureResults, mSecondaryResults));
+        checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+
+        // The order matters. It should acquire the wake lock only once, at the beginning, and
+        // release the wake lock at the end. When running in a single thread, it should dexopt
+        // primary dex files and the secondary dex files together for each package, and it should
+        // dexopt requested packages, in the given order, and then dexopt dependencies.
+        InOrder inOrder = inOrder(mInjector, mWakeLock);
+        inOrder.verify(mWakeLock).setWorkSource(any());
+        inOrder.verify(mWakeLock).acquire(anyLong());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateFoo), same(mPkgFoo), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateFoo), same(mPkgFoo), same(mParams), any());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateBar), same(mPkgBar), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateBar), same(mPkgBar), same(mParams), any());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), any());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLib1), same(mPkgLib1), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLib1), same(mPkgLib1), same(mParams), any());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLib2), same(mPkgLib2), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLib2), same(mPkgLib2), same(mParams), any());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLib4), same(mPkgLib4), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLib4), same(mPkgLib4), same(mParams), any());
+        inOrder.verify(mWakeLock).release();
+
+        verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 6 /* expectedSecondaryTimes */);
+
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    @Test
+    public void testDexoptNoDependencies() throws Exception {
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getPackageDexoptResults()).hasSize(3);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+
+        verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 3 /* expectedSecondaryTimes */);
+    }
+
+    @Test
+    public void testDexoptPrimaryOnly() throws Exception {
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getPackageDexoptResults()).hasSize(6);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+
+        verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+    }
+
+    @Test
+    public void testDexoptPrimaryOnlyNoDependencies() throws Exception {
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(0,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getPackageDexoptResults()).hasSize(3);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+
+        verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+    }
+
+    @Test
+    public void testDexoptCancelledBetweenDex2oatInvocations() throws Exception {
+        when(mPrimaryDexopter.dexopt()).thenAnswer(invocation -> {
+            mCancellationSignal.cancel();
+            return mPrimaryResults;
+        });
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_CANCELLED);
+
+        assertThat(result.getPackageDexoptResults()).hasSize(6);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_CANCELLED,
+                List.of(mPrimaryResults));
+        checkPackageResult(
+                result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_CANCELLED, List.of());
+        checkPackageResult(
+                result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_CANCELLED, List.of());
+        checkPackageResult(
+                result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_CANCELLED, List.of());
+        checkPackageResult(
+                result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_CANCELLED, List.of());
+        checkPackageResult(
+                result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_CANCELLED, List.of());
+
+        verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateFoo), same(mPkgFoo), same(mParams), any());
+
+        verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+    }
+
+    // This test verifies that every child thread can register its own listener on the cancellation
+    // signal through `setOnCancelListener` (i.e., the listeners don't overwrite each other).
+    @Test
+    public void testDexoptCancelledDuringDex2oatInvocationsMultiThreaded() throws Exception {
+        final int NUM_PACKAGES = 6;
+        final long TIMEOUT_SEC = 10;
+        var dexoptStarted = new Semaphore(0);
+        var dexoptCancelled = new Semaphore(0);
+
+        when(mInjector.getPrimaryDexopter(any(), any(), any(), any())).thenAnswer(invocation -> {
+            var cancellationSignal = invocation.<CancellationSignal>getArgument(3);
+            var dexopter = mock(PrimaryDexopter.class);
+            when(dexopter.dexopt()).thenAnswer(innerInvocation -> {
+                // Simulate that the child thread registers its own listener.
+                var isListenerCalled = new AtomicBoolean(false);
+                cancellationSignal.setOnCancelListener(() -> isListenerCalled.set(true));
+
+                dexoptStarted.release();
+                assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+                // Verify that the listener is called.
+                assertThat(isListenerCalled.get()).isTrue();
+
+                return mPrimaryResults;
+            });
+            return dexopter;
+        });
+
+        ExecutorService dexoptExecutor = Executors.newFixedThreadPool(NUM_PACKAGES);
+        Future<DexoptResult> future = ForkJoinPool.commonPool().submit(() -> {
+            return mDexoptHelper.dexopt(
+                    mSnapshot, mRequestedPackages, mParams, mCancellationSignal, dexoptExecutor);
+        });
+
+        try {
+            // Wait for all dexopt operations to start.
+            for (int i = 0; i < NUM_PACKAGES; i++) {
+                assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+            }
+
+            mCancellationSignal.cancel();
+
+            for (int i = 0; i < NUM_PACKAGES; i++) {
+                dexoptCancelled.release();
+            }
+        } finally {
+            dexoptExecutor.shutdown();
+            Utils.getFuture(future);
+        }
+    }
+
+    // This test verifies that dexopt operation on the current thread can be cancelled.
+    @Test
+    public void testDexoptCancelledDuringDex2oatInvocationsOnCurrentThread() throws Exception {
+        final long TIMEOUT_SEC = 10;
+        var dexoptStarted = new Semaphore(0);
+        var dexoptCancelled = new Semaphore(0);
+
+        when(mInjector.getPrimaryDexopter(any(), any(), any(), any())).thenAnswer(invocation -> {
+            var cancellationSignal = invocation.<CancellationSignal>getArgument(3);
+            var dexopter = mock(PrimaryDexopter.class);
+            when(dexopter.dexopt()).thenAnswer(innerInvocation -> {
+                if (cancellationSignal.isCanceled()) {
+                    return mPrimaryResults;
+                }
+
+                var isListenerCalled = new AtomicBoolean(false);
+                cancellationSignal.setOnCancelListener(() -> isListenerCalled.set(true));
+
+                dexoptStarted.release();
+                assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+                // Verify that the listener is called.
+                assertThat(isListenerCalled.get()).isTrue();
+
+                return mPrimaryResults;
+            });
+            return dexopter;
+        });
+
+        // Use the current thread (the one in ForkJoinPool).
+        Executor dexoptExecutor = Runnable::run;
+        Future<DexoptResult> future = ForkJoinPool.commonPool().submit(() -> {
+            return mDexoptHelper.dexopt(
+                    mSnapshot, mRequestedPackages, mParams, mCancellationSignal, dexoptExecutor);
+        });
+
+        try {
+            // Only one dexopt operation should start.
+            assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+            mCancellationSignal.cancel();
+
+            dexoptCancelled.release();
+        } finally {
+            Utils.getFuture(future);
+        }
+    }
+
+    @Test
+    public void testDexoptNotDexoptable() throws Exception {
+        when(PackageStateModulesUtils.isDexoptable(mPkgStateFoo)).thenReturn(false);
+
+        mRequestedPackages = List.of(PKG_NAME_FOO);
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
+        assertThat(result.getPackageDexoptResults()).hasSize(1);
+        checkPackageResult(
+                result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_SKIPPED, List.of());
+
+        verifyNoDexopt();
+    }
+
+    @Test
+    public void testDexoptLibraryNotDexoptable() throws Exception {
+        when(PackageStateModulesUtils.isDexoptable(mPkgStateLib1)).thenReturn(false);
+
+        mRequestedPackages = List.of(PKG_NAME_FOO);
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+        assertThat(result.getPackageDexoptResults()).hasSize(1);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+
+        verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 1 /* expectedSecondaryTimes */);
+    }
+
+    @Test
+    public void testDexoptIsHibernating() throws Exception {
+        lenient().when(mAhm.isHibernatingGlobally(PKG_NAME_FOO)).thenReturn(true);
+
+        mRequestedPackages = List.of(PKG_NAME_FOO);
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
+        checkPackageResult(
+                result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_SKIPPED, List.of());
+
+        verifyNoDexopt();
+    }
+
+    @Test
+    public void testDexoptIsHibernatingButOatArtifactDeletionDisabled() throws Exception {
+        lenient().when(mAhm.isHibernatingGlobally(PKG_NAME_FOO)).thenReturn(true);
+        lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(false);
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getPackageDexoptResults()).hasSize(6);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+    }
+
+    @Test
+    public void testDexoptAlwaysReleasesWakeLock() throws Exception {
+        when(mPrimaryDexopter.dexopt()).thenThrow(IllegalStateException.class);
+
+        try {
+            mDexoptHelper.dexopt(
+                    mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+        } catch (Exception ignored) {
+        }
+
+        verify(mWakeLock).release();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDexoptPackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(any())).thenReturn(null);
+
+        mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        verifyNoDexopt();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDexoptNoPackage() throws Exception {
+        lenient().when(mPkgStateFoo.getAndroidPackage()).thenReturn(null);
+
+        mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        verifyNoDexopt();
+    }
+
+    @Test
+    public void testDexoptSplit() throws Exception {
+        mRequestedPackages = List.of(PKG_NAME_FOO);
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                          .setSplitName("split_0")
+                          .build();
+
+        mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+    }
+
+    @Test
+    public void testDexoptSplitNotFound() throws Exception {
+        mRequestedPackages = List.of(PKG_NAME_FOO);
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                          .setSplitName("split_bogus")
+                          .build();
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            mDexoptHelper.dexopt(
+                    mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+        });
+    }
+
+    @Test
+    public void testCallbacks() throws Exception {
+        List<DexoptResult> list1 = new ArrayList<>();
+        mConfig.addDexoptDoneCallback(
+                false /* onlyIncludeUpdates */, Runnable::run, result -> list1.add(result));
+
+        List<DexoptResult> list2 = new ArrayList<>();
+        mConfig.addDexoptDoneCallback(
+                false /* onlyIncludeUpdates */, Runnable::run, result -> list2.add(result));
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(list1).containsExactly(result);
+        assertThat(list2).containsExactly(result);
+    }
+
+    @Test
+    public void testCallbackRemoved() throws Exception {
+        List<DexoptResult> list1 = new ArrayList<>();
+        DexoptDoneCallback callback1 = result -> list1.add(result);
+        mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback1);
+
+        List<DexoptResult> list2 = new ArrayList<>();
+        mConfig.addDexoptDoneCallback(
+                false /* onlyIncludeUpdates */, Runnable::run, result -> list2.add(result));
+
+        mConfig.removeDexoptDoneCallback(callback1);
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(list1).isEmpty();
+        assertThat(list2).containsExactly(result);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCallbackAlreadyAdded() throws Exception {
+        List<DexoptResult> list = new ArrayList<>();
+        DexoptDoneCallback callback = result -> list.add(result);
+        mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback);
+        mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback);
+    }
+
+    // Tests `addDexoptDoneCallback` with `onlyIncludeUpdates` being true and false.
+    @Test
+    public void testCallbackWithFailureResults() throws Exception {
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(0,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        // This list should collect all results.
+        List<DexoptResult> listAll = new ArrayList<>();
+        mConfig.addDexoptDoneCallback(
+                false /* onlyIncludeUpdates */, Runnable::run, result -> listAll.add(result));
+
+        // This list should only collect results that have updates.
+        List<DexoptResult> listOnlyIncludeUpdates = new ArrayList<>();
+        mConfig.addDexoptDoneCallback(true /* onlyIncludeUpdates */, Runnable::run,
+                result -> listOnlyIncludeUpdates.add(result));
+
+        // Dexopt partially fails on package "foo".
+        List<DexContainerFileDexoptResult> partialFailureResults =
+                createResults("/data/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */,
+                        DexoptResult.DEXOPT_FAILED /* status2 */);
+        var fooPrimaryDexopter = mock(PrimaryDexopter.class);
+        when(mInjector.getPrimaryDexopter(same(mPkgStateFoo), any(), any(), any()))
+                .thenReturn(fooPrimaryDexopter);
+        when(fooPrimaryDexopter.dexopt()).thenReturn(partialFailureResults);
+
+        // Dexopt totally fails on package "bar".
+        List<DexContainerFileDexoptResult> totalFailureResults =
+                createResults("/data/app/bar/base.apk", DexoptResult.DEXOPT_FAILED /* status1 */,
+                        DexoptResult.DEXOPT_FAILED /* status2 */);
+        var barPrimaryDexopter = mock(PrimaryDexopter.class);
+        when(mInjector.getPrimaryDexopter(same(mPkgStateBar), any(), any(), any()))
+                .thenReturn(barPrimaryDexopter);
+        when(barPrimaryDexopter.dexopt()).thenReturn(totalFailureResults);
+
+        DexoptResult resultWithSomeUpdates = mDexoptHelper.dexopt(mSnapshot,
+                List.of(PKG_NAME_FOO, PKG_NAME_BAR), mParams, mCancellationSignal, mExecutor);
+        DexoptResult resultWithNoUpdates = mDexoptHelper.dexopt(
+                mSnapshot, List.of(PKG_NAME_BAR), mParams, mCancellationSignal, mExecutor);
+
+        assertThat(listAll).containsExactly(resultWithSomeUpdates, resultWithNoUpdates);
+
+        assertThat(listOnlyIncludeUpdates).hasSize(1);
+        assertThat(listOnlyIncludeUpdates.get(0)
+                           .getPackageDexoptResults()
+                           .stream()
+                           .map(PackageDexoptResult::getPackageName)
+                           .collect(Collectors.toList()))
+                .containsExactly(PKG_NAME_FOO);
+    }
+
+    @Test
+    public void testProgressCallback() throws Exception {
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        // Delay the executor to verify that the commands passed to the executor are not bound to
+        // changing variables.
+        var progressCallbackExecutor = new DelayedExecutor();
+        Consumer<OperationProgress> progressCallback = mock(Consumer.class);
+
+        mDexoptHelper.dexopt(mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor,
+                progressCallbackExecutor, progressCallback);
+
+        progressCallbackExecutor.runAll();
+
+        InOrder inOrder = inOrder(progressCallback);
+        inOrder.verify(progressCallback)
+                .accept(eq(OperationProgress.create(0 /* current */, 3 /* total */)));
+        inOrder.verify(progressCallback)
+                .accept(eq(OperationProgress.create(1 /* current */, 3 /* total */)));
+        inOrder.verify(progressCallback)
+                .accept(eq(OperationProgress.create(2 /* current */, 3 /* total */)));
+        inOrder.verify(progressCallback)
+                .accept(eq(OperationProgress.create(3 /* current */, 3 /* total */)));
+    }
+
+    private AndroidPackage createPackage(boolean multiSplit) {
+        AndroidPackage pkg = mock(AndroidPackage.class);
+
+        var baseSplit = mock(AndroidPackageSplit.class);
+
+        if (multiSplit) {
+            var split0 = mock(AndroidPackageSplit.class);
+            lenient().when(split0.getName()).thenReturn("split_0");
+
+            lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0));
+        } else {
+            lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit));
+        }
+
+        return pkg;
+    }
+
+    private PackageState createPackageState(
+            String packageName, List<SharedLibrary> deps, boolean multiSplit) {
+        PackageState pkgState = mock(PackageState.class);
+        lenient().when(pkgState.getPackageName()).thenReturn(packageName);
+        lenient().when(pkgState.getAppId()).thenReturn(12345);
+        lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(deps);
+        AndroidPackage pkg = createPackage(multiSplit);
+        lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+        lenient().when(PackageStateModulesUtils.isDexoptable(pkgState)).thenReturn(true);
+        return pkgState;
+    }
+
+    private SharedLibrary createLibrary(
+            String libraryName, String packageName, List<SharedLibrary> deps) {
+        SharedLibrary library = mock(SharedLibrary.class);
+        lenient().when(library.getName()).thenReturn(libraryName);
+        lenient().when(library.getPackageName()).thenReturn(packageName);
+        lenient().when(library.getDependencies()).thenReturn(deps);
+        lenient().when(library.isNative()).thenReturn(false);
+        return library;
+    }
+
+    private void preparePackagesAndLibraries() {
+        // Dependency graph:
+        //                foo                bar
+        //                 |                  |
+        //            lib1a (lib1)       lib1b (lib1)       lib1c (lib1)
+        //               /   \             /   \                  |
+        //              /     \           /     \                 |
+        //  libbaz (libbaz)    lib2 (lib2)    lib4 (lib4)    lib3 (lib3)
+        //
+        // "lib1a", "lib1b", and "lib1c" belong to the same package "lib1".
+
+        mRequestedPackages = List.of(PKG_NAME_FOO, PKG_NAME_BAR, PKG_NAME_LIBBAZ);
+
+        // The native library is not dexoptable.
+        SharedLibrary libNative = createLibrary("libnative", "com.example.libnative", List.of());
+        lenient().when(libNative.isNative()).thenReturn(true);
+
+        SharedLibrary libbaz = createLibrary("libbaz", PKG_NAME_LIBBAZ, List.of());
+        SharedLibrary lib4 = createLibrary("lib4", PKG_NAME_LIB4, List.of());
+        SharedLibrary lib3 = createLibrary("lib3", PKG_NAME_LIB3, List.of());
+        SharedLibrary lib2 = createLibrary("lib2", PKG_NAME_LIB2, List.of());
+        SharedLibrary lib1a = createLibrary("lib1a", PKG_NAME_LIB1, List.of(libbaz, lib2));
+        SharedLibrary lib1b = createLibrary("lib1b", PKG_NAME_LIB1, List.of(lib2, libNative, lib4));
+        SharedLibrary lib1c = createLibrary("lib1c", PKG_NAME_LIB1, List.of(lib3));
+
+        mPkgStateFoo =
+                createPackageState(PKG_NAME_FOO, List.of(lib1a, libNative), true /* multiSplit */);
+        mPkgFoo = mPkgStateFoo.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_FOO)).thenReturn(mPkgStateFoo);
+
+        mPkgStateBar = createPackageState(PKG_NAME_BAR, List.of(lib1b), false /* multiSplit */);
+        mPkgBar = mPkgStateBar.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_BAR)).thenReturn(mPkgStateBar);
+
+        mPkgStateLib1 = createPackageState(
+                PKG_NAME_LIB1, List.of(libbaz, lib2, lib3, lib4), false /* multiSplit */);
+        mPkgLib1 = mPkgStateLib1.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB1)).thenReturn(mPkgStateLib1);
+
+        mPkgStateLib2 = createPackageState(PKG_NAME_LIB2, List.of(), false /* multiSplit */);
+        mPkgLib2 = mPkgStateLib2.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB2)).thenReturn(mPkgStateLib2);
+
+        // This should not be considered as a transitive dependency of any requested package, even
+        // though it is a dependency of package "lib1".
+        PackageState pkgStateLib3 =
+                createPackageState(PKG_NAME_LIB3, List.of(), false /* multiSplit */);
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB3)).thenReturn(pkgStateLib3);
+
+        mPkgStateLib4 = createPackageState(PKG_NAME_LIB4, List.of(), false /* multiSplit */);
+        mPkgLib4 = mPkgStateLib4.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB4)).thenReturn(mPkgStateLib4);
+
+        mPkgStateLibbaz = createPackageState(PKG_NAME_LIBBAZ, List.of(), false /* multiSplit */);
+        mPkgLibbaz = mPkgStateLibbaz.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_LIBBAZ)).thenReturn(mPkgStateLibbaz);
+    }
+
+    private void verifyNoDexopt() {
+        verify(mInjector, never()).getPrimaryDexopter(any(), any(), any(), any());
+        verify(mInjector, never()).getSecondaryDexopter(any(), any(), any(), any());
+    }
+
+    private void verifyNoMoreDexopt(int expectedPrimaryTimes, int expectedSecondaryTimes) {
+        verify(mInjector, times(expectedPrimaryTimes))
+                .getPrimaryDexopter(any(), any(), any(), any());
+        verify(mInjector, times(expectedSecondaryTimes))
+                .getSecondaryDexopter(any(), any(), any(), any());
+    }
+
+    private List<DexContainerFileDexoptResult> createResults(
+            String dexPath, @DexoptResultStatus int status1, @DexoptResultStatus int status2) {
+        return List.of(DexContainerFileDexoptResult.create(dexPath, true /* isPrimaryAbi */,
+                               "arm64-v8a", "verify", status1, 100 /* dex2oatWallTimeMillis */,
+                               400 /* dex2oatCpuTimeMillis */, 0 /* sizeBytes */,
+                               0 /* sizeBeforeBytes */, false /* isSkippedDueToStorageLow */),
+                DexContainerFileDexoptResult.create(dexPath, false /* isPrimaryAbi */,
+                        "armeabi-v7a", "verify", status2, 100 /* dex2oatWallTimeMillis */,
+                        400 /* dex2oatCpuTimeMillis */, 0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                        false /* isSkippedDueToStorageLow */));
+    }
+
+    private void checkPackageResult(DexoptResult result, int index, String packageName,
+            @DexoptResult.DexoptResultStatus int status,
+            List<List<DexContainerFileDexoptResult>> dexContainerFileDexoptResults) {
+        PackageDexoptResult packageResult = result.getPackageDexoptResults().get(index);
+        assertThat(packageResult.getPackageName()).isEqualTo(packageName);
+        assertThat(packageResult.getStatus()).isEqualTo(status);
+        assertThat(packageResult.getDexContainerFileDexoptResults())
+                .containsExactlyElementsIn(dexContainerFileDexoptResults.stream()
+                                                   .flatMap(r -> r.stream())
+                                                   .collect(Collectors.toList()));
+    }
+
+    /** An executor that delays execution until `runAll` is called. */
+    private static class DelayedExecutor implements Executor {
+        private List<Runnable> mCommands = new ArrayList<>();
+
+        public void execute(Runnable command) {
+            mCommands.add(command);
+        }
+
+        public void runAll() {
+            for (Runnable command : mCommands) {
+                command.run();
+            }
+            mCommands.clear();
+        }
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
new file mode 100644
index 0000000..3833249
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DexLoader;
+import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+
+import android.annotation.NonNull;
+import android.os.SystemProperties;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DumpHelperTest {
+    private static final String PKG_NAME_FOO = "com.example1.foo";
+    private static final String PKG_NAME_BAR = "com.example2.bar";
+
+    @Rule
+    public StaticMockitoRule mockitoRule =
+            new StaticMockitoRule(SystemProperties.class, Constants.class);
+
+    @Mock private DumpHelper.Injector mInjector;
+    @Mock private ArtManagerLocal mArtManagerLocal;
+    @Mock private DexUseManagerLocal mDexUseManagerLocal;
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+    @Mock private IArtd mArtd;
+
+    private DumpHelper mDumpHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+        // No ISA translation.
+        lenient()
+                .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+                .thenReturn("");
+
+        lenient().when(mInjector.getArtManagerLocal()).thenReturn(mArtManagerLocal);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManagerLocal);
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+
+        Map<String, PackageState> pkgStates = createPackageStates();
+        lenient().when(mSnapshot.getPackageStates()).thenReturn(pkgStates);
+        for (var entry : pkgStates.entrySet()) {
+            lenient().when(mSnapshot.getPackageState(entry.getKey())).thenReturn(entry.getValue());
+        }
+
+        setUpForFoo();
+        setUpForBar();
+
+        mDumpHelper = new DumpHelper(mInjector);
+    }
+
+    @Test
+    public void testDump() throws Exception {
+        String expected = "[com.example1.foo]\n"
+                + "  path: /data/app/foo/base.apk\n"
+                + "    arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]\n"
+                + "      [location is /data/app/foo/oat/arm64/base.odex]\n"
+                + "    arm: [status=verify] [reason=install]\n"
+                + "      [location is /data/app/foo/oat/arm/base.odex]\n"
+                + "  path: /data/app/foo/split_0.apk\n"
+                + "    arm64: [status=verify] [reason=vdex] [primary-abi]\n"
+                + "      [location is primary.vdex in /data/app/foo/split_0.dm]\n"
+                + "    arm: [status=verify] [reason=vdex]\n"
+                + "      [location is primary.vdex in /data/app/foo/split_0.dm]\n"
+                + "    used by other apps: [com.example2.bar (isa=arm)]\n"
+                + "  known secondary dex files:\n"
+                + "    /data/user_de/0/foo/1.apk (removed)\n"
+                + "      arm: [status=run-from-apk] [reason=unknown]\n"
+                + "        [location is unknown]\n"
+                + "      class loader context: =VaryingClassLoaderContexts=\n"
+                + "        com.example1.foo (isolated): CLC1\n"
+                + "        com.example3.baz: CLC2\n"
+                + "      used by other apps: [com.example1.foo (isolated) (isa=arm64), com.example3.baz (removed)]\n"
+                + "    /data/user_de/0/foo/2.apk (public)\n"
+                + "      arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]\n"
+                + "        [location is /data/user_de/0/foo/oat/arm64/2.odex]\n"
+                + "      arm: [status=verify] [reason=vdex]\n"
+                + "        [location is /data/user_de/0/foo/oat/arm/2.vdex]\n"
+                + "      class loader context: PCL[]\n"
+                + "[com.example2.bar]\n"
+                + "  path: /data/app/bar/base.apk\n"
+                + "    arm: [status=verify] [reason=install] [primary-abi]\n"
+                + "      [location is /data/app/bar/oat/arm/base.odex]\n"
+                + "    arm64: [status=verify] [reason=install]\n"
+                + "      [location is /data/app/bar/oat/arm64/base.odex]\n";
+
+        var stringWriter = new StringWriter();
+        mDumpHelper.dump(new PrintWriter(stringWriter), mSnapshot);
+        assertThat(stringWriter.toString()).isEqualTo(expected);
+    }
+
+    private PackageState createPackageState(@NonNull String packageName, int appId,
+            boolean hasPackage, @NonNull String primaryAbi, @NonNull String secondaryAbi) {
+        var pkgState = mock(PackageState.class);
+        lenient().when(pkgState.getPackageName()).thenReturn(packageName);
+        lenient().when(pkgState.getAppId()).thenReturn(appId);
+        lenient()
+                .when(pkgState.getAndroidPackage())
+                .thenReturn(hasPackage ? mock(AndroidPackage.class) : null);
+        lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn(primaryAbi);
+        lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn(secondaryAbi);
+        return pkgState;
+    }
+
+    private Map<String, PackageState> createPackageStates() {
+        var pkgStates = new HashMap<String, PackageState>();
+        pkgStates.put(PKG_NAME_FOO,
+                createPackageState(PKG_NAME_FOO, 10001 /* appId */, true /* hasPackage */,
+                        "arm64-v8a", "armeabi-v7a"));
+        pkgStates.put(PKG_NAME_BAR,
+                createPackageState(PKG_NAME_BAR, 10003 /* appId */, true /* hasPackage */,
+                        "armeabi-v7a", "arm64-v8a"));
+        // This should not be included in the output because it has a negative app id.
+        pkgStates.put("com.android.art",
+                createPackageState("com.android.art", -1 /* appId */, true /* hasPackage */,
+                        "arm64-v8a", "armeabi-v7a"));
+        // This should not be included in the output because it does't have AndroidPackage.
+        pkgStates.put("com.example.null",
+                createPackageState("com.example.null", 10010 /* appId */, false /* hasPackage */,
+                        "arm64-v8a", "armeabi-v7a"));
+        return pkgStates;
+    }
+
+    private void setUpForFoo() throws Exception {
+        // The order of the primary dex files and the ABIs should be kept in the output. Secondary
+        // dex files should be reordered in lexicographical order.
+        var status = DexoptStatus.create(List.of(
+                DexContainerFileDexoptStatus.create("/data/app/foo/base.apk",
+                        true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                        "speed-profile", "bg-dexopt", "/data/app/foo/oat/arm64/base.odex"),
+                DexContainerFileDexoptStatus.create("/data/app/foo/base.apk",
+                        true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify",
+                        "install", "/data/app/foo/oat/arm/base.odex"),
+                DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk",
+                        true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", "verify",
+                        "vdex", "primary.vdex in /data/app/foo/split_0.dm"),
+                DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk",
+                        true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify",
+                        "vdex", "primary.vdex in /data/app/foo/split_0.dm"),
+                DexContainerFileDexoptStatus.create("/data/user_de/0/foo/2.apk",
+                        false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                        "speed-profile", "bg-dexopt", "/data/user_de/0/foo/oat/arm64/2.odex"),
+                DexContainerFileDexoptStatus.create("/data/user_de/0/foo/2.apk",
+                        false /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify",
+                        "vdex", "/data/user_de/0/foo/oat/arm/2.vdex"),
+                DexContainerFileDexoptStatus.create("/data/user_de/0/foo/1.apk",
+                        false /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
+                        "run-from-apk", "unknown", "unknown")));
+
+        lenient()
+                .when(mArtManagerLocal.getDexoptStatus(any(), eq(PKG_NAME_FOO)))
+                .thenReturn(status);
+
+        // The output should not show "used by other apps:".
+        lenient()
+                .when(mDexUseManagerLocal.getPrimaryDexLoaders(
+                        PKG_NAME_FOO, "/data/app/foo/base.apk"))
+                .thenReturn(Set.of());
+
+        // The output should not show "foo" in "used by other apps:".
+        lenient()
+                .when(mDexUseManagerLocal.getPrimaryDexLoaders(
+                        PKG_NAME_FOO, "/data/app/foo/split_0.apk"))
+                .thenReturn(Set.of(DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */),
+                        DexLoader.create(PKG_NAME_BAR, false /* isolatedProcess */)));
+
+        var info1 = mock(SecondaryDexInfo.class);
+        lenient().when(info1.dexPath()).thenReturn("/data/user_de/0/foo/1.apk");
+        lenient()
+                .when(info1.displayClassLoaderContext())
+                .thenReturn(SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS);
+
+        lenient()
+                .when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO,
+                        "/data/user_de/0/foo/1.apk",
+                        DexLoader.create(PKG_NAME_FOO, true /* isolatedProcess */)))
+                .thenReturn("CLC1");
+        lenient()
+                .when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO,
+                        "/data/user_de/0/foo/1.apk",
+                        DexLoader.create("com.example3.baz", false /* isolatedProcess */)))
+                .thenReturn("CLC2");
+
+        var loaders = new HashSet<DexLoader>();
+        // The output should show "foo" with "(isolated)" in "used by other apps:".
+        loaders.add(DexLoader.create(PKG_NAME_FOO, true /* isolatedProcess */));
+        // The output should show "baz" with "(removed)" in "used by other apps:".
+        loaders.add(DexLoader.create("com.example3.baz", false /* isolatedProcess */));
+        lenient().when(info1.loaders()).thenReturn(loaders);
+
+        // The output should show the dex path with "(removed)".
+        lenient()
+                .when(mArtd.getDexFileVisibility("/data/user_de/0/foo/1.apk"))
+                .thenReturn(FileVisibility.NOT_FOUND);
+
+        var info2 = mock(SecondaryDexInfo.class);
+        lenient().when(info2.dexPath()).thenReturn("/data/user_de/0/foo/2.apk");
+        lenient().when(info2.displayClassLoaderContext()).thenReturn("PCL[]");
+        lenient()
+                .when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO,
+                        "/data/user_de/0/foo/2.apk",
+                        DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */)))
+                .thenReturn("PCL[]");
+        // The output should not show "used by other apps:".
+        lenient()
+                .when(info2.loaders())
+                .thenReturn(Set.of(DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */)));
+        lenient()
+                .when(mArtd.getDexFileVisibility("/data/user_de/0/foo/2.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        lenient()
+                .doReturn(List.of(info1, info2))
+                .when(mDexUseManagerLocal)
+                .getSecondaryDexInfo(PKG_NAME_FOO);
+    }
+
+    private void setUpForBar() {
+        // The order of the ABI should be kept in the output, despite that it's different from the
+        // order for package "foo".
+        // The output should not show "known secondary dex files:".
+        var status = DexoptStatus.create(
+                List.of(DexContainerFileDexoptStatus.create("/data/app/bar/base.apk",
+                                true /* isPrimaryDex */, true /* isPrimaryAbi */, "armeabi-v7a",
+                                "verify", "install", "/data/app/bar/oat/arm/base.odex"),
+                        DexContainerFileDexoptStatus.create("/data/app/bar/base.apk",
+                                true /* isPrimaryDex */, false /* isPrimaryAbi */, "arm64-v8a",
+                                "verify", "install", "/data/app/bar/oat/arm64/base.odex")));
+
+        lenient()
+                .when(mArtManagerLocal.getDexoptStatus(any(), eq(PKG_NAME_BAR)))
+                .thenReturn(status);
+
+        lenient()
+                .when(mDexUseManagerLocal.getPrimaryDexLoaders(
+                        PKG_NAME_BAR, "/data/app/bar/base.apk"))
+                .thenReturn(Set.of());
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java
new file mode 100644
index 0000000..a54b371
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
+import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedLibrary;
+
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.DexClassLoader;
+import dalvik.system.PathClassLoader;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class PrimaryDexUtilsTest {
+    @Before
+    public void setUp() {}
+
+    @Test
+    public void testGetDexInfo() {
+        List<PrimaryDexInfo> infos =
+                PrimaryDexUtils.getDexInfo(createPackage(false /* isIsolatedSplitLoading */));
+        checkBasicInfo(infos);
+    }
+
+    @Test
+    public void testGetDetailedDexInfo() {
+        List<DetailedPrimaryDexInfo> infos = PrimaryDexUtils.getDetailedDexInfo(
+                createPackageState(), createPackage(false /* isIsolatedSplitLoading */));
+        checkBasicInfo(infos);
+
+        String sharedLibrariesContext = "{"
+                + "PCL[library_2.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}#"
+                + "PCL[library_3.jar]#"
+                + "PCL[library_4.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}"
+                + "}";
+
+        assertThat(infos.get(0).classLoaderContext()).isEqualTo("PCL[]" + sharedLibrariesContext);
+        assertThat(infos.get(1).classLoaderContext())
+                .isEqualTo("PCL[base.apk]" + sharedLibrariesContext);
+        assertThat(infos.get(2).classLoaderContext()).isEqualTo(null);
+        assertThat(infos.get(3).classLoaderContext())
+                .isEqualTo("PCL[base.apk:split_0.apk:split_1.apk]" + sharedLibrariesContext);
+        assertThat(infos.get(4).classLoaderContext())
+                .isEqualTo("PCL[base.apk:split_0.apk:split_1.apk:split_2.apk]"
+                        + sharedLibrariesContext);
+    }
+
+    @Test
+    public void testGetDetailedDexInfoIsolated() {
+        List<DetailedPrimaryDexInfo> infos = PrimaryDexUtils.getDetailedDexInfo(
+                createPackageState(), createPackage(true /* isIsolatedSplitLoading */));
+        checkBasicInfo(infos);
+
+        String sharedLibrariesContext = "{"
+                + "PCL[library_2.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}#"
+                + "PCL[library_3.jar]#"
+                + "PCL[library_4.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}"
+                + "}";
+
+        assertThat(infos.get(0).classLoaderContext()).isEqualTo("PCL[]" + sharedLibrariesContext);
+        assertThat(infos.get(1).classLoaderContext())
+                .isEqualTo("PCL[];DLC[split_2.apk];PCL[base.apk]" + sharedLibrariesContext);
+        assertThat(infos.get(2).classLoaderContext()).isEqualTo(null);
+        assertThat(infos.get(3).classLoaderContext())
+                .isEqualTo("DLC[];PCL[base.apk]" + sharedLibrariesContext);
+        assertThat(infos.get(4).classLoaderContext()).isEqualTo("PCL[]");
+        assertThat(infos.get(5).classLoaderContext()).isEqualTo("PCL[];PCL[split_3.apk]");
+    }
+
+    private <T extends PrimaryDexInfo> void checkBasicInfo(List<T> infos) {
+        assertThat(infos.get(0).dexPath()).isEqualTo("/data/app/foo/base.apk");
+        assertThat(infos.get(0).hasCode()).isTrue();
+        assertThat(infos.get(0).splitName()).isNull();
+
+        assertThat(infos.get(1).dexPath()).isEqualTo("/data/app/foo/split_0.apk");
+        assertThat(infos.get(1).hasCode()).isTrue();
+        assertThat(infos.get(1).splitName()).isEqualTo("split_0");
+
+        assertThat(infos.get(2).dexPath()).isEqualTo("/data/app/foo/split_1.apk");
+        assertThat(infos.get(2).hasCode()).isFalse();
+        assertThat(infos.get(2).splitName()).isEqualTo("split_1");
+
+        assertThat(infos.get(3).dexPath()).isEqualTo("/data/app/foo/split_2.apk");
+        assertThat(infos.get(3).hasCode()).isTrue();
+        assertThat(infos.get(3).splitName()).isEqualTo("split_2");
+
+        assertThat(infos.get(4).dexPath()).isEqualTo("/data/app/foo/split_3.apk");
+        assertThat(infos.get(4).hasCode()).isTrue();
+        assertThat(infos.get(4).splitName()).isEqualTo("split_3");
+
+        assertThat(infos.get(5).dexPath()).isEqualTo("/data/app/foo/split_4.apk");
+        assertThat(infos.get(5).hasCode()).isTrue();
+        assertThat(infos.get(5).splitName()).isEqualTo("split_4");
+    }
+
+    private AndroidPackage createPackage(boolean isIsolatedSplitLoading) {
+        AndroidPackage pkg = mock(AndroidPackage.class);
+
+        var baseSplit = mock(AndroidPackageSplit.class);
+        lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk");
+        lenient().when(baseSplit.isHasCode()).thenReturn(true);
+        lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+
+        var split0 = mock(AndroidPackageSplit.class);
+        lenient().when(split0.getName()).thenReturn("split_0");
+        lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
+        lenient().when(split0.isHasCode()).thenReturn(true);
+
+        var split1 = mock(AndroidPackageSplit.class);
+        lenient().when(split1.getName()).thenReturn("split_1");
+        lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
+        lenient().when(split1.isHasCode()).thenReturn(false);
+
+        var split2 = mock(AndroidPackageSplit.class);
+        lenient().when(split2.getName()).thenReturn("split_2");
+        lenient().when(split2.getPath()).thenReturn("/data/app/foo/split_2.apk");
+        lenient().when(split2.isHasCode()).thenReturn(true);
+
+        var split3 = mock(AndroidPackageSplit.class);
+        lenient().when(split3.getName()).thenReturn("split_3");
+        lenient().when(split3.getPath()).thenReturn("/data/app/foo/split_3.apk");
+        lenient().when(split3.isHasCode()).thenReturn(true);
+
+        var split4 = mock(AndroidPackageSplit.class);
+        lenient().when(split4.getName()).thenReturn("split_4");
+        lenient().when(split4.getPath()).thenReturn("/data/app/foo/split_4.apk");
+        lenient().when(split4.isHasCode()).thenReturn(true);
+
+        var splits = List.of(baseSplit, split0, split1, split2, split3, split4);
+        lenient().when(pkg.getSplits()).thenReturn(splits);
+
+        if (isIsolatedSplitLoading) {
+            // split_0: PCL(PathClassLoader), depends on split_2.
+            // split_1: no code.
+            // split_2: DLC(DelegateLastClassLoader), depends on base.
+            // split_3: PCL(DexClassLoader), no dependency.
+            // split_4: PCL(null), depends on split_3.
+            lenient().when(split0.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+            lenient().when(split1.getClassLoaderName()).thenReturn(null);
+            lenient()
+                    .when(split2.getClassLoaderName())
+                    .thenReturn(DelegateLastClassLoader.class.getName());
+            lenient().when(split3.getClassLoaderName()).thenReturn(DexClassLoader.class.getName());
+            lenient().when(split4.getClassLoaderName()).thenReturn(null);
+
+            lenient().when(split0.getDependencies()).thenReturn(List.of(split2));
+            lenient().when(split2.getDependencies()).thenReturn(List.of(baseSplit));
+            lenient().when(split4.getDependencies()).thenReturn(List.of(split3));
+            lenient().when(pkg.isIsolatedSplitLoading()).thenReturn(true);
+        } else {
+            lenient().when(pkg.isIsolatedSplitLoading()).thenReturn(false);
+        }
+
+        return pkg;
+    }
+
+    private PackageState createPackageState() {
+        PackageState pkgState = mock(PackageState.class);
+
+        lenient().when(pkgState.getPackageName()).thenReturn("com.example.foo");
+
+        // Base depends on library 2, 3, 4.
+        // Library 2, 4 depends on library 1.
+        List<SharedLibrary> usesLibraryInfos = new ArrayList<>();
+
+        // The native library should not be added to the CLC.
+        SharedLibrary libraryNative = mock(SharedLibrary.class);
+        lenient().when(libraryNative.getAllCodePaths()).thenReturn(List.of("library_native.so"));
+        lenient().when(libraryNative.getDependencies()).thenReturn(null);
+        lenient().when(libraryNative.isNative()).thenReturn(true);
+        usesLibraryInfos.add(libraryNative);
+
+        SharedLibrary library1 = mock(SharedLibrary.class);
+        lenient()
+                .when(library1.getAllCodePaths())
+                .thenReturn(List.of("library_1_dex_1.jar", "library_1_dex_2.jar"));
+        lenient().when(library1.getDependencies()).thenReturn(null);
+        lenient().when(library1.isNative()).thenReturn(false);
+
+        SharedLibrary library2 = mock(SharedLibrary.class);
+        lenient().when(library2.getAllCodePaths()).thenReturn(List.of("library_2.jar"));
+        lenient().when(library2.getDependencies()).thenReturn(List.of(library1, libraryNative));
+        lenient().when(library2.isNative()).thenReturn(false);
+        usesLibraryInfos.add(library2);
+
+        SharedLibrary library3 = mock(SharedLibrary.class);
+        lenient().when(library3.getAllCodePaths()).thenReturn(List.of("library_3.jar"));
+        lenient().when(library3.getDependencies()).thenReturn(null);
+        lenient().when(library3.isNative()).thenReturn(false);
+        usesLibraryInfos.add(library3);
+
+        SharedLibrary library4 = mock(SharedLibrary.class);
+        lenient().when(library4.getAllCodePaths()).thenReturn(List.of("library_4.jar"));
+        lenient().when(library4.getDependencies()).thenReturn(List.of(library1));
+        lenient().when(library4.isNative()).thenReturn(false);
+        usesLibraryInfos.add(library4);
+
+        lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(usesLibraryInfos);
+
+        return pkgState;
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
new file mode 100644
index 0000000..31ea915
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.AidlUtils.buildFsPermission;
+import static com.android.server.art.AidlUtils.buildOutputArtifacts;
+import static com.android.server.art.AidlUtils.buildPermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.os.Process;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.testing.TestingUtils;
+
+import dalvik.system.DexFile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.ArgumentMatcher;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase {
+    private DexoptParams mDexoptParams;
+
+    private PrimaryDexopter mPrimaryDexopter;
+
+    @Parameter(0) public Params mParams;
+
+    @Parameters(name = "{0}")
+    public static Iterable<Params> data() {
+        List<Params> list = new ArrayList<>();
+        Params params;
+
+        // Baseline.
+        params = new Params();
+        list.add(params);
+
+        params = new Params();
+        params.mRequestedCompilerFilter = "speed";
+        params.mExpectedCompilerFilter = "speed";
+        list.add(params);
+
+        params = new Params();
+        params.mIsSystem = true;
+        params.mExpectedIsInDalvikCache = true;
+        list.add(params);
+
+        params = new Params();
+        params.mIsSystem = true;
+        params.mIsUpdatedSystemApp = true;
+        list.add(params);
+
+        params = new Params();
+        params.mIsIncrementalFsPath = true;
+        params.mExpectedIsInDalvikCache = true;
+        list.add(params);
+
+        params = new Params();
+        params.mIsSystem = true;
+        params.mHiddenApiEnforcementPolicy = ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
+        params.mExpectedIsInDalvikCache = true;
+        params.mExpectedIsHiddenApiPolicyEnabled = false;
+        list.add(params);
+
+        params = new Params();
+        params.mIsDebuggable = true;
+        params.mRequestedCompilerFilter = "speed";
+        params.mExpectedCompilerFilter = "verify";
+        params.mExpectedIsDebuggable = true;
+        list.add(params);
+
+        params = new Params();
+        params.mIsVmSafeMode = true;
+        params.mRequestedCompilerFilter = "speed";
+        params.mExpectedCompilerFilter = "verify";
+        list.add(params);
+
+        params = new Params();
+        params.mIsUseEmbeddedDex = true;
+        params.mRequestedCompilerFilter = "speed";
+        params.mExpectedCompilerFilter = "verify";
+        list.add(params);
+
+        params = new Params();
+        params.mAlwaysDebuggable = true;
+        params.mExpectedIsDebuggable = true;
+        list.add(params);
+
+        params = new Params();
+        params.mIsSystemUi = true;
+        params.mExpectedCompilerFilter = "speed";
+        list.add(params);
+
+        params = new Params();
+        params.mIsLauncher = true;
+        params.mExpectedCompilerFilter = "speed-profile";
+        list.add(params);
+
+        params = new Params();
+        params.mForce = true;
+        params.mShouldDowngrade = false;
+        params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+                | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+                | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
+        list.add(params);
+
+        params = new Params();
+        params.mForce = true;
+        params.mShouldDowngrade = true;
+        params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+                | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+                | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
+        list.add(params);
+
+        params = new Params();
+        params.mShouldDowngrade = true;
+        params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+        list.add(params);
+
+        params = new Params();
+        // This should not change the result.
+        params.mSkipIfStorageLow = true;
+        list.add(params);
+
+        return list;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(mParams.mIsSystemUi);
+        lenient().when(mInjector.isLauncherPackage(any())).thenReturn(mParams.mIsLauncher);
+
+        lenient()
+                .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
+                .thenReturn(mParams.mAlwaysDebuggable);
+
+        lenient().when(mPkg.isVmSafeMode()).thenReturn(mParams.mIsVmSafeMode);
+        lenient().when(mPkg.isDebuggable()).thenReturn(mParams.mIsDebuggable);
+        lenient().when(mPkg.getTargetSdkVersion()).thenReturn(123);
+        lenient()
+                .when(mPkgState.getHiddenApiEnforcementPolicy())
+                .thenReturn(mParams.mHiddenApiEnforcementPolicy);
+        lenient().when(mPkg.isUseEmbeddedDex()).thenReturn(mParams.mIsUseEmbeddedDex);
+        lenient().when(mPkgState.isSystem()).thenReturn(mParams.mIsSystem);
+        lenient().when(mPkgState.isUpdatedSystemApp()).thenReturn(mParams.mIsUpdatedSystemApp);
+
+        // Make all profile-related operations succeed so that "speed-profile" doesn't fall back to
+        // "verify".
+        lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(true);
+        lenient().when(mArtd.getProfileVisibility(any())).thenReturn(FileVisibility.OTHER_READABLE);
+        lenient().when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
+
+        lenient().when(mArtd.isIncrementalFsPath(any())).thenReturn(mParams.mIsIncrementalFsPath);
+
+        mDexoptParams =
+                new DexoptParams.Builder("install")
+                        .setCompilerFilter(mParams.mRequestedCompilerFilter)
+                        .setPriorityClass(ArtFlags.PRIORITY_INTERACTIVE)
+                        .setFlags(mParams.mForce ? ArtFlags.FLAG_FORCE : 0, ArtFlags.FLAG_FORCE)
+                        .setFlags(mParams.mShouldDowngrade ? ArtFlags.FLAG_SHOULD_DOWNGRADE : 0,
+                                ArtFlags.FLAG_SHOULD_DOWNGRADE)
+                        .setFlags(mParams.mSkipIfStorageLow ? ArtFlags.FLAG_SKIP_IF_STORAGE_LOW : 0,
+                                ArtFlags.FLAG_SKIP_IF_STORAGE_LOW)
+                        .build();
+
+        mPrimaryDexopter =
+                new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+    }
+
+    @Test
+    public void testDexopt() throws Exception {
+        PermissionSettings permissionSettings = buildPermissionSettings(
+                buildFsPermission(Process.SYSTEM_UID /* uid */, Process.SYSTEM_UID /* gid */,
+                        false /* isOtherReadable */, true /* isOtherExecutable */),
+                buildFsPermission(Process.SYSTEM_UID /* uid */, SHARED_GID /* gid */,
+                        true /* isOtherReadable */),
+                null /* seContext */);
+
+        // No need to check `generateAppImage`. It is checked in `PrimaryDexopterTest`.
+        ArgumentMatcher<DexoptOptions> dexoptOptionsMatcher = options
+                -> options.compilationReason.equals("install") && options.targetSdkVersion == 123
+                && options.debuggable == mParams.mExpectedIsDebuggable
+                && options.hiddenApiPolicyEnabled == mParams.mExpectedIsHiddenApiPolicyEnabled
+                && options.comments.equals(
+                        String.format("app-version-name:%s,app-version-code:%d,art-version:%d",
+                                APP_VERSION_NAME, APP_VERSION_CODE, ART_VERSION));
+
+        when(mArtd.createCancellationSignal()).thenReturn(mock(IArtdCancellationSignal.class));
+        when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
+
+        // The first one is normal.
+        doReturn(dexoptIsNeeded())
+                .when(mArtd)
+                .getDexoptNeeded("/data/app/foo/base.apk", "arm64", "PCL[]",
+                        mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+        doReturn(createArtdDexoptResult(false /* cancelled */, 100 /* wallTimeMs */,
+                         400 /* cpuTimeMs */, 30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */))
+                .when(mArtd)
+                .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm64",
+                                mParams.mExpectedIsInDalvikCache, permissionSettings)),
+                        eq("/data/app/foo/base.apk"), eq("arm64"), eq("PCL[]"),
+                        eq(mParams.mExpectedCompilerFilter), any() /* profile */,
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any());
+
+        // The second one fails on `dexopt`.
+        doReturn(dexoptIsNeeded())
+                .when(mArtd)
+                .getDexoptNeeded("/data/app/foo/base.apk", "arm", "PCL[]",
+                        mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+        doThrow(ServiceSpecificException.class)
+                .when(mArtd)
+                .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm",
+                                mParams.mExpectedIsInDalvikCache, permissionSettings)),
+                        eq("/data/app/foo/base.apk"), eq("arm"), eq("PCL[]"),
+                        eq(mParams.mExpectedCompilerFilter), any() /* profile */,
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any());
+
+        // The third one doesn't need dexopt.
+        doReturn(dexoptIsNotNeeded())
+                .when(mArtd)
+                .getDexoptNeeded("/data/app/foo/split_0.apk", "arm64", "PCL[base.apk]",
+                        mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+
+        // The fourth one is normal.
+        doReturn(dexoptIsNeeded())
+                .when(mArtd)
+                .getDexoptNeeded("/data/app/foo/split_0.apk", "arm", "PCL[base.apk]",
+                        mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+        doReturn(createArtdDexoptResult(false /* cancelled */, 200 /* wallTimeMs */,
+                         200 /* cpuTimeMs */, 10000 /* sizeBytes */, 0 /* sizeBeforeBytes */))
+                .when(mArtd)
+                .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/split_0.apk", "arm",
+                                mParams.mExpectedIsInDalvikCache, permissionSettings)),
+                        eq("/data/app/foo/split_0.apk"), eq("arm"), eq("PCL[base.apk]"),
+                        eq(mParams.mExpectedCompilerFilter), any() /* profile */,
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any());
+
+        assertThat(mPrimaryDexopter.dexopt())
+                .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality())
+                .containsExactly(
+                        DexContainerFileDexoptResult.create("/data/app/foo/base.apk",
+                                true /* isPrimaryAbi */, "arm64-v8a",
+                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_PERFORMED,
+                                100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */,
+                                30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create("/data/app/foo/base.apk",
+                                false /* isPrimaryAbi */, "armeabi-v7a",
+                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_FAILED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create("/data/app/foo/split_0.apk",
+                                true /* isPrimaryAbi */, "arm64-v8a",
+                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_SKIPPED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create("/data/app/foo/split_0.apk",
+                                false /* isPrimaryAbi */, "armeabi-v7a",
+                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_PERFORMED,
+                                200 /* dex2oatWallTimeMillis */, 200 /* dex2oatCpuTimeMillis */,
+                                10000 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */));
+
+        // Verify that there are no more calls than the ones above.
+        verify(mArtd, times(3))
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        any());
+    }
+
+    private static class Params {
+        // Package information.
+        public boolean mIsSystem = false;
+        public boolean mIsUpdatedSystemApp = false;
+        public boolean mIsIncrementalFsPath = false;
+        public int mHiddenApiEnforcementPolicy = ApplicationInfo.HIDDEN_API_ENFORCEMENT_ENABLED;
+        public boolean mIsVmSafeMode = false;
+        public boolean mIsDebuggable = false;
+        public boolean mIsSystemUi = false;
+        public boolean mIsLauncher = false;
+        public boolean mIsUseEmbeddedDex = false;
+
+        // Options.
+        public String mRequestedCompilerFilter = "verify";
+        public boolean mForce = false;
+        public boolean mShouldDowngrade = false;
+        public boolean mSkipIfStorageLow = false;
+
+        // System properties.
+        public boolean mAlwaysDebuggable = false;
+
+        // Expectations.
+        public String mExpectedCompilerFilter = "verify";
+        public int mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+                | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
+        public boolean mExpectedIsInDalvikCache = false;
+        public boolean mExpectedIsDebuggable = false;
+        public boolean mExpectedIsHiddenApiPolicyEnabled = true;
+
+        public String toString() {
+            return String.format("isSystem=%b,"
+                            + "isUpdatedSystemApp=%b,"
+                            + "isIncrementalFsPath=%b,"
+                            + "mHiddenApiEnforcementPolicy=%d,"
+                            + "isVmSafeMode=%b,"
+                            + "isDebuggable=%b,"
+                            + "isSystemUi=%b,"
+                            + "isLauncher=%b,"
+                            + "isUseEmbeddedDex=%b,"
+                            + "requestedCompilerFilter=%s,"
+                            + "force=%b,"
+                            + "shouldDowngrade=%b,"
+                            + "mSkipIfStorageLow=%b,"
+                            + "alwaysDebuggable=%b"
+                            + " => "
+                            + "targetCompilerFilter=%s,"
+                            + "expectedDexoptTrigger=%d,"
+                            + "expectedIsInDalvikCache=%b,"
+                            + "expectedIsDebuggable=%b,"
+                            + "expectedIsHiddenApiPolicyEnabled=%b",
+                    mIsSystem, mIsUpdatedSystemApp, mIsIncrementalFsPath,
+                    mHiddenApiEnforcementPolicy, mIsVmSafeMode, mIsDebuggable, mIsSystemUi,
+                    mIsLauncher, mIsUseEmbeddedDex, mRequestedCompilerFilter, mForce,
+                    mShouldDowngrade, mSkipIfStorageLow, mAlwaysDebuggable, mExpectedCompilerFilter,
+                    mExpectedDexoptTrigger, mExpectedIsInDalvikCache, mExpectedIsDebuggable,
+                    mExpectedIsHiddenApiPolicyEnabled);
+        }
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
new file mode 100644
index 0000000..f02ebf0
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
@@ -0,0 +1,670 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Process;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.testing.TestingUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
+    private final String mDexPath = "/data/app/foo/base.apk";
+    private final ProfilePath mRefProfile =
+            AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary");
+    private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath);
+    private final ProfilePath mDmProfile = AidlUtils.buildProfilePathForDm(mDexPath);
+    private final DexMetadataPath mDmFile = AidlUtils.buildDexMetadataPath(mDexPath);
+    private final OutputProfile mPublicOutputProfile = AidlUtils.buildOutputProfileForPrimary(
+            PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, true /* isOtherReadable */);
+    private final OutputProfile mPrivateOutputProfile = AidlUtils.buildOutputProfileForPrimary(
+            PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, false /* isOtherReadable */);
+
+    private final String mSplit0DexPath = "/data/app/foo/split_0.apk";
+    private final ProfilePath mSplit0RefProfile =
+            AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "split_0.split");
+
+    private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
+    private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.COMPILER_FILTER_IS_SAME
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
+    private final int mForceDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE
+            | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+            | DexoptTrigger.NEED_EXTRACTION;
+
+    private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions();
+
+    private final ArtdDexoptResult mArtdDexoptResult =
+            createArtdDexoptResult(false /* cancelled */);
+
+    private DexoptParams mDexoptParams =
+            new DexoptParams.Builder("install").setCompilerFilter("speed-profile").build();
+
+    private PrimaryDexopter mPrimaryDexopter;
+
+    private List<ProfilePath> mUsedProfiles;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // By default, none of the profiles are usable.
+        lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(false);
+        lenient().when(mArtd.copyAndRewriteProfile(any(), any(), any())).thenReturn(false);
+
+        // By default, no DM file exists.
+        lenient().when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
+
+        // Dexopt is by default needed and successful.
+        lenient()
+                .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
+                .thenReturn(dexoptIsNeeded());
+        lenient()
+                .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(),
+                        any(), any()))
+                .thenReturn(mArtdDexoptResult);
+
+        lenient()
+                .when(mArtd.createCancellationSignal())
+                .thenReturn(mock(IArtdCancellationSignal.class));
+
+        mPrimaryDexopter =
+                new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+        mUsedProfiles = new ArrayList<>();
+    }
+
+    @Test
+    public void testDexoptInputVdex() throws Exception {
+        // null.
+        doReturn(dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR))
+                .when(mArtd)
+                .getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), any(), anyInt());
+        doReturn(mArtdDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), any(),
+                        anyInt(), any(), any());
+
+        // ArtifactsPath, isInDalvikCache=true.
+        doReturn(dexoptIsNeeded(ArtifactsLocation.DALVIK_CACHE))
+                .when(mArtd)
+                .getDexoptNeeded(eq(mDexPath), eq("arm"), any(), any(), anyInt());
+        doReturn(mArtdDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mDexPath), eq("arm"), any(), any(), any(),
+                        deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                mDexPath, "arm", true /* isInDalvikCache */))),
+                        any(), anyInt(), any(), any());
+
+        // ArtifactsPath, isInDalvikCache=false.
+        doReturn(dexoptIsNeeded(ArtifactsLocation.NEXT_TO_DEX))
+                .when(mArtd)
+                .getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), any(), anyInt());
+        doReturn(mArtdDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mSplit0DexPath), eq("arm64"), any(), any(), any(),
+                        deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                mSplit0DexPath, "arm64", false /* isInDalvikCache */))),
+                        any(), anyInt(), any(), any());
+
+        // DexMetadataPath.
+        doReturn(dexoptIsNeeded(ArtifactsLocation.DM))
+                .when(mArtd)
+                .getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), any(), anyInt());
+        doReturn(mArtdDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(), isNull(), any(),
+                        anyInt(), any(), any());
+
+        mPrimaryDexopter.dexopt();
+    }
+
+    @Test
+    public void testDexoptDm() throws Exception {
+        lenient()
+                .when(mArtd.getDmFileVisibility(deepEq(mDmFile)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd, times(2))
+                .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), deepEq(mDmFile),
+                        anyInt(),
+                        argThat(dexoptOptions
+                                -> dexoptOptions.compilationReason.equals("install-dm")),
+                        any());
+        verify(mArtd, times(2))
+                .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), isNull(),
+                        anyInt(),
+                        argThat(dexoptOptions -> dexoptOptions.compilationReason.equals("install")),
+                        any());
+    }
+
+    @Test
+    public void testDexoptUsesRefProfile() throws Exception {
+        makeProfileUsable(mRefProfile);
+        when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        // Other profiles are also usable, but they shouldn't be used.
+        makeProfileUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile,
+                false /* isOtherReadable */, true /* generateAppImage */);
+
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile,
+                false /* isOtherReadable */, true /* generateAppImage */);
+
+        // There is no profile for split 0, so it should fall back to "verify".
+        verify(mArtd).getDexoptNeeded(
+                eq(mSplit0DexPath), eq("arm64"), any(), eq("verify"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "verify");
+
+        verify(mArtd).getDexoptNeeded(
+                eq(mSplit0DexPath), eq("arm"), any(), eq("verify"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "verify");
+
+        verifyProfileNotUsed(mPrebuiltProfile);
+        verifyProfileNotUsed(mDmProfile);
+    }
+
+    @Test
+    public void testDexoptUsesPublicRefProfile() throws Exception {
+        // The ref profile is usable and public.
+        makeProfileUsable(mRefProfile);
+        when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        // Other profiles are also usable, but they shouldn't be used.
+        makeProfileUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        mPrimaryDexopter.dexopt();
+
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile,
+                true /* isOtherReadable */, true /* generateAppImage */);
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile,
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        verifyProfileNotUsed(mPrebuiltProfile);
+        verifyProfileNotUsed(mDmProfile);
+    }
+
+    @Test
+    public void testDexoptUsesPrebuiltProfile() throws Exception {
+        makeProfileNotUsable(mRefProfile);
+        makeProfileUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        mPrimaryDexopter.dexopt();
+
+        InOrder inOrder = inOrder(mArtd);
+
+        inOrder.verify(mArtd).copyAndRewriteProfile(
+                deepEq(mPrebuiltProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
+
+        checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm64",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+        checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        inOrder.verify(mArtd).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath));
+
+        verifyProfileNotUsed(mRefProfile);
+        verifyProfileNotUsed(mDmProfile);
+    }
+
+    @Test
+    public void testDexoptMergesProfiles() throws Exception {
+        when(mPkgState.getStateForUser(eq(UserHandle.of(0)))).thenReturn(mPkgUserStateInstalled);
+        when(mPkgState.getStateForUser(eq(UserHandle.of(2)))).thenReturn(mPkgUserStateInstalled);
+
+        when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(true);
+
+        makeProfileUsable(mRefProfile);
+        when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        InOrder inOrder = inOrder(mArtd);
+
+        inOrder.verify(mArtd).mergeProfiles(
+                deepEq(List.of(AidlUtils.buildProfilePathForPrimaryCur(
+                                       0 /* userId */, PKG_NAME, "primary"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                2 /* userId */, PKG_NAME, "primary"))),
+                deepEq(mRefProfile), deepEq(mPrivateOutputProfile), deepEq(List.of(mDexPath)),
+                deepEq(mMergeProfileOptions));
+
+        // It should use `mBetterOrSameDexoptTrigger` and the merged profile for both ISAs.
+        inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), eq("speed-profile"),
+                eq(mBetterOrSameDexoptTrigger));
+        checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm64",
+                ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath),
+                false /* isOtherReadable */, true /* generateAppImage */);
+
+        inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm"), any(), eq("speed-profile"),
+                eq(mBetterOrSameDexoptTrigger));
+        checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm",
+                ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath),
+                false /* isOtherReadable */, true /* generateAppImage */);
+
+        inOrder.verify(mArtd).commitTmpProfile(deepEq(mPrivateOutputProfile.profilePath));
+
+        inOrder.verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME, "primary")));
+        inOrder.verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(2 /* userId */, PKG_NAME, "primary")));
+    }
+
+    @Test
+    public void testDexoptMergesProfilesMergeFailed() throws Exception {
+        when(mPkgState.getStateForUser(eq(UserHandle.of(0)))).thenReturn(mPkgUserStateInstalled);
+        when(mPkgState.getStateForUser(eq(UserHandle.of(2)))).thenReturn(mPkgUserStateInstalled);
+
+        when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
+
+        makeProfileUsable(mRefProfile);
+        when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        // It should still use "speed-profile", but with the existing reference profile only.
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile,
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile,
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        verify(mArtd, never()).deleteProfile(any());
+        verify(mArtd, never()).commitTmpProfile(any());
+    }
+
+    @Test
+    public void testDexoptUsesDmProfile() throws Exception {
+        makeProfileNotUsable(mRefProfile);
+        makeProfileNotUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd).copyAndRewriteProfile(
+                deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
+
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        verifyProfileNotUsed(mRefProfile);
+        verifyProfileNotUsed(mPrebuiltProfile);
+    }
+
+    @Test
+    public void testDexoptDeletesProfileOnFailure() throws Exception {
+        makeProfileNotUsable(mRefProfile);
+        makeProfileNotUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        when(mArtd.dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
+                     any(), any()))
+                .thenThrow(ServiceSpecificException.class);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd).deleteProfile(
+                deepEq(ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath)));
+        verify(mArtd, never()).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath));
+    }
+
+    @Test
+    public void testDexoptNeedsToBeShared() throws Exception {
+        when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mDexPath)))
+                .thenReturn(true);
+        when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mSplit0DexPath)))
+                .thenReturn(true);
+
+        // The ref profile is usable but shouldn't be used.
+        makeProfileUsable(mRefProfile);
+
+        makeProfileNotUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        // The existing artifacts are private.
+        when(mArtd.getArtifactsVisibility(
+                     argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd).copyAndRewriteProfile(
+                deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
+
+        // It should re-compile anyway.
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "speed");
+        checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "speed");
+
+        verifyProfileNotUsed(mRefProfile);
+        verifyProfileNotUsed(mPrebuiltProfile);
+    }
+
+    @Test
+    public void testDexoptNeedsToBeSharedArtifactsArePublic() throws Exception {
+        // Same setup as above, but the existing artifacts are public.
+        when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mDexPath)))
+                .thenReturn(true);
+        when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mSplit0DexPath)))
+                .thenReturn(true);
+
+        makeProfileUsable(mRefProfile);
+        makeProfileNotUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+        when(mArtd.getArtifactsVisibility(
+                     argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        // It should use the default dexopt trigger.
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+    }
+
+    @Test
+    public void testDexoptUsesProfileForSplit() throws Exception {
+        makeProfileUsable(mSplit0RefProfile);
+        when(mArtd.getProfileVisibility(deepEq(mSplit0RefProfile)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), eq("speed-profile"),
+                eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mSplit0DexPath, "arm64", mSplit0RefProfile,
+                false /* isOtherReadable */, false /* generateAppImage */);
+
+        verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), eq("speed-profile"),
+                eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mSplit0DexPath, "arm", mSplit0RefProfile,
+                false /* isOtherReadable */, false /* generateAppImage */);
+    }
+
+    @Test
+    public void testDexoptCancelledBeforeDexopt() throws Exception {
+        mCancellationSignal.cancel();
+
+        var artdCancellationSignal = mock(IArtdCancellationSignal.class);
+        when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal);
+
+        doAnswer(invocation -> {
+            verify(artdCancellationSignal).cancel();
+            return createArtdDexoptResult(true /* cancelled */);
+        })
+                .when(mArtd)
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        same(artdCancellationSignal));
+
+        // The result should only contain one element: the result of the first file with
+        // DEXOPT_CANCELLED.
+        assertThat(mPrimaryDexopter.dexopt()
+                           .stream()
+                           .map(DexContainerFileDexoptResult::getStatus)
+                           .collect(Collectors.toList()))
+                .containsExactly(DexoptResult.DEXOPT_CANCELLED);
+
+        // It shouldn't continue after being cancelled on the first file.
+        verify(mArtd, times(1)).createCancellationSignal();
+        verify(mArtd, times(1))
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        any());
+    }
+
+    @Test
+    public void testDexoptCancelledDuringDexopt() throws Exception {
+        Semaphore dexoptStarted = new Semaphore(0);
+        Semaphore dexoptCancelled = new Semaphore(0);
+        final long TIMEOUT_SEC = 10;
+
+        var artdCancellationSignal = mock(IArtdCancellationSignal.class);
+        when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal);
+
+        doAnswer(invocation -> {
+            dexoptStarted.release();
+            assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+            return createArtdDexoptResult(true /* cancelled */);
+        })
+                .when(mArtd)
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        same(artdCancellationSignal));
+        doAnswer(invocation -> {
+            dexoptCancelled.release();
+            return null;
+        })
+                .when(artdCancellationSignal)
+                .cancel();
+
+        Future<List<DexContainerFileDexoptResult>> results =
+                ForkJoinPool.commonPool().submit(() -> { return mPrimaryDexopter.dexopt(); });
+
+        assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+        mCancellationSignal.cancel();
+
+        assertThat(results.get()
+                           .stream()
+                           .map(DexContainerFileDexoptResult::getStatus)
+                           .collect(Collectors.toList()))
+                .containsExactly(DexoptResult.DEXOPT_CANCELLED);
+
+        // It shouldn't continue after being cancelled on the first file.
+        verify(mArtd, times(1)).createCancellationSignal();
+        verify(mArtd, times(1))
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        any());
+    }
+
+    @Test
+    public void testDexoptBaseApk() throws Exception {
+        mDexoptParams =
+                new DexoptParams.Builder("install")
+                        .setCompilerFilter("speed-profile")
+                        .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                        .setSplitName(null)
+                        .build();
+        mPrimaryDexopter =
+                new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd, times(2))
+                .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
+                        any(), any());
+        verify(mArtd, never())
+                .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), any(),
+                        anyInt(), any(), any());
+    }
+
+    @Test
+    public void testDexoptSplitApk() throws Exception {
+        mDexoptParams =
+                new DexoptParams.Builder("install")
+                        .setCompilerFilter("speed-profile")
+                        .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                        .setSplitName("split_0")
+                        .build();
+        mPrimaryDexopter =
+                new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd, never())
+                .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
+                        any(), any());
+        verify(mArtd, times(2))
+                .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), any(),
+                        anyInt(), any(), any());
+    }
+
+    @Test
+    public void testDexoptStorageLow() throws Exception {
+        when(mStorageManager.getAllocatableBytes(any())).thenReturn(1l, 0l, 0l, 1l);
+
+        mDexoptParams =
+                new DexoptParams.Builder("install")
+                        .setCompilerFilter("speed-profile")
+                        .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SKIP_IF_STORAGE_LOW)
+                        .build();
+        mPrimaryDexopter =
+                new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        assertThat(results.get(0).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+        assertThat(results.get(0).isSkippedDueToStorageLow()).isFalse();
+        assertThat(results.get(1).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
+        assertThat(results.get(1).isSkippedDueToStorageLow()).isTrue();
+        assertThat(results.get(2).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
+        assertThat(results.get(2).isSkippedDueToStorageLow()).isTrue();
+        assertThat(results.get(3).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+        assertThat(results.get(3).isSkippedDueToStorageLow()).isFalse();
+
+        verify(mArtd, times(2))
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        any());
+    }
+
+    private void checkDexoptWithProfile(IArtd artd, String dexPath, String isa, ProfilePath profile,
+            boolean isOtherReadable, boolean generateAppImage) throws Exception {
+        artd.dexopt(argThat(artifacts
+                            -> artifacts.permissionSettings.fileFsPermission.isOtherReadable
+                                    == isOtherReadable),
+                eq(dexPath), eq(isa), any(), eq("speed-profile"), deepEq(profile), any(), any(),
+                anyInt(),
+                argThat(dexoptOptions -> dexoptOptions.generateAppImage == generateAppImage),
+                any());
+    }
+
+    private void checkDexoptWithNoProfile(
+            IArtd artd, String dexPath, String isa, String compilerFilter) throws Exception {
+        artd.dexopt(
+                argThat(artifacts
+                        -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == true),
+                eq(dexPath), eq(isa), any(), eq(compilerFilter), isNull(), any(), any(), anyInt(),
+                argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
+    }
+
+    private void verifyProfileNotUsed(ProfilePath profile) throws Exception {
+        assertThat(mUsedProfiles)
+                .comparingElementsUsing(TestingUtils.<ProfilePath>deepEquality())
+                .doesNotContain(profile);
+    }
+
+    private void makeProfileUsable(ProfilePath profile) throws Exception {
+        lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenAnswer(invocation -> {
+            mUsedProfiles.add(invocation.<ProfilePath>getArgument(0));
+            return true;
+        });
+        lenient()
+                .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any()))
+                .thenAnswer(invocation -> {
+                    mUsedProfiles.add(invocation.<ProfilePath>getArgument(0));
+                    return true;
+                });
+    }
+
+    private void makeProfileNotUsable(ProfilePath profile) throws Exception {
+        lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenReturn(false);
+        lenient()
+                .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any()))
+                .thenReturn(false);
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java
new file mode 100644
index 0000000..94e0b48
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.same;
+
+import android.os.CancellationSignal;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+
+import com.android.modules.utils.pm.PackageStateModulesUtils;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageUserState;
+
+import dalvik.system.PathClassLoader;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PrimaryDexopterTestBase {
+    protected static final String PKG_NAME = "com.example.foo";
+    protected static final int UID = 12345;
+    protected static final int SHARED_GID = UserHandle.getSharedAppGid(UID);
+    protected static final long ART_VERSION = 331413030l;
+    protected static final String APP_VERSION_NAME = "12.34.56";
+    protected static final long APP_VERSION_CODE = 1536036288l;
+
+    @Rule
+    public StaticMockitoRule mockitoRule = new StaticMockitoRule(
+            SystemProperties.class, Constants.class, PackageStateModulesUtils.class);
+
+    @Mock protected PrimaryDexopter.Injector mInjector;
+    @Mock protected IArtd mArtd;
+    @Mock protected UserManager mUserManager;
+    @Mock protected DexUseManagerLocal mDexUseManager;
+    @Mock protected StorageManager mStorageManager;
+    protected PackageState mPkgState;
+    protected AndroidPackage mPkg;
+    protected PackageUserState mPkgUserStateNotInstalled;
+    protected PackageUserState mPkgUserStateInstalled;
+    protected CancellationSignal mCancellationSignal;
+
+    @Before
+    public void setUp() throws Exception {
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+        lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
+        lenient().when(mInjector.isLauncherPackage(any())).thenReturn(false);
+        lenient().when(mInjector.getUserManager()).thenReturn(mUserManager);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+        lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager);
+        lenient().when(mInjector.getArtVersion()).thenReturn(ART_VERSION);
+
+        lenient()
+                .when(SystemProperties.get("dalvik.vm.systemuicompilerfilter"))
+                .thenReturn("speed");
+        lenient()
+                .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
+                .thenReturn(false);
+        lenient().when(SystemProperties.get("dalvik.vm.appimageformat")).thenReturn("lz4");
+        lenient().when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed");
+
+        // No ISA translation.
+        lenient()
+                .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+                .thenReturn("");
+
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+        lenient()
+                .when(mUserManager.getUserHandles(anyBoolean()))
+                .thenReturn(List.of(UserHandle.of(0), UserHandle.of(1), UserHandle.of(2)));
+
+        lenient().when(mDexUseManager.isPrimaryDexUsedByOtherApps(any(), any())).thenReturn(false);
+
+        lenient().when(mStorageManager.getAllocatableBytes(any())).thenReturn(1l);
+
+        mPkgUserStateNotInstalled = createPackageUserState(false /* installed */);
+        mPkgUserStateInstalled = createPackageUserState(true /* installed */);
+        mPkgState = createPackageState();
+        mPkg = mPkgState.getAndroidPackage();
+        mCancellationSignal = new CancellationSignal();
+    }
+
+    private AndroidPackage createPackage() {
+        // This package has the base APK and one split APK that has code.
+        AndroidPackage pkg = mock(AndroidPackage.class);
+        var baseSplit = mock(AndroidPackageSplit.class);
+        lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk");
+        lenient().when(baseSplit.isHasCode()).thenReturn(true);
+        lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+
+        var split0 = mock(AndroidPackageSplit.class);
+        lenient().when(split0.getName()).thenReturn("split_0");
+        lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
+        lenient().when(split0.isHasCode()).thenReturn(true);
+
+        var split1 = mock(AndroidPackageSplit.class);
+        lenient().when(split1.getName()).thenReturn("split_1");
+        lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
+        lenient().when(split1.isHasCode()).thenReturn(false);
+
+        var splits = List.of(baseSplit, split0, split1);
+        lenient().when(pkg.getSplits()).thenReturn(splits);
+
+        lenient().when(pkg.isVmSafeMode()).thenReturn(false);
+        lenient().when(pkg.isDebuggable()).thenReturn(false);
+        lenient().when(pkg.getTargetSdkVersion()).thenReturn(123);
+        lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false);
+        lenient().when(pkg.isNonSdkApiRequested()).thenReturn(false);
+        lenient().when(pkg.getVersionName()).thenReturn(APP_VERSION_NAME);
+        lenient().when(pkg.getLongVersionCode()).thenReturn(APP_VERSION_CODE);
+        return pkg;
+    }
+
+    private PackageState createPackageState() {
+        PackageState pkgState = mock(PackageState.class);
+        lenient().when(pkgState.getPackageName()).thenReturn(PKG_NAME);
+        lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
+        lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
+        lenient().when(pkgState.isSystem()).thenReturn(false);
+        lenient().when(pkgState.isUpdatedSystemApp()).thenReturn(false);
+        lenient().when(pkgState.getAppId()).thenReturn(UID);
+        lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(new ArrayList<>());
+        lenient().when(pkgState.getStateForUser(any())).thenReturn(mPkgUserStateNotInstalled);
+        AndroidPackage pkg = createPackage();
+        lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+        lenient()
+                .when(PackageStateModulesUtils.isLoadableInOtherProcesses(
+                        same(pkgState), anyBoolean()))
+                .thenReturn(false);
+        return pkgState;
+    }
+
+    private PackageUserState createPackageUserState(boolean isInstalled) {
+        PackageUserState pkgUserState = mock(PackageUserState.class);
+        lenient().when(pkgUserState.isInstalled()).thenReturn(isInstalled);
+        return pkgUserState;
+    }
+
+    protected GetDexoptNeededResult dexoptIsNotNeeded() {
+        var result = new GetDexoptNeededResult();
+        result.isDexoptNeeded = false;
+        return result;
+    }
+
+    protected GetDexoptNeededResult dexoptIsNeeded() {
+        return dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR);
+    }
+
+    protected GetDexoptNeededResult dexoptIsNeeded(@ArtifactsLocation byte location) {
+        var result = new GetDexoptNeededResult();
+        result.isDexoptNeeded = true;
+        result.artifactsLocation = location;
+        if (location != ArtifactsLocation.NONE_OR_ERROR) {
+            result.isVdexUsable = true;
+        }
+        return result;
+    }
+
+    protected ArtdDexoptResult createArtdDexoptResult(boolean cancelled, long wallTimeMs,
+            long cpuTimeMs, long sizeBytes, long sizeBeforeBytes) {
+        var result = new ArtdDexoptResult();
+        result.cancelled = cancelled;
+        result.wallTimeMs = wallTimeMs;
+        result.cpuTimeMs = cpuTimeMs;
+        result.sizeBytes = sizeBytes;
+        result.sizeBeforeBytes = sizeBeforeBytes;
+        return result;
+    }
+
+    protected ArtdDexoptResult createArtdDexoptResult(boolean cancelled) {
+        return createArtdDexoptResult(cancelled, 0 /* wallTimeMs */, 0 /* cpuTimeMs */,
+                0 /* sizeBytes */, 0 /* sizeBeforeBytes */);
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java b/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java
new file mode 100644
index 0000000..55fd0b4
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 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
+ */
+
+package com.android.server.art;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+import android.os.SystemProperties;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.testing.StaticMockitoRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ReasonMappingTest {
+    @Rule public StaticMockitoRule mockitoRule = new StaticMockitoRule(SystemProperties.class);
+
+    @Test
+    public void testGetCompilerFilterForReason() {
+        when(SystemProperties.get("pm.dexopt.foo")).thenReturn("speed");
+        assertThat(ReasonMapping.getCompilerFilterForReason("foo")).isEqualTo("speed");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetCompilerFilterForReasonInvalidFilter() throws Exception {
+        when(SystemProperties.get("pm.dexopt.foo")).thenReturn("invalid-filter");
+        ReasonMapping.getCompilerFilterForReason("foo");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetCompilerFilterForReasonInvalidReason() throws Exception {
+        ReasonMapping.getCompilerFilterForReason("foo");
+    }
+
+    @Test
+    public void testGetCompilerFilterForShared() {
+        when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed");
+        assertThat(ReasonMapping.getCompilerFilterForShared()).isEqualTo("speed");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetCompilerFilterForSharedProfileGuidedFilter() throws Exception {
+        when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed-profile");
+        ReasonMapping.getCompilerFilterForShared();
+    }
+
+    @Test
+    public void testGetPriorityClassForReason() throws Exception {
+        assertThat(ReasonMapping.getPriorityClassForReason("install"))
+                .isEqualTo(ArtFlags.PRIORITY_INTERACTIVE);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetPriorityClassForReasonInvalidReason() throws Exception {
+        ReasonMapping.getPriorityClassForReason("foo");
+    }
+
+    @Test
+    public void testGetConcurrencyForReason() {
+        when(SystemProperties.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt()))
+                .thenReturn(3);
+        assertThat(ReasonMapping.getConcurrencyForReason("bg-dexopt")).isEqualTo(3);
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
new file mode 100644
index 0000000..5d8661f
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.CancellationSignal;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.art.testing.TestingUtils;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageStateUnserialized;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SecondaryDexopterTest {
+    private static final String PKG_NAME = "com.example.foo";
+    private static final int APP_ID = 12345;
+    private static final UserHandle USER_HANDLE = UserHandle.of(2);
+    private static final int UID = USER_HANDLE.getUid(APP_ID);
+    private static final String APP_DATA_DIR = "/data/user/2/" + PKG_NAME;
+    private static final String DEX_1 = APP_DATA_DIR + "/1.apk";
+    private static final String DEX_2 = APP_DATA_DIR + "/2.apk";
+    private static final String DEX_3 = APP_DATA_DIR + "/3.apk";
+
+    private final DexoptParams mDexoptParams =
+            new DexoptParams.Builder("bg-dexopt")
+                    .setCompilerFilter("speed-profile")
+                    .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX)
+                    .build();
+
+    private final ProfilePath mDex1RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_1);
+    private final ProfilePath mDex1CurProfile = AidlUtils.buildProfilePathForSecondaryCur(DEX_1);
+    private final ProfilePath mDex2RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_2);
+    private final ProfilePath mDex3RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_3);
+    private final OutputProfile mDex1PrivateOutputProfile =
+            AidlUtils.buildOutputProfileForSecondary(DEX_1, UID, UID, false /* isOtherReadable */);
+
+    private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
+    private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.COMPILER_FILTER_IS_SAME
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
+
+    private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions();
+
+    @Rule
+    public StaticMockitoRule mockitoRule =
+            new StaticMockitoRule(SystemProperties.class, Constants.class);
+
+    @Mock private SecondaryDexopter.Injector mInjector;
+    @Mock private IArtd mArtd;
+    @Mock private DexUseManagerLocal mDexUseManager;
+    private PackageState mPkgState;
+    private AndroidPackage mPkg;
+    private CancellationSignal mCancellationSignal;
+
+    private SecondaryDexopter mSecondaryDexopter;
+
+    @Before
+    public void setUp() throws Exception {
+        lenient()
+                .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
+                .thenReturn(false);
+        lenient().when(SystemProperties.get("dalvik.vm.appimageformat")).thenReturn("lz4");
+        lenient().when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed");
+
+        // No ISA translation.
+        lenient()
+                .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+                .thenReturn("");
+
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+        lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
+        lenient().when(mInjector.isLauncherPackage(any())).thenReturn(false);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+
+        List<DetailedSecondaryDexInfo> secondaryDexInfo = createSecondaryDexInfo();
+        lenient()
+                .when(mDexUseManager.getFilteredDetailedSecondaryDexInfo(eq(PKG_NAME)))
+                .thenReturn(secondaryDexInfo);
+
+        mPkgState = createPackageState();
+        mPkg = mPkgState.getAndroidPackage();
+        mCancellationSignal = new CancellationSignal();
+
+        prepareProfiles();
+
+        // Dexopt is always needed and successful.
+        lenient()
+                .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
+                .thenReturn(dexoptIsNeeded());
+        lenient()
+                .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(),
+                        any(), any()))
+                .thenReturn(createArtdDexoptResult());
+
+        lenient()
+                .when(mArtd.createCancellationSignal())
+                .thenReturn(mock(IArtdCancellationSignal.class));
+
+        mSecondaryDexopter = new SecondaryDexopter(
+                mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+    }
+
+    @Test
+    public void testDexopt() throws Exception {
+        assertThat(mSecondaryDexopter.dexopt())
+                .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality())
+                .containsExactly(
+                        DexContainerFileDexoptResult.create(DEX_1, true /* isPrimaryAbi */,
+                                "arm64-v8a", "speed-profile", DexoptResult.DEXOPT_PERFORMED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create(DEX_2, true /* isPrimaryAbi */,
+                                "arm64-v8a", "speed", DexoptResult.DEXOPT_PERFORMED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create(DEX_2, false /* isPrimaryAbi */,
+                                "armeabi-v7a", "speed", DexoptResult.DEXOPT_PERFORMED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create(DEX_3, true /* isPrimaryAbi */,
+                                "arm64-v8a", "verify", DexoptResult.DEXOPT_PERFORMED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */));
+
+        // It should use profile for dex 1.
+
+        verify(mArtd).mergeProfiles(deepEq(List.of(mDex1CurProfile)), deepEq(mDex1RefProfile),
+                deepEq(mDex1PrivateOutputProfile), deepEq(List.of(DEX_1)),
+                deepEq(mMergeProfileOptions));
+
+        verify(mArtd).getDexoptNeeded(
+                eq(DEX_1), eq("arm64"), any(), eq("speed-profile"), eq(mBetterOrSameDexoptTrigger));
+        checkDexoptWithPrivateProfile(verify(mArtd), DEX_1, "arm64",
+                ProfilePath.tmpProfilePath(mDex1PrivateOutputProfile.profilePath), "CLC_FOR_DEX_1");
+
+        verify(mArtd).commitTmpProfile(deepEq(mDex1PrivateOutputProfile.profilePath));
+
+        verify(mArtd).deleteProfile(deepEq(mDex1CurProfile));
+
+        // It should use "speed" for dex 2 for both ISAs and make the artifacts public.
+
+        verify(mArtd, never()).isProfileUsable(deepEq(mDex2RefProfile), any());
+        verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex2RefProfile), any(), any(), any());
+
+        verify(mArtd).getDexoptNeeded(
+                eq(DEX_2), eq("arm64"), any(), eq("speed"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithNoProfile(
+                verify(mArtd), DEX_2, "arm64", "speed", "CLC_FOR_DEX_2", true /* isPublic */);
+
+        verify(mArtd).getDexoptNeeded(
+                eq(DEX_2), eq("arm"), any(), eq("speed"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithNoProfile(
+                verify(mArtd), DEX_2, "arm", "speed", "CLC_FOR_DEX_2", true /* isPublic */);
+
+        // It should use "verify" for dex 3 and make the artifacts private.
+
+        verify(mArtd, never()).isProfileUsable(deepEq(mDex3RefProfile), any());
+        verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex3RefProfile), any(), any(), any());
+
+        verify(mArtd).getDexoptNeeded(
+                eq(DEX_3), eq("arm64"), isNull(), eq("verify"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithNoProfile(verify(mArtd), DEX_3, "arm64", "verify",
+                null /* classLoaderContext */, false /* isPublic */);
+    }
+
+    private AndroidPackage createPackage() {
+        var pkg = mock(AndroidPackage.class);
+        lenient().when(pkg.isVmSafeMode()).thenReturn(false);
+        lenient().when(pkg.isDebuggable()).thenReturn(false);
+        lenient().when(pkg.getTargetSdkVersion()).thenReturn(123);
+        lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false);
+        lenient().when(pkg.isNonSdkApiRequested()).thenReturn(false);
+        return pkg;
+    }
+
+    private PackageState createPackageState() {
+        var pkgState = mock(PackageState.class);
+        lenient().when(pkgState.getPackageName()).thenReturn(PKG_NAME);
+        lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
+        lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
+        lenient().when(pkgState.getAppId()).thenReturn(APP_ID);
+        lenient().when(pkgState.getSeInfo()).thenReturn("se-info");
+        AndroidPackage pkg = createPackage();
+        lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+        return pkgState;
+    }
+
+    private List<DetailedSecondaryDexInfo> createSecondaryDexInfo() throws Exception {
+        // This should be compiled with profile.
+        var dex1Info = mock(DetailedSecondaryDexInfo.class);
+        lenient().when(dex1Info.dexPath()).thenReturn(DEX_1);
+        lenient().when(dex1Info.userHandle()).thenReturn(USER_HANDLE);
+        lenient().when(dex1Info.classLoaderContext()).thenReturn("CLC_FOR_DEX_1");
+        lenient().when(dex1Info.abiNames()).thenReturn(Set.of("arm64-v8a"));
+        lenient().when(dex1Info.isUsedByOtherApps()).thenReturn(false);
+        lenient().when(dex1Info.isDexFilePublic()).thenReturn(true);
+
+        // This should be compiled without profile because it's used by other apps.
+        var dex2Info = mock(DetailedSecondaryDexInfo.class);
+        lenient().when(dex2Info.dexPath()).thenReturn(DEX_2);
+        lenient().when(dex2Info.userHandle()).thenReturn(USER_HANDLE);
+        lenient().when(dex2Info.classLoaderContext()).thenReturn("CLC_FOR_DEX_2");
+        lenient().when(dex2Info.abiNames()).thenReturn(Set.of("arm64-v8a", "armeabi-v7a"));
+        lenient().when(dex2Info.isUsedByOtherApps()).thenReturn(true);
+        lenient().when(dex2Info.isDexFilePublic()).thenReturn(true);
+
+        // This should be compiled with verify because the class loader context is invalid.
+        var dex3Info = mock(DetailedSecondaryDexInfo.class);
+        lenient().when(dex3Info.dexPath()).thenReturn(DEX_3);
+        lenient().when(dex3Info.userHandle()).thenReturn(USER_HANDLE);
+        lenient().when(dex3Info.classLoaderContext()).thenReturn(null);
+        lenient().when(dex3Info.abiNames()).thenReturn(Set.of("arm64-v8a"));
+        lenient().when(dex3Info.isUsedByOtherApps()).thenReturn(false);
+        lenient().when(dex3Info.isDexFilePublic()).thenReturn(false);
+
+        return List.of(dex1Info, dex2Info, dex3Info);
+    }
+
+    private void prepareProfiles() throws Exception {
+        // Profile for dex file 1 is usable.
+        lenient().when(mArtd.isProfileUsable(deepEq(mDex1RefProfile), any())).thenReturn(true);
+        lenient()
+                .when(mArtd.getProfileVisibility(deepEq(mDex1RefProfile)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        // Profiles for dex file 2 and 3 are also usable, but shouldn't be used.
+        lenient().when(mArtd.isProfileUsable(deepEq(mDex2RefProfile), any())).thenReturn(true);
+        lenient()
+                .when(mArtd.getProfileVisibility(deepEq(mDex2RefProfile)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+        lenient().when(mArtd.isProfileUsable(deepEq(mDex3RefProfile), any())).thenReturn(true);
+        lenient()
+                .when(mArtd.getProfileVisibility(deepEq(mDex3RefProfile)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        lenient().when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(true);
+    }
+
+    private GetDexoptNeededResult dexoptIsNeeded() {
+        var result = new GetDexoptNeededResult();
+        result.isDexoptNeeded = true;
+        result.artifactsLocation = ArtifactsLocation.NONE_OR_ERROR;
+        result.isVdexUsable = false;
+        return result;
+    }
+
+    private ArtdDexoptResult createArtdDexoptResult() {
+        var result = new ArtdDexoptResult();
+        result.cancelled = false;
+        result.wallTimeMs = 0;
+        result.cpuTimeMs = 0;
+        result.sizeBytes = 0;
+        result.sizeBeforeBytes = 0;
+        return result;
+    }
+
+    private void checkDexoptWithPrivateProfile(IArtd artd, String dexPath, String isa,
+            ProfilePath profile, String classLoaderContext) throws Exception {
+        PermissionSettings permissionSettings = buildPermissionSettings(false /* isPublic */);
+        OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(
+                dexPath, isa, false /* isInDalvikCache */, permissionSettings);
+        artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext),
+                eq("speed-profile"), deepEq(profile), any(), isNull() /* dmFile */, anyInt(),
+                argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
+    }
+
+    private void checkDexoptWithNoProfile(IArtd artd, String dexPath, String isa,
+            String compilerFilter, String classLoaderContext, boolean isPublic) throws Exception {
+        PermissionSettings permissionSettings = buildPermissionSettings(isPublic);
+        OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(
+                dexPath, isa, false /* isInDalvikCache */, permissionSettings);
+        artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext),
+                eq(compilerFilter), isNull(), any(), isNull() /* dmFile */, anyInt(),
+                argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
+    }
+
+    private PermissionSettings buildPermissionSettings(boolean isPublic) {
+        FsPermission dirFsPermission = AidlUtils.buildFsPermission(UID /* uid */, UID /* gid */,
+                false /* isOtherReadable */, true /* isOtherExecutable */);
+        FsPermission fileFsPermission =
+                AidlUtils.buildFsPermission(UID /* uid */, UID /* gid */, isPublic);
+        return AidlUtils.buildPermissionSettings(
+                dirFsPermission, fileFsPermission, AidlUtils.buildSeContext("se-info", UID));
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/UtilsTest.java b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
new file mode 100644
index 0000000..bc6ed16
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2022 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
+ */
+
+package com.android.server.art;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.SystemProperties;
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.Utils;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.pkg.PackageState;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ForkJoinPool;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UtilsTest {
+    @Rule
+    public StaticMockitoRule mockitoRule =
+            new StaticMockitoRule(SystemProperties.class, Constants.class);
+
+    @Before
+    public void setUp() throws Exception {
+        lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("arm64");
+        lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86"))).thenReturn("arm");
+
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+    }
+
+    @Test
+    public void testCollectionIsEmptyTrue() {
+        assertThat(Utils.isEmpty(List.of())).isTrue();
+    }
+
+    @Test
+    public void testCollectionIsEmptyFalse() {
+        assertThat(Utils.isEmpty(List.of(1))).isFalse();
+    }
+
+    @Test
+    public void testSparseArrayIsEmptyTrue() {
+        assertThat(Utils.isEmpty(new SparseArray<Integer>())).isTrue();
+    }
+
+    @Test
+    public void testSparseArrayIsEmptyFalse() {
+        SparseArray<Integer> array = new SparseArray<>();
+        array.put(1, 1);
+        assertThat(Utils.isEmpty(array)).isFalse();
+    }
+
+    @Test
+    public void testArrayIsEmptyTrue() {
+        assertThat(Utils.isEmpty(new int[0])).isTrue();
+    }
+
+    @Test
+    public void testArrayIsEmptyFalse() {
+        assertThat(Utils.isEmpty(new int[] {1})).isFalse();
+    }
+
+    @Test
+    public void testGetAllAbis() {
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn("armeabi-v7a");
+        when(pkgState.getSecondaryCpuAbi()).thenReturn("arm64-v8a");
+
+        assertThat(Utils.getAllAbis(pkgState))
+                .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", true /* isPrimaryAbi */),
+                        Utils.Abi.create("arm64-v8a", "arm64", false /* isPrimaryAbi */));
+    }
+
+    @Test
+    public void testGetAllAbisTranslated() {
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64");
+        when(pkgState.getSecondaryCpuAbi()).thenReturn("x86");
+
+        assertThat(Utils.getAllAbis(pkgState))
+                .containsExactly(Utils.Abi.create("arm64-v8a", "arm64", true /* isPrimaryAbi */),
+                        Utils.Abi.create("armeabi-v7a", "arm", false /* isPrimaryAbi */));
+    }
+
+    @Test
+    public void testGetAllAbisPrimaryOnly() {
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn("armeabi-v7a");
+        when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+        assertThat(Utils.getAllAbis(pkgState))
+                .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", true /* isPrimaryAbi */));
+    }
+
+    @Test
+    public void testGetAllAbisNone() {
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn(null);
+        when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+        assertThat(Utils.getAllAbis(pkgState))
+                .containsExactly(Utils.Abi.create("arm64-v8a", "arm64", true /* isPrimaryAbi */));
+
+        // Make sure the result does come from `Constants.getPreferredAbi()` rather than somewhere
+        // else.
+        when(Constants.getPreferredAbi()).thenReturn("armeabi-v7a");
+        assertThat(Utils.getAllAbis(pkgState))
+                .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", true /* isPrimaryAbi */));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetAllAbisInvalidNativeIsa() {
+        lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("x86");
+
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64");
+        when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+        Utils.getAllAbis(pkgState);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetAllAbisUnsupportedTranslation() {
+        lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("");
+
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64");
+        when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+        Utils.getAllAbis(pkgState);
+    }
+
+    @Test
+    public void testImplies() {
+        assertThat(Utils.implies(false, false)).isTrue();
+        assertThat(Utils.implies(false, true)).isTrue();
+        assertThat(Utils.implies(true, false)).isFalse();
+        assertThat(Utils.implies(true, true)).isTrue();
+    }
+
+    @Test
+    public void testCheck() {
+        Utils.check(true);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCheckFailed() throws Exception {
+        Utils.check(false);
+    }
+
+    @Test
+    public void testExecuteAndWait() {
+        Executor executor = ForkJoinPool.commonPool();
+        List<String> results = new ArrayList<>();
+        Utils.executeAndWait(executor, () -> {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+            results.add("foo");
+        });
+        assertThat(results).containsExactly("foo");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testExecuteAndWaitPropagatesException() {
+        Executor executor = ForkJoinPool.commonPool();
+        Utils.executeAndWait(executor, () -> { throw new IllegalArgumentException(); });
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/model/DexoptParamsTest.java b/libartservice/service/javatests/com/android/server/art/model/DexoptParamsTest.java
new file mode 100644
index 0000000..6098641
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/model/DexoptParamsTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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
+ */
+
+package com.android.server.art.model;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DexoptParamsTest {
+    @Test
+    public void testBuild() {
+        new DexoptParams.Builder("install").build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildEmptyReason() {
+        new DexoptParams.Builder("").build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildReasonVdex() {
+        new DexoptParams.Builder("vdex").setCompilerFilter("speed").setPriorityClass(90).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildInvalidCompilerFilter() {
+        new DexoptParams.Builder("install").setCompilerFilter("invalid").build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildInvalidPriorityClass() {
+        new DexoptParams.Builder("install").setPriorityClass(101).build();
+    }
+
+    @Test
+    public void testBuildCustomReason() {
+        new DexoptParams.Builder("custom").setCompilerFilter("speed").setPriorityClass(90).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildCustomReasonEmptyCompilerFilter() {
+        new DexoptParams.Builder("custom").setPriorityClass(ArtFlags.PRIORITY_INTERACTIVE).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildCustomReasonEmptyPriorityClass() {
+        new DexoptParams.Builder("custom").setCompilerFilter("speed").build();
+    }
+
+    @Test
+    public void testSingleSplit() {
+        new DexoptParams.Builder("install")
+                .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                .setSplitName("split_0")
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSingleSplitNoPrimaryFlag() {
+        new DexoptParams.Builder("install")
+                .setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                .setSplitName("split_0")
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSingleSplitSecondaryFlag() {
+        new DexoptParams.Builder("install")
+                .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                        | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                .setSplitName("split_0")
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSingleSplitDependenciesFlag() {
+        new DexoptParams.Builder("install")
+                .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES
+                        | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                .setSplitName("split_0")
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSplitNameNoSingleSplitFlag() {
+        new DexoptParams.Builder("install")
+                .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX)
+                .setSplitName("split_0")
+                .build();
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/testing/MockClock.java b/libartservice/service/javatests/com/android/server/art/testing/MockClock.java
new file mode 100644
index 0000000..7b4b23b
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/MockClock.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.testing;
+
+import android.annotation.NonNull;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.concurrent.RunnableScheduledFuture;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class MockClock {
+    private long mCurrentTimeMs = 0;
+    @NonNull private List<ScheduledExecutor> mExecutors = new ArrayList<>();
+
+    @NonNull
+    public ScheduledExecutor createScheduledExecutor() {
+        var executor = new ScheduledExecutor();
+        mExecutors.add(executor);
+        return executor;
+    }
+
+    public long getCurrentTimeMs() {
+        return mCurrentTimeMs;
+    }
+
+    public void advanceTime(long timeMs) {
+        mCurrentTimeMs += timeMs;
+        for (ScheduledExecutor executor : mExecutors) {
+            executor.notifyUpdate();
+        }
+    }
+
+    public class ScheduledExecutor extends ScheduledThreadPoolExecutor {
+        // The second element of the pair is the scheduled time.
+        @NonNull
+        private PriorityQueue<Pair<RunnableScheduledFuture<?>, Long>> tasks = new PriorityQueue<>(
+                1 /* initialCapacity */, Comparator.comparingLong(pair -> pair.second));
+
+        public ScheduledExecutor() {
+            super(1 /* corePoolSize */);
+        }
+
+        @NonNull
+        public ScheduledFuture<?> schedule(
+                @NonNull Runnable command, long delay, @NonNull TimeUnit unit) {
+            // Use `Long.MAX_VALUE` to prevent the task from being automatically run.
+            var task = (RunnableScheduledFuture<?>) super.schedule(
+                    command, Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+            tasks.add(Pair.create(task, getCurrentTimeMs() + unit.toMillis(delay)));
+            return task;
+        }
+
+        public void notifyUpdate() {
+            while (!tasks.isEmpty()) {
+                Pair<RunnableScheduledFuture<?>, Long> pair = tasks.peek();
+                RunnableScheduledFuture<?> task = pair.first;
+                long scheduledTimeMs = pair.second;
+                if (getCurrentTimeMs() >= scheduledTimeMs) {
+                    if (!task.isDone() && !task.isCancelled()) {
+                        task.run();
+                    }
+                    tasks.poll();
+                    // Remove the task from the queue of the executor. Terminate the executor if
+                    // it's shutdown and the queue is empty.
+                    super.remove(task);
+                } else {
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/testing/OnSuccessRule.java b/libartservice/service/javatests/com/android/server/art/testing/OnSuccessRule.java
new file mode 100644
index 0000000..350a8cb
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/OnSuccessRule.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.testing;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+/** A JUnit rule that invokes a runnable on success. */
+public class OnSuccessRule implements MethodRule {
+    private RunnableThrowingException mRunnable;
+
+    public OnSuccessRule(RunnableThrowingException runnable) {
+        mRunnable = runnable;
+    }
+
+    @Override
+    public Statement apply(Statement base, FrameworkMethod method, Object target) {
+        return new Statement() {
+            public void evaluate() throws Throwable {
+                base.evaluate();
+                mRunnable.run();
+            }
+        };
+    }
+
+    public interface RunnableThrowingException {
+        void run() throws Exception;
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/testing/StaticMockitoRule.java b/libartservice/service/javatests/com/android/server/art/testing/StaticMockitoRule.java
new file mode 100644
index 0000000..595370b
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/StaticMockitoRule.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.testing;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+
+/**
+ * Similar to {@link MockitoRule}, but uses {@StaticMockitoSession}, which allows mocking static
+ * methods.
+ */
+public class StaticMockitoRule implements MethodRule {
+    private Class<?>[] mClasses;
+
+    public StaticMockitoRule(Class<?>... classes) {
+        mClasses = classes;
+    }
+
+    @Override
+    public Statement apply(Statement base, FrameworkMethod method, Object target) {
+        return new Statement() {
+            public void evaluate() throws Throwable {
+                StaticMockitoSessionBuilder builder =
+                        mockitoSession()
+                                .name(target.getClass().getSimpleName() + "." + method.getName())
+                                .initMocks(target)
+                                .strictness(Strictness.STRICT_STUBS);
+
+                for (Class<?> clazz : mClasses) {
+                    builder.mockStatic(clazz);
+                }
+
+                StaticMockitoSession session = builder.startMocking();
+                Throwable testFailure = evaluateSafely(base);
+                session.finishMocking(testFailure);
+                if (testFailure != null) {
+                    throw testFailure;
+                }
+            }
+
+            private Throwable evaluateSafely(Statement base) {
+                try {
+                    base.evaluate();
+                    return null;
+                } catch (Throwable throwable) {
+                    return throwable;
+                }
+            }
+        };
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java b/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java
new file mode 100644
index 0000000..5ee0a57
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.testing;
+
+import static org.mockito.Mockito.argThat;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.Truth;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+public final class TestingUtils {
+    private static final String TAG = "ArtServiceTesting";
+
+    private TestingUtils() {}
+
+    /**
+     * Recursively compares two objects using reflection. Returns true if the two objects are equal.
+     * For simplicity, this method only supports types that every field is a primitive type, a
+     * string, a {@link List}, or a supported type.
+     */
+    public static boolean deepEquals(
+            @Nullable Object a, @Nullable Object b, @NonNull StringBuilder errorMsg) {
+        try {
+            if (a == null && b == null) {
+                return true;
+            }
+            if (a == null || b == null) {
+                errorMsg.append(String.format("Nullability mismatch: %s != %s",
+                        a == null ? "null" : "nonnull", b == null ? "null" : "nonnull"));
+                return false;
+            }
+            if (a instanceof List && b instanceof List) {
+                return listDeepEquals((List<?>) a, (List<?>) b, errorMsg);
+            }
+            if (a.getClass() != b.getClass()) {
+                errorMsg.append(
+                        String.format("Type mismatch: %s != %s", a.getClass(), b.getClass()));
+                return false;
+            }
+            if (a.getClass() == String.class) {
+                if (!a.equals(b)) {
+                    errorMsg.append(String.format("%s != %s", a, b));
+                }
+                return a.equals(b);
+            }
+            if (a.getClass().isArray()) {
+                throw new UnsupportedOperationException("Array type is not supported");
+            }
+            for (Field field : a.getClass().getDeclaredFields()) {
+                if (Modifier.isStatic(field.getModifiers())) {
+                    continue;
+                }
+                field.setAccessible(true);
+                if (field.getType().isPrimitive()) {
+                    if (!field.get(a).equals(field.get(b))) {
+                        errorMsg.append(String.format("Field %s mismatch: %s != %s",
+                                field.getName(), field.get(a), field.get(b)));
+                        return false;
+                    }
+                } else if (!deepEquals(field.get(a), field.get(b), errorMsg)) {
+                    errorMsg.insert(0, String.format("Field %s mismatch: ", field.getName()));
+                    return false;
+                }
+            }
+            return true;
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** Same as above, but ignores any error message. */
+    public static boolean deepEquals(@Nullable Object a, @Nullable Object b) {
+        var errorMsgIgnored = new StringBuilder();
+        return deepEquals(a, b, errorMsgIgnored);
+    }
+
+    /**
+     * A Mockito argument matcher that uses {@link #deepEquals} to compare objects and logs any
+     * mismatch.
+     */
+    public static <T> T deepEq(@Nullable T expected) {
+        return argThat(arg -> {
+            var errorMsg = new StringBuilder();
+            boolean result = deepEquals(arg, expected, errorMsg);
+            if (!result) {
+                Log.e(TAG, errorMsg.toString());
+            }
+            return result;
+        });
+    }
+
+    /**
+     * A Mockito argument matcher that matches a list containing expected in any order.
+     */
+    @SafeVarargs
+    public static <ListType extends List<ItemType>, ItemType> ListType inAnyOrder(
+            @Nullable ItemType... expected) {
+        return argThat(argument -> {
+            try {
+                Truth.assertThat(argument).containsExactlyElementsIn(expected);
+                return true;
+            } catch (AssertionError error) {
+                return false;
+            }
+        });
+    }
+
+    /**
+     * {@link #inAnyOrder(Object[])} but using {@link #deepEquals(Object, Object)}} for comparisons.
+     *
+     * @see #inAnyOrder(Object[])
+     */
+    @SafeVarargs
+    public static <ListType extends List<ItemType>, ItemType> ListType inAnyOrderDeepEquals(
+            @Nullable ItemType... expected) {
+        return argThat(argument -> {
+            try {
+                Truth.assertThat(argument)
+                        .comparingElementsUsing(deepEquality())
+                        .containsExactlyElementsIn(expected);
+                return true;
+            } catch (AssertionError error) {
+                return false;
+            }
+        });
+    }
+
+    /**
+     * A Truth correspondence that uses {@link #deepEquals} to compare objects and reports any
+     * mismatch.
+     */
+    public static <T> Correspondence<T, T> deepEquality() {
+        return Correspondence.<T, T>from(TestingUtils::deepEquals, "deeply equals")
+                .formattingDiffsUsing((actual, expected) -> {
+                    var errorMsg = new StringBuilder();
+                    deepEquals(actual, expected, errorMsg);
+                    return errorMsg.toString();
+                });
+    }
+
+    private static boolean listDeepEquals(
+            @NonNull List<?> a, @NonNull List<?> b, @NonNull StringBuilder errorMsg) {
+        if (a.size() != b.size()) {
+            errorMsg.append(String.format("List length mismatch: %d != %d", a.size(), b.size()));
+            return false;
+        }
+        for (int i = 0; i < a.size(); i++) {
+            if (!deepEquals(a.get(i), b.get(i), errorMsg)) {
+                errorMsg.insert(0, String.format("Element %d mismatch: ", i));
+                return false;
+            }
+        }
+        return true;
+    };
+}
diff --git a/libartservice/service/javatests/com/android/server/art/testing/TestingUtilsTest.java b/libartservice/service/javatests/com/android/server/art/testing/TestingUtilsTest.java
new file mode 100644
index 0000000..518f0e4
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/TestingUtilsTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 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
+ */
+
+package com.android.server.art.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.internal.progress.ThreadSafeMockingProgress;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TestingUtilsTest {
+    @Before
+    @After
+    public void resetMockito() {
+        ThreadSafeMockingProgress.mockingProgress().reset();
+    }
+
+    @Test
+    public void testDeepEquals() {
+        var a = new Foo();
+        var b = new Foo();
+        assertThat(TestingUtils.deepEquals(a, b)).isTrue();
+    }
+
+    @Test
+    public void testDeepEqualsNull() {
+        assertThat(TestingUtils.deepEquals(null, null)).isTrue();
+    }
+
+    @Test
+    public void testDeepEqualsNullabilityMismatch() {
+        var a = new Foo();
+        assertThat(TestingUtils.deepEquals(a, null)).isFalse();
+    }
+
+    @Test
+    public void testDeepEqualsTypeMismatch() {
+        var a = new Foo();
+        var b = new Bar();
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test
+    public void testDeepEqualsPrimitiveFieldMismatch() {
+        var a = new Foo();
+        var b = new Foo();
+        b.mA = 11111111;
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test
+    public void testDeepEqualsStringFieldMismatch() {
+        var a = new Foo();
+        var b = new Foo();
+        b.mB = "def";
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test
+    public void deepEqualsNestedFieldMismatch() {
+        var a = new Foo();
+        var b = new Foo();
+        b.mC.setB(11111111);
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testDeepEqualsArrayNotSupported() throws Exception {
+        int[] a = new int[] {1};
+        int[] b = new int[] {2};
+        TestingUtils.deepEquals(a, b);
+    }
+
+    @Test
+    public void testListDeepEquals() throws Exception {
+        var a = new ArrayList<Integer>();
+        a.add(1);
+        a.add(2);
+        a.add(3);
+        a.add(4);
+        a.add(5);
+        var b = List.of(1, 2, 3, 4, 5);
+        assertThat(TestingUtils.deepEquals(a, b)).isTrue();
+    }
+
+    @Test
+    public void testListDeepEqualsSizeMismatch() throws Exception {
+        var a = new ArrayList<Integer>();
+        a.add(1);
+        var b = new ArrayList<Integer>();
+        b.add(1);
+        b.add(2);
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test
+    public void testListDeepEqualsElementMismatch() throws Exception {
+        var a = new ArrayList<Integer>();
+        a.add(1);
+        var b = new ArrayList<Integer>();
+        b.add(2);
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testDeepEqualsOtherContainerNotSupported() throws Exception {
+        var a = new HashSet<Integer>();
+        a.add(1);
+        var b = new HashSet<Integer>();
+        b.add(2);
+        TestingUtils.deepEquals(a, b);
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    @Test
+    public void testInAnyOrderDuplicates() {
+        testInAnyOrderInternal(List.of(1, 1), List.of(1), false, TestingUtils::inAnyOrder);
+        testInAnyOrderInternal(List.of(1, 1), List.of(1, 1), true, TestingUtils::inAnyOrder);
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    @Test
+    public void testInAnyOrderDeepEqualsDuplicates() {
+        testInAnyOrderInternal(
+                Arrays.asList(1, 1), List.of(1), false, TestingUtils::inAnyOrderDeepEquals);
+        testInAnyOrderInternal(
+                Arrays.asList(1, 1), List.of(1, 1), true, TestingUtils::inAnyOrderDeepEquals);
+    }
+
+    private void testInAnyOrderInternal(@NonNull List<Integer> first, @NonNull List<Integer> second,
+            boolean expected, @NonNull Consumer<Integer[]> inAnyOrderBlock) {
+        inAnyOrderBlock.accept(first.toArray(new Integer[0]));
+        var matchers = ThreadSafeMockingProgress.mockingProgress()
+                               .getArgumentMatcherStorage()
+                               .pullLocalizedMatchers();
+        assertThat(matchers).hasSize(1);
+        // noinspection unchecked
+        var matcher = (ArgumentMatcher<List<Integer>>) matchers.get(0).getMatcher();
+        assertThat(matcher.matches(second)).isEqualTo(expected);
+        ThreadSafeMockingProgress.mockingProgress().reset();
+    }
+}
+
+class Foo {
+    public int mA = 1234567;
+    public String mB = "abc";
+    public Bar mC = new Bar();
+}
+
+class Bar {
+    public static int sA = 10000000;
+    private int mB = 7654321;
+    public void setB(int b) {
+        mB = b;
+    }
+}
diff --git a/libartservice/service/proguard.flags b/libartservice/service/proguard.flags
new file mode 100644
index 0000000..8ef413f
--- /dev/null
+++ b/libartservice/service/proguard.flags
@@ -0,0 +1,10 @@
+# Proto field names are used by MessageLiteToString.toString through reflection.
+-keepclassmembers class * extends
+    com.android.server.art.jarjar.com.google.protobuf.GeneratedMessageLite {
+  *** get*();
+  *** set*(***);
+  *** has*();
+}
+
+# A job service is referenced by the framework through reflection.
+-keep class * extends android.app.job.JobService { *; }
diff --git a/libartservice/service/proto/common.proto b/libartservice/service/proto/common.proto
new file mode 100644
index 0000000..c8bb565
--- /dev/null
+++ b/libartservice/service/proto/common.proto
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+syntax = "proto3";
+
+package com.android.server.art.proto;
+option java_multiple_files = true;
+
+// Wrapper message for `int32`, to distinguish between the absence of a field
+// and its default value.
+message Int32Value {
+    int32 value = 1;
+}
diff --git a/libartservice/service/proto/dex_use.proto b/libartservice/service/proto/dex_use.proto
new file mode 100644
index 0000000..1dd962d
--- /dev/null
+++ b/libartservice/service/proto/dex_use.proto
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+syntax = "proto3";
+
+package com.android.server.art.proto;
+option java_multiple_files = true;
+
+import "art/libartservice/service/proto/common.proto";
+
+// The protobuf representation of `DexUseManagerLocal.DexUse`. See classes in
+// java/com/android/server/art/DexUseManagerLocal.java for details.
+// This proto is persisted on disk and both forward and backward compatibility are considerations.
+message DexUseProto {
+    repeated PackageDexUseProto package_dex_use = 1;
+}
+
+message PackageDexUseProto {
+    string owning_package_name = 1;
+    repeated PrimaryDexUseProto primary_dex_use = 2;
+    repeated SecondaryDexUseProto secondary_dex_use = 3;
+}
+
+message PrimaryDexUseProto {
+    string dex_file = 1;
+    repeated PrimaryDexUseRecordProto record = 2;
+}
+
+message PrimaryDexUseRecordProto {
+    string loading_package_name = 1;
+    bool isolated_process = 2;
+    int64 last_used_at_ms = 3;
+}
+
+message SecondaryDexUseProto {
+    string dex_file = 1;
+    Int32Value user_id = 2;  // Must be explicitly set.
+    repeated SecondaryDexUseRecordProto record = 3;
+}
+
+message SecondaryDexUseRecordProto {
+    string loading_package_name = 1;
+    bool isolated_process = 2;
+    string class_loader_context = 3;
+    string abi_name = 4;
+    int64 last_used_at_ms = 5;
+}
diff --git a/libartservice/tests/Android.bp b/libartservice/tests/Android.bp
deleted file mode 100644
index dc110a1..0000000
--- a/libartservice/tests/Android.bp
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-// Copyright (C) 2021 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.
-//
-
-//########################################################################
-// Build ArtServiceTests package
-//########################################################################
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "art_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["art_license"],
-}
-
-java_test {
-    name: "ArtServiceTests",
-
-    // Include all test java files.
-    srcs: [
-        "src/**/*.java",
-    ],
-
-    static_libs: [
-        "androidx.test.runner",
-        "service-art.impl",
-    ],
-
-    test_suites: ["general-tests"],
-}
diff --git a/libartservice/tests/src/com/android/server/art/ArtManagerLocalTests.java b/libartservice/tests/src/com/android/server/art/ArtManagerLocalTests.java
deleted file mode 100644
index 515849b..0000000
--- a/libartservice/tests/src/com/android/server/art/ArtManagerLocalTests.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 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
- */
-
-package com.android.server.art;
-
-import static org.junit.Assert.assertTrue;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.art.ArtManagerLocal;
-
-import junit.framework.TestCase;
-
-public class ArtManagerLocalTests extends TestCase {
-    private ArtManagerLocal mArtManagerLocal;
-
-    public void setup() {
-        mArtManagerLocal = new ArtManagerLocal();
-    }
-
-    public void testScaffolding() {
-        assertTrue(true);
-    }
-}
\ No newline at end of file
diff --git a/libarttools/Android.bp b/libarttools/Android.bp
index 3df40a5..d38ef45 100644
--- a/libarttools/Android.bp
+++ b/libarttools/Android.bp
@@ -23,38 +23,56 @@
     default_applicable_licenses: ["art_license"],
 }
 
-cc_library {
-    // This library contains low-level interfaces used to call dex2oat and
-    // related tools.  It will translate structured messages into command line
-    // arguments.  This will allow other libraries or programs besides the ART
-    // Service to make use of this functionality.
+cc_defaults {
+    // This library contains low-level interfaces used to call dex2oat and related tools. This will
+    // allow other libraries or programs besides the ART Service to make use of this functionality.
 
-    name: "libarttools",
+    name: "libarttools_defaults",
     defaults: ["art_defaults"],
     host_supported: true,
     srcs: [
         "tools/tools.cc",
     ],
     export_include_dirs: ["."],
-    apex_available: [
-        "com.android.art",
-        "com.android.art.debug",
-    ],
     shared_libs: [
         "libbase",
     ],
     export_shared_lib_headers: ["libbase"],
 }
 
+cc_library {
+    name: "libarttools",
+    defaults: ["libarttools_defaults"],
+    whole_static_libs: [
+        // TODO(b/270049598): Investigate the cost of libc++fs compared to its utility.
+        "libc++fs",
+    ],
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
 art_cc_defaults {
     name: "art_libarttools_tests_defaults",
+    defaults: ["libarttools_defaults"],
     srcs: [
+        "tools/art_exec_test.cc",
+        "tools/cmdline_builder_test.cc",
+        "tools/system_properties_test.cc",
         "tools/tools_test.cc",
     ],
     shared_libs: [
         "libbase",
-        "libarttools",
     ],
+    static_libs: [
+        "libgmock",
+    ],
+    target: {
+        android: {
+            static_libs: ["libmodules-utils-build"],
+        },
+    },
 }
 
 // Version of ART gtest `art_libarttools_tests` bundled with the ART APEX on target.
@@ -76,3 +94,26 @@
         "art_libarttools_tests_defaults",
     ],
 }
+
+cc_binary {
+    name: "art_exec",
+    defaults: [
+        "art_defaults",
+    ],
+    srcs: [
+        "tools/art_exec.cc",
+    ],
+    shared_libs: [
+        "libartbase",
+        "libartpalette",
+        "libarttools", // Contains "libc++fs".
+        "libbase",
+    ],
+    static_libs: [
+        "libcap",
+    ],
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
diff --git a/libarttools/tools/art_exec.cc b/libarttools/tools/art_exec.cc
new file mode 100644
index 0000000..1806ed4
--- /dev/null
+++ b/libarttools/tools/art_exec.cc
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <stdlib.h>
+#include <sys/capability.h>
+#include <sys/resource.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <iostream>
+#include <iterator>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/parseint.h"
+#include "android-base/result.h"
+#include "android-base/strings.h"
+#include "base/macros.h"
+#include "base/scoped_cap.h"
+#include "fmt/format.h"
+#include "palette/palette.h"
+#include "system/thread_defs.h"
+
+namespace {
+
+using ::android::base::ConsumePrefix;
+using ::android::base::Join;
+using ::android::base::ParseInt;
+using ::android::base::Result;
+using ::android::base::Split;
+
+using ::fmt::literals::operator""_format;  // NOLINT
+
+constexpr const char* kUsage =
+    R"(A wrapper binary that configures the process and executes a command.
+
+By default, it closes all open file descriptors except stdin, stdout, and stderr. `--keep-fds` can
+be passed to keep some more file descriptors open.
+
+Usage: art_exec [OPTIONS]... -- [COMMAND]...
+
+Supported options:
+  --help: Print this text.
+  --set-task-profile=PROFILES: Apply a set of task profiles (see
+      https://source.android.com/devices/tech/perf/cgroups). Requires root access. PROFILES can be a
+      comma-separated list of task profile names.
+  --set-priority=PRIORITY: Apply the process priority. Currently, the only supported value of
+      PRIORITY is "background".
+  --drop-capabilities: Drop all root capabilities. Note that this has effect only if `art_exec` runs
+      with some root capabilities but not as the root user.
+  --keep-fds=FILE_DESCRIPTORS: A semicolon-separated list of file descriptors to keep open.
+  --env=KEY=VALUE: Set an environment variable. This flag can be passed multiple times to set
+      multiple environment variables.
+)";
+
+constexpr int kErrorUsage = 100;
+constexpr int kErrorOther = 101;
+
+struct Options {
+  int command_pos = -1;
+  std::vector<std::string> task_profiles;
+  std::optional<int> priority = std::nullopt;
+  bool drop_capabilities = false;
+  std::unordered_set<int> keep_fds{fileno(stdin), fileno(stdout), fileno(stderr)};
+  std::unordered_map<std::string, std::string> envs;
+};
+
+[[noreturn]] void Usage(const std::string& error_msg) {
+  LOG(ERROR) << error_msg;
+  std::cerr << error_msg << "\n" << kUsage << "\n";
+  exit(kErrorUsage);
+}
+
+Options ParseOptions(int argc, char** argv) {
+  Options options;
+  for (int i = 1; i < argc; i++) {
+    std::string_view arg = argv[i];
+    if (arg == "--help") {
+      std::cerr << kUsage << "\n";
+      exit(0);
+    } else if (ConsumePrefix(&arg, "--set-task-profile=")) {
+      options.task_profiles = Split(std::string(arg), ",");
+      if (options.task_profiles.empty()) {
+        Usage("Empty task profile list");
+      }
+    } else if (ConsumePrefix(&arg, "--set-priority=")) {
+      if (arg == "background") {
+        options.priority = ANDROID_PRIORITY_BACKGROUND;
+      } else {
+        Usage("Unknown priority " + std::string(arg));
+      }
+    } else if (arg == "--drop-capabilities") {
+      options.drop_capabilities = true;
+    } else if (ConsumePrefix(&arg, "--keep-fds=")) {
+      for (const std::string& fd_str : Split(std::string(arg), ":")) {
+        int fd;
+        if (!ParseInt(fd_str, &fd)) {
+          Usage("Invalid fd " + fd_str);
+        }
+        options.keep_fds.insert(fd);
+      }
+    } else if (ConsumePrefix(&arg, "--env=")) {
+      size_t pos = arg.find('=');
+      if (pos == std::string_view::npos) {
+        Usage("Malformed environment variable. Must contain '='");
+      }
+      options.envs[std::string(arg.substr(/*pos=*/0, /*n=*/pos))] =
+          std::string(arg.substr(pos + 1));
+    } else if (arg == "--") {
+      if (i + 1 >= argc) {
+        Usage("Missing command after '--'");
+      }
+      options.command_pos = i + 1;
+      return options;
+    } else {
+      Usage("Unknown option " + std::string(arg));
+    }
+  }
+  Usage("Missing '--'");
+}
+
+Result<void> DropInheritableCaps() {
+  art::ScopedCap cap(cap_get_proc());
+  if (cap.Get() == nullptr) {
+    return ErrnoErrorf("Failed to call cap_get_proc");
+  }
+  if (cap_clear_flag(cap.Get(), CAP_INHERITABLE) != 0) {
+    return ErrnoErrorf("Failed to call cap_clear_flag");
+  }
+  if (cap_set_proc(cap.Get()) != 0) {
+    return ErrnoErrorf("Failed to call cap_set_proc");
+  }
+  return {};
+}
+
+Result<void> CloseFds(const std::unordered_set<int>& keep_fds) {
+  std::vector<int> open_fds;
+  std::error_code ec;
+  for (const std::filesystem::directory_entry& dir_entry :
+       std::filesystem::directory_iterator("/proc/self/fd", ec)) {
+    int fd;
+    if (!ParseInt(dir_entry.path().filename(), &fd)) {
+      return Errorf("Invalid entry in /proc/self/fd {}", dir_entry.path().filename());
+    }
+    open_fds.push_back(fd);
+  }
+  if (ec) {
+    return Errorf("Failed to list open FDs: {}", ec.message());
+  }
+  for (int fd : open_fds) {
+    if (keep_fds.find(fd) == keep_fds.end()) {
+      if (close(fd) != 0) {
+        Result<void> error = ErrnoErrorf("Failed to close FD {}", fd);
+        if (std::filesystem::exists("/proc/self/fd/{}"_format(fd))) {
+          return error;
+        }
+      }
+    }
+  }
+  return {};
+}
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  android::base::InitLogging(argv);
+
+  Options options = ParseOptions(argc, argv);
+
+  if (auto result = CloseFds(options.keep_fds); !result.ok()) {
+    LOG(ERROR) << "Failed to close open FDs: " << result.error();
+    return kErrorOther;
+  }
+
+  if (!options.task_profiles.empty()) {
+    if (int ret = PaletteSetTaskProfiles(/*tid=*/0, options.task_profiles);
+        ret != PALETTE_STATUS_OK) {
+      LOG(ERROR) << "Failed to set task profile: " << ret;
+      return kErrorOther;
+    }
+  }
+
+  if (options.priority.has_value()) {
+    if (setpriority(PRIO_PROCESS, /*who=*/0, options.priority.value()) != 0) {
+      PLOG(ERROR) << "Failed to setpriority";
+      return kErrorOther;
+    }
+  }
+
+  if (options.drop_capabilities) {
+    if (auto result = DropInheritableCaps(); !result.ok()) {
+      LOG(ERROR) << "Failed to drop inheritable capabilities: " << result.error();
+      return kErrorOther;
+    }
+  }
+
+  for (const auto& [key, value] : options.envs) {
+    setenv(key.c_str(), value.c_str(), /*overwrite=*/1);
+  }
+
+  execv(argv[options.command_pos], argv + options.command_pos);
+
+  std::vector<const char*> command_args(argv + options.command_pos, argv + argc);
+  PLOG(FATAL) << "Failed to execute (" << Join(command_args, ' ') << ")";
+  UNREACHABLE();
+}
diff --git a/libarttools/tools/art_exec_test.cc b/libarttools/tools/art_exec_test.cc
new file mode 100644
index 0000000..9e8b0de
--- /dev/null
+++ b/libarttools/tools/art_exec_test.cc
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <sys/capability.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <csignal>
+#include <filesystem>
+#include <functional>
+#include <string>
+#include <utility>
+
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/scopeguard.h"
+#include "android-base/strings.h"
+#include "base/common_art_test.h"
+#include "base/file_utils.h"
+#include "base/globals.h"
+#include "base/macros.h"
+#include "base/os.h"
+#include "base/scoped_cap.h"
+#include "exec_utils.h"
+#include "fmt/format.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "system/thread_defs.h"
+
+#ifdef ART_TARGET_ANDROID
+#include "android-modules-utils/sdk_level.h"
+#endif
+
+namespace art {
+namespace {
+
+using ::android::base::make_scope_guard;
+using ::android::base::ScopeGuard;
+using ::android::base::Split;
+using ::testing::Contains;
+using ::testing::ElementsAre;
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+// clang-tidy incorrectly complaints about the using declaration while the user-defined literal is
+// actually being used.
+using ::fmt::literals::operator""_format;  // NOLINT
+
+constexpr uid_t kRoot = 0;
+constexpr uid_t kNobody = 9999;
+
+// This test executes a few Linux system commands such as "ls", which are linked against system
+// libraries. In many ART gtests we set LD_LIBRARY_PATH to make the test binaries link to libraries
+// from the ART module first, and if that setting is propagated to the system commands they may also
+// try to link to those libraries instead of the system ones they are built against. This is
+// particularly noticeable when 32-bit tests run on a 64-bit system. Hence we need to set
+// LD_LIBRARY_PATH to an empty string here.
+// TODO(b/247108425): Remove this when ART gtests no longer use LD_LIBRARY_PATH.
+constexpr const char* kEmptyLdLibraryPath = "--env=LD_LIBRARY_PATH=";
+
+std::string GetArtBin(const std::string& name) { return "{}/bin/{}"_format(GetArtRoot(), name); }
+
+std::string GetBin(const std::string& name) { return "{}/bin/{}"_format(GetAndroidRoot(), name); }
+
+// Executes the command, waits for it to finish, and keeps it in a waitable state until the current
+// scope exits.
+std::pair<pid_t, ScopeGuard<std::function<void()>>> ScopedExecAndWait(
+    std::vector<std::string>& args) {
+  std::vector<char*> execv_args;
+  execv_args.reserve(args.size() + 1);
+  for (std::string& arg : args) {
+    execv_args.push_back(arg.data());
+  }
+  execv_args.push_back(nullptr);
+
+  pid_t pid = fork();
+  if (pid == 0) {
+    execv(execv_args[0], execv_args.data());
+    UNREACHABLE();
+  } else if (pid > 0) {
+    siginfo_t info;
+    CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED | WNOWAIT)), 0);
+    CHECK_EQ(info.si_code, CLD_EXITED);
+    CHECK_EQ(info.si_status, 0);
+    std::function<void()> cleanup([=] {
+      siginfo_t info;
+      CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED)), 0);
+    });
+    return std::make_pair(pid, make_scope_guard(std::move(cleanup)));
+  } else {
+    LOG(FATAL) << "Failed to call fork";
+    UNREACHABLE();
+  }
+}
+
+// Grants the current process the given root capability.
+void SetCap(cap_flag_t flag, cap_value_t value) {
+  ScopedCap cap(cap_get_proc());
+  CHECK_NE(cap.Get(), nullptr);
+  cap_value_t caps[]{value};
+  CHECK_EQ(cap_set_flag(cap.Get(), flag, /*ncap=*/1, caps, CAP_SET), 0);
+  CHECK_EQ(cap_set_proc(cap.Get()), 0);
+}
+
+// Returns true if the given process has the given root capability.
+bool GetCap(pid_t pid, cap_flag_t flag, cap_value_t value) {
+  ScopedCap cap(cap_get_pid(pid));
+  CHECK_NE(cap.Get(), nullptr);
+  cap_flag_value_t flag_value;
+  CHECK_EQ(cap_get_flag(cap.Get(), value, flag, &flag_value), 0);
+  return flag_value == CAP_SET;
+}
+
+class ArtExecTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    testing::Test::SetUp();
+    if (!kIsTargetAndroid) {
+      GTEST_SKIP() << "art_exec is for device only";
+    }
+    if (getuid() != kRoot) {
+      GTEST_SKIP() << "art_exec requires root";
+    }
+    art_exec_bin_ = GetArtBin("art_exec");
+  }
+
+  std::string art_exec_bin_;
+};
+
+TEST_F(ArtExecTest, Command) {
+  std::string error_msg;
+  int ret = ExecAndReturnCode({art_exec_bin_, "--", GetBin("sh"), "-c", "exit 123"}, &error_msg);
+  ASSERT_EQ(ret, 123) << error_msg;
+}
+
+TEST_F(ArtExecTest, SetTaskProfiles) {
+// The condition is always true because ArtExecTest is run on device only.
+#ifdef ART_TARGET_ANDROID
+  if (!android::modules::sdklevel::IsAtLeastU()) {
+    GTEST_SKIP() << "This test depends on a libartpalette API that is only available on U+";
+  }
+#endif
+
+  std::string filename = "/data/local/tmp/art-exec-test-XXXXXX";
+  ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false));
+  ASSERT_GE(scratch_file.GetFd(), 0);
+
+  std::vector<std::string> args{art_exec_bin_,
+                                "--set-task-profile=ProcessCapacityHigh",
+                                kEmptyLdLibraryPath,
+                                "--",
+                                GetBin("sh"),
+                                "-c",
+                                "cat /proc/self/cgroup > " + filename};
+  auto [pid, scope_guard] = ScopedExecAndWait(args);
+  std::string cgroup;
+  ASSERT_TRUE(android::base::ReadFileToString(filename, &cgroup));
+  EXPECT_THAT(cgroup, HasSubstr(":cpuset:/foreground\n"));
+}
+
+TEST_F(ArtExecTest, SetPriority) {
+  std::vector<std::string> args{
+      art_exec_bin_, "--set-priority=background", kEmptyLdLibraryPath, "--", GetBin("true")};
+  auto [pid, scope_guard] = ScopedExecAndWait(args);
+  EXPECT_EQ(getpriority(PRIO_PROCESS, pid), ANDROID_PRIORITY_BACKGROUND);
+}
+
+TEST_F(ArtExecTest, DropCapabilities) {
+  // Switch to a non-root user, but still keep the CAP_FOWNER capability available and inheritable.
+  // The order of the following calls matters.
+  CHECK_EQ(cap_setuid(kNobody), 0);
+  SetCap(CAP_INHERITABLE, CAP_FOWNER);
+  SetCap(CAP_EFFECTIVE, CAP_FOWNER);
+  ASSERT_EQ(cap_set_ambient(CAP_FOWNER, CAP_SET), 0);
+
+  // Make sure the test is set up correctly (i.e., the child process should normally have the
+  // inherited root capability: CAP_FOWNER).
+  {
+    std::vector<std::string> args{art_exec_bin_, kEmptyLdLibraryPath, "--", GetBin("true")};
+    auto [pid, scope_guard] = ScopedExecAndWait(args);
+    ASSERT_TRUE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER));
+  }
+
+  {
+    std::vector<std::string> args{
+        art_exec_bin_, "--drop-capabilities", kEmptyLdLibraryPath, "--", GetBin("true")};
+    auto [pid, scope_guard] = ScopedExecAndWait(args);
+    EXPECT_FALSE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER));
+  }
+}
+
+TEST_F(ArtExecTest, CloseFds) {
+  std::unique_ptr<File> file1(OS::OpenFileForReading("/dev/zero"));
+  std::unique_ptr<File> file2(OS::OpenFileForReading("/dev/zero"));
+  std::unique_ptr<File> file3(OS::OpenFileForReading("/dev/zero"));
+  ASSERT_NE(file1, nullptr);
+  ASSERT_NE(file2, nullptr);
+  ASSERT_NE(file3, nullptr);
+
+  std::string filename = "/data/local/tmp/art-exec-test-XXXXXX";
+  ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false));
+  ASSERT_GE(scratch_file.GetFd(), 0);
+
+  std::vector<std::string> args{art_exec_bin_,
+                                "--keep-fds={}:{}"_format(file3->Fd(), file2->Fd()),
+                                kEmptyLdLibraryPath,
+                                "--",
+                                GetBin("sh"),
+                                "-c",
+                                "("
+                                "readlink /proc/self/fd/{} || echo;"
+                                "readlink /proc/self/fd/{} || echo;"
+                                "readlink /proc/self/fd/{} || echo;"
+                                ") > {}"_format(file1->Fd(), file2->Fd(), file3->Fd(), filename)};
+
+  ScopedExecAndWait(args);
+
+  std::string open_fds;
+  ASSERT_TRUE(android::base::ReadFileToString(filename, &open_fds));
+
+  // `file1` should be closed, while the other two should be open. There's a blank line at the end.
+  EXPECT_THAT(Split(open_fds, "\n"), ElementsAre(Not("/dev/zero"), "/dev/zero", "/dev/zero", ""));
+}
+
+TEST_F(ArtExecTest, Env) {
+  std::string filename = "/data/local/tmp/art-exec-test-XXXXXX";
+  ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false));
+  ASSERT_GE(scratch_file.GetFd(), 0);
+
+  std::vector<std::string> args{art_exec_bin_,
+                                "--env=FOO=BAR",
+                                kEmptyLdLibraryPath,
+                                "--",
+                                GetBin("sh"),
+                                "-c",
+                                "env > " + filename};
+
+  ScopedExecAndWait(args);
+
+  std::string envs;
+  ASSERT_TRUE(android::base::ReadFileToString(filename, &envs));
+
+  EXPECT_THAT(Split(envs, "\n"), Contains("FOO=BAR"));
+}
+
+}  // namespace
+}  // namespace art
diff --git a/libarttools/tools/cmdline_builder.h b/libarttools/tools/cmdline_builder.h
new file mode 100644
index 0000000..fd11ee8
--- /dev/null
+++ b/libarttools/tools/cmdline_builder.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 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_LIBARTTOOLS_TOOLS_CMDLINE_BUILDER_H_
+#define ART_LIBARTTOOLS_TOOLS_CMDLINE_BUILDER_H_
+
+#include <algorithm>
+#include <iterator>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+
+namespace art {
+namespace tools {
+
+namespace internal {
+
+constexpr bool ContainsOneFormatSpecifier(std::string_view format, char specifier) {
+  int count = 0;
+  size_t pos = 0;
+  while ((pos = format.find('%', pos)) != std::string_view::npos) {
+    if (pos == format.length() - 1) {
+      // Invalid trailing '%'.
+      return false;
+    }
+    if (format[pos + 1] == specifier) {
+      count++;
+    } else if (format[pos + 1] != '%') {
+      // "%%" is okay. Otherwise, it's a wrong specifier.
+      return false;
+    }
+    pos += 2;
+  }
+  return count == 1;
+}
+
+}  // namespace internal
+
+// A util class that builds cmdline arguments.
+class CmdlineBuilder {
+ public:
+  // Returns all arguments.
+  const std::vector<std::string>& Get() const { return elements_; }
+
+  // Adds an argument as-is.
+  CmdlineBuilder& Add(std::string_view arg) {
+    elements_.push_back(std::string(arg));
+    return *this;
+  }
+
+  // Same as above but adds a runtime argument.
+  CmdlineBuilder& AddRuntime(std::string_view arg) { return Add("--runtime-arg").Add(arg); }
+
+  // Adds a string value formatted by the format string.
+  //
+  // Usage: Add("--flag=%s", "value")
+  CmdlineBuilder& Add(const char* arg_format, const std::string& value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 's'),
+                               "'arg' must be a string literal that contains '%s'"))) {
+    return Add(android::base::StringPrintf(arg_format, value.c_str()));
+  }
+
+  // Same as above but adds a runtime argument.
+  CmdlineBuilder& AddRuntime(const char* arg_format, const std::string& value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 's'),
+                               "'arg' must be a string literal that contains '%s'"))) {
+    return AddRuntime(android::base::StringPrintf(arg_format, value.c_str()));
+  }
+
+  // Adds an integer value formatted by the format string.
+  //
+  // Usage: Add("--flag=%d", 123)
+  CmdlineBuilder& Add(const char* arg_format, int value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 'd'),
+                               "'arg' must be a string literal that contains '%d'"))) {
+    return Add(android::base::StringPrintf(arg_format, value));
+  }
+
+  // Same as above but adds a runtime argument.
+  CmdlineBuilder& AddRuntime(const char* arg_format, int value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 'd'),
+                               "'arg' must be a string literal that contains '%d'"))) {
+    return AddRuntime(android::base::StringPrintf(arg_format, value));
+  }
+
+  // Adds a string value formatted by the format string if the value is non-empty. Does nothing
+  // otherwise.
+  //
+  // Usage: AddIfNonEmpty("--flag=%s", "value")
+  CmdlineBuilder& AddIfNonEmpty(const char* arg_format, const std::string& value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 's'),
+                               "'arg' must be a string literal that contains '%s'"))) {
+    if (!value.empty()) {
+      Add(android::base::StringPrintf(arg_format, value.c_str()));
+    }
+    return *this;
+  }
+
+  // Same as above but adds a runtime argument.
+  CmdlineBuilder& AddRuntimeIfNonEmpty(const char* arg_format, const std::string& value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 's'),
+                               "'arg' must be a string literal that contains '%s'"))) {
+    if (!value.empty()) {
+      AddRuntime(android::base::StringPrintf(arg_format, value.c_str()));
+    }
+    return *this;
+  }
+
+  // Adds an argument as-is if the boolean value is true. Does nothing otherwise.
+  CmdlineBuilder& AddIf(bool value, std::string_view arg) {
+    if (value) {
+      Add(arg);
+    }
+    return *this;
+  }
+
+  // Same as above but adds a runtime argument.
+  CmdlineBuilder& AddRuntimeIf(bool value, std::string_view arg) {
+    if (value) {
+      AddRuntime(arg);
+    }
+    return *this;
+  }
+
+  // Concatenates this builder with another. Returns the concatenated result and nullifies the input
+  // builder.
+  CmdlineBuilder& Concat(CmdlineBuilder&& other) {
+    elements_.reserve(elements_.size() + other.elements_.size());
+    std::move(other.elements_.begin(), other.elements_.end(), std::back_inserter(elements_));
+    other.elements_.clear();
+    return *this;
+  }
+
+ private:
+  std::vector<std::string> elements_;
+};
+
+}  // namespace tools
+}  // namespace art
+
+#endif  // ART_LIBARTTOOLS_TOOLS_CMDLINE_BUILDER_H_
diff --git a/libarttools/tools/cmdline_builder_test.cc b/libarttools/tools/cmdline_builder_test.cc
new file mode 100644
index 0000000..5551860
--- /dev/null
+++ b/libarttools/tools/cmdline_builder_test.cc
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "cmdline_builder.h"
+
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace tools {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+
+class CmdlineBuilderTest : public testing::Test {
+ protected:
+  CmdlineBuilder args_;
+};
+
+TEST_F(CmdlineBuilderTest, ContainsOneFormatSpecifier) {
+  EXPECT_TRUE(internal::ContainsOneFormatSpecifier("--flag=%s", 's'));
+  EXPECT_TRUE(internal::ContainsOneFormatSpecifier("--flag=[%s]", 's'));
+  EXPECT_TRUE(internal::ContainsOneFormatSpecifier("--flag=%s%%", 's'));
+  EXPECT_TRUE(internal::ContainsOneFormatSpecifier("--flag=[%s%%]", 's'));
+  EXPECT_TRUE(internal::ContainsOneFormatSpecifier("--flag=%%%s", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=%s%s", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=%s%", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=%d", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=%s%d", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=%%s", 's'));
+}
+
+TEST_F(CmdlineBuilderTest, Add) {
+  args_.Add("--flag");
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag"));
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntime) {
+  args_.AddRuntime("--flag");
+  EXPECT_THAT(args_.Get(), ElementsAre("--runtime-arg", "--flag"));
+}
+
+TEST_F(CmdlineBuilderTest, AddString) {
+  args_.Add("--flag=[%s]", "foo");
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag=[foo]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeString) {
+  args_.AddRuntime("--flag=[%s]", "foo");
+  EXPECT_THAT(args_.Get(), ElementsAre("--runtime-arg", "--flag=[foo]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddInt) {
+  args_.Add("--flag=[%d]", 123);
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag=[123]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeInt) {
+  args_.AddRuntime("--flag=[%d]", 123);
+  EXPECT_THAT(args_.Get(), ElementsAre("--runtime-arg", "--flag=[123]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddIfNonEmpty) {
+  args_.AddIfNonEmpty("--flag=[%s]", "foo");
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag=[foo]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddIfNonEmptyEmpty) {
+  args_.AddIfNonEmpty("--flag=[%s]", "");
+  EXPECT_THAT(args_.Get(), IsEmpty());
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeIfNonEmpty) {
+  args_.AddRuntimeIfNonEmpty("--flag=[%s]", "foo");
+  EXPECT_THAT(args_.Get(), ElementsAre("--runtime-arg", "--flag=[foo]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeIfNonEmptyEmpty) {
+  args_.AddRuntimeIfNonEmpty("--flag=[%s]", "");
+  EXPECT_THAT(args_.Get(), IsEmpty());
+}
+
+TEST_F(CmdlineBuilderTest, AddIfTrue) {
+  args_.AddIf(true, "--flag");
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag"));
+}
+
+TEST_F(CmdlineBuilderTest, AddIfFalse) {
+  args_.AddIf(false, "--flag");
+  EXPECT_THAT(args_.Get(), IsEmpty());
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeIfTrue) {
+  args_.AddRuntimeIf(true, "--flag");
+  EXPECT_THAT(args_.Get(), ElementsAre("--runtime-arg", "--flag"));
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeIfFalse) {
+  args_.AddRuntimeIf(false, "--flag");
+  EXPECT_THAT(args_.Get(), IsEmpty());
+}
+
+TEST_F(CmdlineBuilderTest, Concat) {
+  args_.Add("--flag1");
+  args_.Add("--flag2");
+
+  CmdlineBuilder other;
+  other.Add("--flag3");
+  other.Add("--flag4");
+
+  args_.Concat(std::move(other));
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag1", "--flag2", "--flag3", "--flag4"));
+  EXPECT_THAT(other.Get(), IsEmpty());
+}
+
+}  // namespace
+}  // namespace tools
+}  // namespace art
diff --git a/libarttools/tools/system_properties.h b/libarttools/tools/system_properties.h
new file mode 100644
index 0000000..06b7bcb
--- /dev/null
+++ b/libarttools/tools/system_properties.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
+#define ART_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
+
+#include <string>
+
+#include "android-base/parsebool.h"
+#include "android-base/properties.h"
+
+namespace art {
+namespace tools {
+
+// A class for getting system properties with fallback lookup support. Different from
+// android::base::GetProperty, this class is mockable.
+class SystemProperties {
+ public:
+  virtual ~SystemProperties() = default;
+
+  // Returns the current value of the system property `key`, or `default_value` if the property
+  // doesn't have a value.
+  std::string Get(const std::string& key, const std::string& default_value) const {
+    std::string value = GetProperty(key);
+    if (!value.empty()) {
+      return value;
+    }
+    return default_value;
+  }
+
+  // Same as above, but allows specifying one or more fallback keys. The last argument is a string
+  // default value that will be used if none of the given keys has a value.
+  //
+  // Usage:
+  //
+  // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return "default":
+  //   Get("key_1", "key_2", "key_3", /*default_value=*/"default")
+  template <typename... Args>
+  std::string Get(const std::string& key, const std::string& fallback_key, Args... args) const {
+    return Get(key, Get(fallback_key, args...));
+  }
+
+  // Returns the current value of the system property `key` with zero or more fallback keys, or an
+  // empty string if none of the given keys has a value.
+  //
+  // Usage:
+  //
+  // Look up for "key_1". If it doesn't have a value, return an empty string:
+  //  GetOrEmpty("key_1")
+  //
+  // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return an empty
+  // string:
+  //  GetOrEmpty("key_1", "key_2", "key_3")
+  template <typename... Args>
+  std::string GetOrEmpty(const std::string& key, Args... fallback_keys) const {
+    return Get(key, fallback_keys..., /*default_value=*/"");
+  }
+
+  // Returns the current value of the boolean system property `key`, or `default_value` if the
+  // property doesn't have a value. See `android::base::ParseBool` for how the value is parsed.
+  bool GetBool(const std::string& key, bool default_value) const {
+    android::base::ParseBoolResult result = android::base::ParseBool(GetProperty(key));
+    if (result != android::base::ParseBoolResult::kError) {
+      return result == android::base::ParseBoolResult::kTrue;
+    }
+    return default_value;
+  }
+
+  // Same as above, but allows specifying one or more fallback keys. The last argument is a bool
+  // default value that will be used if none of the given keys has a value.
+  //
+  // Usage:
+  //
+  // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return true:
+  //   Get("key_1", "key_2", "key_3", /*default_value=*/true)
+  template <typename... Args>
+  bool GetBool(const std::string& key, const std::string& fallback_key, Args... args) const {
+    return GetBool(key, GetBool(fallback_key, args...));
+  }
+
+ protected:
+  // The single source of truth of system properties. Can be mocked in unit tests.
+  virtual std::string GetProperty(const std::string& key) const {
+    return android::base::GetProperty(key, /*default_value=*/"");
+  }
+};
+
+}  // namespace tools
+}  // namespace art
+
+#endif  // ART_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
diff --git a/libarttools/tools/system_properties_test.cc b/libarttools/tools/system_properties_test.cc
new file mode 100644
index 0000000..80300f0
--- /dev/null
+++ b/libarttools/tools/system_properties_test.cc
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "system_properties.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace tools {
+namespace {
+
+using ::testing::Return;
+
+class MockSystemProperties : public SystemProperties {
+ public:
+  MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override));
+};
+
+class SystemPropertiesTest : public testing::Test {
+ protected:
+  MockSystemProperties system_properties_;
+};
+
+TEST_F(SystemPropertiesTest, Get) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("value_1"));
+  EXPECT_EQ(system_properties_.Get("key_1", /*default_value=*/"default"), "value_1");
+}
+
+TEST_F(SystemPropertiesTest, GetWithFallback) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("value_2"));
+  EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("value_3"));
+  EXPECT_EQ(system_properties_.Get("key_1", "key_2", "key_3", /*default_value=*/"default"),
+            "value_2");
+}
+
+TEST_F(SystemPropertiesTest, GetDefault) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_EQ(system_properties_.Get("key_1", /*default_value=*/"default"), "default");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmpty) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("value_1"));
+  EXPECT_EQ(system_properties_.GetOrEmpty("key_1"), "value_1");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmptyWithFallback) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("value_2"));
+  EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("value_3"));
+  EXPECT_EQ(system_properties_.GetOrEmpty("key_1", "key_2", "key_3"), "value_2");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmptyDefault) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_EQ(system_properties_.GetOrEmpty("key_1"), "");
+}
+
+TEST_F(SystemPropertiesTest, GetBoolTrue) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("true"));
+  EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/false), true);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolFalse) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("false"));
+  EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/true), false);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolWithFallback) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("true"));
+  EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("false"));
+  EXPECT_EQ(system_properties_.GetBool("key_1", "key_2", "key_3", /*default_value=*/false), true);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolDefault) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/true), true);
+}
+
+}  // namespace
+}  // namespace tools
+}  // namespace art
diff --git a/libarttools/tools/tools.cc b/libarttools/tools/tools.cc
index a3a91e8..3d5301a 100644
--- a/libarttools/tools/tools.cc
+++ b/libarttools/tools/tools.cc
@@ -16,12 +16,129 @@
 
 #include "tools.h"
 
+#include <errno.h>
+#include <fnmatch.h>
+
+#include <algorithm>
+#include <filesystem>
+#include <functional>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "fmt/format.h"
+
 namespace art {
 namespace tools {
 
-std::string getMsg() {
-    return "hello world!";
+namespace {
+
+using ::std::placeholders::_1;
+
+using ::fmt::literals::operator""_format;  // NOLINT
+
+// Returns true if `path_prefix` matches `pattern` or can be a prefix of a path that matches
+// `pattern` (i.e., `path_prefix` represents a directory that may contain a file whose path matches
+// `pattern`).
+bool PartialMatch(const std::filesystem::path& pattern, const std::filesystem::path& path_prefix) {
+  for (std::filesystem::path::const_iterator pattern_it = pattern.begin(),
+                                             path_prefix_it = path_prefix.begin();
+       ;  // NOLINT
+       pattern_it++, path_prefix_it++) {
+    if (path_prefix_it == path_prefix.end()) {
+      return true;
+    }
+    if (pattern_it == pattern.end()) {
+      return false;
+    }
+    if (*pattern_it == "**") {
+      return true;
+    }
+    if (fnmatch(pattern_it->c_str(), path_prefix_it->c_str(), /*flags=*/0) != 0) {
+      return false;
+    }
+  }
 }
 
+bool FullMatchRecursive(const std::filesystem::path& pattern,
+                        std::filesystem::path::const_iterator pattern_it,
+                        const std::filesystem::path& path,
+                        std::filesystem::path::const_iterator path_it,
+                        bool double_asterisk_visited = false) {
+  if (pattern_it == pattern.end() && path_it == path.end()) {
+    return true;
+  }
+  if (pattern_it == pattern.end()) {
+    return false;
+  }
+  if (*pattern_it == "**") {
+    DCHECK(!double_asterisk_visited);
+    std::filesystem::path::const_iterator next_pattern_it = pattern_it;
+    return FullMatchRecursive(
+               pattern, ++next_pattern_it, path, path_it, /*double_asterisk_visited=*/true) ||
+           (path_it != path.end() && FullMatchRecursive(pattern, pattern_it, path, ++path_it));
+  }
+  if (path_it == path.end()) {
+    return false;
+  }
+  if (fnmatch(pattern_it->c_str(), path_it->c_str(), /*flags=*/0) != 0) {
+    return false;
+  }
+  return FullMatchRecursive(pattern, ++pattern_it, path, ++path_it);
 }
+
+// Returns true if `path` fully matches `pattern`.
+bool FullMatch(const std::filesystem::path& pattern, const std::filesystem::path& path) {
+  return FullMatchRecursive(pattern, pattern.begin(), path, path.begin());
 }
+
+void MatchGlobRecursive(const std::vector<std::filesystem::path>& patterns,
+                        const std::filesystem::path& root_dir,
+                        /*out*/ std::vector<std::string>* results) {
+  std::error_code ec;
+  for (auto it = std::filesystem::recursive_directory_iterator(
+           root_dir, std::filesystem::directory_options::skip_permission_denied, ec);
+       !ec && it != std::filesystem::end(it);
+       it.increment(ec)) {
+    const std::filesystem::directory_entry& entry = *it;
+    if (std::none_of(patterns.begin(), patterns.end(), std::bind(PartialMatch, _1, entry.path()))) {
+      // Avoid unnecessary I/O and SELinux denials.
+      it.disable_recursion_pending();
+      continue;
+    }
+    std::error_code ec2;
+    if (entry.is_regular_file(ec2) &&
+        std::any_of(patterns.begin(), patterns.end(), std::bind(FullMatch, _1, entry.path()))) {
+      results->push_back(entry.path());
+    }
+    if (ec2) {
+      // It's expected that we don't have permission to stat some dirs/files, and we don't care
+      // about them.
+      if (ec2.value() != EACCES) {
+        LOG(ERROR) << "Unable to lstat '{}': {}"_format(entry.path().string(), ec2.message());
+      }
+      continue;
+    }
+  }
+  if (ec) {
+    LOG(ERROR) << "Unable to walk through '{}': {}"_format(root_dir.string(), ec.message());
+  }
+}
+
+}  // namespace
+
+std::vector<std::string> Glob(const std::vector<std::string>& patterns, std::string_view root_dir) {
+  std::vector<std::filesystem::path> parsed_patterns;
+  parsed_patterns.reserve(patterns.size());
+  for (std::string_view pattern : patterns) {
+    parsed_patterns.emplace_back(pattern);
+  }
+  std::vector<std::string> results;
+  MatchGlobRecursive(parsed_patterns, root_dir, &results);
+  return results;
+}
+
+}  // namespace tools
+}  // namespace art
diff --git a/libarttools/tools/tools.h b/libarttools/tools/tools.h
index 8231f5f..c2bcee7 100644
--- a/libarttools/tools/tools.h
+++ b/libarttools/tools/tools.h
@@ -18,11 +18,24 @@
 #define ART_LIBARTTOOLS_TOOLS_TOOLS_H_
 
 #include <string>
+#include <string_view>
+#include <vector>
 
 namespace art {
 namespace tools {
 
-std::string getMsg();
+// Searches in a filesystem, starting from `root_dir`. Returns all regular files (i.e., excluding
+// directories, symlinks, etc.) that match at least one pattern in `patterns`. Each pattern is an
+// absolute path that contains zero or more wildcards. The scan does not follow symlinks to
+// directories.
+//
+// Supported wildcards are:
+// - Those documented in glob(7)
+// - '**': Matches zero or more path elements. This is only recognised by itself as a path segment.
+//
+// For simplicity and efficiency, at most one '**' is allowed.
+std::vector<std::string> Glob(const std::vector<std::string>& patterns,
+                              std::string_view root_dir = "/");
 
 }  // namespace tools
 }  // namespace art
diff --git a/libarttools/tools/tools_test.cc b/libarttools/tools/tools_test.cc
index 6eaa8f6..2f61181 100644
--- a/libarttools/tools/tools_test.cc
+++ b/libarttools/tools/tools_test.cc
@@ -15,14 +15,101 @@
  */
 
 #include "tools.h"
+
+#include <algorithm>
+#include <filesystem>
+#include <iterator>
+
+#include "android-base/file.h"
+#include "base/common_art_test.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 namespace art {
+namespace tools {
+namespace {
 
-class ArtToolsTest : public testing::Test {};
+using ::android::base::WriteStringToFile;
+using ::testing::UnorderedElementsAre;
 
-TEST_F(ArtToolsTest, Hello) {
-  EXPECT_EQ("hello world!", art::tools::getMsg());
+void CreateFile(const std::string& filename) {
+  std::filesystem::path path(filename);
+  std::filesystem::create_directories(path.parent_path());
+  ASSERT_TRUE(WriteStringToFile(/*content=*/"", filename));
 }
 
+class ArtToolsTest : public CommonArtTest {
+ protected:
+  void SetUp() override {
+    CommonArtTest::SetUp();
+    scratch_dir_ = std::make_unique<ScratchDir>();
+    scratch_path_ = scratch_dir_->GetPath();
+    // Remove the trailing '/';
+    scratch_path_.resize(scratch_path_.length() - 1);
+  }
+
+  void TearDown() override {
+    scratch_dir_.reset();
+    CommonArtTest::TearDown();
+  }
+
+  std::unique_ptr<ScratchDir> scratch_dir_;
+  std::string scratch_path_;
+};
+
+TEST_F(ArtToolsTest, Glob) {
+  CreateFile(scratch_path_ + "/abc/def/000.txt");
+  CreateFile(scratch_path_ + "/abc/def/ghi/123.txt");
+  CreateFile(scratch_path_ + "/abc/def/ghi/456.txt");
+  CreateFile(scratch_path_ + "/abc/def/ghi/456.pdf");
+  CreateFile(scratch_path_ + "/abc/def/ghi/jkl/456.txt");
+  CreateFile(scratch_path_ + "/789.txt");
+  CreateFile(scratch_path_ + "/abc/789.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/789.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/789.txt");
+  CreateFile(scratch_path_ + "/abc/mno/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/mno/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/mno/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/mno/ccc/123.txt");
+  CreateFile(scratch_path_ + "/pqr/123.txt");
+  CreateFile(scratch_path_ + "/abc/pqr/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/pqr/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/pqr/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/pqr/ccc/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/pqr/ccc/ddd/123.txt");
+
+  // This symlink will cause infinite recursion. It should not be followed.
+  std::filesystem::create_directory_symlink(scratch_path_ + "/abc/aaa/bbb/pqr",
+                                            scratch_path_ + "/abc/aaa/bbb/pqr/lnk");
+
+  // This is a directory. It should not be included in the results.
+  std::filesystem::create_directory(scratch_path_ + "/abc/def/ghi/000.txt");
+
+  std::vector<std::string> patterns = {
+      scratch_path_ + "/abc/def/000.txt",
+      scratch_path_ + "/abc/def/ghi/*.txt",
+      scratch_path_ + "/abc/**/789.txt",
+      scratch_path_ + "/abc/**/mno/*.txt",
+      scratch_path_ + "/abc/**/pqr/**",
+  };
+
+  EXPECT_THAT(Glob(patterns, scratch_path_),
+              UnorderedElementsAre(scratch_path_ + "/abc/def/000.txt",
+                                   scratch_path_ + "/abc/def/ghi/123.txt",
+                                   scratch_path_ + "/abc/def/ghi/456.txt",
+                                   scratch_path_ + "/abc/789.txt",
+                                   scratch_path_ + "/abc/aaa/789.txt",
+                                   scratch_path_ + "/abc/aaa/bbb/789.txt",
+                                   scratch_path_ + "/abc/mno/123.txt",
+                                   scratch_path_ + "/abc/aaa/mno/123.txt",
+                                   scratch_path_ + "/abc/aaa/bbb/mno/123.txt",
+                                   scratch_path_ + "/abc/pqr/123.txt",
+                                   scratch_path_ + "/abc/aaa/pqr/123.txt",
+                                   scratch_path_ + "/abc/aaa/bbb/pqr/123.txt",
+                                   scratch_path_ + "/abc/aaa/bbb/pqr/ccc/123.txt",
+                                   scratch_path_ + "/abc/aaa/bbb/pqr/ccc/ddd/123.txt"));
+}
+
+}  // namespace
+}  // namespace tools
 }  // namespace art
diff --git a/libdexfile/Android.bp b/libdexfile/Android.bp
index 3d910f2..73f93a6 100644
--- a/libdexfile/Android.bp
+++ b/libdexfile/Android.bp
@@ -57,6 +57,9 @@
         "jni_headers",
         "libdexfile_external_headers",
     ],
+    static: {
+        cflags: ["-DSTATIC_LIB"],
+    },
     target: {
         android: {
             srcs: [
@@ -211,7 +214,16 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
+
+    // This library is exported in stub form by art-module-sdk, and it brings
+    // with it all the exported headers from libartbase and libbase, many of
+    // which are transitive dependencies outside ART. Those may conflict with
+    // other versions of the headers that the caller is using in their build,
+    // but in this case it's fine since external users only depend on this
+    // through runtime_libs (see comment for libdexfile_support), which doesn't
+    // propagate include dirs.
     stubs: {
         symbol_file: "libdexfile.map.txt",
         versions: ["1"],
@@ -288,14 +300,19 @@
         ":art-gtest-jars-GetMethodSignature",
         ":art-gtest-jars-Lookup",
         ":art-gtest-jars-Main",
+        ":art-gtest-jars-MainEmptyUncompressed",
         ":art-gtest-jars-MultiDex",
         ":art-gtest-jars-Nested",
+        ":art-gtest-jars-VerifierDeps",
     ],
     header_libs: ["jni_headers"],
-    shared_libs: [
-        "libbacktrace",
+    static_libs: [
         "libziparchive",
     ],
+    shared_libs: [
+        "libunwindstack",
+        "libz", // libziparchive dependency; must be repeated here since it's a static lib.
+    ],
 }
 
 // Version of ART gtest `art_libdexfile_tests` bundled with the ART APEX on target.
@@ -323,8 +340,6 @@
     defaults: ["art_defaults"],
     visibility: ["//visibility:public"],
     host_supported: true,
-    header_libs: ["libbase_headers"],
-    export_header_lib_headers: ["libbase_headers"],
     export_include_dirs: ["external/include"],
 
     target: {
@@ -403,19 +418,6 @@
         "libartbase",
     ],
 
-    // Support multilib variants (using different suffix per sub-architecture), which is needed on
-    // build targets with secondary architectures, as the CTS test suite packaging logic flattens
-    // all test artifacts into a single `testcases` directory.
-    compile_multilib: "both",
-    multilib: {
-        lib32: {
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
-        },
-    },
-
     test_config_template: ":art-gtests-target-standalone-cts-template",
     test_suites: ["cts"], // For backed-by API coverage.
 }
@@ -444,11 +446,12 @@
         "external/dex_file_supp.cc",
     ],
     runtime_libs: ["libdexfile"],
-    shared_libs: [
-        "liblog",
-        "libbase",
-    ],
+    // Only NDK libs may be dynamic, because this becomes a prebuilt that must work on S+.
+    shared_libs: ["liblog"],
     header_libs: ["libdexfile_external_headers"],
+    // Do not export any headers outside the ART module - they get included in
+    // the prebuilt SDK and may conflict with different versions of themselves
+    // in the build that the SDK user is using.
     export_header_lib_headers: ["libdexfile_external_headers"],
 
     apex_available: [
@@ -500,20 +503,6 @@
         "art_standalone_test_defaults",
         "art_libdexfile_support_tests_defaults",
     ],
-
-    // Support multilib variants (using different suffix per sub-architecture), which is needed on
-    // build targets with secondary architectures, as the MTS test suite packaging logic flattens
-    // all test artifacts into a single `testcases` directory.
-    compile_multilib: "both",
-    multilib: {
-        lib32: {
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
-        },
-    },
-
     test_suites: [
         "mts-art",
     ],
@@ -569,6 +558,13 @@
 // For use by external packages allowed to link in static libdexfile_support.
 // This is not allowed in any module that may end up in an APEX or platform
 // image, so visibility is restrictive.
+//
+// TODO(b/169885605): This library brings with it all the exported headers from
+// libdexfile_support_static_defaults into the prebuilt SDK created by
+// art-module-sdk, many of which are transitive dependencies outside ART. Those
+// may conflict with other versions that the caller is using in their build. One
+// way to deal with that is to provide minimal headers without any transitive
+// dependencies on other headers.
 cc_library_static {
     name: "libdexfile_static",
     host_supported: true,
@@ -600,6 +596,14 @@
     ],
     enabled: false,
     target: {
+        android: {
+            // Build static test binary on device, to make sure libdexfile_static can be used in
+            // static simpleperf binary in ndk.
+            static_executable: true,
+            static_libs: [
+                "libc",
+            ],
+        },
         linux: {
             enabled: true,
         },
diff --git a/libdexfile/art_standalone_libdexfile_tests.xml b/libdexfile/art_standalone_libdexfile_tests.xml
index 2c0e591..0a19f6d 100644
--- a/libdexfile/art_standalone_libdexfile_tests.xml
+++ b/libdexfile/art_standalone_libdexfile_tests.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Runs art_standalone_libdexfile_tests.">
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="art_standalone_libdexfile_tests->/data/local/tmp/art_standalone_libdexfile_tests/art_standalone_libdexfile_tests" />
@@ -25,8 +26,10 @@
         <option name="push" value="art-gtest-jars-GetMethodSignature.jar->/data/local/tmp/art_standalone_libdexfile_tests/art-gtest-jars-GetMethodSignature.jar" />
         <option name="push" value="art-gtest-jars-Lookup.jar->/data/local/tmp/art_standalone_libdexfile_tests/art-gtest-jars-Lookup.jar" />
         <option name="push" value="art-gtest-jars-Main.jar->/data/local/tmp/art_standalone_libdexfile_tests/art-gtest-jars-Main.jar" />
+        <option name="push" value="art-gtest-jars-MainEmptyUncompressed.jar->/data/local/tmp/art_standalone_libdexfile_tests/art-gtest-jars-MainEmptyUncompressed.jar" />
         <option name="push" value="art-gtest-jars-MultiDex.jar->/data/local/tmp/art_standalone_libdexfile_tests/art-gtest-jars-MultiDex.jar" />
         <option name="push" value="art-gtest-jars-Nested.jar->/data/local/tmp/art_standalone_libdexfile_tests/art-gtest-jars-Nested.jar" />
+        <option name="push" value="art-gtest-jars-VerifierDeps.dex->/data/local/tmp/art_standalone_libdexfile_tests/art-gtest-jars-VerifierDeps.dex" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.GTest" >
diff --git a/libdexfile/dex/art_dex_file_loader.cc b/libdexfile/dex/art_dex_file_loader.cc
index a7dd13e..ac52ca0 100644
--- a/libdexfile/dex/art_dex_file_loader.cc
+++ b/libdexfile/dex/art_dex_file_loader.cc
@@ -18,10 +18,12 @@
 
 #include <sys/stat.h>
 
-#include "android-base/stringprintf.h"
+#include <memory>
 
+#include "android-base/stringprintf.h"
 #include "base/file_magic.h"
 #include "base/file_utils.h"
+#include "base/logging.h"
 #include "base/mem_map.h"
 #include "base/mman.h"  // For the PROT_* and MAP_* constants.
 #include "base/stl_util.h"
@@ -35,61 +37,14 @@
 
 namespace art {
 
-namespace {
-
-class MemMapContainer : public DexFileContainer {
- public:
-  explicit MemMapContainer(MemMap&& mem_map) : mem_map_(std::move(mem_map)) { }
-  ~MemMapContainer() override { }
-
-  int GetPermissions() override {
-    if (!mem_map_.IsValid()) {
-      return 0;
-    } else {
-      return mem_map_.GetProtect();
-    }
-  }
-
-  bool IsReadOnly() override {
-    return GetPermissions() == PROT_READ;
-  }
-
-  bool EnableWrite() override {
-    CHECK(IsReadOnly());
-    if (!mem_map_.IsValid()) {
-      return false;
-    } else {
-      return mem_map_.Protect(PROT_READ | PROT_WRITE);
-    }
-  }
-
-  bool DisableWrite() override {
-    CHECK(!IsReadOnly());
-    if (!mem_map_.IsValid()) {
-      return false;
-    } else {
-      return mem_map_.Protect(PROT_READ);
-    }
-  }
-
- private:
-  MemMap mem_map_;
-  DISALLOW_COPY_AND_ASSIGN(MemMapContainer);
-};
-
-}  // namespace
-
 using android::base::StringPrintf;
 
-static constexpr OatDexFile* kNoOatDexFile = nullptr;
-
-
 bool ArtDexFileLoader::GetMultiDexChecksums(const char* filename,
                                             std::vector<uint32_t>* checksums,
                                             std::vector<std::string>* dex_locations,
                                             std::string* error_msg,
                                             int zip_fd,
-                                            bool* zip_file_only_contains_uncompressed_dex) const {
+                                            bool* zip_file_only_contains_uncompressed_dex) {
   CHECK(checksums != nullptr);
   uint32_t magic;
 
@@ -114,18 +69,22 @@
       return false;
     }
 
+    if (zip_file_only_contains_uncompressed_dex != nullptr) {
+      // Start by assuming everything is uncompressed.
+      *zip_file_only_contains_uncompressed_dex = true;
+    }
+
     uint32_t idx = 0;
     std::string zip_entry_name = GetMultiDexClassesDexName(idx);
     std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(zip_entry_name.c_str(), error_msg));
     if (zip_entry.get() == nullptr) {
-      *error_msg = StringPrintf("Zip archive '%s' doesn't contain %s (error msg: %s)", filename,
-          zip_entry_name.c_str(), error_msg->c_str());
-      return false;
-    }
-
-    if (zip_file_only_contains_uncompressed_dex != nullptr) {
-      // Start by assuming everything is uncompressed.
-      *zip_file_only_contains_uncompressed_dex = true;
+      // A zip file with no dex code should be accepted. It's likely a config split APK, which we
+      // are currently passing from higher levels.
+      VLOG(dex) << StringPrintf("Zip archive '%s' doesn't contain %s (error msg: %s)",
+                                filename,
+                                zip_entry_name.c_str(),
+                                error_msg->c_str());
+      return true;
     }
 
     do {
@@ -142,16 +101,18 @@
     return true;
   }
   if (IsMagicValid(magic)) {
-    std::unique_ptr<const DexFile> dex_file(OpenFile(fd.Release(),
-                                                     filename,
-                                                     /* verify= */ false,
-                                                     /* verify_checksum= */ false,
-                                                     /* mmap_shared= */ false,
-                                                     error_msg));
-    if (dex_file == nullptr) {
+    ArtDexFileLoader loader(fd.Release(), filename);
+    std::vector<std::unique_ptr<const DexFile>> dex_files;
+    if (!loader.Open(/* verify= */ false,
+                     /* verify_checksum= */ false,
+                     error_msg,
+                     &dex_files)) {
       return false;
     }
-    checksums->push_back(dex_file->GetHeader().checksum_);
+    for (auto& dex_file : dex_files) {
+      checksums->push_back(dex_file->GetHeader().checksum_);
+      dex_locations->push_back(dex_file->GetLocation());
+    }
     return true;
   }
   *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename);
@@ -168,11 +129,10 @@
     bool verify_checksum,
     std::string* error_msg,
     std::unique_ptr<DexFileContainer> container) const {
-  ScopedTrace trace(std::string("Open dex file from RAM ") + location);
   return OpenCommon(base,
                     size,
-                    /*data_base=*/ nullptr,
-                    /*data_size=*/ 0u,
+                    /*data_base=*/nullptr,
+                    /*data_size=*/0,
                     location,
                     location_checksum,
                     oat_dex_file,
@@ -180,46 +140,17 @@
                     verify_checksum,
                     error_msg,
                     std::move(container),
-                    /*verify_result=*/ nullptr);
+                    /*verify_result=*/nullptr);
 }
 
 std::unique_ptr<const DexFile> ArtDexFileLoader::Open(const std::string& location,
                                                       uint32_t location_checksum,
-                                                      MemMap&& map,
+                                                      MemMap&& mem_map,
                                                       bool verify,
                                                       bool verify_checksum,
                                                       std::string* error_msg) const {
-  ScopedTrace trace(std::string("Open dex file from mapped-memory ") + location);
-  CHECK(map.IsValid());
-
-  size_t size = map.Size();
-  if (size < sizeof(DexFile::Header)) {
-    *error_msg = StringPrintf(
-        "DexFile: failed to open dex file '%s' that is too short to have a header",
-        location.c_str());
-    return nullptr;
-  }
-
-  uint8_t* begin = map.Begin();
-  std::unique_ptr<DexFile> dex_file = OpenCommon(begin,
-                                                 size,
-                                                 /*data_base=*/ nullptr,
-                                                 /*data_size=*/ 0u,
-                                                 location,
-                                                 location_checksum,
-                                                 kNoOatDexFile,
-                                                 verify,
-                                                 verify_checksum,
-                                                 error_msg,
-                                                 std::make_unique<MemMapContainer>(std::move(map)),
-                                                 /*verify_result=*/ nullptr);
-  // Opening CompactDex is only supported from vdex files.
-  if (dex_file != nullptr && dex_file->IsCompactDexFile()) {
-    *error_msg = StringPrintf("Opening CompactDex file '%s' is only supported from vdex files",
-                              location.c_str());
-    return nullptr;
-  }
-  return dex_file;
+  ArtDexFileLoader loader(std::move(mem_map), location);
+  return loader.Open(location_checksum, verify, verify_checksum, error_msg);
 }
 
 bool ArtDexFileLoader::Open(const char* filename,
@@ -228,391 +159,8 @@
                             bool verify_checksum,
                             std::string* error_msg,
                             std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  uint32_t magic;
-  File fd = OpenAndReadMagic(filename, &magic, error_msg);
-  if (fd.Fd() == -1) {
-    DCHECK(!error_msg->empty());
-    return false;
-  }
-  return OpenWithMagic(
-      magic, fd.Release(), location, verify, verify_checksum, error_msg, dex_files);
-}
-
-bool ArtDexFileLoader::Open(int fd,
-                            const std::string& location,
-                            bool verify,
-                            bool verify_checksum,
-                            std::string* error_msg,
-                            std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  uint32_t magic;
-  if (!ReadMagicAndReset(fd, &magic, error_msg)) {
-    DCHECK(!error_msg->empty());
-    return false;
-  }
-  return OpenWithMagic(magic, fd, location, verify, verify_checksum, error_msg, dex_files);
-}
-
-bool ArtDexFileLoader::Open(const char* filename,
-                            int fd,
-                            const std::string& location,
-                            bool verify,
-                            bool verify_checksum,
-                            std::string* error_msg,
-                            std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  return fd == -1
-      ? Open(filename, location, verify, verify_checksum, error_msg, dex_files)
-      : Open(fd, location, verify, verify_checksum, error_msg, dex_files);
-}
-
-bool ArtDexFileLoader::OpenWithMagic(uint32_t magic,
-                                     int fd,
-                                     const std::string& location,
-                                     bool verify,
-                                     bool verify_checksum,
-                                     std::string* error_msg,
-                                     std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  ScopedTrace trace(std::string("Open dex file ") + std::string(location));
-  DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr";
-  if (IsZipMagic(magic)) {
-    return OpenZip(fd, location, verify, verify_checksum, error_msg, dex_files);
-  }
-  if (IsMagicValid(magic)) {
-    std::unique_ptr<const DexFile> dex_file(OpenFile(fd,
-                                                     location,
-                                                     verify,
-                                                     verify_checksum,
-                                                     /* mmap_shared= */ false,
-                                                     error_msg));
-    if (dex_file.get() != nullptr) {
-      dex_files->push_back(std::move(dex_file));
-      return true;
-    } else {
-      return false;
-    }
-  }
-  *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", location.c_str());
-  return false;
-}
-
-std::unique_ptr<const DexFile> ArtDexFileLoader::OpenDex(int fd,
-                                                         const std::string& location,
-                                                         bool verify,
-                                                         bool verify_checksum,
-                                                         bool mmap_shared,
-                                                         std::string* error_msg) const {
-  ScopedTrace trace("Open dex file " + std::string(location));
-  return OpenFile(fd, location, verify, verify_checksum, mmap_shared, error_msg);
-}
-
-bool ArtDexFileLoader::OpenZip(int fd,
-                               const std::string& location,
-                               bool verify,
-                               bool verify_checksum,
-                               std::string* error_msg,
-                               std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  ScopedTrace trace("Dex file open Zip " + std::string(location));
-  return OpenZipInternal(ZipArchive::OpenFromFd(fd, location.c_str(), error_msg),
-                         location,
-                         verify,
-                         verify_checksum,
-                         error_msg,
-                         dex_files);
-}
-
-bool ArtDexFileLoader::OpenZipFromOwnedFd(
-    int fd,
-    const std::string& location,
-    bool verify,
-    bool verify_checksum,
-    std::string* error_msg,
-    std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  ScopedTrace trace("Dex file open Zip " + std::string(location) + " (owned fd)");
-  return OpenZipInternal(ZipArchive::OpenFromOwnedFd(fd, location.c_str(), error_msg),
-                         location,
-                         verify,
-                         verify_checksum,
-                         error_msg,
-                         dex_files);
-}
-
-bool ArtDexFileLoader::OpenZipInternal(
-    ZipArchive* raw_zip_archive,
-    const std::string& location,
-    bool verify,
-    bool verify_checksum,
-    std::string* error_msg,
-    std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  DCHECK(dex_files != nullptr) << "DexFile::OpenZip: out-param is nullptr";
-  std::unique_ptr<ZipArchive> zip_archive(raw_zip_archive);
-  if (zip_archive.get() == nullptr) {
-    DCHECK(!error_msg->empty());
-    return false;
-  }
-  return OpenAllDexFilesFromZip(
-      *zip_archive, location, verify, verify_checksum, error_msg, dex_files);
-}
-
-std::unique_ptr<const DexFile> ArtDexFileLoader::OpenFile(int fd,
-                                                          const std::string& location,
-                                                          bool verify,
-                                                          bool verify_checksum,
-                                                          bool mmap_shared,
-                                                          std::string* error_msg) const {
-  ScopedTrace trace(std::string("Open dex file ") + std::string(location));
-  CHECK(!location.empty());
-  MemMap map;
-  {
-    File delayed_close(fd, /* check_usage= */ false);
-    struct stat sbuf;
-    memset(&sbuf, 0, sizeof(sbuf));
-    if (fstat(fd, &sbuf) == -1) {
-      *error_msg = StringPrintf("DexFile: fstat '%s' failed: %s", location.c_str(),
-                                strerror(errno));
-      return nullptr;
-    }
-    if (S_ISDIR(sbuf.st_mode)) {
-      *error_msg = StringPrintf("Attempt to mmap directory '%s'", location.c_str());
-      return nullptr;
-    }
-    size_t length = sbuf.st_size;
-    map = MemMap::MapFile(length,
-                          PROT_READ,
-                          mmap_shared ? MAP_SHARED : MAP_PRIVATE,
-                          fd,
-                          0,
-                          /*low_4gb=*/false,
-                          location.c_str(),
-                          error_msg);
-    if (!map.IsValid()) {
-      DCHECK(!error_msg->empty());
-      return nullptr;
-    }
-  }
-
-  const uint8_t* begin = map.Begin();
-  size_t size = map.Size();
-  if (size < sizeof(DexFile::Header)) {
-    *error_msg = StringPrintf(
-        "DexFile: failed to open dex file '%s' that is too short to have a header",
-        location.c_str());
-    return nullptr;
-  }
-
-  const DexFile::Header* dex_header = reinterpret_cast<const DexFile::Header*>(begin);
-
-  std::unique_ptr<DexFile> dex_file = OpenCommon(begin,
-                                                 size,
-                                                 /*data_base=*/ nullptr,
-                                                 /*data_size=*/ 0u,
-                                                 location,
-                                                 dex_header->checksum_,
-                                                 kNoOatDexFile,
-                                                 verify,
-                                                 verify_checksum,
-                                                 error_msg,
-                                                 std::make_unique<MemMapContainer>(std::move(map)),
-                                                 /*verify_result=*/ nullptr);
-
-  // Opening CompactDex is only supported from vdex files.
-  if (dex_file != nullptr && dex_file->IsCompactDexFile()) {
-    *error_msg = StringPrintf("Opening CompactDex file '%s' is only supported from vdex files",
-                              location.c_str());
-    return nullptr;
-  }
-  return dex_file;
-}
-
-std::unique_ptr<const DexFile> ArtDexFileLoader::OpenOneDexFileFromZip(
-    const ZipArchive& zip_archive,
-    const char* entry_name,
-    const std::string& location,
-    bool verify,
-    bool verify_checksum,
-    std::string* error_msg,
-    DexFileLoaderErrorCode* error_code) const {
-  ScopedTrace trace("Dex file open from Zip Archive " + std::string(location));
-  CHECK(!location.empty());
-  std::unique_ptr<ZipEntry> zip_entry(zip_archive.Find(entry_name, error_msg));
-  if (zip_entry == nullptr) {
-    *error_code = DexFileLoaderErrorCode::kEntryNotFound;
-    return nullptr;
-  }
-  if (zip_entry->GetUncompressedLength() == 0) {
-    *error_msg = StringPrintf("Dex file '%s' has zero length", location.c_str());
-    *error_code = DexFileLoaderErrorCode::kDexFileError;
-    return nullptr;
-  }
-
-  MemMap map;
-  if (zip_entry->IsUncompressed()) {
-    if (!zip_entry->IsAlignedTo(alignof(DexFile::Header))) {
-      // Do not mmap unaligned ZIP entries because
-      // doing so would fail dex verification which requires 4 byte alignment.
-      LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; "
-                   << "please zipalign to " << alignof(DexFile::Header) << " bytes. "
-                   << "Falling back to extracting file.";
-    } else {
-      // Map uncompressed files within zip as file-backed to avoid a dirty copy.
-      map = zip_entry->MapDirectlyFromFile(location.c_str(), /*out*/error_msg);
-      if (!map.IsValid()) {
-        LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; "
-                     << "is your ZIP file corrupted? Falling back to extraction.";
-        // Try again with Extraction which still has a chance of recovery.
-      }
-    }
-  }
-
-  ScopedTrace map_extract_trace(StringPrintf("Mapped=%s Extracted=%s",
-      map.IsValid() ? "true" : "false",
-      map.IsValid() ? "false" : "true"));  // this is redundant but much easier to read in traces.
-
-  if (!map.IsValid()) {
-    // Default path for compressed ZIP entries,
-    // and fallback for stored ZIP entries.
-    map = zip_entry->ExtractToMemMap(location.c_str(), entry_name, error_msg);
-  }
-
-  if (!map.IsValid()) {
-    *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", entry_name, location.c_str(),
-                              error_msg->c_str());
-    *error_code = DexFileLoaderErrorCode::kExtractToMemoryError;
-    return nullptr;
-  }
-  VerifyResult verify_result;
-  uint8_t* begin = map.Begin();
-  size_t size = map.Size();
-  std::unique_ptr<DexFile> dex_file = OpenCommon(begin,
-                                                 size,
-                                                 /*data_base=*/ nullptr,
-                                                 /*data_size=*/ 0u,
-                                                 location,
-                                                 zip_entry->GetCrc32(),
-                                                 kNoOatDexFile,
-                                                 verify,
-                                                 verify_checksum,
-                                                 error_msg,
-                                                 std::make_unique<MemMapContainer>(std::move(map)),
-                                                 &verify_result);
-  if (dex_file != nullptr && dex_file->IsCompactDexFile()) {
-    *error_msg = StringPrintf("Opening CompactDex file '%s' is only supported from vdex files",
-                              location.c_str());
-    return nullptr;
-  }
-  if (dex_file == nullptr) {
-    if (verify_result == VerifyResult::kVerifyNotAttempted) {
-      *error_code = DexFileLoaderErrorCode::kDexFileError;
-    } else {
-      *error_code = DexFileLoaderErrorCode::kVerifyError;
-    }
-    return nullptr;
-  }
-  if (!dex_file->DisableWrite()) {
-    *error_msg = StringPrintf("Failed to make dex file '%s' read only", location.c_str());
-    *error_code = DexFileLoaderErrorCode::kMakeReadOnlyError;
-    return nullptr;
-  }
-  CHECK(dex_file->IsReadOnly()) << location;
-  if (verify_result != VerifyResult::kVerifySucceeded) {
-    *error_code = DexFileLoaderErrorCode::kVerifyError;
-    return nullptr;
-  }
-  *error_code = DexFileLoaderErrorCode::kNoError;
-  return dex_file;
-}
-
-// Technically we do not have a limitation with respect to the number of dex files that can be in a
-// multidex APK. However, it's bad practice, as each dex file requires its own tables for symbols
-// (types, classes, methods, ...) and dex caches. So warn the user that we open a zip with what
-// seems an excessive number.
-static constexpr size_t kWarnOnManyDexFilesThreshold = 100;
-
-bool ArtDexFileLoader::OpenAllDexFilesFromZip(
-    const ZipArchive& zip_archive,
-    const std::string& location,
-    bool verify,
-    bool verify_checksum,
-    std::string* error_msg,
-    std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  ScopedTrace trace("Dex file open from Zip " + std::string(location));
-  DCHECK(dex_files != nullptr) << "DexFile::OpenFromZip: out-param is nullptr";
-  DexFileLoaderErrorCode error_code;
-  std::unique_ptr<const DexFile> dex_file(OpenOneDexFileFromZip(zip_archive,
-                                                                kClassesDex,
-                                                                location,
-                                                                verify,
-                                                                verify_checksum,
-                                                                error_msg,
-                                                                &error_code));
-  if (dex_file.get() == nullptr) {
-    return false;
-  } else {
-    // Had at least classes.dex.
-    dex_files->push_back(std::move(dex_file));
-
-    // Now try some more.
-
-    // We could try to avoid std::string allocations by working on a char array directly. As we
-    // do not expect a lot of iterations, this seems too involved and brittle.
-
-    for (size_t i = 1; ; ++i) {
-      std::string name = GetMultiDexClassesDexName(i);
-      std::string fake_location = GetMultiDexLocation(i, location.c_str());
-      std::unique_ptr<const DexFile> next_dex_file(OpenOneDexFileFromZip(zip_archive,
-                                                                         name.c_str(),
-                                                                         fake_location,
-                                                                         verify,
-                                                                         verify_checksum,
-                                                                         error_msg,
-                                                                         &error_code));
-      if (next_dex_file.get() == nullptr) {
-        if (error_code != DexFileLoaderErrorCode::kEntryNotFound) {
-          LOG(WARNING) << "Zip open failed: " << *error_msg;
-        }
-        break;
-      } else {
-        dex_files->push_back(std::move(next_dex_file));
-      }
-
-      if (i == kWarnOnManyDexFilesThreshold) {
-        LOG(WARNING) << location << " has in excess of " << kWarnOnManyDexFilesThreshold
-                     << " dex files. Please consider coalescing and shrinking the number to "
-                        " avoid runtime overhead.";
-      }
-
-      if (i == std::numeric_limits<size_t>::max()) {
-        LOG(ERROR) << "Overflow in number of dex files!";
-        break;
-      }
-    }
-
-    return true;
-  }
-}
-
-std::unique_ptr<DexFile> ArtDexFileLoader::OpenCommon(const uint8_t* base,
-                                                      size_t size,
-                                                      const uint8_t* data_base,
-                                                      size_t data_size,
-                                                      const std::string& location,
-                                                      uint32_t location_checksum,
-                                                      const OatDexFile* oat_dex_file,
-                                                      bool verify,
-                                                      bool verify_checksum,
-                                                      std::string* error_msg,
-                                                      std::unique_ptr<DexFileContainer> container,
-                                                      VerifyResult* verify_result) {
-  return DexFileLoader::OpenCommon(base,
-                                   size,
-                                   data_base,
-                                   data_size,
-                                   location,
-                                   location_checksum,
-                                   oat_dex_file,
-                                   verify,
-                                   verify_checksum,
-                                   error_msg,
-                                   std::move(container),
-                                   verify_result);
+  ArtDexFileLoader loader(filename, location);
+  return loader.Open(verify, verify_checksum, error_msg, dex_files);
 }
 
 }  // namespace art
diff --git a/libdexfile/dex/art_dex_file_loader.h b/libdexfile/dex/art_dex_file_loader.h
index 49c81f4..9f2ae82 100644
--- a/libdexfile/dex/art_dex_file_loader.h
+++ b/libdexfile/dex/art_dex_file_loader.h
@@ -36,6 +36,7 @@
 // Class that is used to open dex files and deal with corresponding multidex and location logic.
 class ArtDexFileLoader : public DexFileLoader {
  public:
+  using DexFileLoader::DexFileLoader;
   virtual ~ArtDexFileLoader() { }
 
   // Returns the checksums of a file for comparison with GetLocationChecksum().
@@ -51,136 +52,42 @@
   // locations.
   //
   // Return true if the checksums could be found, false otherwise.
-  bool GetMultiDexChecksums(const char* filename,
-                            std::vector<uint32_t>* checksums,
-                            std::vector<std::string>* dex_locations,
-                            std::string* error_msg,
-                            int zip_fd = -1,
-                            bool* only_contains_uncompressed_dex = nullptr) const override;
+  static bool GetMultiDexChecksums(const char* filename,
+                                   std::vector<uint32_t>* checksums,
+                                   std::vector<std::string>* dex_locations,
+                                   std::string* error_msg,
+                                   int zip_fd = -1,
+                                   bool* only_contains_uncompressed_dex = nullptr);
 
-  // Opens .dex file, backed by existing memory
-  std::unique_ptr<const DexFile> Open(
-      const uint8_t* base,
-      size_t size,
-      const std::string& location,
-      uint32_t location_checksum,
-      const OatDexFile* oat_dex_file,
-      bool verify,
-      bool verify_checksum,
-      std::string* error_msg,
-      std::unique_ptr<DexFileContainer> container = nullptr) const override;
+  // Don't shadow overloads from base class.
+  using DexFileLoader::Open;
 
-  // Opens .dex file that has been memory-mapped by the caller.
+  // Old signature preserved for app-compat.
+  std::unique_ptr<const DexFile> Open(const uint8_t* base,
+                                      size_t size,
+                                      const std::string& location,
+                                      uint32_t location_checksum,
+                                      const OatDexFile* oat_dex_file,
+                                      bool verify,
+                                      bool verify_checksum,
+                                      std::string* error_msg,
+                                      std::unique_ptr<DexFileContainer> container) const;
+
+  // Old signature preserved for app-compat.
   std::unique_ptr<const DexFile> Open(const std::string& location,
-                                      uint32_t location_checkum,
+                                      uint32_t location_checksum,
                                       MemMap&& mem_map,
                                       bool verify,
                                       bool verify_checksum,
                                       std::string* error_msg) const;
 
-  // Opens all .dex files found in the file, guessing the container format based on file magic.
+  // Old signature preserved for app-compat.
   bool Open(const char* filename,
             const std::string& location,
             bool verify,
             bool verify_checksum,
             std::string* error_msg,
             std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
-  bool Open(int fd,
-            const std::string& location,
-            bool verify,
-            bool verify_checksum,
-            std::string* error_msg,
-            std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
-  // Opens all .dex files found in the file, guessing the container format based on file magic.
-  // If the fd is -1 then the dex files are opened using the filename; otherwise they are
-  // opened using the fd.
-  bool Open(const char* filename,
-            int fd,
-            const std::string& location,
-            bool verify,
-            bool verify_checksum,
-            std::string* error_msg,
-            std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
-
-  // Open a single dex file from an fd. This function closes the fd.
-  std::unique_ptr<const DexFile> OpenDex(int fd,
-                                         const std::string& location,
-                                         bool verify,
-                                         bool verify_checksum,
-                                         bool mmap_shared,
-                                         std::string* error_msg) const;
-
-  // Opens dex files from within a .jar, .zip, or .apk file using its file descriptor. The file
-  // descriptor ownership is taken over, i.e. will be closed by this class.
-  bool OpenZip(int fd,
-               const std::string& location,
-               bool verify,
-               bool verify_checksum,
-               std::string* error_msg,
-               std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
-
-  // Opens dex files from within a .jar, .zip, or .apk file using its file descriptor. The file
-  // descriptor is assumed owned by the caller.
-  bool OpenZipFromOwnedFd(int fd,
-                          const std::string& location,
-                          bool verify,
-                          bool verify_checksum,
-                          std::string* error_msg,
-                          std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
-
- private:
-  bool OpenWithMagic(uint32_t magic,
-                     int fd,
-                     const std::string& location,
-                     bool verify,
-                     bool verify_checksum,
-                     std::string* error_msg,
-                     std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
-
-  std::unique_ptr<const DexFile> OpenFile(int fd,
-                                          const std::string& location,
-                                          bool verify,
-                                          bool verify_checksum,
-                                          bool mmap_shared,
-                                          std::string* error_msg) const;
-
-  // Open all classesXXX.dex files from a zip archive.
-  bool OpenAllDexFilesFromZip(const ZipArchive& zip_archive,
-                              const std::string& location,
-                              bool verify,
-                              bool verify_checksum,
-                              std::string* error_msg,
-                              std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
-
-  // Opens .dex file from the entry_name in a zip archive. error_code is undefined when non-null
-  // return.
-  std::unique_ptr<const DexFile> OpenOneDexFileFromZip(const ZipArchive& zip_archive,
-                                                       const char* entry_name,
-                                                       const std::string& location,
-                                                       bool verify,
-                                                       bool verify_checksum,
-                                                       std::string* error_msg,
-                                                       DexFileLoaderErrorCode* error_code) const;
-
-  bool OpenZipInternal(ZipArchive* raw_zip_archive,
-                       const std::string& location,
-                       bool verify,
-                       bool verify_checksum,
-                       std::string* error_msg,
-                       std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
-
-  static std::unique_ptr<DexFile> OpenCommon(const uint8_t* base,
-                                             size_t size,
-                                             const uint8_t* data_base,
-                                             size_t data_size,
-                                             const std::string& location,
-                                             uint32_t location_checksum,
-                                             const OatDexFile* oat_dex_file,
-                                             bool verify,
-                                             bool verify_checksum,
-                                             std::string* error_msg,
-                                             std::unique_ptr<DexFileContainer> container,
-                                             VerifyResult* verify_result);
 };
 
 }  // namespace art
diff --git a/libdexfile/dex/art_dex_file_loader_test.cc b/libdexfile/dex/art_dex_file_loader_test.cc
index 1863c1b..d8911ec 100644
--- a/libdexfile/dex/art_dex_file_loader_test.cc
+++ b/libdexfile/dex/art_dex_file_loader_test.cc
@@ -57,6 +57,38 @@
   ASSERT_TRUE(dex.get() != nullptr);
 }
 
+TEST_F(ArtDexFileLoaderTest, OpenZipMultiDex) {
+  std::string zip_file = GetTestDexFileName("MultiDex");
+  File file(zip_file, O_RDONLY, /*check_usage=*/false);
+  ASSERT_GE(file.Fd(), 0);
+  std::vector<std::unique_ptr<const DexFile>> dex_files;
+  std::string error_msg;
+  ArtDexFileLoader dex_file_loader(file.Release(), zip_file);
+  ASSERT_TRUE(dex_file_loader.Open(/*verify=*/false,
+                                   /*verify_checksum=*/true,
+                                   /*allow_no_dex_files=*/true,
+                                   &error_msg,
+                                   &dex_files))
+      << error_msg;
+  EXPECT_GT(dex_files.size(), 1);
+}
+
+TEST_F(ArtDexFileLoaderTest, OpenZipEmpty) {
+  std::string zip_file = GetTestDexFileName("MainEmptyUncompressed");
+  File file(zip_file, O_RDONLY, /*check_usage=*/false);
+  ASSERT_GE(file.Fd(), 0);
+  std::vector<std::unique_ptr<const DexFile>> dex_files;
+  std::string error_msg;
+  ArtDexFileLoader dex_file_loader(file.Release(), zip_file);
+  ASSERT_TRUE(dex_file_loader.Open(/*verify=*/false,
+                                   /*verify_checksum=*/true,
+                                   /*allow_no_dex_files=*/true,
+                                   &error_msg,
+                                   &dex_files))
+      << error_msg;
+  EXPECT_EQ(dex_files.size(), 0);
+}
+
 TEST_F(ArtDexFileLoaderTest, GetLocationChecksum) {
   std::unique_ptr<const DexFile> raw(OpenTestDexFile("Main"));
   EXPECT_NE(raw->GetHeader().checksum_, raw->GetLocationChecksum());
@@ -66,11 +98,8 @@
   std::vector<uint32_t> checksums;
   std::vector<std::string> dex_locations;
   std::string error_msg;
-  const ArtDexFileLoader dex_file_loader;
-  EXPECT_TRUE(dex_file_loader.GetMultiDexChecksums(GetLibCoreDexFileNames()[0].c_str(),
-                                                   &checksums,
-                                                   &dex_locations,
-                                                   &error_msg))
+  EXPECT_TRUE(ArtDexFileLoader::GetMultiDexChecksums(
+      GetLibCoreDexFileNames()[0].c_str(), &checksums, &dex_locations, &error_msg))
       << error_msg;
   ASSERT_EQ(1U, checksums.size());
   ASSERT_EQ(1U, dex_locations.size());
@@ -83,11 +112,9 @@
   std::vector<uint32_t> checksums;
   std::vector<std::string> dex_locations;
   std::string multidex_file = GetTestDexFileName("MultiDex");
-  const ArtDexFileLoader dex_file_loader;
-  EXPECT_TRUE(dex_file_loader.GetMultiDexChecksums(multidex_file.c_str(),
-                                                   &checksums,
-                                                   &dex_locations,
-                                                   &error_msg)) << error_msg;
+  EXPECT_TRUE(ArtDexFileLoader::GetMultiDexChecksums(
+      multidex_file.c_str(), &checksums, &dex_locations, &error_msg))
+      << error_msg;
 
   std::vector<std::unique_ptr<const DexFile>> dexes = OpenTestDexFiles("MultiDex");
   ASSERT_EQ(2U, dexes.size());
@@ -103,6 +130,32 @@
   EXPECT_EQ(dexes[1]->GetLocationChecksum(), checksums[1]);
 }
 
+TEST_F(ArtDexFileLoaderTest, GetMultiDexChecksumsEmptyZip) {
+  std::string error_msg;
+  std::vector<uint32_t> checksums;
+  std::vector<std::string> dex_locations;
+  std::string multidex_file = GetTestDexFileName("MainEmptyUncompressed");
+  EXPECT_TRUE(ArtDexFileLoader::GetMultiDexChecksums(
+      multidex_file.c_str(), &checksums, &dex_locations, &error_msg))
+      << error_msg;
+
+  EXPECT_EQ(dex_locations.size(), 0);
+  EXPECT_EQ(checksums.size(), 0);
+}
+
+TEST_F(ArtDexFileLoaderTest, GetMultiDexChecksumsDexFile) {
+  std::string error_msg;
+  std::vector<uint32_t> checksums;
+  std::vector<std::string> dex_locations;
+  std::string multidex_file = GetTestDexFileName("VerifierDeps");  // This is a .dex file.
+  EXPECT_TRUE(ArtDexFileLoader::GetMultiDexChecksums(
+      multidex_file.c_str(), &checksums, &dex_locations, &error_msg))
+      << error_msg;
+
+  EXPECT_EQ(dex_locations.size(), 1);
+  EXPECT_EQ(checksums.size(), 1);
+}
+
 TEST_F(ArtDexFileLoaderTest, ClassDefs) {
   std::unique_ptr<const DexFile> raw(OpenTestDexFile("Nested"));
   ASSERT_TRUE(raw.get() != nullptr);
diff --git a/libdexfile/dex/code_item_accessors_test.cc b/libdexfile/dex/code_item_accessors_test.cc
index c5891f9..7cb2fe0 100644
--- a/libdexfile/dex/code_item_accessors_test.cc
+++ b/libdexfile/dex/code_item_accessors_test.cc
@@ -36,16 +36,16 @@
     CompactDexFile::WriteCurrentVersion(header->magic_);
     header->data_off_ = 0;
     header->data_size_ = data->size();
+    header->file_size_ = data->size();
   } else {
+    auto* header = reinterpret_cast<DexFile::Header*>(data->data());
     StandardDexFile::WriteMagic(data->data());
     StandardDexFile::WriteCurrentVersion(data->data());
+    header->file_size_ = data->size();
   }
-  const DexFileLoader dex_file_loader;
+  DexFileLoader dex_file_loader(data->data(), data->size(), "location");
   std::string error_msg;
-  std::unique_ptr<const DexFile> dex(dex_file_loader.Open(data->data(),
-                                                          data->size(),
-                                                          "location",
-                                                          /*location_checksum=*/ 123,
+  std::unique_ptr<const DexFile> dex(dex_file_loader.Open(/*location_checksum=*/123,
                                                           /*oat_dex_file=*/nullptr,
                                                           /*verify=*/false,
                                                           /*verify_checksum=*/false,
diff --git a/libdexfile/dex/compact_dex_file.cc b/libdexfile/dex/compact_dex_file.cc
index a5044aa..9c7000f 100644
--- a/libdexfile/dex/compact_dex_file.cc
+++ b/libdexfile/dex/compact_dex_file.cc
@@ -16,15 +16,14 @@
 
 #include "compact_dex_file.h"
 
+#include <memory>
+
 #include "base/leb128.h"
 #include "code_item_accessors-inl.h"
 #include "dex_file-inl.h"
 
 namespace art {
 
-constexpr uint8_t CompactDexFile::kDexMagic[kDexMagicSize];
-constexpr uint8_t CompactDexFile::kDexMagicVersion[];
-
 void CompactDexFile::WriteMagic(uint8_t* magic) {
   std::copy_n(kDexMagic, kDexMagicSize, magic);
 }
@@ -86,21 +85,17 @@
 
 CompactDexFile::CompactDexFile(const uint8_t* base,
                                size_t size,
-                               const uint8_t* data_begin,
-                               size_t data_size,
                                const std::string& location,
                                uint32_t location_checksum,
                                const OatDexFile* oat_dex_file,
-                               std::unique_ptr<DexFileContainer> container)
+                               std::shared_ptr<DexFileContainer> container)
     : DexFile(base,
               size,
-              data_begin,
-              data_size,
               location,
               location_checksum,
               oat_dex_file,
               std::move(container),
-              /*is_compact_dex=*/ true),
+              /*is_compact_dex=*/true),
       debug_info_offsets_(DataBegin() + GetHeader().debug_info_offsets_pos_,
                           GetHeader().debug_info_base_,
                           GetHeader().debug_info_offsets_table_offset_) {}
diff --git a/libdexfile/dex/compact_dex_file.h b/libdexfile/dex/compact_dex_file.h
index 9c3b7a4..22f6c20 100644
--- a/libdexfile/dex/compact_dex_file.h
+++ b/libdexfile/dex/compact_dex_file.h
@@ -17,9 +17,11 @@
 #ifndef ART_LIBDEXFILE_DEX_COMPACT_DEX_FILE_H_
 #define ART_LIBDEXFILE_DEX_COMPACT_DEX_FILE_H_
 
+#include <memory>
+
 #include "base/casts.h"
-#include "dex_file.h"
 #include "dex/compact_offset_table.h"
+#include "dex_file.h"
 
 namespace art {
 
@@ -304,12 +306,11 @@
  private:
   CompactDexFile(const uint8_t* base,
                  size_t size,
-                 const uint8_t* data_begin,
-                 size_t data_size,
                  const std::string& location,
                  uint32_t location_checksum,
                  const OatDexFile* oat_dex_file,
-                 std::unique_ptr<DexFileContainer> container);
+                 // Shared since several dex files may be stored in the same logical container.
+                 std::shared_ptr<DexFileContainer> container);
 
   CompactOffsetTable::Accessor debug_info_offsets_;
 
diff --git a/libdexfile/dex/compact_offset_table.cc b/libdexfile/dex/compact_offset_table.cc
index 8601b19..deec124 100644
--- a/libdexfile/dex/compact_offset_table.cc
+++ b/libdexfile/dex/compact_offset_table.cc
@@ -21,8 +21,6 @@
 
 namespace art {
 
-constexpr size_t CompactOffsetTable::kElementsPerIndex;
-
 CompactOffsetTable::Accessor::Accessor(const uint8_t* data_begin,
                                        uint32_t minimum_offset,
                                        uint32_t table_offset)
diff --git a/libdexfile/dex/descriptors_names.cc b/libdexfile/dex/descriptors_names.cc
index 44cb7cb..dce5ecd 100644
--- a/libdexfile/dex/descriptors_names.cc
+++ b/libdexfile/dex/descriptors_names.cc
@@ -16,6 +16,8 @@
 
 #include "descriptors_names.h"
 
+#include <algorithm>
+
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 
@@ -36,38 +38,58 @@
   }
 
   // Reference or primitive?
+  bool primitive = false;
   if (*c == 'L') {
     // "[[La/b/C;" -> "a.b.C[][]".
     c++;  // Skip the 'L'.
   } else {
+    primitive = true;
     // "[[B" -> "byte[][]".
-    // To make life easier, we make primitives look like unqualified
-    // reference types.
     switch (*c) {
-      case 'B': c = "byte;"; break;
-      case 'C': c = "char;"; break;
-      case 'D': c = "double;"; break;
-      case 'F': c = "float;"; break;
-      case 'I': c = "int;"; break;
-      case 'J': c = "long;"; break;
-      case 'S': c = "short;"; break;
-      case 'Z': c = "boolean;"; break;
-      case 'V': c = "void;"; break;  // Used when decoding return types.
+      case 'B':
+        c = "byte";
+        break;
+      case 'C':
+        c = "char";
+        break;
+      case 'D':
+        c = "double";
+        break;
+      case 'F':
+        c = "float";
+        break;
+      case 'I':
+        c = "int";
+        break;
+      case 'J':
+        c = "long";
+        break;
+      case 'S':
+        c = "short";
+        break;
+      case 'Z':
+        c = "boolean";
+        break;
+      case 'V':
+        c = "void";
+        break;  // Used when decoding return types.
       default: result->append(descriptor); return;
     }
   }
 
-  // At this point, 'c' is a string of the form "fully/qualified/Type;"
-  // or "primitive;". Rewrite the type with '.' instead of '/':
-  const char* p = c;
-  while (*p != ';') {
-    char ch = *p++;
-    if (ch == '/') {
-      ch = '.';
+  // At this point, 'c' is a string of the form "fully/qualified/Type;" or
+  // "primitive". In the former case, rewrite the type with '.' instead of '/':
+  std::string temp(c);
+  if (!primitive) {
+    std::replace(temp.begin(), temp.end(), '/', '.');
+    // ...and remove the semicolon:
+    if (temp.back() == ';') {
+      temp.pop_back();
     }
-    result->push_back(ch);
   }
-  // ...and replace the semicolon with 'dim' "[]" pairs:
+  result->append(temp);
+
+  // Finally, add 'dim' "[]" pairs:
   for (size_t i = 0; i < dim; ++i) {
     result->append("[]");
   }
@@ -79,6 +101,56 @@
   return result;
 }
 
+std::string InversePrettyDescriptor(const std::string& pretty_descriptor) {
+  std::string result;
+
+  // Used to determine the length of the descriptor without trailing "[]"s.
+  size_t l = pretty_descriptor.length();
+
+  // Determine dimensionality, and append the necessary leading '['s.
+  size_t dim = 0;
+  size_t pos = 0;
+  static const std::string array_indicator = "[]";
+  while ((pos = pretty_descriptor.find(array_indicator, pos)) != std::string::npos) {
+    if (dim == 0) {
+      l = pos;
+    }
+    ++dim;
+    pos += array_indicator.length();
+  }
+  for (size_t i = 0; i < dim; ++i) {
+    result += '[';
+  }
+
+  // temp_descriptor is now in the form of "some.pretty.Type" or "primitive".
+  std::string temp_descriptor(pretty_descriptor, 0, l);
+  if (temp_descriptor == "byte") {
+    result += 'B';
+  } else if (temp_descriptor == "char") {
+    result += 'C';
+  } else if (temp_descriptor == "double") {
+    result += 'D';
+  } else if (temp_descriptor == "float") {
+    result += 'F';
+  } else if (temp_descriptor == "int") {
+    result += 'I';
+  } else if (temp_descriptor == "long") {
+    result += 'J';
+  } else if (temp_descriptor == "short") {
+    result += 'S';
+  } else if (temp_descriptor == "boolean") {
+    result += 'Z';
+  } else if (temp_descriptor == "void") {
+    result += 'V';
+  } else {
+    result += 'L';
+    std::replace(temp_descriptor.begin(), temp_descriptor.end(), '.', '/');
+    result += temp_descriptor;
+    result += ';';
+  }
+  return result;
+}
+
 std::string GetJniShortName(const std::string& class_descriptor, const std::string& method) {
   // Remove the leading 'L' and trailing ';'...
   std::string class_name(class_descriptor);
diff --git a/libdexfile/dex/descriptors_names.h b/libdexfile/dex/descriptors_names.h
index 62b0118..5ece97d 100644
--- a/libdexfile/dex/descriptors_names.h
+++ b/libdexfile/dex/descriptors_names.h
@@ -32,6 +32,11 @@
 std::string PrettyDescriptor(const char* descriptor);
 std::string PrettyDescriptor(Primitive::Type type);
 
+// Used to convert user-specified ignored types ("java.lang.String[]",
+// "byte[][]") to the form returned by art::mirror::Class->GetDescriptor()
+// ("[Ljava/lang/String;", "[[B").
+std::string InversePrettyDescriptor(const std::string& pretty_descriptor);
+
 // Performs JNI name mangling as described in section 11.3 "Linking Native Methods"
 // of the JNI spec.
 std::string MangleForJni(const std::string& s);
diff --git a/libdexfile/dex/descriptors_names_test.cc b/libdexfile/dex/descriptors_names_test.cc
index 90eba28..f3ec3ed 100644
--- a/libdexfile/dex/descriptors_names_test.cc
+++ b/libdexfile/dex/descriptors_names_test.cc
@@ -74,6 +74,45 @@
   EXPECT_EQ("short", PrettyDescriptor("S"));
 }
 
+TEST_F(DescriptorsNamesTest, InversePrettyDescriptor_ArrayReferences) {
+  EXPECT_EQ("[Ljava/lang/Class;", InversePrettyDescriptor("java.lang.Class[]"));
+  EXPECT_EQ("[[Ljava/lang/Class;", InversePrettyDescriptor("java.lang.Class[][]"));
+}
+
+TEST_F(DescriptorsNamesTest, InversePrettyDescriptor_ScalarReferences) {
+  EXPECT_EQ("Ljava/lang/String;", InversePrettyDescriptor("java.lang.String"));
+}
+
+TEST_F(DescriptorsNamesTest, InversePrettyDescriptor_PrimitiveArrays) {
+  EXPECT_EQ("[B", InversePrettyDescriptor("byte[]"));
+  EXPECT_EQ("[[B", InversePrettyDescriptor("byte[][]"));
+  EXPECT_EQ("[C", InversePrettyDescriptor("char[]"));
+  EXPECT_EQ("[[C", InversePrettyDescriptor("char[][]"));
+  EXPECT_EQ("[D", InversePrettyDescriptor("double[]"));
+  EXPECT_EQ("[[D", InversePrettyDescriptor("double[][]"));
+  EXPECT_EQ("[F", InversePrettyDescriptor("float[]"));
+  EXPECT_EQ("[[F", InversePrettyDescriptor("float[][]"));
+  EXPECT_EQ("[I", InversePrettyDescriptor("int[]"));
+  EXPECT_EQ("[[I", InversePrettyDescriptor("int[][]"));
+  EXPECT_EQ("[J", InversePrettyDescriptor("long[]"));
+  EXPECT_EQ("[[J", InversePrettyDescriptor("long[][]"));
+  EXPECT_EQ("[S", InversePrettyDescriptor("short[]"));
+  EXPECT_EQ("[[S", InversePrettyDescriptor("short[][]"));
+  EXPECT_EQ("[Z", InversePrettyDescriptor("boolean[]"));
+  EXPECT_EQ("[[Z", InversePrettyDescriptor("boolean[][]"));
+}
+
+TEST_F(DescriptorsNamesTest, InversePrettyDescriptor_PrimitiveScalars) {
+  EXPECT_EQ("B", InversePrettyDescriptor("byte"));
+  EXPECT_EQ("C", InversePrettyDescriptor("char"));
+  EXPECT_EQ("D", InversePrettyDescriptor("double"));
+  EXPECT_EQ("F", InversePrettyDescriptor("float"));
+  EXPECT_EQ("I", InversePrettyDescriptor("int"));
+  EXPECT_EQ("J", InversePrettyDescriptor("long"));
+  EXPECT_EQ("S", InversePrettyDescriptor("short"));
+  EXPECT_EQ("Z", InversePrettyDescriptor("boolean"));
+}
+
 TEST_F(DescriptorsNamesTest, MangleForJni) {
   EXPECT_EQ("hello_00024world", MangleForJni("hello$world"));
   EXPECT_EQ("hello_000a9world", MangleForJni("hello\xc2\xa9world"));
diff --git a/libdexfile/dex/dex_file.cc b/libdexfile/dex/dex_file.cc
index dc5f27e..882bbfe 100644
--- a/libdexfile/dex/dex_file.cc
+++ b/libdexfile/dex/dex_file.cc
@@ -74,11 +74,6 @@
   return adler32(adler32(0L, Z_NULL, 0), begin, size);
 }
 
-int DexFile::GetPermissions() const {
-  CHECK(container_.get() != nullptr);
-  return container_->GetPermissions();
-}
-
 bool DexFile::IsReadOnly() const {
   CHECK(container_.get() != nullptr);
   return container_->IsReadOnly();
@@ -96,17 +91,14 @@
 
 DexFile::DexFile(const uint8_t* base,
                  size_t size,
-                 const uint8_t* data_begin,
-                 size_t data_size,
                  const std::string& location,
                  uint32_t location_checksum,
                  const OatDexFile* oat_dex_file,
-                 std::unique_ptr<DexFileContainer> container,
+                 std::shared_ptr<DexFileContainer> container,
                  bool is_compact_dex)
     : begin_(base),
       size_(size),
-      data_begin_(data_begin),
-      data_size_(data_size),
+      data_(GetDataRange(base, size, container.get())),
       location_(location),
       location_checksum_(location_checksum),
       header_(reinterpret_cast<const Header*>(base)),
@@ -132,6 +124,11 @@
   // any of the sections via a pointer.
   CHECK_ALIGNED(begin_, alignof(Header));
 
+  if (DataSize() < sizeof(Header)) {
+    // Don't go further if the data doesn't even contain a header.
+    return;
+  }
+
   InitializeSectionsFromMapList();
 }
 
@@ -143,9 +140,23 @@
 }
 
 bool DexFile::Init(std::string* error_msg) {
+  CHECK_GE(container_->End(), reinterpret_cast<const uint8_t*>(header_));
+  size_t container_size = container_->End() - reinterpret_cast<const uint8_t*>(header_);
+  if (container_size < sizeof(Header)) {
+    *error_msg = StringPrintf("Unable to open '%s' : File size is too small to fit dex header",
+                              location_.c_str());
+    return false;
+  }
   if (!CheckMagicAndVersion(error_msg)) {
     return false;
   }
+  if (container_size < header_->file_size_) {
+    *error_msg = StringPrintf("Unable to open '%s' : File size is %zu but the header expects %u",
+                              location_.c_str(),
+                              container_size,
+                              header_->file_size_);
+    return false;
+  }
   return true;
 }
 
@@ -173,12 +184,32 @@
   return true;
 }
 
+ArrayRef<const uint8_t> DexFile::GetDataRange(const uint8_t* data,
+                                              size_t size,
+                                              DexFileContainer* container) {
+  CHECK(container != nullptr);
+  if (size >= sizeof(CompactDexFile::Header) && CompactDexFile::IsMagicValid(data)) {
+    auto header = reinterpret_cast<const CompactDexFile::Header*>(data);
+    // TODO: Remove. This is a hack. See comment of the Data method.
+    ArrayRef<const uint8_t> separate_data = container->Data();
+    if (separate_data.size() > 0) {
+      return separate_data;
+    }
+    // Shared compact dex data is located at the end after all dex files.
+    data += header->data_off_;
+    size = header->data_size_;
+  }
+  return {data, size};
+}
+
 void DexFile::InitializeSectionsFromMapList() {
-  const MapList* map_list = reinterpret_cast<const MapList*>(DataBegin() + header_->map_off_);
-  if (header_->map_off_ == 0 || header_->map_off_ > DataSize()) {
+  static_assert(sizeof(MapList) <= sizeof(Header));
+  DCHECK_GE(DataSize(), sizeof(MapList));
+  if (header_->map_off_ == 0 || header_->map_off_ > DataSize() - sizeof(MapList)) {
     // Bad offset. The dex file verifier runs after this method and will reject the file.
     return;
   }
+  const MapList* map_list = reinterpret_cast<const MapList*>(DataBegin() + header_->map_off_);
   const size_t count = map_list->size_;
 
   size_t map_limit = header_->map_off_ + count * sizeof(MapItem);
diff --git a/libdexfile/dex/dex_file.h b/libdexfile/dex/dex_file.h
index cc5d2fd..1d1b016 100644
--- a/libdexfile/dex/dex_file.h
+++ b/libdexfile/dex/dex_file.h
@@ -17,16 +17,18 @@
 #ifndef ART_LIBDEXFILE_DEX_DEX_FILE_H_
 #define ART_LIBDEXFILE_DEX_DEX_FILE_H_
 
+#include <android-base/logging.h>
+
 #include <memory>
 #include <optional>
 #include <string>
 #include <string_view>
 #include <vector>
 
-#include <android-base/logging.h>
-
+#include "base/array_ref.h"
 #include "base/globals.h"
 #include "base/macros.h"
+#include "base/mman.h"  // For the PROT_* and MAP_* constants.
 #include "base/value_object.h"
 #include "dex_file_structs.h"
 #include "dex_file_types.h"
@@ -51,21 +53,56 @@
 enum class Domain : char;
 }  // namespace hiddenapi
 
-// Some instances of DexFile own the storage referred to by DexFile.  Clients who create
-// such management do so by subclassing Container.
+// Owns the physical storage that backs one or more DexFiles (that is, it can be shared).
+// It frees the storage (e.g. closes file) when all DexFiles that use it are all closed.
+//
+// The Begin()-End() range represents exactly one DexFile (with the size from the header).
+// In particular, the End() does NOT include any shared cdex data from other DexFiles.
 class DexFileContainer {
  public:
   DexFileContainer() { }
-  virtual ~DexFileContainer() { }
-  virtual int GetPermissions() = 0;
-  virtual bool IsReadOnly() = 0;
+  virtual ~DexFileContainer() {}
+
+  virtual bool IsReadOnly() const = 0;
+
+  // Make the underlying writeable. Return true on success (memory can be written).
   virtual bool EnableWrite() = 0;
+  // Make the underlying read-only. Return true on success (memory is read-only now).
   virtual bool DisableWrite() = 0;
 
+  virtual const uint8_t* Begin() const = 0;
+  virtual const uint8_t* End() const = 0;
+  size_t Size() const { return End() - Begin(); }
+
+  // TODO: Remove. This is only used by dexlayout to override the data section of the dex header,
+  //       and redirect it to intermediate memory buffer at completely unrelated memory location.
+  virtual ArrayRef<const uint8_t> Data() const { return {}; }
+
+  bool IsZip() const { return is_zip_; }
+  void SetIsZip() { is_zip_ = true; }
+  virtual bool IsFileMap() const { return false; }
+
  private:
+  bool is_zip_ = false;
   DISALLOW_COPY_AND_ASSIGN(DexFileContainer);
 };
 
+class MemoryDexFileContainer : public DexFileContainer {
+ public:
+  MemoryDexFileContainer(const uint8_t* begin, const uint8_t* end) : begin_(begin), end_(end) {}
+  MemoryDexFileContainer(const uint8_t* begin, size_t size) : begin_(begin), end_(begin + size) {}
+  bool IsReadOnly() const override { return true; }
+  bool EnableWrite() override { return false; }
+  bool DisableWrite() override { return false; }
+  const uint8_t* Begin() const override { return begin_; }
+  const uint8_t* End() const override { return end_; }
+
+ private:
+  const uint8_t* const begin_;
+  const uint8_t* const end_;
+  DISALLOW_COPY_AND_ASSIGN(MemoryDexFileContainer);
+};
+
 // Dex file is the API that exposes native dex files (ordinary dex files) and CompactDex.
 // Originally, the dex file format used by ART was mostly the same as APKs. The only change was
 // quickened opcodes and layout optimizations.
@@ -542,9 +579,8 @@
     // Check that the offset is in bounds.
     // Note that although the specification says that 0 should be used if there
     // is no debug information, some applications incorrectly use 0xFFFFFFFF.
-    return (debug_info_off == 0 || debug_info_off >= data_size_)
-        ? nullptr
-        : DataBegin() + debug_info_off;
+    return (debug_info_off == 0 || debug_info_off >= DataSize()) ? nullptr :
+                                                                   DataBegin() + debug_info_off;
   }
 
   struct PositionInfo {
@@ -570,7 +606,7 @@
   };
 
   // Callback for "new locals table entry".
-  typedef void (*DexDebugNewLocalCb)(void* context, const LocalInfo& entry);
+  using DexDebugNewLocalCb = void (*)(void* context, const LocalInfo& entry);
 
   const dex::AnnotationsDirectoryItem* GetAnnotationsDirectory(const dex::ClassDef& class_def)
       const {
@@ -709,8 +745,6 @@
     }
   }
 
-  int GetPermissions() const;
-
   bool IsReadOnly() const;
 
   bool EnableWrite() const;
@@ -725,13 +759,13 @@
     return size_;
   }
 
-  const uint8_t* DataBegin() const {
-    return data_begin_;
-  }
+  static ArrayRef<const uint8_t> GetDataRange(const uint8_t* data,
+                                              size_t size,
+                                              DexFileContainer* container);
 
-  size_t DataSize() const {
-    return data_size_;
-  }
+  const uint8_t* DataBegin() const { return data_.data(); }
+
+  size_t DataSize() const { return data_.size(); }
 
   template <typename T>
   const T* DataPointer(size_t offset) const {
@@ -821,12 +855,11 @@
 
   DexFile(const uint8_t* base,
           size_t size,
-          const uint8_t* data_begin,
-          size_t data_size,
           const std::string& location,
           uint32_t location_checksum,
           const OatDexFile* oat_dex_file,
-          std::unique_ptr<DexFileContainer> container,
+          // Shared since several dex files may be stored in the same logical container.
+          std::shared_ptr<DexFileContainer> container,
           bool is_compact_dex);
 
   // Top-level initializer that calls other Init methods.
@@ -844,11 +877,12 @@
   // The size of the underlying memory allocation in bytes.
   const size_t size_;
 
-  // The base address of the data section (same as Begin() for standard dex).
-  const uint8_t* const data_begin_;
-
-  // The size of the data section.
-  const size_t data_size_;
+  // Data memory range: Most dex offsets are relative to this memory range.
+  // Standard dex: same as (begin_, size_).
+  // Compact: shared data which is located after all non-shared data.
+  //
+  // This is different to the "data section" in the standard dex header.
+  ArrayRef<const uint8_t> const data_;
 
   // Typically the dex file name when available, alternatively some identifying string.
   //
@@ -901,7 +935,7 @@
   mutable const OatDexFile* oat_dex_file_;
 
   // Manages the underlying memory allocation.
-  std::unique_ptr<DexFileContainer> container_;
+  std::shared_ptr<DexFileContainer> container_;
 
   // If the dex file is a compact dex file. If false then the dex file is a standard dex file.
   const bool is_compact_dex_;
diff --git a/libdexfile/dex/dex_file_layout.cc b/libdexfile/dex/dex_file_layout.cc
index f15e925..f4dd2c5 100644
--- a/libdexfile/dex/dex_file_layout.cc
+++ b/libdexfile/dex/dex_file_layout.cc
@@ -16,63 +16,12 @@
 
 #include "dex_file_layout.h"
 
-
 #include "base/bit_utils.h"
 #include "base/mman.h"
 #include "dex_file.h"
 
 namespace art {
 
-int DexLayoutSection::MadviseLargestPageAlignedRegion(const uint8_t* begin,
-                                                      const uint8_t* end,
-                                                      int advice) {
-#ifdef _WIN32
-  UNUSED(begin);
-  UNUSED(end);
-  UNUSED(advice);
-  PLOG(WARNING) << "madvise is unsupported on Windows.";
-#else
-  DCHECK_LE(begin, end);
-  begin = AlignUp(begin, kPageSize);
-  end = AlignDown(end, kPageSize);
-  if (begin < end) {
-    // TODO: remove the direct dependency on madvise here.
-    int result = madvise(const_cast<uint8_t*>(begin), end - begin, advice);
-    if (result != 0) {
-      PLOG(WARNING) << "madvise failed " << result;
-    }
-    return result;
-  }
-#endif
-  return 0;
-}
-
-void DexLayoutSection::Subsection::Madvise(const DexFile* dex_file, int advice) const {
-  DCHECK(dex_file != nullptr);
-  DCHECK_LT(start_offset_, dex_file->Size());
-  DCHECK_LE(end_offset_, dex_file->Size());
-  MadviseLargestPageAlignedRegion(dex_file->Begin() + start_offset_,
-                                  dex_file->Begin() + end_offset_,
-                                  advice);
-}
-
-void DexLayoutSections::MadviseAtLoad(const DexFile* dex_file) const {
-#ifdef _WIN32
-  UNUSED(dex_file);
-  PLOG(WARNING) << "madvise is unsupported on Windows.";
-#else
-  // The dex file is already defaulted to random access everywhere.
-  for (const DexLayoutSection& section : sections_) {
-    section.parts_[static_cast<size_t>(LayoutType::kLayoutTypeStartupOnly)].Madvise(
-        dex_file,
-        MADV_WILLNEED);
-    section.parts_[static_cast<size_t>(LayoutType::kLayoutTypeHot)].Madvise(
-        dex_file,
-        MADV_WILLNEED);
-  }
-#endif
-}
-
 std::ostream& operator<<(std::ostream& os, const DexLayoutSection& section) {
   for (size_t i = 0; i < static_cast<size_t>(LayoutType::kLayoutTypeCount); ++i) {
     const DexLayoutSection::Subsection& part = section.parts_[i];
diff --git a/libdexfile/dex/dex_file_layout.h b/libdexfile/dex/dex_file_layout.h
index e1ae44d..5fc37d1 100644
--- a/libdexfile/dex/dex_file_layout.h
+++ b/libdexfile/dex/dex_file_layout.h
@@ -82,13 +82,8 @@
         end_offset_ = std::max(end_offset_, end_offset);
       }
     }
-
-    void Madvise(const DexFile* dex_file, int advice) const;
   };
 
-  // Madvise the largest page-aligned region contained in [begin, end).
-  static int MadviseLargestPageAlignedRegion(const uint8_t* begin, const uint8_t* end, int advice);
-
   Subsection parts_[static_cast<size_t>(LayoutType::kLayoutTypeCount)];
 };
 
@@ -101,10 +96,6 @@
     kSectionCount,
   };
 
-  // Advise load access about the dex file based on layout. The caller is expected to have already
-  // madvised to MADV_RANDOM.
-  void MadviseAtLoad(const DexFile* dex_file) const;
-
   DexLayoutSection sections_[static_cast<size_t>(SectionType::kSectionCount)];
 };
 
diff --git a/libdexfile/dex/dex_file_loader.cc b/libdexfile/dex/dex_file_loader.cc
index 861f911..d375aac 100644
--- a/libdexfile/dex/dex_file_loader.cc
+++ b/libdexfile/dex/dex_file_loader.cc
@@ -16,139 +16,110 @@
 
 #include "dex_file_loader.h"
 
-#include "android-base/stringprintf.h"
+#include <sys/stat.h>
 
+#include <memory>
+#include <optional>
+
+#include "android-base/stringprintf.h"
+#include "base/bit_utils.h"
+#include "base/file_magic.h"
+#include "base/mem_map.h"
+#include "base/os.h"
 #include "base/stl_util.h"
+#include "base/systrace.h"
+#include "base/unix_file/fd_file.h"
+#include "base/zip_archive.h"
 #include "compact_dex_file.h"
 #include "dex_file.h"
 #include "dex_file_verifier.h"
 #include "standard_dex_file.h"
-#include "ziparchive/zip_archive.h"
 
 namespace art {
 
+#if defined(STATIC_LIB)
+#define DEXFILE_SCOPED_TRACE(name)
+#else
+#define DEXFILE_SCOPED_TRACE(name) ScopedTrace trace(name)
+#endif
+
 namespace {
 
+// Technically we do not have a limitation with respect to the number of dex files that can be in a
+// multidex APK. However, it's bad practice, as each dex file requires its own tables for symbols
+// (types, classes, methods, ...) and dex caches. So warn the user that we open a zip with what
+// seems an excessive number.
+static constexpr size_t kWarnOnManyDexFilesThreshold = 100;
+
+using android::base::StringPrintf;
+
 class VectorContainer : public DexFileContainer {
  public:
   explicit VectorContainer(std::vector<uint8_t>&& vector) : vector_(std::move(vector)) { }
   ~VectorContainer() override { }
 
-  int GetPermissions() override {
-    return 0;
-  }
+  bool IsReadOnly() const override { return true; }
 
-  bool IsReadOnly() override {
-    return true;
-  }
+  bool EnableWrite() override { return true; }
 
-  bool EnableWrite() override {
-    return false;
-  }
+  bool DisableWrite() override { return false; }
 
-  bool DisableWrite() override {
-    return false;
-  }
+  const uint8_t* Begin() const override { return vector_.data(); }
+
+  const uint8_t* End() const override { return vector_.data() + vector_.size(); }
 
  private:
   std::vector<uint8_t> vector_;
   DISALLOW_COPY_AND_ASSIGN(VectorContainer);
 };
 
+class MemMapContainer : public DexFileContainer {
+ public:
+  explicit MemMapContainer(MemMap&& mem_map, bool is_file_map = false)
+      : mem_map_(std::move(mem_map)), is_file_map_(is_file_map) {}
+
+  int GetPermissions() const {
+    if (!mem_map_.IsValid()) {
+      return 0;
+    } else {
+      return mem_map_.GetProtect();
+    }
+  }
+
+  bool IsReadOnly() const override { return GetPermissions() == PROT_READ; }
+
+  bool EnableWrite() override {
+    CHECK(IsReadOnly());
+    if (!mem_map_.IsValid()) {
+      return false;
+    } else {
+      return mem_map_.Protect(PROT_READ | PROT_WRITE);
+    }
+  }
+
+  bool DisableWrite() override {
+    CHECK(!IsReadOnly());
+    if (!mem_map_.IsValid()) {
+      return false;
+    } else {
+      return mem_map_.Protect(PROT_READ);
+    }
+  }
+
+  const uint8_t* Begin() const override { return mem_map_.Begin(); }
+
+  const uint8_t* End() const override { return mem_map_.End(); }
+
+  bool IsFileMap() const override { return is_file_map_; }
+
+ protected:
+  MemMap mem_map_;
+  bool is_file_map_;
+  DISALLOW_COPY_AND_ASSIGN(MemMapContainer);
+};
+
 }  // namespace
 
-using android::base::StringPrintf;
-
-class DexZipArchive;
-
-class DexZipEntry {
- public:
-  // Extract this entry to memory.
-  // Returns null on failure and sets error_msg.
-  const std::vector<uint8_t> Extract(std::string* error_msg) {
-    std::vector<uint8_t> map(GetUncompressedLength());
-    if (map.size() == 0) {
-      DCHECK(!error_msg->empty());
-      return map;
-    }
-    const int32_t error = ExtractToMemory(handle_, zip_entry_, map.data(), map.size());
-    if (error) {
-      *error_msg = std::string(ErrorCodeString(error));
-    }
-    return map;
-  }
-
-  virtual ~DexZipEntry() {
-    delete zip_entry_;
-  }
-
-  uint32_t GetUncompressedLength() {
-    return zip_entry_->uncompressed_length;
-  }
-
-  uint32_t GetCrc32() {
-    return zip_entry_->crc32;
-  }
-
- private:
-  DexZipEntry(ZipArchiveHandle handle,
-              ::ZipEntry* zip_entry,
-           const std::string& entry_name)
-    : handle_(handle), zip_entry_(zip_entry), entry_name_(entry_name) {}
-
-  ZipArchiveHandle handle_;
-  ::ZipEntry* const zip_entry_;
-  std::string const entry_name_;
-
-  friend class DexZipArchive;
-  DISALLOW_COPY_AND_ASSIGN(DexZipEntry);
-};
-
-class DexZipArchive {
- public:
-  // return new DexZipArchive instance on success, null on error.
-  static DexZipArchive* Open(const uint8_t* base, size_t size, std::string* error_msg) {
-    ZipArchiveHandle handle;
-    uint8_t* nonconst_base = const_cast<uint8_t*>(base);
-    const int32_t error = OpenArchiveFromMemory(nonconst_base, size, "ZipArchiveMemory", &handle);
-    if (error) {
-      *error_msg = std::string(ErrorCodeString(error));
-      CloseArchive(handle);
-      return nullptr;
-    }
-    return new DexZipArchive(handle);
-  }
-
-  DexZipEntry* Find(const char* name, std::string* error_msg) const {
-    DCHECK(name != nullptr);
-    // Resist the urge to delete the space. <: is a bigraph sequence.
-    std::unique_ptr< ::ZipEntry> zip_entry(new ::ZipEntry);
-    const int32_t error = FindEntry(handle_, name, zip_entry.get());
-    if (error) {
-      *error_msg = std::string(ErrorCodeString(error));
-      return nullptr;
-    }
-    return new DexZipEntry(handle_, zip_entry.release(), name);
-  }
-
-  ~DexZipArchive() {
-    CloseArchive(handle_);
-  }
-
-
- private:
-  explicit DexZipArchive(ZipArchiveHandle handle) : handle_(handle) {}
-  ZipArchiveHandle handle_;
-
-  friend class DexZipEntry;
-  DISALLOW_COPY_AND_ASSIGN(DexZipArchive);
-};
-
-static bool IsZipMagic(uint32_t magic) {
-  return (('P' == ((magic >> 0) & 0xff)) &&
-          ('K' == ((magic >> 8) & 0xff)));
-}
-
 bool DexFileLoader::IsMagicValid(uint32_t magic) {
   return IsMagicValid(reinterpret_cast<uint8_t*>(&magic));
 }
@@ -206,41 +177,308 @@
 }
 
 // All of the implementations here should be independent of the runtime.
-// TODO: implement all the virtual methods.
 
-bool DexFileLoader::GetMultiDexChecksums(
-    const char* filename ATTRIBUTE_UNUSED,
-    std::vector<uint32_t>* checksums ATTRIBUTE_UNUSED,
-    std::vector<std::string>* dex_locations ATTRIBUTE_UNUSED,
-    std::string* error_msg,
-    int zip_fd ATTRIBUTE_UNUSED,
-    bool* zip_file_only_contains_uncompress_dex ATTRIBUTE_UNUSED) const {
-  *error_msg = "UNIMPLEMENTED";
+DexFileLoader::DexFileLoader(const uint8_t* base, size_t size, const std::string& location)
+    : DexFileLoader(std::make_shared<MemoryDexFileContainer>(base, base + size), location) {}
+
+DexFileLoader::DexFileLoader(std::vector<uint8_t>&& memory, const std::string& location)
+    : DexFileLoader(std::make_shared<VectorContainer>(std::move(memory)), location) {}
+
+DexFileLoader::DexFileLoader(MemMap&& mem_map, const std::string& location)
+    : DexFileLoader(std::make_shared<MemMapContainer>(std::move(mem_map)), location) {}
+
+std::unique_ptr<const DexFile> DexFileLoader::Open(uint32_t location_checksum,
+                                                   const OatDexFile* oat_dex_file,
+                                                   bool verify,
+                                                   bool verify_checksum,
+                                                   std::string* error_msg) {
+  DEXFILE_SCOPED_TRACE(std::string("Open dex file ") + location_);
+
+  uint32_t magic;
+  if (!InitAndReadMagic(&magic, error_msg) || !MapRootContainer(error_msg)) {
+    DCHECK(!error_msg->empty());
+    return {};
+  }
+  DCHECK(root_container_ != nullptr);
+  std::unique_ptr<const DexFile> dex_file = OpenCommon(root_container_,
+                                                       root_container_->Begin(),
+                                                       root_container_->Size(),
+                                                       location_,
+                                                       location_checksum,
+                                                       oat_dex_file,
+                                                       verify,
+                                                       verify_checksum,
+                                                       error_msg,
+                                                       nullptr);
+  return dex_file;
+}
+
+bool DexFileLoader::InitAndReadMagic(uint32_t* magic, std::string* error_msg) {
+  if (root_container_ != nullptr) {
+    if (root_container_->Size() < sizeof(uint32_t)) {
+      *error_msg = StringPrintf("Unable to open '%s' : Size is too small", location_.c_str());
+      return false;
+    }
+    *magic = *reinterpret_cast<const uint32_t*>(root_container_->Begin());
+  } else {
+    // Open the file if we have not been given the file-descriptor directly before.
+    if (!file_.has_value()) {
+      CHECK(!filename_.empty());
+      file_.emplace(filename_, O_RDONLY, /* check_usage= */ false);
+      if (file_->Fd() == -1) {
+        *error_msg = StringPrintf("Unable to open '%s' : %s", filename_.c_str(), strerror(errno));
+        return false;
+      }
+    }
+    if (!ReadMagicAndReset(file_->Fd(), magic, error_msg)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool DexFileLoader::MapRootContainer(std::string* error_msg) {
+  if (root_container_ != nullptr) {
+    return true;
+  }
+
+  CHECK(MemMap::IsInitialized());
+  CHECK(file_.has_value());
+  struct stat sbuf;
+  memset(&sbuf, 0, sizeof(sbuf));
+  if (fstat(file_->Fd(), &sbuf) == -1) {
+    *error_msg = StringPrintf("DexFile: fstat '%s' failed: %s", filename_.c_str(), strerror(errno));
+    return false;
+  }
+  if (S_ISDIR(sbuf.st_mode)) {
+    *error_msg = StringPrintf("Attempt to mmap directory '%s'", filename_.c_str());
+    return false;
+  }
+  MemMap map = MemMap::MapFile(sbuf.st_size,
+                               PROT_READ,
+                               MAP_PRIVATE,
+                               file_->Fd(),
+                               0,
+                               /*low_4gb=*/false,
+                               filename_.c_str(),
+                               error_msg);
+  if (!map.IsValid()) {
+    DCHECK(!error_msg->empty());
+    return false;
+  }
+  root_container_ = std::make_shared<MemMapContainer>(std::move(map));
+  return true;
+}
+
+bool DexFileLoader::Open(bool verify,
+                         bool verify_checksum,
+                         bool allow_no_dex_files,
+                         DexFileLoaderErrorCode* error_code,
+                         std::string* error_msg,
+                         std::vector<std::unique_ptr<const DexFile>>* dex_files) {
+  DEXFILE_SCOPED_TRACE(std::string("Open dex file ") + location_);
+
+  DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr";
+
+  uint32_t magic;
+  if (!InitAndReadMagic(&magic, error_msg)) {
+    return false;
+  }
+
+  if (IsZipMagic(magic)) {
+    std::unique_ptr<ZipArchive> zip_archive(
+        file_.has_value() ?
+            ZipArchive::OpenFromOwnedFd(file_->Fd(), location_.c_str(), error_msg) :
+            ZipArchive::OpenFromMemory(
+                root_container_->Begin(), root_container_->Size(), location_.c_str(), error_msg));
+    if (zip_archive.get() == nullptr) {
+      DCHECK(!error_msg->empty());
+      return false;
+    }
+    for (size_t i = 0;; ++i) {
+      std::string name = GetMultiDexClassesDexName(i);
+      std::string multidex_location = GetMultiDexLocation(i, location_.c_str());
+      bool ok = OpenFromZipEntry(*zip_archive,
+                                 name.c_str(),
+                                 multidex_location,
+                                 verify,
+                                 verify_checksum,
+                                 error_code,
+                                 error_msg,
+                                 dex_files);
+      if (!ok) {
+        // We keep opening consecutive dex entries as long as we can (until entry is not found).
+        if (*error_code == DexFileLoaderErrorCode::kEntryNotFound) {
+          // Success if we loaded at least one entry, or if empty zip is explicitly allowed.
+          return i > 0 || allow_no_dex_files;
+        }
+        return false;
+      }
+      if (i == kWarnOnManyDexFilesThreshold) {
+        LOG(WARNING) << location_ << " has in excess of " << kWarnOnManyDexFilesThreshold
+                     << " dex files. Please consider coalescing and shrinking the number to "
+                        " avoid runtime overhead.";
+      }
+    }
+  }
+  if (IsMagicValid(magic)) {
+    if (!MapRootContainer(error_msg)) {
+      return false;
+    }
+    DCHECK(root_container_ != nullptr);
+    std::unique_ptr<const DexFile> dex_file =
+        OpenCommon(root_container_,
+                   root_container_->Begin(),
+                   root_container_->Size(),
+                   location_,
+                   /*location_checksum*/ {},  // Use default checksum from dex header.
+                   /*oat_dex_file=*/nullptr,
+                   verify,
+                   verify_checksum,
+                   error_msg,
+                   nullptr);
+    if (dex_file.get() != nullptr) {
+      dex_files->push_back(std::move(dex_file));
+      return true;
+    } else {
+      return false;
+    }
+  }
+  *error_msg = StringPrintf("Expected valid zip or dex file");
   return false;
 }
 
-std::unique_ptr<const DexFile> DexFileLoader::Open(
-    const std::string& location,
-    uint32_t location_checksum,
-    std::vector<uint8_t>&& memory,
-    const OatDexFile* oat_dex_file,
-    bool verify,
-    bool verify_checksum,
-    std::string* error_msg) {
-  auto memory_data = memory.data();
-  auto memory_size = memory.size();
-  return OpenCommon(memory_data,
-                    memory_size,
-                    /*data_base=*/ nullptr,
-                    /*data_size=*/ 0,
-                    location,
-                    location_checksum,
-                    oat_dex_file,
-                    verify,
-                    verify_checksum,
-                    error_msg,
-                    std::make_unique<VectorContainer>(std::move(memory)),
-                    /*verify_result=*/ nullptr);
+std::unique_ptr<DexFile> DexFileLoader::OpenCommon(std::shared_ptr<DexFileContainer> container,
+                                                   const uint8_t* base,
+                                                   size_t size,
+                                                   const std::string& location,
+                                                   std::optional<uint32_t> location_checksum,
+                                                   const OatDexFile* oat_dex_file,
+                                                   bool verify,
+                                                   bool verify_checksum,
+                                                   std::string* error_msg,
+                                                   DexFileLoaderErrorCode* error_code) {
+  if (container == nullptr) {
+    // We should never pass null here, but use reasonable default for app compat anyway.
+    container = std::make_shared<MemoryDexFileContainer>(base, size);
+  }
+  if (error_code != nullptr) {
+    *error_code = DexFileLoaderErrorCode::kDexFileError;
+  }
+  std::unique_ptr<DexFile> dex_file;
+  auto header = reinterpret_cast<const DexFile::Header*>(base);
+  if (size >= sizeof(StandardDexFile::Header) && StandardDexFile::IsMagicValid(base)) {
+    uint32_t checksum = location_checksum.value_or(header->checksum_);
+    dex_file.reset(new StandardDexFile(base, size, location, checksum, oat_dex_file, container));
+  } else if (size >= sizeof(CompactDexFile::Header) && CompactDexFile::IsMagicValid(base)) {
+    uint32_t checksum = location_checksum.value_or(header->checksum_);
+    dex_file.reset(new CompactDexFile(base, size, location, checksum, oat_dex_file, container));
+  } else {
+    *error_msg = StringPrintf("Invalid or truncated dex file '%s'", location.c_str());
+  }
+  if (dex_file == nullptr) {
+    *error_msg =
+        StringPrintf("Failed to open dex file '%s': %s", location.c_str(), error_msg->c_str());
+    return nullptr;
+  }
+  if (!dex_file->Init(error_msg)) {
+    dex_file.reset();
+    return nullptr;
+  }
+  // NB: Dex verifier does not understand the compact dex format.
+  if (verify && !dex_file->IsCompactDexFile()) {
+    DEXFILE_SCOPED_TRACE(std::string("Verify dex file ") + location);
+    if (!dex::Verify(dex_file.get(), location.c_str(), verify_checksum, error_msg)) {
+      if (error_code != nullptr) {
+        *error_code = DexFileLoaderErrorCode::kVerifyError;
+      }
+      return nullptr;
+    }
+  }
+  if (error_code != nullptr) {
+    *error_code = DexFileLoaderErrorCode::kNoError;
+  }
+  return dex_file;
+}
+
+bool DexFileLoader::OpenFromZipEntry(const ZipArchive& zip_archive,
+                                     const char* entry_name,
+                                     const std::string& location,
+                                     bool verify,
+                                     bool verify_checksum,
+                                     DexFileLoaderErrorCode* error_code,
+                                     std::string* error_msg,
+                                     std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
+  CHECK(!location.empty());
+  std::unique_ptr<ZipEntry> zip_entry(zip_archive.Find(entry_name, error_msg));
+  if (zip_entry == nullptr) {
+    *error_code = DexFileLoaderErrorCode::kEntryNotFound;
+    return false;
+  }
+  if (zip_entry->GetUncompressedLength() == 0) {
+    *error_msg = StringPrintf("Dex file '%s' has zero length", location.c_str());
+    *error_code = DexFileLoaderErrorCode::kDexFileError;
+    return false;
+  }
+
+  CHECK(MemMap::IsInitialized());
+  MemMap map;
+  bool is_file_map = false;
+  if (file_.has_value() && zip_entry->IsUncompressed()) {
+    if (!zip_entry->IsAlignedTo(alignof(DexFile::Header))) {
+      // Do not mmap unaligned ZIP entries because
+      // doing so would fail dex verification which requires 4 byte alignment.
+      LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; "
+                   << "please zipalign to " << alignof(DexFile::Header) << " bytes. "
+                   << "Falling back to extracting file.";
+    } else {
+      // Map uncompressed files within zip as file-backed to avoid a dirty copy.
+      map = zip_entry->MapDirectlyFromFile(location.c_str(), /*out*/ error_msg);
+      if (!map.IsValid()) {
+        LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; "
+                     << "is your ZIP file corrupted? Falling back to extraction.";
+        // Try again with Extraction which still has a chance of recovery.
+      }
+      is_file_map = true;
+    }
+  }
+  if (!map.IsValid()) {
+    DEXFILE_SCOPED_TRACE(std::string("Extract dex file ") + location);
+
+    // Default path for compressed ZIP entries,
+    // and fallback for stored ZIP entries.
+    map = zip_entry->ExtractToMemMap(location.c_str(), entry_name, error_msg);
+  }
+  if (!map.IsValid()) {
+    *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", entry_name, location.c_str(),
+                              error_msg->c_str());
+    *error_code = DexFileLoaderErrorCode::kExtractToMemoryError;
+    return false;
+  }
+  auto container = std::make_shared<MemMapContainer>(std::move(map), is_file_map);
+  container->SetIsZip();
+  if (!container->DisableWrite()) {
+    *error_msg = StringPrintf("Failed to make dex file '%s' read only", location.c_str());
+    *error_code = DexFileLoaderErrorCode::kMakeReadOnlyError;
+    return false;
+  }
+
+  std::unique_ptr<const DexFile> dex_file = OpenCommon(container,
+                                                       container->Begin(),
+                                                       container->Size(),
+                                                       location,
+                                                       zip_entry->GetCrc32(),
+                                                       /*oat_dex_file=*/nullptr,
+                                                       verify,
+                                                       verify_checksum,
+                                                       error_msg,
+                                                       error_code);
+  if (dex_file == nullptr) {
+    return false;
+  }
+  CHECK(dex_file->IsReadOnly()) << location;
+  dex_files->push_back(std::move(dex_file));
+  return true;
 }
 
 std::unique_ptr<const DexFile> DexFileLoader::Open(
@@ -255,8 +493,8 @@
     std::unique_ptr<DexFileContainer> container) const {
   return OpenCommon(base,
                     size,
-                    /*data_base=*/ nullptr,
-                    /*data_size=*/ 0,
+                    /*data_base=*/nullptr,
+                    /*data_size=*/0,
                     location,
                     location_checksum,
                     oat_dex_file,
@@ -264,78 +502,7 @@
                     verify_checksum,
                     error_msg,
                     std::move(container),
-                    /*verify_result=*/ nullptr);
-}
-
-std::unique_ptr<const DexFile> DexFileLoader::OpenWithDataSection(
-    const uint8_t* base,
-    size_t size,
-    const uint8_t* data_base,
-    size_t data_size,
-    const std::string& location,
-    uint32_t location_checksum,
-    const OatDexFile* oat_dex_file,
-    bool verify,
-    bool verify_checksum,
-    std::string* error_msg) const {
-  return OpenCommon(base,
-                    size,
-                    data_base,
-                    data_size,
-                    location,
-                    location_checksum,
-                    oat_dex_file,
-                    verify,
-                    verify_checksum,
-                    error_msg,
-                    /*container=*/ nullptr,
-                    /*verify_result=*/ nullptr);
-}
-
-bool DexFileLoader::OpenAll(
-    const uint8_t* base,
-    size_t size,
-    const std::string& location,
-    bool verify,
-    bool verify_checksum,
-    DexFileLoaderErrorCode* error_code,
-    std::string* error_msg,
-    std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr";
-  uint32_t magic = *reinterpret_cast<const uint32_t*>(base);
-  if (IsZipMagic(magic)) {
-    std::unique_ptr<DexZipArchive> zip_archive(DexZipArchive::Open(base, size, error_msg));
-    if (zip_archive.get() == nullptr) {
-      DCHECK(!error_msg->empty());
-      return false;
-    }
-    return OpenAllDexFilesFromZip(*zip_archive.get(),
-                                  location,
-                                  verify,
-                                  verify_checksum,
-                                  error_code,
-                                  error_msg,
-                                  dex_files);
-  }
-  if (IsMagicValid(magic)) {
-    const DexFile::Header* dex_header = reinterpret_cast<const DexFile::Header*>(base);
-    std::unique_ptr<const DexFile> dex_file(Open(base,
-                                                 size,
-                                                 location,
-                                                 dex_header->checksum_,
-                                                 /*oat_dex_file=*/ nullptr,
-                                                 verify,
-                                                 verify_checksum,
-                                                 error_msg));
-    if (dex_file.get() != nullptr) {
-      dex_files->push_back(std::move(dex_file));
-      return true;
-    } else {
-      return false;
-    }
-  }
-  *error_msg = StringPrintf("Expected valid zip or dex file");
-  return false;
+                    /*verify_result=*/nullptr);
 }
 
 std::unique_ptr<DexFile> DexFileLoader::OpenCommon(const uint8_t* base,
@@ -348,190 +515,31 @@
                                                    bool verify,
                                                    bool verify_checksum,
                                                    std::string* error_msg,
-                                                   std::unique_ptr<DexFileContainer> container,
+                                                   std::unique_ptr<DexFileContainer> old_container,
                                                    VerifyResult* verify_result) {
-  if (verify_result != nullptr) {
-    *verify_result = VerifyResult::kVerifyNotAttempted;
-  }
-  std::unique_ptr<DexFile> dex_file;
-  if (size >= sizeof(StandardDexFile::Header) && StandardDexFile::IsMagicValid(base)) {
-    if (data_size != 0) {
-      CHECK_EQ(base, data_base) << "Unsupported for standard dex";
-    }
-    dex_file.reset(new StandardDexFile(base,
-                                       size,
-                                       location,
-                                       location_checksum,
-                                       oat_dex_file,
-                                       std::move(container)));
-  } else if (size >= sizeof(CompactDexFile::Header) && CompactDexFile::IsMagicValid(base)) {
-    if (data_base == nullptr) {
-      // TODO: Is there a clean way to support both an explicit data section and reading the one
-      // from the header.
-      CHECK_EQ(data_size, 0u);
-      const CompactDexFile::Header* const header = CompactDexFile::Header::At(base);
-      data_base = base + header->data_off_;
-      data_size = header->data_size_;
-    }
-    dex_file.reset(new CompactDexFile(base,
-                                      size,
-                                      data_base,
-                                      data_size,
-                                      location,
-                                      location_checksum,
-                                      oat_dex_file,
-                                      std::move(container)));
-    // Disable verification for CompactDex input.
-    verify = false;
-  } else {
-    *error_msg = "Invalid or truncated dex file";
-  }
-  if (dex_file == nullptr) {
-    *error_msg = StringPrintf("Failed to open dex file '%s' from memory: %s", location.c_str(),
-                              error_msg->c_str());
-    return nullptr;
-  }
-  if (!dex_file->Init(error_msg)) {
-    dex_file.reset();
-    return nullptr;
-  }
-  if (verify && !dex::Verify(dex_file.get(),
-                             dex_file->Begin(),
-                             dex_file->Size(),
-                             location.c_str(),
-                             verify_checksum,
-                             error_msg)) {
-    if (verify_result != nullptr) {
-      *verify_result = VerifyResult::kVerifyFailed;
-    }
-    return nullptr;
-  }
-  if (verify_result != nullptr) {
-    *verify_result = VerifyResult::kVerifySucceeded;
-  }
-  return dex_file;
+  CHECK(data_base == base || data_base == nullptr);
+  CHECK(data_size == size || data_size == 0);
+  CHECK(verify_result == nullptr);
+
+  // The provided container probably does implent the new API.
+  // We don't use it, but let's at least call its destructor.
+  struct NewContainer : public MemoryDexFileContainer {
+    using MemoryDexFileContainer::MemoryDexFileContainer;  // ctor.
+    std::unique_ptr<DexFileContainer> old_container_ = nullptr;
+  };
+  auto new_container = std::make_shared<NewContainer>(base, size);
+  new_container->old_container_ = std::move(old_container);
+
+  return OpenCommon(std::move(new_container),
+                    base,
+                    size,
+                    location,
+                    location_checksum,
+                    oat_dex_file,
+                    verify,
+                    verify_checksum,
+                    error_msg,
+                    /*error_code=*/nullptr);
 }
 
-std::unique_ptr<const DexFile> DexFileLoader::OpenOneDexFileFromZip(
-    const DexZipArchive& zip_archive,
-    const char* entry_name,
-    const std::string& location,
-    bool verify,
-    bool verify_checksum,
-    DexFileLoaderErrorCode* error_code,
-    std::string* error_msg) const {
-  CHECK(!location.empty());
-  std::unique_ptr<DexZipEntry> zip_entry(zip_archive.Find(entry_name, error_msg));
-  if (zip_entry == nullptr) {
-    *error_code = DexFileLoaderErrorCode::kEntryNotFound;
-    return nullptr;
-  }
-  if (zip_entry->GetUncompressedLength() == 0) {
-    *error_msg = StringPrintf("Dex file '%s' has zero length", location.c_str());
-    *error_code = DexFileLoaderErrorCode::kDexFileError;
-    return nullptr;
-  }
-
-  std::vector<uint8_t> map(zip_entry->Extract(error_msg));
-  if (map.size() == 0) {
-    *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", entry_name, location.c_str(),
-                              error_msg->c_str());
-    *error_code = DexFileLoaderErrorCode::kExtractToMemoryError;
-    return nullptr;
-  }
-  VerifyResult verify_result;
-  auto map_data = map.data();
-  auto map_size = map.size();
-  std::unique_ptr<const DexFile> dex_file = OpenCommon(
-      map_data,
-      map_size,
-      /*data_base=*/ nullptr,
-      /*data_size=*/ 0u,
-      location,
-      zip_entry->GetCrc32(),
-      /*oat_dex_file=*/ nullptr,
-      verify,
-      verify_checksum,
-      error_msg,
-      std::make_unique<VectorContainer>(std::move(map)),
-      &verify_result);
-  if (verify_result != VerifyResult::kVerifySucceeded) {
-    if (verify_result == VerifyResult::kVerifyNotAttempted) {
-      *error_code = DexFileLoaderErrorCode::kDexFileError;
-    } else {
-      *error_code = DexFileLoaderErrorCode::kVerifyError;
-    }
-    return nullptr;
-  }
-  *error_code = DexFileLoaderErrorCode::kNoError;
-  return dex_file;
-}
-
-// Technically we do not have a limitation with respect to the number of dex files that can be in a
-// multidex APK. However, it's bad practice, as each dex file requires its own tables for symbols
-// (types, classes, methods, ...) and dex caches. So warn the user that we open a zip with what
-// seems an excessive number.
-static constexpr size_t kWarnOnManyDexFilesThreshold = 100;
-
-bool DexFileLoader::OpenAllDexFilesFromZip(
-    const DexZipArchive& zip_archive,
-    const std::string& location,
-    bool verify,
-    bool verify_checksum,
-    DexFileLoaderErrorCode* error_code,
-    std::string* error_msg,
-    std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  DCHECK(dex_files != nullptr) << "DexFile::OpenFromZip: out-param is nullptr";
-  std::unique_ptr<const DexFile> dex_file(OpenOneDexFileFromZip(zip_archive,
-                                                                kClassesDex,
-                                                                location,
-                                                                verify,
-                                                                verify_checksum,
-                                                                error_code,
-                                                                error_msg));
-  if (*error_code != DexFileLoaderErrorCode::kNoError) {
-    return false;
-  } else {
-    // Had at least classes.dex.
-    dex_files->push_back(std::move(dex_file));
-
-    // Now try some more.
-
-    // We could try to avoid std::string allocations by working on a char array directly. As we
-    // do not expect a lot of iterations, this seems too involved and brittle.
-
-    for (size_t i = 1; ; ++i) {
-      std::string name = GetMultiDexClassesDexName(i);
-      std::string fake_location = GetMultiDexLocation(i, location.c_str());
-      std::unique_ptr<const DexFile> next_dex_file(OpenOneDexFileFromZip(zip_archive,
-                                                                         name.c_str(),
-                                                                         fake_location,
-                                                                         verify,
-                                                                         verify_checksum,
-                                                                         error_code,
-                                                                         error_msg));
-      if (next_dex_file.get() == nullptr) {
-        if (*error_code != DexFileLoaderErrorCode::kEntryNotFound) {
-          LOG(WARNING) << "Zip open failed: " << *error_msg;
-        }
-        break;
-      } else {
-        dex_files->push_back(std::move(next_dex_file));
-      }
-
-      if (i == kWarnOnManyDexFilesThreshold) {
-        LOG(WARNING) << location << " has in excess of " << kWarnOnManyDexFilesThreshold
-                     << " dex files. Please consider coalescing and shrinking the number to "
-                        " avoid runtime overhead.";
-      }
-
-      if (i == std::numeric_limits<size_t>::max()) {
-        LOG(ERROR) << "Overflow in number of dex files!";
-        break;
-      }
-    }
-
-    return true;
-  }
-}
 }  // namespace art
diff --git a/libdexfile/dex/dex_file_loader.h b/libdexfile/dex/dex_file_loader.h
index d6268bc..532445a 100644
--- a/libdexfile/dex/dex_file_loader.h
+++ b/libdexfile/dex/dex_file_loader.h
@@ -18,18 +18,22 @@
 #define ART_LIBDEXFILE_DEX_DEX_FILE_LOADER_H_
 
 #include <cstdint>
+#include <functional>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
+#include "base/os.h"
+#include "base/unix_file/fd_file.h"
+#include "dex_file.h"
+
 namespace art {
 
-class DexFile;
-class DexFileContainer;
 class MemMap;
 class OatDexFile;
-
-class DexZipArchive;
+class ScopedTrace;
+class ZipArchive;
 
 enum class DexFileLoaderErrorCode {
   kNoError,
@@ -103,82 +107,124 @@
     return (pos == std::string::npos) ? std::string() : location.substr(pos);
   }
 
-  virtual ~DexFileLoader() { }
+  DexFileLoader(const char* filename, int fd, const std::string& location)
+      : filename_(filename),
+        file_(fd == -1 ? std::optional<File>() : File(fd, /*check_usage=*/false)),
+        location_(location) {}
 
-  // Returns the checksums of a file for comparison with GetLocationChecksum().
-  // For .dex files, this is the single header checksum.
-  // For zip files, this is the zip entry CRC32 checksum for classes.dex and
-  // each additional multidex entry classes2.dex, classes3.dex, etc.
-  // If a valid zip_fd is provided the file content will be read directly from
-  // the descriptor and `filename` will be used as alias for error logging. If
-  // zip_fd is -1, the method will try to open the `filename` and read the
-  // content from it.
-  //
-  // The dex_locations vector will be populated with the corresponding multidex
-  // locations.
-  //
-  // Return true if the checksums could be found, false otherwise.
-  virtual bool GetMultiDexChecksums(const char* filename,
-                                    std::vector<uint32_t>* checksums,
-                                    std::vector<std::string>* dex_locations,
-                                    std::string* error_msg,
-                                    int zip_fd = -1,
-                                    bool* zip_file_only_contains_uncompress_dex = nullptr) const;
+  DexFileLoader(std::shared_ptr<DexFileContainer> container, const std::string& location)
+      : root_container_(std::move(container)), location_(location) {
+    DCHECK(root_container_ != nullptr);
+  }
 
-  // Opens .dex file, backed by existing vector memory.
-  static std::unique_ptr<const DexFile> Open(
-      const std::string& location,
-      uint32_t location_checksum,
-      std::vector<uint8_t>&& memory,
-      const OatDexFile* oat_dex_file,
-      bool verify,
-      bool verify_checksum,
-      std::string* error_msg);
+  DexFileLoader(const uint8_t* base, size_t size, const std::string& location);
 
-  // Opens .dex file, backed by existing memory.
-  virtual std::unique_ptr<const DexFile> Open(
-      const uint8_t* base,
-      size_t size,
-      const std::string& location,
-      uint32_t location_checksum,
-      const OatDexFile* oat_dex_file,
-      bool verify,
-      bool verify_checksum,
-      std::string* error_msg,
-      std::unique_ptr<DexFileContainer> container = nullptr) const;
+  DexFileLoader(std::vector<uint8_t>&& memory, const std::string& location);
 
-  // Open a dex file with a separate data section.
-  virtual std::unique_ptr<const DexFile> OpenWithDataSection(
-      const uint8_t* base,
-      size_t size,
-      const uint8_t* data_base,
-      size_t data_size,
-      const std::string& location,
-      uint32_t location_checksum,
-      const OatDexFile* oat_dex_file,
-      bool verify,
-      bool verify_checksum,
-      std::string* error_msg) const;
+  DexFileLoader(MemMap&& mem_map, const std::string& location);
 
+  DexFileLoader(int fd, const std::string& location)
+      : DexFileLoader(/*filename=*/location.c_str(), fd, location) {}
 
-  // Opens all .dex files found in the memory map, guessing the container format based on file
-  // extension.
-  virtual bool OpenAll(const uint8_t* base,
-                       size_t size,
-                       const std::string& location,
-                       bool verify,
-                       bool verify_checksum,
-                       DexFileLoaderErrorCode* error_code,
-                       std::string* error_msg,
-                       std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
+  DexFileLoader(const char* filename, const std::string& location)
+      : DexFileLoader(filename, /*fd=*/-1, location) {}
+
+  explicit DexFileLoader(const std::string& location)
+      : DexFileLoader(location.c_str(), /*fd=*/-1, location) {}
+
+  virtual ~DexFileLoader() {}
+
+  std::unique_ptr<const DexFile> Open(uint32_t location_checksum,
+                                      const OatDexFile* oat_dex_file,
+                                      bool verify,
+                                      bool verify_checksum,
+                                      std::string* error_msg);
+
+  std::unique_ptr<const DexFile> Open(uint32_t location_checksum,
+                                      bool verify,
+                                      bool verify_checksum,
+                                      std::string* error_msg) {
+    return Open(location_checksum,
+                /*oat_dex_file=*/nullptr,
+                verify,
+                verify_checksum,
+                error_msg);
+  }
+
+  // Opens all dex files, guessing the container format based on file magic.
+  bool Open(bool verify,
+            bool verify_checksum,
+            bool allow_no_dex_files,
+            DexFileLoaderErrorCode* error_code,
+            std::string* error_msg,
+            std::vector<std::unique_ptr<const DexFile>>* dex_files);
+
+  bool Open(bool verify,
+            bool verify_checksum,
+            DexFileLoaderErrorCode* error_code,
+            std::string* error_msg,
+            std::vector<std::unique_ptr<const DexFile>>* dex_files) {
+    return Open(verify,
+                verify_checksum,
+                /*allow_no_dex_files=*/false,
+                error_code,
+                error_msg,
+                dex_files);
+  }
+
+  bool Open(bool verify,
+            bool verify_checksum,
+            bool allow_no_dex_files,
+            std::string* error_msg,
+            std::vector<std::unique_ptr<const DexFile>>* dex_files) {
+    DexFileLoaderErrorCode error_code;
+    return Open(verify, verify_checksum, allow_no_dex_files, &error_code, error_msg, dex_files);
+  }
+
+  bool Open(bool verify,
+            bool verify_checksum,
+            std::string* error_msg,
+            std::vector<std::unique_ptr<const DexFile>>* dex_files) {
+    DexFileLoaderErrorCode error_code;
+    return Open(verify,
+                verify_checksum,
+                /*allow_no_dex_files=*/false,
+                &error_code,
+                error_msg,
+                dex_files);
+  }
 
  protected:
-  enum class VerifyResult {  // private
-    kVerifyNotAttempted,
-    kVerifySucceeded,
-    kVerifyFailed
-  };
+  bool InitAndReadMagic(uint32_t* magic, std::string* error_msg);
 
+  // Ensure we have root container.  If we are backed by a file, memory-map it.
+  // We can only do this for dex files since zip files might be too big to map.
+  bool MapRootContainer(std::string* error_msg);
+
+  static std::unique_ptr<DexFile> OpenCommon(std::shared_ptr<DexFileContainer> container,
+                                             const uint8_t* base,
+                                             size_t size,
+                                             const std::string& location,
+                                             std::optional<uint32_t> location_checksum,
+                                             const OatDexFile* oat_dex_file,
+                                             bool verify,
+                                             bool verify_checksum,
+                                             std::string* error_msg,
+                                             DexFileLoaderErrorCode* error_code);
+
+  // Old signature preserved for app-compat.
+  std::unique_ptr<const DexFile> Open(const uint8_t* base,
+                                      size_t size,
+                                      const std::string& location,
+                                      uint32_t location_checksum,
+                                      const OatDexFile* oat_dex_file,
+                                      bool verify,
+                                      bool verify_checksum,
+                                      std::string* error_msg,
+                                      std::unique_ptr<DexFileContainer> container) const;
+
+  // Old signature preserved for app-compat.
+  enum VerifyResult {};
   static std::unique_ptr<DexFile> OpenCommon(const uint8_t* base,
                                              size_t size,
                                              const uint8_t* data_base,
@@ -192,25 +238,22 @@
                                              std::unique_ptr<DexFileContainer> container,
                                              VerifyResult* verify_result);
 
- private:
-  // Open all classesXXX.dex files from a zip archive.
-  bool OpenAllDexFilesFromZip(const DexZipArchive& zip_archive,
-                              const std::string& location,
-                              bool verify,
-                              bool verify_checksum,
-                              DexFileLoaderErrorCode* error_code,
-                              std::string* error_msg,
-                              std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
+  // Open .dex files from the entry_name in a zip archive.
+  bool OpenFromZipEntry(const ZipArchive& zip_archive,
+                        const char* entry_name,
+                        const std::string& location,
+                        bool verify,
+                        bool verify_checksum,
+                        DexFileLoaderErrorCode* error_code,
+                        std::string* error_msg,
+                        std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
 
-  // Opens .dex file from the entry_name in a zip archive. error_code is undefined when non-null
-  // return.
-  std::unique_ptr<const DexFile> OpenOneDexFileFromZip(const DexZipArchive& zip_archive,
-                                                       const char* entry_name,
-                                                       const std::string& location,
-                                                       bool verify,
-                                                       bool verify_checksum,
-                                                       DexFileLoaderErrorCode* error_code,
-                                                       std::string* error_msg) const;
+  // The DexFileLoader can be backed either by file or by memory (i.e. DexFileContainer).
+  // We can not just mmap the file since APKs might be unreasonably large for 32-bit system.
+  std::string filename_;
+  std::optional<File> file_;
+  std::shared_ptr<DexFileContainer> root_container_;
+  const std::string location_;
 };
 
 }  // namespace art
diff --git a/libdexfile/dex/dex_file_loader_test.cc b/libdexfile/dex/dex_file_loader_test.cc
index 30c60b1..381dfdc 100644
--- a/libdexfile/dex/dex_file_loader_test.cc
+++ b/libdexfile/dex/dex_file_loader_test.cc
@@ -217,15 +217,9 @@
   // read dex file(s)
   static constexpr bool kVerifyChecksum = true;
   std::vector<std::unique_ptr<const DexFile>> tmp;
-  const DexFileLoader dex_file_loader;
-  bool success = dex_file_loader.OpenAll(dex_bytes->data(),
-                                         dex_bytes->size(),
-                                         location,
-                                         /* verify= */ true,
-                                         kVerifyChecksum,
-                                         error_code,
-                                         error_msg,
-                                         dex_files);
+  DexFileLoader dex_file_loader(dex_bytes->data(), dex_bytes->size(), location);
+  bool success =
+      dex_file_loader.Open(/* verify= */ true, kVerifyChecksum, error_code, error_msg, dex_files);
   return success;
 }
 
@@ -251,11 +245,8 @@
   DecodeDexFile(base64, dex_bytes);
 
   std::string error_message;
-  const DexFileLoader dex_file_loader;
-  std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(dex_bytes->data(),
-                                                               dex_bytes->size(),
-                                                               location,
-                                                               location_checksum,
+  DexFileLoader dex_file_loader(dex_bytes->data(), dex_bytes->size(), location);
+  std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(location_checksum,
                                                                /* oat_dex_file= */ nullptr,
                                                                /* verify= */ true,
                                                                /* verify_checksum= */ true,
@@ -353,15 +344,9 @@
   DexFileLoaderErrorCode error_code;
   std::string error_msg;
   std::vector<std::unique_ptr<const DexFile>> dex_files;
-  const DexFileLoader dex_file_loader;
-  ASSERT_FALSE(dex_file_loader.OpenAll(dex_bytes.data(),
-                                       dex_bytes.size(),
-                                       kLocationString,
-                                       /* verify= */ true,
-                                       kVerifyChecksum,
-                                       &error_code,
-                                       &error_msg,
-                                       &dex_files));
+  DexFileLoader dex_file_loader(dex_bytes.data(), dex_bytes.size(), kLocationString);
+  ASSERT_FALSE(dex_file_loader.Open(
+      /* verify= */ true, kVerifyChecksum, &error_code, &error_msg, &dex_files));
 }
 
 TEST_F(DexFileLoaderTest, ZeroLengthDexRejected) {
@@ -372,15 +357,9 @@
   DexFileLoaderErrorCode error_code;
   std::string error_msg;
   std::vector<std::unique_ptr<const DexFile>> dex_files;
-  const DexFileLoader dex_file_loader;
-  ASSERT_FALSE(dex_file_loader.OpenAll(dex_bytes.data(),
-                                       dex_bytes.size(),
-                                       kLocationString,
-                                       /* verify= */ true,
-                                       kVerifyChecksum,
-                                       &error_code,
-                                       &error_msg,
-                                       &dex_files));
+  DexFileLoader dex_file_loader(dex_bytes.data(), dex_bytes.size(), kLocationString);
+  ASSERT_FALSE(dex_file_loader.Open(
+      /* verify= */ true, kVerifyChecksum, &error_code, &error_msg, &dex_files));
 }
 
 TEST_F(DexFileLoaderTest, GetMultiDexClassesDexName) {
diff --git a/libdexfile/dex/dex_file_types.h b/libdexfile/dex/dex_file_types.h
index ecc0482..bf67187 100644
--- a/libdexfile/dex/dex_file_types.h
+++ b/libdexfile/dex/dex_file_types.h
@@ -17,6 +17,7 @@
 #ifndef ART_LIBDEXFILE_DEX_DEX_FILE_TYPES_H_
 #define ART_LIBDEXFILE_DEX_DEX_FILE_TYPES_H_
 
+#include <functional>
 #include <iosfwd>
 #include <limits>
 #include <utility>
diff --git a/libdexfile/dex/dex_file_verifier.cc b/libdexfile/dex/dex_file_verifier.cc
index f72528f..45f7f8f 100644
--- a/libdexfile/dex/dex_file_verifier.cc
+++ b/libdexfile/dex/dex_file_verifier.cc
@@ -191,14 +191,10 @@
 
 class DexFileVerifier {
  public:
-  DexFileVerifier(const DexFile* dex_file,
-                  const uint8_t* begin,
-                  size_t size,
-                  const char* location,
-                  bool verify_checksum)
+  DexFileVerifier(const DexFile* dex_file, const char* location, bool verify_checksum)
       : dex_file_(dex_file),
-        begin_(begin),
-        size_(size),
+        begin_(dex_file->Begin()),
+        size_(dex_file->Size()),
         location_(location),
         verify_checksum_(verify_checksum),
         header_(&dex_file->GetHeader()),
@@ -207,8 +203,7 @@
         init_indices_{std::numeric_limits<size_t>::max(),
                       std::numeric_limits<size_t>::max(),
                       std::numeric_limits<size_t>::max(),
-                      std::numeric_limits<size_t>::max()} {
-  }
+                      std::numeric_limits<size_t>::max()} {}
 
   bool Verify();
 
@@ -3470,7 +3465,7 @@
     return false;
   }
 
-  // Flags allowed on fields, in general. Other lower-16-bit flags are to be ignored.
+  // Flags allowed on methods, in general. Other lower-16-bit flags are to be ignored.
   constexpr uint32_t kMethodAccessFlags = kAccPublic |
                                           kAccPrivate |
                                           kAccProtected |
@@ -3679,13 +3674,11 @@
 }
 
 bool Verify(const DexFile* dex_file,
-            const uint8_t* begin,
-            size_t size,
             const char* location,
             bool verify_checksum,
             std::string* error_msg) {
   std::unique_ptr<DexFileVerifier> verifier(
-      new DexFileVerifier(dex_file, begin, size, location, verify_checksum));
+      new DexFileVerifier(dex_file, location, verify_checksum));
   if (!verifier->Verify()) {
     *error_msg = verifier->FailureReason();
     return false;
diff --git a/libdexfile/dex/dex_file_verifier.h b/libdexfile/dex/dex_file_verifier.h
index 8ae6e7a..423a01a 100644
--- a/libdexfile/dex/dex_file_verifier.h
+++ b/libdexfile/dex/dex_file_verifier.h
@@ -28,8 +28,6 @@
 namespace dex {
 
 bool Verify(const DexFile* dex_file,
-            const uint8_t* begin,
-            size_t size,
             const char* location,
             bool verify_checksum,
             std::string* error_msg);
diff --git a/libdexfile/dex/dex_file_verifier_test.cc b/libdexfile/dex/dex_file_verifier_test.cc
index 79e9c8b..d31635e 100644
--- a/libdexfile/dex/dex_file_verifier_test.cc
+++ b/libdexfile/dex/dex_file_verifier_test.cc
@@ -59,7 +59,8 @@
 class DexFileVerifierTest : public testing::Test {
  protected:
   DexFile* GetDexFile(const uint8_t* dex_bytes, size_t length) {
-    return new StandardDexFile(dex_bytes, length, "tmp", 0, nullptr, nullptr);
+    auto container = std::make_shared<MemoryDexFileContainer>(dex_bytes, length);
+    return new StandardDexFile(dex_bytes, length, "tmp", 0, nullptr, std::move(container));
   }
 
   void VerifyModification(const char* dex_file_base64_content,
@@ -76,12 +77,7 @@
 
     static constexpr bool kVerifyChecksum = true;
     std::string error_msg;
-    bool success = dex::Verify(dex_file.get(),
-                               dex_file->Begin(),
-                               dex_file->Size(),
-                               location,
-                               kVerifyChecksum,
-                               &error_msg);
+    bool success = dex::Verify(dex_file.get(), location, kVerifyChecksum, &error_msg);
     if (expected_error == nullptr) {
       EXPECT_TRUE(success) << error_msg;
     } else {
@@ -104,16 +100,13 @@
 
   // read dex
   std::vector<std::unique_ptr<const DexFile>> tmp;
-  const DexFileLoader dex_file_loader;
+  DexFileLoader dex_file_loader(dex_bytes.get(), length, location);
   DexFileLoaderErrorCode error_code;
-  bool success = dex_file_loader.OpenAll(dex_bytes.get(),
-                                         length,
-                                         location,
-                                         /* verify= */ true,
-                                         /* verify_checksum= */ true,
-                                         &error_code,
-                                         error_msg,
-                                         &tmp);
+  bool success = dex_file_loader.Open(/* verify= */ true,
+                                      /* verify_checksum= */ true,
+                                      &error_code,
+                                      error_msg,
+                                      &tmp);
   CHECK(success) << *error_msg;
   EXPECT_EQ(1U, tmp.size());
   std::unique_ptr<const DexFile> dex_file = std::move(tmp[0]);
@@ -1620,16 +1613,12 @@
 
   // Good checksum: all pass.
   EXPECT_TRUE(dex::Verify(dex_file.get(),
-                          dex_file->Begin(),
-                          dex_file->Size(),
                           "good checksum, no verify",
-                          /*verify_checksum=*/ false,
+                          /*verify_checksum=*/false,
                           &error_msg));
   EXPECT_TRUE(dex::Verify(dex_file.get(),
-                          dex_file->Begin(),
-                          dex_file->Size(),
                           "good checksum, verify",
-                          /*verify_checksum=*/ true,
+                          /*verify_checksum=*/true,
                           &error_msg));
 
   // Bad checksum: !verify_checksum passes verify_checksum fails.
@@ -1637,16 +1626,12 @@
       const_cast<uint8_t*>(dex_file->Begin()));
   header->checksum_ = 0;
   EXPECT_TRUE(dex::Verify(dex_file.get(),
-                          dex_file->Begin(),
-                          dex_file->Size(),
                           "bad checksum, no verify",
-                          /*verify_checksum=*/ false,
+                          /*verify_checksum=*/false,
                           &error_msg));
   EXPECT_FALSE(dex::Verify(dex_file.get(),
-                           dex_file->Begin(),
-                           dex_file->Size(),
                            "bad checksum, verify",
-                           /*verify_checksum=*/ true,
+                           /*verify_checksum=*/true,
                            &error_msg));
   EXPECT_NE(error_msg.find("Bad checksum"), std::string::npos) << error_msg;
 }
@@ -1690,10 +1675,8 @@
   std::unique_ptr<DexFile> dex_file(GetDexFile(dex_bytes.get(), length));
   std::string error_msg;
   EXPECT_FALSE(dex::Verify(dex_file.get(),
-                           dex_file->Begin(),
-                           dex_file->Size(),
                            "bad static method name",
-                           /*verify_checksum=*/ true,
+                           /*verify_checksum=*/true,
                            &error_msg));
 }
 
@@ -1734,10 +1717,8 @@
   std::unique_ptr<DexFile> dex_file(GetDexFile(dex_bytes.get(), length));
   std::string error_msg;
   EXPECT_FALSE(dex::Verify(dex_file.get(),
-                           dex_file->Begin(),
-                           dex_file->Size(),
                            "bad virtual method name",
-                           /*verify_checksum=*/ true,
+                           /*verify_checksum=*/true,
                            &error_msg));
 }
 
@@ -1778,10 +1759,8 @@
   std::unique_ptr<DexFile> dex_file(GetDexFile(dex_bytes.get(), length));
   std::string error_msg;
   EXPECT_FALSE(dex::Verify(dex_file.get(),
-                           dex_file->Begin(),
-                           dex_file->Size(),
                            "bad clinit signature",
-                           /*verify_checksum=*/ true,
+                           /*verify_checksum=*/true,
                            &error_msg));
 }
 
@@ -1822,10 +1801,8 @@
   std::unique_ptr<DexFile> dex_file(GetDexFile(dex_bytes.get(), length));
   std::string error_msg;
   EXPECT_FALSE(dex::Verify(dex_file.get(),
-                           dex_file->Begin(),
-                           dex_file->Size(),
                            "bad clinit signature",
-                           /*verify_checksum=*/ true,
+                           /*verify_checksum=*/true,
                            &error_msg));
 }
 
@@ -1859,10 +1836,8 @@
   std::unique_ptr<DexFile> dex_file(GetDexFile(dex_bytes.get(), length));
   std::string error_msg;
   EXPECT_FALSE(dex::Verify(dex_file.get(),
-                           dex_file->Begin(),
-                           dex_file->Size(),
                            "bad init signature",
-                           /*verify_checksum=*/ true,
+                           /*verify_checksum=*/true,
                            &error_msg));
 }
 
@@ -2062,10 +2037,8 @@
     std::unique_ptr<DexFile> dex_file(GetDexFile(dex_bytes.get(), length));
     std::string error_msg;
     EXPECT_TRUE(dex::Verify(dex_file.get(),
-                            dex_file->Begin(),
-                            dex_file->Size(),
                             "good checksum, verify",
-                            /*verify_checksum=*/ true,
+                            /*verify_checksum=*/true,
                             &error_msg));
     // TODO(oth): Test corruptions (b/35308502)
   }
@@ -2109,10 +2082,8 @@
   std::unique_ptr<DexFile> dex_file(GetDexFile(dex_bytes.get(), length));
   std::string error_msg;
   EXPECT_FALSE(dex::Verify(dex_file.get(),
-                           dex_file->Begin(),
-                           dex_file->Size(),
                            "bad static field initial values array",
-                           /*verify_checksum=*/ true,
+                           /*verify_checksum=*/true,
                            &error_msg));
 }
 
@@ -2165,10 +2136,8 @@
   std::unique_ptr<DexFile> dex_file(GetDexFile(dex_bytes.get(), length));
   std::string error_msg;
   EXPECT_TRUE(dex::Verify(dex_file.get(),
-                          dex_file->Begin(),
-                          dex_file->Size(),
                           "good static field initial values array",
-                          /*verify_checksum=*/ true,
+                          /*verify_checksum=*/true,
                           &error_msg));
 }
 
diff --git a/libdexfile/dex/dex_instruction.h b/libdexfile/dex/dex_instruction.h
index 7e43f75..ff6fcf7 100644
--- a/libdexfile/dex/dex_instruction.h
+++ b/libdexfile/dex/dex_instruction.h
@@ -22,8 +22,8 @@
 #include "base/globals.h"
 #include "base/macros.h"
 
-typedef uint8_t uint4_t;
-typedef int8_t int4_t;
+using uint4_t = uint8_t;
+using int4_t = int8_t;
 
 namespace art {
 
diff --git a/libdexfile/dex/modifiers.h b/libdexfile/dex/modifiers.h
index 72949b0..a17545c 100644
--- a/libdexfile/dex/modifiers.h
+++ b/libdexfile/dex/modifiers.h
@@ -56,6 +56,9 @@
 // Used by a class to denote that this class and any objects with this as a
 // declaring-class/super-class are to be considered obsolete, meaning they should not be used by.
 static constexpr uint32_t kAccObsoleteObject =        0x00200000;  // class (runtime)
+// Set during boot image compilation to indicate that the class is
+// not initialized at compile tile and not in the list of preloaded classes.
+static constexpr uint32_t kAccInBootImageAndNotInPreloadedClasses = 0x00400000;  // class (runtime)
 // This is set by the class linker during LinkInterfaceMethods. It is used by a method
 // to represent that it was copied from its declaring class into another class.
 // We need copies of the original method because the method may end up in different
diff --git a/libdexfile/dex/standard_dex_file.h b/libdexfile/dex/standard_dex_file.h
index 25cf62a..4ab27ef 100644
--- a/libdexfile/dex/standard_dex_file.h
+++ b/libdexfile/dex/standard_dex_file.h
@@ -18,6 +18,7 @@
 #define ART_LIBDEXFILE_DEX_STANDARD_DEX_FILE_H_
 
 #include <iosfwd>
+#include <memory>
 
 #include "dex_file.h"
 
@@ -116,11 +117,10 @@
                   const std::string& location,
                   uint32_t location_checksum,
                   const OatDexFile* oat_dex_file,
-                  std::unique_ptr<DexFileContainer> container)
+                  // Shared since several dex files may be stored in the same logical container.
+                  std::shared_ptr<DexFileContainer> container)
       : DexFile(base,
                 size,
-                /*data_begin*/ base,
-                /*data_size*/ size,
                 location,
                 location_checksum,
                 oat_dex_file,
diff --git a/libdexfile/dex/test_dex_file_builder.h b/libdexfile/dex/test_dex_file_builder.h
index 283dd48..964f196 100644
--- a/libdexfile/dex/test_dex_file_builder.h
+++ b/libdexfile/dex/test_dex_file_builder.h
@@ -237,14 +237,12 @@
     static constexpr bool kVerify = false;
     static constexpr bool kVerifyChecksum = false;
     std::string error_msg;
-    std::unique_ptr<const DexFile> dex_file(DexFileLoader::Open(
-        dex_location,
-        location_checksum,
-        std::move(dex_file_data),
-        /*oat_dex_file=*/ nullptr,
-        kVerify,
-        kVerifyChecksum,
-        &error_msg));
+    DexFileLoader dex_file_loader(std::move(dex_file_data), dex_location);
+    std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(location_checksum,
+                                                                 /*oat_dex_file=*/nullptr,
+                                                                 kVerify,
+                                                                 kVerifyChecksum,
+                                                                 &error_msg));
     CHECK(dex_file != nullptr) << error_msg;
     return dex_file;
   }
diff --git a/libdexfile/dex/utf.cc b/libdexfile/dex/utf.cc
index bfc704d..9692a26 100644
--- a/libdexfile/dex/utf.cc
+++ b/libdexfile/dex/utf.cc
@@ -133,45 +133,11 @@
   }
 
   // String contains non-ASCII characters.
-  while (char_count--) {
-    const uint16_t ch = *utf16_in++;
-    if (ch > 0 && ch <= 0x7f) {
-      *utf8_out++ = ch;
-    } else {
-      // Char_count == 0 here implies we've encountered an unpaired
-      // surrogate and we have no choice but to encode it as 3-byte UTF
-      // sequence. Note that unpaired surrogates can occur as a part of
-      // "normal" operation.
-      if ((ch >= 0xd800 && ch <= 0xdbff) && (char_count > 0)) {
-        const uint16_t ch2 = *utf16_in;
-
-        // Check if the other half of the pair is within the expected
-        // range. If it isn't, we will have to emit both "halves" as
-        // separate 3 byte sequences.
-        if (ch2 >= 0xdc00 && ch2 <= 0xdfff) {
-          utf16_in++;
-          char_count--;
-          const uint32_t code_point = (ch << 10) + ch2 - 0x035fdc00;
-          *utf8_out++ = (code_point >> 18) | 0xf0;
-          *utf8_out++ = ((code_point >> 12) & 0x3f) | 0x80;
-          *utf8_out++ = ((code_point >> 6) & 0x3f) | 0x80;
-          *utf8_out++ = (code_point & 0x3f) | 0x80;
-          continue;
-        }
-      }
-
-      if (ch > 0x07ff) {
-        // Three byte encoding.
-        *utf8_out++ = (ch >> 12) | 0xe0;
-        *utf8_out++ = ((ch >> 6) & 0x3f) | 0x80;
-        *utf8_out++ = (ch & 0x3f) | 0x80;
-      } else /*(ch > 0x7f || ch == 0)*/ {
-        // Two byte encoding.
-        *utf8_out++ = (ch >> 6) | 0xc0;
-        *utf8_out++ = (ch & 0x3f) | 0x80;
-      }
-    }
-  }
+  // FIXME: We should not emit 4-byte sequences. Bug: 192935764
+  auto append = [&](char c) { *utf8_out++ = c; };
+  ConvertUtf16ToUtf8</*kUseShortZero=*/ false,
+                     /*kUse4ByteSequence=*/ true,
+                     /*kReplaceBadSurrogates=*/ false>(utf16_in, char_count, append);
 }
 
 int32_t ComputeUtf16HashFromModifiedUtf8(const char* utf8, size_t utf16_length) {
@@ -240,34 +206,13 @@
   }
 }
 
-size_t CountUtf8Bytes(const uint16_t* chars, size_t char_count) {
+size_t CountModifiedUtf8BytesInUtf16(const uint16_t* chars, size_t char_count) {
+  // FIXME: We should not emit 4-byte sequences. Bug: 192935764
   size_t result = 0;
-  const uint16_t *end = chars + char_count;
-  while (chars < end) {
-    const uint16_t ch = *chars++;
-    if (LIKELY(ch != 0 && ch < 0x80)) {
-      result++;
-      continue;
-    }
-    if (ch < 0x800) {
-      result += 2;
-      continue;
-    }
-    if (ch >= 0xd800 && ch < 0xdc00) {
-      if (chars < end) {
-        const uint16_t ch2 = *chars;
-        // If we find a properly paired surrogate, we emit it as a 4 byte
-        // UTF sequence. If we find an unpaired leading or trailing surrogate,
-        // we emit it as a 3 byte sequence like would have done earlier.
-        if (ch2 >= 0xdc00 && ch2 < 0xe000) {
-          chars++;
-          result += 4;
-          continue;
-        }
-      }
-    }
-    result += 3;
-  }
+  auto append = [&](char c ATTRIBUTE_UNUSED) { ++result; };
+  ConvertUtf16ToUtf8</*kUseShortZero=*/ false,
+                     /*kUse4ByteSequence=*/ true,
+                     /*kReplaceBadSurrogates=*/ false>(chars, char_count, append);
   return result;
 }
 
diff --git a/libdexfile/dex/utf.h b/libdexfile/dex/utf.h
index 35cbf78..d372bff 100644
--- a/libdexfile/dex/utf.h
+++ b/libdexfile/dex/utf.h
@@ -41,12 +41,6 @@
 size_t CountModifiedUtf8Chars(const char* utf8, size_t byte_count);
 
 /*
- * Returns the number of modified UTF-8 bytes needed to represent the given
- * UTF-16 string.
- */
-size_t CountUtf8Bytes(const uint16_t* chars, size_t char_count);
-
-/*
  * Convert from Modified UTF-8 to UTF-16.
  */
 void ConvertModifiedUtf8ToUtf16(uint16_t* utf16_out, const char* utf8_in);
@@ -85,8 +79,14 @@
 void ConvertUtf16ToUtf8(const uint16_t* utf16, size_t char_count, Append&& append);
 
 /*
+ * Returns the number of modified UTF-8 bytes needed to represent the given
+ * UTF-16 string.
+ */
+size_t CountModifiedUtf8BytesInUtf16(const uint16_t* chars, size_t char_count);
+
+/*
  * Convert from UTF-16 to Modified UTF-8. Note that the output is _not_
- * NUL-terminated. You probably need to call CountUtf8Bytes before calling
+ * NUL-terminated. You probably need to call CountModifiedUtf8BytesInUtf16 before calling
  * this anyway, so if you want a NUL-terminated string, you know where to
  * put the NUL byte.
  */
diff --git a/libdexfile/dex/utf_test.cc b/libdexfile/dex/utf_test.cc
index 919259e..85c74d2 100644
--- a/libdexfile/dex/utf_test.cc
+++ b/libdexfile/dex/utf_test.cc
@@ -117,7 +117,7 @@
 
 static void AssertConversion(const std::vector<uint16_t>& input,
                              const std::vector<uint8_t>& expected) {
-  ASSERT_EQ(expected.size(), CountUtf8Bytes(&input[0], input.size()));
+  ASSERT_EQ(expected.size(), CountModifiedUtf8BytesInUtf16(&input[0], input.size()));
 
   std::vector<uint8_t> output(expected.size());
   ConvertUtf16ToModifiedUtf8(reinterpret_cast<char*>(&output[0]), expected.size(),
@@ -229,7 +229,7 @@
   return len;
 }
 
-static size_t CountUtf8Bytes_reference(const uint16_t* chars, size_t char_count) {
+static size_t CountModifiedUtf8BytesInUtf16_reference(const uint16_t* chars, size_t char_count) {
   size_t result = 0;
   while (char_count--) {
     const uint16_t ch = *chars++;
@@ -320,8 +320,8 @@
   int char_count_test, char_count_reference;
 
   // Calculate the number of utf-8 bytes for the utf-16 chars.
-  byte_count_reference = CountUtf8Bytes_reference(buf, char_count);
-  byte_count_test = CountUtf8Bytes(buf, char_count);
+  byte_count_reference = CountModifiedUtf8BytesInUtf16_reference(buf, char_count);
+  byte_count_test = CountModifiedUtf8BytesInUtf16(buf, char_count);
   EXPECT_EQ(byte_count_reference, byte_count_test);
 
   // Convert the utf-16 string to utf-8 bytes.
diff --git a/libdexfile/external/dex_file_ext.cc b/libdexfile/external/dex_file_ext.cc
index 7c0bf2d..e71233c 100644
--- a/libdexfile/external/dex_file_ext.cc
+++ b/libdexfile/external/dex_file_ext.cc
@@ -22,6 +22,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <algorithm>
 #include <cerrno>
 #include <cstring>
 #include <deque>
@@ -154,7 +155,8 @@
   }
 
   const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(address);
-  uint32_t file_size = header->file_size_;
+  uint32_t dex_size = header->file_size_;  // Size of "one dex file" excluding any shared data.
+  uint32_t full_size = dex_size;           // Includes referenced shared data past the end of dex.
   if (art::CompactDexFile::IsMagicValid(header->magic_)) {
     // Compact dex files store the data section separately so that it can be shared.
     // Therefore we need to extend the read memory range to include it.
@@ -164,27 +166,24 @@
     if (__builtin_add_overflow(header->data_off_, header->data_size_, &computed_file_size)) {
       return ADEXFILE_ERROR_INVALID_HEADER;
     }
-    if (computed_file_size > file_size) {
-      file_size = computed_file_size;
+    if (computed_file_size > full_size) {
+      full_size = computed_file_size;
     }
   } else if (!art::StandardDexFile::IsMagicValid(header->magic_)) {
     return ADEXFILE_ERROR_INVALID_HEADER;
   }
 
-  if (size < file_size) {
+  if (size < full_size) {
     if (new_size != nullptr) {
-      *new_size = file_size;
+      *new_size = full_size;
     }
     return ADEXFILE_ERROR_NOT_ENOUGH_DATA;
   }
 
   std::string loc_str(location);
-  art::DexFileLoader loader;
   std::string error_msg;
-  std::unique_ptr<const art::DexFile> dex_file = loader.Open(static_cast<const uint8_t*>(address),
-                                                             size,
-                                                             loc_str,
-                                                             header->checksum_,
+  art::DexFileLoader loader(static_cast<const uint8_t*>(address), dex_size, loc_str);
+  std::unique_ptr<const art::DexFile> dex_file = loader.Open(header->checksum_,
                                                              /*oat_dex_file=*/nullptr,
                                                              /*verify=*/false,
                                                              /*verify_checksum=*/false,
diff --git a/libdexfile/external/dex_file_supp.cc b/libdexfile/external/dex_file_supp.cc
index 23fc88a..617dc88 100644
--- a/libdexfile/external/dex_file_supp.cc
+++ b/libdexfile/external/dex_file_supp.cc
@@ -21,9 +21,6 @@
 #include <mutex>
 #include <sys/stat.h>
 
-#include <android-base/mapped_file.h>
-#include <android-base/stringprintf.h>
-
 #ifndef STATIC_LIB
 // Not used in the static lib, so avoid a dependency on this header in
 // libdexfile_support_static.
diff --git a/libdexfile/external/include/art_api/dex_file_external.h b/libdexfile/external/include/art_api/dex_file_external.h
index 360be92..d9db200 100644
--- a/libdexfile/external/include/art_api/dex_file_external.h
+++ b/libdexfile/external/include/art_api/dex_file_external.h
@@ -28,10 +28,10 @@
 // may only be added here. C++ users should use dex_file_support.h instead.
 
 struct ADexFile;
-typedef struct ADexFile ADexFile;
+typedef struct ADexFile ADexFile; // NOLINT
 
 struct ADexFile_Method;
-typedef struct ADexFile_Method ADexFile_Method;
+typedef struct ADexFile_Method ADexFile_Method; // NOLINT
 
 enum ADexFile_Error : uint32_t {
   ADEXFILE_ERROR_OK = 0,
@@ -39,10 +39,11 @@
   ADEXFILE_ERROR_INVALID_HEADER = 2,
   ADEXFILE_ERROR_NOT_ENOUGH_DATA = 3,
 };
-typedef enum ADexFile_Error ADexFile_Error;
+typedef enum ADexFile_Error ADexFile_Error; // NOLINT
 
 // Callback used to return information about a dex method.
 // The method information is valid only during the callback.
+// NOLINTNEXTLINE
 typedef void ADexFile_MethodCallback(void* _Nullable callback_data,
                                      const ADexFile_Method* _Nonnull method);
 
diff --git a/libdexfile/external/include/art_api/dex_file_support.h b/libdexfile/external/include/art_api/dex_file_support.h
index 2361bf9..d283929 100644
--- a/libdexfile/external/include/art_api/dex_file_support.h
+++ b/libdexfile/external/include/art_api/dex_file_support.h
@@ -22,8 +22,6 @@
 #include <memory>
 #include <string>
 
-#include <android-base/macros.h>
-
 #include "art_api/dex_file_external.h"
 
 namespace art_api {
@@ -128,7 +126,11 @@
 
   ADexFile* const self_;
 
-  DISALLOW_COPY_AND_ASSIGN(DexFile);
+  // Have to expand DISALLOW_COPY_AND_ASSIGN here, since we cannot depend on
+  // libbase headers without re-exporting them, and that may make them override
+  // the non-ABI compatible headers that the libdexfile_support user may have.
+  DexFile(const DexFile&) = delete;
+  void operator=(const DexFile&) = delete;
 };
 
 }  // namespace dex
diff --git a/libelffile/dwarf/register.h b/libelffile/dwarf/register.h
index 7742ec4..33c6795 100644
--- a/libelffile/dwarf/register.h
+++ b/libelffile/dwarf/register.h
@@ -40,6 +40,8 @@
   static Reg ArmDp(int num) { return Reg(256 + num); }  // D0–D31.
   static Reg Arm64Core(int num) { return Reg(num); }  // X0-X31.
   static Reg Arm64Fp(int num) { return Reg(64 + num); }  // V0-V31.
+  static Reg Riscv64Core(int num) { return Reg(num); }  // X0-X31
+  static Reg Riscv64Fp(int num) { return Reg(32 + num); }  // F0-F31
   static Reg X86Core(int num) { return Reg(num); }
   static Reg X86Fp(int num) { return Reg(21 + num); }
   static Reg X86_64Core(int num) {
diff --git a/libelffile/elf/elf_builder.h b/libelffile/elf/elf_builder.h
index 086bd41..6720569 100644
--- a/libelffile/elf/elf_builder.h
+++ b/libelffile/elf/elf_builder.h
@@ -814,6 +814,8 @@
         return InstructionSet::kThumb2;
       case EM_AARCH64:
         return InstructionSet::kArm64;
+      case EM_RISCV:
+        return InstructionSet::kRiscv64;
       case EM_386:
         return InstructionSet::kX86;
       case EM_X86_64:
@@ -839,6 +841,11 @@
         elf_header.e_flags = 0;
         break;
       }
+      case InstructionSet::kRiscv64: {
+        elf_header.e_machine = EM_RISCV;
+        elf_header.e_flags = EF_RISCV_RVC | EF_RISCV_FLOAT_ABI_DOUBLE;
+        break;
+      }
       case InstructionSet::kX86: {
         elf_header.e_machine = EM_386;
         elf_header.e_flags = 0;
diff --git a/libelffile/elf/elf_debug_reader.h b/libelffile/elf/elf_debug_reader.h
index 266c638..6e01989 100644
--- a/libelffile/elf/elf_debug_reader.h
+++ b/libelffile/elf/elf_debug_reader.h
@@ -35,11 +35,11 @@
 class ElfDebugReader {
  public:
   // Note that the input buffer might be misaligned.
-  typedef typename ElfTypes::Ehdr ALIGNED(1) Elf_Ehdr;
-  typedef typename ElfTypes::Phdr ALIGNED(1) Elf_Phdr;
-  typedef typename ElfTypes::Shdr ALIGNED(1) Elf_Shdr;
-  typedef typename ElfTypes::Sym ALIGNED(1) Elf_Sym;
-  typedef typename ElfTypes::Addr ALIGNED(1) Elf_Addr;
+  using Elf_Ehdr ALIGNED(1) = typename ElfTypes::Ehdr;
+  using Elf_Phdr ALIGNED(1) = typename ElfTypes::Phdr;
+  using Elf_Shdr ALIGNED(1) = typename ElfTypes::Shdr;
+  using Elf_Sym ALIGNED(1) = typename ElfTypes::Sym;
+  using Elf_Addr ALIGNED(1) = typename ElfTypes::Addr;
 
   // Call Frame Information.
   struct CFI {
diff --git a/libelffile/elf/elf_utils.h b/libelffile/elf/elf_utils.h
index 9c4f0d81..46b25b0 100644
--- a/libelffile/elf/elf_utils.h
+++ b/libelffile/elf/elf_utils.h
@@ -65,10 +65,21 @@
 
 #define EI_ABIVERSION 8
 #define EM_ARM 40
+#if !defined(STV_DEFAULT)
 #define STV_DEFAULT 0
+#endif
 
 #define EM_AARCH64 183
 
+#ifndef EM_RISCV
+#define EM_RISCV 243
+#endif
+
+#ifndef EF_RISCV_RVC
+#define EF_RISCV_RVC 0x1
+#define EF_RISCV_FLOAT_ABI_DOUBLE 0x4
+#endif
+
 #define DT_BIND_NOW 24
 #define DT_INIT_ARRAY 25
 #define DT_FINI_ARRAY 26
diff --git a/libelffile/stream/error_delaying_output_stream.h b/libelffile/stream/error_delaying_output_stream.h
index b37ff4e..7553de6 100644
--- a/libelffile/stream/error_delaying_output_stream.h
+++ b/libelffile/stream/error_delaying_output_stream.h
@@ -71,8 +71,9 @@
         PLOG(ERROR) << "Failed to seek in " << GetLocation() << ". Offset=" << offset
                     << " whence=" << whence << " new_offset=" << new_offset;
         output_good_ = false;
+      } else {
+        DCHECK_EQ(actual_offset, new_offset);
       }
-      DCHECK_EQ(actual_offset, new_offset);
     }
     output_offset_ = new_offset;
     return new_offset;
diff --git a/libnativebridge/Android.bp b/libnativebridge/Android.bp
index 356a1f4..dc19f07 100644
--- a/libnativebridge/Android.bp
+++ b/libnativebridge/Android.bp
@@ -45,6 +45,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 
     host_supported: true,
diff --git a/libnativebridge/OWNERS b/libnativebridge/OWNERS
index daf87f4..71f76a6 100644
--- a/libnativebridge/OWNERS
+++ b/libnativebridge/OWNERS
@@ -1,3 +1,5 @@
+# Bug component: 86431
[email protected]
 [email protected]
 [email protected]
 [email protected]
diff --git a/libnativebridge/include/nativebridge/native_bridge.h b/libnativebridge/include/nativebridge/native_bridge.h
index 2199bab..5904c0f 100644
--- a/libnativebridge/include/nativebridge/native_bridge.h
+++ b/libnativebridge/include/nativebridge/native_bridge.h
@@ -40,7 +40,7 @@
 // Function pointer type for sigaction. This is mostly the signature of a signal handler, except
 // for the return type. The runtime needs to know whether the signal was handled or should be given
 // to the chain.
-typedef bool (*NativeBridgeSignalHandlerFn)(int, siginfo_t*, void*);
+typedef bool (*NativeBridgeSignalHandlerFn)(int, siginfo_t*, void*);  // NOLINT
 
 // Open the native bridge, if any. Should be called by Runtime::Init(). A null library filename
 // signals that we do not want to load a native bridge.
diff --git a/libnativebridge/libnativebridge.map.txt b/libnativebridge/libnativebridge.map.txt
index 8b51ab8..52d06eb 100644
--- a/libnativebridge/libnativebridge.map.txt
+++ b/libnativebridge/libnativebridge.map.txt
@@ -20,12 +20,12 @@
 # that defines the exported interface. Please keep in sync with this list.
 LIBNATIVEBRIDGE_1 {
   global:
-    NativeBridgeGetError;
-    NativeBridgeInitialized;
-    NativeBridgeGetTrampoline;
-    PreInitializeNativeBridge;
-    NativeBridgeAvailable;
-    NeedsNativeBridge;
+    NativeBridgeGetError; # apex
+    NativeBridgeInitialized; # apex
+    NativeBridgeGetTrampoline; # apex
+    PreInitializeNativeBridge; # apex
+    NativeBridgeAvailable; # apex
+    NeedsNativeBridge; # apex
   local:
     *;
 };
diff --git a/libnativebridge/tests/Android.bp b/libnativebridge/tests/Android.bp
index ae24ae2..dc6965f 100644
--- a/libnativebridge/tests/Android.bp
+++ b/libnativebridge/tests/Android.bp
@@ -82,14 +82,13 @@
     defaults: ["libnativebridge-test-case-defaults"],
 }
 
-cc_defaults {
-    name: "libnativebridge-tests-defaults",
+cc_test {
+    name: "libnativebridge-tests",
     defaults: [
         "art_defaults",
         "art_test_defaults",
     ],
-    // TODO(mast): Split up art_gtest_defaults so that it can be used for the
-    // following without pulling in lots of libs.
+
     target: {
         linux: {
             cflags: [
@@ -100,16 +99,19 @@
             ],
         },
     },
-}
-
-cc_test {
-    name: "libnativebridge-tests",
-    defaults: ["libnativebridge-tests-defaults"],
 
     // native_bridge.cc doesn't support reloading the native bridge after
     // unloading, so each test needs to be its own process.
     test_per_src: true,
 
+    // Disable pre-submit host unit-testing for this test module, as
+    // it is not compatible with TradeFed (because of the use of the
+    // `test_per_src` feature above) and meant to be executed with the
+    // `runtests.sh` script instead.
+    test_options: {
+        unit_test: false,
+    },
+
     srcs: [
         "NativeBridgeApi.c",
         "CodeCacheCreate_test.cpp",
@@ -146,49 +148,14 @@
     header_libs: ["libbase_headers"],
 }
 
-// Variant of libnativebridge-tests that is part of CTS to verify backed-by API
-// coverage.
+// Very basic tests in CTS to verify backed-by API coverage of the exported API
+// in libnativebridge.map.txt.
 cc_test {
     name: "art_libnativebridge_cts_tests",
-    defaults: [
-        "art_standalone_test_defaults",
-        "libnativebridge-tests-defaults",
-    ],
-
-    // TODO(b/189484095): Pick only a subset of the tests in
-    // libnativebridge-tests that don't require the native bridge lib to be
-    // loaded, to avoid the problems with test_per_src and pushing the extra
-    // libnativebridge*-test-case.so files to device through tradefed.
-    srcs: [
-        // ValidNameNativeBridge_test.cpp needs to be first due to global state
-        // had_error that isn't reset between tests.
-        "ValidNameNativeBridge_test.cpp",
-        "NeedsNativeBridge_test.cpp",
-        "UnavailableNativeBridge_test.cpp",
-    ],
-    static_libs: [
-        "libdl_android",
-        "libnativebridge",
-    ],
-    shared_libs: [
-        "liblog",
-    ],
-    header_libs: ["libbase_headers"],
-
-    // Support multilib variants (using different suffix per sub-architecture),
-    // which is needed on build targets with secondary architectures, as the CTS
-    // test suite packaging logic flattens all test artifacts into a single
-    // `testcases` directory.
-    compile_multilib: "both",
-    multilib: {
-        lib32: {
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
-        },
-    },
-
+    defaults: ["art_standalone_test_defaults"],
+    shared_libs: ["libnativebridge"],
+    static_libs: ["libbase"],
+    srcs: ["libnativebridge_api_test.cpp"],
     test_config_template: ":art-gtests-target-standalone-cts-template",
     test_suites: [
         "cts",
@@ -200,12 +167,10 @@
 
 cc_test {
     name: "libnativebridge-lazy-tests",
-    defaults: ["libnativebridge-tests-defaults"],
-    host_supported: false,
-    test_suites: ["device-tests"],
+    defaults: ["art_standalone_test_defaults"],
     static_libs: [
         "libbase",
         "libnativebridge_lazy",
     ],
-    srcs: ["libnativebridge_lazy_test.cpp"],
+    srcs: ["libnativebridge_api_test.cpp"],
 }
diff --git a/libnativebridge/tests/PreInitializeNativeBridge_test.cpp b/libnativebridge/tests/PreInitializeNativeBridge_test.cpp
index 149b05e..98ef30f 100644
--- a/libnativebridge/tests/PreInitializeNativeBridge_test.cpp
+++ b/libnativebridge/tests/PreInitializeNativeBridge_test.cpp
@@ -39,7 +39,7 @@
     // Try to create our mount namespace.
     if (unshare(CLONE_NEWNS) != -1) {
         // Create a placeholder file.
-        FILE* cpuinfo = fopen("./cpuinfo", "w");
+        FILE* cpuinfo = fopen("./cpuinfo", "we");
         ASSERT_NE(nullptr, cpuinfo) << strerror(errno);
         fprintf(cpuinfo, kTestData);
         fclose(cpuinfo);
@@ -47,7 +47,7 @@
         ASSERT_TRUE(PreInitializeNativeBridge("does not matter 1", "short 2"));
 
         // Read /proc/cpuinfo
-        FILE* proc_cpuinfo = fopen("/proc/cpuinfo", "r");
+        FILE* proc_cpuinfo = fopen("/proc/cpuinfo", "re");
         ASSERT_NE(nullptr, proc_cpuinfo) << strerror(errno);
         char buf[1024];
         EXPECT_NE(nullptr, fgets(buf, sizeof(buf), proc_cpuinfo)) << "Error reading.";
diff --git a/libnativebridge/tests/libnativebridge_api_test.cpp b/libnativebridge/tests/libnativebridge_api_test.cpp
new file mode 100644
index 0000000..037587c
--- /dev/null
+++ b/libnativebridge/tests/libnativebridge_api_test.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <android-base/macros.h>
+#include <gtest/gtest.h>
+
+#include "nativebridge/native_bridge.h"
+
+namespace android {
+
+class NativeBridgeApiTest : public ::testing::Test {};
+
+// Test the exported API in libnativebridge and libnativebridge_lazy.
+// The testing we can do here is limited since there's no exported API to
+// actually load the native bridge, but we only need to test the trivial
+// wrappers.
+
+TEST_F(NativeBridgeApiTest, NeedsNativeBridge) {
+  EXPECT_FALSE(NeedsNativeBridge(ABI_STRING));
+}
+
+TEST_F(NativeBridgeApiTest, PreInitializeNativeBridge) {
+  EXPECT_FALSE(PreInitializeNativeBridge(nullptr, ""));
+}
+
+TEST_F(NativeBridgeApiTest, NativeBridgeAvailable) {
+  EXPECT_FALSE(NativeBridgeAvailable());
+}
+
+TEST_F(NativeBridgeApiTest, NativeBridgeInitialized) {
+  EXPECT_FALSE(NativeBridgeInitialized());
+}
+
+TEST_F(NativeBridgeApiTest, NativeBridgeGetTrampoline) {
+  EXPECT_EQ(nullptr, NativeBridgeGetTrampoline(nullptr, nullptr, nullptr, 0));
+}
+
+TEST_F(NativeBridgeApiTest, NativeBridgeGetError) {
+  EXPECT_STREQ("native bridge is not initialized", NativeBridgeGetError());
+}
+
+};  // namespace android
diff --git a/libnativebridge/tests/libnativebridge_lazy_test.cpp b/libnativebridge/tests/libnativebridge_lazy_test.cpp
deleted file mode 100644
index e1d66f5..0000000
--- a/libnativebridge/tests/libnativebridge_lazy_test.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-#include <android-base/macros.h>
-#include <gtest/gtest.h>
-
-#include "nativebridge/native_bridge.h"
-
-namespace android {
-
-class NativeBridgeLazyTest : public ::testing::Test {};
-
-// The testing we can do here is limited since there's no exported API to
-// actually load the native bridge, but we only need to test the trivial
-// wrappers.
-
-TEST_F(NativeBridgeLazyTest, NeedsNativeBridge) {
-  EXPECT_FALSE(NeedsNativeBridge(ABI_STRING));
-}
-
-TEST_F(NativeBridgeLazyTest, PreInitializeNativeBridge) {
-  EXPECT_FALSE(PreInitializeNativeBridge(nullptr, ""));
-}
-
-TEST_F(NativeBridgeLazyTest, NativeBridgeAvailable) {
-  EXPECT_FALSE(NativeBridgeAvailable());
-}
-
-TEST_F(NativeBridgeLazyTest, NativeBridgeInitialized) {
-  EXPECT_FALSE(NativeBridgeInitialized());
-}
-
-TEST_F(NativeBridgeLazyTest, NativeBridgeGetTrampoline) {
-  EXPECT_EQ(nullptr, NativeBridgeGetTrampoline(nullptr, nullptr, nullptr, 0));
-}
-
-TEST_F(NativeBridgeLazyTest, NativeBridgeGetError) {
-  EXPECT_STREQ("native bridge is not initialized", NativeBridgeGetError());
-}
-
-};  // namespace android
diff --git a/libnativeloader/Android.bp b/libnativeloader/Android.bp
index 9286f35..114af70 100644
--- a/libnativeloader/Android.bp
+++ b/libnativeloader/Android.bp
@@ -9,6 +9,28 @@
     default_applicable_licenses: ["art_license"],
 }
 
+cc_library_headers {
+    name: "libnativeloader-headers",
+    defaults: ["art_defaults"],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.art",
+        "com.android.art.debug",
+        "com.android.media",
+    ],
+    visibility: [
+        "//art:__subpackages__",
+        // TODO(b/133140750): Clean this up.
+        "//frameworks/av/media/libstagefright",
+        "//frameworks/native/libs/graphicsenv",
+        "//frameworks/native/vulkan/libvulkan",
+    ],
+    host_supported: true,
+    export_include_dirs: ["include"],
+    header_libs: ["jni_headers"],
+    export_header_lib_headers: ["jni_headers"],
+}
+
 cc_defaults {
     name: "libnativeloader-defaults",
     defaults: ["art_defaults"],
@@ -27,6 +49,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
     host_supported: true,
     srcs: [
@@ -64,7 +87,7 @@
                 "libdl_android",
             ],
             static_libs: [
-                "PlatformProperties",
+                "libPlatformProperties",
             ],
         },
     },
@@ -74,6 +97,9 @@
     },
 }
 
+// Wrapper that loads nativeloader.so lazily, to be used to deal with layer
+// inversion in places like in early boot where libnativeloader and/or
+// libnativebridge aren't available.
 // TODO(b/124250621) eliminate the need for this library
 cc_library {
     name: "libnativeloader_lazy",
@@ -94,33 +120,14 @@
     shared_libs: ["liblog"],
 }
 
-cc_library_headers {
-    name: "libnativeloader-headers",
-    defaults: ["art_defaults"],
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.art",
-        "com.android.art.debug",
-        "com.android.media",
-    ],
-    visibility: [
-        "//art:__subpackages__",
-        // TODO(b/133140750): Clean this up.
-        "//frameworks/av/media/libstagefright",
-        "//frameworks/native/libs/graphicsenv",
-        "//frameworks/native/vulkan/libvulkan",
-    ],
-    host_supported: true,
-    export_include_dirs: ["include"],
-    header_libs: ["jni_headers"],
-    export_header_lib_headers: ["jni_headers"],
-}
-
-cc_defaults {
-    name: "libnativeloader-test-defaults",
+art_cc_test {
+    name: "libnativeloader_test",
     defaults: [
         "art_module_source_build_defaults",
-        "art_test_defaults",
+        // Cannot use art_standalone_gtest_defaults because it makes us link
+        // libnativebridge statically through libart-gtest, but we need to mock
+        // its symbols here.
+        "art_standalone_test_defaults",
     ],
     host_supported: false,
 
@@ -142,70 +149,61 @@
     ],
     shared_libs: [
         "libbase",
+        "libnativeloader",
     ],
 
-    test_suites: ["device-tests"],
-}
-
-art_cc_test {
-    name: "libnativeloader_test",
-    defaults: [
-        "art_standalone_test_defaults",
-        "libnativeloader-test-defaults",
-    ],
     tidy_timeout_srcs: [
         "native_loader_test.cpp",
     ],
     srcs: [
         "native_loader_api_test.c",
         "native_loader_test.cpp",
-        "open_system_library.cpp",
+    ],
+
+    test_for: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+    test_suites: [
+        "device-tests",
+    ],
+}
+
+cc_defaults {
+    name: "libnativeloader_api_test_defaults",
+    defaults: ["art_standalone_test_defaults"],
+
+    srcs: ["native_loader_api_test.cpp"],
+    header_libs: [
+        "libnativebridge-headers",
+        "libnativehelper_header_only",
     ],
     static_libs: [
         "libbase",
+        "libgmock",
+    ],
+}
+
+art_cc_test {
+    name: "art_libnativeloader_cts_test",
+    defaults: ["libnativeloader_api_test_defaults"],
+    shared_libs: [
         "libnativeloader",
     ],
-    shared_libs: [
-        "liblog",
-    ],
-    target: {
-        android: {
-            static_libs: [
-                "libPlatformProperties",
-            ],
-        },
-    },
-
-    // Support multilib variants (using different suffix per sub-architecture), which is needed on
-    // build targets with secondary architectures, as the CTS test suite packaging logic flattens
-    // all test artifacts into a single `testcases` directory.
-    compile_multilib: "both",
-    multilib: {
-        lib32: {
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
-        },
-    },
-
-    // Added to CTS for API coverage of libnativeloader which is backed by the
-    // ART module.
     test_config_template: ":art-gtests-target-standalone-cts-template",
     test_suites: [
         "cts",
-        "mts-art",
         "mcts-art",
     ],
 }
 
 art_cc_test {
     name: "libnativeloader_lazy_test",
-    defaults: ["libnativeloader-test-defaults"],
-    srcs: [
-        "native_loader_lazy_test.cpp",
-    ],
+    defaults: ["libnativeloader_api_test_defaults"],
     static_libs: [
         "libnativeloader_lazy",
     ],
+    test_suites: [
+        "device-tests",
+    ],
 }
diff --git a/libnativeloader/OWNERS b/libnativeloader/OWNERS
index f735653..87f9142 100644
--- a/libnativeloader/OWNERS
+++ b/libnativeloader/OWNERS
@@ -1,6 +1,6 @@
[email protected]
+# Bug component: 86431
[email protected]
 [email protected]
 [email protected]
 [email protected]
[email protected]
 [email protected]
diff --git a/libnativeloader/README.md b/libnativeloader/README.md
index c5ace61..919feff 100644
--- a/libnativeloader/README.md
+++ b/libnativeloader/README.md
@@ -10,11 +10,11 @@
 
 The most typical use case of this library is calling `System.loadLibrary(name)`.
 When the method is called, the ART runtime delegates the call to this library
-along with the reference to the classloader where the call was made.  Then this
-library finds the linker namespace (named `classloader-namespace`) that is
-associated with the given classloader, and tries to load the requested library
-from the namespace. The actual searching, loading, and linking of the library
-is performed by the dynamic linker.
+along with the reference to the classloader where the call was made. Then this
+library finds the linker namespace (typically with the name `clns-` followed by
+a number to make it unique) that is associated with the given classloader, and
+tries to load the requested library from that namespace. The actual searching,
+loading, and linking of the library is performed by the dynamic linker.
 
 The linker namespace is created when an APK is loaded into the process, and is
 associated with the classloader that loaded the APK. The linker namespace is
diff --git a/libnativeloader/libnativeloader.map.txt b/libnativeloader/libnativeloader.map.txt
index 59f457c..8c0fbdd 100644
--- a/libnativeloader/libnativeloader.map.txt
+++ b/libnativeloader/libnativeloader.map.txt
@@ -20,13 +20,13 @@
 # that defines the exported interface. Please keep in sync with this list.
 LIBNATIVELOADER_1 {
   global:
-    OpenNativeLibrary;
-    CloseNativeLibrary;
-    OpenNativeLibraryInNamespace;
-    FindNamespaceByClassLoader;
-    FindNativeLoaderNamespaceByClassLoader;
-    CreateClassLoaderNamespace;
-    NativeLoaderFreeErrorMessage;
+    OpenNativeLibrary; # apex
+    CloseNativeLibrary; # apex
+    OpenNativeLibraryInNamespace; # apex
+    FindNamespaceByClassLoader; # apex
+    FindNativeLoaderNamespaceByClassLoader; # apex
+    CreateClassLoaderNamespace; # apex
+    NativeLoaderFreeErrorMessage; # apex
   local:
     *;
 };
diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp
index f31c430..1e29f4e 100644
--- a/libnativeloader/library_namespaces.cpp
+++ b/libnativeloader/library_namespaces.cpp
@@ -30,6 +30,7 @@
 #include <android-base/macros.h>
 #include <android-base/result.h>
 #include <android-base/strings.h>
+#include <android-base/stringprintf.h>
 #include <nativehelper/scoped_utf_chars.h>
 
 #include "nativeloader/dlext_namespaces.h"
@@ -55,24 +56,26 @@
 // vndk_product namespace for unbundled product apps
 constexpr const char* kVndkProductNamespaceName = "vndk_product";
 
-// classloader-namespace is a linker namespace that is created for the loaded
-// app. To be specific, it is created for the app classloader. When
-// System.load() is called from a Java class that is loaded from the
-// classloader, the classloader-namespace namespace associated with that
-// classloader is selected for dlopen. The namespace is configured so that its
-// search path is set to the app-local JNI directory and it is linked to the
-// system namespace with the names of libs listed in the public.libraries.txt.
-// This way an app can only load its own JNI libraries along with the public libs.
-constexpr const char* kClassloaderNamespaceName = "classloader-namespace";
-// Same thing for vendor APKs.
-constexpr const char* kVendorClassloaderNamespaceName = "vendor-classloader-namespace";
-// If the namespace is shared then add this suffix to form
-// "classloader-namespace-shared" or "vendor-classloader-namespace-shared",
-// respectively. A shared namespace (cf. ANDROID_NAMESPACE_TYPE_SHARED) has
+// clns-XX is a linker namespace that is created for normal apps installed in
+// the data partition. To be specific, it is created for the app classloader.
+// When System.load() is called from a Java class that is loaded from the
+// classloader, the clns namespace associated with that classloader is selected
+// for dlopen. The namespace is configured so that its search path is set to the
+// app-local JNI directory and it is linked to the system namespace with the
+// names of libs listed in the public.libraries.txt and other public libraries.
+// This way an app can only load its own JNI libraries along with the public
+// libs.
+constexpr const char* kClassloaderNamespaceName = "clns";
+// Same thing for unbundled APKs in the vendor partition.
+constexpr const char* kVendorClassloaderNamespaceName = "vendor-clns";
+// Same thing for unbundled APKs in the product partition.
+constexpr const char* kProductClassloaderNamespaceName = "product-clns";
+// If the namespace is shared then add this suffix to help identify it in debug
+// messages. A shared namespace (cf. ANDROID_NAMESPACE_TYPE_SHARED) has
 // inherited all the libraries of the parent classloader namespace, or the
-// system namespace for the main app classloader. It is used to give full
-// access to the platform libraries for apps bundled in the system image,
-// including their later updates installed in /data.
+// system namespace for the main app classloader. It is used to give full access
+// to the platform libraries for apps bundled in the system image, including
+// their later updates installed in /data.
 constexpr const char* kSharedNamespaceSuffix = "-shared";
 
 // (http://b/27588281) This is a workaround for apps using custom classloaders and calling
@@ -81,16 +84,19 @@
 constexpr const char* kAlwaysPermittedDirectories = "/data:/mnt/expand";
 
 constexpr const char* kVendorLibPath = "/vendor/" LIB;
+// TODO(mast): It's unlikely that both paths are necessary for kProductLibPath
+// below, because they can't be two separate directories - either one has to be
+// a symlink to the other.
 constexpr const char* kProductLibPath = "/product/" LIB ":/system/product/" LIB;
 
-const std::regex kVendorDexPathRegex("(^|:)/vendor/");
+const std::regex kVendorDexPathRegex("(^|:)(/system)?/vendor/");
 const std::regex kProductDexPathRegex("(^|:)(/system)?/product/");
 
-// Define origin of APK if it is from vendor partition or product partition
+// Define origin partition of APK
 using ApkOrigin = enum {
   APK_ORIGIN_DEFAULT = 0,
-  APK_ORIGIN_VENDOR = 1,
-  APK_ORIGIN_PRODUCT = 2,
+  APK_ORIGIN_VENDOR = 1,   // Includes both /vendor and /system/vendor
+  APK_ORIGIN_PRODUCT = 2,  // Includes both /product and /system/product
 };
 
 jobject GetParentClassLoader(JNIEnv* env, jobject class_loader) {
@@ -231,51 +237,38 @@
   std::string system_exposed_libraries = default_public_libraries();
   std::string namespace_name = kClassloaderNamespaceName;
   ApkOrigin unbundled_app_origin = APK_ORIGIN_DEFAULT;
-  if ((apk_origin == APK_ORIGIN_VENDOR ||
-       (apk_origin == APK_ORIGIN_PRODUCT &&
-        is_product_vndk_version_defined())) &&
-      !is_shared) {
-    unbundled_app_origin = apk_origin;
-    // For vendor / product apks, give access to the vendor / product lib even though
-    // they are treated as unbundled; the libs and apks are still bundled
-    // together in the vendor / product partition.
-    const char* origin_partition;
-    const char* origin_lib_path;
-    const char* llndk_libraries;
+  const char* apk_origin_msg = "other apk";  // Only for debug logging.
 
-    switch (apk_origin) {
-      case APK_ORIGIN_VENDOR:
-        origin_partition = "vendor";
-        origin_lib_path = kVendorLibPath;
-        llndk_libraries = llndk_libraries_vendor().c_str();
-        break;
-      case APK_ORIGIN_PRODUCT:
-        origin_partition = "product";
-        origin_lib_path = kProductLibPath;
-        llndk_libraries = llndk_libraries_product().c_str();
-        break;
-      default:
-        origin_partition = "unknown";
-        origin_lib_path = "";
-        llndk_libraries = "";
-    }
-    library_path = library_path + ":" + origin_lib_path;
-    permitted_path = permitted_path + ":" + origin_lib_path;
+  if (!is_shared) {
+    if (apk_origin == APK_ORIGIN_VENDOR) {
+      unbundled_app_origin = APK_ORIGIN_VENDOR;
+      apk_origin_msg = "unbundled vendor apk";
 
-    // Also give access to LLNDK libraries since they are available to vendor or product
-    system_exposed_libraries = system_exposed_libraries + ":" + llndk_libraries;
+      // For vendor apks, give access to the vendor libs even though they are
+      // treated as unbundled; the libs and apks are still bundled together in the
+      // vendor partition.
+      library_path = library_path + ':' + kVendorLibPath;
+      permitted_path = permitted_path + ':' + kVendorLibPath;
 
-    // Different name is useful for debugging
-    namespace_name = kVendorClassloaderNamespaceName;
-    ALOGD("classloader namespace configured for unbundled %s apk. library_path=%s",
-          origin_partition, library_path.c_str());
-  } else {
-    auto libs = filter_public_libraries(target_sdk_version, uses_libraries,
-                                        extended_public_libraries());
-    // extended public libraries are NOT available to vendor apks, otherwise it
-    // would be system->vendor violation.
-    if (!libs.empty()) {
-      system_exposed_libraries = system_exposed_libraries + ':' + libs;
+      // Also give access to LLNDK libraries since they are available to vendor.
+      system_exposed_libraries = system_exposed_libraries + ':' + llndk_libraries_vendor();
+
+      // Different name is useful for debugging
+      namespace_name = kVendorClassloaderNamespaceName;
+    } else if (apk_origin == APK_ORIGIN_PRODUCT && is_product_treblelized()) {
+      unbundled_app_origin = APK_ORIGIN_PRODUCT;
+      apk_origin_msg = "unbundled product apk";
+
+      // Like for vendor apks, give access to the product libs since they are
+      // bundled together in the same partition.
+      library_path = library_path + ':' + kProductLibPath;
+      permitted_path = permitted_path + ':' + kProductLibPath;
+
+      // Also give access to LLNDK libraries since they are available to product.
+      system_exposed_libraries = system_exposed_libraries + ':' + llndk_libraries_product();
+
+      // Different name is useful for debugging
+      namespace_name = kProductClassloaderNamespaceName;
     }
   }
 
@@ -285,6 +278,36 @@
     namespace_name = namespace_name + kSharedNamespaceSuffix;
   }
 
+  // Append a unique number to the namespace name, to tell them apart when
+  // debugging linker issues, e.g. with debug.ld.all set to "dlopen,dlerror".
+  static int clns_count = 0;
+  namespace_name = android::base::StringPrintf("%s-%d", namespace_name.c_str(), ++clns_count);
+
+  ALOGD(
+      "Configuring %s for %s %s. target_sdk_version=%u, uses_libraries=%s, library_path=%s, "
+      "permitted_path=%s",
+      namespace_name.c_str(),
+      apk_origin_msg,
+      dex_path.c_str(),
+      static_cast<unsigned>(target_sdk_version),
+      android::base::Join(uses_libraries, ':').c_str(),
+      library_path.c_str(),
+      permitted_path.c_str());
+
+  if (unbundled_app_origin != APK_ORIGIN_VENDOR) {
+    // Extended public libraries are NOT available to unbundled vendor apks, but
+    // they are to other apps, including those in system, system_ext, and
+    // product partitions. The reason is that when GSI is used, the system
+    // partition may get replaced, and then vendor apps may fail. It's fine for
+    // product apps, because that partition isn't mounted in GSI tests.
+    auto libs =
+        filter_public_libraries(target_sdk_version, uses_libraries, extended_public_libraries());
+    if (!libs.empty()) {
+      ALOGD("Extending system_exposed_libraries: %s", libs.c_str());
+      system_exposed_libraries = system_exposed_libraries + ':' + libs;
+    }
+  }
+
   // Create the app namespace
   NativeLoaderNamespace* parent_ns = FindParentNamespaceByClassLoader(env, class_loader);
   // Heuristic: the first classloader with non-empty library_path is assumed to
@@ -383,8 +406,7 @@
                                               product_public_libraries());
   if (!product_libs.empty()) {
     auto target_ns = system_ns;
-    if (is_product_vndk_version_defined()) {
-      // If ro.product.vndk.version is defined, product namespace provides the product libraries.
+    if (is_product_treblelized()) {
       target_ns = NativeLoaderNamespace::GetExportedNamespace(kProductNamespaceName, is_bridged);
     }
     if (target_ns.ok()) {
diff --git a/libnativeloader/native_loader_api_test.cpp b/libnativeloader/native_loader_api_test.cpp
new file mode 100644
index 0000000..78fb29f
--- /dev/null
+++ b/libnativeloader/native_loader_api_test.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#if defined(ART_TARGET_ANDROID)
+
+#include "gtest/gtest.h"
+#include "native_loader_test.h"
+#include "nativehelper/scoped_utf_chars.h"
+#include "nativeloader/native_loader.h"
+
+namespace android {
+namespace nativeloader {
+
+using ::testing::Return;
+using ::testing::StrEq;
+
+// Test the exported API in libnativeloader and libnativeloader_lazy. The
+// testing we can do here outside a full VM is limited, but this is only to
+// complement other tests and ensure coverage of the APIs that aren't in the
+// common call paths.
+
+class NativeLoaderLazyTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    mock = std::make_unique<testing::NiceMock<MockPlatform>>(false);
+    env = std::make_unique<JNIEnv>();
+    env->functions = CreateJNINativeInterface();
+  }
+
+  void TearDown() override {
+    // ResetNativeLoader isn't accessible through the lazy library, so we cannot
+    // reset libnativeloader internal state. Hence be sure to not reuse the same
+    // class loader/namespace names.
+    delete env->functions;
+    mock.reset();
+  }
+
+  void CallCreateClassLoaderNamespace(const char* class_loader) {
+    ON_CALL(*mock, JniObject_getParent(StrEq(class_loader))).WillByDefault(Return(nullptr));
+
+    jstring err = CreateClassLoaderNamespace(env.get(),
+                                             17,
+                                             env.get()->NewStringUTF(class_loader),
+                                             false,
+                                             env.get()->NewStringUTF("/data/app/foo/classes.dex"),
+                                             env.get()->NewStringUTF("/data/app/foo"),
+                                             /*permitted_path=*/nullptr,
+                                             /*uses_library_list=*/nullptr);
+    EXPECT_EQ(err, nullptr) << "Error is: " << std::string(ScopedUtfChars(env.get(), err).c_str());
+  }
+
+  std::unique_ptr<JNIEnv> env;
+};
+
+TEST_F(NativeLoaderLazyTest, CreateClassLoaderNamespace) {
+  CallCreateClassLoaderNamespace("my_classloader_1");
+  EXPECT_NE(FindNamespaceByClassLoader(env.get(), env.get()->NewStringUTF("my_classloader_1")),
+            nullptr);
+}
+
+TEST_F(NativeLoaderLazyTest, OpenNativeLibrary) {
+  bool needs_native_bridge;
+  char* errmsg = nullptr;
+  EXPECT_EQ(nullptr,
+            OpenNativeLibrary(env.get(),
+                              17,
+                              "libnotfound.so",
+                              env.get()->NewStringUTF("my_classloader"),
+                              /*caller_location=*/nullptr,
+                              /*library_path=*/nullptr,
+                              &needs_native_bridge,
+                              &errmsg));
+  EXPECT_NE(nullptr, errmsg);
+  NativeLoaderFreeErrorMessage(errmsg);
+}
+
+TEST_F(NativeLoaderLazyTest, CloseNativeLibrary) {
+  char* errmsg = nullptr;
+  EXPECT_FALSE(CloseNativeLibrary(nullptr, false, &errmsg));
+  EXPECT_NE(nullptr, errmsg);
+  NativeLoaderFreeErrorMessage(errmsg);
+}
+
+TEST_F(NativeLoaderLazyTest, OpenNativeLibraryInNamespace) {
+  CallCreateClassLoaderNamespace("my_classloader_2");
+  struct NativeLoaderNamespace* ns = FindNativeLoaderNamespaceByClassLoader(
+      env.get(), env.get()->NewStringUTF("my_classloader_2"));
+  ASSERT_NE(nullptr, ns);
+
+  bool needs_native_bridge;
+  char* errmsg = nullptr;
+  EXPECT_FALSE(OpenNativeLibraryInNamespace(ns, "libnotfound.so", &needs_native_bridge, &errmsg));
+  EXPECT_NE(nullptr, errmsg);
+  NativeLoaderFreeErrorMessage(errmsg);
+}
+
+TEST_F(NativeLoaderLazyTest, FindNamespaceByClassLoader) {
+  EXPECT_EQ(nullptr, FindNamespaceByClassLoader(env.get(), env.get()->NewStringUTF("namespace")));
+}
+
+TEST_F(NativeLoaderLazyTest, FindNativeLoaderNamespaceByClassLoader) {
+  EXPECT_EQ(
+      nullptr,
+      FindNativeLoaderNamespaceByClassLoader(env.get(), env.get()->NewStringUTF("namespace")));
+}
+
+TEST_F(NativeLoaderLazyTest, NativeLoaderFreeErrorMessage) {
+  NativeLoaderFreeErrorMessage(nullptr);
+}
+
+}  // namespace nativeloader
+}  // namespace android
+
+#endif  // defined(ART_TARGET_ANDROID)
diff --git a/libnativeloader/native_loader_lazy_test.cpp b/libnativeloader/native_loader_lazy_test.cpp
deleted file mode 100644
index b863c85..0000000
--- a/libnativeloader/native_loader_lazy_test.cpp
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-#if defined(ART_TARGET_ANDROID)
-
-#include <gtest/gtest.h>
-
-#include "native_loader_test.h"
-#include "nativehelper/scoped_utf_chars.h"
-#include "nativeloader/native_loader.h"
-
-namespace android {
-namespace nativeloader {
-
-using ::testing::StrEq;
-
-// Only need to test that the trivial lazy lib wrappers call through to the real
-// functions, but still have to mock things well enough to avoid null pointer
-// dereferences.
-
-class NativeLoaderLazyTest : public ::testing::Test {
- protected:
-  void SetUp() override {
-    mock = std::make_unique<testing::NiceMock<MockPlatform>>(false);
-    env = std::make_unique<JNIEnv>();
-    env->functions = CreateJNINativeInterface();
-  }
-
-  void TearDown() override {
-    // ResetNativeLoader isn't accessible through the lazy library, so we cannot
-    // reset libnativeloader internal state. Hence be sure to not reuse the same
-    // class loader/namespace names.
-    delete env->functions;
-    mock.reset();
-  }
-
-  void CallCreateClassLoaderNamespace(const char* class_loader) {
-    ON_CALL(*mock, JniObject_getParent(StrEq(class_loader))).WillByDefault(Return(nullptr));
-    EXPECT_CALL(*mock, mock_create_namespace)
-        .WillOnce(Return(TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE(class_loader))));
-    ON_CALL(*mock, mock_link_namespaces).WillByDefault(Return(true));
-
-    jstring err = CreateClassLoaderNamespace(env.get(),
-                                             17,
-                                             env.get()->NewStringUTF(class_loader),
-                                             false,
-                                             env.get()->NewStringUTF("/data/app/foo/classes.dex"),
-                                             env.get()->NewStringUTF("/data/app/foo"),
-                                             /*permitted_path=*/nullptr,
-                                             /*uses_library_list=*/nullptr);
-    EXPECT_EQ(err, nullptr) << "Error is: " << std::string(ScopedUtfChars(env.get(), err).c_str());
-  }
-
-  std::unique_ptr<JNIEnv> env;
-};
-
-TEST_F(NativeLoaderLazyTest, CreateClassLoaderNamespace) {
-  CallCreateClassLoaderNamespace("my_classloader_1");
-}
-
-TEST_F(NativeLoaderLazyTest, OpenNativeLibrary) {
-  bool needs_native_bridge;
-  char* errmsg = nullptr;
-  EXPECT_EQ(nullptr,
-            OpenNativeLibrary(env.get(),
-                              17,
-                              "libnotfound.so",
-                              env.get()->NewStringUTF("my_classloader"),
-                              /*caller_location=*/nullptr,
-                              /*library_path=*/nullptr,
-                              &needs_native_bridge,
-                              &errmsg));
-  EXPECT_NE(nullptr, errmsg);
-  NativeLoaderFreeErrorMessage(errmsg);
-}
-
-TEST_F(NativeLoaderLazyTest, CloseNativeLibrary) {
-  char* errmsg = nullptr;
-  EXPECT_FALSE(CloseNativeLibrary(nullptr, false, &errmsg));
-  EXPECT_NE(nullptr, errmsg);
-  NativeLoaderFreeErrorMessage(errmsg);
-}
-
-TEST_F(NativeLoaderLazyTest, OpenNativeLibraryInNamespace) {
-  CallCreateClassLoaderNamespace("my_classloader_2");
-  struct NativeLoaderNamespace* ns = FindNativeLoaderNamespaceByClassLoader(
-      env.get(), env.get()->NewStringUTF("my_classloader_2"));
-  ASSERT_NE(nullptr, ns);
-
-  bool needs_native_bridge;
-  char* errmsg = nullptr;
-  EXPECT_FALSE(OpenNativeLibraryInNamespace(ns, "libnotfound.so", &needs_native_bridge, &errmsg));
-  EXPECT_NE(nullptr, errmsg);
-  NativeLoaderFreeErrorMessage(errmsg);
-}
-
-TEST_F(NativeLoaderLazyTest, FindNamespaceByClassLoader) {
-  EXPECT_EQ(nullptr, FindNamespaceByClassLoader(env.get(), env.get()->NewStringUTF("namespace")));
-}
-
-TEST_F(NativeLoaderLazyTest, FindNativeLoaderNamespaceByClassLoader) {
-  EXPECT_EQ(
-      nullptr,
-      FindNativeLoaderNamespaceByClassLoader(env.get(), env.get()->NewStringUTF("namespace")));
-}
-
-TEST_F(NativeLoaderLazyTest, NativeLoaderFreeErrorMessage) {
-  NativeLoaderFreeErrorMessage(nullptr);
-}
-
-}  // namespace nativeloader
-}  // namespace android
-
-#endif  // defined(ART_TARGET_ANDROID)
diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp
index 1dc778a..72348ed 100644
--- a/libnativeloader/native_loader_test.cpp
+++ b/libnativeloader/native_loader_test.cpp
@@ -18,9 +18,9 @@
 
 #include "native_loader_test.h"
 
-#include <dlfcn.h>
-
+#include <android-base/properties.h>
 #include <android-base/strings.h>
+#include <dlfcn.h>
 #include <gtest/gtest.h>
 
 #include "nativehelper/scoped_utf_chars.h"
@@ -31,10 +31,10 @@
 namespace nativeloader {
 
 using ::testing::Eq;
-using ::testing::MatchesRegex;
 using ::testing::NotNull;
+using ::testing::StartsWith;
 using ::testing::StrEq;
-using internal::ConfigEntry;
+using internal::ConfigEntry;  // NOLINT - ConfigEntry is actually used
 using internal::ParseApexLibrariesConfig;
 using internal::ParseConfig;
 
@@ -69,7 +69,7 @@
   void SetExpectations() {
     std::vector<std::string> default_public_libs =
         android::base::Split(preloadable_public_libraries(), ":");
-    for (auto l : default_public_libs) {
+    for (const std::string& l : default_public_libs) {
       EXPECT_CALL(*mock,
                   mock_dlopen_ext(false, StrEq(l.c_str()), RTLD_NOW | RTLD_NODELETE, NotNull()))
           .WillOnce(Return(any_nonnull));
@@ -168,13 +168,16 @@
 
 /////////////////////////////////////////////////////////////////
 
-std::string default_public_and_extended_libraries() {
-  std::string public_libs = default_public_libraries();
-  std::string ext_libs = extended_public_libraries();
+std::string append_extended_libraries(const std::string& libs) {
+  const std::string& ext_libs = extended_public_libraries();
   if (!ext_libs.empty()) {
-    public_libs = public_libs + ":" + ext_libs;
+    return libs + ":" + ext_libs;
   }
-  return public_libs;
+  return libs;
+}
+
+std::string default_public_and_extended_libraries() {
+  return append_extended_libraries(default_public_libraries());
 }
 
 class NativeLoaderTest_Create : public NativeLoaderTest {
@@ -189,7 +192,7 @@
   std::string permitted_path = "/data/app/foo/" LIB_DIR;
 
   // expected output (.. for the default test inputs)
-  std::string expected_namespace_name = "classloader-namespace";
+  std::string expected_namespace_prefix = "clns";
   uint64_t expected_namespace_flags =
       ANDROID_NAMESPACE_TYPE_ISOLATED | ANDROID_NAMESPACE_TYPE_ALSO_USED_AS_ANONYMOUS;
   std::string expected_library_path = library_path;
@@ -225,7 +228,7 @@
     EXPECT_CALL(*mock, NativeBridgeInitialized()).Times(testing::AnyNumber());
 
     EXPECT_CALL(*mock, mock_create_namespace(
-                           Eq(IsBridged()), MatchesRegex(expected_namespace_name), nullptr,
+                           Eq(IsBridged()), StartsWith(expected_namespace_prefix + "-"), nullptr,
                            StrEq(expected_library_path), expected_namespace_flags,
                            StrEq(expected_permitted_path), NsEq(expected_parent_namespace.c_str())))
         .WillOnce(Return(TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE(dex_path.c_str()))));
@@ -320,7 +323,7 @@
   dex_path = "/system/app/foo/foo.apk";
   is_shared = true;
 
-  expected_namespace_name = "classloader-namespace-shared";
+  expected_namespace_prefix = "clns-shared";
   expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED;
   SetExpectations();
   RunTest();
@@ -330,7 +333,7 @@
   dex_path = "/vendor/app/foo/foo.apk";
   is_shared = true;
 
-  expected_namespace_name = "classloader-namespace-shared";
+  expected_namespace_prefix = "clns-shared";
   expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED;
   SetExpectations();
   RunTest();
@@ -340,12 +343,14 @@
   dex_path = "/vendor/app/foo/foo.apk";
   is_shared = false;
 
-  expected_namespace_name = "vendor-classloader-namespace";
+  expected_namespace_prefix = "vendor-clns";
   expected_library_path = expected_library_path + ":/vendor/" LIB_DIR;
   expected_permitted_path = expected_permitted_path + ":/vendor/" LIB_DIR;
   expected_shared_libs_to_platform_ns =
       default_public_libraries() + ":" + llndk_libraries_vendor();
-  expected_link_with_vndk_ns = !get_vndk_version(/*is_product_vndk=*/false).empty();
+  if (android::base::GetProperty("ro.vndk.version", "") != "") {
+    expected_link_with_vndk_ns = true;
+  }
   SetExpectations();
   RunTest();
 }
@@ -354,7 +359,7 @@
   dex_path = "/product/app/foo/foo.apk";
   is_shared = true;
 
-  expected_namespace_name = "classloader-namespace-shared";
+  expected_namespace_prefix = "clns-shared";
   expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED;
   SetExpectations();
   RunTest();
@@ -364,7 +369,7 @@
   dex_path = "/system/framework/services.jar:/apex/com.android.conscrypt/javalib/service-foo.jar";
   is_shared = true;
 
-  expected_namespace_name = "classloader-namespace-shared";
+  expected_namespace_prefix = "clns-shared";
   expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED;
   expected_link_with_conscrypt_ns = true;
   SetExpectations();
@@ -375,30 +380,19 @@
   dex_path = "/product/app/foo/foo.apk";
   is_shared = false;
 
-  if (is_product_vndk_version_defined()) {
-    expected_namespace_name = "(vendor|product)-classloader-namespace";
-    expected_library_path = expected_library_path + ":/product/" LIB_DIR ":/system/product/" LIB_DIR;
+  if (is_product_treblelized()) {
+    expected_namespace_prefix = "product-clns";
+    expected_library_path =
+        expected_library_path + ":/product/" LIB_DIR ":/system/product/" LIB_DIR;
     expected_permitted_path =
         expected_permitted_path + ":/product/" LIB_DIR ":/system/product/" LIB_DIR;
-    expected_link_with_vndk_product_ns = true;
-
-    // The handling of extended libraries for product apps changed in the
-    // M-2022-10 release of the ART module (https://r.android.com/2194871).
-    // Since this test is in CTS for T, we need to accept both new and old
-    // behaviour, i.e. with and without the extended public libraries appended
-    // at the end. Skip the EXPECT_CALL in
-    // NativeLoaderTest_Create::SetExpectations and create a more lenient
-    // variant of it here.
-    expected_link_with_platform_ns = false;
     expected_shared_libs_to_platform_ns =
-        default_public_libraries() + ":" + llndk_libraries_product();
-    EXPECT_CALL(*mock,
-                mock_link_namespaces(Eq(IsBridged()),
-                                     _,
-                                     NsEq("system"),
-                                     ::testing::StartsWith(expected_shared_libs_to_platform_ns)))
-        .WillOnce(Return(true));
+        append_extended_libraries(default_public_libraries() + ":" + llndk_libraries_product());
+    if (android::base::GetProperty("ro.product.vndk.version", "") != "") {
+      expected_link_with_vndk_product_ns = true;
+    }
   }
+
   SetExpectations();
   RunTest();
 }
@@ -429,7 +423,7 @@
   const std::string second_app_permitted_path = "/data/app/bar/" LIB_DIR;
   const std::string expected_second_app_permitted_path =
       std::string("/data:/mnt/expand:") + second_app_permitted_path;
-  const std::string expected_second_app_parent_namespace = "classloader-namespace";
+  const std::string expected_second_app_parent_namespace = "clns";
   // no ALSO_USED_AS_ANONYMOUS
   const uint64_t expected_second_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED;
 
@@ -442,7 +436,7 @@
   // namespace for the second app is created. Its parent is set to the namespace
   // of the first app.
   EXPECT_CALL(*mock, mock_create_namespace(
-                         Eq(IsBridged()), StrEq(expected_namespace_name), nullptr,
+                         Eq(IsBridged()), StartsWith(expected_namespace_prefix + "-"), nullptr,
                          StrEq(second_app_library_path), expected_second_namespace_flags,
                          StrEq(expected_second_app_permitted_path), NsEq(dex_path.c_str())))
       .WillOnce(Return(TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE(second_app_dex_path.c_str()))));
diff --git a/libnativeloader/native_loader_test.h b/libnativeloader/native_loader_test.h
index 5c51f00..30b2f84 100644
--- a/libnativeloader/native_loader_test.h
+++ b/libnativeloader/native_loader_test.h
@@ -42,7 +42,7 @@
   // Instead of having two set of mock APIs for the two, define only one set with an additional
   // argument 'bool bridged' to identify the context (i.e., called for libdl_android or
   // libnativebridge).
-  typedef char* mock_namespace_handle;
+  using mock_namespace_handle = char*;
   virtual bool mock_init_anonymous_namespace(bool bridged, const char* sonames,
                                              const char* search_paths) = 0;
   virtual mock_namespace_handle mock_create_namespace(
diff --git a/libnativeloader/open_system_library.cpp b/libnativeloader/open_system_library.cpp
deleted file mode 100644
index 63175aa..0000000
--- a/libnativeloader/open_system_library.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2014 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.
- */
-
-#define LOG_TAG "nativeloader_test"
-
-#include <dlfcn.h>
-#include <log/log.h>
-
-#ifdef ART_TARGET_ANDROID
-#include "nativeloader/dlext_namespaces.h"
-#endif
-
-namespace android {
-
-extern "C" {
-
-// TODO(b/268440756): Find a way to reuse it from libnativebridge.
-void* OpenSystemLibrary(const char* path, int flags) {
-#ifdef ART_TARGET_ANDROID
-  // The system namespace is called "default" for binaries in /system and
-  // "system" for those in the Runtime APEX. Try "system" first since
-  // "default" always exists.
-  // TODO(b/185587109): Get rid of this error prone logic.
-  android_namespace_t* system_ns = android_get_exported_namespace("system");
-  if (system_ns == nullptr) {
-    system_ns = android_get_exported_namespace("default");
-    const char* message = "Failed to get system namespace for loading %s";
-    LOG_ALWAYS_FATAL_IF(system_ns == nullptr, message, path);
-  }
-  const android_dlextinfo dlextinfo = {
-      .flags = ANDROID_DLEXT_USE_NAMESPACE,
-      .library_namespace = system_ns,
-  };
-  return android_dlopen_ext(path, flags, &dlextinfo);
-#else
-  return dlopen(path, flags);
-#endif
-}
-
-}  // extern "C"
-
-}  // namespace android
diff --git a/libnativeloader/public_libraries.cpp b/libnativeloader/public_libraries.cpp
index 433a909..87210c8 100644
--- a/libnativeloader/public_libraries.cpp
+++ b/libnativeloader/public_libraries.cpp
@@ -46,7 +46,6 @@
 using internal::ConfigEntry;
 using internal::ParseConfig;
 using internal::ParseApexLibrariesConfig;
-using std::literals::string_literals::operator""s;
 
 namespace {
 
@@ -56,6 +55,7 @@
 constexpr const char* kApexLibrariesConfigFile = "/linkerconfig/apex.libraries.config.txt";
 constexpr const char* kVendorPublicLibrariesFile = "/vendor/etc/public.libraries.txt";
 constexpr const char* kLlndkLibrariesFile = "/apex/com.android.vndk.v{}/etc/llndk.libraries.{}.txt";
+constexpr const char* kLlndkLibrariesNoVndkFile = "/system/etc/llndk.libraries.txt";
 constexpr const char* kVndkLibrariesFile = "/apex/com.android.vndk.v{}/etc/vndksp.libraries.{}.txt";
 
 
@@ -117,7 +117,7 @@
       if (android::base::ConsumePrefix(&fn, kExtendedPublicLibrariesFilePrefix) &&
           android::base::ConsumeSuffix(&fn, kExtendedPublicLibrariesFileSuffix)) {
         const std::string company_name(fn);
-        const std::string config_file_path = dirname + "/"s + filename;
+        const std::string config_file_path = std::string(dirname) + std::string("/") + filename;
         LOG_ALWAYS_FATAL_IF(
             company_name.empty(),
             "Error extracting company name from public native library list file path \"%s\"",
@@ -129,8 +129,11 @@
                   android::base::EndsWith(entry.soname, "." + company_name + ".so")) {
                 return true;
               } else {
-                return Errorf("Library name \"{}\" does not end with the company name {}.",
-                              entry.soname, company_name);
+                return Errorf(
+                    "Library name \"{}\" does not start with \"lib\" and/or "
+                    "does not end with the company name \"{}\".",
+                    entry.soname,
+                    company_name);
               }
             });
         if (ret.ok()) {
@@ -161,28 +164,36 @@
   }
 
   // If this is for preloading libs, don't remove the libs from APEXes.
-  if (for_preload) {
-    return android::base::Join(*sonames, ':');
+  if (!for_preload) {
+    // Remove the public libs provided by apexes because these libs are available
+    // from apex namespaces.
+    for (const auto& p : apex_public_libraries()) {
+      auto public_libs = base::Split(p.second, ":");
+      sonames->erase(std::remove_if(sonames->begin(),
+                                    sonames->end(),
+                                    [&public_libs](const std::string& v) {
+                                      return std::find(public_libs.begin(), public_libs.end(), v) !=
+                                             public_libs.end();
+                                    }),
+                     sonames->end());
+    }
   }
 
-  // Remove the public libs provided by apexes because these libs are available
-  // from apex namespaces.
-  for (const auto& p : apex_public_libraries()) {
-    auto public_libs = base::Split(p.second, ":");
-    sonames->erase(std::remove_if(sonames->begin(), sonames->end(), [&public_libs](const std::string& v) {
-      return std::find(public_libs.begin(), public_libs.end(), v) != public_libs.end();
-    }), sonames->end());
-  }
-  return android::base::Join(*sonames, ':');
+  std::string libs = android::base::Join(*sonames, ':');
+  ALOGD("InitDefaultPublicLibraries for_preload=%d: %s", for_preload, libs.c_str());
+  return libs;
 }
 
 static std::string InitVendorPublicLibraries() {
   // This file is optional, quietly ignore if the file does not exist.
   auto sonames = ReadConfig(kVendorPublicLibrariesFile, always_true);
   if (!sonames.ok()) {
+    ALOGI("InitVendorPublicLibraries skipped: %s", sonames.error().message().c_str());
     return "";
   }
-  return android::base::Join(*sonames, ':');
+  std::string libs = android::base::Join(*sonames, ':');
+  ALOGD("InitVendorPublicLibraries: %s", libs.c_str());
+  return libs;
 }
 
 // If ro.product.vndk.version is defined, /product/etc/public.libraries-<companyname>.txt contains
@@ -190,10 +201,12 @@
 // contains the extended public libraries that are loaded from the system namespace.
 static std::string InitProductPublicLibraries() {
   std::vector<std::string> sonames;
-  if (is_product_vndk_version_defined()) {
+  if (is_product_treblelized()) {
     ReadExtensionLibraries("/product/etc", &sonames);
   }
-  return android::base::Join(sonames, ':');
+  std::string libs = android::base::Join(sonames, ':');
+  ALOGD("InitProductPublicLibraries: %s", libs.c_str());
+  return libs;
 }
 
 // read /system/etc/public.libraries-<companyname>.txt,
@@ -205,44 +218,76 @@
   std::vector<std::string> sonames;
   ReadExtensionLibraries("/system/etc", &sonames);
   ReadExtensionLibraries("/system_ext/etc", &sonames);
-  if (!is_product_vndk_version_defined()) {
+  if (!is_product_treblelized()) {
     ReadExtensionLibraries("/product/etc", &sonames);
   }
-  return android::base::Join(sonames, ':');
+  std::string libs = android::base::Join(sonames, ':');
+  ALOGD("InitExtendedPublicLibraries: %s", libs.c_str());
+  return libs;
+}
+
+bool IsVendorVndkEnabled() {
+#if defined(ART_TARGET_ANDROID)
+  return android::base::GetProperty("ro.vndk.version", "") != "";
+#else
+  return true;
+#endif
+}
+
+bool IsProductVndkEnabled() {
+#if defined(ART_TARGET_ANDROID)
+  return android::base::GetProperty("ro.product.vndk.version", "") != "";
+#else
+  return true;
+#endif
 }
 
 static std::string InitLlndkLibrariesVendor() {
-  if (get_vndk_version(/*is_product_vndk=*/false).empty()) {
-    return "";
+  std::string config_file;
+  if (IsVendorVndkEnabled()) {
+    config_file = kLlndkLibrariesFile;
+    InsertVndkVersionStr(&config_file, false);
+  } else {
+    config_file = kLlndkLibrariesNoVndkFile;
   }
-  std::string config_file = kLlndkLibrariesFile;
-  InsertVndkVersionStr(&config_file, false);
   auto sonames = ReadConfig(config_file, always_true);
   if (!sonames.ok()) {
     LOG_ALWAYS_FATAL("%s: %s", config_file.c_str(), sonames.error().message().c_str());
     return "";
   }
-  return android::base::Join(*sonames, ':');
+  std::string libs = android::base::Join(*sonames, ':');
+  ALOGD("InitLlndkLibrariesVendor: %s", libs.c_str());
+  return libs;
 }
 
 static std::string InitLlndkLibrariesProduct() {
-  if (!is_product_vndk_version_defined()) {
+  if (!is_product_treblelized()) {
+    ALOGD("InitLlndkLibrariesProduct: Product is not treblelized");
     return "";
   }
-  std::string config_file = kLlndkLibrariesFile;
-  InsertVndkVersionStr(&config_file, true);
+  std::string config_file;
+  if (IsProductVndkEnabled()) {
+    config_file = kLlndkLibrariesFile;
+    InsertVndkVersionStr(&config_file, true);
+  } else {
+    config_file = kLlndkLibrariesNoVndkFile;
+  }
   auto sonames = ReadConfig(config_file, always_true);
   if (!sonames.ok()) {
     LOG_ALWAYS_FATAL("%s: %s", config_file.c_str(), sonames.error().message().c_str());
     return "";
   }
-  return android::base::Join(*sonames, ':');
+  std::string libs = android::base::Join(*sonames, ':');
+  ALOGD("InitLlndkLibrariesProduct: %s", libs.c_str());
+  return libs;
 }
 
 static std::string InitVndkspLibrariesVendor() {
-  if (get_vndk_version(/*is_product_vndk=*/false).empty()) {
+  if (!IsVendorVndkEnabled()) {
+    ALOGD("InitVndkspLibrariesVendor: VNDK is deprecated with vendor");
     return "";
   }
+
   std::string config_file = kVndkLibrariesFile;
   InsertVndkVersionStr(&config_file, false);
   auto sonames = ReadConfig(config_file, always_true);
@@ -250,11 +295,14 @@
     LOG_ALWAYS_FATAL("%s", sonames.error().message().c_str());
     return "";
   }
-  return android::base::Join(*sonames, ':');
+  std::string libs = android::base::Join(*sonames, ':');
+  ALOGD("InitVndkspLibrariesVendor: %s", libs.c_str());
+  return libs;
 }
 
 static std::string InitVndkspLibrariesProduct() {
-  if (!is_product_vndk_version_defined()) {
+  if (!IsProductVndkEnabled()) {
+    ALOGD("InitVndkspLibrariesProduct: VNDK is deprecated with product");
     return "";
   }
   std::string config_file = kVndkLibrariesFile;
@@ -264,20 +312,33 @@
     LOG_ALWAYS_FATAL("%s", sonames.error().message().c_str());
     return "";
   }
-  return android::base::Join(*sonames, ':');
+  std::string libs = android::base::Join(*sonames, ':');
+  ALOGD("InitVndkspLibrariesProduct: %s", libs.c_str());
+  return libs;
 }
 
 static std::map<std::string, std::string> InitApexLibraries(const std::string& tag) {
   std::string file_content;
   if (!base::ReadFileToString(kApexLibrariesConfigFile, &file_content)) {
     // config is optional
+    ALOGI("InitApexLibraries skipped: %s", strerror(errno));
     return {};
   }
-  auto config = ParseApexLibrariesConfig(file_content, tag);
+  Result<std::map<std::string, std::string>> config = ParseApexLibrariesConfig(file_content, tag);
   if (!config.ok()) {
     LOG_ALWAYS_FATAL("%s: %s", kApexLibrariesConfigFile, config.error().message().c_str());
     return {};
   }
+  ALOGD("InitApexLibraries:\n  %s",
+        [&config]() {
+          std::vector<std::string> lib_list;
+          lib_list.reserve(config->size());
+          for (std::pair<std::string, std::string> elem : *config) {
+            lib_list.emplace_back(elem.first + ": " + elem.second);
+          }
+          return android::base::Join(lib_list, "\n  ");
+        }()
+            .c_str());
   return *config;
 }
 
@@ -364,9 +425,14 @@
   return public_libraries;
 }
 
-bool is_product_vndk_version_defined() {
+bool is_product_treblelized() {
 #if defined(ART_TARGET_ANDROID)
-  return android::sysprop::VndkProperties::product_vndk_version().has_value();
+  // Product is not treblelized iff launching version is prior to R and
+  // ro.product.vndk.version is not defined
+  static bool product_treblelized =
+      !(android::base::GetIntProperty("ro.product.first_api_level", 0) < __ANDROID_API_R__ &&
+        !android::sysprop::VndkProperties::product_vndk_version().has_value());
+  return product_treblelized;
 #else
   return false;
 #endif
@@ -429,6 +495,12 @@
     if (entry.bitness == ONLY_64) continue;
 #endif
 
+    // TODO(b/206676167): Remove this check when renderscript is officially removed.
+#if defined(__riscv)
+    // skip renderscript lib on riscv target
+    if (entry.soname == "libRS.so") continue;
+#endif
+
     Result<bool> ret = filter_fn(entry);
     if (!ret.ok()) {
       return ret.error();
diff --git a/libnativeloader/public_libraries.h b/libnativeloader/public_libraries.h
index 6f5a13c..1830824 100644
--- a/libnativeloader/public_libraries.h
+++ b/libnativeloader/public_libraries.h
@@ -47,12 +47,14 @@
 // but provided by com.android.foo APEX.
 const std::map<std::string, std::string>& apex_public_libraries();
 
-// Returns true if libnativeloader is running on devices and the device has
-// ro.product.vndk.version property. It returns false for host.
-bool is_product_vndk_version_defined();
-
 std::string get_vndk_version(bool is_product_vndk);
 
+// Returnes true if libnativeloader is running on devices and the device has
+// treblelized product partition. It returns false for host.
+// TODO: Remove this function and assume it is always true once when Mainline does not support any
+// devices launched with Q or below.
+bool is_product_treblelized();
+
 // These are exported for testing
 namespace internal {
 
diff --git a/libnativeloader/test/Android.bp b/libnativeloader/test/Android.bp
index fb9ae0d..95b9b88 100644
--- a/libnativeloader/test/Android.bp
+++ b/libnativeloader/test/Android.bp
@@ -23,58 +23,179 @@
     default_applicable_licenses: ["art_license"],
 }
 
+// A native library that goes into /system or /system_ext and that depends on
+// a non-public library that is linked from the system namespace.
 cc_library {
-    name: "libfoo.oem1",
-    srcs: ["test.cpp"],
-    cflags: ["-DLIBNAME=\"libfoo.oem1.so\""],
-    shared_libs: [
-        "libbase",
+    name: "libsystem_testlib",
+    min_sdk_version: "31",
+    stl: "libc++_static",
+    shared_libs: ["liblog"],
+    // It's difficult to add a shared_lib dependency on a non-public library
+    // here, so it dlopens one instead.
+    srcs: ["libsystem_testlib.cc"],
+}
+
+// A native library that goes into /product.
+cc_library {
+    name: "libproduct_testlib",
+    min_sdk_version: "31",
+    stl: "none",
+    srcs: [],
+}
+
+// A native library that goes into /vendor.
+cc_library {
+    name: "libvendor_testlib",
+    min_sdk_version: "31",
+    stl: "none",
+    srcs: [],
+}
+
+// This app is just an intermediate container to be able to include the .so
+// library in the host test. It's not actually installed or started.
+android_test_helper_app {
+    name: "library_container_app",
+    defaults: ["art_module_source_build_java_defaults"],
+    min_sdk_version: "31",
+    manifest: "library_container_app_manifest.xml",
+    compile_multilib: "both",
+    jni_libs: [
+        "libsystem_testlib",
+        "libproduct_testlib",
+        "libvendor_testlib",
     ],
 }
 
-cc_library {
-    name: "libbar.oem1",
-    srcs: ["test.cpp"],
-    cflags: ["-DLIBNAME=\"libbar.oem1.so\""],
-    shared_libs: [
-        "libbase",
+java_library {
+    name: "loadlibrarytest_test_utils",
+    sdk_version: "31",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
     ],
+    srcs: ["src/android/test/lib/TestUtils.java"],
 }
 
-cc_library {
-    name: "libfoo.oem2",
-    srcs: ["test.cpp"],
-    cflags: ["-DLIBNAME=\"libfoo.oem2.so\""],
-    shared_libs: [
-        "libbase",
-    ],
+// Test fixture that represents a shared library in /system/framework.
+java_library {
+    name: "libnativeloader_system_shared_lib",
+    sdk_version: "31",
+    installable: true,
+    srcs: ["src/android/test/systemsharedlib/SystemSharedLib.java"],
 }
 
-cc_library {
-    name: "libbar.oem2",
-    srcs: ["test.cpp"],
-    cflags: ["-DLIBNAME=\"libbar.oem2.so\""],
-    shared_libs: [
-        "libbase",
-    ],
+// Test fixture that represents a shared library in /system_ext/framework.
+java_library {
+    name: "libnativeloader_system_ext_shared_lib",
+    sdk_version: "31",
+    installable: true,
+    srcs: ["src/android/test/systemextsharedlib/SystemExtSharedLib.java"],
 }
 
-cc_library {
-    name: "libfoo.product1",
-    srcs: ["test.cpp"],
-    cflags: ["-DLIBNAME=\"libfoo.product1.so\""],
+// Test fixture that represents a shared library in /product/framework.
+java_library {
+    name: "libnativeloader_product_shared_lib",
     product_specific: true,
-    shared_libs: [
-        "libbase",
+    sdk_version: "31",
+    installable: true,
+    srcs: ["src/android/test/productsharedlib/ProductSharedLib.java"],
+}
+
+// Test fixture that represents a shared library in /vendor/framework.
+java_library {
+    name: "libnativeloader_vendor_shared_lib",
+    vendor: true,
+    sdk_version: "31",
+    installable: true,
+    srcs: ["src/android/test/vendorsharedlib/VendorSharedLib.java"],
+}
+
+java_defaults {
+    name: "loadlibrarytest_app_defaults",
+    defaults: ["art_module_source_build_java_defaults"],
+    sdk_version: "31",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "loadlibrarytest_test_utils",
+    ],
+    libs: [
+        "libnativeloader_system_shared_lib",
+        "libnativeloader_system_ext_shared_lib",
+        "libnativeloader_product_shared_lib",
+        "libnativeloader_vendor_shared_lib",
     ],
 }
 
-cc_library {
-    name: "libbar.product1",
-    srcs: ["test.cpp"],
-    cflags: ["-DLIBNAME=\"libbar.product1.so\""],
+android_test_helper_app {
+    name: "loadlibrarytest_system_priv_app",
+    defaults: ["loadlibrarytest_app_defaults"],
+    manifest: "loadlibrarytest_system_priv_app_manifest.xml",
+    // /system/priv-app currently reuses the same test as /system/app.
+    srcs: ["src/android/test/app/SystemAppTest.java"],
+}
+
+android_test_helper_app {
+    name: "loadlibrarytest_system_app",
+    defaults: ["loadlibrarytest_app_defaults"],
+    manifest: "loadlibrarytest_system_app_manifest.xml",
+    srcs: ["src/android/test/app/SystemAppTest.java"],
+}
+
+android_test_helper_app {
+    name: "loadlibrarytest_system_ext_app",
+    defaults: ["loadlibrarytest_app_defaults"],
+    system_ext_specific: true,
+    manifest: "loadlibrarytest_system_ext_app_manifest.xml",
+    // /system_ext should behave the same as /system, so use the same test class there.
+    srcs: ["src/android/test/app/SystemAppTest.java"],
+}
+
+android_test_helper_app {
+    name: "loadlibrarytest_product_app",
+    defaults: ["loadlibrarytest_app_defaults"],
     product_specific: true,
-    shared_libs: [
-        "libbase",
+    manifest: "loadlibrarytest_product_app_manifest.xml",
+    srcs: ["src/android/test/app/ProductAppTest.java"],
+}
+
+android_test_helper_app {
+    name: "loadlibrarytest_vendor_app",
+    defaults: ["loadlibrarytest_app_defaults"],
+    vendor: true,
+    manifest: "loadlibrarytest_vendor_app_manifest.xml",
+    srcs: ["src/android/test/app/VendorAppTest.java"],
+}
+
+// A normal app installed in /data.
+android_test_helper_app {
+    name: "loadlibrarytest_data_app",
+    defaults: ["loadlibrarytest_app_defaults"],
+    manifest: "loadlibrarytest_data_app_manifest.xml",
+    srcs: ["src/android/test/app/DataAppTest.java"],
+}
+
+java_test_host {
+    name: "libnativeloader_e2e_tests",
+    defaults: ["art_module_source_build_java_defaults"],
+    srcs: ["src/android/test/hostside/*.java"],
+    libs: [
+        "compatibility-tradefed",
+        "tradefed",
     ],
+    data: [
+        ":library_container_app",
+        ":libnativeloader_system_shared_lib",
+        ":libnativeloader_system_ext_shared_lib",
+        ":libnativeloader_product_shared_lib",
+        ":libnativeloader_vendor_shared_lib",
+        ":loadlibrarytest_system_priv_app",
+        ":loadlibrarytest_system_app",
+        ":loadlibrarytest_system_ext_app",
+        ":loadlibrarytest_product_app",
+        ":loadlibrarytest_vendor_app",
+        ":loadlibrarytest_data_app",
+    ],
+    test_config: "libnativeloader_e2e_tests.xml",
+    test_suites: ["general-tests"],
 }
diff --git a/libnativeloader/test/Android.mk b/libnativeloader/test/Android.mk
deleted file mode 100644
index 95fa68a..0000000
--- a/libnativeloader/test/Android.mk
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# 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.
-#
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := public.libraries-oem1.txt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES:= $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := public.libraries-oem2.txt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES:= $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := public.libraries-product1.txt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES:= $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT_ETC)
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_PACKAGE_NAME := oemlibrarytest-system
-LOCAL_MODULE_TAGS := tests
-LOCAL_MANIFEST_FILE := system/AndroidManifest.xml
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SDK_VERSION := current
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_MODULE_PATH := $(TARGET_OUT_APPS)
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_PACKAGE_NAME := oemlibrarytest-vendor
-LOCAL_MODULE_TAGS := tests
-LOCAL_MANIFEST_FILE := vendor/AndroidManifest.xml
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SDK_VERSION := current
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_APPS)
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-include $(BUILD_PACKAGE)
diff --git a/libnativeloader/test/libnativeloader_e2e_tests.xml b/libnativeloader/test/libnativeloader_e2e_tests.xml
new file mode 100644
index 0000000..d4f6348
--- /dev/null
+++ b/libnativeloader/test/libnativeloader_e2e_tests.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<configuration description="Config for libnativeloader e2e test cases">
+    <option name="test-suite-tag" value="libnativeloader_e2e_tests" />
+    <option name="test-suite-tag" value="apct" />
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <!-- We push all files in LibnativeloaderTest.java, but use this
+             preparer to make partitions writable. That since remounting may
+             require a device reboot, and then it's important that the
+             `setenforce 0` from DisableSELinuxTargetPreparer occurs after that. -->
+        <option name="remount-system" value="true" />
+        <option name="remount-vendor" value="true" />
+    </target_preparer>
+
+    <!-- Vendor native libraries aren't accessible by any apps by sepolicy
+         rules. For that they need to be labelled same_process_hal_file in a
+         vendor specific file_contexts file (see
+         https://source.android.com/docs/core/permissions/namespaces_libraries#adding-additional-native-libraries).
+         To avoid setting that up to test loading libvendor_private*.so, disable
+         sepolicy checks while running the tests. It's libnativeloader logic we
+         want to test here. -->
+    <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"/>
+
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="jar" value="libnativeloader_e2e_tests.jar" />
+    </test>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/libnativeloader/test/library_container_app_manifest.xml b/libnativeloader/test/library_container_app_manifest.xml
new file mode 100644
index 0000000..20030de
--- /dev/null
+++ b/libnativeloader/test/library_container_app_manifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.test.app.container">
+</manifest>
diff --git a/libnativeloader/test/libsystem_testlib.cc b/libnativeloader/test/libsystem_testlib.cc
new file mode 100644
index 0000000..97d3223
--- /dev/null
+++ b/libnativeloader/test/libsystem_testlib.cc
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <dlfcn.h>
+
+#include "log/log.h"
+
+static void __attribute__((constructor)) ctor() {
+  // Load a library that should be available to system libraries through a
+  // linked namespace (i.e. is not directly in /system/${LIB}), and that is not
+  // in public.libraries.txt. We use a real one to avoid having to set up an
+  // APEX test fixture and rerun linkerconfig.
+  void* h = dlopen("libandroidicu.so", RTLD_NOW);
+  if (h == nullptr) {
+    LOG_ALWAYS_FATAL("Failed to load dependency: %s", dlerror());
+  }
+  dlclose(h);
+}
diff --git a/libnativeloader/test/loadlibrarytest_data_app_manifest.xml b/libnativeloader/test/loadlibrarytest_data_app_manifest.xml
new file mode 100644
index 0000000..4be78f8
--- /dev/null
+++ b/libnativeloader/test/loadlibrarytest_data_app_manifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.test.app.data">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.test.app.data" />
+    <application>
+        <uses-library android:required="false" android:name="android.test.systemsharedlib" />
+        <uses-library android:required="false" android:name="android.test.systemextsharedlib" />
+        <uses-library android:required="false" android:name="android.test.productsharedlib" />
+        <uses-library android:required="false" android:name="android.test.vendorsharedlib" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub.oem1.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub.oem2.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub1.oem1.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub2.oem1.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub3.oem1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub1.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub2.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub3.product1.so" />
+    </application>
+</manifest>
+
diff --git a/libnativeloader/test/loadlibrarytest_product_app_manifest.xml b/libnativeloader/test/loadlibrarytest_product_app_manifest.xml
new file mode 100644
index 0000000..9061c39
--- /dev/null
+++ b/libnativeloader/test/loadlibrarytest_product_app_manifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.test.app.product">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.test.app.product" />
+    <application>
+        <uses-library android:required="false" android:name="android.test.systemsharedlib" />
+        <uses-library android:required="false" android:name="android.test.systemextsharedlib" />
+        <uses-library android:required="false" android:name="android.test.productsharedlib" />
+        <uses-library android:required="false" android:name="android.test.vendorsharedlib" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub.oem1.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub.oem2.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub1.oem1.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub2.oem1.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub3.oem1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub1.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub2.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub3.product1.so" />
+    </application>
+</manifest>
+
diff --git a/libnativeloader/test/loadlibrarytest_system_app_manifest.xml b/libnativeloader/test/loadlibrarytest_system_app_manifest.xml
new file mode 100644
index 0000000..7ee7532
--- /dev/null
+++ b/libnativeloader/test/loadlibrarytest_system_app_manifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.test.app.system">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.test.app.system" />
+    <application>
+        <uses-library android:required="false" android:name="android.test.systemsharedlib" />
+        <uses-library android:required="false" android:name="android.test.systemextsharedlib" />
+        <uses-library android:required="false" android:name="android.test.productsharedlib" />
+        <uses-library android:required="false" android:name="android.test.vendorsharedlib" />
+        <!-- System apps get a shared classloader namespace, so they don't need
+             uses-native-library entries for anything in /system or /system_ext. -->
+        <uses-native-library android:required="false" android:name="libproduct_extpub.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub1.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub2.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub3.product1.so" />
+    </application>
+</manifest>
+
diff --git a/libnativeloader/test/loadlibrarytest_system_ext_app_manifest.xml b/libnativeloader/test/loadlibrarytest_system_ext_app_manifest.xml
new file mode 100644
index 0000000..a2c1a2c
--- /dev/null
+++ b/libnativeloader/test/loadlibrarytest_system_ext_app_manifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.test.app.system_ext">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.test.app.system_ext" />
+    <application>
+        <uses-library android:required="false" android:name="android.test.systemsharedlib" />
+        <uses-library android:required="false" android:name="android.test.systemextsharedlib" />
+        <uses-library android:required="false" android:name="android.test.productsharedlib" />
+        <uses-library android:required="false" android:name="android.test.vendorsharedlib" />
+        <!-- System apps get a shared classloader namespace, so they don't need
+             uses-native-library entries for anything in /system or /system_ext. -->
+        <uses-native-library android:required="false" android:name="libproduct_extpub.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub1.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub2.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub3.product1.so" />
+    </application>
+</manifest>
+
diff --git a/libnativeloader/test/loadlibrarytest_system_priv_app_manifest.xml b/libnativeloader/test/loadlibrarytest_system_priv_app_manifest.xml
new file mode 100644
index 0000000..87a30ce
--- /dev/null
+++ b/libnativeloader/test/loadlibrarytest_system_priv_app_manifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.test.app.system_priv">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.test.app.system_priv" />
+    <application>
+        <uses-library android:required="false" android:name="android.test.systemsharedlib" />
+        <uses-library android:required="false" android:name="android.test.systemextsharedlib" />
+        <uses-library android:required="false" android:name="android.test.productsharedlib" />
+        <uses-library android:required="false" android:name="android.test.vendorsharedlib" />
+        <!-- System apps get a shared classloader namespace, so they don't need
+             uses-native-library entries for anything in /system or /system_ext. -->
+        <uses-native-library android:required="false" android:name="libproduct_extpub.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub1.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub2.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub3.product1.so" />
+    </application>
+</manifest>
+
diff --git a/libnativeloader/test/loadlibrarytest_vendor_app_manifest.xml b/libnativeloader/test/loadlibrarytest_vendor_app_manifest.xml
new file mode 100644
index 0000000..b3434fb
--- /dev/null
+++ b/libnativeloader/test/loadlibrarytest_vendor_app_manifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.test.app.vendor">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.test.app.vendor" />
+    <application>
+        <uses-library android:required="false" android:name="android.test.systemsharedlib" />
+        <uses-library android:required="false" android:name="android.test.systemextsharedlib" />
+        <uses-library android:required="false" android:name="android.test.productsharedlib" />
+        <uses-library android:required="false" android:name="android.test.vendorsharedlib" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub.oem1.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub.oem2.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub1.oem1.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub2.oem1.so" />
+        <uses-native-library android:required="false" android:name="libsystem_extpub3.oem1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub1.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub2.product1.so" />
+        <uses-native-library android:required="false" android:name="libproduct_extpub3.product1.so" />
+    </application>
+</manifest>
+
diff --git a/libnativeloader/test/public.libraries-oem1.txt b/libnativeloader/test/public.libraries-oem1.txt
deleted file mode 100644
index f9433e2..0000000
--- a/libnativeloader/test/public.libraries-oem1.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-libfoo.oem1.so
-libbar.oem1.so
diff --git a/libnativeloader/test/public.libraries-oem2.txt b/libnativeloader/test/public.libraries-oem2.txt
deleted file mode 100644
index de6bdb0..0000000
--- a/libnativeloader/test/public.libraries-oem2.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-libfoo.oem2.so
-libbar.oem2.so
diff --git a/libnativeloader/test/public.libraries-product1.txt b/libnativeloader/test/public.libraries-product1.txt
deleted file mode 100644
index 358154c..0000000
--- a/libnativeloader/test/public.libraries-product1.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-libfoo.product1.so
-libbar.product1.so
diff --git a/libnativeloader/test/runtest.sh b/libnativeloader/test/runtest.sh
deleted file mode 100755
index 40beb5b..0000000
--- a/libnativeloader/test/runtest.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-adb root
-adb remount
-adb sync
-adb shell stop
-adb shell start
-sleep 5 # wait until device reboots
-adb logcat -c;
-adb shell am start -n android.test.app.system/android.test.app.TestActivity
-adb shell am start -n android.test.app.vendor/android.test.app.TestActivity
-adb logcat | grep android.test.app
diff --git a/libnativeloader/test/src/android/test/app/DataAppTest.java b/libnativeloader/test/src/android/test/app/DataAppTest.java
new file mode 100644
index 0000000..9403494
--- /dev/null
+++ b/libnativeloader/test/src/android/test/app/DataAppTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.test.app;
+
+import android.test.lib.TestUtils;
+import android.test.productsharedlib.ProductSharedLib;
+import android.test.systemextsharedlib.SystemExtSharedLib;
+import android.test.systemsharedlib.SystemSharedLib;
+import android.test.vendorsharedlib.VendorSharedLib;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class DataAppTest {
+    @Test
+    public void testLoadExtendedPublicLibraries() {
+        System.loadLibrary("system_extpub.oem1");
+        System.loadLibrary("system_extpub.oem2");
+        System.loadLibrary("system_extpub1.oem1");
+        TestUtils.assertLinkerNamespaceError( // Missing <uses-native-library>.
+                () -> System.loadLibrary("system_extpub_nouses.oem2"));
+        System.loadLibrary("product_extpub.product1");
+        System.loadLibrary("product_extpub1.product1");
+    }
+
+    @Test
+    public void testLoadPrivateLibraries() {
+        TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("system_private1"));
+        TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("systemext_private1"));
+        TestUtils.assertLibraryNotFound(() -> System.loadLibrary("product_private1"));
+        TestUtils.assertLibraryNotFound(() -> System.loadLibrary("vendor_private1"));
+    }
+
+    @Test
+    public void testLoadExtendedPublicLibrariesViaSystemSharedLib() {
+        SystemSharedLib.loadLibrary("system_extpub2.oem1");
+        SystemSharedLib.loadLibrary("product_extpub2.product1");
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaSystemSharedLib() {
+        // TODO(b/237577392): Loading a private native system library via a shared system library
+        // ought to work.
+        // SystemSharedLib.loadLibrary("system_private2");
+        // SystemSharedLib.loadLibrary("systemext_private2");
+        TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("product_private2"));
+        TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("vendor_private2"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaSystemExtSharedLib() {
+        // TODO(b/237577392): Loading a private native system library via a shared system library
+        // ought to work.
+        // SystemExtSharedLib.loadLibrary("system_private3");
+        // SystemExtSharedLib.loadLibrary("systemext_private3");
+        TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("product_private3"));
+        TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("vendor_private3"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaProductSharedLib() {
+        TestUtils.assertLinkerNamespaceError(() -> ProductSharedLib.loadLibrary("system_private4"));
+        TestUtils.assertLinkerNamespaceError(
+                () -> ProductSharedLib.loadLibrary("systemext_private4"));
+        ProductSharedLib.loadLibrary("product_private4");
+        TestUtils.assertLibraryNotFound(() -> ProductSharedLib.loadLibrary("vendor_private4"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaVendorSharedLib() {
+        TestUtils.assertLinkerNamespaceError(() -> VendorSharedLib.loadLibrary("system_private5"));
+        TestUtils.assertLinkerNamespaceError(
+                () -> VendorSharedLib.loadLibrary("systemext_private5"));
+        TestUtils.assertLibraryNotFound(() -> VendorSharedLib.loadLibrary("product_private5"));
+        VendorSharedLib.loadLibrary("vendor_private5");
+    }
+
+    @Test
+    public void testLoadExtendedPublicLibrariesWithAbsolutePaths() {
+        System.load(TestUtils.libPath("/system", "system_extpub3.oem1"));
+        System.load(TestUtils.libPath("/product", "product_extpub3.product1"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesWithAbsolutePaths() {
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system", "system_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system_ext", "systemext_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/product", "product_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/vendor", "vendor_private6")));
+    }
+}
diff --git a/libnativeloader/test/src/android/test/app/ProductAppTest.java b/libnativeloader/test/src/android/test/app/ProductAppTest.java
new file mode 100644
index 0000000..7ec817b
--- /dev/null
+++ b/libnativeloader/test/src/android/test/app/ProductAppTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.test.app;
+
+import android.test.lib.TestUtils;
+import android.test.productsharedlib.ProductSharedLib;
+import android.test.systemextsharedlib.SystemExtSharedLib;
+import android.test.systemsharedlib.SystemSharedLib;
+import android.test.vendorsharedlib.VendorSharedLib;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ProductAppTest {
+    @Test
+    public void testLoadExtendedPublicLibraries() {
+        System.loadLibrary("system_extpub.oem1");
+        System.loadLibrary("system_extpub.oem2");
+        System.loadLibrary("system_extpub1.oem1");
+        TestUtils.assertLinkerNamespaceError( // Missing <uses-native-library>.
+                () -> System.loadLibrary("system_extpub_nouses.oem2"));
+        System.loadLibrary("product_extpub.product1");
+        System.loadLibrary("product_extpub1.product1");
+    }
+
+    @Test
+    public void testLoadPrivateLibraries() {
+        TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("system_private1"));
+        TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("systemext_private1"));
+        System.loadLibrary("product_private1");
+        TestUtils.assertLibraryNotFound(() -> System.loadLibrary("vendor_private1"));
+    }
+
+    @Test
+    public void testLoadExtendedPublicLibrariesViaSystemSharedLib() {
+        SystemSharedLib.loadLibrary("system_extpub2.oem1");
+        SystemSharedLib.loadLibrary("product_extpub2.product1");
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaSystemSharedLib() {
+        // TODO(b/237577392): Loading a private native system library via a shared system library
+        // ought to work.
+        // SystemSharedLib.loadLibrary("system_private2");
+        // SystemSharedLib.loadLibrary("systemext_private2");
+        TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("product_private2"));
+        TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("vendor_private2"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaSystemExtSharedLib() {
+        // TODO(b/237577392): Loading a private native system library via a shared system library
+        // ought to work.
+        // SystemExtSharedLib.loadLibrary("system_private3");
+        // SystemExtSharedLib.loadLibrary("systemext_private3");
+        TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("product_private3"));
+        TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("vendor_private3"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaProductSharedLib() {
+        TestUtils.assertLinkerNamespaceError(() -> ProductSharedLib.loadLibrary("system_private4"));
+        TestUtils.assertLinkerNamespaceError(
+                () -> ProductSharedLib.loadLibrary("systemext_private4"));
+        ProductSharedLib.loadLibrary("product_private4");
+        TestUtils.assertLibraryNotFound(() -> ProductSharedLib.loadLibrary("vendor_private4"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaVendorSharedLib() {
+        TestUtils.assertLinkerNamespaceError(() -> VendorSharedLib.loadLibrary("system_private5"));
+        TestUtils.assertLinkerNamespaceError(
+                () -> VendorSharedLib.loadLibrary("systemext_private5"));
+        TestUtils.assertLibraryNotFound(() -> VendorSharedLib.loadLibrary("product_private5"));
+        VendorSharedLib.loadLibrary("vendor_private5");
+    }
+
+    @Test
+    public void testLoadExtendedPublicLibrariesWithAbsolutePaths() {
+        System.load(TestUtils.libPath("/system", "system_extpub3.oem1"));
+        System.load(TestUtils.libPath("/product", "product_extpub3.product1"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesWithAbsolutePaths() {
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system", "system_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system_ext", "systemext_private6")));
+        System.load(TestUtils.libPath("/product", "product_private6"));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/vendor", "vendor_private6")));
+    }
+}
diff --git a/libnativeloader/test/src/android/test/app/SystemAppTest.java b/libnativeloader/test/src/android/test/app/SystemAppTest.java
new file mode 100644
index 0000000..cabdfb7
--- /dev/null
+++ b/libnativeloader/test/src/android/test/app/SystemAppTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.test.app;
+
+import android.test.lib.TestUtils;
+import android.test.productsharedlib.ProductSharedLib;
+import android.test.systemextsharedlib.SystemExtSharedLib;
+import android.test.systemsharedlib.SystemSharedLib;
+import android.test.vendorsharedlib.VendorSharedLib;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+// These tests are run from /system/app, /system/priv-app, and /system_ext/app.
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SystemAppTest {
+    @Test
+    public void testLoadExtendedPublicLibraries() {
+        System.loadLibrary("system_extpub.oem1");
+        System.loadLibrary("system_extpub.oem2");
+        System.loadLibrary("system_extpub1.oem1");
+        // Missing <uses-native-library> not relevant for system apps, which have shared classloader
+        // namespaces.
+        System.loadLibrary("system_extpub_nouses.oem2");
+        System.loadLibrary("product_extpub.product1");
+        System.loadLibrary("product_extpub1.product1");
+    }
+
+    @Test
+    public void testLoadPrivateLibraries() {
+        System.loadLibrary("system_private1");
+        System.loadLibrary("systemext_private1");
+        TestUtils.assertLibraryNotFound(() -> System.loadLibrary("product_private1"));
+        TestUtils.assertLibraryNotFound(() -> System.loadLibrary("vendor_private1"));
+    }
+
+    @Test
+    public void testLoadExtendedPublicLibrariesViaSystemSharedLib() {
+        SystemSharedLib.loadLibrary("system_extpub2.oem1");
+        SystemSharedLib.loadLibrary("product_extpub2.product1");
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaSystemSharedLib() {
+        SystemSharedLib.loadLibrary("system_private2");
+        SystemSharedLib.loadLibrary("systemext_private2");
+        TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("product_private2"));
+        TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("vendor_private2"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaSystemExtSharedLib() {
+        SystemExtSharedLib.loadLibrary("system_private3");
+        SystemExtSharedLib.loadLibrary("systemext_private3");
+        TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("product_private3"));
+        TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("vendor_private3"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaProductSharedLib() {
+        ProductSharedLib.loadLibrary("system_private4");
+        ProductSharedLib.loadLibrary("systemext_private4");
+        TestUtils.assertLibraryNotFound(() -> ProductSharedLib.loadLibrary("product_private4"));
+        TestUtils.assertLibraryNotFound(() -> ProductSharedLib.loadLibrary("vendor_private4"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaVendorSharedLib() {
+        VendorSharedLib.loadLibrary("system_private5");
+        VendorSharedLib.loadLibrary("systemext_private5");
+        TestUtils.assertLibraryNotFound(() -> VendorSharedLib.loadLibrary("product_private5"));
+        TestUtils.assertLibraryNotFound(() -> VendorSharedLib.loadLibrary("vendor_private5"));
+    }
+
+    @Test
+    public void testLoadExtendedPublicLibrariesWithAbsolutePaths() {
+        System.load(TestUtils.libPath("/system", "system_extpub3.oem1"));
+        System.load(TestUtils.libPath("/product", "product_extpub3.product1"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesWithAbsolutePaths() {
+        System.load(TestUtils.libPath("/system", "system_private6"));
+        System.load(TestUtils.libPath("/system_ext", "systemext_private6"));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/product", "product_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/vendor", "vendor_private6")));
+    }
+}
diff --git a/libnativeloader/test/src/android/test/app/TestActivity.java b/libnativeloader/test/src/android/test/app/TestActivity.java
deleted file mode 100644
index a7a455d..0000000
--- a/libnativeloader/test/src/android/test/app/TestActivity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-package android.test.app;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestActivity extends Activity {
-
-    @Override
-    public void onCreate(Bundle icicle) {
-         super.onCreate(icicle);
-         tryLoadingLib("foo.oem1");
-         tryLoadingLib("bar.oem1");
-         tryLoadingLib("foo.oem2");
-         tryLoadingLib("bar.oem2");
-         tryLoadingLib("foo.product1");
-         tryLoadingLib("bar.product1");
-    }
-
-    private void tryLoadingLib(String name) {
-        try {
-            System.loadLibrary(name);
-            Log.d(getPackageName(), "library " + name + " is successfully loaded");
-        } catch (UnsatisfiedLinkError e) {
-            Log.d(getPackageName(), "failed to load libarary " + name, e);
-        }
-    }
-}
diff --git a/libnativeloader/test/src/android/test/app/VendorAppTest.java b/libnativeloader/test/src/android/test/app/VendorAppTest.java
new file mode 100644
index 0000000..10d0ea0
--- /dev/null
+++ b/libnativeloader/test/src/android/test/app/VendorAppTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.test.app;
+
+import android.test.lib.TestUtils;
+import android.test.productsharedlib.ProductSharedLib;
+import android.test.systemextsharedlib.SystemExtSharedLib;
+import android.test.systemsharedlib.SystemSharedLib;
+import android.test.vendorsharedlib.VendorSharedLib;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class VendorAppTest {
+    @Test
+    public void testLoadExtendedPublicLibraries() {
+        TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("system_extpub.oem1"));
+        TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("system_extpub.oem2"));
+        TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("system_extpub1.oem1"));
+        TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("system_extpub_nouses.oem2"));
+        System.loadLibrary("product_extpub.product1");
+        System.loadLibrary("product_extpub1.product1");
+    }
+
+    @Test
+    public void testLoadPrivateLibraries() {
+        TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("system_private1"));
+        TestUtils.assertLinkerNamespaceError(() -> System.loadLibrary("systemext_private1"));
+        TestUtils.assertLibraryNotFound(() -> System.loadLibrary("product_private1"));
+        System.loadLibrary("vendor_private1");
+    }
+
+    @Test
+    public void testLoadExtendedPublicLibrariesViaSystemSharedLib() {
+        SystemSharedLib.loadLibrary("system_extpub2.oem1");
+        SystemSharedLib.loadLibrary("product_extpub2.product1");
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaSystemSharedLib() {
+        // TODO(b/237577392): Loading a private native system library via a shared system library
+        // ought to work.
+        // SystemSharedLib.loadLibrary("system_private2");
+        // SystemSharedLib.loadLibrary("systemext_private2");
+        TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("product_private2"));
+        TestUtils.assertLibraryNotFound(() -> SystemSharedLib.loadLibrary("vendor_private2"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaSystemExtSharedLib() {
+        // TODO(b/237577392): Loading a private native system library via a shared system library
+        // ought to work.
+        // SystemExtSharedLib.loadLibrary("system_private3");
+        // SystemExtSharedLib.loadLibrary("systemext_private3");
+        TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("product_private3"));
+        TestUtils.assertLibraryNotFound(() -> SystemExtSharedLib.loadLibrary("vendor_private3"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaProductSharedLib() {
+        TestUtils.assertLinkerNamespaceError(() -> ProductSharedLib.loadLibrary("system_private4"));
+        TestUtils.assertLinkerNamespaceError(
+                () -> ProductSharedLib.loadLibrary("systemext_private4"));
+        ProductSharedLib.loadLibrary("product_private4");
+        TestUtils.assertLibraryNotFound(() -> ProductSharedLib.loadLibrary("vendor_private4"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesViaVendorSharedLib() {
+        TestUtils.assertLinkerNamespaceError(() -> VendorSharedLib.loadLibrary("system_private5"));
+        TestUtils.assertLinkerNamespaceError(
+                () -> VendorSharedLib.loadLibrary("systemext_private5"));
+        TestUtils.assertLibraryNotFound(() -> VendorSharedLib.loadLibrary("product_private5"));
+        VendorSharedLib.loadLibrary("vendor_private5");
+    }
+
+    @Test
+    public void testLoadExtendedPublicLibrariesWithAbsolutePaths() {
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system", "system_extpub3.oem1")));
+        System.load(TestUtils.libPath("/product", "product_extpub3.product1"));
+    }
+
+    @Test
+    public void testLoadPrivateLibrariesWithAbsolutePaths() {
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system", "system_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system_ext", "systemext_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/product", "product_private6")));
+        System.load(TestUtils.libPath("/vendor", "vendor_private6"));
+    }
+}
diff --git a/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java b/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java
new file mode 100644
index 0000000..55a6dd2
--- /dev/null
+++ b/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.test.hostside;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.CommandResult;
+
+import com.google.common.io.ByteStreams;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Test libnativeloader behavior for apps and libs in various partitions by overlaying them over
+ * the system partitions. Requires root.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class LibnativeloaderTest extends BaseHostJUnit4Test {
+    private static final String TAG = "LibnativeloaderTest";
+    private static final String CLEANUP_PATHS_KEY = TAG + ":CLEANUP_PATHS";
+    private static final String LOG_FILE_NAME = "TestActivity.log";
+
+    @BeforeClassWithInfo
+    public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
+        DeviceContext ctx = new DeviceContext(testInfo);
+
+        // A soft reboot is slow, so do setup for all tests and reboot once.
+
+        File libContainerApk = ctx.mBuildHelper.getTestFile("library_container_app.apk");
+        try (ZipFile libApk = new ZipFile(libContainerApk)) {
+            ctx.pushExtendedPublicSystemOemLibs(libApk);
+            ctx.pushExtendedPublicProductLibs(libApk);
+            ctx.pushPrivateLibs(libApk);
+        }
+        ctx.pushSharedLib(
+                "/system", "android.test.systemsharedlib", "libnativeloader_system_shared_lib.jar");
+        ctx.pushSharedLib("/system_ext", "android.test.systemextsharedlib",
+                "libnativeloader_system_ext_shared_lib.jar");
+        ctx.pushSharedLib("/product", "android.test.productsharedlib",
+                "libnativeloader_product_shared_lib.jar");
+        ctx.pushSharedLib(
+                "/vendor", "android.test.vendorsharedlib", "libnativeloader_vendor_shared_lib.jar");
+
+        // "Install" apps in various partitions through plain adb push followed by a soft reboot. We
+        // need them in these locations to test library loading restrictions, so for all except
+        // loadlibrarytest_data_app we cannot use ITestDevice.installPackage for it since it only
+        // installs in /data.
+
+        // For testSystemPrivApp
+        ctx.pushApk("loadlibrarytest_system_priv_app", "/system/priv-app");
+
+        // For testSystemApp
+        ctx.pushApk("loadlibrarytest_system_app", "/system/app");
+
+        // For testSystemExtApp
+        ctx.pushApk("loadlibrarytest_system_ext_app", "/system_ext/app");
+
+        // For testProductApp
+        ctx.pushApk("loadlibrarytest_product_app", "/product/app");
+
+        // For testVendorApp
+        ctx.pushApk("loadlibrarytest_vendor_app", "/vendor/app");
+
+        ctx.softReboot();
+
+        // For testDataApp. Install this the normal way after the system server restart.
+        ctx.installPackage("loadlibrarytest_data_app");
+
+        testInfo.properties().put(CLEANUP_PATHS_KEY, ctx.mCleanup.getPathList());
+    }
+
+    @AfterClassWithInfo
+    public static void afterClassWithDevice(TestInformation testInfo) throws Exception {
+        DeviceContext ctx = new DeviceContext(testInfo);
+
+        // Uninstall loadlibrarytest_data_app.
+        ctx.mDevice.uninstallPackage("android.test.app.data");
+
+        String cleanupPathList = testInfo.properties().get(CLEANUP_PATHS_KEY);
+        CleanupPaths cleanup = new CleanupPaths(ctx.mDevice, cleanupPathList);
+        cleanup.cleanup();
+    }
+
+    @Test
+    public void testSystemPrivApp() throws Exception {
+        // There's currently no difference in the tests between /system/priv-app and /system/app, so
+        // let's reuse the same one.
+        runTests("android.test.app.system_priv", "android.test.app.SystemAppTest");
+    }
+
+    @Test
+    public void testSystemApp() throws Exception {
+        runTests("android.test.app.system", "android.test.app.SystemAppTest");
+    }
+
+    @Test
+    public void testSystemExtApp() throws Exception {
+        // /system_ext should behave the same as /system, so run the same test class there.
+        runTests("android.test.app.system_ext", "android.test.app.SystemAppTest");
+    }
+
+    @Test
+    public void testProductApp() throws Exception {
+        runTests("android.test.app.product", "android.test.app.ProductAppTest");
+    }
+
+    @Test
+    public void testVendorApp() throws Exception {
+        runTests("android.test.app.vendor", "android.test.app.VendorAppTest");
+    }
+
+    @Test
+    public void testDataApp() throws Exception {
+        runTests("android.test.app.data", "android.test.app.DataAppTest");
+    }
+
+    private void runTests(String pkgName, String testClassName) throws Exception {
+        DeviceContext ctx = new DeviceContext(getTestInformation());
+        var options = new DeviceTestRunOptions(pkgName)
+                              .setTestClassName(testClassName)
+                              .addInstrumentationArg("libDirName", ctx.libDirName());
+        runDeviceTests(options);
+    }
+
+    // Utility class that keeps track of a set of paths the need to be deleted after testing.
+    private static class CleanupPaths {
+        private ITestDevice mDevice;
+        private List<String> mCleanupPaths;
+
+        CleanupPaths(ITestDevice device) {
+            mDevice = device;
+            mCleanupPaths = new ArrayList<String>();
+        }
+
+        CleanupPaths(ITestDevice device, String pathList) {
+            mDevice = device;
+            mCleanupPaths = Arrays.asList(pathList.split(":"));
+        }
+
+        String getPathList() { return String.join(":", mCleanupPaths); }
+
+        // Adds the given path, or its topmost nonexisting parent directory, to the list of paths to
+        // clean up.
+        void addPath(String devicePath) throws DeviceNotAvailableException {
+            File path = new File(devicePath);
+            while (true) {
+                File parentPath = path.getParentFile();
+                if (parentPath == null || mDevice.doesFileExist(parentPath.toString())) {
+                    break;
+                }
+                path = parentPath;
+            }
+            String nonExistingPath = path.toString();
+            if (!mCleanupPaths.contains(nonExistingPath)) {
+                mCleanupPaths.add(nonExistingPath);
+            }
+        }
+
+        void cleanup() throws DeviceNotAvailableException {
+            // Clean up in reverse order in case several pushed files were in the same nonexisting
+            // directory.
+            for (int i = mCleanupPaths.size() - 1; i >= 0; --i) {
+                mDevice.deleteFile(mCleanupPaths.get(i));
+            }
+        }
+    }
+
+    // Class for code that needs an ITestDevice. It may be instantiated both in tests and in
+    // (Before|After)ClassWithInfo.
+    private static class DeviceContext implements AutoCloseable {
+        IInvocationContext mContext;
+        ITestDevice mDevice;
+        CompatibilityBuildHelper mBuildHelper;
+        CleanupPaths mCleanup;
+        private String mTestArch;
+
+        DeviceContext(TestInformation testInfo) {
+            mContext = testInfo.getContext();
+            mDevice = testInfo.getDevice();
+            mBuildHelper = new CompatibilityBuildHelper(testInfo.getBuildInfo());
+            mCleanup = new CleanupPaths(mDevice);
+        }
+
+        public void close() throws DeviceNotAvailableException { mCleanup.cleanup(); }
+
+        // Helper class to both push a library to device and record it in a public.libraries-xxx.txt
+        // file.
+        class PublicLibs {
+            private ZipFile mLibApk;
+            private List<String> mPublicLibs = new ArrayList<String>();
+
+            PublicLibs(ZipFile libApk) {
+                mLibApk = libApk;
+            }
+
+            void addLib(String libName, String destDir, String destName) throws Exception {
+                pushNativeTestLib(mLibApk, libName, destDir + "/" + destName);
+                mPublicLibs.add(destName);
+            }
+
+            void pushPublicLibrariesFile(String path) throws DeviceNotAvailableException {
+                pushString(mPublicLibs.stream().collect(Collectors.joining("\n")) + "\n", path);
+            }
+        }
+
+        void pushExtendedPublicSystemOemLibs(ZipFile libApk) throws Exception {
+            var oem1Libs = new PublicLibs(libApk);
+            // Push libsystem_extpub<n>.oem1.so for each test. Since we cannot unload them, we need
+            // a fresh never-before-loaded library in each loadLibrary call.
+            for (int i = 1; i <= 3; ++i) {
+                oem1Libs.addLib("libsystem_testlib.so", "/system/${LIB}",
+                        "libsystem_extpub" + i + ".oem1.so");
+            }
+            oem1Libs.addLib("libsystem_testlib.so", "/system/${LIB}", "libsystem_extpub.oem1.so");
+            oem1Libs.pushPublicLibrariesFile("/system/etc/public.libraries-oem1.txt");
+
+            var oem2Libs = new PublicLibs(libApk);
+            oem2Libs.addLib("libsystem_testlib.so", "/system/${LIB}", "libsystem_extpub.oem2.so");
+            // libextpub_nouses.oem2.so is a library that the test apps don't have
+            // <uses-native-library> dependencies for.
+            oem2Libs.addLib(
+                    "libsystem_testlib.so", "/system/${LIB}", "libsystem_extpub_nouses.oem2.so");
+            oem2Libs.pushPublicLibrariesFile("/system/etc/public.libraries-oem2.txt");
+        }
+
+        void pushExtendedPublicProductLibs(ZipFile libApk) throws Exception {
+            var product1Libs = new PublicLibs(libApk);
+            // Push libproduct_extpub<n>.product1.so for each test. Since we cannot unload them, we
+            // need a fresh never-before-loaded library in each loadLibrary call.
+            for (int i = 1; i <= 3; ++i) {
+                product1Libs.addLib("libproduct_testlib.so", "/product/${LIB}",
+                        "libproduct_extpub" + i + ".product1.so");
+            }
+            product1Libs.addLib(
+                    "libproduct_testlib.so", "/product/${LIB}", "libproduct_extpub.product1.so");
+            product1Libs.pushPublicLibrariesFile("/product/etc/public.libraries-product1.txt");
+        }
+
+        void pushPrivateLibs(ZipFile libApk) throws Exception {
+            // Push the libraries once for each test. Since we cannot unload them, we need a fresh
+            // never-before-loaded library in each loadLibrary call.
+            for (int i = 1; i <= 6; ++i) {
+                pushNativeTestLib(libApk, "libsystem_testlib.so",
+                        "/system/${LIB}/libsystem_private" + i + ".so");
+                pushNativeTestLib(libApk, "libsystem_testlib.so",
+                        "/system_ext/${LIB}/libsystemext_private" + i + ".so");
+                pushNativeTestLib(libApk, "libproduct_testlib.so",
+                        "/product/${LIB}/libproduct_private" + i + ".so");
+                pushNativeTestLib(libApk, "libvendor_testlib.so",
+                        "/vendor/${LIB}/libvendor_private" + i + ".so");
+            }
+        }
+
+        void pushSharedLib(String partitionDir, String packageName, String buildJarName)
+                throws Exception {
+            String path = partitionDir + "/framework/" + packageName + ".jar";
+            pushFile(buildJarName, path);
+            // This permissions xml file is necessary to make it possible to depend on the shared
+            // library from the test app, even if it's in the same partition. It makes the library
+            // public to apps in other partitions as well, which is more than we need, but that
+            // being the case we test all shared libraries from all apps.
+            pushString("<permissions>\n"
+                            + "<library name=\"" + packageName + "\" file=\"" + path + "\" />\n"
+                            + "</permissions>\n",
+                    partitionDir + "/etc/permissions/" + packageName + ".xml");
+        }
+
+        void softReboot() throws DeviceNotAvailableException {
+            assertCommandSucceeds("setprop dev.bootcomplete 0");
+            assertCommandSucceeds("stop");
+            assertCommandSucceeds("start");
+            mDevice.waitForDeviceAvailable();
+        }
+
+        String getTestArch() throws DeviceNotAvailableException {
+            if (mTestArch == null) {
+                IAbi abi = mContext.getConfigurationDescriptor().getAbi();
+                mTestArch = abi != null ? abi.getName()
+                                        : assertCommandSucceeds("getprop ro.product.cpu.abi");
+            }
+            return mTestArch;
+        }
+
+        String libDirName() throws DeviceNotAvailableException {
+            return getTestArch().contains("64") ? "lib64" : "lib";
+        }
+
+        // Pushes the given file contents to the device at the given destination path. destPath is
+        // assumed to have no risk of overlapping with existing files, and is deleted in tearDown(),
+        // along with any directory levels that had to be created.
+        void pushString(String fileContents, String destPath) throws DeviceNotAvailableException {
+            mCleanup.addPath(destPath);
+            assertThat(mDevice.pushString(fileContents, destPath)).isTrue();
+        }
+
+        // Like pushString, but pushes a data file included in the host test.
+        void pushFile(String fileName, String destPath) throws Exception {
+            mCleanup.addPath(destPath);
+            assertThat(mDevice.pushFile(mBuildHelper.getTestFile(fileName), destPath)).isTrue();
+        }
+
+        void pushApk(String apkBaseName, String destPath) throws Exception {
+            pushFile(apkBaseName + ".apk",
+                    destPath + "/" + apkBaseName + "/" + apkBaseName + ".apk");
+        }
+
+        // Like pushString, but extracts libnativeloader_testlib.so from the library_container_app
+        // APK and pushes it to destPath. "${LIB}" is replaced with "lib" or "lib64" as appropriate.
+        void pushNativeTestLib(ZipFile libApk, String libName, String destPath) throws Exception {
+            String libApkPath = "lib/" + getTestArch() + "/" + libName;
+            ZipEntry entry = libApk.getEntry(libApkPath);
+            assertWithMessage("Failed to find " + libApkPath + " in library_container_app.apk")
+                    .that(entry)
+                    .isNotNull();
+
+            File libraryTempFile;
+            try (InputStream inStream = libApk.getInputStream(entry)) {
+                libraryTempFile = writeStreamToTempFile(libName, inStream);
+            }
+
+            destPath = destPath.replace("${LIB}", libDirName());
+
+            mCleanup.addPath(destPath);
+            assertThat(mDevice.pushFile(libraryTempFile, destPath)).isTrue();
+        }
+
+        void installPackage(String apkBaseName) throws Exception {
+            assertThat(mDevice.installPackage(mBuildHelper.getTestFile(apkBaseName + ".apk"),
+                               false /* reinstall */))
+                    .isNull();
+        }
+
+        String assertCommandSucceeds(String command) throws DeviceNotAvailableException {
+            CommandResult result = mDevice.executeShellV2Command(command);
+            assertWithMessage(result.toString()).that(result.getExitCode()).isEqualTo(0);
+            // Remove trailing \n's.
+            return result.getStdout().trim();
+        }
+    }
+
+    static private File writeStreamToTempFile(String tempFileBaseName, InputStream inStream)
+            throws Exception {
+        File hostTempFile = File.createTempFile(tempFileBaseName, null);
+        try (FileOutputStream outStream = new FileOutputStream(hostTempFile)) {
+            ByteStreams.copy(inStream, outStream);
+        }
+        return hostTempFile;
+    }
+}
diff --git a/libnativeloader/test/src/android/test/lib/TestUtils.java b/libnativeloader/test/src/android/test/lib/TestUtils.java
new file mode 100644
index 0000000..1dd917f
--- /dev/null
+++ b/libnativeloader/test/src/android/test/lib/TestUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.test.lib;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.function.ThrowingRunnable;
+
+public final class TestUtils {
+    public static void assertLibraryNotFound(ThrowingRunnable loadLibrary) {
+        Throwable t = assertThrows(UnsatisfiedLinkError.class, loadLibrary);
+        assertThat(t.getMessage()).containsMatch("dlopen failed: library .* not found");
+    }
+
+    public static void assertLinkerNamespaceError(ThrowingRunnable loadLibrary) {
+        Throwable t = assertThrows(UnsatisfiedLinkError.class, loadLibrary);
+        assertThat(t.getMessage())
+                .containsMatch("dlopen failed: .* is not accessible for the namespace");
+    }
+
+    public static String libPath(String dir, String libName) {
+        String libDirName = InstrumentationRegistry.getArguments().getString("libDirName");
+        return dir + "/" + libDirName + "/lib" + libName + ".so";
+    }
+}
diff --git a/libnativeloader/test/src/android/test/productsharedlib/ProductSharedLib.java b/libnativeloader/test/src/android/test/productsharedlib/ProductSharedLib.java
new file mode 100644
index 0000000..a500d2a
--- /dev/null
+++ b/libnativeloader/test/src/android/test/productsharedlib/ProductSharedLib.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.test.productsharedlib;
+
+public final class ProductSharedLib {
+    public static void loadLibrary(String name) { System.loadLibrary(name); }
+}
diff --git a/libnativeloader/test/src/android/test/systemextsharedlib/SystemExtSharedLib.java b/libnativeloader/test/src/android/test/systemextsharedlib/SystemExtSharedLib.java
new file mode 100644
index 0000000..1240e12
--- /dev/null
+++ b/libnativeloader/test/src/android/test/systemextsharedlib/SystemExtSharedLib.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.test.systemextsharedlib;
+
+public final class SystemExtSharedLib {
+    public static void loadLibrary(String name) { System.loadLibrary(name); }
+}
diff --git a/libnativeloader/test/src/android/test/systemsharedlib/SystemSharedLib.java b/libnativeloader/test/src/android/test/systemsharedlib/SystemSharedLib.java
new file mode 100644
index 0000000..8e2af9f
--- /dev/null
+++ b/libnativeloader/test/src/android/test/systemsharedlib/SystemSharedLib.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.test.systemsharedlib;
+
+public final class SystemSharedLib {
+    public static void loadLibrary(String name) { System.loadLibrary(name); }
+}
diff --git a/libnativeloader/test/src/android/test/vendorsharedlib/VendorSharedLib.java b/libnativeloader/test/src/android/test/vendorsharedlib/VendorSharedLib.java
new file mode 100644
index 0000000..8859b63
--- /dev/null
+++ b/libnativeloader/test/src/android/test/vendorsharedlib/VendorSharedLib.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.test.vendorsharedlib;
+
+public final class VendorSharedLib {
+    public static void loadLibrary(String name) { System.loadLibrary(name); }
+}
diff --git a/libnativeloader/test/system/AndroidManifest.xml b/libnativeloader/test/system/AndroidManifest.xml
deleted file mode 100644
index c304889..0000000
--- a/libnativeloader/test/system/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2018 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.test.app.system">
-
-    <application>
-        <activity android:name="android.test.app.TestActivity" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-
-</manifest>
-
diff --git a/libnativeloader/test/test.cpp b/libnativeloader/test/test.cpp
deleted file mode 100644
index b166928..0000000
--- a/libnativeloader/test/test.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.
- */
-#define LOG_TAG "oemlib"
-#include <android-base/logging.h>
-
-static __attribute__((constructor)) void test_lib_init() {
-  LOG(DEBUG) << LIBNAME << " loaded";
-}
diff --git a/libnativeloader/test/vendor/AndroidManifest.xml b/libnativeloader/test/vendor/AndroidManifest.xml
deleted file mode 100644
index c4c1a9c..0000000
--- a/libnativeloader/test/vendor/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2018 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.test.app.vendor">
-
-    <application>
-        <activity android:name="android.test.app.TestActivity" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-
-</manifest>
-
diff --git a/libprofile/Android.bp b/libprofile/Android.bp
index ecbcd0b..beae8a9 100644
--- a/libprofile/Android.bp
+++ b/libprofile/Android.bp
@@ -39,6 +39,7 @@
                 "libz",
             ],
             static_libs: [
+                "libmodules-utils-build",
                 // ZipArchive support, the order matters here to get all symbols.
                 "libziparchive",
             ],
@@ -106,11 +107,6 @@
         "libprofile_defaults",
         "libart_nativeunwind_defaults",
     ],
-    shared_libs: [
-        "libbase",
-        "libziparchive",
-    ],
-    export_shared_lib_headers: ["libbase"],
     target: {
         android: {
             shared_libs: [
@@ -138,6 +134,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -147,10 +144,6 @@
         "art_debug_defaults",
         "libprofile_defaults",
     ],
-    shared_libs: [
-        "libbase",
-        "libziparchive",
-    ],
     target: {
         android: {
             shared_libs: [
@@ -171,13 +164,13 @@
             ],
         },
     },
-    export_shared_lib_headers: ["libbase"],
     apex_available: [
         "com.android.art.debug",
         // TODO(b/183882457): This lib doesn't go into com.android.art, but
         // apex_available lists need to be the same for internal libs to avoid
         // stubs, and libartd depends on this.
         "com.android.art",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -195,9 +188,12 @@
         "profile/profile_boot_info_test.cc",
         "profile/profile_compilation_info_test.cc",
     ],
-    shared_libs: [
+    static_libs: [
         "libziparchive",
     ],
+    shared_libs: [
+        "libz", // libziparchive dependency; must be repeated here since it's a static lib.
+    ],
 }
 
 // Version of ART gtest `art_libprofile_tests` bundled with the ART APEX on target.
diff --git a/libprofile/art_standalone_libprofile_tests.xml b/libprofile/art_standalone_libprofile_tests.xml
index 9ecd9a5..65264f4 100644
--- a/libprofile/art_standalone_libprofile_tests.xml
+++ b/libprofile/art_standalone_libprofile_tests.xml
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 <configuration description="Runs art_standalone_libprofile_tests.">
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
+
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="art_standalone_libprofile_tests->/data/local/tmp/art_standalone_libprofile_tests/art_standalone_libprofile_tests" />
diff --git a/libprofile/profile/profile_boot_info_test.cc b/libprofile/profile/profile_boot_info_test.cc
index 9939f0a..3c5829c 100644
--- a/libprofile/profile/profile_boot_info_test.cc
+++ b/libprofile/profile/profile_boot_info_test.cc
@@ -66,6 +66,7 @@
   ScratchFile profile;
   std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("MultiDex");
   std::vector<const DexFile*> dex_files2;
+  dex_files2.reserve(dex_files.size());
   for (const std::unique_ptr<const DexFile>& file : dex_files) {
     dex_files2.push_back(file.get());
   }
@@ -104,6 +105,7 @@
   ProfileBootInfo loaded_info;
   std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("MultiDex");
   std::vector<const DexFile*> dex_files2;
+  dex_files2.reserve(dex_files.size());
   for (const std::unique_ptr<const DexFile>& file : dex_files) {
     dex_files2.push_back(file.get());
   }
diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc
index f135805..4341a1d 100644
--- a/libprofile/profile/profile_compilation_info.cc
+++ b/libprofile/profile/profile_compilation_info.cc
@@ -16,6 +16,7 @@
 
 #include "profile_compilation_info.h"
 
+#include <fcntl.h>
 #include <sys/file.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -25,6 +26,7 @@
 #include <algorithm>
 #include <cerrno>
 #include <climits>
+#include <cstdio>
 #include <cstdlib>
 #include <iostream>
 #include <numeric>
@@ -33,11 +35,14 @@
 #include <vector>
 
 #include "android-base/file.h"
-
+#include "android-base/properties.h"
+#include "android-base/scopeguard.h"
+#include "android-base/unique_fd.h"
 #include "base/arena_allocator.h"
 #include "base/bit_utils.h"
 #include "base/dumpable.h"
 #include "base/file_utils.h"
+#include "base/globals.h"
 #include "base/logging.h"  // For VLOG.
 #include "base/malloc_arena_pool.h"
 #include "base/os.h"
@@ -52,6 +57,10 @@
 #include "dex/descriptors_names.h"
 #include "dex/dex_file_loader.h"
 
+#ifdef ART_TARGET_ANDROID
+#include "android-modules-utils/sdk_level.h"
+#endif
+
 namespace art {
 
 const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' };
@@ -775,6 +784,9 @@
       LockedFile::Open(filename.c_str(), flags, /*block=*/false, &error);
 
   if (profile_file.get() == nullptr) {
+    if (clear_if_invalid && errno == ENOENT) {
+      return true;
+    }
     LOG(WARNING) << "Couldn't lock the profile file " << filename << ": " << error;
     return false;
   }
@@ -792,6 +804,8 @@
        (status == ProfileLoadStatus::kBadData))) {
     LOG(WARNING) << "Clearing bad or obsolete profile data from file "
                  << filename << ": " << error;
+    // When ART Service is enabled, this is the only place where we mutate a profile in place.
+    // TODO(jiakaiz): Get rid of this.
     if (profile_file->ClearContent()) {
       return true;
     } else {
@@ -806,11 +820,73 @@
 
 bool ProfileCompilationInfo::Save(const std::string& filename, uint64_t* bytes_written) {
   ScopedTrace trace(__PRETTY_FUNCTION__);
+
+#ifndef ART_TARGET_ANDROID
+  return SaveFallback(filename, bytes_written);
+#else
+  // Prior to U, SELinux policy doesn't allow apps to create profile files.
+  // Additionally, when installd is being used for dexopt, it acquires a flock when working on a
+  // profile. It's unclear to us whether the flock means that the file at the fd shouldn't change or
+  // that the file at the path shouldn't change, especially when the installd code is modified by
+  // partners. Therefore, we fall back to using a flock as well just to be safe.
+  if (!android::modules::sdklevel::IsAtLeastU() ||
+      !android::base::GetBoolProperty("dalvik.vm.useartservice", /*default_value=*/false)) {
+    return SaveFallback(filename, bytes_written);
+  }
+
+  std::string tmp_filename = filename + ".XXXXXX.tmp";
+  // mkstemps creates the file with permissions 0600, which is the desired permissions, so there's
+  // no need to chmod.
+  android::base::unique_fd fd(mkostemps(tmp_filename.data(), /*suffixlen=*/4, O_CLOEXEC));
+  if (fd.get() < 0) {
+    PLOG(WARNING) << "Failed to create temp profile file for " << filename;
+    return false;
+  }
+
+  // In case anything goes wrong.
+  auto remove_tmp_file = android::base::make_scope_guard([&]() {
+    if (unlink(tmp_filename.c_str()) != 0) {
+      PLOG(WARNING) << "Failed to remove temp profile file " << tmp_filename;
+    }
+  });
+
+  bool result = Save(fd.get());
+  if (!result) {
+    VLOG(profiler) << "Failed to save profile info to temp profile file " << tmp_filename;
+    return false;
+  }
+
+  fd.reset();
+
+  // Move the temp profile file to the final location.
+  if (rename(tmp_filename.c_str(), filename.c_str()) != 0) {
+    PLOG(WARNING) << "Failed to commit profile file " << filename;
+    return false;
+  }
+
+  remove_tmp_file.Disable();
+
+  int64_t size = OS::GetFileSizeBytes(filename.c_str());
+  if (size != -1) {
+    VLOG(profiler) << "Successfully saved profile info to " << filename << " Size: " << size;
+    if (bytes_written != nullptr) {
+      *bytes_written = static_cast<uint64_t>(size);
+    }
+  } else {
+    VLOG(profiler) << "Saved profile info to " << filename
+                   << " but failed to get size: " << strerror(errno);
+  }
+
+  return true;
+#endif
+}
+
+bool ProfileCompilationInfo::SaveFallback(const std::string& filename, uint64_t* bytes_written) {
   std::string error;
 #ifdef _WIN32
-  int flags = O_WRONLY;
+  int flags = O_WRONLY | O_CREAT;
 #else
-  int flags = O_WRONLY | O_NOFOLLOW | O_CLOEXEC;
+  int flags = O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_CREAT;
 #endif
   // There's no need to fsync profile data right away. We get many chances
   // to write it again in case something goes wrong. We can rely on a simple
@@ -842,6 +918,9 @@
       if (bytes_written != nullptr) {
         *bytes_written = static_cast<uint64_t>(size);
       }
+    } else {
+      VLOG(profiler) << "Saved profile info to " << filename
+                     << " but failed to get size: " << strerror(errno);
     }
   } else {
     VLOG(profiler) << "Failed to save profile info to " << filename;
@@ -1710,14 +1789,14 @@
   if (memcmp(header.GetVersion(), version_, kProfileVersionSize) != 0) {
     *error = IsForBootImage() ? "Expected boot profile, got app profile."
                               : "Expected app profile, got boot profile.";
-    return ProfileLoadStatus::kMergeError;
+    return ProfileLoadStatus::kVersionMismatch;
   }
 
   // Check if there are too many section infos.
   uint32_t section_count = header.GetFileSectionCount();
   uint32_t uncompressed_data_size = sizeof(FileHeader) + section_count * sizeof(FileSectionInfo);
   if (uncompressed_data_size > GetSizeErrorThresholdBytes()) {
-    LOG(ERROR) << "Profile data size exceeds " << GetSizeErrorThresholdBytes()
+    LOG(WARNING) << "Profile data size exceeds " << GetSizeErrorThresholdBytes()
                << " bytes. It has " << uncompressed_data_size << " bytes.";
     return ProfileLoadStatus::kBadData;
   }
@@ -1743,7 +1822,7 @@
   // Allow large profiles for non target builds for the case where we are merging many profiles
   // to generate a boot image profile.
   if (uncompressed_data_size > GetSizeErrorThresholdBytes()) {
-    LOG(ERROR) << "Profile data size exceeds "
+    LOG(WARNING) << "Profile data size exceeds "
                << GetSizeErrorThresholdBytes()
                << " bytes. It has " << uncompressed_data_size << " bytes.";
     return ProfileLoadStatus::kBadData;
@@ -2113,6 +2192,16 @@
   return true;
 }
 
+const ArenaSet<dex::TypeIndex>* ProfileCompilationInfo::GetClasses(
+    const DexFile& dex_file,
+    const ProfileSampleAnnotation& annotation) const {
+  const DexFileData* dex_data = FindDexDataUsingAnnotations(&dex_file, annotation);
+  if (dex_data == nullptr) {
+    return nullptr;
+  }
+  return &dex_data->class_set;
+}
+
 bool ProfileCompilationInfo::SameVersion(const ProfileCompilationInfo& other) const {
   return memcmp(version_, other.version_, kProfileVersionSize) == 0;
 }
@@ -2209,7 +2298,8 @@
     return vec;
   };
   for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
-    const std::string& profile_key = dex_file->GetLocation();
+    const std::string& dex_location = dex_file->GetLocation();
+    std::string profile_key = info.GetProfileDexFileBaseKey(dex_location);
     uint32_t checksum = dex_file->GetLocationChecksum();
 
     uint32_t number_of_classes = dex_file->NumClassDefs();
@@ -2387,7 +2477,8 @@
 }
 
 bool ProfileCompilationInfo::UpdateProfileKeys(
-      const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
+    const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ bool* matched) {
+  *matched = false;
   for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
     for (const std::unique_ptr<DexFileData>& dex_data : info_) {
       if (dex_data->checksum == dex_file->GetLocationChecksum() &&
@@ -2408,6 +2499,7 @@
           dex_data->profile_key = MigrateAnnotationInfo(new_profile_key, dex_data->profile_key);
           profile_key_map_.Put(dex_data->profile_key, dex_data->profile_index);
         }
+        *matched = true;
       }
     }
   }
diff --git a/libprofile/profile/profile_compilation_info.h b/libprofile/profile/profile_compilation_info.h
index 4366078..6817762 100644
--- a/libprofile/profile/profile_compilation_info.h
+++ b/libprofile/profile/profile_compilation_info.h
@@ -463,10 +463,12 @@
   //   the dex_file they are in.
   bool VerifyProfileData(const std::vector<const DexFile*>& dex_files);
 
-  // Load profile information from the given file
+  // Loads profile information from the given file.
+  // Returns true on success, false otherwise.
   // If the current profile is non-empty the load will fail.
-  // If clear_if_invalid is true and the file is invalid the method clears the
-  // the file and returns true.
+  // If clear_if_invalid is true:
+  // - If the file is invalid, the method clears the file and returns true.
+  // - If the file doesn't exist, the method returns true.
   bool Load(const std::string& filename, bool clear_if_invalid);
 
   // Merge the data from another ProfileCompilationInfo into the current object. Only merges
@@ -480,9 +482,12 @@
   // Save the profile data to the given file descriptor.
   bool Save(int fd);
 
-  // Save the current profile into the given file. The file will be cleared before saving.
+  // Save the current profile into the given file. Overwrites any existing data.
   bool Save(const std::string& filename, uint64_t* bytes_written);
 
+  // A fallback implementation of `Save` that uses a flock.
+  bool SaveFallback(const std::string& filename, uint64_t* bytes_written);
+
   // Return the number of dex files referenced in the profile.
   size_t GetNumberOfDexFiles() const {
     return info_.size();
@@ -590,6 +595,10 @@
       /*out*/std::set<uint16_t>* post_startup_method_method_set,
       const ProfileSampleAnnotation& annotation = ProfileSampleAnnotation::kNone) const;
 
+  const ArenaSet<dex::TypeIndex>* GetClasses(
+      const DexFile& dex_file,
+      const ProfileSampleAnnotation& annotation = ProfileSampleAnnotation::kNone) const;
+
   // Returns true iff both profiles have the same version.
   bool SameVersion(const ProfileCompilationInfo& other) const;
 
@@ -645,7 +654,10 @@
   //
   // If the new profile key would collide with an existing key (for a different dex)
   // the method returns false. Otherwise it returns true.
-  bool UpdateProfileKeys(const std::vector<std::unique_ptr<const DexFile>>& dex_files);
+  //
+  // `matched` is set to true if any profile has matched any input dex file.
+  bool UpdateProfileKeys(const std::vector<std::unique_ptr<const DexFile>>& dex_files,
+                         /*out*/ bool* matched);
 
   // Checks if the profile is empty.
   bool IsEmpty() const;
diff --git a/libprofile/profile/profile_compilation_info_test.cc b/libprofile/profile/profile_compilation_info_test.cc
index 8c9d0df..8168004 100644
--- a/libprofile/profile/profile_compilation_info_test.cc
+++ b/libprofile/profile/profile_compilation_info_test.cc
@@ -40,17 +40,23 @@
     CommonArtTest::SetUp();
     allocator_.reset(new ArenaAllocator(&pool_));
 
-    dex1 = BuildDex("location1", /*checksum=*/ 1, "LUnique1;", /*num_method_ids=*/ 101);
-    dex2 = BuildDex("location2", /*checksum=*/ 2, "LUnique2;", /*num_method_ids=*/ 102);
-    dex3 = BuildDex("location3", /*checksum=*/ 3, "LUnique3;", /*num_method_ids=*/ 103);
-    dex4 = BuildDex("location4", /*checksum=*/ 4, "LUnique4;", /*num_method_ids=*/ 104);
+    dex1 = BuildDex("location1", /*location_checksum=*/ 1, "LUnique1;", /*num_method_ids=*/ 101);
+    dex2 = BuildDex("location2", /*location_checksum=*/ 2, "LUnique2;", /*num_method_ids=*/ 102);
+    dex3 = BuildDex("location3", /*location_checksum=*/ 3, "LUnique3;", /*num_method_ids=*/ 103);
+    dex4 = BuildDex("location4", /*location_checksum=*/ 4, "LUnique4;", /*num_method_ids=*/ 104);
 
-    dex1_checksum_missmatch =
-        BuildDex("location1", /*checksum=*/ 12, "LUnique1;", /*num_method_ids=*/ 101);
-    dex1_renamed =
-        BuildDex("location1-renamed", /*checksum=*/ 1, "LUnique1;", /*num_method_ids=*/ 101);
-    dex2_renamed =
-        BuildDex("location2-renamed", /*checksum=*/ 2, "LUnique2;", /*num_method_ids=*/ 102);
+    dex1_checksum_missmatch = BuildDex("location1",
+                                       /*location_checksum=*/ 12,
+                                       "LUnique1;",
+                                       /*num_method_ids=*/ 101);
+    dex1_renamed = BuildDex("location1-renamed",
+                            /*location_checksum=*/ 1,
+                            "LUnique1;",
+                            /*num_method_ids=*/ 101);
+    dex2_renamed = BuildDex("location2-renamed",
+                            /*location_checksum=*/ 2,
+                            "LUnique2;",
+                            /*num_method_ids=*/ 102);
   }
 
  protected:
@@ -142,7 +148,7 @@
 
     // Zip the profile content.
     ScratchFile zip;
-    FILE* file = fopen(zip.GetFile()->GetPath().c_str(), "wb");
+    FILE* file = fopen(zip.GetFile()->GetPath().c_str(), "wbe");
     ZipWriter writer(file);
     writer.StartEntry(zip_entry, zip_flags);
     writer.WriteBytes(data.data(), data.size());
@@ -350,10 +356,16 @@
 TEST_F(ProfileCompilationInfoTest, SaveMaxMethods) {
   ScratchFile profile;
 
-  const DexFile* dex_max1 = BuildDex(
-      "location-max1", /*checksum=*/ 5, "LUniqueMax1;", kMaxMethodIds, kMaxClassIds);
-  const DexFile* dex_max2 = BuildDex(
-      "location-max2", /*checksum=*/ 6, "LUniqueMax2;", kMaxMethodIds, kMaxClassIds);
+  const DexFile* dex_max1 = BuildDex("location-max1",
+                                     /*location_checksum=*/ 5,
+                                     "LUniqueMax1;",
+                                     kMaxMethodIds,
+                                     kMaxClassIds);
+  const DexFile* dex_max2 = BuildDex("location-max2",
+                                     /*location_checksum=*/ 6,
+                                     "LUniqueMax2;",
+                                     kMaxMethodIds,
+                                     kMaxClassIds);
 
 
   ProfileCompilationInfo saved_info;
@@ -733,11 +745,11 @@
   // Save a few methods.
   for (uint16_t i = 0; i < std::numeric_limits<ProfileIndexType>::max(); i++) {
     std::string location = std::to_string(i);
-    const DexFile* dex = BuildDex(location, /*checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
+    const DexFile* dex = BuildDex(location, /*location_checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
     ASSERT_TRUE(AddMethod(&info, dex, /*method_idx=*/ 0));
   }
   // Add an extra dex file.
-  const DexFile* dex = BuildDex("-1", /*checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
+  const DexFile* dex = BuildDex("-1", /*location_checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
   ASSERT_FALSE(AddMethod(&info, dex, /*method_idx=*/ 0));
 }
 
@@ -746,11 +758,11 @@
   // Save a few methods.
   for (uint16_t i = 0; i < std::numeric_limits<ProfileIndexType>::max(); i++) {
     std::string location = std::to_string(i);
-    const DexFile* dex = BuildDex(location, /*checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
+    const DexFile* dex = BuildDex(location, /*location_checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
     ASSERT_TRUE(AddMethod(&info, dex, /*method_idx=*/ 0));
   }
   // Add an extra dex file.
-  const DexFile* dex = BuildDex("-1", /*checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
+  const DexFile* dex = BuildDex("-1", /*location_checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
   ASSERT_FALSE(AddMethod(&info, dex, /*method_idx=*/ 0));
 }
 
@@ -920,7 +932,7 @@
 
   // Zip the profile content.
   ScratchFile zip;
-  FILE* file = fopen(zip.GetFile()->GetPath().c_str(), "wb");
+  FILE* file = fopen(zip.GetFile()->GetPath().c_str(), "wbe");
   ZipWriter writer(file);
   writer.StartEntry("primary.prof", ZipWriter::kAlign32);
   writer.WriteBytes(data.data(), data.size());
@@ -944,7 +956,9 @@
   AddMethod(&info, dex2, /*method_idx=*/ 0);
 
   // Update the profile keys based on the original dex files
-  ASSERT_TRUE(info.UpdateProfileKeys(dex_files));
+  bool matched = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+  ASSERT_TRUE(matched);
 
   // Verify that we find the methods when searched with the original dex files.
   for (const std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -970,7 +984,9 @@
   AddMethod(&info, dex2, /*method_idx=*/ 0, Hotness::kFlagHot, annotation);
 
   // Update the profile keys based on the original dex files
-  ASSERT_TRUE(info.UpdateProfileKeys(dex_files));
+  bool matched = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+  ASSERT_TRUE(matched);
 
   // Verify that we find the methods when searched with the original dex files.
   for (const std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -985,7 +1001,33 @@
   }
 }
 
-TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkButNoUpdate) {
+TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkMatchedButNoUpdate) {
+  std::vector<std::unique_ptr<const DexFile>> dex_files;
+  dex_files.push_back(std::unique_ptr<const DexFile>(dex1));
+
+  // Both the checksum and the location match the original dex file.
+  ProfileCompilationInfo info;
+  AddMethod(&info, dex1, /*method_idx=*/0);
+
+  // No update should happen, but this should be considered as a happy case.
+  bool matched = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+  ASSERT_TRUE(matched);
+
+  // Verify that we find the methods when searched with the original dex files.
+  for (const std::unique_ptr<const DexFile>& dex : dex_files) {
+    ProfileCompilationInfo::MethodHotness loaded_hotness =
+        GetMethod(info, dex.get(), /*method_idx=*/ 0);
+    ASSERT_TRUE(loaded_hotness.IsHot());
+  }
+
+  // Release the ownership as this is held by the test class;
+  for (std::unique_ptr<const DexFile>& dex : dex_files) {
+    UNUSED(dex.release());
+  }
+}
+
+TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkButNoMatch) {
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   dex_files.push_back(std::unique_ptr<const DexFile>(dex1));
 
@@ -993,7 +1035,9 @@
   AddMethod(&info, dex2, /*method_idx=*/ 0);
 
   // Update the profile keys based on the original dex files.
-  ASSERT_TRUE(info.UpdateProfileKeys(dex_files));
+  bool matched = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+  ASSERT_FALSE(matched);
 
   // Verify that we did not perform any update and that we cannot find anything with the new
   // location.
@@ -1025,7 +1069,9 @@
   // This will cause the rename to fail because an existing entry would already have that name.
   AddMethod(&info, dex1_renamed, /*method_idx=*/ 0);
 
-  ASSERT_FALSE(info.UpdateProfileKeys(dex_files));
+  bool matched = false;
+  ASSERT_FALSE(info.UpdateProfileKeys(dex_files, &matched));
+  ASSERT_FALSE(matched);
 
   // Release the ownership as this is held by the test class;
   for (std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -1167,12 +1213,12 @@
   ScratchFile profile;
 
   const DexFile* dex1_1000 = BuildDex("location1_1000",
-                                      /*checksum=*/ 7,
+                                      /*location_checksum=*/ 7,
                                       "LC1_1000;",
                                       /*num_method_ids=*/ 1u,
                                       /*num_class_ids=*/ 1000u);
   const DexFile* dex2_1000 = BuildDex("location2_1000",
-                                      /*checksum=*/ 8,
+                                      /*location_checksum=*/ 8,
                                       "LC2_1000;",
                                       /*num_method_ids=*/ 1u,
                                       /*num_class_ids=*/ 1000u);
diff --git a/oatdump/Android.bp b/oatdump/Android.bp
index 5724280..dacd647 100644
--- a/oatdump/Android.bp
+++ b/oatdump/Android.bp
@@ -70,6 +70,9 @@
                 "libdexfile",
                 "libprofile",
             ],
+            static_libs: [
+                "libelffile",
+            ],
         },
         host: {
             // Make the host binary static, except for system libraries.
@@ -81,6 +84,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -124,6 +128,9 @@
                 "libdexfiled",
                 "libprofiled",
             ],
+            static_libs: [
+                "libelffiled",
+            ],
         },
         host: {
             // Make the host binary static, except for system libraries.
diff --git a/oatdump/art_standalone_oatdump_tests.xml b/oatdump/art_standalone_oatdump_tests.xml
index bcd94ed..ab11b11 100644
--- a/oatdump/art_standalone_oatdump_tests.xml
+++ b/oatdump/art_standalone_oatdump_tests.xml
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 <configuration description="Runs art_standalone_oatdump_tests.">
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
+
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="art_standalone_oatdump_tests->/data/local/tmp/art_standalone_oatdump_tests/art_standalone_oatdump_tests" />
@@ -54,7 +56,8 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
-
+    <!-- Skip on HWASan. TODO(b/230394041): Re-enable -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.SkipHWASanModuleController" />
     <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index e69b32a..9dd704a 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -45,7 +45,6 @@
 #include "class_linker-inl.h"
 #include "class_linker.h"
 #include "class_root-inl.h"
-#include "compiled_method.h"
 #include "debug/debug_info.h"
 #include "debug/elf_debug_writer.h"
 #include "debug/method_debug_info.h"
@@ -183,17 +182,17 @@
     builder_->WriteDynamicSection();
 
     const OatHeader& oat_header = oat_file_->GetOatHeader();
-    #define DO_TRAMPOLINE(fn_name)                                                \
-      if (oat_header.Get ## fn_name ## Offset() != 0) {                           \
-        debug::MethodDebugInfo info = {};                                         \
-        info.custom_name = #fn_name;                                              \
-        info.isa = oat_header.GetInstructionSet();                                \
-        info.is_code_address_text_relative = true;                                \
-        size_t code_offset = oat_header.Get ## fn_name ## Offset();               \
-        code_offset -= CompiledCode::CodeDelta(oat_header.GetInstructionSet());   \
-        info.code_address = code_offset - oat_header.GetExecutableOffset();       \
-        info.code_size = 0;  /* The symbol lasts until the next symbol. */        \
-        method_debug_infos_.push_back(std::move(info));                           \
+    #define DO_TRAMPOLINE(fn_name)                                                            \
+      if (oat_header.Get ## fn_name ## Offset() != 0) {                                       \
+        debug::MethodDebugInfo info = {};                                                     \
+        info.custom_name = #fn_name;                                                          \
+        info.isa = oat_header.GetInstructionSet();                                            \
+        info.is_code_address_text_relative = true;                                            \
+        size_t code_offset = oat_header.Get ## fn_name ## Offset();                           \
+        code_offset -= GetInstructionSetEntryPointAdjustment(oat_header.GetInstructionSet()); \
+        info.code_address = code_offset - oat_header.GetExecutableOffset();                   \
+        info.code_size = 0;  /* The symbol lasts until the next symbol. */                    \
+        method_debug_infos_.push_back(std::move(info));                                       \
       }
     DO_TRAMPOLINE(JniDlsymLookupTrampoline);
     DO_TRAMPOLINE(JniDlsymLookupCriticalTrampoline);
@@ -661,16 +660,13 @@
           DexContainer::Section* main_section = dex_container->GetMainSection();
           CHECK_EQ(dex_container->GetDataSection()->Size(), 0u);
 
-          const ArtDexFileLoader dex_file_loader;
-          std::unique_ptr<const DexFile> dex(dex_file_loader.Open(
-              main_section->Begin(),
-              main_section->Size(),
-              vdex_dex_file->GetLocation(),
-              vdex_file->GetLocationChecksum(i),
-              /*oat_dex_file=*/ nullptr,
-              /*verify=*/ false,
-              /*verify_checksum=*/ true,
-              &error_msg));
+          ArtDexFileLoader dex_file_loader(
+              main_section->Begin(), main_section->Size(), vdex_dex_file->GetLocation());
+          std::unique_ptr<const DexFile> dex(dex_file_loader.Open(vdex_file->GetLocationChecksum(i),
+                                                                  /*oat_dex_file=*/nullptr,
+                                                                  /*verify=*/false,
+                                                                  /*verify_checksum=*/true,
+                                                                  &error_msg));
           if (dex == nullptr) {
             os << "Failed to load DexFile from layout container: " + error_msg;
             success = false;
@@ -1151,11 +1147,17 @@
     if (Runtime::Current() != nullptr) {
       // We need to have the handle scope stay live until after the verifier since the verifier has
       // a handle to the dex cache from hs.
+      ScopedObjectAccess soa(Thread::Current());
       hs.reset(new StackHandleScope<1>(Thread::Current()));
       vios->Stream() << "VERIFIER TYPE ANALYSIS:\n";
       ScopedIndentation indent2(vios);
-      verifier.reset(DumpVerifier(vios, hs.get(),
-                                  dex_method_idx, &dex_file, class_def, code_item,
+      verifier.reset(DumpVerifier(vios,
+                                  soa,
+                                  hs.get(),
+                                  dex_method_idx,
+                                  &dex_file,
+                                  class_def,
+                                  code_item,
                                   method_access_flags));
     }
     {
@@ -1460,14 +1462,15 @@
   }
 
   verifier::MethodVerifier* DumpVerifier(VariableIndentationOutputStream* vios,
+                                         ScopedObjectAccess& soa,
                                          StackHandleScope<1>* hs,
                                          uint32_t dex_method_idx,
                                          const DexFile* dex_file,
                                          const dex::ClassDef& class_def,
                                          const dex::CodeItem* code_item,
-                                         uint32_t method_access_flags) {
+                                         uint32_t method_access_flags)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
     if ((method_access_flags & kAccNative) == 0) {
-      ScopedObjectAccess soa(Thread::Current());
       Runtime* const runtime = Runtime::Current();
       DCHECK(options_.class_loader_ != nullptr);
       Handle<mirror::DexCache> dex_cache = hs->NewHandle(
@@ -3017,7 +3020,7 @@
           table_index++;
 
           std::string p_name = ptr2->PrettyMethod(true);
-          if (android::base::StartsWith(p_name, method.c_str())) {
+          if (android::base::StartsWith(p_name, method)) {
             std::cerr << "  Slot "
                       << index
                       << " ("
@@ -3030,7 +3033,7 @@
         }
       } else {
         std::string p_name = ptr->PrettyMethod(true);
-        if (android::base::StartsWith(p_name, method.c_str())) {
+        if (android::base::StartsWith(p_name, method)) {
           std::cerr << "  Slot " << index << " (1)" << std::endl;
           std::cerr << "    " << p_name << std::endl;
         } else {
@@ -3043,7 +3046,7 @@
               for (ArtMethod& iface_method : iface->GetMethods(pointer_size)) {
                 if (ImTable::GetImtIndex(&iface_method) == index) {
                   std::string i_name = iface_method.PrettyMethod(true);
-                  if (android::base::StartsWith(i_name, method.c_str())) {
+                  if (android::base::StartsWith(i_name, method)) {
                     std::cerr << "  Slot " << index << " (1)" << std::endl;
                     std::cerr << "    " << p_name << " (" << i_name << ")" << std::endl;
                   }
diff --git a/oatdump/oatdump_test.h b/oatdump/oatdump_test.h
index 3ec5b94..708befe 100644
--- a/oatdump/oatdump_test.h
+++ b/oatdump/oatdump_test.h
@@ -328,7 +328,9 @@
 
     auto post_fork_fn = []() {
       setpgid(0, 0);  // Change process groups, so we don't get reaped by ProcessManager.
-      return true;    // Ignore setpgid failures.
+                      // Ignore setpgid failures.
+      return setenv("ANDROID_LOG_TAGS", "*:e", 1) == 0;  // We're only interested in errors and
+                                                         // fatal logs.
     };
 
     ForkAndExecResult res = ForkAndExec(exec_argv, post_fork_fn, line_buf_fn);
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp
index 8fee83a..809e18d 100644
--- a/odrefresh/Android.bp
+++ b/odrefresh/Android.bp
@@ -42,12 +42,12 @@
     ],
     shared_libs: [
         "libartpalette",
+        "libarttools", // Contains "libc++fs".
         "libbase",
         "liblog",
     ],
     static_libs: [
-        "libc++fs",
-        "libtinyxml2",
+        "libmodules-utils-build",
     ],
     tidy: true,
     tidy_disabled_srcs: [":art-apex-cache-info"],
@@ -112,6 +112,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -123,6 +124,7 @@
     local_include_dirs: ["include"],
     header_libs: ["libbase_headers"],
     srcs: ["odrefresh_broken.cc"],
+    installable: false,
     apex_available: ["test_jitzygote_com.android.art"],
 }
 
@@ -146,6 +148,7 @@
         // apex_available lists need to be the same for internal libs to avoid
         // stubs, and this depends on libartd.
         "com.android.art",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -155,6 +158,7 @@
     host_supported: true,
     export_include_dirs: ["include"],
     local_include_dirs: ["include"],
+    header_libs: ["libart_headers"],
     shared_libs: ["libartbase"],
     target: {
         android: {
diff --git a/odrefresh/odr_artifacts.h b/odrefresh/odr_artifacts.h
index 30932e2..9b2e973 100644
--- a/odrefresh/odr_artifacts.h
+++ b/odrefresh/odr_artifacts.h
@@ -29,20 +29,22 @@
 class OdrArtifacts {
  public:
   static OdrArtifacts ForBootImage(const std::string& image_path) {
-    return OdrArtifacts(image_path, "oat");
+    return OdrArtifacts(image_path, /*image_kind=*/"image", /*aot_extension=*/"oat");
   }
 
   static OdrArtifacts ForSystemServer(const std::string& image_path) {
-    return OdrArtifacts(image_path, "odex");
+    return OdrArtifacts(image_path, /*image_kind=*/"app-image", /*aot_extension=*/"odex");
   }
 
   const std::string& ImagePath() const { return image_path_; }
+  const char* ImageKind() const { return image_kind_; }
   const std::string& OatPath() const { return oat_path_; }
   const std::string& VdexPath() const { return vdex_path_; }
 
  private:
-  OdrArtifacts(const std::string& image_path, const char* aot_extension)
+  OdrArtifacts(const std::string& image_path, const char* image_kind, const char* aot_extension)
       : image_path_{image_path},
+        image_kind_{image_kind},
         oat_path_{ReplaceFileExtension(image_path, aot_extension)},
         vdex_path_{ReplaceFileExtension(image_path, "vdex")} {}
 
@@ -51,6 +53,7 @@
   OdrArtifacts& operator=(const OdrArtifacts&) = delete;
 
   const std::string image_path_;
+  const char* image_kind_;
   const std::string oat_path_;
   const std::string vdex_path_;
 };
diff --git a/odrefresh/odr_common.cc b/odrefresh/odr_common.cc
index 94674bc..ec18884 100644
--- a/odrefresh/odr_common.cc
+++ b/odrefresh/odr_common.cc
@@ -28,6 +28,7 @@
 #include "android-base/logging.h"
 #include "android-base/parseint.h"
 #include "android-base/result.h"
+#include "fmt/format.h"
 
 namespace art {
 namespace odrefresh {
@@ -36,19 +37,10 @@
 
 using ::android::base::Result;
 
+using ::fmt::literals::operator""_format;  // NOLINT
 }
 
-std::string Concatenate(std::initializer_list<std::string_view> args) {
-  std::stringstream ss;
-  for (auto arg : args) {
-    ss << arg;
-  }
-  return ss.str();
-}
-
-std::string QuotePath(std::string_view path) {
-  return Concatenate({"'", path, "'"});
-}
+std::string QuotePath(std::string_view path) { return "'{}'"_format(path); }
 
 Result<int> ParseSecurityPatchStr(const std::string& security_patch_str) {
   std::regex security_patch_regex(R"re((\d{4})-(\d{2})-(\d{2}))re");
diff --git a/odrefresh/odr_common.h b/odrefresh/odr_common.h
index 1257ab7..e55258f 100644
--- a/odrefresh/odr_common.h
+++ b/odrefresh/odr_common.h
@@ -26,9 +26,6 @@
 namespace art {
 namespace odrefresh {
 
-// Concatenates a list of strings into a single string.
-std::string Concatenate(std::initializer_list<std::string_view> args);
-
 // Quotes a path with single quotes (').
 std::string QuotePath(std::string_view path);
 
diff --git a/odrefresh/odr_compilation_log.cc b/odrefresh/odr_compilation_log.cc
index 9c50817..0c8dda88 100644
--- a/odrefresh/odr_compilation_log.cc
+++ b/odrefresh/odr_compilation_log.cc
@@ -185,6 +185,12 @@
     return true;
   }
 
+  // The backoff time is for avoiding too many failed attempts. It should not be applied if the last
+  // compilation was successful.
+  if (entries_.back().exit_code == ExitCode::kCompilationSuccess) {
+    return true;
+  }
+
   if (trigger == OdrMetrics::Trigger::kApexVersionMismatch ||
       trigger == OdrMetrics::Trigger::kDexFilesChanged) {
     // Things have changed since the last run.
diff --git a/odrefresh/odr_compilation_log_test.cc b/odrefresh/odr_compilation_log_test.cc
index 46cea79..f28d849 100644
--- a/odrefresh/odr_compilation_log_test.cc
+++ b/odrefresh/odr_compilation_log_test.cc
@@ -109,7 +109,7 @@
       /*apex_version=*/1,
       /*last_update_millis=*/762,
       OdrMetrics::Trigger::kApexVersionMismatch,
-      ExitCode::kCompilationSuccess);
+      ExitCode::kCompilationFailed);
   ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kApexVersionMismatch));
   ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kDexFilesChanged));
   ASSERT_FALSE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown));
@@ -180,8 +180,8 @@
           OdrMetrics::Trigger::kApexVersionMismatch,
           start_time,
           ExitCode::kCompilationSuccess);
-  ASSERT_FALSE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
-  ASSERT_FALSE(
+  ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
+  ASSERT_TRUE(
       ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay / 4));
   ASSERT_TRUE(
       ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay / 2));
@@ -382,7 +382,7 @@
               kLastUpdateMillis,
               OdrMetrics::Trigger::kApexVersionMismatch,
               start_time,
-              ExitCode::kCompilationSuccess);
+              ExitCode::kCompilationFailed);
       ASSERT_FALSE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
     }
   }
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index 0475466..d861721 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -21,6 +21,7 @@
 #include <optional>
 #include <string>
 #include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 #include "android-base/file.h"
@@ -40,6 +41,22 @@
 // everything if any property matching a prefix changes.
 constexpr const char* kCheckedSystemPropertyPrefixes[]{"dalvik.vm.", "ro.dalvik.vm."};
 
+// System property for the phenotype flag to override the device or default-configured
+// system server compiler filter setting.
+static constexpr char kSystemPropertySystemServerCompilerFilterOverride[] =
+    "persist.device_config.runtime_native_boot.systemservercompilerfilter_override";
+
+// The list of system properties that odrefresh ignores. They don't affect compilation results.
+const std::unordered_set<std::string> kIgnoredSystemProperties{
+    "dalvik.vm.dex2oat-cpu-set",
+    "dalvik.vm.dex2oat-threads",
+    "dalvik.vm.boot-dex2oat-cpu-set",
+    "dalvik.vm.boot-dex2oat-threads",
+    "dalvik.vm.restore-dex2oat-cpu-set",
+    "dalvik.vm.restore-dex2oat-threads",
+    "dalvik.vm.background-dex2oat-cpu-set",
+    "dalvik.vm.background-dex2oat-threads"};
+
 struct SystemPropertyConfig {
   const char* name;
   const char* default_value;
@@ -55,7 +72,10 @@
 // requirement (go/platform-experiments-flags#pre-requisites).
 const android::base::NoDestructor<std::vector<SystemPropertyConfig>> kSystemProperties{
     {SystemPropertyConfig{.name = "persist.device_config.runtime_native_boot.enable_uffd_gc",
-                          .default_value = "false"}}};
+                          .default_value = ""},
+     SystemPropertyConfig{.name = kPhDisableCompactDex, .default_value = "false"},
+     SystemPropertyConfig{.name = kSystemPropertySystemServerCompilerFilterOverride,
+                          .default_value = ""}}};
 
 // An enumeration of the possible zygote configurations on Android.
 enum class ZygoteKind : uint8_t {
@@ -83,6 +103,7 @@
   InstructionSet isa_;
   std::string program_name_;
   std::string system_server_classpath_;
+  std::string boot_image_compiler_filter_;
   std::string system_server_compiler_filter_;
   ZygoteKind zygote_kind_;
   std::string boot_classpath_;
@@ -112,11 +133,18 @@
     const auto [isa32, isa64] = GetPotentialInstructionSets();
     switch (zygote_kind_) {
       case ZygoteKind::kZygote32:
+        CHECK_NE(isa32, art::InstructionSet::kNone);
         return {isa32};
       case ZygoteKind::kZygote32_64:
-      case ZygoteKind::kZygote64_32:
+        CHECK_NE(isa32, art::InstructionSet::kNone);
+        CHECK_NE(isa64, art::InstructionSet::kNone);
         return {isa32, isa64};
+      case ZygoteKind::kZygote64_32:
+        CHECK_NE(isa32, art::InstructionSet::kNone);
+        CHECK_NE(isa64, art::InstructionSet::kNone);
+        return {isa64, isa32};
       case ZygoteKind::kZygote64:
+        CHECK_NE(isa64, art::InstructionSet::kNone);
         return {isa64};
     }
   }
@@ -126,9 +154,11 @@
     switch (zygote_kind_) {
       case ZygoteKind::kZygote32:
       case ZygoteKind::kZygote32_64:
+        CHECK_NE(isa32, art::InstructionSet::kNone);
         return isa32;
       case ZygoteKind::kZygote64_32:
       case ZygoteKind::kZygote64:
+        CHECK_NE(isa64, art::InstructionSet::kNone);
         return isa64;
     }
   }
@@ -168,6 +198,9 @@
   const std::string& GetSystemServerClasspath() const {
     return system_server_classpath_;
   }
+  const std::string& GetBootImageCompilerFilter() const {
+    return boot_image_compiler_filter_;
+  }
   const std::string& GetSystemServerCompilerFilter() const {
     return system_server_compiler_filter_;
   }
@@ -204,6 +237,9 @@
     system_server_classpath_ = classpath;
   }
 
+  void SetBootImageCompilerFilter(const std::string& filter) {
+    boot_image_compiler_filter_ = filter;
+  }
   void SetSystemServerCompilerFilter(const std::string& filter) {
     system_server_compiler_filter_ = filter;
   }
@@ -248,6 +284,8 @@
       case art::InstructionSet::kX86:
       case art::InstructionSet::kX86_64:
         return std::make_pair(art::InstructionSet::kX86, art::InstructionSet::kX86_64);
+      case art::InstructionSet::kRiscv64:
+        return std::make_pair(art::InstructionSet::kNone, art::InstructionSet::kRiscv64);
       case art::InstructionSet::kThumb2:
       case art::InstructionSet::kNone:
         LOG(FATAL) << "Invalid instruction set " << isa_;
diff --git a/odrefresh/odr_fs_utils.cc b/odrefresh/odr_fs_utils.cc
index 22cc1b6..3ed8021 100644
--- a/odrefresh/odr_fs_utils.cc
+++ b/odrefresh/odr_fs_utils.cc
@@ -68,9 +68,12 @@
   }
 
   if (rmdir(dir_path.c_str()) != 0) {
-    LOG(ERROR) << "Failed to delete '" << dir_path << "'";
-    return false;
+    // It's possible that we are not able to remove the directory itself. For example, when
+    // odrefresh is running in CompOS, the staging dir is prepared beforehand passed to the VM as an
+    // FD. In this case, just log and ignore the error. It's okay to keep the directory.
+    LOG(WARNING) << "Failed to delete '" << dir_path << "'";
   }
+
   return true;
 }
 
diff --git a/odrefresh/odr_fs_utils_test.cc b/odrefresh/odr_fs_utils_test.cc
index d460be2..0adc1d1 100644
--- a/odrefresh/odr_fs_utils_test.cc
+++ b/odrefresh/odr_fs_utils_test.cc
@@ -136,12 +136,12 @@
   ASSERT_EQ(kFirstFileBytes, static_cast<decltype(kFirstFileBytes)>(sb.st_size));
 
   uint64_t bytes_used = 0;
-  ASSERT_TRUE(GetUsedSpace(scratch_dir.GetPath().c_str(), &bytes_used));
+  ASSERT_TRUE(GetUsedSpace(scratch_dir.GetPath(), &bytes_used));
 
   const std::string second_file_path = scratch_dir.GetPath() + "/2.dat";
   ASSERT_TRUE(CreateFile(second_file_path.c_str(), kSecondFileBytes));
 
-  ASSERT_TRUE(GetUsedSpace(scratch_dir.GetPath().c_str(), &bytes_used));
+  ASSERT_TRUE(GetUsedSpace(scratch_dir.GetPath(), &bytes_used));
   uint64_t expected_bytes_used = RoundUp(kFirstFileBytes, sb.st_blocks * kBytesPerBlock) +
                                  RoundUp(kSecondFileBytes, sb.st_blocks * kBytesPerBlock);
   ASSERT_EQ(expected_bytes_used, bytes_used);
@@ -152,7 +152,7 @@
     const std::string path = android::base::StringPrintf("%s/%zu", sub_dir_path.c_str(), i);
     ASSERT_TRUE(CreateFile(path.c_str(), i));
     expected_bytes_used += RoundUp(i, sb.st_blocks * kBytesPerBlock);
-    ASSERT_TRUE(GetUsedSpace(scratch_dir.GetPath().c_str(), &bytes_used));
+    ASSERT_TRUE(GetUsedSpace(scratch_dir.GetPath(), &bytes_used));
     ASSERT_EQ(expected_bytes_used, bytes_used);
   }
 }
diff --git a/odrefresh/odr_metrics.cc b/odrefresh/odr_metrics.cc
index 4bddb17..a4533d9 100644
--- a/odrefresh/odr_metrics.cc
+++ b/odrefresh/odr_metrics.cc
@@ -36,7 +36,7 @@
 namespace odrefresh {
 
 OdrMetrics::OdrMetrics(const std::string& cache_directory, const std::string& metrics_file)
-    : cache_directory_(cache_directory), metrics_file_(metrics_file), status_(Status::kOK) {
+    : cache_directory_(cache_directory), metrics_file_(metrics_file) {
   DCHECK(StartsWith(metrics_file_, "/"));
 
   // Remove existing metrics file if it exists.
@@ -56,36 +56,57 @@
 }
 
 OdrMetrics::~OdrMetrics() {
-  cache_space_free_end_mib_ = GetFreeSpaceMiB(cache_directory_);
+  CaptureSpaceFreeEnd();
 
-  // Log metrics only if odrefresh detected a reason to compile.
-  if (trigger_.has_value()) {
+  // Log metrics only if this is explicitly enabled (typically when compilation was done or an error
+  // occurred).
+  if (enabled_) {
     WriteToFile(metrics_file_, this);
   }
 }
 
-void OdrMetrics::SetCompilationTime(int32_t seconds) {
-  switch (stage_) {
+void OdrMetrics::CaptureSpaceFreeEnd() {
+  cache_space_free_end_mib_ = GetFreeSpaceMiB(cache_directory_);
+}
+
+void OdrMetrics::SetDex2OatResult(Stage stage,
+                                  int64_t compilation_time_ms,
+                                  const std::optional<ExecResult>& dex2oat_result) {
+  switch (stage) {
     case Stage::kPrimaryBootClasspath:
-      primary_bcp_compilation_seconds_ = seconds;
+      primary_bcp_compilation_millis_ = compilation_time_ms;
+      primary_bcp_dex2oat_result_ = dex2oat_result;
       break;
     case Stage::kSecondaryBootClasspath:
-      secondary_bcp_compilation_seconds_ = seconds;
+      secondary_bcp_compilation_millis_ = compilation_time_ms;
+      secondary_bcp_dex2oat_result_ = dex2oat_result;
       break;
     case Stage::kSystemServerClasspath:
-      system_server_compilation_seconds_ = seconds;
+      system_server_compilation_millis_ = compilation_time_ms;
+      system_server_dex2oat_result_ = dex2oat_result;
       break;
     case Stage::kCheck:
     case Stage::kComplete:
     case Stage::kPreparation:
     case Stage::kUnknown:
-      break;
+      LOG(FATAL) << "Unexpected stage " << stage_ << " when setting dex2oat result";
   }
 }
 
-void OdrMetrics::SetStage(Stage stage) {
-  if (status_ == Status::kOK) {
-    stage_ = stage;
+void OdrMetrics::SetBcpCompilationType(Stage stage, BcpCompilationType type) {
+  switch (stage) {
+    case Stage::kPrimaryBootClasspath:
+      primary_bcp_compilation_type_ = type;
+      break;
+    case Stage::kSecondaryBootClasspath:
+      secondary_bcp_compilation_type_ = type;
+      break;
+    case Stage::kSystemServerClasspath:
+    case Stage::kCheck:
+    case Stage::kComplete:
+    case Stage::kPreparation:
+    case Stage::kUnknown:
+      LOG(FATAL) << "Unexpected stage " << stage_ << " when setting BCP compilation type";
   }
 }
 
@@ -115,32 +136,43 @@
   return static_cast<int32_t>(free_space_mib);
 }
 
-bool OdrMetrics::ToRecord(/*out*/OdrMetricsRecord* record) const {
-  if (!trigger_.has_value()) {
-    return false;
+OdrMetricsRecord OdrMetrics::ToRecord() const {
+  return {
+      .odrefresh_metrics_version = kOdrefreshMetricsVersion,
+      .art_apex_version = art_apex_version_,
+      .trigger = static_cast<int32_t>(trigger_),
+      .stage_reached = static_cast<int32_t>(stage_),
+      .status = static_cast<int32_t>(status_),
+      .cache_space_free_start_mib = cache_space_free_start_mib_,
+      .cache_space_free_end_mib = cache_space_free_end_mib_,
+      .primary_bcp_compilation_millis = primary_bcp_compilation_millis_,
+      .secondary_bcp_compilation_millis = secondary_bcp_compilation_millis_,
+      .system_server_compilation_millis = system_server_compilation_millis_,
+      .primary_bcp_dex2oat_result = ConvertExecResult(primary_bcp_dex2oat_result_),
+      .secondary_bcp_dex2oat_result = ConvertExecResult(secondary_bcp_dex2oat_result_),
+      .system_server_dex2oat_result = ConvertExecResult(system_server_dex2oat_result_),
+      .primary_bcp_compilation_type = static_cast<int32_t>(primary_bcp_compilation_type_),
+      .secondary_bcp_compilation_type = static_cast<int32_t>(secondary_bcp_compilation_type_),
+  };
+}
+
+OdrMetricsRecord::Dex2OatExecResult OdrMetrics::ConvertExecResult(
+    const std::optional<ExecResult>& result) {
+  if (result.has_value()) {
+    return OdrMetricsRecord::Dex2OatExecResult(result.value());
+  } else {
+    return {};
   }
-  record->art_apex_version = art_apex_version_;
-  record->trigger = static_cast<uint32_t>(trigger_.value());
-  record->stage_reached = static_cast<uint32_t>(stage_);
-  record->status = static_cast<uint32_t>(status_);
-  record->primary_bcp_compilation_seconds = primary_bcp_compilation_seconds_;
-  record->secondary_bcp_compilation_seconds = secondary_bcp_compilation_seconds_;
-  record->system_server_compilation_seconds = system_server_compilation_seconds_;
-  record->cache_space_free_start_mib = cache_space_free_start_mib_;
-  record->cache_space_free_end_mib = cache_space_free_end_mib_;
-  return true;
 }
 
 void OdrMetrics::WriteToFile(const std::string& path, const OdrMetrics* metrics) {
-  OdrMetricsRecord record;
-  if (!metrics->ToRecord(&record)) {
-    LOG(ERROR) << "Attempting to report metrics without a compilation trigger.";
-    return;
-  }
+  OdrMetricsRecord record = metrics->ToRecord();
 
-  // Preserve order from frameworks/proto_logging/stats/atoms.proto in metrics file written.
-  std::ofstream ofs(path);
-  ofs << record;
+  const android::base::Result<void>& result = record.WriteToFile(path);
+  if (!result.ok()) {
+    LOG(ERROR) << "Failed to report metrics to file: " << path
+               << ", error: " << result.error().message();
+  }
 }
 
 }  // namespace odrefresh
diff --git a/odrefresh/odr_metrics.h b/odrefresh/odr_metrics.h
index cd80bef..4605277 100644
--- a/odrefresh/odr_metrics.h
+++ b/odrefresh/odr_metrics.h
@@ -24,6 +24,7 @@
 #include <string>
 
 #include "base/macros.h"
+#include "exec_utils.h"
 #include "odr_metrics_record.h"
 
 namespace art {
@@ -33,7 +34,8 @@
  public:
   // Enumeration used to track the latest stage reached running odrefresh.
   //
-  // These values mirror those in OdrefreshReported::Stage in frameworks/proto_logging/atoms.proto.
+  // These values mirror those in OdrefreshReported::Stage in
+  // frameworks/proto_logging/atoms/art/odrefresh_extension_atoms.proto.
   // NB There are gaps between the values in case an additional stages are introduced.
   enum class Stage : uint8_t {
     kUnknown = 0,
@@ -47,22 +49,26 @@
 
   // Enumeration describing the overall status, processing stops on the first error discovered.
   //
-  // These values mirror those in OdrefreshReported::Status in frameworks/proto_logging/atoms.proto.
+  // These values mirror those in OdrefreshReported::Status in
+  // frameworks/proto_logging/atoms/art/odrefresh_extension_atoms.proto.
   enum class Status : uint8_t {
     kUnknown = 0,
     kOK = 1,
     kNoSpace = 2,
     kIoError = 3,
     kDex2OatError = 4,
-    kTimeLimitExceeded = 5,
+    // Value 5 was kTimeLimitExceeded, but has been removed in favour of
+    // reporting the exit code for Dex2Oat (set to ExecResult::kTimedOut)
     kStagingFailed = 6,
     kInstallFailed = 7,
+    // Failed to access the dalvik-cache directory due to lack of permission.
+    kDalvikCachePermissionDenied = 8,
   };
 
   // Enumeration describing the cause of compilation (if any) in odrefresh.
   //
   // These values mirror those in OdrefreshReported::Trigger in
-  // frameworks/proto_logging/atoms.proto.
+  // frameworks/proto_logging/atoms/art/odrefresh_extension_atoms.proto.
   enum class Trigger : uint8_t {
     kUnknown = 0,
     kApexVersionMismatch = 1,
@@ -70,24 +76,33 @@
     kMissingArtifacts = 3,
   };
 
+  // Enumeration describing the type of boot classpath compilation in odrefresh.
+  //
+  // These values mirror those in OdrefreshReported::BcpCompilationType in
+  // frameworks/proto_logging/atoms/art/odrefresh_extension_atoms.proto.
+  enum class BcpCompilationType : uint8_t {
+    kUnknown = 0,
+    // Compiles for both the primary boot image and the mainline extension.
+    kPrimaryAndMainline = 1,
+    // Only compiles for the mainline extension.
+    kMainline = 2,
+  };
+
   explicit OdrMetrics(const std::string& cache_directory,
                       const std::string& metrics_file = kOdrefreshMetricsFile);
   ~OdrMetrics();
 
+  // Enables/disables metrics writing.
+  void SetEnabled(bool value) { enabled_ = value; }
+
   // Gets the ART APEX that metrics are being collected on behalf of.
-  int64_t GetArtApexVersion() const {
-    return art_apex_version_;
-  }
+  int64_t GetArtApexVersion() const { return art_apex_version_; }
 
   // Sets the ART APEX that metrics are being collected on behalf of.
-  void SetArtApexVersion(int64_t version) {
-    art_apex_version_ = version;
-  }
+  void SetArtApexVersion(int64_t version) { art_apex_version_ = version; }
 
   // Gets the ART APEX last update time in milliseconds.
-  int64_t GetArtApexLastUpdateMillis() const {
-    return art_apex_last_update_millis_;
-  }
+  int64_t GetArtApexLastUpdateMillis() const { return art_apex_last_update_millis_; }
 
   // Sets the ART APEX last update time in milliseconds.
   void SetArtApexLastUpdateMillis(int64_t last_update_millis) {
@@ -96,28 +111,32 @@
 
   // Gets the trigger for metrics collection. The trigger is the reason why odrefresh considers
   // compilation necessary.
-  Trigger GetTrigger() const {
-    return trigger_.has_value() ? trigger_.value() : Trigger::kUnknown;
-  }
+  Trigger GetTrigger() const { return trigger_; }
 
   // Sets the trigger for metrics collection. The trigger is the reason why odrefresh considers
   // compilation necessary. Only call this method if compilation is necessary as the presence
   // of a trigger means we will try to record and upload metrics.
-  void SetTrigger(const Trigger trigger) {
-    trigger_ = trigger;
-  }
+  void SetTrigger(const Trigger trigger) { trigger_ = trigger; }
 
   // Sets the execution status of the current odrefresh processing stage.
-  void SetStatus(const Status status) {
-    status_ = status;
-  }
+  void SetStatus(const Status status) { status_ = status; }
 
   // Sets the current odrefresh processing stage.
-  void SetStage(Stage stage);
+  void SetStage(Stage stage) { stage_ = stage; }
 
-  // Record metrics into an OdrMetricsRecord.
-  // returns true on success, false if instance is not valid (because the trigger value is not set).
-  bool ToRecord(/*out*/OdrMetricsRecord* record) const;
+  // Sets the result of the current dex2oat invocation.
+  void SetDex2OatResult(Stage stage,
+                        int64_t compilation_time,
+                        const std::optional<ExecResult>& dex2oat_result);
+
+  // Sets the BCP compilation type.
+  void SetBcpCompilationType(Stage stage, BcpCompilationType type);
+
+  // Captures the current free space as the end free space.
+  void CaptureSpaceFreeEnd();
+
+  // Records metrics into an OdrMetricsRecord.
+  OdrMetricsRecord ToRecord() const;
 
  private:
   OdrMetrics(const OdrMetrics&) = delete;
@@ -126,44 +145,47 @@
   static int32_t GetFreeSpaceMiB(const std::string& path);
   static void WriteToFile(const std::string& path, const OdrMetrics* metrics);
 
-  void SetCompilationTime(int32_t seconds);
+  static OdrMetricsRecord::Dex2OatExecResult
+  ConvertExecResult(const std::optional<ExecResult>& result);
 
   const std::string cache_directory_;
   const std::string metrics_file_;
 
+  bool enabled_ = false;
+
   int64_t art_apex_version_ = 0;
   int64_t art_apex_last_update_millis_ = 0;
-  std::optional<Trigger> trigger_ = {};  // metrics are only logged if compilation is triggered.
+  Trigger trigger_ = Trigger::kUnknown;
   Stage stage_ = Stage::kUnknown;
   Status status_ = Status::kUnknown;
 
-  int32_t primary_bcp_compilation_seconds_ = 0;
-  int32_t secondary_bcp_compilation_seconds_ = 0;
-  int32_t system_server_compilation_seconds_ = 0;
   int32_t cache_space_free_start_mib_ = 0;
   int32_t cache_space_free_end_mib_ = 0;
 
-  friend class ScopedOdrCompilationTimer;
-};
+  // The total time spent on compiling primary BCP.
+  int32_t primary_bcp_compilation_millis_ = 0;
 
-// Timer used to measure compilation time (in seconds). Automatically associates the time recorded
-// with the current stage of the metrics used.
-class ScopedOdrCompilationTimer final {
- public:
-  explicit ScopedOdrCompilationTimer(OdrMetrics& metrics) :
-    metrics_(metrics), start_(std::chrono::steady_clock::now()) {}
+  // The result of the dex2oat invocation for compiling primary BCP, or `std::nullopt` if dex2oat is
+  // not invoked.
+  std::optional<ExecResult> primary_bcp_dex2oat_result_;
 
-  ~ScopedOdrCompilationTimer() {
-    auto elapsed_time = std::chrono::steady_clock::now() - start_;
-    auto elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed_time);
-    metrics_.SetCompilationTime(static_cast<int32_t>(elapsed_seconds.count()));
-  }
+  BcpCompilationType primary_bcp_compilation_type_ = BcpCompilationType::kUnknown;
 
- private:
-  OdrMetrics& metrics_;
-  std::chrono::time_point<std::chrono::steady_clock> start_;
+  // The total time spent on compiling secondary BCP.
+  int32_t secondary_bcp_compilation_millis_ = 0;
 
-  DISALLOW_ALLOCATION();
+  // The result of the dex2oat invocation for compiling secondary BCP, or `std::nullopt` if dex2oat
+  // is not invoked.
+  std::optional<ExecResult> secondary_bcp_dex2oat_result_;
+
+  BcpCompilationType secondary_bcp_compilation_type_ = BcpCompilationType::kUnknown;
+
+  // The total time spent on compiling system server.
+  int32_t system_server_compilation_millis_ = 0;
+
+  // The result of the last dex2oat invocation for compiling system server, or `std::nullopt` if
+  // dex2oat is not invoked.
+  std::optional<ExecResult> system_server_dex2oat_result_;
 };
 
 // Generated ostream operators.
diff --git a/odrefresh/odr_metrics_record.cc b/odrefresh/odr_metrics_record.cc
index fc135d3..74a69cc 100644
--- a/odrefresh/odr_metrics_record.cc
+++ b/odrefresh/odr_metrics_record.cc
@@ -17,56 +17,164 @@
 #include "odr_metrics_record.h"
 
 #include <iosfwd>
-#include <istream>
-#include <ostream>
-#include <streambuf>
 #include <string>
 
+#include "android-base/logging.h"
+#include "tinyxml2.h"
+
 namespace art {
 namespace odrefresh {
 
-std::istream& operator>>(std::istream& is, OdrMetricsRecord& record) {
-  // Block I/O related exceptions
-  auto saved_exceptions = is.exceptions();
-  is.exceptions(std::ios_base::iostate {});
+namespace {
+android::base::Result<int64_t> ReadInt64(tinyxml2::XMLElement* parent, const char* name) {
+  tinyxml2::XMLElement* element = parent->FirstChildElement(name);
+  if (element == nullptr) {
+    return Errorf("Expected Odrefresh metric {} not found", name);
+  }
 
-  // The order here matches the field order of MetricsRecord.
-  is >> record.art_apex_version >> std::ws;
-  is >> record.trigger >> std::ws;
-  is >> record.stage_reached >> std::ws;
-  is >> record.status >> std::ws;
-  is >> record.primary_bcp_compilation_seconds >> std::ws;
-  is >> record.secondary_bcp_compilation_seconds >> std::ws;
-  is >> record.system_server_compilation_seconds >> std::ws;
-  is >> record.cache_space_free_start_mib >> std::ws;
-  is >> record.cache_space_free_end_mib >> std::ws;
-
-  // Restore I/O related exceptions
-  is.exceptions(saved_exceptions);
-  return is;
+  int64_t metric;
+  tinyxml2::XMLError result = element->QueryInt64Text(&metric);
+  if (result == tinyxml2::XML_SUCCESS) {
+    return metric;
+  } else {
+    return Errorf("Odrefresh metric {} is not an int64", name);
+  }
 }
 
-std::ostream& operator<<(std::ostream& os, const OdrMetricsRecord& record) {
-  static const char kSpace = ' ';
+android::base::Result<int32_t> ReadInt32(tinyxml2::XMLElement* parent, const char* name) {
+  tinyxml2::XMLElement* element = parent->FirstChildElement(name);
+  if (element == nullptr) {
+    return Errorf("Expected Odrefresh metric {} not found", name);
+  }
 
-  // Block I/O related exceptions
-  auto saved_exceptions = os.exceptions();
-  os.exceptions(std::ios_base::iostate {});
+  int32_t metric;
+  tinyxml2::XMLError result = element->QueryIntText(&metric);
+  if (result == tinyxml2::XML_SUCCESS) {
+    return metric;
+  } else {
+    return Errorf("Odrefresh metric {} is not an int32", name);
+  }
+}
+
+android::base::Result<int32_t> ReadInt32Attribute(tinyxml2::XMLElement* element,
+                                                  const char* element_name,
+                                                  const char* attribute_name,
+                                                  int min_value,
+                                                  int max_value) {
+  int32_t value;
+  tinyxml2::XMLError result = element->QueryAttribute(attribute_name, &value);
+  if (result != tinyxml2::XML_SUCCESS) {
+    return Errorf("Expected Odrefresh metric {}.{} is not an int32", element_name, attribute_name);
+  }
+
+  if (value < min_value || value > max_value) {
+    return Errorf(
+        "Odrefresh metric {}.{} has a value ({}) outside of the expected range ([{}, {}])",
+        element_name,
+        attribute_name,
+        value,
+        min_value,
+        max_value);
+  }
+
+  return value;
+}
+
+android::base::Result<OdrMetricsRecord::Dex2OatExecResult> ReadExecResult(
+    tinyxml2::XMLElement* parent, const char* nodeName) {
+  tinyxml2::XMLElement* element = parent->FirstChildElement(nodeName);
+  if (element == nullptr) {
+    return Errorf("Expected Odrefresh metric {} not found", nodeName);
+  }
+
+  return OdrMetricsRecord::Dex2OatExecResult(
+      OR_RETURN(ReadInt32Attribute(element, nodeName, "status", 0, kExecResultNotRun)),
+      OR_RETURN(ReadInt32Attribute(element, nodeName, "exit-code", -1, 255)),
+      OR_RETURN(ReadInt32Attribute(element, nodeName, "signal", 0, SIGRTMAX)));
+}
+
+template <typename T>
+void AddMetric(tinyxml2::XMLElement* parent, const char* name, const T& value) {
+  parent->InsertNewChildElement(name)->SetText(value);
+}
+
+void AddResult(tinyxml2::XMLElement* parent,
+               const char* name,
+               const OdrMetricsRecord::Dex2OatExecResult& execResult) {
+  tinyxml2::XMLElement* result = parent->InsertNewChildElement(name);
+  result->SetAttribute("status", execResult.status);
+  result->SetAttribute("exit-code", execResult.exit_code);
+  result->SetAttribute("signal", execResult.signal);
+}
+}  // namespace
+
+android::base::Result<void> OdrMetricsRecord::ReadFromFile(const std::string& filename) {
+  tinyxml2::XMLDocument xml_document;
+  tinyxml2::XMLError result = xml_document.LoadFile(filename.data());
+  if (result != tinyxml2::XML_SUCCESS) {
+    return android::base::Error() << xml_document.ErrorStr();
+  }
+
+  tinyxml2::XMLElement* metrics = xml_document.FirstChildElement("odrefresh_metrics");
+  if (metrics == nullptr) {
+    return Errorf("odrefresh_metrics element not found in {}", filename);
+  }
+
+  odrefresh_metrics_version = OR_RETURN(ReadInt32(metrics, "odrefresh_metrics_version"));
+  if (odrefresh_metrics_version != kOdrefreshMetricsVersion) {
+    return Errorf("odrefresh_metrics_version {} is different than expected ({})",
+                  odrefresh_metrics_version,
+                  kOdrefreshMetricsVersion);
+  }
+
+  art_apex_version = OR_RETURN(ReadInt64(metrics, "art_apex_version"));
+  trigger = OR_RETURN(ReadInt32(metrics, "trigger"));
+  stage_reached = OR_RETURN(ReadInt32(metrics, "stage_reached"));
+  status = OR_RETURN(ReadInt32(metrics, "status"));
+  cache_space_free_start_mib = OR_RETURN(ReadInt32(metrics, "cache_space_free_start_mib"));
+  cache_space_free_end_mib = OR_RETURN(ReadInt32(metrics, "cache_space_free_end_mib"));
+  primary_bcp_compilation_millis = OR_RETURN(ReadInt32(metrics, "primary_bcp_compilation_millis"));
+  secondary_bcp_compilation_millis =
+      OR_RETURN(ReadInt32(metrics, "secondary_bcp_compilation_millis"));
+  system_server_compilation_millis =
+      OR_RETURN(ReadInt32(metrics, "system_server_compilation_millis"));
+  primary_bcp_dex2oat_result = OR_RETURN(ReadExecResult(metrics, "primary_bcp_dex2oat_result"));
+  secondary_bcp_dex2oat_result = OR_RETURN(ReadExecResult(metrics, "secondary_bcp_dex2oat_result"));
+  system_server_dex2oat_result = OR_RETURN(ReadExecResult(metrics, "system_server_dex2oat_result"));
+  primary_bcp_compilation_type = OR_RETURN(ReadInt32(metrics, "primary_bcp_compilation_type"));
+  secondary_bcp_compilation_type = OR_RETURN(ReadInt32(metrics, "secondary_bcp_compilation_type"));
+
+  return {};
+}
+
+android::base::Result<void> OdrMetricsRecord::WriteToFile(const std::string& filename) const {
+  tinyxml2::XMLDocument xml_document;
+  tinyxml2::XMLElement* metrics = xml_document.NewElement("odrefresh_metrics");
+  xml_document.InsertEndChild(metrics);
 
   // The order here matches the field order of MetricsRecord.
-  os << record.art_apex_version << kSpace;
-  os << record.trigger << kSpace;
-  os << record.stage_reached << kSpace;
-  os << record.status << kSpace;
-  os << record.primary_bcp_compilation_seconds << kSpace;
-  os << record.secondary_bcp_compilation_seconds << kSpace;
-  os << record.system_server_compilation_seconds << kSpace;
-  os << record.cache_space_free_start_mib << kSpace;
-  os << record.cache_space_free_end_mib << std::endl;
+  AddMetric(metrics, "odrefresh_metrics_version", odrefresh_metrics_version);
+  AddMetric(metrics, "art_apex_version", art_apex_version);
+  AddMetric(metrics, "trigger", trigger);
+  AddMetric(metrics, "stage_reached", stage_reached);
+  AddMetric(metrics, "status", status);
+  AddMetric(metrics, "cache_space_free_start_mib", cache_space_free_start_mib);
+  AddMetric(metrics, "cache_space_free_end_mib", cache_space_free_end_mib);
+  AddMetric(metrics, "primary_bcp_compilation_millis", primary_bcp_compilation_millis);
+  AddMetric(metrics, "secondary_bcp_compilation_millis", secondary_bcp_compilation_millis);
+  AddMetric(metrics, "system_server_compilation_millis", system_server_compilation_millis);
+  AddResult(metrics, "primary_bcp_dex2oat_result", primary_bcp_dex2oat_result);
+  AddResult(metrics, "secondary_bcp_dex2oat_result", secondary_bcp_dex2oat_result);
+  AddResult(metrics, "system_server_dex2oat_result", system_server_dex2oat_result);
+  AddMetric(metrics, "primary_bcp_compilation_type", primary_bcp_compilation_type);
+  AddMetric(metrics, "secondary_bcp_compilation_type", secondary_bcp_compilation_type);
 
-  // Restore I/O related exceptions
-  os.exceptions(saved_exceptions);
-  return os;
+  tinyxml2::XMLError result = xml_document.SaveFile(filename.data(), /*compact=*/true);
+  if (result == tinyxml2::XML_SUCCESS) {
+    return {};
+  } else {
+    return android::base::Error() << xml_document.ErrorStr();
+  }
 }
 
 }  // namespace odrefresh
diff --git a/odrefresh/odr_metrics_record.h b/odrefresh/odr_metrics_record.h
index 9dd51a6..4b18edb 100644
--- a/odrefresh/odr_metrics_record.h
+++ b/odrefresh/odr_metrics_record.h
@@ -20,43 +20,70 @@
 #include <cstdint>
 #include <iosfwd>  // For forward-declaration of std::string.
 
+#include "android-base/result.h"
+#include "exec_utils.h"
+#include "tinyxml2.h"
+
 namespace art {
 namespace odrefresh {
 
 // Default location for storing metrics from odrefresh.
-constexpr const char* kOdrefreshMetricsFile = "/data/misc/odrefresh/odrefresh-metrics.txt";
+constexpr const char* kOdrefreshMetricsFile = "/data/misc/odrefresh/odrefresh-metrics.xml";
+
+// Initial OdrefreshMetrics version
+static constexpr int32_t kOdrefreshMetricsVersion = 4;
+
+// Constant value used in ExecResult when the process was not run at all.
+// Mirrors EXEC_RESULT_STATUS_NOT_RUN contained in frameworks/proto_logging/atoms.proto.
+static constexpr int32_t kExecResultNotRun = 5;
+static_assert(kExecResultNotRun > ExecResult::Status::kLast,
+              "`art::odrefresh::kExecResultNotRun` value should not overlap with"
+              " values of enum `art::ExecResult::Status`");
+
 
 // MetricsRecord is a simpler container for Odrefresh metric values reported to statsd. The order
 // and types of fields here mirror definition of `OdrefreshReported` in
 // frameworks/proto_logging/stats/atoms.proto.
 struct OdrMetricsRecord {
+  struct Dex2OatExecResult {
+    int32_t status;
+    int32_t exit_code;
+    int32_t signal;
+
+    explicit Dex2OatExecResult(int32_t status, int32_t exit_code, int32_t signal)
+      : status(status), exit_code(exit_code), signal(signal) {}
+
+    explicit Dex2OatExecResult(const ExecResult& result)
+      : status(result.status), exit_code(result.exit_code), signal(result.signal) {}
+
+    Dex2OatExecResult() : status(kExecResultNotRun), exit_code(-1), signal(0) {}
+  };
+
+  int32_t odrefresh_metrics_version;
   int64_t art_apex_version;
   int32_t trigger;
   int32_t stage_reached;
   int32_t status;
-  int32_t primary_bcp_compilation_seconds;
-  int32_t secondary_bcp_compilation_seconds;
-  int32_t system_server_compilation_seconds;
   int32_t cache_space_free_start_mib;
   int32_t cache_space_free_end_mib;
+  int32_t primary_bcp_compilation_millis;
+  int32_t secondary_bcp_compilation_millis;
+  int32_t system_server_compilation_millis;
+  Dex2OatExecResult primary_bcp_dex2oat_result;
+  Dex2OatExecResult secondary_bcp_dex2oat_result;
+  Dex2OatExecResult system_server_dex2oat_result;
+  int32_t primary_bcp_compilation_type;
+  int32_t secondary_bcp_compilation_type;
+
+  // Reads a `MetricsRecord` from an XML file.
+  // Returns an error if the XML document was not found or parsed correctly.
+  android::base::Result<void> ReadFromFile(const std::string& filename);
+
+  // Writes a `MetricsRecord` to an XML file.
+  // Returns an error if the XML document was not saved correctly.
+  android::base::Result<void> WriteToFile(const std::string& filename) const;
 };
 
-// Read a `MetricsRecord` from an `istream`.
-//
-// This method blocks istream related exceptions, the caller should check `is.fail()` is false after
-// calling.
-//
-// Returns `is`.
-std::istream& operator>>(std::istream& is, OdrMetricsRecord& record);
-
-// Write a `MetricsRecord` to an `ostream`.
-//
-// This method blocks ostream related exceptions, the caller should check `os.fail()` is false after
-// calling.
-//
-// Returns `os`
-std::ostream& operator<<(std::ostream& os, const OdrMetricsRecord& record);
-
 }  // namespace odrefresh
 }  // namespace art
 
diff --git a/odrefresh/odr_metrics_record_test.cc b/odrefresh/odr_metrics_record_test.cc
index dd739d6..8c24156 100644
--- a/odrefresh/odr_metrics_record_test.cc
+++ b/odrefresh/odr_metrics_record_test.cc
@@ -20,93 +20,237 @@
 
 #include <fstream>
 
+#include "android-base/result-gmock.h"
+#include "android-base/stringprintf.h"
 #include "base/common_art_test.h"
 
 namespace art {
 namespace odrefresh {
 
-class OdrMetricsRecordTest : public CommonArtTest {};
+class OdrMetricsRecordTest : public CommonArtTest {
+ protected:
+  void WriteFile() {
+    std::ofstream ofs(file_path_);
 
-TEST_F(OdrMetricsRecordTest, HappyPath) {
-  const OdrMetricsRecord expected {
-    .art_apex_version = 0x01233456'789abcde,
-    .trigger = 0x01020304,
-    .stage_reached = 0x11121314,
-    .status = 0x21222324,
-    .primary_bcp_compilation_seconds = 0x31323334,
-    .secondary_bcp_compilation_seconds = 0x41424344,
-    .system_server_compilation_seconds = 0x51525354,
-    .cache_space_free_start_mib = 0x61626364,
-    .cache_space_free_end_mib = 0x71727374
-  };
+    ofs << "<odrefresh_metrics>";
+    ofs << metrics_version_;
+    ofs << "<art_apex_version>81966764218039518</art_apex_version>";
+    ofs << "<trigger>16909060</trigger>";
+    ofs << "<stage_reached>286397204</stage_reached>";
+    ofs << status_;
+    ofs << "<cache_space_free_start_mib>1633837924</cache_space_free_start_mib>";
+    ofs << "<cache_space_free_end_mib>1903326068</cache_space_free_end_mib>";
+    ofs << "<primary_bcp_compilation_millis>825373492</primary_bcp_compilation_millis>";
+    ofs << "<secondary_bcp_compilation_millis>1094861636</secondary_bcp_compilation_millis>";
+    ofs << "<system_server_compilation_millis>1364349780</system_server_compilation_millis>";
+    ofs << primary_bcp_dex2oat_result_;
+    ofs << secondary_bcp_dex2oat_result_;
+    ofs << system_server_dex2oat_result_;
+    ofs << "</odrefresh_metrics>";
 
-  ScratchDir dir(/*keep_files=*/false);
-  std::string file_path = dir.GetPath() + "/metrics-record.txt";
-
-  {
-    std::ofstream ofs(file_path);
-    ofs << expected;
-    ASSERT_FALSE(ofs.fail());
     ofs.close();
   }
 
-  OdrMetricsRecord actual {};
-  {
-    std::ifstream ifs(file_path);
-    ifs >> actual;
-    ASSERT_TRUE(ifs.eof());
+  void SetUp() override {
+    CommonArtTest::SetUp();
+    scratch_dir_ = std::make_unique<ScratchDir>(/*keep_files=*/false);
+    file_path_ = scratch_dir_->GetPath() + "/metrics-record.xml";
   }
 
+  void TearDown() override { scratch_dir_.reset(); }
+
+  std::unique_ptr<ScratchDir> scratch_dir_;
+  std::string file_path_;
+  std::string metrics_version_ = android::base::StringPrintf(
+      "<odrefresh_metrics_version>%d</odrefresh_metrics_version>", kOdrefreshMetricsVersion);
+  std::string status_ = "<status>30</status>";
+  std::string primary_bcp_dex2oat_result_ =
+      R"(<primary_bcp_dex2oat_result status="1" exit-code="-1" signal="0" />)";
+  std::string secondary_bcp_dex2oat_result_ =
+      R"(<secondary_bcp_dex2oat_result status="2" exit-code="15" signal="0" />)";
+  std::string system_server_dex2oat_result_ =
+      R"(<system_server_dex2oat_result status="3" exit-code="-1" signal="9" />)";
+};
+
+using android::base::testing::HasError;
+using android::base::testing::Ok;
+using android::base::testing::WithMessage;
+
+TEST_F(OdrMetricsRecordTest, HappyPath) {
+  OdrMetricsRecord expected{};
+  expected.odrefresh_metrics_version = art::odrefresh::kOdrefreshMetricsVersion;
+  expected.art_apex_version = 0x01233456'789abcde;
+  expected.trigger = 0x01020304;
+  expected.stage_reached = 0x11121314;
+  expected.status = 0x21222324;
+  expected.cache_space_free_start_mib = 0x61626364;
+  expected.cache_space_free_end_mib = 0x71727374;
+  expected.primary_bcp_compilation_millis = 0x31323334;
+  expected.secondary_bcp_compilation_millis = 0x41424344;
+  expected.system_server_compilation_millis = 0x51525354;
+  expected.primary_bcp_dex2oat_result = OdrMetricsRecord::Dex2OatExecResult(1, -1, 0);
+  expected.secondary_bcp_dex2oat_result = OdrMetricsRecord::Dex2OatExecResult(2, 15, 0);
+  expected.system_server_dex2oat_result = OdrMetricsRecord::Dex2OatExecResult(3, -1, 9);
+  expected.primary_bcp_compilation_type = 0x82837192;
+  expected.secondary_bcp_compilation_type = 0x91827312;
+
+  ASSERT_THAT(expected.WriteToFile(file_path_), Ok());
+
+  OdrMetricsRecord actual{};
+  ASSERT_THAT(actual.ReadFromFile(file_path_), Ok());
+
+  ASSERT_EQ(expected.odrefresh_metrics_version, actual.odrefresh_metrics_version);
   ASSERT_EQ(expected.art_apex_version, actual.art_apex_version);
   ASSERT_EQ(expected.trigger, actual.trigger);
   ASSERT_EQ(expected.stage_reached, actual.stage_reached);
   ASSERT_EQ(expected.status, actual.status);
-  ASSERT_EQ(expected.primary_bcp_compilation_seconds, actual.primary_bcp_compilation_seconds);
-  ASSERT_EQ(expected.secondary_bcp_compilation_seconds, actual.secondary_bcp_compilation_seconds);
-  ASSERT_EQ(expected.system_server_compilation_seconds, actual.system_server_compilation_seconds);
   ASSERT_EQ(expected.cache_space_free_start_mib, actual.cache_space_free_start_mib);
   ASSERT_EQ(expected.cache_space_free_end_mib, actual.cache_space_free_end_mib);
+  ASSERT_EQ(expected.primary_bcp_compilation_millis, actual.primary_bcp_compilation_millis);
+  ASSERT_EQ(expected.secondary_bcp_compilation_millis, actual.secondary_bcp_compilation_millis);
+  ASSERT_EQ(expected.system_server_compilation_millis, actual.system_server_compilation_millis);
+  ASSERT_EQ(expected.primary_bcp_dex2oat_result.status, actual.primary_bcp_dex2oat_result.status);
+  ASSERT_EQ(expected.primary_bcp_dex2oat_result.exit_code,
+            actual.primary_bcp_dex2oat_result.exit_code);
+  ASSERT_EQ(expected.primary_bcp_dex2oat_result.signal, actual.primary_bcp_dex2oat_result.signal);
+  ASSERT_EQ(expected.secondary_bcp_dex2oat_result.status,
+            actual.secondary_bcp_dex2oat_result.status);
+  ASSERT_EQ(expected.secondary_bcp_dex2oat_result.exit_code,
+            actual.secondary_bcp_dex2oat_result.exit_code);
+  ASSERT_EQ(expected.secondary_bcp_dex2oat_result.signal,
+            actual.secondary_bcp_dex2oat_result.signal);
+  ASSERT_EQ(expected.system_server_dex2oat_result.status,
+            actual.system_server_dex2oat_result.status);
+  ASSERT_EQ(expected.system_server_dex2oat_result.exit_code,
+            actual.system_server_dex2oat_result.exit_code);
+  ASSERT_EQ(expected.system_server_dex2oat_result.signal,
+            actual.system_server_dex2oat_result.signal);
+  ASSERT_EQ(expected.primary_bcp_compilation_type, actual.primary_bcp_compilation_type);
+  ASSERT_EQ(expected.secondary_bcp_compilation_type, actual.secondary_bcp_compilation_type);
   ASSERT_EQ(0, memcmp(&expected, &actual, sizeof(expected)));
 }
 
 TEST_F(OdrMetricsRecordTest, EmptyInput) {
-  ScratchDir dir(/*keep_files=*/false);
-  std::string file_path = dir.GetPath() + "/metrics-record.txt";
-
-  std::ifstream ifs(file_path);
-  OdrMetricsRecord record;
-  ifs >> record;
-
-  ASSERT_TRUE(ifs.fail());
-  ASSERT_TRUE(!ifs);
+  OdrMetricsRecord record{};
+  ASSERT_THAT(record.ReadFromFile(file_path_), testing::Not(Ok()));
 }
 
-TEST_F(OdrMetricsRecordTest, ClosedInput) {
-  ScratchDir dir(/*keep_files=*/false);
-  std::string file_path = dir.GetPath() + "/metrics-record.txt";
-
-  std::ifstream ifs(file_path);
-  ifs.close();
-
-  OdrMetricsRecord record;
-  ifs >> record;
-
-  ASSERT_TRUE(ifs.fail());
-  ASSERT_TRUE(!ifs);
-}
-
-TEST_F(OdrMetricsRecordTest, ClosedOutput) {
-  ScratchDir dir(/*keep_files=*/false);
-  std::string file_path = dir.GetPath() + "/metrics-record.txt";
-
-  std::ofstream ofs(file_path);
+TEST_F(OdrMetricsRecordTest, UnexpectedInput) {
+  std::ofstream ofs(file_path_);
+  ofs << "<not_odrefresh_metrics></not_odrefresh_metrics>";
   ofs.close();
 
-  OdrMetricsRecord record {};
-  ofs << record;
+  OdrMetricsRecord record{};
+  ASSERT_THAT(record.ReadFromFile(file_path_),
+              HasError(WithMessage("odrefresh_metrics element not found in " + file_path_)));
+}
 
-  ASSERT_TRUE(ofs.fail());
-  ASSERT_TRUE(!ofs.good());
+TEST_F(OdrMetricsRecordTest, ExpectedElementNotFound) {
+  metrics_version_ = "<not_valid_metric>25</not_valid_metric>";
+  WriteFile();
+
+  OdrMetricsRecord record{};
+  ASSERT_THAT(
+      record.ReadFromFile(file_path_),
+      HasError(WithMessage("Expected Odrefresh metric odrefresh_metrics_version not found")));
+}
+
+TEST_F(OdrMetricsRecordTest, ExpectedAttributeNotFound) {
+  // Missing "status".
+  primary_bcp_dex2oat_result_ = R"(<primary_bcp_dex2oat_result exit-code="17" signal="18" />)";
+  WriteFile();
+
+  OdrMetricsRecord record{};
+  ASSERT_THAT(record.ReadFromFile(file_path_),
+              HasError(WithMessage(
+                  "Expected Odrefresh metric primary_bcp_dex2oat_result.status is not an int32")));
+}
+
+TEST_F(OdrMetricsRecordTest, UnexpectedOdrefreshMetricsVersion) {
+  metrics_version_ = "<odrefresh_metrics_version>0</odrefresh_metrics_version>";
+  WriteFile();
+
+  OdrMetricsRecord record{};
+  std::string expected_error = android::base::StringPrintf(
+      "odrefresh_metrics_version 0 is different than expected (%d)", kOdrefreshMetricsVersion);
+  ASSERT_THAT(record.ReadFromFile(file_path_), HasError(WithMessage(expected_error)));
+}
+
+TEST_F(OdrMetricsRecordTest, UnexpectedType) {
+  status_ = "<status>abcd</status>";  // It should be an int32.
+  WriteFile();
+
+  OdrMetricsRecord record{};
+  ASSERT_THAT(record.ReadFromFile(file_path_),
+              HasError(WithMessage("Odrefresh metric status is not an int32")));
+}
+
+TEST_F(OdrMetricsRecordTest, ResultStatusOutsideOfRange) {
+  // Status is valid between 0 and 5 (5 being NOT_RUN)
+  primary_bcp_dex2oat_result_ =
+      R"(<primary_bcp_dex2oat_result status="-1" exit-code="-1" signal="0" />)";
+  WriteFile();
+
+  OdrMetricsRecord record{};
+  ASSERT_THAT(
+      record.ReadFromFile(file_path_),
+      HasError(WithMessage("Odrefresh metric primary_bcp_dex2oat_result.status has a value (-1) "
+                           "outside of the expected range ([0, 5])")));
+
+  primary_bcp_dex2oat_result_ =
+      R"(<primary_bcp_dex2oat_result status="9" exit-code="-1" signal="0" />)";
+  WriteFile();
+
+  ASSERT_THAT(
+      record.ReadFromFile(file_path_),
+      HasError(WithMessage("Odrefresh metric primary_bcp_dex2oat_result.status has a value (9) "
+                           "outside of the expected range ([0, 5])")));
+}
+
+TEST_F(OdrMetricsRecordTest, ResultExitCodeOutsideOfRange) {
+  // Exit Code is valid between -1 and 255
+  secondary_bcp_dex2oat_result_ =
+      R"(<secondary_bcp_dex2oat_result status="2" exit-code="-2" signal="0" />)";
+  WriteFile();
+
+  OdrMetricsRecord record{};
+  ASSERT_THAT(record.ReadFromFile(file_path_),
+              HasError(WithMessage(
+                  "Odrefresh metric secondary_bcp_dex2oat_result.exit-code has a value (-2) "
+                  "outside of the expected range ([-1, 255])")));
+
+  secondary_bcp_dex2oat_result_ =
+      R"(<secondary_bcp_dex2oat_result status="2" exit-code="258" signal="0" />)";
+  WriteFile();
+
+  ASSERT_THAT(record.ReadFromFile(file_path_),
+              HasError(WithMessage(
+                  "Odrefresh metric secondary_bcp_dex2oat_result.exit-code has a value (258) "
+                  "outside of the expected range ([-1, 255])")));
+}
+
+TEST_F(OdrMetricsRecordTest, ResultSignalOutsideOfRange) {
+  // Signal is valid between 0 and SIGRTMAX
+  system_server_dex2oat_result_ =
+      R"(<system_server_dex2oat_result status="3" exit-code="0" signal="-6" />)";
+  WriteFile();
+
+  OdrMetricsRecord record{};
+  ASSERT_THAT(record.ReadFromFile(file_path_),
+              HasError(WithMessage(android::base::StringPrintf(
+                  "Odrefresh metric system_server_dex2oat_result.signal has a value (-6) "
+                  "outside of the expected range ([0, %d])",
+                  SIGRTMAX))));
+
+  system_server_dex2oat_result_ =
+      R"(<system_server_dex2oat_result status="3" exit-code="0" signal="65" />)";
+  WriteFile();
+
+  ASSERT_THAT(record.ReadFromFile(file_path_),
+              HasError(WithMessage(android::base::StringPrintf(
+                  "Odrefresh metric system_server_dex2oat_result.signal has a value (65) "
+                  "outside of the expected range ([0, %d])",
+                  SIGRTMAX))));
 }
 
 }  // namespace odrefresh
diff --git a/odrefresh/odr_metrics_test.cc b/odrefresh/odr_metrics_test.cc
index 4519f00..64f3c59 100644
--- a/odrefresh/odr_metrics_test.cc
+++ b/odrefresh/odr_metrics_test.cc
@@ -15,27 +15,32 @@
  */
 
 #include "odr_metrics.h"
-#include "base/casts.h"
-#include "odr_metrics_record.h"
 
 #include <unistd.h>
 
+#include <chrono>
+#include <cstdint>
 #include <fstream>
 #include <memory>
 #include <string>
+#include <thread>
 
+#include "base/casts.h"
 #include "base/common_art_test.h"
+#include "odr_metrics_record.h"
 
 namespace art {
 namespace odrefresh {
 
+using std::chrono_literals::operator""ms;  // NOLINT
+
 class OdrMetricsTest : public CommonArtTest {
  public:
   void SetUp() override {
     CommonArtTest::SetUp();
 
     scratch_dir_ = std::make_unique<ScratchDir>();
-    metrics_file_path_ = scratch_dir_->GetPath() + "/metrics.txt";
+    metrics_file_path_ = scratch_dir_->GetPath() + "/metrics.xml";
     cache_directory_ = scratch_dir_->GetPath() + "/dir";
     mkdir(cache_directory_.c_str(), S_IRWXU);
   }
@@ -49,14 +54,6 @@
     return OS::FileExists(path);
   }
 
-  bool RemoveMetricsFile() const {
-    const char* path = metrics_file_path_.c_str();
-    if (OS::FileExists(path)) {
-      return unlink(path) == 0;
-    }
-    return true;
-  }
-
   const std::string GetCacheDirectory() const { return cache_directory_; }
   const std::string GetMetricsFilePath() const { return metrics_file_path_; }
 
@@ -66,59 +63,23 @@
   std::string cache_directory_;
 };
 
-TEST_F(OdrMetricsTest, ToRecordFailsIfNotTriggered) {
-  {
-    OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
-    OdrMetricsRecord record {};
-    EXPECT_FALSE(metrics.ToRecord(&record));
-  }
-
-  {
-    OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
-    metrics.SetArtApexVersion(99);
-    metrics.SetStage(OdrMetrics::Stage::kCheck);
-    metrics.SetStatus(OdrMetrics::Status::kNoSpace);
-    OdrMetricsRecord record {};
-    EXPECT_FALSE(metrics.ToRecord(&record));
-  }
-}
-
-TEST_F(OdrMetricsTest, ToRecordSucceedsIfTriggered) {
-  OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
-  metrics.SetArtApexVersion(99);
-  metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-  metrics.SetStage(OdrMetrics::Stage::kCheck);
-  metrics.SetStatus(OdrMetrics::Status::kNoSpace);
-
-  OdrMetricsRecord record{};
-  EXPECT_TRUE(metrics.ToRecord(&record));
-
-  EXPECT_EQ(99, record.art_apex_version);
-  EXPECT_EQ(OdrMetrics::Trigger::kApexVersionMismatch,
-            enum_cast<OdrMetrics::Trigger>(record.trigger));
-  EXPECT_EQ(OdrMetrics::Stage::kCheck, enum_cast<OdrMetrics::Stage>(record.stage_reached));
-  EXPECT_EQ(OdrMetrics::Status::kNoSpace, enum_cast<OdrMetrics::Status>(record.status));
-}
-
-TEST_F(OdrMetricsTest, MetricsFileIsNotCreatedIfNotTriggered) {
-  EXPECT_TRUE(RemoveMetricsFile());
-
+TEST_F(OdrMetricsTest, MetricsFileIsNotCreatedIfNotEnabled) {
   // Metrics file is (potentially) written in OdrMetrics destructor.
   {
     OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
     metrics.SetArtApexVersion(99);
+    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
     metrics.SetStage(OdrMetrics::Stage::kCheck);
     metrics.SetStatus(OdrMetrics::Status::kNoSpace);
   }
   EXPECT_FALSE(MetricsFileExists());
 }
 
-TEST_F(OdrMetricsTest, NoMetricsFileIsCreatedIfTriggered) {
-  EXPECT_TRUE(RemoveMetricsFile());
-
+TEST_F(OdrMetricsTest, MetricsFileIsCreatedIfEnabled) {
   // Metrics file is (potentially) written in OdrMetrics destructor.
   {
     OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
+    metrics.SetEnabled(true);
     metrics.SetArtApexVersion(101);
     metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
     metrics.SetStage(OdrMetrics::Stage::kCheck);
@@ -127,93 +88,113 @@
   EXPECT_TRUE(MetricsFileExists());
 }
 
-TEST_F(OdrMetricsTest, StageDoesNotAdvancedAfterFailure) {
-  OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
-  metrics.SetArtApexVersion(1999);
-  metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
-  metrics.SetStage(OdrMetrics::Stage::kCheck);
-  metrics.SetStatus(OdrMetrics::Status::kNoSpace);
-  metrics.SetStage(OdrMetrics::Stage::kComplete);
-
-  OdrMetricsRecord record{};
-  EXPECT_TRUE(metrics.ToRecord(&record));
-
-  EXPECT_EQ(OdrMetrics::Stage::kCheck, enum_cast<OdrMetrics::Stage>(record.stage_reached));
-}
-
-TEST_F(OdrMetricsTest, TimeValuesAreRecorded) {
-  OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
-  metrics.SetArtApexVersion(1999);
-  metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
-  metrics.SetStage(OdrMetrics::Stage::kCheck);
-  metrics.SetStatus(OdrMetrics::Status::kOK);
-
-  // Primary boot classpath compilation time.
-  OdrMetricsRecord record{};
-  {
-    metrics.SetStage(OdrMetrics::Stage::kPrimaryBootClasspath);
-    ScopedOdrCompilationTimer timer(metrics);
-    sleep(2u);
-  }
-  EXPECT_TRUE(metrics.ToRecord(&record));
-  EXPECT_EQ(OdrMetrics::Stage::kPrimaryBootClasspath,
-            enum_cast<OdrMetrics::Stage>(record.stage_reached));
-  EXPECT_NE(0, record.primary_bcp_compilation_seconds);
-  EXPECT_GT(10, record.primary_bcp_compilation_seconds);
-  EXPECT_EQ(0, record.secondary_bcp_compilation_seconds);
-  EXPECT_EQ(0, record.system_server_compilation_seconds);
-
-  // Secondary boot classpath compilation time.
-  {
-    metrics.SetStage(OdrMetrics::Stage::kSecondaryBootClasspath);
-    ScopedOdrCompilationTimer timer(metrics);
-    sleep(2u);
-  }
-  EXPECT_TRUE(metrics.ToRecord(&record));
-  EXPECT_EQ(OdrMetrics::Stage::kSecondaryBootClasspath,
-            enum_cast<OdrMetrics::Stage>(record.stage_reached));
-  EXPECT_NE(0, record.primary_bcp_compilation_seconds);
-  EXPECT_NE(0, record.secondary_bcp_compilation_seconds);
-  EXPECT_GT(10, record.secondary_bcp_compilation_seconds);
-  EXPECT_EQ(0, record.system_server_compilation_seconds);
-
-  // system_server classpath compilation time.
-  {
-    metrics.SetStage(OdrMetrics::Stage::kSystemServerClasspath);
-    ScopedOdrCompilationTimer timer(metrics);
-    sleep(2u);
-  }
-  EXPECT_TRUE(metrics.ToRecord(&record));
-  EXPECT_EQ(OdrMetrics::Stage::kSystemServerClasspath,
-            enum_cast<OdrMetrics::Stage>(record.stage_reached));
-  EXPECT_NE(0, record.primary_bcp_compilation_seconds);
-  EXPECT_NE(0, record.secondary_bcp_compilation_seconds);
-  EXPECT_NE(0, record.system_server_compilation_seconds);
-  EXPECT_GT(10, record.system_server_compilation_seconds);
-}
-
 TEST_F(OdrMetricsTest, CacheSpaceValuesAreUpdated) {
-  OdrMetricsRecord snap {};
-  snap.cache_space_free_start_mib = -1;
-  snap.cache_space_free_end_mib = -1;
-  {
-    OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
-    metrics.SetArtApexVersion(1999);
-    metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
-    metrics.SetStage(OdrMetrics::Stage::kCheck);
-    metrics.SetStatus(OdrMetrics::Status::kOK);
-    EXPECT_TRUE(metrics.ToRecord(&snap));
-    EXPECT_NE(0, snap.cache_space_free_start_mib);
-    EXPECT_EQ(0, snap.cache_space_free_end_mib);
-  }
+  OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
+  metrics.CaptureSpaceFreeEnd();
+  OdrMetricsRecord record = metrics.ToRecord();
+  EXPECT_GT(record.cache_space_free_start_mib, 0);
+  EXPECT_GT(record.cache_space_free_end_mib, 0);
+}
 
-  OdrMetricsRecord on_disk;
-  std::ifstream ifs(GetMetricsFilePath());
-  EXPECT_TRUE(ifs);
-  ifs >> on_disk;
-  EXPECT_TRUE(ifs);
-  EXPECT_EQ(snap.cache_space_free_start_mib, on_disk.cache_space_free_start_mib);
-  EXPECT_NE(0, on_disk.cache_space_free_end_mib);
+TEST_F(OdrMetricsTest, PrimaryBcpResultWithValue) {
+  OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
+  metrics.SetDex2OatResult(
+      OdrMetrics::Stage::kPrimaryBootClasspath,
+      100,
+      ExecResult{.status = ExecResult::Status::kExited, .exit_code = 0, .signal = 0});
+  metrics.SetBcpCompilationType(OdrMetrics::Stage::kPrimaryBootClasspath,
+                                OdrMetrics::BcpCompilationType::kMainline);
+  OdrMetricsRecord record = metrics.ToRecord();
+
+  EXPECT_EQ(record.primary_bcp_compilation_millis, 100);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.status, ExecResult::Status::kExited);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.exit_code, 0);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.signal, 0);
+  EXPECT_EQ(record.primary_bcp_compilation_type,
+            static_cast<int32_t>(OdrMetrics::BcpCompilationType::kMainline));
+
+  EXPECT_EQ(record.secondary_bcp_compilation_millis, 0);
+  EXPECT_EQ(record.secondary_bcp_dex2oat_result.status, kExecResultNotRun);
+  EXPECT_EQ(record.secondary_bcp_compilation_type,
+            static_cast<int32_t>(OdrMetrics::BcpCompilationType::kUnknown));
+
+  EXPECT_EQ(record.system_server_compilation_millis, 0);
+  EXPECT_EQ(record.system_server_dex2oat_result.status, kExecResultNotRun);
+}
+
+TEST_F(OdrMetricsTest, PrimaryBcpResultWithoutValue) {
+  OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
+
+  OdrMetricsRecord record = metrics.ToRecord();
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.status, kExecResultNotRun);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.exit_code, -1);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.signal, 0);
+}
+
+TEST_F(OdrMetricsTest, SecondaryBcpResultWithValue) {
+  OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
+  metrics.SetDex2OatResult(
+      OdrMetrics::Stage::kPrimaryBootClasspath,
+      100,
+      ExecResult{.status = ExecResult::Status::kExited, .exit_code = 0, .signal = 0});
+  metrics.SetBcpCompilationType(OdrMetrics::Stage::kPrimaryBootClasspath,
+                                OdrMetrics::BcpCompilationType::kMainline);
+  metrics.SetDex2OatResult(
+      OdrMetrics::Stage::kSecondaryBootClasspath,
+      200,
+      ExecResult{.status = ExecResult::Status::kTimedOut, .exit_code = 3, .signal = 0});
+  metrics.SetBcpCompilationType(OdrMetrics::Stage::kSecondaryBootClasspath,
+                                OdrMetrics::BcpCompilationType::kPrimaryAndMainline);
+  OdrMetricsRecord record = metrics.ToRecord();
+
+  EXPECT_EQ(record.primary_bcp_compilation_millis, 100);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.status, ExecResult::Status::kExited);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.exit_code, 0);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.signal, 0);
+  EXPECT_EQ(record.primary_bcp_compilation_type,
+            static_cast<int32_t>(OdrMetrics::BcpCompilationType::kMainline));
+
+  EXPECT_EQ(record.secondary_bcp_compilation_millis, 200);
+  EXPECT_EQ(record.secondary_bcp_dex2oat_result.status, ExecResult::Status::kTimedOut);
+  EXPECT_EQ(record.secondary_bcp_dex2oat_result.exit_code, 3);
+  EXPECT_EQ(record.secondary_bcp_dex2oat_result.signal, 0);
+  EXPECT_EQ(record.secondary_bcp_compilation_type,
+            static_cast<int32_t>(OdrMetrics::BcpCompilationType::kPrimaryAndMainline));
+
+  EXPECT_EQ(record.system_server_compilation_millis, 0);
+  EXPECT_EQ(record.system_server_dex2oat_result.status, kExecResultNotRun);
+}
+
+TEST_F(OdrMetricsTest, SystemServerResultWithValue) {
+  OdrMetrics metrics(GetCacheDirectory(), GetMetricsFilePath());
+  metrics.SetDex2OatResult(
+      OdrMetrics::Stage::kPrimaryBootClasspath,
+      100,
+      ExecResult{.status = ExecResult::Status::kExited, .exit_code = 0, .signal = 0});
+  metrics.SetDex2OatResult(
+      OdrMetrics::Stage::kSecondaryBootClasspath,
+      200,
+      ExecResult{.status = ExecResult::Status::kTimedOut, .exit_code = 3, .signal = 0});
+  metrics.SetDex2OatResult(
+      OdrMetrics::Stage::kSystemServerClasspath,
+      300,
+      ExecResult{.status = ExecResult::Status::kSignaled, .exit_code = 2, .signal = 9});
+  OdrMetricsRecord record = metrics.ToRecord();
+
+  EXPECT_EQ(record.primary_bcp_compilation_millis, 100);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.status, ExecResult::Status::kExited);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.exit_code, 0);
+  EXPECT_EQ(record.primary_bcp_dex2oat_result.signal, 0);
+
+  EXPECT_EQ(record.secondary_bcp_compilation_millis, 200);
+  EXPECT_EQ(record.secondary_bcp_dex2oat_result.status, ExecResult::Status::kTimedOut);
+  EXPECT_EQ(record.secondary_bcp_dex2oat_result.exit_code, 3);
+  EXPECT_EQ(record.secondary_bcp_dex2oat_result.signal, 0);
+
+  EXPECT_EQ(record.system_server_compilation_millis, 300);
+  EXPECT_EQ(record.system_server_dex2oat_result.status, ExecResult::Status::kSignaled);
+  EXPECT_EQ(record.system_server_dex2oat_result.exit_code, 2);
+  EXPECT_EQ(record.system_server_dex2oat_result.signal, 9);
 }
 
 }  // namespace odrefresh
diff --git a/odrefresh/odr_statslog_android.cc b/odrefresh/odr_statslog_android.cc
index 7db348e..fe2d121 100644
--- a/odrefresh/odr_statslog_android.cc
+++ b/odrefresh/odr_statslog_android.cc
@@ -35,106 +35,14 @@
 
 namespace {
 
-// Convert bare value from art::metrics::Stage to value defined in atoms.proto.
-int32_t TranslateStage(int32_t art_metrics_stage) {
-  switch (static_cast<OdrMetrics::Stage>(art_metrics_stage)) {
-    case OdrMetrics::Stage::kUnknown:
-      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_UNKNOWN;
-    case OdrMetrics::Stage::kCheck:
-      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_CHECK;
-    case OdrMetrics::Stage::kPreparation:
-      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_PREPARATION;
-    case OdrMetrics::Stage::kPrimaryBootClasspath:
-      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_PRIMARY_BOOT_CLASSPATH;
-    case OdrMetrics::Stage::kSecondaryBootClasspath:
-      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_SECONDARY_BOOT_CLASSPATH;
-    case OdrMetrics::Stage::kSystemServerClasspath:
-      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_SYSTEM_SERVER_CLASSPATH;
-    case OdrMetrics::Stage::kComplete:
-      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_COMPLETE;
-  }
-
-  LOG(ERROR) << "Unknown stage value: " << art_metrics_stage;
-  return -1;
-}
-
-// Convert bare value from art::metrics::Status to value defined in atoms.proto.
-int32_t TranslateStatus(int32_t art_metrics_status) {
-  switch (static_cast<OdrMetrics::Status>(art_metrics_status)) {
-    case OdrMetrics::Status::kUnknown:
-      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_UNKNOWN;
-    case OdrMetrics::Status::kOK:
-      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_OK;
-    case OdrMetrics::Status::kNoSpace:
-      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_NO_SPACE;
-    case OdrMetrics::Status::kIoError:
-      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_IO_ERROR;
-    case OdrMetrics::Status::kDex2OatError:
-      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_DEX2OAT_ERROR;
-    case OdrMetrics::Status::kTimeLimitExceeded:
-      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_TIME_LIMIT_EXCEEDED;
-    case OdrMetrics::Status::kStagingFailed:
-      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_STAGING_FAILED;
-    case OdrMetrics::Status::kInstallFailed:
-      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_INSTALL_FAILED;
-  }
-
-  LOG(ERROR) << "Unknown status value: " << art_metrics_status;
-  return -1;
-}
-
-// Convert bare value from art::metrics::Trigger to value defined in atoms.proto.
-int32_t TranslateTrigger(int32_t art_metrics_trigger) {
-  switch (static_cast<OdrMetrics::Trigger>(art_metrics_trigger)) {
-    case OdrMetrics::Trigger::kUnknown:
-      return metrics::statsd::ODREFRESH_REPORTED__TRIGGER__TRIGGER_UNKNOWN;
-    case OdrMetrics::Trigger::kApexVersionMismatch:
-      return metrics::statsd::ODREFRESH_REPORTED__TRIGGER__TRIGGER_APEX_VERSION_MISMATCH;
-    case OdrMetrics::Trigger::kDexFilesChanged:
-      return metrics::statsd::ODREFRESH_REPORTED__TRIGGER__TRIGGER_DEX_FILES_CHANGED;
-    case OdrMetrics::Trigger::kMissingArtifacts:
-      return metrics::statsd::ODREFRESH_REPORTED__TRIGGER__TRIGGER_MISSING_ARTIFACTS;
-  }
-
-  LOG(ERROR) << "Unknown trigger value: " << art_metrics_trigger;
-  return -1;
-}
-
 bool ReadValues(const char* metrics_file,
                 /*out*/ OdrMetricsRecord* record,
                 /*out*/ std::string* error_msg) {
-  std::ifstream ifs(metrics_file);
-  if (!ifs) {
-    *error_msg = android::base::StringPrintf(
-        "metrics file '%s' could not be opened: %s", metrics_file, strerror(errno));
-    return false;
-  }
-
-  ifs >> *record;
-  if (!ifs) {
-    *error_msg = "file parsing error.";
-    return false;
-  }
-
-  //
-  // Convert values defined as enums to their statsd values.
-  //
-
-  record->trigger = TranslateTrigger(record->trigger);
-  if (record->trigger < 0) {
-    *error_msg = "failed to parse trigger.";
-    return false;
-  }
-
-  record->stage_reached = TranslateStage(record->stage_reached);
-  if (record->stage_reached < 0) {
-    *error_msg = "failed to parse stage_reached.";
-    return false;
-  }
-
-  record->status = TranslateStatus(record->status);
-  if (record->status < 0) {
-    *error_msg = "failed to parse status.";
+  const android::base::Result<void>& result = record->ReadFromFile(metrics_file);
+  if (!result.ok()) {
+    *error_msg = android::base::StringPrintf("Unable to open or parse metrics file %s (error: %s)",
+                                             metrics_file,
+                                             result.error().message().data());
     return false;
   }
 
@@ -151,16 +59,31 @@
 
   // Write values to statsd. The order of values passed is the same as the order of the
   // fields in OdrMetricsRecord.
-  int bytes_written = art::metrics::statsd::stats_write(metrics::statsd::ODREFRESH_REPORTED,
-                                                        record.art_apex_version,
-                                                        record.trigger,
-                                                        record.stage_reached,
-                                                        record.status,
-                                                        record.primary_bcp_compilation_seconds,
-                                                        record.secondary_bcp_compilation_seconds,
-                                                        record.system_server_compilation_seconds,
-                                                        record.cache_space_free_start_mib,
-                                                        record.cache_space_free_end_mib);
+  int bytes_written =
+      art::metrics::statsd::stats_write(metrics::statsd::ODREFRESH_REPORTED,
+                                        record.art_apex_version,
+                                        record.trigger,
+                                        record.stage_reached,
+                                        record.status,
+                                        record.primary_bcp_compilation_millis / 1000,
+                                        record.secondary_bcp_compilation_millis / 1000,
+                                        record.system_server_compilation_millis / 1000,
+                                        record.cache_space_free_start_mib,
+                                        record.cache_space_free_end_mib,
+                                        record.primary_bcp_compilation_millis,
+                                        record.secondary_bcp_compilation_millis,
+                                        record.system_server_compilation_millis,
+                                        record.primary_bcp_dex2oat_result.status,
+                                        record.primary_bcp_dex2oat_result.exit_code,
+                                        record.primary_bcp_dex2oat_result.signal,
+                                        record.secondary_bcp_dex2oat_result.status,
+                                        record.secondary_bcp_dex2oat_result.exit_code,
+                                        record.secondary_bcp_dex2oat_result.signal,
+                                        record.system_server_dex2oat_result.status,
+                                        record.system_server_dex2oat_result.exit_code,
+                                        record.system_server_dex2oat_result.signal,
+                                        record.primary_bcp_compilation_type,
+                                        record.secondary_bcp_compilation_type);
   if (bytes_written <= 0) {
     *error_msg = android::base::StringPrintf("stats_write returned %d", bytes_written);
     return false;
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index f829bb8..7bbc922 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -53,19 +53,23 @@
 #include <utility>
 #include <vector>
 
+#include "android-base/chrono_utils.h"
 #include "android-base/file.h"
 #include "android-base/logging.h"
 #include "android-base/macros.h"
+#include "android-base/parsebool.h"
 #include "android-base/parseint.h"
 #include "android-base/properties.h"
 #include "android-base/result.h"
 #include "android-base/scopeguard.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
+#include "android-modules-utils/sdk_level.h"
 #include "android/log.h"
 #include "arch/instruction_set.h"
 #include "base/file_utils.h"
 #include "base/globals.h"
+#include "base/logging.h"
 #include "base/macros.h"
 #include "base/os.h"
 #include "base/stl_util.h"
@@ -76,6 +80,8 @@
 #include "dex/art_dex_file_loader.h"
 #include "dexoptanalyzer.h"
 #include "exec_utils.h"
+#include "fmt/format.h"
+#include "gc/collector/mark_compact.h"
 #include "log/log.h"
 #include "odr_artifacts.h"
 #include "odr_common.h"
@@ -86,31 +92,54 @@
 #include "odrefresh/odrefresh.h"
 #include "palette/palette.h"
 #include "palette/palette_types.h"
+#include "read_barrier_config.h"
 
 namespace art {
 namespace odrefresh {
 
+namespace {
+
 namespace apex = com::android::apex;
 namespace art_apex = com::android::art;
 
-using android::base::Result;
+using ::android::base::Basename;
+using ::android::base::Dirname;
+using ::android::base::GetProperty;
+using ::android::base::Join;
+using ::android::base::ParseBool;
+using ::android::base::ParseBoolResult;
+using ::android::base::ParseInt;
+using ::android::base::Result;
+using ::android::base::SetProperty;
+using ::android::base::Split;
+using ::android::base::StartsWith;
+using ::android::base::StringPrintf;
+using ::android::base::Timer;
+using ::android::modules::sdklevel::IsAtLeastU;
 
-namespace {
+using ::fmt::literals::operator""_format;  // NOLINT
 
 // Name of cache info file in the ART Apex artifact cache.
 constexpr const char* kCacheInfoFile = "cache-info.xml";
 
 // Maximum execution time for odrefresh from start to end.
-constexpr time_t kMaximumExecutionSeconds = 300;
+constexpr time_t kMaximumExecutionSeconds = 480;
 
 // Maximum execution time for any child process spawned.
-constexpr time_t kMaxChildProcessSeconds = 90;
+constexpr time_t kMaxChildProcessSeconds = 120;
 
 constexpr mode_t kFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
 
 constexpr const char* kFirstBootImageBasename = "boot.art";
 constexpr const char* kMinimalBootImageBasename = "boot_minimal.art";
 
+// The default compiler filter for primary boot image.
+constexpr const char* kPrimaryCompilerFilter = "speed-profile";
+
+// The compiler filter for boot image mainline extension. We don't have profiles for mainline BCP
+// jars, so we always use "verify".
+constexpr const char* kMainlineCompilerFilter = "verify";
+
 void EraseFiles(const std::vector<std::unique_ptr<File>>& files) {
   for (auto& file : files) {
     file->Erase(/*unlink=*/true);
@@ -127,9 +156,9 @@
                       std::string_view output_directory_path) {
   std::vector<std::unique_ptr<File>> output_files;
   for (auto& file : files) {
-    const std::string file_basename(android::base::Basename(file->GetPath()));
-    const std::string output_file_path = Concatenate({output_directory_path, "/", file_basename});
-    const std::string input_file_path = file->GetPath();
+    std::string file_basename(Basename(file->GetPath()));
+    std::string output_file_path = "{}/{}"_format(output_directory_path, file_basename);
+    std::string input_file_path = file->GetPath();
 
     output_files.emplace_back(OS::CreateEmptyFileWriteOnly(output_file_path.c_str()));
     if (output_files.back() == nullptr) {
@@ -147,7 +176,7 @@
       return false;
     }
 
-    const size_t file_bytes = file->GetLength();
+    size_t file_bytes = file->GetLength();
     if (!output_files.back()->Copy(file.get(), /*offset=*/0, file_bytes)) {
       PLOG(ERROR) << "Failed to copy " << QuotePath(file->GetPath()) << " to "
                   << QuotePath(output_file_path);
@@ -204,10 +233,12 @@
   return module_info_list;
 }
 
-// Returns a rewritten path based on ANDROID_ROOT if the path starts with "/system/".
-std::string AndroidRootRewrite(const std::string& path) {
+// Returns a rewritten path based on environment variables for interesting paths.
+std::string RewriteParentDirectoryIfNeeded(const std::string& path) {
   if (StartsWith(path, "/system/")) {
-    return Concatenate({GetAndroidRoot(), path.substr(7)});
+    return GetAndroidRoot() + path.substr(7);
+  } else if (StartsWith(path, "/system_ext/")) {
+    return GetSystemExtRoot() + path.substr(11);
   } else {
     return path;
   }
@@ -279,9 +310,8 @@
         custom_generator) {
   std::vector<T> components;
 
-  ArtDexFileLoader loader;
   for (const std::string& path : jars) {
-    std::string actual_path = AndroidRootRewrite(path);
+    std::string actual_path = RewriteParentDirectoryIfNeeded(path);
     struct stat sb;
     if (stat(actual_path.c_str(), &sb) == -1) {
       PLOG(ERROR) << "Failed to stat component: " << QuotePath(actual_path);
@@ -291,7 +321,8 @@
     std::vector<uint32_t> checksums;
     std::vector<std::string> dex_locations;
     std::string error_msg;
-    if (!loader.GetMultiDexChecksums(actual_path.c_str(), &checksums, &dex_locations, &error_msg)) {
+    if (!ArtDexFileLoader::GetMultiDexChecksums(
+            actual_path.c_str(), &checksums, &dex_locations, &error_msg)) {
       LOG(ERROR) << "Failed to get multi-dex checksums: " << error_msg;
       return {};
     }
@@ -301,7 +332,7 @@
       if (i != 0) {
         oss << ';';
       }
-      oss << android::base::StringPrintf("%08x", checksums[i]);
+      oss << StringPrintf("%08x", checksums[i]);
     }
     const std::string checksum = oss.str();
 
@@ -370,31 +401,47 @@
 }
 
 bool IsCpuSetSpecValid(const std::string& cpu_set) {
-  for (auto& str : android::base::Split(cpu_set, ",")) {
+  for (const std::string& str : Split(cpu_set, ",")) {
     int id;
-    if (!android::base::ParseInt(str, &id, 0)) {
+    if (!ParseInt(str, &id, 0)) {
       return false;
     }
   }
   return true;
 }
 
-bool AddDex2OatConcurrencyArguments(/*inout*/ std::vector<std::string>& args) {
-  std::string threads = android::base::GetProperty("dalvik.vm.boot-dex2oat-threads", "");
+Result<void> AddDex2OatConcurrencyArguments(/*inout*/ std::vector<std::string>& args,
+                                            bool is_compilation_os) {
+  std::string threads;
+  if (is_compilation_os) {
+    threads = GetProperty("dalvik.vm.background-dex2oat-threads", "");
+    if (threads.empty()) {
+      threads = GetProperty("dalvik.vm.dex2oat-threads", "");
+    }
+  } else {
+    threads = GetProperty("dalvik.vm.boot-dex2oat-threads", "");
+  }
   if (!threads.empty()) {
     args.push_back("-j" + threads);
   }
 
-  std::string cpu_set = android::base::GetProperty("dalvik.vm.boot-dex2oat-cpu-set", "");
-  if (cpu_set.empty()) {
-    return true;
+  std::string cpu_set;
+  if (is_compilation_os) {
+    cpu_set = GetProperty("dalvik.vm.background-dex2oat-cpu-set", "");
+    if (cpu_set.empty()) {
+      cpu_set = GetProperty("dalvik.vm.dex2oat-cpu-set", "");
+    }
+  } else {
+    cpu_set = GetProperty("dalvik.vm.boot-dex2oat-cpu-set", "");
   }
-  if (!IsCpuSetSpecValid(cpu_set)) {
-    LOG(ERROR) << "Invalid CPU set spec: " << cpu_set;
-    return false;
+  if (!cpu_set.empty()) {
+    if (!IsCpuSetSpecValid(cpu_set)) {
+      return Errorf("Invalid CPU set spec '{}'", cpu_set);
+    }
+    args.push_back("--cpu-set=" + cpu_set);
   }
-  args.push_back("--cpu-set=" + cpu_set);
-  return true;
+
+  return {};
 }
 
 void AddDex2OatDebugInfo(/*inout*/ std::vector<std::string>& args) {
@@ -404,10 +451,11 @@
 
 void AddDex2OatInstructionSet(/*inout*/ std::vector<std::string>& args, InstructionSet isa) {
   const char* isa_str = GetInstructionSetString(isa);
-  args.emplace_back(Concatenate({"--instruction-set=", isa_str}));
+  args.emplace_back(StringPrintf("--instruction-set=%s", isa_str));
 }
 
-void AddDex2OatProfileAndCompilerFilter(
+// Returns true if any profile has been added.
+bool AddDex2OatProfile(
     /*inout*/ std::vector<std::string>& args,
     /*inout*/ std::vector<std::unique_ptr<File>>& output_files,
     const std::vector<std::string>& profile_paths) {
@@ -415,22 +463,17 @@
   for (auto& path : profile_paths) {
     std::unique_ptr<File> profile_file(OS::OpenFileForReading(path.c_str()));
     if (profile_file && profile_file->IsOpened()) {
-      args.emplace_back(android::base::StringPrintf("--profile-file-fd=%d", profile_file->Fd()));
+      args.emplace_back(StringPrintf("--profile-file-fd=%d", profile_file->Fd()));
       output_files.emplace_back(std::move(profile_file));
       has_any_profile = true;
     }
   }
-
-  if (has_any_profile) {
-    args.emplace_back("--compiler-filter=speed-profile");
-  } else {
-    args.emplace_back("--compiler-filter=speed");
-  }
+  return has_any_profile;
 }
 
-bool AddBootClasspathFds(/*inout*/ std::vector<std::string>& args,
-                         /*inout*/ std::vector<std::unique_ptr<File>>& output_files,
-                         const std::vector<std::string>& bcp_jars) {
+Result<void> AddBootClasspathFds(/*inout*/ std::vector<std::string>& args,
+                                 /*inout*/ std::vector<std::unique_ptr<File>>& output_files,
+                                 const std::vector<std::string>& bcp_jars) {
   std::vector<std::string> bcp_fds;
   for (const std::string& jar : bcp_jars) {
     // Special treatment for Compilation OS. JARs in staged APEX may not be visible to Android, and
@@ -439,26 +482,38 @@
     if (StartsWith(jar, "/apex/")) {
       bcp_fds.emplace_back("-1");
     } else {
-      std::string actual_path = AndroidRootRewrite(jar);
+      std::string actual_path = RewriteParentDirectoryIfNeeded(jar);
       std::unique_ptr<File> jar_file(OS::OpenFileForReading(actual_path.c_str()));
       if (!jar_file || !jar_file->IsValid()) {
-        LOG(ERROR) << "Failed to open a BCP jar " << actual_path;
-        return false;
+        return Errorf("Failed to open a BCP jar '{}'", actual_path);
       }
       bcp_fds.push_back(std::to_string(jar_file->Fd()));
       output_files.push_back(std::move(jar_file));
     }
   }
   args.emplace_back("--runtime-arg");
-  args.emplace_back(Concatenate({"-Xbootclasspathfds:", android::base::Join(bcp_fds, ':')}));
-  return true;
+  args.emplace_back("-Xbootclasspathfds:" + Join(bcp_fds, ':'));
+  return {};
+}
+
+Result<void> AddCacheInfoFd(/*inout*/ std::vector<std::string>& args,
+                            /*inout*/ std::vector<std::unique_ptr<File>>& readonly_files_raii,
+                            const std::string& cache_info_filename) {
+  std::unique_ptr<File> cache_info_file(OS::OpenFileForReading(cache_info_filename.c_str()));
+  if (cache_info_file == nullptr) {
+    return ErrnoErrorf("Failed to open a cache info file '{}'", cache_info_file);
+  }
+
+  args.emplace_back("--cache-info-fd=" + std::to_string(cache_info_file->Fd()));
+  readonly_files_raii.push_back(std::move(cache_info_file));
+  return {};
 }
 
 std::string GetBootImageComponentBasename(const std::string& jar_path, bool is_first_jar) {
   if (is_first_jar) {
     return kFirstBootImageBasename;
   }
-  const std::string jar_name = android::base::Basename(jar_path);
+  std::string jar_name = Basename(jar_path);
   return "boot-" + ReplaceFileExtension(jar_name, "art");
 }
 
@@ -466,17 +521,27 @@
     /*inout*/ std::vector<std::string>& args,
     /*inout*/ std::vector<std::unique_ptr<File>>& output_files,
     const std::vector<std::string>& bcp_jars,
-    const InstructionSet isa,
-    const std::string& artifact_dir) {
+    InstructionSet isa,
+    const std::vector<std::string>& boot_image_locations) {
   std::vector<std::string> bcp_image_fds;
   std::vector<std::string> bcp_oat_fds;
   std::vector<std::string> bcp_vdex_fds;
   std::vector<std::unique_ptr<File>> opened_files;
   bool added_any = false;
+  std::string artifact_dir;
   for (size_t i = 0; i < bcp_jars.size(); i++) {
     const std::string& jar = bcp_jars[i];
-    std::string image_path =
-        artifact_dir + "/" + GetBootImageComponentBasename(jar, /*is_first_jar=*/i == 0);
+    std::string basename = GetBootImageComponentBasename(jar, /*is_first_jar=*/i == 0);
+    // If there is an entry in `boot_image_locations` for the current jar, update `artifact_dir` for
+    // the current jar and the subsequent jars.
+    for (const std::string& location : boot_image_locations) {
+      if (Basename(location) == basename) {
+        artifact_dir = Dirname(location);
+        break;
+      }
+    }
+    CHECK(!artifact_dir.empty());
+    std::string image_path = artifact_dir + "/" + basename;
     image_path = GetSystemImageFilename(image_path.c_str(), isa);
     std::unique_ptr<File> image_file(OS::OpenFileForReading(image_path.c_str()));
     if (image_file && image_file->IsValid()) {
@@ -512,19 +577,16 @@
     std::move(opened_files.begin(), opened_files.end(), std::back_inserter(output_files));
 
     args.emplace_back("--runtime-arg");
-    args.emplace_back(
-        Concatenate({"-Xbootclasspathimagefds:", android::base::Join(bcp_image_fds, ':')}));
+    args.emplace_back("-Xbootclasspathimagefds:" + Join(bcp_image_fds, ':'));
     args.emplace_back("--runtime-arg");
-    args.emplace_back(
-        Concatenate({"-Xbootclasspathoatfds:", android::base::Join(bcp_oat_fds, ':')}));
+    args.emplace_back("-Xbootclasspathoatfds:" + Join(bcp_oat_fds, ':'));
     args.emplace_back("--runtime-arg");
-    args.emplace_back(
-        Concatenate({"-Xbootclasspathvdexfds:", android::base::Join(bcp_vdex_fds, ':')}));
+    args.emplace_back("-Xbootclasspathvdexfds:" + Join(bcp_vdex_fds, ':'));
   }
 }
 
 std::string GetStagingLocation(const std::string& staging_dir, const std::string& path) {
-  return Concatenate({staging_dir, "/", android::base::Basename(path)});
+  return staging_dir + "/" + Basename(path);
 }
 
 WARN_UNUSED bool CheckCompilationSpace() {
@@ -553,13 +615,59 @@
   return true;
 }
 
-std::string GetSystemBootImageDir() { return GetAndroidRoot() + "/framework"; }
+bool HasVettedDeviceSystemServerProfiles() {
+  // While system_server profiles were bundled on the device prior to U+, they were not used by
+  // default or rigorously tested, so we cannot vouch for their efficacy.
+  static const bool kDeviceIsAtLeastU = IsAtLeastU();
+  return kDeviceIsAtLeastU;
+}
 
 }  // namespace
 
+CompilationOptions CompilationOptions::CompileAll(const OnDeviceRefresh& odr) {
+  CompilationOptions options;
+  for (InstructionSet isa : odr.Config().GetBootClasspathIsas()) {
+    options.boot_images_to_generate_for_isas.emplace_back(
+        isa, BootImages{.primary_boot_image = true, .boot_image_mainline_extension = true});
+  }
+  options.system_server_jars_to_compile = odr.AllSystemServerJars();
+  return options;
+}
+
+int BootImages::Count() const {
+  int count = 0;
+  if (primary_boot_image) {
+    count++;
+  }
+  if (boot_image_mainline_extension) {
+    count++;
+  }
+  return count;
+}
+
+OdrMetrics::BcpCompilationType BootImages::GetTypeForMetrics() const {
+  if (primary_boot_image && boot_image_mainline_extension) {
+    return OdrMetrics::BcpCompilationType::kPrimaryAndMainline;
+  }
+  if (boot_image_mainline_extension) {
+    return OdrMetrics::BcpCompilationType::kMainline;
+  }
+  LOG(FATAL) << "Unexpected BCP compilation type";
+  UNREACHABLE();
+}
+
+int CompilationOptions::CompilationUnitCount() const {
+  int count = 0;
+  for (const auto& [isa, boot_images] : boot_images_to_generate_for_isas) {
+    count += boot_images.Count();
+  }
+  count += system_server_jars_to_compile.size();
+  return count;
+}
+
 OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config)
     : OnDeviceRefresh(config,
-                      Concatenate({config.GetArtifactDirectory(), "/", kCacheInfoFile}),
+                      config.GetArtifactDirectory() + "/" + kCacheInfoFile,
                       std::make_unique<ExecUtils>()) {}
 
 OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config,
@@ -569,19 +677,17 @@
       cache_info_filename_{cache_info_filename},
       start_time_{time(nullptr)},
       exec_utils_{std::move(exec_utils)} {
-  for (const std::string& jar : android::base::Split(config_.GetDex2oatBootClasspath(), ":")) {
-    // Updatable APEXes should not have DEX files in the DEX2OATBOOTCLASSPATH. At the time of
-    // writing i18n is a non-updatable APEX and so does appear in the DEX2OATBOOTCLASSPATH.
-    boot_classpath_compilable_jars_.emplace_back(jar);
-  }
+  // Updatable APEXes should not have DEX files in the DEX2OATBOOTCLASSPATH. At the time of
+  // writing i18n is a non-updatable APEX and so does appear in the DEX2OATBOOTCLASSPATH.
+  dex2oat_boot_classpath_jars_ = Split(config_.GetDex2oatBootClasspath(), ":");
 
-  all_systemserver_jars_ = android::base::Split(config_.GetSystemServerClasspath(), ":");
+  all_systemserver_jars_ = Split(config_.GetSystemServerClasspath(), ":");
   systemserver_classpath_jars_ = {all_systemserver_jars_.begin(), all_systemserver_jars_.end()};
-  boot_classpath_jars_ = android::base::Split(config_.GetBootClasspath(), ":");
+  boot_classpath_jars_ = Split(config_.GetBootClasspath(), ":");
   std::string standalone_system_server_jars_str = config_.GetStandaloneSystemServerJars();
   if (!standalone_system_server_jars_str.empty()) {
     std::vector<std::string> standalone_systemserver_jars =
-        android::base::Split(standalone_system_server_jars_str, ":");
+        Split(standalone_system_server_jars_str, ":");
     std::move(standalone_systemserver_jars.begin(),
               standalone_systemserver_jars.end(),
               std::back_inserter(all_systemserver_jars_));
@@ -610,8 +716,8 @@
   std::unordered_set<std::string_view> relevant_apexes;
   relevant_apexes.reserve(info_list->getApexInfo().size());
   for (const std::vector<std::string>* jar_list :
-       {&boot_classpath_compilable_jars_, &all_systemserver_jars_, &boot_classpath_jars_}) {
-    for (auto& jar : *jar_list) {
+       {&all_systemserver_jars_, &boot_classpath_jars_}) {
+    for (const std::string& jar : *jar_list) {
       std::string_view apex = ApexNameFromLocation(jar);
       if (!apex.empty()) {
         relevant_apexes.insert(apex);
@@ -632,8 +738,16 @@
   return filtered_info_list;
 }
 
-std::optional<art_apex::CacheInfo> OnDeviceRefresh::ReadCacheInfo() const {
-  return art_apex::read(cache_info_filename_.c_str());
+Result<art_apex::CacheInfo> OnDeviceRefresh::ReadCacheInfo() const {
+  std::optional<art_apex::CacheInfo> cache_info = art_apex::read(cache_info_filename_.c_str());
+  if (!cache_info.has_value()) {
+    if (errno != 0) {
+      return ErrnoErrorf("Failed to load {}", QuotePath(cache_info_filename_));
+    } else {
+      return Errorf("Failed to parse {}", QuotePath(cache_info_filename_));
+    }
+  }
+  return cache_info.value();
 }
 
 Result<void> OnDeviceRefresh::WriteCacheInfo() const {
@@ -643,7 +757,7 @@
     }
   }
 
-  const std::string dir_name = android::base::Dirname(cache_info_filename_);
+  std::string dir_name = Dirname(cache_info_filename_);
   if (!EnsureDirectoryExists(dir_name)) {
     return Errorf("Could not create directory {}", QuotePath(dir_name));
   }
@@ -667,23 +781,11 @@
   std::vector<art_apex::ModuleInfo> module_info_list =
       GenerateModuleInfoList(apex_info_list.value());
 
-  std::optional<std::vector<art_apex::Component>> bcp_components =
-      GenerateBootClasspathComponents();
-  if (!bcp_components.has_value()) {
-    return Errorf("No boot classpath components.");
-  }
-
-  std::optional<std::vector<art_apex::Component>> bcp_compilable_components =
-      GenerateBootClasspathCompilableComponents();
-  if (!bcp_compilable_components.has_value()) {
-    return Errorf("No boot classpath compilable components.");
-  }
-
-  std::optional<std::vector<art_apex::SystemServerComponent>> system_server_components =
+  std::vector<art_apex::Component> bcp_components = GenerateBootClasspathComponents();
+  std::vector<art_apex::Component> dex2oat_bcp_components =
+      GenerateDex2oatBootClasspathComponents();
+  std::vector<art_apex::SystemServerComponent> system_server_components =
       GenerateSystemServerComponents();
-  if (!system_server_components.has_value()) {
-    return Errorf("No system_server components.");
-  }
 
   std::ofstream out(cache_info_filename_.c_str());
   if (out.fail()) {
@@ -694,9 +796,9 @@
       {art_apex::KeyValuePairList(system_properties)},
       {art_module_info},
       {art_apex::ModuleInfoList(module_info_list)},
-      {art_apex::Classpath(bcp_components.value())},
-      {art_apex::Classpath(bcp_compilable_components.value())},
-      {art_apex::SystemServerComponents(system_server_components.value())},
+      {art_apex::Classpath(bcp_components)},
+      {art_apex::Classpath(dex2oat_bcp_components)},
+      {art_apex::SystemServerComponents(system_server_components)},
       config_.GetCompilationOsMode() ? std::make_optional(true) : std::nullopt));
 
   art_apex::write(out, *info);
@@ -713,16 +815,15 @@
   // We arbitrarily show progress until 90%, expecting that our compilations take a large chunk of
   // boot time.
   uint32_t value = (90 * current_compilation) / number_of_compilations;
-  android::base::SetProperty("service.bootanim.progress", std::to_string(value));
+  SetProperty("service.bootanim.progress", std::to_string(value));
 }
 
 std::vector<art_apex::Component> OnDeviceRefresh::GenerateBootClasspathComponents() const {
   return GenerateComponents(boot_classpath_jars_);
 }
 
-std::vector<art_apex::Component> OnDeviceRefresh::GenerateBootClasspathCompilableComponents()
-    const {
-  return GenerateComponents(boot_classpath_compilable_jars_);
+std::vector<art_apex::Component> OnDeviceRefresh::GenerateDex2oatBootClasspathComponents() const {
+  return GenerateComponents(dex2oat_boot_classpath_jars_);
 }
 
 std::vector<art_apex::SystemServerComponent> OnDeviceRefresh::GenerateSystemServerComponents()
@@ -735,7 +836,43 @@
       });
 }
 
-std::string OnDeviceRefresh::GetBootImage(bool on_system, bool minimal) const {
+std::vector<std::string> OnDeviceRefresh::GetArtBcpJars() const {
+  std::string art_root = GetArtRoot() + "/";
+  std::vector<std::string> art_bcp_jars;
+  for (const std::string& jar : dex2oat_boot_classpath_jars_) {
+    if (StartsWith(jar, art_root)) {
+      art_bcp_jars.push_back(jar);
+    }
+  }
+  CHECK(!art_bcp_jars.empty());
+  return art_bcp_jars;
+}
+
+std::vector<std::string> OnDeviceRefresh::GetFrameworkBcpJars() const {
+  std::string art_root = GetArtRoot() + "/";
+  std::vector<std::string> framework_bcp_jars;
+  for (const std::string& jar : dex2oat_boot_classpath_jars_) {
+    if (!StartsWith(jar, art_root)) {
+      framework_bcp_jars.push_back(jar);
+    }
+  }
+  CHECK(!framework_bcp_jars.empty());
+  return framework_bcp_jars;
+}
+
+std::vector<std::string> OnDeviceRefresh::GetMainlineBcpJars() const {
+  // Elements in `dex2oat_boot_classpath_jars_` should be at the beginning of
+  // `boot_classpath_jars_`, followed by mainline BCP jars.
+  CHECK_LT(dex2oat_boot_classpath_jars_.size(), boot_classpath_jars_.size());
+  CHECK(std::equal(dex2oat_boot_classpath_jars_.begin(),
+                   dex2oat_boot_classpath_jars_.end(),
+                   boot_classpath_jars_.begin(),
+                   boot_classpath_jars_.begin() + dex2oat_boot_classpath_jars_.size()));
+  return {boot_classpath_jars_.begin() + dex2oat_boot_classpath_jars_.size(),
+          boot_classpath_jars_.end()};
+}
+
+std::string OnDeviceRefresh::GetPrimaryBootImage(bool on_system, bool minimal) const {
   DCHECK(!on_system || !minimal);
   const char* basename = minimal ? kMinimalBootImageBasename : kFirstBootImageBasename;
   if (on_system) {
@@ -747,28 +884,74 @@
   }
 }
 
-std::string OnDeviceRefresh::GetBootImagePath(bool on_system,
-                                              bool minimal,
-                                              const InstructionSet isa) const {
+std::string OnDeviceRefresh::GetPrimaryBootImagePath(bool on_system,
+                                                     bool minimal,
+                                                     InstructionSet isa) const {
   // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/<isa>/boot.art".
-  return GetSystemImageFilename(GetBootImage(on_system, minimal).c_str(), isa);
+  return GetSystemImageFilename(GetPrimaryBootImage(on_system, minimal).c_str(), isa);
 }
 
-std::string OnDeviceRefresh::GetSystemBootImageExtension() const {
-  std::string art_root = GetArtRoot() + "/";
-  // Find the first boot extension jar.
-  auto it = std::find_if_not(
-      boot_classpath_compilable_jars_.begin(),
-      boot_classpath_compilable_jars_.end(),
-      [&](const std::string& jar) { return android::base::StartsWith(jar, art_root); });
-  CHECK(it != boot_classpath_compilable_jars_.end());
+std::string OnDeviceRefresh::GetSystemBootImageFrameworkExtension() const {
+  std::vector<std::string> framework_bcp_jars = GetFrameworkBcpJars();
+  std::string basename =
+      GetBootImageComponentBasename(framework_bcp_jars[0], /*is_first_jar=*/false);
   // Typically "/system/framework/boot-framework.art".
-  return GetSystemBootImageDir() + "/" + GetBootImageComponentBasename(*it, /*is_first_jar=*/false);
+  return "{}/framework/{}"_format(GetAndroidRoot(), basename);
 }
 
-std::string OnDeviceRefresh::GetSystemBootImageExtensionPath(const InstructionSet isa) const {
+std::string OnDeviceRefresh::GetSystemBootImageFrameworkExtensionPath(InstructionSet isa) const {
   // Typically "/system/framework/<isa>/boot-framework.art".
-  return GetSystemImageFilename(GetSystemBootImageExtension().c_str(), isa);
+  return GetSystemImageFilename(GetSystemBootImageFrameworkExtension().c_str(), isa);
+}
+
+std::string OnDeviceRefresh::GetBootImageMainlineExtension(bool on_system) const {
+  std::vector<std::string> mainline_bcp_jars = GetMainlineBcpJars();
+  std::string basename =
+      GetBootImageComponentBasename(mainline_bcp_jars[0], /*is_first_jar=*/false);
+  if (on_system) {
+    // Typically "/system/framework/boot-framework-adservices.art".
+    return "{}/framework/{}"_format(GetAndroidRoot(), basename);
+  } else {
+    // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot-framework-adservices.art".
+    return "{}/{}"_format(config_.GetArtifactDirectory(), basename);
+  }
+}
+
+std::string OnDeviceRefresh::GetBootImageMainlineExtensionPath(bool on_system,
+                                                               InstructionSet isa) const {
+  // Typically
+  // "/data/misc/apexdata/com.android.art/dalvik-cache/<isa>/boot-framework-adservices.art".
+  return GetSystemImageFilename(GetBootImageMainlineExtension(on_system).c_str(), isa);
+}
+
+std::vector<std::string> OnDeviceRefresh::GetBestBootImages(InstructionSet isa,
+                                                            bool include_mainline_extension) const {
+  std::vector<std::string> locations;
+  std::string unused_error_msg;
+  bool primary_on_data = false;
+  if (PrimaryBootImageExist(
+          /*on_system=*/false, /*minimal=*/false, isa, &unused_error_msg)) {
+    primary_on_data = true;
+    locations.push_back(GetPrimaryBootImage(/*on_system=*/false, /*minimal=*/false));
+  } else {
+    locations.push_back(GetPrimaryBootImage(/*on_system=*/true, /*minimal=*/false));
+    if (!IsAtLeastU()) {
+      // Prior to U, there was a framework extension.
+      locations.push_back(GetSystemBootImageFrameworkExtension());
+    }
+  }
+  if (include_mainline_extension) {
+    if (BootImageMainlineExtensionExist(/*on_system=*/false, isa, &unused_error_msg)) {
+      locations.push_back(GetBootImageMainlineExtension(/*on_system=*/false));
+    } else {
+      // If the primary boot image is on /data, it means we have regenerated all boot images, so the
+      // mainline extension must be on /data too.
+      CHECK(!primary_on_data)
+          << "Mainline extension not found while primary boot image is on /data";
+      locations.push_back(GetBootImageMainlineExtension(/*on_system=*/true));
+    }
+  }
+  return locations;
 }
 
 std::string OnDeviceRefresh::GetSystemServerImagePath(bool on_system,
@@ -777,15 +960,15 @@
     if (LocationIsOnApex(jar_path)) {
       return GetSystemOdexFilenameForApex(jar_path, config_.GetSystemServerIsa());
     }
-    const std::string jar_name = android::base::Basename(jar_path);
-    const std::string image_name = ReplaceFileExtension(jar_name, "art");
+    std::string jar_name = Basename(jar_path);
+    std::string image_name = ReplaceFileExtension(jar_name, "art");
     const char* isa_str = GetInstructionSetString(config_.GetSystemServerIsa());
     // Typically "/system/framework/oat/<isa>/services.art".
-    return Concatenate({GetAndroidRoot(), "/framework/oat/", isa_str, "/", image_name});
+    return "{}/oat/{}/{}"_format(Dirname(jar_path), isa_str, image_name);
   } else {
     // Typically
     // "/data/misc/apexdata/.../dalvik-cache/<isa>/system@[email protected]@classes.art".
-    const std::string image = GetApexDataImage(jar_path.c_str());
+    const std::string image = GetApexDataImage(jar_path);
     return GetSystemImageFilename(image.c_str(), config_.GetSystemServerIsa());
   }
 }
@@ -799,21 +982,21 @@
   return RemoveDirectory(config_.GetArtifactDirectory());
 }
 
-WARN_UNUSED bool OnDeviceRefresh::BootClasspathArtifactsExist(
+WARN_UNUSED bool OnDeviceRefresh::PrimaryBootImageExist(
     bool on_system,
     bool minimal,
-    const InstructionSet isa,
+    InstructionSet isa,
     /*out*/ std::string* error_msg,
     /*out*/ std::vector<std::string>* checked_artifacts) const {
-  std::string path = GetBootImagePath(on_system, minimal, isa);
+  std::string path = GetPrimaryBootImagePath(on_system, minimal, isa);
   OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path);
   if (!ArtifactsExist(artifacts, /*check_art_file=*/true, error_msg, checked_artifacts)) {
     return false;
   }
-  // There is a split between the primary boot image and the extension on /system, so they need to
-  // be checked separately. This does not apply to the boot image on /data.
-  if (on_system) {
-    std::string extension_path = GetSystemBootImageExtensionPath(isa);
+  // Prior to U, there was a split between the primary boot image and the extension on /system, so
+  // they need to be checked separately. This does not apply to the boot image on /data.
+  if (on_system && !IsAtLeastU()) {
+    std::string extension_path = GetSystemBootImageFrameworkExtensionPath(isa);
     OdrArtifacts extension_artifacts = OdrArtifacts::ForBootImage(extension_path);
     if (!ArtifactsExist(
             extension_artifacts, /*check_art_file=*/true, error_msg, checked_artifacts)) {
@@ -823,7 +1006,17 @@
   return true;
 }
 
-WARN_UNUSED bool OnDeviceRefresh::SystemServerArtifactsExist(
+WARN_UNUSED bool OnDeviceRefresh::BootImageMainlineExtensionExist(
+    bool on_system,
+    InstructionSet isa,
+    /*out*/ std::string* error_msg,
+    /*out*/ std::vector<std::string>* checked_artifacts) const {
+  std::string path = GetBootImageMainlineExtensionPath(on_system, isa);
+  OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path);
+  return ArtifactsExist(artifacts, /*check_art_file=*/true, error_msg, checked_artifacts);
+}
+
+bool OnDeviceRefresh::SystemServerArtifactsExist(
     bool on_system,
     /*out*/ std::string* error_msg,
     /*out*/ std::set<std::string>* jars_missing_artifacts,
@@ -907,106 +1100,125 @@
   return true;
 }
 
-WARN_UNUSED bool OnDeviceRefresh::BootClasspathArtifactsOnSystemUsable(
-    const apex::ApexInfo& art_apex_info) const {
-  if (!art_apex_info.getIsFactory()) {
+WARN_UNUSED bool OnDeviceRefresh::CheckBuildUserfaultFdGc() const {
+  auto it = config_.GetSystemProperties().find("ro.dalvik.vm.enable_uffd_gc");
+  bool build_enable_uffd_gc = it != config_.GetSystemProperties().end() ?
+                                  ParseBool(it->second) == ParseBoolResult::kTrue :
+                                  false;
+  bool kernel_supports_uffd = KernelSupportsUffd();
+  if (build_enable_uffd_gc && !kernel_supports_uffd) {
+    // Normally, this should not happen. If this happens, the system image was probably built with a
+    // wrong PRODUCT_ENABLE_UFFD_GC flag.
+    LOG(WARNING) << "Userfaultfd GC check failed (build-time: {}, runtime: {})."_format(
+        build_enable_uffd_gc, kernel_supports_uffd);
     return false;
   }
-  LOG(INFO) << "Factory ART APEX mounted.";
-
-  if (!CheckSystemPropertiesAreDefault()) {
-    return false;
-  }
-  LOG(INFO) << "System properties are set to default values.";
-
   return true;
 }
 
-WARN_UNUSED bool OnDeviceRefresh::SystemServerArtifactsOnSystemUsable(
+WARN_UNUSED PreconditionCheckResult OnDeviceRefresh::CheckPreconditionForSystem(
     const std::vector<apex::ApexInfo>& apex_info_list) const {
+  if (!CheckSystemPropertiesAreDefault()) {
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
+  }
+
+  if (!CheckBuildUserfaultFdGc()) {
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
+  }
+
+  std::optional<apex::ApexInfo> art_apex_info = GetArtApexInfo(apex_info_list);
+  if (!art_apex_info.has_value()) {
+    // This should never happen, further up-to-date checks are not possible if it does.
+    LOG(ERROR) << "Could not get ART APEX info.";
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kUnknown);
+  }
+
+  if (!art_apex_info->getIsFactory()) {
+    LOG(INFO) << "Updated ART APEX mounted";
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
+  }
+
   if (std::any_of(apex_info_list.begin(),
                   apex_info_list.end(),
                   [](const apex::ApexInfo& apex_info) { return !apex_info.getIsFactory(); })) {
-    return false;
+    LOG(INFO) << "Updated APEXes mounted";
+    return PreconditionCheckResult::BootImageMainlineExtensionNotOk(
+        OdrMetrics::Trigger::kApexVersionMismatch);
   }
-  LOG(INFO) << "Factory APEXes mounted.";
 
-  if (!CheckSystemPropertiesAreDefault()) {
+  return PreconditionCheckResult::AllOk();
+}
+
+WARN_UNUSED static bool CheckModuleInfo(const art_apex::ModuleInfo& cached_info,
+                                        const apex::ApexInfo& current_info) {
+  if (cached_info.getVersionCode() != current_info.getVersionCode()) {
+    LOG(INFO) << "APEX ({}) version code mismatch (before: {}, now: {})"_format(
+        current_info.getModuleName(), cached_info.getVersionCode(), current_info.getVersionCode());
     return false;
   }
-  LOG(INFO) << "System properties are set to default values.";
+
+  if (cached_info.getVersionName() != current_info.getVersionName()) {
+    LOG(INFO) << "APEX ({}) version name mismatch (before: {}, now: {})"_format(
+        current_info.getModuleName(), cached_info.getVersionName(), current_info.getVersionName());
+    return false;
+  }
+
+  // Check lastUpdateMillis for samegrade installs. If `cached_info` is missing the lastUpdateMillis
+  // field then it is not current with the schema used by this binary so treat it as a samegrade
+  // update. Otherwise check whether the lastUpdateMillis changed.
+  const int64_t cached_last_update_millis =
+      cached_info.hasLastUpdateMillis() ? cached_info.getLastUpdateMillis() : -1;
+  if (cached_last_update_millis != current_info.getLastUpdateMillis()) {
+    LOG(INFO) << "APEX ({}) last update time mismatch (before: {}, now: {})"_format(
+        current_info.getModuleName(),
+        cached_info.getLastUpdateMillis(),
+        current_info.getLastUpdateMillis());
+    return false;
+  }
 
   return true;
 }
 
-WARN_UNUSED bool OnDeviceRefresh::CheckBootClasspathArtifactsAreUpToDate(
-    OdrMetrics& metrics,
-    const InstructionSet isa,
-    const apex::ApexInfo& art_apex_info,
-    const std::optional<art_apex::CacheInfo>& cache_info,
-    /*out*/ std::vector<std::string>* checked_artifacts) const {
-  if (BootClasspathArtifactsOnSystemUsable(art_apex_info)) {
-    // We can use the artifacts on /system. Check if they exist.
-    std::string error_msg;
-    if (BootClasspathArtifactsExist(/*on_system=*/true, /*minimal=*/false, isa, &error_msg)) {
-      return true;
+WARN_UNUSED PreconditionCheckResult OnDeviceRefresh::CheckPreconditionForData(
+    const std::vector<com::android::apex::ApexInfo>& apex_info_list) const {
+  Result<art_apex::CacheInfo> cache_info = ReadCacheInfo();
+  if (!cache_info.ok()) {
+    if (cache_info.error().code() == ENOENT) {
+      // If the cache info file does not exist, it usually means it's the first boot, or the
+      // dalvik-cache directory is cleared by odsign due to corrupted files. Set the trigger to be
+      // `kApexVersionMismatch` to force generate the cache info file and compile if necessary.
+      LOG(INFO) << "No prior cache-info file: " << QuotePath(cache_info_filename_);
+    } else {
+      // This should not happen unless odrefresh is updated to a new version that is not compatible
+      // with an old cache-info file. Further up-to-date checks are not possible if it does.
+      LOG(ERROR) << cache_info.error().message();
     }
-
-    LOG(INFO) << "Incomplete boot classpath artifacts on /system. " << error_msg;
-    LOG(INFO) << "Checking cache.";
-  }
-
-  if (!cache_info.has_value()) {
-    // If the cache info file does not exist, it usually means on-device compilation has not been
-    // done before because the device was using the factory version of modules, or artifacts were
-    // cleared because an updated version was uninstalled. Set the trigger to be
-    // `kApexVersionMismatch` so that compilation will always be performed.
-    PLOG(INFO) << "No prior cache-info file: " << QuotePath(cache_info_filename_);
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    return false;
-  }
-
-  // Check whether the current cache ART module info differs from the current ART module info.
-  const art_apex::ModuleInfo* cached_art_info = cache_info->getFirstArtModuleInfo();
-
-  if (cached_art_info == nullptr) {
-    LOG(INFO) << "Missing ART APEX info from cache-info.";
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    return false;
-  }
-
-  if (cached_art_info->getVersionCode() != art_apex_info.getVersionCode()) {
-    LOG(INFO) << "ART APEX version code mismatch (" << cached_art_info->getVersionCode()
-              << " != " << art_apex_info.getVersionCode() << ").";
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    return false;
-  }
-
-  if (cached_art_info->getVersionName() != art_apex_info.getVersionName()) {
-    LOG(INFO) << "ART APEX version name mismatch (" << cached_art_info->getVersionName()
-              << " != " << art_apex_info.getVersionName() << ").";
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    return false;
-  }
-
-  // Check lastUpdateMillis for samegrade installs. If `cached_art_info` is missing the
-  // lastUpdateMillis field then it is not current with the schema used by this binary so treat
-  // it as a samegrade update. Otherwise check whether the lastUpdateMillis changed.
-  const int64_t cached_art_last_update_millis =
-      cached_art_info->hasLastUpdateMillis() ? cached_art_info->getLastUpdateMillis() : -1;
-  if (cached_art_last_update_millis != art_apex_info.getLastUpdateMillis()) {
-    LOG(INFO) << "ART APEX last update time mismatch (" << cached_art_last_update_millis
-              << " != " << art_apex_info.getLastUpdateMillis() << ").";
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    return false;
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
   }
 
   if (!CheckSystemPropertiesHaveNotChanged(cache_info.value())) {
     // We don't have a trigger kind for system property changes. For now, we reuse
     // `kApexVersionMismatch` as it implies the expected behavior: re-compile regardless of the last
     // compilation attempt.
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    return false;
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
+  }
+
+  // Check whether the current cache ART module info differs from the current ART module info.
+  const art_apex::ModuleInfo* cached_art_info = cache_info->getFirstArtModuleInfo();
+  if (cached_art_info == nullptr) {
+    LOG(ERROR) << "Missing ART APEX info from cache-info.";
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
+  }
+
+  std::optional<apex::ApexInfo> current_art_info = GetArtApexInfo(apex_info_list);
+  if (!current_art_info.has_value()) {
+    // This should never happen, further up-to-date checks are not possible if it does.
+    LOG(ERROR) << "Could not get ART APEX info.";
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kUnknown);
+  }
+
+  if (!CheckModuleInfo(*cached_art_info, *current_art_info)) {
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
   }
 
   // Check boot class components.
@@ -1017,102 +1229,33 @@
   //
   // The boot class components may change unexpectedly, for example an OTA could update
   // framework.jar.
-  const std::vector<art_apex::Component> expected_bcp_compilable_components =
-      GenerateBootClasspathCompilableComponents();
-  if (expected_bcp_compilable_components.size() != 0 &&
-      (!cache_info->hasDex2oatBootClasspath() ||
-       !cache_info->getFirstDex2oatBootClasspath()->hasComponent())) {
+  const std::vector<art_apex::Component> current_dex2oat_bcp_components =
+      GenerateDex2oatBootClasspathComponents();
+
+  const art_apex::Classpath* cached_dex2oat_bcp_components =
+      cache_info->getFirstDex2oatBootClasspath();
+  if (cached_dex2oat_bcp_components == nullptr) {
     LOG(INFO) << "Missing Dex2oatBootClasspath components.";
-    metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
-    return false;
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch);
   }
 
-  const std::vector<art_apex::Component>& bcp_compilable_components =
-      cache_info->getFirstDex2oatBootClasspath()->getComponent();
-  Result<void> result =
-      CheckComponents(expected_bcp_compilable_components, bcp_compilable_components);
+  Result<void> result = CheckComponents(current_dex2oat_bcp_components,
+                                        cached_dex2oat_bcp_components->getComponent());
   if (!result.ok()) {
     LOG(INFO) << "Dex2OatClasspath components mismatch: " << result.error();
-    metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
-    return false;
-  }
-
-  // Cache info looks good, check all compilation artifacts exist.
-  std::string error_msg;
-  if (!BootClasspathArtifactsExist(
-          /*on_system=*/false, /*minimal=*/false, isa, &error_msg, checked_artifacts)) {
-    LOG(INFO) << "Incomplete boot classpath artifacts. " << error_msg;
-    metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
-    // Add the minimal boot image to `checked_artifacts` if exists. This is to prevent the minimal
-    // boot image from being deleted. It does not affect the return value because we should still
-    // attempt to generate a full boot image even if the minimal one exists.
-    if (BootClasspathArtifactsExist(
-            /*on_system=*/false, /*minimal=*/true, isa, &error_msg, checked_artifacts)) {
-      LOG(INFO) << "Found minimal boot classpath artifacts.";
-    }
-    return false;
-  }
-
-  return true;
-}
-
-bool OnDeviceRefresh::CheckSystemServerArtifactsAreUpToDate(
-    OdrMetrics& metrics,
-    const std::vector<apex::ApexInfo>& apex_info_list,
-    const std::optional<art_apex::CacheInfo>& cache_info,
-    /*out*/ std::set<std::string>* jars_to_compile,
-    /*out*/ std::vector<std::string>* checked_artifacts) const {
-  auto compile_all = [&, this]() {
-    *jars_to_compile = AllSystemServerJars();
-    return false;
-  };
-
-  std::set<std::string> jars_missing_artifacts_on_system;
-  bool artifacts_on_system_up_to_date = false;
-
-  if (SystemServerArtifactsOnSystemUsable(apex_info_list)) {
-    // We can use the artifacts on /system. Check if they exist.
-    std::string error_msg;
-    if (SystemServerArtifactsExist(
-            /*on_system=*/true, &error_msg, &jars_missing_artifacts_on_system)) {
-      return true;
-    }
-
-    LOG(INFO) << "Incomplete system server artifacts on /system. " << error_msg;
-    LOG(INFO) << "Checking cache.";
-    artifacts_on_system_up_to_date = true;
-  }
-
-  if (!cache_info.has_value()) {
-    // If the cache info file does not exist, it usually means on-device compilation has not been
-    // done before because the device was using the factory version of modules, or artifacts were
-    // cleared because an updated version was uninstalled. Set the trigger to be
-    // `kApexVersionMismatch` so that compilation will always be performed.
-    PLOG(INFO) << "No prior cache-info file: " << QuotePath(cache_info_filename_);
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    if (artifacts_on_system_up_to_date) {
-      *jars_to_compile = jars_missing_artifacts_on_system;
-      return false;
-    }
-    return compile_all();
+    return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kDexFilesChanged);
   }
 
   // Check whether the current cached module info differs from the current module info.
   const art_apex::ModuleInfoList* cached_module_info_list = cache_info->getFirstModuleInfoList();
-
   if (cached_module_info_list == nullptr) {
-    LOG(INFO) << "Missing APEX info list from cache-info.";
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    return compile_all();
+    LOG(ERROR) << "Missing APEX info list from cache-info.";
+    return PreconditionCheckResult::BootImageMainlineExtensionNotOk(
+        OdrMetrics::Trigger::kApexVersionMismatch);
   }
 
   std::unordered_map<std::string, const art_apex::ModuleInfo*> cached_module_info_map;
   for (const art_apex::ModuleInfo& module_info : cached_module_info_list->getModuleInfo()) {
-    if (!module_info.hasName()) {
-      LOG(INFO) << "Unexpected module info from cache-info. Missing module name.";
-      metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-      return compile_all();
-    }
     cached_module_info_map[module_info.getName()] = &module_info;
   }
 
@@ -1125,44 +1268,33 @@
     auto it = cached_module_info_map.find(apex_name);
     if (it == cached_module_info_map.end()) {
       LOG(INFO) << "Missing APEX info from cache-info (" << apex_name << ").";
-      metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-      return compile_all();
+      return PreconditionCheckResult::BootImageMainlineExtensionNotOk(
+          OdrMetrics::Trigger::kApexVersionMismatch);
     }
 
     const art_apex::ModuleInfo* cached_module_info = it->second;
-
-    if (cached_module_info->getVersionCode() != current_apex_info.getVersionCode()) {
-      LOG(INFO) << "APEX (" << apex_name << ") version code mismatch ("
-                << cached_module_info->getVersionCode()
-                << " != " << current_apex_info.getVersionCode() << ").";
-      metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-      return compile_all();
-    }
-
-    if (cached_module_info->getVersionName() != current_apex_info.getVersionName()) {
-      LOG(INFO) << "APEX (" << apex_name << ") version name mismatch ("
-                << cached_module_info->getVersionName()
-                << " != " << current_apex_info.getVersionName() << ").";
-      metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-      return compile_all();
-    }
-
-    if (!cached_module_info->hasLastUpdateMillis() ||
-        cached_module_info->getLastUpdateMillis() != current_apex_info.getLastUpdateMillis()) {
-      LOG(INFO) << "APEX (" << apex_name << ") last update time mismatch ("
-                << cached_module_info->getLastUpdateMillis()
-                << " != " << current_apex_info.getLastUpdateMillis() << ").";
-      metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-      return compile_all();
+    if (!CheckModuleInfo(*cached_module_info, current_apex_info)) {
+      return PreconditionCheckResult::BootImageMainlineExtensionNotOk(
+          OdrMetrics::Trigger::kApexVersionMismatch);
     }
   }
 
-  if (!CheckSystemPropertiesHaveNotChanged(cache_info.value())) {
-    // We don't have a trigger kind for system property changes. For now, we reuse
-    // `kApexVersionMismatch` as it implies the expected behavior: re-compile regardless of the last
-    // compilation attempt.
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    return false;
+  const std::vector<art_apex::Component> current_bcp_components = GenerateBootClasspathComponents();
+
+  const art_apex::Classpath* cached_bcp_components = cache_info->getFirstBootClasspath();
+  if (cached_bcp_components == nullptr) {
+    LOG(INFO) << "Missing BootClasspath components.";
+    return PreconditionCheckResult::BootImageMainlineExtensionNotOk(
+        OdrMetrics::Trigger::kApexVersionMismatch);
+  }
+
+  result = CheckComponents(current_bcp_components, cached_bcp_components->getComponent());
+  if (!result.ok()) {
+    LOG(INFO) << "BootClasspath components mismatch: " << result.error();
+    // Boot classpath components can be dependencies of system_server components, so system_server
+    // components need to be recompiled if boot classpath components are changed.
+    return PreconditionCheckResult::BootImageMainlineExtensionNotOk(
+        OdrMetrics::Trigger::kDexFilesChanged);
   }
 
   // Check system server components.
@@ -1174,78 +1306,166 @@
   //
   // The system_server components may change unexpectedly, for example an OTA could update
   // services.jar.
-  const std::vector<art_apex::SystemServerComponent> expected_system_server_components =
+  const std::vector<art_apex::SystemServerComponent> current_system_server_components =
       GenerateSystemServerComponents();
-  if (expected_system_server_components.size() != 0 &&
-      (!cache_info->hasSystemServerComponents() ||
-       !cache_info->getFirstSystemServerComponents()->hasComponent())) {
+
+  const art_apex::SystemServerComponents* cached_system_server_components =
+      cache_info->getFirstSystemServerComponents();
+  if (cached_system_server_components == nullptr) {
     LOG(INFO) << "Missing SystemServerComponents.";
-    metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
-    return compile_all();
+    return PreconditionCheckResult::SystemServerNotOk(OdrMetrics::Trigger::kApexVersionMismatch);
   }
 
-  const std::vector<art_apex::SystemServerComponent>& system_server_components =
-      cache_info->getFirstSystemServerComponents()->getComponent();
-  Result<void> result =
-      CheckSystemServerComponents(expected_system_server_components, system_server_components);
+  result = CheckSystemServerComponents(current_system_server_components,
+                                       cached_system_server_components->getComponent());
   if (!result.ok()) {
     LOG(INFO) << "SystemServerComponents mismatch: " << result.error();
-    metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
-    return compile_all();
+    return PreconditionCheckResult::SystemServerNotOk(OdrMetrics::Trigger::kDexFilesChanged);
   }
 
-  const std::vector<art_apex::Component> expected_bcp_components =
-      GenerateBootClasspathComponents();
-  if (expected_bcp_components.size() != 0 &&
-      (!cache_info->hasBootClasspath() || !cache_info->getFirstBootClasspath()->hasComponent())) {
-    LOG(INFO) << "Missing BootClasspath components.";
-    metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
-    return false;
+  return PreconditionCheckResult::AllOk();
+}
+
+WARN_UNUSED BootImages OnDeviceRefresh::CheckBootClasspathArtifactsAreUpToDate(
+    OdrMetrics& metrics,
+    InstructionSet isa,
+    const PreconditionCheckResult& system_result,
+    const PreconditionCheckResult& data_result,
+    /*out*/ std::vector<std::string>* checked_artifacts) const {
+  const char* isa_str = GetInstructionSetString(isa);
+
+  BootImages boot_images_on_system{.primary_boot_image = false,
+                                   .boot_image_mainline_extension = false};
+  if (system_result.IsPrimaryBootImageOk()) {
+    // We can use the artifacts on /system. Check if they exist.
+    std::string error_msg;
+    if (PrimaryBootImageExist(/*on_system=*/true, /*minimal=*/false, isa, &error_msg)) {
+      boot_images_on_system.primary_boot_image = true;
+    } else {
+      LOG(INFO) << "Incomplete primary boot image or framework extension on /system: " << error_msg;
+    }
   }
 
-  const std::vector<art_apex::Component>& bcp_components =
-      cache_info->getFirstBootClasspath()->getComponent();
-  result = CheckComponents(expected_bcp_components, bcp_components);
-  if (!result.ok()) {
-    LOG(INFO) << "BootClasspath components mismatch: " << result.error();
-    metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
-    // Boot classpath components can be dependencies of system_server components, so system_server
-    // components need to be recompiled if boot classpath components are changed.
-    return compile_all();
+  if (boot_images_on_system.primary_boot_image && system_result.IsBootImageMainlineExtensionOk()) {
+    std::string error_msg;
+    if (BootImageMainlineExtensionExist(/*on_system=*/true, isa, &error_msg)) {
+      boot_images_on_system.boot_image_mainline_extension = true;
+    } else {
+      LOG(INFO) << "Incomplete boot image mainline extension on /system: " << error_msg;
+    }
   }
 
-  std::string error_msg;
-  std::set<std::string> jars_missing_artifacts_on_data;
-  if (!SystemServerArtifactsExist(
-          /*on_system=*/false, &error_msg, &jars_missing_artifacts_on_data, checked_artifacts)) {
-    if (artifacts_on_system_up_to_date) {
-      // Check if the remaining system_server artifacts are on /data.
-      std::set_intersection(jars_missing_artifacts_on_system.begin(),
-                            jars_missing_artifacts_on_system.end(),
-                            jars_missing_artifacts_on_data.begin(),
-                            jars_missing_artifacts_on_data.end(),
-                            std::inserter(*jars_to_compile, jars_to_compile->end()));
-      if (!jars_to_compile->empty()) {
-        LOG(INFO) << "Incomplete system_server artifacts on /data. " << error_msg;
-        metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
-        return false;
+  if (boot_images_on_system.Count() == BootImages::kMaxCount) {
+    LOG(INFO) << "Boot images on /system OK ({})"_format(isa_str);
+    // Nothing to compile.
+    return BootImages{.primary_boot_image = false, .boot_image_mainline_extension = false};
+  }
+
+  LOG(INFO) << "Checking boot images /data ({})"_format(isa_str);
+  BootImages boot_images_on_data{.primary_boot_image = false,
+                                 .boot_image_mainline_extension = false};
+
+  if (data_result.IsPrimaryBootImageOk()) {
+    std::string error_msg;
+    if (PrimaryBootImageExist(
+            /*on_system=*/false, /*minimal=*/false, isa, &error_msg, checked_artifacts)) {
+      boot_images_on_data.primary_boot_image = true;
+    } else {
+      LOG(INFO) << "Incomplete primary boot image on /data: " << error_msg;
+      metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
+      // Add the minimal boot image to `checked_artifacts` if exists. This is to prevent the minimal
+      // boot image from being deleted. It does not affect the return value because we should still
+      // attempt to generate a full boot image even if the minimal one exists.
+      if (PrimaryBootImageExist(
+              /*on_system=*/false, /*minimal=*/true, isa, &error_msg, checked_artifacts)) {
+        LOG(INFO) << "Found minimal primary boot image ({})"_format(isa_str);
       }
+    }
+  } else {
+    metrics.SetTrigger(data_result.GetTrigger());
+  }
 
-      LOG(INFO) << "Found the remaining system_server artifacts on /data.";
-      return true;
+  if (boot_images_on_data.primary_boot_image) {
+    if (data_result.IsBootImageMainlineExtensionOk()) {
+      std::string error_msg;
+      if (BootImageMainlineExtensionExist(
+              /*on_system=*/false, isa, &error_msg, checked_artifacts)) {
+        boot_images_on_data.boot_image_mainline_extension = true;
+      } else {
+        LOG(INFO) << "Incomplete boot image mainline extension on /data: " << error_msg;
+        metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
+      }
+    } else {
+      metrics.SetTrigger(data_result.GetTrigger());
+    }
+  }
+
+  BootImages boot_images_to_generate{
+      .primary_boot_image =
+          !boot_images_on_system.primary_boot_image && !boot_images_on_data.primary_boot_image,
+      .boot_image_mainline_extension = !boot_images_on_system.boot_image_mainline_extension &&
+                                       !boot_images_on_data.boot_image_mainline_extension,
+  };
+
+  if (boot_images_to_generate.Count() == 0) {
+    LOG(INFO) << "Boot images on /data OK ({})"_format(isa_str);
+  }
+
+  return boot_images_to_generate;
+}
+
+std::set<std::string> OnDeviceRefresh::CheckSystemServerArtifactsAreUpToDate(
+    OdrMetrics& metrics,
+    const PreconditionCheckResult& system_result,
+    const PreconditionCheckResult& data_result,
+    /*out*/ std::vector<std::string>* checked_artifacts) const {
+  std::set<std::string> jars_to_compile;
+  std::set<std::string> jars_missing_artifacts_on_system;
+  if (system_result.IsSystemServerOk()) {
+    // We can use the artifacts on /system. Check if they exist.
+    std::string error_msg;
+    if (SystemServerArtifactsExist(
+            /*on_system=*/true, &error_msg, &jars_missing_artifacts_on_system)) {
+      LOG(INFO) << "system_server artifacts on /system OK";
+      return {};
     }
 
-    LOG(INFO) << "Incomplete system_server artifacts. " << error_msg;
-    metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
-    *jars_to_compile = jars_missing_artifacts_on_data;
-    return false;
+    LOG(INFO) << "Incomplete system server artifacts on /system: " << error_msg;
+    LOG(INFO) << "Checking system server artifacts /data";
+  } else {
+    jars_missing_artifacts_on_system = AllSystemServerJars();
   }
 
-  return true;
+  std::set<std::string> jars_missing_artifacts_on_data;
+  std::string error_msg;
+  if (data_result.IsSystemServerOk()) {
+    SystemServerArtifactsExist(
+        /*on_system=*/false, &error_msg, &jars_missing_artifacts_on_data, checked_artifacts);
+  } else {
+    jars_missing_artifacts_on_data = AllSystemServerJars();
+  }
+
+  std::set_intersection(jars_missing_artifacts_on_system.begin(),
+                        jars_missing_artifacts_on_system.end(),
+                        jars_missing_artifacts_on_data.begin(),
+                        jars_missing_artifacts_on_data.end(),
+                        std::inserter(jars_to_compile, jars_to_compile.end()));
+  if (!jars_to_compile.empty()) {
+    if (data_result.IsSystemServerOk()) {
+      LOG(INFO) << "Incomplete system_server artifacts on /data: " << error_msg;
+      metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
+    } else {
+      metrics.SetTrigger(data_result.GetTrigger());
+    }
+    return jars_to_compile;
+  }
+
+  LOG(INFO) << "system_server artifacts on /data OK";
+  return {};
 }
 
 Result<void> OnDeviceRefresh::CleanupArtifactDirectory(
-    const std::vector<std::string>& artifacts_to_keep) const {
+    OdrMetrics& metrics, const std::vector<std::string>& artifacts_to_keep) const {
   const std::string& artifact_dir = config_.GetArtifactDirectory();
   std::unordered_set<std::string> artifact_set{artifacts_to_keep.begin(), artifacts_to_keep.end()};
 
@@ -1263,7 +1483,9 @@
     // undefined behavior;
     entries.push_back(entry);
   }
-  if (ec) {
+  if (ec && ec.value() != ENOENT) {
+    metrics.SetStatus(ec.value() == EPERM ? OdrMetrics::Status::kDalvikCachePermissionDenied :
+                                            OdrMetrics::Status::kIoError);
     return Errorf("Failed to iterate over entries in the artifact directory: {}", ec.message());
   }
 
@@ -1273,6 +1495,7 @@
       if (!ContainsElement(artifact_set, path)) {
         LOG(INFO) << "Removing " << path;
         if (unlink(path.c_str()) != 0) {
+          metrics.SetStatus(OdrMetrics::Status::kIoError);
           return ErrnoErrorf("Failed to remove file {}", QuotePath(path));
         }
       }
@@ -1280,6 +1503,7 @@
       // Neither a regular file nor a directory. Unexpected file type.
       LOG(INFO) << "Removing " << path;
       if (unlink(path.c_str()) != 0) {
+        metrics.SetStatus(OdrMetrics::Status::kIoError);
         return ErrnoErrorf("Failed to remove file {}", QuotePath(path));
       }
     }
@@ -1338,9 +1562,12 @@
 
   // Clean-up helper used to simplify clean-ups and handling failures there.
   auto cleanup_and_compile_all = [&, this]() {
-    compilation_options->compile_boot_classpath_for_isas = config_.GetBootClasspathIsas();
-    compilation_options->system_server_jars_to_compile = AllSystemServerJars();
-    return RemoveArtifactsDirectory() ? ExitCode::kCompilationRequired : ExitCode::kCleanupFailed;
+    *compilation_options = CompilationOptions::CompileAll(*this);
+    if (!RemoveArtifactsDirectory()) {
+      metrics.SetStatus(OdrMetrics::Status::kIoError);
+      return ExitCode::kCleanupFailed;
+    }
+    return ExitCode::kCompilationRequired;
   };
 
   std::optional<std::vector<apex::ApexInfo>> apex_info_list = GetApexInfoList();
@@ -1368,23 +1595,18 @@
   // Record ART APEX last update milliseconds (used in compilation log).
   metrics.SetArtApexLastUpdateMillis(art_apex_info->getLastUpdateMillis());
 
-  std::optional<art_apex::CacheInfo> cache_info = ReadCacheInfo();
-  if (!cache_info.has_value() && OS::FileExists(cache_info_filename_.c_str())) {
-    // This should not happen unless odrefresh is updated to a new version that is not
-    // compatible with an old cache-info file. Further up-to-date checks are not possible if it
-    // does.
-    PLOG(ERROR) << "Failed to parse cache-info file: " << QuotePath(cache_info_filename_);
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    return cleanup_and_compile_all();
-  }
-
   InstructionSet system_server_isa = config_.GetSystemServerIsa();
   std::vector<std::string> checked_artifacts;
 
-  for (const InstructionSet isa : config_.GetBootClasspathIsas()) {
-    if (!CheckBootClasspathArtifactsAreUpToDate(
-            metrics, isa, art_apex_info.value(), cache_info, &checked_artifacts)) {
-      compilation_options->compile_boot_classpath_for_isas.push_back(isa);
+  PreconditionCheckResult system_result = CheckPreconditionForSystem(apex_info_list.value());
+  PreconditionCheckResult data_result = CheckPreconditionForData(apex_info_list.value());
+
+  for (InstructionSet isa : config_.GetBootClasspathIsas()) {
+    BootImages boot_images_to_generate = CheckBootClasspathArtifactsAreUpToDate(
+        metrics, isa, system_result, data_result, &checked_artifacts);
+    if (boot_images_to_generate.Count() > 0) {
+      compilation_options->boot_images_to_generate_for_isas.emplace_back(isa,
+                                                                         boot_images_to_generate);
       // system_server artifacts are invalid without valid boot classpath artifacts.
       if (isa == system_server_isa) {
         compilation_options->system_server_jars_to_compile = AllSystemServerJars();
@@ -1393,15 +1615,17 @@
   }
 
   if (compilation_options->system_server_jars_to_compile.empty()) {
-    CheckSystemServerArtifactsAreUpToDate(metrics,
-                                          apex_info_list.value(),
-                                          cache_info,
-                                          &compilation_options->system_server_jars_to_compile,
-                                          &checked_artifacts);
+    compilation_options->system_server_jars_to_compile = CheckSystemServerArtifactsAreUpToDate(
+        metrics, system_result, data_result, &checked_artifacts);
   }
 
-  bool compilation_required = (!compilation_options->compile_boot_classpath_for_isas.empty() ||
-                               !compilation_options->system_server_jars_to_compile.empty());
+  bool compilation_required = compilation_options->CompilationUnitCount() > 0;
+
+  if (!compilation_required && !data_result.IsAllOk()) {
+    // Return kCompilationRequired to generate the cache info even if there's nothing to compile.
+    compilation_required = true;
+    metrics.SetTrigger(data_result.GetTrigger());
+  }
 
   // If partial compilation is disabled, we should compile everything regardless of what's in
   // `compilation_options`.
@@ -1409,12 +1633,10 @@
     return cleanup_and_compile_all();
   }
 
-  // We should only keep the cache info if we have artifacts on /data.
-  if (!checked_artifacts.empty()) {
-    checked_artifacts.push_back(cache_info_filename_);
-  }
+  // Always keep the cache info.
+  checked_artifacts.push_back(cache_info_filename_);
 
-  Result<void> result = CleanupArtifactDirectory(checked_artifacts);
+  Result<void> result = CleanupArtifactDirectory(metrics, checked_artifacts);
   if (!result.ok()) {
     LOG(ERROR) << result.error();
     return ExitCode::kCleanupFailed;
@@ -1423,304 +1645,364 @@
   return compilation_required ? ExitCode::kCompilationRequired : ExitCode::kOkay;
 }
 
-WARN_UNUSED bool OnDeviceRefresh::CompileBootClasspathArtifacts(
-    const InstructionSet isa,
+WARN_UNUSED CompilationResult OnDeviceRefresh::RunDex2oat(
     const std::string& staging_dir,
-    OdrMetrics& metrics,
-    const std::function<void()>& on_dex2oat_success,
-    bool minimal,
-    std::string* error_msg) const {
-  ScopedOdrCompilationTimer compilation_timer(metrics);
+    const std::string& debug_message,
+    InstructionSet isa,
+    const std::vector<std::string>& dex_files,
+    const std::vector<std::string>& boot_classpath,
+    const std::vector<std::string>& input_boot_images,
+    const OdrArtifacts& artifacts,
+    const std::vector<std::string>& extra_args,
+    /*inout*/ std::vector<std::unique_ptr<File>>& readonly_files_raii) const {
   std::vector<std::string> args;
   args.push_back(config_.GetDex2Oat());
 
   AddDex2OatCommonOptions(args);
   AddDex2OatDebugInfo(args);
   AddDex2OatInstructionSet(args, isa);
-  if (!AddDex2OatConcurrencyArguments(args)) {
-    return false;
+  Result<void> result = AddDex2OatConcurrencyArguments(args, config_.GetCompilationOsMode());
+  if (!result.ok()) {
+    return CompilationResult::Error(OdrMetrics::Status::kUnknown, result.error().message());
   }
 
-  std::vector<std::unique_ptr<File>> readonly_files_raii;
-  const std::string art_boot_profile_file = GetArtRoot() + "/etc/boot-image.prof";
-  const std::string framework_boot_profile_file = GetAndroidRoot() + "/etc/boot-image.prof";
-  AddDex2OatProfileAndCompilerFilter(args, readonly_files_raii,
-                                     {art_boot_profile_file, framework_boot_profile_file});
-
-  // Compile as a single image for fewer files and slightly less memory overhead.
-  args.emplace_back("--single-image");
-
-  args.emplace_back(android::base::StringPrintf("--base=0x%08x", ART_BASE_ADDRESS));
-
-  const std::string dirty_image_objects_file(GetAndroidRoot() + "/etc/dirty-image-objects");
-  if (OS::FileExists(dirty_image_objects_file.c_str())) {
-    std::unique_ptr<File> file(OS::OpenFileForReading(dirty_image_objects_file.c_str()));
-    args.emplace_back(android::base::StringPrintf("--dirty-image-objects-fd=%d", file->Fd()));
-    readonly_files_raii.push_back(std::move(file));
-  } else {
-    LOG(WARNING) << "Missing dirty objects file : " << QuotePath(dirty_image_objects_file);
+  // dex2oat reads some system properties from cache-info.xml generated by odrefresh.
+  result = AddCacheInfoFd(args, readonly_files_raii, cache_info_filename_);
+  if (!result.ok()) {
+    return CompilationResult::Error(OdrMetrics::Status::kUnknown, result.error().message());
   }
 
-  const std::string preloaded_classes_file(GetAndroidRoot() + "/etc/preloaded-classes");
-  if (OS::FileExists(preloaded_classes_file.c_str())) {
-    std::unique_ptr<File> file(OS::OpenFileForReading(preloaded_classes_file.c_str()));
-    args.emplace_back(android::base::StringPrintf("--preloaded-classes-fds=%d", file->Fd()));
-    readonly_files_raii.push_back(std::move(file));
-  } else {
-    LOG(WARNING) << "Missing preloaded classes file : " << QuotePath(preloaded_classes_file);
-  }
-
-  // Add boot classpath jars to compile.
-  std::vector<std::string> jars_to_compile = boot_classpath_compilable_jars_;
-  if (minimal) {
-    auto end =
-        std::remove_if(jars_to_compile.begin(), jars_to_compile.end(), [](const std::string& jar) {
-          return !android::base::StartsWith(jar, GetArtRoot());
-        });
-    jars_to_compile.erase(end, jars_to_compile.end());
-  }
-
-  for (const std::string& component : jars_to_compile) {
-    std::string actual_path = AndroidRootRewrite(component);
-    args.emplace_back("--dex-file=" + component);
+  for (const std::string& dex_file : dex_files) {
+    std::string actual_path = RewriteParentDirectoryIfNeeded(dex_file);
+    args.emplace_back("--dex-file=" + dex_file);
     std::unique_ptr<File> file(OS::OpenFileForReading(actual_path.c_str()));
-    args.emplace_back(android::base::StringPrintf("--dex-fd=%d", file->Fd()));
+    args.emplace_back(StringPrintf("--dex-fd=%d", file->Fd()));
     readonly_files_raii.push_back(std::move(file));
   }
 
   args.emplace_back("--runtime-arg");
-  args.emplace_back(Concatenate({"-Xbootclasspath:", android::base::Join(jars_to_compile, ":")}));
-  if (!AddBootClasspathFds(args, readonly_files_raii, jars_to_compile)) {
-    return false;
+  args.emplace_back("-Xbootclasspath:" + Join(boot_classpath, ":"));
+  result = AddBootClasspathFds(args, readonly_files_raii, boot_classpath);
+  if (!result.ok()) {
+    return CompilationResult::Error(OdrMetrics::Status::kIoError, result.error().message());
   }
 
-  const std::string image_location = GetBootImagePath(/*on_system=*/false, minimal, isa);
-  const OdrArtifacts artifacts = OdrArtifacts::ForBootImage(image_location);
+  if (!input_boot_images.empty()) {
+    args.emplace_back("--boot-image=" + Join(input_boot_images, ':'));
+    AddCompiledBootClasspathFdsIfAny(
+        args, readonly_files_raii, boot_classpath, isa, input_boot_images);
+  }
 
   args.emplace_back("--oat-location=" + artifacts.OatPath());
-  const std::pair<const std::string, const char*> location_kind_pairs[] = {
-      std::make_pair(artifacts.ImagePath(), "image"),
+  std::pair<std::string, const char*> location_kind_pairs[] = {
+      std::make_pair(artifacts.ImagePath(), artifacts.ImageKind()),
       std::make_pair(artifacts.OatPath(), "oat"),
       std::make_pair(artifacts.VdexPath(), "output-vdex")};
   std::vector<std::unique_ptr<File>> staging_files;
-  for (const auto& location_kind_pair : location_kind_pairs) {
-    auto& [location, kind] = location_kind_pair;
-    const std::string staging_location = GetStagingLocation(staging_dir, location);
+  for (const auto& [location, kind] : location_kind_pairs) {
+    std::string staging_location = GetStagingLocation(staging_dir, location);
     std::unique_ptr<File> staging_file(OS::CreateEmptyFile(staging_location.c_str()));
     if (staging_file == nullptr) {
-      PLOG(ERROR) << "Failed to create " << kind << " file: " << staging_location;
-      metrics.SetStatus(OdrMetrics::Status::kIoError);
-      EraseFiles(staging_files);
-      return false;
+      return CompilationResult::Error(
+          OdrMetrics::Status::kIoError,
+          "Failed to create {} file '{}'"_format(kind, staging_location));
     }
-
-    if (fchmod(staging_file->Fd(), S_IRUSR | S_IWUSR) != 0) {
-      PLOG(ERROR) << "Could not set file mode on " << QuotePath(staging_location);
-      metrics.SetStatus(OdrMetrics::Status::kIoError);
-      EraseFiles(staging_files);
-      return false;
-    }
-
-    args.emplace_back(android::base::StringPrintf("--%s-fd=%d", kind, staging_file->Fd()));
+    // Don't check the state of the staging file. It doesn't need to be flushed because it's removed
+    // after the compilation regardless of success or failure.
+    staging_file->MarkUnchecked();
+    args.emplace_back(StringPrintf("--%s-fd=%d", kind, staging_file->Fd()));
     staging_files.emplace_back(std::move(staging_file));
   }
 
-  const std::string install_location = android::base::Dirname(image_location);
+  std::string install_location = Dirname(artifacts.OatPath());
   if (!EnsureDirectoryExists(install_location)) {
-    metrics.SetStatus(OdrMetrics::Status::kIoError);
-    return false;
+    return CompilationResult::Error(
+        OdrMetrics::Status::kIoError,
+        "Error encountered when preparing directory '{}'"_format(install_location));
   }
 
-  const time_t timeout = GetSubprocessTimeout();
-  const std::string cmd_line = android::base::Join(args, ' ');
-  LOG(INFO) << android::base::StringPrintf("Compiling boot classpath (%s%s): %s [timeout %lds]",
-                                           GetInstructionSetString(isa),
-                                           minimal ? ", minimal" : "",
-                                           cmd_line.c_str(),
-                                           timeout);
+  std::copy(extra_args.begin(), extra_args.end(), std::back_inserter(args));
+
+  Timer timer;
+  time_t timeout = GetSubprocessTimeout();
+  std::string cmd_line = Join(args, ' ');
+  LOG(INFO) << "{}: {} [timeout {}s]"_format(debug_message, cmd_line, timeout);
   if (config_.GetDryRun()) {
     LOG(INFO) << "Compilation skipped (dry-run).";
-    return true;
+    return CompilationResult::Ok();
   }
 
-  bool timed_out = false;
-  int dex2oat_exit_code = exec_utils_->ExecAndReturnCode(args, timeout, &timed_out, error_msg);
+  std::string error_msg;
+  ExecResult dex2oat_result = exec_utils_->ExecAndReturnResult(args, timeout, &error_msg);
 
-  if (dex2oat_exit_code != 0) {
-    if (timed_out) {
-      metrics.SetStatus(OdrMetrics::Status::kTimeLimitExceeded);
-    } else {
-      metrics.SetStatus(OdrMetrics::Status::kDex2OatError);
-    }
-    EraseFiles(staging_files);
-    return false;
+  if (dex2oat_result.exit_code != 0) {
+    return CompilationResult::Dex2oatError(
+        dex2oat_result.exit_code < 0 ?
+            error_msg :
+            "dex2oat returned an unexpected code: {}"_format(dex2oat_result.exit_code),
+        timer.duration().count(),
+        dex2oat_result);
   }
 
   if (!MoveOrEraseFiles(staging_files, install_location)) {
-    metrics.SetStatus(OdrMetrics::Status::kInstallFailed);
-    return false;
+    return CompilationResult::Error(OdrMetrics::Status::kIoError,
+                                    "Failed to commit artifacts to '{}'"_format(install_location));
   }
 
-  on_dex2oat_success();
-  return true;
+  return CompilationResult::Dex2oatOk(timer.duration().count(), dex2oat_result);
 }
 
-WARN_UNUSED bool OnDeviceRefresh::CompileSystemServerArtifacts(
-    const std::string& staging_dir,
-    OdrMetrics& metrics,
-    const std::set<std::string>& system_server_jars_to_compile,
-    const std::function<void()>& on_dex2oat_success,
-    std::string* error_msg) const {
-  ScopedOdrCompilationTimer compilation_timer(metrics);
-  std::vector<std::string> classloader_context;
+WARN_UNUSED CompilationResult
+OnDeviceRefresh::RunDex2oatForBootClasspath(const std::string& staging_dir,
+                                            const std::string& debug_name,
+                                            InstructionSet isa,
+                                            const std::vector<std::string>& dex_files,
+                                            const std::vector<std::string>& boot_classpath,
+                                            const std::vector<std::string>& input_boot_images,
+                                            const std::string& output_path) const {
+  std::vector<std::string> args;
+  std::vector<std::unique_ptr<File>> readonly_files_raii;
 
-  const std::string dex2oat = config_.GetDex2Oat();
-  const InstructionSet isa = config_.GetSystemServerIsa();
-  for (const std::string& jar : all_systemserver_jars_) {
-    auto scope_guard = android::base::make_scope_guard([&]() {
-      if (ContainsElement(systemserver_classpath_jars_, jar)) {
-        classloader_context.emplace_back(jar);
-      }
-    });
+  // Compile as a single image for fewer files and slightly less memory overhead.
+  args.emplace_back("--single-image");
 
-    if (!ContainsElement(system_server_jars_to_compile, jar)) {
-      continue;
+  if (input_boot_images.empty()) {
+    // Primary boot image.
+    std::string art_boot_profile_file = GetArtRoot() + "/etc/boot-image.prof";
+    std::string framework_boot_profile_file = GetAndroidRoot() + "/etc/boot-image.prof";
+    bool has_any_profile = AddDex2OatProfile(
+        args, readonly_files_raii, {art_boot_profile_file, framework_boot_profile_file});
+    if (!has_any_profile) {
+      return CompilationResult::Error(OdrMetrics::Status::kIoError, "Missing boot image profile");
     }
-
-    std::vector<std::unique_ptr<File>> readonly_files_raii;
-    std::vector<std::string> args;
-    args.emplace_back(dex2oat);
-    args.emplace_back("--dex-file=" + jar);
-
-    std::string actual_jar_path = AndroidRootRewrite(jar);
-    std::unique_ptr<File> dex_file(OS::OpenFileForReading(actual_jar_path.c_str()));
-    args.emplace_back(android::base::StringPrintf("--dex-fd=%d", dex_file->Fd()));
-    readonly_files_raii.push_back(std::move(dex_file));
-
-    AddDex2OatCommonOptions(args);
-    AddDex2OatDebugInfo(args);
-    AddDex2OatInstructionSet(args, isa);
-    if (!AddDex2OatConcurrencyArguments(args)) {
-      return false;
-    }
-
-    const std::string jar_name(android::base::Basename(jar));
-    const std::string profile = Concatenate({GetAndroidRoot(), "/framework/", jar_name, ".prof"});
-    std::string compiler_filter = config_.GetSystemServerCompilerFilter();
-    if (compiler_filter == "speed-profile") {
-      AddDex2OatProfileAndCompilerFilter(args, readonly_files_raii, {profile});
-    } else {
+    const std::string& compiler_filter = config_.GetBootImageCompilerFilter();
+    if (!compiler_filter.empty()) {
       args.emplace_back("--compiler-filter=" + compiler_filter);
-    }
-
-    const std::string image_location = GetSystemServerImagePath(/*on_system=*/false, jar);
-    const std::string install_location = android::base::Dirname(image_location);
-    if (!EnsureDirectoryExists(install_location)) {
-      metrics.SetStatus(OdrMetrics::Status::kIoError);
-      return false;
-    }
-
-    OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location);
-    CHECK_EQ(artifacts.OatPath(), GetApexDataOdexFilename(jar.c_str(), isa));
-
-    const std::pair<const std::string, const char*> location_kind_pairs[] = {
-        std::make_pair(artifacts.ImagePath(), "app-image"),
-        std::make_pair(artifacts.OatPath(), "oat"),
-        std::make_pair(artifacts.VdexPath(), "output-vdex")};
-
-    std::vector<std::unique_ptr<File>> staging_files;
-    for (const auto& location_kind_pair : location_kind_pairs) {
-      auto& [location, kind] = location_kind_pair;
-      const std::string staging_location = GetStagingLocation(staging_dir, location);
-      std::unique_ptr<File> staging_file(OS::CreateEmptyFile(staging_location.c_str()));
-      if (staging_file == nullptr) {
-        PLOG(ERROR) << "Failed to create " << kind << " file: " << staging_location;
-        metrics.SetStatus(OdrMetrics::Status::kIoError);
-        EraseFiles(staging_files);
-        return false;
-      }
-      args.emplace_back(android::base::StringPrintf("--%s-fd=%d", kind, staging_file->Fd()));
-      staging_files.emplace_back(std::move(staging_file));
-    }
-    args.emplace_back("--oat-location=" + artifacts.OatPath());
-
-    args.emplace_back("--runtime-arg");
-    args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetBootClasspath()}));
-
-    auto bcp_jars = android::base::Split(config_.GetBootClasspath(), ":");
-    if (!AddBootClasspathFds(args, readonly_files_raii, bcp_jars)) {
-      return false;
-    }
-    std::string unused_error_msg;
-    // If the boot classpath artifacts are not on /data, then the boot classpath are not re-compiled
-    // and the artifacts must exist on /system.
-    bool boot_image_on_system = !BootClasspathArtifactsExist(
-        /*on_system=*/false, /*minimal=*/false, isa, &unused_error_msg);
-    AddCompiledBootClasspathFdsIfAny(
-        args,
-        readonly_files_raii,
-        bcp_jars,
-        isa,
-        boot_image_on_system ? GetSystemBootImageDir() : config_.GetArtifactDirectory());
-    args.emplace_back(
-        Concatenate({"--boot-image=",
-                     boot_image_on_system ? GetBootImage(/*on_system=*/true, /*minimal=*/false) +
-                                                ":" + GetSystemBootImageExtension() :
-                                            GetBootImage(/*on_system=*/false, /*minimal=*/false)}));
-
-    const std::string context_path = android::base::Join(classloader_context, ':');
-    if (art::ContainsElement(systemserver_classpath_jars_, jar)) {
-      args.emplace_back("--class-loader-context=PCL[" + context_path + "]");
     } else {
-      args.emplace_back("--class-loader-context=PCL[];PCL[" + context_path + "]");
-    }
-    if (!classloader_context.empty()) {
-      std::vector<int> fds;
-      for (const std::string& path : classloader_context) {
-        std::string actual_path = AndroidRootRewrite(path);
-        std::unique_ptr<File> file(OS::OpenFileForReading(actual_path.c_str()));
-        if (!file->IsValid()) {
-          PLOG(ERROR) << "Failed to open classloader context " << actual_path;
-          metrics.SetStatus(OdrMetrics::Status::kIoError);
-          return false;
-        }
-        fds.emplace_back(file->Fd());
-        readonly_files_raii.emplace_back(std::move(file));
-      }
-      const std::string context_fds = android::base::Join(fds, ':');
-      args.emplace_back(Concatenate({"--class-loader-context-fds=", context_fds}));
+      args.emplace_back(StringPrintf("--compiler-filter=%s", kPrimaryCompilerFilter));
     }
 
-    const time_t timeout = GetSubprocessTimeout();
-    const std::string cmd_line = android::base::Join(args, ' ');
-    LOG(INFO) << "Compiling " << jar << ": " << cmd_line << " [timeout " << timeout << "s]";
-    if (config_.GetDryRun()) {
-      LOG(INFO) << "Compilation skipped (dry-run).";
-      return true;
+    args.emplace_back(StringPrintf("--base=0x%08x", ART_BASE_ADDRESS));
+
+    std::string dirty_image_objects_file(GetAndroidRoot() + "/etc/dirty-image-objects");
+    if (OS::FileExists(dirty_image_objects_file.c_str())) {
+      std::unique_ptr<File> file(OS::OpenFileForReading(dirty_image_objects_file.c_str()));
+      args.emplace_back(StringPrintf("--dirty-image-objects-fd=%d", file->Fd()));
+      readonly_files_raii.push_back(std::move(file));
+    } else {
+      LOG(WARNING) << "Missing dirty objects file: '{}'"_format(dirty_image_objects_file);
     }
 
-    bool timed_out = false;
-    int dex2oat_exit_code = exec_utils_->ExecAndReturnCode(args, timeout, &timed_out, error_msg);
-
-    if (dex2oat_exit_code != 0) {
-      if (timed_out) {
-        metrics.SetStatus(OdrMetrics::Status::kTimeLimitExceeded);
-      } else {
-        metrics.SetStatus(OdrMetrics::Status::kDex2OatError);
-      }
-      EraseFiles(staging_files);
-      return false;
+    std::string preloaded_classes_file(GetAndroidRoot() + "/etc/preloaded-classes");
+    if (OS::FileExists(preloaded_classes_file.c_str())) {
+      std::unique_ptr<File> file(OS::OpenFileForReading(preloaded_classes_file.c_str()));
+      args.emplace_back(StringPrintf("--preloaded-classes-fds=%d", file->Fd()));
+      readonly_files_raii.push_back(std::move(file));
+    } else {
+      LOG(WARNING) << "Missing preloaded classes file: '{}'"_format(preloaded_classes_file);
     }
-
-    if (!MoveOrEraseFiles(staging_files, install_location)) {
-      metrics.SetStatus(OdrMetrics::Status::kInstallFailed);
-      return false;
-    }
-
-    on_dex2oat_success();
+  } else {
+    // Mainline extension.
+    args.emplace_back(StringPrintf("--compiler-filter=%s", kMainlineCompilerFilter));
   }
 
-  return true;
+  return RunDex2oat(
+      staging_dir,
+      "Compiling boot classpath ({}, {})"_format(GetInstructionSetString(isa), debug_name),
+      isa,
+      dex_files,
+      boot_classpath,
+      input_boot_images,
+      OdrArtifacts::ForBootImage(output_path),
+      args,
+      readonly_files_raii);
+}
+
+WARN_UNUSED CompilationResult
+OnDeviceRefresh::CompileBootClasspath(const std::string& staging_dir,
+                                      InstructionSet isa,
+                                      BootImages boot_images,
+                                      const std::function<void()>& on_dex2oat_success) const {
+  DCHECK_GT(boot_images.Count(), 0);
+  DCHECK_IMPLIES(boot_images.primary_boot_image, boot_images.boot_image_mainline_extension);
+
+  CompilationResult result = CompilationResult::Ok();
+
+  if (config_.GetMinimal()) {
+    result.Merge(
+        CompilationResult::Error(OdrMetrics::Status::kUnknown, "Minimal boot image requested"));
+  }
+
+  if (!CheckCompilationSpace()) {
+    result.Merge(CompilationResult::Error(OdrMetrics::Status::kNoSpace, "Insufficient space"));
+  }
+
+  if (result.IsOk() && boot_images.primary_boot_image) {
+    CompilationResult primary_result = RunDex2oatForBootClasspath(
+        staging_dir,
+        "primary",
+        isa,
+        dex2oat_boot_classpath_jars_,
+        dex2oat_boot_classpath_jars_,
+        /*input_boot_images=*/{},
+        GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/false, isa));
+    result.Merge(primary_result);
+
+    if (primary_result.IsOk()) {
+      on_dex2oat_success();
+
+      // Remove the minimal boot image only if the full boot image is successfully generated.
+      std::string path = GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/true, isa);
+      OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path);
+      unlink(artifacts.ImagePath().c_str());
+      unlink(artifacts.OatPath().c_str());
+      unlink(artifacts.VdexPath().c_str());
+    }
+  }
+
+  if (!result.IsOk() && boot_images.primary_boot_image) {
+    LOG(ERROR) << "Compilation of primary BCP failed: " << result.error_msg;
+
+    // Fall back to generating a minimal boot image.
+    // The compilation of the full boot image will be retried on later reboots with a backoff
+    // time, and the minimal boot image will be removed once the compilation of the full boot
+    // image succeeds.
+    std::string ignored_error_msg;
+    if (PrimaryBootImageExist(
+            /*on_system=*/false, /*minimal=*/true, isa, &ignored_error_msg)) {
+      LOG(INFO) << "Minimal boot image already up-to-date";
+      return result;
+    }
+    std::vector<std::string> art_bcp_jars = GetArtBcpJars();
+    CompilationResult minimal_result = RunDex2oatForBootClasspath(
+        staging_dir,
+        "minimal",
+        isa,
+        art_bcp_jars,
+        art_bcp_jars,
+        /*input_boot_images=*/{},
+        GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/true, isa));
+    result.Merge(minimal_result);
+
+    if (!minimal_result.IsOk()) {
+      LOG(ERROR) << "Compilation of minimal BCP failed: " << result.error_msg;
+    }
+
+    return result;
+  }
+
+  if (result.IsOk() && boot_images.boot_image_mainline_extension) {
+    CompilationResult mainline_result =
+        RunDex2oatForBootClasspath(staging_dir,
+                                   "mainline",
+                                   isa,
+                                   GetMainlineBcpJars(),
+                                   boot_classpath_jars_,
+                                   GetBestBootImages(isa, /*include_mainline_extension=*/false),
+                                   GetBootImageMainlineExtensionPath(/*on_system=*/false, isa));
+    result.Merge(mainline_result);
+
+    if (mainline_result.IsOk()) {
+      on_dex2oat_success();
+    }
+  }
+
+  if (!result.IsOk() && boot_images.boot_image_mainline_extension) {
+    LOG(ERROR) << "Compilation of mainline BCP failed: " << result.error_msg;
+  }
+
+  return result;
+}
+
+WARN_UNUSED CompilationResult OnDeviceRefresh::RunDex2oatForSystemServer(
+    const std::string& staging_dir,
+    const std::string& dex_file,
+    const std::vector<std::string>& classloader_context) const {
+  std::vector<std::string> args;
+  std::vector<std::unique_ptr<File>> readonly_files_raii;
+  InstructionSet isa = config_.GetSystemServerIsa();
+  std::string output_path = GetSystemServerImagePath(/*on_system=*/false, dex_file);
+
+  std::string actual_jar_path = RewriteParentDirectoryIfNeeded(dex_file);
+  std::string profile = actual_jar_path + ".prof";
+  const std::string& compiler_filter = config_.GetSystemServerCompilerFilter();
+  bool maybe_add_profile = !compiler_filter.empty() || HasVettedDeviceSystemServerProfiles();
+  bool has_added_profile =
+      maybe_add_profile && AddDex2OatProfile(args, readonly_files_raii, {profile});
+  if (!compiler_filter.empty()) {
+    args.emplace_back("--compiler-filter=" + compiler_filter);
+  } else if (has_added_profile) {
+    args.emplace_back("--compiler-filter=speed-profile");
+  } else {
+    args.emplace_back("--compiler-filter=speed");
+  }
+
+  std::string context_path = Join(classloader_context, ':');
+  if (art::ContainsElement(systemserver_classpath_jars_, dex_file)) {
+    args.emplace_back("--class-loader-context=PCL[" + context_path + "]");
+  } else {
+    args.emplace_back("--class-loader-context=PCL[];PCL[" + context_path + "]");
+  }
+  if (!classloader_context.empty()) {
+    std::vector<int> fds;
+    for (const std::string& path : classloader_context) {
+      std::string actual_path = RewriteParentDirectoryIfNeeded(path);
+      std::unique_ptr<File> file(OS::OpenFileForReading(actual_path.c_str()));
+      if (!file->IsValid()) {
+        return CompilationResult::Error(
+            OdrMetrics::Status::kIoError,
+            "Failed to open classloader context '{}': {}"_format(actual_path, strerror(errno)));
+      }
+      fds.emplace_back(file->Fd());
+      readonly_files_raii.emplace_back(std::move(file));
+    }
+    args.emplace_back("--class-loader-context-fds=" + Join(fds, ':'));
+  }
+
+  return RunDex2oat(staging_dir,
+                    "Compiling {}"_format(Basename(dex_file)),
+                    isa,
+                    {dex_file},
+                    boot_classpath_jars_,
+                    GetBestBootImages(isa, /*include_mainline_extension=*/true),
+                    OdrArtifacts::ForSystemServer(output_path),
+                    args,
+                    readonly_files_raii);
+}
+
+WARN_UNUSED CompilationResult
+OnDeviceRefresh::CompileSystemServer(const std::string& staging_dir,
+                                     const std::set<std::string>& system_server_jars_to_compile,
+                                     const std::function<void()>& on_dex2oat_success) const {
+  DCHECK(!system_server_jars_to_compile.empty());
+
+  CompilationResult result = CompilationResult::Ok();
+  std::vector<std::string> classloader_context;
+
+  if (!CheckCompilationSpace()) {
+    LOG(ERROR) << "Compilation of system_server failed: Insufficient space";
+    return CompilationResult::Error(OdrMetrics::Status::kNoSpace, "Insufficient space");
+  }
+
+  for (const std::string& jar : all_systemserver_jars_) {
+    if (ContainsElement(system_server_jars_to_compile, jar)) {
+      CompilationResult current_result =
+          RunDex2oatForSystemServer(staging_dir, jar, classloader_context);
+      result.Merge(current_result);
+
+      if (current_result.IsOk()) {
+        on_dex2oat_success();
+      } else {
+        LOG(ERROR) << "Compilation of {} failed: {}"_format(Basename(jar), result.error_msg);
+      }
+    }
+
+    if (ContainsElement(systemserver_classpath_jars_, jar)) {
+      classloader_context.emplace_back(jar);
+    }
+  }
+
+  return result;
 }
 
 WARN_UNUSED ExitCode OnDeviceRefresh::Compile(OdrMetrics& metrics,
@@ -1728,10 +2010,18 @@
   const char* staging_dir = nullptr;
   metrics.SetStage(OdrMetrics::Stage::kPreparation);
 
+  if (!EnsureDirectoryExists(config_.GetArtifactDirectory())) {
+    LOG(ERROR) << "Failed to prepare artifact directory";
+    metrics.SetStatus(errno == EPERM ? OdrMetrics::Status::kDalvikCachePermissionDenied :
+                                       OdrMetrics::Status::kIoError);
+    return ExitCode::kCleanupFailed;
+  }
+
   if (config_.GetRefresh()) {
     Result<void> result = RefreshExistingArtifacts();
     if (!result.ok()) {
       LOG(ERROR) << "Failed to refresh existing artifacts: " << result.error();
+      metrics.SetStatus(OdrMetrics::Status::kIoError);
       return ExitCode::kCleanupFailed;
     }
   }
@@ -1740,6 +2030,7 @@
   Result<void> result = WriteCacheInfo();
   if (!result.ok()) {
     LOG(ERROR) << result.error();
+    metrics.SetStatus(OdrMetrics::Status::kIoError);
     return ExitCode::kCleanupFailed;
   }
 
@@ -1756,99 +2047,59 @@
   std::string error_msg;
 
   uint32_t dex2oat_invocation_count = 0;
-  uint32_t total_dex2oat_invocation_count =
-      compilation_options.compile_boot_classpath_for_isas.size() +
-      compilation_options.system_server_jars_to_compile.size();
+  uint32_t total_dex2oat_invocation_count = compilation_options.CompilationUnitCount();
   ReportNextBootAnimationProgress(dex2oat_invocation_count, total_dex2oat_invocation_count);
   auto advance_animation_progress = [&]() {
     ReportNextBootAnimationProgress(++dex2oat_invocation_count, total_dex2oat_invocation_count);
   };
 
-  const auto& bcp_instruction_sets = config_.GetBootClasspathIsas();
+  const std::vector<InstructionSet>& bcp_instruction_sets = config_.GetBootClasspathIsas();
   DCHECK(!bcp_instruction_sets.empty() && bcp_instruction_sets.size() <= 2);
-  bool full_compilation_failed = false;
-  for (const InstructionSet isa : compilation_options.compile_boot_classpath_for_isas) {
-    auto stage = (isa == bcp_instruction_sets.front()) ? OdrMetrics::Stage::kPrimaryBootClasspath :
-                                                         OdrMetrics::Stage::kSecondaryBootClasspath;
-    metrics.SetStage(stage);
-    if (!config_.GetMinimal()) {
-      if (CheckCompilationSpace()) {
-        if (CompileBootClasspathArtifacts(isa,
-                                          staging_dir,
-                                          metrics,
-                                          advance_animation_progress,
-                                          /*minimal=*/false,
-                                          &error_msg)) {
-          // Remove the minimal boot image only if the full boot image is successfully generated.
-          std::string path = GetBootImagePath(/*on_system=*/false, /*minimal=*/true, isa);
-          OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path);
-          unlink(artifacts.ImagePath().c_str());
-          unlink(artifacts.OatPath().c_str());
-          unlink(artifacts.VdexPath().c_str());
-          continue;
-        }
-        LOG(ERROR) << "Compilation of BCP failed: " << error_msg;
-      } else {
-        metrics.SetStatus(OdrMetrics::Status::kNoSpace);
-      }
-    }
+  InstructionSet system_server_isa = config_.GetSystemServerIsa();
 
-    // Fall back to generating a minimal boot image.
-    // The compilation of the full boot image will be retried on later reboots with a backoff time,
-    // and the minimal boot image will be removed once the compilation of the full boot image
-    // succeeds.
-    full_compilation_failed = true;
-    std::string ignored_error_msg;
-    if (BootClasspathArtifactsExist(
-            /*on_system=*/false, /*minimal=*/true, isa, &ignored_error_msg)) {
-      continue;
+  bool system_server_isa_failed = false;
+  std::optional<std::pair<OdrMetrics::Stage, OdrMetrics::Status>> first_failure;
+
+  for (const auto& [isa, boot_images_to_generate] :
+       compilation_options.boot_images_to_generate_for_isas) {
+    OdrMetrics::Stage stage = (isa == bcp_instruction_sets.front()) ?
+                                  OdrMetrics::Stage::kPrimaryBootClasspath :
+                                  OdrMetrics::Stage::kSecondaryBootClasspath;
+    CompilationResult bcp_result =
+        CompileBootClasspath(staging_dir, isa, boot_images_to_generate, advance_animation_progress);
+    metrics.SetDex2OatResult(stage, bcp_result.elapsed_time_ms, bcp_result.dex2oat_result);
+    metrics.SetBcpCompilationType(stage, boot_images_to_generate.GetTypeForMetrics());
+    if (!bcp_result.IsOk()) {
+      if (isa == system_server_isa) {
+        system_server_isa_failed = true;
+      }
+      first_failure = first_failure.value_or(std::make_pair(stage, bcp_result.status));
     }
-    if (CompileBootClasspathArtifacts(isa,
-                                      staging_dir,
-                                      metrics,
-                                      advance_animation_progress,
-                                      /*minimal=*/true,
-                                      &error_msg)) {
-      continue;
+  }
+
+  // Don't compile system server if the compilation of BCP failed.
+  if (!system_server_isa_failed && !compilation_options.system_server_jars_to_compile.empty()) {
+    OdrMetrics::Stage stage = OdrMetrics::Stage::kSystemServerClasspath;
+    CompilationResult ss_result = CompileSystemServer(
+        staging_dir, compilation_options.system_server_jars_to_compile, advance_animation_progress);
+    metrics.SetDex2OatResult(stage, ss_result.elapsed_time_ms, ss_result.dex2oat_result);
+    if (!ss_result.IsOk()) {
+      first_failure = first_failure.value_or(std::make_pair(stage, ss_result.status));
     }
-    LOG(ERROR) << "Compilation of minimal BCP failed: " << error_msg;
+  }
+
+  if (first_failure.has_value()) {
+    metrics.SetStage(first_failure->first);
+    metrics.SetStatus(first_failure->second);
+
     if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) {
       return ExitCode::kCleanupFailed;
     }
     return ExitCode::kCompilationFailed;
   }
 
-  if (full_compilation_failed) {
-    if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) {
-      return ExitCode::kCleanupFailed;
-    }
-    return ExitCode::kCompilationFailed;
-  }
-
-  if (!compilation_options.system_server_jars_to_compile.empty()) {
-    metrics.SetStage(OdrMetrics::Stage::kSystemServerClasspath);
-
-    if (!CheckCompilationSpace()) {
-      metrics.SetStatus(OdrMetrics::Status::kNoSpace);
-      // Return kCompilationFailed so odsign will keep and sign whatever we have been able to
-      // compile.
-      return ExitCode::kCompilationFailed;
-    }
-
-    if (!CompileSystemServerArtifacts(staging_dir,
-                                      metrics,
-                                      compilation_options.system_server_jars_to_compile,
-                                      advance_animation_progress,
-                                      &error_msg)) {
-      LOG(ERROR) << "Compilation of system_server failed: " << error_msg;
-      if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) {
-        return ExitCode::kCleanupFailed;
-      }
-      return ExitCode::kCompilationFailed;
-    }
-  }
-
   metrics.SetStage(OdrMetrics::Stage::kComplete);
+  metrics.SetStatus(OdrMetrics::Status::kOK);
   return ExitCode::kCompilationSuccess;
 }
 
diff --git a/odrefresh/odrefresh.h b/odrefresh/odrefresh.h
index e48567f..93812eb 100644
--- a/odrefresh/odrefresh.h
+++ b/odrefresh/odrefresh.h
@@ -27,6 +27,7 @@
 #include <vector>
 
 #include "android-base/result.h"
+#include "base/os.h"
 #include "com_android_apex.h"
 #include "com_android_art.h"
 #include "exec_utils.h"
@@ -38,12 +39,122 @@
 namespace art {
 namespace odrefresh {
 
+class OnDeviceRefresh;
+
+struct BootImages {
+  static constexpr int kMaxCount = 2;
+
+  bool primary_boot_image : 1;
+  bool boot_image_mainline_extension : 1;
+
+  int Count() const;
+
+  OdrMetrics::BcpCompilationType GetTypeForMetrics() const;
+};
+
 struct CompilationOptions {
-  // If not empty, compile the bootclasspath jars for ISAs in the list.
-  std::vector<InstructionSet> compile_boot_classpath_for_isas;
+  // If not empty, generate the boot images for ISAs in the list.
+  std::vector<std::pair<InstructionSet, BootImages>> boot_images_to_generate_for_isas;
 
   // If not empty, compile the system server jars in the list.
   std::set<std::string> system_server_jars_to_compile;
+
+  static CompilationOptions CompileAll(const OnDeviceRefresh& odr);
+
+  int CompilationUnitCount() const;
+};
+
+struct CompilationResult {
+  OdrMetrics::Status status = OdrMetrics::Status::kOK;
+  std::string error_msg;
+  int64_t elapsed_time_ms = 0;
+  std::optional<ExecResult> dex2oat_result;
+
+  static CompilationResult Ok() { return {}; }
+
+  static CompilationResult Dex2oatOk(int64_t elapsed_time_ms, const ExecResult& dex2oat_result) {
+    return {.elapsed_time_ms = elapsed_time_ms, .dex2oat_result = dex2oat_result};
+  }
+
+  static CompilationResult Error(OdrMetrics::Status status, const std::string& error_msg) {
+    return {.status = status, .error_msg = error_msg};
+  }
+
+  static CompilationResult Dex2oatError(const std::string& error_msg,
+                                        int64_t elapsed_time_ms,
+                                        const ExecResult& dex2oat_result) {
+    return {.status = OdrMetrics::Status::kDex2OatError,
+            .error_msg = error_msg,
+            .elapsed_time_ms = elapsed_time_ms,
+            .dex2oat_result = dex2oat_result};
+  }
+
+  bool IsOk() { return status == OdrMetrics::Status::kOK; }
+
+  void Merge(const CompilationResult& other) {
+    // Accumulate the compilation time.
+    elapsed_time_ms += other.elapsed_time_ms;
+
+    // Only keep the first failure.
+    if (status == OdrMetrics::Status::kOK) {
+      status = other.status;
+      error_msg = other.error_msg;
+      dex2oat_result = other.dex2oat_result;
+    }
+  }
+
+ private:
+  CompilationResult() = default;
+};
+
+class PreconditionCheckResult {
+ public:
+  static PreconditionCheckResult NoneOk(OdrMetrics::Trigger trigger) {
+    return PreconditionCheckResult(trigger,
+                                   /*primary_boot_image_ok=*/false,
+                                   /*boot_image_mainline_extension_ok=*/false,
+                                   /*system_server_ok=*/false);
+  }
+  static PreconditionCheckResult BootImageMainlineExtensionNotOk(OdrMetrics::Trigger trigger) {
+    return PreconditionCheckResult(trigger,
+                                   /*primary_boot_image_ok=*/true,
+                                   /*boot_image_mainline_extension_ok=*/false,
+                                   /*system_server_ok=*/false);
+  }
+  static PreconditionCheckResult SystemServerNotOk(OdrMetrics::Trigger trigger) {
+    return PreconditionCheckResult(trigger,
+                                   /*primary_boot_image_ok=*/true,
+                                   /*boot_image_mainline_extension_ok=*/true,
+                                   /*system_server_ok=*/false);
+  }
+  static PreconditionCheckResult AllOk() {
+    return PreconditionCheckResult(/*trigger=*/std::nullopt,
+                                   /*primary_boot_image_ok=*/true,
+                                   /*boot_image_mainline_extension_ok=*/true,
+                                   /*system_server_ok=*/true);
+  }
+  bool IsAllOk() const { return !trigger_.has_value(); }
+  OdrMetrics::Trigger GetTrigger() const { return trigger_.value(); }
+  bool IsPrimaryBootImageOk() const { return primary_boot_image_ok_; }
+  bool IsBootImageMainlineExtensionOk() const { return boot_image_mainline_extension_ok_; }
+  bool IsSystemServerOk() const { return system_server_ok_; }
+
+ private:
+  // Use static factory methods instead.
+  PreconditionCheckResult(std::optional<OdrMetrics::Trigger> trigger,
+                          bool primary_boot_image_ok,
+                          bool boot_image_mainline_extension_ok,
+                          bool system_server_ok)
+      : trigger_(trigger),
+        primary_boot_image_ok_(primary_boot_image_ok),
+        boot_image_mainline_extension_ok_(boot_image_mainline_extension_ok),
+        system_server_ok_(system_server_ok) {}
+
+  // Indicates why the precondition is not okay, or `std::nullopt` if it's okay.
+  std::optional<OdrMetrics::Trigger> trigger_;
+  bool primary_boot_image_ok_;
+  bool boot_image_mainline_extension_ok_;
+  bool system_server_ok_;
 };
 
 class OnDeviceRefresh final {
@@ -70,6 +181,8 @@
     return {all_systemserver_jars_.begin(), all_systemserver_jars_.end()};
   }
 
+  const OdrConfig& Config() const { return config_; }
+
  private:
   time_t GetExecutionTimeUsed() const;
 
@@ -81,52 +194,80 @@
   std::optional<std::vector<com::android::apex::ApexInfo>> GetApexInfoList() const;
 
   // Reads the ART APEX cache information (if any) found in the output artifact directory.
-  std::optional<com::android::art::CacheInfo> ReadCacheInfo() const;
+  android::base::Result<com::android::art::CacheInfo> ReadCacheInfo() const;
 
   // Writes ART APEX cache information to `kOnDeviceRefreshOdrefreshArtifactDirectory`.
   android::base::Result<void> WriteCacheInfo() const;
 
   std::vector<com::android::art::Component> GenerateBootClasspathComponents() const;
 
-  std::vector<com::android::art::Component> GenerateBootClasspathCompilableComponents() const;
+  std::vector<com::android::art::Component> GenerateDex2oatBootClasspathComponents() const;
 
   std::vector<com::android::art::SystemServerComponent> GenerateSystemServerComponents() const;
 
-  // Returns the symbolic boot image location (without ISA). If `minimal` is true, returns the
-  // symbolic location of the minimal boot image.
-  std::string GetBootImage(bool on_system, bool minimal) const;
+  // Returns the list of BCP jars in the ART module.
+  std::vector<std::string> GetArtBcpJars() const;
 
-  // Returns the real boot image location (with ISA).  If `minimal` is true, returns the
-  // symbolic location of the minimal boot image.
-  std::string GetBootImagePath(bool on_system, bool minimal, const InstructionSet isa) const;
+  // Returns the list of BCP jars for the boot image framework extension.
+  std::vector<std::string> GetFrameworkBcpJars() const;
 
-  // Returns the symbolic boot image extension location (without ISA). Note that this only applies
-  // to boot images on /system.
-  std::string GetSystemBootImageExtension() const;
+  // Returns the list of BCP jars for the boot image mainline extension.
+  std::vector<std::string> GetMainlineBcpJars() const;
 
-  // Returns the real boot image location extension (with ISA). Note that this only applies to boot
-  // images on /system.
-  std::string GetSystemBootImageExtensionPath(const InstructionSet isa) const;
+  // Returns the symbolic primary boot image location (without ISA). If `minimal` is true, returns
+  // the symbolic location of the minimal boot image.
+  std::string GetPrimaryBootImage(bool on_system, bool minimal) const;
+
+  // Returns the real primary boot image location (with ISA).  If `minimal` is true, returns the
+  // real location of the minimal boot image.
+  std::string GetPrimaryBootImagePath(bool on_system, bool minimal, InstructionSet isa) const;
+
+  // Returns the symbolic boot image framework extension location (without ISA). Note that this only
+  // applies to boot images on /system.
+  std::string GetSystemBootImageFrameworkExtension() const;
+
+  // Returns the real boot image framework extension location (with ISA). Note that this only
+  // applies to boot images on /system.
+  std::string GetSystemBootImageFrameworkExtensionPath(InstructionSet isa) const;
+
+  // Returns the symbolic boot image mainline extension location (without ISA).
+  std::string GetBootImageMainlineExtension(bool on_system) const;
+
+  // Returns the real boot image mainline extension location (with ISA).
+  std::string GetBootImageMainlineExtensionPath(bool on_system, InstructionSet isa) const;
+
+  // Returns the best combination of symbolic boot image locations (without ISA) based on file
+  // existence.
+  std::vector<std::string> GetBestBootImages(InstructionSet isa,
+                                             bool include_mainline_extension) const;
 
   std::string GetSystemServerImagePath(bool on_system, const std::string& jar_path) const;
 
   // Removes files that are not in the list.
   android::base::Result<void> CleanupArtifactDirectory(
-      const std::vector<std::string>& artifacts_to_keep) const;
+      OdrMetrics& metrics, const std::vector<std::string>& artifacts_to_keep) const;
 
   // Loads artifacts to memory and writes them back. This is a workaround for old versions of
   // odsign, which encounters "file exists" error when it adds existing artifacts to fs-verity. This
   // function essentially removes existing artifacts from fs-verity to avoid the error.
   android::base::Result<void> RefreshExistingArtifacts() const;
 
-  // Checks whether all boot classpath artifacts are present. Returns true if all are present, false
-  // otherwise.
+  // Returns whether the primary boot image is present.
+  // If `on_system` is true, checks both the primary boot image and the framework extension on
+  // /system.
   // If `minimal` is true, checks the minimal boot image.
   // If `checked_artifacts` is present, adds checked artifacts to `checked_artifacts`.
-  WARN_UNUSED bool BootClasspathArtifactsExist(
+  WARN_UNUSED bool PrimaryBootImageExist(
       bool on_system,
       bool minimal,
-      const InstructionSet isa,
+      InstructionSet isa,
+      /*out*/ std::string* error_msg,
+      /*out*/ std::vector<std::string>* checked_artifacts = nullptr) const;
+
+  // Returns whether the boot image mainline extension exists.
+  WARN_UNUSED bool BootImageMainlineExtensionExist(
+      bool on_system,
+      InstructionSet isa,
       /*out*/ std::string* error_msg,
       /*out*/ std::vector<std::string>* checked_artifacts = nullptr) const;
 
@@ -134,7 +275,7 @@
   // order of compilation. Returns true if all are present, false otherwise.
   // Adds the paths to the jars that are missing artifacts in `jars_with_missing_artifacts`.
   // If `checked_artifacts` is present, adds checked artifacts to `checked_artifacts`.
-  WARN_UNUSED bool SystemServerArtifactsExist(
+  bool SystemServerArtifactsExist(
       bool on_system,
       /*out*/ std::string* error_msg,
       /*out*/ std::set<std::string>* jars_missing_artifacts,
@@ -150,51 +291,73 @@
   WARN_UNUSED bool CheckSystemPropertiesHaveNotChanged(
       const com::android::art::CacheInfo& cache_info) const;
 
-  // Returns true if boot classpath artifacts on /system are usable if they exist. Note that this
-  // function does not check file existence.
-  WARN_UNUSED bool BootClasspathArtifactsOnSystemUsable(
-      const com::android::apex::ApexInfo& art_apex_info) const;
+  // Returns true if the system image is built with the right userfaultfd GC flag.
+  WARN_UNUSED bool CheckBuildUserfaultFdGc() const;
 
-  // Returns true if system_server artifacts on /system are usable if they exist. Note that this
-  // function does not check file existence.
-  WARN_UNUSED bool SystemServerArtifactsOnSystemUsable(
-      const std::vector<com::android::apex::ApexInfo>& apex_info_list) const;
+  // Returns whether the precondition for using artifacts on /system is met. Note that this function
+  // does not check the artifacts.
+  WARN_UNUSED PreconditionCheckResult
+  CheckPreconditionForSystem(const std::vector<com::android::apex::ApexInfo>& apex_info_list) const;
 
-  // Checks whether all boot classpath artifacts are up to date. Returns true if all are present,
-  // false otherwise.
-  // If `checked_artifacts` is present, adds checked artifacts to `checked_artifacts`.
-  WARN_UNUSED bool CheckBootClasspathArtifactsAreUpToDate(
-      OdrMetrics& metrics,
-      const InstructionSet isa,
-      const com::android::apex::ApexInfo& art_apex_info,
-      const std::optional<com::android::art::CacheInfo>& cache_info,
-      /*out*/ std::vector<std::string>* checked_artifacts) const;
+  // Returns whether the precondition for using artifacts on /data is met. Note that this function
+  // does not check the artifacts.
+  WARN_UNUSED PreconditionCheckResult
+  CheckPreconditionForData(const std::vector<com::android::apex::ApexInfo>& apex_info_list) const;
+
+  // Checks whether all boot classpath artifacts are up to date. Returns the boot images that need
+  // to be (re-)generated. If `checked_artifacts` is present, adds checked artifacts to
+  // `checked_artifacts`.
+  WARN_UNUSED BootImages
+  CheckBootClasspathArtifactsAreUpToDate(OdrMetrics& metrics,
+                                         InstructionSet isa,
+                                         const PreconditionCheckResult& system_result,
+                                         const PreconditionCheckResult& data_result,
+                                         /*out*/ std::vector<std::string>* checked_artifacts) const;
 
   // Checks whether all system_server artifacts are up to date. The artifacts are checked in their
-  // order of compilation. Returns true if all are present, false otherwise.
-  // Adds the paths to the jars that needs to be compiled in `jars_to_compile`.
+  // order of compilation. Returns the paths to the jars that need to be compiled.
   // If `checked_artifacts` is present, adds checked artifacts to `checked_artifacts`.
-  bool CheckSystemServerArtifactsAreUpToDate(
+  WARN_UNUSED std::set<std::string> CheckSystemServerArtifactsAreUpToDate(
       OdrMetrics& metrics,
-      const std::vector<com::android::apex::ApexInfo>& apex_info_list,
-      const std::optional<com::android::art::CacheInfo>& cache_info,
-      /*out*/ std::set<std::string>* jars_to_compile,
+      const PreconditionCheckResult& system_result,
+      const PreconditionCheckResult& data_result,
       /*out*/ std::vector<std::string>* checked_artifacts) const;
 
-  // Compiles boot classpath. If `minimal` is true, only compiles the jars in the ART module.
-  WARN_UNUSED bool CompileBootClasspathArtifacts(const InstructionSet isa,
-                                                 const std::string& staging_dir,
-                                                 OdrMetrics& metrics,
-                                                 const std::function<void()>& on_dex2oat_success,
-                                                 bool minimal,
-                                                 std::string* error_msg) const;
+  WARN_UNUSED CompilationResult
+  RunDex2oat(const std::string& staging_dir,
+             const std::string& debug_message,
+             InstructionSet isa,
+             const std::vector<std::string>& dex_files,
+             const std::vector<std::string>& boot_classpath,
+             const std::vector<std::string>& input_boot_images,
+             const OdrArtifacts& artifacts,
+             const std::vector<std::string>& extra_args,
+             /*inout*/ std::vector<std::unique_ptr<File>>& readonly_files_raii) const;
 
-  WARN_UNUSED bool CompileSystemServerArtifacts(
-      const std::string& staging_dir,
-      OdrMetrics& metrics,
-      const std::set<std::string>& system_server_jars_to_compile,
-      const std::function<void()>& on_dex2oat_success,
-      std::string* error_msg) const;
+  WARN_UNUSED CompilationResult
+  RunDex2oatForBootClasspath(const std::string& staging_dir,
+                             const std::string& debug_name,
+                             InstructionSet isa,
+                             const std::vector<std::string>& dex_files,
+                             const std::vector<std::string>& boot_classpath,
+                             const std::vector<std::string>& input_boot_images,
+                             const std::string& output_path) const;
+
+  WARN_UNUSED CompilationResult
+  CompileBootClasspath(const std::string& staging_dir,
+                       InstructionSet isa,
+                       BootImages boot_images,
+                       const std::function<void()>& on_dex2oat_success) const;
+
+  WARN_UNUSED CompilationResult
+  RunDex2oatForSystemServer(const std::string& staging_dir,
+                            const std::string& dex_file,
+                            const std::vector<std::string>& classloader_context) const;
+
+  WARN_UNUSED CompilationResult
+  CompileSystemServer(const std::string& staging_dir,
+                      const std::set<std::string>& system_server_jars_to_compile,
+                      const std::function<void()>& on_dex2oat_success) const;
 
   // Configuration to use.
   const OdrConfig& config_;
@@ -202,16 +365,16 @@
   // Path to cache information file that is used to speed up artifact checking.
   const std::string cache_info_filename_;
 
-  // List of boot classpath components that should be compiled.
-  std::vector<std::string> boot_classpath_compilable_jars_;
+  // The raw list from DEX2OATBOOTCLASSPATH. This is the list of jars that should be compiled into
+  // the primary boot image.
+  std::vector<std::string> dex2oat_boot_classpath_jars_;
+
+  // The raw list from BOOTCLASSPATH. This is the list of all BCP jars.
+  std::vector<std::string> boot_classpath_jars_;
 
   // Set of system_server components in SYSTEMSERVERCLASSPATH that should be compiled.
   std::unordered_set<std::string> systemserver_classpath_jars_;
 
-  // List of all boot classpath components. Used as the dependencies for compiling the
-  // system_server.
-  std::vector<std::string> boot_classpath_jars_;
-
   // List of all system_server components, including those in SYSTEMSERVERCLASSPATH and those in
   // STANDALONE_SYSTEMSERVER_JARS (jars that system_server loads dynamically using separate
   // classloaders).
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index 58ef28f..2871a99 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -26,6 +26,7 @@
 #include "arch/instruction_set.h"
 #include "base/file_utils.h"
 #include "base/globals.h"
+#include "base/stl_util.h"
 #include "odr_common.h"
 #include "odr_compilation_log.h"
 #include "odr_config.h"
@@ -40,7 +41,9 @@
 using ::art::odrefresh::CompilationOptions;
 using ::art::odrefresh::ExitCode;
 using ::art::odrefresh::kCheckedSystemPropertyPrefixes;
+using ::art::odrefresh::kIgnoredSystemProperties;
 using ::art::odrefresh::kSystemProperties;
+using ::art::odrefresh::kSystemPropertySystemServerCompilerFilterOverride;
 using ::art::odrefresh::OdrCompilationLog;
 using ::art::odrefresh::OdrConfig;
 using ::art::odrefresh::OdrMetrics;
@@ -144,6 +147,8 @@
       config->SetArtifactDirectory(GetApexDataDalvikCacheDirectory(art::InstructionSet::kNone));
     } else if (ArgumentMatches(arg, "--zygote-arch=", &value)) {
       zygote = value;
+    } else if (ArgumentMatches(arg, "--boot-image-compiler-filter=", &value)) {
+      config->SetBootImageCompilerFilter(value);
     } else if (ArgumentMatches(arg, "--system-server-compiler-filter=", &value)) {
       config->SetSystemServerCompilerFilter(value);
     } else if (ArgumentMatches(arg, "--staging-dir=", &value)) {
@@ -172,7 +177,8 @@
   config->SetZygoteKind(zygote_kind);
 
   if (config->GetSystemServerCompilerFilter().empty()) {
-    std::string filter = GetProperty("dalvik.vm.systemservercompilerfilter", "speed");
+    std::string filter = GetProperty("dalvik.vm.systemservercompilerfilter", "");
+    filter = GetProperty(kSystemPropertySystemServerCompilerFilterOverride, filter);
     config->SetSystemServerCompilerFilter(filter);
   }
 
@@ -195,7 +201,7 @@
       return;
     }
     for (const char* prefix : kCheckedSystemPropertyPrefixes) {
-      if (StartsWith(name, prefix)) {
+      if (StartsWith(name, prefix) && !art::ContainsElement(kIgnoredSystemProperties, name)) {
         (*system_properties)[name] = value;
       }
     }
@@ -232,6 +238,9 @@
   UsageMsg("--staging-dir=<DIR>              Write temporary artifacts to <DIR> rather than");
   UsageMsg("                                 .../staging");
   UsageMsg("--zygote-arch=<STRING>           Zygote kind that overrides ro.zygote");
+  UsageMsg("--boot-image-compiler-filter=<STRING>");
+  UsageMsg("                                 Compiler filter for the boot image. Default: ");
+  UsageMsg("                                 speed-profile");
   UsageMsg("--system-server-compiler-filter=<STRING>");
   UsageMsg("                                 Compiler filter that overrides");
   UsageMsg("                                 dalvik.vm.systemservercompilerfilter");
@@ -248,16 +257,21 @@
   // by others and prevents system_server from loading generated artifacts.
   umask(S_IWGRP | S_IWOTH);
 
-  // Explicitly initialize logging (b/201042799).
-  android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
-
   OdrConfig config(argv[0]);
   int n = InitializeConfig(argc, argv, &config);
+
+  // Explicitly initialize logging (b/201042799).
+  // But not in CompOS mode - logd doesn't exist in Microdroid (b/265153235).
+  if (!config.GetCompilationOsMode()) {
+    android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
+  }
+
   argv += n;
   argc -= n;
   if (argc != 1) {
     ArgumentError("Expected 1 argument, but have %d.", argc);
   }
+
   GetSystemProperties(config.MutableSystemProperties());
 
   OdrMetrics metrics(config.GetArtifactDirectory());
@@ -267,10 +281,17 @@
   CompilationOptions compilation_options;
   if (action == "--check") {
     // Fast determination of whether artifacts are up to date.
-    return odr.CheckArtifactsAreUpToDate(metrics, &compilation_options);
+    ExitCode exit_code = odr.CheckArtifactsAreUpToDate(metrics, &compilation_options);
+    // Normally, `--check` should not write metrics. If compilation is not required, there's no need
+    // to write metrics; if compilation is required, `--compile` will write metrics. Therefore,
+    // `--check` should only write metrics when things went wrong.
+    metrics.SetEnabled(exit_code != ExitCode::kOkay && exit_code != ExitCode::kCompilationRequired);
+    return exit_code;
   } else if (action == "--compile") {
-    const ExitCode exit_code = odr.CheckArtifactsAreUpToDate(metrics, &compilation_options);
+    ExitCode exit_code = odr.CheckArtifactsAreUpToDate(metrics, &compilation_options);
     if (exit_code != ExitCode::kCompilationRequired) {
+      // No compilation required, so only write metrics when things went wrong.
+      metrics.SetEnabled(exit_code != ExitCode::kOkay);
       return exit_code;
     }
     OdrCompilationLog compilation_log;
@@ -278,6 +299,8 @@
       LOG(INFO) << "Compilation skipped because it was attempted recently";
       return ExitCode::kOkay;
     }
+    // Compilation required, so always write metrics.
+    metrics.SetEnabled(true);
     ExitCode compile_result = odr.Compile(metrics, compilation_options);
     compilation_log.Log(metrics.GetArtApexVersion(),
                         metrics.GetArtApexLastUpdateMillis(),
@@ -290,11 +313,7 @@
       metrics.SetStatus(OdrMetrics::Status::kIoError);
       return ExitCode::kCleanupFailed;
     }
-    return odr.Compile(metrics,
-                       CompilationOptions{
-                           .compile_boot_classpath_for_isas = config.GetBootClasspathIsas(),
-                           .system_server_jars_to_compile = odr.AllSystemServerJars(),
-                       });
+    return odr.Compile(metrics, CompilationOptions::CompileAll(odr));
   } else if (action == "--help") {
     UsageHelp(argv[0]);
   } else {
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index ae7cc78..8ebeeab 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -29,11 +29,13 @@
 #include "android-base/scopeguard.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
+#include "android-modules-utils/sdk_level.h"
 #include "arch/instruction_set.h"
 #include "base/common_art_test.h"
 #include "base/file_utils.h"
 #include "base/stl_util.h"
 #include "exec_utils.h"
+#include "fmt/format.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "odr_artifacts.h"
@@ -46,13 +48,18 @@
 namespace art {
 namespace odrefresh {
 
+using ::android::base::Split;
+using ::android::modules::sdklevel::IsAtLeastU;
 using ::testing::_;
 using ::testing::AllOf;
 using ::testing::Contains;
-using ::testing::HasSubstr;
+using ::testing::ElementsAre;
 using ::testing::Not;
+using ::testing::ResultOf;
 using ::testing::Return;
 
+using ::fmt::literals::operator""_format;  // NOLINT
+
 constexpr int kReplace = 1;
 
 void CreateEmptyFile(const std::string& name) {
@@ -71,29 +78,31 @@
  public:
   // A workaround to avoid MOCK_METHOD on a method with an `std::string*` parameter, which will lead
   // to a conflict between gmock and android-base/logging.h (b/132668253).
-  int ExecAndReturnCode(std::vector<std::string>& arg_vector,
-                        time_t,
-                        bool*,
-                        std::string*) const override {
-    return DoExecAndReturnCode(arg_vector);
+  ExecResult ExecAndReturnResult(const std::vector<std::string>& arg_vector,
+                                 int,
+                                 std::string*) const override {
+    return {.status = ExecResult::kExited, .exit_code = DoExecAndReturnCode(arg_vector)};
   }
 
-  MOCK_METHOD(int, DoExecAndReturnCode, (std::vector<std::string> & arg_vector), (const));
+  MOCK_METHOD(int, DoExecAndReturnCode, (const std::vector<std::string>& arg_vector), (const));
 };
 
-// Matches a flag that starts with `flag` and is a colon-separated list that contains an element
-// that matches `matcher`.
-MATCHER_P2(FlagContains, flag, matcher, "") {
-  std::string_view value = arg;
+// Matches a flag that starts with `flag` and whose value matches `matcher`.
+MATCHER_P2(Flag, flag, matcher, "") {
+  std::string_view value(arg);
   if (!android::base::ConsumePrefix(&value, flag)) {
     return false;
   }
-  for (std::string_view s : SplitString(value, ':')) {
-    if (ExplainMatchResult(matcher, s, result_listener)) {
-      return true;
-    }
-  }
-  return false;
+  return ExplainMatchResult(matcher, std::string(value), result_listener);
+}
+
+// Matches a flag that starts with `flag` and whose value is a colon-separated list that matches
+// `matcher`. The matcher acts on an `std::vector<std::string>` of the split list argument.
+MATCHER_P2(ListFlag, flag, matcher, "") {
+  return ExplainMatchResult(
+      Flag(flag, ResultOf(std::bind(Split, std::placeholders::_1, ":"), matcher)),
+      arg,
+      result_listener);
 }
 
 // Matches an FD of a file whose path matches `matcher`.
@@ -140,20 +149,21 @@
     CommonArtTest::SetUp();
 
     temp_dir_ = std::make_unique<ScratchDir>();
-    std::string_view temp_dir_path = temp_dir_->GetPath();
-    android::base::ConsumeSuffix(&temp_dir_path, "/");
+    std::string temp_dir_path = temp_dir_->GetPath();
+    // Remove the trailing '/';
+    temp_dir_path.resize(temp_dir_path.length() - 1);
 
-    std::string android_root_path = Concatenate({temp_dir_path, "/system"});
+    std::string android_root_path = temp_dir_path + "/system";
     ASSERT_TRUE(EnsureDirectoryExists(android_root_path));
     android_root_env_ = std::make_unique<ScopedUnsetEnvironmentVariable>("ANDROID_ROOT");
     setenv("ANDROID_ROOT", android_root_path.c_str(), kReplace);
 
-    std::string android_art_root_path = Concatenate({temp_dir_path, "/apex/com.android.art"});
+    std::string android_art_root_path = temp_dir_path + "/apex/com.android.art";
     ASSERT_TRUE(EnsureDirectoryExists(android_art_root_path));
     android_art_root_env_ = std::make_unique<ScopedUnsetEnvironmentVariable>("ANDROID_ART_ROOT");
     setenv("ANDROID_ART_ROOT", android_art_root_path.c_str(), kReplace);
 
-    std::string art_apex_data_path = Concatenate({temp_dir_path, kArtApexDataDefaultPath});
+    std::string art_apex_data_path = temp_dir_path + kArtApexDataDefaultPath;
     ASSERT_TRUE(EnsureDirectoryExists(art_apex_data_path));
     art_apex_data_env_ = std::make_unique<ScopedUnsetEnvironmentVariable>("ART_APEX_DATA");
     setenv("ART_APEX_DATA", art_apex_data_path.c_str(), kReplace);
@@ -161,11 +171,15 @@
     dalvik_cache_dir_ = art_apex_data_path + "/dalvik-cache";
     ASSERT_TRUE(EnsureDirectoryExists(dalvik_cache_dir_ + "/x86_64"));
 
-    std::string system_etc_dir = Concatenate({android_root_path, "/etc"});
+    std::string system_etc_dir = android_root_path + "/etc";
     ASSERT_TRUE(EnsureDirectoryExists(system_etc_dir));
     framework_profile_ = system_etc_dir + "/boot-image.prof";
     CreateEmptyFile(framework_profile_);
-    std::string art_etc_dir = Concatenate({android_art_root_path, "/etc"});
+    dirty_image_objects_file_ = system_etc_dir + "/dirty-image-objects";
+    CreateEmptyFile(dirty_image_objects_file_);
+    preloaded_classes_file_ = system_etc_dir + "/preloaded-classes";
+    CreateEmptyFile(preloaded_classes_file_);
+    std::string art_etc_dir = android_art_root_path + "/etc";
     ASSERT_TRUE(EnsureDirectoryExists(art_etc_dir));
     art_profile_ = art_etc_dir + "/boot-image.prof";
     CreateEmptyFile(art_profile_);
@@ -176,9 +190,13 @@
     services_jar_ = framework_dir_ + "/services.jar";
     services_foo_jar_ = framework_dir_ + "/services-foo.jar";
     services_bar_jar_ = framework_dir_ + "/services-bar.jar";
-    std::string services_jar_prof = framework_dir_ + "/services.jar.prof";
-    art_javalib_dir_ = android_art_root_path + "/javalib";
-    core_oj_jar_ = art_javalib_dir_ + "/core-oj.jar";
+    services_jar_profile_ = framework_dir_ + "/services.jar.prof";
+    std::string art_javalib_dir = android_art_root_path + "/javalib";
+    core_oj_jar_ = art_javalib_dir + "/core-oj.jar";
+    std::string conscrypt_javalib_dir = temp_dir_path + "/apex/com.android.conscrypt/javalib";
+    conscrypt_jar_ = conscrypt_javalib_dir + "/conscrypt.jar";
+    std::string wifi_javalib_dir = temp_dir_path + "/apex/com.android.wifi/javalib";
+    framework_wifi_jar_ = wifi_javalib_dir + "/framework-wifi.jar";
 
     // Create placeholder files.
     ASSERT_TRUE(EnsureDirectoryExists(framework_dir_ + "/x86_64"));
@@ -187,22 +205,27 @@
     CreateEmptyFile(services_jar_);
     CreateEmptyFile(services_foo_jar_);
     CreateEmptyFile(services_bar_jar_);
-    CreateEmptyFile(services_jar_prof);
-    ASSERT_TRUE(EnsureDirectoryExists(art_javalib_dir_));
+    CreateEmptyFile(services_jar_profile_);
+    ASSERT_TRUE(EnsureDirectoryExists(art_javalib_dir));
     CreateEmptyFile(core_oj_jar_);
+    ASSERT_TRUE(EnsureDirectoryExists(conscrypt_javalib_dir));
+    CreateEmptyFile(conscrypt_jar_);
+    ASSERT_TRUE(EnsureDirectoryExists(wifi_javalib_dir));
+    CreateEmptyFile(framework_wifi_jar_);
 
-    std::string apex_info_filename = Concatenate({temp_dir_path, "/apex-info-list.xml"});
+    std::string apex_info_filename = temp_dir_path + "/apex-info-list.xml";
     WriteFakeApexInfoList(apex_info_filename);
     config_.SetApexInfoListFile(apex_info_filename);
 
-    config_.SetArtBinDir(Concatenate({temp_dir_path, "/bin"}));
-    config_.SetBootClasspath(Concatenate({core_oj_jar_, ":", framework_jar_}));
-    config_.SetDex2oatBootclasspath(Concatenate({core_oj_jar_, ":", framework_jar_}));
-    config_.SetSystemServerClasspath(Concatenate({location_provider_jar_, ":", services_jar_}));
-    config_.SetStandaloneSystemServerJars(Concatenate({services_foo_jar_, ":", services_bar_jar_}));
+    config_.SetArtBinDir(temp_dir_path + "/bin");
+    config_.SetBootClasspath(core_oj_jar_ + ":" + framework_jar_ + ":" + conscrypt_jar_ + ":" +
+                             framework_wifi_jar_);
+    config_.SetDex2oatBootclasspath(core_oj_jar_ + ":" + framework_jar_);
+    config_.SetSystemServerClasspath(location_provider_jar_ + ":" + services_jar_);
+    config_.SetStandaloneSystemServerJars(services_foo_jar_ + ":" + services_bar_jar_);
     config_.SetIsa(InstructionSet::kX86_64);
     config_.SetZygoteKind(ZygoteKind::kZygote64_32);
-    config_.SetSystemServerCompilerFilter("speed");  // specify a default
+    config_.SetSystemServerCompilerFilter("");
     config_.SetArtifactDirectory(dalvik_cache_dir_);
 
     std::string staging_dir = dalvik_cache_dir_ + "/staging";
@@ -213,8 +236,9 @@
     mock_exec_utils_ = mock_exec_utils.get();
 
     metrics_ = std::make_unique<OdrMetrics>(dalvik_cache_dir_);
+    cache_info_xml_ = dalvik_cache_dir_ + "/cache-info.xml";
     odrefresh_ = std::make_unique<OnDeviceRefresh>(
-        config_, dalvik_cache_dir_ + "/cache-info.xml", std::move(mock_exec_utils));
+        config_, cache_info_xml_, std::move(mock_exec_utils));
   }
 
   void TearDown() override {
@@ -237,87 +261,208 @@
   std::unique_ptr<OdrMetrics> metrics_;
   std::string core_oj_jar_;
   std::string framework_jar_;
+  std::string conscrypt_jar_;
+  std::string framework_wifi_jar_;
   std::string location_provider_jar_;
   std::string services_jar_;
   std::string services_foo_jar_;
   std::string services_bar_jar_;
   std::string dalvik_cache_dir_;
   std::string framework_dir_;
-  std::string art_javalib_dir_;
   std::string framework_profile_;
   std::string art_profile_;
+  std::string services_jar_profile_;
+  std::string dirty_image_objects_file_;
+  std::string preloaded_classes_file_;
+  std::string cache_info_xml_;
 };
 
-TEST_F(OdRefreshTest, BootClasspathJars) {
+TEST_F(OdRefreshTest, PrimaryBootImage) {
   EXPECT_CALL(*mock_exec_utils_,
               DoExecAndReturnCode(AllOf(
-                  Contains(Concatenate({"--dex-file=", core_oj_jar_})),
-                  Contains(Concatenate({"--dex-file=", framework_jar_})),
-                  Contains(FlagContains("--dex-fd=", FdOf(core_oj_jar_))),
-                  Contains(FlagContains("--dex-fd=", FdOf(framework_jar_))),
-                  Contains(FlagContains("--profile-file-fd=", FdOf(art_profile_))),
-                  Contains(FlagContains("--profile-file-fd=", FdOf(framework_profile_))),
-                  Contains(Concatenate({"--oat-location=", dalvik_cache_dir_, "/x86_64/boot.oat"})),
-                  Contains(HasSubstr("--base=")),
-                  Contains("--compiler-filter=speed-profile"))))
+                  Contains(Flag("--dex-file=", core_oj_jar_)),
+                  Contains(Flag("--dex-file=", framework_jar_)),
+                  Not(Contains(Flag("--dex-file=", conscrypt_jar_))),
+                  Not(Contains(Flag("--dex-file=", framework_wifi_jar_))),
+                  Contains(Flag("--dex-fd=", FdOf(core_oj_jar_))),
+                  Contains(Flag("--dex-fd=", FdOf(framework_jar_))),
+                  Not(Contains(Flag("--dex-fd=", FdOf(conscrypt_jar_)))),
+                  Not(Contains(Flag("--dex-fd=", FdOf(framework_wifi_jar_)))),
+                  Contains(ListFlag("-Xbootclasspath:", ElementsAre(core_oj_jar_, framework_jar_))),
+                  Contains(ListFlag("-Xbootclasspathfds:",
+                                    ElementsAre(FdOf(core_oj_jar_), FdOf(framework_jar_)))),
+                  Contains(Flag("--oat-location=", dalvik_cache_dir_ + "/x86_64/boot.oat")),
+                  Contains(Flag("--base=", _)),
+                  Not(Contains(Flag("--boot-image=", _))),
+                  Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
       .WillOnce(Return(0));
 
-  EXPECT_EQ(odrefresh_->Compile(*metrics_,
-                                CompilationOptions{
-                                    .compile_boot_classpath_for_isas = {InstructionSet::kX86_64},
-                                }),
+  // Ignore the invocation for the mainline extension.
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(Contains(Flag("--dex-file=", conscrypt_jar_))))
+      .WillOnce(Return(0));
+
+  EXPECT_EQ(odrefresh_->Compile(
+                *metrics_,
+                CompilationOptions{
+                    .boot_images_to_generate_for_isas{
+                        {InstructionSet::kX86_64,
+                         {.primary_boot_image = true, .boot_image_mainline_extension = true}}},
+                }),
+            ExitCode::kCompilationSuccess);
+}
+
+TEST_F(OdRefreshTest, BootImageMainlineExtension) {
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(AllOf(
+          Not(Contains(Flag("--dex-file=", core_oj_jar_))),
+          Not(Contains(Flag("--dex-file=", framework_jar_))),
+          Contains(Flag("--dex-file=", conscrypt_jar_)),
+          Contains(Flag("--dex-file=", framework_wifi_jar_)),
+          Not(Contains(Flag("--dex-fd=", FdOf(core_oj_jar_)))),
+          Not(Contains(Flag("--dex-fd=", FdOf(framework_jar_)))),
+          Contains(Flag("--dex-fd=", FdOf(conscrypt_jar_))),
+          Contains(Flag("--dex-fd=", FdOf(framework_wifi_jar_))),
+          Contains(ListFlag(
+              "-Xbootclasspath:",
+              ElementsAre(core_oj_jar_, framework_jar_, conscrypt_jar_, framework_wifi_jar_))),
+          Contains(ListFlag("-Xbootclasspathfds:",
+                            ElementsAre(FdOf(core_oj_jar_),
+                                        FdOf(framework_jar_),
+                                        FdOf(conscrypt_jar_),
+                                        FdOf(framework_wifi_jar_)))),
+          Contains(Flag("--oat-location=", dalvik_cache_dir_ + "/x86_64/boot-conscrypt.oat")),
+          Not(Contains(Flag("--base=", _))),
+          Contains(Flag("--boot-image=", _)),
+          Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
+      .WillOnce(Return(0));
+
+  EXPECT_EQ(odrefresh_->Compile(
+                *metrics_,
+                CompilationOptions{
+                    .boot_images_to_generate_for_isas{
+                        {InstructionSet::kX86_64, {.boot_image_mainline_extension = true}}},
+                }),
+            ExitCode::kCompilationSuccess);
+}
+
+TEST_F(OdRefreshTest, BootClasspathJarsWithExplicitCompilerFilter) {
+  config_.SetBootImageCompilerFilter("speed");
+
+  // Profiles should still be passed for primary boot image.
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", core_oj_jar_)),
+                                Contains(Flag("--profile-file-fd=", FdOf(art_profile_))),
+                                Contains(Flag("--profile-file-fd=", FdOf(framework_profile_))),
+                                Contains("--compiler-filter=speed"))))
+      .WillOnce(Return(0));
+
+  // "verify" should always be used for boot image mainline extension.
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", conscrypt_jar_)),
+                                        Not(Contains(Flag("--profile-file-fd=", _))),
+                                        Contains("--compiler-filter=verify"))))
+      .WillOnce(Return(0));
+
+  EXPECT_EQ(odrefresh_->Compile(
+                *metrics_,
+                CompilationOptions{
+                    .boot_images_to_generate_for_isas{
+                        {InstructionSet::kX86_64,
+                         {.primary_boot_image = true, .boot_image_mainline_extension = true}}},
+                }),
+            ExitCode::kCompilationSuccess);
+}
+
+TEST_F(OdRefreshTest, BootClasspathJarsWithDefaultCompilerFilter) {
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", core_oj_jar_)),
+                                Contains(Flag("--profile-file-fd=", FdOf(art_profile_))),
+                                Contains(Flag("--profile-file-fd=", FdOf(framework_profile_))),
+                                Contains("--compiler-filter=speed-profile"))))
+      .WillOnce(Return(0));
+
+  // "verify" should always be used for boot image mainline extension.
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", conscrypt_jar_)),
+                                        Not(Contains(Flag("--profile-file-fd=", _))),
+                                        Contains("--compiler-filter=verify"))))
+      .WillOnce(Return(0));
+
+  EXPECT_EQ(odrefresh_->Compile(
+                *metrics_,
+                CompilationOptions{
+                    .boot_images_to_generate_for_isas{
+                        {InstructionSet::kX86_64,
+                         {.primary_boot_image = true, .boot_image_mainline_extension = true}}},
+                }),
             ExitCode::kCompilationSuccess);
 }
 
 TEST_F(OdRefreshTest, BootClasspathJarsFallback) {
   // Simulate the case where dex2oat fails when generating the full boot image.
   EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", core_oj_jar_})),
-                                        Contains(Concatenate({"--dex-file=", framework_jar_})))))
+              DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", core_oj_jar_)),
+                                        Contains(Flag("--dex-file=", framework_jar_)))))
       .Times(2)
       .WillRepeatedly(Return(1));
 
   // It should fall back to generating a minimal boot image.
-  EXPECT_CALL(
-      *mock_exec_utils_,
-      DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", core_oj_jar_})),
-                                Not(Contains(Concatenate({"--dex-file=", framework_jar_}))))))
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", core_oj_jar_)),
+                                        Not(Contains(Flag("--dex-file=", framework_jar_))))))
       .Times(2)
       .WillOnce(Return(0));
 
-  EXPECT_EQ(
-      odrefresh_->Compile(
-          *metrics_,
-          CompilationOptions{
-              .compile_boot_classpath_for_isas = {InstructionSet::kX86, InstructionSet::kX86_64},
-              .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
-          }),
-      ExitCode::kCompilationFailed);
+  EXPECT_EQ(odrefresh_->Compile(
+                *metrics_,
+                CompilationOptions{
+                    .boot_images_to_generate_for_isas{
+                        {InstructionSet::kX86_64,
+                         {.primary_boot_image = true, .boot_image_mainline_extension = true}},
+                        {InstructionSet::kX86,
+                         {.primary_boot_image = true, .boot_image_mainline_extension = true}}},
+                    .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
+                }),
+            ExitCode::kCompilationFailed);
 }
 
 TEST_F(OdRefreshTest, AllSystemServerJars) {
   EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(AllOf(
-                  Contains(Concatenate({"--dex-file=", location_provider_jar_})),
-                  Contains("--class-loader-context=PCL[]"))))
+              DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", location_provider_jar_)),
+                                        Contains("--class-loader-context=PCL[]"),
+                                        Not(Contains(Flag("--class-loader-context-fds=", _))),
+                                        Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
       .WillOnce(Return(0));
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(AllOf(
-                  Contains(Concatenate({"--dex-file=", services_jar_})),
-                  Contains(Concatenate({"--class-loader-context=PCL[", location_provider_jar_,
-                                        "]"})))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(Contains(Flag("--dex-file=", services_jar_)),
+                Contains(Flag("--class-loader-context=", "PCL[{}]"_format(location_provider_jar_))),
+                Contains(Flag("--class-loader-context-fds=", FdOf(location_provider_jar_))),
+                Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
       .WillOnce(Return(0));
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(AllOf(
-                  Contains(Concatenate({"--dex-file=", services_foo_jar_})),
-                  Contains(Concatenate({"--class-loader-context=PCL[];PCL[", location_provider_jar_,
-                                        ":", services_jar_, "]"})))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(AllOf(
+          Contains(Flag("--dex-file=", services_foo_jar_)),
+          Contains(Flag("--class-loader-context=",
+                        "PCL[];PCL[{}:{}]"_format(location_provider_jar_, services_jar_))),
+          Contains(ListFlag("--class-loader-context-fds=",
+                            ElementsAre(FdOf(location_provider_jar_), FdOf(services_jar_)))),
+          Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
       .WillOnce(Return(0));
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(AllOf(
-                  Contains(Concatenate({"--dex-file=", services_bar_jar_})),
-                  Contains(Concatenate({"--class-loader-context=PCL[];PCL[", location_provider_jar_,
-                                        ":", services_jar_, "]"})))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(AllOf(
+          Contains(Flag("--dex-file=", services_bar_jar_)),
+          Contains(Flag("--class-loader-context=",
+                        "PCL[];PCL[{}:{}]"_format(location_provider_jar_, services_jar_))),
+          Contains(ListFlag("--class-loader-context-fds=",
+                            ElementsAre(FdOf(location_provider_jar_), FdOf(services_jar_)))),
+          Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
       .WillOnce(Return(0));
 
   EXPECT_EQ(
@@ -329,16 +474,21 @@
 }
 
 TEST_F(OdRefreshTest, PartialSystemServerJars) {
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(AllOf(
-                  Contains(Concatenate({"--dex-file=", services_jar_})),
-                  Contains(Concatenate({"--class-loader-context=PCL[", location_provider_jar_, "]"})))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(Contains(Flag("--dex-file=", services_jar_)),
+                Contains(Flag("--class-loader-context=", "PCL[{}]"_format(location_provider_jar_))),
+                Contains(Flag("--class-loader-context-fds=", FdOf(location_provider_jar_))))))
       .WillOnce(Return(0));
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(AllOf(
-                  Contains(Concatenate({"--dex-file=", services_bar_jar_})),
-                  Contains(Concatenate({"--class-loader-context=PCL[];PCL[", location_provider_jar_,
-                                        ":", services_jar_, "]"})))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(AllOf(
+          Contains(Flag("--dex-file=", services_bar_jar_)),
+          Contains(Flag("--class-loader-context=",
+                        "PCL[];PCL[{}:{}]"_format(location_provider_jar_, services_jar_))),
+          Contains(ListFlag("--class-loader-context-fds=",
+                            ElementsAre(FdOf(location_provider_jar_), FdOf(services_jar_)))))))
       .WillOnce(Return(0));
 
   EXPECT_EQ(
@@ -362,176 +512,435 @@
       ExitCode::kCompilationSuccess);
 }
 
-TEST_F(OdRefreshTest, CompileSetsCompilerFilterToSpeed) {
-  // Test setup: use "speed" compiler filter.
-  config_.SetSystemServerCompilerFilter("speed");
-
-  // Uninteresting calls.
-  EXPECT_CALL(
-      *mock_exec_utils_, DoExecAndReturnCode(_))
-      .Times(odrefresh_->AllSystemServerJars().size() - 2)
-      .WillRepeatedly(Return(0));
-
-  EXPECT_CALL(
-      *mock_exec_utils_,
-      DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
-                                Not(Contains(HasSubstr("--profile-file-fd="))),
-                                Contains("--compiler-filter=speed"))))
+TEST_F(OdRefreshTest, ContinueWhenBcpCompilationFailed) {
+  // Simulate that the compilation of BCP for the system server ISA succeeds.
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(Contains("--instruction-set=x86_64"),
+                                        Contains(Flag("--dex-file=", core_oj_jar_)))))
       .WillOnce(Return(0));
-  EXPECT_CALL(
-      *mock_exec_utils_,
-      DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
-                                Not(Contains(HasSubstr("--profile-file-fd="))),
-                                Contains("--compiler-filter=speed"))))
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(Contains("--instruction-set=x86_64"),
+                                        Contains(Flag("--dex-file=", conscrypt_jar_)))))
       .WillOnce(Return(0));
+
+  // Simulate that the compilation of BCP for the other ISA fails.
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(Contains("--instruction-set=x86"),
+                                        Contains(Flag("--dex-file=", core_oj_jar_)))))
+      .Times(2)
+      .WillRepeatedly(Return(1));
+
+  // It should still compile system server.
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(Contains(Flag("--dex-file=", location_provider_jar_))))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(Contains(Flag("--dex-file=", services_jar_))))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(Contains(Flag("--dex-file=", services_foo_jar_))))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(Contains(Flag("--dex-file=", services_bar_jar_))))
+      .WillOnce(Return(0));
+
+  EXPECT_EQ(odrefresh_->Compile(
+                *metrics_,
+                CompilationOptions{
+                    .boot_images_to_generate_for_isas{
+                        {InstructionSet::kX86_64,
+                         {.primary_boot_image = true, .boot_image_mainline_extension = true}},
+                        {InstructionSet::kX86,
+                         {.primary_boot_image = true, .boot_image_mainline_extension = true}}},
+                    .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
+                }),
+            ExitCode::kCompilationFailed);
+}
+
+TEST_F(OdRefreshTest, ContinueWhenSystemServerCompilationFailed) {
+  // Simulate that the compilation of "services.jar" fails, while others still succeed.
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(Contains(Flag("--dex-file=", location_provider_jar_))))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(Contains(Flag("--dex-file=", services_jar_))))
+      .WillOnce(Return(1));
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(Contains(Flag("--dex-file=", services_foo_jar_))))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(Contains(Flag("--dex-file=", services_bar_jar_))))
+      .WillOnce(Return(0));
+
   EXPECT_EQ(
       odrefresh_->Compile(*metrics_,
                           CompilationOptions{
-                            .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
+                              .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
                           }),
-      ExitCode::kCompilationSuccess);
+      ExitCode::kCompilationFailed);
 }
 
-TEST_F(OdRefreshTest, CompileSetsCompilerFilterToSpeedProfile) {
-  // Test setup: with "speed-profile" compiler filter in the request, only apply if there is a
-  // profile, otherwise fallback to speed.
+// Test setup: The compiler filter is explicitly set to "speed-profile". Use it regardless of
+// whether the profile exists or not. Dex2oat will fall back to "verify" if the profile doesn't
+// exist.
+TEST_F(OdRefreshTest, CompileSetsCompilerFilterWithExplicitValue) {
   config_.SetSystemServerCompilerFilter("speed-profile");
 
   // Uninteresting calls.
-  EXPECT_CALL(
-      *mock_exec_utils_, DoExecAndReturnCode(_))
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_))
       .Times(odrefresh_->AllSystemServerJars().size() - 2)
       .WillRepeatedly(Return(0));
 
-  // services.jar has a profile, while location.provider.jar does not.
-  EXPECT_CALL(
-      *mock_exec_utils_,
-      DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
-                                Not(Contains(HasSubstr("--profile-file-fd="))),
-                                Contains("--compiler-filter=speed"))))
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", location_provider_jar_)),
+                                        Not(Contains(Flag("--profile-file-fd=", _))),
+                                        Contains("--compiler-filter=speed-profile"))))
       .WillOnce(Return(0));
   EXPECT_CALL(
       *mock_exec_utils_,
-      DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
-                                Contains(HasSubstr("--profile-file-fd=")),
+      DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", services_jar_)),
+                                Contains(Flag("--profile-file-fd=", FdOf(services_jar_profile_))),
                                 Contains("--compiler-filter=speed-profile"))))
       .WillOnce(Return(0));
   EXPECT_EQ(
       odrefresh_->Compile(*metrics_,
                           CompilationOptions{
-                            .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
-                          }),
-      ExitCode::kCompilationSuccess);
-}
-
-TEST_F(OdRefreshTest, CompileSetsCompilerFilterToVerify) {
-  // Test setup: use "speed" compiler filter.
-  config_.SetSystemServerCompilerFilter("verify");
-
-  // Uninteresting calls.
-  EXPECT_CALL(
-      *mock_exec_utils_, DoExecAndReturnCode(_))
-      .Times(odrefresh_->AllSystemServerJars().size() - 2)
-      .WillRepeatedly(Return(0));
-
-  EXPECT_CALL(
-      *mock_exec_utils_,
-      DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
-                                Not(Contains(HasSubstr("--profile-file-fd="))),
-                                Contains("--compiler-filter=verify"))))
-      .WillOnce(Return(0));
-  EXPECT_CALL(
-      *mock_exec_utils_,
-      DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
-                                Not(Contains(HasSubstr("--profile-file-fd="))),
-                                Contains("--compiler-filter=verify"))))
-      .WillOnce(Return(0));
-  EXPECT_EQ(
-      odrefresh_->Compile(*metrics_,
-                          CompilationOptions{
                               .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
                           }),
       ExitCode::kCompilationSuccess);
 }
 
+// Test setup: The compiler filter is not explicitly set. Use "speed-profile" if there is a vetted
+// profile (on U+), otherwise fall back to "speed".
+TEST_F(OdRefreshTest, CompileSetsCompilerFilterWithDefaultValue) {
+  // Uninteresting calls.
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_))
+      .Times(odrefresh_->AllSystemServerJars().size() - 2)
+      .WillRepeatedly(Return(0));
+
+  // services.jar has a profile, while location.provider.jar does not.
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", location_provider_jar_)),
+                                        Not(Contains(Flag("--profile-file-fd=", _))),
+                                        Contains("--compiler-filter=speed"))))
+      .WillOnce(Return(0));
+  // Only on U+ should we use the profile by default if available.
+  if (IsAtLeastU()) {
+    EXPECT_CALL(
+        *mock_exec_utils_,
+        DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", services_jar_)),
+                                  Contains(Flag("--profile-file-fd=", FdOf(services_jar_profile_))),
+                                  Contains("--compiler-filter=speed-profile"))))
+        .WillOnce(Return(0));
+  } else {
+    EXPECT_CALL(*mock_exec_utils_,
+                DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", services_jar_)),
+                                          Not(Contains(Flag("--profile-file-fd=", _))),
+                                          Contains("--compiler-filter=speed"))))
+        .WillOnce(Return(0));
+  }
+  EXPECT_EQ(
+      odrefresh_->Compile(*metrics_,
+                          CompilationOptions{
+                              .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
+                          }),
+      ExitCode::kCompilationSuccess);
+}
+
 TEST_F(OdRefreshTest, OutputFilesAndIsa) {
-  EXPECT_CALL(
-      *mock_exec_utils_,
-      DoExecAndReturnCode(AllOf(Contains("--instruction-set=x86_64"),
-                                Contains(HasSubstr("--image-fd=")),
-                                Contains(HasSubstr("--output-vdex-fd=")),
-                                Contains(HasSubstr("--oat-fd=")))))
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(Contains("--instruction-set=x86_64"),
+                                        Contains(Flag("--image-fd=", FdOf(_))),
+                                        Contains(Flag("--output-vdex-fd=", FdOf(_))),
+                                        Contains(Flag("--oat-fd=", FdOf(_))))))
+      .Times(2)
       .WillOnce(Return(0));
 
-  EXPECT_CALL(
-      *mock_exec_utils_,
-      DoExecAndReturnCode(AllOf(Contains("--instruction-set=x86_64"),
-                                Contains(HasSubstr("--app-image-fd=")),
-                                Contains(HasSubstr("--output-vdex-fd=")),
-                                Contains(HasSubstr("--oat-fd=")))))
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(Contains("--instruction-set=x86_64"),
+                                        Contains(Flag("--app-image-fd=", FdOf(_))),
+                                        Contains(Flag("--output-vdex-fd=", FdOf(_))),
+                                        Contains(Flag("--oat-fd=", FdOf(_))))))
       .Times(odrefresh_->AllSystemServerJars().size())
       .WillRepeatedly(Return(0));
 
-  EXPECT_EQ(
-      odrefresh_->Compile(*metrics_,
-                          CompilationOptions{
-                            .compile_boot_classpath_for_isas = {InstructionSet::kX86_64},
-                            .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
-                          }),
-      ExitCode::kCompilationSuccess);
+  EXPECT_EQ(odrefresh_->Compile(
+                *metrics_,
+                CompilationOptions{
+                    .boot_images_to_generate_for_isas{
+                        {InstructionSet::kX86_64,
+                         {.primary_boot_image = true, .boot_image_mainline_extension = true}}},
+                    .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
+                }),
+            ExitCode::kCompilationSuccess);
 }
 
-TEST_F(OdRefreshTest, CompileChoosesBootImage_OnData) {
-  // Boot image is on /data.
-  OdrArtifacts artifacts = OdrArtifacts::ForBootImage(dalvik_cache_dir_ + "/x86_64/boot.art");
-  auto file1 = ScopedCreateEmptyFile(artifacts.ImagePath());
-  auto file2 = ScopedCreateEmptyFile(artifacts.VdexPath());
-  auto file3 = ScopedCreateEmptyFile(artifacts.OatPath());
+TEST_F(OdRefreshTest, GenerateBootImageMainlineExtensionChoosesBootImage_OnData) {
+  // Primary boot image is on /data.
+  OdrArtifacts primary = OdrArtifacts::ForBootImage(dalvik_cache_dir_ + "/x86_64/boot.art");
+  auto file1 = ScopedCreateEmptyFile(primary.ImagePath());
+  auto file2 = ScopedCreateEmptyFile(primary.VdexPath());
+  auto file3 = ScopedCreateEmptyFile(primary.OatPath());
 
-  EXPECT_CALL(
-      *mock_exec_utils_,
-      DoExecAndReturnCode(AllOf(
-          Contains(Concatenate({"--boot-image=", dalvik_cache_dir_, "/boot.art"})),
-          Contains(FlagContains("-Xbootclasspathimagefds:", FdOf(artifacts.ImagePath()))),
-          Contains(FlagContains("-Xbootclasspathvdexfds:", FdOf(artifacts.VdexPath()))),
-          Contains(FlagContains("-Xbootclasspathoatfds:", FdOf(artifacts.OatPath()))))))
-      .Times(odrefresh_->AllSystemServerJars().size())
-      .WillRepeatedly(Return(0));
-  EXPECT_EQ(
-      odrefresh_->Compile(*metrics_,
-                          CompilationOptions{
-                            .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
-                          }),
-      ExitCode::kCompilationSuccess);
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(AllOf(
+                  Contains(Flag("--dex-file=", conscrypt_jar_)),
+                  Contains(Flag("--boot-image=", dalvik_cache_dir_ + "/boot.art")),
+                  Contains(ListFlag("-Xbootclasspathimagefds:",
+                                    ElementsAre(FdOf(primary.ImagePath()), "-1", "-1", "-1"))),
+                  Contains(ListFlag("-Xbootclasspathvdexfds:",
+                                    ElementsAre(FdOf(primary.VdexPath()), "-1", "-1", "-1"))),
+                  Contains(ListFlag("-Xbootclasspathoatfds:",
+                                    ElementsAre(FdOf(primary.OatPath()), "-1", "-1", "-1"))))))
+      .WillOnce(Return(0));
+
+  EXPECT_EQ(odrefresh_->Compile(
+                *metrics_,
+                CompilationOptions{
+                    .boot_images_to_generate_for_isas{
+                        {InstructionSet::kX86_64, {.boot_image_mainline_extension = true}}},
+                }),
+            ExitCode::kCompilationSuccess);
 }
 
-TEST_F(OdRefreshTest, CompileChoosesBootImage_OnSystem) {
-  // Boot image is on /system.
-  OdrArtifacts artifacts =
+TEST_F(OdRefreshTest, GenerateBootImageMainlineExtensionChoosesBootImage_OnSystem) {
+  // Primary boot image and framework extension are on /system.
+  OdrArtifacts primary = OdrArtifacts::ForBootImage(framework_dir_ + "/x86_64/boot.art");
+  auto file1 = ScopedCreateEmptyFile(primary.ImagePath());
+  auto file2 = ScopedCreateEmptyFile(primary.VdexPath());
+  auto file3 = ScopedCreateEmptyFile(primary.OatPath());
+  OdrArtifacts framework_ext =
       OdrArtifacts::ForBootImage(framework_dir_ + "/x86_64/boot-framework.art");
-  auto file1 = ScopedCreateEmptyFile(artifacts.ImagePath());
-  auto file2 = ScopedCreateEmptyFile(artifacts.VdexPath());
-  auto file3 = ScopedCreateEmptyFile(artifacts.OatPath());
+  auto file4 = ScopedCreateEmptyFile(framework_ext.ImagePath());
+  auto file5 = ScopedCreateEmptyFile(framework_ext.VdexPath());
+  auto file6 = ScopedCreateEmptyFile(framework_ext.OatPath());
 
-  // Ignore the execution for compiling the boot classpath.
-  EXPECT_CALL(
-      *mock_exec_utils_, DoExecAndReturnCode(Contains(HasSubstr("--image-fd="))))
-      .WillRepeatedly(Return(0));
+  if (IsAtLeastU()) {
+    EXPECT_CALL(
+        *mock_exec_utils_,
+        DoExecAndReturnCode(AllOf(
+            Contains(Flag("--dex-file=", conscrypt_jar_)),
+            Contains(ListFlag("--boot-image=", ElementsAre(framework_dir_ + "/boot.art"))),
+            Contains(ListFlag(
+                "-Xbootclasspathimagefds:",
+                ElementsAre(
+                    FdOf(primary.ImagePath()), FdOf(framework_ext.ImagePath()), "-1", "-1"))),
+            Contains(ListFlag(
+                "-Xbootclasspathvdexfds:",
+                ElementsAre(FdOf(primary.VdexPath()), FdOf(framework_ext.VdexPath()), "-1", "-1"))),
+            Contains(ListFlag(
+                "-Xbootclasspathoatfds:",
+                ElementsAre(FdOf(primary.OatPath()), FdOf(framework_ext.OatPath()), "-1", "-1"))))))
+        .WillOnce(Return(0));
+  } else {
+    EXPECT_CALL(
+        *mock_exec_utils_,
+        DoExecAndReturnCode(AllOf(
+            Contains(Flag("--dex-file=", conscrypt_jar_)),
+            Contains(ListFlag(
+                "--boot-image=",
+                ElementsAre(framework_dir_ + "/boot.art", framework_dir_ + "/boot-framework.art"))),
+            Contains(ListFlag(
+                "-Xbootclasspathimagefds:",
+                ElementsAre(
+                    FdOf(primary.ImagePath()), FdOf(framework_ext.ImagePath()), "-1", "-1"))),
+            Contains(ListFlag(
+                "-Xbootclasspathvdexfds:",
+                ElementsAre(FdOf(primary.VdexPath()), FdOf(framework_ext.VdexPath()), "-1", "-1"))),
+            Contains(ListFlag(
+                "-Xbootclasspathoatfds:",
+                ElementsAre(FdOf(primary.OatPath()), FdOf(framework_ext.OatPath()), "-1", "-1"))))))
+        .WillOnce(Return(0));
+  }
+
+  EXPECT_EQ(odrefresh_->Compile(
+                *metrics_,
+                CompilationOptions{
+                    .boot_images_to_generate_for_isas{
+                        {InstructionSet::kX86_64, {.boot_image_mainline_extension = true}}},
+                }),
+            ExitCode::kCompilationSuccess);
+}
+
+TEST_F(OdRefreshTest, CompileSystemServerChoosesBootImage_OnData) {
+  // Boot images are on /data.
+  OdrArtifacts primary = OdrArtifacts::ForBootImage(dalvik_cache_dir_ + "/x86_64/boot.art");
+  auto file1 = ScopedCreateEmptyFile(primary.ImagePath());
+  auto file2 = ScopedCreateEmptyFile(primary.VdexPath());
+  auto file3 = ScopedCreateEmptyFile(primary.OatPath());
+  OdrArtifacts mainline_ext =
+      OdrArtifacts::ForBootImage(dalvik_cache_dir_ + "/x86_64/boot-conscrypt.art");
+  auto file4 = ScopedCreateEmptyFile(mainline_ext.ImagePath());
+  auto file5 = ScopedCreateEmptyFile(mainline_ext.VdexPath());
+  auto file6 = ScopedCreateEmptyFile(mainline_ext.OatPath());
+
   EXPECT_CALL(
       *mock_exec_utils_,
       DoExecAndReturnCode(AllOf(
-          Contains(Concatenate({"--boot-image=",
-              GetPrebuiltPrimaryBootImageDir(), "/boot.art:",
-              framework_dir_, "/boot-framework.art"})),
-          Contains(FlagContains("-Xbootclasspathimagefds:", FdOf(artifacts.ImagePath()))),
-          Contains(FlagContains("-Xbootclasspathvdexfds:", FdOf(artifacts.VdexPath()))),
-          Contains(FlagContains("-Xbootclasspathoatfds:", FdOf(artifacts.OatPath()))))))
+          Contains(ListFlag("--boot-image=",
+                            ElementsAre(dalvik_cache_dir_ + "/boot.art",
+                                        dalvik_cache_dir_ + "/boot-conscrypt.art"))),
+          Contains(ListFlag(
+              "-Xbootclasspathimagefds:",
+              ElementsAre(FdOf(primary.ImagePath()), "-1", FdOf(mainline_ext.ImagePath()), "-1"))),
+          Contains(ListFlag(
+              "-Xbootclasspathvdexfds:",
+              ElementsAre(FdOf(primary.VdexPath()), "-1", FdOf(mainline_ext.VdexPath()), "-1"))),
+          Contains(ListFlag(
+              "-Xbootclasspathoatfds:",
+              ElementsAre(FdOf(primary.OatPath()), "-1", FdOf(mainline_ext.OatPath()), "-1"))))))
       .Times(odrefresh_->AllSystemServerJars().size())
       .WillRepeatedly(Return(0));
   EXPECT_EQ(
       odrefresh_->Compile(*metrics_,
                           CompilationOptions{
-                            .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
+                              .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
+                          }),
+      ExitCode::kCompilationSuccess);
+}
+
+TEST_F(OdRefreshTest, CompileSystemServerChoosesBootImage_OnSystemAndData) {
+  // The mainline extension is on /data, while others are on /system.
+  OdrArtifacts primary = OdrArtifacts::ForBootImage(framework_dir_ + "/x86_64/boot.art");
+  auto file1 = ScopedCreateEmptyFile(primary.ImagePath());
+  auto file2 = ScopedCreateEmptyFile(primary.VdexPath());
+  auto file3 = ScopedCreateEmptyFile(primary.OatPath());
+  OdrArtifacts framework_ext =
+      OdrArtifacts::ForBootImage(framework_dir_ + "/x86_64/boot-framework.art");
+  auto file4 = ScopedCreateEmptyFile(framework_ext.ImagePath());
+  auto file5 = ScopedCreateEmptyFile(framework_ext.VdexPath());
+  auto file6 = ScopedCreateEmptyFile(framework_ext.OatPath());
+  OdrArtifacts mainline_ext =
+      OdrArtifacts::ForBootImage(dalvik_cache_dir_ + "/x86_64/boot-conscrypt.art");
+  auto file7 = ScopedCreateEmptyFile(mainline_ext.ImagePath());
+  auto file8 = ScopedCreateEmptyFile(mainline_ext.VdexPath());
+  auto file9 = ScopedCreateEmptyFile(mainline_ext.OatPath());
+
+  if (IsAtLeastU()) {
+    EXPECT_CALL(*mock_exec_utils_,
+                DoExecAndReturnCode(AllOf(
+                    Contains(ListFlag("--boot-image=",
+                                      ElementsAre(GetPrebuiltPrimaryBootImageDir() + "/boot.art",
+                                                  dalvik_cache_dir_ + "/boot-conscrypt.art"))),
+                    Contains(ListFlag("-Xbootclasspathimagefds:",
+                                      ElementsAre(FdOf(primary.ImagePath()),
+                                                  FdOf(framework_ext.ImagePath()),
+                                                  FdOf(mainline_ext.ImagePath()),
+                                                  "-1"))),
+                    Contains(ListFlag("-Xbootclasspathvdexfds:",
+                                      ElementsAre(FdOf(primary.VdexPath()),
+                                                  FdOf(framework_ext.VdexPath()),
+                                                  FdOf(mainline_ext.VdexPath()),
+                                                  "-1"))),
+                    Contains(ListFlag("-Xbootclasspathoatfds:",
+                                      ElementsAre(FdOf(primary.OatPath()),
+                                                  FdOf(framework_ext.OatPath()),
+                                                  FdOf(mainline_ext.OatPath()),
+                                                  "-1"))))))
+        .Times(odrefresh_->AllSystemServerJars().size())
+        .WillRepeatedly(Return(0));
+  } else {
+    EXPECT_CALL(*mock_exec_utils_,
+                DoExecAndReturnCode(AllOf(
+                    Contains(ListFlag("--boot-image=",
+                                      ElementsAre(GetPrebuiltPrimaryBootImageDir() + "/boot.art",
+                                                  framework_dir_ + "/boot-framework.art",
+                                                  dalvik_cache_dir_ + "/boot-conscrypt.art"))),
+                    Contains(ListFlag("-Xbootclasspathimagefds:",
+                                      ElementsAre(FdOf(primary.ImagePath()),
+                                                  FdOf(framework_ext.ImagePath()),
+                                                  FdOf(mainline_ext.ImagePath()),
+                                                  "-1"))),
+                    Contains(ListFlag("-Xbootclasspathvdexfds:",
+                                      ElementsAre(FdOf(primary.VdexPath()),
+                                                  FdOf(framework_ext.VdexPath()),
+                                                  FdOf(mainline_ext.VdexPath()),
+                                                  "-1"))),
+                    Contains(ListFlag("-Xbootclasspathoatfds:",
+                                      ElementsAre(FdOf(primary.OatPath()),
+                                                  FdOf(framework_ext.OatPath()),
+                                                  FdOf(mainline_ext.OatPath()),
+                                                  "-1"))))))
+        .Times(odrefresh_->AllSystemServerJars().size())
+        .WillRepeatedly(Return(0));
+  }
+
+  EXPECT_EQ(
+      odrefresh_->Compile(*metrics_,
+                          CompilationOptions{
+                              .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
+                          }),
+      ExitCode::kCompilationSuccess);
+}
+
+TEST_F(OdRefreshTest, CompileSystemServerChoosesBootImage_OnSystem) {
+  // Boot images are on /system.
+  OdrArtifacts primary = OdrArtifacts::ForBootImage(framework_dir_ + "/x86_64/boot.art");
+  auto file1 = ScopedCreateEmptyFile(primary.ImagePath());
+  auto file2 = ScopedCreateEmptyFile(primary.VdexPath());
+  auto file3 = ScopedCreateEmptyFile(primary.OatPath());
+  OdrArtifacts framework_ext =
+      OdrArtifacts::ForBootImage(framework_dir_ + "/x86_64/boot-framework.art");
+  auto file4 = ScopedCreateEmptyFile(framework_ext.ImagePath());
+  auto file5 = ScopedCreateEmptyFile(framework_ext.VdexPath());
+  auto file6 = ScopedCreateEmptyFile(framework_ext.OatPath());
+  OdrArtifacts mainline_ext =
+      OdrArtifacts::ForBootImage(framework_dir_ + "/x86_64/boot-conscrypt.art");
+  auto file7 = ScopedCreateEmptyFile(mainline_ext.ImagePath());
+  auto file8 = ScopedCreateEmptyFile(mainline_ext.VdexPath());
+  auto file9 = ScopedCreateEmptyFile(mainline_ext.OatPath());
+
+  if (IsAtLeastU()) {
+    EXPECT_CALL(*mock_exec_utils_,
+                DoExecAndReturnCode(AllOf(
+                    Contains(ListFlag("--boot-image=",
+                                      ElementsAre(GetPrebuiltPrimaryBootImageDir() + "/boot.art",
+                                                  framework_dir_ + "/boot-conscrypt.art"))),
+                    Contains(ListFlag("-Xbootclasspathimagefds:",
+                                      ElementsAre(FdOf(primary.ImagePath()),
+                                                  FdOf(framework_ext.ImagePath()),
+                                                  FdOf(mainline_ext.ImagePath()),
+                                                  "-1"))),
+                    Contains(ListFlag("-Xbootclasspathvdexfds:",
+                                      ElementsAre(FdOf(primary.VdexPath()),
+                                                  FdOf(framework_ext.VdexPath()),
+                                                  FdOf(mainline_ext.VdexPath()),
+                                                  "-1"))),
+                    Contains(ListFlag("-Xbootclasspathoatfds:",
+                                      ElementsAre(FdOf(primary.OatPath()),
+                                                  FdOf(framework_ext.OatPath()),
+                                                  FdOf(mainline_ext.OatPath()),
+                                                  "-1"))))))
+        .Times(odrefresh_->AllSystemServerJars().size())
+        .WillRepeatedly(Return(0));
+  } else {
+    EXPECT_CALL(*mock_exec_utils_,
+                DoExecAndReturnCode(AllOf(
+                    Contains(ListFlag("--boot-image=",
+                                      ElementsAre(GetPrebuiltPrimaryBootImageDir() + "/boot.art",
+                                                  framework_dir_ + "/boot-framework.art",
+                                                  framework_dir_ + "/boot-conscrypt.art"))),
+                    Contains(ListFlag("-Xbootclasspathimagefds:",
+                                      ElementsAre(FdOf(primary.ImagePath()),
+                                                  FdOf(framework_ext.ImagePath()),
+                                                  FdOf(mainline_ext.ImagePath()),
+                                                  "-1"))),
+                    Contains(ListFlag("-Xbootclasspathvdexfds:",
+                                      ElementsAre(FdOf(primary.VdexPath()),
+                                                  FdOf(framework_ext.VdexPath()),
+                                                  FdOf(mainline_ext.VdexPath()),
+                                                  "-1"))),
+                    Contains(ListFlag("-Xbootclasspathoatfds:",
+                                      ElementsAre(FdOf(primary.OatPath()),
+                                                  FdOf(framework_ext.OatPath()),
+                                                  FdOf(mainline_ext.OatPath()),
+                                                  "-1"))))))
+        .Times(odrefresh_->AllSystemServerJars().size())
+        .WillRepeatedly(Return(0));
+  }
+
+  EXPECT_EQ(
+      odrefresh_->Compile(*metrics_,
+                          CompilationOptions{
+                              .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
                           }),
       ExitCode::kCompilationSuccess);
 }
diff --git a/openjdkjvm/Android.bp b/openjdkjvm/Android.bp
index 2757d9d..eb379d8 100644
--- a/openjdkjvm/Android.bp
+++ b/openjdkjvm/Android.bp
@@ -35,10 +35,10 @@
     name: "art_openjdkjvm_license",
     visibility: [":__subpackages__"],
     license_kinds: [
-        "SPDX-license-identifier-GPL-with-classpath-exception",
+        "SPDX-license-identifier-GPL-2.0-with-classpath-exception",
     ],
     license_text: [
-        "NOTICE",
+        "LICENSE",
     ],
 }
 
diff --git a/openjdkjvm/NOTICE b/openjdkjvm/LICENSE
similarity index 100%
rename from openjdkjvm/NOTICE
rename to openjdkjvm/LICENSE
diff --git a/openjdkjvm/OpenjdkJvm.cc b/openjdkjvm/OpenjdkJvm.cc
index 8c1f773..da6fc85 100644
--- a/openjdkjvm/OpenjdkJvm.cc
+++ b/openjdkjvm/OpenjdkJvm.cc
@@ -32,6 +32,8 @@
 /*
  * Services that OpenJDK expects the VM to provide.
  */
+
+#include <assert.h>
 #include <dlfcn.h>
 #include <limits.h>
 #include <stdio.h>
diff --git a/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp
index d01357f..98cb466 100644
--- a/openjdkjvmti/Android.bp
+++ b/openjdkjvmti/Android.bp
@@ -36,10 +36,10 @@
     visibility: [":__subpackages__"],
     license_kinds: [
         "SPDX-license-identifier-Apache-2.0",
-        "SPDX-license-identifier-GPL-with-classpath-exception",
+        "SPDX-license-identifier-GPL-2.0-with-classpath-exception",
     ],
     license_text: [
-        "NOTICE",
+        "LICENSE",
     ],
 }
 
@@ -111,13 +111,13 @@
     shared_libs: [
         "libart",
         "libart-compiler",
-        "libart-dexlayout",
         "libdexfile",
         "libartbase",
     ],
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -130,7 +130,6 @@
     shared_libs: [
         "libartd",
         "libartd-compiler",
-        "libartd-dexlayout",
         "libdexfiled",
         "libartbased",
     ],
diff --git a/openjdkjvmti/NOTICE b/openjdkjvmti/LICENSE
similarity index 100%
rename from openjdkjvmti/NOTICE
rename to openjdkjvmti/LICENSE
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index 09900e1..276b3a8 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -89,12 +89,6 @@
     }                           \
   } while (false)
 
-// Returns whether we are able to use all jvmti features.
-static bool IsFullJvmtiAvailable() {
-  art::Runtime* runtime = art::Runtime::Current();
-  return runtime->GetInstrumentation()->IsForcedInterpretOnly() || runtime->IsJavaDebuggable();
-}
-
 class JvmtiFunctions {
  private:
   static jvmtiError getEnvironmentError(jvmtiEnv* env) {
@@ -1474,19 +1468,21 @@
   FieldUtil::Register(gEventHandler);
   BreakpointUtil::Register(gEventHandler);
   Transformer::Register(gEventHandler);
-
-  {
-    // Make sure we can deopt anything we need to.
-    art::ScopedSuspendAll ssa(__FUNCTION__);
-    gDeoptManager->FinishSetup();
-  }
-
+  gDeoptManager->FinishSetup();
   runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler);
 
   return true;
 }
 
 extern "C" bool ArtPlugin_Deinitialize() {
+  // When runtime is shutting down, it is not necessary to unregister callbacks or update
+  // instrumentation levels. Removing callbacks require a GC critical section in some cases and
+  // when runtime is shutting down we already stop GC and hence it is not safe to request to
+  // enter a GC critical section.
+  if (art::Runtime::Current()->IsShuttingDown(art::Thread::Current())) {
+    return true;
+  }
+
   gEventHandler->Shutdown();
   gDeoptManager->Shutdown();
   PhaseUtil::Unregister();
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 083ba6d..bc965a2 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -47,9 +47,11 @@
 #include "base/strlcpy.h"
 #include "base/mutex.h"
 #include "events.h"
+#include "instrumentation.h"
 #include "jni/java_vm_ext.h"
 #include "jni/jni_env_ext.h"
 #include "jvmti.h"
+#include "runtime.h"
 #include "ti_breakpoint.h"
 
 namespace art {
@@ -69,6 +71,13 @@
 // This is the value 0x70010200.
 static constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000;
 
+// Returns whether we are able to use all jvmti features.
+static inline bool IsFullJvmtiAvailable() {
+  art::Runtime* runtime = art::Runtime::Current();
+  return runtime->GetInstrumentation()->IsForcedInterpretOnly() ||
+         runtime->IsJavaDebuggableAtInit();
+}
+
 // A structure that is a jvmtiEnv with additional information for the runtime.
 struct ArtJvmTiEnv : public jvmtiEnv {
   art::JavaVMExt* art_vm;
@@ -141,7 +150,7 @@
   explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
 
   JvmtiDeleter(JvmtiDeleter&) = default;
-  JvmtiDeleter(JvmtiDeleter&&) = default;
+  JvmtiDeleter(JvmtiDeleter&&) noexcept = default;
   JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
 
   void operator()(T* ptr) const {
@@ -161,7 +170,7 @@
   explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
 
   JvmtiDeleter(JvmtiDeleter&) = default;
-  JvmtiDeleter(JvmtiDeleter&&) = default;
+  JvmtiDeleter(JvmtiDeleter&&) noexcept = default;
   JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
 
   template <typename U>
diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc
index 312a797..978843c 100644
--- a/openjdkjvmti/deopt_manager.cc
+++ b/openjdkjvmti/deopt_manager.cc
@@ -47,10 +47,12 @@
 #include "gc/scoped_gc_critical_section.h"
 #include "instrumentation.h"
 #include "jit/jit.h"
+#include "jit/jit_code_cache.h"
 #include "jni/jni_internal.h"
 #include "mirror/class-inl.h"
 #include "mirror/object_array-inl.h"
 #include "nativehelper/scoped_local_ref.h"
+#include "oat_file_manager.h"
 #include "read_barrier_config.h"
 #include "runtime_callbacks.h"
 #include "scoped_thread_state_change-inl.h"
@@ -61,16 +63,15 @@
 
 namespace openjdkjvmti {
 
-// TODO We should make this much more selective in the future so we only return true when we
+static constexpr const char* kInstrumentationKey = "JVMTI_DeoptRequester";
+
+// We could make this much more selective in the future so we only return true when we
 // actually care about the method at this time (ie active frames had locals changed). For now we
-// just assume that if anything has changed any frame's locals we care about all methods. If nothing
-// has we only care about methods with active breakpoints on them. In the future we should probably
-// rewrite all of this to instead do this at the ShadowFrame or thread granularity.
-bool JvmtiMethodInspectionCallback::IsMethodBeingInspected(art::ArtMethod* method) {
-  // In non-java-debuggable runtimes the breakpoint check would miss if we have breakpoints on
-  // methods that are inlined. Since these features are best effort in non-java-debuggable
-  // runtimes it is OK to be less precise. For debuggable runtimes, inlining is disabled.
-  return manager_->HaveLocalsChanged() || manager_->MethodHasBreakpoints(method);
+// just assume that if anything has changed any frame's locals we care about all methods. This only
+// impacts whether we are able to OSR or not so maybe not really important to maintain frame
+// specific information.
+bool JvmtiMethodInspectionCallback::HaveLocalsChanged() {
+  return manager_->HaveLocalsChanged();
 }
 
 DeoptManager::DeoptManager()
@@ -94,14 +95,6 @@
   callbacks->AddMethodInspectionCallback(&inspection_callback_);
 }
 
-void DeoptManager::Shutdown() {
-  art::ScopedThreadStateChange stsc(art::Thread::Current(),
-                                    art::ThreadState::kWaitingForDebuggerToAttach);
-  art::ScopedSuspendAll ssa("remove method Inspection Callback");
-  art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks();
-  callbacks->RemoveMethodInspectionCallback(&inspection_callback_);
-}
-
 void DeoptManager::DumpDeoptInfo(art::Thread* self, std::ostream& stream) {
   art::ScopedObjectAccess soa(self);
   art::MutexLock mutll(self, *art::Locks::thread_list_lock_);
@@ -151,48 +144,59 @@
 
 void DeoptManager::FinishSetup() {
   art::Thread* self = art::Thread::Current();
-  art::MutexLock mu(self, deoptimization_status_lock_);
-
   art::Runtime* runtime = art::Runtime::Current();
-  // See if we need to do anything.
-  if (!runtime->IsJavaDebuggable()) {
-    // See if we can enable all JVMTI functions. If this is false, only kArtTiVersion agents can be
-    // retrieved and they will all be best-effort.
-    if (PhaseUtil::GetPhaseUnchecked() == JVMTI_PHASE_ONLOAD) {
-      // We are still early enough to change the compiler options and get full JVMTI support.
-      LOG(INFO) << "Openjdkjvmti plugin loaded on a non-debuggable runtime. Changing runtime to "
-                << "debuggable state. Please pass '--debuggable' to dex2oat and "
-                << "'-Xcompiler-option --debuggable' to dalvikvm in the future.";
-      DCHECK(runtime->GetJit() == nullptr) << "Jit should not be running yet!";
-      runtime->AddCompilerOption("--debuggable");
-      runtime->SetJavaDebuggable(true);
-    } else {
-      LOG(WARNING) << "Openjdkjvmti plugin was loaded on a non-debuggable Runtime. Plugin was "
-                   << "loaded too late to change runtime state to DEBUGGABLE. Only kArtTiVersion "
-                   << "(0x" << std::hex << kArtTiVersion << ") environments are available. Some "
-                   << "functionality might not work properly.";
-      if (runtime->GetJit() == nullptr &&
-          runtime->GetJITOptions()->UseJitCompilation() &&
-          !runtime->GetInstrumentation()->IsForcedInterpretOnly()) {
-        // If we don't have a jit we should try to start the jit for performance reasons. We only
-        // need to do this for late attach on non-debuggable processes because for debuggable
-        // processes we already rely on jit and we cannot force this jit to start if we are still in
-        // OnLoad since the runtime hasn't started up sufficiently. This is only expected to happen
-        // on userdebug/eng builds.
-        LOG(INFO) << "Attempting to start jit for openjdkjvmti plugin.";
-        // Note: use rwx allowed = true, because if this is the system server, we will not be
-        //       allowed to allocate any JIT code cache, anyways.
-        runtime->CreateJitCodeCache(/*rwx_memory_allowed=*/true);
-        runtime->CreateJit();
-        if (runtime->GetJit() == nullptr) {
-          LOG(WARNING) << "Could not start jit for openjdkjvmti plugin. This process might be "
-                       << "quite slow as it is running entirely in the interpreter. Try running "
-                       << "'setenforce 0' and restarting this process.";
-        }
-      }
-    }
-    runtime->DeoptimizeBootImage();
+  if (runtime->IsJavaDebuggable()) {
+    return;
   }
+
+  // See if we can enable all JVMTI functions.
+  if (PhaseUtil::GetPhaseUnchecked() == JVMTI_PHASE_ONLOAD) {
+    // We are still early enough to change the compiler options and get full JVMTI support.
+    LOG(INFO) << "Openjdkjvmti plugin loaded on a non-debuggable runtime. Changing runtime to "
+              << "debuggable state. Please pass '--debuggable' to dex2oat and "
+              << "'-Xcompiler-option --debuggable' to dalvikvm in the future.";
+    DCHECK(runtime->GetJit() == nullptr) << "Jit should not be running yet!";
+    art::ScopedSuspendAll ssa(__FUNCTION__);
+    // TODO check if we need to hold deoptimization_status_lock_ here.
+    art::MutexLock mu(self, deoptimization_status_lock_);
+    runtime->AddCompilerOption("--debuggable");
+    runtime->SetRuntimeDebugState(art::Runtime::RuntimeDebugState::kJavaDebuggableAtInit);
+    runtime->DeoptimizeBootImage();
+    return;
+  }
+
+  // Runtime has already started in non-debuggable mode. Only kArtTiVersion agents can be
+  // retrieved and they will all be best-effort.
+  LOG(WARNING) << "Openjdkjvmti plugin was loaded on a non-debuggable Runtime. Plugin was "
+               << "loaded too late to change runtime state to support all capabilities. Only "
+               << "kArtTiVersion (0x" << std::hex << kArtTiVersion << ") environments are "
+               << "available. Some functionality might not work properly.";
+
+  // Transition the runtime to debuggable:
+  // 1. Wait for any background verification tasks to finish. We don't support
+  // background verification after moving to debuggable state.
+  runtime->GetOatFileManager().WaitForBackgroundVerificationTasksToFinish();
+
+  // Do the transition in ScopedJITSuspend, so we don't start any JIT compilations
+  // before the transition to debuggable is finished.
+  art::jit::ScopedJitSuspend suspend_jit;
+  art::ScopedSuspendAll ssa(__FUNCTION__);
+
+  // 2. Discard any JITed code that was generated before, since they would be
+  // compiled without debug support.
+  art::jit::Jit* jit = runtime->GetJit();
+  if (jit != nullptr) {
+    jit->GetCodeCache()->InvalidateAllCompiledCode();
+    jit->GetCodeCache()->TransitionToDebuggable();
+    jit->GetJitCompiler()->SetDebuggableCompilerOption(true);
+  }
+
+  // 3. Change the state to JavaDebuggable, so that debug features can be
+  // enabled from now on.
+  runtime->SetRuntimeDebugState(art::Runtime::RuntimeDebugState::kJavaDebuggable);
+
+  // 4. Update all entrypoints to avoid using any AOT code.
+  runtime->GetInstrumentation()->UpdateEntrypointsForDebuggable();
 }
 
 bool DeoptManager::MethodHasBreakpoints(art::ArtMethod* method) {
@@ -365,6 +369,30 @@
   }
 }
 
+void DeoptManager::Shutdown() {
+  art::Thread* self = art::Thread::Current();
+  art::Runtime* runtime = art::Runtime::Current();
+
+  // Do the transition in ScopedJITSuspend, so we don't start any JIT compilations
+  // before the transition to debuggable is finished.
+  art::jit::ScopedJitSuspend suspend_jit;
+
+  art::ScopedThreadStateChange sts(self, art::ThreadState::kSuspended);
+  deoptimization_status_lock_.ExclusiveLock(self);
+  ScopedDeoptimizationContext sdc(self, this);
+
+  art::RuntimeCallbacks* callbacks = runtime->GetRuntimeCallbacks();
+  callbacks->RemoveMethodInspectionCallback(&inspection_callback_);
+
+  if (runtime->IsShuttingDown(self)) {
+    return;
+  }
+
+  runtime->GetInstrumentation()->DisableDeoptimization(kInstrumentationKey);
+  runtime->GetInstrumentation()->DisableDeoptimization(kDeoptManagerInstrumentationKey);
+  runtime->GetInstrumentation()->MaybeSwitchRuntimeDebugState(self);
+}
+
 void DeoptManager::RemoveDeoptimizeAllMethodsLocked(art::Thread* self) {
   DCHECK_GT(global_deopt_count_, 0u) << "Request to remove non-existent global deoptimization!";
   global_deopt_count_--;
@@ -438,7 +466,6 @@
   return OK;
 }
 
-static constexpr const char* kInstrumentationKey = "JVMTI_DeoptRequester";
 
 void DeoptManager::RemoveDeoptimizationRequester() {
   art::Thread* self = art::Thread::Current();
@@ -460,6 +487,15 @@
   art::ScopedThreadStateChange stsc(self, art::ThreadState::kSuspended);
   deoptimization_status_lock_.ExclusiveLock(self);
   deopter_count_++;
+  if (deopter_count_ == 1) {
+    // When we add a deoptimization requester, we should enable entry / exit hooks. We only call
+    // this in debuggable runtimes and hence it won't be necessary to update entrypoints but we
+    // still need to inform instrumentation that we need to actually run entry / exit hooks. Though
+    // entrypoints are capable of running entry / exit hooks they won't run them unless enabled.
+    ScopedDeoptimizationContext sdc(self, this);
+    art::Runtime::Current()->GetInstrumentation()->EnableEntryExitHooks(kInstrumentationKey);
+    return;
+  }
   deoptimization_status_lock_.ExclusiveUnlock(self);
 }
 
diff --git a/openjdkjvmti/deopt_manager.h b/openjdkjvmti/deopt_manager.h
index c0a788b..e9b91de 100644
--- a/openjdkjvmti/deopt_manager.h
+++ b/openjdkjvmti/deopt_manager.h
@@ -57,8 +57,7 @@
  public:
   explicit JvmtiMethodInspectionCallback(DeoptManager* manager) : manager_(manager) {}
 
-  bool IsMethodBeingInspected(art::ArtMethod* method)
-      override REQUIRES_SHARED(art::Locks::mutator_lock_);
+  bool HaveLocalsChanged() override REQUIRES_SHARED(art::Locks::mutator_lock_);
 
  private:
   DeoptManager* manager_;
@@ -111,9 +110,7 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_);
   void DeoptimizeAllThreads() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
-  void FinishSetup()
-      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
-      REQUIRES(art::Locks::mutator_lock_);
+  void FinishSetup() REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_);
 
   static DeoptManager* Get();
 
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index a6425af..64da6ed 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -29,25 +29,24 @@
  * questions.
  */
 
-#include <android-base/thread_annotations.h>
+#include "events.h"
 
-#include "alloc_manager.h"
-#include "base/locks.h"
-#include "base/mutex.h"
-#include "events-inl.h"
+#include <sys/time.h>
 
 #include <array>
 #include <functional>
-#include <sys/time.h>
 
+#include "alloc_manager.h"
+#include "android-base/thread_annotations.h"
 #include "arch/context.h"
 #include "art_field-inl.h"
 #include "art_jvmti.h"
 #include "art_method-inl.h"
+#include "base/locks.h"
 #include "base/mutex.h"
 #include "deopt_manager.h"
 #include "dex/dex_file_types.h"
-#include "events.h"
+#include "events-inl.h"
 #include "gc/allocation_listener.h"
 #include "gc/gc_pause_listener.h"
 #include "gc/heap.h"
@@ -71,8 +70,8 @@
 #include "scoped_thread_state_change-inl.h"
 #include "scoped_thread_state_change.h"
 #include "stack.h"
-#include "thread.h"
 #include "thread-inl.h"
+#include "thread.h"
 #include "thread_list.h"
 #include "ti_phase.h"
 #include "ti_thread.h"
@@ -447,9 +446,8 @@
     if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorWait)) {
       art::Thread* self = art::Thread::Current();
       art::JNIEnvExt* jnienv = self->GetJniEnv();
-      art::ArtField* parkBlockerField = art::jni::DecodeArtField(
-          art::WellKnownClasses::java_lang_Thread_parkBlocker);
-      art::ObjPtr<art::mirror::Object> blocker_obj = parkBlockerField->GetObj(self->GetPeer());
+      art::ObjPtr<art::mirror::Object> blocker_obj =
+          art::WellKnownClasses::java_lang_Thread_parkBlocker->GetObj(self->GetPeer());
       if (blocker_obj.IsNull()) {
         blocker_obj = self->GetPeer();
       }
@@ -505,9 +503,8 @@
     if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorWaited)) {
       art::Thread* self = art::Thread::Current();
       art::JNIEnvExt* jnienv = self->GetJniEnv();
-      art::ArtField* parkBlockerField = art::jni::DecodeArtField(
-          art::WellKnownClasses::java_lang_Thread_parkBlocker);
-      art::ObjPtr<art::mirror::Object> blocker_obj = parkBlockerField->GetObj(self->GetPeer());
+      art::ObjPtr<art::mirror::Object> blocker_obj =
+          art::WellKnownClasses::java_lang_Thread_parkBlocker->GetObj(self->GetPeer());
       if (blocker_obj.IsNull()) {
         blocker_obj = self->GetPeer();
       }
@@ -741,7 +738,6 @@
   // Call-back for when a method is popped due to an exception throw. A method will either cause a
   // MethodExited call-back or a MethodUnwind call-back when its activation is removed.
   void MethodUnwind(art::Thread* self,
-                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
                     art::ArtMethod* method,
                     uint32_t dex_pc ATTRIBUTE_UNUSED)
       REQUIRES_SHARED(art::Locks::mutator_lock_) override {
@@ -1133,13 +1129,11 @@
   switch (event) {
     case ArtJvmtiEvent::kBreakpoint:
     case ArtJvmtiEvent::kException:
-      return DeoptRequirement::kLimited;
-    // TODO MethodEntry is needed due to inconsistencies between the interpreter and the trampoline
-    // in how to handle exceptions.
     case ArtJvmtiEvent::kMethodEntry:
+    case ArtJvmtiEvent::kMethodExit:
+      return DeoptRequirement::kLimited;
     case ArtJvmtiEvent::kExceptionCatch:
       return DeoptRequirement::kFull;
-    case ArtJvmtiEvent::kMethodExit:
     case ArtJvmtiEvent::kFieldModification:
     case ArtJvmtiEvent::kFieldAccess:
     case ArtJvmtiEvent::kSingleStep:
@@ -1256,10 +1250,10 @@
       }
       for (auto& m : klass->GetMethods(art::kRuntimePointerSize)) {
         const void* code = m.GetEntryPointFromQuickCompiledCode();
-        if (m.IsNative() || m.IsProxyMethod()) {
+        if (m.IsNative() || m.IsProxyMethod() || !m.IsInvokable()) {
           continue;
         } else if (!runtime_->GetClassLinker()->IsQuickToInterpreterBridge(code) &&
-                   !runtime_->IsAsyncDeoptimizeable(reinterpret_cast<uintptr_t>(code))) {
+                   !runtime_->IsAsyncDeoptimizeable(&m, reinterpret_cast<uintptr_t>(code))) {
           runtime_->GetInstrumentation()->InitializeMethodsCode(&m, /*aot_code=*/ nullptr);
         }
       }
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index be0839b..7420296 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -21,9 +21,7 @@
 #include <unordered_map>
 #include <vector>
 
-#include <android-base/logging.h>
-#include <android-base/thread_annotations.h>
-
+#include "android-base/logging.h"
 #include "android-base/thread_annotations.h"
 #include "base/macros.h"
 #include "base/mutex.h"
diff --git a/openjdkjvmti/jvmti_allocator.h b/openjdkjvmti/jvmti_allocator.h
index bd4c85b..4adf769 100644
--- a/openjdkjvmti/jvmti_allocator.h
+++ b/openjdkjvmti/jvmti_allocator.h
@@ -46,13 +46,13 @@
 template <>
 class JvmtiAllocator<void> {
  public:
-  typedef void value_type;
-  typedef void* pointer;
-  typedef const void* const_pointer;
+  using value_type = void;
+  using pointer = void*;
+  using const_pointer = const void*;
 
   template <typename U>
   struct rebind {
-    typedef JvmtiAllocator<U> other;
+    using other = JvmtiAllocator<U>;
   };
 
   explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {}
@@ -79,17 +79,17 @@
 template <typename T>
 class JvmtiAllocator {
  public:
-  typedef T value_type;
-  typedef T* pointer;
-  typedef T& reference;
-  typedef const T* const_pointer;
-  typedef const T& const_reference;
-  typedef size_t size_type;
-  typedef ptrdiff_t difference_type;
+  using value_type = T;
+  using pointer = T*;
+  using reference = T&;
+  using const_pointer = const T*;
+  using const_reference = const T&;
+  using size_type = size_t;
+  using difference_type = ptrdiff_t;
 
   template <typename U>
   struct rebind {
-    typedef JvmtiAllocator<U> other;
+    using other = JvmtiAllocator<U>;
   };
 
   explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {}
diff --git a/openjdkjvmti/jvmti_weak_table-inl.h b/openjdkjvmti/jvmti_weak_table-inl.h
index 5b28e45..c5663e5 100644
--- a/openjdkjvmti/jvmti_weak_table-inl.h
+++ b/openjdkjvmti/jvmti_weak_table-inl.h
@@ -114,7 +114,7 @@
     return true;
   }
 
-  if (art::kUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
+  if (art::gUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
     // Under concurrent GC, there is a window between moving objects and sweeping of system
     // weaks in which mutators are active. We may receive a to-space object pointer in obj,
     // but still have from-space pointers in the table. Explicitly update the table once.
@@ -156,7 +156,7 @@
     return true;
   }
 
-  if (art::kUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
+  if (art::gUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
     // Under concurrent GC, there is a window between moving objects and sweeping of system
     // weaks in which mutators are active. We may receive a to-space object pointer in obj,
     // but still have from-space pointers in the table. Explicitly update the table once.
@@ -210,13 +210,13 @@
 template <typename T>
 template <typename Updater, typename JvmtiWeakTable<T>::TableUpdateNullTarget kTargetNull>
 ALWAYS_INLINE inline void JvmtiWeakTable<T>::UpdateTableWith(Updater& updater) {
-  // We optimistically hope that elements will still be well-distributed when re-inserting them.
-  // So play with the map mechanics, and postpone rehashing. This avoids the need of a side
-  // vector and two passes.
-  float original_max_load_factor = tagged_objects_.max_load_factor();
-  tagged_objects_.max_load_factor(std::numeric_limits<float>::max());
-  // For checking that a max load-factor actually does what we expect.
-  size_t original_bucket_count = tagged_objects_.bucket_count();
+  // We can't emplace within the map as a to-space reference could be the same as some
+  // from-space object reference in the map, causing correctness issues. The problem
+  // doesn't arise if all updated <K,V> pairs are inserted after the loop as by then such
+  // from-space object references would also have been taken care of.
+
+  // Side vector to hold node handles of entries which are updated.
+  std::vector<typename TagMap::node_type> updated_node_handles;
 
   for (auto it = tagged_objects_.begin(); it != tagged_objects_.end();) {
     DCHECK(!it->first.IsNull());
@@ -226,22 +226,24 @@
       if (kTargetNull == kIgnoreNull && target_obj == nullptr) {
         // Ignore null target, don't do anything.
       } else {
-        T tag = it->second;
-        it = tagged_objects_.erase(it);
+        auto nh = tagged_objects_.extract(it++);
+        DCHECK(!nh.empty());
         if (target_obj != nullptr) {
-          tagged_objects_.emplace(art::GcRoot<art::mirror::Object>(target_obj), tag);
-          DCHECK_EQ(original_bucket_count, tagged_objects_.bucket_count());
+          nh.key() = art::GcRoot<art::mirror::Object>(target_obj);
+          updated_node_handles.push_back(std::move(nh));
         } else if (kTargetNull == kCallHandleNull) {
-          HandleNullSweep(tag);
+          HandleNullSweep(nh.mapped());
         }
-        continue;  // Iterator was implicitly updated by erase.
+        continue;  // Iterator already updated above.
       }
     }
     it++;
   }
-
-  tagged_objects_.max_load_factor(original_max_load_factor);
-  // TODO: consider rehash here.
+  while (!updated_node_handles.empty()) {
+    auto ret = tagged_objects_.insert(std::move(updated_node_handles.back()));
+    DCHECK(ret.inserted);
+    updated_node_handles.pop_back();
+  }
 }
 
 template <typename T>
diff --git a/openjdkjvmti/jvmti_weak_table.h b/openjdkjvmti/jvmti_weak_table.h
index ea0d023..674b2a3 100644
--- a/openjdkjvmti/jvmti_weak_table.h
+++ b/openjdkjvmti/jvmti_weak_table.h
@@ -152,7 +152,7 @@
 
     // Performance optimization: To avoid multiple table updates, ensure that during GC we
     // only update once. See the comment on the implementation of GetTagSlowPath.
-    if (art::kUseReadBarrier &&
+    if (art::gUseReadBarrier &&
         self != nullptr &&
         self->GetIsGcMarking() &&
         !update_since_last_sweep_) {
@@ -211,13 +211,13 @@
   };
 
   using TagAllocator = JvmtiAllocator<std::pair<const art::GcRoot<art::mirror::Object>, T>>;
-  std::unordered_map<art::GcRoot<art::mirror::Object>,
-                     T,
-                     HashGcRoot,
-                     EqGcRoot,
-                     TagAllocator> tagged_objects_
-      GUARDED_BY(allow_disallow_lock_)
-      GUARDED_BY(art::Locks::mutator_lock_);
+  using TagMap = std::unordered_map<art::GcRoot<art::mirror::Object>,
+                                    T,
+                                    HashGcRoot,
+                                    EqGcRoot,
+                                    TagAllocator>;
+
+  TagMap tagged_objects_ GUARDED_BY(allow_disallow_lock_) GUARDED_BY(art::Locks::mutator_lock_);
   // To avoid repeatedly scanning the whole table, remember if we did that since the last sweep.
   bool update_since_last_sweep_;
 };
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
index 9752fb1..3d44516 100644
--- a/openjdkjvmti/ti_class.cc
+++ b/openjdkjvmti/ti_class.cc
@@ -80,7 +80,7 @@
 #include "ti_phase.h"
 #include "ti_redefine.h"
 #include "transform.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace openjdkjvmti {
 
@@ -113,10 +113,8 @@
   }
   uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map.Begin())->checksum_;
   std::string map_name = map.GetName();
-  const art::ArtDexFileLoader dex_file_loader;
-  std::unique_ptr<const art::DexFile> dex_file(dex_file_loader.Open(map_name,
-                                                                    checksum,
-                                                                    std::move(map),
+  art::ArtDexFileLoader dex_file_loader(std::move(map), map_name);
+  std::unique_ptr<const art::DexFile> dex_file(dex_file_loader.Open(checksum,
                                                                     /*verify=*/true,
                                                                     /*verify_checksum=*/true,
                                                                     &error_msg));
@@ -927,40 +925,42 @@
   } else if (count_ptr == nullptr || classes == nullptr) {
     return ERR(NULL_POINTER);
   }
-  art::JNIEnvExt* jnienv = self->GetJniEnv();
-  if (loader == nullptr ||
-      jnienv->IsInstanceOf(loader, art::WellKnownClasses::java_lang_BootClassLoader)) {
+  std::vector<const art::DexFile*> dex_files_storage;
+  const std::vector<const art::DexFile*>* dex_files = nullptr;
+  if (loader == nullptr) {
     // We can just get the dex files directly for the boot class path.
-    return CopyClassDescriptors(env,
-                                art::Runtime::Current()->GetClassLinker()->GetBootClassPath(),
-                                count_ptr,
-                                classes);
+    dex_files = &art::Runtime::Current()->GetClassLinker()->GetBootClassPath();
+  } else {
+    art::ScopedObjectAccess soa(self);
+    art::StackHandleScope<1> hs(self);
+    art::Handle<art::mirror::ClassLoader> class_loader(
+        hs.NewHandle(soa.Decode<art::mirror::ClassLoader>(loader)));
+    if (class_loader->InstanceOf(art::WellKnownClasses::java_lang_BootClassLoader.Get())) {
+      // We can just get the dex files directly for the boot class path.
+      dex_files = &art::Runtime::Current()->GetClassLinker()->GetBootClassPath();
+    } else if (!class_loader->InstanceOf(art::WellKnownClasses::java_lang_ClassLoader.Get())) {
+      return ERR(ILLEGAL_ARGUMENT);
+    } else if (!class_loader->InstanceOf(
+          art::WellKnownClasses::dalvik_system_BaseDexClassLoader.Get())) {
+      JVMTI_LOG(ERROR, env) << "GetClassLoaderClassDescriptors is only implemented for "
+                            << "BootClassPath and dalvik.system.BaseDexClassLoader class loaders";
+      // TODO Possibly return OK With no classes would  be better since these ones cannot have any
+      // real classes associated with them.
+      return ERR(NOT_IMPLEMENTED);
+    } else {
+      art::VisitClassLoaderDexFiles(
+          self,
+          class_loader,
+          [&](const art::DexFile* dex_file) {
+            dex_files_storage.push_back(dex_file);
+            return true;  // Continue with other dex files.
+          });
+      dex_files = &dex_files_storage;
+    }
   }
-  if (!jnienv->IsInstanceOf(loader, art::WellKnownClasses::java_lang_ClassLoader)) {
-    return ERR(ILLEGAL_ARGUMENT);
-  } else if (!jnienv->IsInstanceOf(loader,
-                                   art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
-    JVMTI_LOG(ERROR, env) << "GetClassLoaderClassDescriptors is only implemented for "
-                          << "BootClassPath and dalvik.system.BaseDexClassLoader class loaders";
-    // TODO Possibly return OK With no classes would  be better since these ones cannot have any
-    // real classes associated with them.
-    return ERR(NOT_IMPLEMENTED);
-  }
-
-  art::ScopedObjectAccess soa(self);
-  art::StackHandleScope<1> hs(self);
-  art::Handle<art::mirror::ClassLoader> class_loader(
-      hs.NewHandle(soa.Decode<art::mirror::ClassLoader>(loader)));
-  std::vector<const art::DexFile*> dex_files;
-  art::VisitClassLoaderDexFiles(
-      soa,
-      class_loader,
-      [&](const art::DexFile* dex_file) {
-        dex_files.push_back(dex_file);
-        return true;  // Continue with other dex files.
-      });
   // We hold the loader so the dex files won't go away until after this call at worst.
-  return CopyClassDescriptors(env, dex_files, count_ptr, classes);
+  DCHECK(dex_files != nullptr);
+  return CopyClassDescriptors(env, *dex_files, count_ptr, classes);
 }
 
 jvmtiError ClassUtil::GetClassLoaderClasses(jvmtiEnv* env,
@@ -973,19 +973,17 @@
     return ERR(NULL_POINTER);
   }
   art::Thread* self = art::Thread::Current();
-  if (!self->GetJniEnv()->IsInstanceOf(initiating_loader,
-                                       art::WellKnownClasses::java_lang_ClassLoader)) {
-    return ERR(ILLEGAL_ARGUMENT);
-  }
-  if (self->GetJniEnv()->IsInstanceOf(initiating_loader,
-                                      art::WellKnownClasses::java_lang_BootClassLoader)) {
-    // Need to use null for the BootClassLoader.
-    initiating_loader = nullptr;
-  }
-
   art::ScopedObjectAccess soa(self);
   art::ObjPtr<art::mirror::ClassLoader> class_loader =
       soa.Decode<art::mirror::ClassLoader>(initiating_loader);
+  if (class_loader == nullptr) {
+    // Keep null, meaning the boot class loader.
+  } else if (!class_loader->InstanceOf(art::WellKnownClasses::java_lang_ClassLoader.Get())) {
+    return ERR(ILLEGAL_ARGUMENT);
+  } else if (class_loader->InstanceOf(art::WellKnownClasses::java_lang_BootClassLoader.Get())) {
+    // Need to use null for the BootClassLoader.
+    class_loader = nullptr;
+  }
 
   art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker();
 
diff --git a/openjdkjvmti/ti_class_definition.cc b/openjdkjvmti/ti_class_definition.cc
index f1f5593..a9e7b31 100644
--- a/openjdkjvmti/ti_class_definition.cc
+++ b/openjdkjvmti/ti_class_definition.cc
@@ -172,10 +172,8 @@
   if (dex_file.IsCompactDexFile()) {
     std::string error_msg;
     std::vector<std::unique_ptr<const art::DexFile>> dex_files;
-    const art::ArtDexFileLoader dex_file_loader;
-    if (!dex_file_loader.Open(dex_file.GetLocation().c_str(),
-                              dex_file.GetLocation().c_str(),
-                              /* verify= */ false,
+    art::ArtDexFileLoader dex_file_loader(dex_file.GetLocation());
+    if (!dex_file_loader.Open(/* verify= */ false,
                               /* verify_checksum= */ false,
                               &error_msg,
                               &dex_files)) {
diff --git a/openjdkjvmti/ti_class_loader-inl.h b/openjdkjvmti/ti_class_loader-inl.h
index 29ea684..f6b0126 100644
--- a/openjdkjvmti/ti_class_loader-inl.h
+++ b/openjdkjvmti/ti_class_loader-inl.h
@@ -48,8 +48,8 @@
                                                    art::Handle<art::mirror::ClassLoader> loader,
                                                    const Visitor& visitor) {
   art::StackHandleScope<1> hs(self);
-  art::ArtField* element_dex_file_field = art::jni::DecodeArtField(
-      art::WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+  art::ArtField* element_dex_file_field =
+      art::WellKnownClasses::dalvik_system_DexPathList__Element_dexFile;
 
   art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(
       hs.NewHandle(GetDexElementList(self, loader)));
diff --git a/openjdkjvmti/ti_class_loader.cc b/openjdkjvmti/ti_class_loader.cc
index d0a6634..c117937 100644
--- a/openjdkjvmti/ti_class_loader.cc
+++ b/openjdkjvmti/ti_class_loader.cc
@@ -57,16 +57,17 @@
 #include "object_lock.h"
 #include "runtime.h"
 #include "transform.h"
+#include "well_known_classes-inl.h"
 
 namespace openjdkjvmti {
 
 bool ClassLoaderHelper::AddToClassLoader(art::Thread* self,
                                          art::Handle<art::mirror::ClassLoader> loader,
                                          const art::DexFile* dex_file) {
-  art::ScopedObjectAccessUnchecked soa(self);
   art::StackHandleScope<3> hs(self);
-  if (art::ClassLinker::IsBootClassLoader(soa, loader.Get())) {
-    art::Runtime::Current()->GetClassLinker()->AppendToBootClassPath(self, dex_file);
+  if (art::ClassLinker::IsBootClassLoader(loader.Get())) {
+    art::Runtime::Current()->AppendToBootClassPath(
+        dex_file->GetLocation(), dex_file->GetLocation(), {dex_file});
     return true;
   }
   art::Handle<art::mirror::Object> java_dex_file_obj(
@@ -139,15 +140,14 @@
     art::Handle<art::mirror::ClassLoader> loader) {
   art::StackHandleScope<4> hs(self);
 
-  art::Handle<art::mirror::Class>
-      base_dex_loader_class(hs.NewHandle(self->DecodeJObject(
-          art::WellKnownClasses::dalvik_system_BaseDexClassLoader)->AsClass()));
+  art::Handle<art::mirror::Class> base_dex_loader_class =
+      hs.NewHandle(art::WellKnownClasses::dalvik_system_BaseDexClassLoader.Get());
 
   // Get all the ArtFields so we can look in the BaseDexClassLoader
-  art::ArtField* path_list_field = art::jni::DecodeArtField(
-      art::WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList);
+  art::ArtField* path_list_field =
+      art::WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList;
   art::ArtField* dex_path_list_element_field =
-      art::jni::DecodeArtField(art::WellKnownClasses::dalvik_system_DexPathList_dexElements);
+      art::WellKnownClasses::dalvik_system_DexPathList_dexElements;
 
   // Check if loader is a BaseDexClassLoader
   art::Handle<art::mirror::Class> loader_class(hs.NewHandle(loader->GetClass()));
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index 10ea43a..02dc9f1 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -398,8 +398,7 @@
 
   // These require index-ids and debuggable to function
   art::Runtime* runtime = art::Runtime::Current();
-  if (runtime->GetJniIdType() == art::JniIdType::kIndices &&
-      (runtime->GetInstrumentation()->IsForcedInterpretOnly() || runtime->IsJavaDebuggable())) {
+  if (runtime->GetJniIdType() == art::JniIdType::kIndices && IsFullJvmtiAvailable()) {
     // IsStructurallyModifiableClass
     error = add_extension(
         reinterpret_cast<jvmtiExtensionFunction>(Redefiner::IsStructurallyModifiableClass),
@@ -703,8 +702,7 @@
     return error;
   }
   art::Runtime* runtime = art::Runtime::Current();
-  if (runtime->GetJniIdType() == art::JniIdType::kIndices &&
-      (runtime->GetInstrumentation()->IsForcedInterpretOnly() || runtime->IsJavaDebuggable())) {
+  if (runtime->GetJniIdType() == art::JniIdType::kIndices && IsFullJvmtiAvailable()) {
     error = add_extension(
         ArtJvmtiEvent::kStructuralDexFileLoadHook,
         "com.android.art.class.structural_dex_file_load_hook",
diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
index 2a1d442..01864cd3 100644
--- a/openjdkjvmti/ti_heap.cc
+++ b/openjdkjvmti/ti_heap.cc
@@ -1851,7 +1851,9 @@
     const ObjectMap& map_;
   };
   ReplaceWeaksVisitor rwv(map);
-  art::Runtime::Current()->SweepSystemWeaks(&rwv);
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->SweepSystemWeaks(&rwv);
+  runtime->GetThreadList()->SweepInterpreterCaches(&rwv);
   // Re-add the object tags. At this point all weak-references to the old_obj_ptr are gone.
   event_handler->ForEachEnv(self, [&](ArtJvmTiEnv* env) {
     // Cannot have REQUIRES(art::Locks::mutator_lock_) since ForEachEnv doesn't require it.
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 15cb6de..aafca47 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -89,7 +89,7 @@
 #include "jni/jni_id_manager.h"
 #include "jvmti.h"
 #include "jvmti_allocator.h"
-#include "linear_alloc.h"
+#include "linear_alloc-inl.h"
 #include "mirror/array-alloc-inl.h"
 #include "mirror/array.h"
 #include "mirror/class-alloc-inl.h"
@@ -130,7 +130,7 @@
 #include "transform.h"
 #include "verifier/class_verifier.h"
 #include "verifier/verifier_enums.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 #include "write_barrier.h"
 
 namespace openjdkjvmti {
@@ -285,8 +285,7 @@
       art::Thread* thread,
       art::LinearAlloc* allocator,
       const std::unordered_set<art::ArtMethod*>& obsoleted_methods,
-      ObsoleteMap* obsolete_maps)
-        REQUIRES(art::Locks::mutator_lock_) {
+      ObsoleteMap* obsolete_maps) REQUIRES(art::Locks::mutator_lock_) {
     ObsoleteMethodStackVisitor visitor(thread,
                                        allocator,
                                        obsoleted_methods,
@@ -310,7 +309,9 @@
         art::ClassLinker* cl = runtime->GetClassLinker();
         auto ptr_size = cl->GetImagePointerSize();
         const size_t method_size = art::ArtMethod::Size(ptr_size);
-        auto* method_storage = allocator_->Alloc(art::Thread::Current(), method_size);
+        auto* method_storage = allocator_->Alloc(art::Thread::Current(),
+                                                 method_size,
+                                                 art::LinearAllocKind::kArtMethod);
         CHECK(method_storage != nullptr) << "Unable to allocate storage for obsolete version of '"
                                          << old_method->PrettyMethod() << "'";
         new_obsolete_method = new (method_storage) art::ArtMethod();
@@ -454,8 +455,7 @@
     }
     // Check Thread specifically since it's not a root but too many things reach into it with Unsafe
     // too allow structural redefinition.
-    if (klass->IsAssignableFrom(
-            self->DecodeJObject(art::WellKnownClasses::java_lang_Thread)->AsClass())) {
+    if (klass->IsAssignableFrom(art::WellKnownClasses::java_lang_Thread.Get())) {
       *error_msg =
           "java.lang.Thread has fields accessed using sun.misc.unsafe directly. It is not "
           "safe to structurally redefine it.";
@@ -512,8 +512,15 @@
 art::MemMap Redefiner::MoveDataToMemMap(const std::string& original_location,
                                         art::ArrayRef<const unsigned char> data,
                                         std::string* error_msg) {
+  std::string modified_location = StringPrintf("%s-transformed", original_location.c_str());
+  // A dangling multi-dex location appended to bootclasspath can cause inaccuracy in oat file
+  // validation. For simplicity, just convert it to a normal location.
+  size_t pos = modified_location.find(art::DexFileLoader::kMultiDexSeparator);
+  if (pos != std::string::npos) {
+    modified_location[pos] = '-';
+  }
   art::MemMap map = art::MemMap::MapAnonymous(
-      StringPrintf("%s-transformed", original_location.c_str()).c_str(),
+      modified_location.c_str(),
       data.size(),
       PROT_READ|PROT_WRITE,
       /*low_4gb=*/ false,
@@ -545,6 +552,13 @@
   if (driver_ != nullptr && lock_acquired_) {
     GetMirrorClass()->MonitorExit(driver_->self_);
   }
+  if (art::kIsDebugBuild) {
+    if (dex_file_ != nullptr) {
+      art::Thread* self = art::Thread::Current();
+      art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker();
+      CHECK(!cl->IsDexFileRegistered(self, *dex_file_));
+    }
+  }
 }
 
 template<RedefinitionType kType>
@@ -711,10 +725,8 @@
   }
   std::string name = map.GetName();
   uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map.Begin())->checksum_;
-  const art::ArtDexFileLoader dex_file_loader;
-  std::unique_ptr<const art::DexFile> dex_file(dex_file_loader.Open(name,
-                                                                    checksum,
-                                                                    std::move(map),
+  art::ArtDexFileLoader dex_file_loader(std::move(map), name);
+  std::unique_ptr<const art::DexFile> dex_file(dex_file_loader.Open(checksum,
                                                                     /*verify=*/true,
                                                                     /*verify_checksum=*/true,
                                                                     error_msg_));
@@ -1217,6 +1229,8 @@
     actually_structural_(redefinitions_->size(), false),
     initial_structural_(redefinitions_->size(), false) {}
 
+  ~RedefinitionDataHolder() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
   bool IsNull() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
     return arr_.IsNull();
   }
@@ -1423,8 +1437,9 @@
 
   RedefinitionDataIter(const RedefinitionDataIter&) = default;
   RedefinitionDataIter(RedefinitionDataIter&&) = default;
-  RedefinitionDataIter& operator=(const RedefinitionDataIter&) = default;
-  RedefinitionDataIter& operator=(RedefinitionDataIter&&) = default;
+  // Assignments are deleted because holder_ is a reference.
+  RedefinitionDataIter& operator=(const RedefinitionDataIter&) = delete;
+  RedefinitionDataIter& operator=(RedefinitionDataIter&&) = delete;
 
   bool operator==(const RedefinitionDataIter& other) const
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
@@ -1613,6 +1628,24 @@
   return RedefinitionDataIter(Length(), *this);
 }
 
+RedefinitionDataHolder::~RedefinitionDataHolder() {
+  art::Thread* self = art::Thread::Current();
+  art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker();
+  for (RedefinitionDataIter data = begin(); data != end(); ++data) {
+    art::ObjPtr<art::mirror::DexCache> dex_cache = data.GetNewDexCache();
+    // When redefinition fails, the dex file will be deleted in the
+    // `ClassRedefinition` destructor. To avoid having a heap `DexCache` pointing
+    // to a dangling pointer, we clear the entries of those dex caches that are
+    // not registered in the runtime.
+    if (dex_cache != nullptr &&
+        dex_cache->GetDexFile() != nullptr &&
+        !cl->IsDexFileRegistered(self, *dex_cache->GetDexFile())) {
+      dex_cache->ResetNativeArrays();
+      dex_cache->SetDexFile(nullptr);
+    }
+  }
+}
+
 bool Redefiner::ClassRedefinition::CheckVerification(const RedefinitionDataIter& iter) {
   DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
   art::StackHandleScope<3> hs(driver_->self_);
@@ -1651,10 +1684,12 @@
   art::MutableHandle<art::mirror::LongArray> old_cookie(
       hs.NewHandle<art::mirror::LongArray>(nullptr));
   bool has_older_cookie = false;
-  // See if we already have a cookie that a previous redefinition got from the same classloader.
+  // See if we already have a cookie that a previous redefinition got from the same classloader
+  // and the same JavaDex file.
   for (auto old_data = cur_data->GetHolder().begin(); old_data != *cur_data; ++old_data) {
-    if (old_data.GetSourceClassLoader() == source_class_loader.Get()) {
-      // Since every instance of this classloader should have the same cookie associated with it we
+    if (old_data.GetSourceClassLoader() == source_class_loader.Get() &&
+        old_data.GetJavaDexFile() == dex_file_obj.Get()) {
+      // Since every instance of this JavaDex file should have the same cookie associated with it we
       // can stop looking here.
       has_older_cookie = true;
       old_cookie.Assign(old_data.GetNewDexFileCookie());
@@ -1679,12 +1714,13 @@
 
   // Save the cookie.
   cur_data->SetNewDexFileCookie(new_cookie.Get());
-  // If there are other copies of this same classloader we need to make sure that we all have the
-  // same cookie.
+  // If there are other copies of the same classloader and the same JavaDex file we need to
+  // make sure that we all have the same cookie.
   if (has_older_cookie) {
     for (auto old_data = cur_data->GetHolder().begin(); old_data != *cur_data; ++old_data) {
       // We will let the GC take care of the cookie we allocated for this one.
-      if (old_data.GetSourceClassLoader() == source_class_loader.Get()) {
+      if (old_data.GetSourceClassLoader() == source_class_loader.Get() &&
+          old_data.GetJavaDexFile() == dex_file_obj.Get()) {
         old_data.SetNewDexFileCookie(new_cookie.Get());
       }
     }
@@ -1802,13 +1838,12 @@
 
 bool Redefiner::ClassRedefinition::FinishRemainingCommonAllocations(
     /*out*/RedefinitionDataIter* cur_data) {
-  art::ScopedObjectAccessUnchecked soa(driver_->self_);
   art::StackHandleScope<2> hs(driver_->self_);
   cur_data->SetMirrorClass(GetMirrorClass());
   // This shouldn't allocate
   art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader()));
   // The bootclasspath is handled specially so it doesn't have a j.l.DexFile.
-  if (!art::ClassLinker::IsBootClassLoader(soa, loader.Get())) {
+  if (!art::ClassLinker::IsBootClassLoader(loader.Get())) {
     cur_data->SetSourceClassLoader(loader.Get());
     art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(
         ClassLoaderHelper::FindSourceDexFileObject(driver_->self_, loader)));
@@ -2218,6 +2253,11 @@
 }
 
 void Redefiner::ClassRedefinition::ReleaseDexFile() {
+  if (art::kIsDebugBuild) {
+    art::Thread* self = art::Thread::Current();
+    art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker();
+    CHECK(cl->IsDexFileRegistered(self, *dex_file_));
+  }
   dex_file_.release();  // NOLINT b/117926937
 }
 
@@ -2477,7 +2517,9 @@
     art::ClassLinker* cl = runtime_->GetClassLinker();
     if (data.GetSourceClassLoader() == nullptr) {
       // AppendToBootClassPath includes dex file registration.
-      cl->AppendToBootClassPath(&data.GetRedefinition().GetDexFile(), data.GetNewDexCache());
+      const art::DexFile& dex_file = data.GetRedefinition().GetDexFile();
+      runtime_->AppendToBootClassPath(
+          dex_file.GetLocation(), dex_file.GetLocation(), {{&dex_file, data.GetNewDexCache()}});
     } else {
       cl->RegisterExistingDexCache(data.GetNewDexCache(), data.GetSourceClassLoader());
     }
@@ -2910,6 +2952,27 @@
   // be undone. This replaces the mirror::Class in 'holder' as well. It's magic!
   HeapExtensions::ReplaceReferences(driver_->self_, map);
 
+  // Undo the replacement of old_class with new_class for the methods / fields on the old_class.
+  // It is hard to ensure that we don't replace the declaring class of the old class field / methods
+  // isn't impacted by ReplaceReferences. It is just simpler to undo the replacement here.
+  std::for_each(
+      old_classes_vec.cbegin(),
+      old_classes_vec.cend(),
+      [](art::ObjPtr<art::mirror::Class> orig) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+        orig->VisitMethods(
+            [&](art::ArtMethod* method) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+              if (method->IsCopied()) {
+                // Copied methods have interfaces as their declaring class.
+                return;
+              }
+              method->SetDeclaringClass(orig);
+            },
+            art::kRuntimePointerSize);
+        orig->VisitFields([&](art::ArtField* field) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+          field->SetDeclaringClass(orig);
+        });
+      });
+
   // Save the old class so that the JIT gc doesn't get confused by it being collected before the
   // jit code. This is also needed to keep the dex-caches of any obsolete methods live.
   for (auto [new_class, old_class] :
@@ -3081,10 +3144,14 @@
     // First save the old values of the 2 arrays that make up the obsolete methods maps. Then
     // allocate the 2 arrays that make up the obsolete methods map. Since the contents of the arrays
     // are only modified when all threads (other than the modifying one) are suspended we don't need
-    // to worry about missing the unsyncronized writes to the array. We do synchronize when setting
+    // to worry about missing the unsynchronized writes to the array. We do synchronize when setting
     // it however, since that can happen at any time.
     cur_data->SetOldObsoleteMethods(ext->GetObsoleteMethods());
     cur_data->SetOldDexCaches(ext->GetObsoleteDexCaches());
+    // FIXME: The `ClassExt::ExtendObsoleteArrays()` is non-atomic and does not ensure proper
+    // memory visibility, so it can race with `ArtMethod::GetObsoleteDexCache()`.
+    // We should allocate the new arrays here but record it in the redefinition data and set the
+    // new arrays in `ClassExt` later with all other threads suspended.
     if (!art::mirror::ClassExt::ExtendObsoleteArrays(
             ext, driver_->self_, klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size())) {
       // OOM. Clear exception and return error.
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index 85b1070..cdd6627 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -126,7 +126,7 @@
     ~ClassRedefinition() NO_THREAD_SAFETY_ANALYSIS;
 
     // Move assignment so we can sort these in a vector.
-    ClassRedefinition& operator=(ClassRedefinition&& other) {
+    ClassRedefinition& operator=(ClassRedefinition&& other) noexcept {
       driver_ = other.driver_;
       klass_ = other.klass_;
       dex_file_ = std::move(other.dex_file_);
@@ -138,7 +138,7 @@
     }
 
     // Move constructor so we can put these into a vector.
-    ClassRedefinition(ClassRedefinition&& other)
+    ClassRedefinition(ClassRedefinition&& other) noexcept
         : driver_(other.driver_),
           klass_(other.klass_),
           dex_file_(std::move(other.dex_file_)),
diff --git a/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc
index 526836e..2e98e0d 100644
--- a/openjdkjvmti/ti_search.cc
+++ b/openjdkjvmti/ti_search.cc
@@ -60,7 +60,7 @@
 #include "thread_list.h"
 #include "ti_logging.h"
 #include "ti_phase.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace openjdkjvmti {
 
@@ -235,10 +235,8 @@
 
   std::string error_msg;
   std::vector<std::unique_ptr<const art::DexFile>> dex_files;
-  const art::ArtDexFileLoader dex_file_loader;
-  if (!dex_file_loader.Open(segment,
-                            segment,
-                            /* verify= */ true,
+  art::ArtDexFileLoader dex_file_loader(segment);
+  if (!dex_file_loader.Open(/* verify= */ true,
                             /* verify_checksum= */ true,
                             &error_msg,
                             &dex_files)) {
@@ -247,12 +245,8 @@
     return ERR(ILLEGAL_ARGUMENT);
   }
 
-  art::ScopedObjectAccess soa(art::Thread::Current());
-  for (std::unique_ptr<const art::DexFile>& dex_file : dex_files) {
-    current->GetClassLinker()->AppendToBootClassPath(art::Thread::Current(), dex_file.release());
-  }
-
-  return ERR(NONE);
+  current->AddExtraBootDexFiles(segment, segment, std::move(dex_files));
+  return OK;
 }
 
 jvmtiError SearchUtil::AddToDexClassLoaderInMemory(jvmtiEnv* jvmti_env,
@@ -343,33 +337,33 @@
   // exceptions are swallowed.
 
   art::Thread* self = art::Thread::Current();
-  JNIEnv* env = self->GetJniEnv();
-  if (!env->IsInstanceOf(classloader, art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
+  art::ScopedObjectAccess soa(self);
+  art::StackHandleScope<2u> hs(self);
+  art::Handle<art::mirror::ClassLoader> class_loader =
+      hs.NewHandle(soa.Decode<art::mirror::ClassLoader>(classloader));
+  if (!class_loader->InstanceOf(art::WellKnownClasses::dalvik_system_BaseDexClassLoader.Get())) {
     JVMTI_LOG(ERROR, jvmti_env) << "Unable to add " << segment << " to non BaseDexClassLoader!";
     return ERR(CLASS_LOADER_UNSUPPORTED);
   }
 
-  jmethodID add_dex_path_id = env->GetMethodID(
-      art::WellKnownClasses::dalvik_system_BaseDexClassLoader,
-      "addDexPath",
-      "(Ljava/lang/String;)V");
+  art::ArtMethod* add_dex_path_id =
+      art::WellKnownClasses::dalvik_system_BaseDexClassLoader->FindClassMethod(
+          "addDexPath", "(Ljava/lang/String;)V", art::kRuntimePointerSize);
   if (add_dex_path_id == nullptr) {
     return ERR(INTERNAL);
   }
 
-  ScopedLocalRef<jstring> dex_path(env, env->NewStringUTF(segment));
-  if (dex_path.get() == nullptr) {
+  art::Handle<art::mirror::String> dex_path =
+      hs.NewHandle(art::mirror::String::AllocFromModifiedUtf8(self, segment));
+  if (dex_path == nullptr) {
     return ERR(INTERNAL);
   }
-  env->CallVoidMethod(classloader, add_dex_path_id, dex_path.get());
 
-  if (env->ExceptionCheck()) {
-    {
-      art::ScopedObjectAccess soa(self);
-      JVMTI_LOG(ERROR, jvmti_env) << "Failed to add " << segment << " to classloader. Error was "
-                                  << self->GetException()->Dump();
-    }
-    env->ExceptionClear();
+  add_dex_path_id->InvokeVirtual<'V', 'L'>(self, class_loader.Get(), dex_path.Get());
+  if (self->IsExceptionPending()) {
+    JVMTI_LOG(ERROR, jvmti_env) << "Failed to add " << segment << " to classloader. Error was "
+                                << self->GetException()->Dump();
+    self->ClearException();
     return ERR(ILLEGAL_ARGUMENT);
   }
   return OK;
@@ -396,10 +390,13 @@
     return ERR(INTERNAL);
   }
 
-  art::Thread* self = art::Thread::Current();
-  JNIEnv* env = self->GetJniEnv();
-  if (!env->IsInstanceOf(loader, art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
-    return ERR(INTERNAL);
+  {
+    art::ScopedObjectAccess soa(art::Thread::Current());
+    art::ObjPtr<art::mirror::ClassLoader> class_loader =
+        soa.Decode<art::mirror::ClassLoader>(loader);
+    if (!class_loader->InstanceOf(art::WellKnownClasses::dalvik_system_BaseDexClassLoader.Get())) {
+      return ERR(INTERNAL);
+    }
   }
 
   return AddToDexClassLoader(jvmti_env, loader, segment);
diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
index 38257f1..8ee4adb 100644
--- a/openjdkjvmti/ti_stack.cc
+++ b/openjdkjvmti/ti_stack.cc
@@ -41,7 +41,6 @@
 #include "android-base/thread_annotations.h"
 #include "arch/context.h"
 #include "art_field-inl.h"
-#include "art_method-inl.h"
 #include "art_jvmti.h"
 #include "art_method-inl.h"
 #include "barrier.h"
@@ -74,15 +73,14 @@
 #include "scoped_thread_state_change-inl.h"
 #include "scoped_thread_state_change.h"
 #include "stack.h"
+#include "thread-current-inl.h"
 #include "thread.h"
+#include "thread_list.h"
+#include "thread_pool.h"
 #include "thread_state.h"
 #include "ti_logging.h"
 #include "ti_thread.h"
-#include "thread-current-inl.h"
-#include "thread_list.h"
-#include "thread_pool.h"
-#include "ti_thread.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace openjdkjvmti {
 
@@ -578,10 +576,11 @@
     if (thread_list[i] == nullptr) {
       return ERR(INVALID_THREAD);
     }
-    if (!soa.Env()->IsInstanceOf(thread_list[i], art::WellKnownClasses::java_lang_Thread)) {
+    art::ObjPtr<art::mirror::Object> thread = soa.Decode<art::mirror::Object>(thread_list[i]);
+    if (!thread->InstanceOf(art::WellKnownClasses::java_lang_Thread.Get())) {
       return ERR(INVALID_THREAD);
     }
-    data.handles.push_back(hs.NewHandle(soa.Decode<art::mirror::Object>(thread_list[i])));
+    data.handles.push_back(hs.NewHandle(thread));
   }
 
   RunCheckpointAndWait(&data, static_cast<size_t>(max_frame_count));
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index f31759e..b5bc35e 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -59,7 +59,7 @@
 #include "thread-current-inl.h"
 #include "thread_list.h"
 #include "ti_phase.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace openjdkjvmti {
 
@@ -131,6 +131,7 @@
         if (name != "JDWP" && name != "Signal Catcher" && name != "perfetto_hprof_listener" &&
             name != art::metrics::MetricsReporter::kBackgroundThreadName &&
             !android::base::StartsWith(name, "Jit thread pool") &&
+            !android::base::StartsWith(name, "Heap thread pool worker thread") &&
             !android::base::StartsWith(name, "Runtime worker thread")) {
           LOG(FATAL) << "Unexpected thread before start: " << name << " id: "
                      << self->GetThreadId();
@@ -173,12 +174,7 @@
 
 
 static void WaitForSystemDaemonStart(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
-  {
-    art::ScopedThreadStateChange strc(self, art::ThreadState::kNative);
-    JNIEnv* jni = self->GetJniEnv();
-    jni->CallStaticVoidMethod(art::WellKnownClasses::java_lang_Daemons,
-                              art::WellKnownClasses::java_lang_Daemons_waitForDaemonStart);
-  }
+  art::WellKnownClasses::java_lang_Daemons_waitForDaemonStart->InvokeStatic<'V'>(self);
   if (self->IsExceptionPending()) {
     LOG(WARNING) << "Exception occurred when waiting for system daemons to start: "
                  << self->GetException()->Dump();
@@ -191,8 +187,7 @@
   gThreadCallback.started = true;
   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);
+  art::ObjPtr<art::mirror::Class> thread_class = art::WellKnownClasses::java_lang_Thread.Get();
   CHECK(thread_class != nullptr);
   context_class_loader_ = thread_class->FindDeclaredInstanceField("contextClassLoader",
                                                                   "Ljava/lang/ClassLoader;");
@@ -235,7 +230,9 @@
   if (thread == nullptr) {
     *thr = art::Thread::Current();
     return true;
-  } else if (!soa.Env()->IsInstanceOf(thread, art::WellKnownClasses::java_lang_Thread)) {
+  }
+  art::ObjPtr<art::mirror::Object> othread = soa.Decode<art::mirror::Object>(thread);
+  if (!othread->InstanceOf(art::WellKnownClasses::java_lang_Thread.Get())) {
     *err = ERR(INVALID_THREAD);
     return false;
   } else {
@@ -296,7 +293,7 @@
 
     // ThreadGroup.
     if (peer != nullptr) {
-      art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group);
+      art::ArtField* f = art::WellKnownClasses::java_lang_Thread_group;
       CHECK(f != nullptr);
       art::ObjPtr<art::mirror::Object> group = f->GetObject(peer);
       info_ptr->thread_group = group == nullptr
@@ -321,7 +318,7 @@
 
     // Name.
     {
-      art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_name);
+      art::ArtField* f = art::WellKnownClasses::java_lang_Thread_name;
       CHECK(f != nullptr);
       art::ObjPtr<art::mirror::Object> name = f->GetObject(peer);
       std::string name_cpp;
@@ -342,21 +339,21 @@
 
     // Priority.
     {
-      art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_priority);
+      art::ArtField* f = art::WellKnownClasses::java_lang_Thread_priority;
       CHECK(f != nullptr);
       info_ptr->priority = static_cast<jint>(f->GetInt(peer));
     }
 
     // Daemon.
     {
-      art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_daemon);
+      art::ArtField* f = art::WellKnownClasses::java_lang_Thread_daemon;
       CHECK(f != nullptr);
       info_ptr->is_daemon = f->GetBoolean(peer) == 0 ? JNI_FALSE : JNI_TRUE;
     }
 
     // ThreadGroup.
     {
-      art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group);
+      art::ArtField* f = art::WellKnownClasses::java_lang_Thread_group;
       CHECK(f != nullptr);
       art::ObjPtr<art::mirror::Object> group = f->GetObject(peer);
       info_ptr->thread_group = group == nullptr
@@ -616,8 +613,7 @@
 
   // Need to read the Java "started" field to know whether this is starting or terminated.
   art::Handle<art::mirror::Object> peer(hs.NewHandle(soa.Decode<art::mirror::Object>(thread)));
-  art::ObjPtr<art::mirror::Class> thread_klass =
-      soa.Decode<art::mirror::Class>(art::WellKnownClasses::java_lang_Thread);
+  art::ObjPtr<art::mirror::Class> thread_klass = art::WellKnownClasses::java_lang_Thread.Get();
   if (!thread_klass->IsAssignableFrom(peer->GetClass())) {
     return ERR(INVALID_THREAD);
   }
@@ -814,46 +810,52 @@
   if (priority < JVMTI_THREAD_MIN_PRIORITY || priority > JVMTI_THREAD_MAX_PRIORITY) {
     return ERR(INVALID_PRIORITY);
   }
-  JNIEnv* env = art::Thread::Current()->GetJniEnv();
-  if (thread == nullptr || !env->IsInstanceOf(thread, art::WellKnownClasses::java_lang_Thread)) {
+  if (thread == nullptr) {
     return ERR(INVALID_THREAD);
   }
-  if (proc == nullptr) {
-    return ERR(NULL_POINTER);
-  }
-
+  art::Runtime* runtime = art::Runtime::Current();
+  art::Thread* self = art::Thread::Current();
+  std::unique_ptr<AgentData> data;
   {
-    art::Runtime* runtime = art::Runtime::Current();
-    art::MutexLock mu(art::Thread::Current(), *art::Locks::runtime_shutdown_lock_);
-    if (runtime->IsShuttingDownLocked()) {
-      // The runtime is shutting down so we cannot create new threads.
-      // TODO It's not fully clear from the spec what we should do here. We aren't yet in
-      // JVMTI_PHASE_DEAD so we cannot return ERR(WRONG_PHASE) but creating new threads is now
-      // impossible. Existing agents don't seem to generally do anything with this return value so
-      // it doesn't matter too much. We could do something like sending a fake ThreadStart event
-      // even though code is never actually run.
-      return ERR(INTERNAL);
+    art::ScopedObjectAccess soa(self);
+    art::ObjPtr<art::mirror::Object> othread = soa.Decode<art::mirror::Object>(thread);
+    if (!othread->InstanceOf(art::WellKnownClasses::java_lang_Thread.Get())) {
+      return ERR(INVALID_THREAD);
     }
-    runtime->StartThreadBirth();
-  }
+    if (proc == nullptr) {
+      return ERR(NULL_POINTER);
+    }
 
-  std::unique_ptr<AgentData> data(new AgentData);
-  data->arg = arg;
-  data->proc = proc;
-  // We need a global ref for Java objects, as local refs will be invalid.
-  data->thread = env->NewGlobalRef(thread);
-  data->java_vm = art::Runtime::Current()->GetJavaVM();
-  data->jvmti_env = jvmti_env;
-  data->priority = priority;
-  ScopedLocalRef<jstring> s(
-      env,
-      reinterpret_cast<jstring>(
-          env->GetObjectField(thread, art::WellKnownClasses::java_lang_Thread_name)));
-  if (s == nullptr) {
-    data->name = "JVMTI Agent Thread";
-  } else {
-    ScopedUtfChars name(env, s.get());
-    data->name = name.c_str();
+    {
+      art::MutexLock mu(soa.Self(), *art::Locks::runtime_shutdown_lock_);
+      if (runtime->IsShuttingDownLocked()) {
+        // The runtime is shutting down so we cannot create new threads.
+        // TODO It's not fully clear from the spec what we should do here. We aren't yet in
+        // JVMTI_PHASE_DEAD so we cannot return ERR(WRONG_PHASE) but creating new threads is now
+        // impossible. Existing agents don't seem to generally do anything with this return value so
+        // it doesn't matter too much. We could do something like sending a fake ThreadStart event
+        // even though code is never actually run.
+        return ERR(INTERNAL);
+      }
+      runtime->StartThreadBirth();
+    }
+
+    data.reset(new AgentData);
+    data->arg = arg;
+    data->proc = proc;
+    // We need a global ref for Java objects, as local refs will be invalid.
+    data->thread = runtime->GetJavaVM()->AddGlobalRef(soa.Self(), othread);
+    data->java_vm = runtime->GetJavaVM();
+    data->jvmti_env = jvmti_env;
+    data->priority = priority;
+    art::ObjPtr<art::mirror::Object> name =
+        art::WellKnownClasses::java_lang_Thread_name->GetObject(
+            soa.Decode<art::mirror::Object>(thread));
+    if (name == nullptr) {
+      data->name = "JVMTI Agent Thread";
+    } else {
+      data->name = name->AsString()->ToModifiedUtf8();
+    }
   }
 
   pthread_t pthread;
@@ -863,8 +865,7 @@
                                             reinterpret_cast<void*>(data.get()));
   if (pthread_create_result != 0) {
     // If the create succeeded the other thread will call EndThreadBirth.
-    art::Runtime* runtime = art::Runtime::Current();
-    art::MutexLock mu(art::Thread::Current(), *art::Locks::runtime_shutdown_lock_);
+    art::MutexLock mu(self, *art::Locks::runtime_shutdown_lock_);
     runtime->EndThreadBirth();
     return ERR(INTERNAL);
   }
diff --git a/openjdkjvmti/ti_threadgroup.cc b/openjdkjvmti/ti_threadgroup.cc
index bc912cf..120024e 100644
--- a/openjdkjvmti/ti_threadgroup.cc
+++ b/openjdkjvmti/ti_threadgroup.cc
@@ -47,7 +47,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "thread-current-inl.h"
 #include "thread_list.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace openjdkjvmti {
 
@@ -95,22 +95,21 @@
   }
 
   art::ScopedObjectAccess soa(art::Thread::Current());
-  if (soa.Env()->IsInstanceOf(group, art::WellKnownClasses::java_lang_ThreadGroup) == JNI_FALSE) {
+  art::StackHandleScope<2> hs(soa.Self());
+  art::Handle<art::mirror::Class> tg_class =
+      hs.NewHandle(art::WellKnownClasses::java_lang_ThreadGroup.Get());
+  art::Handle<art::mirror::Object> thread_group =
+      hs.NewHandle(soa.Decode<art::mirror::Object>(group));
+  if (!thread_group->InstanceOf(tg_class.Get())) {
     return ERR(INVALID_THREAD_GROUP);
   }
 
-  art::StackHandleScope<2> hs(soa.Self());
-  art::Handle<art::mirror::Class> tg_class(
-      hs.NewHandle(soa.Decode<art::mirror::Class>(art::WellKnownClasses::java_lang_ThreadGroup)));
-  art::Handle<art::mirror::Object> obj(hs.NewHandle(soa.Decode<art::mirror::Object>(group)));
-
   // Do the name first. It's the only thing that can fail.
   {
-    art::ArtField* name_field =
-        art::jni::DecodeArtField(art::WellKnownClasses::java_lang_ThreadGroup_name);
+    art::ArtField* name_field = art::WellKnownClasses::java_lang_ThreadGroup_name;
     CHECK(name_field != nullptr);
     art::ObjPtr<art::mirror::String> name_obj =
-        art::ObjPtr<art::mirror::String>::DownCast(name_field->GetObject(obj.Get()));
+        art::ObjPtr<art::mirror::String>::DownCast(name_field->GetObject(thread_group.Get()));
     std::string tmp_str;
     const char* tmp_cstr;
     if (name_obj == nullptr) {
@@ -129,10 +128,9 @@
 
   // Parent.
   {
-    art::ArtField* parent_field =
-        art::jni::DecodeArtField(art::WellKnownClasses::java_lang_ThreadGroup_parent);
+    art::ArtField* parent_field = art::WellKnownClasses::java_lang_ThreadGroup_parent;
     CHECK(parent_field != nullptr);
-    art::ObjPtr<art::mirror::Object> parent_group = parent_field->GetObject(obj.Get());
+    art::ObjPtr<art::mirror::Object> parent_group = parent_field->GetObject(thread_group.Get());
     info_ptr->parent = parent_group == nullptr
                            ? nullptr
                            : soa.AddLocalReference<jthreadGroup>(parent_group);
@@ -142,14 +140,14 @@
   {
     art::ArtField* prio_field = tg_class->FindDeclaredInstanceField("maxPriority", "I");
     CHECK(prio_field != nullptr);
-    info_ptr->max_priority = static_cast<jint>(prio_field->GetInt(obj.Get()));
+    info_ptr->max_priority = static_cast<jint>(prio_field->GetInt(thread_group.Get()));
   }
 
   // Daemon.
   {
     art::ArtField* daemon_field = tg_class->FindDeclaredInstanceField("daemon", "Z");
     CHECK(daemon_field != nullptr);
-    info_ptr->is_daemon = daemon_field->GetBoolean(obj.Get()) == 0 ? JNI_FALSE : JNI_TRUE;
+    info_ptr->is_daemon = daemon_field->GetBoolean(thread_group.Get()) == 0 ? JNI_FALSE : JNI_TRUE;
   }
 
   return ERR(NONE);
@@ -161,8 +159,7 @@
     REQUIRES_SHARED(art::Locks::mutator_lock_) {
   CHECK(desired_thread_group != nullptr);
 
-  art::ArtField* thread_group_field =
-      art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group);
+  art::ArtField* thread_group_field = art::WellKnownClasses::java_lang_Thread_group;
   DCHECK(thread_group_field != nullptr);
   art::ObjPtr<art::mirror::Object> group = thread_group_field->GetObject(peer);
   return (group == desired_thread_group.Get());
@@ -194,8 +191,7 @@
   CHECK(thread_group != nullptr);
 
   // Get the ThreadGroup[] "groups" out of this thread group...
-  art::ArtField* groups_field =
-      art::jni::DecodeArtField(art::WellKnownClasses::java_lang_ThreadGroup_groups);
+  art::ArtField* groups_field = art::WellKnownClasses::java_lang_ThreadGroup_groups;
   art::ObjPtr<art::mirror::Object> groups_array = groups_field->GetObject(thread_group.Get());
 
   if (groups_array == nullptr) {
@@ -225,14 +221,13 @@
   }
 
   art::ScopedObjectAccess soa(art::Thread::Current());
-
-  if (!soa.Env()->IsInstanceOf(group, art::WellKnownClasses::java_lang_ThreadGroup)) {
+  art::StackHandleScope<1> hs(soa.Self());
+  art::Handle<art::mirror::Object> thread_group =
+      hs.NewHandle(soa.Decode<art::mirror::Object>(group));
+  if (!thread_group->InstanceOf(art::WellKnownClasses::java_lang_ThreadGroup.Get())) {
     return ERR(INVALID_THREAD_GROUP);
   }
 
-  art::StackHandleScope<1> hs(soa.Self());
-  art::Handle<art::mirror::Object> thread_group = hs.NewHandle(
-      soa.Decode<art::mirror::Object>(group));
 
   art::ObjectLock<art::mirror::Object> thread_group_lock(soa.Self(), thread_group);
 
diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc
index 1f36da4..bccdcb1 100644
--- a/openjdkjvmti/transform.cc
+++ b/openjdkjvmti/transform.cc
@@ -29,14 +29,14 @@
  * questions.
  */
 
+#include "transform.h"
+
 #include <stddef.h>
 #include <sys/types.h>
 
 #include <unordered_map>
 #include <unordered_set>
 
-#include "transform.h"
-
 #include "art_method.h"
 #include "base/array_ref.h"
 #include "base/globals.h"
@@ -64,9 +64,8 @@
 #include "scoped_thread_state_change-inl.h"
 #include "stack.h"
 #include "thread_list.h"
-#include "ti_redefine.h"
 #include "ti_logging.h"
-#include "transform.h"
+#include "ti_redefine.h"
 
 namespace openjdkjvmti {
 
diff --git a/perfetto_hprof/Android.bp b/perfetto_hprof/Android.bp
index a81a4fa..8cfc7d4 100644
--- a/perfetto_hprof/Android.bp
+++ b/perfetto_hprof/Android.bp
@@ -50,6 +50,7 @@
     compile_multilib: "both",
 
     shared_libs: [
+        "libartpalette",
         "libbase",
         "liblog",
     ],
@@ -68,6 +69,12 @@
     header_libs: [
         "libnativehelper_header_only",
     ],
+    // FIXME: Workaround LTO build breakage
+    // http://b/241700157
+    lto: {
+        never: true,
+    },
+
 }
 
 art_cc_library {
@@ -81,6 +88,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
diff --git a/perfetto_hprof/perfetto_hprof.cc b/perfetto_hprof/perfetto_hprof.cc
index 669fb0c..906362a 100644
--- a/perfetto_hprof/perfetto_hprof.cc
+++ b/perfetto_hprof/perfetto_hprof.cc
@@ -18,9 +18,8 @@
 
 #include "perfetto_hprof.h"
 
-#include <android-base/logging.h>
-#include <base/fast_exit.h>
 #include <fcntl.h>
+#include <fnmatch.h>
 #include <inttypes.h>
 #include <sched.h>
 #include <signal.h>
@@ -36,6 +35,11 @@
 #include <optional>
 #include <type_traits>
 
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/properties.h"
+#include "base/fast_exit.h"
+#include "base/systrace.h"
 #include "gc/heap-visit-objects-inl.h"
 #include "gc/heap.h"
 #include "gc/scoped_gc_critical_section.h"
@@ -86,6 +90,8 @@
 
 static int requested_tracing_session_id = 0;
 static State g_state = State::kUninitialized;
+static bool g_oome_triggered = false;
+static uint32_t g_oome_sessions_pending = 0;
 
 // Pipe to signal from the signal handler into a worker thread that handles the
 // dump requests.
@@ -151,19 +157,52 @@
   return false;
 }
 
+uint64_t GetCurrentBootClockNs() {
+  struct timespec ts = {};
+  if (clock_gettime(CLOCK_BOOTTIME, &ts) != 0) {
+    LOG(FATAL) << "Failed to get boottime.";
+  }
+  return ts.tv_sec * 1000000000LL + ts.tv_nsec;
+}
+
+bool IsDebugBuild() {
+  std::string build_type = android::base::GetProperty("ro.build.type", "");
+  return !build_type.empty() && build_type != "user";
+}
+
+// Verifies the manifest restrictions are respected.
+// For regular heap dumps this is already handled by heapprofd.
+bool IsOomeHeapDumpAllowed(const perfetto::DataSourceConfig& ds_config) {
+  if (art::Runtime::Current()->IsJavaDebuggable() || IsDebugBuild()) {
+    return true;
+  }
+
+  if (ds_config.session_initiator() ==
+      perfetto::DataSourceConfig::SESSION_INITIATOR_TRUSTED_SYSTEM) {
+    return art::Runtime::Current()->IsProfileable() || art::Runtime::Current()->IsSystemServer();
+  } else {
+    return art::Runtime::Current()->IsProfileableFromShell();
+  }
+}
+
 class JavaHprofDataSource : public perfetto::DataSource<JavaHprofDataSource> {
  public:
   constexpr static perfetto::BufferExhaustedPolicy kBufferExhaustedPolicy =
     perfetto::BufferExhaustedPolicy::kStall;
+
+  explicit JavaHprofDataSource(bool is_oome_heap) : is_oome_heap_(is_oome_heap) {}
+
   void OnSetup(const SetupArgs& args) override {
-    uint64_t normalized_cfg_tracing_session_id =
-      args.config->tracing_session_id() % std::numeric_limits<int32_t>::max();
-    if (requested_tracing_session_id < 0) {
-      LOG(ERROR) << "invalid requested tracing session id " << requested_tracing_session_id;
-      return;
-    }
-    if (static_cast<uint64_t>(requested_tracing_session_id) != normalized_cfg_tracing_session_id) {
-      return;
+    if (!is_oome_heap_) {
+      uint64_t normalized_tracing_session_id =
+        args.config->tracing_session_id() % std::numeric_limits<int32_t>::max();
+      if (requested_tracing_session_id < 0) {
+        LOG(ERROR) << "invalid requested tracing session id " << requested_tracing_session_id;
+        return;
+      }
+      if (static_cast<uint64_t>(requested_tracing_session_id) != normalized_tracing_session_id) {
+        return;
+      }
     }
 
     // This is on the heap as it triggers -Wframe-larger-than.
@@ -174,24 +213,35 @@
     dump_smaps_ = cfg->dump_smaps();
     for (auto it = cfg->ignored_types(); it; ++it) {
       std::string name = (*it).ToStdString();
-      ignored_types_.emplace_back(std::move(name));
+      ignored_types_.emplace_back(art::InversePrettyDescriptor(name));
     }
     // This tracing session ID matches the requesting tracing session ID, so we know heapprofd
     // has verified it targets this process.
-    enabled_ = true;
+    enabled_ =
+        !is_oome_heap_ || (IsOomeHeapDumpAllowed(*args.config) && IsOomeDumpEnabled(*cfg.get()));
   }
 
   bool dump_smaps() { return dump_smaps_; }
+
+  // Per-DataSource enable bit. Invoked by the ::Trace method.
   bool enabled() { return enabled_; }
 
   void OnStart(const StartArgs&) override {
-    if (!enabled()) {
-      return;
-    }
     art::MutexLock lk(art_thread(), GetStateMutex());
+    // In case there are multiple tracing sessions waiting for an OOME error,
+    // there will be a data source instance for each of them. Before the
+    // transition to kStart and signaling the dumping thread, we need to make
+    // sure all the data sources are ready.
+    if (is_oome_heap_ && g_oome_sessions_pending > 0) {
+      --g_oome_sessions_pending;
+    }
     if (g_state == State::kWaitForStart) {
-      g_state = State::kStart;
-      GetStateCV().Broadcast(art_thread());
+      // WriteHeapPackets is responsible for checking whether the DataSource is\
+      // actually enabled.
+      if (!is_oome_heap_ || g_oome_sessions_pending == 0) {
+        g_state = State::kStart;
+        GetStateCV().Broadcast(art_thread());
+      }
     }
   }
 
@@ -232,10 +282,26 @@
   }
 
  private:
+  static bool IsOomeDumpEnabled(const perfetto::protos::pbzero::JavaHprofConfig::Decoder& cfg) {
+    std::string cmdline;
+    if (!android::base::ReadFileToString("/proc/self/cmdline", &cmdline)) {
+      return false;
+    }
+    const char* argv0 = cmdline.c_str();
+
+    for (auto it = cfg.process_cmdline(); it; ++it) {
+      std::string pattern = (*it).ToStdString();
+      if (fnmatch(pattern.c_str(), argv0, FNM_NOESCAPE) == 0) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool is_oome_heap_ = false;
   bool enabled_ = false;
   bool dump_smaps_ = false;
   std::vector<std::string> ignored_types_;
-  static art::Thread* self_;
 
   art::Mutex finish_mutex_{"perfetto_hprof_ds_mutex", art::LockLevel::kGenericBottomLock};
   bool is_finished_ = false;
@@ -243,27 +309,40 @@
   std::function<void()> async_stop_;
 };
 
-art::Thread* JavaHprofDataSource::self_ = nullptr;
-
-
-void WaitForDataSource(art::Thread* self) {
+void SetupDataSource(const std::string& ds_name, bool is_oome_heap) {
   perfetto::TracingInitArgs args;
   args.backends = perfetto::BackendType::kSystemBackend;
   perfetto::Tracing::Initialize(args);
 
   perfetto::DataSourceDescriptor dsd;
-  dsd.set_name("android.java_hprof");
+  dsd.set_name(ds_name);
   dsd.set_will_notify_on_stop(true);
-  JavaHprofDataSource::Register(dsd);
+  JavaHprofDataSource::Register(dsd, is_oome_heap);
+  LOG(INFO) << "registered data source " << ds_name;
+}
 
-  LOG(INFO) << "waiting for data source";
-
+// Waits for the data source OnStart
+void WaitForDataSource(art::Thread* self) {
   art::MutexLock lk(self, GetStateMutex());
   while (g_state != State::kStart) {
     GetStateCV().Wait(self);
   }
 }
 
+// Waits for the data source OnStart with a timeout. Returns false on timeout.
+bool TimedWaitForDataSource(art::Thread* self, int64_t timeout_ms) {
+  const uint64_t cutoff_ns = GetCurrentBootClockNs() + timeout_ms * 1000000;
+  art::MutexLock lk(self, GetStateMutex());
+  while (g_state != State::kStart) {
+    const uint64_t current_ns = GetCurrentBootClockNs();
+    if (current_ns >= cutoff_ns) {
+      return false;
+    }
+    GetStateCV().TimedWait(self, (cutoff_ns - current_ns) / 1000000, 0);
+  }
+  return true;
+}
+
 // Helper class to write Java heap dumps to `ctx`. The whole heap dump can be
 // split into more perfetto.protos.HeapGraph messages, to avoid making each
 // message too big.
@@ -333,8 +412,9 @@
 class ReferredObjectsFinder {
  public:
   explicit ReferredObjectsFinder(
-      std::vector<std::pair<std::string, art::mirror::Object*>>* referred_objects)
-      : referred_objects_(referred_objects) {}
+      std::vector<std::pair<std::string, art::mirror::Object*>>* referred_objects,
+      bool emit_field_ids)
+      : referred_objects_(referred_objects), emit_field_ids_(emit_field_ids) {}
 
   // For art::mirror::Object::VisitReferences.
   void operator()(art::ObjPtr<art::mirror::Object> obj, art::MemberOffset offset,
@@ -352,7 +432,7 @@
       field = art::ArtField::FindInstanceFieldWithOffset(obj->GetClass(), offset.Uint32Value());
     }
     std::string field_name = "";
-    if (field != nullptr) {
+    if (field != nullptr && emit_field_ids_) {
       field_name = field->PrettyField(/*with_type=*/true);
     }
     referred_objects_->emplace_back(std::move(field_name), ref);
@@ -367,6 +447,8 @@
   // We can use a raw Object* pointer here, because there are no concurrent GC threads after the
   // fork.
   std::vector<std::pair<std::string, art::mirror::Object*>>* referred_objects_;
+  // Prettifying field names is expensive; avoid if field name will not be used.
+  bool emit_field_ids_;
 };
 
 class RootFinder : public art::SingleRootVisitor {
@@ -461,7 +543,7 @@
 }
 
 void DumpSmaps(JavaHprofDataSource::TraceContext* ctx) {
-  FILE* smaps = fopen("/proc/self/smaps", "r");
+  FILE* smaps = fopen("/proc/self/smaps", "re");
   if (smaps != nullptr) {
     auto trace_packet = ctx->NewTracePacket();
     auto* smaps_packet = trace_packet->set_smaps_packet();
@@ -504,10 +586,11 @@
 
 // Returns all the references that `*obj` (an object of type `*klass`) is holding.
 std::vector<std::pair<std::string, art::mirror::Object*>> GetReferences(art::mirror::Object* obj,
-                                                                        art::mirror::Class* klass)
+                                                                        art::mirror::Class* klass,
+                                                                        bool emit_field_ids)
     REQUIRES_SHARED(art::Locks::mutator_lock_) {
   std::vector<std::pair<std::string, art::mirror::Object*>> referred_objects;
-  ReferredObjectsFinder objf(&referred_objects);
+  ReferredObjectsFinder objf(&referred_objects, emit_field_ids);
 
   if (klass->GetClassFlags() != art::mirror::kClassFlagNormal &&
       klass->GetClassFlags() != art::mirror::kClassFlagPhantomReference) {
@@ -718,17 +801,16 @@
                       art::mirror::Class* klass,
                       perfetto::protos::pbzero::HeapGraphObject* object_proto)
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    const bool emit_field_ids = klass->GetClassFlags() != art::mirror::kClassFlagObjectArray &&
+                                klass->GetClassFlags() != art::mirror::kClassFlagNormal &&
+                                klass->GetClassFlags() != art::mirror::kClassFlagPhantomReference;
     std::vector<std::pair<std::string, art::mirror::Object*>> referred_objects =
-        GetReferences(obj, klass);
+        GetReferences(obj, klass, emit_field_ids);
 
     art::mirror::Object* min_nonnull_ptr = FilterIgnoredReferencesAndFindMin(referred_objects);
 
     uint64_t base_obj_id = EncodeBaseObjId(referred_objects, min_nonnull_ptr);
 
-    const bool emit_field_ids = klass->GetClassFlags() != art::mirror::kClassFlagObjectArray &&
-                                klass->GetClassFlags() != art::mirror::kClassFlagNormal &&
-                                klass->GetClassFlags() != art::mirror::kClassFlagPhantomReference;
-
     for (const auto& p : referred_objects) {
       const std::string& field_name = p.first;
       art::mirror::Object* referred_obj = p.second;
@@ -806,8 +888,9 @@
       return false;
     }
     art::mirror::Class* klass = obj->GetClass();
-    return std::find(ignored_types_.begin(), ignored_types_.end(), PrettyType(klass)) !=
-           ignored_types_.end();
+    std::string temp;
+    std::string_view name(klass->GetDescriptor(&temp));
+    return std::find(ignored_types_.begin(), ignored_types_.end(), name) != ignored_types_.end();
   }
 
   // Name of classes whose instances should be ignored.
@@ -831,10 +914,45 @@
   uint64_t prev_object_id_ = 0;
 };
 
-void DumpPerfetto(art::Thread* self) {
-  pid_t parent_pid = getpid();
-  LOG(INFO) << "preparing to dump heap for " << parent_pid;
+// waitpid with a timeout implemented by ~busy-waiting
+// See b/181031512 for rationale.
+void BusyWaitpid(pid_t pid, uint32_t timeout_ms) {
+  for (size_t i = 0;; ++i) {
+    if (i == timeout_ms) {
+      // The child hasn't exited.
+      // Give up and SIGKILL it. The next waitpid should succeed.
+      LOG(ERROR) << "perfetto_hprof child timed out. Sending SIGKILL.";
+      kill(pid, SIGKILL);
+    }
+    int stat_loc;
+    pid_t wait_result = waitpid(pid, &stat_loc, WNOHANG);
+    if (wait_result == -1 && errno != EINTR) {
+      if (errno != ECHILD) {
+        // This hopefully never happens (should only be EINVAL).
+        PLOG(FATAL_WITHOUT_ABORT) << "waitpid";
+      }
+      // If we get ECHILD, the parent process was handling SIGCHLD, or did a wildcard wait.
+      // The child is no longer here either way, so that's good enough for us.
+      break;
+    } else if (wait_result > 0) {
+      break;
+    } else {  // wait_result == 0 || errno == EINTR.
+      usleep(1000);
+    }
+  }
+}
 
+enum class ResumeParentPolicy {
+  IMMEDIATELY,
+  DEFERRED
+};
+
+void ForkAndRun(art::Thread* self,
+                ResumeParentPolicy resume_parent_policy,
+                const std::function<void(pid_t child)>& parent_runnable,
+                const std::function<void(pid_t parent, uint64_t timestamp)>& child_runnable) {
+  pid_t parent_pid = getpid();
+  LOG(INFO) << "forking for " << parent_pid;
   // Need to take a heap dump while GC isn't running. See the comment in
   // Heap::VisitObjects(). Also we need the critical section to avoid visiting
   // the same object twice. See b/34967844.
@@ -859,41 +977,20 @@
   }
   if (pid != 0) {
     // Parent
-    // Stop the thread suspension as soon as possible to allow the rest of the application to
-    // continue while we waitpid here.
-    ssa.reset();
-    gcs.reset();
-    for (size_t i = 0;; ++i) {
-      if (i == 1000) {
-        // The child hasn't exited for 1 second (and all it was supposed to do was fork itself).
-        // Give up and SIGKILL it. The next waitpid should succeed.
-        LOG(ERROR) << "perfetto_hprof child timed out. Sending SIGKILL.";
-        kill(pid, SIGKILL);
-      }
-      // Busy waiting here will introduce some extra latency, but that is okay because we have
-      // already unsuspended all other threads. This runs on the perfetto_hprof_listener, which
-      // is not needed for progress of the app itself.
-      int stat_loc;
-      pid_t wait_result = waitpid(pid, &stat_loc, WNOHANG);
-      if (wait_result == -1 && errno != EINTR) {
-        if (errno != ECHILD) {
-          // This hopefully never happens (should only be EINVAL).
-          PLOG(FATAL_WITHOUT_ABORT) << "waitpid";
-        }
-        // If we get ECHILD, the parent process was handling SIGCHLD, or did a wildcard wait.
-        // The child is no longer here either way, so that's good enough for us.
-        break;
-      } else if (wait_result > 0) {
-        break;
-      } else {  // wait_result == 0 || errno == EINTR.
-        usleep(1000);
-      }
+    if (resume_parent_policy == ResumeParentPolicy::IMMEDIATELY) {
+      // Stop the thread suspension as soon as possible to allow the rest of the application to
+      // continue while we waitpid here.
+      ssa.reset();
+      gcs.reset();
+    }
+    parent_runnable(pid);
+    if (resume_parent_policy != ResumeParentPolicy::IMMEDIATELY) {
+      ssa.reset();
+      gcs.reset();
     }
     return;
   }
-
   // The following code is only executed by the child of the original process.
-
   // Uninstall signal handler, so we don't trigger a profile on it.
   if (sigaction(kJavaHeapprofdSignal, &g_orig_act, nullptr) != 0) {
     close(g_signal_pipe_fds[0]);
@@ -902,25 +999,14 @@
     return;
   }
 
-  // Daemon creates a new process that is the grand-child of the original process, and exits.
-  if (daemon(0, 0) == -1) {
-    PLOG(FATAL) << "daemon";
-  }
+  uint64_t ts = GetCurrentBootClockNs();
+  child_runnable(parent_pid, ts);
+  // Prevent the `atexit` handlers from running. We do not want to call cleanup
+  // functions the parent process has registered.
+  art::FastExit(0);
+}
 
-  // The following code is only executed by the grand-child of the original process.
-
-  // Make sure that this is the first thing we do after forking, so if anything
-  // below hangs, the fork will go away from the watchdog.
-  ArmWatchdogOrDie();
-
-  struct timespec ts = {};
-  if (clock_gettime(CLOCK_BOOTTIME, &ts) != 0) {
-    LOG(FATAL) << "Failed to get boottime.";
-  }
-  uint64_t timestamp = ts.tv_sec * 1000000000LL + ts.tv_nsec;
-
-  WaitForDataSource(self);
-
+void WriteHeapPackets(pid_t parent_pid, uint64_t timestamp) {
   JavaHprofDataSource::Trace(
       [parent_pid, timestamp](JavaHprofDataSource::TraceContext ctx)
           NO_THREAD_SAFETY_ANALYSIS {
@@ -968,11 +1054,101 @@
               }
             }
           });
+}
 
-  LOG(INFO) << "finished dumping heap for " << parent_pid;
-  // Prevent the `atexit` handlers from running. We do not want to call cleanup
-  // functions the parent process has registered.
-  art::FastExit(0);
+void DumpPerfetto(art::Thread* self) {
+  ForkAndRun(
+    self,
+    ResumeParentPolicy::IMMEDIATELY,
+    // parent thread
+    [](pid_t child) {
+      // Busy waiting here will introduce some extra latency, but that is okay because we have
+      // already unsuspended all other threads. This runs on the perfetto_hprof_listener, which
+      // is not needed for progress of the app itself.
+      // We daemonize the child process, so effectively we only need to wait
+      // for it to fork and exit.
+      BusyWaitpid(child, 1000);
+    },
+    // child thread
+    [self](pid_t dumped_pid, uint64_t timestamp) {
+      // Daemon creates a new process that is the grand-child of the original process, and exits.
+      if (daemon(0, 0) == -1) {
+        PLOG(FATAL) << "daemon";
+      }
+      // The following code is only executed by the grand-child of the original process.
+
+      // Make sure that this is the first thing we do after forking, so if anything
+      // below hangs, the fork will go away from the watchdog.
+      ArmWatchdogOrDie();
+      SetupDataSource("android.java_hprof", false);
+      WaitForDataSource(self);
+      WriteHeapPackets(dumped_pid, timestamp);
+      LOG(INFO) << "finished dumping heap for " << dumped_pid;
+    });
+}
+
+void DumpPerfettoOutOfMemory() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  art::Thread* self = art::Thread::Current();
+  if (!self) {
+    LOG(FATAL_WITHOUT_ABORT) << "no thread in DumpPerfettoOutOfMemory";
+    return;
+  }
+
+  // Ensure that there is an active, armed tracing session
+  uint32_t session_cnt =
+      android::base::GetUintProperty<uint32_t>("traced.oome_heap_session.count", 0);
+  if (session_cnt == 0) {
+    return;
+  }
+  {
+    // OutOfMemoryErrors are reentrant, make sure we do not fork and process
+    // more than once.
+    art::MutexLock lk(self, GetStateMutex());
+    if (g_oome_triggered) {
+      return;
+    }
+    g_oome_triggered = true;
+    g_oome_sessions_pending = session_cnt;
+  }
+
+  art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+  // If we fork & resume the original process execution it will most likely exit
+  // ~immediately due to the OOME error thrown. When the system detects that
+  // that, it will cleanup by killing all processes in the cgroup (including
+  // the process we just forked).
+  // We need to avoid the race between the heap dump and the process group
+  // cleanup, and the only way to do this is to avoid resuming the original
+  // process until the heap dump is complete.
+  // Given we are already about to crash anyway, the diagnostic data we get
+  // outweighs the cost of introducing some latency.
+  ForkAndRun(
+    self,
+    ResumeParentPolicy::DEFERRED,
+    // parent process
+    [](pid_t child) {
+      // waitpid to reap the zombie
+      // we are explicitly waiting for the child to exit
+      // The reason for the timeout on top of the watchdog is that it is
+      // possible (albeit unlikely) that even the watchdog will fail to be
+      // activated in the case of an atfork handler.
+      BusyWaitpid(child, kWatchdogTimeoutSec * 1000);
+    },
+    // child process
+    [self](pid_t dumped_pid, uint64_t timestamp) {
+      ArmWatchdogOrDie();
+      art::ScopedTrace trace("perfetto_hprof oome");
+      SetupDataSource("android.java_hprof.oom", true);
+      perfetto::Tracing::ActivateTriggers({"com.android.telemetry.art-outofmemory"}, 500);
+
+      // A pre-armed tracing session might not exist, so we should wait for a
+      // limited amount of time before we decide to let the execution continue.
+      if (!TimedWaitForDataSource(self, 1000)) {
+        LOG(INFO) << "OOME hprof timeout (state " << g_state << ")";
+        return;
+      }
+      WriteHeapPackets(dumped_pid, timestamp);
+      LOG(INFO) << "OOME hprof complete for " << dumped_pid;
+    });
 }
 
 // The plugin initialization function.
@@ -1062,10 +1238,15 @@
   });
   th.detach();
 
+  // Register the OOM error handler.
+  art::Runtime::Current()->SetOutOfMemoryErrorHook(perfetto_hprof::DumpPerfettoOutOfMemory);
+
   return true;
 }
 
 extern "C" bool ArtPlugin_Deinitialize() {
+  art::Runtime::Current()->SetOutOfMemoryErrorHook(nullptr);
+
   if (sigaction(kJavaHeapprofdSignal, &g_orig_act, nullptr) != 0) {
     PLOG(ERROR) << "failed to reset signal handler";
     // We cannot close the pipe if the signal handler wasn't unregistered,
diff --git a/profman/Android.bp b/profman/Android.bp
index b231499..ac80641 100644
--- a/profman/Android.bp
+++ b/profman/Android.bp
@@ -32,6 +32,7 @@
         "profman.cc",
         "profile_assistant.cc",
     ],
+    header_libs: ["profman_headers"],
 
     target: {
         android: {
@@ -109,6 +110,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -160,11 +162,31 @@
     },
 }
 
+cc_library_headers {
+    name: "profman_headers",
+    defaults: ["art_defaults"],
+    export_include_dirs: ["include"],
+    host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        },
+        windows: {
+            enabled: true,
+        },
+    },
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
 art_cc_defaults {
     name: "art_profman_tests_defaults",
     data: [
         ":art-gtest-jars-ProfileTestMultiDex",
     ],
+    header_libs: ["profman_headers"],
     tidy_timeout_srcs: ["profile_assistant_test.cc"],
     srcs: ["profile_assistant_test.cc"],
 }
diff --git a/profman/include/profman/profman_result.h b/profman/include/profman/profman_result.h
new file mode 100644
index 0000000..9c9aca9
--- /dev/null
+++ b/profman/include/profman/profman_result.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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_PROFMAN_INCLUDE_PROFMAN_PROFMAN_RESULT_H_
+#define ART_PROFMAN_INCLUDE_PROFMAN_PROFMAN_RESULT_H_
+
+namespace art {
+
+class ProfmanResult {
+ public:
+  static constexpr int kErrorUsage = 100;
+
+  // The return codes of processing profiles (running profman in normal mode).
+  //
+  // On a successful run:
+  // - If `--force-merge` is specified, the return code can only be `kSuccess`.
+  // - If no `--profile-file(-fd)` is specified, the return code can only be
+  // `kSkipCompilationSmallDelta` or `kSkipCompilationEmptyProfiles`.
+  // - Otherwise, the return code can only be `kCompile`, `kSkipCompilationSmallDelta`, or
+  //   `kSkipCompilationEmptyProfiles`.
+  //
+  // Note that installd consumes the returns codes with its own copy of these values
+  // (frameworks/native/cmds/installd/dexopt.cpp).
+  enum ProcessingResult {
+    // The success code for `--force-merge`.
+    // This is also the generic success code for non-analysis runs.
+    kSuccess = 0,
+    // A merge has been performed, meaning the reference profile has been changed.
+    kCompile = 1,
+    // `--profile-file(-fd)` is not specified, or the specified profiles are outdated (i.e., APK
+    // filename or checksum mismatch), empty, or don't contain enough number of new classes and
+    // methods that meets the threshold to trigger a merge.
+    kSkipCompilationSmallDelta = 2,
+    // All the input profiles (including the reference profile) are either outdated (i.e., APK
+    // filename or checksum mismatch) or empty.
+    kSkipCompilationEmptyProfiles = 7,
+    // Errors.
+    kErrorBadProfiles = 3,
+    kErrorIO = 4,
+    kErrorCannotLock = 5,
+    kErrorDifferentVersions = 6,
+  };
+
+  // The return codes of running profman with `--copy-and-update-profile-key`.
+  enum CopyAndUpdateResult {
+    kCopyAndUpdateSuccess = 0,
+    kCopyAndUpdateNoMatch = 21,
+    kCopyAndUpdateErrorFailedToUpdateProfile = 22,
+    kCopyAndUpdateErrorFailedToSaveProfile = 23,
+    kCopyAndUpdateErrorFailedToLoadProfile = 24,
+  };
+};
+
+}  // namespace art
+
+#endif  // ART_PROFMAN_INCLUDE_PROFMAN_PROFMAN_RESULT_H_
diff --git a/profman/profile_assistant.cc b/profman/profile_assistant.cc
index d098738..abbde2d 100644
--- a/profman/profile_assistant.cc
+++ b/profman/profile_assistant.cc
@@ -18,6 +18,7 @@
 
 #include "base/os.h"
 #include "base/unix_file/fd_file.h"
+#include "profman/profman_result.h"
 
 namespace art {
 
@@ -26,25 +27,22 @@
 static constexpr const uint32_t kMinNewMethodsForCompilation = 100;
 static constexpr const uint32_t kMinNewClassesForCompilation = 50;
 
-
-ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfilesInternal(
-        const std::vector<ScopedFlock>& profile_files,
-        const ScopedFlock& reference_profile_file,
-        const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
-        const Options& options) {
-  DCHECK(!profile_files.empty());
-
+ProfmanResult::ProcessingResult ProfileAssistant::ProcessProfilesInternal(
+    const std::vector<ScopedFlock>& profile_files,
+    const ScopedFlock& reference_profile_file,
+    const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
+    const Options& options) {
   ProfileCompilationInfo info(options.IsBootImageMerge());
 
   // Load the reference profile.
   if (!info.Load(reference_profile_file->Fd(), /*merge_classes=*/ true, filter_fn)) {
     LOG(WARNING) << "Could not load reference profile file";
-    return kErrorBadProfiles;
+    return ProfmanResult::kErrorBadProfiles;
   }
 
   if (options.IsBootImageMerge() && !info.IsForBootImage()) {
     LOG(WARNING) << "Requested merge for boot image profile but the reference profile is regular.";
-    return kErrorBadProfiles;
+    return ProfmanResult::kErrorBadProfiles;
   }
 
   // Store the current state of the reference profile before merging with the current profiles.
@@ -65,21 +63,21 @@
       // TODO: Do we really need to use a different error code for version mismatch?
       ProfileCompilationInfo wrong_info(!options.IsBootImageMerge());
       if (wrong_info.Load(profile_files[i]->Fd(), /*merge_classes=*/ true, filter_fn)) {
-        return kErrorDifferentVersions;
+        return ProfmanResult::kErrorDifferentVersions;
       }
-      return kErrorBadProfiles;
+      return ProfmanResult::kErrorBadProfiles;
     }
 
     if (!info.MergeWith(cur_info)) {
       LOG(WARNING) << "Could not merge profile file at index " << i;
-      return kErrorBadProfiles;
+      return ProfmanResult::kErrorBadProfiles;
     }
   }
 
   // If we perform a forced merge do not analyze the difference between profiles.
   if (!options.IsForceMerge()) {
     if (info.IsEmpty()) {
-      return kSkipCompilationEmptyProfiles;
+      return ProfmanResult::kSkipCompilationEmptyProfiles;
     }
     uint32_t min_change_in_methods_for_compilation = std::max(
         (options.GetMinNewMethodsPercentChangeForCompilation() * number_of_methods) / 100,
@@ -91,21 +89,21 @@
     if (((info.GetNumberOfMethods() - number_of_methods) < min_change_in_methods_for_compilation) &&
         ((info.GetNumberOfResolvedClasses() - number_of_classes)
             < min_change_in_classes_for_compilation)) {
-      return kSkipCompilationSmallDelta;
+      return ProfmanResult::kSkipCompilationSmallDelta;
     }
   }
 
   // We were successful in merging all profile information. Update the reference profile.
   if (!reference_profile_file->ClearContent()) {
     PLOG(WARNING) << "Could not clear reference profile file";
-    return kErrorIO;
+    return ProfmanResult::kErrorIO;
   }
   if (!info.Save(reference_profile_file->Fd())) {
     LOG(WARNING) << "Could not save reference profile file";
-    return kErrorIO;
+    return ProfmanResult::kErrorIO;
   }
 
-  return options.IsForceMerge() ? kSuccess : kCompile;
+  return options.IsForceMerge() ? ProfmanResult::kSuccess : ProfmanResult::kCompile;
 }
 
 class ScopedFlockList {
@@ -144,7 +142,7 @@
   std::vector<ScopedFlock> flocks_;
 };
 
-ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfiles(
+ProfmanResult::ProcessingResult ProfileAssistant::ProcessProfiles(
         const std::vector<int>& profile_files_fd,
         int reference_profile_file_fd,
         const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
@@ -155,7 +153,7 @@
   ScopedFlockList profile_files(profile_files_fd.size());
   if (!profile_files.Init(profile_files_fd, &error)) {
     LOG(WARNING) << "Could not lock profile files: " << error;
-    return kErrorCannotLock;
+    return ProfmanResult::kErrorCannotLock;
   }
 
   // The reference_profile_file is opened in read/write mode because it's
@@ -166,7 +164,7 @@
                                                          &error);
   if (reference_profile_file.get() == nullptr) {
     LOG(WARNING) << "Could not lock reference profiled files: " << error;
-    return kErrorCannotLock;
+    return ProfmanResult::kErrorCannotLock;
   }
 
   return ProcessProfilesInternal(profile_files.Get(),
@@ -175,7 +173,7 @@
                                  options);
 }
 
-ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfiles(
+ProfmanResult::ProcessingResult ProfileAssistant::ProcessProfiles(
         const std::vector<std::string>& profile_files,
         const std::string& reference_profile_file,
         const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
@@ -185,14 +183,14 @@
   ScopedFlockList profile_files_list(profile_files.size());
   if (!profile_files_list.Init(profile_files, &error)) {
     LOG(WARNING) << "Could not lock profile files: " << error;
-    return kErrorCannotLock;
+    return ProfmanResult::kErrorCannotLock;
   }
 
   ScopedFlock locked_reference_profile_file = LockedFile::Open(
       reference_profile_file.c_str(), O_RDWR, /* block= */ true, &error);
   if (locked_reference_profile_file.get() == nullptr) {
     LOG(WARNING) << "Could not lock reference profile files: " << error;
-    return kErrorCannotLock;
+    return ProfmanResult::kErrorCannotLock;
   }
 
   return ProcessProfilesInternal(profile_files_list.Get(),
diff --git a/profman/profile_assistant.h b/profman/profile_assistant.h
index 0ef4f88..6b7a7a6 100644
--- a/profman/profile_assistant.h
+++ b/profman/profile_assistant.h
@@ -22,24 +22,12 @@
 
 #include "base/scoped_flock.h"
 #include "profile/profile_compilation_info.h"
+#include "profman/profman_result.h"
 
 namespace art {
 
 class ProfileAssistant {
  public:
-  // These also serve as return codes of profman and are processed by installd
-  // (frameworks/native/cmds/installd/commands.cpp)
-  enum ProcessingResult {
-    kSuccess = 0,  // Generic success code for non-analysis runs.
-    kCompile = 1,
-    kSkipCompilationSmallDelta = 2,
-    kErrorBadProfiles = 3,
-    kErrorIO = 4,
-    kErrorCannotLock = 5,
-    kErrorDifferentVersions = 6,
-    kSkipCompilationEmptyProfiles = 7,
-  };
-
   class Options {
    public:
     static constexpr bool kForceMergeDefault = false;
@@ -101,14 +89,14 @@
   // this case no file will be updated. A variation of this code is
   // kSkipCompilationEmptyProfiles which indicates that all the profiles are empty.
   // This allow the caller to make fine grain decisions on the compilation strategy.
-  static ProcessingResult ProcessProfiles(
+  static ProfmanResult::ProcessingResult ProcessProfiles(
       const std::vector<std::string>& profile_files,
       const std::string& reference_profile_file,
       const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn
           = ProfileCompilationInfo::ProfileFilterFnAcceptAll,
       const Options& options = Options());
 
-  static ProcessingResult ProcessProfiles(
+  static ProfmanResult::ProcessingResult ProcessProfiles(
       const std::vector<int>& profile_files_fd_,
       int reference_profile_file_fd,
       const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn
@@ -116,7 +104,7 @@
       const Options& options = Options());
 
  private:
-  static ProcessingResult ProcessProfilesInternal(
+  static ProfmanResult::ProcessingResult ProcessProfilesInternal(
       const std::vector<ScopedFlock>& profile_files,
       const ScopedFlock& reference_profile_file,
       const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index f446c09..f7c4255 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+#include "profile_assistant.h"
+
 #include <sstream>
 #include <string>
 
@@ -31,12 +32,13 @@
 #include "dex/dex_instruction_iterator.h"
 #include "dex/type_reference.h"
 #include "exec_utils.h"
+#include "gtest/gtest.h"
 #include "linear_alloc.h"
 #include "mirror/class-inl.h"
 #include "obj_ptr-inl.h"
 #include "profile/profile_compilation_info.h"
 #include "profile/profile_test_helper.h"
-#include "profile_assistant.h"
+#include "profman/profman_result.h"
 #include "scoped_thread_state_change-inl.h"
 
 namespace art {
@@ -50,13 +52,13 @@
   void PostRuntimeCreate() override {
     allocator_.reset(new ArenaAllocator(Runtime::Current()->GetArenaPool()));
 
-    dex1 = BuildDex("location1", /*checksum=*/ 1, "LUnique1;", /*num_method_ids=*/ 10001);
-    dex2 = BuildDex("location2", /*checksum=*/ 2, "LUnique2;", /*num_method_ids=*/ 10002);
-    dex3 = BuildDex("location3", /*checksum=*/ 3, "LUnique3;", /*num_method_ids=*/ 10003);
-    dex4 = BuildDex("location4", /*checksum=*/ 4, "LUnique4;", /*num_method_ids=*/ 10004);
+    dex1 = BuildDex("location1", /*location_checksum=*/ 1, "LUnique1;", /*num_method_ids=*/ 10001);
+    dex2 = BuildDex("location2", /*location_checksum=*/ 2, "LUnique2;", /*num_method_ids=*/ 10002);
+    dex3 = BuildDex("location3", /*location_checksum=*/ 3, "LUnique3;", /*num_method_ids=*/ 10003);
+    dex4 = BuildDex("location4", /*location_checksum=*/ 4, "LUnique4;", /*num_method_ids=*/ 10004);
 
     dex1_checksum_missmatch =
-        BuildDex("location1", /*checksum=*/ 12, "LUnique1;", /*num_method_ids=*/ 10001);
+        BuildDex("location1", /*location_checksum=*/ 12, "LUnique1;", /*num_method_ids=*/ 10001);
   }
 
  protected:
@@ -267,7 +269,7 @@
 
   bool CreateAndDump(const std::string& input_file_contents,
                      std::string* output_file_contents,
-                     std::optional<const std::string> target = std::nullopt) {
+                     const std::optional<const std::string>& target = std::nullopt) {
     ScratchFile profile_file;
     EXPECT_TRUE(CreateProfile(input_file_contents,
                               profile_file.GetFilename(),
@@ -442,10 +444,16 @@
                                          const std::vector<const std::string>& extra_args =
                                              std::vector<const std::string>()) {
     uint16_t max_classes = std::max(classes_in_cur_profile, classes_in_ref_profile);
-    const DexFile* dex1_x = BuildDex(
-        "location1_x", /*checksum=*/ 0x101, "LUnique1_x;", /*num_method_ids=*/ 0, max_classes);
-    const DexFile* dex2_x = BuildDex(
-        "location2_x", /*checksum=*/ 0x102, "LUnique2_x;", /*num_method_ids=*/ 0, max_classes);
+    const DexFile* dex1_x = BuildDex("location1_x",
+                                     /*location_checksum=*/ 0x101,
+                                     "LUnique1_x;",
+                                     /*num_method_ids=*/ 0,
+                                     max_classes);
+    const DexFile* dex2_x = BuildDex("location2_x",
+                                     /*location_checksum=*/ 0x102,
+                                     "LUnique2_x;",
+                                     /*num_method_ids=*/ 0,
+                                     max_classes);
 
     ScratchFile profile;
     ScratchFile reference_profile;
@@ -486,8 +494,7 @@
   SetupProfile(dex3, dex4, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2);
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
   // The resulting compilation info must be equal to the merge of the inputs.
   ProfileCompilationInfo result;
   ASSERT_TRUE(result.Load(reference_profile_fd));
@@ -506,15 +513,15 @@
 TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferencesBecauseOfClasses) {
   const uint16_t kNumberOfClassesToEnableCompilation = 100;
   const DexFile* dex1_100 = BuildDex("location1_100",
-                                     /*checksum=*/ 101,
+                                     /*location_checksum=*/ 101,
                                      "LUnique1_100;",
                                      /*num_method_ids=*/ 0,
-                                     /*num_type_ids=*/ 100);
+                                     /*num_class_ids=*/ 100);
   const DexFile* dex2_100 = BuildDex("location2_100",
-                                     /*checksum=*/ 102,
+                                     /*location_checksum=*/ 102,
                                      "LUnique2_100;",
                                      /*num_method_ids=*/ 0,
-                                     /*num_type_ids=*/ 100);
+                                     /*num_class_ids=*/ 100);
 
   ScratchFile profile1;
   ScratchFile reference_profile;
@@ -527,8 +534,7 @@
   SetupProfile(dex1_100, dex2_100, 0, kNumberOfClassesToEnableCompilation, profile1, &info1);
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
   // The resulting compilation info must be equal to the merge of the inputs.
   ProfileCompilationInfo result;
   ASSERT_TRUE(result.Load(reference_profile_fd));
@@ -566,8 +572,7 @@
       &reference_info, kNumberOfMethodsToEnableCompilation / 2);
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The resulting compilation info must be equal to the merge of the inputs
   ProfileCompilationInfo result;
@@ -600,7 +605,7 @@
   SetupProfile(dex3, dex4, /*number_of_methods=*/ 0, /*number_of_classes*/ 0, profile2, &info2);
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationEmptyProfiles,
+  ASSERT_EQ(ProfmanResult::kSkipCompilationEmptyProfiles,
             ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The information from profiles must remain the same.
@@ -637,7 +642,7 @@
   SetupProfile(dex3, dex4, kNumberOfMethodsToSkipCompilation, 0, profile2, &info2);
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
+  ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
             ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The information from profiles must remain the same.
@@ -663,10 +668,9 @@
   std::vector<const std::string> extra_args({"--min-new-methods-percent-change=2"});
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
-            CheckCompilationMethodPercentChange(kNumberOfMethodsInCurProfile,
-                                                kNumberOfMethodsInRefProfile,
-                                                extra_args));
+  ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
+            CheckCompilationMethodPercentChange(
+                kNumberOfMethodsInCurProfile, kNumberOfMethodsInRefProfile, extra_args));
 }
 
 TEST_F(ProfileAssistantTest, ShouldAdviseCompilationMethodPercentage) {
@@ -675,10 +679,9 @@
   std::vector<const std::string> extra_args({"--min-new-methods-percent-change=2"});
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            CheckCompilationMethodPercentChange(kNumberOfMethodsInCurProfile,
-                                                kNumberOfMethodsInRefProfile,
-                                                extra_args));
+  ASSERT_EQ(ProfmanResult::kCompile,
+            CheckCompilationMethodPercentChange(
+                kNumberOfMethodsInCurProfile, kNumberOfMethodsInRefProfile, extra_args));
 }
 
 TEST_F(ProfileAssistantTest, DoNotAdviseCompilationMethodPercentageWithNewMin) {
@@ -686,7 +689,7 @@
   const uint16_t kNumberOfMethodsInCurProfile = 6200;  // Threshold is 20%.
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
+  ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
             CheckCompilationMethodPercentChange(kNumberOfMethodsInCurProfile,
                                                 kNumberOfMethodsInRefProfile));
 }
@@ -697,10 +700,9 @@
   std::vector<const std::string> extra_args({"--min-new-classes-percent-change=2"});
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
-            CheckCompilationClassPercentChange(kNumberOfClassesInCurProfile,
-                                               kNumberOfClassesInRefProfile,
-                                               extra_args));
+  ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
+            CheckCompilationClassPercentChange(
+                kNumberOfClassesInCurProfile, kNumberOfClassesInRefProfile, extra_args));
 }
 
 TEST_F(ProfileAssistantTest, ShouldAdviseCompilationClassPercentage) {
@@ -709,10 +711,9 @@
   std::vector<const std::string> extra_args({"--min-new-classes-percent-change=2"});
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            CheckCompilationClassPercentChange(kNumberOfClassesInCurProfile,
-                                               kNumberOfClassesInRefProfile,
-                                               extra_args));
+  ASSERT_EQ(ProfmanResult::kCompile,
+            CheckCompilationClassPercentChange(
+                kNumberOfClassesInCurProfile, kNumberOfClassesInRefProfile, extra_args));
 }
 
 TEST_F(ProfileAssistantTest, DoNotAdviseCompilationClassPercentageWithNewMin) {
@@ -720,7 +721,7 @@
   const uint16_t kNumberOfClassesInCurProfile = 6200;  // Threshold is 20%.
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
+  ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
             CheckCompilationClassPercentChange(kNumberOfClassesInCurProfile,
                                                kNumberOfClassesInRefProfile));
 }
@@ -744,8 +745,7 @@
       dex1_checksum_missmatch, dex2, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2);
 
   // We should fail processing.
-  ASSERT_EQ(ProfileAssistant::kErrorBadProfiles,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kErrorBadProfiles, ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The information from profiles must remain the same.
   CheckProfileInfo(profile1, info1);
@@ -776,8 +776,7 @@
                &reference_info);
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kErrorBadProfiles,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kErrorBadProfiles, ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The information from profiles must remain the same.
   CheckProfileInfo(profile1, info1);
@@ -971,7 +970,7 @@
                             /*for_boot_image=*/ true));
 
   ProfileCompilationInfo bootProfile(/*for_boot_image=*/ true);
-  bootProfile.Load(profile.GetFilename(), /*clear_if_invalid=*/ true);
+  EXPECT_TRUE(bootProfile.Load(profile.GetFilename(), /*clear_if_invalid=*/ false));
 
   // Generate the boot profile.
   ScratchFile out_profile;
@@ -1065,10 +1064,10 @@
                             core_dex,
                             /*for_boot_image=*/ true));
 
-  ProfileCompilationInfo boot_profile1;
-  ProfileCompilationInfo boot_profile2;
-  boot_profile1.Load(profile1.GetFilename(), /*for_boot_image=*/ true);
-  boot_profile2.Load(profile2.GetFilename(), /*for_boot_image=*/ true);
+  ProfileCompilationInfo boot_profile1(/*for_boot_image=*/ true);
+  ProfileCompilationInfo boot_profile2(/*for_boot_image=*/ true);
+  EXPECT_TRUE(boot_profile1.Load(profile1.GetFilename(), /*clear_if_invalid=*/ false));
+  EXPECT_TRUE(boot_profile2.Load(profile2.GetFilename(), /*clear_if_invalid=*/ false));
 
   // Generate the boot profile.
   ScratchFile out_profile;
@@ -1525,8 +1524,7 @@
       &reference_info, kNumberOfMethodsToEnableCompilation / 2, /*reverse_dex_write_order=*/true);
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The resulting compilation info must be equal to the merge of the inputs.
   ProfileCompilationInfo result;
@@ -1787,7 +1785,7 @@
   argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
   std::string error;
 
-  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfileAssistant::kCompile) << error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCompile) << error;
 
   // Verify that we can load the result.
 
@@ -1820,6 +1818,169 @@
   ASSERT_TRUE(expected.Equals(result));
 }
 
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfile) {
+  ScratchFile reference_profile;
+
+  // Use a real dex file to generate profile test data.
+  std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex");
+  const DexFile& d1 = *dex_files[0];
+  const DexFile& d2 = *dex_files[0];
+
+  // The reference profile info will contain the methods with indices 0-100.
+  ProfileCompilationInfo reference_info;
+  SetupProfile(&d1,
+               &d2,
+               /*number_of_methods=*/ 100,
+               /*number_of_classes=*/ 0,
+               reference_profile,
+               &reference_info);
+
+  std::string content_before;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+  // Run profman and pass the dex file with --apk-fd.
+  android::base::unique_fd apk_fd(
+      // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+  ASSERT_GE(apk_fd.get(), 0);
+
+  std::string profman_cmd = GetProfmanCmd();
+  std::vector<std::string> argv_str;
+  argv_str.push_back(profman_cmd);
+  argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+  argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+  // Must return kSkipCompilationSmallDelta.
+  std::string error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationSmallDelta)
+      << error;
+
+  // Verify that the content has not changed.
+  std::string content_after;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+  EXPECT_EQ(content_before, content_after);
+}
+
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfilePassByFilename) {
+  ScratchFile reference_profile;
+
+  // Use a real dex file to generate profile test data.
+  std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex");
+  const DexFile& d1 = *dex_files[0];
+  const DexFile& d2 = *dex_files[0];
+
+  // The reference profile info will contain the methods with indices 0-100.
+  ProfileCompilationInfo reference_info;
+  SetupProfile(&d1,
+               &d2,
+               /*number_of_methods=*/100,
+               /*number_of_classes=*/0,
+               reference_profile,
+               &reference_info);
+
+  std::string content_before;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+  // Run profman and pass the dex file with --apk-fd.
+  android::base::unique_fd apk_fd(
+      // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+  ASSERT_GE(apk_fd.get(), 0);
+
+  std::string profman_cmd = GetProfmanCmd();
+  std::vector<std::string> argv_str;
+  argv_str.push_back(profman_cmd);
+  argv_str.push_back("--reference-profile-file=" + reference_profile.GetFilename());
+  argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+  // Must return kSkipCompilationSmallDelta.
+  std::string error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationSmallDelta)
+      << error;
+
+  // Verify that the content has not changed.
+  std::string content_after;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+  EXPECT_EQ(content_before, content_after);
+}
+
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfileEmptyReferenceProfile) {
+  ScratchFile reference_profile;
+
+  // The reference profile info will only contain the header.
+  ProfileCompilationInfo reference_info;
+  SetupProfile(/*dex_file1=*/ nullptr,
+               /*dex_file2=*/ nullptr,
+               /*number_of_methods=*/ 0,
+               /*number_of_classes=*/ 0,
+               reference_profile,
+               &reference_info);
+
+  std::string content_before;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+  // Run profman and pass the dex file with --apk-fd.
+  android::base::unique_fd apk_fd(
+      // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+  ASSERT_GE(apk_fd.get(), 0);
+
+  std::string profman_cmd = GetProfmanCmd();
+  std::vector<std::string> argv_str;
+  argv_str.push_back(profman_cmd);
+  argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+  argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+  // Must return kSkipCompilationEmptyProfiles.
+  std::string error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationEmptyProfiles)
+      << error;
+
+  // Verify that the content has not changed.
+  std::string content_after;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+  EXPECT_EQ(content_before, content_after);
+}
+
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfileEmptyReferenceProfileAfterFiltering) {
+  ScratchFile reference_profile;
+
+  // Use fake dex files to generate profile test data.
+  // All the methods will be filtered out during the profman invocation.
+  ProfileCompilationInfo reference_info;
+  SetupProfile(dex1,
+               dex2,
+               /*number_of_methods=*/ 100,
+               /*number_of_classes=*/ 0,
+               reference_profile,
+               &reference_info);
+
+  std::string content_before;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+  // Run profman and pass the real dex file with --apk-fd.
+  android::base::unique_fd apk_fd(
+      // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+  ASSERT_GE(apk_fd.get(), 0);
+
+  std::string profman_cmd = GetProfmanCmd();
+  std::vector<std::string> argv_str;
+  argv_str.push_back(profman_cmd);
+  argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+  argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+  // Must return kSkipCompilationEmptyProfiles.
+  std::string error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationEmptyProfiles)
+      << error;
+
+  // Verify that the content has not changed.
+  std::string content_after;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+  EXPECT_EQ(content_before, content_after);
+}
+
 TEST_F(ProfileAssistantTest, CopyAndUpdateProfileKey) {
   ScratchFile profile1;
   ScratchFile reference_profile;
@@ -1846,7 +2007,8 @@
 
   // Run profman and pass the dex file with --apk-fd.
   android::base::unique_fd apk_fd(
-      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));  // NOLINT
+      // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
   ASSERT_GE(apk_fd.get(), 0);
 
   std::string profman_cmd = GetProfmanCmd();
@@ -1858,7 +2020,8 @@
   argv_str.push_back("--copy-and-update-profile-key");
   std::string error;
 
-  ASSERT_EQ(ExecAndReturnCode(argv_str, &error), 0) << error;
+  // Must return kCopyAndUpdateSuccess.
+  ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateSuccess) << error;
 
   // Verify that we can load the result.
   ProfileCompilationInfo result;
@@ -1874,6 +2037,46 @@
   }
 }
 
+TEST_F(ProfileAssistantTest, CopyAndUpdateProfileKeyNoUpdate) {
+  ScratchFile profile1;
+  ScratchFile reference_profile;
+
+  // Use fake dex files to generate profile test data.
+  ProfileCompilationInfo info1;
+  SetupProfile(dex1,
+               dex2,
+               /*number_of_methods=*/ 100,
+               /*number_of_classes=*/ 0,
+               profile1,
+               &info1);
+
+  std::string input_content;
+  ASSERT_TRUE(android::base::ReadFileToString(profile1.GetFilename(), &input_content));
+
+  // Run profman and pass the real dex file with --apk-fd. It won't match any entry in the profile.
+  android::base::unique_fd apk_fd(
+      // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+  ASSERT_GE(apk_fd.get(), 0);
+
+  std::string profman_cmd = GetProfmanCmd();
+  std::vector<std::string> argv_str;
+  argv_str.push_back(profman_cmd);
+  argv_str.push_back("--profile-file-fd=" + std::to_string(profile1.GetFd()));
+  argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+  argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+  argv_str.push_back("--copy-and-update-profile-key");
+  std::string error;
+
+  // Must return kCopyAndUpdateNoMatch.
+  ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateNoMatch) << error;
+
+  // Verify that the content is the same.
+  std::string output_content;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &output_content));
+  EXPECT_EQ(input_content, output_content);
+}
+
 TEST_F(ProfileAssistantTest, BootImageMerge) {
   ScratchFile profile;
   ScratchFile reference_profile;
@@ -1900,7 +2103,7 @@
 
   int return_code = ProcessProfiles(profile_fds, reference_profile_fd, extra_args);
 
-  ASSERT_EQ(return_code, ProfileAssistant::kSuccess);
+  ASSERT_EQ(return_code, ProfmanResult::kSuccess);
 
   // Verify the result: it should be equal to info2 since info1 is a regular profile
   // and should be ignored.
@@ -1918,15 +2121,15 @@
   const uint16_t kNumberOfClassesInCurProfile = 6110;  // Threshold is 2%.
 
   const DexFile* dex1_7000 = BuildDex("location1_7000",
-                                      /*checksum=*/ 7001,
+                                      /*location_checksum=*/ 7001,
                                       "LUnique1_7000;",
                                       /*num_method_ids=*/ 0,
-                                      /*num_type_ids=*/ 7000);
+                                      /*num_class_ids=*/ 7000);
   const DexFile* dex2_7000 = BuildDex("location2_7000",
-                                      /*checksum=*/ 7002,
+                                      /*location_checksum=*/ 7002,
                                       "LUnique2_7000;",
                                       /*num_method_ids=*/ 0,
-                                      /*num_type_ids=*/ 7000);
+                                      /*num_class_ids=*/ 7000);
 
   ScratchFile profile;
   ScratchFile reference_profile;
@@ -1942,7 +2145,7 @@
   std::vector<const std::string> extra_args({"--force-merge"});
   int return_code = ProcessProfiles(profile_fds, reference_profile_fd, extra_args);
 
-  ASSERT_EQ(return_code, ProfileAssistant::kSuccess);
+  ASSERT_EQ(return_code, ProfmanResult::kSuccess);
 
   // Check that the result is the aggregation.
   ProfileCompilationInfo result;
@@ -1974,7 +2177,8 @@
 
   // Run profman and pass the dex file with --apk-fd.
   android::base::unique_fd apk_fd(
-      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));  // NOLINT
+      // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
   ASSERT_GE(apk_fd.get(), 0);
 
   std::string profman_cmd = GetProfmanCmd();
@@ -1987,7 +2191,7 @@
   argv_str.push_back("--boot-image-merge");
   std::string error;
 
-  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfileAssistant::kSuccess) << error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSuccess) << error;
 
   // Verify that we can load the result and that it equals to what we saved.
   ProfileCompilationInfo result(/*for_boot_image=*/ true);
@@ -2009,17 +2213,16 @@
   int reference_profile_fd = GetFd(profile2);
   std::vector<const std::string> boot_image_args({"--boot-image-merge"});
   ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, boot_image_args),
-            ProfileAssistant::kErrorDifferentVersions);
-  ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd),
-            ProfileAssistant::kErrorBadProfiles);
+            ProfmanResult::kErrorDifferentVersions);
+  ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd), ProfmanResult::kErrorBadProfiles);
 
   // Reverse the order of the profiles to verify we get the same behaviour.
   profile_fds[0] = GetFd(profile2);
   reference_profile_fd = GetFd(profile1);
   ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, boot_image_args),
-            ProfileAssistant::kErrorBadProfiles);
+            ProfmanResult::kErrorBadProfiles);
   ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd),
-            ProfileAssistant::kErrorDifferentVersions);
+            ProfmanResult::kErrorDifferentVersions);
 }
 
 // Under default behaviour we will abort if we cannot load a profile during a merge
@@ -2042,7 +2245,7 @@
   // With force-merge we should merge successfully.
   std::vector<const std::string> extra_args({"--force-merge", "--boot-image-merge"});
   ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, extra_args),
-            ProfileAssistant::kSuccess);
+            ProfmanResult::kSuccess);
 
   ProfileCompilationInfo result(/*for_boot_image=*/ true);
   ASSERT_TRUE(result.Load(reference_profile_fd));
@@ -2051,7 +2254,7 @@
   // Without force-merge we should fail.
   std::vector<const std::string> extra_args2({"--boot-image-merge"});
   ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, extra_args2),
-            ProfileAssistant::kErrorBadProfiles);
+            ProfmanResult::kErrorBadProfiles);
 }
 
 }  // namespace art
diff --git a/profman/profman.cc b/profman/profman.cc
index 1968468..375a489 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -36,7 +36,6 @@
 #include "android-base/parsebool.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
-
 #include "base/array_ref.h"
 #include "base/dumpable.h"
 #include "base/logging.h"  // For InitLogging.
@@ -64,6 +63,7 @@
 #include "profile/profile_boot_info.h"
 #include "profile/profile_compilation_info.h"
 #include "profile_assistant.h"
+#include "profman/profman_result.h"
 
 namespace art {
 
@@ -118,7 +118,10 @@
   UsageError("");
   UsageError("  --profile-file=<filename>: specify profiler output file to use for compilation.");
   UsageError("      Can be specified multiple time, in which case the data from the different");
-  UsageError("      profiles will be aggregated.");
+  UsageError("      profiles will be aggregated. Can also be specified zero times, in which case");
+  UsageError("      profman will still analyze the reference profile against the given --apk and");
+  UsageError("      return exit code based on whether the reference profile is empty and whether");
+  UsageError("      an error occurs, but no merge will happen.");
   UsageError("");
   UsageError("  --profile-file-fd=<number>: same as --profile-file but accepts a file descriptor.");
   UsageError("      Cannot be used together with --profile-file.");
@@ -126,8 +129,8 @@
   UsageError("  --reference-profile-file=<filename>: specify a reference profile.");
   UsageError("      The data in this file will be compared with the data obtained by merging");
   UsageError("      all the files specified with --profile-file or --profile-file-fd.");
-  UsageError("      If the exit code is EXIT_COMPILE then all --profile-file will be merged into");
-  UsageError("      --reference-profile-file. ");
+  UsageError("      If the exit code is ProfmanResult::kCompile then all --profile-file will be");
+  UsageError("      merged into --reference-profile-file. ");
   UsageError("");
   UsageError("  --reference-profile-file-fd=<number>: same as --reference-profile-file but");
   UsageError("      accepts a file descriptor. Cannot be used together with");
@@ -194,7 +197,7 @@
   UsageError("      the min percent of new classes to trigger a compilation.");
   UsageError("");
 
-  exit(EXIT_FAILURE);
+  exit(ProfmanResult::kErrorUsage);
 }
 
 // Note: make sure you update the Usage if you change these values.
@@ -501,11 +504,10 @@
     }
   };
 
-  ProfileAssistant::ProcessingResult ProcessProfiles() {
-    // Validate that at least one profile file was passed, as well as a reference profile.
-    if (profile_files_.empty() && profile_files_fd_.empty()) {
-      Usage("No profile files specified.");
-    }
+  ProfmanResult::ProcessingResult ProcessProfiles() {
+    // Validate that a reference profile was passed, at the very least. It's okay that profiles are
+    // missing, in which case profman will still analyze the reference profile (to check whether
+    // it's empty), but no merge will happen.
     if (reference_profile_file_.empty() && !FdIsValid(reference_profile_file_fd_)) {
       Usage("No reference profile file specified.");
     }
@@ -518,7 +520,7 @@
     // Check if we have any apks which we should use to filter the profile data.
     std::set<ProfileFilterKey> profile_filter_keys;
     if (!GetProfileFilterKeyFromApks(&profile_filter_keys)) {
-      return ProfileAssistant::kErrorIO;
+      return ProfmanResult::kErrorIO;
     }
 
     // Build the profile filter function. If the set of keys is empty it means we
@@ -536,9 +538,9 @@
             }
         };
 
-    ProfileAssistant::ProcessingResult result;
+    ProfmanResult::ProcessingResult result;
 
-    if (profile_files_.empty()) {
+    if (reference_profile_file_.empty()) {
       // The file doesn't need to be flushed here (ProcessProfiles will do it)
       // so don't check the usage.
       File file(reference_profile_file_fd_, false);
@@ -605,25 +607,29 @@
     static constexpr bool kVerifyChecksum = true;
     for (size_t i = 0; i < dex_locations_.size(); ++i) {
       std::string error_msg;
-      const ArtDexFileLoader dex_file_loader;
       std::vector<std::unique_ptr<const DexFile>> dex_files_for_location;
       // We do not need to verify the apk for processing profiles.
       if (use_apk_fd_list) {
-        if (dex_file_loader.OpenZip(apks_fd_[i],
-                                    dex_locations_[i],
-                                    /* verify= */ false,
-                                    kVerifyChecksum,
-                                    &error_msg,
-                                    &dex_files_for_location)) {
-        } else {
-          LOG(ERROR) << "OpenZip failed for '" << dex_locations_[i] << "' " << error_msg;
+          ArtDexFileLoader dex_file_loader(apks_fd_[i], dex_locations_[i]);
+          if (dex_file_loader.Open(/*verify=*/false,
+                                   kVerifyChecksum,
+                                   /*allow_no_dex_files=*/true,
+                                   &error_msg,
+                                   &dex_files_for_location)) {
+          } else {
+            LOG(ERROR) << "OpenZip failed for '" << dex_locations_[i] << "' " << error_msg;
+            return false;
+          }
+      } else {
+        File file(apk_files_[i], O_RDONLY, /*check_usage=*/false);
+        if (file.Fd() < 0) {
+          PLOG(ERROR) << "Unable to open '" << apk_files_[i] << "'";
           return false;
         }
-      } else {
-        if (dex_file_loader.Open(apk_files_[i].c_str(),
-                                 dex_locations_[i],
-                                 /* verify= */ false,
+        ArtDexFileLoader dex_file_loader(file.Release(), dex_locations_[i]);
+        if (dex_file_loader.Open(/*verify=*/false,
                                  kVerifyChecksum,
+                                 /*allow_no_dex_files=*/true,
                                  &error_msg,
                                  &dex_files_for_location)) {
         } else {
@@ -1205,8 +1211,23 @@
       for (std::string_view t :
            SplitString(ic_line.substr(1), kProfileParsingInlineChacheTargetSep)) {
         InlineCacheSegment out;
-        DCHECK_EQ(t[0], 'L') << "Target is not a class? " << t;
-        size_t recv_end = t.find_first_of(';');
+        // The target may be an array for methods defined in `j.l.Object`, such as `clone()`.
+        size_t recv_end;
+        if (UNLIKELY(t[0] == '[')) {
+          recv_end = t.find_first_not_of('[', 1u);
+          DCHECK_NE(recv_end, std::string_view::npos);
+          if (t[recv_end] == 'L') {
+            recv_end = t.find_first_of(';', recv_end + 1u);
+            DCHECK_NE(recv_end, std::string_view::npos);
+          } else {
+            // Primitive array.
+            DCHECK_NE(Primitive::GetType(t[recv_end]), Primitive::kPrimNot);
+          }
+        } else {
+          DCHECK_EQ(t[0], 'L') << "Target is not a class? " << t;
+          recv_end = t.find_first_of(';', 1u);
+          DCHECK_NE(recv_end, std::string_view::npos);
+        }
         out.receiver_ = t.substr(0, recv_end + 1);
         Split(t.substr(recv_end + 1), kProfileParsingTypeSep, &out.inline_caches_);
         res->push_back(out);
@@ -1854,7 +1875,7 @@
     return copy_and_update_profile_key_;
   }
 
-  int32_t CopyAndUpdateProfileKey() {
+  ProfmanResult::CopyAndUpdateResult CopyAndUpdateProfileKey() {
     // Validate that at least one profile file was passed, as well as a reference profile.
     if (!(profile_files_.size() == 1 ^ profile_files_fd_.size() == 1)) {
       Usage("Only one profile file should be specified.");
@@ -1867,10 +1888,6 @@
       Usage("No apk files specified");
     }
 
-    static constexpr int32_t kErrorFailedToUpdateProfile = -1;
-    static constexpr int32_t kErrorFailedToSaveProfile = -2;
-    static constexpr int32_t kErrorFailedToLoadProfile = -3;
-
     bool use_fds = profile_files_fd_.size() == 1;
 
     ProfileCompilationInfo profile;
@@ -1882,15 +1899,19 @@
       // Open the dex files to look up classes and methods.
       std::vector<std::unique_ptr<const DexFile>> dex_files;
       OpenApkFilesFromLocations(&dex_files);
-      if (!profile.UpdateProfileKeys(dex_files)) {
-        return kErrorFailedToUpdateProfile;
+      bool matched = false;
+      if (!profile.UpdateProfileKeys(dex_files, &matched)) {
+        return ProfmanResult::kCopyAndUpdateErrorFailedToUpdateProfile;
       }
       bool result = use_fds
           ? profile.Save(reference_profile_file_fd_)
           : profile.Save(reference_profile_file_, /*bytes_written=*/ nullptr);
-      return result ? 0 : kErrorFailedToSaveProfile;
+      if (!result) {
+        return ProfmanResult::kCopyAndUpdateErrorFailedToSaveProfile;
+      }
+      return matched ? ProfmanResult::kCopyAndUpdateSuccess : ProfmanResult::kCopyAndUpdateNoMatch;
     } else {
-      return kErrorFailedToLoadProfile;
+      return ProfmanResult::kCopyAndUpdateErrorFailedToLoadProfile;
     }
   }
 
@@ -1950,7 +1971,7 @@
   return ics.Dump(os);
 }
 
-// See ProfileAssistant::ProcessingResult for return codes.
+// See ProfmanResult for return codes.
 static int profman(int argc, char** argv) {
   ProfMan profman;
 
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 9e0e78e..1c4b871 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -37,6 +37,12 @@
 libart_cc_defaults {
     name: "libart_nativeunwind_defaults",
     target: {
+        host: {
+            cflags: [
+                "-fsanitize-address-use-after-return=never",
+                "-Wno-unused-command-line-argument",
+            ],
+        },
         android_arm: {
             // Arm 32 bit does not produce complete exidx unwind information
             // so keep the .debug_frame which is relatively small and does
@@ -53,6 +59,11 @@
                 keep_symbols: true,
             },
         },
+        android_riscv64: {
+            strip: {
+                keep_symbols: true,
+            },
+        },
         android_x86: {
             strip: {
                 keep_symbols: true,
@@ -110,6 +121,7 @@
         "art_method.cc",
         "backtrace_helper.cc",
         "barrier.cc",
+        "base/gc_visited_arena_pool.cc",
         "base/locks.cc",
         "base/mem_map_arena_pool.cc",
         "base/mutex.cc",
@@ -130,7 +142,7 @@
         "exec_utils.cc",
         "fault_handler.cc",
         "gc/allocation_record.cc",
-        "gc/allocator/dlmalloc.cc",
+        "gc/allocator/art-dlmalloc.cc",
         "gc/allocator/rosalloc.cc",
         "gc/accounting/bitmap.cc",
         "gc/accounting/card_table.cc",
@@ -142,6 +154,7 @@
         "gc/collector/garbage_collector.cc",
         "gc/collector/immune_region.cc",
         "gc/collector/immune_spaces.cc",
+        "gc/collector/mark_compact.cc",
         "gc/collector/mark_sweep.cc",
         "gc/collector/partial_mark_sweep.cc",
         "gc/collector/semi_space.cc",
@@ -173,11 +186,8 @@
         "interpreter/interpreter.cc",
         "interpreter/interpreter_cache.cc",
         "interpreter/interpreter_common.cc",
-        "interpreter/interpreter_intrinsics.cc",
         "interpreter/interpreter_switch_impl0.cc",
         "interpreter/interpreter_switch_impl1.cc",
-        "interpreter/interpreter_switch_impl2.cc",
-        "interpreter/interpreter_switch_impl3.cc",
         "interpreter/lock_count_data.cc",
         "interpreter/shadow_frame.cc",
         "interpreter/unstarted_runtime.cc",
@@ -194,7 +204,7 @@
         "jni/jni_env_ext.cc",
         "jni/jni_id_manager.cc",
         "jni/jni_internal.cc",
-        "linear_alloc.cc",
+        "jni/local_reference_table.cc",
         "method_handles.cc",
         "metrics/reporter.cc",
         "mirror/array.cc",
@@ -209,6 +219,7 @@
         "mirror/method_handles_lookup.cc",
         "mirror/method_type.cc",
         "mirror/object.cc",
+        "mirror/stack_frame_info.cc",
         "mirror/stack_trace_element.cc",
         "mirror/string.cc",
         "mirror/throwable.cc",
@@ -225,6 +236,7 @@
         "native/dalvik_system_ZygoteHooks.cc",
         "native/java_lang_Class.cc",
         "native/java_lang_Object.cc",
+        "native/java_lang_StackStreamFactory.cc",
         "native/java_lang_String.cc",
         "native/java_lang_StringFactory.cc",
         "native/java_lang_System.cc",
@@ -254,6 +266,7 @@
         "oat.cc",
         "oat_file.cc",
         "oat_file_assistant.cc",
+        "oat_file_assistant_context.cc",
         "oat_file_manager.cc",
         "oat_quick_method_header.cc",
         "object_lock.cc",
@@ -269,12 +282,14 @@
         "runtime.cc",
         "runtime_callbacks.cc",
         "runtime_common.cc",
+        "runtime_image.cc",
         "runtime_intrinsics.cc",
         "runtime_options.cc",
         "scoped_thread_state_change.cc",
         "signal_catcher.cc",
         "stack.cc",
         "stack_map.cc",
+        "startup_completed_task.cc",
         "string_builder_append.cc",
         "thread.cc",
         "thread_list.cc",
@@ -301,6 +316,8 @@
         "arch/arm/registers_arm.cc",
         "arch/arm64/instruction_set_features_arm64.cc",
         "arch/arm64/registers_arm64.cc",
+        "arch/riscv64/instruction_set_features_riscv64.cc",
+        "arch/riscv64/registers_riscv64.cc",
         "arch/x86/instruction_set_features_x86.cc",
         "arch/x86/registers_x86.cc",
         "arch/x86_64/registers_x86_64.cc",
@@ -327,6 +344,7 @@
         arm: {
             srcs: [
                 "interpreter/mterp/nterp.cc",
+                "interpreter/mterp/nterp_impl.cc",
                 ":libart_mterp.armng",
                 "arch/arm/context_arm.cc",
                 "arch/arm/entrypoints_init_arm.cc",
@@ -342,6 +360,7 @@
         arm64: {
             srcs: [
                 "interpreter/mterp/nterp.cc",
+                "interpreter/mterp/nterp_impl.cc",
                 ":libart_mterp.arm64ng",
                 "arch/arm64/context_arm64.cc",
                 "arch/arm64/entrypoints_init_arm64.cc",
@@ -353,9 +372,24 @@
                 "arch/arm64/fault_handler_arm64.cc",
             ],
         },
+        riscv64: {
+            srcs: [
+                ":libart_mterp.riscv64",
+                "arch/riscv64/context_riscv64.cc",
+                "arch/riscv64/entrypoints_init_riscv64.cc",
+                "arch/riscv64/fault_handler_riscv64.cc",
+                "arch/riscv64/jni_entrypoints_riscv64.S",
+                "arch/riscv64/quick_entrypoints_riscv64.S",
+                "arch/riscv64/thread_riscv64.cc",
+                "interpreter/mterp/nterp.cc",
+                "interpreter/mterp/nterp_impl.cc",
+                "monitor_pool.cc",
+            ],
+        },
         x86: {
             srcs: [
                 "interpreter/mterp/nterp.cc",
+                "interpreter/mterp/nterp_impl.cc",
                 ":libart_mterp.x86ng",
                 "arch/x86/context_x86.cc",
                 "arch/x86/entrypoints_init_x86.cc",
@@ -377,6 +411,7 @@
                 // Note that the fault_handler_x86.cc is not a mistake.  This file is
                 // shared between the x86 and x86_64 architectures.
                 "interpreter/mterp/nterp.cc",
+                "interpreter/mterp/nterp_impl.cc",
                 ":libart_mterp.x86_64ng",
                 "arch/x86_64/context_x86_64.cc",
                 "arch/x86_64/entrypoints_init_x86_64.cc",
@@ -415,11 +450,12 @@
             ],
             static_libs: [
                 "libstatslog_art",
-                "libtinyxml2",
             ],
             generated_sources: [
                 "apex-info-list-tinyxml",
+                "art-apex-cache-info",
             ],
+            tidy_disabled_srcs: [":art-apex-cache-info"],
         },
         android_arm: {
             ldflags: JIT_DEBUG_REGISTER_CODE_LDFLAGS,
@@ -439,6 +475,10 @@
                 "runtime_linux.cc",
                 "thread_linux.cc",
             ],
+            cflags: [
+                "-fsanitize-address-use-after-return=never",
+                "-Wno-unused-command-line-argument",
+            ],
             shared_libs: [
                 "libz", // For adler32.
             ],
@@ -456,17 +496,20 @@
     header_libs: [
         "art_cmdlineparser_headers",
         "cpp-define-generator-definitions",
+        "dlmalloc",
         "jni_platform_headers",
         "libart_headers",
         "libnativehelper_header_only",
     ],
-    export_header_lib_headers: ["libart_headers"],
+    export_header_lib_headers: [
+        "dlmalloc",
+        "libart_headers",
+    ],
     whole_static_libs: [
         "libcpu_features",
     ],
     shared_libs: [
         "libartpalette",
-        "libbacktrace",
         "libbase", // For common macros.
         "liblog",
         "liblz4",
@@ -496,7 +539,6 @@
     name: "libart_static_base_defaults",
     whole_static_libs: [
         "libartpalette",
-        "libbacktrace",
         "libbase",
         "liblog",
         "liblz4",
@@ -509,6 +551,12 @@
         "libz",
     ],
     target: {
+        host: {
+            cflags: [
+                "-fsanitize-address-use-after-return=never",
+                "-Wno-unused-command-line-argument",
+            ],
+        },
         bionic: {
             whole_static_libs: [
                 "libasync_safe", // libunwindstack dependency on Bionic.
@@ -561,6 +609,7 @@
         "gc/allocator/rosalloc.h",
         "gc/collector_type.h",
         "gc/collector/gc_type.h",
+        "gc/collector/mark_compact.h",
         "gc/space/region_space.h",
         "gc/space/space.h",
         "gc/weak_root_state.h",
@@ -569,7 +618,9 @@
         "indirect_reference_table.h",
         "jdwp_provider.h",
         "jni_id_type.h",
+        "linear_alloc.h",
         "lock_word.h",
+        "oat.h",
         "oat_file.h",
         "process_state.h",
         "reflective_value_visitor.h",
@@ -623,6 +674,12 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        // libart doesn't go into test_broken_com.android.art, but the libart-broken
+        // needs to have the same apex_available list as its dependencies in order
+        // to compile against their sources. Then that change comes back up to affect
+        // libart as well, because it also needs to have the same apex_available as its
+        // dependencies.
+        "test_broken_com.android.art",
     ],
     afdo: true,
 }
@@ -635,6 +692,11 @@
     gtest: false,
     cflags: ["-DART_CRASH_RUNTIME_DELIBERATELY"],
     apex_available: [
+        // libart-broken only goes into test_broken_com.android.art, but the libart-broken
+        // needs to have the same apex_available list as its dependencies in order
+        // to compile against their sources.
+        "com.android.art",
+        "com.android.art.debug",
         "test_broken_com.android.art",
     ],
 }
@@ -665,11 +727,20 @@
         // apex_available lists need to be the same for internal libs to avoid
         // stubs, and this depends on libsigchain.
         "com.android.art",
+        "test_broken_com.android.art",
     ],
 }
 
 art_cc_defaults {
     name: "libart-runtime-gtest-defaults",
+    target: {
+        host: {
+            cflags: [
+                "-fsanitize-address-use-after-return=never",
+                "-Wno-unused-command-line-argument",
+            ],
+        },
+    },
     tidy_timeout_srcs: [
         "common_runtime_test.cc",
     ],
@@ -679,9 +750,11 @@
     ],
     shared_libs: [
         "libbase",
+        "libz", // libziparchive dependency; must be repeated here since it's a static lib.
     ],
     static_libs: [
         "libprocinfo",
+        "libziparchive",
     ],
     header_libs: [
         "libnativehelper_header_only",
@@ -714,6 +787,14 @@
 
 art_cc_defaults {
     name: "art_runtime_tests_defaults",
+    target: {
+        host: {
+            cflags: [
+                "-fsanitize-address-use-after-return=never",
+                "-Wno-unused-command-line-argument",
+            ],
+        },
+    },
     data: [
         ":art-gtest-jars-AllFields",
         ":art-gtest-jars-ErroneousA",
@@ -741,6 +822,7 @@
         ":art-gtest-jars-MyClass",
         ":art-gtest-jars-MyClassNatives",
         ":art-gtest-jars-Nested",
+        ":art-gtest-jars-NonStaticLeafMethods",
         ":art-gtest-jars-Packages",
         ":art-gtest-jars-ProfileTestMultiDex",
         ":art-gtest-jars-ProtoCompare",
@@ -773,13 +855,15 @@
     srcs: [
         "app_info_test.cc",
         "arch/arch_test.cc",
+        "arch/arm/instruction_set_features_arm_test.cc",
+        "arch/arm64/instruction_set_features_arm64_test.cc",
         "arch/instruction_set_features_test.cc",
         "arch/memcmp16_test.cc",
         "arch/stub_test.cc",
-        "arch/arm/instruction_set_features_arm_test.cc",
-        "arch/arm64/instruction_set_features_arm64_test.cc",
+        "arch/riscv64/instruction_set_features_riscv64_test.cc",
         "arch/x86/instruction_set_features_x86_test.cc",
         "arch/x86_64/instruction_set_features_x86_64_test.cc",
+        "art_method_test.cc",
         "barrier_test.cc",
         "base/message_queue_test.cc",
         "base/mutex_test.cc",
@@ -799,12 +883,12 @@
         "gc/heap_test.cc",
         "gc/heap_verification_test.cc",
         "gc/reference_queue_test.cc",
-        "gc/space/dlmalloc_space_static_test.cc",
         "gc/space/dlmalloc_space_random_test.cc",
+        "gc/space/dlmalloc_space_static_test.cc",
         "gc/space/image_space_test.cc",
         "gc/space/large_object_space_test.cc",
-        "gc/space/rosalloc_space_static_test.cc",
         "gc/space/rosalloc_space_random_test.cc",
+        "gc/space/rosalloc_space_static_test.cc",
         "gc/space/space_create_test.cc",
         "gc/system_weak_test.cc",
         "gc/task_processor_test.cc",
@@ -817,11 +901,13 @@
         "intern_table_test.cc",
         "interpreter/safe_math_test.cc",
         "interpreter/unstarted_runtime_test.cc",
+        "jit/jit_load_test.cc",
         "jit/jit_memory_region_test.cc",
         "jit/profile_saver_test.cc",
         "jit/profiling_info_test.cc",
         "jni/java_vm_ext_test.cc",
         "jni/jni_internal_test.cc",
+        "jni/local_reference_table_test.cc",
         "method_handles_test.cc",
         "metrics/reporter_test.cc",
         "mirror/dex_cache_test.cc",
@@ -830,12 +916,14 @@
         "mirror/var_handle_test.cc",
         "monitor_pool_test.cc",
         "monitor_test.cc",
-        "oat_file_test.cc",
+        "native_stack_dump_test.cc",
         "oat_file_assistant_test.cc",
+        "oat_file_test.cc",
         "parsed_options_test.cc",
         "prebuilt_tools_test.cc",
         "proxy_test.cc",
         "reference_table_test.cc",
+        "reflection_test.cc",
         "runtime_callbacks_test.cc",
         "runtime_test.cc",
         "subtype_check_info_test.cc",
@@ -848,7 +936,12 @@
         "verifier/reg_type_test.cc",
     ],
     shared_libs: [
-        "libbacktrace",
+        "libunwindstack",
+        "libz", // libziparchive dependency; must be repeated here since it's a static lib.
+    ],
+    static_libs: [
+        "libgmock",
+        "libziparchive",
     ],
     header_libs: [
         "art_cmdlineparser_headers", // For parsed_options_test.
@@ -877,6 +970,7 @@
         "art_standalone_gtest_defaults",
         "art_runtime_tests_defaults",
     ],
+    data: [":generate-boot-image"],
     target: {
         host: {
             required: ["dex2oat"],
@@ -889,58 +983,6 @@
     test_config: "art_standalone_runtime_tests.xml",
 }
 
-art_cc_defaults {
-    name: "art_runtime_compiler_tests_defaults",
-    tidy_timeout_srcs: [
-        "reflection_test.cc",
-    ],
-    srcs: [
-        "reflection_test.cc",
-    ],
-    data: [
-        ":art-gtest-jars-Main",
-        ":art-gtest-jars-NonStaticLeafMethods",
-        ":art-gtest-jars-StaticLeafMethods",
-    ],
-}
-
-// Version of ART gtest `art_runtime_compiler_tests` bundled with the ART APEX on target.
-// TODO(b/192274705): Remove this module when the migration to standalone ART gtests is complete.
-art_cc_test {
-    name: "art_runtime_compiler_tests",
-    defaults: [
-        "art_gtest_defaults",
-        "art_runtime_compiler_tests_defaults",
-    ],
-    shared_libs: [
-        "libartd-compiler",
-    ],
-    target: {
-        host: {
-            required: ["dex2oatd"],
-        },
-    },
-}
-
-// Standalone version of ART gtest `art_runtime_compiler_tests`, not bundled with the ART APEX on
-// target.
-art_cc_test {
-    name: "art_standalone_runtime_compiler_tests",
-    defaults: [
-        "art_standalone_gtest_defaults",
-        "art_runtime_compiler_tests_defaults",
-    ],
-    shared_libs: [
-        "libart-compiler",
-    ],
-    target: {
-        host: {
-            required: ["dex2oat"],
-        },
-    },
-    test_config: "art_standalone_runtime_compiler_tests.xml",
-}
-
 genrule {
     name: "libart_mterp.x86ng",
     out: ["mterp_x86ng.S"],
@@ -997,6 +1039,20 @@
     cmd: "$(location interpreter/mterp/gen_mterp.py) $(out) $(in)",
 }
 
+genrule {
+    name: "libart_mterp.riscv64",
+    out: ["mterp_riscv64.S"],
+    srcs: [
+        "interpreter/mterp/riscv64/*.S",
+    ],
+    tool_files: [
+        "interpreter/mterp/gen_mterp.py",
+        "interpreter/mterp/common/gen_setup.py",
+        ":art_libdexfile_dex_instruction_list_header",
+    ],
+    cmd: "$(location interpreter/mterp/gen_mterp.py) $(out) $(in)",
+}
+
 cc_library_static {
     name: "libstatslog_art",
     defaults: ["art_defaults"],
diff --git a/runtime/alloc_instrumentation.md b/runtime/alloc_instrumentation.md
index 513bbe3..66e9a6a 100644
--- a/runtime/alloc_instrumentation.md
+++ b/runtime/alloc_instrumentation.md
@@ -17,7 +17,7 @@
 - These in turn are called by `SetStatsEnabled()`, `SetAllocationListener()`, et al, which
 require the mutator lock is not held.
 
-- With a started runtime, `SetEntrypointsInstrumented()` calls `ScopedSupendAll(`) before updating
+- With a started runtime, `SetEntrypointsInstrumented()` calls `ScopedSuspendAll(`) before updating
   the function table.
 
 Mutual exclusion in the dispatch table is thus ensured by the fact that it is only updated while
diff --git a/runtime/app_info.cc b/runtime/app_info.cc
index c72951e..3fef565 100644
--- a/runtime/app_info.cc
+++ b/runtime/app_info.cc
@@ -93,6 +93,12 @@
         << "\nodex_status=" << odex_status;
 }
 
+bool AppInfo::HasRegisteredAppInfo() {
+  MutexLock mu(Thread::Current(), update_mutex_);
+
+  return package_name_.has_value();
+}
+
 void AppInfo::GetPrimaryApkOptimizationStatus(
     std::string* out_compiler_filter,
     std::string* out_compilation_reason) {
@@ -110,6 +116,13 @@
   *out_compilation_reason = kUnknownValue;
 }
 
+AppInfo::CodeType AppInfo::GetRegisteredCodeType(const std::string& code_path) {
+  MutexLock mu(Thread::Current(), update_mutex_);
+
+  const auto it = registered_code_locations_.find(code_path);
+  return it != registered_code_locations_.end() ? it->second.code_type : CodeType::kUnknown;
+}
+
 std::ostream& operator<<(std::ostream& os, AppInfo& rhs) {
   MutexLock mu(Thread::Current(), rhs.update_mutex_);
 
@@ -130,4 +143,17 @@
   return os;
 }
 
+std::string AppInfo::GetPrimaryApkReferenceProfile() {
+  MutexLock mu(Thread::Current(), update_mutex_);
+
+  for (const auto& it : registered_code_locations_) {
+    const CodeLocationInfo& cli = it.second;
+    if (cli.code_type == CodeType::kPrimaryApk) {
+      return cli.ref_profile_path.value_or("");
+    }
+  }
+  return "";
+}
+
+
 }  // namespace art
diff --git a/runtime/app_info.h b/runtime/app_info.h
index 68f2c58..0b8132a 100644
--- a/runtime/app_info.h
+++ b/runtime/app_info.h
@@ -68,15 +68,29 @@
                           const std::string& compilation_reason,
                           const std::string& odex_status);
 
-  // Extracts the optimization status of the primary apk into the given arguments.
+  // Extracts the optimization status of the primary APK into the given arguments.
   // If there are multiple primary APKs registed via RegisterAppInfo, the method
   // will assign the status of the first APK, sorted by the location name.
   //
-  // Assigns "unknown" if there is no primary apk or the optimization status was
+  // Assigns "unknown" if there is no primary APK or the optimization status was
   // not set via RegisterOdexStatus,
   void GetPrimaryApkOptimizationStatus(std::string* out_compiler_filter,
                                        std::string* out_compilation_reason);
 
+  // Returns the reference profile path of the primary APK.
+  // If there are multiple primary APKs registed via RegisterAppInfo, the method
+  // will return the profile of the first APK, sorted by the location name.
+  //
+  // Returns an empty string if there is no primary APK.
+  std::string GetPrimaryApkReferenceProfile();
+
+  // Whether we've received a call to RegisterAppInfo.
+  bool HasRegisteredAppInfo();
+
+  // The registered code type for a given code path. Note that this will
+  // be kUnknown until an explicit registration for that path has been made.
+  CodeType GetRegisteredCodeType(const std::string& code_path);
+
  private:
   // Encapsulates optimization information about a particular code location.
   struct CodeLocationInfo {
diff --git a/runtime/app_info_test.cc b/runtime/app_info_test.cc
index 4a365de..51dd42f 100644
--- a/runtime/app_info_test.cc
+++ b/runtime/app_info_test.cc
@@ -24,12 +24,17 @@
 
 TEST(AppInfoTest, RegisterAppInfo) {
   AppInfo app_info;
+  EXPECT_FALSE(app_info.HasRegisteredAppInfo());
+  EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kUnknown);
+
   app_info.RegisterAppInfo(
       "package_name",
       std::vector<std::string>({"code_location"}),
       "",
       "",
       AppInfo::CodeType::kPrimaryApk);
+  EXPECT_TRUE(app_info.HasRegisteredAppInfo());
+  EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk);
 
   std::string filter;
   std::string reason;
@@ -48,11 +53,13 @@
       "",
       "",
       AppInfo::CodeType::kPrimaryApk);
+  EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk);
   app_info.RegisterOdexStatus(
       "code_location",
       "filter",
       "reason",
       "odex_status");
+  EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk);
 
   std::string filter;
   std::string reason;
@@ -69,17 +76,22 @@
       "filter",
       "reason",
       "odex_status");
+  EXPECT_FALSE(app_info.HasRegisteredAppInfo());
   app_info.RegisterOdexStatus(
       "code_location2",
       "filter2",
       "reason2",
       "odex_status");
+  EXPECT_FALSE(app_info.HasRegisteredAppInfo());
   app_info.RegisterAppInfo(
       "package_name",
       std::vector<std::string>({"code_location"}),
       "",
       "",
       AppInfo::CodeType::kPrimaryApk);
+  EXPECT_TRUE(app_info.HasRegisteredAppInfo());
+  EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk);
+  EXPECT_EQ(app_info.GetRegisteredCodeType("code_location2"), AppInfo::CodeType::kUnknown);
 
   std::string filter;
   std::string reason;
@@ -110,7 +122,7 @@
       "filter",
       "reason",
       "odex_status");
-
+  EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kSplitApk);
 
   // The optimization status is unknown since we don't have primary apks.
   app_info.GetPrimaryApkOptimizationStatus(&filter, &reason);
diff --git a/runtime/arch/arch_test.cc b/runtime/arch/arch_test.cc
index 23213d9..83fd986 100644
--- a/runtime/arch/arch_test.cc
+++ b/runtime/arch/arch_test.cc
@@ -18,27 +18,13 @@
 
 #include "art_method-inl.h"
 #include "base/callee_save_type.h"
+#include "base/common_art_test.h"
 #include "entrypoints/quick/callee_save_frame.h"
-#include "common_runtime_test.h"
 #include "quick/quick_method_frame_info.h"
 
 namespace art {
 
-class ArchTest : public CommonRuntimeTest {
- protected:
-  void SetUpRuntimeOptions(RuntimeOptions *options) override {
-    // Use 64-bit ISA for runtime setup to make method size potentially larger
-    // than necessary (rather than smaller) during CreateCalleeSaveMethod
-    options->push_back(std::make_pair("imageinstructionset", "x86_64"));
-  }
-
-  // Do not do any of the finalization. We don't want to run any code, we don't need the heap
-  // prepared, it actually will be a problem with setting the instruction set to x86_64 in
-  // SetUpRuntimeOptions.
-  void FinalizeSetup() override {
-    ASSERT_EQ(InstructionSet::kX86_64, Runtime::Current()->GetInstructionSet());
-  }
-};
+class ArchTest : public CommonArtTest {};
 
 // Grab architecture specific constants.
 namespace arm {
diff --git a/runtime/arch/arm/asm_support_arm.S b/runtime/arch/arm/asm_support_arm.S
index 23d82ba..dbc6a5d 100644
--- a/runtime/arch/arm/asm_support_arm.S
+++ b/runtime/arch/arm/asm_support_arm.S
@@ -27,10 +27,8 @@
 // Register holding Thread::Current().
 #define rSELF r9
 
-#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+#ifdef RESERVE_MARKING_REGISTER
 // Marking Register, holding Thread::Current()->GetIsGcMarking().
-// Only used with the Concurrent Copying (CC) garbage
-// collector, with the Baker read barrier configuration.
 #define rMR r8
 #endif
 
@@ -65,6 +63,10 @@
     .endif
 .endm
 
+.macro CFI_REMEMBER_STATE
+    .cfi_remember_state
+.endm
+
 // The spec is not clear whether the CFA is part of the saved state and tools
 // differ in the behaviour, so explicitly set the CFA to avoid any ambiguity.
 // The restored CFA state should match the CFA state during CFI_REMEMBER_STATE.
@@ -88,7 +90,7 @@
     // Prefix the assembly code with 0xFFs, which means there is no method header.
     .byte 0xFF, 0xFF, 0xFF, 0xFF
     // Cache alignment for function entry.
-    // NB: 0xFF because there is a bug in balign where 0x00 creates nop instructions.
+    // Use 0xFF as the last 4 bytes of alignment stand for OatQuickMethodHeader.
     .balign \alignment, 0xFF
 \name:
     .cfi_startproc
@@ -160,7 +162,7 @@
 // entrypoints that possibly (directly or indirectly) perform a
 // suspend check (before they return).
 .macro REFRESH_MARKING_REGISTER
-#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+#ifdef RESERVE_MARKING_REGISTER
     ldr rMR, [rSELF, #THREAD_IS_GC_MARKING_OFFSET]
 #endif
 .endm
@@ -208,9 +210,6 @@
     // later; but it's not worth handling this special case.
     push {r1-r3, r5-r8, r10-r11, lr}   @ 10 words of callee saves and args.
     .cfi_adjust_cfa_offset 40
-    .cfi_rel_offset r1, 0
-    .cfi_rel_offset r2, 4
-    .cfi_rel_offset r3, 8
     .cfi_rel_offset r5, 12
     .cfi_rel_offset r6, 16
     .cfi_rel_offset r7, 20
@@ -237,9 +236,6 @@
     // read barriers, as it is overwritten by REFRESH_MARKING_REGISTER
     // later; but it's not worth handling this special case.
     pop {r1-r3, r5-r8, r10-r11, lr}  @ 10 words of callee saves and args.
-    .cfi_restore r1
-    .cfi_restore r2
-    .cfi_restore r3
     .cfi_restore r5
     .cfi_restore r6
     .cfi_restore r7
@@ -315,10 +311,6 @@
     DELIVER_PENDING_EXCEPTION
 .endm
 
-.macro  RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-    RETURN_OR_DELIVER_PENDING_EXCEPTION_REG r1
-.endm
-
 .macro  RETURN_OR_DELIVER_PENDING_EXCEPTION
     ldr ip, [rSELF, #THREAD_EXCEPTION_OFFSET]  @ Get exception field.
     cmp ip, #0
diff --git a/runtime/arch/arm/asm_support_arm.h b/runtime/arch/arm/asm_support_arm.h
index aff055e..063270f 100644
--- a/runtime/arch/arm/asm_support_arm.h
+++ b/runtime/arch/arm/asm_support_arm.h
@@ -18,6 +18,7 @@
 #define ART_RUNTIME_ARCH_ARM_ASM_SUPPORT_ARM_H_
 
 #include "asm_support.h"
+#include "entrypoints/entrypoint_asm_constants.h"
 
 #define FRAME_SIZE_SAVE_ALL_CALLEE_SAVES 112
 #define FRAME_SIZE_SAVE_REFS_ONLY 32
@@ -25,6 +26,8 @@
 #define FRAME_SIZE_SAVE_EVERYTHING 192
 #define FRAME_SIZE_SAVE_EVERYTHING_FOR_CLINIT FRAME_SIZE_SAVE_EVERYTHING
 #define FRAME_SIZE_SAVE_EVERYTHING_FOR_SUSPEND_CHECK FRAME_SIZE_SAVE_EVERYTHING
+#define SAVE_EVERYTHING_FRAME_R0_OFFSET \
+    (FRAME_SIZE_SAVE_EVERYTHING - CALLEE_SAVE_EVERYTHING_NUM_CORE_SPILLS * POINTER_SIZE)
 
 // The offset from the art_quick_read_barrier_mark_introspection (used for field
 // loads with 32-bit LDR) to the entrypoint for field loads with 16-bit LDR,
@@ -43,22 +46,22 @@
 
 // The offset of the reference load LDR from the return address in LR for field loads.
 #ifdef USE_HEAP_POISONING
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET -8
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET -4
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET (-8)
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET (-4)
 #else
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET -4
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET -2
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET (-4)
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET (-2)
 #endif
 // The offset of the reference load LDR from the return address in LR for array loads.
 #ifdef USE_HEAP_POISONING
-#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET -8
+#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET (-8)
 #else
-#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET -4
+#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET (-4)
 #endif
 // The offset of the reference load LDR from the return address in LR for GC root loads.
-#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_WIDE_OFFSET -8
-#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_NARROW_OFFSET -6
+#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_WIDE_OFFSET (-8)
+#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_NARROW_OFFSET (-6)
 // The offset of the MOV from the return address in LR for intrinsic CAS.
-#define BAKER_MARK_INTROSPECTION_INTRINSIC_CAS_MOV_OFFSET -8
+#define BAKER_MARK_INTROSPECTION_INTRINSIC_CAS_MOV_OFFSET (-8)
 
 #endif  // ART_RUNTIME_ARCH_ARM_ASM_SUPPORT_ARM_H_
diff --git a/runtime/arch/arm/context_arm.cc b/runtime/arch/arm/context_arm.cc
index 711452c..e118daa 100644
--- a/runtime/arch/arm/context_arm.cc
+++ b/runtime/arch/arm/context_arm.cc
@@ -33,8 +33,8 @@
   gprs_[PC] = &pc_;
   gprs_[R0] = &arg0_;
   // Initialize registers with easy to spot debug values.
-  sp_ = ArmContext::kBadGprBase + SP;
-  pc_ = ArmContext::kBadGprBase + PC;
+  sp_ = kBadGprBase + SP;
+  pc_ = kBadGprBase + PC;
   arg0_ = 0;
 }
 
@@ -103,10 +103,10 @@
   uintptr_t gprs[kNumberOfCoreRegisters];
   uint32_t fprs[kNumberOfSRegisters];
   for (size_t i = 0; i < kNumberOfCoreRegisters; ++i) {
-    gprs[i] = gprs_[i] != nullptr ? *gprs_[i] : ArmContext::kBadGprBase + i;
+    gprs[i] = gprs_[i] != nullptr ? *gprs_[i] : kBadGprBase + i;
   }
   for (size_t i = 0; i < kNumberOfSRegisters; ++i) {
-    fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : ArmContext::kBadFprBase + i;
+    fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : kBadFprBase + i;
   }
   // Ensure the Thread Register contains the address of the current thread.
   DCHECK_EQ(reinterpret_cast<uintptr_t>(Thread::Current()), gprs[TR]);
diff --git a/runtime/arch/arm/entrypoints_init_arm.cc b/runtime/arch/arm/entrypoints_init_arm.cc
index b0b0064..555babe 100644
--- a/runtime/arch/arm/entrypoints_init_arm.cc
+++ b/runtime/arch/arm/entrypoints_init_arm.cc
@@ -91,7 +91,7 @@
   qpoints->SetReadBarrierMarkReg10(is_active ? art_quick_read_barrier_mark_reg10 : nullptr);
   qpoints->SetReadBarrierMarkReg11(is_active ? art_quick_read_barrier_mark_reg11 : nullptr);
 
-  if (kUseReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     // For the alignment check, strip the Thumb mode bit.
     DCHECK_ALIGNED(reinterpret_cast<intptr_t>(art_quick_read_barrier_mark_introspection) - 1u,
                    256u);
diff --git a/runtime/arch/arm/fault_handler_arm.cc b/runtime/arch/arm/fault_handler_arm.cc
index 7bd402f..f02ec65 100644
--- a/runtime/arch/arm/fault_handler_arm.cc
+++ b/runtime/arch/arm/fault_handler_arm.cc
@@ -45,79 +45,62 @@
   return instr_size;
 }
 
-void FaultManager::GetMethodAndReturnPcAndSp(siginfo_t* siginfo ATTRIBUTE_UNUSED,
-                                             void* context,
-                                             ArtMethod** out_method,
-                                             uintptr_t* out_return_pc,
-                                             uintptr_t* out_sp,
-                                             bool* out_is_stack_overflow) {
-  struct ucontext* uc = reinterpret_cast<struct ucontext*>(context);
-  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
-  *out_sp = static_cast<uintptr_t>(sc->arm_sp);
-  VLOG(signals) << "sp: " << std::hex << *out_sp;
-  if (*out_sp == 0) {
-    return;
+uintptr_t FaultManager::GetFaultPc(siginfo_t* siginfo ATTRIBUTE_UNUSED, void* context) {
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  if (mc->arm_sp == 0) {
+    VLOG(signals) << "Missing SP";
+    return 0u;
   }
+  return mc->arm_pc;
+}
 
-  // In the case of a stack overflow, the stack is not valid and we can't
-  // get the method from the top of the stack.  However it's in r0.
-  uintptr_t* fault_addr = reinterpret_cast<uintptr_t*>(sc->fault_address);
-  uintptr_t* overflow_addr = reinterpret_cast<uintptr_t*>(
-      reinterpret_cast<uint8_t*>(*out_sp) - GetStackOverflowReservedBytes(InstructionSet::kArm));
-  if (overflow_addr == fault_addr) {
-    *out_method = reinterpret_cast<ArtMethod*>(sc->arm_r0);
-    *out_is_stack_overflow = true;
-  } else {
-    // The method is at the top of the stack.
-    *out_method = reinterpret_cast<ArtMethod*>(reinterpret_cast<uintptr_t*>(*out_sp)[0]);
-    *out_is_stack_overflow = false;
-  }
-
-  // Work out the return PC.  This will be the address of the instruction
-  // following the faulting ldr/str instruction.  This is in thumb mode so
-  // the instruction might be a 16 or 32 bit one.  Also, the GC map always
-  // has the bottom bit of the PC set so we also need to set that.
-
-  // Need to work out the size of the instruction that caused the exception.
-  uint8_t* ptr = reinterpret_cast<uint8_t*>(sc->arm_pc);
-  VLOG(signals) << "pc: " << std::hex << static_cast<void*>(ptr);
-
-  if (ptr == nullptr) {
-    // Somebody jumped to 0x0. Definitely not ours, and will definitely segfault below.
-    *out_method = nullptr;
-    return;
-  }
-
-  uint32_t instr_size = GetInstructionSize(ptr);
-
-  *out_return_pc = (sc->arm_pc + instr_size) | 1;
+uintptr_t FaultManager::GetFaultSp(void* context) {
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  return mc->arm_sp;
 }
 
 bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info, void* context) {
-  if (!IsValidImplicitCheck(info)) {
+  uintptr_t fault_address = reinterpret_cast<uintptr_t>(info->si_addr);
+  if (!IsValidFaultAddress(fault_address)) {
     return false;
   }
-  // The code that looks for the catch location needs to know the value of the
-  // ARM PC at the point of call.  For Null checks we insert a GC map that is immediately after
-  // the load/store instruction that might cause the fault.  However the mapping table has
-  // the low bits set for thumb mode so we need to set the bottom bit for the LR
-  // register in order to find the mapping.
+
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  ArtMethod** sp = reinterpret_cast<ArtMethod**>(mc->arm_sp);
+  if (!IsValidMethod(*sp)) {
+    return false;
+  }
+
+  // For null checks in compiled code we insert a stack map that is immediately
+  // after the load/store instruction that might cause the fault and we need to
+  // pass the return PC to the handler. For null checks in Nterp, we similarly
+  // need the return PC to recognize that this was a null check in Nterp, so
+  // that the handler can get the needed data from the Nterp frame.
+
+  // Note: Currently, Nterp is compiled to the A32 instruction set and managed
+  // code is compiled to the T32 instruction set.
+  // To find the stack map for compiled code, we need to set the bottom bit in
+  // the return PC indicating T32 just like we would if we were going to return
+  // to that PC (though we're going to jump to the exception handler instead).
 
   // Need to work out the size of the instruction that caused the exception.
-  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
-  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
-  uint8_t* ptr = reinterpret_cast<uint8_t*>(sc->arm_pc);
-  bool in_thumb_mode = sc->arm_cpsr & (1 << 5);
+  uint8_t* ptr = reinterpret_cast<uint8_t*>(mc->arm_pc);
+  bool in_thumb_mode = mc->arm_cpsr & (1 << 5);
   uint32_t instr_size = in_thumb_mode ? GetInstructionSize(ptr) : 4;
-  uintptr_t gc_map_location = (sc->arm_pc + instr_size) | (in_thumb_mode ? 1 : 0);
+  uintptr_t return_pc = (mc->arm_pc + instr_size) | (in_thumb_mode ? 1 : 0);
 
-  // Push the gc map location to the stack and pass the fault address in LR.
-  sc->arm_sp -= sizeof(uintptr_t);
-  *reinterpret_cast<uintptr_t*>(sc->arm_sp) = gc_map_location;
-  sc->arm_lr = reinterpret_cast<uintptr_t>(info->si_addr);
-  sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception_from_signal);
+  // Push the return PC to the stack and pass the fault address in LR.
+  mc->arm_sp -= sizeof(uintptr_t);
+  *reinterpret_cast<uintptr_t*>(mc->arm_sp) = return_pc;
+  mc->arm_lr = fault_address;
+
+  // Arrange for the signal handler to return to the NPE entrypoint.
+  mc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception_from_signal);
   // Make sure the thumb bit is set as the handler is in thumb mode.
-  sc->arm_cpsr = sc->arm_cpsr | (1 << 5);
+  mc->arm_cpsr = mc->arm_cpsr | (1 << 5);
   // Pass the faulting address as the first argument of
   // art_quick_throw_null_pointer_exception_from_signal.
   VLOG(signals) << "Generating null pointer exception";
@@ -140,9 +123,9 @@
       + Thread::ThreadSuspendTriggerOffset<PointerSize::k32>().Int32Value();
   uint16_t checkinst2 = 0x6800;
 
-  struct ucontext* uc = reinterpret_cast<struct ucontext*>(context);
-  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
-  uint8_t* ptr2 = reinterpret_cast<uint8_t*>(sc->arm_pc);
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  uint8_t* ptr2 = reinterpret_cast<uint8_t*>(mc->arm_pc);
   uint8_t* ptr1 = ptr2 - 4;
   VLOG(signals) << "checking suspend";
 
@@ -175,10 +158,10 @@
 
     // NB: remember that we need to set the bottom bit of the LR register
     // to switch to thumb mode.
-    VLOG(signals) << "arm lr: " << std::hex << sc->arm_lr;
-    VLOG(signals) << "arm pc: " << std::hex << sc->arm_pc;
-    sc->arm_lr = sc->arm_pc + 3;      // +2 + 1 (for thumb)
-    sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_implicit_suspend);
+    VLOG(signals) << "arm lr: " << std::hex << mc->arm_lr;
+    VLOG(signals) << "arm pc: " << std::hex << mc->arm_pc;
+    mc->arm_lr = mc->arm_pc + 3;  // +2 + 1 (for thumb)
+    mc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_implicit_suspend);
 
     // Now remove the suspend trigger that caused this fault.
     Thread::Current()->RemoveSuspendTrigger();
@@ -205,15 +188,15 @@
 
 bool StackOverflowHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIBUTE_UNUSED,
                                   void* context) {
-  struct ucontext* uc = reinterpret_cast<struct ucontext*>(context);
-  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
   VLOG(signals) << "stack overflow handler with sp at " << std::hex << &uc;
-  VLOG(signals) << "sigcontext: " << std::hex << sc;
+  VLOG(signals) << "sigcontext: " << std::hex << mc;
 
-  uintptr_t sp = sc->arm_sp;
+  uintptr_t sp = mc->arm_sp;
   VLOG(signals) << "sp: " << std::hex << sp;
 
-  uintptr_t fault_addr = sc->fault_address;
+  uintptr_t fault_addr = mc->fault_address;
   VLOG(signals) << "fault_addr: " << std::hex << fault_addr;
   VLOG(signals) << "checking for stack overflow, sp: " << std::hex << sp <<
     ", fault_addr: " << fault_addr;
@@ -232,10 +215,10 @@
   // The value of LR must be the same as it was when we entered the code that
   // caused this fault.  This will be inserted into a callee save frame by
   // the function to which this handler returns (art_quick_throw_stack_overflow).
-  sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow);
+  mc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow);
 
   // Make sure the thumb bit is set as the handler is in thumb mode.
-  sc->arm_cpsr = sc->arm_cpsr | (1 << 5);
+  mc->arm_cpsr = mc->arm_cpsr | (1 << 5);
 
   // The kernel will now return to the address in sc->arm_pc.
   return true;
diff --git a/runtime/arch/arm/instruction_set_features_arm.cc b/runtime/arch/arm/instruction_set_features_arm.cc
index 64ed2a7..749476b 100644
--- a/runtime/arch/arm/instruction_set_features_arm.cc
+++ b/runtime/arch/arm/instruction_set_features_arm.cc
@@ -247,10 +247,10 @@
                             siginfo_t* si ATTRIBUTE_UNUSED,
                             void* data) {
 #if defined(__arm__)
-  struct ucontext *uc = (struct ucontext *)data;
-  struct sigcontext *sc = &uc->uc_mcontext;
-  sc->arm_r0 = 0;     // Set R0 to #0 to signal error.
-  sc->arm_pc += 4;    // Skip offending instruction.
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(data);
+  mcontext_t* mc = &uc->uc_mcontext;
+  mc->arm_r0 = 0;   // Set R0 to #0 to signal error.
+  mc->arm_pc += 4;  // Skip offending instruction.
 #else
   UNUSED(data);
 #endif
diff --git a/runtime/arch/arm/jni_entrypoints_arm.S b/runtime/arch/arm/jni_entrypoints_arm.S
index fc57df7..d91882c 100644
--- a/runtime/arch/arm/jni_entrypoints_arm.S
+++ b/runtime/arch/arm/jni_entrypoints_arm.S
@@ -99,7 +99,7 @@
     // Call artFindNativeMethod() for normal native and artFindNativeMethodRunnable()
     // for @FastNative or @CriticalNative.
     ldr    ip, [r0, #THREAD_TOP_QUICK_FRAME_OFFSET]   // uintptr_t tagged_quick_frame
-    bic    ip, #1                                     // ArtMethod** sp
+    bic    ip, #TAGGED_JNI_SP_MASK                    // ArtMethod** sp
     ldr    ip, [ip]                                   // ArtMethod* method
     ldr    ip, [ip, #ART_METHOD_ACCESS_FLAGS_OFFSET]  // uint32_t access_flags
     tst    ip, #(ACCESS_FLAGS_METHOD_IS_FAST_NATIVE | ACCESS_FLAGS_METHOD_IS_CRITICAL_NATIVE)
@@ -327,6 +327,11 @@
 JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_start, artJniMethodStart, rSELF
 
     /*
+     * Trampoline to `artJniMethodEntryHook()` that preserves all managed arguments.
+     */
+JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_entry_hook, artJniMethodEntryHook, rSELF
+
+    /*
      * Trampoline to `artJniMonitoredMethodStart()` that preserves all managed arguments.
      */
 JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_monitored_method_start, artJniMonitoredMethodStart, rSELF
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index d6f129b..23a324e 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -134,16 +134,50 @@
     .cfi_adjust_cfa_offset -52
 .endm
 
-.macro RETURN_IF_RESULT_IS_ZERO
-    cbnz   r0, 1f              @ result non-zero branch over
-    bx     lr                  @ return
+.macro RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+    ldr r1, [rSELF, # THREAD_EXCEPTION_OFFSET]  // Get exception field.
+    cbnz r1, 1f
+    DEOPT_OR_RETURN r1                          // Check if deopt is required
 1:
+    DELIVER_PENDING_EXCEPTION
 .endm
 
-.macro RETURN_IF_RESULT_IS_NON_ZERO
-    cbz    r0, 1f              @ result zero branch over
-    bx     lr                  @ return
-1:
+.macro DEOPT_OR_RETURN temp, is_ref = 0
+  ldr \temp, [rSELF, #THREAD_DEOPT_CHECK_REQUIRED_OFFSET]
+  cbnz \temp, 2f
+  bx     lr
+2:
+  SETUP_SAVE_EVERYTHING_FRAME \temp
+  mov r2, \is_ref                      // pass if result is a reference
+  mov r1, r0                           // pass the result
+  mov r0, rSELF                        // Thread::Current
+  bl artDeoptimizeIfNeeded
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME
+  REFRESH_MARKING_REGISTER
+  bx     lr
+  CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
+.endm
+
+.macro DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_R0 temp, is_ref
+  ldr \temp, [rSELF, #THREAD_DEOPT_CHECK_REQUIRED_OFFSET]
+  cbnz \temp, 2f
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME_KEEP_R0
+  REFRESH_MARKING_REGISTER
+  bx     lr
+  CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
+2:
+  str    r0, [sp, SAVE_EVERYTHING_FRAME_R0_OFFSET] // update result in the frame
+  mov r2, \is_ref                                  // pass if result is a reference
+  mov r1, r0                                       // pass the result
+  mov r0, rSELF                                    // Thread::Current
+  bl artDeoptimizeIfNeeded
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME
+  REFRESH_MARKING_REGISTER
+  bx     lr
+  CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
 .endm
 
 .macro NO_ARG_RUNTIME_EXCEPTION c_name, cxx_name
@@ -183,12 +217,16 @@
 .endm
 
 .macro RETURN_IF_RESULT_IS_ZERO_OR_DELIVER
-    RETURN_IF_RESULT_IS_ZERO
+    cbnz   r0, 1f              @ result non-zero branch over
+    DEOPT_OR_RETURN r1
+1:
     DELIVER_PENDING_EXCEPTION
 .endm
 
-.macro RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
-    RETURN_IF_RESULT_IS_NON_ZERO
+.macro RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
+    cbz    r0, 1f              @ result zero branch over
+    DEOPT_OR_RETURN r1, /*is_ref=*/1
+1:
     DELIVER_PENDING_EXCEPTION
 .endm
 
@@ -427,7 +465,7 @@
     SAVE_SIZE=(9*4+16*4)
     mov    r11, sp                         @ Save the stack pointer
     .cfi_def_cfa r11, SAVE_SIZE            @ CFA = r11 + SAVE_SIZE
-    .cfi_remember_state
+    CFI_REMEMBER_STATE
     mov    r10, r1                         @ Save size of stack
     ldr    r9, [r11, #(SAVE_SIZE+4)]       @ Move managed thread pointer into r9
     REFRESH_MARKING_REGISTER
@@ -517,8 +555,7 @@
     bl     artLockObjectFromCode      @ (Object* obj, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_ZERO
-    DELIVER_PENDING_EXCEPTION
+    RETURN_IF_RESULT_IS_ZERO_OR_DELIVER
 END art_quick_lock_object_no_inline
 
     /*
@@ -548,8 +585,7 @@
     bl     artUnlockObjectFromCode    @ (Object* obj, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_ZERO
-    DELIVER_PENDING_EXCEPTION
+    RETURN_IF_RESULT_IS_ZERO_OR_DELIVER
 END art_quick_unlock_object_no_inline
 
     /*
@@ -601,13 +637,33 @@
     .cfi_rel_offset \rReg, \offset
 .endm
 
-    /*
-     * Macro to insert read barrier, only used in art_quick_aput_obj.
-     * rObj and rDest are registers, offset is a defined literal such as MIRROR_OBJECT_CLASS_OFFSET.
-     * TODO: When read barrier has a fast path, add heap unpoisoning support for the fast path.
-     */
-.macro READ_BARRIER rDest, rObj, offset
+    // Helper macros for `art_quick_aput_obj`.
 #ifdef USE_READ_BARRIER
+#ifdef USE_BAKER_READ_BARRIER
+.macro BAKER_RB_CHECK_GRAY_BIT_AND_LOAD rDest, rObj, offset, gray_slow_path_label
+    ldr ip, [\rObj, #MIRROR_OBJECT_LOCK_WORD_OFFSET]
+    tst ip, #LOCK_WORD_READ_BARRIER_STATE_MASK_SHIFTED
+    bne \gray_slow_path_label
+    // False dependency to avoid needing load/load fence.
+    add \rObj, \rObj, ip, lsr #32
+    ldr \rDest, [\rObj, #\offset]
+    UNPOISON_HEAP_REF \rDest
+.endm
+
+.macro BAKER_RB_LOAD_AND_MARK rDest, rObj, offset, mark_function
+    ldr \rDest, [\rObj, #\offset]
+    UNPOISON_HEAP_REF \rDest
+    str lr, [sp, #-8]!             @ Save LR with correct stack alignment.
+    .cfi_rel_offset lr, 0
+    .cfi_adjust_cfa_offset 8
+    bl \mark_function
+    ldr lr, [sp], #8               @ Restore LR.
+    .cfi_restore lr
+    .cfi_adjust_cfa_offset -8
+.endm
+#else  // USE_BAKER_READ_BARRIER
+    .extern artReadBarrierSlow
+.macro READ_BARRIER_SLOW rDest, rObj, offset
     push {r0-r3, ip, lr}            @ 6 words for saved registers (used in art_quick_aput_obj)
     .cfi_adjust_cfa_offset 24
     .cfi_rel_offset r0, 0
@@ -640,30 +696,36 @@
     pop {lr}                        @ restore lr
     .cfi_adjust_cfa_offset -4
     .cfi_restore lr
-#else
-    ldr \rDest, [\rObj, #\offset]
-    UNPOISON_HEAP_REF \rDest
-#endif  // USE_READ_BARRIER
 .endm
+#endif // USE_BAKER_READ_BARRIER
+#endif  // USE_READ_BARRIER
 
-#ifdef USE_READ_BARRIER
-    .extern artReadBarrierSlow
-#endif
     .hidden art_quick_aput_obj
 ENTRY art_quick_aput_obj
-#ifdef USE_READ_BARRIER
-    @ The offset to .Ldo_aput_null is too large to use cbz due to expansion from READ_BARRIER macro.
+#if defined(USE_READ_BARRIER) && !defined(USE_BAKER_READ_BARRIER)
+    @ The offset to .Ldo_aput_null is too large to use cbz due to expansion from `READ_BARRIER_SLOW`.
     tst r2, r2
-    beq .Ldo_aput_null
-#else
-    cbz r2, .Ldo_aput_null
+    beq .Laput_obj_null
+    READ_BARRIER_SLOW r3, r0, MIRROR_OBJECT_CLASS_OFFSET
+    READ_BARRIER_SLOW r3, r3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET
+    READ_BARRIER_SLOW r4, r2, MIRROR_OBJECT_CLASS_OFFSET
+#else  // !defined(USE_READ_BARRIER) || defined(USE_BAKER_READ_BARRIER)
+    cbz r2, .Laput_obj_null
+#ifdef USE_READ_BARRIER
+    cmp rMR, #0
+    bne .Laput_obj_gc_marking
 #endif  // USE_READ_BARRIER
-    READ_BARRIER r3, r0, MIRROR_OBJECT_CLASS_OFFSET
-    READ_BARRIER ip, r2, MIRROR_OBJECT_CLASS_OFFSET
-    READ_BARRIER r3, r3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET
-    cmp r3, ip  @ value's type == array's component type - trivial assignability
-    bne .Lcheck_assignability
-.Ldo_aput:
+    ldr r3, [r0, #MIRROR_OBJECT_CLASS_OFFSET]
+    UNPOISON_HEAP_REF r3
+    // R4 is a scratch register in managed ARM ABI.
+    ldr r4, [r2, #MIRROR_OBJECT_CLASS_OFFSET]
+    UNPOISON_HEAP_REF r4
+    ldr r3, [r3, #MIRROR_CLASS_COMPONENT_TYPE_OFFSET]
+    UNPOISON_HEAP_REF r3
+#endif  // !defined(USE_READ_BARRIER) || defined(USE_BAKER_READ_BARRIER)
+    cmp r3, r4  @ value's type == array's component type - trivial assignability
+    bne .Laput_obj_check_assignability
+.Laput_obj_store:
     add r3, r0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
     POISON_HEAP_REF r2
     str r2, [r3, r1, lsl #2]
@@ -671,26 +733,22 @@
     lsr r0, r0, #CARD_TABLE_CARD_SHIFT
     strb r3, [r3, r0]
     blx lr
-.Ldo_aput_null:
+
+.Laput_obj_null:
     add r3, r0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
     str r2, [r3, r1, lsl #2]
     blx lr
-.Lcheck_assignability:
+
+.Laput_obj_check_assignability:
     push {r0-r2, lr}             @ save arguments
     .cfi_adjust_cfa_offset 16
-    .cfi_rel_offset r0, 0
-    .cfi_rel_offset r1, 4
-    .cfi_rel_offset r2, 8
     .cfi_rel_offset lr, 12
-    mov r1, ip
+    mov r1, r4
     mov r0, r3
     bl artIsAssignableFromCode
     cbz r0, .Lthrow_array_store_exception
-    .cfi_remember_state
+    CFI_REMEMBER_STATE
     pop {r0-r2, lr}
-    .cfi_restore r0
-    .cfi_restore r1
-    .cfi_restore r2
     .cfi_restore lr
     .cfi_adjust_cfa_offset -16
     add r3, r0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
@@ -700,19 +758,52 @@
     lsr r0, r0, #CARD_TABLE_CARD_SHIFT
     strb r3, [r3, r0]
     blx lr
+
 .Lthrow_array_store_exception:
     CFI_RESTORE_STATE_AND_DEF_CFA sp, 16
     pop {r0-r2, lr}
-    .cfi_restore r0
-    .cfi_restore r1
-    .cfi_restore r2
     .cfi_restore lr
     .cfi_adjust_cfa_offset -16
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+    CFI_REMEMBER_STATE
+#endif  // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
     SETUP_SAVE_ALL_CALLEE_SAVES_FRAME r3
     mov r1, r2
-    mov r2, rSELF                  @ pass Thread::Current
+    mov r2, rSELF                  @ Pass Thread::Current.
     bl artThrowArrayStoreException @ (Class*, Class*, Thread*)
-    bkpt                           @ unreached
+    bkpt                           @ Unreachable.
+
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+    CFI_RESTORE_STATE_AND_DEF_CFA sp, 0
+.Laput_obj_gc_marking:
+    BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+        r3, r0, MIRROR_OBJECT_CLASS_OFFSET, .Laput_obj_mark_array_class
+.Laput_obj_mark_array_class_continue:
+    BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+        r3, r3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, .Laput_obj_mark_array_element
+.Laput_obj_mark_array_element_continue:
+    BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+        r4, r2, MIRROR_OBJECT_CLASS_OFFSET, .Laput_obj_mark_object_class
+.Laput_obj_mark_object_class_continue:
+
+    cmp r3, r4  @ value's type == array's component type - trivial assignability
+    // All registers are set up for correctly `.Laput_obj_check_assignability`.
+    bne .Laput_obj_check_assignability
+    b   .Laput_obj_store
+
+.Laput_obj_mark_array_class:
+    BAKER_RB_LOAD_AND_MARK r3, r0, MIRROR_OBJECT_CLASS_OFFSET, art_quick_read_barrier_mark_reg03
+    b .Laput_obj_mark_array_class_continue
+
+.Laput_obj_mark_array_element:
+    BAKER_RB_LOAD_AND_MARK \
+        r3, r3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, art_quick_read_barrier_mark_reg03
+    b .Laput_obj_mark_array_element_continue
+
+.Laput_obj_mark_object_class:
+    BAKER_RB_LOAD_AND_MARK r4, r2, MIRROR_OBJECT_CLASS_OFFSET, art_quick_read_barrier_mark_reg04
+    b .Laput_obj_mark_object_class_continue
+#endif  // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
 END art_quick_aput_obj
 
 // Macro to facilitate adding new allocation entrypoints.
@@ -782,11 +873,8 @@
     mov    r1, rSELF                  @ pass Thread::Current
     bl     \entrypoint                @ (uint32_t index, Thread*)
     cbz    r0, 1f                     @ If result is null, deliver the OOME.
-    .cfi_remember_state
-    RESTORE_SAVE_EVERYTHING_FRAME_KEEP_R0
-    REFRESH_MARKING_REGISTER
-    bx     lr
-    CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
+    str    r0, [sp, #136]             @ store result in the frame
+    DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_R0 r1, /* is_ref= */ 1
 1:
     DELIVER_PENDING_EXCEPTION_FRAME_READY
 END \name
@@ -809,12 +897,12 @@
     /*
      * Called by managed code to resolve a static field and load a non-wide value.
      */
-ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
+ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
     /*
      * Called by managed code to resolve a static field and load a 64-bit primitive value.
      */
@@ -827,7 +915,7 @@
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
     cbnz   r2, 1f                        @ success if no exception pending
-    bx     lr                            @ return on success
+    DEOPT_OR_RETURN r2                   @ check if deopt is required or return
 1:
     DELIVER_PENDING_EXCEPTION
 END art_quick_get64_static
@@ -835,12 +923,12 @@
     /*
      * Called by managed code to resolve an instance field and load a non-wide value.
      */
-TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
+TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
     /*
      * Called by managed code to resolve an instance field and load a 64-bit primitive value.
      */
@@ -853,7 +941,7 @@
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
     cbnz   r2, 1f                        @ success if no exception pending
-    bx     lr                            @ return on success
+    DEOPT_OR_RETURN r2                   @ check if deopt is required or return
 1:
     DELIVER_PENDING_EXCEPTION
 END art_quick_get64_instance
@@ -888,8 +976,7 @@
     .cfi_adjust_cfa_offset -16
     RESTORE_SAVE_REFS_ONLY_FRAME         @ TODO: we can clearly save an add here
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_ZERO
-    DELIVER_PENDING_EXCEPTION
+    RETURN_IF_RESULT_IS_ZERO_OR_DELIVER
 END art_quick_set64_instance
 
     .extern artSet64StaticFromCompiledCode
@@ -903,8 +990,7 @@
     .cfi_adjust_cfa_offset -16
     RESTORE_SAVE_REFS_ONLY_FRAME          @ TODO: we can clearly save an add here
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_ZERO
-    DELIVER_PENDING_EXCEPTION
+    RETURN_IF_RESULT_IS_ZERO_OR_DELIVER
 END art_quick_set64_static
 
 // Generate the allocation entrypoints for each allocator.
@@ -1037,7 +1123,7 @@
     bl     \cxx_name                  @ (mirror::Class* cls, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 END \c_name
 .endm
 
@@ -1096,10 +1182,6 @@
     str    r1, [rSELF, #THREAD_LOCAL_OBJECTS_OFFSET]
     POISON_HEAP_REF r0
     str    r0, [r2, #MIRROR_OBJECT_CLASS_OFFSET]              // Store the class pointer.
-                                                              // Fence. This is "ish" not "ishst" so
-                                                              // that the code after this allocation
-                                                              // site will see the right values in
-                                                              // the fields of the class.
     mov    r0, r2
     // No barrier. The class is already observably initialized (otherwise the fast
     // path size check above would fail) and new-instance allocations are protected
@@ -1122,7 +1204,7 @@
     bl     \entrypoint                                        // (mirror::Class* klass, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 END \name
 .endm
 
@@ -1162,10 +1244,6 @@
     POISON_HEAP_REF r0
     str    r0, [r3, #MIRROR_OBJECT_CLASS_OFFSET]              // Store the class pointer.
     str    r1, [r3, #MIRROR_ARRAY_LENGTH_OFFSET]              // Store the array length.
-                                                              // Fence. This is "ish" not "ishst" so
-                                                              // that the code after this allocation
-                                                              // site will see the right values in
-                                                              // the fields of the class.
     mov    r0, r3
 // new-array is special. The class is loaded and immediately goes to the Initialized state
 // before it is published. Therefore the only fence needed is for the publication of the object.
@@ -1195,7 +1273,7 @@
     bl     \entrypoint
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 END \name
 .endm
 
@@ -1362,7 +1440,7 @@
     mov     r3, sp                 @ pass SP
     blx     artQuickResolutionTrampoline  @ (Method* called, receiver, Thread*, SP)
     cbz     r0, 1f                 @ is code pointer null? goto exception
-    .cfi_remember_state
+    CFI_REMEMBER_STATE
     mov     r12, r0
     ldr     r0, [sp, #0]           @ load resolved method in r0
     RESTORE_SAVE_REFS_AND_ARGS_FRAME
@@ -1386,7 +1464,7 @@
     mov r10, sp
     .cfi_def_cfa_register r10
 
-    sub sp, sp, #5120
+    sub sp, sp, #GENERIC_JNI_TRAMPOLINE_RESERVED_AREA
 
     // prepare for artQuickGenericJniTrampoline call
     // (Thread*, managed_sp, reserved_area)
@@ -1445,21 +1523,40 @@
 
     // Tear down the alloca.
     mov sp, r10
-    .cfi_remember_state
-    .cfi_def_cfa_register sp
-
-    // Tear down the callee-save frame. Skip arg registers.
-    add     sp, #FRAME_SIZE_SAVE_REFS_AND_ARGS-FRAME_SIZE_SAVE_REFS_ONLY
-    .cfi_adjust_cfa_offset -(FRAME_SIZE_SAVE_REFS_AND_ARGS-FRAME_SIZE_SAVE_REFS_ONLY)
-    RESTORE_SAVE_REFS_ONLY_FRAME
-    REFRESH_MARKING_REGISTER
 
     // store into fpr, for when it's a fpr return...
     vmov d0, r0, r1
+
+    LOAD_RUNTIME_INSTANCE r2
+    ldr r2, [r2,  #RUN_EXIT_HOOKS_OFFSET_FROM_RUNTIME_INSTANCE]
+    cbnz r2, .Lcall_method_exit_hook
+.Lcall_method_exit_hook_done:
+
+    // Tear down the callee-save frame. Skip arg registers.
+    CFI_REMEMBER_STATE
+    .cfi_def_cfa_register sp
+    add sp, #(FRAME_SIZE_SAVE_REFS_AND_ARGS - 7 * 4)
+    .cfi_adjust_cfa_offset -(FRAME_SIZE_SAVE_REFS_AND_ARGS - 7 * 4)
+    pop {r5-r8, r10-r11, lr}  @ This must match the non-args registers restored by
+    .cfi_restore r5           @ `RESTORE_SAVE_REFS_AND_ARGS_FRAME`.
+    .cfi_restore r6
+    .cfi_restore r7
+    .cfi_restore r8
+    .cfi_restore r10
+    .cfi_restore r11
+    .cfi_restore lr
+    .cfi_adjust_cfa_offset -(7 * 4)
+    REFRESH_MARKING_REGISTER
     bx lr      // ret
 
     // Undo the unwinding information from above since it doesn't apply below.
     CFI_RESTORE_STATE_AND_DEF_CFA r10, FRAME_SIZE_SAVE_REFS_AND_ARGS
+
+.Lcall_method_exit_hook:
+    mov r2, #FRAME_SIZE_SAVE_REFS_AND_ARGS
+    bl art_quick_method_exit_hook
+    b .Lcall_method_exit_hook_done
+
 .Lexception_in_native:
     ldr ip, [rSELF, #THREAD_TOP_QUICK_FRAME_OFFSET]
     add ip, ip, #-1  // Remove the GenericJNI tag. ADD/SUB writing directly to SP is UNPREDICTABLE.
@@ -1497,79 +1594,6 @@
 ONE_ARG_RUNTIME_EXCEPTION art_invoke_obsolete_method_stub, artInvokeObsoleteMethod
 
     /*
-     * Routine that intercepts method calls and returns.
-     */
-    .extern artInstrumentationMethodEntryFromCode
-    .extern artInstrumentationMethodExitFromCode
-ENTRY art_quick_instrumentation_entry
-    @ Make stack crawlable and clobber r2 and r3 (post saving)
-    SETUP_SAVE_REFS_AND_ARGS_FRAME r2
-    @ preserve r0 (not normally an arg) knowing there is a spare slot in kSaveRefsAndArgs.
-    str   r0, [sp, #4]
-    mov   r2, rSELF      @ pass Thread::Current
-    mov   r3, sp         @ pass SP
-    blx   artInstrumentationMethodEntryFromCode  @ (Method*, Object*, Thread*, SP)
-    cbz   r0, .Ldeliver_instrumentation_entry_exception
-    .cfi_remember_state
-                         @ Deliver exception if we got nullptr as function.
-    mov   r12, r0        @ r12 holds reference to code
-    ldr   r0, [sp, #4]   @ restore r0
-    RESTORE_SAVE_REFS_AND_ARGS_FRAME
-    adr   lr, art_quick_instrumentation_exit + /* thumb mode */ 1
-                         @ load art_quick_instrumentation_exit into lr in thumb mode
-    REFRESH_MARKING_REGISTER
-    bx    r12            @ call method with lr set to art_quick_instrumentation_exit
-.Ldeliver_instrumentation_entry_exception:
-    CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_REFS_AND_ARGS
-    @ Deliver exception for art_quick_instrumentation_entry placed after
-    @ art_quick_instrumentation_exit so that the fallthrough works.
-    RESTORE_SAVE_REFS_AND_ARGS_FRAME
-    DELIVER_PENDING_EXCEPTION
-END art_quick_instrumentation_entry
-
-ENTRY art_quick_instrumentation_exit
-    mov   lr, #0         @ link register is to here, so clobber with 0 for later checks
-    SETUP_SAVE_EVERYTHING_FRAME r2
-
-    add   r3, sp, #8     @ store fpr_res pointer, in kSaveEverything frame
-    add   r2, sp, #136   @ store gpr_res pointer, in kSaveEverything frame
-    mov   r1, sp         @ pass SP
-    mov   r0, rSELF      @ pass Thread::Current
-    blx   artInstrumentationMethodExitFromCode  @ (Thread*, SP, gpr_res*, fpr_res*)
-
-    cbz   r0, .Ldo_deliver_instrumentation_exception
-                         @ Deliver exception if we got nullptr as function.
-    .cfi_remember_state
-    cbnz  r1, .Ldeoptimize
-    // Normal return.
-    str   r0, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 4]
-                         @ Set return pc.
-    RESTORE_SAVE_EVERYTHING_FRAME
-    REFRESH_MARKING_REGISTER
-    bx lr
-.Ldo_deliver_instrumentation_exception:
-    CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
-    DELIVER_PENDING_EXCEPTION_FRAME_READY
-.Ldeoptimize:
-    str   r1, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 4]
-                         @ Set return pc.
-    RESTORE_SAVE_EVERYTHING_FRAME
-    // Jump to art_quick_deoptimize.
-    b     art_quick_deoptimize
-END art_quick_instrumentation_exit
-
-    /*
-     * Instrumentation has requested that we deoptimize into the interpreter. The deoptimization
-     * will long jump to the upcall with a special exception of -1.
-     */
-    .extern artDeoptimize
-ENTRY art_quick_deoptimize
-    SETUP_SAVE_EVERYTHING_FRAME r0
-    mov    r0, rSELF      @ pass Thread::Current
-    blx    artDeoptimize  @ (Thread*)
-END art_quick_deoptimize
-
-    /*
      * Compiled code has requested that we deoptimize into the interpreter. The deoptimization
      * will long jump to the interpreter bridge.
      */
@@ -1883,7 +1907,7 @@
     bl     artStringBuilderAppend       @ (uint32_t, const unit32_t*, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 END art_quick_string_builder_append
 
     /*
@@ -1895,13 +1919,9 @@
      *
      * If `reg` is different from `r0`, the generated function follows a
      * non-standard runtime calling convention:
-     * - register `reg` is used to pass the (sole) argument of this
-     *   function (instead of R0);
-     * - register `reg` is used to return the result of this function
-     *   (instead of R0);
-     * - R0 is treated like a normal (non-argument) caller-save register;
-     * - everything else is the same as in the standard runtime calling
-     *   convention (e.g. standard callee-save registers are preserved).
+     * - register `reg` (which may be different from R0) is used to pass the (sole) argument,
+     * - register `reg` (which may be different from R0) is used to return the result,
+     * - all other registers are callee-save (the values they hold are preserved).
      */
 .macro READ_BARRIER_MARK_REG name, reg
 ENTRY \name
@@ -2481,29 +2501,27 @@
     SETUP_SAVE_EVERYTHING_FRAME r0
     ldr r0, [sp, FRAME_SIZE_SAVE_EVERYTHING] @ pass ArtMethod
     mov r1, rSELF                            @ pass Thread::Current
-    bl  artMethodEntryHook                   @ (ArtMethod*, Thread*)
+    mov r2, sp                               @ pass SP
+    bl  artMethodEntryHook                   @ (ArtMethod*, Thread*, SP)
     RESTORE_SAVE_EVERYTHING_FRAME
     REFRESH_MARKING_REGISTER
     blx lr
 END art_quick_method_entry_hook
 
 ENTRY art_quick_method_exit_hook
-    SETUP_SAVE_EVERYTHING_FRAME r2
+    SETUP_SAVE_EVERYTHING_FRAME r5
 
-    add r3, sp, #8                            @ store fpr_res pointer, in kSaveEverything frame
-    add r2, sp, #136                          @ store gpr_res pointer, in kSaveEverything frame
-    ldr r1, [sp, #FRAME_SIZE_SAVE_EVERYTHING] @ pass ArtMethod*
+    sub sp, #4                                @ align stack
+    push {r2}                                 @ pass frame_size stack
+    add r3, sp, #(8 + 8)                      @ store fpr_res pointer, in kSaveEverything frame
+    add r2, sp, #(136 + 8)                    @ store gpr_res pointer, in kSaveEverything frame
+    add r1, sp, #(FRAME_SIZE_SAVE_EVERYTHING + 8)   @ pass ArtMethod**
     mov r0, rSELF                             @ pass Thread::Current
-    blx artMethodExitHook                     @ (Thread*, ArtMethod*, gpr_res*, fpr_res*)
+    blx artMethodExitHook                     @ (Thread*, ArtMethod**, gpr_res*, fpr_res*,
+                                              @ frame_size)
 
-    .cfi_remember_state
-    cbnz r0, .Ldo_deliver_instrumentation_exception_exit @ Deliver exception
-
-    // Normal return.
+    add sp, #8                                @ pop arguments on stack
     RESTORE_SAVE_EVERYTHING_FRAME
     REFRESH_MARKING_REGISTER
     blx lr
-.Ldo_deliver_instrumentation_exception_exit:
-    CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
-    DELIVER_PENDING_EXCEPTION_FRAME_READY
 END art_quick_method_exit_hook
diff --git a/runtime/arch/arm/quick_entrypoints_cc_arm.cc b/runtime/arch/arm/quick_entrypoints_cc_arm.cc
index 987b459..d7fef6f 100644
--- a/runtime/arch/arm/quick_entrypoints_cc_arm.cc
+++ b/runtime/arch/arm/quick_entrypoints_cc_arm.cc
@@ -25,6 +25,7 @@
                                                uint32_t*);
 
 template <bool kIsStatic>
+NO_STACK_PROTECTOR
 static void quick_invoke_reg_setup(ArtMethod* method, uint32_t* args, uint32_t args_size,
                                    Thread* self, JValue* result, const char* shorty) {
   // Note: We do not follow aapcs ABI in quick code for both softfp and hardfp.
@@ -96,6 +97,7 @@
 
 // Called by art::ArtMethod::Invoke to do entry into a non-static method.
 // TODO: migrate into an assembly implementation as with ARM64.
+NO_STACK_PROTECTOR
 extern "C" void art_quick_invoke_stub(ArtMethod* method, uint32_t* args, uint32_t args_size,
                                       Thread* self, JValue* result, const char* shorty) {
   quick_invoke_reg_setup<false>(method, args, args_size, self, result, shorty);
@@ -103,6 +105,7 @@
 
 // Called by art::ArtMethod::Invoke to do entry into a static method.
 // TODO: migrate into an assembly implementation as with ARM64.
+NO_STACK_PROTECTOR
 extern "C" void art_quick_invoke_static_stub(ArtMethod* method, uint32_t* args,
                                              uint32_t args_size, Thread* self, JValue* result,
                                              const char* shorty) {
diff --git a/runtime/arch/arm64/asm_support_arm64.S b/runtime/arch/arm64/asm_support_arm64.S
index ca6b6fd..4ef6ce0 100644
--- a/runtime/arch/arm64/asm_support_arm64.S
+++ b/runtime/arch/arm64/asm_support_arm64.S
@@ -34,10 +34,8 @@
 #define xIP1 x17
 #define wIP1 w17
 
-#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+#ifdef RESERVE_MARKING_REGISTER
 // Marking Register, holding Thread::Current()->GetIsGcMarking().
-// Only used with the Concurrent Copying (CC) garbage
-// collector, with the Baker read barrier configuration.
 #define wMR w20
 #endif
 
@@ -70,6 +68,10 @@
     .endif
 .endm
 
+.macro CFI_REMEMBER_STATE
+    .cfi_remember_state
+.endm
+
 // The spec is not clear whether the CFA is part of the saved state and tools
 // differ in the behaviour, so explicitly set the CFA to avoid any ambiguity.
 // The restored CFA state should match the CFA state during CFI_REMEMBER_STATE.
@@ -86,7 +88,7 @@
     // Prefix the assembly code with 0xFFs, which means there is no method header.
     .byte 0xFF, 0xFF, 0xFF, 0xFF
     // Cache alignment for function entry.
-    // NB: 0xFF because there is a bug in balign where 0x00 creates nop instructions.
+    // Use 0xFF as the last 4 bytes of alignment stand for OatQuickMethodHeader.
     .balign \alignment, 0xFF
 \name:
     .cfi_startproc
@@ -166,7 +168,7 @@
 .endm
 
 .macro LOAD_RUNTIME_INSTANCE reg
-#if __has_feature(hwaddress_sanitizer) && __clang_major__ >= 10
+#if __has_feature(hwaddress_sanitizer)
     adrp \reg, :pg_hi21_nc:_ZN3art7Runtime9instance_E
 #else
     adrp \reg, _ZN3art7Runtime9instance_E
@@ -180,7 +182,7 @@
 // entrypoints that possibly (directly or indirectly) perform a
 // suspend check (before they return).
 .macro REFRESH_MARKING_REGISTER
-#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+#ifdef RESERVE_MARKING_REGISTER
     ldr wMR, [xSELF, #THREAD_IS_GC_MARKING_OFFSET]
 #endif
 .endm
@@ -267,15 +269,16 @@
     stp d6, d7, [\base, #64]
 
     // Core args.
-    SAVE_TWO_REGS_BASE \base, x1, x2, 80
-    SAVE_TWO_REGS_BASE \base, x3, x4, 96
-    SAVE_TWO_REGS_BASE \base, x5, x6, 112
+    stp x1, x2, [\base, #80]
+    stp x3, x4, [\base, #96]
+    stp x5, x6, [\base, #112]
 
     // x7, Callee-saves.
     // Note: We could avoid saving X20 in the case of Baker read
     // barriers, as it is overwritten by REFRESH_MARKING_REGISTER
     // later; but it's not worth handling this special case.
-    SAVE_TWO_REGS_BASE \base, x7, x20, 128
+    stp x7, x20, [\base, #128]
+    .cfi_rel_offset x20, 136
     SAVE_TWO_REGS_BASE \base, x21, x22, 144
     SAVE_TWO_REGS_BASE \base, x23, x24, 160
     SAVE_TWO_REGS_BASE \base, x25, x26, 176
@@ -295,21 +298,20 @@
     ldp d6, d7, [\base, #64]
 
     // Core args.
-    RESTORE_TWO_REGS_BASE \base, x1, x2, 80
-    RESTORE_TWO_REGS_BASE \base, x3, x4, 96
-    RESTORE_TWO_REGS_BASE \base, x5, x6, 112
+    ldp x1, x2, [\base, #80]
+    ldp x3, x4, [\base, #96]
+    ldp x5, x6, [\base, #112]
 
-    // x7, Callee-saves.
+    // x7, callee-saves and LR.
     // Note: Likewise, we could avoid restoring X20 in the case of Baker
     // read barriers, as it is overwritten by REFRESH_MARKING_REGISTER
     // later; but it's not worth handling this special case.
-    RESTORE_TWO_REGS_BASE \base, x7, x20, 128
+    ldp x7, x20, [\base, #128]
+    .cfi_restore x20
     RESTORE_TWO_REGS_BASE \base, x21, x22, 144
     RESTORE_TWO_REGS_BASE \base, x23, x24, 160
     RESTORE_TWO_REGS_BASE \base, x25, x26, 176
     RESTORE_TWO_REGS_BASE \base, x27, x28, 192
-
-    // x29(callee-save) and LR.
     RESTORE_TWO_REGS_BASE \base, x29, xLR, 208
 .endm
 
diff --git a/runtime/arch/arm64/asm_support_arm64.h b/runtime/arch/arm64/asm_support_arm64.h
index 887ee02..39a8296 100644
--- a/runtime/arch/arm64/asm_support_arm64.h
+++ b/runtime/arch/arm64/asm_support_arm64.h
@@ -18,7 +18,9 @@
 #define ART_RUNTIME_ARCH_ARM64_ASM_SUPPORT_ARM64_H_
 
 #include "asm_support.h"
+#include "entrypoints/entrypoint_asm_constants.h"
 
+// TODO(mythria): Change these to use constants from callee_save_frame_arm64.h
 #define CALLEE_SAVES_SIZE (12 * 8 + 8 * 8)
 // +8 for the ArtMethod, +8 for alignment.
 #define FRAME_SIZE_SAVE_ALL_CALLEE_SAVES (CALLEE_SAVES_SIZE + 16)
@@ -27,6 +29,8 @@
 #define FRAME_SIZE_SAVE_EVERYTHING 512
 #define FRAME_SIZE_SAVE_EVERYTHING_FOR_CLINIT FRAME_SIZE_SAVE_EVERYTHING
 #define FRAME_SIZE_SAVE_EVERYTHING_FOR_SUSPEND_CHECK FRAME_SIZE_SAVE_EVERYTHING
+#define SAVE_EVERYTHING_FRAME_X0_OFFSET \
+    (FRAME_SIZE_SAVE_EVERYTHING - CALLEE_SAVE_EVERYTHING_NUM_CORE_SPILLS * POINTER_SIZE)
 
 // The offset from art_quick_read_barrier_mark_introspection to the array switch cases,
 // i.e. art_quick_read_barrier_mark_introspection_arrays.
@@ -37,17 +41,17 @@
 
 // The offset of the reference load LDR from the return address in LR for field loads.
 #ifdef USE_HEAP_POISONING
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET -8
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET (-8)
 #else
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET -4
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET (-4)
 #endif
 // The offset of the reference load LDR from the return address in LR for array loads.
 #ifdef USE_HEAP_POISONING
-#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET -8
+#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET (-8)
 #else
-#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET -4
+#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET (-4)
 #endif
 // The offset of the reference load LDR from the return address in LR for GC root loads.
-#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_OFFSET -8
+#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_OFFSET (-8)
 
 #endif  // ART_RUNTIME_ARCH_ARM64_ASM_SUPPORT_ARM64_H_
diff --git a/runtime/arch/arm64/context_arm64.cc b/runtime/arch/arm64/context_arm64.cc
index 16f4792..eca9ed7 100644
--- a/runtime/arch/arm64/context_arm64.cc
+++ b/runtime/arch/arm64/context_arm64.cc
@@ -23,12 +23,13 @@
 #include "quick/quick_method_frame_info.h"
 #include "thread-current-inl.h"
 
-#if __has_feature(hwaddress_sanitizer)
-#include <sanitizer/hwasan_interface.h>
-#else
-#define __hwasan_handle_longjmp(sp)
+
+#if defined(__aarch64__) && defined(__BIONIC__)
+#include <bionic/malloc.h>
 #endif
 
+extern "C" __attribute__((weak)) void __hwasan_handle_longjmp(const void* sp_dst);
+
 namespace art {
 namespace arm64 {
 
@@ -41,8 +42,8 @@
   gprs_[kPC] = &pc_;
   gprs_[X0] = &arg0_;
   // Initialize registers with easy to spot debug values.
-  sp_ = Arm64Context::kBadGprBase + SP;
-  pc_ = Arm64Context::kBadGprBase + kPC;
+  sp_ = kBadGprBase + SP;
+  pc_ = kBadGprBase + kPC;
   arg0_ = 0;
 }
 
@@ -130,7 +131,21 @@
 
 extern "C" NO_RETURN void art_quick_do_long_jump(uint64_t*, uint64_t*);
 
-void Arm64Context::DoLongJump() {
+#if defined(__aarch64__) && defined(__BIONIC__) && defined(M_MEMTAG_STACK_IS_ON)
+static inline __attribute__((no_sanitize("memtag"))) void untag_memory(void* from, void* to) {
+  __asm__ __volatile__(
+      ".arch_extension mte\n"
+      "1:\n"
+      "stg %[Ptr], [%[Ptr]], #16\n"
+      "cmp %[Ptr], %[End]\n"
+      "b.lt 1b\n"
+      : [Ptr] "+&r"(from)
+      : [End] "r"(to)
+      : "memory");
+}
+#endif
+
+__attribute__((no_sanitize("memtag"))) void Arm64Context::DoLongJump() {
   uint64_t gprs[arraysize(gprs_)];
   uint64_t fprs[kNumberOfDRegisters];
 
@@ -138,15 +153,23 @@
   DCHECK_EQ(SP, 31);
 
   for (size_t i = 0; i < arraysize(gprs_); ++i) {
-    gprs[i] = gprs_[i] != nullptr ? *gprs_[i] : Arm64Context::kBadGprBase + i;
+    gprs[i] = gprs_[i] != nullptr ? *gprs_[i] : kBadGprBase + i;
   }
   for (size_t i = 0; i < kNumberOfDRegisters; ++i) {
-    fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : Arm64Context::kBadFprBase + i;
+    fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : kBadFprBase + i;
   }
   // Ensure the Thread Register contains the address of the current thread.
   DCHECK_EQ(reinterpret_cast<uintptr_t>(Thread::Current()), gprs[TR]);
+#if defined(__aarch64__) && defined(__BIONIC__) && defined(M_MEMTAG_STACK_IS_ON)
+  bool memtag_stack;
+  // This works fine because versions of Android that did not support M_MEMTAG_STACK_ON did not
+  // support stack tagging either.
+  if (android_mallopt(M_MEMTAG_STACK_IS_ON, &memtag_stack, sizeof(memtag_stack)) && memtag_stack)
+    untag_memory(__builtin_frame_address(0), reinterpret_cast<void*>(gprs[SP]));
+#endif
   // Tell HWASan about the new stack top.
-  __hwasan_handle_longjmp(reinterpret_cast<void*>(gprs[SP]));
+  if (__hwasan_handle_longjmp != nullptr)
+    __hwasan_handle_longjmp(reinterpret_cast<void*>(gprs[SP]));
   // The Marking Register will be updated by art_quick_do_long_jump.
   art_quick_do_long_jump(gprs, fprs);
 }
diff --git a/runtime/arch/arm64/fault_handler_arm64.cc b/runtime/arch/arm64/fault_handler_arm64.cc
index a5becf6..3878b57 100644
--- a/runtime/arch/arm64/fault_handler_arm64.cc
+++ b/runtime/arch/arm64/fault_handler_arm64.cc
@@ -38,67 +38,57 @@
 
 namespace art {
 
-void FaultManager::GetMethodAndReturnPcAndSp(siginfo_t* siginfo,
-                                             void* context,
-                                             ArtMethod** out_method,
-                                             uintptr_t* out_return_pc,
-                                             uintptr_t* out_sp,
-                                             bool* out_is_stack_overflow) {
-  struct ucontext *uc = reinterpret_cast<struct ucontext *>(context);
-  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
-
+uintptr_t FaultManager::GetFaultPc(siginfo_t* siginfo, void* context) {
   // SEGV_MTEAERR (Async MTE fault) is delivered at an arbitrary point after the actual fault.
   // Register contents, including PC and SP, are unrelated to the fault and can only confuse ART
   // signal handlers.
   if (siginfo->si_signo == SIGSEGV && siginfo->si_code == SEGV_MTEAERR) {
-    return;
+    VLOG(signals) << "Async MTE fault";
+    return 0u;
   }
 
-  *out_sp = static_cast<uintptr_t>(sc->sp);
-  VLOG(signals) << "sp: " << *out_sp;
-  if (*out_sp == 0) {
-    return;
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  if (mc->sp == 0) {
+    VLOG(signals) << "Missing SP";
+    return 0u;
   }
+  return mc->pc;
+}
 
-  // In the case of a stack overflow, the stack is not valid and we can't
-  // get the method from the top of the stack.  However it's in x0.
-  uintptr_t* fault_addr = reinterpret_cast<uintptr_t*>(sc->fault_address);
-  uintptr_t* overflow_addr = reinterpret_cast<uintptr_t*>(
-      reinterpret_cast<uint8_t*>(*out_sp) - GetStackOverflowReservedBytes(InstructionSet::kArm64));
-  if (overflow_addr == fault_addr) {
-    *out_method = reinterpret_cast<ArtMethod*>(sc->regs[0]);
-    *out_is_stack_overflow = true;
-  } else {
-    // The method is at the top of the stack.
-    *out_method = *reinterpret_cast<ArtMethod**>(*out_sp);
-    *out_is_stack_overflow = false;
-  }
-
-  // Work out the return PC.  This will be the address of the instruction
-  // following the faulting ldr/str instruction.
-  VLOG(signals) << "pc: " << std::hex
-      << static_cast<void*>(reinterpret_cast<uint8_t*>(sc->pc));
-
-  *out_return_pc = sc->pc + 4;
+uintptr_t FaultManager::GetFaultSp(void* context) {
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  return mc->sp;
 }
 
 bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info, void* context) {
-  if (!IsValidImplicitCheck(info)) {
+  uintptr_t fault_address = reinterpret_cast<uintptr_t>(info->si_addr);
+  if (!IsValidFaultAddress(fault_address)) {
     return false;
   }
-  // The code that looks for the catch location needs to know the value of the
-  // PC at the point of call.  For Null checks we insert a GC map that is immediately after
-  // the load/store instruction that might cause the fault.
 
-  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
-  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
+  // For null checks in compiled code we insert a stack map that is immediately
+  // after the load/store instruction that might cause the fault and we need to
+  // pass the return PC to the handler. For null checks in Nterp, we similarly
+  // need the return PC to recognize that this was a null check in Nterp, so
+  // that the handler can get the needed data from the Nterp frame.
 
-  // Push the gc map location to the stack and pass the fault address in LR.
-  sc->sp -= sizeof(uintptr_t);
-  *reinterpret_cast<uintptr_t*>(sc->sp) = sc->pc + 4;
-  sc->regs[30] = reinterpret_cast<uintptr_t>(info->si_addr);
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  ArtMethod** sp = reinterpret_cast<ArtMethod**>(mc->sp);
+  uintptr_t return_pc = mc->pc + 4u;
+  if (!IsValidMethod(*sp) || !IsValidReturnPc(sp, return_pc)) {
+    return false;
+  }
 
-  sc->pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception_from_signal);
+  // Push the return PC to the stack and pass the fault address in LR.
+  mc->sp -= sizeof(uintptr_t);
+  *reinterpret_cast<uintptr_t*>(mc->sp) = return_pc;
+  mc->regs[30] = fault_address;
+
+  // Arrange for the signal handler to return to the NPE entrypoint.
+  mc->pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception_from_signal);
   VLOG(signals) << "Generating null pointer exception";
   return true;
 }
@@ -112,12 +102,11 @@
   constexpr uint32_t checkinst =
       0xf9400000 | (kSuspendCheckRegister << 5) | (kSuspendCheckRegister << 0);
 
-  struct ucontext *uc = reinterpret_cast<struct ucontext *>(context);
-  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
-  VLOG(signals) << "checking suspend";
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
 
-  uint32_t inst = *reinterpret_cast<uint32_t*>(sc->pc);
-  VLOG(signals) << "inst: " << std::hex << inst << " checkinst: " << checkinst;
+  uint32_t inst = *reinterpret_cast<uint32_t*>(mc->pc);
+  VLOG(signals) << "checking suspend; inst: " << std::hex << inst << " checkinst: " << checkinst;
   if (inst != checkinst) {
     // The instruction is not good, not ours.
     return false;
@@ -128,9 +117,9 @@
 
   // Set LR so that after the suspend check it will resume after the
   // `ldr x21, [x21,#0]` instruction that triggered the suspend check.
-  sc->regs[30] = sc->pc + 4;
+  mc->regs[30] = mc->pc + 4;
   // Arrange for the signal handler to return to `art_quick_implicit_suspend()`.
-  sc->pc = reinterpret_cast<uintptr_t>(art_quick_implicit_suspend);
+  mc->pc = reinterpret_cast<uintptr_t>(art_quick_implicit_suspend);
 
   // Now remove the suspend trigger that caused this fault.
   Thread::Current()->RemoveSuspendTrigger();
@@ -141,15 +130,15 @@
 
 bool StackOverflowHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIBUTE_UNUSED,
                                   void* context) {
-  struct ucontext *uc = reinterpret_cast<struct ucontext *>(context);
-  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
   VLOG(signals) << "stack overflow handler with sp at " << std::hex << &uc;
-  VLOG(signals) << "sigcontext: " << std::hex << sc;
+  VLOG(signals) << "sigcontext: " << std::hex << mc;
 
-  uintptr_t sp = sc->sp;
+  uintptr_t sp = mc->sp;
   VLOG(signals) << "sp: " << std::hex << sp;
 
-  uintptr_t fault_addr = sc->fault_address;
+  uintptr_t fault_addr = mc->fault_address;
   VLOG(signals) << "fault_addr: " << std::hex << fault_addr;
   VLOG(signals) << "checking for stack overflow, sp: " << std::hex << sp <<
       ", fault_addr: " << fault_addr;
@@ -168,7 +157,7 @@
   // The value of LR must be the same as it was when we entered the code that
   // caused this fault.  This will be inserted into a callee save frame by
   // the function to which this handler returns (art_quick_throw_stack_overflow).
-  sc->pc = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow);
+  mc->pc = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow);
 
   // The kernel will now return to the address in sc->pc.
   return true;
diff --git a/runtime/arch/arm64/instruction_set_features_arm64.cc b/runtime/arch/arm64/instruction_set_features_arm64.cc
index ad082ae..93400d9 100644
--- a/runtime/arch/arm64/instruction_set_features_arm64.cc
+++ b/runtime/arch/arm64/instruction_set_features_arm64.cc
@@ -171,6 +171,18 @@
                                                                 has_sve));
 }
 
+Arm64FeaturesUniquePtr Arm64InstructionSetFeatures::IntersectWithHwcap() const {
+  Arm64FeaturesUniquePtr hwcaps = Arm64InstructionSetFeatures::FromHwcap();
+  return Arm64FeaturesUniquePtr(new Arm64InstructionSetFeatures(
+      fix_cortex_a53_835769_,
+      fix_cortex_a53_843419_,
+      has_crc_ && hwcaps->has_crc_,
+      has_lse_ && hwcaps->has_lse_,
+      has_fp16_ && hwcaps->has_fp16_,
+      has_dotprod_ && hwcaps->has_dotprod_,
+      has_sve_ && hwcaps->has_sve_));
+}
+
 Arm64FeaturesUniquePtr Arm64InstructionSetFeatures::FromBitmap(uint32_t bitmap) {
   bool is_a53 = (bitmap & kA53Bitfield) != 0;
   bool has_crc = (bitmap & kCRCBitField) != 0;
diff --git a/runtime/arch/arm64/instruction_set_features_arm64.h b/runtime/arch/arm64/instruction_set_features_arm64.h
index eb98c01..8f0013a 100644
--- a/runtime/arch/arm64/instruction_set_features_arm64.h
+++ b/runtime/arch/arm64/instruction_set_features_arm64.h
@@ -53,6 +53,10 @@
   // Use external cpu_features library.
   static Arm64FeaturesUniquePtr FromCpuFeatures();
 
+  // Return a new set of instruction set features, intersecting `this` features
+  // with hardware capabilities.
+  Arm64FeaturesUniquePtr IntersectWithHwcap() const;
+
   bool Equals(const InstructionSetFeatures* other) const override;
 
   // Note that newer CPUs do not have a53 erratum 835769 and 843419,
diff --git a/runtime/arch/arm64/jni_entrypoints_arm64.S b/runtime/arch/arm64/jni_entrypoints_arm64.S
index 463767c..9612a7b 100644
--- a/runtime/arch/arm64/jni_entrypoints_arm64.S
+++ b/runtime/arch/arm64/jni_entrypoints_arm64.S
@@ -103,7 +103,7 @@
     // Call artFindNativeMethod() for normal native and artFindNativeMethodRunnable()
     // for @FastNative or @CriticalNative.
     ldr   xIP0, [x0, #THREAD_TOP_QUICK_FRAME_OFFSET]      // uintptr_t tagged_quick_frame
-    bic   xIP0, xIP0, #1                                  // ArtMethod** sp
+    bic   xIP0, xIP0, #TAGGED_JNI_SP_MASK                 // ArtMethod** sp
     ldr   xIP0, [xIP0]                                    // ArtMethod* method
     ldr   xIP0, [xIP0, #ART_METHOD_ACCESS_FLAGS_OFFSET]   // uint32_t access_flags
     mov   xIP1, #(ACCESS_FLAGS_METHOD_IS_FAST_NATIVE | ACCESS_FLAGS_METHOD_IS_CRITICAL_NATIVE)
@@ -366,6 +366,11 @@
 JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_start, artJniMethodStart, xSELF
 
     /*
+     * Trampoline to `artJniMethodEntryHook` that preserves all managed arguments.
+     */
+JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_entry_hook, artJniMethodEntryHook, xSELF
+
+    /*
      * Trampoline to `artJniMonitoredMethodStart()` that preserves all managed arguments.
      */
 JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_monitored_method_start, artJniMonitoredMethodStart, xSELF
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index d8c91e1..18ec810 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -87,7 +87,8 @@
      * when the SP has already been decremented by FRAME_SIZE_SAVE_EVERYTHING
      * and saving registers x29 and LR is handled elsewhere.
      */
-.macro SETUP_SAVE_EVERYTHING_FRAME_DECREMENTED_SP_SKIP_X29_LR runtime_method_offset = RUNTIME_SAVE_EVERYTHING_METHOD_OFFSET
+.macro SETUP_SAVE_EVERYTHING_FRAME_DECREMENTED_SP_SKIP_X29_LR \
+        runtime_method_offset = RUNTIME_SAVE_EVERYTHING_METHOD_OFFSET
     // Ugly compile-time check, but we only have the preprocessor.
 #if (FRAME_SIZE_SAVE_EVERYTHING != 512)
 #error "FRAME_SIZE_SAVE_EVERYTHING(ARM64) size not as expected."
@@ -194,26 +195,56 @@
     RESTORE_SAVE_EVERYTHING_FRAME_KEEP_X0
 .endm
 
-.macro RETURN_IF_RESULT_IS_ZERO
-    cbnz x0, 1f                // result non-zero branch over
-    ret                        // return
-1:
+.macro RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+    ldr x1, [xSELF, # THREAD_EXCEPTION_OFFSET]  // Get exception field.
+    cbnz x1, 1f
+    DEOPT_OR_RETURN x1                         // Check if deopt is required
+1:                                             // deliver exception on current thread
+    DELIVER_PENDING_EXCEPTION
 .endm
 
-.macro RETURN_IF_RESULT_IS_NON_ZERO
-    cbz x0, 1f                 // result zero branch over
-    ret                        // return
-1:
+.macro DEOPT_OR_RETURN temp, is_ref = 0
+  ldr \temp, [xSELF, #THREAD_DEOPT_CHECK_REQUIRED_OFFSET]
+  cbnz \temp, 2f
+  ret
+2:
+  SETUP_SAVE_EVERYTHING_FRAME
+  mov x2, \is_ref                   // pass if result is a reference
+  mov x1, x0                        // pass the result
+  mov x0, xSELF                     // Thread::Current
+  bl artDeoptimizeIfNeeded
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME
+  REFRESH_MARKING_REGISTER
+  ret
+  CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
 .endm
 
-// Same as above with x1. This is helpful in stubs that want to avoid clobbering another register.
-.macro RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-    RETURN_OR_DELIVER_PENDING_EXCEPTION_REG x1
+.macro DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_X0 temp, is_ref
+  ldr \temp, [xSELF, #THREAD_DEOPT_CHECK_REQUIRED_OFFSET]
+  cbnz \temp, 2f
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME_KEEP_X0
+  REFRESH_MARKING_REGISTER
+  ret
+  CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
+2:
+  str x0, [sp, #SAVE_EVERYTHING_FRAME_X0_OFFSET] // update result in the frame
+  mov x2, \is_ref                                // pass if result is a reference
+  mov x1, x0                                     // pass the result
+  mov x0, xSELF                                  // Thread::Current
+  bl artDeoptimizeIfNeeded
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME
+  REFRESH_MARKING_REGISTER
+  ret
+  CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
 .endm
 
+
 .macro RETURN_IF_W0_IS_ZERO_OR_DELIVER
     cbnz w0, 1f                // result non-zero branch over
-    ret                        // return
+    DEOPT_OR_RETURN x1
 1:
     DELIVER_PENDING_EXCEPTION
 .endm
@@ -267,7 +298,8 @@
     /*
      * Called by managed code to create and deliver a NullPointerException.
      */
-NO_ARG_RUNTIME_EXCEPTION_SAVE_EVERYTHING art_quick_throw_null_pointer_exception, artThrowNullPointerExceptionFromCode
+NO_ARG_RUNTIME_EXCEPTION_SAVE_EVERYTHING \
+        art_quick_throw_null_pointer_exception, artThrowNullPointerExceptionFromCode
 
     /*
      * Call installed by a signal handler to create and deliver a NullPointerException.
@@ -303,7 +335,8 @@
      * Called by managed code to create and deliver a StringIndexOutOfBoundsException
      * as if thrown from a call to String.charAt(). Arg1 holds index, arg2 holds limit.
      */
-TWO_ARG_RUNTIME_EXCEPTION_SAVE_EVERYTHING art_quick_throw_string_bounds, artThrowStringBoundsFromCode
+TWO_ARG_RUNTIME_EXCEPTION_SAVE_EVERYTHING \
+        art_quick_throw_string_bounds, artThrowStringBoundsFromCode
 
     /*
      * Called by managed code to create and deliver a StackOverflowError.
@@ -352,12 +385,17 @@
 END \c_name
 .endm
 
-INVOKE_TRAMPOLINE art_quick_invoke_interface_trampoline_with_access_check, artInvokeInterfaceTrampolineWithAccessCheck
+INVOKE_TRAMPOLINE art_quick_invoke_interface_trampoline_with_access_check, \
+                  artInvokeInterfaceTrampolineWithAccessCheck
 
-INVOKE_TRAMPOLINE art_quick_invoke_static_trampoline_with_access_check, artInvokeStaticTrampolineWithAccessCheck
-INVOKE_TRAMPOLINE art_quick_invoke_direct_trampoline_with_access_check, artInvokeDirectTrampolineWithAccessCheck
-INVOKE_TRAMPOLINE art_quick_invoke_super_trampoline_with_access_check, artInvokeSuperTrampolineWithAccessCheck
-INVOKE_TRAMPOLINE art_quick_invoke_virtual_trampoline_with_access_check, artInvokeVirtualTrampolineWithAccessCheck
+INVOKE_TRAMPOLINE art_quick_invoke_static_trampoline_with_access_check, \
+                  artInvokeStaticTrampolineWithAccessCheck
+INVOKE_TRAMPOLINE art_quick_invoke_direct_trampoline_with_access_check, \
+                  artInvokeDirectTrampolineWithAccessCheck
+INVOKE_TRAMPOLINE art_quick_invoke_super_trampoline_with_access_check, \
+                  artInvokeSuperTrampolineWithAccessCheck
+INVOKE_TRAMPOLINE art_quick_invoke_virtual_trampoline_with_access_check, \
+                  artInvokeVirtualTrampolineWithAccessCheck
 
 
 .macro INVOKE_STUB_CREATE_FRAME
@@ -387,12 +425,12 @@
 
     // Copy parameters into the stack. Use numeric label as this is a macro and Clang's assembler
     // does not have unique-id variables.
-1:
     cbz w2, 2f
+1:
     sub w2, w2, #4      // Need 65536 bytes of range.
     ldr w10, [x1, x2]
     str w10, [x9, x2]
-    b 1b
+    cbnz w2, 1b
 
 2:
     // Store null into ArtMethod* at bottom of frame.
@@ -454,6 +492,134 @@
 .endm
 
 
+// Macro for loading an argument into a register.
+//  label - the base name of the label of the load routine,
+//  reg - the register to load,
+//  args - pointer to current argument, incremented by size,
+//  size - the size of the register - 4 or 8 bytes,
+//  nh4_reg - the register to fill with the address of the next handler for 4-byte values,
+//  nh4_l - the base name of the label of the next handler for 4-byte values,
+//  nh8_reg - the register to fill with the address of the next handler for 8-byte values,
+//  nh8_l - the base name of the label of the next handler for 8-byte values,
+//  cont - the base name of the label for continuing the shorty processing loop,
+//  suffix - suffix added to all labels to make labels unique for different users.
+.macro INVOKE_STUB_LOAD_REG label, reg, args, size, nh4_reg, nh4_l, nh8_reg, nh8_l, cont, suffix
+\label\suffix:
+    ldr \reg, [\args], #\size
+    adr \nh4_reg, \nh4_l\suffix
+    adr \nh8_reg, \nh8_l\suffix
+    b \cont\suffix
+.endm
+
+// Macro for skipping an argument that does not fit into argument registers.
+//  label - the base name of the label of the skip routine,
+//  args - pointer to current argument, incremented by size,
+//  size - the size of the argument - 4 or 8 bytes,
+//  cont - the base name of the label for continuing the shorty processing loop,
+//  suffix - suffix added to all labels to make labels unique for different users.
+.macro INVOKE_STUB_SKIP_ARG label, args, size, cont, suffix
+\label\suffix:
+    add \args, \args, #\size
+    b \cont\suffix
+.endm
+
+// Fill registers x/w1 to x/w7 and s/d0 to s/d7 with parameters.
+// Parse the passed shorty to determine which register to load.
+//  x5 - shorty,
+//  x9 - points to arguments on the stack,
+//  suffix - suffix added to all labels to make labels unique for different users.
+.macro INVOKE_STUB_LOAD_ALL_ARGS suffix
+    add x10, x5, #1                 // Load shorty address, plus one to skip the return type.
+
+    // Load this (if instance method) and addresses for routines that load WXSD registers.
+    .ifc \suffix, _instance
+        ldr w1, [x9], #4            // Load "this" parameter, and increment arg pointer.
+        adr x11, .Lload_w2\suffix
+        adr x12, .Lload_x2\suffix
+    .else
+        adr x11, .Lload_w1\suffix
+        adr x12, .Lload_x1\suffix
+    .endif
+    adr  x13, .Lload_s0\suffix
+    adr  x14, .Lload_d0\suffix
+
+    // Loop to fill registers.
+.Lfill_regs\suffix:
+    ldrb w17, [x10], #1             // Load next character in signature, and increment.
+    cbz w17, .Lcall_method\suffix   // Exit at end of signature. Shorty 0 terminated.
+
+    cmp w17, #'J'                   // Is this a long?
+    beq .Lload_long\suffix
+
+    cmp  w17, #'F'                  // Is this a float?
+    beq .Lload_float\suffix
+
+    cmp w17, #'D'                   // Is this a double?
+    beq .Lload_double\suffix
+
+    // Everything else uses a 4-byte GPR.
+    br x11
+
+.Lload_long\suffix:
+    br x12
+
+.Lload_float\suffix:
+    br x13
+
+.Lload_double\suffix:
+    br x14
+
+// Handlers for loading other args (not float/double/long) into W registers.
+    .ifnc \suffix, _instance
+        INVOKE_STUB_LOAD_REG \
+            .Lload_w1, w1, x9, 4, x11, .Lload_w2, x12, .Lload_x2, .Lfill_regs, \suffix
+    .endif
+    INVOKE_STUB_LOAD_REG .Lload_w2, w2, x9, 4, x11, .Lload_w3, x12, .Lload_x3, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_w3, w3, x9, 4, x11, .Lload_w4, x12, .Lload_x4, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_w4, w4, x9, 4, x11, .Lload_w5, x12, .Lload_x5, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_w5, w5, x9, 4, x11, .Lload_w6, x12, .Lload_x6, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_w6, w6, x9, 4, x11, .Lload_w7, x12, .Lload_x7, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_w7, w7, x9, 4, x11, .Lskip4, x12, .Lskip8, .Lfill_regs, \suffix
+
+// Handlers for loading longs into X registers.
+    .ifnc \suffix, _instance
+        INVOKE_STUB_LOAD_REG \
+            .Lload_x1, x1, x9, 8, x11, .Lload_w2, x12, .Lload_x2, .Lfill_regs, \suffix
+    .endif
+    INVOKE_STUB_LOAD_REG .Lload_x2, x2, x9, 8, x11, .Lload_w3, x12, .Lload_x3, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_x3, x3, x9, 8, x11, .Lload_w4, x12, .Lload_x4, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_x4, x4, x9, 8, x11, .Lload_w5, x12, .Lload_x5, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_x5, x5, x9, 8, x11, .Lload_w6, x12, .Lload_x6, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_x6, x6, x9, 8, x11, .Lload_w7, x12, .Lload_x7, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_x7, x7, x9, 8, x11, .Lskip4, x12, .Lskip8, .Lfill_regs, \suffix
+
+// Handlers for loading singles into S registers.
+    INVOKE_STUB_LOAD_REG .Lload_s0, s0, x9, 4, x13, .Lload_s1, x14, .Lload_d1, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_s1, s1, x9, 4, x13, .Lload_s2, x14, .Lload_d2, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_s2, s2, x9, 4, x13, .Lload_s3, x14, .Lload_d3, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_s3, s3, x9, 4, x13, .Lload_s4, x14, .Lload_d4, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_s4, s4, x9, 4, x13, .Lload_s5, x14, .Lload_d5, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_s5, s5, x9, 4, x13, .Lload_s6, x14, .Lload_d6, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_s6, s6, x9, 4, x13, .Lload_s7, x14, .Lload_d7, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_s7, s7, x9, 4, x13, .Lskip4, x14, .Lskip8, .Lfill_regs, \suffix
+
+// Handlers for loading doubles into D registers.
+    INVOKE_STUB_LOAD_REG .Lload_d0, d0, x9, 8, x13, .Lload_s1, x14, .Lload_d1, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_d1, d1, x9, 8, x13, .Lload_s2, x14, .Lload_d2, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_d2, d2, x9, 8, x13, .Lload_s3, x14, .Lload_d3, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_d3, d3, x9, 8, x13, .Lload_s4, x14, .Lload_d4, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_d4, d4, x9, 8, x13, .Lload_s5, x14, .Lload_d5, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_d5, d5, x9, 8, x13, .Lload_s6, x14, .Lload_d6, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_d6, d6, x9, 8, x13, .Lload_s7, x14, .Lload_d7, .Lfill_regs, \suffix
+    INVOKE_STUB_LOAD_REG .Lload_d7, d7, x9, 8, x13, .Lskip4, x14, .Lskip8, .Lfill_regs, \suffix
+
+// Handlers for skipping arguments that do not fit into registers.
+    INVOKE_STUB_SKIP_ARG .Lskip4, x9, 4, .Lfill_regs, \suffix
+    INVOKE_STUB_SKIP_ARG .Lskip8, x9, 8, .Lfill_regs, \suffix
+
+.Lcall_method\suffix:
+.endm
+
 /*
  *  extern"C" void art_quick_invoke_stub(ArtMethod *method,   x0
  *                                       uint32_t  *args,     x1
@@ -496,126 +662,11 @@
     // Spill registers as per AACPS64 calling convention.
     INVOKE_STUB_CREATE_FRAME
 
-    // Fill registers x/w1 to x/w7 and s/d0 to s/d7 with parameters.
-    // Parse the passed shorty to determine which register to load.
-    // Load addresses for routines that load WXSD registers.
-    adr  x11, .LstoreW2
-    adr  x12, .LstoreX2
-    adr  x13, .LstoreS0
-    adr  x14, .LstoreD0
+    // Load args into registers.
+    INVOKE_STUB_LOAD_ALL_ARGS _instance
 
-    // Initialize routine offsets to 0 for integers and floats.
-    // x8 for integers, x15 for floating point.
-    mov x8, #0
-    mov x15, #0
-
-    add x10, x5, #1         // Load shorty address, plus one to skip return value.
-    ldr w1, [x9],#4         // Load "this" parameter, and increment arg pointer.
-
-    // Loop to fill registers.
-.LfillRegisters:
-    ldrb w17, [x10], #1       // Load next character in signature, and increment.
-    cbz w17, .LcallFunction   // Exit at end of signature. Shorty 0 terminated.
-
-    cmp  w17, #'F' // is this a float?
-    bne .LisDouble
-
-    cmp x15, # 8*12         // Skip this load if all registers full.
-    beq .Ladvance4
-
-    add x17, x13, x15       // Calculate subroutine to jump to.
-    br  x17
-
-.LisDouble:
-    cmp w17, #'D'           // is this a double?
-    bne .LisLong
-
-    cmp x15, # 8*12         // Skip this load if all registers full.
-    beq .Ladvance8
-
-    add x17, x14, x15       // Calculate subroutine to jump to.
-    br x17
-
-.LisLong:
-    cmp w17, #'J'           // is this a long?
-    bne .LisOther
-
-    cmp x8, # 6*12          // Skip this load if all registers full.
-    beq .Ladvance8
-
-    add x17, x12, x8        // Calculate subroutine to jump to.
-    br x17
-
-.LisOther:                  // Everything else takes one vReg.
-    cmp x8, # 6*12          // Skip this load if all registers full.
-    beq .Ladvance4
-
-    add x17, x11, x8        // Calculate subroutine to jump to.
-    br x17
-
-.Ladvance4:
-    add x9, x9, #4
-    b .LfillRegisters
-
-.Ladvance8:
-    add x9, x9, #8
-    b .LfillRegisters
-
-// Macro for loading a parameter into a register.
-//  counter - the register with offset into these tables
-//  size - the size of the register - 4 or 8 bytes.
-//  register - the name of the register to be loaded.
-.macro LOADREG counter size register return
-    ldr \register , [x9], #\size
-    add \counter, \counter, 12
-    b \return
-.endm
-
-// Store ints.
-.LstoreW2:
-    LOADREG x8 4 w2 .LfillRegisters
-    LOADREG x8 4 w3 .LfillRegisters
-    LOADREG x8 4 w4 .LfillRegisters
-    LOADREG x8 4 w5 .LfillRegisters
-    LOADREG x8 4 w6 .LfillRegisters
-    LOADREG x8 4 w7 .LfillRegisters
-
-// Store longs.
-.LstoreX2:
-    LOADREG x8 8 x2 .LfillRegisters
-    LOADREG x8 8 x3 .LfillRegisters
-    LOADREG x8 8 x4 .LfillRegisters
-    LOADREG x8 8 x5 .LfillRegisters
-    LOADREG x8 8 x6 .LfillRegisters
-    LOADREG x8 8 x7 .LfillRegisters
-
-// Store singles.
-.LstoreS0:
-    LOADREG x15 4 s0 .LfillRegisters
-    LOADREG x15 4 s1 .LfillRegisters
-    LOADREG x15 4 s2 .LfillRegisters
-    LOADREG x15 4 s3 .LfillRegisters
-    LOADREG x15 4 s4 .LfillRegisters
-    LOADREG x15 4 s5 .LfillRegisters
-    LOADREG x15 4 s6 .LfillRegisters
-    LOADREG x15 4 s7 .LfillRegisters
-
-// Store doubles.
-.LstoreD0:
-    LOADREG x15 8 d0 .LfillRegisters
-    LOADREG x15 8 d1 .LfillRegisters
-    LOADREG x15 8 d2 .LfillRegisters
-    LOADREG x15 8 d3 .LfillRegisters
-    LOADREG x15 8 d4 .LfillRegisters
-    LOADREG x15 8 d5 .LfillRegisters
-    LOADREG x15 8 d6 .LfillRegisters
-    LOADREG x15 8 d7 .LfillRegisters
-
-
-.LcallFunction:
-
+    // Call the method and return.
     INVOKE_STUB_CALL_AND_RETURN
-
 END art_quick_invoke_stub
 
 /*  extern"C"
@@ -630,117 +681,11 @@
     // Spill registers as per AACPS64 calling convention.
     INVOKE_STUB_CREATE_FRAME
 
-    // Fill registers x/w1 to x/w7 and s/d0 to s/d7 with parameters.
-    // Parse the passed shorty to determine which register to load.
-    // Load addresses for routines that load WXSD registers.
-    adr  x11, .LstoreW1_2
-    adr  x12, .LstoreX1_2
-    adr  x13, .LstoreS0_2
-    adr  x14, .LstoreD0_2
+    // Load args into registers.
+    INVOKE_STUB_LOAD_ALL_ARGS _static
 
-    // Initialize routine offsets to 0 for integers and floats.
-    // x8 for integers, x15 for floating point.
-    mov x8, #0
-    mov x15, #0
-
-    add x10, x5, #1     // Load shorty address, plus one to skip return value.
-
-    // Loop to fill registers.
-.LfillRegisters2:
-    ldrb w17, [x10], #1         // Load next character in signature, and increment.
-    cbz w17, .LcallFunction2    // Exit at end of signature. Shorty 0 terminated.
-
-    cmp  w17, #'F'          // is this a float?
-    bne .LisDouble2
-
-    cmp x15, # 8*12         // Skip this load if all registers full.
-    beq .Ladvance4_2
-
-    add x17, x13, x15       // Calculate subroutine to jump to.
-    br  x17
-
-.LisDouble2:
-    cmp w17, #'D'           // is this a double?
-    bne .LisLong2
-
-    cmp x15, # 8*12         // Skip this load if all registers full.
-    beq .Ladvance8_2
-
-    add x17, x14, x15       // Calculate subroutine to jump to.
-    br x17
-
-.LisLong2:
-    cmp w17, #'J'           // is this a long?
-    bne .LisOther2
-
-    cmp x8, # 7*12          // Skip this load if all registers full.
-    beq .Ladvance8_2
-
-    add x17, x12, x8        // Calculate subroutine to jump to.
-    br x17
-
-.LisOther2:                 // Everything else takes one vReg.
-    cmp x8, # 7*12          // Skip this load if all registers full.
-    beq .Ladvance4_2
-
-    add x17, x11, x8        // Calculate subroutine to jump to.
-    br x17
-
-.Ladvance4_2:
-    add x9, x9, #4
-    b .LfillRegisters2
-
-.Ladvance8_2:
-    add x9, x9, #8
-    b .LfillRegisters2
-
-// Store ints.
-.LstoreW1_2:
-    LOADREG x8 4 w1 .LfillRegisters2
-    LOADREG x8 4 w2 .LfillRegisters2
-    LOADREG x8 4 w3 .LfillRegisters2
-    LOADREG x8 4 w4 .LfillRegisters2
-    LOADREG x8 4 w5 .LfillRegisters2
-    LOADREG x8 4 w6 .LfillRegisters2
-    LOADREG x8 4 w7 .LfillRegisters2
-
-// Store longs.
-.LstoreX1_2:
-    LOADREG x8 8 x1 .LfillRegisters2
-    LOADREG x8 8 x2 .LfillRegisters2
-    LOADREG x8 8 x3 .LfillRegisters2
-    LOADREG x8 8 x4 .LfillRegisters2
-    LOADREG x8 8 x5 .LfillRegisters2
-    LOADREG x8 8 x6 .LfillRegisters2
-    LOADREG x8 8 x7 .LfillRegisters2
-
-// Store singles.
-.LstoreS0_2:
-    LOADREG x15 4 s0 .LfillRegisters2
-    LOADREG x15 4 s1 .LfillRegisters2
-    LOADREG x15 4 s2 .LfillRegisters2
-    LOADREG x15 4 s3 .LfillRegisters2
-    LOADREG x15 4 s4 .LfillRegisters2
-    LOADREG x15 4 s5 .LfillRegisters2
-    LOADREG x15 4 s6 .LfillRegisters2
-    LOADREG x15 4 s7 .LfillRegisters2
-
-// Store doubles.
-.LstoreD0_2:
-    LOADREG x15 8 d0 .LfillRegisters2
-    LOADREG x15 8 d1 .LfillRegisters2
-    LOADREG x15 8 d2 .LfillRegisters2
-    LOADREG x15 8 d3 .LfillRegisters2
-    LOADREG x15 8 d4 .LfillRegisters2
-    LOADREG x15 8 d5 .LfillRegisters2
-    LOADREG x15 8 d6 .LfillRegisters2
-    LOADREG x15 8 d7 .LfillRegisters2
-
-
-.LcallFunction2:
-
+    // Call the method and return.
     INVOKE_STUB_CALL_AND_RETURN
-
 END art_quick_invoke_static_stub
 
 
@@ -774,7 +719,7 @@
     str xzr, [sp]                         // Store null for ArtMethod* slot
     // Branch to stub.
     bl .Losr_entry
-    .cfi_remember_state
+    CFI_REMEMBER_STATE
     DECREASE_FRAME 16
 
     // Restore saved registers including value address and shorty address.
@@ -959,7 +904,7 @@
     cbz x0, .Lthrow_class_cast_exception
 
     // Restore and return
-    .cfi_remember_state
+    CFI_REMEMBER_STATE
     RESTORE_TWO_REGS_DECREASE_FRAME x0, x1, 32
     ret
     CFI_RESTORE_STATE_AND_DEF_CFA sp, 32
@@ -999,25 +944,30 @@
     .cfi_restore \xReg2
 .endm
 
-    /*
-     * Macro to insert read barrier, only used in art_quick_aput_obj.
-     * xDest, wDest and xObj are registers, offset is a defined literal such as
-     * MIRROR_OBJECT_CLASS_OFFSET. Dest needs both x and w versions of the same register to handle
-     * name mismatch between instructions. This macro uses the lower 32b of register when possible.
-     * TODO: When read barrier has a fast path, add heap unpoisoning support for the fast path.
-     */
-.macro READ_BARRIER xDest, wDest, xObj, xTemp, wTemp, offset, number
+    // Helper macros for `art_quick_aput_obj`.
 #ifdef USE_READ_BARRIER
-# ifdef USE_BAKER_READ_BARRIER
-    ldr \wTemp, [\xObj, #MIRROR_OBJECT_LOCK_WORD_OFFSET]
-    tbnz \wTemp, #LOCK_WORD_READ_BARRIER_STATE_SHIFT, .Lrb_slowpath\number
+#ifdef USE_BAKER_READ_BARRIER
+.macro BAKER_RB_CHECK_GRAY_BIT_AND_LOAD wDest, xObj, offset, gray_slow_path_label
+    ldr wIP0, [\xObj, #MIRROR_OBJECT_LOCK_WORD_OFFSET]
+    tbnz wIP0, #LOCK_WORD_READ_BARRIER_STATE_SHIFT, \gray_slow_path_label
     // False dependency to avoid needing load/load fence.
-    add \xObj, \xObj, \xTemp, lsr #32
-    ldr \wDest, [\xObj, #\offset]   // Heap reference = 32b. This also zero-extends to \xDest.
+    add \xObj, \xObj, xIP0, lsr #32
+    ldr \wDest, [\xObj, #\offset]                      // Heap reference = 32b; zero-extends to xN.
     UNPOISON_HEAP_REF \wDest
-    b .Lrb_exit\number
-# endif  // USE_BAKER_READ_BARRIER
-.Lrb_slowpath\number:
+.endm
+
+.macro BAKER_RB_LOAD_AND_MARK wDest, xObj, offset, mark_function
+    ldr \wDest, [\xObj, #\offset]                      // Heap reference = 32b; zero-extends to xN.
+    UNPOISON_HEAP_REF \wDest
+    // Save LR in a register preserved by `art_quick_read_barrier_mark_regNN`
+    // and unused by the `art_quick_aput_obj`.
+    mov x5, lr
+    bl \mark_function
+    mov lr, x5                                         // Restore LR.
+.endm
+#else  // USE_BAKER_READ_BARRIER
+    .extern artReadBarrierSlow
+.macro READ_BARRIER_SLOW xDest, wDest, xObj, offset
     // Store registers used in art_quick_aput_obj (x0-x4, LR), stack is 16B aligned.
     SAVE_TWO_REGS_INCREASE_FRAME x0, x1, 48
     SAVE_TWO_REGS x2, x3, 16
@@ -1042,41 +992,44 @@
     POP_REG_NE x4, 32, \xDest
     RESTORE_REG xLR, 40
     DECREASE_FRAME 48
-.Lrb_exit\number:
-#else
-    ldr \wDest, [\xObj, #\offset]   // Heap reference = 32b. This also zero-extends to \xDest.
-    UNPOISON_HEAP_REF \wDest
-#endif  // USE_READ_BARRIER
 .endm
+#endif // USE_BAKER_READ_BARRIER
+#endif  // USE_READ_BARRIER
 
-#ifdef USE_READ_BARRIER
-    .extern artReadBarrierSlow
-#endif
 ENTRY art_quick_aput_obj
-    cbz x2, .Ldo_aput_null
-    READ_BARRIER x3, w3, x0, x3, w3, MIRROR_OBJECT_CLASS_OFFSET, 0  // Heap reference = 32b
-                                                                    // This also zero-extends to x3
-    READ_BARRIER x3, w3, x3, x4, w4, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, 1 // Heap reference = 32b
-    // This also zero-extends to x3
-    READ_BARRIER x4, w4, x2, x4, w4, MIRROR_OBJECT_CLASS_OFFSET, 2  // Heap reference = 32b
-                                                                    // This also zero-extends to x4
+    cbz x2, .Laput_obj_null
+#if defined(USE_READ_BARRIER) && !defined(USE_BAKER_READ_BARRIER)
+    READ_BARRIER_SLOW x3, w3, x0, MIRROR_OBJECT_CLASS_OFFSET
+    READ_BARRIER_SLOW x3, w3, x3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET
+    READ_BARRIER_SLOW x4, w4, x2, MIRROR_OBJECT_CLASS_OFFSET
+#else  // !defined(USE_READ_BARRIER) || defined(USE_BAKER_READ_BARRIER)
+#ifdef USE_READ_BARRIER
+    cbnz wMR, .Laput_obj_gc_marking
+#endif  // USE_READ_BARRIER
+    ldr w3, [x0, #MIRROR_OBJECT_CLASS_OFFSET]          // Heap reference = 32b; zero-extends to x3.
+    UNPOISON_HEAP_REF w3
+    ldr w3, [x3, #MIRROR_CLASS_COMPONENT_TYPE_OFFSET]  // Heap reference = 32b; zero-extends to x3.
+    UNPOISON_HEAP_REF w3
+    ldr w4, [x2, #MIRROR_OBJECT_CLASS_OFFSET]          // Heap reference = 32b; zero-extends to x4.
+    UNPOISON_HEAP_REF w4
+#endif  // !defined(USE_READ_BARRIER) || defined(USE_BAKER_READ_BARRIER)
     cmp w3, w4  // value's type == array's component type - trivial assignability
-    bne .Lcheck_assignability
-.Ldo_aput:
+    bne .Laput_obj_check_assignability
+.Laput_obj_store:
     add x3, x0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
-                                                         // "Compress" = do nothing
     POISON_HEAP_REF w2
-    str w2, [x3, x1, lsl #2]                             // Heap reference = 32b
+    str w2, [x3, x1, lsl #2]                           // Heap reference = 32b.
     ldr x3, [xSELF, #THREAD_CARD_TABLE_OFFSET]
     lsr x0, x0, #CARD_TABLE_CARD_SHIFT
     strb w3, [x3, x0]
     ret
-.Ldo_aput_null:
+
+.Laput_obj_null:
     add x3, x0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
-                                                         // "Compress" = do nothing
-    str w2, [x3, x1, lsl #2]                             // Heap reference = 32b
+    str w2, [x3, x1, lsl #2]                           // Heap reference = 32b.
     ret
-.Lcheck_assignability:
+
+.Laput_obj_check_assignability:
     // Store arguments and link register
     SAVE_TWO_REGS_INCREASE_FRAME x0, x1, 32
     SAVE_TWO_REGS x2, xLR, 16
@@ -1087,31 +1040,64 @@
     bl artIsAssignableFromCode
 
     // Check for exception
-    cbz x0, .Lthrow_array_store_exception
+    cbz x0, .Laput_obj_throw_array_store_exception
 
     // Restore
-    .cfi_remember_state
+    CFI_REMEMBER_STATE
     RESTORE_TWO_REGS x2, xLR, 16
     RESTORE_TWO_REGS_DECREASE_FRAME x0, x1, 32
 
     add x3, x0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
-                                                          // "Compress" = do nothing
     POISON_HEAP_REF w2
-    str w2, [x3, x1, lsl #2]                              // Heap reference = 32b
+    str w2, [x3, x1, lsl #2]                           // Heap reference = 32b.
     ldr x3, [xSELF, #THREAD_CARD_TABLE_OFFSET]
     lsr x0, x0, #CARD_TABLE_CARD_SHIFT
     strb w3, [x3, x0]
     ret
     CFI_RESTORE_STATE_AND_DEF_CFA sp, 32
-.Lthrow_array_store_exception:
+
+.Laput_obj_throw_array_store_exception:
     RESTORE_TWO_REGS x2, xLR, 16
     RESTORE_TWO_REGS_DECREASE_FRAME x0, x1, 32
 
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+    CFI_REMEMBER_STATE
+#endif  // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
     SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
     mov x1, x2                      // Pass value.
     mov x2, xSELF                   // Pass Thread::Current.
     bl artThrowArrayStoreException  // (Object*, Object*, Thread*).
-    brk 0                           // Unreached.
+    brk 0                           // Unreachable.
+
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+    CFI_RESTORE_STATE_AND_DEF_CFA sp, 0
+.Laput_obj_gc_marking:
+    BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+        w3, x0, MIRROR_OBJECT_CLASS_OFFSET, .Laput_obj_mark_array_class
+.Laput_obj_mark_array_class_continue:
+    BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+        w3, x3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, .Laput_obj_mark_array_element
+.Laput_obj_mark_array_element_continue:
+    BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+        w4, x2, MIRROR_OBJECT_CLASS_OFFSET, .Laput_obj_mark_object_class
+.Laput_obj_mark_object_class_continue:
+    cmp w3, w4  // value's type == array's component type - trivial assignability
+    bne .Laput_obj_check_assignability
+    b   .Laput_obj_store
+
+.Laput_obj_mark_array_class:
+    BAKER_RB_LOAD_AND_MARK w3, x0, MIRROR_OBJECT_CLASS_OFFSET, art_quick_read_barrier_mark_reg03
+    b .Laput_obj_mark_array_class_continue
+
+.Laput_obj_mark_array_element:
+    BAKER_RB_LOAD_AND_MARK \
+        w3, x3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, art_quick_read_barrier_mark_reg03
+    b .Laput_obj_mark_array_element_continue
+
+.Laput_obj_mark_object_class:
+    BAKER_RB_LOAD_AND_MARK w4, x2, MIRROR_OBJECT_CLASS_OFFSET, art_quick_read_barrier_mark_reg04
+    b .Laput_obj_mark_object_class_continue
+#endif  // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
 END art_quick_aput_obj
 
 // Macro to facilitate adding new allocation entrypoints.
@@ -1207,48 +1193,50 @@
      * Macro for resolution and initialization of indexed DEX file
      * constants such as classes and strings.
      */
-.macro ONE_ARG_SAVE_EVERYTHING_DOWNCALL name, entrypoint, runtime_method_offset = RUNTIME_SAVE_EVERYTHING_METHOD_OFFSET
+.macro ONE_ARG_SAVE_EVERYTHING_DOWNCALL \
+        name, entrypoint, runtime_method_offset = RUNTIME_SAVE_EVERYTHING_METHOD_OFFSET
     .extern \entrypoint
 ENTRY \name
     SETUP_SAVE_EVERYTHING_FRAME \runtime_method_offset       // save everything for stack crawl
     mov   x1, xSELF                   // pass Thread::Current
     bl    \entrypoint                 // (int32_t index, Thread* self)
     cbz   w0, 1f                      // If result is null, deliver the OOME.
-    .cfi_remember_state
-    RESTORE_SAVE_EVERYTHING_FRAME_KEEP_X0
-    REFRESH_MARKING_REGISTER
-    ret                        // return
-    CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
+    DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_X0 x1, /* is_ref= */ 1
 1:
     DELIVER_PENDING_EXCEPTION_FRAME_READY
 END \name
 .endm
 
 .macro ONE_ARG_SAVE_EVERYTHING_DOWNCALL_FOR_CLINIT name, entrypoint
-    ONE_ARG_SAVE_EVERYTHING_DOWNCALL \name, \entrypoint, RUNTIME_SAVE_EVERYTHING_FOR_CLINIT_METHOD_OFFSET
+    ONE_ARG_SAVE_EVERYTHING_DOWNCALL \
+            \name, \entrypoint, RUNTIME_SAVE_EVERYTHING_FOR_CLINIT_METHOD_OFFSET
 .endm
 
-.macro RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
-    cbz w0, 1f                 // result zero branch over
-    ret                        // return
+.macro RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
+    cbz w0, 1f                       // result zero branch over
+    DEOPT_OR_RETURN x1, /*is_ref=*/1 // check for deopt or return
 1:
     DELIVER_PENDING_EXCEPTION
 .endm
 
+
     /*
      * Entry from managed code that calls artHandleFillArrayDataFromCode and delivers exception on
      * failure.
      */
-TWO_ARG_REF_DOWNCALL art_quick_handle_fill_data, artHandleFillArrayDataFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+TWO_ARG_REF_DOWNCALL \
+        art_quick_handle_fill_data, artHandleFillArrayDataFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
 
     /*
      * Entry from managed code when uninitialized static storage, this stub will run the class
      * initializer and deliver the exception on error. On success the static storage base is
      * returned.
      */
-ONE_ARG_SAVE_EVERYTHING_DOWNCALL_FOR_CLINIT art_quick_initialize_static_storage, artInitializeStaticStorageFromCode
+ONE_ARG_SAVE_EVERYTHING_DOWNCALL_FOR_CLINIT \
+        art_quick_initialize_static_storage, artInitializeStaticStorageFromCode
 ONE_ARG_SAVE_EVERYTHING_DOWNCALL_FOR_CLINIT art_quick_resolve_type, artResolveTypeFromCode
-ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_resolve_type_and_verify_access, artResolveTypeAndVerifyAccessFromCode
+ONE_ARG_SAVE_EVERYTHING_DOWNCALL \
+        art_quick_resolve_type_and_verify_access, artResolveTypeAndVerifyAccessFromCode
 ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_resolve_method_handle, artResolveMethodHandleFromCode
 ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_resolve_method_type, artResolveMethodTypeFromCode
 ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_resolve_string, artResolveStringFromCode
@@ -1256,33 +1244,71 @@
 // Note: Functions `art{Get,Set}<Kind>{Static,Instance}FromCompiledCode` are
 // defined with a macro in runtime/entrypoints/quick/quick_field_entrypoints.cc.
 
-ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
+ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, \
+                     artGetBooleanStaticFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, \
+                     artGetByteStaticFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_char_static, \
+                     artGetCharStaticFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_short_static, \
+                     artGetShortStaticFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get32_static, \
+                     artGet32StaticFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get64_static, \
+                     artGet64StaticFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, \
+                     artGetObjStaticFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
 
-TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
+TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, \
+                     artGetBooleanInstanceFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, \
+                     artGetByteInstanceFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, \
+                     artGetCharInstanceFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, \
+                     artGetShortInstanceFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get32_instance, \
+                     artGet32InstanceFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get64_instance, \
+                     artGet64InstanceFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, \
+                     artGetObjInstanceFromCompiledCode, \
+                     RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
 
-TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
-TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
-TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
-TWO_ARG_REF_DOWNCALL art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
-TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+TWO_ARG_REF_DOWNCALL \
+    art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+TWO_ARG_REF_DOWNCALL \
+    art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+TWO_ARG_REF_DOWNCALL \
+    art_quick_set32_static, artSet32StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+TWO_ARG_REF_DOWNCALL \
+    art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+TWO_ARG_REF_DOWNCALL \
+    art_quick_set_obj_static, artSetObjStaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
 
-THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
-THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
-THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
-THREE_ARG_REF_DOWNCALL art_quick_set64_instance, artSet64InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
-THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+THREE_ARG_REF_DOWNCALL \
+    art_quick_set8_instance, artSet8InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+THREE_ARG_REF_DOWNCALL \
+    art_quick_set16_instance, artSet16InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+THREE_ARG_REF_DOWNCALL \
+    art_quick_set32_instance, artSet32InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+THREE_ARG_REF_DOWNCALL \
+    art_quick_set64_instance, artSet64InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
+THREE_ARG_REF_DOWNCALL \
+    art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
 
 // Generate the allocation entrypoints for each allocator.
 GENERATE_ALLOC_ENTRYPOINTS_FOR_NON_TLAB_ALLOCATORS
@@ -1410,12 +1436,14 @@
     bl     \cxx_name
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 END \c_name
 .endm
 
-ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_resolved_rosalloc, artAllocObjectFromCodeResolvedRosAlloc, /* isInitialized */ 0
-ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_initialized_rosalloc, artAllocObjectFromCodeInitializedRosAlloc, /* isInitialized */ 1
+ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_resolved_rosalloc, \
+                                artAllocObjectFromCodeResolvedRosAlloc, /* isInitialized */ 0
+ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_initialized_rosalloc, \
+                                artAllocObjectFromCodeInitializedRosAlloc, /* isInitialized */ 1
 
 // If isInitialized=1 then the compiler assumes the object's class has already been initialized.
 // If isInitialized=0 the compiler can only assume it's been at least resolved.
@@ -1439,10 +1467,6 @@
     str    x5, [xSELF, #THREAD_LOCAL_OBJECTS_OFFSET]
     POISON_HEAP_REF w0
     str    w0, [x4, #MIRROR_OBJECT_CLASS_OFFSET]              // Store the class pointer.
-                                                              // Fence. This is "ish" not "ishst" so
-                                                              // that the code after this allocation
-                                                              // site will see the right values in
-                                                              // the fields of the class.
     mov    x0, x4
     // No barrier. The class is already observably initialized (otherwise the fast
     // path size check above would fail) and new-instance allocations are protected
@@ -1465,16 +1489,25 @@
     bl     \entrypoint                         // (mirror::Class*, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 END \name
 .endm
 
-GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_resolved_region_tlab, artAllocObjectFromCodeResolvedRegionTLAB, /* isInitialized */ 0
-GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_initialized_region_tlab, artAllocObjectFromCodeInitializedRegionTLAB, /* isInitialized */ 1
-GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_resolved_tlab, artAllocObjectFromCodeResolvedTLAB, /* isInitialized */ 0
-GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_initialized_tlab, artAllocObjectFromCodeInitializedTLAB, /* isInitialized */ 1
+GENERATE_ALLOC_OBJECT_RESOLVED_TLAB \
+    art_quick_alloc_object_resolved_region_tlab, \
+    artAllocObjectFromCodeResolvedRegionTLAB, /* isInitialized */ 0
+GENERATE_ALLOC_OBJECT_RESOLVED_TLAB \
+    art_quick_alloc_object_initialized_region_tlab, \
+    artAllocObjectFromCodeInitializedRegionTLAB, /* isInitialized */ 1
+GENERATE_ALLOC_OBJECT_RESOLVED_TLAB \
+    art_quick_alloc_object_resolved_tlab, \
+    artAllocObjectFromCodeResolvedTLAB, /* isInitialized */ 0
+GENERATE_ALLOC_OBJECT_RESOLVED_TLAB \
+    art_quick_alloc_object_initialized_tlab, \
+    artAllocObjectFromCodeInitializedTLAB, /* isInitialized */ 1
 
-.macro ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE slowPathLabel, xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
+.macro ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE \
+    slowPathLabel, xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
     and    \xTemp1, \xTemp1, #OBJECT_ALIGNMENT_MASK_TOGGLED64 // Apply alignment mask
                                                               // (addr + 7) & ~7. The mask must
                                                               // be 64 bits to keep high bits in
@@ -1510,7 +1543,6 @@
     POISON_HEAP_REF \wClass
     str    \wClass, [x0, #MIRROR_OBJECT_CLASS_OFFSET]         // Store the class pointer.
     str    \wCount, [x0, #MIRROR_ARRAY_LENGTH_OFFSET]         // Store the array length.
-                                                              // Fence.
 // new-array is special. The class is loaded and immediately goes to the Initialized state
 // before it is published. Therefore the only fence needed is for the publication of the object.
 // See ClassLinker::CreateArrayClass() for more details.
@@ -1529,7 +1561,8 @@
     // x2-x7: free.
     mov    x3, x0
     \size_setup x3, w3, x1, w1, x4, w4, x5, w5, x6, w6
-    ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE .Lslow_path\name, x3, w3, x1, w1, x4, w4, x5, w5, x6, w6
+    ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE \
+        .Lslow_path\name, x3, w3, x1, w1, x4, w4, x5, w5, x6, w6
 .Lslow_path\name:
     // x0: mirror::Class* klass
     // x1: int32_t component_count
@@ -1539,11 +1572,12 @@
     bl     \entrypoint
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 END \name
 .endm
 
-.macro COMPUTE_ARRAY_SIZE_UNKNOWN xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
+.macro COMPUTE_ARRAY_SIZE_UNKNOWN \
+        xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
     // Array classes are never finalizable or uninitialized, no need to check.
     ldr    \wTemp0, [\xClass, #MIRROR_CLASS_COMPONENT_TYPE_OFFSET] // Load component type
     UNPOISON_HEAP_REF \wTemp0
@@ -1566,24 +1600,28 @@
     add    \xTemp1, \xTemp1, \xTemp0
 .endm
 
-.macro COMPUTE_ARRAY_SIZE_8 xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
+.macro COMPUTE_ARRAY_SIZE_8 \
+        xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
     // Add array data offset and alignment.
     add    \xTemp1, \xCount, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK)
 .endm
 
-.macro COMPUTE_ARRAY_SIZE_16 xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
+.macro COMPUTE_ARRAY_SIZE_16 \
+        xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
     lsl    \xTemp1, \xCount, #1
     // Add array data offset and alignment.
     add    \xTemp1, \xTemp1, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK)
 .endm
 
-.macro COMPUTE_ARRAY_SIZE_32 xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
+.macro COMPUTE_ARRAY_SIZE_32 \
+        xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
     lsl    \xTemp1, \xCount, #2
     // Add array data offset and alignment.
     add    \xTemp1, \xTemp1, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK)
 .endm
 
-.macro COMPUTE_ARRAY_SIZE_64 xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
+.macro COMPUTE_ARRAY_SIZE_64 \
+        xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
     lsl    \xTemp1, \xCount, #3
     // Add array data offset and alignment.
     add    \xTemp1, \xTemp1, #(MIRROR_WIDE_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK)
@@ -1591,16 +1629,36 @@
 
 // TODO(ngeoffray): art_quick_alloc_array_resolved_region_tlab is not used for arm64, remove
 // the entrypoint once all backends have been updated to use the size variants.
-GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN
-GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_8
-GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_16
-GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_32
-GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_64
-GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN
-GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_8
-GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_16
-GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_32
-GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_64
+GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_region_tlab, \
+                          artAllocArrayFromCodeResolvedRegionTLAB, \
+                          COMPUTE_ARRAY_SIZE_UNKNOWN
+GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_region_tlab, \
+                          artAllocArrayFromCodeResolvedRegionTLAB, \
+                          COMPUTE_ARRAY_SIZE_8
+GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_region_tlab, \
+                          artAllocArrayFromCodeResolvedRegionTLAB, \
+                          COMPUTE_ARRAY_SIZE_16
+GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_region_tlab, \
+                          artAllocArrayFromCodeResolvedRegionTLAB, \
+                          COMPUTE_ARRAY_SIZE_32
+GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_region_tlab, \
+                          artAllocArrayFromCodeResolvedRegionTLAB, \
+                          COMPUTE_ARRAY_SIZE_64
+GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_tlab, \
+                          artAllocArrayFromCodeResolvedTLAB, \
+                          COMPUTE_ARRAY_SIZE_UNKNOWN
+GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_tlab, \
+                          artAllocArrayFromCodeResolvedTLAB, \
+                          COMPUTE_ARRAY_SIZE_8
+GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_tlab, \
+                          artAllocArrayFromCodeResolvedTLAB, \
+                          COMPUTE_ARRAY_SIZE_16
+GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_tlab, \
+                          artAllocArrayFromCodeResolvedTLAB, \
+                          COMPUTE_ARRAY_SIZE_32
+GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_tlab, \
+                          artAllocArrayFromCodeResolvedTLAB, \
+                          COMPUTE_ARRAY_SIZE_64
 
     /*
      * Called by managed code when the thread has been asked to suspend.
@@ -1645,7 +1703,7 @@
     bl      artQuickProxyInvokeHandler  // (Method* proxy method, receiver, Thread*, SP)
     ldr     x2, [xSELF, THREAD_EXCEPTION_OFFSET]
     cbnz    x2, .Lexception_in_proxy    // success if no exception is pending
-    .cfi_remember_state
+    CFI_REMEMBER_STATE
     RESTORE_SAVE_REFS_AND_ARGS_FRAME    // Restore frame
     REFRESH_MARKING_REGISTER
     fmov    d0, x0                      // Store result in d0 in case it was float or double
@@ -1694,7 +1752,7 @@
     mov x3, sp
     bl artQuickResolutionTrampoline  // (called, receiver, Thread*, SP)
     cbz x0, 1f
-    .cfi_remember_state
+    CFI_REMEMBER_STATE
     mov xIP0, x0            // Remember returned code pointer in xIP0.
     ldr x0, [sp, #0]        // artQuickResolutionTrampoline puts called method in *SP.
     RESTORE_SAVE_REFS_AND_ARGS_FRAME
@@ -1743,8 +1801,7 @@
  * | Method*           | <- X0 (Managed frame similar to SaveRefsAndArgs.)
  * #-------------------#
  * | local ref cookie  | // 4B
- * | padding           | // 0B or 4B to align handle scope on 8B address
- * | handle scope      | // Size depends on number of references; multiple of 4B.
+ * | padding           | // 0B or 4B to align stack args on 8B address
  * #-------------------#
  * | JNI Stack Args    | // Empty if all args fit into registers x0-x7, d0-d7.
  * #-------------------#    <--- SP on native call (1)
@@ -1766,24 +1823,20 @@
 ENTRY art_quick_generic_jni_trampoline
     SETUP_SAVE_REFS_AND_ARGS_FRAME_WITH_METHOD_IN_X0
 
-    // Save SP , so we can have static CFI info.
+    // Save SP, so we can have static CFI info.
     mov x28, sp
     .cfi_def_cfa_register x28
 
-    // This looks the same, but is different: this will be updated to point to the bottom
-    // of the frame when the handle scope is inserted.
-    mov xFP, sp
-
-    mov xIP0, #5120
+    mov xIP0, #GENERIC_JNI_TRAMPOLINE_RESERVED_AREA
     sub sp, sp, xIP0
 
     // prepare for artQuickGenericJniTrampoline call
     // (Thread*, managed_sp, reserved_area)
     //    x0         x1            x2   <= C calling convention
-    //  xSELF       xFP            sp   <= where they are
+    //  xSELF       x28            sp   <= where they are
 
     mov x0, xSELF   // Thread*
-    mov x1, xFP     // SP for the managed frame.
+    mov x1, x28     // SP for the managed frame.
     mov x2, sp      // reserved area for arguments and other saved data (up to managed frame)
     bl artQuickGenericJniTrampoline  // (Thread*, sp)
 
@@ -1834,11 +1887,32 @@
 
     // Tear down the alloca.
     mov sp, x28
-    .cfi_remember_state
-    .cfi_def_cfa_register sp
+
+    LOAD_RUNTIME_INSTANCE x1
+    ldrb w1, [x1, #RUN_EXIT_HOOKS_OFFSET_FROM_RUNTIME_INSTANCE]
+    cbnz w1, .Lcall_method_exit_hook
+.Lcall_method_exit_hook_done:
 
     // Tear down the callee-save frame.
-    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    CFI_REMEMBER_STATE
+    .cfi_def_cfa_register sp
+    // Restore callee-saves and LR as in `RESTORE_SAVE_REFS_AND_ARGS_FRAME`
+    // but do not restore argument registers.
+    // Note: Likewise, we could avoid restoring X20 in the case of Baker
+    // read barriers, as it is overwritten by REFRESH_MARKING_REGISTER
+    // later; but it's not worth handling this special case.
+#if (FRAME_SIZE_SAVE_REFS_AND_ARGS != 224)
+#error "FRAME_SIZE_SAVE_REFS_AND_ARGS(ARM64) size not as expected."
+#endif
+    RESTORE_REG x20, 136
+    RESTORE_TWO_REGS x21, x22, 144
+    RESTORE_TWO_REGS x23, x24, 160
+    RESTORE_TWO_REGS x25, x26, 176
+    RESTORE_TWO_REGS x27, x28, 192
+    RESTORE_TWO_REGS x29, xLR, 208
+    // Remove the frame.
+    DECREASE_FRAME FRAME_SIZE_SAVE_REFS_AND_ARGS
+
     REFRESH_MARKING_REGISTER
 
     // store into fpr, for when it's a fpr return...
@@ -1847,6 +1921,13 @@
 
     // Undo the unwinding information from above since it doesn't apply below.
     CFI_RESTORE_STATE_AND_DEF_CFA x28, FRAME_SIZE_SAVE_REFS_AND_ARGS
+
+.Lcall_method_exit_hook:
+    fmov d0, x0
+    mov x4, FRAME_SIZE_SAVE_REFS_AND_ARGS
+    bl art_quick_method_exit_hook
+    b .Lcall_method_exit_hook_done
+
 .Lexception_in_native:
     // Move to x1 then sp to please assembler.
     ldr x1, [xSELF, # THREAD_TOP_QUICK_FRAME_OFFSET]
@@ -1889,77 +1970,6 @@
  */
 ONE_ARG_RUNTIME_EXCEPTION art_invoke_obsolete_method_stub, artInvokeObsoleteMethod
 
-
-//
-// Instrumentation-related stubs
-//
-    .extern artInstrumentationMethodEntryFromCode
-ENTRY art_quick_instrumentation_entry
-    SETUP_SAVE_REFS_AND_ARGS_FRAME
-
-    mov   x20, x0             // Preserve method reference in a callee-save.
-
-    mov   x2, xSELF
-    mov   x3, sp  // Pass SP
-    bl    artInstrumentationMethodEntryFromCode  // (Method*, Object*, Thread*, SP)
-
-    mov   xIP0, x0            // x0 = result of call.
-    mov   x0, x20             // Reload method reference.
-
-    RESTORE_SAVE_REFS_AND_ARGS_FRAME  // Note: will restore xSELF
-    REFRESH_MARKING_REGISTER
-    cbz   xIP0, 1f            // Deliver the pending exception if method is null.
-    adr   xLR, art_quick_instrumentation_exit
-    br    xIP0                // Tail-call method with lr set to art_quick_instrumentation_exit.
-
-1:
-    DELIVER_PENDING_EXCEPTION
-END art_quick_instrumentation_entry
-
-    .extern artInstrumentationMethodExitFromCode
-ENTRY art_quick_instrumentation_exit
-    mov   xLR, #0             // Clobber LR for later checks.
-    SETUP_SAVE_EVERYTHING_FRAME
-
-    add   x3, sp, #16         // Pass floating-point result pointer, in kSaveEverything frame.
-    add   x2, sp, #272        // Pass integer result pointer, in kSaveEverything frame.
-    mov   x1, sp              // Pass SP.
-    mov   x0, xSELF           // Pass Thread.
-    bl   artInstrumentationMethodExitFromCode    // (Thread*, SP, gpr_res*, fpr_res*)
-
-    cbz   x0, .Ldo_deliver_instrumentation_exception
-    .cfi_remember_state
-                              // Handle error
-    cbnz  x1, .Ldeoptimize
-    // Normal return.
-    str   x0, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 8]
-                              // Set return pc.
-    RESTORE_SAVE_EVERYTHING_FRAME
-    REFRESH_MARKING_REGISTER
-    br    lr
-.Ldo_deliver_instrumentation_exception:
-    CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
-    DELIVER_PENDING_EXCEPTION_FRAME_READY
-.Ldeoptimize:
-    str   x1, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 8]
-                              // Set return pc.
-    RESTORE_SAVE_EVERYTHING_FRAME
-    // Jump to art_quick_deoptimize.
-    b     art_quick_deoptimize
-END art_quick_instrumentation_exit
-
-    /*
-     * Instrumentation has requested that we deoptimize into the interpreter. The deoptimization
-     * will long jump to the upcall with a special exception of -1.
-     */
-    .extern artDeoptimize
-ENTRY art_quick_deoptimize
-    SETUP_SAVE_EVERYTHING_FRAME
-    mov    x0, xSELF          // Pass thread.
-    bl     artDeoptimize      // (Thread*)
-    brk 0
-END art_quick_deoptimize
-
     /*
      * Compiled code has requested that we deoptimize into the interpreter. The deoptimization
      * will long jump to the upcall with a special exception of -1.
@@ -2102,7 +2112,7 @@
     bl     artStringBuilderAppend       // (uint32_t, const unit32_t*, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 END art_quick_string_builder_append
 
     /*
@@ -2111,15 +2121,10 @@
      * `wreg` (corresponding to X register `xreg`), saving and restoring
      * all caller-save registers.
      *
-     * If `wreg` is different from `w0`, the generated function follows a
-     * non-standard runtime calling convention:
-     * - register `wreg` is used to pass the (sole) argument of this
-     *   function (instead of W0);
-     * - register `wreg` is used to return the result of this function
-     *   (instead of W0);
-     * - W0 is treated like a normal (non-argument) caller-save register;
-     * - everything else is the same as in the standard runtime calling
-     *   convention (e.g. standard callee-save registers are preserved).
+     * The generated function follows a non-standard runtime calling convention:
+     * - register `reg` (which may be different from W0) is used to pass the (sole) argument,
+     * - register `reg` (which may be different from W0) is used to return the result,
+     * - all other registers are callee-save (the values they hold are preserved).
      */
 .macro READ_BARRIER_MARK_REG name, wreg, xreg
 ENTRY \name
@@ -2598,7 +2603,8 @@
 
     ldr x0, [sp, #FRAME_SIZE_SAVE_EVERYTHING] // pass ArtMethod*
     mov x1, xSELF                             // pass Thread::Current
-    bl  artMethodEntryHook                    // (ArtMethod*, Thread*)
+    mov x2, sp                                // pass SP
+    bl  artMethodEntryHook                    // (ArtMethod*, Thread*, SP)
 
     RESTORE_SAVE_EVERYTHING_FRAME             // Note: will restore xSELF
     REFRESH_MARKING_REGISTER
@@ -2609,21 +2615,16 @@
 ENTRY art_quick_method_exit_hook
     SETUP_SAVE_EVERYTHING_FRAME
 
+    // frame_size is passed from JITed code in x4
     add x3, sp, #16                           // floating-point result ptr in kSaveEverything frame
     add x2, sp, #272                          // integer result ptr in kSaveEverything frame
-    ldr x1, [sp, #FRAME_SIZE_SAVE_EVERYTHING] // ArtMethod*
+    add x1, sp, #FRAME_SIZE_SAVE_EVERYTHING   // ArtMethod**
     mov x0, xSELF                             // Thread::Current
-    bl  artMethodExitHook                     // (Thread*, ArtMethod*, gpr_res*, fpr_res*)
-
-    .cfi_remember_state
-    cbnz x0, .Ldo_deliver_instrumentation_exception_exit // Handle exception
+    bl  artMethodExitHook                     // (Thread*, ArtMethod**, gpr_res*, fpr_res*,
+                                              // frame_size)
 
     // Normal return.
     RESTORE_SAVE_EVERYTHING_FRAME
     REFRESH_MARKING_REGISTER
     ret
-.Ldo_deliver_instrumentation_exception_exit:
-    CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
-    DELIVER_PENDING_EXCEPTION_FRAME_READY
 END art_quick_method_exit_hook
-
diff --git a/runtime/arch/context-inl.h b/runtime/arch/context-inl.h
index cac7c43..453432b 100644
--- a/runtime/arch/context-inl.h
+++ b/runtime/arch/context-inl.h
@@ -28,6 +28,9 @@
 #elif defined(__aarch64__)
 #include "arm64/context_arm64.h"
 #define RUNTIME_CONTEXT_TYPE arm64::Arm64Context
+#elif defined(__riscv)
+#include "riscv64/context_riscv64.h"
+#define RUNTIME_CONTEXT_TYPE riscv64::Riscv64Context
 #elif defined(__i386__)
 #include "x86/context_x86.h"
 #define RUNTIME_CONTEXT_TYPE x86::X86Context
diff --git a/runtime/arch/instruction_set_features.cc b/runtime/arch/instruction_set_features.cc
index ec1e340..d88c544 100644
--- a/runtime/arch/instruction_set_features.cc
+++ b/runtime/arch/instruction_set_features.cc
@@ -14,20 +14,17 @@
  * limitations under the License.
  */
 
-#include <algorithm>
-
 #include "instruction_set_features.h"
 
 #include <algorithm>
 #include <ostream>
 
 #include "android-base/strings.h"
-
-#include "base/casts.h"
-#include "base/utils.h"
-
 #include "arm/instruction_set_features_arm.h"
 #include "arm64/instruction_set_features_arm64.h"
+#include "base/casts.h"
+#include "base/utils.h"
+#include "riscv64/instruction_set_features_riscv64.h"
 #include "x86/instruction_set_features_x86.h"
 #include "x86_64/instruction_set_features_x86_64.h"
 
@@ -41,6 +38,8 @@
       return ArmInstructionSetFeatures::FromVariant(variant, error_msg);
     case InstructionSet::kArm64:
       return Arm64InstructionSetFeatures::FromVariant(variant, error_msg);
+    case InstructionSet::kRiscv64:
+      return Riscv64InstructionSetFeatures::FromVariant(variant, error_msg);
     case InstructionSet::kX86:
       return X86InstructionSetFeatures::FromVariant(variant, error_msg);
     case InstructionSet::kX86_64:
@@ -53,6 +52,33 @@
   UNREACHABLE();
 }
 
+std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromVariantAndHwcap(
+    InstructionSet isa, const std::string& variant, std::string* error_msg) {
+  auto variant_features = FromVariant(isa, variant, error_msg);
+  if (variant_features == nullptr) {
+    return nullptr;
+  }
+  // Pixel3a is wrongly reporting itself as cortex-a75, so validate the features
+  // with hwcaps.
+  // Note that when cross-compiling on device (using dex2oat32 for compiling
+  // arm64), the hwcaps will report that no feature is supported. This is
+  // currently our best approach to be safe/correct. Maybe using the
+  // cpu_features library could fix this issue.
+  if (isa == InstructionSet::kArm64) {
+    auto new_features = down_cast<const Arm64InstructionSetFeatures*>(variant_features.get())
+        ->IntersectWithHwcap();
+    if (!variant_features->Equals(new_features.get())) {
+      LOG(WARNING) << "Mismatch between instruction set variant of device ("
+            << *variant_features
+            << ") and features returned by the hardware (" << *new_features << ")";
+    }
+    return new_features;
+  } else {
+    // TODO: Implement this validation on all architectures.
+    return variant_features;
+  }
+}
+
 std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromBitmap(InstructionSet isa,
                                                                                  uint32_t bitmap) {
   std::unique_ptr<const InstructionSetFeatures> result;
@@ -64,6 +90,9 @@
     case InstructionSet::kArm64:
       result = Arm64InstructionSetFeatures::FromBitmap(bitmap);
       break;
+    case InstructionSet::kRiscv64:
+      result = Riscv64InstructionSetFeatures::FromBitmap(bitmap);
+      break;
     case InstructionSet::kX86:
       result = X86InstructionSetFeatures::FromBitmap(bitmap);
       break;
@@ -86,6 +115,8 @@
       return ArmInstructionSetFeatures::FromCppDefines();
     case InstructionSet::kArm64:
       return Arm64InstructionSetFeatures::FromCppDefines();
+    case InstructionSet::kRiscv64:
+      return Riscv64InstructionSetFeatures::FromCppDefines();
     case InstructionSet::kX86:
       return X86InstructionSetFeatures::FromCppDefines();
     case InstructionSet::kX86_64:
@@ -116,6 +147,8 @@
       return ArmInstructionSetFeatures::FromCpuInfo();
     case InstructionSet::kArm64:
       return Arm64InstructionSetFeatures::FromCpuInfo();
+    case InstructionSet::kRiscv64:
+      return Riscv64InstructionSetFeatures::FromCpuInfo();
     case InstructionSet::kX86:
       return X86InstructionSetFeatures::FromCpuInfo();
     case InstructionSet::kX86_64:
@@ -135,6 +168,8 @@
       return ArmInstructionSetFeatures::FromHwcap();
     case InstructionSet::kArm64:
       return Arm64InstructionSetFeatures::FromHwcap();
+    case InstructionSet::kRiscv64:
+      return Riscv64InstructionSetFeatures::FromHwcap();
     case InstructionSet::kX86:
       return X86InstructionSetFeatures::FromHwcap();
     case InstructionSet::kX86_64:
@@ -154,6 +189,8 @@
       return ArmInstructionSetFeatures::FromAssembly();
     case InstructionSet::kArm64:
       return Arm64InstructionSetFeatures::FromAssembly();
+    case InstructionSet::kRiscv64:
+      return Riscv64InstructionSetFeatures::FromAssembly();
     case InstructionSet::kX86:
       return X86InstructionSetFeatures::FromAssembly();
     case InstructionSet::kX86_64:
@@ -173,6 +210,8 @@
       return ArmInstructionSetFeatures::FromCpuFeatures();
     case InstructionSet::kArm64:
       return Arm64InstructionSetFeatures::FromCpuFeatures();
+    case InstructionSet::kRiscv64:
+      return Riscv64InstructionSetFeatures::FromCpuFeatures();
     case InstructionSet::kX86:
       return X86InstructionSetFeatures::FromCpuFeatures();
     case InstructionSet::kX86_64:
@@ -249,6 +288,12 @@
   return down_cast<const Arm64InstructionSetFeatures*>(this);
 }
 
+const Riscv64InstructionSetFeatures* InstructionSetFeatures::AsRiscv64InstructionSetFeatures()
+    const {
+  DCHECK_EQ(InstructionSet::kRiscv64, GetInstructionSet());
+  return down_cast<const Riscv64InstructionSetFeatures*>(this);
+}
+
 const X86InstructionSetFeatures* InstructionSetFeatures::AsX86InstructionSetFeatures() const {
   DCHECK(InstructionSet::kX86 == GetInstructionSet() ||
          InstructionSet::kX86_64 == GetInstructionSet());
diff --git a/runtime/arch/instruction_set_features.h b/runtime/arch/instruction_set_features.h
index b80d36f..1f41b39 100644
--- a/runtime/arch/instruction_set_features.h
+++ b/runtime/arch/instruction_set_features.h
@@ -28,6 +28,7 @@
 
 class ArmInstructionSetFeatures;
 class Arm64InstructionSetFeatures;
+class Riscv64InstructionSetFeatures;
 class X86InstructionSetFeatures;
 class X86_64InstructionSetFeatures;
 
@@ -39,6 +40,12 @@
                                                                    const std::string& variant,
                                                                    std::string* error_msg);
 
+  // Process a CPU variant string for the given ISA and make sure the features advertised
+  // are supported by the hardware. This is needed for Pixel3a which wrongly
+  // reports itself as cortex-a75.
+  static std::unique_ptr<const InstructionSetFeatures> FromVariantAndHwcap(
+      InstructionSet isa, const std::string& variant, std::string* error_msg);
+
   // Parse a bitmap for the given isa and create an InstructionSetFeatures.
   static std::unique_ptr<const InstructionSetFeatures> FromBitmap(InstructionSet isa,
                                                                   uint32_t bitmap);
@@ -115,6 +122,9 @@
   // Down cast this Arm64InstructionFeatures.
   const Arm64InstructionSetFeatures* AsArm64InstructionSetFeatures() const;
 
+  // Down cast this Riscv64InstructionFeatures.
+  const Riscv64InstructionSetFeatures* AsRiscv64InstructionSetFeatures() const;
+
   // Down cast this X86InstructionFeatures.
   const X86InstructionSetFeatures* AsX86InstructionSetFeatures() const;
 
diff --git a/runtime/arch/quick_alloc_entrypoints.S b/runtime/arch/quick_alloc_entrypoints.S
index 32888ed..5d4b24b 100644
--- a/runtime/arch/quick_alloc_entrypoints.S
+++ b/runtime/arch/quick_alloc_entrypoints.S
@@ -16,27 +16,27 @@
 
 .macro GENERATE_ALLOC_ENTRYPOINTS c_suffix, cxx_suffix
 // Called by managed code to allocate an object of a resolved class.
-ONE_ARG_DOWNCALL art_quick_alloc_object_resolved\c_suffix, artAllocObjectFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ONE_ARG_DOWNCALL art_quick_alloc_object_resolved\c_suffix, artAllocObjectFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 // Called by managed code to allocate an object of an initialized class.
-ONE_ARG_DOWNCALL art_quick_alloc_object_initialized\c_suffix, artAllocObjectFromCodeInitialized\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ONE_ARG_DOWNCALL art_quick_alloc_object_initialized\c_suffix, artAllocObjectFromCodeInitialized\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 // Called by managed code to allocate an object when the caller doesn't know whether it has access
 // to the created type.
-ONE_ARG_DOWNCALL art_quick_alloc_object_with_checks\c_suffix, artAllocObjectFromCodeWithChecks\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ONE_ARG_DOWNCALL art_quick_alloc_object_with_checks\c_suffix, artAllocObjectFromCodeWithChecks\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 // Called by managed code to allocate a string if it could not be removed by any optimizations
-ONE_ARG_DOWNCALL art_quick_alloc_string_object\c_suffix, artAllocStringObject\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ONE_ARG_DOWNCALL art_quick_alloc_string_object\c_suffix, artAllocStringObject\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 // Called by managed code to allocate an array of a resolve class.
-TWO_ARG_DOWNCALL art_quick_alloc_array_resolved\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+TWO_ARG_DOWNCALL art_quick_alloc_array_resolved\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 // Called by managed code to allocate a string from bytes
-FOUR_ARG_DOWNCALL art_quick_alloc_string_from_bytes\c_suffix, artAllocStringFromBytesFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+FOUR_ARG_DOWNCALL art_quick_alloc_string_from_bytes\c_suffix, artAllocStringFromBytesFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 // Called by managed code to allocate a string from chars
-THREE_ARG_DOWNCALL art_quick_alloc_string_from_chars\c_suffix, artAllocStringFromCharsFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+THREE_ARG_DOWNCALL art_quick_alloc_string_from_chars\c_suffix, artAllocStringFromCharsFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 // Called by managed code to allocate a string from string
-ONE_ARG_DOWNCALL art_quick_alloc_string_from_string\c_suffix, artAllocStringFromStringFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ONE_ARG_DOWNCALL art_quick_alloc_string_from_string\c_suffix, artAllocStringFromStringFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 
-TWO_ARG_DOWNCALL art_quick_alloc_array_resolved8\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
-TWO_ARG_DOWNCALL art_quick_alloc_array_resolved16\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
-TWO_ARG_DOWNCALL art_quick_alloc_array_resolved32\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
-TWO_ARG_DOWNCALL art_quick_alloc_array_resolved64\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+TWO_ARG_DOWNCALL art_quick_alloc_array_resolved8\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
+TWO_ARG_DOWNCALL art_quick_alloc_array_resolved16\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
+TWO_ARG_DOWNCALL art_quick_alloc_array_resolved32\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
+TWO_ARG_DOWNCALL art_quick_alloc_array_resolved64\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 .endm
 
 .macro GENERATE_ALL_ALLOC_ENTRYPOINTS
@@ -58,29 +58,29 @@
 // GENERATE_ALL_ALLOC_ENTRYPOINTS for selectively implementing allocation fast paths in
 // hand-written assembly.
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(c_suffix, cxx_suffix) \
-  ONE_ARG_DOWNCALL art_quick_alloc_object_resolved ## c_suffix, artAllocObjectFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  ONE_ARG_DOWNCALL art_quick_alloc_object_resolved ## c_suffix, artAllocObjectFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(c_suffix, cxx_suffix) \
-  ONE_ARG_DOWNCALL art_quick_alloc_object_initialized ## c_suffix, artAllocObjectFromCodeInitialized ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  ONE_ARG_DOWNCALL art_quick_alloc_object_initialized ## c_suffix, artAllocObjectFromCodeInitialized ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(c_suffix, cxx_suffix) \
-  ONE_ARG_DOWNCALL art_quick_alloc_object_with_checks ## c_suffix, artAllocObjectFromCodeWithChecks ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  ONE_ARG_DOWNCALL art_quick_alloc_object_with_checks ## c_suffix, artAllocObjectFromCodeWithChecks ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_OBJECT(c_suffix, cxx_suffix) \
-  ONE_ARG_DOWNCALL art_quick_alloc_string_object ## c_suffix, artAllocStringObject ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  ONE_ARG_DOWNCALL art_quick_alloc_string_object ## c_suffix, artAllocStringObject ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(c_suffix, cxx_suffix) \
-  FOUR_ARG_DOWNCALL art_quick_alloc_string_from_bytes ## c_suffix, artAllocStringFromBytesFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  FOUR_ARG_DOWNCALL art_quick_alloc_string_from_bytes ## c_suffix, artAllocStringFromBytesFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(c_suffix, cxx_suffix) \
-  THREE_ARG_DOWNCALL art_quick_alloc_string_from_chars ## c_suffix, artAllocStringFromCharsFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  THREE_ARG_DOWNCALL art_quick_alloc_string_from_chars ## c_suffix, artAllocStringFromCharsFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(c_suffix, cxx_suffix) \
-  ONE_ARG_DOWNCALL art_quick_alloc_string_from_string ## c_suffix, artAllocStringFromStringFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  ONE_ARG_DOWNCALL art_quick_alloc_string_from_string ## c_suffix, artAllocStringFromStringFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(c_suffix, cxx_suffix) \
-  TWO_ARG_DOWNCALL art_quick_alloc_array_resolved ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  TWO_ARG_DOWNCALL art_quick_alloc_array_resolved ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(c_suffix, cxx_suffix) \
-  TWO_ARG_DOWNCALL art_quick_alloc_array_resolved8 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  TWO_ARG_DOWNCALL art_quick_alloc_array_resolved8 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(c_suffix, cxx_suffix) \
-  TWO_ARG_DOWNCALL art_quick_alloc_array_resolved16 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  TWO_ARG_DOWNCALL art_quick_alloc_array_resolved16 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(c_suffix, cxx_suffix) \
-  TWO_ARG_DOWNCALL art_quick_alloc_array_resolved32 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  TWO_ARG_DOWNCALL art_quick_alloc_array_resolved32 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(c_suffix, cxx_suffix) \
-  TWO_ARG_DOWNCALL art_quick_alloc_array_resolved64 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+  TWO_ARG_DOWNCALL art_quick_alloc_array_resolved64 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
 
 .macro GENERATE_ALLOC_ENTRYPOINTS_FOR_REGION_TLAB_ALLOCATOR
 // This is to be separately defined for each architecture to allow a hand-written assembly fast path.
diff --git a/runtime/arch/riscv64/asm_support_riscv64.S b/runtime/arch/riscv64/asm_support_riscv64.S
new file mode 100644
index 0000000..71c9928
--- /dev/null
+++ b/runtime/arch/riscv64/asm_support_riscv64.S
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2023 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_RUNTIME_ARCH_RISCV64_ASM_SUPPORT_RISCV64_S_
+#define ART_RUNTIME_ARCH_RISCV64_ASM_SUPPORT_RISCV64_S_
+
+#include "asm_support_riscv64.h"
+#include "interpreter/cfi_asm_support.h"
+
+// Define special registers.
+
+// Register holding Thread::Current().
+#define xSELF s1
+
+
+.macro ENTRY name
+    .hidden \name  // Hide this as a global symbol, so we do not incur plt calls.
+    .global \name
+    // ART-compiled functions have OatQuickMethodHeader but assembly functions do not.
+    // Prefix the assembly code with 0xFFs, which means there is no method header.
+    .byte 0xFF, 0xFF, 0xFF, 0xFF
+    // Cache alignment for function entry.
+    // Use 0xFF as the last 4 bytes of alignment stand for OatQuickMethodHeader.
+    .balign 16, 0xFF
+\name:
+    .cfi_startproc
+.endm
+
+
+.macro END name
+    .cfi_endproc
+.endm
+
+
+.macro UNDEFINED name
+    ENTRY \name
+        unimp
+    END \name
+.endm
+
+
+// The spec is not clear whether the CFA is part of the saved state and tools differ in the
+// behaviour, so explicitly set the CFA to avoid any ambiguity.
+.macro CFI_RESTORE_STATE_AND_DEF_CFA reg, offset
+    .cfi_restore_state
+    .cfi_def_cfa \reg, \offset
+.endm
+
+
+.macro INCREASE_FRAME frame_adjustment
+    addi sp, sp, -(\frame_adjustment)
+    .cfi_adjust_cfa_offset (\frame_adjustment)
+.endm
+
+
+.macro DECREASE_FRAME frame_adjustment
+    addi sp, sp, (\frame_adjustment)
+    .cfi_adjust_cfa_offset -(\frame_adjustment)
+.endm
+
+
+.macro SAVE_GPR reg, offset
+    sd \reg, (\offset)(sp)
+    .cfi_rel_offset \reg, (\offset)
+.endm
+
+
+.macro RESTORE_GPR reg, offset
+    ld \reg, (\offset)(sp)
+    .cfi_restore \reg
+.endm
+
+
+.macro SAVE_FPR reg, offset
+    fsd \reg, (\offset)(sp)
+    .cfi_rel_offset \reg, (\offset)
+.endm
+
+
+.macro RESTORE_FPR reg, offset
+    fld \reg, (\offset)(sp)
+    .cfi_restore \reg
+.endm
+
+
+.macro LOAD_RUNTIME_INSTANCE reg
+#if __has_feature(hwaddress_sanitizer)
+#error "ART does not support HWASAN on RISC-V yet"
+#else
+    la \reg, _ZN3art7Runtime9instance_E
+#endif
+    ld \reg, 0(\reg)
+.endm
+
+
+// We need to save callee-save GPRs on the stack as they may contain references, and must be
+// visible to GC (unless the called method holds mutator lock and prevents GC from happening).
+// FP callee-saves shall be preserved by whatever runtime function we call, so they do not need
+// to be saved.
+.macro SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL
+#if (FRAME_SIZE_SAVE_REFS_AND_ARGS != 8*(1 + 8 + 7 + 11 + 1))
+#error "FRAME_SIZE_SAVE_REFS_AND_ARGS(RISCV64) size not as expected."
+#endif
+    // stack slot (0*8)(sp) is for ArtMethod*
+
+    SAVE_FPR fa0, (1*8)
+    SAVE_FPR fa1, (2*8)
+    SAVE_FPR fa2, (3*8)
+    SAVE_FPR fa3, (4*8)
+    SAVE_FPR fa4, (5*8)
+    SAVE_FPR fa5, (6*8)
+    SAVE_FPR fa6, (7*8)
+    SAVE_FPR fa7, (8*8)
+
+    SAVE_GPR fp,  (9*8)  // x8, frame pointer
+
+    // a0 is the method pointer
+    SAVE_GPR a1,  (10*8)  // x11
+    SAVE_GPR a2,  (11*8)  // x12
+    SAVE_GPR a3,  (12*8)  // x13
+    SAVE_GPR a4,  (13*8)  // x14
+    SAVE_GPR a5,  (14*8)  // x15
+    SAVE_GPR a6,  (15*8)  // x16
+    SAVE_GPR a7,  (16*8)  // x17
+
+    // s1 is the ART thread register
+    SAVE_GPR s2,  (17*8)  // x18
+    SAVE_GPR s3,  (18*8)  // x19
+    SAVE_GPR s4,  (19*8)  // x20
+    SAVE_GPR s5,  (20*8)  // x21
+    SAVE_GPR s6,  (21*8)  // x22
+    SAVE_GPR s7,  (22*8)  // x23
+    SAVE_GPR s8,  (23*8)  // x24
+    SAVE_GPR s9,  (24*8)  // x25
+    SAVE_GPR s10, (25*8)  // x26
+    SAVE_GPR s11, (26*8)  // x27
+
+    SAVE_GPR ra,  (27*8)  // x1, return address
+.endm
+
+
+.macro RESTORE_SAVE_REFS_AND_ARGS_FRAME_INTERNAL base
+    // stack slot (0*8)(sp) is for ArtMethod*
+
+    RESTORE_FPR fa0, (1*8)
+    RESTORE_FPR fa1, (2*8)
+    RESTORE_FPR fa2, (3*8)
+    RESTORE_FPR fa3, (4*8)
+    RESTORE_FPR fa4, (5*8)
+    RESTORE_FPR fa5, (6*8)
+    RESTORE_FPR fa6, (7*8)
+    RESTORE_FPR fa7, (8*8)
+
+    RESTORE_GPR fp,  (9*8)  // x8, frame pointer
+
+    // a0 is the method pointer
+    RESTORE_GPR a1,  (10*8)  // x11
+    RESTORE_GPR a2,  (11*8)  // x12
+    RESTORE_GPR a3,  (12*8)  // x13
+    RESTORE_GPR a4,  (13*8)  // x14
+    RESTORE_GPR a5,  (14*8)  // x15
+    RESTORE_GPR a6,  (15*8)  // x16
+    RESTORE_GPR a7,  (16*8)  // x17
+
+    // s1 is the ART thread register
+    RESTORE_GPR s2,  (17*8)  // x18
+    RESTORE_GPR s3,  (18*8)  // x19
+    RESTORE_GPR s4,  (19*8)  // x20
+    RESTORE_GPR s5,  (20*8)  // x21
+    RESTORE_GPR s6,  (21*8)  // x22
+    RESTORE_GPR s7,  (22*8)  // x23
+    RESTORE_GPR s8,  (23*8)  // x24
+    RESTORE_GPR s9,  (24*8)  // x25
+    RESTORE_GPR s10, (25*8)  // x26
+    RESTORE_GPR s11, (26*8)  // x27
+
+    RESTORE_GPR ra,  (27*8)  // x1, return address
+.endm
+
+
+.macro SETUP_CALLEE_SAVE_FRAME_COMMON_INTERNAL reg
+    // ArtMethod* is in reg, store it at the bottom of the stack.
+    sd \reg, (sp)
+
+    // Place sp in Thread::Current()->top_quick_frame.
+    sd sp, THREAD_TOP_QUICK_FRAME_OFFSET(xSELF)
+.endm
+
+
+.macro SETUP_CALLEE_SAVE_FRAME_COMMON tmpreg, offset
+    // art::Runtime* tmpreg = art::Runtime::instance_;
+    LOAD_RUNTIME_INSTANCE \tmpreg
+
+    // ArtMethod* tmpreg = Runtime::instance_->callee_save_methods_[<callee-save-frame-type>];
+    ld  \tmpreg, \offset(\tmpreg)
+
+    SETUP_CALLEE_SAVE_FRAME_COMMON_INTERNAL \tmpreg
+.endm
+
+
+.macro SETUP_SAVE_REFS_AND_ARGS_FRAME
+    INCREASE_FRAME FRAME_SIZE_SAVE_REFS_AND_ARGS
+    SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL
+    SETUP_CALLEE_SAVE_FRAME_COMMON t0, RUNTIME_SAVE_REFS_AND_ARGS_METHOD_OFFSET
+.endm
+
+
+.macro SETUP_SAVE_REFS_AND_ARGS_FRAME_WITH_METHOD_IN_A0
+    INCREASE_FRAME FRAME_SIZE_SAVE_REFS_AND_ARGS
+    SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL
+    SETUP_CALLEE_SAVE_FRAME_COMMON_INTERNAL a0
+.endm
+
+
+.macro RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME_INTERNAL
+    DECREASE_FRAME FRAME_SIZE_SAVE_REFS_AND_ARGS
+.endm
+
+
+.macro SAVE_ALL_CALLEE_SAVES
+#if (FRAME_SIZE_SAVE_ALL_CALLEE_SAVES != 8*(12 + 11 + 1 + 1 + 1))
+#error "FRAME_SIZE_SAVE_ALL_CALLEE_SAVES(RISCV64) size not as expected."
+#endif
+    // stack slot (0*8)(sp) is for ArtMethod*
+    // stack slot (1*8)(sp) is for padding
+
+    // FP callee-saves.
+    SAVE_FPR fs0,  (8*2)   // f8
+    SAVE_FPR fs1,  (8*3)   // f9
+    SAVE_FPR fs2,  (8*4)   // f18
+    SAVE_FPR fs3,  (8*5)   // f19
+    SAVE_FPR fs4,  (8*6)   // f20
+    SAVE_FPR fs5,  (8*7)   // f21
+    SAVE_FPR fs6,  (8*8)   // f22
+    SAVE_FPR fs7,  (8*9)   // f23
+    SAVE_FPR fs8,  (8*10)  // f24
+    SAVE_FPR fs9,  (8*11)  // f25
+    SAVE_FPR fs10, (8*12)  // f26
+    SAVE_FPR fs11, (8*13)  // f27
+
+    // GP callee-saves
+    SAVE_GPR s0,  (8*14)  // x8/fp, frame pointer
+    // s1 is the ART thread register
+    SAVE_GPR s2,  (8*15)  // x18
+    SAVE_GPR s3,  (8*16)  // x19
+    SAVE_GPR s4,  (8*17)  // x20
+    SAVE_GPR s5,  (8*18)  // x21
+    SAVE_GPR s6,  (8*19)  // x22
+    SAVE_GPR s7,  (8*20)  // x23
+    SAVE_GPR s8,  (8*21)  // x24
+    SAVE_GPR s9,  (8*22)  // x25
+    SAVE_GPR s10, (8*23)  // x26
+    SAVE_GPR s11, (8*24)  // x27
+
+    SAVE_GPR ra,  (8*25)  // x1, return address
+.endm
+
+
+.macro SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
+    INCREASE_FRAME FRAME_SIZE_SAVE_ALL_CALLEE_SAVES
+    SAVE_ALL_CALLEE_SAVES
+    SETUP_CALLEE_SAVE_FRAME_COMMON t0, RUNTIME_SAVE_ALL_CALLEE_SAVES_METHOD_OFFSET
+.endm
+
+
+.macro SETUP_SAVE_EVERYTHING_FRAME
+#if (FRAME_SIZE_SAVE_EVERYTHING != 8*(1 + 32 + 27))
+#error "FRAME_SIZE_SAVE_EVERYTHING(ARM64) size not as expected."
+#endif
+    INCREASE_FRAME FRAME_SIZE_SAVE_EVERYTHING
+
+    // stack slot (8*0)(sp) is for ArtMethod*
+
+    // 32 slots for FPRs
+    SAVE_FPR ft0,  8*1   // f0
+    SAVE_FPR ft1,  8*2   // f1
+    SAVE_FPR ft2,  8*3   // f2
+    SAVE_FPR ft3,  8*4   // f3
+    SAVE_FPR ft4,  8*5   // f4
+    SAVE_FPR ft5,  8*6   // f5
+    SAVE_FPR ft6,  8*7   // f6
+    SAVE_FPR ft7,  8*8   // f7
+    SAVE_FPR fs0,  8*9   // f8
+    SAVE_FPR fs1,  8*10  // f9
+#define SAVE_EVERYTHING_FRAME_OFFSET_FA0 (8*11)
+    SAVE_FPR fa0,  8*11  // f10, offset must equal SAVE_EVERYTHING_FRAME_OFFSET_FA0
+    SAVE_FPR fa1,  8*12  // f11
+    SAVE_FPR fa2,  8*13  // f12
+    SAVE_FPR fa3,  8*14  // f13
+    SAVE_FPR fa4,  8*15  // f14
+    SAVE_FPR fa5,  8*16  // f15
+    SAVE_FPR fa6,  8*17  // f16
+    SAVE_FPR fa7,  8*18  // f17
+    SAVE_FPR fs2,  8*19  // f18
+    SAVE_FPR fs3,  8*20  // f19
+    SAVE_FPR fs4,  8*21  // f20
+    SAVE_FPR fs5,  8*22  // f21
+    SAVE_FPR fs6,  8*23  // f22
+    SAVE_FPR fs7,  8*24  // f23
+    SAVE_FPR fs8,  8*25  // f24
+    SAVE_FPR fs9,  8*26  // f25
+    SAVE_FPR fs10, 8*27  // f26
+    SAVE_FPR fs11, 8*28  // f27
+    SAVE_FPR ft8,  8*29  // f28
+    SAVE_FPR ft9,  8*30  // f29
+    SAVE_FPR ft10, 8*31  // f30
+    SAVE_FPR ft11, 8*32  // f31
+
+    // 27 slots for GPRs (excluded: zero/x0, sp/x2, gp/x3, tp/x4, s1/x9 -- the ART thread register)
+    SAVE_GPR t0,  8*33  // x5
+    SAVE_GPR t1,  8*34  // x6
+    SAVE_GPR t2,  8*35  // x7
+    SAVE_GPR s0,  8*36  // x8
+#define SAVE_EVERYTHING_FRAME_OFFSET_A0 (8*37)
+    SAVE_GPR a0,  8*37  // x10, offset must equal SAVE_EVERYTHING_FRAME_OFFSET_A0
+    SAVE_GPR a1,  8*38  // x11
+    SAVE_GPR a2,  8*39  // x12
+    SAVE_GPR a3,  8*40  // x13
+    SAVE_GPR a4,  8*41  // x14
+    SAVE_GPR a5,  8*42  // x15
+    SAVE_GPR a6,  8*43  // x16
+    SAVE_GPR a7,  8*44  // x17
+    SAVE_GPR s2,  8*45  // x18
+    SAVE_GPR s3,  8*46  // x19
+    SAVE_GPR s4,  8*47  // x20
+    SAVE_GPR s5,  8*48  // x21
+    SAVE_GPR s6,  8*49  // x22
+    SAVE_GPR s7,  8*50  // x23
+    SAVE_GPR s8,  8*51  // x24
+    SAVE_GPR s9,  8*52  // x25
+    SAVE_GPR s10, 8*53  // x26
+    SAVE_GPR s11, 8*54  // x27
+    SAVE_GPR t3,  8*55  // x28
+    SAVE_GPR t4,  8*56  // x29
+    SAVE_GPR t5,  8*57  // x30
+    SAVE_GPR t6,  8*58  // x31
+
+    SAVE_GPR ra,  8*59  // x1, return address
+
+    SETUP_CALLEE_SAVE_FRAME_COMMON t0, RUNTIME_SAVE_EVERYTHING_METHOD_OFFSET
+.endm
+
+
+.macro RESTORE_SAVE_EVERYTHING_FRAME
+    // stack slot (8*0)(sp) is for ArtMethod*
+
+    // 32 slots for FPRs
+    RESTORE_FPR ft0,  (8*1)   // f0
+    RESTORE_FPR ft1,  (8*2)   // f1
+    RESTORE_FPR ft2,  (8*3)   // f2
+    RESTORE_FPR ft3,  (8*4)   // f3
+    RESTORE_FPR ft4,  (8*5)   // f4
+    RESTORE_FPR ft5,  (8*6)   // f5
+    RESTORE_FPR ft6,  (8*7)   // f6
+    RESTORE_FPR ft7,  (8*8)   // f7
+    RESTORE_FPR fs0,  (8*9)   // f8
+    RESTORE_FPR fs1,  (8*10)  // f9
+#if SAVE_EVERYTHING_FRAME_OFFSET_FA0 != (8*11)
+#error "unexpected SAVE_EVERYTHING_FRAME_OFFSET_FA0"
+#endif
+    RESTORE_FPR fa0,  (8*11)  // f10, offset must equal SAVE_EVERYTHING_FRAME_OFFSET_FA0
+    RESTORE_FPR fa1,  (8*12)  // f11
+    RESTORE_FPR fa2,  (8*13)  // f12
+    RESTORE_FPR fa3,  (8*14)  // f13
+    RESTORE_FPR fa4,  (8*15)  // f14
+    RESTORE_FPR fa5,  (8*16)  // f15
+    RESTORE_FPR fa6,  (8*17)  // f16
+    RESTORE_FPR fa7,  (8*18)  // f17
+    RESTORE_FPR fs2,  (8*19)  // f18
+    RESTORE_FPR fs3,  (8*20)  // f19
+    RESTORE_FPR fs4,  (8*21)  // f20
+    RESTORE_FPR fs5,  (8*22)  // f21
+    RESTORE_FPR fs6,  (8*23)  // f22
+    RESTORE_FPR fs7,  (8*24)  // f23
+    RESTORE_FPR fs8,  (8*25)  // f24
+    RESTORE_FPR fs9,  (8*26)  // f25
+    RESTORE_FPR fs10, (8*27)  // f26
+    RESTORE_FPR fs11, (8*28)  // f27
+    RESTORE_FPR ft8,  (8*29)  // f28
+    RESTORE_FPR ft9,  (8*30)  // f29
+    RESTORE_FPR ft10, (8*31)  // f30
+    RESTORE_FPR ft11, (8*32)  // f31
+
+    // 26 slots for GPRs (excluded: zero/x0, sp/x2, gp/x3, tp/x4, s1/x9 -- the ART thread register)
+    RESTORE_GPR t0,  (8*33)  // x5
+    RESTORE_GPR t1,  (8*34)  // x6
+    RESTORE_GPR t2,  (8*35)  // x7
+    RESTORE_GPR s0,  (8*36)  // x8
+#if SAVE_EVERYTHING_FRAME_OFFSET_A0 != (8*37)
+#error "unexpected SAVE_EVERYTHING_FRAME_OFFSET_A0"
+#endif
+    RESTORE_GPR a0,  (8*37)  // x10, offset must equal SAVE_EVERYTHING_FRAME_OFFSET_A0
+    RESTORE_GPR a1,  (8*38)  // x11
+    RESTORE_GPR a2,  (8*39)  // x12
+    RESTORE_GPR a3,  (8*40)  // x13
+    RESTORE_GPR a4,  (8*41)  // x14
+    RESTORE_GPR a5,  (8*42)  // x15
+    RESTORE_GPR a6,  (8*43)  // x16
+    RESTORE_GPR a7,  (8*44)  // x17
+    RESTORE_GPR s2,  (8*45)  // x18
+    RESTORE_GPR s3,  (8*46)  // x19
+    RESTORE_GPR s4,  (8*47)  // x20
+    RESTORE_GPR s5,  (8*48)  // x21
+    RESTORE_GPR s6,  (8*49)  // x22
+    RESTORE_GPR s7,  (8*50)  // x23
+    RESTORE_GPR s8,  (8*51)  // x24
+    RESTORE_GPR s9,  (8*52)  // x25
+    RESTORE_GPR s10, (8*53)  // x26
+    RESTORE_GPR s11, (8*54)  // x27
+    RESTORE_GPR t3,  (8*55)  // x28
+    RESTORE_GPR t4,  (8*56)  // x29
+    RESTORE_GPR t5,  (8*57)  // x30
+    RESTORE_GPR t6,  (8*58)  // x31
+
+    RESTORE_GPR ra,  (8*59)  // x1, return address
+
+    DECREASE_FRAME FRAME_SIZE_SAVE_EVERYTHING
+.endm
+
+
+// Macro that calls through to artDeliverPendingExceptionFromCode, where the pending exception is
+// Thread::Current()->exception_ when the runtime method frame is ready.
+.macro DELIVER_PENDING_EXCEPTION_FRAME_READY
+    mv a0, xSELF
+    call artDeliverPendingExceptionFromCode  // Point of no return.
+    unimp                                    // Unreachable.
+.endm
+
+
+// Macro that calls through to artDeliverPendingExceptionFromCode, where the pending exception is
+// Thread::Current()->exception_.
+.macro DELIVER_PENDING_EXCEPTION
+    SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
+    DELIVER_PENDING_EXCEPTION_FRAME_READY
+.endm
+
+
+.macro RETURN_OR_DELIVER_PENDING_EXCEPTION_REG reg
+    ld \reg, THREAD_EXCEPTION_OFFSET(xSELF)
+    bnez \reg, 1f
+    ret
+1:
+    DELIVER_PENDING_EXCEPTION
+.endm
+
+#endif  // ART_RUNTIME_ARCH_RISCV64_ASM_SUPPORT_RISCV64_S_
diff --git a/runtime/arch/riscv64/asm_support_riscv64.h b/runtime/arch/riscv64/asm_support_riscv64.h
new file mode 100644
index 0000000..d4c90f6
--- /dev/null
+++ b/runtime/arch/riscv64/asm_support_riscv64.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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_RUNTIME_ARCH_RISCV64_ASM_SUPPORT_RISCV64_H_
+#define ART_RUNTIME_ARCH_RISCV64_ASM_SUPPORT_RISCV64_H_
+
+#include "asm_support.h"
+#include "entrypoints/entrypoint_asm_constants.h"
+
+// FS0 - FS11, S0, S2 - S11, RA, ArtMethod* and padding, total 8*(12 + 11 + 1 + 1 + 1) = 208
+#define FRAME_SIZE_SAVE_ALL_CALLEE_SAVES 208
+// FA0 - FA7, A1 - A7, S0, S2 - S11, RA and ArtMethod*, total 8*(8 + 7 + 11 + 1 + 1) = 224
+// Excluded GPRs are: A0 (ArtMethod*), S1/TR (ART thread register).
+#define FRAME_SIZE_SAVE_REFS_AND_ARGS    224
+// All 32 FPRs, 27 GPRs and ArtMethod*, total 8*(32 + 27 + 1) = 480
+// Excluded GPRs are: SP, Zero, TP, GP, S1/TR (ART thread register).
+#define FRAME_SIZE_SAVE_EVERYTHING       480
+
+#endif  // ART_RUNTIME_ARCH_RISCV64_ASM_SUPPORT_RISCV64_H_
diff --git a/runtime/arch/riscv64/callee_save_frame_riscv64.h b/runtime/arch/riscv64/callee_save_frame_riscv64.h
new file mode 100644
index 0000000..a1c081c
--- /dev/null
+++ b/runtime/arch/riscv64/callee_save_frame_riscv64.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 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_RUNTIME_ARCH_RISCV64_CALLEE_SAVE_FRAME_RISCV64_H_
+#define ART_RUNTIME_ARCH_RISCV64_CALLEE_SAVE_FRAME_RISCV64_H_
+
+#include "arch/instruction_set.h"
+#include "base/bit_utils.h"
+#include "base/callee_save_type.h"
+#include "base/enums.h"
+#include "quick/quick_method_frame_info.h"
+#include "registers_riscv64.h"
+#include "runtime_globals.h"
+
+namespace art {
+namespace riscv64 {
+
+static constexpr uint32_t kRiscv64CalleeSaveAlwaysSpills =
+    (1 << art::riscv64::RA);  // Return address
+// Callee-saved registers except for SP and S1 (SP is callee-saved according to RISC-V spec, but
+// it cannot contain object reference, and S1(TR) is excluded as the ART thread register).
+static constexpr uint32_t kRiscv64CalleeSaveRefSpills =
+    (1 << art::riscv64::S0) | (1 << art::riscv64::S2) | (1 << art::riscv64::S3) |
+    (1 << art::riscv64::S4) | (1 << art::riscv64::S5) | (1 << art::riscv64::S6) |
+    (1 << art::riscv64::S7) | (1 << art::riscv64::S8) | (1 << art::riscv64::S9) |
+    (1 << art::riscv64::S10) | (1 << art::riscv64::S11);
+// Stack pointer SP is excluded (although it is callee-saved by calling convention) because it is
+// restored by the code logic and not from a stack frame.
+static constexpr uint32_t kRiscv64CalleeSaveAllSpills = 0;
+// Argument registers except X10/A0 (which contains method pointer).
+static constexpr uint32_t kRiscv64CalleeSaveArgSpills =
+    (1 << art::riscv64::A1) | (1 << art::riscv64::A2) | (1 << art::riscv64::A3) |
+    (1 << art::riscv64::A4) | (1 << art::riscv64::A5) | (1 << art::riscv64::A6) |
+    (1 << art::riscv64::A7);
+// All registers except SP, immutable Zero, unallocatable TP and GP, and the ART thread register TR.
+static constexpr uint32_t kRiscv64CalleeSaveEverythingSpills =
+    (1 << art::riscv64::T0) | (1 << art::riscv64::T1) | (1 << art::riscv64::T2) |
+    (1 << art::riscv64::T3) | (1 << art::riscv64::T4) | (1 << art::riscv64::T5) |
+    (1 << art::riscv64::T6) | (1 << art::riscv64::A0) | (1 << art::riscv64::A1) |
+    (1 << art::riscv64::A2) | (1 << art::riscv64::A3) | (1 << art::riscv64::A4) |
+    (1 << art::riscv64::A5) | (1 << art::riscv64::A6) | (1 << art::riscv64::A7);
+
+// No references in floating-point registers.
+static constexpr uint32_t kRiscv64CalleeSaveFpSpills = 0;
+// Floating-point argument registers FA0 - FA7.
+static constexpr uint32_t kRiscv64CalleeSaveFpArgSpills =
+    (1 << art::riscv64::FA0) | (1 << art::riscv64::FA1) | (1 << art::riscv64::FA2) |
+    (1 << art::riscv64::FA3) | (1 << art::riscv64::FA4) | (1 << art::riscv64::FA5) |
+    (1 << art::riscv64::FA6) | (1 << art::riscv64::FA7);
+// Floating-point callee-saved registers FS0 - FS11.
+static constexpr uint32_t kRiscv64CalleeSaveFpAllSpills =
+    (1 << art::riscv64::FS0) | (1 << art::riscv64::FS1) | (1 << art::riscv64::FS2) |
+    (1 << art::riscv64::FS3) | (1 << art::riscv64::FS4) | (1 << art::riscv64::FS5) |
+    (1 << art::riscv64::FS6) | (1 << art::riscv64::FS7) | (1 << art::riscv64::FS8) |
+    (1 << art::riscv64::FS9) | (1 << art::riscv64::FS10) | (1 << art::riscv64::FS11);
+// All floating-point registers.
+static constexpr uint32_t kRiscv64CalleeSaveFpEverythingSpills =
+    (1 << art::riscv64::FT0) | (1 << art::riscv64::FT1) | (1 << art::riscv64::FT2) |
+    (1 << art::riscv64::FT3) | (1 << art::riscv64::FT4) | (1 << art::riscv64::FT5) |
+    (1 << art::riscv64::FT6) | (1 << art::riscv64::FT7) | (1 << art::riscv64::FT8) |
+    (1 << art::riscv64::FT9) | (1 << art::riscv64::FT10) | (1 << art::riscv64::FT11) |
+    (1 << art::riscv64::FS0) | (1 << art::riscv64::FS1) | (1 << art::riscv64::FS2) |
+    (1 << art::riscv64::FS3) | (1 << art::riscv64::FS4) | (1 << art::riscv64::FS5) |
+    (1 << art::riscv64::FS6) | (1 << art::riscv64::FS7) | (1 << art::riscv64::FS8) |
+    (1 << art::riscv64::FS9) | (1 << art::riscv64::FS10) | (1 << art::riscv64::FS11) |
+    (1 << art::riscv64::FA0) | (1 << art::riscv64::FA1) | (1 << art::riscv64::FA2) |
+    (1 << art::riscv64::FA3) | (1 << art::riscv64::FA4) | (1 << art::riscv64::FA5) |
+    (1 << art::riscv64::FA6) | (1 << art::riscv64::FA7);
+
+class Riscv64CalleeSaveFrame {
+ public:
+  static constexpr uint32_t GetCoreSpills(CalleeSaveType type) {
+    type = GetCanonicalCalleeSaveType(type);
+    return kRiscv64CalleeSaveAlwaysSpills | kRiscv64CalleeSaveRefSpills |
+           (type == CalleeSaveType::kSaveRefsAndArgs ? kRiscv64CalleeSaveArgSpills : 0) |
+           (type == CalleeSaveType::kSaveAllCalleeSaves ? kRiscv64CalleeSaveAllSpills : 0) |
+           (type == CalleeSaveType::kSaveEverything ? kRiscv64CalleeSaveEverythingSpills : 0);
+  }
+
+  static constexpr uint32_t GetFpSpills(CalleeSaveType type) {
+    type = GetCanonicalCalleeSaveType(type);
+    return kRiscv64CalleeSaveFpSpills |
+           (type == CalleeSaveType::kSaveRefsAndArgs ? kRiscv64CalleeSaveFpArgSpills : 0) |
+           (type == CalleeSaveType::kSaveAllCalleeSaves ? kRiscv64CalleeSaveFpAllSpills : 0) |
+           (type == CalleeSaveType::kSaveEverything ? kRiscv64CalleeSaveFpEverythingSpills : 0);
+  }
+
+  static constexpr uint32_t GetFrameSize(CalleeSaveType type) {
+    type = GetCanonicalCalleeSaveType(type);
+    return RoundUp((POPCOUNT(GetCoreSpills(type)) /* gprs */ +
+                    POPCOUNT(GetFpSpills(type)) /* fprs */ + 1 /* Method* */) *
+                       static_cast<size_t>(kRiscv64PointerSize),
+                   kStackAlignment);
+  }
+
+  static constexpr QuickMethodFrameInfo GetMethodFrameInfo(CalleeSaveType type) {
+    type = GetCanonicalCalleeSaveType(type);
+    return QuickMethodFrameInfo(GetFrameSize(type), GetCoreSpills(type), GetFpSpills(type));
+  }
+
+  static constexpr size_t GetFpr1Offset(CalleeSaveType type) {
+    type = GetCanonicalCalleeSaveType(type);
+    return GetFrameSize(type) - (POPCOUNT(GetCoreSpills(type)) + POPCOUNT(GetFpSpills(type))) *
+                                    static_cast<size_t>(kRiscv64PointerSize);
+  }
+
+  static constexpr size_t GetGpr1Offset(CalleeSaveType type) {
+    type = GetCanonicalCalleeSaveType(type);
+    return GetFrameSize(type) -
+           POPCOUNT(GetCoreSpills(type)) * static_cast<size_t>(kRiscv64PointerSize);
+  }
+
+  static constexpr size_t GetReturnPcOffset(CalleeSaveType type) {
+    type = GetCanonicalCalleeSaveType(type);
+    return GetFrameSize(type) - static_cast<size_t>(kRiscv64PointerSize);
+  }
+};
+
+// Assembly entrypoints rely on these constants.
+static_assert(Riscv64CalleeSaveFrame::GetFrameSize(CalleeSaveType::kSaveRefsAndArgs) == 224);
+static_assert(Riscv64CalleeSaveFrame::GetFrameSize(CalleeSaveType::kSaveAllCalleeSaves) == 208);
+static_assert(Riscv64CalleeSaveFrame::GetFrameSize(CalleeSaveType::kSaveEverything) == 480);
+
+}  // namespace riscv64
+}  // namespace art
+
+#endif  // ART_RUNTIME_ARCH_RISCV64_CALLEE_SAVE_FRAME_RISCV64_H_
diff --git a/runtime/arch/riscv64/context_riscv64.cc b/runtime/arch/riscv64/context_riscv64.cc
new file mode 100644
index 0000000..84bd963
--- /dev/null
+++ b/runtime/arch/riscv64/context_riscv64.cc
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "context_riscv64.h"
+
+#include <stdint.h>
+
+#include "base/bit_utils.h"
+#include "base/bit_utils_iterator.h"
+#include "quick/quick_method_frame_info.h"
+#include "thread-current-inl.h"
+
+#if __has_feature(hwaddress_sanitizer)
+#include <sanitizer/hwasan_interface.h>
+#else
+#define __hwasan_handle_longjmp(sp)
+#endif
+
+namespace art {
+namespace riscv64 {
+
+static constexpr uint64_t gZero = 0;
+
+void Riscv64Context::Reset() {
+  std::fill_n(gprs_, arraysize(gprs_), nullptr);
+  std::fill_n(fprs_, arraysize(fprs_), nullptr);
+  gprs_[SP] = &sp_;
+  gprs_[kPC] = &pc_;
+  gprs_[A0] = &arg0_;
+  // Initialize registers with easy to spot debug values.
+  sp_ = kBadGprBase + SP;
+  pc_ = kBadGprBase + kPC;
+  arg0_ = 0;
+}
+
+void Riscv64Context::FillCalleeSaves(uint8_t* frame, const QuickMethodFrameInfo& frame_info) {
+  // RA is at top of the frame
+  DCHECK_NE(frame_info.CoreSpillMask() & ~(1u << RA), 0u);
+  gprs_[RA] = CalleeSaveAddress(frame, 0, frame_info.FrameSizeInBytes());
+
+  // Core registers come first, from the highest down to the lowest, with the exception of RA/X1.
+  int spill_pos = 1;
+  for (uint32_t core_reg : HighToLowBits(frame_info.CoreSpillMask() & ~(1u << RA))) {
+    gprs_[core_reg] = CalleeSaveAddress(frame, spill_pos, frame_info.FrameSizeInBytes());
+    ++spill_pos;
+  }
+  DCHECK_EQ(spill_pos, POPCOUNT(frame_info.CoreSpillMask()));
+
+  // FP registers come second, from the highest down to the lowest.
+  for (uint32_t fp_reg : HighToLowBits(frame_info.FpSpillMask())) {
+    fprs_[fp_reg] = CalleeSaveAddress(frame, spill_pos, frame_info.FrameSizeInBytes());
+    ++spill_pos;
+  }
+  DCHECK_EQ(spill_pos, POPCOUNT(frame_info.CoreSpillMask()) + POPCOUNT(frame_info.FpSpillMask()));
+}
+
+void Riscv64Context::SetGPR(uint32_t reg, uintptr_t value) {
+  DCHECK_LT(reg, arraysize(gprs_));
+  DCHECK_NE(reg, static_cast<uint32_t>(Zero));  // Zero/X0 is immutable (hard-wired zero)
+  DCHECK(IsAccessibleGPR(reg));
+  DCHECK_NE(gprs_[reg], &gZero);  // Can't overwrite this static value since they are never reset.
+  *gprs_[reg] = value;
+}
+
+void Riscv64Context::SetFPR(uint32_t reg, uintptr_t value) {
+  DCHECK_LT(reg, static_cast<uint32_t>(kNumberOfFRegisters));
+  DCHECK(IsAccessibleFPR(reg));
+  DCHECK_NE(fprs_[reg], &gZero);  // Can't overwrite this static value since they are never reset.
+  *fprs_[reg] = value;
+}
+
+void Riscv64Context::SmashCallerSaves() {
+  // Temporary registers T0 - T6 and argument registers A0 - A7 are caller-saved.
+  gprs_[Zero] = const_cast<uint64_t*>(&gZero);  // hard-wired zero
+  gprs_[T0] = nullptr;
+  gprs_[T1] = nullptr;
+  gprs_[T2] = nullptr;
+  gprs_[T3] = nullptr;
+  gprs_[T4] = nullptr;
+  gprs_[T5] = nullptr;
+  gprs_[T6] = nullptr;
+  gprs_[A0] = const_cast<uint64_t*>(&gZero);  // must be 0 because we want a null/zero return value
+  gprs_[A1] = nullptr;
+  gprs_[A2] = nullptr;
+  gprs_[A3] = nullptr;
+  gprs_[A4] = nullptr;
+  gprs_[A5] = nullptr;
+  gprs_[A6] = nullptr;
+  gprs_[A7] = nullptr;
+
+  // Temporary registers FT0 - FT11 and argument registers FA0 - FA7 are caller-saved.
+  fprs_[FT0] = nullptr;
+  fprs_[FT1] = nullptr;
+  fprs_[FT2] = nullptr;
+  fprs_[FT3] = nullptr;
+  fprs_[FT4] = nullptr;
+  fprs_[FT5] = nullptr;
+  fprs_[FT6] = nullptr;
+  fprs_[FT7] = nullptr;
+  fprs_[FT8] = nullptr;
+  fprs_[FT9] = nullptr;
+  fprs_[FT10] = nullptr;
+  fprs_[FT11] = nullptr;
+  fprs_[FA0] = nullptr;
+  fprs_[FA1] = nullptr;
+  fprs_[FA2] = nullptr;
+  fprs_[FA3] = nullptr;
+  fprs_[FA4] = nullptr;
+  fprs_[FA5] = nullptr;
+  fprs_[FA6] = nullptr;
+  fprs_[FA7] = nullptr;
+}
+
+extern "C" NO_RETURN void art_quick_do_long_jump(uint64_t*, uint64_t*);
+
+void Riscv64Context::DoLongJump() {
+  uint64_t gprs[arraysize(gprs_)];
+  uint64_t fprs[kNumberOfFRegisters];
+
+  // The long jump routine called below expects to find the value for SP at index 2.
+  DCHECK_EQ(SP, 2);
+
+  for (size_t i = 0; i < arraysize(gprs_); ++i) {
+    gprs[i] = gprs_[i] != nullptr ? *gprs_[i] : kBadGprBase + i;
+  }
+  for (size_t i = 0; i < kNumberOfFRegisters; ++i) {
+    fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : kBadFprBase + i;
+  }
+
+  // Fill in TR (the ART Thread Register) with the address of the current thread.
+  gprs[TR] = reinterpret_cast<uintptr_t>(Thread::Current());
+
+  // Tell HWASan about the new stack top.
+  __hwasan_handle_longjmp(reinterpret_cast<void*>(gprs[SP]));
+  art_quick_do_long_jump(gprs, fprs);
+}
+
+}  // namespace riscv64
+}  // namespace art
diff --git a/runtime/arch/riscv64/context_riscv64.h b/runtime/arch/riscv64/context_riscv64.h
new file mode 100644
index 0000000..437d6d9
--- /dev/null
+++ b/runtime/arch/riscv64/context_riscv64.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 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_RUNTIME_ARCH_RISCV64_CONTEXT_RISCV64_H_
+#define ART_RUNTIME_ARCH_RISCV64_CONTEXT_RISCV64_H_
+
+#include <android-base/logging.h>
+
+#include <cstdlib>
+
+#include "arch/context.h"
+#include "base/macros.h"
+#include "registers_riscv64.h"
+
+namespace art {
+namespace riscv64 {
+
+class Riscv64Context final : public Context {
+ public:
+  Riscv64Context() { Reset(); }
+
+  ~Riscv64Context() {}
+
+  void Reset() override;
+
+  void FillCalleeSaves(uint8_t* frame, const QuickMethodFrameInfo& fr) override;
+
+  void SetSP(uintptr_t new_sp) override { SetGPR(SP, new_sp); }
+
+  void SetPC(uintptr_t new_pc) override { SetGPR(kPC, new_pc); }
+
+  void SetNterpDexPC(uintptr_t /*dex_pc_ptr*/) override {
+    UNREACHABLE();  // Nterp is not supported on RISC-V  yet
+  }
+
+  void SetArg0(uintptr_t new_arg0_value) override { SetGPR(A0, new_arg0_value); }
+
+  bool IsAccessibleGPR(uint32_t reg) override {
+    DCHECK_LT(reg, arraysize(gprs_));
+    return gprs_[reg] != nullptr;
+  }
+
+  uintptr_t* GetGPRAddress(uint32_t reg) override {
+    DCHECK_LT(reg, arraysize(gprs_));
+    return gprs_[reg];
+  }
+
+  uintptr_t GetGPR(uint32_t reg) override {
+    // Note: PC isn't an available GPR (outside of internals), so don't allow retrieving the value.
+    DCHECK_LT(reg, static_cast<uint32_t>(kNumberOfXRegisters));
+    DCHECK(IsAccessibleGPR(reg));
+    return *gprs_[reg];
+  }
+
+  void SetGPR(uint32_t reg, uintptr_t value) override;
+
+  bool IsAccessibleFPR(uint32_t reg) override {
+    DCHECK_LT(reg, static_cast<uint32_t>(kNumberOfFRegisters));
+    return fprs_[reg] != nullptr;
+  }
+
+  uintptr_t GetFPR(uint32_t reg) override {
+    DCHECK_LT(reg, static_cast<uint32_t>(kNumberOfFRegisters));
+    DCHECK(IsAccessibleFPR(reg));
+    return *fprs_[reg];
+  }
+
+  void SetFPR(uint32_t reg, uintptr_t value) override;
+
+  void SmashCallerSaves() override;
+  NO_RETURN void DoLongJump() override;
+
+  static constexpr size_t kPC = kNumberOfXRegisters;
+
+ private:
+  // Pointers to register locations, initialized to null or the specific registers below. We need
+  // an additional one for the PC.
+  uintptr_t* gprs_[kNumberOfXRegisters + 1];
+  uint64_t* fprs_[kNumberOfFRegisters];
+  // Hold values for sp, pc and arg0 if they are not located within a stack frame.
+  uintptr_t sp_, pc_, arg0_;
+};
+
+}  // namespace riscv64
+}  // namespace art
+
+#endif  // ART_RUNTIME_ARCH_RISCV64_CONTEXT_RISCV64_H_
diff --git a/runtime/arch/riscv64/entrypoints_init_riscv64.cc b/runtime/arch/riscv64/entrypoints_init_riscv64.cc
new file mode 100644
index 0000000..811624c
--- /dev/null
+++ b/runtime/arch/riscv64/entrypoints_init_riscv64.cc
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "entrypoints/quick/quick_default_init_entrypoints.h"
+#include "entrypoints/quick/quick_entrypoints.h"
+
+namespace art {
+
+void UpdateReadBarrierEntrypoints(QuickEntryPoints* /*qpoints*/, bool /*is_active*/) {
+  // TODO(riscv64): add read barrier entrypoints
+}
+
+void InitEntryPoints(JniEntryPoints* jpoints,
+                     QuickEntryPoints* qpoints,
+                     bool monitor_jni_entry_exit) {
+  DefaultInitEntryPoints(jpoints, qpoints, monitor_jni_entry_exit);
+  // TODO(riscv64): add other entrypoints
+}
+
+}  // namespace art
diff --git a/runtime/arch/riscv64/fault_handler_riscv64.cc b/runtime/arch/riscv64/fault_handler_riscv64.cc
new file mode 100644
index 0000000..581f8b7
--- /dev/null
+++ b/runtime/arch/riscv64/fault_handler_riscv64.cc
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "fault_handler.h"
+
+#include <sys/ucontext.h>
+
+#include "base/logging.h"  // For VLOG.
+
+extern "C" void art_quick_throw_stack_overflow();
+extern "C" void art_quick_throw_null_pointer_exception_from_signal();
+extern "C" void art_quick_implicit_suspend();
+
+// RISCV64 specific fault handler functions (or stubs if unimplemented yet).
+
+namespace art {
+
+uintptr_t FaultManager::GetFaultPc(siginfo_t*, void* context) {
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  if (mc->__gregs[REG_SP] == 0) {
+    VLOG(signals) << "Missing SP";
+    return 0u;
+  }
+  return mc->__gregs[REG_PC];
+}
+
+uintptr_t FaultManager::GetFaultSp(void* context) {
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  return mc->__gregs[REG_SP];
+}
+
+bool NullPointerHandler::Action(int, siginfo_t*, void*) {
+  LOG(FATAL) << "NullPointerHandler::Action is not implemented for RISC-V";
+  return false;
+}
+
+bool SuspensionHandler::Action(int, siginfo_t*, void*) {
+  LOG(FATAL) << "SuspensionHandler::Action is not implemented for RISC-V";
+  return false;
+}
+
+bool StackOverflowHandler::Action(int, siginfo_t*, void*) {
+  LOG(FATAL) << "StackOverflowHandler::Action is not implemented for RISC-V";
+  return false;
+}
+
+}  // namespace art
diff --git a/runtime/arch/riscv64/instruction_set_features_riscv64.cc b/runtime/arch/riscv64/instruction_set_features_riscv64.cc
new file mode 100644
index 0000000..2ef4f84
--- /dev/null
+++ b/runtime/arch/riscv64/instruction_set_features_riscv64.cc
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "instruction_set_features_riscv64.h"
+
+#include <fstream>
+#include <sstream>
+
+#include "android-base/strings.h"
+#include "base/logging.h"
+
+namespace art {
+
+// Basic feature set is rv64gc, aka rv64imafdc.
+constexpr uint32_t BasicFeatures() {
+  return Riscv64InstructionSetFeatures::kExtGeneric | Riscv64InstructionSetFeatures::kExtCompressed;
+}
+
+Riscv64FeaturesUniquePtr Riscv64InstructionSetFeatures::FromVariant(
+    const std::string& variant, std::string* error_msg ATTRIBUTE_UNUSED) {
+  if (variant != "generic") {
+    LOG(WARNING) << "Unexpected CPU variant for Riscv64 using defaults: " << variant;
+  }
+  return Riscv64FeaturesUniquePtr(new Riscv64InstructionSetFeatures(BasicFeatures()));
+}
+
+Riscv64FeaturesUniquePtr Riscv64InstructionSetFeatures::FromBitmap(uint32_t bitmap) {
+  return Riscv64FeaturesUniquePtr(new Riscv64InstructionSetFeatures(bitmap));
+}
+
+Riscv64FeaturesUniquePtr Riscv64InstructionSetFeatures::FromCppDefines() {
+  return Riscv64FeaturesUniquePtr(new Riscv64InstructionSetFeatures(BasicFeatures()));
+}
+
+Riscv64FeaturesUniquePtr Riscv64InstructionSetFeatures::FromCpuInfo() {
+  UNIMPLEMENTED(WARNING);
+  return FromCppDefines();
+}
+
+Riscv64FeaturesUniquePtr Riscv64InstructionSetFeatures::FromHwcap() {
+  UNIMPLEMENTED(WARNING);
+  return FromCppDefines();
+}
+
+Riscv64FeaturesUniquePtr Riscv64InstructionSetFeatures::FromAssembly() {
+  UNIMPLEMENTED(WARNING);
+  return FromCppDefines();
+}
+
+Riscv64FeaturesUniquePtr Riscv64InstructionSetFeatures::FromCpuFeatures() {
+  UNIMPLEMENTED(WARNING);
+  return FromCppDefines();
+}
+
+bool Riscv64InstructionSetFeatures::Equals(const InstructionSetFeatures* other) const {
+  if (InstructionSet::kRiscv64 != other->GetInstructionSet()) {
+    return false;
+  }
+  return bits_ == other->AsRiscv64InstructionSetFeatures()->bits_;
+}
+
+uint32_t Riscv64InstructionSetFeatures::AsBitmap() const { return bits_; }
+
+std::string Riscv64InstructionSetFeatures::GetFeatureString() const {
+  std::string result = "rv64";
+  if (bits_ & kExtGeneric) {
+    result += "g";
+  }
+  if (bits_ & kExtCompressed) {
+    result += "c";
+  }
+  if (bits_ & kExtVector) {
+    result += "v";
+  }
+  return result;
+}
+
+std::unique_ptr<const InstructionSetFeatures>
+Riscv64InstructionSetFeatures::AddFeaturesFromSplitString(
+    const std::vector<std::string>& features ATTRIBUTE_UNUSED,
+    std::string* error_msg ATTRIBUTE_UNUSED) const {
+  UNIMPLEMENTED(WARNING);
+  return std::unique_ptr<const InstructionSetFeatures>(new Riscv64InstructionSetFeatures(bits_));
+}
+
+}  // namespace art
diff --git a/runtime/arch/riscv64/instruction_set_features_riscv64.h b/runtime/arch/riscv64/instruction_set_features_riscv64.h
new file mode 100644
index 0000000..1ad2ffd
--- /dev/null
+++ b/runtime/arch/riscv64/instruction_set_features_riscv64.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 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_RUNTIME_ARCH_RISCV64_INSTRUCTION_SET_FEATURES_RISCV64_H_
+#define ART_RUNTIME_ARCH_RISCV64_INSTRUCTION_SET_FEATURES_RISCV64_H_
+
+#include "arch/instruction_set_features.h"
+
+namespace art {
+
+class Riscv64InstructionSetFeatures;
+using Riscv64FeaturesUniquePtr = std::unique_ptr<const Riscv64InstructionSetFeatures>;
+
+// Instruction set features relevant to the RISCV64 architecture.
+class Riscv64InstructionSetFeatures final : public InstructionSetFeatures {
+ public:
+  // Bitmap positions for encoding features as a bitmap.
+  enum {
+    kExtGeneric = (1 << 0),     // G extension covers the basic set IMAFD
+    kExtCompressed = (1 << 1),  // C extension adds compressed instructions
+    kExtVector = (1 << 2)       // V extension adds vector instructions
+  };
+
+  static Riscv64FeaturesUniquePtr FromVariant(const std::string& variant, std::string* error_msg);
+
+  // Parse a bitmap and create an InstructionSetFeatures.
+  static Riscv64FeaturesUniquePtr FromBitmap(uint32_t bitmap);
+
+  // Turn C pre-processor #defines into the equivalent instruction set features.
+  static Riscv64FeaturesUniquePtr FromCppDefines();
+
+  // Process /proc/cpuinfo and use kRuntimeISA to produce InstructionSetFeatures.
+  static Riscv64FeaturesUniquePtr FromCpuInfo();
+
+  // Process the auxiliary vector AT_HWCAP entry and use kRuntimeISA to produce
+  // InstructionSetFeatures.
+  static Riscv64FeaturesUniquePtr FromHwcap();
+
+  // Use assembly tests of the current runtime (ie kRuntimeISA) to determine the
+  // InstructionSetFeatures. This works around kernel bugs in AT_HWCAP and /proc/cpuinfo.
+  static Riscv64FeaturesUniquePtr FromAssembly();
+
+  // Use external cpu_features library.
+  static Riscv64FeaturesUniquePtr FromCpuFeatures();
+
+  bool Equals(const InstructionSetFeatures* other) const override;
+
+  InstructionSet GetInstructionSet() const override { return InstructionSet::kRiscv64; }
+
+  uint32_t AsBitmap() const override;
+
+  std::string GetFeatureString() const override;
+
+  virtual ~Riscv64InstructionSetFeatures() {}
+
+ protected:
+  std::unique_ptr<const InstructionSetFeatures> AddFeaturesFromSplitString(
+      const std::vector<std::string>& features, std::string* error_msg) const override;
+
+ private:
+  explicit Riscv64InstructionSetFeatures(uint32_t bits) : InstructionSetFeatures(), bits_(bits) {}
+
+  // Extension bitmap.
+  const uint32_t bits_;
+
+  DISALLOW_COPY_AND_ASSIGN(Riscv64InstructionSetFeatures);
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_ARCH_RISCV64_INSTRUCTION_SET_FEATURES_RISCV64_H_
diff --git a/runtime/arch/riscv64/instruction_set_features_riscv64_test.cc b/runtime/arch/riscv64/instruction_set_features_riscv64_test.cc
new file mode 100644
index 0000000..aef3d67
--- /dev/null
+++ b/runtime/arch/riscv64/instruction_set_features_riscv64_test.cc
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "instruction_set_features_riscv64.h"
+
+#include <gtest/gtest.h>
+
+namespace art {
+
+TEST(Riscv64InstructionSetFeaturesTest, Riscv64FeaturesFromDefaultVariant) {
+  std::string error_msg;
+  std::unique_ptr<const InstructionSetFeatures> riscv64_features(
+      InstructionSetFeatures::FromVariant(InstructionSet::kRiscv64, "generic", &error_msg));
+  ASSERT_TRUE(riscv64_features.get() != nullptr) << error_msg;
+
+  EXPECT_EQ(riscv64_features->GetInstructionSet(), InstructionSet::kRiscv64);
+
+  EXPECT_TRUE(riscv64_features->Equals(riscv64_features.get()));
+
+  uint32_t expected_extensions =
+      Riscv64InstructionSetFeatures::kExtGeneric | Riscv64InstructionSetFeatures::kExtCompressed;
+  EXPECT_EQ(riscv64_features->AsBitmap(), expected_extensions);  // rv64gc, aka rv64imafdc
+}
+
+}  // namespace art
diff --git a/runtime/arch/riscv64/jni_entrypoints_riscv64.S b/runtime/arch/riscv64/jni_entrypoints_riscv64.S
new file mode 100644
index 0000000..63412b0
--- /dev/null
+++ b/runtime/arch/riscv64/jni_entrypoints_riscv64.S
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "asm_support_riscv64.S"
+
+UNDEFINED art_jni_method_start
+UNDEFINED art_jni_method_end
+UNDEFINED art_jni_read_barrier
+UNDEFINED art_jni_method_entry_hook
+UNDEFINED art_jni_lock_object_no_inline
+UNDEFINED art_jni_lock_object
+UNDEFINED art_jni_unlock_object_no_inline
+UNDEFINED art_jni_unlock_object
+
+
+// 8 argument GPRS: a0 - a7 and 8 argument FPRs: fa0 - fa7
+#define ALL_ARGS_SIZE (8 * (8 + 8))
+
+
+.macro SAVE_ALL_ARGS_INCREASE_FRAME extra_space
+    // Reserve space for all argument registers, plus the extra space.
+    INCREASE_FRAME (ALL_ARGS_SIZE + \extra_space)
+
+    // Argument GPRs a0 - a7.
+    SAVE_GPR a0, (8*0)
+    SAVE_GPR a1, (8*1)
+    SAVE_GPR a2, (8*2)
+    SAVE_GPR a3, (8*3)
+    SAVE_GPR a4, (8*4)
+    SAVE_GPR a5, (8*5)
+    SAVE_GPR a6, (8*6)
+    SAVE_GPR a7, (8*7)
+
+    // Argument FPRs fa0 - fa7.
+    SAVE_FPR fa0, (8*8)
+    SAVE_FPR fa1, (8*9)
+    SAVE_FPR fa2, (8*10)
+    SAVE_FPR fa3, (8*11)
+    SAVE_FPR fa4, (8*12)
+    SAVE_FPR fa5, (8*13)
+    SAVE_FPR fa6, (8*14)
+    SAVE_FPR fa7, (8*15)
+.endm
+
+
+.macro RESTORE_ALL_ARGS_DECREASE_FRAME extra_space
+    // Argument GPRs a0 - a7.
+    RESTORE_GPR a0, (8*0)
+    RESTORE_GPR a1, (8*1)
+    RESTORE_GPR a2, (8*2)
+    RESTORE_GPR a3, (8*3)
+    RESTORE_GPR a4, (8*4)
+    RESTORE_GPR a5, (8*5)
+    RESTORE_GPR a6, (8*6)
+    RESTORE_GPR a7, (8*7)
+
+    // Argument FPRs fa0 - fa7.
+    RESTORE_FPR fa0, (8*8)
+    RESTORE_FPR fa1, (8*9)
+    RESTORE_FPR fa2, (8*10)
+    RESTORE_FPR fa3, (8*11)
+    RESTORE_FPR fa4, (8*12)
+    RESTORE_FPR fa5, (8*13)
+    RESTORE_FPR fa6, (8*14)
+    RESTORE_FPR fa7, (8*15)
+
+    DECREASE_FRAME (ALL_ARGS_SIZE + \extra_space)
+.endm
+
+
+// JNI dlsym lookup stub.
+.extern artFindNativeMethod
+.extern artFindNativeMethodRunnable
+ENTRY art_jni_dlsym_lookup_stub
+    SAVE_ALL_ARGS_INCREASE_FRAME 2*8
+    SAVE_GPR fp, (ALL_ARGS_SIZE + 0)
+    SAVE_GPR ra, (ALL_ARGS_SIZE + 8)
+    add  fp, sp, ALL_ARGS_SIZE
+
+    // Call artFindNativeMethod for normal native.
+    // Call artFindNativeMethodRunnable for @FastNative or @CriticalNative.
+    // Both functions have a single argument: Thread::Current() in a0.
+    mv   a0, xSELF
+    ld   t0, THREAD_TOP_QUICK_FRAME_OFFSET(a0)   // uintptr_t tagged_quick_frame
+    andi t0, t0, ~TAGGED_JNI_SP_MASK             // ArtMethod** sp
+    ld   t0, (t0)                                // ArtMethod* method
+    lw   t0, ART_METHOD_ACCESS_FLAGS_OFFSET(t0)  // uint32_t access_flags
+    li   t1, (ACCESS_FLAGS_METHOD_IS_FAST_NATIVE | ACCESS_FLAGS_METHOD_IS_CRITICAL_NATIVE)
+    and  t0, t0, t1
+    bnez t0, .Llookup_stub_fast_or_critical_native
+    call artFindNativeMethod
+    j    .Llookup_stub_continue
+
+.Llookup_stub_fast_or_critical_native:
+    call  artFindNativeMethodRunnable
+
+.Llookup_stub_continue:
+    mv    t0, a0  // store result in a temp reg.
+    RESTORE_GPR fp, (ALL_ARGS_SIZE + 0)
+    RESTORE_GPR ra, (ALL_ARGS_SIZE + 8)
+    RESTORE_ALL_ARGS_DECREASE_FRAME 2*8
+
+    beqz  t0, 1f  // is method code null?
+    jr    t0      // if non-null, tail call to method code.
+1:
+    ret           // restore regs and return to caller to handle exception.
+END art_jni_dlsym_lookup_stub
+
+
+// JNI dlsym lookup stub for @CriticalNative.
+ENTRY art_jni_dlsym_lookup_critical_stub
+    // The hidden arg holding the tagged method is t6 (loaded by art_quick_generic_jni_trampoline).
+    // Bit 0 set means generic JNI.
+    // For generic JNI we already have a managed frame, so we reuse the art_jni_dlsym_lookup_stub.
+    andi  t6, t6, 1
+    beqz  t6, 1f
+    j     art_jni_dlsym_lookup_stub
+1:
+    // TODO(riscv64): implement for code paths other than generic JNI trampoline.
+    unimp
+END art_jni_dlsym_lookup_critical_stub
diff --git a/runtime/arch/riscv64/quick_entrypoints_riscv64.S b/runtime/arch/riscv64/quick_entrypoints_riscv64.S
new file mode 100644
index 0000000..ef9a043
--- /dev/null
+++ b/runtime/arch/riscv64/quick_entrypoints_riscv64.S
@@ -0,0 +1,789 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "asm_support_riscv64.S"
+#include "interpreter/cfi_asm_support.h"
+
+
+// Wrap ExecuteSwitchImpl in assembly method which specifies DEX PC for unwinding.
+//  Argument 0: a0: The context pointer for ExecuteSwitchImpl.
+//  Argument 1: a1: Pointer to the templated ExecuteSwitchImpl to call.
+//  Argument 2: a2: The value of DEX PC (memory address of the methods bytecode).
+ENTRY ExecuteSwitchImplAsm
+    INCREASE_FRAME 16
+    SAVE_GPR s1, 0
+    SAVE_GPR ra, 8
+
+    mv s1, a2   // s1 = DEX PC
+    CFI_DEFINE_DEX_PC_WITH_OFFSET(0 /* a0 */, 9 /* s1, a.k.a. x9 */, 0)
+    jalr a1     // Call the wrapped method.
+
+    RESTORE_GPR s1, 0
+    RESTORE_GPR ra, 8
+    DECREASE_FRAME 16
+    ret
+END ExecuteSwitchImplAsm
+
+
+.macro INVOKE_STUB_CREATE_FRAME
+    // Save ra, fp, xSELF (current thread) a4, a5 (they will be needed in the invoke stub return)
+    // and callee-save regs s3 - s5 that are clobbered here and in art_quick_invoke_(static_)_stub.
+    INCREASE_FRAME 48
+    SAVE_GPR fp,    (8*0)
+    SAVE_GPR xSELF, (8*1)
+    SAVE_GPR a4,    (8*2)
+    SAVE_GPR a5,    (8*3)
+    SAVE_GPR s3,    (8*4)
+    SAVE_GPR ra,    (8*5)
+
+    mv fp, sp  // save frame pointer
+    .cfi_def_cfa_register fp
+
+    addi t0, a2, (__SIZEOF_POINTER__ + 0xf) // Reserve space for ArtMethod*, arguments and
+    andi t0, t0, ~0xf                       // round up for 16-byte stack alignment.
+    sub  sp, sp, t0
+
+    mv xSELF, a3
+
+    // Copy arguments on stack (4 bytes per slot):
+    //   a1: source address
+    //   a2: arguments length
+    //   s3: destination address.
+
+    add s3, sp, 8  // destination address is bottom of the stack + 8 bytes for ArtMethod* (null)
+
+    beqz a2, 2f      // loop through 4-byte arguments from the last to the first
+1:
+    addi a2, a2, -4
+    add  t0, a1, a2  // t0 is the source address of the next copied argument
+    lw   t1, (t0)    // t1 is the 4 bytes at address t0
+    add  t0, s3, a2  // t0 is the destination address of the next copied argument
+    sw   t1, (t0)    // save t1 at the destination address t0
+    bnez a2, 1b
+2:
+    sd zero, (sp)  // Store null into ArtMethod* at bottom of frame.
+.endm
+
+
+.macro INVOKE_STUB_CALL_AND_RETURN
+    // Call the method.
+    ld   t0, ART_METHOD_QUICK_CODE_OFFSET_64(a0)
+    jalr t0
+
+    mv sp, fp  // restore frame pointer
+    .cfi_def_cfa_register sp
+
+    // Restore ra, fp, xSELF (current thread) a4 (shorty), a5 (result pointer) and callee-save
+    // regs s3 - s5 from stack.
+    RESTORE_GPR fp,    (8*0)
+    RESTORE_GPR xSELF, (8*1)
+    RESTORE_GPR a4,    (8*2)
+    RESTORE_GPR a5,    (8*3)
+    RESTORE_GPR s3,    (8*4)
+    RESTORE_GPR ra,    (8*5)
+    DECREASE_FRAME 48
+
+    // Load result type (1-byte symbol) from a5.
+    // Check result type and store the correct register into the jvalue in memory at a4 address.
+    lbu t0, (a5)
+
+    li t1, 'V'  // void (do not store result at all)
+    beq t1, t0, 1f
+
+    li t1, 'D'  // double
+    beq t1, t0, 2f
+
+    li t1, 'F'  // float
+    beq t1, t0, 3f
+
+    // Otherwise, result is in a0 (either 8 or 4 bytes, but it is fine to store 8 bytes as the
+    // upper bytes in a0 in that case are zero, and jvalue has enough space).
+    sd a0, (a4)
+1:
+    ret
+
+2:  // double: result in fa0 (8 bytes)
+    fsd fa0, (a4)
+    ret
+
+3:  // float: result in fa0 (4 bytes)
+    fsw fa0, (a4)
+    ret
+.endm
+
+
+ENTRY art_deliver_pending_exception
+    DELIVER_PENDING_EXCEPTION
+END art_deliver_pending_exception
+
+
+// Macros for loading an argument into a register.
+//  label - the base name of the label of the load routine,
+//  reg - the register to load,
+//  args - pointer to current argument, incremented by size,
+//  size - the size of the register - 4 or 8 bytes,
+//  load - instruction used for loading,
+//  nh4_reg - the register to fill with the address of the next handler for 4-byte values,
+//  nh4_l - the base name of the label of the next handler for 4-byte values,
+//  nh8_reg - the register to fill with the address of the next handler for 8-byte values,
+//  nh8_l - the base name of the label of the next handler for 8-byte values,
+//  cont - the base name of the label for continuing the shorty processing loop,
+//  sfx - suffix added to all labels to make labels unique for different users.
+.macro INVOKE_STUB_LOAD_REG label, reg, args, size, load, nh4_reg, nh4_l, nh8_reg, nh8_l, cont, sfx
+\label\sfx:
+    \load \reg, (\args)
+    addi  \args, \args, \size
+    la    \nh4_reg, \nh4_l\sfx
+    la    \nh8_reg, \nh8_l\sfx
+    j     \cont\sfx
+.endm
+
+
+// Macro for skipping an argument that does not fit into argument registers.
+//  label - the base name of the label of the skip routine,
+//  args - pointer to current argument, incremented by size,
+//  size - the size of the argument - 4 or 8 bytes,
+//  cont - the base name of the label for continuing the shorty processing loop,
+//  sfx - suffix added to all labels to make labels unique for different users.
+.macro INVOKE_STUB_SKIP_ARG label, args, size, cont, sfx
+\label\sfx:
+    addi \args, \args, \size
+    j    \cont\sfx
+.endm
+
+
+// Fill registers a1 to a7 and fa0 to fa7 with parameters.
+// Parse the passed shorty to determine which register to load.
+//  a5 - shorty,
+//  s3 - points to arguments on the stack,
+//  sfx - suffix added to all labels to make labels unique for different users.
+.macro INVOKE_STUB_LOAD_ALL_ARGS sfx
+    mv   t0, a5                        // Load shorty address,
+    addi t0, t0, 1                     // plus one to skip the return type.
+
+    // Load this (if instance method) and addresses for routines that load argument GPRs and FPRs.
+    .ifc \sfx, _instance
+        lw   a1, (s3)                  // Load "this" parameter,
+        addi s3, s3, 4                 // and increment arg pointer.
+        la   t3, .Lload4i2\sfx
+        la   t4, .Lload8i2\sfx
+    .else
+        la   t3, .Lload4i1\sfx
+        la   t4, .Lload8i1\sfx
+    .endif
+    la   t5, .Lload4f0\sfx
+    la   t6, .Lload8f0\sfx
+
+    // Loop to fill registers.
+.Lfill_regs\sfx:
+    lb   t1, (t0)                      // Load next character in signature, and increment.
+    addi t0, t0, 1                     // and increment.
+    beqz t1, .Lcall_method\sfx         // Exit at end of signature. Shorty 0 terminated.
+
+    li   t2, 'J'
+    beq  t1, t2, .Lload_long\sfx       // Is this a long?
+
+    li   t2, 'F'
+    beq  t1, t2, .Lload_float\sfx      // Is this a float?
+
+    li   t2, 'D'
+    beq  t1, t2, .Lload_double\sfx     // Is this a double?
+
+    // Everything else uses a 4-byte GPR.
+    jr   t3
+
+.Lload_long\sfx:
+    jr   t4
+
+.Lload_float\sfx:
+    jr   t5
+
+.Lload_double\sfx:
+    jr   t6
+
+// Handlers for loading other args (not float/double/long) into 4-byte GPRs.
+    .ifnc \sfx, _instance
+        INVOKE_STUB_LOAD_REG \
+            .Lload4i1, a1, s3, 4, lw, t3, .Lload4i2, t4, .Lload8i2, .Lfill_regs, \sfx
+    .endif
+    INVOKE_STUB_LOAD_REG .Lload4i2, a2, s3, 4, lw, t3, .Lload4i3, t4, .Lload8i3, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4i3, a3, s3, 4, lw, t3, .Lload4i4, t4, .Lload8i4, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4i4, a4, s3, 4, lw, t3, .Lload4i5, t4, .Lload8i5, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4i5, a5, s3, 4, lw, t3, .Lload4i6, t4, .Lload8i6, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4i6, a6, s3, 4, lw, t3, .Lload4i7, t4, .Lload8i7, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4i7, a7, s3, 4, lw, t3, .Lskip4, t4, .Lskip8, .Lfill_regs, \sfx
+
+// Handlers for loading longs into 8-byte GPRs.
+    .ifnc \sfx, _instance
+        INVOKE_STUB_LOAD_REG \
+            .Lload8i1, a1, s3, 8, ld, t3, .Lload4i2, t4, .Lload8i2, .Lfill_regs, \sfx
+    .endif
+    INVOKE_STUB_LOAD_REG .Lload8i2, a2, s3, 8, ld, t3, .Lload4i3, t4, .Lload8i3, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8i3, a3, s3, 8, ld, t3, .Lload4i4, t4, .Lload8i4, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8i4, a4, s3, 8, ld, t3, .Lload4i5, t4, .Lload8i5, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8i5, a5, s3, 8, ld, t3, .Lload4i6, t4, .Lload8i6, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8i6, a6, s3, 8, ld, t3, .Lload4i7, t4, .Lload8i7, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8i7, a7, s3, 8, ld, t3, .Lskip4, t4, .Lskip8, .Lfill_regs, \sfx
+
+// Handlers for loading floats into FPRs.
+    INVOKE_STUB_LOAD_REG .Lload4f0, fa0, s3, 4, flw, t5, .Lload4f1, t6, .Lload8f1, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4f1, fa1, s3, 4, flw, t5, .Lload4f2, t6, .Lload8f2, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4f2, fa2, s3, 4, flw, t5, .Lload4f3, t6, .Lload8f3, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4f3, fa3, s3, 4, flw, t5, .Lload4f4, t6, .Lload8f4, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4f4, fa4, s3, 4, flw, t5, .Lload4f5, t6, .Lload8f5, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4f5, fa5, s3, 4, flw, t5, .Lload4f6, t6, .Lload8f6, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4f6, fa6, s3, 4, flw, t5, .Lload4f7, t6, .Lload8f7, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload4f7, fa7, s3, 4, flw, t5, .Lskip4, t6, .Lskip8, .Lfill_regs, \sfx
+
+// Handlers for loading doubles into FPRs.
+    INVOKE_STUB_LOAD_REG .Lload8f0, fa0, s3, 8, fld, t5, .Lload4f1, t6, .Lload8f1, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8f1, fa1, s3, 8, fld, t5, .Lload4f2, t6, .Lload8f2, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8f2, fa2, s3, 8, fld, t5, .Lload4f3, t6, .Lload8f3, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8f3, fa3, s3, 8, fld, t5, .Lload4f4, t6, .Lload8f4, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8f4, fa4, s3, 8, fld, t5, .Lload4f5, t6, .Lload8f5, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8f5, fa5, s3, 8, fld, t5, .Lload4f6, t6, .Lload8f6, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8f6, fa6, s3, 8, fld, t5, .Lload4f7, t6, .Lload8f7, .Lfill_regs, \sfx
+    INVOKE_STUB_LOAD_REG .Lload8f7, fa7, s3, 8, fld, t5, .Lskip4, t6, .Lskip8, .Lfill_regs, \sfx
+
+// Handlers for skipping arguments that do not fit into registers.
+    INVOKE_STUB_SKIP_ARG .Lskip4, s3, 4, .Lfill_regs, \sfx
+    INVOKE_STUB_SKIP_ARG .Lskip8, s3, 8, .Lfill_regs, \sfx
+
+.Lcall_method\sfx:
+.endm
+
+
+// void art_quick_invoke_stub(ArtMethod* method,   // a0
+//                            uint32_t*  args,     // a1
+//                            uint32_t   argsize,  // a2
+//                            Thread*    self,     // a3
+//                            JValue*    result,   // a4
+//                            char*      shorty)   // a5
+ENTRY art_quick_invoke_stub
+    INVOKE_STUB_CREATE_FRAME
+
+    // Load args into registers.
+    INVOKE_STUB_LOAD_ALL_ARGS _instance
+
+    // Call the method and return.
+    INVOKE_STUB_CALL_AND_RETURN
+END art_quick_invoke_stub
+
+
+// void art_quick_invoke_static_stub(ArtMethod* method,   // a0
+//                                   uint32_t*  args,     // a1
+//                                   uint32_t   argsize,  // a2
+//                                   Thread*    self,     // a3
+//                                   JValue*    result,   // a4
+//                                   char*      shorty)   // a5
+ENTRY art_quick_invoke_static_stub
+    INVOKE_STUB_CREATE_FRAME
+
+    // Load args into registers.
+    INVOKE_STUB_LOAD_ALL_ARGS _static
+
+    // Call the method and return.
+    INVOKE_STUB_CALL_AND_RETURN
+END art_quick_invoke_static_stub
+
+
+ENTRY art_quick_generic_jni_trampoline
+    SETUP_SAVE_REFS_AND_ARGS_FRAME_WITH_METHOD_IN_A0
+
+    // Save sp, so we can have static CFI info.
+    mv   fp, sp
+    .cfi_def_cfa_register fp
+
+    li   t0, GENERIC_JNI_TRAMPOLINE_RESERVED_AREA
+    sub  sp, sp, t0
+
+    mv   a0, xSELF    // Thread*
+    mv   a1, fp       // SP for the managed frame.
+    mv   a2, sp       // reserved area for arguments and other saved data (up to managed frame)
+    call artQuickGenericJniTrampoline
+
+    // Check for error (class init check or locking for synchronized native method can throw).
+    beqz a0, .Lexception_in_native
+
+    mv   t0, a0       // save pointer to native method code into temporary
+
+    // Load argument GPRs from stack (saved there by artQuickGenericJniTrampoline).
+    ld  a0, 8*0(sp)   // JniEnv* for the native method
+    ld  a1, 8*1(sp)
+    ld  a2, 8*2(sp)
+    ld  a3, 8*3(sp)
+    ld  a4, 8*4(sp)
+    ld  a5, 8*5(sp)
+    ld  a6, 8*6(sp)
+    ld  a7, 8*7(sp)
+
+    // Load argument FPRs from stack (saved there by artQuickGenericJniTrampoline).
+    fld  fa0, 8*8(sp)
+    fld  fa1, 8*9(sp)
+    fld  fa2, 8*10(sp)
+    fld  fa3, 8*11(sp)
+    fld  fa4, 8*12(sp)
+    fld  fa5, 8*13(sp)
+    fld  fa6, 8*14(sp)
+    fld  fa7, 8*15(sp)
+
+    ld  t6, 8*16(sp)  // @CriticalNative arg, used by art_jni_dlsym_lookup_critical_stub
+
+    ld  t1, 8*17(sp)  // restore stack
+    mv  sp, t1
+
+    jalr  t0  // call native method
+
+    // result sign extension is handled in C code, prepare for artQuickGenericJniEndTrampoline call:
+    // uint64_t artQuickGenericJniEndTrampoline(Thread* self,       // a0
+    //                                          jvalue result,      // a1 (need to move from a0)
+    //                                          uint64_t result_f)  // a2 (need to move from fa0)
+    mv  a1, a0
+    mv  a0, xSELF
+    fmv.x.d  a2, fa0
+    call artQuickGenericJniEndTrampoline
+
+    // Pending exceptions possible.
+    ld   t0, THREAD_EXCEPTION_OFFSET(xSELF)
+    bnez t0, .Lexception_in_native
+
+    // Tear down the alloca.
+    mv   sp, fp
+    .cfi_remember_state
+    .cfi_def_cfa_register sp
+
+    LOAD_RUNTIME_INSTANCE a1
+    lb   a1, RUN_EXIT_HOOKS_OFFSET_FROM_RUNTIME_INSTANCE(a1)
+    bnez a1, .Lcall_method_exit_hook
+
+.Lcall_method_exit_hook_done:
+    // This does not clobber the result register a0. a1 is not used for result as the managed code
+    // does not have a 128-bit type. Alternatively we could restore a subset of these registers.
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    fmv.d.x  fa0, a0
+    ret
+    CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_REFS_AND_ARGS
+
+.Lcall_method_exit_hook:
+    fmv.d.x  fa0, a0
+    li   a4, FRAME_SIZE_SAVE_REFS_AND_ARGS
+    jal  art_quick_method_exit_hook
+    j    .Lcall_method_exit_hook_done
+
+.Lexception_in_native:
+    // Move to a1 then sp to please assembler.
+    ld   a1, THREAD_TOP_QUICK_FRAME_OFFSET(xSELF)
+    addi sp, a1, -1  // Remove the GenericJNI tag.
+    call art_deliver_pending_exception
+END art_quick_generic_jni_trampoline
+
+
+ENTRY art_quick_to_interpreter_bridge
+    SETUP_SAVE_REFS_AND_ARGS_FRAME
+
+    // uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, ArtMethod** sp)
+    // a0 will contain ArtMethod*
+    mv   a1, xSELF
+    mv   a2, sp
+    call artQuickToInterpreterBridge
+
+    // TODO: no need to restore arguments in this case.
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+
+    fmv.d.x  fa0, a0  // copy the result to FP result register
+
+    RETURN_OR_DELIVER_PENDING_EXCEPTION_REG t0
+END art_quick_to_interpreter_bridge
+
+
+    .extern artMethodExitHook
+ENTRY art_quick_method_exit_hook
+    SETUP_SAVE_EVERYTHING_FRAME
+
+    addi a3, sp, SAVE_EVERYTHING_FRAME_OFFSET_FA0  // FP result ptr in kSaveEverything frame
+    addi a2, sp, SAVE_EVERYTHING_FRAME_OFFSET_A0   // integer result ptr in kSaveEverything frame
+    addi a1, sp, FRAME_SIZE_SAVE_EVERYTHING        // ArtMethod**
+    mv   a0, xSELF                                 // Thread::Current
+    call artMethodExitHook                         // (Thread*, ArtMethod**, gpr_res*, fpr_res*,
+                                                   // frame_size)
+
+    // Normal return.
+    RESTORE_SAVE_EVERYTHING_FRAME
+    ret
+END art_quick_method_exit_hook
+
+
+// On entry a0 is uintptr_t* gprs_ and a1 is uint64_t* fprs_.
+// Both must reside on the stack, between current sp and target sp.
+ENTRY art_quick_do_long_jump
+    // Load FPRs
+    fld  ft0,  8*0(a1)   // f0
+    fld  ft1,  8*1(a1)   // f1
+    fld  ft2,  8*2(a1)   // f2
+    fld  ft3,  8*3(a1)   // f3
+    fld  ft4,  8*4(a1)   // f4
+    fld  ft5,  8*5(a1)   // f5
+    fld  ft6,  8*6(a1)   // f6
+    fld  ft7,  8*7(a1)   // f7
+    fld  fs0,  8*8(a1)   // f8
+    fld  fs1,  8*9(a1)   // f9
+    fld  fa0,  8*10(a1)  // f10
+    fld  fa1,  8*11(a1)  // f11
+    fld  fa2,  8*12(a1)  // f12
+    fld  fa3,  8*13(a1)  // f13
+    fld  fa4,  8*14(a1)  // f14
+    fld  fa5,  8*15(a1)  // f15
+    fld  fa6,  8*16(a1)  // f16
+    fld  fa7,  8*17(a1)  // f17
+    fld  fs2,  8*18(a1)  // f18
+    fld  fs3,  8*19(a1)  // f19
+    fld  fs4,  8*20(a1)  // f20
+    fld  fs5,  8*21(a1)  // f21
+    fld  fs6,  8*22(a1)  // f22
+    fld  fs7,  8*23(a1)  // f23
+    fld  fs8,  8*24(a1)  // f24
+    fld  fs9,  8*25(a1)  // f25
+    fld  fs10, 8*26(a1)  // f26
+    fld  fs11, 8*27(a1)  // f27
+    fld  ft8,  8*28(a1)  // f28
+    fld  ft9,  8*29(a1)  // f29
+    fld  ft10, 8*30(a1)  // f30
+    fld  ft11, 8*31(a1)  // f31
+
+    // Load GPRs.
+    // Skip slot 8*0(a0) for zero/x0 as it is hard-wired zero.
+    ld  ra,   8*1(a0)   // x1
+    // Skip slot 8*2(a0) for sp/x2 as it is set below.
+    // Skip slot 8*3(a0) for platform-specific thread pointer gp/x3.
+    // Skip slot 8*4(a0) for platform-specific global pointer tp/x4.
+    // Skip slot 8*5(a0) for t0/x5 as it is clobbered below.
+    // Skip slot 8*6(a0) for t1/x6 as it is clobbered below.
+    ld  t2,   8*7(a0)   // x7
+    ld  s0,   8*8(a0)   // x8
+    ld  s1,   8*9(a0)   // x9
+    // Delay loading a0 as the base is in a0.
+    ld  a1,   8*11(a0)  // x11
+    ld  a2,   8*12(a0)  // x12
+    ld  a3,   8*13(a0)  // x13
+    ld  a4,   8*14(a0)  // x14
+    ld  a5,   8*15(a0)  // x15
+    ld  a6,   8*16(a0)  // x16
+    ld  a7,   8*17(a0)  // x17
+    ld  s2,   8*18(a0)  // x18
+    ld  s3,   8*19(a0)  // x19
+    ld  s4,   8*20(a0)  // x20
+    ld  s5,   8*21(a0)  // x21
+    ld  s6,   8*22(a0)  // x22
+    ld  s7,   8*23(a0)  // x23
+    ld  s8,   8*24(a0)  // x24
+    ld  s9,   8*25(a0)  // x25
+    ld  s10,  8*26(a0)  // x26
+    ld  s11,  8*27(a0)  // x27
+    ld  t3,   8*28(a0)  // x28
+    ld  t4,   8*29(a0)  // x29
+    ld  t5,   8*30(a0)  // x30
+    ld  t6,   8*31(a0)  // x31
+
+    // Load sp to t0.
+    ld  t0, 8*2(a0)
+
+    // Load PC to t1, it is in the last stack slot.
+    ld  t1, 8*32(a0)
+
+    // Now load a0.
+    ld  a0, 8*10(a0)  // x10
+
+    // Set sp. Do not access fprs_ and gprs_ from now, they are below sp.
+    mv sp, t0
+
+    jr  t1
+END art_quick_do_long_jump
+
+
+// Called by managed code that is attempting to call a method on a proxy class. On entry a0 holds
+// the proxy method and a1 holds the receiver. The frame size of the invoked proxy method agrees
+// with kSaveRefsAndArgs frame.
+.extern artQuickProxyInvokeHandler
+ENTRY art_quick_proxy_invoke_handler
+    SETUP_SAVE_REFS_AND_ARGS_FRAME_WITH_METHOD_IN_A0
+
+    // uint64_t artQuickProxyInvokeHandler(ArtMethod* proxy_method,   // a0
+    //                                     mirror::Object* receiver,  // a1
+    //                                     Thread* self,              // a2
+    //                                     ArtMethod** sp)            // a3
+    mv    a2, xSELF                   // pass Thread::Current
+    mv    a3, sp                      // pass sp
+    call  artQuickProxyInvokeHandler  // (Method* proxy method, receiver, Thread*, sp)
+
+    ld    a2, THREAD_EXCEPTION_OFFSET(xSELF)
+    bnez  a2, .Lexception_in_proxy    // success if no exception is pending
+    .cfi_remember_state
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME  // Restore frame
+    fmv.d.x  fa0, a0                  // Store result in fa0 in case it was float or double
+    ret                               // return on success
+
+.Lexception_in_proxy:
+    CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_REFS_AND_ARGS
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    DELIVER_PENDING_EXCEPTION
+END art_quick_proxy_invoke_handler
+
+
+.macro ONE_ARG_RUNTIME_EXCEPTION c_name, cxx_name
+.extern \cxx_name
+ENTRY \c_name
+    SETUP_SAVE_ALL_CALLEE_SAVES_FRAME // save all registers as basis for long jump context.
+    mv  a1, xSELF                     // pass Thread::Current.
+    jal \cxx_name                     // \cxx_name(arg, Thread*).
+    ebreak
+END \c_name
+.endm
+
+
+// Called to attempt to execute an obsolete method.
+ONE_ARG_RUNTIME_EXCEPTION art_invoke_obsolete_method_stub, artInvokeObsoleteMethod
+
+
+ENTRY art_quick_resolution_trampoline
+    SETUP_SAVE_REFS_AND_ARGS_FRAME
+
+    // const void* artQuickResolutionTrampoline(ArtMethod* called,         // a0
+    //                                          mirror::Object* receiver,  // a1
+    //                                          Thread* self,              // a2
+    //                                          ArtMethod** sp)            // a3
+    mv   a2, xSELF
+    mv   a3, sp
+    call artQuickResolutionTrampoline
+
+    beqz a0, 1f
+    .cfi_remember_state
+    mv   t0, a0    // Remember returned code pointer in t0.
+    ld   a0, (sp)  // artQuickResolutionTrampoline puts called method in *sp.
+
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    jr   t0
+1:
+    CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_REFS_AND_ARGS
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    DELIVER_PENDING_EXCEPTION
+END art_quick_resolution_trampoline
+
+
+UNDEFINED art_quick_imt_conflict_trampoline
+UNDEFINED art_quick_deoptimize_from_compiled_code
+UNDEFINED art_quick_string_builder_append
+UNDEFINED art_quick_compile_optimized
+UNDEFINED art_quick_method_entry_hook
+UNDEFINED art_quick_check_instance_of
+UNDEFINED art_quick_osr_stub
+
+UNDEFINED art_quick_alloc_array_resolved_dlmalloc
+UNDEFINED art_quick_alloc_array_resolved_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_array_resolved8_dlmalloc
+UNDEFINED art_quick_alloc_array_resolved8_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_array_resolved16_dlmalloc
+UNDEFINED art_quick_alloc_array_resolved16_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_array_resolved32_dlmalloc
+UNDEFINED art_quick_alloc_array_resolved32_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_array_resolved64_dlmalloc
+UNDEFINED art_quick_alloc_array_resolved64_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_object_resolved_dlmalloc
+UNDEFINED art_quick_alloc_object_resolved_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_object_initialized_dlmalloc
+UNDEFINED art_quick_alloc_object_initialized_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_object_with_checks_dlmalloc
+UNDEFINED art_quick_alloc_object_with_checks_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_string_object_dlmalloc
+UNDEFINED art_quick_alloc_string_object_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_string_from_bytes_dlmalloc
+UNDEFINED art_quick_alloc_string_from_bytes_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_string_from_chars_dlmalloc
+UNDEFINED art_quick_alloc_string_from_chars_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_string_from_string_dlmalloc
+UNDEFINED art_quick_alloc_string_from_string_dlmalloc_instrumented
+UNDEFINED art_quick_alloc_array_resolved_rosalloc
+UNDEFINED art_quick_alloc_array_resolved_rosalloc_instrumented
+UNDEFINED art_quick_alloc_array_resolved8_rosalloc
+UNDEFINED art_quick_alloc_array_resolved8_rosalloc_instrumented
+UNDEFINED art_quick_alloc_array_resolved16_rosalloc
+UNDEFINED art_quick_alloc_array_resolved16_rosalloc_instrumented
+UNDEFINED art_quick_alloc_array_resolved32_rosalloc
+UNDEFINED art_quick_alloc_array_resolved32_rosalloc_instrumented
+UNDEFINED art_quick_alloc_array_resolved64_rosalloc
+UNDEFINED art_quick_alloc_array_resolved64_rosalloc_instrumented
+UNDEFINED art_quick_alloc_object_resolved_rosalloc
+UNDEFINED art_quick_alloc_object_resolved_rosalloc_instrumented
+UNDEFINED art_quick_alloc_object_initialized_rosalloc
+UNDEFINED art_quick_alloc_object_initialized_rosalloc_instrumented
+UNDEFINED art_quick_alloc_object_with_checks_rosalloc
+UNDEFINED art_quick_alloc_object_with_checks_rosalloc_instrumented
+UNDEFINED art_quick_alloc_string_object_rosalloc
+UNDEFINED art_quick_alloc_string_object_rosalloc_instrumented
+UNDEFINED art_quick_alloc_string_from_bytes_rosalloc
+UNDEFINED art_quick_alloc_string_from_bytes_rosalloc_instrumented
+UNDEFINED art_quick_alloc_string_from_chars_rosalloc
+UNDEFINED art_quick_alloc_string_from_chars_rosalloc_instrumented
+UNDEFINED art_quick_alloc_string_from_string_rosalloc
+UNDEFINED art_quick_alloc_string_from_string_rosalloc_instrumented
+UNDEFINED art_quick_alloc_array_resolved_bump_pointer
+UNDEFINED art_quick_alloc_array_resolved_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_array_resolved8_bump_pointer
+UNDEFINED art_quick_alloc_array_resolved8_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_array_resolved16_bump_pointer
+UNDEFINED art_quick_alloc_array_resolved16_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_array_resolved32_bump_pointer
+UNDEFINED art_quick_alloc_array_resolved32_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_array_resolved64_bump_pointer
+UNDEFINED art_quick_alloc_array_resolved64_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_object_resolved_bump_pointer
+UNDEFINED art_quick_alloc_object_resolved_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_object_initialized_bump_pointer
+UNDEFINED art_quick_alloc_object_initialized_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_object_with_checks_bump_pointer
+UNDEFINED art_quick_alloc_object_with_checks_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_string_object_bump_pointer
+UNDEFINED art_quick_alloc_string_object_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_string_from_bytes_bump_pointer
+UNDEFINED art_quick_alloc_string_from_bytes_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_string_from_chars_bump_pointer
+UNDEFINED art_quick_alloc_string_from_chars_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_string_from_string_bump_pointer
+UNDEFINED art_quick_alloc_string_from_string_bump_pointer_instrumented
+UNDEFINED art_quick_alloc_array_resolved_tlab
+UNDEFINED art_quick_alloc_array_resolved_tlab_instrumented
+UNDEFINED art_quick_alloc_array_resolved8_tlab
+UNDEFINED art_quick_alloc_array_resolved8_tlab_instrumented
+UNDEFINED art_quick_alloc_array_resolved16_tlab
+UNDEFINED art_quick_alloc_array_resolved16_tlab_instrumented
+UNDEFINED art_quick_alloc_array_resolved32_tlab
+UNDEFINED art_quick_alloc_array_resolved32_tlab_instrumented
+UNDEFINED art_quick_alloc_array_resolved64_tlab
+UNDEFINED art_quick_alloc_array_resolved64_tlab_instrumented
+UNDEFINED art_quick_alloc_object_resolved_tlab
+UNDEFINED art_quick_alloc_object_resolved_tlab_instrumented
+UNDEFINED art_quick_alloc_object_initialized_tlab
+UNDEFINED art_quick_alloc_object_initialized_tlab_instrumented
+UNDEFINED art_quick_alloc_object_with_checks_tlab
+UNDEFINED art_quick_alloc_object_with_checks_tlab_instrumented
+UNDEFINED art_quick_alloc_string_object_tlab
+UNDEFINED art_quick_alloc_string_object_tlab_instrumented
+UNDEFINED art_quick_alloc_string_from_bytes_tlab
+UNDEFINED art_quick_alloc_string_from_bytes_tlab_instrumented
+UNDEFINED art_quick_alloc_string_from_chars_tlab
+UNDEFINED art_quick_alloc_string_from_chars_tlab_instrumented
+UNDEFINED art_quick_alloc_string_from_string_tlab
+UNDEFINED art_quick_alloc_string_from_string_tlab_instrumented
+UNDEFINED art_quick_alloc_array_resolved_region
+UNDEFINED art_quick_alloc_array_resolved_region_instrumented
+UNDEFINED art_quick_alloc_array_resolved8_region
+UNDEFINED art_quick_alloc_array_resolved8_region_instrumented
+UNDEFINED art_quick_alloc_array_resolved16_region
+UNDEFINED art_quick_alloc_array_resolved16_region_instrumented
+UNDEFINED art_quick_alloc_array_resolved32_region
+UNDEFINED art_quick_alloc_array_resolved32_region_instrumented
+UNDEFINED art_quick_alloc_array_resolved64_region
+UNDEFINED art_quick_alloc_array_resolved64_region_instrumented
+UNDEFINED art_quick_alloc_object_resolved_region
+UNDEFINED art_quick_alloc_object_resolved_region_instrumented
+UNDEFINED art_quick_alloc_object_initialized_region
+UNDEFINED art_quick_alloc_object_initialized_region_instrumented
+UNDEFINED art_quick_alloc_object_with_checks_region
+UNDEFINED art_quick_alloc_object_with_checks_region_instrumented
+UNDEFINED art_quick_alloc_string_object_region
+UNDEFINED art_quick_alloc_string_object_region_instrumented
+UNDEFINED art_quick_alloc_string_from_bytes_region
+UNDEFINED art_quick_alloc_string_from_bytes_region_instrumented
+UNDEFINED art_quick_alloc_string_from_chars_region
+UNDEFINED art_quick_alloc_string_from_chars_region_instrumented
+UNDEFINED art_quick_alloc_string_from_string_region
+UNDEFINED art_quick_alloc_string_from_string_region_instrumented
+UNDEFINED art_quick_alloc_array_resolved_region_tlab
+UNDEFINED art_quick_alloc_array_resolved_region_tlab_instrumented
+UNDEFINED art_quick_alloc_array_resolved8_region_tlab
+UNDEFINED art_quick_alloc_array_resolved8_region_tlab_instrumented
+UNDEFINED art_quick_alloc_array_resolved16_region_tlab
+UNDEFINED art_quick_alloc_array_resolved16_region_tlab_instrumented
+UNDEFINED art_quick_alloc_array_resolved32_region_tlab
+UNDEFINED art_quick_alloc_array_resolved32_region_tlab_instrumented
+UNDEFINED art_quick_alloc_array_resolved64_region_tlab
+UNDEFINED art_quick_alloc_array_resolved64_region_tlab_instrumented
+UNDEFINED art_quick_alloc_object_resolved_region_tlab
+UNDEFINED art_quick_alloc_object_resolved_region_tlab_instrumented
+UNDEFINED art_quick_alloc_object_initialized_region_tlab
+UNDEFINED art_quick_alloc_object_initialized_region_tlab_instrumented
+UNDEFINED art_quick_alloc_object_with_checks_region_tlab
+UNDEFINED art_quick_alloc_object_with_checks_region_tlab_instrumented
+UNDEFINED art_quick_alloc_string_object_region_tlab
+UNDEFINED art_quick_alloc_string_object_region_tlab_instrumented
+UNDEFINED art_quick_alloc_string_from_bytes_region_tlab
+UNDEFINED art_quick_alloc_string_from_bytes_region_tlab_instrumented
+UNDEFINED art_quick_alloc_string_from_chars_region_tlab
+UNDEFINED art_quick_alloc_string_from_chars_region_tlab_instrumented
+UNDEFINED art_quick_alloc_string_from_string_region_tlab
+UNDEFINED art_quick_alloc_string_from_string_region_tlab_instrumented
+UNDEFINED art_quick_initialize_static_storage
+UNDEFINED art_quick_resolve_type_and_verify_access
+UNDEFINED art_quick_resolve_type
+UNDEFINED art_quick_resolve_method_handle
+UNDEFINED art_quick_resolve_method_type
+UNDEFINED art_quick_resolve_string
+UNDEFINED art_quick_set8_instance
+UNDEFINED art_quick_set8_static
+UNDEFINED art_quick_set16_instance
+UNDEFINED art_quick_set16_static
+UNDEFINED art_quick_set32_instance
+UNDEFINED art_quick_set32_static
+UNDEFINED art_quick_set64_instance
+UNDEFINED art_quick_set64_static
+UNDEFINED art_quick_set_obj_instance
+UNDEFINED art_quick_set_obj_static
+UNDEFINED art_quick_get_byte_instance
+UNDEFINED art_quick_get_boolean_instance
+UNDEFINED art_quick_get_short_instance
+UNDEFINED art_quick_get_char_instance
+UNDEFINED art_quick_get32_instance
+UNDEFINED art_quick_get64_instance
+UNDEFINED art_quick_get_obj_instance
+UNDEFINED art_quick_get_byte_static
+UNDEFINED art_quick_get_boolean_static
+UNDEFINED art_quick_get_short_static
+UNDEFINED art_quick_get_char_static
+UNDEFINED art_quick_get32_static
+UNDEFINED art_quick_get64_static
+UNDEFINED art_quick_get_obj_static
+UNDEFINED art_quick_aput_obj
+UNDEFINED art_quick_lock_object_no_inline
+UNDEFINED art_quick_lock_object
+UNDEFINED art_quick_unlock_object_no_inline
+UNDEFINED art_quick_unlock_object
+UNDEFINED art_quick_invoke_direct_trampoline_with_access_check
+UNDEFINED art_quick_invoke_interface_trampoline_with_access_check
+UNDEFINED art_quick_invoke_static_trampoline_with_access_check
+UNDEFINED art_quick_invoke_super_trampoline_with_access_check
+UNDEFINED art_quick_invoke_virtual_trampoline_with_access_check
+UNDEFINED art_quick_invoke_polymorphic
+UNDEFINED art_quick_invoke_custom
+UNDEFINED art_quick_test_suspend
+UNDEFINED art_quick_deliver_exception
+UNDEFINED art_quick_throw_array_bounds
+UNDEFINED art_quick_throw_div_zero
+UNDEFINED art_quick_throw_null_pointer_exception
+UNDEFINED art_quick_throw_stack_overflow
+UNDEFINED art_quick_throw_string_bounds
+UNDEFINED art_quick_update_inline_cache
+UNDEFINED art_jni_monitored_method_start
+UNDEFINED art_jni_monitored_method_end
+UNDEFINED art_quick_indexof
diff --git a/runtime/arch/riscv64/registers_riscv64.cc b/runtime/arch/riscv64/registers_riscv64.cc
new file mode 100644
index 0000000..b0acb47
--- /dev/null
+++ b/runtime/arch/riscv64/registers_riscv64.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "registers_riscv64.h"
+
+#include <ostream>
+
+namespace art {
+namespace riscv64 {
+
+static const char* kXRegisterNames[] = {"zero", "ra", "sp",  "gp",  "tp", "t0", "t1", "t2",
+                                        "fp",   "s1", "a0",  "a1",  "a2", "a3", "a4", "a5",
+                                        "a6",   "a7", "s2",  "s3",  "s4", "s5", "s6", "s7",
+                                        "s8",   "s9", "s10", "s11", "t3", "t4", "t5", "t6"};
+
+static const char* kFRegisterNames[] = {"ft0", "ft1", "ft2",  "ft3",  "ft4", "ft5", "ft6",  "ft7",
+                                        "fs0", "fs1", "fa0",  "fa1",  "fa2", "fa3", "fa4",  "fa5",
+                                        "fa6", "fa7", "fs2",  "fs3",  "fs4", "fs5", "fs6",  "fs7",
+                                        "fs8", "fs9", "fs10", "fs11", "ft8", "ft9", "ft10", "ft11"};
+
+std::ostream& operator<<(std::ostream& os, const XRegister& rhs) {
+  if (rhs >= Zero && rhs < kNumberOfXRegisters) {
+    os << kXRegisterNames[rhs];
+  } else {
+    os << "XRegister[" << static_cast<int>(rhs) << "]";
+  }
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const FRegister& rhs) {
+  if (rhs >= FT0 && rhs < kNumberOfFRegisters) {
+    os << kFRegisterNames[rhs];
+  } else {
+    os << "FRegister[" << static_cast<int>(rhs) << "]";
+  }
+  return os;
+}
+
+}  // namespace riscv64
+}  // namespace art
diff --git a/runtime/arch/riscv64/registers_riscv64.h b/runtime/arch/riscv64/registers_riscv64.h
new file mode 100644
index 0000000..bd0991e
--- /dev/null
+++ b/runtime/arch/riscv64/registers_riscv64.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 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_RUNTIME_ARCH_RISCV64_REGISTERS_RISCV64_H_
+#define ART_RUNTIME_ARCH_RISCV64_REGISTERS_RISCV64_H_
+
+#include <iosfwd>
+
+#include "base/macros.h"
+
+namespace art {
+namespace riscv64 {
+
+enum XRegister {
+  Zero = 0,  // X0, hard-wired zero
+  RA = 1,    // X1, return address
+  SP = 2,    // X2, stack pointer
+  GP = 3,    // X3, global pointer (unavailable, used for shadow stack by the compiler / libc)
+  TP = 4,    // X4, thread pointer (points to TLS area, not ART-internal thread)
+
+  T0 = 5,  // X5, temporary 0
+  T1 = 6,  // X6, temporary 1
+  T2 = 7,  // X7, temporary 2
+
+  S0 = 8,  // X8/FP, callee-saved 0 / frame pointer
+  S1 = 9,  // X9, callee-saved 1 / ART thread register
+
+  A0 = 10,  // X10, argument 0 / return value 0
+  A1 = 11,  // X11, argument 1 / return value 1
+  A2 = 12,  // X12, argument 2
+  A3 = 13,  // X13, argument 3
+  A4 = 14,  // X14, argument 4
+  A5 = 15,  // X15, argument 5
+  A6 = 16,  // X16, argument 6
+  A7 = 17,  // X17, argument 7
+
+  S2 = 18,   // X18, callee-saved 2
+  S3 = 19,   // X19, callee-saved 3
+  S4 = 20,   // X20, callee-saved 4
+  S5 = 21,   // X21, callee-saved 5
+  S6 = 22,   // X22, callee-saved 6
+  S7 = 23,   // X23, callee-saved 7
+  S8 = 24,   // X24, callee-saved 8
+  S9 = 25,   // X25, callee-saved 9
+  S10 = 26,  // X26, callee-saved 10
+  S11 = 27,  // X27, callee-saved 11
+
+  T3 = 28,  // X28, temporary 3
+  T4 = 29,  // X29, temporary 4
+  T5 = 30,  // X30, temporary 5
+  T6 = 31,  // X31, temporary 6
+
+  kNumberOfXRegisters = 32,
+  kNoXRegister = -1,  // Signals an illegal X register.
+
+  // Aliases.
+  TR = S1,  // ART Thread Register - managed runtime
+};
+
+std::ostream& operator<<(std::ostream& os, const XRegister& rhs);
+
+enum FRegister {
+  FT0 = 0,  // F0, temporary 0
+  FT1 = 1,  // F1, temporary 1
+  FT2 = 2,  // F2, temporary 2
+  FT3 = 3,  // F3, temporary 3
+  FT4 = 4,  // F4, temporary 4
+  FT5 = 5,  // F5, temporary 5
+  FT6 = 6,  // F6, temporary 6
+  FT7 = 7,  // F7, temporary 7
+
+  FS0 = 8,  // F8, callee-saved 0
+  FS1 = 9,  // F9, callee-saved 1
+
+  FA0 = 10,  // F10, argument 0 / return value 0
+  FA1 = 11,  // F11, argument 1 / return value 1
+  FA2 = 12,  // F12, argument 2
+  FA3 = 13,  // F13, argument 3
+  FA4 = 14,  // F14, argument 4
+  FA5 = 15,  // F15, argument 5
+  FA6 = 16,  // F16, argument 6
+  FA7 = 17,  // F17, argument 7
+
+  FS2 = 18,   // F18, callee-saved 2
+  FS3 = 19,   // F19, callee-saved 3
+  FS4 = 20,   // F20, callee-saved 4
+  FS5 = 21,   // F21, callee-saved 5
+  FS6 = 22,   // F22, callee-saved 6
+  FS7 = 23,   // F23, callee-saved 7
+  FS8 = 24,   // F24, callee-saved 8
+  FS9 = 25,   // F25, callee-saved 9
+  FS10 = 26,  // F26, callee-saved 10
+  FS11 = 27,  // F27, callee-saved 11
+
+  FT8 = 28,   // F28, temporary 8
+  FT9 = 29,   // F29, temporary 9
+  FT10 = 30,  // F30, temporary 10
+  FT11 = 31,  // F31, temporary 11
+
+  kNumberOfFRegisters = 32,
+  kNoFRegister = -1,  // Signals an illegal F register.
+};
+
+std::ostream& operator<<(std::ostream& os, const FRegister& rhs);
+
+}  // namespace riscv64
+}  // namespace art
+
+#endif  // ART_RUNTIME_ARCH_RISCV64_REGISTERS_RISCV64_H_
diff --git a/runtime/arch/riscv64/thread_riscv64.cc b/runtime/arch/riscv64/thread_riscv64.cc
new file mode 100644
index 0000000..cb2d2ad
--- /dev/null
+++ b/runtime/arch/riscv64/thread_riscv64.cc
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "asm_support_riscv64.h"
+#include "base/enums.h"
+#include "thread.h"
+
+namespace art {
+
+void Thread::InitCpu() {
+  CHECK_EQ(THREAD_FLAGS_OFFSET, ThreadFlagsOffset<PointerSize::k64>().Int32Value());
+  CHECK_EQ(THREAD_CARD_TABLE_OFFSET, CardTableOffset<PointerSize::k64>().Int32Value());
+  CHECK_EQ(THREAD_EXCEPTION_OFFSET, ExceptionOffset<PointerSize::k64>().Int32Value());
+  CHECK_EQ(THREAD_ID_OFFSET, ThinLockIdOffset<PointerSize::k64>().Int32Value());
+}
+
+void Thread::CleanupCpu() {
+  // Do nothing.
+}
+
+}  // namespace art
diff --git a/runtime/arch/stub_test.cc b/runtime/arch/stub_test.cc
index 772681d..759cd9a 100644
--- a/runtime/arch/stub_test.cc
+++ b/runtime/arch/stub_test.cc
@@ -26,7 +26,7 @@
 #include "entrypoints/quick/quick_entrypoints_enum.h"
 #include "imt_conflict_table.h"
 #include "jni/jni_internal.h"
-#include "linear_alloc.h"
+#include "linear_alloc-inl.h"
 #include "mirror/class-alloc-inl.h"
 #include "mirror/string-inl.h"
 #include "mirror/object_array-alloc-inl.h"
@@ -961,7 +961,6 @@
 
 
 TEST_F(StubTest, StringCompareTo) {
-  TEST_DISABLED_FOR_STRING_COMPRESSION();
   // There is no StringCompareTo runtime entrypoint for __arm__ or __aarch64__.
 #if defined(__i386__) || (defined(__x86_64__) && !defined(__APPLE__))
   // TODO: Check the "Unresolved" allocation stubs
@@ -1777,7 +1776,8 @@
       Runtime::Current()->GetClassLinker()->CreateImtConflictTable(/*count=*/0u, linear_alloc);
   void* data = linear_alloc->Alloc(
       self,
-      ImtConflictTable::ComputeSizeWithOneMoreEntry(empty_conflict_table, kRuntimePointerSize));
+      ImtConflictTable::ComputeSizeWithOneMoreEntry(empty_conflict_table, kRuntimePointerSize),
+      LinearAllocKind::kNoGCRoots);
   ImtConflictTable* new_table = new (data) ImtConflictTable(
       empty_conflict_table, inf_contains, contains_amethod, kRuntimePointerSize);
   conflict_method->SetImtConflictTable(new_table, kRuntimePointerSize);
@@ -1918,74 +1918,74 @@
 // TODO: Exercise the ReadBarrierMarkRegX entry points.
 
 TEST_F(StubTest, ReadBarrier) {
-#if defined(ART_USE_READ_BARRIER) && (defined(__i386__) || defined(__arm__) || \
-      defined(__aarch64__) || (defined(__x86_64__) && !defined(__APPLE__)))
-  Thread* self = Thread::Current();
+#if defined(__i386__) || defined(__arm__) || defined(__aarch64__) ||\
+      (defined(__x86_64__) && !defined(__APPLE__))
+  if (gUseReadBarrier) {
+    Thread* self = Thread::Current();
 
-  const uintptr_t readBarrierSlow = StubTest::GetEntrypoint(self, kQuickReadBarrierSlow);
+    const uintptr_t readBarrierSlow = StubTest::GetEntrypoint(self, kQuickReadBarrierSlow);
 
-  // Create an object
-  ScopedObjectAccess soa(self);
-  // garbage is created during ClassLinker::Init
+    // Create an object
+    ScopedObjectAccess soa(self);
+    // garbage is created during ClassLinker::Init
 
-  StackHandleScope<2> hs(soa.Self());
-  Handle<mirror::Class> c(
-      hs.NewHandle(class_linker_->FindSystemClass(soa.Self(), "Ljava/lang/Object;")));
+    StackHandleScope<2> hs(soa.Self());
+    Handle<mirror::Class> c(
+        hs.NewHandle(class_linker_->FindSystemClass(soa.Self(), "Ljava/lang/Object;")));
 
-  // Build an object instance
-  Handle<mirror::Object> obj(hs.NewHandle(c->AllocObject(soa.Self())));
+    // Build an object instance
+    Handle<mirror::Object> obj(hs.NewHandle(c->AllocObject(soa.Self())));
 
-  EXPECT_FALSE(self->IsExceptionPending());
+    EXPECT_FALSE(self->IsExceptionPending());
 
-  size_t result = Invoke3(0U, reinterpret_cast<size_t>(obj.Get()),
-                          mirror::Object::ClassOffset().SizeValue(), readBarrierSlow, self);
+    size_t result = Invoke3(0U, reinterpret_cast<size_t>(obj.Get()),
+                            mirror::Object::ClassOffset().SizeValue(), readBarrierSlow, self);
 
-  EXPECT_FALSE(self->IsExceptionPending());
-  EXPECT_NE(reinterpret_cast<size_t>(nullptr), result);
-  mirror::Class* klass = reinterpret_cast<mirror::Class*>(result);
-  EXPECT_OBJ_PTR_EQ(klass, obj->GetClass());
-
-  // Tests done.
-#else
+    EXPECT_FALSE(self->IsExceptionPending());
+    EXPECT_NE(reinterpret_cast<size_t>(nullptr), result);
+    mirror::Class* klass = reinterpret_cast<mirror::Class*>(result);
+    EXPECT_OBJ_PTR_EQ(klass, obj->GetClass());
+    return;
+  }
+#endif
   LOG(INFO) << "Skipping read_barrier_slow";
   // Force-print to std::cout so it's also outside the logcat.
   std::cout << "Skipping read_barrier_slow" << std::endl;
-#endif
 }
 
 TEST_F(StubTest, ReadBarrierForRoot) {
-#if defined(ART_USE_READ_BARRIER) && (defined(__i386__) || defined(__arm__) || \
-      defined(__aarch64__) || (defined(__x86_64__) && !defined(__APPLE__)))
-  Thread* self = Thread::Current();
+#if defined(__i386__) || defined(__arm__) || defined(__aarch64__) ||\
+      (defined(__x86_64__) && !defined(__APPLE__))
+  if (gUseReadBarrier) {
+    Thread* self = Thread::Current();
 
-  const uintptr_t readBarrierForRootSlow =
-      StubTest::GetEntrypoint(self, kQuickReadBarrierForRootSlow);
+    const uintptr_t readBarrierForRootSlow =
+        StubTest::GetEntrypoint(self, kQuickReadBarrierForRootSlow);
 
-  // Create an object
-  ScopedObjectAccess soa(self);
-  // garbage is created during ClassLinker::Init
+    // Create an object
+    ScopedObjectAccess soa(self);
+    // garbage is created during ClassLinker::Init
 
-  StackHandleScope<1> hs(soa.Self());
+    StackHandleScope<1> hs(soa.Self());
 
-  Handle<mirror::String> obj(
-      hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "hello, world!")));
+    Handle<mirror::String> obj(
+        hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "hello, world!")));
 
-  EXPECT_FALSE(self->IsExceptionPending());
+    EXPECT_FALSE(self->IsExceptionPending());
 
-  GcRoot<mirror::Class> root(GetClassRoot<mirror::String>());
-  size_t result = Invoke3(reinterpret_cast<size_t>(&root), 0U, 0U, readBarrierForRootSlow, self);
+    GcRoot<mirror::Class> root(GetClassRoot<mirror::String>());
+    size_t result = Invoke3(reinterpret_cast<size_t>(&root), 0U, 0U, readBarrierForRootSlow, self);
 
-  EXPECT_FALSE(self->IsExceptionPending());
-  EXPECT_NE(reinterpret_cast<size_t>(nullptr), result);
-  mirror::Class* klass = reinterpret_cast<mirror::Class*>(result);
-  EXPECT_OBJ_PTR_EQ(klass, obj->GetClass());
-
-  // Tests done.
-#else
+    EXPECT_FALSE(self->IsExceptionPending());
+    EXPECT_NE(reinterpret_cast<size_t>(nullptr), result);
+    mirror::Class* klass = reinterpret_cast<mirror::Class*>(result);
+    EXPECT_OBJ_PTR_EQ(klass, obj->GetClass());
+    return;
+  }
+#endif
   LOG(INFO) << "Skipping read_barrier_for_root_slow";
   // Force-print to std::cout so it's also outside the logcat.
   std::cout << "Skipping read_barrier_for_root_slow" << std::endl;
-#endif
 }
 
 }  // namespace art
diff --git a/runtime/arch/x86/asm_support_x86.S b/runtime/arch/x86/asm_support_x86.S
index c42aa67..f5562f6 100644
--- a/runtime/arch/x86/asm_support_x86.S
+++ b/runtime/arch/x86/asm_support_x86.S
@@ -168,6 +168,7 @@
     // Prefix the assembly code with 0xFFs, which means there is no method header.
     .byte 0xFF, 0xFF, 0xFF, 0xFF
     // Cache alignment for function entry.
+    // Use 0xFF as the last 4 bytes of alignment stand for OatQuickMethodHeader.
     .balign 16, 0xFF
 END_MACRO
 
@@ -362,7 +363,7 @@
 END_MACRO
 
 MACRO0(RESTORE_SAVE_REFS_AND_ARGS_FRAME)
-    // Restore FPRs. EAX is still on the stack.
+    // Restore FPRs. The method is still on the stack.
     movsd 4(%esp), %xmm0
     movsd 12(%esp), %xmm1
     movsd 20(%esp), %xmm2
diff --git a/runtime/arch/x86/asm_support_x86.h b/runtime/arch/x86/asm_support_x86.h
index 737d736..8c19e07 100644
--- a/runtime/arch/x86/asm_support_x86.h
+++ b/runtime/arch/x86/asm_support_x86.h
@@ -18,6 +18,7 @@
 #define ART_RUNTIME_ARCH_X86_ASM_SUPPORT_X86_H_
 
 #include "asm_support.h"
+#include "entrypoints/entrypoint_asm_constants.h"
 
 #define FRAME_SIZE_SAVE_ALL_CALLEE_SAVES 32
 #define FRAME_SIZE_SAVE_REFS_ONLY 32
@@ -25,5 +26,7 @@
 #define FRAME_SIZE_SAVE_EVERYTHING (48 + 64)
 #define FRAME_SIZE_SAVE_EVERYTHING_FOR_CLINIT FRAME_SIZE_SAVE_EVERYTHING
 #define FRAME_SIZE_SAVE_EVERYTHING_FOR_SUSPEND_CHECK FRAME_SIZE_SAVE_EVERYTHING
+#define SAVE_EVERYTHING_FRAME_EAX_OFFSET \
+    (FRAME_SIZE_SAVE_EVERYTHING - CALLEE_SAVE_EVERYTHING_NUM_CORE_SPILLS * POINTER_SIZE)
 
 #endif  // ART_RUNTIME_ARCH_X86_ASM_SUPPORT_X86_H_
diff --git a/runtime/arch/x86/context_x86.cc b/runtime/arch/x86/context_x86.cc
index 5c31712..cff3d7f 100644
--- a/runtime/arch/x86/context_x86.cc
+++ b/runtime/arch/x86/context_x86.cc
@@ -32,8 +32,8 @@
   gprs_[ESP] = &esp_;
   gprs_[EAX] = &arg0_;
   // Initialize registers with easy to spot debug values.
-  esp_ = X86Context::kBadGprBase + ESP;
-  eip_ = X86Context::kBadGprBase + kNumberOfCpuRegisters;
+  esp_ = kBadGprBase + ESP;
+  eip_ = kBadGprBase + kNumberOfCpuRegisters;
   arg0_ = 0;
 }
 
@@ -94,11 +94,11 @@
   // the top for the stack pointer that doesn't get popped in a pop-all.
   volatile uintptr_t gprs[kNumberOfCpuRegisters + 1];
   for (size_t i = 0; i < kNumberOfCpuRegisters; ++i) {
-    gprs[kNumberOfCpuRegisters - i - 1] = gprs_[i] != nullptr ? *gprs_[i] : X86Context::kBadGprBase + i;
+    gprs[kNumberOfCpuRegisters - i - 1] = gprs_[i] != nullptr ? *gprs_[i] : kBadGprBase + i;
   }
   uint32_t fprs[kNumberOfFloatRegisters];
   for (size_t i = 0; i < kNumberOfFloatRegisters; ++i) {
-    fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : X86Context::kBadFprBase + i;
+    fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : kBadFprBase + i;
   }
   // We want to load the stack pointer one slot below so that the ret will pop eip.
   uintptr_t esp = gprs[kNumberOfCpuRegisters - ESP - 1] - sizeof(intptr_t);
diff --git a/runtime/arch/x86/fault_handler_x86.cc b/runtime/arch/x86/fault_handler_x86.cc
index 3a08ec5..cd2d38f 100644
--- a/runtime/arch/x86/fault_handler_x86.cc
+++ b/runtime/arch/x86/fault_handler_x86.cc
@@ -25,6 +25,7 @@
 #include "base/logging.h"  // For VLOG.
 #include "base/macros.h"
 #include "base/safe_copy.h"
+#include "oat_quick_method_header.h"
 #include "runtime_globals.h"
 #include "thread-current-inl.h"
 
@@ -77,30 +78,18 @@
 
 // Get the size of an instruction in bytes.
 // Return 0 if the instruction is not handled.
-static uint32_t GetInstructionSize(const uint8_t* pc) {
-  // Don't segfault if pc points to garbage.
-  char buf[15];  // x86/x86-64 have a maximum instruction length of 15 bytes.
-  ssize_t bytes = SafeCopy(buf, pc, sizeof(buf));
-
-  if (bytes == 0) {
-    // Nothing was readable.
-    return 0;
-  }
-
-  if (bytes == -1) {
-    // SafeCopy not supported, assume that the entire range is readable.
-    bytes = 16;
-  } else {
-    pc = reinterpret_cast<uint8_t*>(buf);
-  }
-
-#define INCREMENT_PC()          \
-  do {                          \
-    pc++;                       \
-    if (pc - startpc > bytes) { \
-      return 0;                 \
-    }                           \
+static uint32_t GetInstructionSize(const uint8_t* pc, size_t bytes) {
+#define FETCH_OR_SKIP_BYTE(assignment)  \
+  do {                                  \
+    if (bytes == 0u) {                  \
+      return 0u;                        \
+    }                                   \
+    (assignment);                       \
+    ++pc;                               \
+    --bytes;                            \
   } while (0)
+#define FETCH_BYTE(var) FETCH_OR_SKIP_BYTE((var) = *pc)
+#define SKIP_BYTE() FETCH_OR_SKIP_BYTE((void)0)
 
 #if defined(__x86_64)
   const bool x86_64 = true;
@@ -110,8 +99,8 @@
 
   const uint8_t* startpc = pc;
 
-  uint8_t opcode = *pc;
-  INCREMENT_PC();
+  uint8_t opcode;
+  FETCH_BYTE(opcode);
   uint8_t modrm;
   bool has_modrm = false;
   bool two_byte = false;
@@ -143,8 +132,7 @@
 
       // Group 4
       case 0x67:
-        opcode = *pc;
-        INCREMENT_PC();
+        FETCH_BYTE(opcode);
         prefix_present = true;
         break;
     }
@@ -154,15 +142,13 @@
   }
 
   if (x86_64 && opcode >= 0x40 && opcode <= 0x4f) {
-    opcode = *pc;
-    INCREMENT_PC();
+    FETCH_BYTE(opcode);
   }
 
   if (opcode == 0x0f) {
     // Two byte opcode
     two_byte = true;
-    opcode = *pc;
-    INCREMENT_PC();
+    FETCH_BYTE(opcode);
   }
 
   bool unhandled_instruction = false;
@@ -175,8 +161,7 @@
       case 0xb7:
       case 0xbe:        // movsx
       case 0xbf:
-        modrm = *pc;
-        INCREMENT_PC();
+        FETCH_BYTE(modrm);
         has_modrm = true;
         break;
       default:
@@ -195,32 +180,28 @@
       case 0x3c:
       case 0x3d:
       case 0x85:        // test.
-        modrm = *pc;
-        INCREMENT_PC();
+        FETCH_BYTE(modrm);
         has_modrm = true;
         break;
 
       case 0x80:        // group 1, byte immediate.
       case 0x83:
       case 0xc6:
-        modrm = *pc;
-        INCREMENT_PC();
+        FETCH_BYTE(modrm);
         has_modrm = true;
         immediate_size = 1;
         break;
 
       case 0x81:        // group 1, word immediate.
       case 0xc7:        // mov
-        modrm = *pc;
-        INCREMENT_PC();
+        FETCH_BYTE(modrm);
         has_modrm = true;
         immediate_size = operand_size_prefix ? 2 : 4;
         break;
 
       case 0xf6:
       case 0xf7:
-        modrm = *pc;
-        INCREMENT_PC();
+        FETCH_BYTE(modrm);
         has_modrm = true;
         switch ((modrm >> 3) & 7) {  // Extract "reg/opcode" from "modr/m".
           case 0:  // test
@@ -255,7 +236,7 @@
 
     // Check for SIB.
     if (mod != 3U /* 0b11 */ && (modrm & 7U /* 0b111 */) == 4) {
-      INCREMENT_PC();     // SIB
+      SKIP_BYTE();  // SIB
     }
 
     switch (mod) {
@@ -271,86 +252,79 @@
   pc += displacement_size + immediate_size;
 
   VLOG(signals) << "x86 instruction length calculated as " << (pc - startpc);
-  if (pc - startpc > bytes) {
-    return 0;
-  }
   return pc - startpc;
+
+#undef SKIP_BYTE
+#undef FETCH_BYTE
+#undef FETCH_OR_SKIP_BYTE
 }
 
-void FaultManager::GetMethodAndReturnPcAndSp(siginfo_t* siginfo, void* context,
-                                             ArtMethod** out_method,
-                                             uintptr_t* out_return_pc,
-                                             uintptr_t* out_sp,
-                                             bool* out_is_stack_overflow) {
-  struct ucontext* uc = reinterpret_cast<struct ucontext*>(context);
-  *out_sp = static_cast<uintptr_t>(uc->CTX_ESP);
-  VLOG(signals) << "sp: " << std::hex << *out_sp;
-  if (*out_sp == 0) {
-    return;
+uintptr_t FaultManager::GetFaultPc(siginfo_t* siginfo ATTRIBUTE_UNUSED, void* context) {
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  if (uc->CTX_ESP == 0) {
+    VLOG(signals) << "Missing SP";
+    return 0u;
   }
+  return uc->CTX_EIP;
+}
 
-  // In the case of a stack overflow, the stack is not valid and we can't
-  // get the method from the top of the stack.  However it's in EAX(x86)/RDI(x86_64).
-  uintptr_t* fault_addr = reinterpret_cast<uintptr_t*>(siginfo->si_addr);
-  uintptr_t* overflow_addr = reinterpret_cast<uintptr_t*>(
-#if defined(__x86_64__)
-      reinterpret_cast<uint8_t*>(*out_sp) - GetStackOverflowReservedBytes(InstructionSet::kX86_64));
-#else
-      reinterpret_cast<uint8_t*>(*out_sp) - GetStackOverflowReservedBytes(InstructionSet::kX86));
-#endif
-  if (overflow_addr == fault_addr) {
-    *out_method = reinterpret_cast<ArtMethod*>(uc->CTX_METHOD);
-    *out_is_stack_overflow = true;
-  } else {
-    // The method is at the top of the stack.
-    *out_method = *reinterpret_cast<ArtMethod**>(*out_sp);
-    *out_is_stack_overflow = false;
-  }
-
-  uint8_t* pc = reinterpret_cast<uint8_t*>(uc->CTX_EIP);
-  VLOG(signals) << HexDump(pc, 32, true, "PC ");
-
-  if (pc == nullptr) {
-    // Somebody jumped to 0x0. Definitely not ours, and will definitely segfault below.
-    *out_method = nullptr;
-    return;
-  }
-
-  uint32_t instr_size = GetInstructionSize(pc);
-  if (instr_size == 0) {
-    // Unknown instruction, tell caller it's not ours.
-    *out_method = nullptr;
-    return;
-  }
-  *out_return_pc = reinterpret_cast<uintptr_t>(pc + instr_size);
+uintptr_t FaultManager::GetFaultSp(void* context) {
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  return uc->CTX_ESP;
 }
 
 bool NullPointerHandler::Action(int, siginfo_t* sig, void* context) {
-  if (!IsValidImplicitCheck(sig)) {
-    return false;
-  }
-  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
-  uint8_t* pc = reinterpret_cast<uint8_t*>(uc->CTX_EIP);
-  uint8_t* sp = reinterpret_cast<uint8_t*>(uc->CTX_ESP);
-
-  uint32_t instr_size = GetInstructionSize(pc);
-  if (instr_size == 0) {
-    // Unknown instruction, can't really happen.
+  uintptr_t fault_address = reinterpret_cast<uintptr_t>(sig->si_addr);
+  if (!IsValidFaultAddress(fault_address)) {
     return false;
   }
 
-  // We need to arrange for the signal handler to return to the null pointer
-  // exception generator.  The return address must be the address of the
-  // next instruction (this instruction + instruction size).  The return address
-  // is on the stack at the top address of the current frame.
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  ArtMethod** sp = reinterpret_cast<ArtMethod**>(uc->CTX_ESP);
+  ArtMethod* method = *sp;
+  if (!IsValidMethod(method)) {
+    return false;
+  }
 
-  // Push the return address and fault address onto the stack.
-  uintptr_t retaddr = reinterpret_cast<uintptr_t>(pc + instr_size);
-  uintptr_t* next_sp = reinterpret_cast<uintptr_t*>(sp - 2 * sizeof(uintptr_t));
-  next_sp[1] = retaddr;
-  next_sp[0] = reinterpret_cast<uintptr_t>(sig->si_addr);
+  // For null checks in compiled code we insert a stack map that is immediately
+  // after the load/store instruction that might cause the fault and we need to
+  // pass the return PC to the handler. For null checks in Nterp, we similarly
+  // need the return PC to recognize that this was a null check in Nterp, so
+  // that the handler can get the needed data from the Nterp frame.
+
+  // Note: Allowing nested faults if `IsValidMethod()` returned a false positive.
+  // Note: The `ArtMethod::GetOatQuickMethodHeader()` can acquire locks, which is
+  // essentially unsafe in a signal handler, but we allow that here just like in
+  // `NullPointerHandler::IsValidReturnPc()`. For more details see comments there.
+  uintptr_t pc = uc->CTX_EIP;
+  const OatQuickMethodHeader* method_header = method->GetOatQuickMethodHeader(pc);
+  if (method_header == nullptr) {
+    VLOG(signals) << "No method header.";
+    return false;
+  }
+  const uint8_t* pc_ptr = reinterpret_cast<const uint8_t*>(pc);
+  size_t offset = pc_ptr - method_header->GetCode();
+  size_t code_size = method_header->GetCodeSize();
+  CHECK_LT(offset, code_size);
+  size_t max_instr_size = code_size - offset;
+  uint32_t instr_size = GetInstructionSize(pc_ptr, max_instr_size);
+  if (instr_size == 0u) {
+    // Unknown instruction (can't really happen) or not enough bytes until end of method code.
+    return false;
+  }
+
+  uintptr_t return_pc = reinterpret_cast<uintptr_t>(pc + instr_size);
+  if (!IsValidReturnPc(sp, return_pc)) {
+    return false;
+  }
+
+  // Push the return PC and fault address onto the stack.
+  uintptr_t* next_sp = reinterpret_cast<uintptr_t*>(sp) - 2;
+  next_sp[1] = return_pc;
+  next_sp[0] = fault_address;
   uc->CTX_ESP = reinterpret_cast<uintptr_t>(next_sp);
 
+  // Arrange for the signal handler to return to the NPE entrypoint.
   uc->CTX_EIP = reinterpret_cast<uintptr_t>(
       art_quick_throw_null_pointer_exception_from_signal);
   VLOG(signals) << "Generating null pointer exception";
@@ -385,7 +359,7 @@
 #endif
   uint8_t checkinst2[] = {0x85, 0x00};
 
-  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
   uint8_t* pc = reinterpret_cast<uint8_t*>(uc->CTX_EIP);
   uint8_t* sp = reinterpret_cast<uint8_t*>(uc->CTX_ESP);
 
@@ -441,7 +415,7 @@
 // address for the previous method is on the stack at ESP.
 
 bool StackOverflowHandler::Action(int, siginfo_t* info, void* context) {
-  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
   uintptr_t sp = static_cast<uintptr_t>(uc->CTX_ESP);
 
   uintptr_t fault_addr = reinterpret_cast<uintptr_t>(info->si_addr);
diff --git a/runtime/arch/x86/instruction_set_features_x86.cc b/runtime/arch/x86/instruction_set_features_x86.cc
index 325f47f..f11aca9 100644
--- a/runtime/arch/x86/instruction_set_features_x86.cc
+++ b/runtime/arch/x86/instruction_set_features_x86.cc
@@ -44,6 +44,9 @@
     "atom",
     "sandybridge",
     "silvermont",
+    "goldmont",
+    "goldmont-plus",
+    "tremont",
     "kabylake",
     "default",
 };
@@ -52,24 +55,36 @@
     "atom",
     "sandybridge",
     "silvermont",
+    "goldmont",
+    "goldmont-plus",
+    "tremont",
     "kabylake",
 };
 
 static constexpr const char* x86_variants_with_sse4_1[] = {
     "sandybridge",
     "silvermont",
+    "goldmont",
+    "goldmont-plus",
+    "tremont",
     "kabylake",
 };
 
 static constexpr const char* x86_variants_with_sse4_2[] = {
     "sandybridge",
     "silvermont",
+    "goldmont",
+    "goldmont-plus",
+    "tremont",
     "kabylake",
 };
 
 static constexpr const char* x86_variants_with_popcnt[] = {
     "sandybridge",
     "silvermont",
+    "goldmont",
+    "goldmont-plus",
+    "tremont",
     "kabylake",
 };
 static constexpr const char* x86_variants_with_avx[] = {
diff --git a/runtime/arch/x86/instruction_set_features_x86_test.cc b/runtime/arch/x86/instruction_set_features_x86_test.cc
index ce8e9f4..c50360a 100644
--- a/runtime/arch/x86/instruction_set_features_x86_test.cc
+++ b/runtime/arch/x86/instruction_set_features_x86_test.cc
@@ -110,6 +110,81 @@
   EXPECT_FALSE(x86_64_features->Equals(x86_features.get()));
 }
 
+TEST(X86InstructionSetFeaturesTest, X86FeaturesFromGoldmontVariant) {
+  // Build features for a 32-bit x86 goldmont processor.
+  std::string error_msg;
+  std::unique_ptr<const InstructionSetFeatures> x86_features(
+      InstructionSetFeatures::FromVariant(InstructionSet::kX86, "goldmont", &error_msg));
+  ASSERT_TRUE(x86_features.get() != nullptr) << error_msg;
+  EXPECT_EQ(x86_features->GetInstructionSet(), InstructionSet::kX86);
+  EXPECT_TRUE(x86_features->Equals(x86_features.get()));
+  EXPECT_STREQ("ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt",
+               x86_features->GetFeatureString().c_str());
+  EXPECT_EQ(x86_features->AsBitmap(), 39U);
+
+  // Build features for a 64-bit x86-64 goldmont processor.
+  std::unique_ptr<const InstructionSetFeatures> x86_64_features(
+      InstructionSetFeatures::FromVariant(InstructionSet::kX86_64, "goldmont", &error_msg));
+  ASSERT_TRUE(x86_64_features.get() != nullptr) << error_msg;
+  EXPECT_EQ(x86_64_features->GetInstructionSet(), InstructionSet::kX86_64);
+  EXPECT_TRUE(x86_64_features->Equals(x86_64_features.get()));
+  EXPECT_STREQ("ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt",
+               x86_64_features->GetFeatureString().c_str());
+  EXPECT_EQ(x86_64_features->AsBitmap(), 39U);
+
+  EXPECT_FALSE(x86_64_features->Equals(x86_features.get()));
+}
+
+TEST(X86InstructionSetFeaturesTest, X86FeaturesFromGoldmontPlusVariant) {
+  // Build features for a 32-bit x86 goldmont-plus processor.
+  std::string error_msg;
+  std::unique_ptr<const InstructionSetFeatures> x86_features(
+      InstructionSetFeatures::FromVariant(InstructionSet::kX86, "goldmont-plus", &error_msg));
+  ASSERT_TRUE(x86_features.get() != nullptr) << error_msg;
+  EXPECT_EQ(x86_features->GetInstructionSet(), InstructionSet::kX86);
+  EXPECT_TRUE(x86_features->Equals(x86_features.get()));
+  EXPECT_STREQ("ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt",
+               x86_features->GetFeatureString().c_str());
+  EXPECT_EQ(x86_features->AsBitmap(), 39U);
+
+  // Build features for a 64-bit x86-64 goldmont-plus processor.
+  std::unique_ptr<const InstructionSetFeatures> x86_64_features(
+      InstructionSetFeatures::FromVariant(InstructionSet::kX86_64, "goldmont-plus", &error_msg));
+  ASSERT_TRUE(x86_64_features.get() != nullptr) << error_msg;
+  EXPECT_EQ(x86_64_features->GetInstructionSet(), InstructionSet::kX86_64);
+  EXPECT_TRUE(x86_64_features->Equals(x86_64_features.get()));
+  EXPECT_STREQ("ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt",
+               x86_64_features->GetFeatureString().c_str());
+  EXPECT_EQ(x86_64_features->AsBitmap(), 39U);
+
+  EXPECT_FALSE(x86_64_features->Equals(x86_features.get()));
+}
+
+TEST(X86InstructionSetFeaturesTest, X86FeaturesFromTremontVariant) {
+  // Build features for a 32-bit x86 tremont processor.
+  std::string error_msg;
+  std::unique_ptr<const InstructionSetFeatures> x86_features(
+      InstructionSetFeatures::FromVariant(InstructionSet::kX86, "tremont", &error_msg));
+  ASSERT_TRUE(x86_features.get() != nullptr) << error_msg;
+  EXPECT_EQ(x86_features->GetInstructionSet(), InstructionSet::kX86);
+  EXPECT_TRUE(x86_features->Equals(x86_features.get()));
+  EXPECT_STREQ("ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt",
+               x86_features->GetFeatureString().c_str());
+  EXPECT_EQ(x86_features->AsBitmap(), 39U);
+
+  // Build features for a 64-bit x86-64 tremont processor.
+  std::unique_ptr<const InstructionSetFeatures> x86_64_features(
+      InstructionSetFeatures::FromVariant(InstructionSet::kX86_64, "tremont", &error_msg));
+  ASSERT_TRUE(x86_64_features.get() != nullptr) << error_msg;
+  EXPECT_EQ(x86_64_features->GetInstructionSet(), InstructionSet::kX86_64);
+  EXPECT_TRUE(x86_64_features->Equals(x86_64_features.get()));
+  EXPECT_STREQ("ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt",
+               x86_64_features->GetFeatureString().c_str());
+  EXPECT_EQ(x86_64_features->AsBitmap(), 39U);
+
+  EXPECT_FALSE(x86_64_features->Equals(x86_features.get()));
+}
+
 TEST(X86InstructionSetFeaturesTest, X86FeaturesFromKabylakeVariant) {
   // Build features for a 32-bit kabylake x86 processor.
   std::string error_msg;
diff --git a/runtime/arch/x86/jni_entrypoints_x86.S b/runtime/arch/x86/jni_entrypoints_x86.S
index d827509..c7cf856 100644
--- a/runtime/arch/x86/jni_entrypoints_x86.S
+++ b/runtime/arch/x86/jni_entrypoints_x86.S
@@ -98,7 +98,7 @@
     // for @FastNative or @CriticalNative.
     movl (%esp), %eax                                // Thread* self
     movl THREAD_TOP_QUICK_FRAME_OFFSET(%eax), %eax   // uintptr_t tagged_quick_frame
-    andl LITERAL(0xfffffffe), %eax                   // ArtMethod** sp
+    andl LITERAL(TAGGED_JNI_SP_MASK_TOGGLED32), %eax // ArtMethod** sp
     movl (%eax), %eax                                // ArtMethod* method
     testl LITERAL(ACCESS_FLAGS_METHOD_IS_FAST_NATIVE | ACCESS_FLAGS_METHOD_IS_CRITICAL_NATIVE), \
           ART_METHOD_ACCESS_FLAGS_OFFSET(%eax)
@@ -286,6 +286,12 @@
 JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_start, artJniMethodStart, fs:THREAD_SELF_OFFSET
 
     /*
+     * Trampoline to `artJniMethodEntryHook` that preserves all managed arguments.
+     */
+JNI_SAVE_MANAGED_ARGS_TRAMPOLINE \
+    art_jni_method_entry_hook, artJniMethodEntryHook, fs:THREAD_SELF_OFFSET
+
+    /*
      * Trampoline to `artJniMonitoredMethodStart()` that preserves all managed arguments.
      */
 JNI_SAVE_MANAGED_ARGS_TRAMPOLINE \
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 7f1311c..bb4399f 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -794,12 +794,9 @@
     call CALLVAR(cxx_name)                            // cxx_name(arg1, Thread*)
     addl MACRO_LITERAL(16), %esp                      // pop arguments
     CFI_ADJUST_CFA_OFFSET(-16)
-    testl %eax, %eax                                  // If result is null, deliver the OOME.
+    testl %eax, %eax                                  // If result is null deliver pending exception
     jz 1f
-    CFI_REMEMBER_STATE
-    RESTORE_SAVE_EVERYTHING_FRAME_KEEP_EAX            // restore frame up to return address
-    ret                                               // return
-    CFI_RESTORE_STATE_AND_DEF_CFA esp, FRAME_SIZE_SAVE_EVERYTHING
+    DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_EAX ebx,  /* is_ref= */1  // Check for deopt
 1:
     DELIVER_PENDING_EXCEPTION_FRAME_READY
     END_FUNCTION VAR(c_name)
@@ -809,18 +806,72 @@
     ONE_ARG_SAVE_EVERYTHING_DOWNCALL \c_name, \cxx_name, RUNTIME_SAVE_EVERYTHING_FOR_CLINIT_METHOD_OFFSET
 END_MACRO
 
-MACRO0(RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER)
-    testl %eax, %eax               // eax == 0 ?
-    jz  1f                         // if eax == 0 goto 1
-    ret                            // return
-1:                                 // deliver exception on current thread
+MACRO0(RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER)
+    testl %eax, %eax                  // eax == 0 ?
+    jz  1f                            // if eax == 0 goto 1
+    DEOPT_OR_RETURN ebx, /*is_ref=*/1 // check if deopt is required
+1:                                    // deliver exception on current thread
     DELIVER_PENDING_EXCEPTION
 END_MACRO
 
+MACRO0(RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION)
+    cmpl MACRO_LITERAL(0),%fs:THREAD_EXCEPTION_OFFSET // exception field == 0 ?
+    jne 1f                                            // if exception field != 0 goto 1
+    DEOPT_OR_RETURN ebx                               // check if deopt is required
+1:                                                    // deliver exception on current thread
+    DELIVER_PENDING_EXCEPTION
+END_MACRO
+
+MACRO2(DEOPT_OR_RETURN, temp, is_ref = 0)
+  cmpl LITERAL(0), %fs:THREAD_DEOPT_CHECK_REQUIRED_OFFSET
+  jne 2f
+  ret
+2:
+  SETUP_SAVE_EVERYTHING_FRAME \temp
+  subl MACRO_LITERAL(4), %esp       // alignment padding
+  CFI_ADJUST_CFA_OFFSET(4)
+  pushl MACRO_LITERAL(\is_ref)      // is_ref
+  CFI_ADJUST_CFA_OFFSET(4)
+  PUSH_ARG eax                      // result
+  pushl %fs:THREAD_SELF_OFFSET      // Pass Thread::Current
+  CFI_ADJUST_CFA_OFFSET(4)
+  call SYMBOL(artDeoptimizeIfNeeded)
+  addl LITERAL(16), %esp             // pop arguments
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME
+  ret
+  CFI_RESTORE_STATE_AND_DEF_CFA esp, FRAME_SIZE_SAVE_EVERYTHING
+END_MACRO
+
+MACRO2(DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_EAX, temp, is_ref = 0)
+  cmpl LITERAL(0), %fs:THREAD_DEOPT_CHECK_REQUIRED_OFFSET
+  jne 2f
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME_KEEP_EAX
+  ret
+  CFI_RESTORE_STATE_AND_DEF_CFA esp, FRAME_SIZE_SAVE_EVERYTHING
+2:
+  movl %eax, SAVE_EVERYTHING_FRAME_EAX_OFFSET(%esp) // update eax in the frame
+  subl MACRO_LITERAL(4), %esp                       // alignment padding
+  CFI_ADJUST_CFA_OFFSET(4)
+  pushl MACRO_LITERAL(\is_ref)                      // is_ref
+  CFI_ADJUST_CFA_OFFSET(4)
+  PUSH_ARG eax                                      // result
+  pushl %fs:THREAD_SELF_OFFSET                      // Pass Thread::Current
+  CFI_ADJUST_CFA_OFFSET(4)
+  call SYMBOL(artDeoptimizeIfNeeded)
+  addl LITERAL(16), %esp                            // pop arguments
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME
+  ret
+  CFI_RESTORE_STATE_AND_DEF_CFA esp, FRAME_SIZE_SAVE_EVERYTHING
+END_MACRO
+
+
 MACRO0(RETURN_IF_EAX_ZERO)
     testl %eax, %eax               // eax == 0 ?
     jnz  1f                        // if eax != 0 goto 1
-    ret                            // return
+    DEOPT_OR_RETURN ebx            // check if deopt is needed
 1:                                 // deliver exception on current thread
     DELIVER_PENDING_EXCEPTION
 END_MACRO
@@ -927,7 +978,7 @@
     addl LITERAL(16), %esp                       // pop arguments
     CFI_ADJUST_CFA_OFFSET(-16)
     RESTORE_SAVE_REFS_ONLY_FRAME                 // restore frame up to return address
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER      // return or deliver exception
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER      // return or deliver exception
     END_FUNCTION VAR(c_name)
 END_MACRO
 
@@ -974,7 +1025,7 @@
     addl LITERAL(16), %esp
     CFI_ADJUST_CFA_OFFSET(-16)
     RESTORE_SAVE_REFS_ONLY_FRAME                        // restore frame up to return address
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER             // return or deliver exception
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER    // return or deliver exception
 END_MACRO
 
 MACRO2(ART_QUICK_ALLOC_OBJECT_TLAB, c_name, cxx_name)
@@ -1107,7 +1158,7 @@
     addl LITERAL(16), %esp                              // pop arguments
     CFI_ADJUST_CFA_OFFSET(-16)
     RESTORE_SAVE_REFS_ONLY_FRAME                        // restore frame up to return address
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER             // return or deliver exception
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER    // return or deliver exception
     END_FUNCTION VAR(c_entrypoint)
 END_MACRO
 
@@ -1254,126 +1305,102 @@
     .endif
 END_MACRO
 
-    /*
-     * Macro to insert read barrier, only used in art_quick_aput_obj.
-     * obj_reg and dest_reg are registers, offset is a defined literal such as
-     * MIRROR_OBJECT_CLASS_OFFSET.
-     * pop_eax is a boolean flag, indicating if eax is popped after the call.
-     * TODO: When read barrier has a fast path, add heap unpoisoning support for the fast path.
-     */
-MACRO4(READ_BARRIER, obj_reg, offset, dest_reg, pop_eax)
-#ifdef USE_READ_BARRIER
-    PUSH eax                        // save registers used in art_quick_aput_obj
-    PUSH ebx
-    PUSH edx
-    PUSH ecx
-    // Outgoing argument set up
-    pushl MACRO_LITERAL((RAW_VAR(offset)))  // pass offset, double parentheses are necessary
-    CFI_ADJUST_CFA_OFFSET(4)
-    PUSH RAW_VAR(obj_reg)           // pass obj_reg
-    PUSH eax                        // pass ref, just pass eax for now since parameter ref is unused
-    call SYMBOL(artReadBarrierSlow) // artReadBarrierSlow(ref, obj_reg, offset)
-    // No need to unpoison return value in eax, artReadBarrierSlow() would do the unpoisoning.
-    .ifnc RAW_VAR(dest_reg), eax
-      movl %eax, REG_VAR(dest_reg)  // save loaded ref in dest_reg
-    .endif
-    addl MACRO_LITERAL(12), %esp    // pop arguments
-    CFI_ADJUST_CFA_OFFSET(-12)
-    POP_REG_NE ecx, RAW_VAR(dest_reg) // Restore args except dest_reg
-    POP_REG_NE edx, RAW_VAR(dest_reg)
-    POP_REG_NE ebx, RAW_VAR(dest_reg)
-    .ifc RAW_VAR(pop_eax), true
-      POP_REG_NE eax, RAW_VAR(dest_reg)
-    .endif
-#else
-    movl RAW_VAR(offset)(REG_VAR(obj_reg)), REG_VAR(dest_reg)
-    UNPOISON_HEAP_REF RAW_VAR(dest_reg)
-#endif  // USE_READ_BARRIER
-END_MACRO
-
 DEFINE_FUNCTION art_quick_aput_obj
     test %edx, %edx              // store of null
-    jz .Ldo_aput_null
-    READ_BARRIER eax, MIRROR_OBJECT_CLASS_OFFSET, ebx, true
-    READ_BARRIER ebx, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, ebx, true
-    // value's type == array's component type - trivial assignability
-#if defined(USE_READ_BARRIER)
-    READ_BARRIER edx, MIRROR_OBJECT_CLASS_OFFSET, eax, false
-    cmpl %eax, %ebx
-    POP eax                      // restore eax from the push in the beginning of READ_BARRIER macro
-    // This asymmetric push/pop saves a push of eax and maintains stack alignment.
-#elif defined(USE_HEAP_POISONING)
-    PUSH eax                     // save eax
-    movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %eax
-    UNPOISON_HEAP_REF eax
-    cmpl %eax, %ebx
-    POP eax                      // restore eax
-#else
-    cmpl MIRROR_OBJECT_CLASS_OFFSET(%edx), %ebx
-#endif
-    jne .Lcheck_assignability
-.Ldo_aput:
+    jz .Laput_obj_null
+    movl MIRROR_OBJECT_CLASS_OFFSET(%eax), %ebx
+    UNPOISON_HEAP_REF ebx
+#ifdef USE_READ_BARRIER
+    cmpl LITERAL(0), %fs:THREAD_IS_GC_MARKING_OFFSET
+    jnz .Laput_obj_gc_marking
+#endif  // USE_READ_BARRIER
+    movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%ebx), %ebx
+    cmpl MIRROR_OBJECT_CLASS_OFFSET(%edx), %ebx  // Both poisoned if heap poisoning is enabled.
+    jne .Laput_obj_check_assignability
+.Laput_obj_store:
     POISON_HEAP_REF edx
     movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%eax, %ecx, 4)
     movl %fs:THREAD_CARD_TABLE_OFFSET, %edx
     shrl LITERAL(CARD_TABLE_CARD_SHIFT), %eax
     movb %dl, (%edx, %eax)
     ret
-.Ldo_aput_null:
+
+.Laput_obj_null:
     movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%eax, %ecx, 4)
     ret
-.Lcheck_assignability:
-    PUSH eax                      // save arguments
-    PUSH ecx
-    PUSH edx
-#if defined(USE_READ_BARRIER)
-    subl LITERAL(4), %esp         // alignment padding
-    CFI_ADJUST_CFA_OFFSET(4)
-    READ_BARRIER edx, MIRROR_OBJECT_CLASS_OFFSET, eax, true
-    subl LITERAL(4), %esp         // alignment padding
-    CFI_ADJUST_CFA_OFFSET(4)
-    PUSH eax                      // pass arg2 - type of the value to be stored
-#elif defined(USE_HEAP_POISONING)
-    subl LITERAL(8), %esp         // alignment padding
-    CFI_ADJUST_CFA_OFFSET(8)
+
+.Laput_obj_check_assignability:
+    UNPOISON_HEAP_REF ebx         // Unpoison array component type if poisoning is enabled.
+    PUSH_ARG eax                  // Save `art_quick_aput_obj()` arguments.
+    PUSH_ARG ecx
+    PUSH_ARG edx
+    INCREASE_FRAME 8              // Alignment padding.
+    // Pass arg2 - type of the value to be stored.
+#if defined(USE_HEAP_POISONING)
     movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %eax
     UNPOISON_HEAP_REF eax
-    PUSH eax                      // pass arg2 - type of the value to be stored
+    PUSH_ARG eax
 #else
-    subl LITERAL(8), %esp         // alignment padding
-    CFI_ADJUST_CFA_OFFSET(8)
-    pushl MIRROR_OBJECT_CLASS_OFFSET(%edx)  // pass arg2 - type of the value to be stored
+    pushl MIRROR_OBJECT_CLASS_OFFSET(%edx)
     CFI_ADJUST_CFA_OFFSET(4)
 #endif
-    PUSH ebx                      // pass arg1 - component type of the array
+.Laput_obj_check_assignability_call:
+    PUSH_ARG ebx                  // Pass arg1 - component type of the array.
     call SYMBOL(artIsAssignableFromCode)  // (Class* a, Class* b)
-    addl LITERAL(16), %esp        // pop arguments
-    CFI_ADJUST_CFA_OFFSET(-16)
+    DECREASE_FRAME 16             // Pop `artIsAssignableFromCode()` arguments
     testl %eax, %eax
+    POP_ARG edx                   // Pop `art_quick_aput_obj()` arguments; flags unaffected.
+    POP_ARG ecx
+    POP_ARG eax
     jz   .Lthrow_array_store_exception
-    POP  edx
-    POP  ecx
-    POP  eax
     POISON_HEAP_REF edx
-    movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%eax, %ecx, 4)  // do the aput
+    movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%eax, %ecx, 4)  // Do the aput.
     movl %fs:THREAD_CARD_TABLE_OFFSET, %edx
     shrl LITERAL(CARD_TABLE_CARD_SHIFT), %eax
     movb %dl, (%edx, %eax)
     ret
-    CFI_ADJUST_CFA_OFFSET(12)     // 3 POP after the jz for unwinding.
+
 .Lthrow_array_store_exception:
-    POP  edx
-    POP  ecx
-    POP  eax
-    SETUP_SAVE_ALL_CALLEE_SAVES_FRAME ebx // save all registers as basis for long jump context
-    // Outgoing argument set up
-    PUSH eax                      // alignment padding
-    pushl %fs:THREAD_SELF_OFFSET  // pass Thread::Current()
-    CFI_ADJUST_CFA_OFFSET(4)
-    PUSH edx                      // pass arg2 - value
-    PUSH eax                      // pass arg1 - array
+#ifdef USE_READ_BARRIER
+    CFI_REMEMBER_STATE
+#endif  // USE_READ_BARRIER
+    SETUP_SAVE_ALL_CALLEE_SAVES_FRAME ebx // Save all registers as basis for long jump context.
+    // Outgoing argument set up.
+    PUSH_ARG eax                  // Alignment padding.
+    PUSH_ARG fs:THREAD_SELF_OFFSET  // Pass Thread::Current()
+    PUSH_ARG edx                  // Pass arg2 - value.
+    PUSH_ARG eax                  // Pass arg1 - array.
     call SYMBOL(artThrowArrayStoreException) // (array, value, Thread*)
     UNREACHABLE
+
+#ifdef USE_READ_BARRIER
+    CFI_RESTORE_STATE_AND_DEF_CFA esp, 4
+.Laput_obj_gc_marking:
+    PUSH_ARG eax                  // Save `art_quick_aput_obj()` arguments.
+    PUSH_ARG ecx                  // We need to align stack for `art_quick_read_barrier_mark_regNN`
+    PUSH_ARG edx                  // and use a register (EAX) as a temporary for the object class.
+    call SYMBOL(art_quick_read_barrier_mark_reg03)  // Mark EBX.
+    movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%ebx), %ebx
+    UNPOISON_HEAP_REF ebx
+    call SYMBOL(art_quick_read_barrier_mark_reg03)  // Mark EBX.
+    movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %eax
+    UNPOISON_HEAP_REF eax
+    call SYMBOL(art_quick_read_barrier_mark_reg00)  // Mark EAX.
+    cmpl %eax, %ebx
+    jne .Laput_obj_check_assignability_gc_marking
+    POP_ARG edx                   // Restore `art_quick_aput_obj()` arguments.
+    POP_ARG ecx
+    POP_ARG eax
+    jmp .Laput_obj_store
+
+.Laput_obj_check_assignability_gc_marking:
+    // Prepare arguments in line with `.Laput_obj_check_assignability_call` and jump there.
+    // (EAX, ECX and EDX were already saved in the right stack slots.)
+    INCREASE_FRAME 8              // Alignment padding.
+    PUSH_ARG eax                  // Pass arg2 - type of the value to be stored.
+    // The arg1 shall be pushed at `.Laput_obj_check_assignability_call`.
+    jmp .Laput_obj_check_assignability_call
+#endif  // USE_READ_BARRIER
 END_FUNCTION art_quick_aput_obj
 
 DEFINE_FUNCTION art_quick_memcpy
@@ -1501,21 +1528,21 @@
 // Note: Functions `art{Get,Set}<Kind>{Static,Instance}FromCompiledCode` are
 // defined with a macro in runtime/entrypoints/quick/quick_field_entrypoints.cc.
 
-ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
 
-TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
 
 TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_EAX_ZERO
 TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_EAX_ZERO
@@ -1616,7 +1643,7 @@
     movl %eax, %edi               // remember code pointer in EDI
     addl LITERAL(16), %esp        // pop arguments
     CFI_ADJUST_CFA_OFFSET(-16)
-    test %eax, %eax               // if code pointer is null goto deliver pending exception
+    test %eax, %eax               // if code pointer is null goto deliver the OOME.
     jz 1f
     RESTORE_SAVE_REFS_AND_ARGS_FRAME_AND_JUMP
 1:
@@ -1628,7 +1655,7 @@
     SETUP_SAVE_REFS_AND_ARGS_FRAME_WITH_METHOD_IN_EAX
     movl %esp, %ebp               // save SP at callee-save frame
     CFI_DEF_CFA_REGISTER(ebp)
-    subl LITERAL(5120), %esp
+    subl LITERAL(GENERIC_JNI_TRAMPOLINE_RESERVED_AREA), %esp
     // prepare for artQuickGenericJniTrampoline call
     // (Thread*, managed_sp, reserved_area)
     //   (esp)    4(esp)        8(esp)  <= C calling convention
@@ -1683,29 +1710,36 @@
 
     // Tear down the alloca.
     movl %ebp, %esp
-    CFI_REMEMBER_STATE
-    CFI_DEF_CFA_REGISTER(esp)
 
-    // Tear down the callee-save frame.
-    // Remove space for FPR args and EAX
-    addl LITERAL(4 + 4 * 8), %esp
-    CFI_ADJUST_CFA_OFFSET(-(4 + 4 * 8))
-
-    POP ecx
-    addl LITERAL(4), %esp         // Avoid edx, as it may be part of the result.
-    CFI_ADJUST_CFA_OFFSET(-4)
-    POP ebx
-    POP ebp  // Restore callee saves
-    POP esi
-    POP edi
     // Quick expects the return value to be in xmm0.
     movd %eax, %xmm0
     movd %edx, %xmm1
     punpckldq %xmm1, %xmm0
+
+    LOAD_RUNTIME_INSTANCE ebx
+    cmpb MACRO_LITERAL(0), RUN_EXIT_HOOKS_OFFSET_FROM_RUNTIME_INSTANCE(%ebx)
+    jne .Lcall_method_exit_hook
+.Lcall_method_exit_hook_done:
+
+    // Tear down the callee-save frame.
+    CFI_REMEMBER_STATE
+    CFI_DEF_CFA_REGISTER(esp)
+    // Remove space for the method, FPR and GPR args
+    DECREASE_FRAME 4 + 4 * 8 + 3*4
+    // Restore callee saves and return.
+    POP ebp
+    POP esi
+    POP edi
     ret
 
     // Undo the unwinding information from above since it doesn't apply below.
     CFI_RESTORE_STATE_AND_DEF_CFA ebp, 64
+
+.Lcall_method_exit_hook:
+    movl LITERAL(FRAME_SIZE_SAVE_REFS_AND_ARGS), %ebx
+    call art_quick_method_exit_hook
+    jmp .Lcall_method_exit_hook_done
+
 .Lexception_in_native:
     pushl %fs:THREAD_TOP_QUICK_FRAME_OFFSET
     addl LITERAL(-1), (%esp)  // Remove the GenericJNI tag.
@@ -1751,115 +1785,6 @@
 ONE_ARG_RUNTIME_EXCEPTION art_invoke_obsolete_method_stub, artInvokeObsoleteMethod
 
     /*
-     * Routine that intercepts method calls and returns.
-     */
-DEFINE_FUNCTION art_quick_instrumentation_entry
-    SETUP_SAVE_REFS_AND_ARGS_FRAME edx
-    PUSH eax                      // Save eax which will be clobbered by the callee-save method.
-    subl LITERAL(16), %esp        // Align stack (12 bytes) and reserve space for the SP argument
-    CFI_ADJUST_CFA_OFFSET(16)     // (4 bytes). We lack the scratch registers to calculate the SP
-                                  // right now, so we will just fill it in later.
-    pushl %fs:THREAD_SELF_OFFSET  // Pass Thread::Current().
-    CFI_ADJUST_CFA_OFFSET(4)
-    PUSH ecx                      // Pass receiver.
-    PUSH eax                      // Pass Method*.
-    leal 32(%esp), %eax           // Put original SP into eax
-    movl %eax, 12(%esp)           // set SP
-    call SYMBOL(artInstrumentationMethodEntryFromCode) // (Method*, Object*, Thread*, SP)
-
-    addl LITERAL(28), %esp        // Pop arguments upto saved Method*.
-    CFI_ADJUST_CFA_OFFSET(-28)
-
-    testl %eax, %eax
-    jz 1f                         // Test for null return (indicating exception) and handle it.
-
-    movl 60(%esp), %edi           // Restore edi.
-    movl %eax, 60(%esp)           // Place code* over edi, just under return pc.
-    SETUP_PC_REL_BASE ebx, .Linstrumentation_entry_pc_rel_base
-    leal SYMBOL(art_quick_instrumentation_exit) - .Linstrumentation_entry_pc_rel_base(%ebx), %ebx
-    // Place instrumentation exit as return pc. ebx holds the GOT computed on entry.
-    movl %ebx, 64(%esp)
-    movl 0(%esp), %eax           // Restore eax.
-    // Restore FPRs (extra 4 bytes of offset due to EAX push at top).
-    movsd 8(%esp), %xmm0
-    movsd 16(%esp), %xmm1
-    movsd 24(%esp), %xmm2
-    movsd 32(%esp), %xmm3
-
-    // Restore GPRs.
-    movl 40(%esp), %ecx           // Restore ecx.
-    movl 44(%esp), %edx           // Restore edx.
-    movl 48(%esp), %ebx           // Restore ebx.
-    movl 52(%esp), %ebp           // Restore ebp.
-    movl 56(%esp), %esi           // Restore esi.
-    addl LITERAL(60), %esp        // Wind stack back upto code*.
-    CFI_ADJUST_CFA_OFFSET(-60)
-    ret                           // Call method (and pop).
-1:
-    // Make caller handle exception
-    addl LITERAL(4), %esp
-    CFI_ADJUST_CFA_OFFSET(-4)
-    RESTORE_SAVE_REFS_AND_ARGS_FRAME
-    DELIVER_PENDING_EXCEPTION
-END_FUNCTION art_quick_instrumentation_entry
-
-DEFINE_FUNCTION_CUSTOM_CFA art_quick_instrumentation_exit, 0
-    pushl LITERAL(0)              // Push a fake return PC as there will be none on the stack.
-    CFI_ADJUST_CFA_OFFSET(4)
-    SETUP_SAVE_EVERYTHING_FRAME ebx
-
-    movl %esp, %ecx               // Remember SP
-    subl LITERAL(8), %esp         // Align stack.
-    CFI_ADJUST_CFA_OFFSET(8)
-    PUSH edx                      // Save gpr return value. edx and eax need to be together,
-                                  // which isn't the case in kSaveEverything frame.
-    PUSH eax
-    leal 32(%esp), %eax           // Get pointer to fpr_result, in kSaveEverything frame
-    movl %esp, %edx               // Get pointer to gpr_result
-    PUSH eax                      // Pass fpr_result
-    PUSH edx                      // Pass gpr_result
-    PUSH ecx                      // Pass SP
-    pushl %fs:THREAD_SELF_OFFSET  // Pass Thread::Current.
-    CFI_ADJUST_CFA_OFFSET(4)
-
-    call SYMBOL(artInstrumentationMethodExitFromCode)  // (Thread*, SP, gpr_result*, fpr_result*)
-    // Return result could have been changed if it's a reference.
-    movl 16(%esp), %ecx
-    movl %ecx, (80+32)(%esp)
-    addl LITERAL(32), %esp        // Pop arguments and grp_result.
-    CFI_ADJUST_CFA_OFFSET(-32)
-
-    testl %eax, %eax              // Check if we returned error.
-    jz .Ldo_deliver_instrumentation_exception
-    testl %edx, %edx
-    jnz .Ldeoptimize
-    // Normal return.
-    movl %eax, FRAME_SIZE_SAVE_EVERYTHING-4(%esp)   // Set return pc.
-    RESTORE_SAVE_EVERYTHING_FRAME
-    ret
-.Ldeoptimize:
-    mov %edx, (FRAME_SIZE_SAVE_EVERYTHING-4)(%esp)  // Set return pc.
-    RESTORE_SAVE_EVERYTHING_FRAME
-    jmp SYMBOL(art_quick_deoptimize)
-.Ldo_deliver_instrumentation_exception:
-    DELIVER_PENDING_EXCEPTION_FRAME_READY
-END_FUNCTION art_quick_instrumentation_exit
-
-    /*
-     * Instrumentation has requested that we deoptimize into the interpreter. The deoptimization
-     * will long jump to the upcall with a special exception of -1.
-     */
-DEFINE_FUNCTION art_quick_deoptimize
-    SETUP_SAVE_EVERYTHING_FRAME ebx
-    subl LITERAL(12), %esp        // Align stack.
-    CFI_ADJUST_CFA_OFFSET(12)
-    pushl %fs:THREAD_SELF_OFFSET  // Pass Thread::Current().
-    CFI_ADJUST_CFA_OFFSET(4)
-    call SYMBOL(artDeoptimize)    // (Thread*)
-    UNREACHABLE
-END_FUNCTION art_quick_deoptimize
-
-    /*
      * Compiled code has requested that we deoptimize into the interpreter. The deoptimization
      * will long jump to the interpreter bridge.
      */
@@ -1974,34 +1899,29 @@
     SETUP_SAVE_REFS_ONLY_FRAME ebx            // save ref containing registers for GC
     // Outgoing argument set up
     leal FRAME_SIZE_SAVE_REFS_ONLY + __SIZEOF_POINTER__(%esp), %edi  // prepare args
-    push %eax                                 // push padding
+    push %eax                                         // push padding
     CFI_ADJUST_CFA_OFFSET(4)
-    pushl %fs:THREAD_SELF_OFFSET              // pass Thread::Current()
+    pushl %fs:THREAD_SELF_OFFSET                      // pass Thread::Current()
     CFI_ADJUST_CFA_OFFSET(4)
-    push %edi                                 // pass args
+    push %edi                                         // pass args
     CFI_ADJUST_CFA_OFFSET(4)
-    push %eax                                 // pass format
+    push %eax                                         // pass format
     CFI_ADJUST_CFA_OFFSET(4)
-    call SYMBOL(artStringBuilderAppend)       // (uint32_t, const unit32_t*, Thread*)
-    addl MACRO_LITERAL(16), %esp              // pop arguments
+    call SYMBOL(artStringBuilderAppend)               // (uint32_t, const unit32_t*, Thread*)
+    addl MACRO_LITERAL(16), %esp                      // pop arguments
     CFI_ADJUST_CFA_OFFSET(-16)
-    RESTORE_SAVE_REFS_ONLY_FRAME              // restore frame up to return address
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER   // return or deliver exception
+    RESTORE_SAVE_REFS_ONLY_FRAME                      // restore frame up to return address
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER  // return or deliver exception
 END_FUNCTION art_quick_string_builder_append
 
 // Create a function `name` calling the ReadBarrier::Mark routine,
 // getting its argument and returning its result through register
 // `reg`, saving and restoring all caller-save registers.
 //
-// If `reg` is different from `eax`, the generated function follows a
-// non-standard runtime calling convention:
-// - register `reg` is used to pass the (sole) argument of this function
-//   (instead of EAX);
-// - register `reg` is used to return the result of this function
-//   (instead of EAX);
-// - EAX is treated like a normal (non-argument) caller-save register;
-// - everything else is the same as in the standard runtime calling
-//   convention (e.g. standard callee-save registers are preserved).
+// The generated function follows a non-standard runtime calling convention:
+// - register `reg` (which may differ from EAX) is used to pass the (sole) argument,
+// - register `reg` (which may differ from EAX) is used to return the result,
+// - all other registers are callee-save (the values they hold are preserved).
 MACRO2(READ_BARRIER_MARK_REG, name, reg)
     DEFINE_FUNCTION VAR(name)
     // Null check so that we can load the lock word.
@@ -2313,15 +2233,18 @@
 DEFINE_FUNCTION art_quick_method_entry_hook
     SETUP_SAVE_EVERYTHING_FRAME edx
     mov FRAME_SIZE_SAVE_EVERYTHING(%esp), %eax // Fetch ArtMethod
-    subl LITERAL(8), %esp
-    CFI_ADJUST_CFA_OFFSET(8)
+    mov %esp, %edx  // Store esp before pushing anything on stack.
+    subl LITERAL(4), %esp
+    CFI_ADJUST_CFA_OFFSET(4)
 
+    push %edx                       // Pass SP
+    CFI_ADJUST_CFA_OFFSET(4)
     pushl %fs:THREAD_SELF_OFFSET    // Pass Thread::Current().
     CFI_ADJUST_CFA_OFFSET(4)
     pushl %eax                      // Pass Method*.
     CFI_ADJUST_CFA_OFFSET(4)
 
-    call SYMBOL(artMethodEntryHook) // (Method*, Thread*)
+    call SYMBOL(artMethodEntryHook) // (Method*, Thread*, SP)
 
     addl LITERAL(16), %esp          // Pop arguments.
     CFI_ADJUST_CFA_OFFSET(-16)
@@ -2331,40 +2254,34 @@
 END_FUNCTION art_quick_method_entry_hook
 
 DEFINE_FUNCTION art_quick_method_exit_hook
-    SETUP_SAVE_EVERYTHING_FRAME ebx
+    PUSH edi
+    SETUP_SAVE_EVERYTHING_FRAME_EDI_SAVED edi
 
-    mov FRAME_SIZE_SAVE_EVERYTHING(%esp), %ebx // Remember ArtMethod*
-    subl LITERAL(8), %esp                      // Align stack.
-    CFI_ADJUST_CFA_OFFSET(8)
+    leal FRAME_SIZE_SAVE_EVERYTHING(%esp), %edi // Remember ArtMethod**
+    subl LITERAL(4), %esp                      // Align stack.
+    CFI_ADJUST_CFA_OFFSET(4)
+
     PUSH_ARG edx                   // Save gpr return value. edx and eax need to be together
                                    // which isn't the case in kSaveEverything frame.
     PUSH_ARG eax
     movl %esp, %edx                // Get pointer to gpr_result
-    leal 32(%esp), %eax            // Get pointer to fpr_result, in kSaveEverything frame
+    leal 28(%esp), %eax            // Get pointer to fpr_result, in kSaveEverything frame
+    PUSH_ARG ebx                   // push frame_size
     PUSH_ARG eax                   // Pass fpr_result
     PUSH_ARG edx                   // Pass gpr_result
-    PUSH_ARG ebx                   // Pass ArtMethod*
+    PUSH_ARG edi                   // Pass ArtMethod**
     pushl %fs:THREAD_SELF_OFFSET   // Pass Thread::Current.
     CFI_ADJUST_CFA_OFFSET(4)
-    call SYMBOL(artMethodExitHook) // (Thread*, ArtMethod*, gpr_result*, fpr_result*)
+    call SYMBOL(artMethodExitHook) // (Thread*, ArtMethod**, gpr_result*, fpr_result*,
+                                   // frame_size)
 
     // Return result could have been changed if it's a reference.
-    movl 16(%esp), %ecx
+    movl 20(%esp), %ecx
     movl %ecx, (80+32)(%esp)
     addl LITERAL(32), %esp         // Pop arguments and grp_result.
     CFI_ADJUST_CFA_OFFSET(-32)
 
-    cmpl LITERAL(1), %eax          // Check if we returned error.
-    CFI_REMEMBER_STATE
-    je .Ldo_deliver_instrumentation_exception_exit
-
     // Normal return.
     RESTORE_SAVE_EVERYTHING_FRAME
     ret
-.Ldo_deliver_instrumentation_exception_exit:
-    CFI_RESTORE_STATE_AND_DEF_CFA esp, FRAME_SIZE_SAVE_EVERYTHING
-    DELIVER_PENDING_EXCEPTION_FRAME_READY
 END_FUNCTION art_quick_method_exit_hook
-
-
-
diff --git a/runtime/arch/x86_64/asm_support_x86_64.S b/runtime/arch/x86_64/asm_support_x86_64.S
index bfec8c0..012b1df 100644
--- a/runtime/arch/x86_64/asm_support_x86_64.S
+++ b/runtime/arch/x86_64/asm_support_x86_64.S
@@ -174,6 +174,7 @@
     // Prefix the assembly code with 0xFFs, which means there is no method header.
     .byte 0xFF, 0xFF, 0xFF, 0xFF
     // Cache alignment for function entry.
+    // Use 0xFF as the last 4 bytes of alignment stand for OatQuickMethodHeader.
     .balign 16, 0xFF
 END_MACRO
 
@@ -355,7 +356,7 @@
     PUSH rbx      // Callee save.
     PUSH_ARG rdx  // Quick arg 2.
     PUSH_ARG rcx  // Quick arg 3.
-    // Create space for FPR args and create 2 slots for ArtMethod*.
+    // Create space for FPR args and create 2 slots for ArtMethod* and padding.
     INCREASE_FRAME 16 + 12 * 8
     // Save FPRs.
     movq %xmm0, 16(%rsp)
@@ -485,11 +486,10 @@
 END_MACRO
 
 MACRO0(RETURN_OR_DELIVER_PENDING_EXCEPTION)
-    movq %gs:THREAD_EXCEPTION_OFFSET, %rcx // get exception field
-    testq %rcx, %rcx               // rcx == 0 ?
-    jnz 1f                         // if rcx != 0 goto 1
-    ret                            // return
-1:                                 // deliver exception on current thread
+    cmpq MACRO_LITERAL(0), %gs:THREAD_EXCEPTION_OFFSET  // compare exception field with 0
+    jne 1f                                              // if exception != 0 goto 1
+    ret                                                 // return
+1:                                                      // deliver exception on current thread
     DELIVER_PENDING_EXCEPTION
 END_MACRO
 
diff --git a/runtime/arch/x86_64/asm_support_x86_64.h b/runtime/arch/x86_64/asm_support_x86_64.h
index 51befbe..e4d7aa9 100644
--- a/runtime/arch/x86_64/asm_support_x86_64.h
+++ b/runtime/arch/x86_64/asm_support_x86_64.h
@@ -18,6 +18,7 @@
 #define ART_RUNTIME_ARCH_X86_64_ASM_SUPPORT_X86_64_H_
 
 #include "asm_support.h"
+#include "entrypoints/entrypoint_asm_constants.h"
 
 #define FRAME_SIZE_SAVE_ALL_CALLEE_SAVES (64 + 4*8)
 #define FRAME_SIZE_SAVE_REFS_ONLY (64 + 4*8)
@@ -25,5 +26,7 @@
 #define FRAME_SIZE_SAVE_EVERYTHING (144 + 16*8)
 #define FRAME_SIZE_SAVE_EVERYTHING_FOR_CLINIT FRAME_SIZE_SAVE_EVERYTHING
 #define FRAME_SIZE_SAVE_EVERYTHING_FOR_SUSPEND_CHECK FRAME_SIZE_SAVE_EVERYTHING
+#define SAVE_EVERYTHING_FRAME_RAX_OFFSET \
+    (FRAME_SIZE_SAVE_EVERYTHING - CALLEE_SAVE_EVERYTHING_NUM_CORE_SPILLS * POINTER_SIZE)
 
 #endif  // ART_RUNTIME_ARCH_X86_64_ASM_SUPPORT_X86_64_H_
diff --git a/runtime/arch/x86_64/context_x86_64.cc b/runtime/arch/x86_64/context_x86_64.cc
index a4db223..ab3b2c5 100644
--- a/runtime/arch/x86_64/context_x86_64.cc
+++ b/runtime/arch/x86_64/context_x86_64.cc
@@ -31,8 +31,8 @@
   gprs_[RSP] = &rsp_;
   gprs_[RDI] = &arg0_;
   // Initialize registers with easy to spot debug values.
-  rsp_ = X86_64Context::kBadGprBase + RSP;
-  rip_ = X86_64Context::kBadGprBase + kNumberOfCpuRegisters;
+  rsp_ = kBadGprBase + RSP;
+  rip_ = kBadGprBase + kNumberOfCpuRegisters;
   arg0_ = 0;
 }
 
@@ -108,10 +108,10 @@
   uintptr_t fprs[kNumberOfFloatRegisters];
 
   for (size_t i = 0; i < kNumberOfCpuRegisters; ++i) {
-    gprs[kNumberOfCpuRegisters - i - 1] = gprs_[i] != nullptr ? *gprs_[i] : X86_64Context::kBadGprBase + i;
+    gprs[kNumberOfCpuRegisters - i - 1] = gprs_[i] != nullptr ? *gprs_[i] : kBadGprBase + i;
   }
   for (size_t i = 0; i < kNumberOfFloatRegisters; ++i) {
-    fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : X86_64Context::kBadFprBase + i;
+    fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : kBadFprBase + i;
   }
 
   // We want to load the stack pointer one slot below so that the ret will pop eip.
diff --git a/runtime/arch/x86_64/jni_entrypoints_x86_64.S b/runtime/arch/x86_64/jni_entrypoints_x86_64.S
index 0d5fa3f..55f01b7 100644
--- a/runtime/arch/x86_64/jni_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/jni_entrypoints_x86_64.S
@@ -118,7 +118,7 @@
     // Call artFindNativeMethod() for normal native and artFindNativeMethodRunnable()
     // for @FastNative or @CriticalNative.
     movq THREAD_TOP_QUICK_FRAME_OFFSET(%rdi), %rax   // uintptr_t tagged_quick_frame
-    andq LITERAL(0xfffffffffffffffe), %rax           // ArtMethod** sp
+    andq LITERAL(TAGGED_JNI_SP_MASK_TOGGLED64), %rax // ArtMethod** sp
     movq (%rax), %rax                                // ArtMethod* method
     testl LITERAL(ACCESS_FLAGS_METHOD_IS_FAST_NATIVE | ACCESS_FLAGS_METHOD_IS_CRITICAL_NATIVE), \
           ART_METHOD_ACCESS_FLAGS_OFFSET(%rax)
@@ -400,6 +400,12 @@
 JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_start, artJniMethodStart, gs:THREAD_SELF_OFFSET
 
     /*
+     * Trampoline to `artJniMethodEntryHook` that preserves all managed arguments.
+     */
+JNI_SAVE_MANAGED_ARGS_TRAMPOLINE \
+    art_jni_method_entry_hook, artJniMethodEntryHook, gs:THREAD_SELF_OFFSET
+
+    /*
      * Trampoline to `artJniMonitoredMethodStart()` that preserves all managed arguments.
      */
 JNI_SAVE_MANAGED_ARGS_TRAMPOLINE \
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index 673696c..e5e200b 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -731,12 +731,9 @@
     movl %eax, %edi                               // pass the index of the constant as arg0
     movq %gs:THREAD_SELF_OFFSET, %rsi             // pass Thread::Current()
     call CALLVAR(cxx_name)                        // cxx_name(arg0, Thread*)
-    testl %eax, %eax                              // If result is null, deliver the OOME.
+    testl %eax, %eax                              // If result is null, deliver pending exception.
     jz 1f
-    CFI_REMEMBER_STATE
-    RESTORE_SAVE_EVERYTHING_FRAME_KEEP_RAX        // restore frame up to return address
-    ret
-    CFI_RESTORE_STATE_AND_DEF_CFA rsp, FRAME_SIZE_SAVE_EVERYTHING
+    DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_RAX /*is_ref=*/1
 1:
     DELIVER_PENDING_EXCEPTION_FRAME_READY
     END_FUNCTION VAR(c_name)
@@ -746,18 +743,65 @@
     ONE_ARG_SAVE_EVERYTHING_DOWNCALL \c_name, \cxx_name, RUNTIME_SAVE_EVERYTHING_FOR_CLINIT_METHOD_OFFSET
 END_MACRO
 
-MACRO0(RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER)
+MACRO0(RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER)
     testq %rax, %rax               // rax == 0 ?
     jz  1f                         // if rax == 0 goto 1
-    ret                            // return
+    DEOPT_OR_RETURN /*is_ref=*/1   // Check if deopt is required
 1:                                 // deliver exception on current thread
     DELIVER_PENDING_EXCEPTION
 END_MACRO
 
+
+MACRO0(RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION)
+    movq %gs:THREAD_EXCEPTION_OFFSET, %rcx // get exception field
+    testq %rcx, %rcx               // rcx == 0 ?
+    jnz 1f                         // if rcx != 0 goto 1
+    DEOPT_OR_RETURN                // Check if deopt is required
+1:                                 // deliver exception on current thread
+    DELIVER_PENDING_EXCEPTION
+END_MACRO
+
+MACRO1(DEOPT_OR_RETURN, is_ref = 0)
+  cmpl LITERAL(0), %gs:THREAD_DEOPT_CHECK_REQUIRED_OFFSET
+  jne 2f
+  ret
+2:
+  SETUP_SAVE_EVERYTHING_FRAME
+  movq LITERAL(\is_ref), %rdx          // pass if result is a reference
+  movq %rax, %rsi                      // pass the result
+  movq %gs:THREAD_SELF_OFFSET, %rdi    // pass Thread::Current
+  call SYMBOL(artDeoptimizeIfNeeded)
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME
+  ret
+  CFI_RESTORE_STATE_AND_DEF_CFA rsp, FRAME_SIZE_SAVE_EVERYTHING
+END_MACRO
+
+MACRO1(DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_RAX, is_ref = 0)
+  cmpl LITERAL(0), %gs:THREAD_DEOPT_CHECK_REQUIRED_OFFSET
+  jne 2f
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME_KEEP_RAX
+  ret
+  CFI_RESTORE_STATE_AND_DEF_CFA rsp, FRAME_SIZE_SAVE_EVERYTHING
+2:
+  movq %rax, SAVE_EVERYTHING_FRAME_RAX_OFFSET(%rsp) // update result in the frame
+  movq LITERAL(\is_ref), %rdx                       // pass if result is a reference
+  movq %rax, %rsi                                   // pass the result
+  movq %gs:THREAD_SELF_OFFSET, %rdi                 // pass Thread::Current
+  call SYMBOL(artDeoptimizeIfNeeded)
+  CFI_REMEMBER_STATE
+  RESTORE_SAVE_EVERYTHING_FRAME
+  ret
+  CFI_RESTORE_STATE_AND_DEF_CFA rsp, FRAME_SIZE_SAVE_EVERYTHING
+END_MACRO
+
+
+
 MACRO0(RETURN_IF_EAX_ZERO)
     testl %eax, %eax               // eax == 0 ?
     jnz  1f                        // if eax != 0 goto 1
-    ret                            // return
+    DEOPT_OR_RETURN                // Check if we need a deopt
 1:                                 // deliver exception on current thread
     DELIVER_PENDING_EXCEPTION
 END_MACRO
@@ -859,7 +903,7 @@
     movq %gs:THREAD_SELF_OFFSET, %rsi                      // pass Thread::Current()
     call CALLVAR(cxx_name)                                 // cxx_name(arg0, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME                           // restore frame up to return address
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER                // return or deliver exception
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER       // return or deliver exception
     END_FUNCTION VAR(c_name)
 END_MACRO
 
@@ -931,7 +975,7 @@
     movq %gs:THREAD_SELF_OFFSET, %rsi                      // pass Thread::Current()
     call CALLVAR(cxx_name)                                 // cxx_name(arg0, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME                           // restore frame up to return address
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER                // return or deliver exception
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER       // return or deliver exception
 END_MACRO
 
 // A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB). May be
@@ -1019,7 +1063,7 @@
     movq %gs:THREAD_SELF_OFFSET, %rdx                          // pass Thread::Current()
     call CALLVAR(cxx_name)                                     // cxx_name(arg0, arg1, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME                               // restore frame up to return address
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER                    // return or deliver exception
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER           // return or deliver exception
     END_FUNCTION VAR(c_entrypoint)
 END_MACRO
 
@@ -1163,134 +1207,89 @@
     .endif
 END_MACRO
 
-    /*
-     * Macro to insert read barrier, used in art_quick_aput_obj.
-     * obj_reg and dest_reg{32|64} are registers, offset is a defined literal such as
-     * MIRROR_OBJECT_CLASS_OFFSET. dest_reg needs two versions to handle the mismatch between
-     * 64b PUSH/POP and 32b argument.
-     * TODO: When read barrier has a fast path, add heap unpoisoning support for the fast path.
-     *
-     * As with art_quick_aput_obj function, the 64b versions are in comments.
-     */
-MACRO4(READ_BARRIER, obj_reg, offset, dest_reg32, dest_reg64)
-#ifdef USE_READ_BARRIER
-    PUSH rax                            // save registers that might be used
-    PUSH rdi
-    PUSH rsi
-    PUSH rdx
-    PUSH rcx
-    SETUP_FP_CALLEE_SAVE_FRAME
-    // Outgoing argument set up
-    // movl REG_VAR(ref_reg32), %edi    // pass ref, no-op for now since parameter ref is unused
-    // // movq REG_VAR(ref_reg64), %rdi
-    movl REG_VAR(obj_reg), %esi         // pass obj_reg
-    // movq REG_VAR(obj_reg), %rsi
-    movl MACRO_LITERAL((RAW_VAR(offset))), %edx // pass offset, double parentheses are necessary
-    // movq MACRO_LITERAL((RAW_VAR(offset))), %rdx
-    call SYMBOL(artReadBarrierSlow)     // artReadBarrierSlow(ref, obj_reg, offset)
-    // No need to unpoison return value in rax, artReadBarrierSlow() would do the unpoisoning.
-    .ifnc RAW_VAR(dest_reg32), eax
-    // .ifnc RAW_VAR(dest_reg64), rax
-      movl %eax, REG_VAR(dest_reg32)    // save loaded ref in dest_reg
-      // movq %rax, REG_VAR(dest_reg64)
-    .endif
-    RESTORE_FP_CALLEE_SAVE_FRAME
-    POP_REG_NE rcx, RAW_VAR(dest_reg64) // Restore registers except dest_reg
-    POP_REG_NE rdx, RAW_VAR(dest_reg64)
-    POP_REG_NE rsi, RAW_VAR(dest_reg64)
-    POP_REG_NE rdi, RAW_VAR(dest_reg64)
-    POP_REG_NE rax, RAW_VAR(dest_reg64)
-#else
-    movl RAW_VAR(offset)(REG_VAR(obj_reg)), REG_VAR(dest_reg32)
-    // movq RAW_VAR(offset)(REG_VAR(obj_reg)), REG_VAR(dest_reg64)
-    UNPOISON_HEAP_REF RAW_VAR(dest_reg32) // UNPOISON_HEAP_REF only takes a 32b register
-#endif  // USE_READ_BARRIER
-END_MACRO
-
 DEFINE_FUNCTION art_quick_aput_obj
-    testl %edx, %edx                // store of null
-//  test %rdx, %rdx
-    jz .Ldo_aput_null
-    READ_BARRIER edi, MIRROR_OBJECT_CLASS_OFFSET, ecx, rcx
-    // READ_BARRIER rdi, MIRROR_OBJECT_CLASS_OFFSET, ecx, rcx
-    READ_BARRIER ecx, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, ecx, rcx
-    // READ_BARRIER rcx, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, ecx, rcx
-#if defined(USE_HEAP_POISONING) || defined(USE_READ_BARRIER)
-    READ_BARRIER edx, MIRROR_OBJECT_CLASS_OFFSET, eax, rax  // rax is free.
-    // READ_BARRIER rdx, MIRROR_OBJECT_CLASS_OFFSET, eax, rax
-    cmpl %eax, %ecx  // value's type == array's component type - trivial assignability
-#else
-    cmpl MIRROR_OBJECT_CLASS_OFFSET(%edx), %ecx // value's type == array's component type - trivial assignability
-//  cmpq MIRROR_CLASS_OFFSET(%rdx), %rcx
-#endif
-    jne .Lcheck_assignability
-.Ldo_aput:
+    test %edx, %edx              // store of null
+    jz .Laput_obj_null
+    movl MIRROR_OBJECT_CLASS_OFFSET(%rdi), %ecx
+    UNPOISON_HEAP_REF ecx
+#ifdef USE_READ_BARRIER
+    cmpl LITERAL(0), %gs:THREAD_IS_GC_MARKING_OFFSET
+    jnz .Laput_obj_gc_marking
+#endif  // USE_READ_BARRIER
+    movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%rcx), %ecx
+    cmpl MIRROR_OBJECT_CLASS_OFFSET(%rdx), %ecx  // Both poisoned if heap poisoning is enabled.
+    jne .Laput_obj_check_assignability
+.Laput_obj_store:
     POISON_HEAP_REF edx
-    movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%edi, %esi, 4)
-//  movq %rdx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
+    movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
     movq %gs:THREAD_CARD_TABLE_OFFSET, %rdx
     shrl LITERAL(CARD_TABLE_CARD_SHIFT), %edi
-//  shrl LITERAL(CARD_TABLE_CARD_SHIFT), %rdi
-    movb %dl, (%rdx, %rdi)                       // Note: this assumes that top 32b of %rdi are zero
+    movb %dl, (%rdx, %rdi)
     ret
-.Ldo_aput_null:
-    movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%edi, %esi, 4)
-//  movq %rdx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
+
+.Laput_obj_null:
+    movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
     ret
-.Lcheck_assignability:
-    // Save arguments.
-    PUSH rdi
-    PUSH rsi
-    PUSH rdx
+
+.Laput_obj_check_assignability:
+    UNPOISON_HEAP_REF ecx         // Unpoison array component type if poisoning is enabled.
+    PUSH_ARG rdi                  // Save arguments.
+    PUSH_ARG rsi
+    PUSH_ARG rdx
+    movl MIRROR_OBJECT_CLASS_OFFSET(%rdx), %esi  // Pass arg2 = value's class.
+    UNPOISON_HEAP_REF esi
+.Laput_obj_check_assignability_call:
+    movl %ecx, %edi               // Pass arg1 = array's component type.
     SETUP_FP_CALLEE_SAVE_FRAME
-
-#if defined(USE_HEAP_POISONING) || defined(USE_READ_BARRIER)
-    // The load of MIRROR_OBJECT_CLASS_OFFSET(%edx) is redundant, eax still holds the value.
-    movl %eax, %esi               // Pass arg2 = value's class.
-    // movq %rax, %rsi
-#else
-                                     // "Uncompress" = do nothing, as already zero-extended on load.
-    movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %esi  // Pass arg2 = value's class.
-#endif
-    movq %rcx, %rdi               // Pass arg1 = array's component type.
-
     call SYMBOL(artIsAssignableFromCode)  // (Class* a, Class* b)
-
-    // Exception?
-    testq %rax, %rax
-    jz   .Lthrow_array_store_exception
-
-    RESTORE_FP_CALLEE_SAVE_FRAME
-    // Restore arguments.
-    POP  rdx
-    POP  rsi
-    POP  rdi
-
+    RESTORE_FP_CALLEE_SAVE_FRAME  // Resore FP registers.
+    POP_ARG rdx                   // Restore arguments.
+    POP_ARG rsi
+    POP_ARG rdi
+    testq %rax, %rax              // Check for exception.
+    jz   .Laput_obj_throw_array_store_exception
     POISON_HEAP_REF edx
-    movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%edi, %esi, 4)
-//  movq %rdx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
+    movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
     movq %gs:THREAD_CARD_TABLE_OFFSET, %rdx
     shrl LITERAL(CARD_TABLE_CARD_SHIFT), %edi
-//  shrl LITERAL(CARD_TABLE_CARD_SHIFT), %rdi
-    movb %dl, (%rdx, %rdi)                       // Note: this assumes that top 32b of %rdi are zero
-//  movb %dl, (%rdx, %rdi)
+    movb %dl, (%rdx, %rdi)
     ret
-    CFI_ADJUST_CFA_OFFSET(24 + 4 * 8)  // Reset unwind info so following code unwinds.
-.Lthrow_array_store_exception:
-    RESTORE_FP_CALLEE_SAVE_FRAME
-    // Restore arguments.
-    POP  rdx
-    POP  rsi
-    POP  rdi
 
+.Laput_obj_throw_array_store_exception:
+#ifdef USE_READ_BARRIER
+    CFI_REMEMBER_STATE
+#endif  // USE_READ_BARRIER
     SETUP_SAVE_ALL_CALLEE_SAVES_FRAME  // Save all registers as basis for long jump context.
-
     // Outgoing argument set up.
     movq %rdx, %rsi                         // Pass arg 2 = value.
     movq %gs:THREAD_SELF_OFFSET, %rdx       // Pass arg 3 = Thread::Current().
                                             // Pass arg 1 = array.
     call SYMBOL(artThrowArrayStoreException) // (array, value, Thread*)
     UNREACHABLE
+
+#ifdef USE_READ_BARRIER
+    CFI_RESTORE_STATE_AND_DEF_CFA rsp, 4
+.Laput_obj_gc_marking:
+    // We need to align stack for `art_quick_read_barrier_mark_regNN`.
+    INCREASE_FRAME 8                        // Stack alignment.
+    call SYMBOL(art_quick_read_barrier_mark_reg01)  // Mark ECX
+    movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%rcx), %ecx
+    UNPOISON_HEAP_REF ecx
+    call SYMBOL(art_quick_read_barrier_mark_reg01)  // Mark ECX
+    movl MIRROR_OBJECT_CLASS_OFFSET(%rdx), %eax
+    UNPOISON_HEAP_REF eax
+    call SYMBOL(art_quick_read_barrier_mark_reg00)  // Mark EAX
+    DECREASE_FRAME 8                        // Remove stack alignment.
+    cmpl %eax, %ecx
+    je .Laput_obj_store
+    // Prepare arguments in line with `.Laput_obj_check_assignability_call` and jump there.
+    PUSH_ARG rdi                  // Save arguments.
+    PUSH_ARG rsi
+    PUSH_ARG rdx
+    movl %eax, %esi               // Pass arg2 - type of the value to be stored.
+    // The arg1 shall be moved at `.Ldo_assignability_check_call`.
+    jmp .Laput_obj_check_assignability_call
+#endif  // USE_READ_BARRIER
 END_FUNCTION art_quick_aput_obj
 
 // TODO: This is quite silly on X86_64 now.
@@ -1324,27 +1323,27 @@
 THREE_ARG_REF_DOWNCALL art_quick_set64_instance, artSet64InstanceFromCompiledCode, RETURN_IF_EAX_ZERO
 THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_EAX_ZERO
 
-TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
 
 TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_EAX_ZERO
 TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_EAX_ZERO
 TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCompiledCode, RETURN_IF_EAX_ZERO
-TWO_ARG_REF_DOWNCALL art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
 TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCompiledCode, RETURN_IF_EAX_ZERO
 
-ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
 
 DEFINE_FUNCTION art_quick_proxy_invoke_handler
     SETUP_SAVE_REFS_AND_ARGS_FRAME_WITH_METHOD_IN_RDI
@@ -1488,24 +1487,7 @@
     movq %rsp, %rbp                 // save SP at (old) callee-save frame
     CFI_DEF_CFA_REGISTER(rbp)
 
-    //
-    // reserve a lot of space
-    //
-    //      4    local state ref
-    //      4    padding
-    //   4196    4k scratch space, enough for 2x 256 8-byte parameters (TODO: handle scope overhead?)
-    //     16    handle scope member fields ?
-    // +  112    14x 8-byte stack-2-register space
-    // ------
-    //   4332
-    // 16-byte aligned: 4336
-    // Note: 14x8 = 7*16, so the stack stays aligned for the native call...
-    //       Also means: the padding is somewhere in the middle
-    //
-    //
-    // New test: use 5K and release
-    // 5k = 5120
-    subq LITERAL(5120), %rsp
+    subq LITERAL(GENERIC_JNI_TRAMPOLINE_RESERVED_AREA), %rsp
     // prepare for artQuickGenericJniTrampoline call
     // (Thread*, managed_sp, reserved_area)
     //    rdi       rsi           rdx   <= C calling convention
@@ -1572,44 +1554,43 @@
 
     // Tear down the alloca.
     movq %rbp, %rsp
-    CFI_REMEMBER_STATE
-    CFI_DEF_CFA_REGISTER(rsp)
+
+    // store into fpr, for when it's a fpr return...
+    movq %rax, %xmm0
+
+    LOAD_RUNTIME_INSTANCE rcx
+    cmpb MACRO_LITERAL(0), RUN_EXIT_HOOKS_OFFSET_FROM_RUNTIME_INSTANCE(%rcx)
+    jne .Lcall_method_exit_hook
+.Lcall_method_exit_hook_done:
 
     // Tear down the callee-save frame.
-    // Load FPRs.
-    // movq %xmm0, 16(%rsp)         // doesn't make sense!!!
-    movq 24(%rsp), %xmm1            // neither does this!!!
-    movq 32(%rsp), %xmm2
-    movq 40(%rsp), %xmm3
-    movq 48(%rsp), %xmm4
-    movq 56(%rsp), %xmm5
-    movq 64(%rsp), %xmm6
-    movq 72(%rsp), %xmm7
+    CFI_REMEMBER_STATE
+    CFI_DEF_CFA_REGISTER(rsp)
+    // Load callee-save FPRs. Skip FP args.
     movq 80(%rsp), %xmm12
     movq 88(%rsp), %xmm13
     movq 96(%rsp), %xmm14
     movq 104(%rsp), %xmm15
-    // was 80 bytes
-    addq LITERAL(80 + 4*8), %rsp
-    CFI_ADJUST_CFA_OFFSET(-80 - 4*8)
-    // Save callee and GPR args, mixed together to agree with core spills bitmap.
-    POP rcx  // Arg.
-    POP rdx  // Arg.
+    // Pop method, padding, FP args and two GRP args (rcx, rdx).
+    DECREASE_FRAME 16 + 12*8 + 2*8
+    // Load callee-save GPRs and skip args, mixed together to agree with core spills bitmap.
     POP rbx  // Callee save.
     POP rbp  // Callee save.
-    POP rsi  // Arg.
-    POP r8   // Arg.
-    POP r9   // Arg.
+    DECREASE_FRAME 3*8  // Skip three args (RSI, R8, R9).
     POP r12  // Callee save.
     POP r13  // Callee save.
     POP r14  // Callee save.
     POP r15  // Callee save.
-    // store into fpr, for when it's a fpr return...
-    movq %rax, %xmm0
     ret
 
     // Undo the unwinding information from above since it doesn't apply below.
     CFI_RESTORE_STATE_AND_DEF_CFA rbp, 208
+
+.Lcall_method_exit_hook:
+   movq LITERAL(FRAME_SIZE_SAVE_REFS_AND_ARGS), %r8
+   call art_quick_method_exit_hook
+   jmp .Lcall_method_exit_hook_done
+
 .Lexception_in_native:
     pushq %gs:THREAD_TOP_QUICK_FRAME_OFFSET
     addq LITERAL(-1), (%rsp)  // Remove the GenericJNI tag.
@@ -1645,82 +1626,6 @@
 ONE_ARG_RUNTIME_EXCEPTION art_invoke_obsolete_method_stub, artInvokeObsoleteMethod
 
     /*
-     * Routine that intercepts method calls and returns.
-     */
-DEFINE_FUNCTION art_quick_instrumentation_entry
-#if defined(__APPLE__)
-    int3
-    int3
-#else
-    SETUP_SAVE_REFS_AND_ARGS_FRAME
-
-    movq %rdi, %r12               // Preserve method pointer in a callee-save.
-
-    movq %gs:THREAD_SELF_OFFSET, %rdx   // Pass thread.
-    movq %rsp, %rcx                     // Pass SP.
-
-    call SYMBOL(artInstrumentationMethodEntryFromCode) // (Method*, Object*, Thread*, SP)
-
-                                  // %rax = result of call.
-    testq %rax, %rax
-    jz 1f
-
-    movq %r12, %rdi               // Reload method pointer.
-    leaq art_quick_instrumentation_exit(%rip), %r12   // Set up return through instrumentation
-    movq %r12, FRAME_SIZE_SAVE_REFS_AND_ARGS-8(%rsp)  // exit.
-
-    RESTORE_SAVE_REFS_AND_ARGS_FRAME
-
-    jmp *%rax                     // Tail call to intended method.
-1:
-    RESTORE_SAVE_REFS_AND_ARGS_FRAME
-    DELIVER_PENDING_EXCEPTION
-#endif  // __APPLE__
-END_FUNCTION art_quick_instrumentation_entry
-
-DEFINE_FUNCTION_CUSTOM_CFA art_quick_instrumentation_exit, 0
-    pushq LITERAL(0)          // Push a fake return PC as there will be none on the stack.
-    CFI_ADJUST_CFA_OFFSET(8)
-
-    SETUP_SAVE_EVERYTHING_FRAME
-
-    leaq 16(%rsp), %rcx       // Pass floating-point result pointer, in kSaveEverything frame.
-    leaq 144(%rsp), %rdx      // Pass integer result pointer, in kSaveEverything frame.
-    movq %rsp, %rsi           // Pass SP.
-    movq %gs:THREAD_SELF_OFFSET, %rdi  // Pass Thread.
-
-    call SYMBOL(artInstrumentationMethodExitFromCode)   // (Thread*, SP, gpr_res*, fpr_res*)
-
-    testq %rax, %rax          // Check if we have a return-pc to go to. If we don't then there was
-                              // an exception
-    jz .Ldo_deliver_instrumentation_exception
-    testq %rdx, %rdx
-    jnz .Ldeoptimize
-    // Normal return.
-    movq %rax, FRAME_SIZE_SAVE_EVERYTHING-8(%rsp)  // Set return pc.
-    RESTORE_SAVE_EVERYTHING_FRAME
-    ret
-.Ldeoptimize:
-    movq %rdx, FRAME_SIZE_SAVE_EVERYTHING-8(%rsp)  // Set return pc.
-    RESTORE_SAVE_EVERYTHING_FRAME
-    // Jump to art_quick_deoptimize.
-    jmp SYMBOL(art_quick_deoptimize)
-.Ldo_deliver_instrumentation_exception:
-    DELIVER_PENDING_EXCEPTION_FRAME_READY
-END_FUNCTION art_quick_instrumentation_exit
-
-    /*
-     * Instrumentation has requested that we deoptimize into the interpreter. The deoptimization
-     * will long jump to the upcall with a special exception of -1.
-     */
-DEFINE_FUNCTION art_quick_deoptimize
-    SETUP_SAVE_EVERYTHING_FRAME        // Stack should be aligned now.
-    movq %gs:THREAD_SELF_OFFSET, %rdi  // Pass Thread.
-    call SYMBOL(artDeoptimize)         // (Thread*)
-    UNREACHABLE
-END_FUNCTION art_quick_deoptimize
-
-    /*
      * Compiled code has requested that we deoptimize into the interpreter. The deoptimization
      * will long jump to the interpreter bridge.
      */
@@ -1846,7 +1751,7 @@
     movq %gs:THREAD_SELF_OFFSET, %rdx         // pass Thread::Current()
     call artStringBuilderAppend               // (uint32_t, const unit32_t*, Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME              // restore frame up to return address
-    RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER   // return or deliver exception
+    RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER  // return or deopt or deliver exception
 END_FUNCTION art_quick_string_builder_append
 
 // Create a function `name` calling the ReadBarrier::Mark routine,
@@ -1855,16 +1760,9 @@
 //
 // The generated function follows a non-standard runtime calling
 // convention:
-// - register `reg` (which may be different from RDI) is used to pass
-//   the (sole) argument of this function;
-// - register `reg` (which may be different from RAX) is used to return
-//   the result of this function (instead of RAX);
-// - if `reg` is different from `rdi`, RDI is treated like a normal
-//   (non-argument) caller-save register;
-// - if `reg` is different from `rax`, RAX is treated like a normal
-//   (non-result) caller-save register;
-// - everything else is the same as in the standard runtime calling
-//   convention (e.g. standard callee-save registers are preserved).
+// - register `reg` (which may be different from RDI) is used to pass the (sole) argument,
+// - register `reg` (which may be different from RAX) is used to return the result,
+// - all other registers are callee-save (the values they hold are preserved).
 MACRO2(READ_BARRIER_MARK_REG, name, reg)
     DEFINE_FUNCTION VAR(name)
     // Null check so that we can load the lock word.
@@ -2164,33 +2062,28 @@
 
     movq FRAME_SIZE_SAVE_EVERYTHING(%rsp), %rdi // pass ArtMethod
     movq %gs:THREAD_SELF_OFFSET, %rsi           // pass Thread::Current()
+    movq %rsp, %rdx                             // SP
 
-    call SYMBOL(artMethodEntryHook)              // (ArtMethod*, Thread*)
+    call SYMBOL(artMethodEntryHook)              // (ArtMethod*, Thread*, sp)
 
     RESTORE_SAVE_EVERYTHING_FRAME
     ret
 END_FUNCTION art_quick_method_entry_hook
 
 // On entry, method is at the bottom of the stack.
-// and r8 has should_deopt_frame value.
 DEFINE_FUNCTION art_quick_method_exit_hook
     SETUP_SAVE_EVERYTHING_FRAME
 
+    // R8 passed from JITed code contains frame_size
     leaq 16(%rsp), %rcx                         // floating-point result pointer in kSaveEverything
                                                 // frame
     leaq 144(%rsp), %rdx                        // integer result pointer in kSaveEverything frame
-    movq FRAME_SIZE_SAVE_EVERYTHING(%rsp), %rsi // ArtMethod
+    leaq FRAME_SIZE_SAVE_EVERYTHING(%rsp), %rsi // ArtMethod**
     movq %gs:THREAD_SELF_OFFSET, %rdi           // Thread::Current
-    call SYMBOL(artMethodExitHook)              // (Thread*, SP, gpr_res*, fpr_res*)
-
-    cmpq LITERAL(1), %rax
-    CFI_REMEMBER_STATE
-    je .Ldo_deliver_instrumentation_exception_exit
+    call SYMBOL(artMethodExitHook)              // (Thread*, ArtMethod**, gpr_res*, fpr_res*,
+                                                //  frame_size)
 
     // Normal return.
     RESTORE_SAVE_EVERYTHING_FRAME
     ret
-.Ldo_deliver_instrumentation_exception_exit:
-    CFI_RESTORE_STATE_AND_DEF_CFA rsp, FRAME_SIZE_SAVE_EVERYTHING
-    DELIVER_PENDING_EXCEPTION_FRAME_READY
-END_FUNCTION art_quick_method_entry_hook
+END_FUNCTION art_quick_method_exit_hook
diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h
index 5f23f1e..f6a99ac 100644
--- a/runtime/art_field-inl.h
+++ b/runtime/art_field-inl.h
@@ -40,15 +40,11 @@
   return GetDeclaringClass<kWithoutReadBarrier>()->IsProxyClass<kVerifyNone>();
 }
 
-// We are only ever allowed to set our own final fields. We do need to be careful since if a
-// structural redefinition occurs during <clinit> we can end up trying to set the non-obsolete
-// class's fields from the obsolete class. This is something we want to allow. This is tested by
-// run-test 2002-virtual-structural-initializing.
+// We are only ever allowed to set our own final fields
 inline bool ArtField::CanBeChangedBy(ArtMethod* method) {
   ObjPtr<mirror::Class> declaring_class(GetDeclaringClass());
   ObjPtr<mirror::Class> referring_class(method->GetDeclaringClass());
-  return !IsFinal() || (declaring_class == referring_class) ||
-         UNLIKELY(referring_class->IsObsoleteVersionOf(declaring_class));
+  return !IsFinal() || (declaring_class == referring_class);
 }
 
 template<ReadBarrierOption kReadBarrierOption>
@@ -64,6 +60,32 @@
   declaring_class_ = GcRoot<mirror::Class>(new_declaring_class);
 }
 
+template<typename RootVisitorType>
+void ArtField::VisitArrayRoots(RootVisitorType& visitor,
+                               uint8_t* start_boundary,
+                               uint8_t* end_boundary,
+                               LengthPrefixedArray<ArtField>* array) {
+  DCHECK_LE(start_boundary, end_boundary);
+  DCHECK_NE(array->size(), 0u);
+  ArtField* first_field = &array->At(0);
+  DCHECK_LE(static_cast<void*>(end_boundary), static_cast<void*>(first_field + array->size()));
+  static constexpr size_t kFieldSize = sizeof(ArtField);
+  // Confirm the assumption that ArtField size is power of two. It's important
+  // as we assume so below (RoundUp).
+  static_assert(IsPowerOfTwo(kFieldSize));
+  uint8_t* declaring_class =
+      reinterpret_cast<uint8_t*>(first_field) + DeclaringClassOffset().Int32Value();
+  // Jump to the first class to visit.
+  if (declaring_class < start_boundary) {
+    declaring_class += RoundUp(start_boundary - declaring_class, kFieldSize);
+  }
+  while (declaring_class < end_boundary) {
+    visitor.VisitRoot(
+        reinterpret_cast<mirror::CompressedReference<mirror::Object>*>(declaring_class));
+    declaring_class += kFieldSize;
+  }
+}
+
 inline MemberOffset ArtField::GetOffsetDuringLinking() {
   DCHECK(GetDeclaringClass()->IsLoaded() || GetDeclaringClass()->IsErroneous());
   return MemberOffset(offset_);
diff --git a/runtime/art_field.h b/runtime/art_field.h
index 4e77e7f..c205920 100644
--- a/runtime/art_field.h
+++ b/runtime/art_field.h
@@ -27,6 +27,7 @@
 namespace art {
 
 class DexFile;
+template<typename T> class LengthPrefixedArray;
 class ScopedObjectAccessAlreadyRunnable;
 
 namespace mirror {
@@ -39,6 +40,15 @@
 
 class ArtField final {
  public:
+  // Visit declaring classes of all the art-fields in 'array' that reside
+  // in [start_boundary, end_boundary).
+  template<typename RootVisitorType>
+  static void VisitArrayRoots(RootVisitorType& visitor,
+                              uint8_t* start_boundary,
+                              uint8_t* end_boundary,
+                              LengthPrefixedArray<ArtField>* array)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ObjPtr<mirror::Class> GetDeclaringClass() REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/art_method-alloc-inl.h b/runtime/art_method-alloc-inl.h
new file mode 100644
index 0000000..13051b1
--- /dev/null
+++ b/runtime/art_method-alloc-inl.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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_RUNTIME_ART_METHOD_ALLOC_INL_H_
+#define ART_RUNTIME_ART_METHOD_ALLOC_INL_H_
+
+#include "art_method-inl.h"
+
+#include "handle.h"
+#include "handle_scope.h"
+#include "mirror/class-alloc-inl.h"
+
+namespace art {
+
+namespace detail {
+
+template <char Shorty>
+struct HandleShortyTraits {
+  using Type = typename ShortyTraits<Shorty>::Type;
+  static Type Extract(Type value) ALWAYS_INLINE { return value; }
+};
+
+template <> struct HandleShortyTraits<'L'> {
+  using Type = Handle<mirror::Object>;
+  static typename ShortyTraits<'L'>::Type Extract(Type value)
+      REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE {
+    return value.Get();
+  }
+};
+
+}  // namespace detail
+
+template <char... ArgType, typename HandleScopeType>
+inline Handle<mirror::Object> ArtMethod::NewObject(
+    HandleScopeType& hs,
+    Thread* self,
+    typename detail::HandleShortyTraits<ArgType>::Type... args) {
+  DCHECK(!GetDeclaringClass()->IsInterface());
+  DCHECK(GetDeclaringClass()->IsInitialized());
+  DCHECK(IsConstructor());
+  DCHECK(!IsStatic());
+  MutableHandle<mirror::Object> new_object = hs.NewHandle(GetDeclaringClass()->AllocObject(self));
+  DCHECK_EQ(new_object == nullptr, self->IsExceptionPending());
+  if (LIKELY(new_object != nullptr)) {
+    InvokeInstance<'V', ArgType...>(
+        self, new_object.Get(), detail::HandleShortyTraits<ArgType>::Extract(args)...);
+    if (UNLIKELY(self->IsExceptionPending())) {
+      new_object.Assign(nullptr);
+    }
+  }
+  return new_object;
+}
+
+template <char... ArgType>
+inline ObjPtr<mirror::Object> ArtMethod::NewObject(
+    Thread* self, typename detail::HandleShortyTraits<ArgType>::Type... args) {
+  StackHandleScope<1u> hs(self);
+  return NewObject<ArgType...>(hs, self, args...).Get();
+}
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_ART_METHOD_ALLOC_INL_H_
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 844a0ff..3ea5130 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -48,6 +48,201 @@
 
 namespace art {
 
+namespace detail {
+
+template <> struct ShortyTraits<'V'> {
+  using Type = void;
+  static Type Get(const JValue& value ATTRIBUTE_UNUSED) {}
+  // `kVRegCount` and `Set()` are not defined.
+};
+
+template <> struct ShortyTraits<'Z'> {
+  // Despite using `uint8_t` for `boolean` in `JValue`, we shall use `bool` here.
+  using Type = bool;
+  static Type Get(const JValue& value) { return value.GetZ() != 0u; }
+  static constexpr size_t kVRegCount = 1u;
+  static void Set(uint32_t* args, Type value) { args[0] = static_cast<uint32_t>(value ? 1u : 0u); }
+};
+
+template <> struct ShortyTraits<'B'> {
+  using Type = int8_t;
+  static Type Get(const JValue& value) { return value.GetB(); }
+  static constexpr size_t kVRegCount = 1u;
+  static void Set(uint32_t* args, Type value) { args[0] = static_cast<uint32_t>(value); }
+};
+
+template <> struct ShortyTraits<'C'> {
+  using Type = uint16_t;
+  static Type Get(const JValue& value) { return value.GetC(); }
+  static constexpr size_t kVRegCount = 1u;
+  static void Set(uint32_t* args, Type value) { args[0] = static_cast<uint32_t>(value); }
+};
+
+template <> struct ShortyTraits<'S'> {
+  using Type = int16_t;
+  static Type Get(const JValue& value) { return value.GetS(); }
+  static constexpr size_t kVRegCount = 1u;
+  static void Set(uint32_t* args, Type value) { args[0] = static_cast<uint32_t>(value); }
+};
+
+template <> struct ShortyTraits<'I'> {
+  using Type = int32_t;
+  static Type Get(const JValue& value) { return value.GetI(); }
+  static constexpr size_t kVRegCount = 1u;
+  static void Set(uint32_t* args, Type value) { args[0] = static_cast<uint32_t>(value); }
+};
+
+template <> struct ShortyTraits<'J'> {
+  using Type = int64_t;
+  static Type Get(const JValue& value) { return value.GetJ(); }
+  static constexpr size_t kVRegCount = 2u;
+  static void Set(uint32_t* args, Type value) {
+    // Little-endian representation.
+    args[0] = static_cast<uint32_t>(value);
+    args[1] = static_cast<uint32_t>(static_cast<uint64_t>(value) >> 32);
+  }
+};
+
+template <> struct ShortyTraits<'F'> {
+  using Type = float;
+  static Type Get(const JValue& value) { return value.GetF(); }
+  static constexpr size_t kVRegCount = 1u;
+  static void Set(uint32_t* args, Type value) { args[0] = bit_cast<uint32_t>(value); }
+};
+
+template <> struct ShortyTraits<'D'> {
+  using Type = double;
+  static Type Get(const JValue& value) { return value.GetD(); }
+  static constexpr size_t kVRegCount = 2u;
+  static void Set(uint32_t* args, Type value) {
+    // Little-endian representation.
+    uint64_t v = bit_cast<uint64_t>(value);
+    args[0] = static_cast<uint32_t>(v);
+    args[1] = static_cast<uint32_t>(v >> 32);
+  }
+};
+
+template <> struct ShortyTraits<'L'> {
+  using Type = ObjPtr<mirror::Object>;
+  static Type Get(const JValue& value) REQUIRES_SHARED(Locks::mutator_lock_) {
+      return value.GetL();
+  }
+  static constexpr size_t kVRegCount = 1u;
+  static void Set(uint32_t* args, Type value) REQUIRES_SHARED(Locks::mutator_lock_) {
+    args[0] = StackReference<mirror::Object>::FromMirrorPtr(value.Ptr()).AsVRegValue();
+  }
+};
+
+template <char... Shorty>
+constexpr auto MaterializeShorty() {
+  constexpr size_t kSize = std::size({Shorty...}) + 1u;
+  return std::array<char, kSize>{Shorty..., '\0'};
+}
+
+template <char... ArgType>
+constexpr size_t NumberOfVRegs() {
+  constexpr size_t kArgVRegCount[] = {
+    ShortyTraits<ArgType>::kVRegCount...
+  };
+  size_t sum = 0u;
+  for (size_t count : kArgVRegCount) {
+    sum += count;
+  }
+  return sum;
+}
+
+template <char... ArgType>
+inline ALWAYS_INLINE void FillVRegs(uint32_t* vregs ATTRIBUTE_UNUSED,
+                                    typename ShortyTraits<ArgType>::Type... args ATTRIBUTE_UNUSED)
+    REQUIRES_SHARED(Locks::mutator_lock_) {}
+
+template <char FirstArgType, char... ArgType>
+inline ALWAYS_INLINE void FillVRegs(uint32_t* vregs,
+                                    typename ShortyTraits<FirstArgType>::Type first_arg,
+                                    typename ShortyTraits<ArgType>::Type... args)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ShortyTraits<FirstArgType>::Set(vregs, first_arg);
+  FillVRegs<ArgType...>(vregs + ShortyTraits<FirstArgType>::kVRegCount, args...);
+}
+
+template <char... ArgType>
+inline ALWAYS_INLINE auto MaterializeVRegs(typename ShortyTraits<ArgType>::Type... args)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  constexpr size_t kNumVRegs = NumberOfVRegs<ArgType...>();
+  std::array<uint32_t, kNumVRegs> vregs;
+  FillVRegs<ArgType...>(vregs.data(), args...);
+  return vregs;
+}
+
+}  // namespace detail
+
+template <char ReturnType, char... ArgType>
+inline typename detail::ShortyTraits<ReturnType>::Type
+ArtMethod::InvokeStatic(Thread* self, typename detail::ShortyTraits<ArgType>::Type... args) {
+  DCHECK(IsStatic());
+  DCHECK(GetDeclaringClass()->IsInitialized());  // Used only for initialized well-known classes.
+  JValue result;
+  constexpr auto shorty = detail::MaterializeShorty<ReturnType, ArgType...>();
+  auto vregs = detail::MaterializeVRegs<ArgType...>(args...);
+  Invoke(self, vregs.data(), sizeof(vregs), &result, shorty.data());
+  return detail::ShortyTraits<ReturnType>::Get(result);
+}
+
+template <char ReturnType, char... ArgType>
+typename detail::ShortyTraits<ReturnType>::Type
+ArtMethod::InvokeInstance(Thread* self,
+                          ObjPtr<mirror::Object> receiver,
+                          typename detail::ShortyTraits<ArgType>::Type... args) {
+  DCHECK(!GetDeclaringClass()->IsInterface());
+  DCHECK(!IsStatic());
+  JValue result;
+  constexpr auto shorty = detail::MaterializeShorty<ReturnType, ArgType...>();
+  auto vregs = detail::MaterializeVRegs<'L', ArgType...>(receiver, args...);
+  Invoke(self, vregs.data(), sizeof(vregs), &result, shorty.data());
+  return detail::ShortyTraits<ReturnType>::Get(result);
+}
+
+template <char ReturnType, char... ArgType>
+typename detail::ShortyTraits<ReturnType>::Type
+ArtMethod::InvokeFinal(Thread* self,
+                       ObjPtr<mirror::Object> receiver,
+                       typename detail::ShortyTraits<ArgType>::Type... args) {
+  DCHECK(!GetDeclaringClass()->IsInterface());
+  DCHECK(!IsStatic());
+  DCHECK(IsFinal() || GetDeclaringClass()->IsFinal());
+  DCHECK(receiver != nullptr);
+  return InvokeInstance<ReturnType, ArgType...>(self, receiver, args...);
+}
+
+template <char ReturnType, char... ArgType>
+typename detail::ShortyTraits<ReturnType>::Type
+ArtMethod::InvokeVirtual(Thread* self,
+                         ObjPtr<mirror::Object> receiver,
+                         typename detail::ShortyTraits<ArgType>::Type... args) {
+  DCHECK(!GetDeclaringClass()->IsInterface());
+  DCHECK(!IsStatic());
+  DCHECK(!IsFinal());
+  DCHECK(receiver != nullptr);
+  ArtMethod* target_method =
+      receiver->GetClass()->FindVirtualMethodForVirtual(this, kRuntimePointerSize);
+  DCHECK(target_method != nullptr);
+  return target_method->InvokeInstance<ReturnType, ArgType...>(self, receiver, args...);
+}
+
+template <char ReturnType, char... ArgType>
+typename detail::ShortyTraits<ReturnType>::Type
+ArtMethod::InvokeInterface(Thread* self,
+                           ObjPtr<mirror::Object> receiver,
+                           typename detail::ShortyTraits<ArgType>::Type... args) {
+  DCHECK(GetDeclaringClass()->IsInterface());
+  DCHECK(!IsStatic());
+  DCHECK(receiver != nullptr);
+  ArtMethod* target_method =
+      receiver->GetClass()->FindVirtualMethodForInterface(this, kRuntimePointerSize);
+  DCHECK(target_method != nullptr);
+  return target_method->InvokeInstance<ReturnType, ArgType...>(self, receiver, args...);
+}
+
 template <ReadBarrierOption kReadBarrierOption>
 inline ObjPtr<mirror::Class> ArtMethod::GetDeclaringClassUnchecked() {
   GcRootSource gc_root_source(this);
@@ -102,6 +297,15 @@
   return type;
 }
 
+inline bool ArtMethod::IsStringConstructor() {
+  uint32_t access_flags = GetAccessFlags();
+  DCHECK(!IsClassInitializer(access_flags));
+  return IsConstructor(access_flags) &&
+         // No read barrier needed for reading a constant reference only to read
+         // a constant string class flag. See `ReadBarrierOption`.
+         GetDeclaringClass<kWithoutReadBarrier>()->IsStringClass();
+}
+
 inline bool ArtMethod::IsOverridableByDefaultMethod() {
   // It is safe to avoid the read barrier here since the constant interface flag
   // in the `Class` object is stored before creating the `ArtMethod` and storing
@@ -332,7 +536,7 @@
     return klass->GetDexCache<kDefaultVerifyFlags, kReadBarrierOption>();
   } else {
     DCHECK(!IsProxyMethod());
-    return GetObsoleteDexCache();
+    return GetObsoleteDexCache<kReadBarrierOption>();
   }
 }
 
@@ -378,9 +582,10 @@
   return ResolveClassFromTypeIndex(GetReturnTypeIndex());
 }
 
-template <ReadBarrierOption kReadBarrierOption>
 inline bool ArtMethod::HasSingleImplementation() {
-  if (IsFinal() || GetDeclaringClass<kReadBarrierOption>()->IsFinal()) {
+  // No read barrier needed for reading a constant reference only to read
+  // a constant final class flag. See `ReadBarrierOption`.
+  if (IsFinal() || GetDeclaringClass<kWithoutReadBarrier>()->IsFinal()) {
     // We don't set kAccSingleImplementation for these cases since intrinsic
     // can use the flag also.
     return true;
@@ -388,21 +593,66 @@
   return (GetAccessFlags() & kAccSingleImplementation) != 0;
 }
 
-template<ReadBarrierOption kReadBarrierOption, typename RootVisitorType>
+template<ReadBarrierOption kReadBarrierOption, bool kVisitProxyMethod, typename RootVisitorType>
 void ArtMethod::VisitRoots(RootVisitorType& visitor, PointerSize pointer_size) {
   if (LIKELY(!declaring_class_.IsNull())) {
     visitor.VisitRoot(declaring_class_.AddressWithoutBarrier());
-    ObjPtr<mirror::Class> klass = declaring_class_.Read<kReadBarrierOption>();
-    if (UNLIKELY(klass->IsProxyClass())) {
-      // For normal methods, dex cache shortcuts will be visited through the declaring class.
-      // However, for proxies we need to keep the interface method alive, so we visit its roots.
-      ArtMethod* interface_method = GetInterfaceMethodForProxyUnchecked(pointer_size);
-      DCHECK(interface_method != nullptr);
-      interface_method->VisitRoots<kReadBarrierOption>(visitor, pointer_size);
+    if (kVisitProxyMethod) {
+      ObjPtr<mirror::Class> klass = declaring_class_.Read<kReadBarrierOption>();
+      if (UNLIKELY(klass->IsProxyClass())) {
+        // For normal methods, dex cache shortcuts will be visited through the declaring class.
+        // However, for proxies we need to keep the interface method alive, so we visit its roots.
+        ArtMethod* interface_method = GetInterfaceMethodForProxyUnchecked(pointer_size);
+        DCHECK(interface_method != nullptr);
+        interface_method->VisitRoots<kReadBarrierOption, kVisitProxyMethod>(visitor, pointer_size);
+      }
     }
   }
 }
 
+template<typename RootVisitorType>
+void ArtMethod::VisitRoots(RootVisitorType& visitor,
+                           uint8_t* start_boundary,
+                           uint8_t* end_boundary,
+                           ArtMethod* method) {
+  mirror::CompressedReference<mirror::Object>* cls_ptr =
+      reinterpret_cast<mirror::CompressedReference<mirror::Object>*>(
+          reinterpret_cast<uint8_t*>(method) + DeclaringClassOffset().Int32Value());
+  if (reinterpret_cast<uint8_t*>(cls_ptr) >= start_boundary
+      && reinterpret_cast<uint8_t*>(cls_ptr) < end_boundary) {
+    visitor.VisitRootIfNonNull(cls_ptr);
+  }
+}
+
+template<PointerSize kPointerSize, typename RootVisitorType>
+void ArtMethod::VisitArrayRoots(RootVisitorType& visitor,
+                                uint8_t* start_boundary,
+                                uint8_t* end_boundary,
+                                LengthPrefixedArray<ArtMethod>* array) {
+  DCHECK_LE(start_boundary, end_boundary);
+  DCHECK_NE(array->size(), 0u);
+  static constexpr size_t kMethodSize = ArtMethod::Size(kPointerSize);
+  ArtMethod* first_method = &array->At(0, kMethodSize, ArtMethod::Alignment(kPointerSize));
+  DCHECK_LE(static_cast<void*>(end_boundary),
+            static_cast<void*>(reinterpret_cast<uint8_t*>(first_method)
+                               + array->size() * kMethodSize));
+  uint8_t* declaring_class =
+      reinterpret_cast<uint8_t*>(first_method) + DeclaringClassOffset().Int32Value();
+  // Jump to the first class to visit.
+  if (declaring_class < start_boundary) {
+    size_t remainder = (start_boundary - declaring_class) % kMethodSize;
+    declaring_class = start_boundary;
+    if (remainder > 0) {
+      declaring_class += kMethodSize - remainder;
+    }
+  }
+  while (declaring_class < end_boundary) {
+    visitor.VisitRootIfNonNull(
+        reinterpret_cast<mirror::CompressedReference<mirror::Object>*>(declaring_class));
+    declaring_class += kMethodSize;
+  }
+}
+
 template <typename Visitor>
 inline void ArtMethod::UpdateEntrypoints(const Visitor& visitor, PointerSize pointer_size) {
   if (IsNative()) {
@@ -419,6 +669,43 @@
   }
 }
 
+template <ReadBarrierOption kReadBarrierOption>
+inline bool ArtMethod::StillNeedsClinitCheck() {
+  if (!NeedsClinitCheckBeforeCall()) {
+    return false;
+  }
+  ObjPtr<mirror::Class> klass = GetDeclaringClass<kReadBarrierOption>();
+  return !klass->IsVisiblyInitialized();
+}
+
+inline bool ArtMethod::StillNeedsClinitCheckMayBeDead() {
+  if (!NeedsClinitCheckBeforeCall()) {
+    return false;
+  }
+  ObjPtr<mirror::Class> klass = GetDeclaringClassMayBeDead();
+  return !klass->IsVisiblyInitialized();
+}
+
+inline bool ArtMethod::IsDeclaringClassVerifiedMayBeDead() {
+  ObjPtr<mirror::Class> klass = GetDeclaringClassMayBeDead();
+  return klass->IsVerified();
+}
+
+inline ObjPtr<mirror::Class> ArtMethod::GetDeclaringClassMayBeDead() {
+  // Helper method for checking the status of the declaring class which may be dead.
+  //
+  // To avoid resurrecting an unreachable object, or crashing the GC in some GC phases,
+  // we must not use a full read barrier. Therefore we read the declaring class without
+  // a read barrier and check if it's already marked. If yes, we check the status of the
+  // to-space class object as intended. Otherwise, there is no to-space object and the
+  // from-space class object contains the most recent value of the status field; even if
+  // this races with another thread doing a read barrier and updating the status, that's
+  // no different from a race with a thread that just updates the status.
+  ObjPtr<mirror::Class> klass = GetDeclaringClass<kWithoutReadBarrier>();
+  ObjPtr<mirror::Class> marked = ReadBarrier::IsMarked(klass.Ptr());
+  return (marked != nullptr) ? marked : klass;
+}
+
 inline CodeItemInstructionAccessor ArtMethod::DexInstructions() {
   return CodeItemInstructionAccessor(*GetDexFile(), GetCodeItem());
 }
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index f6f8b5f..b500d9b 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -111,35 +111,63 @@
   return executable->GetArtMethod();
 }
 
+template <ReadBarrierOption kReadBarrierOption>
 ObjPtr<mirror::DexCache> ArtMethod::GetObsoleteDexCache() {
+  // Note: The class redefinition happens with GC disabled, so at the point where we
+  // create obsolete methods, the `ClassExt` and its obsolete methods and dex caches
+  // members are reachable without a read barrier. If we start a GC later, and we
+  // look at these objects without read barriers (`kWithoutReadBarrier`), the method
+  // pointers shall be the same in from-space array as in to-space array (if these
+  // arrays are different) and the dex cache array entry can point to from-space or
+  // to-space `DexCache` but either is a valid result for `kWithoutReadBarrier`.
+  ScopedAssertNoThreadSuspension ants(__FUNCTION__);
+  std::optional<ScopedDebugDisallowReadBarriers> sddrb(std::nullopt);
+  if (kIsDebugBuild && kReadBarrierOption == kWithoutReadBarrier) {
+    sddrb.emplace(Thread::Current());
+  }
   PointerSize pointer_size = kRuntimePointerSize;
   DCHECK(!Runtime::Current()->IsAotCompiler()) << PrettyMethod();
   DCHECK(IsObsolete());
-  ObjPtr<mirror::ClassExt> ext(GetDeclaringClass()->GetExtData());
-  ObjPtr<mirror::PointerArray> obsolete_methods(ext.IsNull() ? nullptr : ext->GetObsoleteMethods());
-  int32_t len = (obsolete_methods.IsNull() ? 0 : obsolete_methods->GetLength());
-  DCHECK(len == 0 || len == ext->GetObsoleteDexCaches()->GetLength())
-      << "len=" << len << " ext->GetObsoleteDexCaches()=" << ext->GetObsoleteDexCaches();
+  ObjPtr<mirror::Class> declaring_class = GetDeclaringClass<kReadBarrierOption>();
+  ObjPtr<mirror::ClassExt> ext =
+      declaring_class->GetExtData<kDefaultVerifyFlags, kReadBarrierOption>();
+  ObjPtr<mirror::PointerArray> obsolete_methods(
+      ext.IsNull() ? nullptr : ext->GetObsoleteMethods<kDefaultVerifyFlags, kReadBarrierOption>());
+  int32_t len = 0;
+  ObjPtr<mirror::ObjectArray<mirror::DexCache>> obsolete_dex_caches = nullptr;
+  if (!obsolete_methods.IsNull()) {
+    len = obsolete_methods->GetLength();
+    obsolete_dex_caches = ext->GetObsoleteDexCaches<kDefaultVerifyFlags, kReadBarrierOption>();
+    // FIXME: `ClassExt::SetObsoleteArrays()` is not atomic, so one of the arrays we see here
+    // could be extended for a new class redefinition while the other may be shorter.
+    // Furthermore, there is no synchronization to ensure that copied contents of an old
+    // obsolete array are visible to a thread reading the new array.
+    DCHECK_EQ(len, obsolete_dex_caches->GetLength())
+        << " ext->GetObsoleteDexCaches()=" << obsolete_dex_caches;
+  }
   // Using kRuntimePointerSize (instead of using the image's pointer size) is fine since images
   // should never have obsolete methods in them so they should always be the same.
   DCHECK_EQ(pointer_size, Runtime::Current()->GetClassLinker()->GetImagePointerSize());
   for (int32_t i = 0; i < len; i++) {
     if (this == obsolete_methods->GetElementPtrSize<ArtMethod*>(i, pointer_size)) {
-      return ext->GetObsoleteDexCaches()->Get(i);
+      return obsolete_dex_caches->GetWithoutChecks<kDefaultVerifyFlags, kReadBarrierOption>(i);
     }
   }
-  CHECK(GetDeclaringClass()->IsObsoleteObject())
+  CHECK(declaring_class->IsObsoleteObject())
       << "This non-structurally obsolete method does not appear in the obsolete map of its class: "
-      << GetDeclaringClass()->PrettyClass() << " Searched " << len << " caches.";
+      << declaring_class->PrettyClass() << " Searched " << len << " caches.";
   CHECK_EQ(this,
            std::clamp(this,
-                      &(*GetDeclaringClass()->GetMethods(pointer_size).begin()),
-                      &(*GetDeclaringClass()->GetMethods(pointer_size).end())))
+                      &(*declaring_class->GetMethods(pointer_size).begin()),
+                      &(*declaring_class->GetMethods(pointer_size).end())))
       << "class is marked as structurally obsolete method but not found in normal obsolete-map "
       << "despite not being the original method pointer for " << GetDeclaringClass()->PrettyClass();
-  return GetDeclaringClass()->GetDexCache();
+  return declaring_class->template GetDexCache<kDefaultVerifyFlags, kReadBarrierOption>();
 }
 
+template ObjPtr<mirror::DexCache> ArtMethod::GetObsoleteDexCache<kWithReadBarrier>();
+template ObjPtr<mirror::DexCache> ArtMethod::GetObsoleteDexCache<kWithoutReadBarrier>();
+
 uint16_t ArtMethod::FindObsoleteDexClassDefIndex() {
   DCHECK(!Runtime::Current()->IsAotCompiler()) << PrettyMethod();
   DCHECK(IsObsolete());
@@ -150,10 +178,34 @@
   return dex_file->GetIndexForClassDef(*class_def);
 }
 
-void ArtMethod::ThrowInvocationTimeError() {
+void ArtMethod::ThrowInvocationTimeError(ObjPtr<mirror::Object> receiver) {
   DCHECK(!IsInvokable());
   if (IsDefaultConflicting()) {
     ThrowIncompatibleClassChangeErrorForMethodConflict(this);
+  } else if (GetDeclaringClass()->IsInterface() && receiver != nullptr) {
+    // If this was an interface call, check whether there is a method in the
+    // superclass chain that isn't public. In this situation, we should throw an
+    // IllegalAccessError.
+    DCHECK(IsAbstract());
+    ObjPtr<mirror::Class> current = receiver->GetClass();
+    while (current != nullptr) {
+      for (ArtMethod& method : current->GetDeclaredMethodsSlice(kRuntimePointerSize)) {
+        ArtMethod* np_method = method.GetInterfaceMethodIfProxy(kRuntimePointerSize);
+        if (!np_method->IsStatic() &&
+            np_method->GetNameView() == GetNameView() &&
+            np_method->GetSignature() == GetSignature()) {
+          if (!np_method->IsPublic()) {
+            ThrowIllegalAccessErrorForImplementingMethod(receiver->GetClass(), np_method, this);
+            return;
+          } else if (np_method->IsAbstract()) {
+            ThrowAbstractMethodError(this);
+            return;
+          }
+        }
+      }
+      current = current->GetSuperClass();
+    }
+    ThrowAbstractMethodError(this);
   } else {
     DCHECK(IsAbstract());
     ThrowAbstractMethodError(this);
@@ -310,6 +362,7 @@
   return found_dex_pc;
 }
 
+NO_STACK_PROTECTOR
 void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
                        const char* shorty) {
   if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEnd())) {
@@ -532,10 +585,6 @@
 }
 
 const OatQuickMethodHeader* ArtMethod::GetOatQuickMethodHeader(uintptr_t pc) {
-  // Our callers should make sure they don't pass the instrumentation exit pc,
-  // as this method does not look at the side instrumentation stack.
-  DCHECK_NE(pc, reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()));
-
   if (IsRuntimeMethod()) {
     return nullptr;
   }
@@ -551,11 +600,16 @@
     return nullptr;
   }
 
+  // We should not reach here with a pc of 0. pc can be 0 for downcalls when walking the stack.
+  // For native methods this case is handled by the caller by checking the quick frame tag. See
+  // StackVisitor::WalkStack for more details. For non-native methods pc can be 0 only for runtime
+  // methods or proxy invoke handlers which are handled earlier.
+  DCHECK_NE(pc, 0u) << "PC 0 for " << PrettyMethod();
+
   // Check whether the current entry point contains this pc.
   if (!class_linker->IsQuickGenericJniStub(existing_entry_point) &&
       !class_linker->IsQuickResolutionStub(existing_entry_point) &&
       !class_linker->IsQuickToInterpreterBridge(existing_entry_point) &&
-      existing_entry_point != GetQuickInstrumentationEntryPoint() &&
       existing_entry_point != GetInvokeObsoleteMethodStub()) {
     OatQuickMethodHeader* method_header =
         OatQuickMethodHeader::FromEntryPoint(existing_entry_point);
@@ -592,21 +646,16 @@
   OatFile::OatMethod oat_method =
       FindOatMethodFor(this, class_linker->GetImagePointerSize(), &found);
   if (!found) {
-    if (IsNative()) {
-      // We are running the GenericJNI stub. The entrypoint may point
-      // to different entrypoints or to a JIT-compiled JNI stub.
-      DCHECK(class_linker->IsQuickGenericJniStub(existing_entry_point) ||
-             class_linker->IsQuickResolutionStub(existing_entry_point) ||
-             existing_entry_point == GetQuickInstrumentationEntryPoint() ||
-             (jit != nullptr && jit->GetCodeCache()->ContainsPc(existing_entry_point)))
-          << " entrypoint: " << existing_entry_point
-          << " size: " << OatQuickMethodHeader::FromEntryPoint(existing_entry_point)->GetCodeSize()
-          << " pc: " << reinterpret_cast<const void*>(pc);
-      return nullptr;
-    }
-    // Only for unit tests.
-    // TODO(ngeoffray): Update these tests to pass the right pc?
-    return OatQuickMethodHeader::FromEntryPoint(existing_entry_point);
+    CHECK(IsNative());
+    // We are running the GenericJNI stub. The entrypoint may point
+    // to different entrypoints or to a JIT-compiled JNI stub.
+    DCHECK(class_linker->IsQuickGenericJniStub(existing_entry_point) ||
+           class_linker->IsQuickResolutionStub(existing_entry_point) ||
+           (jit != nullptr && jit->GetCodeCache()->ContainsPc(existing_entry_point)))
+        << " entrypoint: " << existing_entry_point
+        << " size: " << OatQuickMethodHeader::FromEntryPoint(existing_entry_point)->GetCodeSize()
+        << " pc: " << reinterpret_cast<const void*>(pc);
+    return nullptr;
   }
   const void* oat_entry_point = oat_method.GetQuickCode();
   if (oat_entry_point == nullptr || class_linker->IsQuickGenericJniStub(oat_entry_point)) {
@@ -615,10 +664,13 @@
   }
 
   OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromEntryPoint(oat_entry_point);
-  if (pc == 0) {
-    // This is a downcall, it can only happen for a native method.
-    DCHECK(IsNative());
-    return method_header;
+  // We could have existing Oat code for native methods but we may not use it if the runtime is java
+  // debuggable or when profiling boot class path. There is no easy way to check if the pc
+  // corresponds to QuickGenericJniStub. Since we have eliminated all the other cases, if the pc
+  // doesn't correspond to the AOT code then we must be running QuickGenericJniStub.
+  if (IsNative() && !method_header->Contains(pc)) {
+    DCHECK_NE(pc, 0u) << "PC 0 for " << PrettyMethod();
+    return nullptr;
   }
 
   DCHECK(method_header->Contains(pc))
@@ -728,16 +780,16 @@
   // the entry point to the JIT code, but this would require taking the JIT code cache
   // lock to notify it, which we do not want at this level.
   Runtime* runtime = Runtime::Current();
+  const void* entry_point = GetEntryPointFromQuickCompiledCodePtrSize(image_pointer_size);
   if (runtime->UseJitCompilation()) {
-    if (runtime->GetJit()->GetCodeCache()->ContainsPc(GetEntryPointFromQuickCompiledCode())) {
+    if (runtime->GetJit()->GetCodeCache()->ContainsPc(entry_point)) {
       SetEntryPointFromQuickCompiledCodePtrSize(
           src->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge(),
           image_pointer_size);
     }
   }
-  if (interpreter::IsNterpSupported() &&
-      (GetEntryPointFromQuickCompiledCodePtrSize(image_pointer_size) ==
-          interpreter::GetNterpEntryPoint())) {
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  if (interpreter::IsNterpSupported() && class_linker->IsNterpEntryPoint(entry_point)) {
     // If the entrypoint is nterp, it's too early to check if the new method
     // will support it. So for simplicity, use the interpreter bridge.
     SetEntryPointFromQuickCompiledCodePtrSize(GetQuickToInterpreterBridge(), image_pointer_size);
diff --git a/runtime/art_method.h b/runtime/art_method.h
index c2de718..dce4066 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -49,6 +49,7 @@
 class ImtConflictTable;
 enum InvokeType : uint32_t;
 union JValue;
+template<typename T> class LengthPrefixedArray;
 class OatQuickMethodHeader;
 class ProfilingInfo;
 class ScopedObjectAccessAlreadyRunnable;
@@ -67,6 +68,22 @@
 class String;
 }  // namespace mirror
 
+namespace detail {
+template <char Shorty> struct ShortyTraits;
+template <> struct ShortyTraits<'V'>;
+template <> struct ShortyTraits<'Z'>;
+template <> struct ShortyTraits<'B'>;
+template <> struct ShortyTraits<'C'>;
+template <> struct ShortyTraits<'S'>;
+template <> struct ShortyTraits<'I'>;
+template <> struct ShortyTraits<'J'>;
+template <> struct ShortyTraits<'F'>;
+template <> struct ShortyTraits<'D'>;
+template <> struct ShortyTraits<'L'>;
+template <char Shorty> struct HandleShortyTraits;
+template <> struct HandleShortyTraits<'L'>;
+}  // namespace detail
+
 class ArtMethod final {
  public:
   // Should the class state be checked on sensitive operations?
@@ -87,6 +104,23 @@
                                         jobject jlr_method)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Visit the declaring class in 'method' if it is within [start_boundary, end_boundary).
+  template<typename RootVisitorType>
+  static void VisitRoots(RootVisitorType& visitor,
+                         uint8_t* start_boundary,
+                         uint8_t* end_boundary,
+                         ArtMethod* method)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Visit declaring classes of all the art-methods in 'array' that reside
+  // in [start_boundary, end_boundary).
+  template<PointerSize kPointerSize, typename RootVisitorType>
+  static void VisitArrayRoots(RootVisitorType& visitor,
+                              uint8_t* start_boundary,
+                              uint8_t* end_boundary,
+                              LengthPrefixedArray<ArtMethod>* array)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ALWAYS_INLINE ObjPtr<mirror::Class> GetDeclaringClass() REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -131,27 +165,47 @@
 
   // Returns true if the method is declared public.
   bool IsPublic() const {
-    return (GetAccessFlags() & kAccPublic) != 0;
+    return IsPublic(GetAccessFlags());
+  }
+
+  static bool IsPublic(uint32_t access_flags) {
+    return (access_flags & kAccPublic) != 0;
   }
 
   // Returns true if the method is declared private.
   bool IsPrivate() const {
-    return (GetAccessFlags() & kAccPrivate) != 0;
+    return IsPrivate(GetAccessFlags());
+  }
+
+  static bool IsPrivate(uint32_t access_flags) {
+    return (access_flags & kAccPrivate) != 0;
   }
 
   // Returns true if the method is declared static.
   bool IsStatic() const {
-    return (GetAccessFlags() & kAccStatic) != 0;
+    return IsStatic(GetAccessFlags());
+  }
+
+  static bool IsStatic(uint32_t access_flags) {
+    return (access_flags & kAccStatic) != 0;
   }
 
   // Returns true if the method is a constructor according to access flags.
   bool IsConstructor() const {
-    return (GetAccessFlags() & kAccConstructor) != 0;
+    return IsConstructor(GetAccessFlags());
+  }
+
+  static bool IsConstructor(uint32_t access_flags) {
+    return (access_flags & kAccConstructor) != 0;
   }
 
   // Returns true if the method is a class initializer according to access flags.
   bool IsClassInitializer() const {
-    return IsConstructor() && IsStatic();
+    return IsClassInitializer(GetAccessFlags());
+  }
+
+  static bool IsClassInitializer(uint32_t access_flags) {
+    return IsConstructor(access_flags) && IsStatic(access_flags);
   }
 
   // Returns true if the method is static, private, or a constructor.
@@ -166,16 +220,30 @@
 
   // Returns true if the method is declared synchronized.
   bool IsSynchronized() const {
+    return IsSynchronized(GetAccessFlags());
+  }
+
+  static bool IsSynchronized(uint32_t access_flags) {
     constexpr uint32_t synchonized = kAccSynchronized | kAccDeclaredSynchronized;
-    return (GetAccessFlags() & synchonized) != 0;
+    return (access_flags & synchonized) != 0;
   }
 
+  // Returns true if the method is declared final.
   bool IsFinal() const {
-    return (GetAccessFlags() & kAccFinal) != 0;
+    return IsFinal(GetAccessFlags());
   }
 
+  static bool IsFinal(uint32_t access_flags) {
+    return (access_flags & kAccFinal) != 0;
+  }
+
+  // Returns true if the method is an intrinsic.
   bool IsIntrinsic() const {
-    return (GetAccessFlags() & kAccIntrinsic) != 0;
+    return IsIntrinsic(GetAccessFlags());
+  }
+
+  static bool IsIntrinsic(uint32_t access_flags) {
+    return (access_flags & kAccIntrinsic) != 0;
   }
 
   ALWAYS_INLINE void SetIntrinsic(uint32_t intrinsic) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -192,53 +260,76 @@
 
   void SetNotIntrinsic() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Returns true if the method is a copied method.
   bool IsCopied() const {
+    return IsCopied(GetAccessFlags());
+  }
+
+  static bool IsCopied(uint32_t access_flags) {
     // We do not have intrinsics for any default methods and therefore intrinsics are never copied.
     // So we are using a flag from the intrinsic flags range and need to check `kAccIntrinsic` too.
     static_assert((kAccCopied & kAccIntrinsicBits) != 0,
                   "kAccCopied deliberately overlaps intrinsic bits");
-    const bool copied = (GetAccessFlags() & (kAccIntrinsic | kAccCopied)) == kAccCopied;
+    const bool copied = (access_flags & (kAccIntrinsic | kAccCopied)) == kAccCopied;
     // (IsMiranda() || IsDefaultConflicting()) implies copied
-    DCHECK(!(IsMiranda() || IsDefaultConflicting()) || copied)
+    DCHECK(!(IsMiranda(access_flags) || IsDefaultConflicting(access_flags)) || copied)
         << "Miranda or default-conflict methods must always be copied.";
     return copied;
   }
 
   bool IsMiranda() const {
+    return IsMiranda(GetAccessFlags());
+  }
+
+  static bool IsMiranda(uint32_t access_flags) {
     // Miranda methods are marked as copied and abstract but not default.
     // We need to check the kAccIntrinsic too, see `IsCopied()`.
     static constexpr uint32_t kMask = kAccIntrinsic | kAccCopied | kAccAbstract | kAccDefault;
     static constexpr uint32_t kValue = kAccCopied | kAccAbstract;
-    return (GetAccessFlags() & kMask) == kValue;
+    return (access_flags & kMask) == kValue;
   }
 
   // A default conflict method is a special sentinel method that stands for a conflict between
   // multiple default methods. It cannot be invoked, throwing an IncompatibleClassChangeError
   // if one attempts to do so.
   bool IsDefaultConflicting() const {
+    return IsDefaultConflicting(GetAccessFlags());
+  }
+
+  static bool IsDefaultConflicting(uint32_t access_flags) {
     // Default conflct methods are marked as copied, abstract and default.
     // We need to check the kAccIntrinsic too, see `IsCopied()`.
     static constexpr uint32_t kMask = kAccIntrinsic | kAccCopied | kAccAbstract | kAccDefault;
     static constexpr uint32_t kValue = kAccCopied | kAccAbstract | kAccDefault;
-    return (GetAccessFlags() & kMask) == kValue;
+    return (access_flags & kMask) == kValue;
   }
 
   // Returns true if invoking this method will not throw an AbstractMethodError or
   // IncompatibleClassChangeError.
   bool IsInvokable() const {
-    // Default conflicting methods are marked with `kAccAbstract` (as well as `kAccCopied`
-    // and `kAccDefault`) but they are not considered abstract, see `IsAbstract()`.
-    DCHECK_EQ((GetAccessFlags() & kAccAbstract) == 0, !IsDefaultConflicting() && !IsAbstract());
-    return (GetAccessFlags() & kAccAbstract) == 0;
+    return IsInvokable(GetAccessFlags());
   }
 
+  static bool IsInvokable(uint32_t access_flags) {
+    // Default conflicting methods are marked with `kAccAbstract` (as well as `kAccCopied`
+    // and `kAccDefault`) but they are not considered abstract, see `IsAbstract()`.
+    DCHECK_EQ((access_flags & kAccAbstract) == 0,
+              !IsDefaultConflicting(access_flags) && !IsAbstract(access_flags));
+    return (access_flags & kAccAbstract) == 0;
+  }
+
+  // Returns true if the method is marked as pre-compiled.
   bool IsPreCompiled() const {
+    return IsPreCompiled(GetAccessFlags());
+  }
+
+  static bool IsPreCompiled(uint32_t access_flags) {
     // kAccCompileDontBother and kAccPreCompiled overlap with kAccIntrinsicBits.
     static_assert((kAccCompileDontBother & kAccIntrinsicBits) != 0);
     static_assert((kAccPreCompiled & kAccIntrinsicBits) != 0);
     static constexpr uint32_t kMask = kAccIntrinsic | kAccCompileDontBother | kAccPreCompiled;
     static constexpr uint32_t kValue = kAccCompileDontBother | kAccPreCompiled;
-    return (GetAccessFlags() & kMask) == kValue;
+    return (access_flags & kMask) == kValue;
   }
 
   void SetPreCompiled() REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -254,41 +345,51 @@
     AddAccessFlags(kAccPreCompiled | kAccCompileDontBother);
   }
 
+  void ClearPreCompiled() REQUIRES_SHARED(Locks::mutator_lock_) {
+    ClearAccessFlags(kAccPreCompiled | kAccCompileDontBother);
+  }
+
+  // Returns true if the method resides in shared memory.
   bool IsMemorySharedMethod() {
-    return (GetAccessFlags() & kAccMemorySharedMethod) != 0;
+    return IsMemorySharedMethod(GetAccessFlags());
+  }
+
+  static bool IsMemorySharedMethod(uint32_t access_flags) {
+    return (access_flags & kAccMemorySharedMethod) != 0;
   }
 
   void SetMemorySharedMethod() REQUIRES_SHARED(Locks::mutator_lock_) {
-    // Disable until we make sure critical code is AOTed.
-    static constexpr bool kEnabledMemorySharedMethod = false;
-    if (kEnabledMemorySharedMethod && !IsIntrinsic() && !IsAbstract()) {
+    uint32_t access_flags = GetAccessFlags();
+    if (!IsIntrinsic(access_flags) && !IsAbstract(access_flags)) {
       AddAccessFlags(kAccMemorySharedMethod);
       SetHotCounter();
     }
   }
 
   void ClearMemorySharedMethod() REQUIRES_SHARED(Locks::mutator_lock_) {
-    if (IsIntrinsic() || IsAbstract()) {
+    uint32_t access_flags = GetAccessFlags();
+    if (IsIntrinsic(access_flags) || IsAbstract(access_flags)) {
       return;
     }
-    if (IsMemorySharedMethod()) {
+    if (IsMemorySharedMethod(access_flags)) {
       ClearAccessFlags(kAccMemorySharedMethod);
     }
   }
 
-  void ClearPreCompiled() REQUIRES_SHARED(Locks::mutator_lock_) {
-    ClearAccessFlags(kAccPreCompiled | kAccCompileDontBother);
+  // Returns true if the method can be compiled.
+  bool IsCompilable() const {
+    return IsCompilable(GetAccessFlags());
   }
 
-  bool IsCompilable() const {
-    if (IsIntrinsic()) {
+  static bool IsCompilable(uint32_t access_flags) {
+    if (IsIntrinsic(access_flags)) {
       // kAccCompileDontBother overlaps with kAccIntrinsicBits.
       return true;
     }
-    if (IsPreCompiled()) {
+    if (IsPreCompiled(access_flags)) {
       return true;
     }
-    return (GetAccessFlags() & kAccCompileDontBother) == 0;
+    return (access_flags & kAccCompileDontBother) == 0;
   }
 
   void ClearDontCompile() REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -303,52 +404,107 @@
 
   // This is set by the class linker.
   bool IsDefault() const {
-    static_assert((kAccDefault & (kAccIntrinsic | kAccIntrinsicBits)) == 0,
-                  "kAccDefault conflicts with intrinsic modifier");
-    return (GetAccessFlags() & kAccDefault) != 0;
+    return IsDefault(GetAccessFlags());
   }
 
+  static bool IsDefault(uint32_t access_flags) {
+    static_assert((kAccDefault & (kAccIntrinsic | kAccIntrinsicBits)) == 0,
+                  "kAccDefault conflicts with intrinsic modifier");
+    return (access_flags & kAccDefault) != 0;
+  }
+
+  // Returns true if the method is obsolete.
   bool IsObsolete() const {
-    return (GetAccessFlags() & kAccObsoleteMethod) != 0;
+    return IsObsolete(GetAccessFlags());
+  }
+
+  static bool IsObsolete(uint32_t access_flags) {
+    return (access_flags & kAccObsoleteMethod) != 0;
   }
 
   void SetIsObsolete() REQUIRES_SHARED(Locks::mutator_lock_) {
     AddAccessFlags(kAccObsoleteMethod);
   }
 
+  // Returns true if the method is native.
   bool IsNative() const {
-    return (GetAccessFlags() & kAccNative) != 0;
+    return IsNative(GetAccessFlags());
+  }
+
+  static bool IsNative(uint32_t access_flags) {
+    return (access_flags & kAccNative) != 0;
   }
 
   // Checks to see if the method was annotated with @dalvik.annotation.optimization.FastNative.
   bool IsFastNative() const {
+    return IsFastNative(GetAccessFlags());
+  }
+
+  static bool IsFastNative(uint32_t access_flags) {
     // The presence of the annotation is checked by ClassLinker and recorded in access flags.
     // The kAccFastNative flag value is used with a different meaning for non-native methods,
     // so we need to check the kAccNative flag as well.
     constexpr uint32_t mask = kAccFastNative | kAccNative;
-    return (GetAccessFlags() & mask) == mask;
+    return (access_flags & mask) == mask;
   }
 
   // Checks to see if the method was annotated with @dalvik.annotation.optimization.CriticalNative.
   bool IsCriticalNative() const {
+    return IsCriticalNative(GetAccessFlags());
+  }
+
+  static bool IsCriticalNative(uint32_t access_flags) {
     // The presence of the annotation is checked by ClassLinker and recorded in access flags.
     // The kAccCriticalNative flag value is used with a different meaning for non-native methods,
     // so we need to check the kAccNative flag as well.
     constexpr uint32_t mask = kAccCriticalNative | kAccNative;
-    return (GetAccessFlags() & mask) == mask;
+    return (access_flags & mask) == mask;
   }
 
+  // Returns true if the method is managed (not native).
+  bool IsManaged() const {
+    return IsManaged(GetAccessFlags());
+  }
+
+  static bool IsManaged(uint32_t access_flags) {
+    return !IsNative(access_flags);
+  }
+
+  // Returns true if the method is managed (not native) and invokable.
+  bool IsManagedAndInvokable() const {
+    return IsManagedAndInvokable(GetAccessFlags());
+  }
+
+  static bool IsManagedAndInvokable(uint32_t access_flags) {
+    return IsManaged(access_flags) && IsInvokable(access_flags);
+  }
+
+  // Returns true if the method is abstract.
   bool IsAbstract() const {
+    return IsAbstract(GetAccessFlags());
+  }
+
+  static bool IsAbstract(uint32_t access_flags) {
     // Default confliciting methods have `kAccAbstract` set but they are not actually abstract.
-    return (GetAccessFlags() & kAccAbstract) != 0 && !IsDefaultConflicting();
+    return (access_flags & kAccAbstract) != 0 && !IsDefaultConflicting(access_flags);
   }
 
+  // Returns true if the method is declared synthetic.
   bool IsSynthetic() const {
-    return (GetAccessFlags() & kAccSynthetic) != 0;
+    return IsSynthetic(GetAccessFlags());
   }
 
+  static bool IsSynthetic(uint32_t access_flags) {
+    return (access_flags & kAccSynthetic) != 0;
+  }
+
+  // Returns true if the method is declared varargs.
   bool IsVarargs() const {
-    return (GetAccessFlags() & kAccVarargs) != 0;
+    return IsVarargs(GetAccessFlags());
+  }
+
+  static bool IsVarargs(uint32_t access_flags) {
+    return (access_flags & kAccVarargs) != 0;
   }
 
   bool IsProxyMethod() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -372,10 +528,15 @@
     ClearAccessFlags(kAccSkipAccessChecks);
   }
 
+  // Returns true if the method has previously been warm.
   bool PreviouslyWarm() const {
+    return PreviouslyWarm(GetAccessFlags());
+  }
+
+  static bool PreviouslyWarm(uint32_t access_flags) {
     // kAccPreviouslyWarm overlaps with kAccIntrinsicBits. Return true for intrinsics.
     constexpr uint32_t mask = kAccPreviouslyWarm | kAccIntrinsic;
-    return (GetAccessFlags() & mask) != 0u;
+    return (access_flags & mask) != 0u;
   }
 
   void SetPreviouslyWarm() REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -389,10 +550,14 @@
   // Should this method be run in the interpreter and count locks (e.g., failed structured-
   // locking verification)?
   bool MustCountLocks() const {
-    if (IsIntrinsic()) {
+    return MustCountLocks(GetAccessFlags());
+  }
+
+  static bool MustCountLocks(uint32_t access_flags) {
+    if (IsIntrinsic(access_flags)) {
       return false;
     }
-    return (GetAccessFlags() & kAccMustCountLocks) != 0;
+    return (access_flags & kAccMustCountLocks) != 0;
   }
 
   void ClearMustCountLocks() REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -400,13 +565,18 @@
   }
 
   void SetMustCountLocks() REQUIRES_SHARED(Locks::mutator_lock_) {
-    AddAccessFlags(kAccMustCountLocks);
     ClearAccessFlags(kAccSkipAccessChecks);
+    AddAccessFlags(kAccMustCountLocks);
   }
 
+  // Returns true if the method is using the nterp entrypoint fast path.
   bool HasNterpEntryPointFastPathFlag() const {
+    return HasNterpEntryPointFastPathFlag(GetAccessFlags());
+  }
+
+  static bool HasNterpEntryPointFastPathFlag(uint32_t access_flags) {
     constexpr uint32_t mask = kAccNative | kAccNterpEntryPointFastPathFlag;
-    return (GetAccessFlags() & mask) == kAccNterpEntryPointFastPathFlag;
+    return (access_flags & mask) == kAccNterpEntryPointFastPathFlag;
   }
 
   void SetNterpEntryPointFastPathFlag() REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -418,14 +588,21 @@
     AddAccessFlags(kAccNterpInvokeFastPathFlag);
   }
 
+  // Returns whether the method is a string constructor. The method must not
+  // be a class initializer. (Class initializers are called from a different
+  // context where we do not need to check for string constructors.)
+  bool IsStringConstructor() REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Returns true if this method could be overridden by a default method.
   bool IsOverridableByDefaultMethod() REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool CheckIncompatibleClassChange(InvokeType type) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Throws the error that would result from trying to invoke this method (i.e.
-  // IncompatibleClassChangeError or AbstractMethodError). Only call if !IsInvokable();
-  void ThrowInvocationTimeError() REQUIRES_SHARED(Locks::mutator_lock_);
+  // IncompatibleClassChangeError, AbstractMethodError, or IllegalAccessError).
+  // Only call if !IsInvokable();
+  void ThrowInvocationTimeError(ObjPtr<mirror::Object> receiver)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   uint16_t GetMethodIndex() REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -490,6 +667,80 @@
   void Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result, const char* shorty)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template <char ReturnType, char... ArgType>
+  typename detail::ShortyTraits<ReturnType>::Type
+  InvokeStatic(Thread* self, typename detail::ShortyTraits<ArgType>::Type... args)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  template <char ReturnType, char... ArgType>
+  typename detail::ShortyTraits<ReturnType>::Type
+  InvokeInstance(Thread* self,
+                 ObjPtr<mirror::Object> receiver,
+                 typename detail::ShortyTraits<ArgType>::Type... args)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  template <char ReturnType, char... ArgType>
+  typename detail::ShortyTraits<ReturnType>::Type
+  InvokeFinal(Thread* self,
+              ObjPtr<mirror::Object> receiver,
+              typename detail::ShortyTraits<ArgType>::Type... args)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  template <char ReturnType, char... ArgType>
+  typename detail::ShortyTraits<ReturnType>::Type
+  InvokeVirtual(Thread* self,
+                ObjPtr<mirror::Object> receiver,
+                typename detail::ShortyTraits<ArgType>::Type... args)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  template <char ReturnType, char... ArgType>
+  typename detail::ShortyTraits<ReturnType>::Type
+  InvokeInterface(Thread* self,
+                  ObjPtr<mirror::Object> receiver,
+                  typename detail::ShortyTraits<ArgType>::Type... args)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  template <char... ArgType, typename HandleScopeType>
+  Handle<mirror::Object> NewObject(HandleScopeType& hs,
+                                   Thread* self,
+                                   typename detail::HandleShortyTraits<ArgType>::Type... args)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  template <char... ArgType>
+  ObjPtr<mirror::Object> NewObject(Thread* self,
+                                   typename detail::HandleShortyTraits<ArgType>::Type... args)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Returns true if the method needs a class initialization check according to access flags.
+  // Only static methods other than the class initializer need this check.
+  // The caller is responsible for performing the actual check.
+  bool NeedsClinitCheckBeforeCall() const {
+    return NeedsClinitCheckBeforeCall(GetAccessFlags());
+  }
+
+  static bool NeedsClinitCheckBeforeCall(uint32_t access_flags) {
+    // The class initializer is special as it is invoked during initialization
+    // and does not need the check.
+    return IsStatic(access_flags) && !IsConstructor(access_flags);
+  }
+
+  // Check if the method needs a class initialization check before call
+  // and its declaring class is not yet visibly initialized.
+  // (The class needs to be visibly initialized before we can use entrypoints
+  // to compiled code for static methods. See b/18161648 .)
+  template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
+  bool StillNeedsClinitCheck() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Similar to `StillNeedsClinitCheck()` but the method's declaring class may
+  // be dead but not yet reclaimed by the GC, so we cannot do a full read barrier
+  // but we still want to check the class status in the to-space class if any.
+  // Note: JIT can hold and use such methods during managed heap GC.
+  bool StillNeedsClinitCheckMayBeDead() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Check if the declaring class has been verified and look at the to-space
+  // class object, if any, as in `StillNeedsClinitCheckMayBeDead()`.
+  bool IsDeclaringClassVerifiedMayBeDead() REQUIRES_SHARED(Locks::mutator_lock_);
+
   const void* GetEntryPointFromQuickCompiledCode() const {
     return GetEntryPointFromQuickCompiledCodePtrSize(kRuntimePointerSize);
   }
@@ -538,7 +789,6 @@
     SetDataPtrSize(table, pointer_size);
   }
 
-  template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ALWAYS_INLINE bool HasSingleImplementation() REQUIRES_SHARED(Locks::mutator_lock_);
 
   ALWAYS_INLINE void SetHasSingleImplementation(bool single_impl)
@@ -612,7 +862,11 @@
   }
 
   bool HasCodeItem() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return !IsRuntimeMethod() && !IsNative() && !IsProxyMethod() && !IsAbstract();
+    uint32_t access_flags = GetAccessFlags();
+    return !IsNative(access_flags) &&
+           !IsAbstract(access_flags) &&
+           !IsRuntimeMethod() &&
+           !IsProxyMethod();
   }
 
   // We need to explicitly indicate whether the code item is obtained from the compact dex file,
@@ -635,7 +889,9 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // NO_THREAD_SAFETY_ANALYSIS since we don't know what the callback requires.
-  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename RootVisitorType>
+  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier,
+           bool kVisitProxyMethod = true,
+           typename RootVisitorType>
   void VisitRoots(RootVisitorType& visitor, PointerSize pointer_size) NO_THREAD_SAFETY_ANALYSIS;
 
   const DexFile* GetDexFile() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -691,6 +947,7 @@
 
   template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ObjPtr<mirror::DexCache> GetDexCache() REQUIRES_SHARED(Locks::mutator_lock_);
+  template <ReadBarrierOption kReadBarrierOption>
   ObjPtr<mirror::DexCache> GetObsoleteDexCache() REQUIRES_SHARED(Locks::mutator_lock_);
 
   ALWAYS_INLINE ArtMethod* GetInterfaceMethodForProxyUnchecked(PointerSize pointer_size)
@@ -824,7 +1081,7 @@
 
   // Entry within a dispatch table for this method. For static/direct methods the index is into
   // the declaringClass.directMethods, for virtual methods the vtable and for interface methods the
-  // ifTable.
+  // interface's method array in `IfTable`s of implementing classes.
   uint16_t method_index_;
 
   union {
@@ -918,6 +1175,10 @@
     access_flags_.fetch_and(~flag, std::memory_order_relaxed);
   }
 
+  // Helper method for checking the class status of a possibly dead declaring class.
+  // See `StillNeedsClinitCheckMayBeDead()` and `IsDeclaringClassVerifierMayBeDead()`.
+  ObjPtr<mirror::Class> GetDeclaringClassMayBeDead() REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Used by GetName and GetNameView to share common code.
   const char* GetRuntimeMethodName() REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/art_method_test.cc b/runtime/art_method_test.cc
new file mode 100644
index 0000000..b1e9ed3
--- /dev/null
+++ b/runtime/art_method_test.cc
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <type_traits>
+
+#include "art_method-alloc-inl.h"
+
+#include "base/casts.h"
+#include "common_runtime_test.h"
+#include "mirror/class-alloc-inl.h"
+#include "well_known_classes.h"
+
+namespace art {
+
+namespace {
+// Helper function to avoid `ASSERT_EQ` with floating point types.
+int32_t ToIntegralType(float value) { return bit_cast<int32_t>(value); }
+int64_t ToIntegralType(double value) { return bit_cast<int64_t>(value); }
+template <typename T> T ToIntegralType(T value) { return value; }
+}
+
+class ArtMethodTest : public CommonRuntimeTest {
+ protected:
+  ArtMethodTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
+  // Test primitive type boxing and unboxing.
+  //
+  // This provides basic checks that the translation of the compile-time shorty
+  // to argument types and return type are correct and that values are passed
+  // correctly for these single-argument calls (`ArtMethod::InvokeStatic()` with
+  // primitive args and `ArtMethod::InvokeInstance()` with a reference arg).
+  template <typename Type, char kPrimitive>
+  void TestBoxUnbox(ArtMethod* value_of, const char* unbox_name, Type value) {
+    Thread* self = Thread::Current();
+    ScopedObjectAccess soa(self);
+    ASSERT_STREQ(value_of->GetName(), "valueOf");
+    std::string unbox_signature = std::string("()") + kPrimitive;
+    ArtMethod* unbox_method = value_of->GetDeclaringClass()->FindClassMethod(
+        unbox_name, unbox_signature, kRuntimePointerSize);
+    ASSERT_TRUE(unbox_method != nullptr);
+    ASSERT_FALSE(unbox_method->IsStatic());
+    ASSERT_TRUE(value_of->GetDeclaringClass()->IsFinal());
+
+    static_assert(std::is_same_v<ObjPtr<mirror::Object> (ArtMethod::*)(Thread*, Type),
+                                 decltype(&ArtMethod::InvokeStatic<'L', kPrimitive>)>);
+    StackHandleScope<1u> hs(self);
+    Handle<mirror::Object> boxed =
+        hs.NewHandle(value_of->InvokeStatic<'L', kPrimitive>(self, value));
+    ASSERT_TRUE(boxed != nullptr);
+    ASSERT_OBJ_PTR_EQ(boxed->GetClass(), value_of->GetDeclaringClass());
+    static_assert(std::is_same_v<Type (ArtMethod::*)(Thread*, ObjPtr<mirror::Object>),
+                                 decltype(&ArtMethod::InvokeInstance<kPrimitive>)>);
+    // Exercise both `InvokeInstance()` and `InvokeFinal()` (boxing classes are final).
+    Type unboxed1 = unbox_method->InvokeInstance<kPrimitive>(self, boxed.Get());
+    ASSERT_EQ(ToIntegralType(value), ToIntegralType(unboxed1));
+    Type unboxed2 = unbox_method->InvokeFinal<kPrimitive>(self, boxed.Get());
+    ASSERT_EQ(ToIntegralType(value), ToIntegralType(unboxed2));
+  }
+};
+
+TEST_F(ArtMethodTest, BoxUnboxBoolean) {
+  TestBoxUnbox<bool, 'Z'>(WellKnownClasses::java_lang_Boolean_valueOf, "booleanValue", true);
+}
+
+TEST_F(ArtMethodTest, BoxUnboxByte) {
+  TestBoxUnbox<int8_t, 'B'>(WellKnownClasses::java_lang_Byte_valueOf, "byteValue", -12);
+}
+
+TEST_F(ArtMethodTest, BoxUnboxChar) {
+  TestBoxUnbox<uint16_t, 'C'>(WellKnownClasses::java_lang_Character_valueOf, "charValue", 0xffaa);
+}
+
+TEST_F(ArtMethodTest, BoxUnboxShort) {
+  TestBoxUnbox<int16_t, 'S'>(WellKnownClasses::java_lang_Short_valueOf, "shortValue", -0x1234);
+}
+
+TEST_F(ArtMethodTest, BoxUnboxInt) {
+  TestBoxUnbox<int32_t, 'I'>(WellKnownClasses::java_lang_Integer_valueOf, "intValue", -0x12345678);
+}
+
+TEST_F(ArtMethodTest, BoxUnboxLong) {
+  TestBoxUnbox<int64_t, 'J'>(
+      WellKnownClasses::java_lang_Long_valueOf, "longValue", UINT64_C(-0x1234567887654321));
+}
+
+TEST_F(ArtMethodTest, BoxUnboxFloat) {
+  TestBoxUnbox<float, 'F'>(WellKnownClasses::java_lang_Float_valueOf, "floatValue", -2.0f);
+}
+
+TEST_F(ArtMethodTest, BoxUnboxDouble) {
+  TestBoxUnbox<double, 'D'>(WellKnownClasses::java_lang_Double_valueOf, "doubleValue", 8.0);
+}
+
+TEST_F(ArtMethodTest, ArrayList) {
+  Thread* self = Thread::Current();
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  ScopedObjectAccess soa(self);
+  StackHandleScope<4u> hs(self);
+  Handle<mirror::Class> list_class =
+      hs.NewHandle(class_linker->FindSystemClass(self, "Ljava/util/List;"));
+  ASSERT_TRUE(list_class != nullptr);
+  Handle<mirror::Class> abstract_list_class =
+      hs.NewHandle(class_linker->FindSystemClass(self, "Ljava/util/AbstractList;"));
+  ASSERT_TRUE(abstract_list_class != nullptr);
+  Handle<mirror::Class> array_list_class =
+      hs.NewHandle(class_linker->FindSystemClass(self, "Ljava/util/ArrayList;"));
+  ASSERT_TRUE(array_list_class != nullptr);
+  ASSERT_TRUE(abstract_list_class->Implements(list_class.Get()));
+  ASSERT_TRUE(array_list_class->IsSubClass(abstract_list_class.Get()));
+
+  ArtMethod* init = array_list_class->FindClassMethod("<init>", "()V", kRuntimePointerSize);
+  ASSERT_TRUE(init != nullptr);
+  ArtMethod* array_list_size_method =
+      array_list_class->FindClassMethod("size", "()I", kRuntimePointerSize);
+  DCHECK(array_list_size_method != nullptr);
+  ArtMethod* abstract_list_size_method =
+      abstract_list_class->FindClassMethod("size", "()I", kRuntimePointerSize);
+  DCHECK(abstract_list_size_method != nullptr);
+  ArtMethod* list_size_method =
+      list_class->FindInterfaceMethod("size", "()I", kRuntimePointerSize);
+  DCHECK(list_size_method != nullptr);
+
+  Handle<mirror::Object> array_list = init->NewObject<>(hs, self);
+  ASSERT_FALSE(self->IsExceptionPending());
+  ASSERT_TRUE(array_list != nullptr);
+
+  // Invoke `ArrayList.size()` directly, with virtual dispatch from
+  // `AbstractList.size()` and with interface dispatch from `List.size()`.
+  int32_t size = array_list_size_method->InvokeInstance<'I'>(self, array_list.Get());
+  ASSERT_FALSE(self->IsExceptionPending());
+  ASSERT_EQ(0, size);
+  size = abstract_list_size_method->InvokeVirtual<'I'>(self, array_list.Get());
+  ASSERT_FALSE(self->IsExceptionPending());
+  ASSERT_EQ(0, size);
+  size = list_size_method->InvokeInterface<'I'>(self, array_list.Get());
+  ASSERT_FALSE(self->IsExceptionPending());
+  ASSERT_EQ(0, size);
+
+  // Try to invoke abstract method `AbstractList.size()` directly.
+  abstract_list_size_method->InvokeInstance<'I'>(self, array_list.Get());
+  ASSERT_TRUE(self->IsExceptionPending());
+  self->ClearException();
+}
+
+}  // namespace art
diff --git a/runtime/art_standalone_runtime_compiler_tests.xml b/runtime/art_standalone_runtime_compiler_tests.xml
deleted file mode 100644
index 3ae3a82..0000000
--- a/runtime/art_standalone_runtime_compiler_tests.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 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.
--->
-<configuration description="Runs art_standalone_runtime_compiler_tests.">
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-        <option name="cleanup" value="true" />
-        <option name="push" value="art_standalone_runtime_compiler_tests->/data/local/tmp/art_standalone_runtime_compiler_tests/art_standalone_runtime_compiler_tests" />
-        <option name="append-bitness" value="true" />
-    </target_preparer>
-
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-        <option name="cleanup" value="true" />
-        <option name="push" value="art-gtest-jars-Main.jar->/data/local/tmp/art_standalone_runtime_compiler_tests/art-gtest-jars-Main.jar" />
-        <option name="push" value="art-gtest-jars-NonStaticLeafMethods.jar->/data/local/tmp/art_standalone_runtime_compiler_tests/art-gtest-jars-NonStaticLeafMethods.jar" />
-        <option name="push" value="art-gtest-jars-StaticLeafMethods.jar->/data/local/tmp/art_standalone_runtime_compiler_tests/art-gtest-jars-StaticLeafMethods.jar" />
-    </target_preparer>
-
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-        <option name="cleanup" value="true" />
-        <option name="append-bitness" value="true" />
-        <option name="push-file" key="generate-boot-image" value="/data/local/tmp/art_standalone_runtime_compiler_tests/generate-boot-image" />
-    </target_preparer>
-
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="throw-if-cmd-fail" value="true" />
-        <option name="run-command" value="mkdir -p /data/local/tmp/art_standalone_runtime_compiler_tests/art_boot_images" />
-        <option name="run-command" value="/data/local/tmp/art_standalone_runtime_compiler_tests/generate-boot-image --output-dir=/data/local/tmp/art_standalone_runtime_compiler_tests/art_boot_images" />
-        <option name="teardown-command" value="rm -rf /data/local/tmp/art_standalone_runtime_compiler_tests/art_boot_images" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp/art_standalone_runtime_compiler_tests" />
-        <option name="module-name" value="art_standalone_runtime_compiler_tests" />
-        <option name="ld-library-path-32" value="/apex/com.android.art/lib" />
-        <option name="ld-library-path-64" value="/apex/com.android.art/lib64" />
-    </test>
-
-    <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
-         one of the Mainline modules below is present on the device used for testing. -->
-    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <!-- ART Mainline Module (internal version). -->
-        <option name="mainline-module-package-name" value="com.google.android.art" />
-        <!-- ART Mainline Module (external (AOSP) version). -->
-        <option name="mainline-module-package-name" value="com.android.art" />
-    </object>
-
-    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
-    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
-</configuration>
diff --git a/runtime/art_standalone_runtime_tests.xml b/runtime/art_standalone_runtime_tests.xml
index 5200529..b210f5e 100644
--- a/runtime/art_standalone_runtime_tests.xml
+++ b/runtime/art_standalone_runtime_tests.xml
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 <configuration description="Runs art_standalone_runtime_tests.">
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
+
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="art_standalone_runtime_tests->/data/local/tmp/art_standalone_runtime_tests/art_standalone_runtime_tests" />
@@ -48,6 +50,7 @@
         <option name="push" value="art-gtest-jars-MyClass.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-MyClass.jar" />
         <option name="push" value="art-gtest-jars-MyClassNatives.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-MyClassNatives.jar" />
         <option name="push" value="art-gtest-jars-Nested.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-Nested.jar" />
+        <option name="push" value="art-gtest-jars-NonStaticLeafMethods.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-NonStaticLeafMethods.jar" />
         <option name="push" value="art-gtest-jars-Packages.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-Packages.jar" />
         <option name="push" value="art-gtest-jars-ProfileTestMultiDex.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-ProfileTestMultiDex.jar" />
         <option name="push" value="art-gtest-jars-ProtoCompare.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-ProtoCompare.jar" />
@@ -93,7 +96,7 @@
         <option name="exclude-filter" value="HiddenApiTest.DexDomain_SystemSystemExtFrameworkDir" />
         <option name="exclude-filter" value="HiddenApiTest.DexDomain_SystemSystemExtFrameworkDir_MultiDex" />
         <option name="exclude-filter" value="JniInternalTest.CallVarArgMethodBadPrimitive" />
-        <option name="exclude-filter" value="OatFileAssistantTest.SystemFrameworkDir" />
+        <option name="exclude-filter" value="OatFileAssistantBaseTest.SystemFrameworkDir" />
         <option name="exclude-filter" value="StubTest.Fields16" />
         <option name="exclude-filter" value="StubTest.Fields32" />
         <option name="exclude-filter" value="StubTest.Fields64" />
@@ -107,10 +110,11 @@
              them fail to dynamically link to the expected (64-bit) libraries.
 
              TODO(b/204649079): Investigate these failures and re-enable these tests. -->
-        <option name="exclude-filter" value="ExecUtilsTest.EnvSnapshotDeletionsAreNotVisible" />
-        <option name="exclude-filter" value="ExecUtilsTest.ExecNoTimeout" />
-        <option name="exclude-filter" value="ExecUtilsTest.ExecSuccess" />
-        <option name="exclude-filter" value="ExecUtilsTest.ExecTimeout" />
+        <option name="exclude-filter" value="*ExecUtilsTest.EnvSnapshotDeletionsAreNotVisible*" />
+        <option name="exclude-filter" value="*ExecUtilsTest.ExecNoTimeout*" />
+        <option name="exclude-filter" value="*ExecUtilsTest.ExecStatFailed*" />
+        <option name="exclude-filter" value="*ExecUtilsTest.ExecSuccess*" />
+        <option name="exclude-filter" value="*ExecUtilsTest.ExecTimeout*" />
     </test>
 
     <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
diff --git a/runtime/backtrace_helper.cc b/runtime/backtrace_helper.cc
index d76a501..260843d 100644
--- a/runtime/backtrace_helper.cc
+++ b/runtime/backtrace_helper.cc
@@ -131,14 +131,6 @@
       CHECK_LT(num_frames_, max_depth_);
       out_frames_[num_frames_++] = static_cast<uintptr_t>(it->pc);
 
-      // Expected early end: Instrumentation breaks unwinding (b/138296821).
-      // Inexact compare because the unwinder does not give us exact return address,
-      // but rather it tries to guess the address of the preceding call instruction.
-      size_t exit_pc = reinterpret_cast<size_t>(GetQuickInstrumentationExitPc());
-      if (exit_pc - 4 <= it->pc && it->pc <= exit_pc) {
-        return true;
-      }
-
       if (kStrictUnwindChecks) {
         if (it->function_name.empty()) {
           return false;
diff --git a/runtime/backtrace_helper.h b/runtime/backtrace_helper.h
index a74d0e0..9be2550 100644
--- a/runtime/backtrace_helper.h
+++ b/runtime/backtrace_helper.h
@@ -26,7 +26,7 @@
 
 namespace art {
 
-// Using libbacktrace
+// Using libunwindstack
 class BacktraceCollector {
  public:
   BacktraceCollector(uintptr_t* out_frames, size_t max_depth, size_t skip_count)
diff --git a/runtime/barrier.cc b/runtime/barrier.cc
index d144591..a6cc9ba 100644
--- a/runtime/barrier.cc
+++ b/runtime/barrier.cc
@@ -40,6 +40,11 @@
   SetCountLocked(self, count_ - 1);
 }
 
+void Barrier::IncrementNoWait(Thread* self) {
+  MutexLock mu(self, *GetLock());
+  SetCountLocked(self, count_ + 1);
+}
+
 void Barrier::Wait(Thread* self) {
   Increment(self, -1);
 }
diff --git a/runtime/barrier.h b/runtime/barrier.h
index 432df76..4c94a14 100644
--- a/runtime/barrier.h
+++ b/runtime/barrier.h
@@ -51,6 +51,9 @@
 
   // Pass through the barrier, decrement the count but do not block.
   void Pass(Thread* self) REQUIRES(!GetLock());
+  // Increment the barrier but do not block. The caller should ensure that it
+  // decrements/passes it eventually.
+  void IncrementNoWait(Thread* self) REQUIRES(!GetLock());
 
   // Decrement the count, then wait until the count is zero.
   void Wait(Thread* self) REQUIRES(!GetLock());
diff --git a/runtime/barrier_test.cc b/runtime/barrier_test.cc
index 5ec24bc..52959bd 100644
--- a/runtime/barrier_test.cc
+++ b/runtime/barrier_test.cc
@@ -52,6 +52,10 @@
 
 class BarrierTest : public CommonRuntimeTest {
  public:
+  BarrierTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
   static int32_t num_threads;
 };
 
diff --git a/runtime/base/atomic_pair.h b/runtime/base/atomic_pair.h
index 3e9e820..1523b3b 100644
--- a/runtime/base/atomic_pair.h
+++ b/runtime/base/atomic_pair.h
@@ -40,18 +40,16 @@
 template <typename IntType>
 ALWAYS_INLINE static inline AtomicPair<IntType> AtomicPairLoadAcquire(
     std::atomic<AtomicPair<IntType>>* target) {
-  static_assert(std::atomic<AtomicPair<IntType>>::is_always_lock_free);
   return target->load(std::memory_order_acquire);
 }
 
 template <typename IntType>
-ALWAYS_INLINE static inline void AtomicPairStoreRelease(
-    std::atomic<AtomicPair<IntType>>* target, AtomicPair<IntType> value) {
-  static_assert(std::atomic<AtomicPair<IntType>>::is_always_lock_free);
+ALWAYS_INLINE static inline void AtomicPairStoreRelease(std::atomic<AtomicPair<IntType>>* target,
+                                                        AtomicPair<IntType> value) {
   target->store(value, std::memory_order_release);
 }
 
-// llvm does not implement 16-byte atomic operations on x86-64.
+// LLVM uses generic lock-based implementation for x86_64, we can do better with CMPXCHG16B.
 #if defined(__x86_64__)
 ALWAYS_INLINE static inline AtomicPair<uint64_t> AtomicPairLoadAcquire(
     std::atomic<AtomicPair<uint64_t>>* target) {
diff --git a/runtime/base/gc_visited_arena_pool.cc b/runtime/base/gc_visited_arena_pool.cc
new file mode 100644
index 0000000..52b3829
--- /dev/null
+++ b/runtime/base/gc_visited_arena_pool.cc
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include "base/gc_visited_arena_pool.h"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/arena_allocator-inl.h"
+#include "base/memfd.h"
+#include "base/utils.h"
+#include "gc/collector/mark_compact-inl.h"
+
+namespace art {
+
+TrackedArena::TrackedArena(uint8_t* start, size_t size, bool pre_zygote_fork)
+    : Arena(), first_obj_array_(nullptr), pre_zygote_fork_(pre_zygote_fork) {
+  static_assert(ArenaAllocator::kArenaAlignment <= kPageSize,
+                "Arena should not need stronger alignment than kPageSize.");
+  DCHECK_ALIGNED(size, kPageSize);
+  DCHECK_ALIGNED(start, kPageSize);
+  memory_ = start;
+  size_ = size;
+  size_t arr_size = size / kPageSize;
+  first_obj_array_.reset(new uint8_t*[arr_size]);
+  std::fill_n(first_obj_array_.get(), arr_size, nullptr);
+}
+
+void TrackedArena::Release() {
+  if (bytes_allocated_ > 0) {
+    // Userfaultfd GC uses MAP_SHARED mappings for linear-alloc and therefore
+    // MADV_DONTNEED will not free the pages from page cache. Therefore use
+    // MADV_REMOVE instead, which is meant for this purpose.
+    // Arenas allocated pre-zygote fork are private anonymous and hence must be
+    // released using MADV_DONTNEED.
+    if (!gUseUserfaultfd || pre_zygote_fork_ ||
+        (madvise(Begin(), Size(), MADV_REMOVE) == -1 && errno == EINVAL)) {
+      // MADV_REMOVE fails if invoked on anonymous mapping, which could happen
+      // if the arena is released before userfaultfd-GC starts using memfd. So
+      // use MADV_DONTNEED.
+      ZeroAndReleasePages(Begin(), Size());
+    }
+    std::fill_n(first_obj_array_.get(), Size() / kPageSize, nullptr);
+    bytes_allocated_ = 0;
+  }
+}
+
+void TrackedArena::SetFirstObject(uint8_t* obj_begin, uint8_t* obj_end) {
+  DCHECK_LE(static_cast<void*>(Begin()), static_cast<void*>(obj_end));
+  DCHECK_LT(static_cast<void*>(obj_begin), static_cast<void*>(obj_end));
+  size_t idx = static_cast<size_t>(obj_begin - Begin()) / kPageSize;
+  size_t last_byte_idx = static_cast<size_t>(obj_end - 1 - Begin()) / kPageSize;
+  // If the addr is at the beginning of a page, then we set it for that page too.
+  if (IsAligned<kPageSize>(obj_begin)) {
+    first_obj_array_[idx] = obj_begin;
+  }
+  while (idx < last_byte_idx) {
+    first_obj_array_[++idx] = obj_begin;
+  }
+}
+
+uint8_t* GcVisitedArenaPool::AddMap(size_t min_size) {
+  size_t size = std::max(min_size, kLinearAllocPoolSize);
+#if defined(__LP64__)
+  // This is true only when we are running a 64-bit dex2oat to compile a 32-bit image.
+  if (low_4gb_) {
+    size = std::max(min_size, kLow4GBLinearAllocPoolSize);
+  }
+#endif
+  size_t alignment = BestPageTableAlignment(size);
+  DCHECK_GE(size, kPMDSize);
+  std::string err_msg;
+  maps_.emplace_back(MemMap::MapAnonymousAligned(
+      name_, size, PROT_READ | PROT_WRITE, low_4gb_, alignment, &err_msg));
+  MemMap& map = maps_.back();
+  if (!map.IsValid()) {
+    LOG(FATAL) << "Failed to allocate " << name_ << ": " << err_msg;
+    UNREACHABLE();
+  }
+
+  if (gUseUserfaultfd) {
+    // Create a shadow-map for the map being added for userfaultfd GC
+    gc::collector::MarkCompact* mark_compact =
+        Runtime::Current()->GetHeap()->MarkCompactCollector();
+    DCHECK_NE(mark_compact, nullptr);
+    mark_compact->AddLinearAllocSpaceData(map.Begin(), map.Size());
+  }
+  Chunk* chunk = new Chunk(map.Begin(), map.Size());
+  best_fit_allocs_.insert(chunk);
+  free_chunks_.insert(chunk);
+  return map.Begin();
+}
+
+GcVisitedArenaPool::GcVisitedArenaPool(bool low_4gb, bool is_zygote, const char* name)
+    : bytes_allocated_(0), name_(name), low_4gb_(low_4gb), pre_zygote_fork_(is_zygote) {}
+
+GcVisitedArenaPool::~GcVisitedArenaPool() {
+  for (Chunk* chunk : free_chunks_) {
+    delete chunk;
+  }
+  // Must not delete chunks from best_fit_allocs_ as they are shared with
+  // free_chunks_.
+}
+
+size_t GcVisitedArenaPool::GetBytesAllocated() const {
+  std::lock_guard<std::mutex> lock(lock_);
+  return bytes_allocated_;
+}
+
+uint8_t* GcVisitedArenaPool::AddPreZygoteForkMap(size_t size) {
+  DCHECK(pre_zygote_fork_);
+  DCHECK(Runtime::Current()->IsZygote());
+  std::string pre_fork_name = "Pre-zygote-";
+  pre_fork_name += name_;
+  std::string err_msg;
+  maps_.emplace_back(MemMap::MapAnonymous(
+      pre_fork_name.c_str(), size, PROT_READ | PROT_WRITE, low_4gb_, &err_msg));
+  MemMap& map = maps_.back();
+  if (!map.IsValid()) {
+    LOG(FATAL) << "Failed to allocate " << pre_fork_name << ": " << err_msg;
+    UNREACHABLE();
+  }
+  return map.Begin();
+}
+
+Arena* GcVisitedArenaPool::AllocArena(size_t size) {
+  // Return only page aligned sizes so that madvise can be leveraged.
+  size = RoundUp(size, kPageSize);
+  std::lock_guard<std::mutex> lock(lock_);
+
+  if (pre_zygote_fork_) {
+    // The first fork out of zygote hasn't happened yet. Allocate arena in a
+    // private-anonymous mapping to retain clean pages across fork.
+    DCHECK(Runtime::Current()->IsZygote());
+    uint8_t* addr = AddPreZygoteForkMap(size);
+    auto emplace_result = allocated_arenas_.emplace(addr, size, /*pre_zygote_fork=*/true);
+    return const_cast<TrackedArena*>(&(*emplace_result.first));
+  }
+
+  Chunk temp_chunk(nullptr, size);
+  auto best_fit_iter = best_fit_allocs_.lower_bound(&temp_chunk);
+  if (UNLIKELY(best_fit_iter == best_fit_allocs_.end())) {
+    AddMap(size);
+    best_fit_iter = best_fit_allocs_.lower_bound(&temp_chunk);
+    CHECK(best_fit_iter != best_fit_allocs_.end());
+  }
+  auto free_chunks_iter = free_chunks_.find(*best_fit_iter);
+  DCHECK(free_chunks_iter != free_chunks_.end());
+  Chunk* chunk = *best_fit_iter;
+  DCHECK_EQ(chunk, *free_chunks_iter);
+  // if the best-fit chunk < 2x the requested size, then give the whole chunk.
+  if (chunk->size_ < 2 * size) {
+    DCHECK_GE(chunk->size_, size);
+    auto emplace_result = allocated_arenas_.emplace(chunk->addr_,
+                                                    chunk->size_,
+                                                    /*pre_zygote_fork=*/false);
+    DCHECK(emplace_result.second);
+    free_chunks_.erase(free_chunks_iter);
+    best_fit_allocs_.erase(best_fit_iter);
+    delete chunk;
+    return const_cast<TrackedArena*>(&(*emplace_result.first));
+  } else {
+    auto emplace_result = allocated_arenas_.emplace(chunk->addr_,
+                                                    size,
+                                                    /*pre_zygote_fork=*/false);
+    DCHECK(emplace_result.second);
+    // Compute next iterators for faster insert later.
+    auto next_best_fit_iter = best_fit_iter;
+    next_best_fit_iter++;
+    auto next_free_chunks_iter = free_chunks_iter;
+    next_free_chunks_iter++;
+    auto best_fit_nh = best_fit_allocs_.extract(best_fit_iter);
+    auto free_chunks_nh = free_chunks_.extract(free_chunks_iter);
+    best_fit_nh.value()->addr_ += size;
+    best_fit_nh.value()->size_ -= size;
+    DCHECK_EQ(free_chunks_nh.value()->addr_, chunk->addr_);
+    best_fit_allocs_.insert(next_best_fit_iter, std::move(best_fit_nh));
+    free_chunks_.insert(next_free_chunks_iter, std::move(free_chunks_nh));
+    return const_cast<TrackedArena*>(&(*emplace_result.first));
+  }
+}
+
+void GcVisitedArenaPool::FreeRangeLocked(uint8_t* range_begin, size_t range_size) {
+  Chunk temp_chunk(range_begin, range_size);
+  bool merge_with_next = false;
+  bool merge_with_prev = false;
+  auto next_iter = free_chunks_.lower_bound(&temp_chunk);
+  auto iter_for_extract = free_chunks_.end();
+  // Can we merge with the previous chunk?
+  if (next_iter != free_chunks_.begin()) {
+    auto prev_iter = next_iter;
+    prev_iter--;
+    merge_with_prev = (*prev_iter)->addr_ + (*prev_iter)->size_ == range_begin;
+    if (merge_with_prev) {
+      range_begin = (*prev_iter)->addr_;
+      range_size += (*prev_iter)->size_;
+      // Hold on to the iterator for faster extract later
+      iter_for_extract = prev_iter;
+    }
+  }
+  // Can we merge with the next chunk?
+  if (next_iter != free_chunks_.end()) {
+    merge_with_next = range_begin + range_size == (*next_iter)->addr_;
+    if (merge_with_next) {
+      range_size += (*next_iter)->size_;
+      if (merge_with_prev) {
+        auto iter = next_iter;
+        next_iter++;
+        // Keep only one of the two chunks to be expanded.
+        Chunk* chunk = *iter;
+        size_t erase_res = best_fit_allocs_.erase(chunk);
+        DCHECK_EQ(erase_res, 1u);
+        free_chunks_.erase(iter);
+        delete chunk;
+      } else {
+        iter_for_extract = next_iter;
+        next_iter++;
+      }
+    }
+  }
+
+  // Extract-insert avoids 2/4 destroys and 2/2 creations
+  // as compared to erase-insert, so use that when merging.
+  if (merge_with_prev || merge_with_next) {
+    auto free_chunks_nh = free_chunks_.extract(iter_for_extract);
+    auto best_fit_allocs_nh = best_fit_allocs_.extract(*iter_for_extract);
+
+    free_chunks_nh.value()->addr_ = range_begin;
+    DCHECK_EQ(best_fit_allocs_nh.value()->addr_, range_begin);
+    free_chunks_nh.value()->size_ = range_size;
+    DCHECK_EQ(best_fit_allocs_nh.value()->size_, range_size);
+
+    free_chunks_.insert(next_iter, std::move(free_chunks_nh));
+    // Since the chunk's size has expanded, the hint won't be useful
+    // for best-fit set.
+    best_fit_allocs_.insert(std::move(best_fit_allocs_nh));
+  } else {
+    DCHECK(iter_for_extract == free_chunks_.end());
+    Chunk* chunk = new Chunk(range_begin, range_size);
+    free_chunks_.insert(next_iter, chunk);
+    best_fit_allocs_.insert(chunk);
+  }
+}
+
+void GcVisitedArenaPool::FreeArenaChain(Arena* first) {
+  if (kRunningOnMemoryTool) {
+    for (Arena* arena = first; arena != nullptr; arena = arena->Next()) {
+      MEMORY_TOOL_MAKE_UNDEFINED(arena->Begin(), arena->GetBytesAllocated());
+    }
+  }
+
+  // TODO: Handle the case when arena_allocator::kArenaAllocatorPreciseTracking
+  // is true. See MemMapArenaPool::FreeArenaChain() for example.
+  CHECK(!arena_allocator::kArenaAllocatorPreciseTracking);
+
+  // madvise the arenas before acquiring lock for scalability
+  for (Arena* temp = first; temp != nullptr; temp = temp->Next()) {
+    temp->Release();
+  }
+
+  std::lock_guard<std::mutex> lock(lock_);
+  arenas_freed_ = true;
+  while (first != nullptr) {
+    FreeRangeLocked(first->Begin(), first->Size());
+    // In other implementations of ArenaPool this is calculated when asked for,
+    // thanks to the list of free arenas that is kept around. But in this case,
+    // we release the freed arena back to the pool and therefore need to
+    // calculate here.
+    bytes_allocated_ += first->GetBytesAllocated();
+    TrackedArena* temp = down_cast<TrackedArena*>(first);
+    // TODO: Add logic to unmap the maps corresponding to pre-zygote-fork
+    // arenas, which are expected to be released only during shutdown.
+    first = first->Next();
+    size_t erase_count = allocated_arenas_.erase(*temp);
+    DCHECK_EQ(erase_count, 1u);
+  }
+}
+
+}  // namespace art
diff --git a/runtime/base/gc_visited_arena_pool.h b/runtime/base/gc_visited_arena_pool.h
new file mode 100644
index 0000000..e307147
--- /dev/null
+++ b/runtime/base/gc_visited_arena_pool.h
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2022 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_RUNTIME_BASE_GC_VISITED_ARENA_POOL_H_
+#define ART_RUNTIME_BASE_GC_VISITED_ARENA_POOL_H_
+
+#include "base/casts.h"
+#include "base/arena_allocator.h"
+#include "base/locks.h"
+#include "base/mem_map.h"
+
+#include <set>
+
+namespace art {
+
+// GcVisitedArenaPool can be used for tracking allocations so that they can
+// be visited during GC to update the GC-roots inside them.
+
+// An Arena which tracks its allocations.
+class TrackedArena final : public Arena {
+ public:
+  // Used for searching in maps. Only arena's starting address is relevant.
+  explicit TrackedArena(uint8_t* addr) : pre_zygote_fork_(false) { memory_ = addr; }
+  TrackedArena(uint8_t* start, size_t size, bool pre_zygote_fork);
+
+  template <typename PageVisitor>
+  void VisitRoots(PageVisitor& visitor) const REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK_ALIGNED(Size(), kPageSize);
+    DCHECK_ALIGNED(Begin(), kPageSize);
+    int nr_pages = Size() / kPageSize;
+    uint8_t* page_begin = Begin();
+    for (int i = 0; i < nr_pages && first_obj_array_[i] != nullptr; i++, page_begin += kPageSize) {
+      visitor(page_begin, first_obj_array_[i]);
+    }
+  }
+
+  // Return the page addr of the first page with first_obj set to nullptr.
+  uint8_t* GetLastUsedByte() const REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK_ALIGNED(Begin(), kPageSize);
+    DCHECK_ALIGNED(End(), kPageSize);
+    // Jump past bytes-allocated for arenas which are not currently being used
+    // by arena-allocator. This helps in reducing loop iterations below.
+    uint8_t* last_byte = AlignUp(Begin() + GetBytesAllocated(), kPageSize);
+    DCHECK_LE(last_byte, End());
+    for (size_t i = (last_byte - Begin()) / kPageSize;
+         last_byte < End() && first_obj_array_[i] != nullptr;
+         last_byte += kPageSize, i++) {
+      // No body.
+    }
+    return last_byte;
+  }
+
+  uint8_t* GetFirstObject(uint8_t* addr) const REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK_LE(Begin(), addr);
+    DCHECK_GT(End(), addr);
+    return first_obj_array_[(addr - Begin()) / kPageSize];
+  }
+
+  // Set 'obj_begin' in first_obj_array_ in every element for which it's the
+  // first object.
+  void SetFirstObject(uint8_t* obj_begin, uint8_t* obj_end);
+
+  void Release() override;
+  bool IsPreZygoteForkArena() const { return pre_zygote_fork_; }
+
+ private:
+  // first_obj_array_[i] is the object that overlaps with the ith page's
+  // beginning, i.e. first_obj_array_[i] <= ith page_begin.
+  std::unique_ptr<uint8_t*[]> first_obj_array_;
+  const bool pre_zygote_fork_;
+};
+
+// An arena-pool wherein allocations can be tracked so that the GC can visit all
+// the GC roots. All the arenas are allocated in one sufficiently large memory
+// range to avoid multiple calls to mremapped/mprotected syscalls.
+class GcVisitedArenaPool final : public ArenaPool {
+ public:
+#if defined(__LP64__)
+  // Use a size in multiples of 1GB as that can utilize the optimized mremap
+  // page-table move.
+  static constexpr size_t kLinearAllocPoolSize = 1 * GB;
+  static constexpr size_t kLow4GBLinearAllocPoolSize = 32 * MB;
+#else
+  static constexpr size_t kLinearAllocPoolSize = 32 * MB;
+#endif
+
+  explicit GcVisitedArenaPool(bool low_4gb = false,
+                              bool is_zygote = false,
+                              const char* name = "LinearAlloc");
+  virtual ~GcVisitedArenaPool();
+  Arena* AllocArena(size_t size) override;
+  void FreeArenaChain(Arena* first) override;
+  size_t GetBytesAllocated() const override;
+  void ReclaimMemory() override {}
+  void LockReclaimMemory() override {}
+  void TrimMaps() override {}
+
+  bool Contains(void* ptr) {
+    std::lock_guard<std::mutex> lock(lock_);
+    for (auto& map : maps_) {
+      if (map.HasAddress(ptr)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  template <typename PageVisitor>
+  void VisitRoots(PageVisitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
+    std::lock_guard<std::mutex> lock(lock_);
+    for (auto& arena : allocated_arenas_) {
+      arena.VisitRoots(visitor);
+    }
+  }
+
+  template <typename Callback>
+  void ForEachAllocatedArena(Callback cb) REQUIRES_SHARED(Locks::mutator_lock_) {
+    std::lock_guard<std::mutex> lock(lock_);
+    for (auto& arena : allocated_arenas_) {
+      cb(arena);
+    }
+  }
+
+  // Called in Heap::PreZygoteFork(). All allocations after this are done in
+  // arena-pool which is visited by userfaultfd.
+  void SetupPostZygoteMode() {
+    std::lock_guard<std::mutex> lock(lock_);
+    DCHECK(pre_zygote_fork_);
+    pre_zygote_fork_ = false;
+  }
+
+  // For userfaultfd GC to be able to acquire the lock to avoid concurrent
+  // release of arenas when it is visiting them.
+  std::mutex& GetLock() { return lock_; }
+
+  // Find the given arena in allocated_arenas_. The function is called with
+  // lock_ acquired.
+  bool FindAllocatedArena(const TrackedArena* arena) const NO_THREAD_SAFETY_ANALYSIS {
+    for (auto& allocated_arena : allocated_arenas_) {
+      if (arena == &allocated_arena) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  void ClearArenasFreed() {
+    std::lock_guard<std::mutex> lock(lock_);
+    arenas_freed_ = false;
+  }
+
+  // The function is called with lock_ acquired.
+  bool AreArenasFreed() const NO_THREAD_SAFETY_ANALYSIS { return arenas_freed_; }
+
+ private:
+  void FreeRangeLocked(uint8_t* range_begin, size_t range_size) REQUIRES(lock_);
+  // Add a map (to be visited by userfaultfd) to the pool of at least min_size
+  // and return its address.
+  uint8_t* AddMap(size_t min_size) REQUIRES(lock_);
+  // Add a private anonymous map prior to zygote fork to the pool and return its
+  // address.
+  uint8_t* AddPreZygoteForkMap(size_t size) REQUIRES(lock_);
+
+  class Chunk {
+   public:
+    Chunk(uint8_t* addr, size_t size) : addr_(addr), size_(size) {}
+    uint8_t* addr_;
+    size_t size_;
+  };
+
+  class LessByChunkAddr {
+   public:
+    bool operator()(const Chunk* a, const Chunk* b) const {
+      return std::less<uint8_t*>{}(a->addr_, b->addr_);
+    }
+  };
+
+  class LessByChunkSize {
+   public:
+    // Since two chunks could have the same size, use addr when that happens.
+    bool operator()(const Chunk* a, const Chunk* b) const {
+      return a->size_ < b->size_ ||
+             (a->size_ == b->size_ && std::less<uint8_t*>{}(a->addr_, b->addr_));
+    }
+  };
+
+  class LessByArenaAddr {
+   public:
+    bool operator()(const TrackedArena& a, const TrackedArena& b) const {
+      return std::less<uint8_t*>{}(a.Begin(), b.Begin());
+    }
+  };
+
+  // Use a std::mutex here as Arenas are second-from-the-bottom when using MemMaps, and MemMap
+  // itself uses std::mutex scoped to within an allocate/free only.
+  mutable std::mutex lock_;
+  std::vector<MemMap> maps_ GUARDED_BY(lock_);
+  std::set<Chunk*, LessByChunkSize> best_fit_allocs_ GUARDED_BY(lock_);
+  std::set<Chunk*, LessByChunkAddr> free_chunks_ GUARDED_BY(lock_);
+  // Set of allocated arenas. It's required to be able to find the arena
+  // corresponding to a given address.
+  // TODO: consider using HashSet, which is more memory efficient.
+  std::set<TrackedArena, LessByArenaAddr> allocated_arenas_ GUARDED_BY(lock_);
+  // Number of bytes allocated so far.
+  size_t bytes_allocated_ GUARDED_BY(lock_);
+  const char* name_;
+  // Flag to indicate that some arenas have been freed. This flag is used as an
+  // optimization by GC to know if it needs to find if the arena being visited
+  // has been freed or not. The flag is cleared in the compaction pause and read
+  // when linear-alloc space is concurrently visited updated to update GC roots.
+  bool arenas_freed_ GUARDED_BY(lock_);
+  const bool low_4gb_;
+  // Set to true in zygote process so that all linear-alloc allocations are in
+  // private-anonymous mappings and not on userfaultfd visited pages. At
+  // first zygote fork, it's set to false, after which all allocations are done
+  // in userfaultfd visited space.
+  bool pre_zygote_fork_ GUARDED_BY(lock_);
+
+  DISALLOW_COPY_AND_ASSIGN(GcVisitedArenaPool);
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_BASE_GC_VISITED_ARENA_POOL_H_
diff --git a/runtime/base/locks.h b/runtime/base/locks.h
index 829adff..c15e5de 100644
--- a/runtime/base/locks.h
+++ b/runtime/base/locks.h
@@ -68,12 +68,12 @@
   // Can be held while GC related work is done, and thus must be above kMarkSweepMarkStackLock
   kThreadWaitLock,
   kCHALock,
-  kJitCodeCacheLock,
   kRosAllocGlobalLock,
   kRosAllocBracketLock,
   kRosAllocBulkFreeLock,
   kAllocSpaceLock,
   kTaggingLockLevel,
+  kJitCodeCacheLock,
   kTransactionLogLock,
   kCustomTlsLock,
   kJniFunctionTableLock,
diff --git a/runtime/base/mem_map_arena_pool.cc b/runtime/base/mem_map_arena_pool.cc
index ae7db45..fc1a61e 100644
--- a/runtime/base/mem_map_arena_pool.cc
+++ b/runtime/base/mem_map_arena_pool.cc
@@ -57,13 +57,24 @@
   // and we want to be able to use all memory that we actually allocate.
   size = RoundUp(size, kPageSize);
   std::string error_msg;
-  MemMap map = MemMap::MapAnonymous(name,
-                                    size,
-                                    PROT_READ | PROT_WRITE,
-                                    low_4gb,
-                                    &error_msg);
-  CHECK(map.IsValid()) << error_msg;
-  return map;
+  // TODO(b/278665389): remove this retry logic if the root cause is found.
+  constexpr int MAX_RETRY_CNT = 3;
+  int retry_cnt = 0;
+  while (true) {
+    MemMap map = MemMap::MapAnonymous(name, size, PROT_READ | PROT_WRITE, low_4gb, &error_msg);
+    if (map.IsValid()) {
+      if (retry_cnt > 0) {
+        LOG(WARNING) << "Succeed with retry(cnt=" << retry_cnt << ")";
+      }
+      return map;
+    } else {
+      if (retry_cnt == MAX_RETRY_CNT) {
+        CHECK(map.IsValid()) << error_msg << "(retried " << retry_cnt << " times)";
+      }
+    }
+    retry_cnt++;
+    LOG(ERROR) << error_msg << " but retry(cnt=" << retry_cnt << ")";
+  }
 }
 
 MemMapArena::~MemMapArena() {
diff --git a/runtime/base/message_queue_test.cc b/runtime/base/message_queue_test.cc
index 7a788a9..09dbc32 100644
--- a/runtime/base/message_queue_test.cc
+++ b/runtime/base/message_queue_test.cc
@@ -20,10 +20,16 @@
 
 #include "common_runtime_test.h"
 #include "thread-current-inl.h"
+#include "runtime.h"
 
 namespace art {
 
-class MessageQueueTest : public CommonRuntimeTest {};
+class MessageQueueTest : public CommonRuntimeTest {
+ protected:
+  MessageQueueTest() {
+    this->use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+};
 
 namespace {
 
@@ -81,6 +87,8 @@
 }
 
 TEST_F(MessageQueueTest, TwoWayMessaging) {
+  CHECK(Runtime::Current() != nullptr);  // Runtime is needed by Mutex.
+
   TestMessageQueue queue1;
   TestMessageQueue queue2;
 
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index 5709333..728dc84 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -28,6 +28,7 @@
 #include "base/systrace.h"
 #include "base/time_utils.h"
 #include "base/value_object.h"
+#include "monitor.h"
 #include "mutex-inl.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
@@ -59,18 +60,19 @@
 };
 
 #if ART_USE_FUTEXES
+// Compute a relative timespec as *result_ts = lhs - rhs.
+// Return false (and produce an invalid *result_ts) if lhs < rhs.
 static bool ComputeRelativeTimeSpec(timespec* result_ts, const timespec& lhs, const timespec& rhs) {
   const int32_t one_sec = 1000 * 1000 * 1000;  // one second in nanoseconds.
+  static_assert(std::is_signed<decltype(result_ts->tv_sec)>::value);  // Signed on Linux.
   result_ts->tv_sec = lhs.tv_sec - rhs.tv_sec;
   result_ts->tv_nsec = lhs.tv_nsec - rhs.tv_nsec;
   if (result_ts->tv_nsec < 0) {
     result_ts->tv_sec--;
     result_ts->tv_nsec += one_sec;
-  } else if (result_ts->tv_nsec > one_sec) {
-    result_ts->tv_sec++;
-    result_ts->tv_nsec -= one_sec;
   }
-  return result_ts->tv_sec < 0;
+  DCHECK(result_ts->tv_nsec >= 0 && result_ts->tv_nsec < one_sec);
+  return result_ts->tv_sec >= 0;
 }
 #endif
 
@@ -462,7 +464,10 @@
           do {
             timespec timeout_ts;
             timeout_ts.tv_sec = 0;
-            timeout_ts.tv_nsec = Runtime::Current()->GetMonitorTimeoutNs();
+            // NB: Some tests use the mutex without the runtime.
+            timeout_ts.tv_nsec = Runtime::Current() != nullptr
+                ? Runtime::Current()->GetMonitorTimeoutNs()
+                : Monitor::kDefaultMonitorTimeoutMs;
             if (futex(state_and_contenders_.Address(), FUTEX_WAIT_PRIVATE, cur_state,
                       enable_monitor_timeout_ ? &timeout_ts : nullptr , nullptr, 0) != 0) {
               // We only went to sleep after incrementing and contenders and checking that the
@@ -512,6 +517,7 @@
   Locks::thread_list_lock_->ExclusiveLock(self);
   std::string owner_stack_dump;
   pid_t owner_tid = GetExclusiveOwnerTid();
+  CHECK(Runtime::Current() != nullptr);
   Thread *owner = Runtime::Current()->GetThreadList()->FindThreadByTid(owner_tid);
   if (owner != nullptr) {
     if (IsDumpFrequent(owner, try_times)) {
@@ -852,7 +858,7 @@
       timespec now_abs_ts;
       InitTimeSpec(true, CLOCK_MONOTONIC, 0, 0, &now_abs_ts);
       timespec rel_ts;
-      if (ComputeRelativeTimeSpec(&rel_ts, end_abs_ts, now_abs_ts)) {
+      if (!ComputeRelativeTimeSpec(&rel_ts, end_abs_ts, now_abs_ts)) {
         return false;  // Timed out.
       }
       ScopedContentionRecorder scr(this, SafeGetTid(self), GetExclusiveOwnerTid());
@@ -869,6 +875,7 @@
             // EAGAIN and EINTR both indicate a spurious failure,
             // recompute the relative time out from now and try again.
             // We don't use TEMP_FAILURE_RETRY so we can recompute rel_ts;
+            num_contenders_.fetch_sub(1);  // Unlikely to matter.
             PLOG(FATAL) << "timed futex wait failed for " << name_;
           }
         }
diff --git a/runtime/base/mutex_test.cc b/runtime/base/mutex_test.cc
index 7eba50b..f1b4e49 100644
--- a/runtime/base/mutex_test.cc
+++ b/runtime/base/mutex_test.cc
@@ -21,7 +21,12 @@
 
 namespace art {
 
-class MutexTest : public CommonRuntimeTest {};
+class MutexTest : public CommonRuntimeTest {
+ protected:
+  MutexTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+};
 
 struct MutexTester {
   static void AssertDepth(Mutex& mu, uint32_t expected_depth) {
@@ -37,6 +42,9 @@
 };
 
 TEST_F(MutexTest, LockUnlock) {
+  // TODO: Remove `Mutex` dependency on `Runtime` or at least make sure it works
+  // without a `Runtime` with reasonable defaults (and without dumping stack for timeout).
+  ASSERT_TRUE(Runtime::Current() != nullptr);
   Mutex mu("test mutex");
   MutexTester::AssertDepth(mu, 0U);
   mu.Lock(Thread::Current());
diff --git a/runtime/base/timing_logger.cc b/runtime/base/timing_logger.cc
index abf4f58..c39b44e 100644
--- a/runtime/base/timing_logger.cc
+++ b/runtime/base/timing_logger.cc
@@ -33,8 +33,6 @@
 
 namespace art {
 
-constexpr size_t TimingLogger::kIndexNotFound;
-
 CumulativeLogger::CumulativeLogger(const std::string& name)
     : name_(name),
       lock_name_("CumulativeLoggerLock" + name),
diff --git a/runtime/base/timing_logger_test.cc b/runtime/base/timing_logger_test.cc
index 6f8d8cd..38ae9a5 100644
--- a/runtime/base/timing_logger_test.cc
+++ b/runtime/base/timing_logger_test.cc
@@ -16,11 +16,11 @@
 
 #include "timing_logger.h"
 
-#include "common_runtime_test.h"
+#include "base/common_art_test.h"
 
 namespace art {
 
-class TimingLoggerTest : public CommonRuntimeTest {};
+class TimingLoggerTest : public CommonArtTest {};
 
 // TODO: Negative test cases (improper pairing of EndSplit, etc.)
 
diff --git a/runtime/cha.cc b/runtime/cha.cc
index d19d4f6..a40afb4 100644
--- a/runtime/cha.cc
+++ b/runtime/cha.cc
@@ -144,14 +144,14 @@
       ArtMethod* super_method = super_it->
           GetVTableEntry<kDefaultVerifyFlags, kWithoutReadBarrier>(vtbl_index, pointer_size);
       if (super_method->IsAbstract() &&
-          super_method->HasSingleImplementation<kWithoutReadBarrier>() &&
+          super_method->HasSingleImplementation() &&
           super_method->GetSingleImplementation(pointer_size) == method) {
         // Do like there was no single implementation defined previously
         // for this method of the superclass.
         super_method->SetSingleImplementation(nullptr, pointer_size);
       } else {
         // No related SingleImplementations could possibly be found any further.
-        DCHECK(!super_method->HasSingleImplementation<kWithoutReadBarrier>());
+        DCHECK(!super_method->HasSingleImplementation());
         break;
       }
     }
@@ -168,7 +168,7 @@
          j < count;
          ++j) {
       ArtMethod* method = interface->GetVirtualMethod(j, pointer_size);
-      if (method->HasSingleImplementation<kWithoutReadBarrier>() &&
+      if (method->HasSingleImplementation() &&
           alloc->ContainsUnsafe(method->GetSingleImplementation(pointer_size)) &&
           !method->IsDefault()) {
         // Do like there was no single implementation defined previously for this method.
@@ -692,8 +692,9 @@
   }
 }
 
-void ClassHierarchyAnalysis::RemoveDependenciesForLinearAlloc(const LinearAlloc* linear_alloc) {
-  MutexLock mu(Thread::Current(), *Locks::cha_lock_);
+void ClassHierarchyAnalysis::RemoveDependenciesForLinearAlloc(Thread* self,
+                                                              const LinearAlloc* linear_alloc) {
+  MutexLock mu(self, *Locks::cha_lock_);
   for (auto it = cha_dependency_map_.begin(); it != cha_dependency_map_.end(); ) {
     // Use unsafe to avoid locking since the allocator is going to be deleted.
     if (linear_alloc->ContainsUnsafe(it->first)) {
diff --git a/runtime/cha.h b/runtime/cha.h
index 14af43e..5a403ec 100644
--- a/runtime/cha.h
+++ b/runtime/cha.h
@@ -126,7 +126,7 @@
 
   // Remove all of the dependencies for a linear allocator. This is called when dex cache unloading
   // occurs.
-  void RemoveDependenciesForLinearAlloc(const LinearAlloc* linear_alloc)
+  void RemoveDependenciesForLinearAlloc(Thread* self, const LinearAlloc* linear_alloc)
       REQUIRES(!Locks::cha_lock_);
 
  private:
diff --git a/runtime/cha_test.cc b/runtime/cha_test.cc
index c60720f..48fd06d 100644
--- a/runtime/cha_test.cc
+++ b/runtime/cha_test.cc
@@ -16,11 +16,12 @@
 
 #include "cha.h"
 
-#include "common_runtime_test.h"
+#include "base/common_art_test.h"
+#include "thread-current-inl.h"
 
 namespace art {
 
-class CHATest : public CommonRuntimeTest {};
+class CHATest : public CommonArtTest {};
 
 // Mocks some methods.
 #define METHOD1 (reinterpret_cast<ArtMethod*>(8u))
diff --git a/runtime/check_reference_map_visitor.h b/runtime/check_reference_map_visitor.h
index a7c3e45..d03139b 100644
--- a/runtime/check_reference_map_visitor.h
+++ b/runtime/check_reference_map_visitor.h
@@ -91,7 +91,7 @@
     CodeItemDataAccessor accessor(m->DexInstructionData());
     uint16_t number_of_dex_registers = accessor.RegistersSize();
 
-    if (!Runtime::Current()->IsAsyncDeoptimizeable(GetCurrentQuickFramePc())) {
+    if (!Runtime::Current()->IsAsyncDeoptimizeable(GetOuterMethod(), GetCurrentQuickFramePc())) {
       // We can only guarantee dex register info presence for debuggable methods.
       return;
     }
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index 02b2778..c4f3b15 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -24,6 +24,7 @@
 #include "art_method-inl.h"
 #include "base/mutex.h"
 #include "class_linker.h"
+#include "class_table-inl.h"
 #include "dex/dex_file.h"
 #include "dex/dex_file_structs.h"
 #include "gc_root-inl.h"
@@ -70,12 +71,10 @@
                                                          ArtField* referrer) {
   Thread::PoisonObjectPointersIfDebug();
   DCHECK(!Thread::Current()->IsExceptionPending());
-  // We do not need the read barrier for getting the DexCache for the initial resolved type
-  // lookup as both from-space and to-space copies point to the same native resolved types array.
-  ObjPtr<mirror::String> resolved =
-      referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedString(string_idx);
+  ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
+  ObjPtr<mirror::String> resolved = dex_cache->GetResolvedString(string_idx);
   if (resolved == nullptr) {
-    resolved = DoResolveString(string_idx, referrer->GetDexCache());
+    resolved = DoResolveString(string_idx, dex_cache);
   }
   return resolved;
 }
@@ -84,12 +83,10 @@
                                                          ArtMethod* referrer) {
   Thread::PoisonObjectPointersIfDebug();
   DCHECK(!Thread::Current()->IsExceptionPending());
-  // We do not need the read barrier for getting the DexCache for the initial resolved type
-  // lookup as both from-space and to-space copies point to the same native resolved types array.
-  ObjPtr<mirror::String> resolved =
-      referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedString(string_idx);
+  ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
+  ObjPtr<mirror::String> resolved = dex_cache->GetResolvedString(string_idx);
   if (resolved == nullptr) {
-    resolved = DoResolveString(string_idx, referrer->GetDexCache());
+    resolved = DoResolveString(string_idx, dex_cache);
   }
   return resolved;
 }
@@ -122,10 +119,7 @@
     Thread::Current()->PoisonObjectPointers();
   }
   DCHECK(!Thread::Current()->IsExceptionPending());
-  // We do not need the read barrier for getting the DexCache for the initial resolved type
-  // lookup as both from-space and to-space copies point to the same native resolved types array.
-  ObjPtr<mirror::Class> resolved_type =
-      referrer->GetDexCache<kDefaultVerifyFlags, kWithoutReadBarrier>()->GetResolvedType(type_idx);
+  ObjPtr<mirror::Class> resolved_type = referrer->GetDexCache()->GetResolvedType(type_idx);
   if (resolved_type == nullptr) {
     resolved_type = DoResolveType(type_idx, referrer);
   }
@@ -136,10 +130,7 @@
                                                       ArtField* referrer) {
   Thread::PoisonObjectPointersIfDebug();
   DCHECK(!Thread::Current()->IsExceptionPending());
-  // We do not need the read barrier for getting the DexCache for the initial resolved type
-  // lookup as both from-space and to-space copies point to the same native resolved types array.
-  ObjPtr<mirror::Class> resolved_type =
-      referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedType(type_idx);
+  ObjPtr<mirror::Class> resolved_type = referrer->GetDexCache()->GetResolvedType(type_idx);
   if (UNLIKELY(resolved_type == nullptr)) {
     resolved_type = DoResolveType(type_idx, referrer);
   }
@@ -150,10 +141,7 @@
                                                       ArtMethod* referrer) {
   Thread::PoisonObjectPointersIfDebug();
   DCHECK(!Thread::Current()->IsExceptionPending());
-  // We do not need the read barrier for getting the DexCache for the initial resolved type
-  // lookup as both from-space and to-space copies point to the same native resolved types array.
-  ObjPtr<mirror::Class> resolved_type =
-      referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedType(type_idx);
+  ObjPtr<mirror::Class> resolved_type = referrer->GetDexCache()->GetResolvedType(type_idx);
   if (UNLIKELY(resolved_type == nullptr)) {
     resolved_type = DoResolveType(type_idx, referrer);
   }
@@ -175,10 +163,7 @@
 
 inline ObjPtr<mirror::Class> ClassLinker::LookupResolvedType(dex::TypeIndex type_idx,
                                                              ObjPtr<mirror::Class> referrer) {
-  // We do not need the read barrier for getting the DexCache for the initial resolved type
-  // lookup as both from-space and to-space copies point to the same native resolved types array.
-  ObjPtr<mirror::Class> type =
-      referrer->GetDexCache<kDefaultVerifyFlags, kWithoutReadBarrier>()->GetResolvedType(type_idx);
+  ObjPtr<mirror::Class> type = referrer->GetDexCache()->GetResolvedType(type_idx);
   if (type == nullptr) {
     type = DoLookupResolvedType(type_idx, referrer);
   }
@@ -189,8 +174,7 @@
                                                              ArtField* referrer) {
   // We do not need the read barrier for getting the DexCache for the initial resolved type
   // lookup as both from-space and to-space copies point to the same native resolved types array.
-  ObjPtr<mirror::Class> type =
-      referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedType(type_idx);
+  ObjPtr<mirror::Class> type = referrer->GetDexCache()->GetResolvedType(type_idx);
   if (type == nullptr) {
     type = DoLookupResolvedType(type_idx, referrer->GetDeclaringClass());
   }
@@ -201,8 +185,7 @@
                                                              ArtMethod* referrer) {
   // We do not need the read barrier for getting the DexCache for the initial resolved type
   // lookup as both from-space and to-space copies point to the same native resolved types array.
-  ObjPtr<mirror::Class> type =
-      referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedType(type_idx);
+  ObjPtr<mirror::Class> type = referrer->GetDexCache()->GetResolvedType(type_idx);
   if (type == nullptr) {
     type = DoLookupResolvedType(type_idx, referrer->GetDeclaringClass());
   }
@@ -304,129 +287,32 @@
   return resolved;
 }
 
-template <InvokeType type, ClassLinker::ResolveMode kResolveMode>
-inline ArtMethod* ClassLinker::GetResolvedMethod(uint32_t method_idx, ArtMethod* referrer) {
-  DCHECK(referrer != nullptr);
-  // Note: The referrer can be a Proxy constructor. In that case, we need to do the
-  // lookup in the context of the original method from where it steals the code.
-  // However, we delay the GetInterfaceMethodIfProxy() until needed.
-  DCHECK_IMPLIES(referrer->IsProxyMethod(), referrer->IsConstructor());
-  // We do not need the read barrier for getting the DexCache for the initial resolved method
-  // lookup as both from-space and to-space copies point to the same native resolved methods array.
-  ArtMethod* resolved_method = referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedMethod(
-      method_idx);
-  if (resolved_method == nullptr) {
-    return nullptr;
-  }
-  DCHECK(!resolved_method->IsRuntimeMethod());
-  if (kResolveMode == ResolveMode::kCheckICCEAndIAE) {
-    referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_);
-    // Check if the invoke type matches the class type.
-    ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
-    ObjPtr<mirror::ClassLoader> class_loader = referrer->GetClassLoader();
-    const dex::MethodId& method_id = referrer->GetDexFile()->GetMethodId(method_idx);
-    ObjPtr<mirror::Class> cls = LookupResolvedType(method_id.class_idx_, dex_cache, class_loader);
-    if (cls == nullptr) {
-      // The verifier breaks the invariant that a resolved method must have its
-      // class in the class table. Because this method should only lookup and not
-      // resolve class, return null. The caller is responsible for calling
-      // `ResolveMethod` afterwards.
-      // b/73760543
-      return nullptr;
-    }
-    if (CheckInvokeClassMismatch</* kThrow= */ false>(dex_cache, type, method_idx, class_loader)) {
-      return nullptr;
-    }
-    // Check access.
-    ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
-    if (!referring_class->CanAccessResolvedMethod(resolved_method->GetDeclaringClass(),
-                                                  resolved_method,
-                                                  dex_cache,
-                                                  method_idx)) {
-      return nullptr;
-    }
-    // Check if the invoke type matches the method type.
-    if (UNLIKELY(resolved_method->CheckIncompatibleClassChange(type))) {
-      return nullptr;
-    }
-  }
-  return resolved_method;
-}
-
 template <ClassLinker::ResolveMode kResolveMode>
 inline ArtMethod* ClassLinker::ResolveMethod(Thread* self,
                                              uint32_t method_idx,
                                              ArtMethod* referrer,
                                              InvokeType type) {
   DCHECK(referrer != nullptr);
-  // Note: The referrer can be a Proxy constructor. In that case, we need to do the
-  // lookup in the context of the original method from where it steals the code.
-  // However, we delay the GetInterfaceMethodIfProxy() until needed.
   DCHECK_IMPLIES(referrer->IsProxyMethod(), referrer->IsConstructor());
+
   Thread::PoisonObjectPointersIfDebug();
-  // We do not need the read barrier for getting the DexCache for the initial resolved method
-  // lookup as both from-space and to-space copies point to the same native resolved methods array.
-  ArtMethod* resolved_method = referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedMethod(
-      method_idx);
-  DCHECK(resolved_method == nullptr || !resolved_method->IsRuntimeMethod());
-  if (UNLIKELY(resolved_method == nullptr)) {
-    referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_);
-    ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass();
-    StackHandleScope<2> hs(self);
-    Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(referrer->GetDexCache()));
-    Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(declaring_class->GetClassLoader()));
-    resolved_method = ResolveMethod<kResolveMode>(method_idx,
-                                                  h_dex_cache,
-                                                  h_class_loader,
-                                                  referrer,
-                                                  type);
-  } else if (kResolveMode == ResolveMode::kCheckICCEAndIAE) {
-    referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_);
-    const dex::MethodId& method_id = referrer->GetDexFile()->GetMethodId(method_idx);
-    ObjPtr<mirror::Class> cls =
-        LookupResolvedType(method_id.class_idx_,
-                           referrer->GetDexCache(),
-                           referrer->GetClassLoader());
-    if (cls == nullptr) {
-      // The verifier breaks the invariant that a resolved method must have its
-      // class in the class table, so resolve the type in case we haven't found it.
-      // b/73760543
-      StackHandleScope<2> hs(Thread::Current());
-      Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(referrer->GetDexCache()));
-      Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(referrer->GetClassLoader()));
-      cls = ResolveType(method_id.class_idx_, h_dex_cache, h_class_loader);
-      if (hs.Self()->IsExceptionPending()) {
-        return nullptr;
-      }
-    }
-    // Check if the invoke type matches the class type.
-    if (CheckInvokeClassMismatch</* kThrow= */ true>(
-            referrer->GetDexCache(), type, [cls]() { return cls; })) {
-      DCHECK(Thread::Current()->IsExceptionPending());
-      return nullptr;
-    }
-    // Check access.
-    ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
-    if (!referring_class->CheckResolvedMethodAccess(resolved_method->GetDeclaringClass(),
-                                                    resolved_method,
-                                                    referrer->GetDexCache(),
-                                                    method_idx,
-                                                    type)) {
-      DCHECK(Thread::Current()->IsExceptionPending());
-      return nullptr;
-    }
-    // Check if the invoke type matches the method type.
-    if (UNLIKELY(resolved_method->CheckIncompatibleClassChange(type))) {
-      ThrowIncompatibleClassChangeError(type,
-                                        resolved_method->GetInvokeType(),
-                                        resolved_method,
-                                        referrer);
-      return nullptr;
+  // Fast path: no checks and in the dex cache.
+  if (kResolveMode == ResolveMode::kNoChecks) {
+    ArtMethod* resolved_method = referrer->GetDexCache()->GetResolvedMethod(method_idx);
+    if (resolved_method != nullptr) {
+      DCHECK(!resolved_method->IsRuntimeMethod());
+      return resolved_method;
     }
   }
-  // Note: We cannot check here to see whether we added the method to the cache. It
-  //       might be an erroneous class, which results in it being hidden from us.
-  return resolved_method;
+
+  // For a Proxy constructor, we need to do the lookup in the context of the original method
+  // from where it steals the code.
+  referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_);
+  StackHandleScope<2> hs(self);
+  Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache()));
+  Handle<mirror::ClassLoader> class_loader(
+      hs.NewHandle(referrer->GetDeclaringClass()->GetClassLoader()));
+  return ResolveMethod<kResolveMode>(method_idx, dex_cache, class_loader, referrer, type);
 }
 
 template <ClassLinker::ResolveMode kResolveMode>
@@ -435,10 +321,11 @@
                                              Handle<mirror::ClassLoader> class_loader,
                                              ArtMethod* referrer,
                                              InvokeType type) {
+  DCHECK(dex_cache != nullptr);
   DCHECK(dex_cache->GetClassLoader() == class_loader.Get());
   DCHECK(!Thread::Current()->IsExceptionPending()) << Thread::Current()->GetException()->Dump();
-  DCHECK(dex_cache != nullptr);
   DCHECK(referrer == nullptr || !referrer->IsProxyMethod());
+
   // Check for hit in the dex cache.
   ArtMethod* resolved = dex_cache->GetResolvedMethod(method_idx);
   Thread::PoisonObjectPointersIfDebug();
@@ -475,6 +362,11 @@
       DCHECK(Thread::Current()->IsExceptionPending());
       return nullptr;
     }
+    // Look for the method again in case the type resolution updated the cache.
+    resolved = dex_cache->GetResolvedMethod(method_idx);
+    if (kResolveMode == ResolveMode::kNoChecks && resolved != nullptr) {
+      return resolved;
+    }
   }
 
   // Check if the invoke type matches the class type.
@@ -493,12 +385,20 @@
   if (kResolveMode == ResolveMode::kCheckICCEAndIAE && resolved != nullptr && referrer != nullptr) {
     ObjPtr<mirror::Class> methods_class = resolved->GetDeclaringClass();
     ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
-    if (!referring_class->CheckResolvedMethodAccess(methods_class,
-                                                    resolved,
-                                                    dex_cache.Get(),
-                                                    method_idx,
-                                                    type)) {
-      DCHECK(Thread::Current()->IsExceptionPending());
+    if (UNLIKELY(!referring_class->CanAccess(methods_class))) {
+      // The referrer class can't access the method's declaring class but may still be able
+      // to access the method if the MethodId specifies an accessible subclass of the declaring
+      // class rather than the declaring class itself.
+      if (UNLIKELY(!referring_class->CanAccess(klass))) {
+        ThrowIllegalAccessErrorClassForMethodDispatch(referring_class,
+                                                      klass,
+                                                      resolved,
+                                                      type);
+        return nullptr;
+      }
+    }
+    if (UNLIKELY(!referring_class->CanAccessMember(methods_class, resolved->GetAccessFlags()))) {
+      ThrowIllegalAccessErrorMethod(referring_class, resolved);
       return nullptr;
     }
   }
@@ -508,36 +408,34 @@
       LIKELY(kResolveMode == ResolveMode::kNoChecks ||
              !resolved->CheckIncompatibleClassChange(type))) {
     return resolved;
-  } else {
-    // If we had a method, or if we can find one with another lookup type,
-    // it's an incompatible-class-change error.
-    if (resolved == nullptr) {
-      resolved = FindIncompatibleMethod(klass, dex_cache.Get(), class_loader.Get(), method_idx);
-    }
-    if (resolved != nullptr) {
-      ThrowIncompatibleClassChangeError(type, resolved->GetInvokeType(), resolved, referrer);
-    } else {
-      // We failed to find the method (using all lookup types), so throw a NoSuchMethodError.
-      const char* name = dex_file.StringDataByIdx(method_id.name_idx_);
-      const Signature signature = dex_file.GetMethodSignature(method_id);
-      ThrowNoSuchMethodError(type, klass, name, signature);
-    }
-    Thread::Current()->AssertPendingException();
-    return nullptr;
   }
+
+  // If we had a method, or if we can find one with another lookup type,
+  // it's an incompatible-class-change error.
+  if (resolved == nullptr) {
+    resolved = FindIncompatibleMethod(klass, dex_cache.Get(), class_loader.Get(), method_idx);
+  }
+  if (resolved != nullptr) {
+    ThrowIncompatibleClassChangeError(type, resolved->GetInvokeType(), resolved, referrer);
+  } else {
+    // We failed to find the method (using all lookup types), so throw a NoSuchMethodError.
+    const char* name = dex_file.StringDataByIdx(method_id.name_idx_);
+    const Signature signature = dex_file.GetMethodSignature(method_id);
+    ThrowNoSuchMethodError(type, klass, name, signature);
+  }
+  Thread::Current()->AssertPendingException();
+  return nullptr;
 }
 
 inline ArtField* ClassLinker::LookupResolvedField(uint32_t field_idx,
                                                   ArtMethod* referrer,
                                                   bool is_static) {
-  // We do not need the read barrier for getting the DexCache for the initial resolved field
-  // lookup as both from-space and to-space copies point to the same native resolved fields array.
-  ArtField* field = referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedField(
-      field_idx);
+  ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
+  ArtField* field = dex_cache->GetResolvedField(field_idx);
   if (field == nullptr) {
     referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_);
     ObjPtr<mirror::ClassLoader> class_loader = referrer->GetDeclaringClass()->GetClassLoader();
-    field = LookupResolvedField(field_idx, referrer->GetDexCache(), class_loader, is_static);
+    field = LookupResolvedField(field_idx, dex_cache, class_loader, is_static);
   }
   return field;
 }
@@ -546,17 +444,15 @@
                                            ArtMethod* referrer,
                                            bool is_static) {
   Thread::PoisonObjectPointersIfDebug();
-  // We do not need the read barrier for getting the DexCache for the initial resolved field
-  // lookup as both from-space and to-space copies point to the same native resolved fields array.
-  ArtField* resolved_field = referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedField(
-      field_idx);
+  ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
+  ArtField* resolved_field = dex_cache->GetResolvedField(field_idx);
   if (UNLIKELY(resolved_field == nullptr)) {
     StackHandleScope<2> hs(Thread::Current());
     referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_);
     ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
-    Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache()));
+    Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(dex_cache));
     Handle<mirror::ClassLoader> class_loader(hs.NewHandle(referring_class->GetClassLoader()));
-    resolved_field = ResolveField(field_idx, dex_cache, class_loader, is_static);
+    resolved_field = ResolveField(field_idx, h_dex_cache, class_loader, is_static);
     // Note: We cannot check here to see whether we added the field to the cache. The type
     //       might be an erroneous class, which results in it being hidden from us.
   }
@@ -583,6 +479,12 @@
     return nullptr;
   }
 
+  // Look for the field again in case the type resolution updated the cache.
+  resolved = dex_cache->GetResolvedField(field_idx);
+  if (resolved != nullptr) {
+    return resolved;
+  }
+
   resolved = FindResolvedField(klass, dex_cache.Get(), class_loader.Get(), field_idx, is_static);
   if (resolved == nullptr) {
     const char* name = dex_file.GetFieldName(field_id);
@@ -592,6 +494,11 @@
   return resolved;
 }
 
+template <typename Visitor>
+inline void ClassLinker::VisitBootClasses(Visitor* visitor) {
+  boot_class_table_->Visit(*visitor);
+}
+
 template <class Visitor>
 inline void ClassLinker::VisitClassTables(const Visitor& visitor) {
   Thread* const self = Thread::Current();
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index c8dbc75..ecb2fcf 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -22,6 +22,7 @@
 #include <deque>
 #include <forward_list>
 #include <iostream>
+#include <iterator>
 #include <map>
 #include <memory>
 #include <queue>
@@ -32,17 +33,19 @@
 #include <vector>
 
 #include "android-base/stringprintf.h"
-
+#include "android-base/strings.h"
 #include "art_field-inl.h"
 #include "art_method-inl.h"
 #include "barrier.h"
 #include "base/arena_allocator.h"
+#include "base/arena_bit_vector.h"
 #include "base/casts.h"
 #include "base/file_utils.h"
 #include "base/hash_map.h"
 #include "base/hash_set.h"
 #include "base/leb128.h"
 #include "base/logging.h"
+#include "base/mem_map_arena_pool.h"
 #include "base/metrics/metrics.h"
 #include "base/mutex-inl.h"
 #include "base/os.h"
@@ -67,6 +70,7 @@
 #include "dex/class_accessor-inl.h"
 #include "dex/descriptors_names.h"
 #include "dex/dex_file-inl.h"
+#include "dex/dex_file_annotations.h"
 #include "dex/dex_file_exception_helpers.h"
 #include "dex/dex_file_loader.h"
 #include "dex/signature-inl.h"
@@ -96,7 +100,7 @@
 #include "jit/jit_code_cache.h"
 #include "jni/java_vm_ext.h"
 #include "jni/jni_internal.h"
-#include "linear_alloc.h"
+#include "linear_alloc-inl.h"
 #include "mirror/array-alloc-inl.h"
 #include "mirror/array-inl.h"
 #include "mirror/call_site.h"
@@ -120,8 +124,8 @@
 #include "mirror/object_array-alloc-inl.h"
 #include "mirror/object_array-inl.h"
 #include "mirror/object_array.h"
-#include "mirror/object_reference.h"
 #include "mirror/object_reference-inl.h"
+#include "mirror/object_reference.h"
 #include "mirror/proxy.h"
 #include "mirror/reference-inl.h"
 #include "mirror/stack_trace_element.h"
@@ -141,6 +145,7 @@
 #include "runtime.h"
 #include "runtime_callbacks.h"
 #include "scoped_thread_state_change-inl.h"
+#include "startup_completed_task.h"
 #include "thread-inl.h"
 #include "thread.h"
 #include "thread_list.h"
@@ -270,7 +275,6 @@
   }
 
   void Run(Thread* self) override {
-    self->ClearMakeVisiblyInitializedCounter();
     AdjustThreadVisibilityCounter(self, -1);
   }
 
@@ -298,7 +302,13 @@
     }
   }
 
-  static constexpr size_t kMaxClasses = 16;
+  // Making classes initialized in bigger batches helps with app startup for
+  // apps that initialize a lot of classes by running fewer checkpoints.
+  // (On the other hand, bigger batches make class initialization checks more
+  // likely to take a slow path but that is mitigated by making partially
+  // filled buffers visibly initialized if we take the slow path many times.
+  // See `Thread::kMakeVisiblyInitializedCounterTriggerCount`.)
+  static constexpr size_t kMaxClasses = 48;
 
   ClassLinker* const class_linker_;
   size_t num_classes_;
@@ -321,6 +331,7 @@
   }
   std::optional<Barrier> maybe_barrier;  // Avoid constructing the Barrier for `wait == false`.
   if (wait) {
+    Locks::mutator_lock_->AssertNotHeld(self);
     maybe_barrier.emplace(0);
   }
   int wait_count = 0;
@@ -534,10 +545,9 @@
 static void WrapExceptionInInitializer(Handle<mirror::Class> klass)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   Thread* self = Thread::Current();
-  JNIEnv* env = self->GetJniEnv();
 
-  ScopedLocalRef<jthrowable> cause(env, env->ExceptionOccurred());
-  CHECK(cause.get() != nullptr);
+  ObjPtr<mirror::Throwable> cause = self->GetException();
+  CHECK(cause != nullptr);
 
   // Boot classpath classes should not fail initialization. This is a consistency debug check.
   // This cannot in general be guaranteed, but in all likelihood leads to breakage down the line.
@@ -552,12 +562,8 @@
                                             << self->GetException()->Dump();
   }
 
-  env->ExceptionClear();
-  bool is_error = env->IsInstanceOf(cause.get(), WellKnownClasses::java_lang_Error);
-  env->Throw(cause.get());
-
   // We only wrap non-Error exceptions; an Error can just be used as-is.
-  if (!is_error) {
+  if (!cause->IsError()) {
     self->ThrowNewWrappedException("Ljava/lang/ExceptionInInitializerError;", nullptr);
   }
   VlogClassInitializationFailure(klass);
@@ -1084,22 +1090,116 @@
   VLOG(startup) << "ClassLinker::FinishInit exiting";
 }
 
-void ClassLinker::RunRootClinits(Thread* self) {
-  for (size_t i = 0; i < static_cast<size_t>(ClassRoot::kMax); ++i) {
-    ObjPtr<mirror::Class> c = GetClassRoot(ClassRoot(i), this);
-    if (!c->IsArrayClass() && !c->IsPrimitive()) {
-      StackHandleScope<1> hs(self);
-      Handle<mirror::Class> h_class(hs.NewHandle(c));
-      if (!EnsureInitialized(self, h_class, true, true)) {
-        LOG(FATAL) << "Exception when initializing " << h_class->PrettyClass()
-            << ": " << self->GetException()->Dump();
-      }
-    } else {
-      DCHECK(c->IsInitialized());
+static void EnsureRootInitialized(ClassLinker* class_linker,
+                                  Thread* self,
+                                  ObjPtr<mirror::Class> klass)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (!klass->IsVisiblyInitialized()) {
+    DCHECK(!klass->IsArrayClass());
+    DCHECK(!klass->IsPrimitive());
+    StackHandleScope<1> hs(self);
+    Handle<mirror::Class> h_class(hs.NewHandle(klass));
+    if (!class_linker->EnsureInitialized(
+             self, h_class, /*can_init_fields=*/ true, /*can_init_parents=*/ true)) {
+      LOG(FATAL) << "Exception when initializing " << h_class->PrettyClass()
+          << ": " << self->GetException()->Dump();
     }
   }
 }
 
+void ClassLinker::RunEarlyRootClinits(Thread* self) {
+  StackHandleScope<1u> hs(self);
+  Handle<mirror::ObjectArray<mirror::Class>> class_roots = hs.NewHandle(GetClassRoots());
+  EnsureRootInitialized(this, self, GetClassRoot<mirror::Class>(class_roots.Get()));
+  EnsureRootInitialized(this, self, GetClassRoot<mirror::String>(class_roots.Get()));
+  // `Field` class is needed for register_java_net_InetAddress in libcore, b/28153851.
+  EnsureRootInitialized(this, self, GetClassRoot<mirror::Field>(class_roots.Get()));
+
+  WellKnownClasses::Init(self->GetJniEnv());
+
+  // `FinalizerReference` class is needed for initialization of `java.net.InetAddress`.
+  // (Indirectly by constructing a `ObjectStreamField` which uses a `StringBuilder`
+  // and, when resizing, initializes the `System` class for `System.arraycopy()`
+  // and `System.<clinit> creates a finalizable object.)
+  EnsureRootInitialized(
+      this, self, WellKnownClasses::java_lang_ref_FinalizerReference_add->GetDeclaringClass());
+}
+
+void ClassLinker::RunRootClinits(Thread* self) {
+  StackHandleScope<1u> hs(self);
+  Handle<mirror::ObjectArray<mirror::Class>> class_roots = hs.NewHandle(GetClassRoots());
+  for (size_t i = 0; i < static_cast<size_t>(ClassRoot::kMax); ++i) {
+    EnsureRootInitialized(this, self, GetClassRoot(ClassRoot(i), class_roots.Get()));
+  }
+
+  // Make sure certain well-known classes are initialized. Note that well-known
+  // classes are always in the boot image, so this code is primarily intended
+  // for running without boot image but may be needed for boot image if the
+  // AOT-initialization fails due to introduction of new code to `<clinit>`.
+  ArtMethod* methods_of_classes_to_initialize[] = {
+      // Initialize primitive boxing classes (avoid check at runtime).
+      WellKnownClasses::java_lang_Boolean_valueOf,
+      WellKnownClasses::java_lang_Byte_valueOf,
+      WellKnownClasses::java_lang_Character_valueOf,
+      WellKnownClasses::java_lang_Double_valueOf,
+      WellKnownClasses::java_lang_Float_valueOf,
+      WellKnownClasses::java_lang_Integer_valueOf,
+      WellKnownClasses::java_lang_Long_valueOf,
+      WellKnownClasses::java_lang_Short_valueOf,
+      // Initialize `StackOverflowError`.
+      WellKnownClasses::java_lang_StackOverflowError_init,
+      // Ensure class loader classes are initialized (avoid check at runtime).
+      // Superclass `ClassLoader` is a class root and already initialized above.
+      // Superclass `BaseDexClassLoader` is initialized implicitly.
+      WellKnownClasses::dalvik_system_DelegateLastClassLoader_init,
+      WellKnownClasses::dalvik_system_DexClassLoader_init,
+      WellKnownClasses::dalvik_system_InMemoryDexClassLoader_init,
+      WellKnownClasses::dalvik_system_PathClassLoader_init,
+      WellKnownClasses::java_lang_BootClassLoader_init,
+      // Ensure `Daemons` class is initialized (avoid check at runtime).
+      WellKnownClasses::java_lang_Daemons_start,
+      // Ensure `Thread` and `ThreadGroup` classes are initialized (avoid check at runtime).
+      WellKnownClasses::java_lang_Thread_init,
+      WellKnownClasses::java_lang_ThreadGroup_add,
+      // Ensure reference classes are initialized (avoid check at runtime).
+      // The `FinalizerReference` class was initialized in `RunEarlyRootClinits()`.
+      WellKnownClasses::java_lang_ref_ReferenceQueue_add,
+      // Ensure `InvocationTargetException` class is initialized (avoid check at runtime).
+      WellKnownClasses::java_lang_reflect_InvocationTargetException_init,
+      // Ensure `Parameter` class is initialized (avoid check at runtime).
+      WellKnownClasses::java_lang_reflect_Parameter_init,
+      // Ensure `MethodHandles` class is initialized (avoid check at runtime).
+      WellKnownClasses::java_lang_invoke_MethodHandles_lookup,
+      // Ensure `DirectByteBuffer` class is initialized (avoid check at runtime).
+      WellKnownClasses::java_nio_DirectByteBuffer_init,
+      // Ensure `FloatingDecimal` class is initialized (avoid check at runtime).
+      WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D,
+      // Ensure reflection annotation classes are initialized (avoid check at runtime).
+      WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation,
+      WellKnownClasses::libcore_reflect_AnnotationMember_init,
+      // We're suppressing exceptions from `DdmServer` and we do not want to repeatedly
+      // suppress class initialization error (say, due to OOM), so initialize it early.
+      WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_dispatch,
+  };
+  for (ArtMethod* method : methods_of_classes_to_initialize) {
+    EnsureRootInitialized(this, self, method->GetDeclaringClass());
+  }
+  ArtField* fields_of_classes_to_initialize[] = {
+      // Ensure classes used by class loaders are initialized (avoid check at runtime).
+      WellKnownClasses::dalvik_system_DexFile_cookie,
+      WellKnownClasses::dalvik_system_DexPathList_dexElements,
+      WellKnownClasses::dalvik_system_DexPathList__Element_dexFile,
+      // Ensure `VMRuntime` is initialized (avoid check at runtime).
+      WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer,
+      // Initialize empty arrays needed by `StackOverflowError`.
+      WellKnownClasses::java_util_Collections_EMPTY_LIST,
+      WellKnownClasses::libcore_util_EmptyArray_STACK_TRACE_ELEMENT,
+  };
+  for (ArtField* field : fields_of_classes_to_initialize) {
+    EnsureRootInitialized(this, self, field->GetDeclaringClass());
+  }
+}
+
 ALWAYS_INLINE
 static uint32_t ComputeMethodHash(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(!method->IsRuntimeMethod());
@@ -1298,22 +1398,13 @@
   runtime->SetSentinel(boot_image_live_objects->Get(ImageHeader::kClearedJniWeakSentinel));
   DCHECK(runtime->GetSentinel().Read()->GetClass() == GetClassRoot<mirror::Object>(this));
 
-  for (size_t i = 0u, size = spaces.size(); i != size; ++i) {
-    // Boot class loader, use a null handle.
-    std::vector<std::unique_ptr<const DexFile>> dex_files;
-    if (!AddImageSpace(spaces[i],
-                       ScopedNullHandle<mirror::ClassLoader>(),
-                       /*out*/&dex_files,
-                       error_msg)) {
-      return false;
-    }
-    // Append opened dex files at the end.
-    boot_dex_files_.insert(boot_dex_files_.end(),
-                           std::make_move_iterator(dex_files.begin()),
-                           std::make_move_iterator(dex_files.end()));
-  }
-  for (const std::unique_ptr<const DexFile>& dex_file : boot_dex_files_) {
-    OatDexFile::MadviseDexFileAtLoad(*dex_file);
+  // Boot class loader, use a null handle.
+  if (!AddImageSpaces(ArrayRef<gc::space::ImageSpace*>(spaces),
+                      ScopedNullHandle<mirror::ClassLoader>(),
+                      /*context=*/nullptr,
+                      &boot_dex_files_,
+                      error_msg)) {
+    return false;
   }
   InitializeObjectVirtualMethodHashes(GetClassRoot<mirror::Object>(this),
                                       image_pointer_size_,
@@ -1338,11 +1429,9 @@
   }
 }
 
-bool ClassLinker::IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                    ObjPtr<mirror::ClassLoader> class_loader) {
+bool ClassLinker::IsBootClassLoader(ObjPtr<mirror::Object> class_loader) {
   return class_loader == nullptr ||
-       soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader) ==
-           class_loader->GetClass();
+         WellKnownClasses::java_lang_BootClassLoader == class_loader->GetClass();
 }
 
 class CHAOnDeleteUpdateClassVisitor {
@@ -1491,24 +1580,40 @@
 
     uint32_t raw_member_offset = sro_base[offset_index].second;
     DCHECK_ALIGNED(base_offset, 2);
-    DCHECK_ALIGNED(raw_member_offset, 2);
 
     ObjPtr<mirror::Object> obj_ptr =
         reinterpret_cast<mirror::Object*>(space->Begin() + base_offset);
-    MemberOffset member_offset(raw_member_offset);
-    ObjPtr<mirror::String> referred_string =
-        obj_ptr->GetFieldObject<mirror::String,
-                                kVerifyNone,
-                                kWithoutReadBarrier,
-                                /* kIsVolatile= */ false>(member_offset);
-    DCHECK(referred_string != nullptr);
+    if (obj_ptr->IsDexCache() && raw_member_offset >= sizeof(mirror::DexCache)) {
+      // Special case for strings referenced from dex cache array: the offset is
+      // actually decoded as an index into the dex cache string array.
+      uint32_t index = raw_member_offset - sizeof(mirror::DexCache);
+      mirror::GcRootArray<mirror::String>* array = obj_ptr->AsDexCache()->GetStringsArray();
+      // The array could be concurrently set to null. See `StartupCompletedTask`.
+      if (array != nullptr) {
+        ObjPtr<mirror::String> referred_string = array->Get(index);
+        DCHECK(referred_string != nullptr);
+        ObjPtr<mirror::String> visited = visitor(referred_string);
+        if (visited != referred_string) {
+          array->Set(index, visited.Ptr());
+        }
+      }
+    } else {
+      DCHECK_ALIGNED(raw_member_offset, 2);
+      MemberOffset member_offset(raw_member_offset);
+      ObjPtr<mirror::String> referred_string =
+          obj_ptr->GetFieldObject<mirror::String,
+                                  kVerifyNone,
+                                  kWithoutReadBarrier,
+                                  /* kIsVolatile= */ false>(member_offset);
+      DCHECK(referred_string != nullptr);
 
-    ObjPtr<mirror::String> visited = visitor(referred_string);
-    if (visited != referred_string) {
-      obj_ptr->SetFieldObject</* kTransactionActive= */ false,
-                              /* kCheckTransaction= */ false,
-                              kVerifyNone,
-                              /* kIsVolatile= */ false>(member_offset, visited);
+      ObjPtr<mirror::String> visited = visitor(referred_string);
+      if (visited != referred_string) {
+        obj_ptr->SetFieldObject</* kTransactionActive= */ false,
+                                /* kCheckTransaction= */ false,
+                                kVerifyNone,
+                                /* kIsVolatile= */ false>(member_offset, visited);
+      }
     }
   }
 }
@@ -1574,6 +1679,7 @@
   Runtime* const runtime = Runtime::Current();
   gc::Heap* const heap = runtime->GetHeap();
   const ImageHeader& header = space->GetImageHeader();
+  int32_t number_of_dex_cache_arrays_cleared = 0;
   {
     // Register dex caches with the class loader.
     WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
@@ -1582,10 +1688,24 @@
       {
         WriterMutexLock mu2(self, *Locks::dex_lock_);
         CHECK(class_linker->FindDexCacheDataLocked(*dex_file) == nullptr);
+        if (runtime->GetStartupCompleted()) {
+          number_of_dex_cache_arrays_cleared++;
+          // Free up dex cache arrays that we would only allocate at startup.
+          // We do this here before registering and within the lock to be
+          // consistent with `StartupCompletedTask`.
+          dex_cache->UnlinkStartupCaches();
+        }
         class_linker->RegisterDexFileLocked(*dex_file, dex_cache, class_loader.Get());
       }
     }
   }
+  if (number_of_dex_cache_arrays_cleared == dex_caches->GetLength()) {
+    // Free up dex cache arrays that we would only allocate at startup.
+    // If `number_of_dex_cache_arrays_cleared` isn't the number of dex caches in
+    // the image, then there is a race with the `StartupCompletedTask`, which
+    // will release the space instead.
+    space->ReleaseMetadata();
+  }
 
   if (ClassLinker::kAppImageMayContainStrings) {
     HandleAppImageStrings(space);
@@ -1730,114 +1850,159 @@
   return true;
 }
 
+bool ClassLinker::OpenAndInitImageDexFiles(
+    const gc::space::ImageSpace* space,
+    Handle<mirror::ClassLoader> class_loader,
+    std::vector<std::unique_ptr<const DexFile>>* out_dex_files,
+    std::string* error_msg) {
+  DCHECK(out_dex_files != nullptr);
+  const bool app_image = class_loader != nullptr;
+  const ImageHeader& header = space->GetImageHeader();
+  ObjPtr<mirror::Object> dex_caches_object = header.GetImageRoot(ImageHeader::kDexCaches);
+  DCHECK(dex_caches_object != nullptr);
+  Thread* const self = Thread::Current();
+  StackHandleScope<3> hs(self);
+  Handle<mirror::ObjectArray<mirror::DexCache>> dex_caches(
+      hs.NewHandle(dex_caches_object->AsObjectArray<mirror::DexCache>()));
+  const OatFile* oat_file = space->GetOatFile();
+  if (oat_file->GetOatHeader().GetDexFileCount() !=
+      static_cast<uint32_t>(dex_caches->GetLength())) {
+    *error_msg =
+        "Dex cache count and dex file count mismatch while trying to initialize from image";
+    return false;
+  }
+
+  for (auto dex_cache : dex_caches.Iterate<mirror::DexCache>()) {
+    std::string dex_file_location = dex_cache->GetLocation()->ToModifiedUtf8();
+    std::unique_ptr<const DexFile> dex_file =
+        OpenOatDexFile(oat_file, dex_file_location.c_str(), error_msg);
+    if (dex_file == nullptr) {
+      return false;
+    }
+
+    {
+      // Native fields are all null.  Initialize them.
+      WriterMutexLock mu(self, *Locks::dex_lock_);
+      dex_cache->Initialize(dex_file.get(), class_loader.Get());
+    }
+    if (!app_image) {
+      // Register dex files, keep track of existing ones that are conflicts.
+      AppendToBootClassPath(dex_file.get(), dex_cache);
+    }
+    out_dex_files->push_back(std::move(dex_file));
+  }
+  return true;
+}
+
 // Helper class for ArtMethod checks when adding an image. Keeps all required functionality
 // together and caches some intermediate results.
+template <PointerSize kPointerSize>
 class ImageChecker final {
  public:
-  static void CheckObjects(gc::Heap* heap, ClassLinker* class_linker)
+  static void CheckObjects(gc::Heap* heap, gc::space::ImageSpace* space)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    ImageChecker ic(heap, class_linker);
+    // There can be no GC during boot image initialization, so we do not need read barriers.
+    ScopedDebugDisallowReadBarriers sddrb(Thread::Current());
+
+    CHECK_EQ(kPointerSize, space->GetImageHeader().GetPointerSize());
+    const ImageSection& objects_section = space->GetImageHeader().GetObjectsSection();
+    uintptr_t space_begin = reinterpret_cast<uintptr_t>(space->Begin());
+    uintptr_t objects_begin = space_begin + objects_section.Offset();
+    uintptr_t objects_end = objects_begin + objects_section.Size();
+    ImageChecker ic(heap);
     auto visitor = [&](mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
       DCHECK(obj != nullptr);
-      CHECK(obj->GetClass() != nullptr) << "Null class in object " << obj;
-      CHECK(obj->GetClass()->GetClass() != nullptr) << "Null class class " << obj;
-      if (obj->IsClass()) {
+      mirror::Class* obj_klass = obj->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>();
+      CHECK(obj_klass != nullptr) << "Null class in object " << obj;
+      mirror::Class* class_class = obj_klass->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>();
+      CHECK(class_class != nullptr) << "Null class class " << obj;
+      if (obj_klass == class_class) {
         auto klass = obj->AsClass();
         for (ArtField& field : klass->GetIFields()) {
-          CHECK_EQ(field.GetDeclaringClass(), klass);
+          CHECK_EQ(field.GetDeclaringClass<kWithoutReadBarrier>(), klass);
         }
         for (ArtField& field : klass->GetSFields()) {
-          CHECK_EQ(field.GetDeclaringClass(), klass);
+          CHECK_EQ(field.GetDeclaringClass<kWithoutReadBarrier>(), klass);
         }
-        const PointerSize pointer_size = ic.pointer_size_;
-        for (ArtMethod& m : klass->GetMethods(pointer_size)) {
+        for (ArtMethod& m : klass->GetMethods(kPointerSize)) {
           ic.CheckArtMethod(&m, klass);
         }
-        ObjPtr<mirror::PointerArray> vtable = klass->GetVTable();
+        ObjPtr<mirror::PointerArray> vtable =
+            klass->GetVTable<kDefaultVerifyFlags, kWithoutReadBarrier>();
         if (vtable != nullptr) {
-          ic.CheckArtMethodPointerArray(vtable, nullptr);
+          ic.CheckArtMethodPointerArray(vtable);
         }
         if (klass->ShouldHaveImt()) {
-          ImTable* imt = klass->GetImt(pointer_size);
+          ImTable* imt = klass->GetImt(kPointerSize);
           for (size_t i = 0; i < ImTable::kSize; ++i) {
-            ic.CheckArtMethod(imt->Get(i, pointer_size), nullptr);
+            ic.CheckArtMethod(imt->Get(i, kPointerSize), /*expected_class=*/ nullptr);
           }
         }
         if (klass->ShouldHaveEmbeddedVTable()) {
           for (int32_t i = 0; i < klass->GetEmbeddedVTableLength(); ++i) {
-            ic.CheckArtMethod(klass->GetEmbeddedVTableEntry(i, pointer_size), nullptr);
+            ic.CheckArtMethod(klass->GetEmbeddedVTableEntry(i, kPointerSize),
+                              /*expected_class=*/ nullptr);
           }
         }
-        ObjPtr<mirror::IfTable> iftable = klass->GetIfTable();
-        for (int32_t i = 0; i < klass->GetIfTableCount(); ++i) {
-          if (iftable->GetMethodArrayCount(i) > 0) {
-            ic.CheckArtMethodPointerArray(iftable->GetMethodArray(i), nullptr);
+        ObjPtr<mirror::IfTable> iftable =
+            klass->GetIfTable<kDefaultVerifyFlags, kWithoutReadBarrier>();
+        int32_t iftable_count = (iftable != nullptr) ? iftable->Count() : 0;
+        for (int32_t i = 0; i < iftable_count; ++i) {
+          ObjPtr<mirror::PointerArray> method_array =
+              iftable->GetMethodArrayOrNull<kDefaultVerifyFlags, kWithoutReadBarrier>(i);
+          if (method_array != nullptr) {
+            ic.CheckArtMethodPointerArray(method_array);
           }
         }
       }
     };
-    heap->VisitObjects(visitor);
+    space->GetLiveBitmap()->VisitMarkedRange(objects_begin, objects_end, visitor);
   }
 
  private:
-  ImageChecker(gc::Heap* heap, ClassLinker* class_linker)
-     :  spaces_(heap->GetBootImageSpaces()),
-        pointer_size_(class_linker->GetImagePointerSize()) {
-    space_begin_.reserve(spaces_.size());
-    method_sections_.reserve(spaces_.size());
-    runtime_method_sections_.reserve(spaces_.size());
-    for (gc::space::ImageSpace* space : spaces_) {
+  explicit ImageChecker(gc::Heap* heap) {
+    ArrayRef<gc::space::ImageSpace* const> spaces(heap->GetBootImageSpaces());
+    space_begin_.reserve(spaces.size());
+    for (gc::space::ImageSpace* space : spaces) {
+      CHECK_EQ(static_cast<const void*>(space->Begin()), &space->GetImageHeader());
       space_begin_.push_back(space->Begin());
-      auto& header = space->GetImageHeader();
-      method_sections_.push_back(&header.GetMethodsSection());
-      runtime_method_sections_.push_back(&header.GetRuntimeMethodsSection());
     }
   }
 
   void CheckArtMethod(ArtMethod* m, ObjPtr<mirror::Class> expected_class)
       REQUIRES_SHARED(Locks::mutator_lock_) {
+    ObjPtr<mirror::Class> declaring_class = m->GetDeclaringClassUnchecked<kWithoutReadBarrier>();
     if (m->IsRuntimeMethod()) {
-      ObjPtr<mirror::Class> declaring_class = m->GetDeclaringClassUnchecked();
       CHECK(declaring_class == nullptr) << declaring_class << " " << m->PrettyMethod();
     } else if (m->IsCopied()) {
-      CHECK(m->GetDeclaringClass() != nullptr) << m->PrettyMethod();
+      CHECK(declaring_class != nullptr) << m->PrettyMethod();
     } else if (expected_class != nullptr) {
-      CHECK_EQ(m->GetDeclaringClassUnchecked(), expected_class) << m->PrettyMethod();
+      CHECK_EQ(declaring_class, expected_class) << m->PrettyMethod();
     }
-    if (!spaces_.empty()) {
-      bool contains = false;
-      for (size_t i = 0; !contains && i != space_begin_.size(); ++i) {
-        const size_t offset = reinterpret_cast<uint8_t*>(m) - space_begin_[i];
-        contains = method_sections_[i]->Contains(offset) ||
-            runtime_method_sections_[i]->Contains(offset);
+    bool contains = false;
+    for (const uint8_t* begin : space_begin_) {
+      const size_t offset = reinterpret_cast<uint8_t*>(m) - begin;
+      const ImageHeader* header = reinterpret_cast<const ImageHeader*>(begin);
+      if (header->GetMethodsSection().Contains(offset) ||
+          header->GetRuntimeMethodsSection().Contains(offset)) {
+        contains = true;
+        break;
       }
-      CHECK(contains) << m << " not found";
     }
+    CHECK(contains) << m << " not found";
   }
 
-  void CheckArtMethodPointerArray(ObjPtr<mirror::PointerArray> arr,
-                                  ObjPtr<mirror::Class> expected_class)
+  void CheckArtMethodPointerArray(ObjPtr<mirror::PointerArray> arr)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     CHECK(arr != nullptr);
     for (int32_t j = 0; j < arr->GetLength(); ++j) {
-      auto* method = arr->GetElementPtrSize<ArtMethod*>(j, pointer_size_);
-      // expected_class == null means we are a dex cache.
-      if (expected_class != nullptr) {
-        CHECK(method != nullptr);
-      }
-      if (method != nullptr) {
-        CheckArtMethod(method, expected_class);
-      }
+      auto* method = arr->GetElementPtrSize<ArtMethod*>(j, kPointerSize);
+      CHECK(method != nullptr);
+      CheckArtMethod(method, /*expected_class=*/ nullptr);
     }
   }
 
-  const std::vector<gc::space::ImageSpace*>& spaces_;
-  const PointerSize pointer_size_;
-
-  // Cached sections from the spaces.
   std::vector<const uint8_t*> space_begin_;
-  std::vector<const ImageSection*> method_sections_;
-  std::vector<const ImageSection*> runtime_method_sections_;
 };
 
 static void VerifyAppImage(const ImageHeader& header,
@@ -1872,12 +2037,11 @@
   }
 }
 
-bool ClassLinker::AddImageSpace(
-    gc::space::ImageSpace* space,
-    Handle<mirror::ClassLoader> class_loader,
-    std::vector<std::unique_ptr<const DexFile>>* out_dex_files,
-    std::string* error_msg) {
-  DCHECK(out_dex_files != nullptr);
+bool ClassLinker::AddImageSpace(gc::space::ImageSpace* space,
+                                Handle<mirror::ClassLoader> class_loader,
+                                ClassLoaderContext* context,
+                                const std::vector<std::unique_ptr<const DexFile>>& dex_files,
+                                std::string* error_msg) {
   DCHECK(error_msg != nullptr);
   const uint64_t start_time = NanoTime();
   const bool app_image = class_loader != nullptr;
@@ -1906,9 +2070,8 @@
       hs.NewHandle(dex_caches_object->AsObjectArray<mirror::DexCache>()));
   Handle<mirror::ObjectArray<mirror::Class>> class_roots(hs.NewHandle(
       header.GetImageRoot(ImageHeader::kClassRoots)->AsObjectArray<mirror::Class>()));
-  MutableHandle<mirror::ClassLoader> image_class_loader(hs.NewHandle(
-      app_image ? header.GetImageRoot(ImageHeader::kAppImageClassLoader)->AsClassLoader()
-                : nullptr));
+  MutableHandle<mirror::Object> special_root(hs.NewHandle(
+      app_image ? header.GetImageRoot(ImageHeader::kSpecialRoots) : nullptr));
   DCHECK(class_roots != nullptr);
   if (class_roots->GetLength() != static_cast<int32_t>(ClassRoot::kMax)) {
     *error_msg = StringPrintf("Expected %d class roots but got %d",
@@ -1925,46 +2088,105 @@
     }
   }
   const OatFile* oat_file = space->GetOatFile();
-  if (oat_file->GetOatHeader().GetDexFileCount() !=
-      static_cast<uint32_t>(dex_caches->GetLength())) {
-    *error_msg = "Dex cache count and dex file count mismatch while trying to initialize from "
-                 "image";
-    return false;
-  }
-
-  for (auto dex_cache : dex_caches.Iterate<mirror::DexCache>()) {
-    std::string dex_file_location = dex_cache->GetLocation()->ToModifiedUtf8();
-    std::unique_ptr<const DexFile> dex_file = OpenOatDexFile(oat_file,
-                                                             dex_file_location.c_str(),
-                                                             error_msg);
-    if (dex_file == nullptr) {
-      return false;
-    }
-
-    {
-      // Native fields are all null.  Initialize them.
-      WriterMutexLock mu(self, *Locks::dex_lock_);
-      dex_cache->Initialize(dex_file.get(), class_loader.Get());
-    }
-    if (!app_image) {
-      // Register dex files, keep track of existing ones that are conflicts.
-      AppendToBootClassPath(dex_file.get(), dex_cache);
-    }
-    out_dex_files->push_back(std::move(dex_file));
-  }
 
   if (app_image) {
-    ScopedObjectAccessUnchecked soa(Thread::Current());
-    ScopedAssertNoThreadSuspension sants("Checking app image", soa.Self());
-    if (IsBootClassLoader(soa, image_class_loader.Get())) {
+    ScopedAssertNoThreadSuspension sants("Checking app image");
+    if (special_root == nullptr) {
+      *error_msg = "Unexpected null special root in app image";
+      return false;
+    } else if (special_root->IsByteArray()) {
+      OatHeader* oat_header = reinterpret_cast<OatHeader*>(special_root->AsByteArray()->GetData());
+      if (!oat_header->IsValid()) {
+        *error_msg = "Invalid oat header in special root";
+        return false;
+      }
+      if (oat_file->GetVdexFile()->GetNumberOfDexFiles() != oat_header->GetDexFileCount()) {
+        *error_msg = "Checksums count does not match";
+        return false;
+      }
+      if (oat_header->IsConcurrentCopying() != gUseReadBarrier) {
+        *error_msg = "GCs do not match";
+        return false;
+      }
+
+      // Check if the dex checksums match the dex files that we just loaded.
+      uint32_t* checksums = reinterpret_cast<uint32_t*>(
+          reinterpret_cast<uint8_t*>(oat_header) + oat_header->GetHeaderSize());
+      for (uint32_t i = 0; i  < oat_header->GetDexFileCount(); ++i) {
+        uint32_t dex_checksum = dex_files.at(i)->GetHeader().checksum_;
+        if (checksums[i] != dex_checksum) {
+          *error_msg = StringPrintf(
+              "Image and dex file checksums did not match for %s: image has %d, dex file has %d",
+              dex_files.at(i)->GetLocation().c_str(),
+              checksums[i],
+              dex_checksum);
+          return false;
+        }
+      }
+
+      // Validate the class loader context.
+      const char* stored_context = oat_header->GetStoreValueByKey(OatHeader::kClassPathKey);
+      if (stored_context == nullptr) {
+        *error_msg = "Missing class loader context in special root";
+        return false;
+      }
+      if (context->VerifyClassLoaderContextMatch(stored_context) ==
+              ClassLoaderContext::VerificationResult::kMismatch) {
+        *error_msg = StringPrintf("Class loader contexts don't match: %s", stored_context);
+        return false;
+      }
+
+      // Validate the apex versions.
+      if (!gc::space::ImageSpace::ValidateApexVersions(*oat_header,
+                                                       runtime->GetApexVersions(),
+                                                       space->GetImageLocation(),
+                                                       error_msg)) {
+        return false;
+      }
+
+      // Validate the boot classpath.
+      const char* bcp = oat_header->GetStoreValueByKey(OatHeader::kBootClassPathKey);
+      if (bcp == nullptr) {
+        *error_msg = "Missing boot classpath in special root";
+        return false;
+      }
+      std::string runtime_bcp = android::base::Join(runtime->GetBootClassPathLocations(), ':');
+      if (strcmp(bcp, runtime_bcp.c_str()) != 0) {
+        *error_msg = StringPrintf("Mismatch boot classpath: image has %s, runtime has %s",
+                                  bcp,
+                                  runtime_bcp.c_str());
+        return false;
+      }
+
+      // Validate the dex checksums of the boot classpath.
+      const char* bcp_checksums =
+          oat_header->GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey);
+      if (bcp_checksums == nullptr) {
+        *error_msg = "Missing boot classpath checksums in special root";
+        return false;
+      }
+      if (strcmp(bcp_checksums, runtime->GetBootClassPathChecksums().c_str()) != 0) {
+        *error_msg = StringPrintf("Mismatch boot classpath checksums: image has %s, runtime has %s",
+                                  bcp_checksums,
+                                  runtime->GetBootClassPathChecksums().c_str());
+        return false;
+      }
+    } else if (IsBootClassLoader(special_root.Get())) {
       *error_msg = "Unexpected BootClassLoader in app image";
       return false;
+    } else if (!special_root->IsClassLoader()) {
+      *error_msg = "Unexpected special root in app image";
+      return false;
     }
   }
 
   if (kCheckImageObjects) {
     if (!app_image) {
-      ImageChecker::CheckObjects(heap, this);
+      if (image_pointer_size_ == PointerSize::k64) {
+        ImageChecker<PointerSize::k64>::CheckObjects(heap, space);
+      } else {
+        ImageChecker<PointerSize::k32>::CheckObjects(heap, space);
+      }
     }
   }
 
@@ -2012,8 +2234,7 @@
           // Set image methods' entry point that point to the nterp trampoline to the
           // nterp entry point. This allows taking the fast path when doing a
           // nterp->nterp call.
-          DCHECK_IMPLIES(NeedsClinitCheckBeforeCall(&method),
-                         method.GetDeclaringClass()->IsVisiblyInitialized());
+          DCHECK(!method.StillNeedsClinitCheck());
           method.SetEntryPointFromQuickCompiledCode(interpreter::GetNterpEntryPoint());
         } else {
           method.SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
@@ -2024,7 +2245,7 @@
 
   if (runtime->IsVerificationSoftFail()) {
     header.VisitPackedArtMethods([&](ArtMethod& method) REQUIRES_SHARED(Locks::mutator_lock_) {
-      if (!method.IsNative() && method.IsInvokable()) {
+      if (method.IsManagedAndInvokable()) {
         method.ClearSkipAccessChecks();
       }
     }, space->Begin(), image_pointer_size_);
@@ -2062,9 +2283,11 @@
         ObjPtr<mirror::Class> klass(root.Read());
         // Do not update class loader for boot image classes where the app image
         // class loader is only the initiating loader but not the defining loader.
-        // Avoid read barrier since we are comparing against null.
-        if (klass->GetClassLoader<kDefaultVerifyFlags, kWithoutReadBarrier>() != nullptr) {
+        if (space->HasAddress(klass.Ptr())) {
           klass->SetClassLoader(loader);
+        } else {
+          DCHECK(klass->IsBootStrapClassLoaded());
+          DCHECK(Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(klass.Ptr()));
         }
       }
     }
@@ -2109,13 +2332,39 @@
   return true;
 }
 
+bool ClassLinker::AddImageSpaces(ArrayRef<gc::space::ImageSpace*> spaces,
+                                 Handle<mirror::ClassLoader> class_loader,
+                                 ClassLoaderContext* context,
+                                 /*out*/ std::vector<std::unique_ptr<const DexFile>>* dex_files,
+                                 /*out*/ std::string* error_msg) {
+  std::vector<std::vector<std::unique_ptr<const DexFile>>> dex_files_by_space_index;
+  for (const gc::space::ImageSpace* space : spaces) {
+    std::vector<std::unique_ptr<const DexFile>> space_dex_files;
+    if (!OpenAndInitImageDexFiles(space, class_loader, /*out*/ &space_dex_files, error_msg)) {
+      return false;
+    }
+    dex_files_by_space_index.push_back(std::move(space_dex_files));
+  }
+  // This must be done in a separate loop after all dex files are initialized because there can be
+  // references from an image space to another image space that comes after it.
+  for (size_t i = 0u, size = spaces.size(); i != size; ++i) {
+    std::vector<std::unique_ptr<const DexFile>>& space_dex_files = dex_files_by_space_index[i];
+    if (!AddImageSpace(spaces[i], class_loader, context, space_dex_files, error_msg)) {
+      return false;
+    }
+    // Append opened dex files at the end.
+    std::move(space_dex_files.begin(), space_dex_files.end(), std::back_inserter(*dex_files));
+  }
+  return true;
+}
+
 void ClassLinker::VisitClassRoots(RootVisitor* visitor, VisitRootFlags flags) {
   // Acquire tracing_enabled before locking class linker lock to prevent lock order violation. Since
   // enabling tracing requires the mutator lock, there are no race conditions here.
   const bool tracing_enabled = Trace::IsTracingEnabled();
   Thread* const self = Thread::Current();
   WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
-  if (kUseReadBarrier) {
+  if (gUseReadBarrier) {
     // We do not track new roots for CC.
     DCHECK_EQ(0, flags & (kVisitRootFlagNewRoots |
                           kVisitRootFlagClearRootLog |
@@ -2146,12 +2395,20 @@
     boot_class_table_->VisitRoots(root_visitor);
     // If tracing is enabled, then mark all the class loaders to prevent unloading.
     if ((flags & kVisitRootFlagClassLoader) != 0 || tracing_enabled) {
-      for (const ClassLoaderData& data : class_loaders_) {
-        GcRoot<mirror::Object> root(GcRoot<mirror::Object>(self->DecodeJObject(data.weak_root)));
-        root.VisitRoot(visitor, RootInfo(kRootVMInternal));
+      gc::Heap* const heap = Runtime::Current()->GetHeap();
+      // Don't visit class-loaders if compacting with userfaultfd GC as these
+      // weaks are updated using Runtime::SweepSystemWeaks() and the GC doesn't
+      // tolerate double updates.
+      if (!heap->IsPerformingUffdCompaction()) {
+        for (const ClassLoaderData& data : class_loaders_) {
+          GcRoot<mirror::Object> root(GcRoot<mirror::Object>(self->DecodeJObject(data.weak_root)));
+          root.VisitRoot(visitor, RootInfo(kRootVMInternal));
+        }
+      } else {
+        DCHECK_EQ(heap->CurrentCollectorType(), gc::CollectorType::kCollectorTypeCMC);
       }
     }
-  } else if (!kUseReadBarrier && (flags & kVisitRootFlagNewRoots) != 0) {
+  } else if (!gUseReadBarrier && (flags & kVisitRootFlagNewRoots) != 0) {
     for (auto& root : new_class_roots_) {
       ObjPtr<mirror::Class> old_ref = root.Read<kWithoutReadBarrier>();
       root.VisitRoot(visitor, RootInfo(kRootStickyClass));
@@ -2172,13 +2429,13 @@
       }
     }
   }
-  if (!kUseReadBarrier && (flags & kVisitRootFlagClearRootLog) != 0) {
+  if (!gUseReadBarrier && (flags & kVisitRootFlagClearRootLog) != 0) {
     new_class_roots_.clear();
     new_bss_roots_boot_oat_files_.clear();
   }
-  if (!kUseReadBarrier && (flags & kVisitRootFlagStartLoggingNewRoots) != 0) {
+  if (!gUseReadBarrier && (flags & kVisitRootFlagStartLoggingNewRoots) != 0) {
     log_new_roots_ = true;
-  } else if (!kUseReadBarrier && (flags & kVisitRootFlagStopLoggingNewRoots) != 0) {
+  } else if (!gUseReadBarrier && (flags & kVisitRootFlagStopLoggingNewRoots) != 0) {
     log_new_roots_ = false;
   }
   // We deliberately ignore the class roots in the image since we
@@ -2366,7 +2623,7 @@
     }
   } else if (cha_ != nullptr) {
     // If we don't have a JIT, we need to manually remove the CHA dependencies manually.
-    cha_->RemoveDependenciesForLinearAlloc(data.allocator);
+    cha_->RemoveDependenciesForLinearAlloc(self, data.allocator);
   }
   // Cleanup references to single implementation ArtMethods that will be deleted.
   if (cleanup_cha) {
@@ -2652,19 +2909,16 @@
   }                                                                           \
 } while (0)
 
-bool ClassLinker::FindClassInSharedLibraries(ScopedObjectAccessAlreadyRunnable& soa,
-                                             Thread* self,
+bool ClassLinker::FindClassInSharedLibraries(Thread* self,
                                              const char* descriptor,
                                              size_t hash,
                                              Handle<mirror::ClassLoader> class_loader,
                                              /*out*/ ObjPtr<mirror::Class>* result) {
-  ArtField* field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders);
-  return FindClassInSharedLibrariesHelper(soa, self, descriptor, hash, class_loader, field, result);
+  ArtField* field = WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
+  return FindClassInSharedLibrariesHelper(self, descriptor, hash, class_loader, field, result);
 }
 
-bool ClassLinker::FindClassInSharedLibrariesHelper(ScopedObjectAccessAlreadyRunnable& soa,
-                                                   Thread* self,
+bool ClassLinker::FindClassInSharedLibrariesHelper(Thread* self,
                                                    const char* descriptor,
                                                    size_t hash,
                                                    Handle<mirror::ClassLoader> class_loader,
@@ -2682,38 +2936,35 @@
   for (auto loader : shared_libraries.Iterate<mirror::ClassLoader>()) {
     temp_loader.Assign(loader);
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
-        FindClassInBaseDexClassLoader(soa, self, descriptor, hash, temp_loader, result),
+        FindClassInBaseDexClassLoader(self, descriptor, hash, temp_loader, result),
         *result,
         self);
   }
   return true;
 }
 
-bool ClassLinker::FindClassInSharedLibrariesAfter(ScopedObjectAccessAlreadyRunnable& soa,
-                                                  Thread* self,
+bool ClassLinker::FindClassInSharedLibrariesAfter(Thread* self,
                                                   const char* descriptor,
                                                   size_t hash,
                                                   Handle<mirror::ClassLoader> class_loader,
                                                   /*out*/ ObjPtr<mirror::Class>* result) {
-  ArtField* field = jni::DecodeArtField(
-      WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter);
-  return FindClassInSharedLibrariesHelper(soa, self, descriptor, hash, class_loader, field, result);
+  ArtField* field = WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter;
+  return FindClassInSharedLibrariesHelper(self, descriptor, hash, class_loader, field, result);
 }
 
-bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                                Thread* self,
+bool ClassLinker::FindClassInBaseDexClassLoader(Thread* self,
                                                 const char* descriptor,
                                                 size_t hash,
                                                 Handle<mirror::ClassLoader> class_loader,
                                                 /*out*/ ObjPtr<mirror::Class>* result) {
   // Termination case: boot class loader.
-  if (IsBootClassLoader(soa, class_loader.Get())) {
+  if (IsBootClassLoader(class_loader.Get())) {
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
         FindClassInBootClassLoaderClassPath(self, descriptor, hash, result), *result, self);
     return true;
   }
 
-  if (IsPathOrDexClassLoader(soa, class_loader) || IsInMemoryDexClassLoader(soa, class_loader)) {
+  if (IsPathOrDexClassLoader(class_loader) || IsInMemoryDexClassLoader(class_loader)) {
     // For regular path or dex class loader the search order is:
     //    - parent
     //    - shared libraries
@@ -2723,19 +2974,19 @@
     StackHandleScope<1> hs(self);
     Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent()));
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
-        FindClassInBaseDexClassLoader(soa, self, descriptor, hash, h_parent, result),
+        FindClassInBaseDexClassLoader(self, descriptor, hash, h_parent, result),
         *result,
         self);
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
-        FindClassInSharedLibraries(soa, self, descriptor, hash, class_loader, result),
+        FindClassInSharedLibraries(self, descriptor, hash, class_loader, result),
         *result,
         self);
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
-        FindClassInBaseDexClassLoaderClassPath(soa, descriptor, hash, class_loader, result),
+        FindClassInBaseDexClassLoaderClassPath(self, descriptor, hash, class_loader, result),
         *result,
         self);
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
-        FindClassInSharedLibrariesAfter(soa, self, descriptor, hash, class_loader, result),
+        FindClassInSharedLibrariesAfter(self, descriptor, hash, class_loader, result),
         *result,
         self);
     // We did not find a class, but the class loader chain was recognized, so we
@@ -2743,7 +2994,7 @@
     return true;
   }
 
-  if (IsDelegateLastClassLoader(soa, class_loader)) {
+  if (IsDelegateLastClassLoader(class_loader)) {
     // For delegate last, the search order is:
     //    - boot class path
     //    - shared libraries
@@ -2752,15 +3003,15 @@
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
         FindClassInBootClassLoaderClassPath(self, descriptor, hash, result), *result, self);
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
-        FindClassInSharedLibraries(soa, self, descriptor, hash, class_loader, result),
+        FindClassInSharedLibraries(self, descriptor, hash, class_loader, result),
         *result,
         self);
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
-        FindClassInBaseDexClassLoaderClassPath(soa, descriptor, hash, class_loader, result),
+        FindClassInBaseDexClassLoaderClassPath(self, descriptor, hash, class_loader, result),
         *result,
         self);
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
-        FindClassInSharedLibrariesAfter(soa, self, descriptor, hash, class_loader, result),
+        FindClassInSharedLibrariesAfter(self, descriptor, hash, class_loader, result),
         *result,
         self);
 
@@ -2768,7 +3019,7 @@
     StackHandleScope<1> hs(self);
     Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent()));
     RETURN_IF_UNRECOGNIZED_OR_FOUND_OR_EXCEPTION(
-        FindClassInBaseDexClassLoader(soa, self, descriptor, hash, h_parent, result),
+        FindClassInBaseDexClassLoader(self, descriptor, hash, h_parent, result),
         *result,
         self);
     // We did not find a class, but the class loader chain was recognized, so we
@@ -2837,14 +3088,14 @@
 }
 
 bool ClassLinker::FindClassInBaseDexClassLoaderClassPath(
-    ScopedObjectAccessAlreadyRunnable& soa,
+    Thread* self,
     const char* descriptor,
     size_t hash,
     Handle<mirror::ClassLoader> class_loader,
     /*out*/ ObjPtr<mirror::Class>* result) {
-  DCHECK(IsPathOrDexClassLoader(soa, class_loader) ||
-         IsInMemoryDexClassLoader(soa, class_loader) ||
-         IsDelegateLastClassLoader(soa, class_loader))
+  DCHECK(IsPathOrDexClassLoader(class_loader) ||
+         IsInMemoryDexClassLoader(class_loader) ||
+         IsDelegateLastClassLoader(class_loader))
       << "Unexpected class loader for descriptor " << descriptor;
 
   const DexFile* dex_file = nullptr;
@@ -2859,15 +3110,15 @@
     }
     return true;  // Continue with the next DexFile.
   };
-  VisitClassLoaderDexFiles(soa, class_loader, find_class_def);
+  VisitClassLoaderDexFiles(self, class_loader, find_class_def);
 
   if (class_def != nullptr) {
-    *result = DefineClass(soa.Self(), descriptor, hash, class_loader, *dex_file, *class_def);
+    *result = DefineClass(self, descriptor, hash, class_loader, *dex_file, *class_def);
     if (UNLIKELY(*result == nullptr)) {
-      CHECK(soa.Self()->IsExceptionPending()) << descriptor;
-      FilterDexFileCaughtExceptions(soa.Self(), this);
+      CHECK(self->IsExceptionPending()) << descriptor;
+      FilterDexFileCaughtExceptions(self, this);
     } else {
-      DCHECK(!soa.Self()->IsExceptionPending());
+      DCHECK(!self->IsExceptionPending());
     }
   }
   // A BaseDexClassLoader is always a known lookup.
@@ -2923,7 +3174,7 @@
   } else {
     ScopedObjectAccessUnchecked soa(self);
     bool known_hierarchy =
-        FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result_ptr);
+        FindClassInBaseDexClassLoader(self, descriptor, hash, class_loader, &result_ptr);
     if (result_ptr != nullptr) {
       // The chain was understood and we found the class. We still need to add the class to
       // the class table to protect from racy programs that can try and redefine the path list
@@ -2978,29 +3229,23 @@
                                  "%s",
                                  class_name_string.c_str());
       } else {
-        ScopedLocalRef<jobject> class_loader_object(
-            soa.Env(), soa.AddLocalReference<jobject>(class_loader.Get()));
-        ScopedLocalRef<jobject> result(soa.Env(), nullptr);
-        {
-          ScopedThreadStateChange tsc(self, ThreadState::kNative);
-          ScopedLocalRef<jobject> class_name_object(
-              soa.Env(), soa.Env()->NewStringUTF(class_name_string.c_str()));
-          if (class_name_object.get() == nullptr) {
-            DCHECK(self->IsExceptionPending());  // OOME.
-            return nullptr;
-          }
-          CHECK(class_loader_object.get() != nullptr);
-          result.reset(soa.Env()->CallObjectMethod(class_loader_object.get(),
-                                                   WellKnownClasses::java_lang_ClassLoader_loadClass,
-                                                   class_name_object.get()));
+        StackHandleScope<1u> hs(self);
+        Handle<mirror::String> class_name_object = hs.NewHandle(
+            mirror::String::AllocFromModifiedUtf8(self, class_name_string.c_str()));
+        if (class_name_object == nullptr) {
+          DCHECK(self->IsExceptionPending());  // OOME.
+          return nullptr;
         }
-        if (result.get() == nullptr && !self->IsExceptionPending()) {
+        DCHECK(class_loader != nullptr);
+        result_ptr = ObjPtr<mirror::Class>::DownCast(
+            WellKnownClasses::java_lang_ClassLoader_loadClass->InvokeVirtual<'L', 'L'>(
+                self, class_loader.Get(), class_name_object.Get()));
+        if (result_ptr == nullptr && !self->IsExceptionPending()) {
           // broken loader - throw NPE to be compatible with Dalvik
           ThrowNullPointerException(StringPrintf("ClassLoader.loadClass returned null for %s",
                                                  class_name_string.c_str()).c_str());
           return nullptr;
         }
-        result_ptr = soa.Decode<mirror::Class>(result.get());
         // Check the name of the returned class.
         descriptor_equals = (result_ptr != nullptr) && result_ptr->DescriptorEquals(descriptor);
       }
@@ -3114,6 +3359,7 @@
   ScopedDefiningClass sdc(self);
   StackHandleScope<3> hs(self);
   metrics::AutoTimer timer{GetMetrics()->ClassLoadingTotalTime()};
+  metrics::AutoTimer timeDelta{GetMetrics()->ClassLoadingTotalTimeDelta()};
   auto klass = hs.NewHandle<mirror::Class>(nullptr);
 
   // Load the class from the dex file.
@@ -3271,7 +3517,7 @@
   // classes. However it could not update methods of this class while we
   // were loading it. Now the class is resolved, we can update entrypoints
   // as required by instrumentation.
-  if (Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()) {
+  if (Runtime::Current()->GetInstrumentation()->EntryExitStubsInstalled()) {
     // We must be in the kRunnable state to prevent instrumentation from
     // suspending all threads to update entrypoints while we are doing it
     // for this class.
@@ -3389,14 +3635,11 @@
   }
 
   instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
-  // Link the code of methods skipped by LinkCode.
   for (size_t method_index = 0; method_index < num_direct_methods; ++method_index) {
     ArtMethod* method = klass->GetDirectMethod(method_index, pointer_size);
-    if (!method->IsStatic()) {
-      // Only update static methods.
-      continue;
+    if (method->NeedsClinitCheckBeforeCall()) {
+      instrumentation->UpdateMethodsCode(method, instrumentation->GetCodeForInvoke(method));
     }
-    instrumentation->UpdateMethodsCode(method, instrumentation->GetCodeForInvoke(method));
   }
   // Ignore virtual methods on the iterator.
 }
@@ -3424,7 +3667,7 @@
   }
 
   // Method shouldn't have already been linked.
-  DCHECK(method->GetEntryPointFromQuickCompiledCode() == nullptr);
+  DCHECK_EQ(method->GetEntryPointFromQuickCompiledCode(), nullptr);
   DCHECK(!method->GetDeclaringClass()->IsVisiblyInitialized());  // Actually ClassStatus::Idx.
 
   if (!method->IsInvokable()) {
@@ -3480,7 +3723,7 @@
   // If the ArtField alignment changes, review all uses of LengthPrefixedArray<ArtField>.
   static_assert(alignof(ArtField) == 4, "ArtField alignment is expected to be 4.");
   size_t storage_size = LengthPrefixedArray<ArtField>::ComputeSize(length);
-  void* array_storage = allocator->Alloc(self, storage_size);
+  void* array_storage = allocator->Alloc(self, storage_size, LinearAllocKind::kArtFieldArray);
   auto* ret = new(array_storage) LengthPrefixedArray<ArtField>(length);
   CHECK(ret != nullptr);
   std::uninitialized_fill_n(&ret->At(0), length, ArtField());
@@ -3497,7 +3740,7 @@
   const size_t method_size = ArtMethod::Size(image_pointer_size_);
   const size_t storage_size =
       LengthPrefixedArray<ArtMethod>::ComputeSize(length, method_size, method_alignment);
-  void* array_storage = allocator->Alloc(self, storage_size);
+  void* array_storage = allocator->Alloc(self, storage_size, LinearAllocKind::kArtMethodArray);
   auto* ret = new (array_storage) LengthPrefixedArray<ArtMethod>(length);
   CHECK(ret != nullptr);
   for (size_t i = 0; i < length; ++i) {
@@ -3810,7 +4053,8 @@
   std::string dex_file_location = dex_file.GetLocation();
   // The following paths checks don't work on preopt when using boot dex files, where the dex
   // cache location is the one on device, and the dex_file's location is the one on host.
-  if (!(Runtime::Current()->IsAotCompiler() && class_loader == nullptr && !kIsTargetBuild)) {
+  Runtime* runtime = Runtime::Current();
+  if (!(runtime->IsAotCompiler() && class_loader == nullptr && !kIsTargetBuild)) {
     CHECK_GE(dex_file_location.length(), dex_cache_length)
         << dex_cache_location << " " << dex_file.GetLocation();
     const std::string dex_file_suffix = dex_file_location.substr(
@@ -3820,34 +4064,37 @@
     // dex file location is /system/priv-app/SettingsProvider/SettingsProvider.apk
     CHECK_EQ(dex_cache_location, dex_file_suffix);
   }
+
+  // Check if we need to initialize OatFile data (.data.bimg.rel.ro and .bss
+  // sections) needed for code execution and register the oat code range.
   const OatFile* oat_file =
       (dex_file.GetOatDexFile() != nullptr) ? dex_file.GetOatDexFile()->GetOatFile() : nullptr;
-  // Clean up pass to remove null dex caches; null dex caches can occur due to class unloading
-  // and we are lazily removing null entries. Also check if we need to initialize OatFile data
-  // (.data.bimg.rel.ro and .bss sections) needed for code execution.
   bool initialize_oat_file_data = (oat_file != nullptr) && oat_file->IsExecutable();
-  JavaVMExt* const vm = self->GetJniEnv()->GetVm();
-  for (auto it = dex_caches_.begin(); it != dex_caches_.end(); ) {
-    const DexCacheData& data = it->second;
-    if (self->IsJWeakCleared(data.weak_root)) {
-      vm->DeleteWeakGlobalRef(self, data.weak_root);
-      it = dex_caches_.erase(it);
-    } else {
-      if (initialize_oat_file_data &&
-          it->first->GetOatDexFile() != nullptr &&
-          it->first->GetOatDexFile()->GetOatFile() == oat_file) {
+  if (initialize_oat_file_data) {
+    for (const auto& entry : dex_caches_) {
+      if (!self->IsJWeakCleared(entry.second.weak_root) &&
+          entry.first->GetOatDexFile() != nullptr &&
+          entry.first->GetOatDexFile()->GetOatFile() == oat_file) {
         initialize_oat_file_data = false;  // Already initialized.
+        break;
       }
-      ++it;
     }
   }
   if (initialize_oat_file_data) {
     oat_file->InitializeRelocations();
+    // Notify the fault handler about the new executable code range if needed.
+    size_t exec_offset = oat_file->GetOatHeader().GetExecutableOffset();
+    DCHECK_LE(exec_offset, oat_file->Size());
+    size_t exec_size = oat_file->Size() - exec_offset;
+    if (exec_size != 0u) {
+      runtime->AddGeneratedCodeRange(oat_file->Begin() + exec_offset, exec_size);
+    }
   }
+
   // Let hiddenapi assign a domain to the newly registered dex file.
   hiddenapi::InitializeDexFileDomain(dex_file, class_loader);
 
-  jweak dex_cache_jweak = vm->AddWeakGlobalRef(self, dex_cache);
+  jweak dex_cache_jweak = self->GetJniEnv()->GetVm()->AddWeakGlobalRef(self, dex_cache);
   DexCacheData data;
   data.weak_root = dex_cache_jweak;
   data.class_table = ClassTableForClassLoader(class_loader);
@@ -4045,11 +4292,22 @@
   for (const auto& entry : dex_caches_) {
     const DexCacheData& data = entry.second;
     if (DecodeDexCacheLocked(self, &data) != nullptr) {
-      LOG(FATAL_WITHOUT_ABORT) << "Registered dex file " << entry.first->GetLocation();
+      const OatDexFile* other_oat_dex_file = entry.first->GetOatDexFile();
+      const OatFile* oat_file =
+          (other_oat_dex_file == nullptr) ? nullptr : other_oat_dex_file->GetOatFile();
+      LOG(FATAL_WITHOUT_ABORT)
+          << "Registered dex file " << entry.first->GetLocation()
+          << " oat_dex_file=" << other_oat_dex_file
+          << " oat_file=" << oat_file
+          << " oat_location=" << (oat_file == nullptr ? "null" : oat_file->GetLocation())
+          << " dex_file=" << &entry.first;
     }
   }
-  LOG(FATAL) << "Failed to find DexCache for OatDexFile " << oat_dex_file.GetDexFileLocation()
-             << " " << &oat_dex_file;
+  LOG(FATAL) << "Failed to find DexCache for OatDexFile "
+             << oat_dex_file.GetDexFileLocation()
+             << " oat_dex_file=" << &oat_dex_file
+             << " oat_file=" << oat_dex_file.GetOatFile()
+             << " oat_location=" << oat_dex_file.GetOatFile()->GetLocation();
   UNREACHABLE();
 }
 
@@ -4144,10 +4402,11 @@
                                                                      class_loader)));
   if (component_type == nullptr) {
     DCHECK(self->IsExceptionPending());
-    // We need to accept erroneous classes as component types.
+    // We need to accept erroneous classes as component types. Under AOT, we
+    // don't accept them as we cannot encode the erroneous class in an image.
     const size_t component_hash = ComputeModifiedUtf8Hash(descriptor + 1);
     component_type.Assign(LookupClass(self, descriptor + 1, component_hash, class_loader.Get()));
-    if (component_type == nullptr) {
+    if (component_type == nullptr || Runtime::Current()->IsAotCompiler()) {
       DCHECK(self->IsExceptionPending());
       return nullptr;
     } else {
@@ -4766,6 +5025,12 @@
     return;  // nothing to process
   }
   const uint8_t* handlers_ptr = accessor.GetCatchHandlerData(0);
+  CHECK(method->GetDexFile()->IsInDataSection(handlers_ptr))
+      << method->PrettyMethod()
+      << "@" << method->GetDexFile()->GetLocation()
+      << "@" << reinterpret_cast<const void*>(handlers_ptr)
+      << " is_compact_dex=" << method->GetDexFile()->IsCompactDexFile();
+
   uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr);
   for (uint32_t idx = 0; idx < handlers_size; idx++) {
     CatchHandlerIterator iterator(handlers_ptr);
@@ -5022,8 +5287,7 @@
 
   // Find the <init>(InvocationHandler)V method. The exact method offset varies depending
   // on which front-end compiler was used to build the libcore DEX files.
-  ArtMethod* proxy_constructor =
-      jni::DecodeArtMethod(WellKnownClasses::java_lang_reflect_Proxy_init);
+  ArtMethod* proxy_constructor = WellKnownClasses::java_lang_reflect_Proxy_init;
   DCHECK(proxy_constructor != nullptr)
       << "Could not find <init> method in java.lang.reflect.Proxy";
 
@@ -5088,11 +5352,19 @@
   CHECK_EQ(prototype, method->GetInterfaceMethodIfProxy(image_pointer_size_));
 }
 
-bool ClassLinker::CanWeInitializeClass(ObjPtr<mirror::Class> klass, bool can_init_statics,
+bool ClassLinker::CanWeInitializeClass(ObjPtr<mirror::Class> klass,
+                                       bool can_init_statics,
                                        bool can_init_parents) {
   if (can_init_statics && can_init_parents) {
     return true;
   }
+  DCHECK(Runtime::Current()->IsAotCompiler());
+
+  // We currently don't support initializing at AOT time classes that need access
+  // checks.
+  if (klass->IsVerifiedNeedsAccessChecks()) {
+    return false;
+  }
   if (!can_init_statics) {
     // Check if there's a class initializer.
     ArtMethod* clinit = klass->FindClassInitializer(image_pointer_size_);
@@ -5842,17 +6114,6 @@
   return class_loader == nullptr ? boot_class_table_.get() : class_loader->GetClassTable();
 }
 
-static ImTable* FindSuperImt(ObjPtr<mirror::Class> klass, PointerSize pointer_size)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  while (klass->HasSuperClass()) {
-    klass = klass->GetSuperClass();
-    if (klass->ShouldHaveImt()) {
-      return klass->GetImt(pointer_size);
-    }
-  }
-  return nullptr;
-}
-
 bool ClassLinker::LinkClass(Thread* self,
                             const char* descriptor,
                             Handle<mirror::Class> klass,
@@ -5888,7 +6149,7 @@
     // will possibly create a table that is incorrect for either of the classes.
     // Same IMT with new_conflict does not happen very often.
     if (!new_conflict) {
-      ImTable* super_imt = FindSuperImt(klass.Get(), image_pointer_size_);
+      ImTable* super_imt = klass->FindSuperImt(image_pointer_size_);
       if (super_imt != nullptr) {
         bool imt_equals = true;
         for (size_t i = 0; i < ImTable::kSize && imt_equals; ++i) {
@@ -5902,7 +6163,9 @@
     if (imt == nullptr) {
       LinearAlloc* allocator = GetAllocatorForClassLoader(klass->GetClassLoader());
       imt = reinterpret_cast<ImTable*>(
-          allocator->Alloc(self, ImTable::SizeInBytes(image_pointer_size_)));
+          allocator->Alloc(self,
+                           ImTable::SizeInBytes(image_pointer_size_),
+                           LinearAllocKind::kNoGCRoots));
       if (imt == nullptr) {
         return false;
       }
@@ -6093,6 +6356,10 @@
                             klass->PrettyDescriptor().c_str());
     return false;
   }
+  if (!VerifyRecordClass(klass, super)) {
+    DCHECK(Thread::Current()->IsExceptionPending());
+    return false;
+  }
 
   // Inherit kAccClassIsFinalizable from the superclass in case this
   // class doesn't override finalize.
@@ -6169,13 +6436,36 @@
   std::string_view name_view_;
 };
 
+static ObjPtr<mirror::Class> GetImtOwner(ObjPtr<mirror::Class> klass)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ImTable* imt = klass->GetImt(kRuntimePointerSize);
+  DCHECK(imt != nullptr);
+  while (klass->HasSuperClass()) {
+    ObjPtr<mirror::Class> super_class = klass->GetSuperClass();
+    if (super_class->ShouldHaveImt() && imt != super_class->GetImt(kRuntimePointerSize)) {
+      // IMT not shared with the super class, return the current class.
+      return klass;
+    }
+    klass = super_class;
+  }
+  return nullptr;
+}
+
 ArtMethod* ClassLinker::AddMethodToConflictTable(ObjPtr<mirror::Class> klass,
                                                  ArtMethod* conflict_method,
                                                  ArtMethod* interface_method,
                                                  ArtMethod* method) {
   ImtConflictTable* current_table = conflict_method->GetImtConflictTable(kRuntimePointerSize);
   Runtime* const runtime = Runtime::Current();
-  LinearAlloc* linear_alloc = GetAllocatorForClassLoader(klass->GetClassLoader());
+
+  // The IMT may be shared with a super class, in which case we need to use that
+  // super class's `LinearAlloc`. The conflict itself should be limited to
+  // methods at or higher up the chain of the IMT owner, otherwise class
+  // linker would have created a different IMT.
+  ObjPtr<mirror::Class> imt_owner = GetImtOwner(klass);
+  DCHECK(imt_owner != nullptr);
+
+  LinearAlloc* linear_alloc = GetAllocatorForClassLoader(imt_owner->GetClassLoader());
 
   // Create a new entry if the existing one is the shared conflict method.
   ArtMethod* new_conflict_method = (conflict_method == runtime->GetImtConflictMethod())
@@ -6185,8 +6475,9 @@
   // Allocate a new table. Note that we will leak this table at the next conflict,
   // but that's a tradeoff compared to making the table fixed size.
   void* data = linear_alloc->Alloc(
-      Thread::Current(), ImtConflictTable::ComputeSizeWithOneMoreEntry(current_table,
-                                                                       image_pointer_size_));
+      Thread::Current(),
+      ImtConflictTable::ComputeSizeWithOneMoreEntry(current_table, image_pointer_size_),
+      LinearAllocKind::kNoGCRoots);
   if (data == nullptr) {
     LOG(ERROR) << "Failed to allocate conflict table";
     return conflict_method;
@@ -6259,9 +6550,8 @@
   // Compare the IMT with the super class including the conflict methods. If they are equivalent,
   // we can just use the same pointer.
   ImTable* imt = nullptr;
-  ObjPtr<mirror::Class> super_class = klass->GetSuperClass();
-  if (super_class != nullptr && super_class->ShouldHaveImt()) {
-    ImTable* super_imt = super_class->GetImt(image_pointer_size_);
+  ImTable* super_imt = klass->FindSuperImt(image_pointer_size_);
+  if (super_imt != nullptr) {
     bool same = true;
     for (size_t i = 0; same && i < ImTable::kSize; ++i) {
       ArtMethod* method = imt_data[i];
@@ -6290,6 +6580,7 @@
   if (imt == nullptr) {
     imt = klass->GetImt(image_pointer_size_);
     DCHECK(imt != nullptr);
+    DCHECK_NE(imt, super_imt);
     imt->Populate(imt_data, image_pointer_size_);
   } else {
     klass->SetImt(imt, image_pointer_size_);
@@ -6300,8 +6591,8 @@
                                                       LinearAlloc* linear_alloc,
                                                       PointerSize image_pointer_size) {
   void* data = linear_alloc->Alloc(Thread::Current(),
-                                   ImtConflictTable::ComputeSize(count,
-                                                                 image_pointer_size));
+                                   ImtConflictTable::ComputeSize(count, image_pointer_size),
+                                   LinearAllocKind::kNoGCRoots);
   return (data != nullptr) ? new (data) ImtConflictTable(count, image_pointer_size) : nullptr;
 }
 
@@ -6917,7 +7208,7 @@
         klass_(klass),
         self_(self),
         runtime_(runtime),
-        stack_(runtime->GetLinearAlloc()->GetArenaPool()),
+        stack_(runtime->GetArenaPool()),
         allocator_(&stack_),
         copied_method_records_(copied_method_records_initial_buffer_,
                                kCopiedMethodRecordInitialBufferSize,
@@ -6997,6 +7288,10 @@
                                                                             kMethodSize,
                                                                             kMethodAlignment);
         memset(old_methods, 0xFEu, old_size);
+        // Set size to 0 to avoid visiting declaring classes.
+        if (gUseUserfaultfd) {
+          old_methods->SetSize(0);
+        }
       }
     }
   }
@@ -7599,16 +7894,25 @@
   const size_t old_methods_ptr_size = (old_methods != nullptr) ? old_size : 0;
   auto* methods = reinterpret_cast<LengthPrefixedArray<ArtMethod>*>(
       class_linker_->GetAllocatorForClassLoader(klass->GetClassLoader())->Realloc(
-          self_, old_methods, old_methods_ptr_size, new_size));
+          self_, old_methods, old_methods_ptr_size, new_size, LinearAllocKind::kArtMethodArray));
   CHECK(methods != nullptr);  // Native allocation failure aborts.
 
   if (methods != old_methods) {
-    StrideIterator<ArtMethod> out = methods->begin(kMethodSize, kMethodAlignment);
-    // Copy over the old methods. The `ArtMethod::CopyFrom()` is only necessary to not miss
-    // read barriers since `LinearAlloc::Realloc()` won't do read barriers when it copies.
-    for (auto& m : klass->GetMethods(kPointerSize)) {
-      out->CopyFrom(&m, kPointerSize);
-      ++out;
+    if (gUseReadBarrier) {
+      StrideIterator<ArtMethod> out = methods->begin(kMethodSize, kMethodAlignment);
+      // Copy over the old methods. The `ArtMethod::CopyFrom()` is only necessary to not miss
+      // read barriers since `LinearAlloc::Realloc()` won't do read barriers when it copies.
+      for (auto& m : klass->GetMethods(kPointerSize)) {
+        out->CopyFrom(&m, kPointerSize);
+        ++out;
+      }
+    } else if (gUseUserfaultfd) {
+      // Clear the declaring class of the old dangling method array so that GC doesn't
+      // try to update them, which could cause crashes in userfaultfd GC due to
+      // checks in post-compact address computation.
+      for (auto& m : klass->GetMethods(kPointerSize)) {
+        m.SetDeclaringClass(nullptr);
+      }
     }
   }
 
@@ -7756,7 +8060,8 @@
       LengthPrefixedArray<ArtMethod>* const new_methods = klass->GetMethodsPtr();
       if (new_methods != nullptr) {
         DCHECK_NE(new_methods->size(), 0u);
-        imt_methods_begin = reinterpret_cast<uintptr_t>(&new_methods->At(0));
+        imt_methods_begin =
+            reinterpret_cast<uintptr_t>(&new_methods->At(0, kMethodSize, kMethodAlignment));
         imt_methods_size = new_methods->size() * kMethodSize;
       }
     }
@@ -7859,20 +8164,6 @@
   return true;
 }
 
-NO_INLINE
-static void ThrowIllegalAccessErrorForImplementingMethod(ObjPtr<mirror::Class> klass,
-                                                         ArtMethod* vtable_method,
-                                                         ArtMethod* interface_method)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  DCHECK(!vtable_method->IsAbstract());
-  DCHECK(!vtable_method->IsPublic());
-  ThrowIllegalAccessError(
-      klass,
-      "Method '%s' implementing interface method '%s' is not public",
-      vtable_method->PrettyMethod().c_str(),
-      interface_method->PrettyMethod().c_str());
-}
-
 template <PointerSize kPointerSize>
 ObjPtr<mirror::PointerArray> ClassLinker::LinkMethodsHelper<kPointerSize>::AllocPointerArray(
     Thread* self, size_t length) {
@@ -7961,55 +8252,17 @@
   static constexpr double kMinLoadFactor = 0.3;
   static constexpr double kMaxLoadFactor = 0.5;
   static constexpr size_t kMaxStackBuferSize = 256;
-  const size_t super_vtable_buffer_size = super_vtable_length * 3;
   const size_t declared_virtuals_buffer_size = num_virtual_methods * 3;
-  const size_t total_buffer_size = super_vtable_buffer_size + declared_virtuals_buffer_size;
-  uint32_t* super_vtable_buffer_ptr = (total_buffer_size <= kMaxStackBuferSize)
-      ? reinterpret_cast<uint32_t*>(alloca(total_buffer_size * sizeof(uint32_t)))
-      : allocator_.AllocArray<uint32_t>(total_buffer_size);
-  uint32_t* declared_virtuals_buffer_ptr = super_vtable_buffer_ptr + super_vtable_buffer_size;
-  VTableSignatureSet super_vtable_signatures(
-      kMinLoadFactor,
-      kMaxLoadFactor,
-      VTableSignatureHash(super_vtable_accessor),
-      VTableSignatureEqual(super_vtable_accessor),
-      super_vtable_buffer_ptr,
-      super_vtable_buffer_size,
-      allocator_.Adapter());
-  ArrayRef<uint32_t> same_signature_vtable_lists;
-  // Insert the first `mirror::Object::kVTableLength` indexes with pre-calculated hashes.
-  DCHECK_GE(super_vtable_length, mirror::Object::kVTableLength);
-  for (uint32_t i = 0; i != mirror::Object::kVTableLength; ++i) {
-    size_t hash = class_linker_->object_virtual_method_hashes_[i];
-    // There are no duplicate signatures in `java.lang.Object`, so use `HashSet<>::PutWithHash()`.
-    // This avoids equality comparison for the three `java.lang.Object.wait()` overloads.
-    super_vtable_signatures.PutWithHash(i, hash);
-  }
-  // Insert the remaining indexes, check for duplicate signatures.
-  if (super_vtable_length > mirror::Object::kVTableLength) {
-    for (size_t i = mirror::Object::kVTableLength; i < super_vtable_length; ++i) {
-      // Use `super_vtable_accessor` for getting the method for hash calculation.
-      // Letting `HashSet<>::insert()` use the internal accessor copy in the hash
-      // function prevents the compiler from optimizing this properly because the
-      // compiler cannot prove that the accessor copy is immutable.
-      size_t hash = ComputeMethodHash(super_vtable_accessor.GetVTableEntry(i));
-      auto [it, inserted] = super_vtable_signatures.InsertWithHash(i, hash);
-      if (UNLIKELY(!inserted)) {
-        if (same_signature_vtable_lists.empty()) {
-          same_signature_vtable_lists = ArrayRef<uint32_t>(
-              allocator_.AllocArray<uint32_t>(super_vtable_length), super_vtable_length);
-          std::fill_n(same_signature_vtable_lists.data(), super_vtable_length, dex::kDexNoIndex);
-          same_signature_vtable_lists_ = same_signature_vtable_lists;
-        }
-        DCHECK_LT(*it, i);
-        same_signature_vtable_lists[i] = *it;
-        *it = i;
-      }
-    }
-  }
+  const size_t super_vtable_buffer_size = super_vtable_length * 3;
+  const size_t bit_vector_size = BitVector::BitsToWords(num_virtual_methods);
+  const size_t total_size =
+      declared_virtuals_buffer_size + super_vtable_buffer_size + bit_vector_size;
 
-  // For each declared virtual method, look for a superclass virtual method
-  // to override and assign a new vtable index if no method was overridden.
+  uint32_t* declared_virtuals_buffer_ptr = (total_size <= kMaxStackBuferSize)
+      ? reinterpret_cast<uint32_t*>(alloca(total_size * sizeof(uint32_t)))
+      : allocator_.AllocArray<uint32_t>(total_size);
+  uint32_t* bit_vector_buffer_ptr = declared_virtuals_buffer_ptr + declared_virtuals_buffer_size;
+
   DeclaredVirtualSignatureSet declared_virtual_signatures(
       kMinLoadFactor,
       kMaxLoadFactor,
@@ -8018,8 +8271,24 @@
       declared_virtuals_buffer_ptr,
       declared_virtuals_buffer_size,
       allocator_.Adapter());
+
+  ArrayRef<uint32_t> same_signature_vtable_lists;
   const bool is_proxy_class = klass->IsProxyClass();
   size_t vtable_length = super_vtable_length;
+
+  // Record which declared methods are overriding a super method.
+  BitVector initialized_methods(/* expandable= */ false,
+                                Allocator::GetNoopAllocator(),
+                                bit_vector_size,
+                                bit_vector_buffer_ptr);
+
+  // Note: our sets hash on the method name, and therefore we pay a high
+  // performance price when a class has many overloads.
+  //
+  // We populate a set of declared signatures instead of signatures from the
+  // super vtable (which is only lazy populated in case of interface overriding,
+  // see below). This makes sure that we pay the performance price only on that
+  // class, and not on its subclasses (except in the case of interface overriding, see below).
   for (size_t i = 0; i != num_virtual_methods; ++i) {
     ArtMethod* virtual_method = klass->GetVirtualMethodDuringLinking(i, kPointerSize);
     DCHECK(!virtual_method->IsStatic()) << virtual_method->PrettyMethod();
@@ -8028,59 +8297,79 @@
         : virtual_method;
     size_t hash = ComputeMethodHash(signature_method);
     declared_virtual_signatures.PutWithHash(i, hash);
-    auto it = super_vtable_signatures.FindWithHash(signature_method, hash);
-    if (it != super_vtable_signatures.end()) {
-      size_t super_index = *it;
-      DCHECK_LT(super_index, super_vtable_length);
-      ArtMethod* super_method = super_vtable_accessor.GetVTableEntry(super_index);
-      // Historical note: Before Android 4.1, an inaccessible package-private
-      // superclass method would have been incorrectly overridden.
-      bool overrides = klass->CanAccessMember(super_method->GetDeclaringClass(),
-                                              super_method->GetAccessFlags());
-      if (overrides && super_method->IsFinal()) {
-        sants.reset();
-        ThrowLinkageError(klass, "Method %s overrides final method in class %s",
-                          virtual_method->PrettyMethod().c_str(),
-                          super_method->GetDeclaringClassDescriptor());
-        return 0u;
-      }
-      if (UNLIKELY(!same_signature_vtable_lists.empty())) {
-        // We may override more than one method according to JLS, see b/211854716 .
-        // We record the highest overridden vtable index here so that we can walk
-        // the list to find other overridden methods when constructing the vtable.
-        // However, we walk all the methods to check for final method overriding.
-        size_t current_index = super_index;
-        while (same_signature_vtable_lists[current_index] != dex::kDexNoIndex) {
-          DCHECK_LT(same_signature_vtable_lists[current_index], current_index);
-          current_index = same_signature_vtable_lists[current_index];
-          ArtMethod* current_method = super_vtable_accessor.GetVTableEntry(current_index);
-          if (klass->CanAccessMember(current_method->GetDeclaringClass(),
-                                     current_method->GetAccessFlags())) {
-            if (current_method->IsFinal()) {
-              sants.reset();
-              ThrowLinkageError(klass, "Method %s overrides final method in class %s",
-                                virtual_method->PrettyMethod().c_str(),
-                                current_method->GetDeclaringClassDescriptor());
-              return 0u;
-            }
-            if (!overrides) {
-              overrides = true;
-              super_index = current_index;
-              super_method = current_method;
-            }
-          }
-        }
-      }
-      if (overrides) {
-        virtual_method->SetMethodIndex(super_index);
-        continue;
-      }
-    }
-    // The method does not override any method from superclass, so it needs a new vtable index.
-    virtual_method->SetMethodIndex(vtable_length);
-    ++vtable_length;
   }
 
+  // Loop through each super vtable method and see if they are overridden by a method we added to
+  // the hash table.
+  for (size_t j = 0; j < super_vtable_length; ++j) {
+    // Search the hash table to see if we are overridden by any method.
+    ArtMethod* super_method = super_vtable_accessor.GetVTableEntry(j);
+    if (!klass->CanAccessMember(super_method->GetDeclaringClass(),
+                                super_method->GetAccessFlags())) {
+      // Continue on to the next method since this one is package private and cannot be overridden.
+      // Before Android 4.1, the package-private method super_method might have been incorrectly
+      // overridden.
+      continue;
+    }
+    size_t hash = (j < mirror::Object::kVTableLength)
+        ? class_linker_->object_virtual_method_hashes_[j]
+        : ComputeMethodHash(super_method);
+    auto it = declared_virtual_signatures.FindWithHash(super_method, hash);
+    if (it == declared_virtual_signatures.end()) {
+      continue;
+    }
+    ArtMethod* virtual_method = klass->GetVirtualMethodDuringLinking(*it, kPointerSize);
+    if (super_method->IsFinal()) {
+      sants.reset();
+      ThrowLinkageError(klass, "Method %s overrides final method in class %s",
+                        virtual_method->PrettyMethod().c_str(),
+                        super_method->GetDeclaringClassDescriptor());
+      return 0u;
+    }
+    if (initialized_methods.IsBitSet(*it)) {
+      // The method is overriding more than one method.
+      // We record that information in a linked list to later set the method in the vtable
+      // locations that are not the method index.
+      if (same_signature_vtable_lists.empty()) {
+        same_signature_vtable_lists = ArrayRef<uint32_t>(
+            allocator_.AllocArray<uint32_t>(super_vtable_length), super_vtable_length);
+        std::fill_n(same_signature_vtable_lists.data(), super_vtable_length, dex::kDexNoIndex);
+        same_signature_vtable_lists_ = same_signature_vtable_lists;
+      }
+      same_signature_vtable_lists[j] = virtual_method->GetMethodIndexDuringLinking();
+    } else {
+      initialized_methods.SetBit(*it);
+    }
+
+    // We arbitrarily set to the largest index. This is also expected when
+    // iterating over the `same_signature_vtable_lists_`.
+    virtual_method->SetMethodIndex(j);
+  }
+
+  // Add the non-overridden methods at the end.
+  for (size_t i = 0; i < num_virtual_methods; ++i) {
+    if (!initialized_methods.IsBitSet(i)) {
+      ArtMethod* local_method = klass->GetVirtualMethodDuringLinking(i, kPointerSize);
+      local_method->SetMethodIndex(vtable_length);
+      vtable_length++;
+    }
+  }
+
+  // A lazily constructed super vtable set, which we only populate in the less
+  // common sittuation of a superclass implementing a method declared in an
+  // interface this class inherits.
+  // We still try to allocate the set on the stack as using the arena will have
+  // a larger cost.
+  uint32_t* super_vtable_buffer_ptr = bit_vector_buffer_ptr + bit_vector_size;
+  VTableSignatureSet super_vtable_signatures(
+      kMinLoadFactor,
+      kMaxLoadFactor,
+      VTableSignatureHash(super_vtable_accessor),
+      VTableSignatureEqual(super_vtable_accessor),
+      super_vtable_buffer_ptr,
+      super_vtable_buffer_size,
+      allocator_.Adapter());
+
   // Assign vtable indexes for interface methods in new interfaces and store them
   // in implementation method arrays. These shall be replaced by actual method
   // pointers later. We do not need to do this for superclass interfaces as we can
@@ -8099,42 +8388,39 @@
       ArtMethod* interface_method = iface->GetVirtualMethod(j, kPointerSize);
       size_t hash = ComputeMethodHash(interface_method);
       ArtMethod* vtable_method = nullptr;
-      bool found = false;
       auto it1 = declared_virtual_signatures.FindWithHash(interface_method, hash);
       if (it1 != declared_virtual_signatures.end()) {
-        vtable_method = klass->GetVirtualMethodDuringLinking(*it1, kPointerSize);
-        found = true;
+        ArtMethod* found_method = klass->GetVirtualMethodDuringLinking(*it1, kPointerSize);
+        // For interface overriding, we only look at public methods.
+        if (found_method->IsPublic()) {
+          vtable_method = found_method;
+        }
       } else {
+        // This situation should be rare (a superclass implements a method
+        // declared in an interface this class is inheriting). Only in this case
+        // do we lazily populate the super_vtable_signatures.
+        if (super_vtable_signatures.empty()) {
+          for (size_t k = 0; k < super_vtable_length; ++k) {
+            ArtMethod* super_method = super_vtable_accessor.GetVTableEntry(k);
+            if (!super_method->IsPublic()) {
+              // For interface overriding, we only look at public methods.
+              continue;
+            }
+            size_t super_hash = (k < mirror::Object::kVTableLength)
+                ? class_linker_->object_virtual_method_hashes_[k]
+                : ComputeMethodHash(super_method);
+            auto [it, inserted] = super_vtable_signatures.InsertWithHash(k, super_hash);
+            DCHECK(inserted || super_vtable_accessor.GetVTableEntry(*it) == super_method);
+          }
+        }
         auto it2 = super_vtable_signatures.FindWithHash(interface_method, hash);
         if (it2 != super_vtable_signatures.end()) {
-          // If there are multiple vtable methods with the same signature, the one with
-          // the highest vtable index is not nessarily the one in most-derived class.
-          // Find the most-derived method. See b/211854716 .
           vtable_method = super_vtable_accessor.GetVTableEntry(*it2);
-          if (UNLIKELY(!same_signature_vtable_lists.empty())) {
-            size_t current_index = *it2;
-            while (same_signature_vtable_lists[current_index] != dex::kDexNoIndex) {
-              DCHECK_LT(same_signature_vtable_lists[current_index], current_index);
-              current_index = same_signature_vtable_lists[current_index];
-              ArtMethod* current_method = super_vtable_accessor.GetVTableEntry(current_index);
-              ObjPtr<mirror::Class> current_class = current_method->GetDeclaringClass();
-              if (current_class->IsSubClass(vtable_method->GetDeclaringClass())) {
-                vtable_method = current_method;
-              }
-            }
-          }
-          found = true;
         }
       }
+
       uint32_t vtable_index = vtable_length;
-      if (found) {
-        DCHECK(vtable_method != nullptr);
-        if (!vtable_method->IsAbstract() && !vtable_method->IsPublic()) {
-          // FIXME: Delay the exception until we actually try to call the method. b/211854716
-          sants.reset();
-          ThrowIllegalAccessErrorForImplementingMethod(klass, vtable_method, interface_method);
-          return 0u;
-        }
+      if (vtable_method != nullptr) {
         vtable_index = vtable_method->GetMethodIndexDuringLinking();
         if (!vtable_method->IsOverridableByDefaultMethod()) {
           method_array->SetElementPtrSize(j, vtable_index, kPointerSize);
@@ -8144,7 +8430,7 @@
 
       auto [it, inserted] = copied_method_records_.InsertWithHash(
           CopiedMethodRecord(interface_method, vtable_index), hash);
-      if (found) {
+      if (vtable_method != nullptr) {
         DCHECK_EQ(vtable_index, it->GetMethodIndex());
       } else if (inserted) {
         DCHECK_EQ(vtable_index, it->GetMethodIndex());
@@ -8310,26 +8596,29 @@
       ThrowClassFormatError(klass.Get(), "Too many methods on interface: %zu", num_virtual_methods);
       return false;
     }
+    // Assign each method an interface table index and set the default flag.
     bool has_defaults = false;
-    // Assign each method an IMT index and set the default flag.
     for (size_t i = 0; i < num_virtual_methods; ++i) {
       ArtMethod* m = klass->GetVirtualMethodDuringLinking(i, kPointerSize);
       m->SetMethodIndex(i);
-      if (!m->IsAbstract()) {
+      uint32_t access_flags = m->GetAccessFlags();
+      DCHECK(!ArtMethod::IsDefault(access_flags));
+      DCHECK_EQ(!ArtMethod::IsAbstract(access_flags), ArtMethod::IsInvokable(access_flags));
+      if (ArtMethod::IsInvokable(access_flags)) {
         // If the dex file does not support default methods, throw ClassFormatError.
         // This check is necessary to protect from odd cases, such as native default
         // methods, that the dex file verifier permits for old dex file versions. b/157170505
         // FIXME: This should be `if (!m->GetDexFile()->SupportsDefaultMethods())` but we're
         // currently running CTS tests for default methods with dex file version 035 which
         // does not support default methods. So, we limit this to native methods. b/157718952
-        if (m->IsNative()) {
+        if (ArtMethod::IsNative(access_flags)) {
           DCHECK(!m->GetDexFile()->SupportsDefaultMethods());
           ThrowClassFormatError(klass.Get(),
                                 "Dex file does not support default method '%s'",
                                 m->PrettyMethod().c_str());
           return false;
         }
-        if (!m->IsPublic()) {
+        if (!ArtMethod::IsPublic(access_flags)) {
           // The verifier should have caught the non-public method for dex version 37.
           // Just warn and skip it since this is from before default-methods so we don't
           // really need to care that it has code.
@@ -8337,7 +8626,7 @@
                        << "This will be a fatal error in subsequent versions of android. "
                        << "Continuing anyway.";
         }
-        m->SetAccessFlags(m->GetAccessFlags() | kAccDefault);
+        m->SetAccessFlags(access_flags | kAccDefault);
         has_defaults = true;
       }
     }
@@ -8484,17 +8773,16 @@
       uint32_t vtable_index = virtual_method.GetMethodIndexDuringLinking();
       vtable->SetElementPtrSize(vtable_index, &virtual_method, kPointerSize);
       if (UNLIKELY(vtable_index < same_signature_vtable_lists.size())) {
-        // We may override more than one method according to JLS, see b/211854716 .
-        // If we do, arbitrarily update the method index to the lowest overridden vtable index.
+        // We may override more than one method according to JLS, see b/211854716.
         while (same_signature_vtable_lists[vtable_index] != dex::kDexNoIndex) {
           DCHECK_LT(same_signature_vtable_lists[vtable_index], vtable_index);
           vtable_index = same_signature_vtable_lists[vtable_index];
-          ArtMethod* current_method = super_class->GetVTableEntry(vtable_index, kPointerSize);
-          if (klass->CanAccessMember(current_method->GetDeclaringClass(),
-                                     current_method->GetAccessFlags())) {
+          vtable->SetElementPtrSize(vtable_index, &virtual_method, kPointerSize);
+          if (kIsDebugBuild) {
+            ArtMethod* current_method = super_class->GetVTableEntry(vtable_index, kPointerSize);
+            DCHECK(klass->CanAccessMember(current_method->GetDeclaringClass(),
+                                          current_method->GetAccessFlags()));
             DCHECK(!current_method->IsFinal());
-            vtable->SetElementPtrSize(vtable_index, &virtual_method, kPointerSize);
-            virtual_method.SetMethodIndex(vtable_index);
           }
         }
       }
@@ -8606,7 +8894,8 @@
 };
 
 // We use the following order of field types for assigning offsets.
-// Some fields can be shuffled forward to fill gaps, see `ClassLinker::LinkFields()`.
+// Some fields can be shuffled forward to fill gaps, see
+// `ClassLinker::LinkFieldsHelper::LinkFields()`.
 enum class ClassLinker::LinkFieldsHelper::FieldTypeOrder : uint16_t {
   kReference = 0u,
   kLong,
@@ -9082,6 +9371,308 @@
   return LinkFieldsHelper::LinkFields(this, self, klass, true, class_size);
 }
 
+enum class RecordElementType : uint8_t {
+  kNames = 0,
+  kTypes = 1,
+  kSignatures = 2,
+  kAnnotationVisibilities = 3,
+  kAnnotations = 4
+};
+
+static const char* kRecordElementNames[] = {"componentNames",
+                                            "componentTypes",
+                                            "componentSignatures",
+                                            "componentAnnotationVisibilities",
+                                            "componentAnnotations"};
+
+class RecordAnnotationVisitor final : public annotations::AnnotationVisitor {
+ public:
+  RecordAnnotationVisitor() {}
+
+  bool ValidateCounts() {
+    if (is_error_) {
+      return false;
+    }
+
+    // Verify the counts.
+    bool annotation_element_exists =
+        (signatures_count_ != UINT32_MAX) || (annotations_count_ != UINT32_MAX);
+    if (count_ >= 2) {
+      SetErrorMsg("Record class can't have more than one @Record Annotation");
+    } else if (names_count_ == UINT32_MAX) {
+      SetErrorMsg("componentNames element is required");
+    } else if (types_count_ == UINT32_MAX) {
+      SetErrorMsg("componentTypes element is required");
+    } else if (names_count_ != types_count_) {  // Every component must have a name and a type.
+      SetErrorMsg(StringPrintf(
+          "componentTypes is expected to have %i, but has %i types", names_count_, types_count_));
+      // The other 3 elements are optional, but is expected to have the same count if it exists.
+    } else if (signatures_count_ != UINT32_MAX && signatures_count_ != names_count_) {
+      SetErrorMsg(StringPrintf("componentSignatures size is %i, but is expected to be %i",
+                               signatures_count_,
+                               names_count_));
+    } else if (annotation_element_exists && visibilities_count_ != names_count_) {
+      SetErrorMsg(
+          StringPrintf("componentAnnotationVisibilities size is %i, but is expected to be %i",
+                       visibilities_count_,
+                       names_count_));
+    } else if (annotation_element_exists && annotations_count_ != names_count_) {
+      SetErrorMsg(StringPrintf("componentAnnotations size is %i, but is expected to be %i",
+                               annotations_count_,
+                               names_count_));
+    }
+
+    return !is_error_;
+  }
+
+  const std::string& GetErrorMsg() { return error_msg_; }
+
+  bool IsRecordAnnotationFound() { return count_ != 0; }
+
+  annotations::VisitorStatus VisitAnnotation(const char* descriptor, uint8_t visibility) override {
+    if (is_error_) {
+      return annotations::VisitorStatus::kVisitBreak;
+    }
+
+    if (visibility != DexFile::kDexVisibilitySystem) {
+      return annotations::VisitorStatus::kVisitNext;
+    }
+
+    if (strcmp(descriptor, "Ldalvik/annotation/Record;") != 0) {
+      return annotations::VisitorStatus::kVisitNext;
+    }
+
+    count_ += 1;
+    if (count_ >= 2) {
+      return annotations::VisitorStatus::kVisitBreak;
+    }
+    return annotations::VisitorStatus::kVisitInner;
+  }
+
+  annotations::VisitorStatus VisitAnnotationElement(const char* element_name,
+                                                    uint8_t type,
+                                                    [[maybe_unused]] const JValue& value) override {
+    if (is_error_) {
+      return annotations::VisitorStatus::kVisitBreak;
+    }
+
+    RecordElementType visiting_type;
+    uint32_t* element_count;
+    if (strcmp(element_name, "componentNames") == 0) {
+      visiting_type = RecordElementType::kNames;
+      element_count = &names_count_;
+    } else if (strcmp(element_name, "componentTypes") == 0) {
+      visiting_type = RecordElementType::kTypes;
+      element_count = &types_count_;
+    } else if (strcmp(element_name, "componentSignatures") == 0) {
+      visiting_type = RecordElementType::kSignatures;
+      element_count = &signatures_count_;
+    } else if (strcmp(element_name, "componentAnnotationVisibilities") == 0) {
+      visiting_type = RecordElementType::kAnnotationVisibilities;
+      element_count = &visibilities_count_;
+    } else if (strcmp(element_name, "componentAnnotations") == 0) {
+      visiting_type = RecordElementType::kAnnotations;
+      element_count = &annotations_count_;
+    } else {
+      // ignore this element that could be introduced in the future ART.
+      return annotations::VisitorStatus::kVisitNext;
+    }
+
+    if ((*element_count) != UINT32_MAX) {
+      SetErrorMsg(StringPrintf("Two %s annotation elements are found but only one is expected",
+                               kRecordElementNames[static_cast<uint8_t>(visiting_type)]));
+      return annotations::VisitorStatus::kVisitBreak;
+    }
+
+    if (type != DexFile::kDexAnnotationArray) {
+      SetErrorMsg(StringPrintf("%s must be array type", element_name));
+      return annotations::VisitorStatus::kVisitBreak;
+    }
+
+    *element_count = 0;
+    visiting_type_ = visiting_type;
+    return annotations::VisitorStatus::kVisitInner;
+  }
+
+  annotations::VisitorStatus VisitArrayElement(uint8_t depth,
+                                               uint32_t index,
+                                               uint8_t type,
+                                               [[maybe_unused]] const JValue& value) override {
+    if (is_error_) {
+      return annotations::VisitorStatus::kVisitBreak;
+    }
+    switch (visiting_type_) {
+      case RecordElementType::kNames: {
+        if (depth == 0) {
+          if (!ExpectedTypeOrError(
+                  type, DexFile::kDexAnnotationString, visiting_type_, index, depth)) {
+            return annotations::VisitorStatus::kVisitBreak;
+          }
+          names_count_++;
+          return annotations::VisitorStatus::kVisitNext;
+        }
+        break;
+      }
+      case RecordElementType::kTypes: {
+        if (depth == 0) {
+          if (!ExpectedTypeOrError(
+                  type, DexFile::kDexAnnotationType, visiting_type_, index, depth)) {
+            return annotations::VisitorStatus::kVisitBreak;
+          }
+          types_count_++;
+          return annotations::VisitorStatus::kVisitNext;
+        }
+        break;
+      }
+      case RecordElementType::kSignatures: {
+        if (depth == 0) {
+          // kDexAnnotationNull implies no generic signature for the component.
+          if (type != DexFile::kDexAnnotationNull &&
+              !ExpectedTypeOrError(
+                  type, DexFile::kDexAnnotationAnnotation, visiting_type_, index, depth)) {
+            return annotations::VisitorStatus::kVisitBreak;
+          }
+          signatures_count_++;
+          return annotations::VisitorStatus::kVisitNext;
+        }
+        break;
+      }
+      case RecordElementType::kAnnotationVisibilities: {
+        if (depth == 0) {
+          if (!ExpectedTypeOrError(
+                  type, DexFile::kDexAnnotationArray, visiting_type_, index, depth)) {
+            return annotations::VisitorStatus::kVisitBreak;
+          }
+          visibilities_count_++;
+          return annotations::VisitorStatus::kVisitInner;
+        } else if (depth == 1) {
+          if (!ExpectedTypeOrError(
+                  type, DexFile::kDexAnnotationByte, visiting_type_, index, depth)) {
+            return annotations::VisitorStatus::kVisitBreak;
+          }
+          return annotations::VisitorStatus::kVisitNext;
+        }
+        break;
+      }
+      case RecordElementType::kAnnotations: {
+        if (depth == 0) {
+          if (!ExpectedTypeOrError(
+                  type, DexFile::kDexAnnotationArray, visiting_type_, index, depth)) {
+            return annotations::VisitorStatus::kVisitBreak;
+          }
+          annotations_count_++;
+          return annotations::VisitorStatus::kVisitInner;
+        } else if (depth == 1) {
+          if (!ExpectedTypeOrError(
+                  type, DexFile::kDexAnnotationAnnotation, visiting_type_, index, depth)) {
+            return annotations::VisitorStatus::kVisitBreak;
+          }
+          return annotations::VisitorStatus::kVisitNext;
+        }
+        break;
+      }
+    }
+
+    // Should never happen if every next depth level is handled above whenever kVisitInner is
+    // returned.
+    DCHECK(false) << StringPrintf("Unexpected depth %i for element %s",
+                                  depth,
+                                  kRecordElementNames[static_cast<uint8_t>(visiting_type_)]);
+    return annotations::VisitorStatus::kVisitBreak;
+  }
+
+ private:
+  bool is_error_ = false;
+  uint32_t count_ = 0;
+  uint32_t names_count_ = UINT32_MAX;
+  uint32_t types_count_ = UINT32_MAX;
+  uint32_t signatures_count_ = UINT32_MAX;
+  uint32_t visibilities_count_ = UINT32_MAX;
+  uint32_t annotations_count_ = UINT32_MAX;
+  std::string error_msg_;
+  RecordElementType visiting_type_;
+
+  inline bool ExpectedTypeOrError(uint8_t type,
+                                  uint8_t expected,
+                                  RecordElementType visiting_type,
+                                  uint8_t depth,
+                                  uint32_t index) {
+    if (type == expected) {
+      return true;
+    }
+
+    SetErrorMsg(StringPrintf(
+        "Expect 0x%02x type but got 0x%02x at the index %i and depth %i for the element %s",
+        expected,
+        type,
+        index,
+        depth,
+        kRecordElementNames[static_cast<uint8_t>(visiting_type)]));
+    return false;
+  }
+
+  void SetErrorMsg(const std::string& msg) {
+    is_error_ = true;
+    error_msg_ = msg;
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(RecordAnnotationVisitor);
+};
+
+/**
+ * Set kClassFlagRecord and verify if klass is a record class.
+ * If the verification fails, a pending java exception is thrown.
+ *
+ * @return false if verification fails. If klass isn't a record class,
+ * it should always return true.
+ */
+bool ClassLinker::VerifyRecordClass(Handle<mirror::Class> klass, ObjPtr<mirror::Class> super) {
+  CHECK(klass != nullptr);
+  // First, we check the conditions specified in java.lang.Class#isRecord().
+  // If any of the conditions isn't fulfilled, it's not a record class and
+  // ART should treat it as a normal class even if it's inherited from java.lang.Record.
+  if (!klass->IsFinal()) {
+    return true;
+  }
+
+  if (super == nullptr) {
+    return true;
+  }
+
+  // Compare the string directly when this ClassLinker is initializing before
+  // WellKnownClasses initializes
+  if (WellKnownClasses::java_lang_Record == nullptr) {
+    if (!super->DescriptorEquals("Ljava/lang/Record;")) {
+      return true;
+    }
+  } else {
+    ObjPtr<mirror::Class> java_lang_Record =
+        WellKnownClasses::ToClass(WellKnownClasses::java_lang_Record);
+    if (super.Ptr() != java_lang_Record.Ptr()) {
+      return true;
+    }
+  }
+
+  // Verify @dalvik.annotation.Record
+  // The annotation has a mandatory element componentNames[] and componentTypes[] of the same size.
+  // componentSignatures[], componentAnnotationVisibilities[][], componentAnnotations[][] are
+  // optional, but should have the same size if it exists.
+  RecordAnnotationVisitor visitor;
+  annotations::VisitClassAnnotations(klass, &visitor);
+  if (!visitor.IsRecordAnnotationFound()) {
+    return true;
+  }
+
+  if (!visitor.ValidateCounts()) {
+    ThrowClassFormatError(klass.Get(), "%s", visitor.GetErrorMsg().c_str());
+    return false;
+  }
+
+  // Set kClassFlagRecord.
+  klass->SetRecordClass();
+  return true;
+}
+
 //  Set the bitmap of reference instance field offsets.
 void ClassLinker::CreateReferenceInstanceOffsets(Handle<mirror::Class> klass) {
   uint32_t reference_offsets = 0;
@@ -9340,19 +9931,7 @@
     Thread::Current()->AssertPendingException();
     return nullptr;
   }
-  if (klass->IsInterface()) {
-    resolved = klass->FindInterfaceMethod(dex_cache.Get(), method_idx, image_pointer_size_);
-  } else {
-    resolved = klass->FindClassMethod(dex_cache.Get(), method_idx, image_pointer_size_);
-  }
-  if (resolved != nullptr &&
-      hiddenapi::ShouldDenyAccessToMember(
-          resolved,
-          hiddenapi::AccessContext(class_loader.Get(), dex_cache.Get()),
-          hiddenapi::AccessMethod::kLinking)) {
-    resolved = nullptr;
-  }
-  return resolved;
+  return FindResolvedMethod(klass, dex_cache.Get(), class_loader.Get(), method_idx);
 }
 
 ArtField* ClassLinker::LookupResolvedField(uint32_t field_idx,
@@ -9504,6 +10083,9 @@
   Handle<mirror::MethodType> type = hs.NewHandle(
       mirror::MethodType::Create(self, return_type, method_params));
   if (type != nullptr) {
+    // Ensure all stores for the newly created MethodType are visible, before we attempt to place
+    // it in the DexCache (b/224733324).
+    std::atomic_thread_fence(std::memory_order_release);
     dex_cache->SetResolvedMethodType(proto_idx, type.Get());
   }
 
@@ -10000,11 +10582,13 @@
     Handle<mirror::ClassLoader> parent_loader,
     Handle<mirror::ObjectArray<mirror::ClassLoader>> shared_libraries,
     Handle<mirror::ObjectArray<mirror::ClassLoader>> shared_libraries_after) {
+  CHECK(loader_class.Get() == WellKnownClasses::dalvik_system_PathClassLoader ||
+        loader_class.Get() == WellKnownClasses::dalvik_system_DelegateLastClassLoader ||
+        loader_class.Get() == WellKnownClasses::dalvik_system_InMemoryDexClassLoader);
 
   StackHandleScope<5> hs(self);
 
-  ArtField* dex_elements_field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements);
+  ArtField* dex_elements_field = WellKnownClasses::dalvik_system_DexPathList_dexElements;
 
   Handle<mirror::Class> dex_elements_class(hs.NewHandle(dex_elements_field->ResolveType()));
   DCHECK(dex_elements_class != nullptr);
@@ -10016,14 +10600,13 @@
   Handle<mirror::Class> h_dex_element_class =
       hs.NewHandle(dex_elements_class->GetComponentType());
 
-  ArtField* element_file_field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+  ArtField* element_file_field = WellKnownClasses::dalvik_system_DexPathList__Element_dexFile;
   DCHECK_EQ(h_dex_element_class.Get(), element_file_field->GetDeclaringClass());
 
-  ArtField* cookie_field = jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
+  ArtField* cookie_field = WellKnownClasses::dalvik_system_DexFile_cookie;
   DCHECK_EQ(cookie_field->GetDeclaringClass(), element_file_field->LookupResolvedType());
 
-  ArtField* file_name_field = jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_fileName);
+  ArtField* file_name_field = WellKnownClasses::dalvik_system_DexFile_fileName;
   DCHECK_EQ(file_name_field->GetDeclaringClass(), element_file_field->LookupResolvedType());
 
   // Fill the elements array.
@@ -10095,88 +10678,45 @@
       ObjPtr<mirror::ClassLoader>::DownCast(loader_class->AllocObject(self)));
   DCHECK(h_class_loader != nullptr);
   // Set DexPathList.
-  ArtField* path_list_field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList);
+  ArtField* path_list_field = WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList;
   DCHECK(path_list_field != nullptr);
   path_list_field->SetObject<false>(h_class_loader.Get(), h_dex_path_list.Get());
 
   // Make a pretend boot-classpath.
   // TODO: Should we scan the image?
-  ArtField* const parent_field =
-      jni::DecodeArtField(WellKnownClasses::java_lang_ClassLoader_parent);
+  ArtField* const parent_field = WellKnownClasses::java_lang_ClassLoader_parent;
   DCHECK(parent_field != nullptr);
   if (parent_loader.Get() == nullptr) {
-    ScopedObjectAccessUnchecked soa(self);
-    ObjPtr<mirror::Object> boot_loader(soa.Decode<mirror::Class>(
-        WellKnownClasses::java_lang_BootClassLoader)->AllocObject(self));
+    ObjPtr<mirror::Object> boot_loader(
+        WellKnownClasses::java_lang_BootClassLoader->AllocObject(self));
     parent_field->SetObject<false>(h_class_loader.Get(), boot_loader);
   } else {
     parent_field->SetObject<false>(h_class_loader.Get(), parent_loader.Get());
   }
 
   ArtField* shared_libraries_field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders);
+      WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
   DCHECK(shared_libraries_field != nullptr);
   shared_libraries_field->SetObject<false>(h_class_loader.Get(), shared_libraries.Get());
 
   ArtField* shared_libraries_after_field =
-        jni::DecodeArtField(
-        WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter);
+        WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter;
   DCHECK(shared_libraries_after_field != nullptr);
   shared_libraries_after_field->SetObject<false>(h_class_loader.Get(),
                                                  shared_libraries_after.Get());
   return h_class_loader.Get();
 }
 
-jobject ClassLinker::CreateWellKnownClassLoader(Thread* self,
-                                                const std::vector<const DexFile*>& dex_files,
-                                                jclass loader_class,
-                                                jobject parent_loader,
-                                                jobject shared_libraries,
-                                                jobject shared_libraries_after) {
-  CHECK(self->GetJniEnv()->IsSameObject(loader_class,
-                                        WellKnownClasses::dalvik_system_PathClassLoader) ||
-        self->GetJniEnv()->IsSameObject(loader_class,
-                                        WellKnownClasses::dalvik_system_DelegateLastClassLoader) ||
-        self->GetJniEnv()->IsSameObject(loader_class,
-                                        WellKnownClasses::dalvik_system_InMemoryDexClassLoader));
-
-  // SOAAlreadyRunnable is protected, and we need something to add a global reference.
-  // We could move the jobject to the callers, but all call-sites do this...
-  ScopedObjectAccessUnchecked soa(self);
-
-  // For now, create a libcore-level DexFile for each ART DexFile. This "explodes" multidex.
-  StackHandleScope<5> hs(self);
-
-  Handle<mirror::Class> h_loader_class =
-      hs.NewHandle<mirror::Class>(soa.Decode<mirror::Class>(loader_class));
-  Handle<mirror::ClassLoader> h_parent =
-      hs.NewHandle<mirror::ClassLoader>(soa.Decode<mirror::ClassLoader>(parent_loader));
-  Handle<mirror::ObjectArray<mirror::ClassLoader>> h_shared_libraries =
-      hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::ClassLoader>>(shared_libraries));
-  Handle<mirror::ObjectArray<mirror::ClassLoader>> h_shared_libraries_after =
-        hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::ClassLoader>>(shared_libraries_after));
-
-  ObjPtr<mirror::ClassLoader> loader = CreateWellKnownClassLoader(
-      self,
-      dex_files,
-      h_loader_class,
-      h_parent,
-      h_shared_libraries,
-      h_shared_libraries_after);
-
-  // Make it a global ref and return.
-  ScopedLocalRef<jobject> local_ref(
-      soa.Env(), soa.Env()->AddLocalReference<jobject>(loader));
-  return soa.Env()->NewGlobalRef(local_ref.get());
-}
-
 jobject ClassLinker::CreatePathClassLoader(Thread* self,
                                            const std::vector<const DexFile*>& dex_files) {
-  return CreateWellKnownClassLoader(self,
-                                    dex_files,
-                                    WellKnownClasses::dalvik_system_PathClassLoader,
-                                    nullptr);
+  StackHandleScope<3u> hs(self);
+  Handle<mirror::Class> d_s_pcl =
+      hs.NewHandle(WellKnownClasses::dalvik_system_PathClassLoader.Get());
+  auto null_parent = hs.NewHandle<mirror::ClassLoader>(nullptr);
+  auto null_libs = hs.NewHandle<mirror::ObjectArray<mirror::ClassLoader>>(nullptr);
+  ObjPtr<mirror::ClassLoader> class_loader =
+      CreateWellKnownClassLoader(self, dex_files, d_s_pcl, null_parent, null_libs, null_libs);
+  return Runtime::Current()->GetJavaVM()->AddGlobalRef(self, class_loader);
 }
 
 void ClassLinker::DropFindArrayClassCache() {
@@ -10196,6 +10736,18 @@
   }
 }
 
+void ClassLinker::VisitDexCaches(DexCacheVisitor* visitor) const {
+  Thread* const self = Thread::Current();
+  for (const auto& it : dex_caches_) {
+    // Need to use DecodeJObject so that we get null for cleared JNI weak globals.
+    ObjPtr<mirror::DexCache> dex_cache = ObjPtr<mirror::DexCache>::DownCast(
+        self->DecodeJObject(it.second.weak_root));
+    if (dex_cache != nullptr) {
+      visitor->Visit(dex_cache);
+    }
+  }
+}
+
 void ClassLinker::VisitAllocators(AllocatorVisitor* visitor) const {
   for (const ClassLoaderData& data : class_loaders_) {
     LinearAlloc* alloc = data.allocator;
@@ -10221,27 +10773,77 @@
 
 void ClassLinker::CleanupClassLoaders() {
   Thread* const self = Thread::Current();
-  std::vector<ClassLoaderData> to_delete;
+  std::list<ClassLoaderData> to_delete;
   // Do the delete outside the lock to avoid lock violation in jit code cache.
   {
     WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
     for (auto it = class_loaders_.begin(); it != class_loaders_.end(); ) {
-      const ClassLoaderData& data = *it;
+      auto this_it = it;
+      ++it;
+      const ClassLoaderData& data = *this_it;
       // Need to use DecodeJObject so that we get null for cleared JNI weak globals.
       ObjPtr<mirror::ClassLoader> class_loader =
           ObjPtr<mirror::ClassLoader>::DownCast(self->DecodeJObject(data.weak_root));
-      if (class_loader != nullptr) {
-        ++it;
-      } else {
+      if (class_loader == nullptr) {
         VLOG(class_linker) << "Freeing class loader";
-        to_delete.push_back(data);
-        it = class_loaders_.erase(it);
+        to_delete.splice(to_delete.end(), class_loaders_, this_it);
       }
     }
   }
-  for (ClassLoaderData& data : to_delete) {
-    // CHA unloading analysis and SingleImplementaion cleanups are required.
-    DeleteClassLoader(self, data, /*cleanup_cha=*/ true);
+  if (to_delete.empty()) {
+    return;
+  }
+  std::set<const OatFile*> unregistered_oat_files;
+  JavaVMExt* vm = self->GetJniEnv()->GetVm();
+  {
+    WriterMutexLock mu(self, *Locks::dex_lock_);
+    for (auto it = dex_caches_.begin(), end = dex_caches_.end(); it != end; ) {
+      const DexFile* dex_file = it->first;
+      const DexCacheData& data = it->second;
+      if (self->DecodeJObject(data.weak_root) == nullptr) {
+        DCHECK(to_delete.end() != std::find_if(
+            to_delete.begin(),
+            to_delete.end(),
+            [&](const ClassLoaderData& cld) { return cld.class_table == data.class_table; }));
+        if (dex_file->GetOatDexFile() != nullptr &&
+            dex_file->GetOatDexFile()->GetOatFile() != nullptr &&
+            dex_file->GetOatDexFile()->GetOatFile()->IsExecutable()) {
+          unregistered_oat_files.insert(dex_file->GetOatDexFile()->GetOatFile());
+        }
+        vm->DeleteWeakGlobalRef(self, data.weak_root);
+        it = dex_caches_.erase(it);
+      } else {
+        ++it;
+      }
+    }
+  }
+  {
+    ScopedDebugDisallowReadBarriers sddrb(self);
+    for (ClassLoaderData& data : to_delete) {
+      // CHA unloading analysis and SingleImplementaion cleanups are required.
+      DeleteClassLoader(self, data, /*cleanup_cha=*/ true);
+    }
+  }
+  Runtime* runtime = Runtime::Current();
+  if (!unregistered_oat_files.empty()) {
+    for (const OatFile* oat_file : unregistered_oat_files) {
+      // Notify the fault handler about removal of the executable code range if needed.
+      DCHECK(oat_file->IsExecutable());
+      size_t exec_offset = oat_file->GetOatHeader().GetExecutableOffset();
+      DCHECK_LE(exec_offset, oat_file->Size());
+      size_t exec_size = oat_file->Size() - exec_offset;
+      if (exec_size != 0u) {
+        runtime->RemoveGeneratedCodeRange(oat_file->Begin() + exec_offset, exec_size);
+      }
+    }
+  }
+
+  if (runtime->GetStartupLinearAlloc() != nullptr) {
+    // Because the startup linear alloc can contain dex cache arrays associated
+    // to class loaders that got unloaded, we need to delete these
+    // arrays.
+    StartupCompletedTask::DeleteStartupDexCaches(self, /* called_by_gc= */ true);
+    DCHECK_EQ(runtime->GetStartupLinearAlloc(), nullptr);
   }
 }
 
@@ -10269,9 +10871,73 @@
   CHECK(method->IsCopied());
   FindVirtualMethodHolderVisitor visitor(method, image_pointer_size_);
   VisitClasses(&visitor);
+  DCHECK(visitor.holder_ != nullptr);
   return visitor.holder_;
 }
 
+ObjPtr<mirror::ClassLoader> ClassLinker::GetHoldingClassLoaderOfCopiedMethod(Thread* self,
+                                                                             ArtMethod* method) {
+  // Note: `GetHoldingClassOfCopiedMethod(method)` is a lot more expensive than finding
+  // the class loader, so we're using it only to verify the result in debug mode.
+  CHECK(method->IsCopied());
+  gc::Heap* heap = Runtime::Current()->GetHeap();
+  // Check if the copied method is in the boot class path.
+  if (heap->IsBootImageAddress(method) || GetAllocatorForClassLoader(nullptr)->Contains(method)) {
+    DCHECK(GetHoldingClassOfCopiedMethod(method)->GetClassLoader() == nullptr);
+    return nullptr;
+  }
+  // Check if the copied method is in an app image.
+  // Note: Continuous spaces contain boot image spaces and app image spaces.
+  // However, they are sorted by address, so boot images are not trivial to skip.
+  ArrayRef<gc::space::ContinuousSpace* const> spaces(heap->GetContinuousSpaces());
+  DCHECK_GE(spaces.size(), heap->GetBootImageSpaces().size());
+  for (gc::space::ContinuousSpace* space : spaces) {
+    if (space->IsImageSpace()) {
+      gc::space::ImageSpace* image_space = space->AsImageSpace();
+      size_t offset = reinterpret_cast<const uint8_t*>(method) - image_space->Begin();
+      const ImageSection& methods_section = image_space->GetImageHeader().GetMethodsSection();
+      if (offset - methods_section.Offset() < methods_section.Size()) {
+        // Grab the class loader from the first non-BCP class in the app image class table.
+        // Note: If we allow classes from arbitrary parent or library class loaders in app
+        // images, this shall need to be updated to actually search for the exact class.
+        const ImageSection& class_table_section =
+            image_space->GetImageHeader().GetClassTableSection();
+        CHECK_NE(class_table_section.Size(), 0u);
+        const uint8_t* ptr = image_space->Begin() + class_table_section.Offset();
+        size_t read_count = 0;
+        ClassTable::ClassSet class_set(ptr, /*make_copy_of_data=*/ false, &read_count);
+        CHECK(!class_set.empty());
+        auto it = class_set.begin();
+        // No read barrier needed for references to non-movable image classes.
+        while ((*it).Read<kWithoutReadBarrier>()->IsBootStrapClassLoaded()) {
+          ++it;
+          CHECK(it != class_set.end());
+        }
+        ObjPtr<mirror::ClassLoader> class_loader =
+            (*it).Read<kWithoutReadBarrier>()->GetClassLoader();
+        DCHECK(GetHoldingClassOfCopiedMethod(method)->GetClassLoader() == class_loader);
+        return class_loader;
+      }
+    }
+  }
+  // Otherwise, the method must be in one of the `LinearAlloc` memory areas.
+  jweak result = nullptr;
+  {
+    ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);
+    for (const ClassLoaderData& data : class_loaders_) {
+      if (data.allocator->Contains(method)) {
+        result = data.weak_root;
+        break;
+      }
+    }
+  }
+  CHECK(result != nullptr) << "Did not find allocator holding the copied method: " << method
+      << " " << method->PrettyMethod();
+  // The `method` is alive, so the class loader must also be alive.
+  return ObjPtr<mirror::ClassLoader>::DownCast(
+      Runtime::Current()->GetJavaVM()->DecodeWeakGlobalAsStrong(result));
+}
+
 bool ClassLinker::DenyAccessBasedOnPublicSdk(ArtMethod* art_method ATTRIBUTE_UNUSED) const
     REQUIRES_SHARED(Locks::mutator_lock_) {
   // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
@@ -10298,6 +10964,15 @@
   UNREACHABLE();
 }
 
+void ClassLinker::RemoveDexFromCaches(const DexFile& dex_file) {
+  ReaderMutexLock mu(Thread::Current(), *Locks::dex_lock_);
+
+  auto it = dex_caches_.find(&dex_file);
+  if (it != dex_caches_.end()) {
+      dex_caches_.erase(it);
+  }
+}
+
 // Instantiate ClassLinker::AllocClass.
 template ObjPtr<mirror::Class> ClassLinker::AllocClass</* kMovable= */ true>(
     Thread* self,
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 3b20c75b..d14e46a 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -26,6 +26,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/array_ref.h"
 #include "base/enums.h"
 #include "base/hash_map.h"
 #include "base/intrusive_forward_list.h"
@@ -36,6 +37,7 @@
 #include "dex/dex_file_types.h"
 #include "gc_root.h"
 #include "handle.h"
+#include "interpreter/mterp/nterp.h"
 #include "jni.h"
 #include "mirror/class.h"
 #include "mirror/object.h"
@@ -47,6 +49,7 @@
 class ArtField;
 class ArtMethod;
 class ClassHierarchyAnalysis;
+class ClassLoaderContext;
 enum class ClassRoot : uint32_t;
 class ClassTable;
 class DexFile;
@@ -127,6 +130,13 @@
       REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_) = 0;
 };
 
+class DexCacheVisitor {
+ public:
+  virtual ~DexCacheVisitor() {}
+  virtual void Visit(ObjPtr<mirror::DexCache> dex_cache)
+      REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) = 0;
+};
+
 template <typename Func>
 class ClassLoaderFuncVisitor final : public ClassLoaderVisitor {
  public:
@@ -168,20 +178,21 @@
 
   // Add boot class path dex files that were not included in the boot image.
   // ClassLinker takes ownership of these dex files.
+  // DO NOT use directly. Use `Runtime::AddExtraBootDexFiles`.
   void AddExtraBootDexFiles(Thread* self,
                             std::vector<std::unique_ptr<const DexFile>>&& additional_dex_files)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Add an image space to the class linker, may fix up classloader fields and dex cache fields.
-  // The dex files that were newly opened for the space are placed in the out argument
-  // out_dex_files. Returns true if the operation succeeded.
+  // Add image spaces to the class linker, may fix up classloader fields and dex cache fields.
+  // The dex files that were newly opened for the space are placed in the out argument `dex_files`.
+  // Returns true if the operation succeeded.
   // The space must be already added to the heap before calling AddImageSpace since we need to
   // properly handle read barriers and object marking.
-  bool AddImageSpace(gc::space::ImageSpace* space,
-                     Handle<mirror::ClassLoader> class_loader,
-                     std::vector<std::unique_ptr<const DexFile>>* out_dex_files,
-                     std::string* error_msg)
-      REQUIRES(!Locks::dex_lock_)
+  bool AddImageSpaces(ArrayRef<gc::space::ImageSpace*> spaces,
+                      Handle<mirror::ClassLoader> class_loader,
+                      ClassLoaderContext* context,
+                      /*out*/ std::vector<std::unique_ptr<const DexFile>>* dex_files,
+                      /*out*/ std::string* error_msg) REQUIRES(!Locks::dex_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool OpenImageDexFiles(gc::space::ImageSpace* space,
@@ -358,14 +369,11 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
 
-  template <InvokeType type, ResolveMode kResolveMode>
-  ArtMethod* GetResolvedMethod(uint32_t method_idx, ArtMethod* referrer)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   template <ResolveMode kResolveMode>
   ArtMethod* ResolveMethod(Thread* self, uint32_t method_idx, ArtMethod* referrer, InvokeType type)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
+
   ArtMethod* ResolveMethodWithoutInvokeType(uint32_t method_idx,
                                             Handle<mirror::DexCache> dex_cache,
                                             Handle<mirror::ClassLoader> class_loader)
@@ -374,6 +382,12 @@
 
   ArtField* LookupResolvedField(uint32_t field_idx, ArtMethod* referrer, bool is_static)
       REQUIRES_SHARED(Locks::mutator_lock_);
+  // Find a field by its field index.
+  ArtField* LookupResolvedField(uint32_t field_idx,
+                                ObjPtr<mirror::DexCache> dex_cache,
+                                ObjPtr<mirror::ClassLoader> class_loader,
+                                bool is_static)
+      REQUIRES_SHARED(Locks::mutator_lock_);
   ArtField* ResolveField(uint32_t field_idx, ArtMethod* referrer, bool is_static)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
@@ -452,6 +466,12 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
 
+  // Initializes a few essential classes, namely `java.lang.Class`,
+  // `java.lang.Object` and `java.lang.reflect.Field`.
+  void RunEarlyRootClinits(Thread* self)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
+
   // Initializes classes that have instances in the image but that have
   // <clinit> methods so they could not be initialized by the compiler.
   void RunRootClinits(Thread* self)
@@ -478,6 +498,11 @@
       REQUIRES(!Locks::classlinker_classes_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Visits only the classes in the boot class path.
+  template <typename Visitor>
+  inline void VisitBootClasses(Visitor* visitor)
+      REQUIRES_SHARED(Locks::classlinker_classes_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
   // Less efficient variant of VisitClasses that copies the class_table_ into secondary storage
   // so that it can visit individual classes without holding the doesn't hold the
   // Locks::classlinker_classes_lock_. As the Locks::classlinker_classes_lock_ isn't held this code
@@ -608,6 +633,11 @@
     return nterp_trampoline_ == entry_point;
   }
 
+  bool IsNterpEntryPoint(const void* entry_point) const {
+    return entry_point == interpreter::GetNterpEntryPoint() ||
+        entry_point == interpreter::GetNterpWithClinitEntryPoint();
+  }
+
   const void* GetQuickToInterpreterBridgeTrampoline() const {
     return quick_to_interpreter_bridge_trampoline_;
   }
@@ -645,31 +675,19 @@
       REQUIRES(!Locks::classlinker_classes_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Creates a GlobalRef PathClassLoader or DelegateLastClassLoader (specified by loader_class)
-  // that can be used to load classes from the given dex files. The parent of the class loader
-  // will be set to `parent_loader`. If `parent_loader` is null the parent will be
-  // the boot class loader.
-  // If class_loader points to a different class than PathClassLoader or DelegateLastClassLoader
-  // this method will abort.
-  // Note: the objects are not completely set up. Do not use this outside of tests and the compiler.
-  jobject CreateWellKnownClassLoader(Thread* self,
-                                     const std::vector<const DexFile*>& dex_files,
-                                     jclass loader_class,
-                                     jobject parent_loader,
-                                     jobject shared_libraries = nullptr,
-                                     jobject shared_libraries_after = nullptr)
-      REQUIRES_SHARED(Locks::mutator_lock_)
-      REQUIRES(!Locks::dex_lock_);
-
-  // Calls CreateWellKnownClassLoader(self,
-  //                                  dex_files,
-  //                                  WellKnownClasses::dalvik_system_PathClassLoader,
-  //                                  nullptr)
+  // Calls `CreateWellKnownClassLoader()` with `WellKnownClasses::dalvik_system_PathClassLoader`,
+  // and null parent and libraries. Wraps the result in a JNI global reference.
   jobject CreatePathClassLoader(Thread* self, const std::vector<const DexFile*>& dex_files)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_);
 
-  // Non-GlobalRef version of CreateWellKnownClassLoader
+  // Creates a `PathClassLoader`, `DelegateLastClassLoader` or `InMemoryDexClassLoader`
+  // (specified by loader_class) that can be used to load classes from the given dex files.
+  // The parent of the class loader will be set to `parent_loader`. If `parent_loader` is
+  // null the parent will be the boot class loader.
+  // If `loader_class` points to a different class than `PathClassLoader`,
+  // `DelegateLastClassLoader` or `InMemoryDexClassLoader` this method will abort.
+  // Note: the objects are not completely set up. Do not use this outside of tests and the compiler.
   ObjPtr<mirror::ClassLoader> CreateWellKnownClassLoader(
       Thread* self,
       const std::vector<const DexFile*>& dex_files,
@@ -710,8 +728,7 @@
       REQUIRES(!Locks::classlinker_classes_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  static bool IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                ObjPtr<mirror::ClassLoader> class_loader)
+  static bool IsBootClassLoader(ObjPtr<mirror::Object> class_loader)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   ArtMethod* AddMethodToConflictTable(ObjPtr<mirror::Class> klass,
@@ -755,6 +772,11 @@
   ObjPtr<mirror::Class> GetHoldingClassOfCopiedMethod(ArtMethod* method)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Get the class loader holding class for a copied method.
+  ObjPtr<mirror::ClassLoader> GetHoldingClassLoaderOfCopiedMethod(Thread* self, ArtMethod* method)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::classlinker_classes_lock_);
+
   // Returns null if not found.
   // This returns a pointer to the class-table, without requiring any locking - including the
   // boot class-table. It is the caller's responsibility to access this under lock, if required.
@@ -762,10 +784,12 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       NO_THREAD_SAFETY_ANALYSIS;
 
+  // DO NOT use directly. Use `Runtime::AppendToBootClassPath`.
   void AppendToBootClassPath(Thread* self, const DexFile* dex_file)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_);
 
+  // DO NOT use directly. Use `Runtime::AppendToBootClassPath`.
   void AppendToBootClassPath(const DexFile* dex_file, ObjPtr<mirror::DexCache> dex_cache)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_);
@@ -774,6 +798,10 @@
   void VisitClassLoaders(ClassLoaderVisitor* visitor) const
       REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_);
 
+  // Visit all of the dex caches in the class linker.
+  void VisitDexCaches(DexCacheVisitor* visitor) const
+      REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_);
+
   // Checks that a class and its superclass from another class loader have the same virtual methods.
   bool ValidateSuperClassDescriptors(Handle<mirror::Class> klass)
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -782,7 +810,7 @@
     return cha_.get();
   }
 
-  void MakeInitializedClassesVisiblyInitialized(Thread* self, bool wait);
+  void MakeInitializedClassesVisiblyInitialized(Thread* self, bool wait /* ==> no locks held */);
 
   // Registers the native method and returns the new entry point. NB The returned entry point
   // might be different from the native_method argument if some MethodCallback modifies it.
@@ -837,6 +865,7 @@
   virtual bool DenyAccessBasedOnPublicSdk(const char* type_descriptor) const;
   // Enable or disable public sdk checks.
   virtual void SetEnablePublicSdkChecks(bool enabled);
+  void RemoveDexFromCaches(const DexFile& dex_file);
 
  protected:
   virtual bool InitializeClass(Thread* self,
@@ -987,8 +1016,7 @@
   // class-loader chain could be handled, false otherwise, i.e., a non-supported class-loader
   // was encountered while walking the parent chain (currently only BootClassLoader and
   // PathClassLoader are supported).
-  bool FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                     Thread* self,
+  bool FindClassInBaseDexClassLoader(Thread* self,
                                      const char* descriptor,
                                      size_t hash,
                                      Handle<mirror::ClassLoader> class_loader,
@@ -996,8 +1024,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_);
 
-  bool FindClassInSharedLibraries(ScopedObjectAccessAlreadyRunnable& soa,
-                                  Thread* self,
+  bool FindClassInSharedLibraries(Thread* self,
                                   const char* descriptor,
                                   size_t hash,
                                   Handle<mirror::ClassLoader> class_loader,
@@ -1005,8 +1032,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_);
 
-  bool FindClassInSharedLibrariesHelper(ScopedObjectAccessAlreadyRunnable& soa,
-                                        Thread* self,
+  bool FindClassInSharedLibrariesHelper(Thread* self,
                                         const char* descriptor,
                                         size_t hash,
                                         Handle<mirror::ClassLoader> class_loader,
@@ -1015,8 +1041,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_);
 
-  bool FindClassInSharedLibrariesAfter(ScopedObjectAccessAlreadyRunnable& soa,
-                                       Thread* self,
+  bool FindClassInSharedLibrariesAfter(Thread* self,
                                        const char* descriptor,
                                        size_t hash,
                                        Handle<mirror::ClassLoader> class_loader,
@@ -1032,7 +1057,7 @@
   // The method always returns true, to notify to the caller a
   // BaseDexClassLoader has a known lookup.
   bool FindClassInBaseDexClassLoaderClassPath(
-          ScopedObjectAccessAlreadyRunnable& soa,
+          Thread* self,
           const char* descriptor,
           size_t hash,
           Handle<mirror::ClassLoader> class_loader,
@@ -1094,13 +1119,6 @@
       REQUIRES(!Locks::classlinker_classes_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Find a field by its field index.
-  ArtField* LookupResolvedField(uint32_t field_idx,
-                                ObjPtr<mirror::DexCache> dex_cache,
-                                ObjPtr<mirror::ClassLoader> class_loader,
-                                bool is_static)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   void RegisterDexFileLocked(const DexFile& dex_file,
                              ObjPtr<mirror::DexCache> dex_cache,
                              ObjPtr<mirror::ClassLoader> class_loader)
@@ -1167,6 +1185,8 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
   bool LinkInstanceFields(Thread* self, Handle<mirror::Class> klass)
       REQUIRES_SHARED(Locks::mutator_lock_);
+  bool VerifyRecordClass(Handle<mirror::Class> klass, ObjPtr<mirror::Class> super)
+      REQUIRES_SHARED(Locks::mutator_lock_);
   void CreateReferenceInstanceOffsets(Handle<mirror::Class> klass)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -1275,6 +1295,19 @@
 
   ObjPtr<mirror::IfTable> GetArrayIfTable() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  bool OpenAndInitImageDexFiles(const gc::space::ImageSpace* space,
+                                Handle<mirror::ClassLoader> class_loader,
+                                std::vector<std::unique_ptr<const DexFile>>* out_dex_files,
+                                std::string* error_msg) REQUIRES(!Locks::dex_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  bool AddImageSpace(gc::space::ImageSpace* space,
+                     Handle<mirror::ClassLoader> class_loader,
+                     ClassLoaderContext* context,
+                     const std::vector<std::unique_ptr<const DexFile>>& dex_files,
+                     std::string* error_msg) REQUIRES(!Locks::dex_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   std::vector<const DexFile*> boot_class_path_;
   std::vector<std::unique_ptr<const DexFile>> boot_dex_files_;
 
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 010b384..95b224f 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -52,6 +52,7 @@
 #include "mirror/object_array-inl.h"
 #include "mirror/proxy.h"
 #include "mirror/reference.h"
+#include "mirror/stack_frame_info.h"
 #include "mirror/stack_trace_element.h"
 #include "mirror/string-inl.h"
 #include "mirror/var_handle.h"
@@ -130,7 +131,14 @@
     EXPECT_TRUE(JavaLangObject->GetSuperClass() == nullptr);
     EXPECT_FALSE(JavaLangObject->HasSuperClass());
     EXPECT_TRUE(JavaLangObject->GetClassLoader() == nullptr);
-    class_linker_->MakeInitializedClassesVisiblyInitialized(Thread::Current(), /*wait=*/ true);
+    {
+      Thread* self = Thread::Current();
+      StackHandleScope<1> hs(self);
+      HandleWrapperObjPtr<mirror::Class> h(hs.NewHandleWrapper(&JavaLangObject));
+      ScopedThreadSuspension sts(self, ThreadState::kNative);
+      class_linker_->MakeInitializedClassesVisiblyInitialized(self, /*wait=*/ true);
+      // HandleWrapperObjPtr restores JavaLangObject here after becoming runnable again.
+    }
     EXPECT_EQ(ClassStatus::kVisiblyInitialized, JavaLangObject->GetStatus());
     EXPECT_FALSE(JavaLangObject->IsErroneous());
     EXPECT_TRUE(JavaLangObject->IsLoaded());
@@ -275,9 +283,10 @@
                                                klass->GetDescriptor(&temp2)));
     if (klass->IsInterface()) {
       EXPECT_TRUE(klass->IsAbstract());
-      // Check that all direct methods are static (either <clinit> or a regular static method).
+      // Check that all methods are direct and either static (<clinit> or a regular static method),
+      // or private.
       for (ArtMethod& m : klass->GetDirectMethods(kRuntimePointerSize)) {
-        EXPECT_TRUE(m.IsStatic());
+        EXPECT_TRUE(m.IsStatic() || m.IsPrivate());
         EXPECT_TRUE(m.IsDirect());
       }
     } else {
@@ -598,6 +607,7 @@
 
 struct ClassExtOffsets : public CheckOffsets<mirror::ClassExt> {
   ClassExtOffsets() : CheckOffsets<mirror::ClassExt>(false, "Ldalvik/system/ClassExt;") {
+    addOffset(OFFSETOF_MEMBER(mirror::ClassExt, class_value_map_), "classValueMap");
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, erroneous_state_error_), "erroneousStateError");
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, instance_jfield_ids_), "instanceJfieldIDs");
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, jmethod_ids_), "jmethodIDs");
@@ -640,6 +650,20 @@
   }
 };
 
+struct StackFrameInfoOffsets : public CheckOffsets<mirror::StackFrameInfo> {
+  StackFrameInfoOffsets() : CheckOffsets<mirror::StackFrameInfo>(
+      false, "Ljava/lang/StackFrameInfo;") {
+    addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, bci_), "bci");
+    addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, declaring_class_), "declaringClass");
+    addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, file_name_), "fileName");
+    addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, line_number_), "lineNumber");
+    addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, method_name_), "methodName");
+    addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, method_type_), "methodType");
+    addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, retain_class_ref_), "retainClassRef");
+    addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, ste_), "ste");
+  }
+};
+
 struct ClassLoaderOffsets : public CheckOffsets<mirror::ClassLoader> {
   ClassLoaderOffsets() : CheckOffsets<mirror::ClassLoader>(false, "Ljava/lang/ClassLoader;") {
     addOffset(OFFSETOF_MEMBER(mirror::ClassLoader, allocator_), "allocator");
@@ -661,20 +685,18 @@
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, class_loader_), "classLoader");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, dex_file_), "dexFile");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, location_), "location");
-    addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_preresolved_strings_), "numPreResolvedStrings");
-    addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_resolved_call_sites_), "numResolvedCallSites");
-    addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_resolved_fields_), "numResolvedFields");
-    addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_resolved_method_types_), "numResolvedMethodTypes");
-    addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_resolved_methods_), "numResolvedMethods");
-    addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_resolved_types_), "numResolvedTypes");
-    addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_strings_), "numStrings");
-    addOffset(OFFSETOF_MEMBER(mirror::DexCache, preresolved_strings_), "preResolvedStrings");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_call_sites_), "resolvedCallSites");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_fields_), "resolvedFields");
+    addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_fields_array_), "resolvedFieldsArray");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_method_types_), "resolvedMethodTypes");
+    addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_method_types_array_),
+              "resolvedMethodTypesArray");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_methods_), "resolvedMethods");
+    addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_methods_array_), "resolvedMethodsArray");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_types_), "resolvedTypes");
+    addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_types_array_), "resolvedTypesArray");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, strings_), "strings");
+    addOffset(OFFSETOF_MEMBER(mirror::DexCache, strings_array_), "stringsArray");
   }
 };
 
@@ -830,7 +852,7 @@
 
 // C++ fields must exactly match the fields in the Java classes. If this fails,
 // reorder the fields in the C++ class. Managed class fields are ordered by
-// ClassLinker::LinkFields.
+// ClassLinker::LinkFieldsHelper::LinkFields.
 TEST_F(ClassLinkerTest, ValidateFieldOrderOfJavaCppUnionClasses) {
   ScopedObjectAccess soa(Thread::Current());
   EXPECT_TRUE(ObjectOffsets().Check());
@@ -858,6 +880,7 @@
   EXPECT_TRUE(ArrayElementVarHandleOffsets().Check());
   EXPECT_TRUE(ByteArrayViewVarHandleOffsets().Check());
   EXPECT_TRUE(ByteBufferViewVarHandleOffsets().Check());
+  EXPECT_TRUE(StackFrameInfoOffsets().Check());
 }
 
 TEST_F(ClassLinkerTest, FindClassNonexistent) {
@@ -1520,12 +1543,14 @@
                                                                               data)));
   const DexFile* old_dex_file = dex_cache->GetDexFile();
 
+  auto container =
+      std::make_shared<MemoryDexFileContainer>(old_dex_file->Begin(), old_dex_file->Size());
   std::unique_ptr<DexFile> dex_file(new StandardDexFile(old_dex_file->Begin(),
                                                         old_dex_file->Size(),
                                                         location->ToModifiedUtf8(),
                                                         0u,
                                                         nullptr,
-                                                        nullptr));
+                                                        std::move(container)));
   // Make a copy of the dex cache with changed name.
   dex_cache.Assign(class_linker->AllocAndInitializeDexCache(Thread::Current(),
                                                             *dex_file,
@@ -1689,7 +1714,7 @@
       }
       ASSERT_TRUE(klass == nullptr);
     } else if (expected_class_loader_obj == nullptr) {
-      ASSERT_TRUE(ClassLinker::IsBootClassLoader(soa, klass->GetClassLoader()));
+      ASSERT_TRUE(ClassLinker::IsBootClassLoader(klass->GetClassLoader()));
     } else {
       ASSERT_TRUE(klass != nullptr) << descriptor;
       Handle<mirror::ClassLoader> expected_class_loader(
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 2419b7b7..2f34f04 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -18,9 +18,9 @@
 
 #include <algorithm>
 
-#include <android-base/parseint.h>
-#include <android-base/strings.h>
-
+#include "android-base/file.h"
+#include "android-base/parseint.h"
+#include "android-base/strings.h"
 #include "art_field-inl.h"
 #include "base/casts.h"
 #include "base/dchecked_vector.h"
@@ -44,7 +44,7 @@
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -63,8 +63,7 @@
 static constexpr char kInMemoryDexClassLoaderDexLocationMagic[] = "<unknown>";
 
 ClassLoaderContext::ClassLoaderContext()
-    : dex_files_state_(ContextDexFilesState::kDexFilesNotOpened),
-      owns_the_dex_files_(true) {}
+    : dex_files_state_(ContextDexFilesState::kDexFilesNotOpened), owns_the_dex_files_(true) {}
 
 ClassLoaderContext::ClassLoaderContext(bool owns_the_dex_files)
     : dex_files_state_(ContextDexFilesState::kDexFilesOpened),
@@ -72,9 +71,8 @@
 
 // Utility method to add parent and shared libraries of `info` into
 // the `work_list`.
-static void AddToWorkList(
-    ClassLoaderContext::ClassLoaderInfo* info,
-    std::vector<ClassLoaderContext::ClassLoaderInfo*>& work_list) {
+static void AddToWorkList(ClassLoaderContext::ClassLoaderInfo* info,
+                          std::vector<ClassLoaderContext::ClassLoaderInfo*>& work_list) {
   if (info->parent != nullptr) {
     work_list.push_back(info->parent.get());
   }
@@ -106,9 +104,7 @@
   }
 }
 
-std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Default() {
-  return Create("");
-}
+std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Default() { return Create(""); }
 
 std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Create(const std::string& spec) {
   std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext());
@@ -127,8 +123,7 @@
   uint32_t string_index = shared_library_open_index + 1;
   size_t shared_library_close = std::string::npos;
   while (counter != 0) {
-    shared_library_close =
-        spec.find_first_of(kClassLoaderSharedLibraryClosingMark, string_index);
+    shared_library_close = spec.find_first_of(kClassLoaderSharedLibraryClosingMark, string_index);
     size_t shared_library_open =
         spec.find_first_of(kClassLoaderSharedLibraryOpeningMark, string_index);
     if (shared_library_close == std::string::npos) {
@@ -156,8 +151,7 @@
 // "ClassLoaderType1[ClasspathElem1*Checksum1:ClasspathElem2*Checksum2...]{ClassLoaderType2[...]}".
 // The checksum part of the format is expected only if parse_cheksums is true.
 std::unique_ptr<ClassLoaderContext::ClassLoaderInfo> ClassLoaderContext::ParseClassLoaderSpec(
-    const std::string& class_loader_spec,
-    bool parse_checksums) {
+    const std::string& class_loader_spec, bool parse_checksums) {
   ClassLoaderType class_loader_type = ExtractClassLoaderType(class_loader_spec);
   if (class_loader_type == kInvalidClassLoader) {
     return nullptr;
@@ -200,8 +194,8 @@
 
   // At this point we know the format is ok; continue and extract the classpath.
   // Note that class loaders with an empty class path are allowed.
-  std::string classpath = class_loader_spec.substr(type_str_size + 1,
-                                                   closing_index - type_str_size - 1);
+  std::string classpath =
+      class_loader_spec.substr(type_str_size + 1, closing_index - type_str_size - 1);
 
   std::unique_ptr<ClassLoaderInfo> info(new ClassLoaderInfo(class_loader_type));
 
@@ -299,7 +293,7 @@
       }
 
       std::unique_ptr<ClassLoaderInfo> shared_library_info(
-                ParseInternal(shared_library_spec, parse_checksums));
+          ParseInternal(shared_library_spec, parse_checksums));
       if (shared_library_info == nullptr) {
         return nullptr;
       }
@@ -317,11 +311,10 @@
 // Extracts the class loader type from the given spec.
 // Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not
 // recognized.
-ClassLoaderContext::ClassLoaderType
-ClassLoaderContext::ExtractClassLoaderType(const std::string& class_loader_spec) {
-  const ClassLoaderType kValidTypes[] = { kPathClassLoader,
-                                          kDelegateLastClassLoader,
-                                          kInMemoryDexClassLoader };
+ClassLoaderContext::ClassLoaderType ClassLoaderContext::ExtractClassLoaderType(
+    const std::string& class_loader_spec) {
+  const ClassLoaderType kValidTypes[] = {
+      kPathClassLoader, kDelegateLastClassLoader, kInMemoryDexClassLoader};
   for (const ClassLoaderType& type : kValidTypes) {
     const char* type_str = GetClassLoaderTypeName(type);
     if (class_loader_spec.compare(0, strlen(type_str), type_str) == 0) {
@@ -348,8 +341,8 @@
   return class_loader_chain_ != nullptr;
 }
 
-ClassLoaderContext::ClassLoaderInfo* ClassLoaderContext::ParseInternal(
-    const std::string& spec, bool parse_checksums) {
+ClassLoaderContext::ClassLoaderInfo* ClassLoaderContext::ParseInternal(const std::string& spec,
+                                                                       bool parse_checksums) {
   CHECK(!spec.empty());
   std::string remaining = spec;
   std::unique_ptr<ClassLoaderInfo> first(nullptr);
@@ -391,8 +384,8 @@
         LOG(ERROR) << "Invalid class loader spec: " << class_loader_spec;
         return nullptr;
       } else {
-        remaining = remaining.substr(shared_library_close + 2,
-                                     remaining.size() - shared_library_close - 2);
+        remaining =
+            remaining.substr(shared_library_close + 2, remaining.size() - shared_library_close - 2);
       }
     }
 
@@ -420,9 +413,12 @@
                                       const std::vector<int>& fds,
                                       bool only_read_checksums) {
   switch (dex_files_state_) {
-    case kDexFilesNotOpened: break;  // files not opened, continue.
-    case kDexFilesOpenFailed: return false;  // previous attempt failed.
-    case kDexFilesOpened: return true;  // previous attempt succeed.
+    case kDexFilesNotOpened:
+      break;  // files not opened, continue.
+    case kDexFilesOpenFailed:
+      return false;  // previous attempt failed.
+    case kDexFilesOpened:
+      return true;  // previous attempt succeed.
     case kDexFilesChecksumsRead:
       if (only_read_checksums) {
         return true;  // we already read the checksums.
@@ -438,7 +434,6 @@
   // We may get resource-only apks which we cannot load.
   // TODO(calin): Refine the dex opening interface to be able to tell if an archive contains
   // no dex files. So that we can distinguish the real failures...
-  const ArtDexFileLoader dex_file_loader;
   std::vector<ClassLoaderInfo*> work_list;
   if (class_loader_chain_ == nullptr) {
     return true;
@@ -480,12 +475,12 @@
       std::string error_msg;
       if (only_read_checksums) {
         bool zip_file_only_contains_uncompress_dex;
-        if (!dex_file_loader.GetMultiDexChecksums(location.c_str(),
-                                                  &dex_checksums,
-                                                  &dex_locations,
-                                                  &error_msg,
-                                                  fd,
-                                                  &zip_file_only_contains_uncompress_dex)) {
+        if (!ArtDexFileLoader::GetMultiDexChecksums(location.c_str(),
+                                                    &dex_checksums,
+                                                    &dex_locations,
+                                                    &error_msg,
+                                                    fd,
+                                                    &zip_file_only_contains_uncompress_dex)) {
           LOG(WARNING) << "Could not get dex checksums for location " << location << ", fd=" << fd;
           dex_files_state_ = kDexFilesOpenFailed;
         }
@@ -495,11 +490,9 @@
         // We don't need to do structural dex file verification, we only need to
         // check the checksum, so pass false to verify.
         size_t opened_dex_files_index = info->opened_dex_files.size();
-        if (!dex_file_loader.Open(location.c_str(),
-                                  fd,
-                                  location.c_str(),
-                                  /*verify=*/ false,
-                                  /*verify_checksum=*/ true,
+        ArtDexFileLoader dex_file_loader(location.c_str(), fd, location);
+        if (!dex_file_loader.Open(/*verify=*/false,
+                                  /*verify_checksum=*/true,
                                   &error_msg,
                                   &info->opened_dex_files)) {
           LOG(WARNING) << "Could not open dex files for location " << location << ", fd=" << fd;
@@ -534,7 +527,7 @@
   // as we have encountered while iterating over this class loader context.
   if (dex_file_index != fds.size()) {
     LOG(WARNING) << fds.size() << " FDs provided but only " << dex_file_index
-        << " dex files are in the class loader context";
+                 << " dex files are in the class loader context";
     dex_files_state_ = kDexFilesOpenFailed;
   }
 
@@ -561,13 +554,13 @@
     ClassLoaderInfo* info = work_list.back();
     work_list.pop_back();
     size_t initial_size = info->classpath.size();
-    auto kept_it = std::remove_if(
-        info->classpath.begin(),
-        info->classpath.end(),
-        [canonical_locations](const std::string& location) {
-            return ContainsElement(canonical_locations,
-                                   DexFileLoader::GetDexCanonicalLocation(location.c_str()));
-        });
+    auto kept_it = std::remove_if(info->classpath.begin(),
+                                  info->classpath.end(),
+                                  [canonical_locations](const std::string& location) {
+                                    return ContainsElement(
+                                        canonical_locations,
+                                        DexFileLoader::GetDexCanonicalLocation(location.c_str()));
+                                  });
     info->classpath.erase(kept_it, info->classpath.end());
     if (initial_size != info->classpath.size()) {
       removed_locations = true;
@@ -578,16 +571,16 @@
 }
 
 std::string ClassLoaderContext::EncodeContextForDex2oat(const std::string& base_dir) const {
-  return EncodeContext(base_dir, /*for_dex2oat=*/ true, /*stored_context=*/ nullptr);
+  return EncodeContext(base_dir, /*for_dex2oat=*/true, /*stored_context=*/nullptr);
 }
 
 std::string ClassLoaderContext::EncodeContextForOatFile(const std::string& base_dir,
                                                         ClassLoaderContext* stored_context) const {
-  return EncodeContext(base_dir, /*for_dex2oat=*/ false, stored_context);
+  return EncodeContext(base_dir, /*for_dex2oat=*/false, stored_context);
 }
 
-std::map<std::string, std::string>
-ClassLoaderContext::EncodeClassPathContexts(const std::string& base_dir) const {
+std::map<std::string, std::string> ClassLoaderContext::EncodeClassPathContexts(
+    const std::string& base_dir) const {
   CheckDexFilesOpened("EncodeClassPathContexts");
   if (class_loader_chain_ == nullptr) {
     return std::map<std::string, std::string>{};
@@ -639,8 +632,7 @@
   if (class_loader_chain_ == nullptr) {
     // We can get in this situation if the context was created with a class path containing the
     // source dex files which were later removed (happens during run-tests).
-    out << GetClassLoaderTypeName(kPathClassLoader)
-        << kClassLoaderOpeningMark
+    out << GetClassLoaderTypeName(kPathClassLoader) << kClassLoaderOpeningMark
         << kClassLoaderClosingMark;
     return out.str();
   }
@@ -670,8 +662,7 @@
     }
     if (type == kInMemoryDexClassLoader) {
       out << kInMemoryDexClassLoaderDexLocationMagic;
-    } else if (!base_dir.empty()
-               && location.substr(0, base_dir.length()) == base_dir) {
+    } else if (!base_dir.empty() && location.substr(0, base_dir.length()) == base_dir) {
       // Find paths that were relative and convert them back from absolute.
       out << location.substr(base_dir.length() + 1).c_str();
     } else {
@@ -706,8 +697,8 @@
     if (for_dex2oat) {
       // dex2oat only needs the base location. It cannot accept multidex locations.
       // So ensure we only add each file once.
-      bool new_insert = seen_locations.insert(
-          DexFileLoader::GetBaseLocation(dex_file->GetLocation())).second;
+      bool new_insert =
+          seen_locations.insert(DexFileLoader::GetBaseLocation(dex_file->GetLocation())).second;
       if (!new_insert) {
         continue;
       }
@@ -768,25 +759,26 @@
   }
   if (info.parent != nullptr) {
     out << kClassLoaderSeparator;
-    EncodeContextInternal(
-        *info.parent.get(),
-        base_dir,
-        for_dex2oat,
-        (stored_info == nullptr ? nullptr : stored_info->parent.get()),
-        out);
+    EncodeContextInternal(*info.parent.get(),
+                          base_dir,
+                          for_dex2oat,
+                          (stored_info == nullptr ? nullptr : stored_info->parent.get()),
+                          out);
   }
 }
 
 // Returns the WellKnownClass for the given class loader type.
-static jclass GetClassLoaderClass(ClassLoaderContext::ClassLoaderType type) {
+static ObjPtr<mirror::Class> GetClassLoaderClass(ClassLoaderContext::ClassLoaderType type)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
   switch (type) {
     case ClassLoaderContext::kPathClassLoader:
-      return WellKnownClasses::dalvik_system_PathClassLoader;
+      return WellKnownClasses::dalvik_system_PathClassLoader.Get();
     case ClassLoaderContext::kDelegateLastClassLoader:
-      return WellKnownClasses::dalvik_system_DelegateLastClassLoader;
+      return WellKnownClasses::dalvik_system_DelegateLastClassLoader.Get();
     case ClassLoaderContext::kInMemoryDexClassLoader:
-      return WellKnownClasses::dalvik_system_InMemoryDexClassLoader;
-    case ClassLoaderContext::kInvalidClassLoader: break;  // will fail after the switch.
+      return WellKnownClasses::dalvik_system_InMemoryDexClassLoader.Get();
+    case ClassLoaderContext::kInvalidClassLoader:
+      break;  // will fail after the switch.
   }
   LOG(FATAL) << "Invalid class loader type " << type;
   UNREACHABLE();
@@ -804,8 +796,7 @@
     VariableSizedHandleScope& map_scope,
     std::map<std::string, Handle<mirror::ClassLoader>>& canonicalized_libraries,
     bool add_compilation_sources,
-    const std::vector<const DexFile*>& compilation_sources)
-      REQUIRES_SHARED(Locks::mutator_lock_) {
+    const std::vector<const DexFile*>& compilation_sources) REQUIRES_SHARED(Locks::mutator_lock_) {
   if (for_shared_library) {
     // Check if the shared library has already been created.
     auto search = canonicalized_libraries.find(FlattenClasspath(info.classpath));
@@ -818,7 +809,7 @@
   MutableHandle<mirror::ObjectArray<mirror::ClassLoader>> libraries(
       hs.NewHandle<mirror::ObjectArray<mirror::ClassLoader>>(nullptr));
   MutableHandle<mirror::ObjectArray<mirror::ClassLoader>> libraries_after(
-        hs.NewHandle<mirror::ObjectArray<mirror::ClassLoader>>(nullptr));
+      hs.NewHandle<mirror::ObjectArray<mirror::ClassLoader>>(nullptr));
 
   if (!info.shared_libraries.empty()) {
     libraries.Assign(mirror::ObjectArray<mirror::ClassLoader>::Alloc(
@@ -828,15 +819,14 @@
     for (uint32_t i = 0; i < info.shared_libraries.size(); ++i) {
       // We should only add the compilation sources to the first class loader.
       libraries->Set(i,
-                     CreateClassLoaderInternal(
-                         self,
-                         soa,
-                         *info.shared_libraries[i].get(),
-                         /* for_shared_library= */ true,
-                         map_scope,
-                         canonicalized_libraries,
-                         /* add_compilation_sources= */ false,
-                         compilation_sources));
+                     CreateClassLoaderInternal(self,
+                                               soa,
+                                               *info.shared_libraries[i].get(),
+                                               /* for_shared_library= */ true,
+                                               map_scope,
+                                               canonicalized_libraries,
+                                               /* add_compilation_sources= */ false,
+                                               compilation_sources));
     }
   }
 
@@ -848,51 +838,41 @@
     for (uint32_t i = 0; i < info.shared_libraries_after.size(); ++i) {
       // We should only add the compilation sources to the first class loader.
       libraries_after->Set(i,
-                     CreateClassLoaderInternal(
-                         self,
-                         soa,
-                         *info.shared_libraries_after[i].get(),
-                         /* for_shared_library= */ true,
-                         map_scope,
-                         canonicalized_libraries,
-                         /* add_compilation_sources= */ false,
-                         compilation_sources));
+                           CreateClassLoaderInternal(self,
+                                                     soa,
+                                                     *info.shared_libraries_after[i].get(),
+                                                     /* for_shared_library= */ true,
+                                                     map_scope,
+                                                     canonicalized_libraries,
+                                                     /* add_compilation_sources= */ false,
+                                                     compilation_sources));
     }
   }
 
   MutableHandle<mirror::ClassLoader> parent = hs.NewHandle<mirror::ClassLoader>(nullptr);
   if (info.parent != nullptr) {
     // We should only add the compilation sources to the first class loader.
-    parent.Assign(CreateClassLoaderInternal(
-        self,
-        soa,
-        *info.parent.get(),
-        /* for_shared_library= */ false,
-        map_scope,
-        canonicalized_libraries,
-        /* add_compilation_sources= */ false,
-        compilation_sources));
+    parent.Assign(CreateClassLoaderInternal(self,
+                                            soa,
+                                            *info.parent.get(),
+                                            /* for_shared_library= */ false,
+                                            map_scope,
+                                            canonicalized_libraries,
+                                            /* add_compilation_sources= */ false,
+                                            compilation_sources));
   }
-  std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(
-      info.opened_dex_files);
+  std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(info.opened_dex_files);
   if (add_compilation_sources) {
     // For the first class loader, its classpath comes first, followed by compilation sources.
     // This ensures that whenever we need to resolve classes from it the classpath elements
     // come first.
-    class_path_files.insert(class_path_files.end(),
-                            compilation_sources.begin(),
-                            compilation_sources.end());
+    class_path_files.insert(
+        class_path_files.end(), compilation_sources.begin(), compilation_sources.end());
   }
-  Handle<mirror::Class> loader_class = hs.NewHandle<mirror::Class>(
-      soa.Decode<mirror::Class>(GetClassLoaderClass(info.type)));
+  Handle<mirror::Class> loader_class = hs.NewHandle<mirror::Class>(GetClassLoaderClass(info.type));
   ObjPtr<mirror::ClassLoader> loader =
       Runtime::Current()->GetClassLinker()->CreateWellKnownClassLoader(
-          self,
-          class_path_files,
-          loader_class,
-          parent,
-          libraries,
-          libraries_after);
+          self, class_path_files, loader_class, parent, libraries, libraries_after);
   if (for_shared_library) {
     canonicalized_libraries[FlattenClasspath(info.classpath)] =
         map_scope.NewHandle<mirror::ClassLoader>(loader);
@@ -925,8 +905,7 @@
                                 /* add_compilation_sources= */ true,
                                 compilation_sources);
   // Make it a global ref and return.
-  ScopedLocalRef<jobject> local_ref(
-      soa.Env(), soa.Env()->AddLocalReference<jobject>(loader));
+  ScopedLocalRef<jobject> local_ref(soa.Env(), soa.Env()->AddLocalReference<jobject>(loader));
   return soa.Env()->NewGlobalRef(local_ref.get());
 }
 
@@ -950,12 +929,13 @@
   return result;
 }
 
-std::string ClassLoaderContext::FlattenDexPaths() const {
+std::vector<std::string> ClassLoaderContext::FlattenDexPaths() const {
+  std::vector<std::string> result;
+
   if (class_loader_chain_ == nullptr) {
-    return "";
+    return result;
   }
 
-  std::vector<std::string> result;
   std::vector<ClassLoaderInfo*> work_list;
   work_list.push_back(class_loader_chain_.get());
   while (!work_list.empty()) {
@@ -966,14 +946,17 @@
     }
     AddToWorkList(info, work_list);
   }
-  return FlattenClasspath(result);
+  return result;
 }
 
 const char* ClassLoaderContext::GetClassLoaderTypeName(ClassLoaderType type) {
   switch (type) {
-    case kPathClassLoader: return kPathClassLoaderString;
-    case kDelegateLastClassLoader: return kDelegateLastClassLoaderString;
-    case kInMemoryDexClassLoader: return kInMemoryDexClassLoaderString;
+    case kPathClassLoader:
+      return kPathClassLoaderString;
+    case kDelegateLastClassLoader:
+      return kDelegateLastClassLoaderString;
+    case kInMemoryDexClassLoader:
+      return kInMemoryDexClassLoaderString;
     default:
       LOG(FATAL) << "Invalid class loader type " << type;
       UNREACHABLE();
@@ -991,7 +974,7 @@
 static bool CollectDexFilesFromJavaDexFile(ObjPtr<mirror::Object> java_dex_file,
                                            ArtField* const cookie_field,
                                            std::vector<const DexFile*>* out_dex_files)
-      REQUIRES_SHARED(Locks::mutator_lock_) {
+    REQUIRES_SHARED(Locks::mutator_lock_) {
   if (java_dex_file == nullptr) {
     return true;
   }
@@ -1019,21 +1002,18 @@
 // Collects all the dex files loaded by the given class loader.
 // Returns true for success or false if an unexpected state is discovered (e.g. a null dex cookie,
 // a null list of dex elements or a null dex element).
-static bool CollectDexFilesFromSupportedClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+static bool CollectDexFilesFromSupportedClassLoader(Thread* self,
                                                     Handle<mirror::ClassLoader> class_loader,
                                                     std::vector<const DexFile*>* out_dex_files)
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-  CHECK(IsInstanceOfBaseDexClassLoader(soa, class_loader));
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  CHECK(IsInstanceOfBaseDexClassLoader(class_loader));
 
   // All supported class loaders inherit from BaseDexClassLoader.
   // We need to get the DexPathList and loop through it.
-  ArtField* const cookie_field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
-  ArtField* const dex_file_field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+  ArtField* const cookie_field = WellKnownClasses::dalvik_system_DexFile_cookie;
+  ArtField* const dex_file_field = WellKnownClasses::dalvik_system_DexPathList__Element_dexFile;
   ObjPtr<mirror::Object> dex_path_list =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)->
-          GetObject(class_loader.Get());
+      WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList->GetObject(class_loader.Get());
   CHECK(cookie_field != nullptr);
   CHECK(dex_file_field != nullptr);
   if (dex_path_list == nullptr) {
@@ -1043,8 +1023,7 @@
   }
   // DexPathList has an array dexElements of Elements[] which each contain a dex file.
   ObjPtr<mirror::Object> dex_elements_obj =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)->
-          GetObject(dex_path_list);
+      WellKnownClasses::dalvik_system_DexPathList_dexElements->GetObject(dex_path_list);
   // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look
   // at the mCookie which is a DexFile vector.
   if (dex_elements_obj == nullptr) {
@@ -1052,7 +1031,7 @@
     // and assume we have no elements.
     return true;
   } else {
-    StackHandleScope<1> hs(soa.Self());
+    StackHandleScope<1> hs(self);
     Handle<mirror::ObjectArray<mirror::Object>> dex_elements(
         hs.NewHandle(dex_elements_obj->AsObjectArray<mirror::Object>()));
     for (auto element : dex_elements.Iterate<mirror::Object>()) {
@@ -1075,19 +1054,15 @@
 }
 
 static bool GetDexFilesFromDexElementsArray(
-    ScopedObjectAccessAlreadyRunnable& soa,
     Handle<mirror::ObjectArray<mirror::Object>> dex_elements,
     std::vector<const DexFile*>* out_dex_files) REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(dex_elements != nullptr);
 
-  ArtField* const cookie_field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
-  ArtField* const dex_file_field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
-  const ObjPtr<mirror::Class> element_class = soa.Decode<mirror::Class>(
-      WellKnownClasses::dalvik_system_DexPathList__Element);
-  const ObjPtr<mirror::Class> dexfile_class = soa.Decode<mirror::Class>(
-      WellKnownClasses::dalvik_system_DexFile);
+  ArtField* const cookie_field = WellKnownClasses::dalvik_system_DexFile_cookie;
+  ArtField* const dex_file_field = WellKnownClasses::dalvik_system_DexPathList__Element_dexFile;
+  const ObjPtr<mirror::Class> element_class =
+      WellKnownClasses::dalvik_system_DexPathList__Element.Get();
+  const ObjPtr<mirror::Class> dexfile_class = WellKnownClasses::dalvik_system_DexFile.Get();
 
   for (auto element : dex_elements.Iterate<mirror::Object>()) {
     // We can hit a null element here because this is invoked with a partially filled dex_elements
@@ -1124,24 +1099,23 @@
 // This method is recursive (w.r.t. the class loader parent) and will stop once it reaches the
 // BootClassLoader. Note that the class loader chain is expected to be short.
 bool ClassLoaderContext::CreateInfoFromClassLoader(
-      ScopedObjectAccessAlreadyRunnable& soa,
-      Handle<mirror::ClassLoader> class_loader,
-      Handle<mirror::ObjectArray<mirror::Object>> dex_elements,
-      ClassLoaderInfo* child_info,
-      bool is_shared_library,
-      bool is_after)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  if (ClassLinker::IsBootClassLoader(soa, class_loader.Get())) {
+    ScopedObjectAccessAlreadyRunnable& soa,
+    Handle<mirror::ClassLoader> class_loader,
+    Handle<mirror::ObjectArray<mirror::Object>> dex_elements,
+    ClassLoaderInfo* child_info,
+    bool is_shared_library,
+    bool is_after) REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (ClassLinker::IsBootClassLoader(class_loader.Get())) {
     // Nothing to do for the boot class loader as we don't add its dex files to the context.
     return true;
   }
 
   ClassLoaderContext::ClassLoaderType type;
-  if (IsPathOrDexClassLoader(soa, class_loader)) {
+  if (IsPathOrDexClassLoader(class_loader)) {
     type = kPathClassLoader;
-  } else if (IsDelegateLastClassLoader(soa, class_loader)) {
+  } else if (IsDelegateLastClassLoader(class_loader)) {
     type = kDelegateLastClassLoader;
-  } else if (IsInMemoryDexClassLoader(soa, class_loader)) {
+  } else if (IsInMemoryDexClassLoader(class_loader)) {
     type = kInMemoryDexClassLoader;
   } else {
     LOG(WARNING) << "Unsupported class loader";
@@ -1150,7 +1124,7 @@
 
   // Inspect the class loader for its dex files.
   std::vector<const DexFile*> dex_files_loaded;
-  CollectDexFilesFromSupportedClassLoader(soa, class_loader, &dex_files_loaded);
+  CollectDexFilesFromSupportedClassLoader(soa.Self(), class_loader, &dex_files_loaded);
 
   // If we have a dex_elements array extract its dex elements now.
   // This is used in two situations:
@@ -1162,7 +1136,7 @@
   //      BaseDexClassLoader, the paths already present in the class loader will be passed
   //      in the dex_elements array.
   if (dex_elements != nullptr) {
-    GetDexFilesFromDexElementsArray(soa, dex_elements, &dex_files_loaded);
+    GetDexFilesFromDexElementsArray(dex_elements, &dex_files_loaded);
   }
 
   ClassLoaderInfo* info = new ClassLoaderContext::ClassLoaderInfo(type);
@@ -1184,9 +1158,9 @@
   for (const DexFile* dex_file : dex_files_loaded) {
     // Dex location of dex files loaded with InMemoryDexClassLoader is always bogus.
     // Use a magic value for the classpath instead.
-    info->classpath.push_back((type == kInMemoryDexClassLoader)
-        ? kInMemoryDexClassLoaderDexLocationMagic
-        : dex_file->GetLocation());
+    info->classpath.push_back((type == kInMemoryDexClassLoader) ?
+                                  kInMemoryDexClassLoaderDexLocationMagic :
+                                  dex_file->GetLocation());
     info->checksums.push_back(dex_file->GetLocationChecksum());
     info->opened_dex_files.emplace_back(dex_file);
   }
@@ -1197,8 +1171,7 @@
 
   // Add the shared libraries.
   StackHandleScope<5> hs(Thread::Current());
-  ArtField* field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders);
+  ArtField* field = WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
   ObjPtr<mirror::Object> raw_shared_libraries = field->GetObject(class_loader.Get());
   if (raw_shared_libraries != nullptr) {
     Handle<mirror::ObjectArray<mirror::ClassLoader>> shared_libraries =
@@ -1210,14 +1183,13 @@
                                      temp_loader,
                                      null_dex_elements,
                                      info,
-                                     /*is_shared_library=*/ true,
-                                     /*is_after=*/ false)) {
+                                     /*is_shared_library=*/true,
+                                     /*is_after=*/false)) {
         return false;
       }
     }
   }
-  ArtField* field2 = jni::DecodeArtField(
-      WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter);
+  ArtField* field2 = WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter;
   ObjPtr<mirror::Object> raw_shared_libraries_after = field2->GetObject(class_loader.Get());
   if (raw_shared_libraries_after != nullptr) {
     Handle<mirror::ObjectArray<mirror::ClassLoader>> shared_libraries_after =
@@ -1229,8 +1201,8 @@
                                      temp_loader,
                                      null_dex_elements,
                                      info,
-                                     /*is_shared_library=*/ true,
-                                     /*is_after=*/ true)) {
+                                     /*is_shared_library=*/true,
+                                     /*is_after=*/true)) {
         return false;
       }
     }
@@ -1242,16 +1214,15 @@
                                  parent,
                                  null_dex_elements,
                                  info,
-                                 /*is_shared_library=*/ false,
-                                 /*is_after=*/ false)) {
+                                 /*is_shared_library=*/false,
+                                 /*is_after=*/false)) {
     return false;
   }
   return true;
 }
 
 std::unique_ptr<ClassLoaderContext> ClassLoaderContext::CreateContextForClassLoader(
-    jobject class_loader,
-    jobjectArray dex_elements) {
+    jobject class_loader, jobjectArray dex_elements) {
   ScopedTrace trace(__FUNCTION__);
 
   if (class_loader == nullptr) {
@@ -1263,20 +1234,20 @@
       hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
   Handle<mirror::ObjectArray<mirror::Object>> h_dex_elements =
       hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>>(dex_elements));
-  std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext(/*owns_the_dex_files=*/ false));
+  std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext(/*owns_the_dex_files=*/false));
   if (!result->CreateInfoFromClassLoader(soa,
                                          h_class_loader,
                                          h_dex_elements,
                                          nullptr,
-                                         /*is_shared_library=*/ false,
-                                         /*is_after=*/ false)) {
+                                         /*is_shared_library=*/false,
+                                         /*is_after=*/false)) {
     return nullptr;
   }
   return result;
 }
 
-std::map<std::string, std::string>
-ClassLoaderContext::EncodeClassPathContextsForClassLoader(jobject class_loader) {
+std::map<std::string, std::string> ClassLoaderContext::EncodeClassPathContextsForClassLoader(
+    jobject class_loader) {
   std::unique_ptr<ClassLoaderContext> clc =
       ClassLoaderContext::CreateContextForClassLoader(class_loader, nullptr);
   if (clc != nullptr) {
@@ -1287,12 +1258,12 @@
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::ClassLoader> h_class_loader =
       hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
-  if (!IsInstanceOfBaseDexClassLoader(soa, h_class_loader)) {
+  if (!IsInstanceOfBaseDexClassLoader(h_class_loader)) {
     return std::map<std::string, std::string>{};
   }
 
   std::vector<const DexFile*> dex_files_loaded;
-  CollectDexFilesFromSupportedClassLoader(soa, h_class_loader, &dex_files_loaded);
+  CollectDexFilesFromSupportedClassLoader(soa.Self(), h_class_loader, &dex_files_loaded);
 
   std::map<std::string, std::string> results;
   for (const DexFile* dex_file : dex_files_loaded) {
@@ -1303,14 +1274,12 @@
 }
 
 bool ClassLoaderContext::IsValidEncoding(const std::string& possible_encoded_class_loader_context) {
-  return ClassLoaderContext::Create(possible_encoded_class_loader_context.c_str()) != nullptr
-      || possible_encoded_class_loader_context == kUnsupportedClassLoaderContextEncoding;
+  return ClassLoaderContext::Create(possible_encoded_class_loader_context) != nullptr ||
+         possible_encoded_class_loader_context == kUnsupportedClassLoaderContextEncoding;
 }
 
 ClassLoaderContext::VerificationResult ClassLoaderContext::VerifyClassLoaderContextMatch(
-    const std::string& context_spec,
-    bool verify_names,
-    bool verify_checksums) const {
+    const std::string& context_spec, bool verify_names, bool verify_checksums) const {
   ScopedTrace trace(__FUNCTION__);
   if (verify_names || verify_checksums) {
     DCHECK(dex_files_state_ == kDexFilesChecksumsRead || dex_files_state_ == kDexFilesOpened)
@@ -1340,11 +1309,32 @@
                                                  const std::string& suffix) {
   DCHECK(IsAbsoluteLocation(path));
   DCHECK(!IsAbsoluteLocation(suffix));
-  return (path.size() > suffix.size()) &&
-         (path[path.size() - suffix.size() - 1u] == '/') &&
+  return (path.size() > suffix.size()) && (path[path.size() - suffix.size() - 1u] == '/') &&
          (std::string_view(path).substr(/*pos*/ path.size() - suffix.size()) == suffix);
 }
 
+// Resolves symlinks and returns the canonicalized absolute path. Returns relative path as is.
+static std::string ResolveIfAbsolutePath(const std::string& path) {
+  if (!IsAbsoluteLocation(path)) {
+    return path;
+  }
+
+  std::string filename = path;
+  std::string multi_dex_suffix;
+  size_t pos = filename.find(DexFileLoader::kMultiDexSeparator);
+  if (pos != std::string::npos) {
+    multi_dex_suffix = filename.substr(pos);
+    filename.resize(pos);
+  }
+
+  std::string resolved_filename;
+  if (!android::base::Realpath(filename, &resolved_filename)) {
+    PLOG(ERROR) << "Unable to resolve path '" << path << "'";
+    return path;
+  }
+  return resolved_filename + multi_dex_suffix;
+}
+
 // Returns true if the given dex names are mathing, false otherwise.
 static bool AreDexNameMatching(const std::string& actual_dex_name,
                                const std::string& expected_dex_name) {
@@ -1355,51 +1345,52 @@
   bool is_dex_name_absolute = IsAbsoluteLocation(actual_dex_name);
   bool is_expected_dex_name_absolute = IsAbsoluteLocation(expected_dex_name);
   bool dex_names_match = false;
+  std::string resolved_actual_dex_name = ResolveIfAbsolutePath(actual_dex_name);
+  std::string resolved_expected_dex_name = ResolveIfAbsolutePath(expected_dex_name);
 
   if (is_dex_name_absolute == is_expected_dex_name_absolute) {
     // If both locations are absolute or relative then compare them as they are.
     // This is usually the case for: shared libraries and secondary dex files.
-    dex_names_match = (actual_dex_name == expected_dex_name);
+    dex_names_match = (resolved_actual_dex_name == resolved_expected_dex_name);
   } else if (is_dex_name_absolute) {
     // The runtime name is absolute but the compiled name (the expected one) is relative.
     // This is the case for split apks which depend on base or on other splits.
     dex_names_match =
-        AbsolutePathHasRelativeSuffix(actual_dex_name, expected_dex_name);
+        AbsolutePathHasRelativeSuffix(resolved_actual_dex_name, resolved_expected_dex_name);
   } else if (is_expected_dex_name_absolute) {
     // The runtime name is relative but the compiled name is absolute.
     // There is no expected use case that would end up here as dex files are always loaded
     // with their absolute location. However, be tolerant and do the best effort (in case
     // there are unexpected new use case...).
     dex_names_match =
-        AbsolutePathHasRelativeSuffix(expected_dex_name, actual_dex_name);
+        AbsolutePathHasRelativeSuffix(resolved_expected_dex_name, resolved_actual_dex_name);
   } else {
     // Both locations are relative. In this case there's not much we can be sure about
     // except that the names are the same. The checksum will ensure that the files are
     // are same. This should not happen outside testing and manual invocations.
-    dex_names_match = (actual_dex_name == expected_dex_name);
+    dex_names_match = (resolved_actual_dex_name == resolved_expected_dex_name);
   }
 
   return dex_names_match;
 }
 
-bool ClassLoaderContext::ClassLoaderInfoMatch(
-    const ClassLoaderInfo& info,
-    const ClassLoaderInfo& expected_info,
-    const std::string& context_spec,
-    bool verify_names,
-    bool verify_checksums) const {
+bool ClassLoaderContext::ClassLoaderInfoMatch(const ClassLoaderInfo& info,
+                                              const ClassLoaderInfo& expected_info,
+                                              const std::string& context_spec,
+                                              bool verify_names,
+                                              bool verify_checksums) const {
   if (info.type != expected_info.type) {
     LOG(WARNING) << "ClassLoaderContext type mismatch"
-        << ". expected=" << GetClassLoaderTypeName(expected_info.type)
-        << ", found=" << GetClassLoaderTypeName(info.type)
-        << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+                 << ". expected=" << GetClassLoaderTypeName(expected_info.type)
+                 << ", found=" << GetClassLoaderTypeName(info.type) << " (" << context_spec << " | "
+                 << EncodeContextForOatFile("") << ")";
     return false;
   }
   if (info.classpath.size() != expected_info.classpath.size()) {
     LOG(WARNING) << "ClassLoaderContext classpath size mismatch"
-          << ". expected=" << expected_info.classpath.size()
-          << ", found=" << info.classpath.size()
-          << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+                 << ". expected=" << expected_info.classpath.size()
+                 << ", found=" << info.classpath.size() << " (" << context_spec << " | "
+                 << EncodeContextForOatFile("") << ")";
     return false;
   }
 
@@ -1415,9 +1406,9 @@
       // Compare the locations.
       if (!dex_names_match) {
         LOG(WARNING) << "ClassLoaderContext classpath element mismatch"
-            << ". expected=" << expected_info.classpath[k]
-            << ", found=" << info.classpath[k]
-            << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+                     << ". expected=" << expected_info.classpath[k]
+                     << ", found=" << info.classpath[k] << " (" << context_spec << " | "
+                     << EncodeContextForOatFile("") << ")";
         return false;
       }
 
@@ -1425,8 +1416,8 @@
       if (info.checksums[k] != expected_info.checksums[k]) {
         LOG(WARNING) << "ClassLoaderContext classpath element checksum mismatch"
                      << ". expected=" << expected_info.checksums[k]
-                     << ", found=" << info.checksums[k]
-                     << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+                     << ", found=" << info.checksums[k] << " (" << context_spec << " | "
+                     << EncodeContextForOatFile("") << ")";
         return false;
       }
     }
@@ -1434,9 +1425,9 @@
 
   if (info.shared_libraries.size() != expected_info.shared_libraries.size()) {
     LOG(WARNING) << "ClassLoaderContext shared library size mismatch. "
-          << "Expected=" << expected_info.shared_libraries.size()
-          << ", found=" << info.shared_libraries.size()
-          << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+                 << "Expected=" << expected_info.shared_libraries.size()
+                 << ", found=" << info.shared_libraries.size() << " (" << context_spec << " | "
+                 << EncodeContextForOatFile("") << ")";
     return false;
   }
   for (size_t i = 0; i < info.shared_libraries.size(); ++i) {
@@ -1451,13 +1442,13 @@
   if (info.parent.get() == nullptr) {
     if (expected_info.parent.get() != nullptr) {
       LOG(WARNING) << "ClassLoaderContext parent mismatch. "
-            << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+                   << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
       return false;
     }
     return true;
   } else if (expected_info.parent.get() == nullptr) {
     LOG(WARNING) << "ClassLoaderContext parent mismatch. "
-          << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+                 << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
     return false;
   } else {
     return ClassLoaderInfoMatch(*info.parent.get(),
@@ -1487,8 +1478,8 @@
   ClassLoaderInfo* info = class_loader_chain_.get();
   for (size_t k = 0; k < info->classpath.size(); k++) {
     for (const DexFile* dex_file : dex_files_to_check) {
-      if (info->checksums[k] == dex_file->GetLocationChecksum()
-          && AreDexNameMatching(info->classpath[k], dex_file->GetLocation())) {
+      if (info->checksums[k] == dex_file->GetLocationChecksum() &&
+          AreDexNameMatching(info->classpath[k], dex_file->GetLocation())) {
         result.insert(dex_file);
       }
     }
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index eceea00..806ab5e 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -51,7 +51,10 @@
 
   // Special encoding used to denote a foreign ClassLoader was found when trying to encode class
   // loader contexts for each classpath element in a ClassLoader. See
-  // EncodeClassPathContextsForClassLoader. Keep in sync with PackageDexUsage in the framework.
+  // EncodeClassPathContextsForClassLoader. Keep in sync with PackageDexUsage in the framework
+  // (frameworks/base/services/core/java/com/android/server/pm/dex/PackageDexUsage.java) and
+  // DexUseManager in ART Services
+  // (art/libartservice/service/java/com/android/server/art/DexUseManager.java).
   static constexpr const char* kUnsupportedClassLoaderContextEncoding =
       "=UnsupportedClassLoaderContext=";
 
@@ -155,9 +158,8 @@
   // Should only be called if OpenDexFiles() returned true.
   std::vector<const DexFile*> FlattenOpenedDexFiles() const;
 
-  // Return a colon-separated list of dex file locations from this class loader
-  // context after flattening.
-  std::string FlattenDexPaths() const;
+  // Return a list of dex file locations from this class loader context after flattening.
+  std::vector<std::string> FlattenDexPaths() const;
 
   // Verifies that the current context is identical to the context encoded as `context_spec`.
   // Identical means:
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index 073b4b6..ce9780a 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -18,8 +18,13 @@
 
 #include <gtest/gtest.h>
 
+#include <filesystem>
+#include <fstream>
+
+#include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "art_field-inl.h"
+#include "art_method-alloc-inl.h"
 #include "base/dchecked_vector.h"
 #include "base/stl_util.h"
 #include "class_linker.h"
@@ -36,12 +41,29 @@
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
 class ClassLoaderContextTest : public CommonRuntimeTest {
  public:
+  ClassLoaderContextTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
+  void SetUp() override {
+    CommonRuntimeTest::SetUp();
+    scratch_dir_ = std::make_unique<ScratchDir>();
+    scratch_path_ = scratch_dir_->GetPath();
+    // Remove the trailing '/';
+    scratch_path_.resize(scratch_path_.length() - 1);
+  }
+
+  void TearDown() override {
+    scratch_dir_.reset();
+    CommonRuntimeTest::TearDown();
+  }
+
   void VerifyContextSize(ClassLoaderContext* context, size_t expected_size) {
     ASSERT_TRUE(context != nullptr);
     ASSERT_EQ(expected_size, context->GetParentChainSize());
@@ -219,14 +241,14 @@
     ASSERT_FALSE(context->owns_the_dex_files_);
   }
 
-  void VerifyClassLoaderDexFiles(ScopedObjectAccess& soa,
+  void VerifyClassLoaderDexFiles(Thread* self,
                                  Handle<mirror::ClassLoader> class_loader,
-                                 jclass type,
+                                 ObjPtr<mirror::Class> type,
                                  std::vector<const DexFile*>& expected_dex_files)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    ASSERT_TRUE(class_loader->GetClass() == soa.Decode<mirror::Class>(type));
+    ASSERT_TRUE(class_loader->GetClass() == type);
 
-    std::vector<const DexFile*> class_loader_dex_files = GetDexFiles(soa, class_loader);
+    std::vector<const DexFile*> class_loader_dex_files = GetDexFiles(self, class_loader);
     ASSERT_EQ(expected_dex_files.size(), class_loader_dex_files.size());
 
     for (size_t i = 0; i < expected_dex_files.size(); i++) {
@@ -340,6 +362,9 @@
     return true;
   }
 
+  std::unique_ptr<ScratchDir> scratch_dir_;
+  std::string scratch_path_;
+
  private:
   void VerifyClassLoaderInfo(ClassLoaderContext* context,
                              size_t index,
@@ -653,10 +678,9 @@
   Handle<mirror::ClassLoader> class_loader = hs.NewHandle(
       soa.Decode<mirror::ClassLoader>(jclass_loader));
 
-  ASSERT_TRUE(class_loader->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader));
+  ASSERT_TRUE(class_loader->GetClass() == WellKnownClasses::dalvik_system_PathClassLoader);
   ASSERT_TRUE(class_loader->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
 
   // For the first class loader the class path dex files must come first and then the
   // compilation sources.
@@ -665,9 +689,9 @@
     expected_classpath.push_back(dex);
   }
 
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             expected_classpath);
 }
 
@@ -690,12 +714,12 @@
       soa.Decode<mirror::ClassLoader>(jclass_loader));
 
   // An empty context should create a single PathClassLoader with only the compilation sources.
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             compilation_sources_raw);
   ASSERT_TRUE(class_loader->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
 }
 
 TEST_F(ClassLoaderContextTest, CreateClassLoaderWithComplexChain) {
@@ -741,31 +765,31 @@
   for (auto& dex : compilation_sources_raw) {
     class_loader_1_dex_files.push_back(dex);
   }
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_1,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_1_dex_files);
 
   // Verify the second class loader
   Handle<mirror::ClassLoader> class_loader_2 = hs.NewHandle(class_loader_1->GetParent());
   std::vector<const DexFile*> class_loader_2_dex_files =
       MakeNonOwningPointerVector(classpath_dex_c);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_2,
-                            WellKnownClasses::dalvik_system_DelegateLastClassLoader,
+                            WellKnownClasses::dalvik_system_DelegateLastClassLoader.Get(),
                             class_loader_2_dex_files);
 
   // Verify the third class loader
   Handle<mirror::ClassLoader> class_loader_3 = hs.NewHandle(class_loader_2->GetParent());
   std::vector<const DexFile*> class_loader_3_dex_files =
       MakeNonOwningPointerVector(classpath_dex_d);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_3,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_3_dex_files);
   // The last class loader should have the BootClassLoader as a parent.
   ASSERT_TRUE(class_loader_3->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
 }
 
 TEST_F(ClassLoaderContextTest, CreateClassLoaderWithSharedLibraries) {
@@ -809,14 +833,13 @@
   for (auto& dex : compilation_sources_raw) {
     class_loader_1_dex_files.push_back(dex);
   }
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_1,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_1_dex_files);
 
   // Verify the shared libraries.
-  ArtField* field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders);
+  ArtField* field = WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
   ObjPtr<mirror::Object> raw_shared_libraries = field->GetObject(class_loader_1.Get());
   ASSERT_TRUE(raw_shared_libraries != nullptr);
 
@@ -828,9 +851,9 @@
   Handle<mirror::ClassLoader> class_loader_2 = hs.NewHandle(shared_libraries->Get(0));
   std::vector<const DexFile*> class_loader_2_dex_files =
       MakeNonOwningPointerVector(classpath_dex_c);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_2,
-                            WellKnownClasses::dalvik_system_DelegateLastClassLoader,
+                            WellKnownClasses::dalvik_system_DelegateLastClassLoader.Get(),
                             class_loader_2_dex_files);
   raw_shared_libraries = field->GetObject(class_loader_2.Get());
   ASSERT_TRUE(raw_shared_libraries == nullptr);
@@ -839,20 +862,20 @@
   Handle<mirror::ClassLoader> class_loader_3 = hs.NewHandle(shared_libraries->Get(1));
   std::vector<const DexFile*> class_loader_3_dex_files =
       MakeNonOwningPointerVector(classpath_dex_d);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_3,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_3_dex_files);
   raw_shared_libraries = field->GetObject(class_loader_3.Get());
   ASSERT_TRUE(raw_shared_libraries == nullptr);
 
   // All class loaders should have the BootClassLoader as a parent.
   ASSERT_TRUE(class_loader_1->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
   ASSERT_TRUE(class_loader_2->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
   ASSERT_TRUE(class_loader_3->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
 }
 
 TEST_F(ClassLoaderContextTest, CreateClassLoaderWithSharedLibrariesInParentToo) {
@@ -894,14 +917,13 @@
   for (auto& dex : compilation_sources_raw) {
     class_loader_1_dex_files.push_back(dex);
   }
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_1,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_1_dex_files);
 
   // Verify its shared library.
-  ArtField* field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders);
+  ArtField* field = WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
   ObjPtr<mirror::Object> raw_shared_libraries = field->GetObject(class_loader_1.Get());
   ASSERT_TRUE(raw_shared_libraries != nullptr);
 
@@ -912,9 +934,9 @@
   Handle<mirror::ClassLoader> class_loader_2 = hs.NewHandle(shared_libraries->Get(0));
   std::vector<const DexFile*> class_loader_2_dex_files =
       MakeNonOwningPointerVector(classpath_dex_b);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_2,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_2_dex_files);
   raw_shared_libraries = field->GetObject(class_loader_2.Get());
   ASSERT_TRUE(raw_shared_libraries == nullptr);
@@ -923,9 +945,9 @@
   Handle<mirror::ClassLoader> class_loader_3 = hs.NewHandle(class_loader_1->GetParent());
   std::vector<const DexFile*> class_loader_3_dex_files =
       MakeNonOwningPointerVector(classpath_dex_c);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_3,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_3_dex_files);
 
   // Verify its shared library.
@@ -939,20 +961,20 @@
   Handle<mirror::ClassLoader> class_loader_4 = hs.NewHandle(shared_libraries_2->Get(0));
   std::vector<const DexFile*> class_loader_4_dex_files =
       MakeNonOwningPointerVector(classpath_dex_d);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_4,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_4_dex_files);
   raw_shared_libraries = field->GetObject(class_loader_4.Get());
   ASSERT_TRUE(raw_shared_libraries == nullptr);
 
   // Class loaders should have the BootClassLoader as a parent.
   ASSERT_TRUE(class_loader_2->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
   ASSERT_TRUE(class_loader_3->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
   ASSERT_TRUE(class_loader_4->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
 }
 
 TEST_F(ClassLoaderContextTest, CreateClassLoaderWithSharedLibrariesDependencies) {
@@ -994,14 +1016,13 @@
   for (auto& dex : compilation_sources_raw) {
     class_loader_1_dex_files.push_back(dex);
   }
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_1,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_1_dex_files);
 
   // Verify its shared library.
-  ArtField* field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders);
+  ArtField* field = WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
   ObjPtr<mirror::Object> raw_shared_libraries = field->GetObject(class_loader_1.Get());
   ASSERT_TRUE(raw_shared_libraries != nullptr);
 
@@ -1012,9 +1033,9 @@
   Handle<mirror::ClassLoader> class_loader_2 = hs.NewHandle(shared_libraries->Get(0));
   std::vector<const DexFile*> class_loader_2_dex_files =
       MakeNonOwningPointerVector(classpath_dex_b);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_2,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_2_dex_files);
 
   // Verify the shared library dependency of the shared library.
@@ -1028,9 +1049,9 @@
   Handle<mirror::ClassLoader> class_loader_3 = hs.NewHandle(shared_libraries_2->Get(0));
   std::vector<const DexFile*> class_loader_3_dex_files =
       MakeNonOwningPointerVector(classpath_dex_c);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_3,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_3_dex_files);
   raw_shared_libraries = field->GetObject(class_loader_3.Get());
   ASSERT_TRUE(raw_shared_libraries == nullptr);
@@ -1039,20 +1060,20 @@
   Handle<mirror::ClassLoader> class_loader_4 = hs.NewHandle(class_loader_1->GetParent());
   std::vector<const DexFile*> class_loader_4_dex_files =
       MakeNonOwningPointerVector(classpath_dex_d);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_4,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_4_dex_files);
   raw_shared_libraries = field->GetObject(class_loader_4.Get());
   ASSERT_TRUE(raw_shared_libraries == nullptr);
 
   // Class loaders should have the BootClassLoader as a parent.
   ASSERT_TRUE(class_loader_2->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
   ASSERT_TRUE(class_loader_3->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
   ASSERT_TRUE(class_loader_4->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
 }
 
 TEST_F(ClassLoaderContextTest, RemoveSourceLocations) {
@@ -1109,14 +1130,13 @@
   for (auto& dex : compilation_sources_raw) {
     class_loader_1_dex_files.push_back(dex);
   }
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_1,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_1_dex_files);
 
   // Verify its shared library.
-  ArtField* field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders);
+  ArtField* field = WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
   ObjPtr<mirror::Object> raw_shared_libraries = field->GetObject(class_loader_1.Get());
   ASSERT_TRUE(raw_shared_libraries != nullptr);
 
@@ -1127,18 +1147,18 @@
   Handle<mirror::ClassLoader> class_loader_2 = hs.NewHandle(shared_libraries->Get(0));
   std::vector<const DexFile*> class_loader_2_dex_files =
       MakeNonOwningPointerVector(classpath_dex_b);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_2,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_2_dex_files);
 
   // Verify the parent.
   Handle<mirror::ClassLoader> class_loader_3 = hs.NewHandle(class_loader_1->GetParent());
   std::vector<const DexFile*> class_loader_3_dex_files =
       MakeNonOwningPointerVector(classpath_dex_c);
-  VerifyClassLoaderDexFiles(soa,
+  VerifyClassLoaderDexFiles(soa.Self(),
                             class_loader_3,
-                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                             class_loader_3_dex_files);
 
   // Verify its shared library is the same as the child.
@@ -1151,9 +1171,9 @@
 
   // Class loaders should have the BootClassLoader as a parent.
   ASSERT_TRUE(class_loader_2->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
   ASSERT_TRUE(class_loader_3->GetParent()->GetClass() ==
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+      WellKnownClasses::java_lang_BootClassLoader);
 }
 
 TEST_F(ClassLoaderContextTest, EncodeInOatFile) {
@@ -1343,13 +1363,14 @@
 
 static jobject CreateForeignClassLoader() {
   ScopedObjectAccess soa(Thread::Current());
-  JNIEnv* env = soa.Env();
 
   // We cannot instantiate a ClassLoader directly, so instead we allocate an Object to represent
   // our foreign ClassLoader (this works because the runtime does proper instanceof checks before
   // operating on this object.
-  jmethodID ctor = env->GetMethodID(WellKnownClasses::java_lang_Object, "<init>", "()V");
-  return env->NewObject(WellKnownClasses::java_lang_Object, ctor);
+  ArtMethod* ctor =
+      GetClassRoot<mirror::Object>()->FindClassMethod("<init>", "()V", kRuntimePointerSize);
+  CHECK(ctor != nullptr);
+  return soa.AddLocalReference<jobject>(ctor->NewObject<>(soa.Self()));
 }
 
 TEST_F(ClassLoaderContextTest, EncodeContextsForUnsupportedBase) {
@@ -1600,6 +1621,28 @@
             ClassLoaderContext::VerificationResult::kVerifies);
 }
 
+TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterResolvingSymlinks) {
+  {
+    std::ofstream ofs(scratch_path_ + "/foo.jar");
+    ASSERT_TRUE(ofs);
+  }
+  std::filesystem::create_directory_symlink(scratch_path_, scratch_path_ + "/bar");
+
+  std::string context_spec =
+      android::base::StringPrintf("PCL[%s/foo.jar*123:%s/foo.jar!classes2.dex*456]",
+                                  scratch_path_.c_str(),
+                                  scratch_path_.c_str());
+  std::unique_ptr<ClassLoaderContext> context = ParseContextWithChecksums(context_spec);
+  PretendContextOpenedDexFilesForChecksums(context.get());
+
+  std::string context_spec_with_symlinks =
+      android::base::StringPrintf("PCL[%s/bar/foo.jar*123:%s/bar/foo.jar!classes2.dex*456]",
+                                  scratch_path_.c_str(),
+                                  scratch_path_.c_str());
+  ASSERT_EQ(context->VerifyClassLoaderContextMatch(context_spec_with_symlinks),
+            ClassLoaderContext::VerificationResult::kVerifies);
+}
+
 TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterEncoding) {
   jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
   jobject class_loader_b = LoadDexInDelegateLastClassLoader("ForClassLoaderB", class_loader_a);
diff --git a/runtime/class_loader_utils.h b/runtime/class_loader_utils.h
index 934c92b..6868b3f 100644
--- a/runtime/class_loader_utils.h
+++ b/runtime/class_loader_utils.h
@@ -26,46 +26,36 @@
 #include "mirror/object.h"
 #include "native/dalvik_system_DexFile.h"
 #include "scoped_thread_state_change-inl.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
 // Returns true if the given class loader derives from BaseDexClassLoader.
-inline bool IsInstanceOfBaseDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                           Handle<mirror::ClassLoader> class_loader)
+inline bool IsInstanceOfBaseDexClassLoader(Handle<mirror::ClassLoader> class_loader)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  return class_loader->InstanceOf(
-      soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_BaseDexClassLoader));
+  return class_loader->InstanceOf(WellKnownClasses::dalvik_system_BaseDexClassLoader.Get());
 }
 
 // Returns true if the given class loader is either a PathClassLoader or a DexClassLoader.
 // (they both have the same behaviour with respect to class lookup order)
-inline bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                   Handle<mirror::ClassLoader> class_loader)
+inline bool IsPathOrDexClassLoader(Handle<mirror::ClassLoader> class_loader)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ObjPtr<mirror::Class> class_loader_class = class_loader->GetClass();
-  return
-      (class_loader_class ==
-          soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)) ||
-      (class_loader_class ==
-          soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader));
+  return (class_loader_class == WellKnownClasses::dalvik_system_PathClassLoader) ||
+         (class_loader_class == WellKnownClasses::dalvik_system_DexClassLoader);
 }
 
 // Returns true if the given class loader is an InMemoryDexClassLoader.
-inline bool IsInMemoryDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                     Handle<mirror::ClassLoader> class_loader)
+inline bool IsInMemoryDexClassLoader(Handle<mirror::ClassLoader> class_loader)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ObjPtr<mirror::Class> class_loader_class = class_loader->GetClass();
-  return (class_loader_class ==
-      soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_InMemoryDexClassLoader));
+  return (class_loader_class == WellKnownClasses::dalvik_system_InMemoryDexClassLoader);
 }
 
-inline bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                      Handle<mirror::ClassLoader> class_loader)
+inline bool IsDelegateLastClassLoader(Handle<mirror::ClassLoader> class_loader)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ObjPtr<mirror::Class> class_loader_class = class_loader->GetClass();
-  return class_loader_class ==
-      soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader);
+  return class_loader_class == WellKnownClasses::dalvik_system_DelegateLastClassLoader;
 }
 
 // Visit the DexPathList$Element instances in the given classloader with the given visitor.
@@ -75,20 +65,17 @@
 //     when the visitor ends the visit (by returning false).
 // This function assumes that the given classloader is a subclass of BaseDexClassLoader!
 template <typename Visitor, typename RetType>
-inline RetType VisitClassLoaderDexElements(ScopedObjectAccessAlreadyRunnable& soa,
+inline RetType VisitClassLoaderDexElements(Thread* self,
                                            Handle<mirror::ClassLoader> class_loader,
                                            Visitor fn,
                                            RetType defaultReturn)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  Thread* self = soa.Self();
   ObjPtr<mirror::Object> dex_path_list =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)->
-          GetObject(class_loader.Get());
+      WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList->GetObject(class_loader.Get());
   if (dex_path_list != nullptr) {
     // DexPathList has an array dexElements of Elements[] which each contain a dex file.
     ObjPtr<mirror::Object> dex_elements_obj =
-        jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)->
-            GetObject(dex_path_list);
+        WellKnownClasses::dalvik_system_DexPathList_dexElements->GetObject(dex_path_list);
     // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look
     // at the mCookie which is a DexFile vector.
     if (dex_elements_obj != nullptr) {
@@ -117,15 +104,13 @@
 //     when the visitor ends the visit (by returning false).
 // This function assumes that the given classloader is a subclass of BaseDexClassLoader!
 template <typename Visitor, typename RetType>
-inline RetType VisitClassLoaderDexFiles(ScopedObjectAccessAlreadyRunnable& soa,
+inline RetType VisitClassLoaderDexFiles(Thread* self,
                                         Handle<mirror::ClassLoader> class_loader,
                                         Visitor fn,
                                         RetType defaultReturn)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  ArtField* const cookie_field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
-  ArtField* const dex_file_field =
-      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+  ArtField* const cookie_field = WellKnownClasses::dalvik_system_DexFile_cookie;
+  ArtField* const dex_file_field = WellKnownClasses::dalvik_system_DexPathList__Element_dexFile;
   if (dex_file_field == nullptr || cookie_field == nullptr) {
     return defaultReturn;
   }
@@ -133,7 +118,9 @@
       REQUIRES_SHARED(Locks::mutator_lock_) {
     ObjPtr<mirror::Object> dex_file = dex_file_field->GetObject(element);
     if (dex_file != nullptr) {
-      ObjPtr<mirror::LongArray> long_array = cookie_field->GetObject(dex_file)->AsLongArray();
+      StackHandleScope<1> hs(self);
+      Handle<mirror::LongArray> long_array =
+          hs.NewHandle(cookie_field->GetObject(dex_file)->AsLongArray());
       if (long_array == nullptr) {
         // This should never happen so log a warning.
         LOG(WARNING) << "Null DexFile::mCookie";
@@ -155,12 +142,12 @@
     return true;
   };
 
-  return VisitClassLoaderDexElements(soa, class_loader, visit_dex_files, defaultReturn);
+  return VisitClassLoaderDexElements(self, class_loader, visit_dex_files, defaultReturn);
 }
 
 // Simplified version of the above, w/o out argument.
 template <typename Visitor>
-inline void VisitClassLoaderDexFiles(ScopedObjectAccessAlreadyRunnable& soa,
+inline void VisitClassLoaderDexFiles(Thread* self,
                                      Handle<mirror::ClassLoader> class_loader,
                                      Visitor fn)
     REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -174,10 +161,10 @@
 
     return fn(dex_file);
   };
-  VisitClassLoaderDexFiles<decltype(helper), void*>(soa,
+  VisitClassLoaderDexFiles<decltype(helper), void*>(self,
                                                     class_loader,
                                                     helper,
-                                                    /* default= */ nullptr);
+                                                    /* defaultReturn= */ nullptr);
 }
 
 }  // namespace art
diff --git a/runtime/class_table-inl.h b/runtime/class_table-inl.h
index 071376c..ecc8a0a 100644
--- a/runtime/class_table-inl.h
+++ b/runtime/class_table-inl.h
@@ -104,6 +104,43 @@
   }
 }
 
+template <typename Visitor>
+class ClassTable::TableSlot::ClassAndRootVisitor {
+ public:
+  explicit ClassAndRootVisitor(Visitor& visitor) : visitor_(visitor) {}
+
+  void VisitRoot(mirror::CompressedReference<mirror::Object>* klass) const
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK(!klass->IsNull());
+    // Visit roots in the klass object
+    visitor_(klass->AsMirrorPtr());
+    // Visit the GC-root holding klass' reference
+    visitor_.VisitRoot(klass);
+  }
+
+ private:
+  Visitor& visitor_;
+};
+
+template <typename Visitor>
+void ClassTable::VisitClassesAndRoots(Visitor& visitor) {
+  TableSlot::ClassAndRootVisitor class_visitor(visitor);
+  ReaderMutexLock mu(Thread::Current(), lock_);
+  for (ClassSet& class_set : classes_) {
+    for (TableSlot& table_slot : class_set) {
+      table_slot.VisitRoot(class_visitor);
+    }
+  }
+  for (GcRoot<mirror::Object>& root : strong_roots_) {
+    visitor.VisitRoot(root.AddressWithoutBarrier());
+  }
+  for (const OatFile* oat_file : oat_files_) {
+    for (GcRoot<mirror::Object>& root : oat_file->GetBssGcRoots()) {
+      visitor.VisitRootIfNonNull(root.AddressWithoutBarrier());
+    }
+  }
+}
+
 template <ReadBarrierOption kReadBarrierOption, typename Visitor>
 bool ClassTable::Visit(Visitor& visitor) {
   ReaderMutexLock mu(Thread::Current(), lock_);
@@ -176,6 +213,11 @@
   DCHECK_EQ(descriptor_hash, klass->DescriptorHash());
 }
 
+inline ClassTable::TableSlot::TableSlot(uint32_t ptr, uint32_t descriptor_hash)
+    : data_(ptr | MaskHash(descriptor_hash)) {
+  DCHECK_ALIGNED(ptr, kObjectAlignment);
+}
+
 template <typename Filter>
 inline void ClassTable::RemoveStrongRoots(const Filter& filter) {
   WriterMutexLock mu(Thread::Current(), lock_);
@@ -190,6 +232,11 @@
   return Lookup(descriptor, hash);
 }
 
+inline size_t ClassTable::Size() const {
+  ReaderMutexLock mu(Thread::Current(), lock_);
+  return classes_.size();
+}
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_CLASS_TABLE_INL_H_
diff --git a/runtime/class_table.h b/runtime/class_table.h
index 212a7d6..7e26373 100644
--- a/runtime/class_table.h
+++ b/runtime/class_table.h
@@ -58,18 +58,31 @@
     explicit TableSlot(ObjPtr<mirror::Class> klass);
 
     TableSlot(ObjPtr<mirror::Class> klass, uint32_t descriptor_hash);
+    TableSlot(uint32_t ptr, uint32_t descriptor_hash);
 
     TableSlot& operator=(const TableSlot& copy) {
       data_.store(copy.data_.load(std::memory_order_relaxed), std::memory_order_relaxed);
       return *this;
     }
 
+    uint32_t Data() const {
+      return data_.load(std::memory_order_relaxed);
+    }
+
     bool IsNull() const REQUIRES_SHARED(Locks::mutator_lock_);
 
     uint32_t Hash() const {
       return MaskHash(data_.load(std::memory_order_relaxed));
     }
 
+    uint32_t NonHashData() const {
+      return RemoveHash(Data());
+    }
+
+    static uint32_t RemoveHash(uint32_t hash) {
+      return hash & ~kHashMask;
+    }
+
     static uint32_t MaskHash(uint32_t hash) {
       return hash & kHashMask;
     }
@@ -85,6 +98,9 @@
     template<typename Visitor>
     void VisitRoot(const Visitor& visitor) const NO_THREAD_SAFETY_ANALYSIS;
 
+    template<typename Visitor>
+    class ClassAndRootVisitor;
+
    private:
     // Extract a raw pointer from an address.
     static ObjPtr<mirror::Class> ExtractPtr(uint32_t data)
@@ -165,6 +181,11 @@
       REQUIRES(!lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Returns the number of classes in the class table.
+  size_t Size() const
+      REQUIRES(!lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Update a class in the table with the new class. Returns the existing class which was replaced.
   ObjPtr<mirror::Class> UpdateClass(const char* descriptor,
                                     ObjPtr<mirror::Class> new_klass,
@@ -185,6 +206,12 @@
       REQUIRES(!lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template<class Visitor>
+  void VisitClassesAndRoots(Visitor& visitor)
+      NO_THREAD_SAFETY_ANALYSIS
+      REQUIRES(!lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Stops visit if the visitor returns false.
   template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
   bool Visit(Visitor& visitor)
diff --git a/runtime/class_table_test.cc b/runtime/class_table_test.cc
index 7dbeba5..14c5d70 100644
--- a/runtime/class_table_test.cc
+++ b/runtime/class_table_test.cc
@@ -66,7 +66,12 @@
 };
 
 
-class ClassTableTest : public CommonRuntimeTest {};
+class ClassTableTest : public CommonRuntimeTest {
+ protected:
+  ClassTableTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+};
 
 TEST_F(ClassTableTest, ClassTable) {
   ScopedObjectAccess soa(Thread::Current());
diff --git a/runtime/common_dex_operations.h b/runtime/common_dex_operations.h
index 882e3ce..ce2090c 100644
--- a/runtime/common_dex_operations.h
+++ b/runtime/common_dex_operations.h
@@ -26,6 +26,7 @@
 #include "dex/code_item_accessors.h"
 #include "dex/dex_file_structs.h"
 #include "dex/primitive.h"
+#include "entrypoints/entrypoint_utils.h"
 #include "handle_scope-inl.h"
 #include "instrumentation.h"
 #include "interpreter/interpreter.h"
@@ -55,8 +56,28 @@
                                           ShadowFrame* shadow_frame,
                                           uint16_t arg_offset,
                                           JValue* result);
+
 }  // namespace interpreter
 
+inline bool EnsureInitialized(Thread* self, ShadowFrame* shadow_frame)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (LIKELY(!shadow_frame->GetMethod()->StillNeedsClinitCheck())) {
+    return true;
+  }
+
+  // Save the shadow frame.
+  ScopedStackedShadowFramePusher pusher(self, shadow_frame);
+  StackHandleScope<1> hs(self);
+  Handle<mirror::Class> h_class = hs.NewHandle(shadow_frame->GetMethod()->GetDeclaringClass());
+  if (UNLIKELY(!Runtime::Current()->GetClassLinker()->EnsureInitialized(
+                    self, h_class, /*can_init_fields=*/ true, /*can_init_parents=*/ true))) {
+    DCHECK(self->IsExceptionPending());
+    return false;
+  }
+  DCHECK(h_class->IsInitializing());
+  return true;
+}
+
 inline void PerformCall(Thread* self,
                         const CodeItemDataAccessor& accessor,
                         ArtMethod* caller_method,
@@ -65,15 +86,20 @@
                         JValue* result,
                         bool use_interpreter_entrypoint)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  if (LIKELY(Runtime::Current()->IsStarted())) {
-    if (use_interpreter_entrypoint) {
-      interpreter::ArtInterpreterToInterpreterBridge(self, accessor, callee_frame, result);
-    } else {
-      interpreter::ArtInterpreterToCompiledCodeBridge(
-          self, caller_method, callee_frame, first_dest_reg, result);
-    }
-  } else {
+  if (UNLIKELY(!Runtime::Current()->IsStarted())) {
     interpreter::UnstartedRuntime::Invoke(self, accessor, callee_frame, result, first_dest_reg);
+    return;
+  }
+
+  if (!EnsureInitialized(self, callee_frame)) {
+    return;
+  }
+
+  if (use_interpreter_entrypoint) {
+    interpreter::ArtInterpreterToInterpreterBridge(self, accessor, callee_frame, result);
+  } else {
+    interpreter::ArtInterpreterToCompiledCodeBridge(
+        self, caller_method, callee_frame, first_dest_reg, result);
   }
 }
 
@@ -149,7 +175,7 @@
   return true;
 }
 
-template<Primitive::Type field_type, bool do_assignability_check, bool transaction_active>
+template<Primitive::Type field_type, bool transaction_active>
 ALWAYS_INLINE bool DoFieldPutCommon(Thread* self,
                                     const ShadowFrame& shadow_frame,
                                     ObjPtr<mirror::Object> obj,
@@ -210,7 +236,7 @@
       break;
     case Primitive::kPrimNot: {
       ObjPtr<mirror::Object> reg = value.GetL();
-      if (do_assignability_check && reg != nullptr) {
+      if (reg != nullptr && !shadow_frame.GetMethod()->SkipAccessChecks()) {
         // FieldHelper::GetType can resolve classes, use a handle wrapper which will restore the
         // object in the destructor.
         ObjPtr<mirror::Class> field_class;
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index a48d860..ee574bc 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -63,7 +63,7 @@
 #include "runtime_intrinsics.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -121,9 +121,12 @@
   }
 
   PreRuntimeCreate();
-  if (!Runtime::Create(options, false)) {
-    LOG(FATAL) << "Failed to create runtime";
-    UNREACHABLE();
+  {
+    ScopedLogSeverity sls(LogSeverity::WARNING);
+    if (!Runtime::Create(options, false)) {
+      LOG(FATAL) << "Failed to create runtime";
+      UNREACHABLE();
+    }
   }
   PostRuntimeCreate();
   runtime_.reset(Runtime::Current());
@@ -158,17 +161,11 @@
 
   {
     ScopedObjectAccess soa(Thread::Current());
+    runtime_->GetClassLinker()->RunEarlyRootClinits(soa.Self());
+    InitializeIntrinsics();
     runtime_->RunRootClinits(soa.Self());
   }
 
-  // We're back in native, take the opportunity to initialize well known classes and ensure
-  // intrinsics are initialized.
-  WellKnownClasses::Init(Thread::Current()->GetJniEnv());
-  InitializeIntrinsics();
-
-  // Create the heap thread pool so that the GC runs in parallel for tests. Normally, the thread
-  // pool is created by the runtime.
-  runtime_->GetHeap()->CreateThreadPool();
   runtime_->GetHeap()->VerifyHeap();  // Check for heap corruption before the test
   // Reduce timinig-dependent flakiness in OOME behavior (eg StubTest.AllocObject).
   runtime_->GetHeap()->SetMinIntervalHomogeneousSpaceCompactionByOom(0U);
@@ -198,20 +195,17 @@
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::ClassLoader> class_loader = hs.NewHandle(
       soa.Decode<mirror::ClassLoader>(jclass_loader));
-  return GetDexFiles(soa, class_loader);
+  return GetDexFiles(soa.Self(), class_loader);
 }
 
 std::vector<const DexFile*> CommonRuntimeTestImpl::GetDexFiles(
-    ScopedObjectAccess& soa,
+    Thread* self,
     Handle<mirror::ClassLoader> class_loader) {
-  DCHECK(
-      (class_loader->GetClass() ==
-          soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)) ||
-      (class_loader->GetClass() ==
-          soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader)));
+  DCHECK((class_loader->GetClass() == WellKnownClasses::dalvik_system_PathClassLoader) ||
+         (class_loader->GetClass() == WellKnownClasses::dalvik_system_DelegateLastClassLoader));
 
   std::vector<const DexFile*> ret;
-  VisitClassLoaderDexFiles(soa,
+  VisitClassLoaderDexFiles(self,
                            class_loader,
                            [&](const DexFile* cp_dex_file) {
                              if (cp_dex_file == nullptr) {
@@ -262,8 +256,9 @@
 }
 
 jobject
-CommonRuntimeTestImpl::LoadDexInWellKnownClassLoader(const std::vector<std::string>& dex_names,
-                                                     jclass loader_class,
+CommonRuntimeTestImpl::LoadDexInWellKnownClassLoader(ScopedObjectAccess& soa,
+                                                     const std::vector<std::string>& dex_names,
+                                                     ObjPtr<mirror::Class> loader_class,
                                                      jobject parent_loader,
                                                      jobject shared_libraries,
                                                      jobject shared_libraries_after) {
@@ -276,39 +271,44 @@
       loaded_dex_files_.push_back(std::move(dex_file));
     }
   }
-  Thread* self = Thread::Current();
-  ScopedObjectAccess soa(self);
+  StackHandleScope<4> hs(soa.Self());
+  Handle<mirror::Class> h_loader_class = hs.NewHandle(loader_class);
+  Handle<mirror::ClassLoader> h_parent_loader =
+      hs.NewHandle(soa.Decode<mirror::ClassLoader>(parent_loader));
+  Handle<mirror::ObjectArray<mirror::ClassLoader>> h_shared_libraries =
+      hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::ClassLoader>>(shared_libraries));
+  Handle<mirror::ObjectArray<mirror::ClassLoader>> h_shared_libraries_after =
+      hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::ClassLoader>>(shared_libraries_after));
 
-  jobject result = Runtime::Current()->GetClassLinker()->CreateWellKnownClassLoader(
-      self,
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  ObjPtr<mirror::ClassLoader> result = class_linker->CreateWellKnownClassLoader(
+      soa.Self(),
       class_path,
-      loader_class,
-      parent_loader,
-      shared_libraries,
-      shared_libraries_after);
+      h_loader_class,
+      h_parent_loader,
+      h_shared_libraries,
+      h_shared_libraries_after);
 
   {
     // Verify we build the correct chain.
 
-    ObjPtr<mirror::ClassLoader> actual_class_loader = soa.Decode<mirror::ClassLoader>(result);
     // Verify that the result has the correct class.
-    CHECK_EQ(soa.Decode<mirror::Class>(loader_class), actual_class_loader->GetClass());
+    CHECK_EQ(h_loader_class.Get(), result->GetClass());
     // Verify that the parent is not null. The boot class loader will be set up as a
     // proper object.
-    ObjPtr<mirror::ClassLoader> actual_parent(actual_class_loader->GetParent());
+    ObjPtr<mirror::ClassLoader> actual_parent(result->GetParent());
     CHECK(actual_parent != nullptr);
 
     if (parent_loader != nullptr) {
       // We were given a parent. Verify that it's what we expect.
-      ObjPtr<mirror::ClassLoader> expected_parent = soa.Decode<mirror::ClassLoader>(parent_loader);
-      CHECK_EQ(expected_parent, actual_parent);
+      CHECK_EQ(h_parent_loader.Get(), actual_parent);
     } else {
       // No parent given. The parent must be the BootClassLoader.
-      CHECK(Runtime::Current()->GetClassLinker()->IsBootClassLoader(soa, actual_parent));
+      CHECK(class_linker->IsBootClassLoader(actual_parent));
     }
   }
 
-  return result;
+  return soa.Env()->GetVm()->AddGlobalRef(soa.Self(), result);
 }
 
 jobject CommonRuntimeTestImpl::LoadDexInPathClassLoader(const std::string& dex_name,
@@ -325,8 +325,10 @@
                                                         jobject parent_loader,
                                                         jobject shared_libraries,
                                                         jobject shared_libraries_after) {
-  return LoadDexInWellKnownClassLoader(names,
-                                       WellKnownClasses::dalvik_system_PathClassLoader,
+  ScopedObjectAccess soa(Thread::Current());
+  return LoadDexInWellKnownClassLoader(soa,
+                                       names,
+                                       WellKnownClasses::dalvik_system_PathClassLoader.Get(),
                                        parent_loader,
                                        shared_libraries,
                                        shared_libraries_after);
@@ -334,16 +336,22 @@
 
 jobject CommonRuntimeTestImpl::LoadDexInDelegateLastClassLoader(const std::string& dex_name,
                                                                 jobject parent_loader) {
-  return LoadDexInWellKnownClassLoader({ dex_name },
-                                       WellKnownClasses::dalvik_system_DelegateLastClassLoader,
-                                       parent_loader);
+  ScopedObjectAccess soa(Thread::Current());
+  return LoadDexInWellKnownClassLoader(
+      soa,
+      { dex_name },
+      WellKnownClasses::dalvik_system_DelegateLastClassLoader.Get(),
+      parent_loader);
 }
 
 jobject CommonRuntimeTestImpl::LoadDexInInMemoryDexClassLoader(const std::string& dex_name,
                                                                jobject parent_loader) {
-  return LoadDexInWellKnownClassLoader({ dex_name },
-                                       WellKnownClasses::dalvik_system_InMemoryDexClassLoader,
-                                       parent_loader);
+  ScopedObjectAccess soa(Thread::Current());
+  return LoadDexInWellKnownClassLoader(
+      soa,
+      { dex_name },
+      WellKnownClasses::dalvik_system_InMemoryDexClassLoader.Get(),
+      parent_loader);
 }
 
 void CommonRuntimeTestImpl::FillHeap(Thread* self,
@@ -564,10 +572,8 @@
   for (const std::string& dex : dexes) {
     std::vector<std::unique_ptr<const DexFile>> dex_files;
     std::string error_msg;
-    const ArtDexFileLoader dex_file_loader;
-    CHECK(dex_file_loader.Open(dex.c_str(),
-                               dex,
-                               /*verify*/ true,
+    ArtDexFileLoader dex_file_loader(dex);
+    CHECK(dex_file_loader.Open(/*verify*/ true,
                                /*verify_checksum*/ false,
                                &error_msg,
                                &dex_files))
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index 9fa9c5d..85c48a2 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -46,12 +46,13 @@
 using ScopedLogSeverity = android::base::ScopedLogSeverity;
 
 template<class MirrorType>
-static inline ObjPtr<MirrorType> MakeObjPtr(MirrorType* ptr) {
+static inline ObjPtr<MirrorType> MakeObjPtr(MirrorType* ptr) REQUIRES_SHARED(Locks::mutator_lock_) {
   return ptr;
 }
 
 template<class MirrorType>
-static inline ObjPtr<MirrorType> MakeObjPtr(ObjPtr<MirrorType> ptr) {
+static inline ObjPtr<MirrorType> MakeObjPtr(ObjPtr<MirrorType> ptr)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
   return ptr;
 }
 
@@ -87,13 +88,12 @@
   bool MutateDexFile(File* output_dex, const std::string& input_jar, const Mutator& mutator) {
     std::vector<std::unique_ptr<const DexFile>> dex_files;
     std::string error_msg;
-    const ArtDexFileLoader dex_file_loader;
-    CHECK(dex_file_loader.Open(input_jar.c_str(),
-                               input_jar.c_str(),
-                               /*verify=*/ true,
-                               /*verify_checksum=*/ true,
+    ArtDexFileLoader dex_file_loader(input_jar);
+    CHECK(dex_file_loader.Open(/*verify=*/true,
+                               /*verify_checksum=*/true,
                                &error_msg,
-                               &dex_files)) << error_msg;
+                               &dex_files))
+        << error_msg;
     EXPECT_EQ(dex_files.size(), 1u) << "Only one input dex is supported";
     const std::unique_ptr<const DexFile>& dex = dex_files[0];
     CHECK(dex->EnableWrite()) << "Failed to enable write";
@@ -152,6 +152,7 @@
   jobject LoadMultiDex(const char* first_dex_name, const char* second_dex_name)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // The following helper functions return global JNI references to the class loader.
   jobject LoadDexInPathClassLoader(const std::string& dex_name,
                                    jobject parent_loader,
                                    jobject shared_libraries = nullptr,
@@ -162,11 +163,13 @@
                                    jobject shared_libraries_after = nullptr);
   jobject LoadDexInDelegateLastClassLoader(const std::string& dex_name, jobject parent_loader);
   jobject LoadDexInInMemoryDexClassLoader(const std::string& dex_name, jobject parent_loader);
-  jobject LoadDexInWellKnownClassLoader(const std::vector<std::string>& dex_names,
-                                        jclass loader_class,
+  jobject LoadDexInWellKnownClassLoader(ScopedObjectAccess& soa,
+                                        const std::vector<std::string>& dex_names,
+                                        ObjPtr<mirror::Class> loader_class,
                                         jobject parent_loader,
                                         jobject shared_libraries = nullptr,
-                                        jobject shared_libraries_after = nullptr);
+                                        jobject shared_libraries_after = nullptr)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   void VisitDexes(ArrayRef<const std::string> dexes,
                   const std::function<void(MethodReference)>& method_visitor,
@@ -198,8 +201,7 @@
   // Get the dex files from a PathClassLoader or DelegateLastClassLoader.
   // This only looks into the current class loader and does not recurse into the parents.
   std::vector<const DexFile*> GetDexFiles(jobject jclass_loader);
-  std::vector<const DexFile*> GetDexFiles(ScopedObjectAccess& soa,
-                                          Handle<mirror::ClassLoader> class_loader)
+  std::vector<const DexFile*> GetDexFiles(Thread* self, Handle<mirror::ClassLoader> class_loader)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Get the first dex file from a PathClassLoader. Will abort if it is null.
@@ -221,7 +223,7 @@
   static std::string GetImageLocation();
   static std::string GetSystemImageFile();
 
-  static void EnterTransactionMode();
+  static void EnterTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
   static void ExitTransactionMode();
   static void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
   static bool IsTransactionAborted();
@@ -298,14 +300,8 @@
     return; \
   }
 
-#define TEST_DISABLED_FOR_STRING_COMPRESSION() \
-  if (mirror::kUseStringCompression) { \
-    printf("WARNING: TEST DISABLED FOR STRING COMPRESSION\n"); \
-    return; \
-  }
-
 #define TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS() \
-  if (!kEmitCompilerReadBarrier || !kUseBakerReadBarrier) { \
+  if (!gUseReadBarrier || !kUseBakerReadBarrier) { \
     printf("WARNING: TEST DISABLED FOR GC WITHOUT BAKER READ BARRIER\n"); \
     return; \
   }
@@ -317,7 +313,7 @@
   }
 
 #define TEST_DISABLED_FOR_MEMORY_TOOL_WITH_HEAP_POISONING_WITHOUT_READ_BARRIERS() \
-  if (kRunningOnMemoryTool && kPoisonHeapReferences && !kEmitCompilerReadBarrier) { \
+  if (kRunningOnMemoryTool && kPoisonHeapReferences && !gUseReadBarrier) { \
     printf("WARNING: TEST DISABLED FOR MEMORY TOOL WITH HEAP POISONING WITHOUT READ BARRIERS\n"); \
     return; \
   }
diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc
index 17a0a8a..5182689 100644
--- a/runtime/common_throws.cc
+++ b/runtime/common_throws.cc
@@ -29,14 +29,14 @@
 #include "dex/dex_file-inl.h"
 #include "dex/dex_instruction-inl.h"
 #include "dex/invoke_type.h"
-#include "mirror/class-inl.h"
+#include "mirror/class-alloc-inl.h"
 #include "mirror/method_type.h"
 #include "mirror/object-inl.h"
 #include "mirror/object_array-inl.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "obj_ptr-inl.h"
 #include "thread.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -152,7 +152,6 @@
 // ClassCastException
 
 void ThrowClassCastException(ObjPtr<mirror::Class> dest_type, ObjPtr<mirror::Class> src_type) {
-  DumpB77342775DebugData(dest_type, src_type);
   ThrowException("Ljava/lang/ClassCastException;", nullptr,
                  StringPrintf("%s cannot be cast to %s",
                               mirror::Class::PrettyDescriptor(src_type).c_str(),
@@ -238,6 +237,19 @@
   va_end(args);
 }
 
+void ThrowIllegalAccessErrorForImplementingMethod(ObjPtr<mirror::Class> klass,
+                                                  ArtMethod* implementation_method,
+                                                  ArtMethod* interface_method)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  DCHECK(!implementation_method->IsAbstract());
+  DCHECK(!implementation_method->IsPublic());
+  ThrowIllegalAccessError(
+      klass,
+      "Method '%s' implementing interface method '%s' is not public",
+      implementation_method->PrettyMethod().c_str(),
+      interface_method->PrettyMethod().c_str());
+}
+
 // IllegalAccessException
 
 void ThrowIllegalAccessException(const char* msg) {
@@ -281,7 +293,6 @@
       << "' does not implement interface '"
       << mirror::Class::PrettyDescriptor(interface_method->GetDeclaringClass())
       << "' in call to '" << ArtMethod::PrettyMethod(interface_method) << "'";
-  DumpB77342775DebugData(interface_method->GetDeclaringClass(), this_object->GetClass());
   ThrowException("Ljava/lang/IncompatibleClassChangeError;",
                  referrer != nullptr ? referrer->GetDeclaringClass() : nullptr,
                  msg.str().c_str());
@@ -437,7 +448,7 @@
 }
 
 static bool IsValidReadBarrierImplicitCheck(uintptr_t addr) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   uint32_t monitor_offset = mirror::Object::MonitorOffset().Uint32Value();
   if (kUseBakerReadBarrier &&
       (kRuntimeISA == InstructionSet::kX86 || kRuntimeISA == InstructionSet::kX86_64)) {
@@ -472,7 +483,7 @@
     }
 
     case Instruction::IGET_OBJECT:
-      if (kEmitCompilerReadBarrier && IsValidReadBarrierImplicitCheck(addr)) {
+      if (gUseReadBarrier && IsValidReadBarrierImplicitCheck(addr)) {
         return true;
       }
       FALLTHROUGH_INTENDED;
@@ -496,7 +507,7 @@
     }
 
     case Instruction::AGET_OBJECT:
-      if (kEmitCompilerReadBarrier && IsValidReadBarrierImplicitCheck(addr)) {
+      if (gUseReadBarrier && IsValidReadBarrierImplicitCheck(addr)) {
         return true;
       }
       FALLTHROUGH_INTENDED;
@@ -690,20 +701,25 @@
   }
 
   self->SetStackEndForStackOverflow();  // Allow space on the stack for constructor to execute.
-  JNIEnvExt* env = self->GetJniEnv();
-  std::string msg("stack size ");
-  msg += PrettySize(self->GetStackSize());
 
   // Avoid running Java code for exception initialization.
   // TODO: Checks to make this a bit less brittle.
   //
-  // Note: this lambda ensures that the destruction of the ScopedLocalRefs will run in the extended
-  //       stack, which is important for modes with larger stack sizes (e.g., ASAN). Using a lambda
-  //       instead of a block simplifies the control flow.
-  auto create_and_throw = [&]() REQUIRES_SHARED(Locks::mutator_lock_) {
+  // Note: This lambda is used to make sure the `StackOverflowError` intitialization code
+  //       does not increase the frame size of `ThrowStackOverflowError()` itself. It runs
+  //       with its own frame in the extended stack, which is especially important for modes
+  //       with larger stack sizes (e.g., ASAN).
+  auto create_and_throw = [self]() REQUIRES_SHARED(Locks::mutator_lock_) NO_INLINE {
+    std::string msg("stack size ");
+    msg += PrettySize(self->GetStackSize());
+
+    ScopedObjectAccessUnchecked soa(self);
+    StackHandleScope<1u> hs(self);
+
     // Allocate an uninitialized object.
-    ScopedLocalRef<jobject> exc(env,
-                                env->AllocObject(WellKnownClasses::java_lang_StackOverflowError));
+    DCHECK(WellKnownClasses::java_lang_StackOverflowError->IsInitialized());
+    Handle<mirror::Object> exc = hs.NewHandle(
+        WellKnownClasses::java_lang_StackOverflowError->AllocObject(self));
     if (exc == nullptr) {
       LOG(WARNING) << "Could not allocate StackOverflowError object.";
       return;
@@ -722,53 +738,54 @@
     //   fillInStackTrace();
 
     // detailMessage.
-    // TODO: Use String::FromModifiedUTF...?
-    ScopedLocalRef<jstring> s(env, env->NewStringUTF(msg.c_str()));
-    if (s == nullptr) {
-      LOG(WARNING) << "Could not throw new StackOverflowError because JNI NewStringUTF failed.";
-      return;
+    {
+      ObjPtr<mirror::String> s = mirror::String::AllocFromModifiedUtf8(self, msg.c_str());
+      if (s == nullptr) {
+        LOG(WARNING) << "Could not throw new StackOverflowError because message allocation failed.";
+        return;
+      }
+      WellKnownClasses::java_lang_Throwable_detailMessage
+          ->SetObject</*kTransactionActive=*/ false>(exc.Get(), s);
     }
 
-    env->SetObjectField(exc.get(), WellKnownClasses::java_lang_Throwable_detailMessage, s.get());
-
     // cause.
-    env->SetObjectField(exc.get(), WellKnownClasses::java_lang_Throwable_cause, exc.get());
+    WellKnownClasses::java_lang_Throwable_cause
+        ->SetObject</*kTransactionActive=*/ false>(exc.Get(), exc.Get());
 
     // suppressedExceptions.
-    ScopedLocalRef<jobject> emptylist(env, env->GetStaticObjectField(
-        WellKnownClasses::java_util_Collections,
-        WellKnownClasses::java_util_Collections_EMPTY_LIST));
-    CHECK(emptylist != nullptr);
-    env->SetObjectField(exc.get(),
-                        WellKnownClasses::java_lang_Throwable_suppressedExceptions,
-                        emptylist.get());
+    {
+      ObjPtr<mirror::Class> j_u_c = WellKnownClasses::java_util_Collections.Get();
+      DCHECK(j_u_c->IsInitialized());
+      ObjPtr<mirror::Object> empty_list =
+          WellKnownClasses::java_util_Collections_EMPTY_LIST->GetObject(j_u_c);
+      CHECK(empty_list != nullptr);
+      WellKnownClasses::java_lang_Throwable_suppressedExceptions
+          ->SetObject</*kTransactionActive=*/ false>(exc.Get(), empty_list);
+    }
 
     // stackState is set as result of fillInStackTrace. fillInStackTrace calls
     // nativeFillInStackTrace.
-    ScopedLocalRef<jobject> stack_state_val(env, nullptr);
-    {
-      ScopedObjectAccessUnchecked soa(env);  // TODO: Is this necessary?
-      stack_state_val.reset(soa.Self()->CreateInternalStackTrace(soa));
-    }
+    ObjPtr<mirror::Object> stack_state_val =
+        soa.Decode<mirror::Object>(self->CreateInternalStackTrace(soa));
     if (stack_state_val != nullptr) {
-      env->SetObjectField(exc.get(),
-                          WellKnownClasses::java_lang_Throwable_stackState,
-                          stack_state_val.get());
+      WellKnownClasses::java_lang_Throwable_stackState
+          ->SetObject</*kTransactionActive=*/ false>(exc.Get(), stack_state_val);
 
       // stackTrace.
-      ScopedLocalRef<jobject> stack_trace_elem(env, env->GetStaticObjectField(
-          WellKnownClasses::libcore_util_EmptyArray,
-          WellKnownClasses::libcore_util_EmptyArray_STACK_TRACE_ELEMENT));
-      env->SetObjectField(exc.get(),
-                          WellKnownClasses::java_lang_Throwable_stackTrace,
-                          stack_trace_elem.get());
+      ObjPtr<mirror::Class> l_u_ea = WellKnownClasses::libcore_util_EmptyArray.Get();
+      DCHECK(l_u_ea->IsInitialized());
+      ObjPtr<mirror::Object> empty_ste =
+          WellKnownClasses::libcore_util_EmptyArray_STACK_TRACE_ELEMENT->GetObject(l_u_ea);
+      CHECK(empty_ste != nullptr);
+      WellKnownClasses::java_lang_Throwable_stackTrace
+          ->SetObject</*kTransactionActive=*/ false>(exc.Get(), empty_ste);
     } else {
       LOG(WARNING) << "Could not create stack trace.";
       // Note: we'll create an exception without stack state, which is valid.
     }
 
     // Throw the exception.
-    self->SetException(self->DecodeJObject(exc.get())->AsThrowable());
+    self->SetException(exc->AsThrowable());
   };
   create_and_throw();
   CHECK(self->IsExceptionPending());
diff --git a/runtime/common_throws.h b/runtime/common_throws.h
index 843c455..d9620df 100644
--- a/runtime/common_throws.h
+++ b/runtime/common_throws.h
@@ -111,6 +111,11 @@
     __attribute__((__format__(__printf__, 2, 3)))
     REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
 
+void ThrowIllegalAccessErrorForImplementingMethod(ObjPtr<mirror::Class> klass,
+                                                  ArtMethod* implementation_method,
+                                                  ArtMethod* interface_method)
+    REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
+
 // IllegalAccessException
 
 void ThrowIllegalAccessException(const char* msg)
diff --git a/runtime/compiler_callbacks.h b/runtime/compiler_callbacks.h
index c71d4ac..f76ee66 100644
--- a/runtime/compiler_callbacks.h
+++ b/runtime/compiler_callbacks.h
@@ -48,6 +48,7 @@
   virtual ~CompilerCallbacks() { }
 
   virtual void AddUncompilableMethod(MethodReference ref) = 0;
+  virtual void AddUncompilableClass(ClassReference ref) = 0;
   virtual void ClassRejected(ClassReference ref) = 0;
 
   virtual verifier::VerifierDeps* GetVerifierDeps() const = 0;
diff --git a/runtime/debug_print.cc b/runtime/debug_print.cc
index cde4d86..fd9e050 100644
--- a/runtime/debug_print.cc
+++ b/runtime/debug_print.cc
@@ -29,7 +29,7 @@
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-current-inl.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -63,12 +63,10 @@
 std::string DescribeLoaders(ObjPtr<mirror::ClassLoader> loader, const char* class_descriptor) {
   std::ostringstream oss;
   uint32_t hash = ComputeModifiedUtf8Hash(class_descriptor);
-  ObjPtr<mirror::Class> path_class_loader =
-      WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_PathClassLoader);
-  ObjPtr<mirror::Class> dex_class_loader =
-      WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_DexClassLoader);
+  ObjPtr<mirror::Class> path_class_loader = WellKnownClasses::dalvik_system_PathClassLoader.Get();
+  ObjPtr<mirror::Class> dex_class_loader = WellKnownClasses::dalvik_system_DexClassLoader.Get();
   ObjPtr<mirror::Class> delegate_last_class_loader =
-      WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_DelegateLastClassLoader);
+      WellKnownClasses::dalvik_system_DelegateLastClassLoader.Get();
 
   // Print the class loader chain.
   bool found_class = false;
@@ -97,13 +95,13 @@
         loader->GetClass() == dex_class_loader ||
         loader->GetClass() == delegate_last_class_loader) {
       oss << "(";
-      ScopedObjectAccessUnchecked soa(Thread::Current());
-      StackHandleScope<1> hs(soa.Self());
+      Thread* self = Thread::Current();
+      StackHandleScope<1> hs(self);
       Handle<mirror::ClassLoader> handle(hs.NewHandle(loader));
       const char* path_separator = "";
       const DexFile* base_dex_file = nullptr;
       VisitClassLoaderDexFiles(
-          soa,
+          self,
           handle,
           [&](const DexFile* dex_file) {
               oss << path_separator;
@@ -129,60 +127,4 @@
   return oss.str();
 }
 
-void DumpB77342775DebugData(ObjPtr<mirror::Class> target_class, ObjPtr<mirror::Class> src_class) {
-  std::string target_descriptor_storage;
-  const char* target_descriptor = target_class->GetDescriptor(&target_descriptor_storage);
-  const char kCheckedPrefix[] = "Lorg/apache/http/";
-  // Avoid spam for other packages. (That spam would break some ART run-tests for example.)
-  if (strncmp(target_descriptor, kCheckedPrefix, sizeof(kCheckedPrefix) - 1) != 0) {
-    return;
-  }
-  auto matcher = [target_descriptor, target_class](ObjPtr<mirror::Class> klass)
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    if (klass->DescriptorEquals(target_descriptor)) {
-      LOG(ERROR) << "    descriptor match in "
-          << DescribeLoaders(klass->GetClassLoader(), target_descriptor)
-          << " match? " << std::boolalpha << (klass == target_class);
-    }
-  };
-
-  std::string source_descriptor_storage;
-  const char* source_descriptor = src_class->GetDescriptor(&source_descriptor_storage);
-
-  LOG(ERROR) << "Maybe bug 77342775, looking for " << target_descriptor
-      << " " << target_class.Ptr() << "[" << DescribeSpace(target_class) << "]"
-      << " defined in " << target_class->GetDexFile().GetLocation()
-      << "/" << static_cast<const void*>(&target_class->GetDexFile())
-      << "\n  with loader: " << DescribeLoaders(target_class->GetClassLoader(), target_descriptor);
-  if (target_class->IsInterface()) {
-    ObjPtr<mirror::IfTable> iftable = src_class->GetIfTable();
-    CHECK(iftable != nullptr);
-    size_t ifcount = iftable->Count();
-    LOG(ERROR) << "  in interface table for " << source_descriptor
-        << " " << src_class.Ptr() << "[" << DescribeSpace(src_class) << "]"
-        << " defined in " << src_class->GetDexFile().GetLocation()
-        << "/" << static_cast<const void*>(&src_class->GetDexFile())
-        << " ifcount=" << ifcount
-        << "\n  with loader " << DescribeLoaders(src_class->GetClassLoader(), source_descriptor);
-    for (size_t i = 0; i != ifcount; ++i) {
-      ObjPtr<mirror::Class> iface = iftable->GetInterface(i);
-      CHECK(iface != nullptr);
-      LOG(ERROR) << "  iface #" << i << ": " << iface->PrettyDescriptor();
-      matcher(iface);
-    }
-  } else {
-    LOG(ERROR) << "  in superclass chain for " << source_descriptor
-        << " " << src_class.Ptr() << "[" << DescribeSpace(src_class) << "]"
-        << " defined in " << src_class->GetDexFile().GetLocation()
-        << "/" << static_cast<const void*>(&src_class->GetDexFile())
-        << "\n  with loader " << DescribeLoaders(src_class->GetClassLoader(), source_descriptor);
-    for (ObjPtr<mirror::Class> klass = src_class;
-         klass != nullptr;
-         klass = klass->GetSuperClass()) {
-      LOG(ERROR) << "  - " << klass->PrettyDescriptor();
-      matcher(klass);
-    }
-  }
-}
-
 }  // namespace art
diff --git a/runtime/debug_print.h b/runtime/debug_print.h
index e2990d4..7c68402 100644
--- a/runtime/debug_print.h
+++ b/runtime/debug_print.h
@@ -29,9 +29,6 @@
 std::string DescribeLoaders(ObjPtr<mirror::ClassLoader> loader, const char* class_descriptor)
     REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
 
-void DumpB77342775DebugData(ObjPtr<mirror::Class> target_class, ObjPtr<mirror::Class> src_class)
-    REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
-
 }  // namespace art
 
 #endif  // ART_RUNTIME_DEBUG_PRINT_H_
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index af36531..a7b818e 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -187,33 +187,28 @@
                          const ArrayRef<const jbyte>& data,
                          /*out*/uint32_t* out_type,
                          /*out*/std::vector<uint8_t>* out_data) {
-  ScopedLocalRef<jbyteArray> dataArray(env, env->NewByteArray(data.size()));
-  if (dataArray.get() == nullptr) {
+  ScopedObjectAccess soa(env);
+  StackHandleScope<1u> hs(soa.Self());
+  Handle<mirror::ByteArray> data_array =
+      hs.NewHandle(mirror::ByteArray::Alloc(soa.Self(), data.size()));
+  if (data_array == nullptr) {
     LOG(WARNING) << "byte[] allocation failed: " << data.size();
     env->ExceptionClear();
     return false;
   }
-  env->SetByteArrayRegion(dataArray.get(),
-                          0,
-                          data.size(),
-                          reinterpret_cast<const jbyte*>(data.data()));
+  memcpy(data_array->GetData(), data.data(), data.size());
   // Call "private static Chunk dispatch(int type, byte[] data, int offset, int length)".
-  ScopedLocalRef<jobject> chunk(
-      env,
-      env->CallStaticObjectMethod(
-          WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer,
-          WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_dispatch,
-          type, dataArray.get(), 0, data.size()));
-  if (env->ExceptionCheck()) {
-    Thread* self = Thread::Current();
-    ScopedObjectAccess soa(self);
+  ArtMethod* dispatch = WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_dispatch;
+  ObjPtr<mirror::Object> chunk = dispatch->InvokeStatic<'L', 'I', 'L', 'I', 'I'>(
+      soa.Self(), type, data_array.Get(), 0, static_cast<jint>(data.size()));
+  if (soa.Self()->IsExceptionPending()) {
     LOG(INFO) << StringPrintf("Exception thrown by dispatcher for 0x%08x", type) << std::endl
-              << self->GetException()->Dump();
-    self->ClearException();
+              << soa.Self()->GetException()->Dump();
+    soa.Self()->ClearException();
     return false;
   }
 
-  if (chunk.get() == nullptr) {
+  if (chunk == nullptr) {
     return false;
   }
 
@@ -229,38 +224,33 @@
    *
    * So we're pretty much stuck with copying data around multiple times.
    */
-  ScopedLocalRef<jbyteArray> replyData(
-      env,
-      reinterpret_cast<jbyteArray>(
-          env->GetObjectField(
-              chunk.get(), WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_data)));
-  jint offset = env->GetIntField(chunk.get(),
-                                 WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_offset);
-  jint length = env->GetIntField(chunk.get(),
-                                 WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_length);
-  *out_type = env->GetIntField(chunk.get(),
-                               WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_type);
+  ObjPtr<mirror::ByteArray> reply_data = ObjPtr<mirror::ByteArray>::DownCast(
+      WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_data->GetObject(chunk));
+  jint offset = WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_offset->GetInt(chunk);
+  jint length = WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_length->GetInt(chunk);
+  *out_type = WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_type->GetInt(chunk);
 
   VLOG(jdwp) << StringPrintf("DDM reply: type=0x%08x data=%p offset=%d length=%d",
                              type,
-                             replyData.get(),
+                             reply_data.Ptr(),
                              offset,
                              length);
-  out_data->resize(length);
-  env->GetByteArrayRegion(replyData.get(),
-                          offset,
-                          length,
-                          reinterpret_cast<jbyte*>(out_data->data()));
 
-  if (env->ExceptionCheck()) {
-    Thread* self = Thread::Current();
-    ScopedObjectAccess soa(self);
-    LOG(INFO) << StringPrintf("Exception thrown when reading response data from dispatcher 0x%08x",
-                              type) << std::endl << self->GetException()->Dump();
-    self->ClearException();
+  if (reply_data == nullptr) {
+    LOG(INFO) << "Null reply data";
     return false;
   }
 
+  jint reply_length = reply_data->GetLength();
+  if (offset < 0 || offset > reply_length || length < 0 || length > reply_length - offset) {
+    LOG(INFO) << "Invalid reply data range: offset=" << offset << ", length=" << length
+              << " reply_length=" << reply_length;
+    return false;
+  }
+
+  out_data->resize(length);
+  memcpy(out_data->data(), reply_data->GetData() + offset, length);
+
   return true;
 }
 
@@ -273,12 +263,13 @@
     /* try anyway? */
   }
 
+  // TODO: Can we really get here while not `Runnable`? If not, we do not need the `soa`.
+  ScopedObjectAccessUnchecked soa(self);
   JNIEnv* env = self->GetJniEnv();
   jint event = connect ? 1 /*DdmServer.CONNECTED*/ : 2 /*DdmServer.DISCONNECTED*/;
-  env->CallStaticVoidMethod(WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer,
-                            WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_broadcast,
-                            event);
-  if (env->ExceptionCheck()) {
+  ArtMethod* broadcast = WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
+  broadcast->InvokeStatic<'V', 'I'>(self, event);
+  if (self->IsExceptionPending()) {
     LOG(ERROR) << "DdmServer.broadcast " << event << " failed";
     env->ExceptionDescribe();
     env->ExceptionClear();
diff --git a/runtime/deoptimization_kind.h b/runtime/deoptimization_kind.h
index c2e6a65..65a1cf1 100644
--- a/runtime/deoptimization_kind.h
+++ b/runtime/deoptimization_kind.h
@@ -56,9 +56,10 @@
 // for functions that are already on stack. The value in the slot specifies the
 // reason we need to deoptimize.
 enum class DeoptimizeFlagValue: uint8_t {
-  kCHA = 0b01,
-  kDebug = 0b10,
-  kAll = kCHA | kDebug
+  kCHA = 0b001,
+  kForceDeoptForRedefinition = 0b010,
+  kCheckCallerForDeopt = 0b100,
+  kAll = kCHA | kForceDeoptForRedefinition | kCheckCallerForDeopt
 };
 
 }  // namespace art
diff --git a/runtime/dex/dex_file_annotations.cc b/runtime/dex/dex_file_annotations.cc
index 5a409f0..7c1dd1e 100644
--- a/runtime/dex/dex_file_annotations.cc
+++ b/runtime/dex/dex_file_annotations.cc
@@ -18,10 +18,10 @@
 
 #include <stdlib.h>
 
+#include "android-base/macros.h"
 #include "android-base/stringprintf.h"
-
 #include "art_field-inl.h"
-#include "art_method-inl.h"
+#include "art_method-alloc-inl.h"
 #include "base/sdk_version.h"
 #include "class_linker-inl.h"
 #include "class_root-inl.h"
@@ -202,6 +202,10 @@
   return result;
 }
 
+inline static void SkipEncodedValueHeaderByte(const uint8_t** annotation_ptr) {
+  (*annotation_ptr)++;
+}
+
 bool SkipAnnotationValue(const DexFile& dex_file, const uint8_t** annotation_ptr)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   const uint8_t* annotation = *annotation_ptr;
@@ -356,7 +360,6 @@
   uint32_t size = DecodeUnsignedLeb128(annotation);
 
   Thread* self = Thread::Current();
-  ScopedObjectAccessUnchecked soa(self);
   StackHandleScope<4> hs(self);
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   Handle<mirror::Class> annotation_class(hs.NewHandle(
@@ -371,10 +374,8 @@
     return nullptr;
   }
 
-  ObjPtr<mirror::Class> annotation_member_class =
-      soa.Decode<mirror::Class>(WellKnownClasses::libcore_reflect_AnnotationMember);
   ObjPtr<mirror::Class> annotation_member_array_class =
-      class_linker->FindArrayClass(self, annotation_member_class);
+      WellKnownClasses::ToClass(WellKnownClasses::libcore_reflect_AnnotationMember__array);
   if (annotation_member_array_class == nullptr) {
     return nullptr;
   }
@@ -397,18 +398,16 @@
     h_element_array->SetWithoutChecks<false>(i, new_member);
   }
 
-  JValue result;
   ArtMethod* create_annotation_method =
-      jni::DecodeArtMethod(WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation);
-  uint32_t args[2] = { static_cast<uint32_t>(reinterpret_cast<uintptr_t>(annotation_class.Get())),
-                       static_cast<uint32_t>(reinterpret_cast<uintptr_t>(h_element_array.Get())) };
-  create_annotation_method->Invoke(self, args, sizeof(args), &result, "LLL");
+      WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation;
+  ObjPtr<mirror::Object> result = create_annotation_method->InvokeStatic<'L', 'L', 'L'>(
+      self, annotation_class.Get(), h_element_array.Get());
   if (self->IsExceptionPending()) {
     LOG(INFO) << "Exception in AnnotationFactory.createAnnotation";
     return nullptr;
   }
 
-  return result.GetL();
+  return result;
 }
 
 template <bool kTransactionActive>
@@ -704,8 +703,6 @@
   StackHandleScope<5> hs(self);
   uint32_t element_name_index = DecodeUnsignedLeb128(annotation);
   const char* name = dex_file.StringDataByIdx(dex::StringIndex(element_name_index));
-  Handle<mirror::String> string_name(
-      hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, name)));
 
   PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
   ArtMethod* annotation_method =
@@ -713,7 +710,19 @@
   if (annotation_method == nullptr) {
     return nullptr;
   }
-  Handle<mirror::Class> method_return(hs.NewHandle(annotation_method->ResolveReturnType()));
+
+  Handle<mirror::String> string_name =
+      hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, name));
+  if (UNLIKELY(string_name == nullptr)) {
+    LOG(ERROR) << "Failed to allocate name for annotation member";
+    return nullptr;
+  }
+
+  Handle<mirror::Class> method_return = hs.NewHandle(annotation_method->ResolveReturnType());
+  if (UNLIKELY(method_return == nullptr)) {
+    LOG(ERROR) << "Failed to resolve method return type for annotation member";
+    return nullptr;
+  }
 
   DexFile::AnnotationValue annotation_value;
   if (!ProcessAnnotationValue<false>(klass,
@@ -721,37 +730,26 @@
                                      &annotation_value,
                                      method_return,
                                      DexFile::kAllObjects)) {
+    // TODO: Logging the error breaks run-test 005-annotations.
+    // LOG(ERROR) << "Failed to process annotation value for annotation member";
     return nullptr;
   }
-  Handle<mirror::Object> value_object(hs.NewHandle(annotation_value.value_.GetL()));
+  Handle<mirror::Object> value_object = hs.NewHandle(annotation_value.value_.GetL());
 
-  ObjPtr<mirror::Class> annotation_member_class =
-      WellKnownClasses::ToClass(WellKnownClasses::libcore_reflect_AnnotationMember);
-  Handle<mirror::Object> new_member(hs.NewHandle(annotation_member_class->AllocObject(self)));
-  ObjPtr<mirror::Method> method_obj_ptr = (pointer_size == PointerSize::k64)
+  Handle<mirror::Method> method_object = hs.NewHandle((pointer_size == PointerSize::k64)
       ? mirror::Method::CreateFromArtMethod<PointerSize::k64>(self, annotation_method)
-      : mirror::Method::CreateFromArtMethod<PointerSize::k32>(self, annotation_method);
-  Handle<mirror::Method> method_object(hs.NewHandle(method_obj_ptr));
-
-  if (new_member == nullptr || string_name == nullptr ||
-      method_object == nullptr || method_return == nullptr) {
-    LOG(ERROR) << StringPrintf("Failed creating annotation element (m=%p n=%p a=%p r=%p",
-        new_member.Get(), string_name.Get(), method_object.Get(), method_return.Get());
+      : mirror::Method::CreateFromArtMethod<PointerSize::k32>(self, annotation_method));
+  if (UNLIKELY(method_object == nullptr)) {
+    LOG(ERROR) << "Failed to create method object for annotation member";
     return nullptr;
   }
 
-  JValue result;
-  ArtMethod* annotation_member_init =
-      jni::DecodeArtMethod(WellKnownClasses::libcore_reflect_AnnotationMember_init);
-  uint32_t args[5] = { static_cast<uint32_t>(reinterpret_cast<uintptr_t>(new_member.Get())),
-                       static_cast<uint32_t>(reinterpret_cast<uintptr_t>(string_name.Get())),
-                       static_cast<uint32_t>(reinterpret_cast<uintptr_t>(value_object.Get())),
-                       static_cast<uint32_t>(reinterpret_cast<uintptr_t>(method_return.Get())),
-                       static_cast<uint32_t>(reinterpret_cast<uintptr_t>(method_object.Get()))
-  };
-  annotation_member_init->Invoke(self, args, sizeof(args), &result, "VLLLL");
-  if (self->IsExceptionPending()) {
-    LOG(INFO) << "Exception in AnnotationMember.<init>";
+  Handle<mirror::Object> new_member =
+      WellKnownClasses::libcore_reflect_AnnotationMember_init->NewObject<'L', 'L', 'L', 'L'>(
+          hs, self, string_name, value_object, method_return, method_object);
+  if (new_member == nullptr) {
+    DCHECK(self->IsExceptionPending());
+    LOG(ERROR) << "Failed to create annotation member";
     return nullptr;
   }
 
@@ -841,6 +839,38 @@
   return annotation_value.value_.GetL();
 }
 
+template<typename T>
+static inline ObjPtr<mirror::ObjectArray<T>> GetAnnotationArrayValue(
+                                     Handle<mirror::Class> klass,
+                                     const char* annotation_name,
+                                     const char* value_name)
+            REQUIRES_SHARED(Locks::mutator_lock_) {
+  ClassData data(klass);
+  const AnnotationSetItem* annotation_set = FindAnnotationSetForClass(data);
+  if (annotation_set == nullptr) {
+    return nullptr;
+  }
+  const AnnotationItem* annotation_item =
+      SearchAnnotationSet(data.GetDexFile(), annotation_set, annotation_name,
+                          DexFile::kDexVisibilitySystem);
+  if (annotation_item == nullptr) {
+    return nullptr;
+  }
+  StackHandleScope<1> hs(Thread::Current());
+  Handle<mirror::Class> class_array_class =
+      hs.NewHandle(GetClassRoot<mirror::ObjectArray<T>>());
+  DCHECK(class_array_class != nullptr);
+  ObjPtr<mirror::Object> obj = GetAnnotationValue(data,
+                                                  annotation_item,
+                                                  value_name,
+                                                  class_array_class,
+                                                  DexFile::kDexAnnotationArray);
+  if (obj == nullptr) {
+    return nullptr;
+  }
+  return obj->AsObjectArray<T>();
+}
+
 static ObjPtr<mirror::ObjectArray<mirror::String>> GetSignatureValue(
     const ClassData& klass,
     const AnnotationSetItem* annotation_set)
@@ -895,10 +925,9 @@
     REQUIRES_SHARED(Locks::mutator_lock_) {
   const DexFile& dex_file = klass.GetDexFile();
   Thread* self = Thread::Current();
-  ScopedObjectAccessUnchecked soa(self);
   StackHandleScope<2> hs(self);
   Handle<mirror::Class> annotation_array_class(hs.NewHandle(
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_annotation_Annotation__array)));
+      WellKnownClasses::ToClass(WellKnownClasses::java_lang_annotation_Annotation__array)));
   if (annotation_set == nullptr) {
     return mirror::ObjectArray<mirror::Object>::Alloc(self, annotation_array_class.Get(), 0);
   }
@@ -953,10 +982,9 @@
     REQUIRES_SHARED(Locks::mutator_lock_) {
   const DexFile& dex_file = klass.GetDexFile();
   Thread* self = Thread::Current();
-  ScopedObjectAccessUnchecked soa(self);
   StackHandleScope<1> hs(self);
   ObjPtr<mirror::Class> annotation_array_class =
-      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_annotation_Annotation__array);
+      WellKnownClasses::ToClass(WellKnownClasses::java_lang_annotation_Annotation__array);
   ObjPtr<mirror::Class> annotation_array_array_class =
       Runtime::Current()->GetClassLinker()->FindArrayClass(self, annotation_array_class);
   if (annotation_array_array_class == nullptr) {
@@ -1310,6 +1338,20 @@
       WellKnownClasses::dalvik_annotation_optimization_NeverCompile);
 }
 
+bool MethodIsNeverInline(const DexFile& dex_file,
+                         const dex::ClassDef& class_def,
+                         uint32_t method_index) {
+  const dex::AnnotationSetItem* annotation_set =
+      FindAnnotationSetForMethod(dex_file, class_def, method_index);
+  if (annotation_set == nullptr) {
+    return false;
+  }
+  return IsMethodBuildAnnotationPresent(
+      dex_file,
+      *annotation_set,
+      "Ldalvik/annotation/optimization/NeverInline;",
+      WellKnownClasses::dalvik_annotation_optimization_NeverInline);
+}
 
 bool FieldIsReachabilitySensitive(const DexFile& dex_file,
                                   const dex::ClassDef& class_def,
@@ -1478,28 +1520,9 @@
 }
 
 ObjPtr<mirror::ObjectArray<mirror::Class>> GetDeclaredClasses(Handle<mirror::Class> klass) {
-  ClassData data(klass);
-  const AnnotationSetItem* annotation_set = FindAnnotationSetForClass(data);
-  if (annotation_set == nullptr) {
-    return nullptr;
-  }
-  const AnnotationItem* annotation_item =
-      SearchAnnotationSet(data.GetDexFile(), annotation_set, "Ldalvik/annotation/MemberClasses;",
-                          DexFile::kDexVisibilitySystem);
-  if (annotation_item == nullptr) {
-    return nullptr;
-  }
-  StackHandleScope<1> hs(Thread::Current());
-  Handle<mirror::Class> class_array_class =
-      hs.NewHandle(GetClassRoot<mirror::ObjectArray<mirror::Class>>());
-  DCHECK(class_array_class != nullptr);
-  ObjPtr<mirror::Object> obj =
-      GetAnnotationValue(data, annotation_item, "value", class_array_class,
-                         DexFile::kDexAnnotationArray);
-  if (obj == nullptr) {
-    return nullptr;
-  }
-  return obj->AsObjectArray<mirror::Class>();
+  return GetAnnotationArrayValue<mirror::Class>(klass,
+                                                "Ldalvik/annotation/MemberClasses;",
+                                                "value");
 }
 
 ObjPtr<mirror::Class> GetDeclaringClass(Handle<mirror::Class> klass) {
@@ -1714,6 +1737,83 @@
   return data.GetDexFile().StringDataByIdx(index);
 }
 
+ObjPtr<mirror::Class> GetNestHost(Handle<mirror::Class> klass) {
+  ClassData data(klass);
+  const AnnotationSetItem* annotation_set = FindAnnotationSetForClass(data);
+  if (annotation_set == nullptr) {
+    return nullptr;
+  }
+  const AnnotationItem* annotation_item =
+      SearchAnnotationSet(data.GetDexFile(), annotation_set, "Ldalvik/annotation/NestHost;",
+                          DexFile::kDexVisibilitySystem);
+  if (annotation_item == nullptr) {
+    return nullptr;
+  }
+  ObjPtr<mirror::Object> obj = GetAnnotationValue(data,
+                                                  annotation_item,
+                                                  "host",
+                                                  ScopedNullHandle<mirror::Class>(),
+                                                  DexFile::kDexAnnotationType);
+  if (obj == nullptr) {
+    return nullptr;
+  }
+  if (!obj->IsClass()) {
+    // TypeNotPresentException, throw the NoClassDefFoundError.
+    Thread::Current()->SetException(obj->AsThrowable()->GetCause());
+    return nullptr;
+  }
+  return obj->AsClass();
+}
+
+ObjPtr<mirror::ObjectArray<mirror::Class>> GetNestMembers(Handle<mirror::Class> klass) {
+  return GetAnnotationArrayValue<mirror::Class>(klass,
+                                                "Ldalvik/annotation/NestMembers;",
+                                                "classes");
+}
+
+ObjPtr<mirror::ObjectArray<mirror::Class>> GetPermittedSubclasses(Handle<mirror::Class> klass) {
+  return GetAnnotationArrayValue<mirror::Class>(klass,
+                                                "Ldalvik/annotation/PermittedSubclasses;",
+                                                "value");
+}
+
+ObjPtr<mirror::Object> getRecordAnnotationElement(Handle<mirror::Class> klass,
+                                                  Handle<mirror::Class> array_class,
+                                                  const char* element_name) {
+  ClassData data(klass);
+  const DexFile& dex_file = klass->GetDexFile();
+  const AnnotationSetItem* annotation_set = FindAnnotationSetForClass(data);
+  if (annotation_set == nullptr) {
+    return nullptr;
+  }
+  const AnnotationItem* annotation_item = SearchAnnotationSet(
+      dex_file, annotation_set, "Ldalvik/annotation/Record;", DexFile::kDexVisibilitySystem);
+  if (annotation_item == nullptr) {
+    return nullptr;
+  }
+  const uint8_t* annotation =
+      SearchEncodedAnnotation(dex_file, annotation_item->annotation_, element_name);
+  if (annotation == nullptr) {
+    return nullptr;
+  }
+  DexFile::AnnotationValue annotation_value;
+  bool result = Runtime::Current()->IsActiveTransaction()
+      ? ProcessAnnotationValue<true>(data,
+                                     &annotation,
+                                     &annotation_value,
+                                     array_class,
+                                     DexFile::kPrimitivesOrObjects)
+      : ProcessAnnotationValue<false>(data,
+                                      &annotation,
+                                      &annotation_value,
+                                      array_class,
+                                      DexFile::kPrimitivesOrObjects);
+  if (!result) {
+    return nullptr;
+  }
+  return annotation_value.value_.GetL();
+}
+
 bool IsClassAnnotationPresent(Handle<mirror::Class> klass, Handle<mirror::Class> annotation_class) {
   ClassData data(klass);
   const AnnotationSetItem* annotation_set = FindAnnotationSetForClass(data);
@@ -1776,6 +1876,132 @@
 template
 void RuntimeEncodedStaticFieldValueIterator::ReadValueToField<false>(ArtField* field) const;
 
+inline static VisitorStatus VisitElement(AnnotationVisitor* visitor,
+                                         const char* element_name,
+                                         uint8_t depth,
+                                         uint32_t element_index,
+                                         const DexFile::AnnotationValue& annotation_value)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (depth == 0) {
+    return visitor->VisitAnnotationElement(
+        element_name, annotation_value.type_, annotation_value.value_);
+  } else {
+    return visitor->VisitArrayElement(
+        depth - 1, element_index, annotation_value.type_, annotation_value.value_);
+  }
+}
+
+static VisitorStatus VisitEncodedValue(const ClassData& klass,
+                                       const DexFile& dex_file,
+                                       const uint8_t** annotation_ptr,
+                                       AnnotationVisitor* visitor,
+                                       const char* element_name,
+                                       uint8_t depth,
+                                       uint32_t element_index)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  DexFile::AnnotationValue annotation_value;
+  // kTransactionActive is safe because the result_style is kAllRaw.
+  bool is_consumed = ProcessAnnotationValue<false>(klass,
+                                                   annotation_ptr,
+                                                   &annotation_value,
+                                                   ScopedNullHandle<mirror::Class>(),
+                                                   DexFile::kAllRaw);
+
+  VisitorStatus status =
+      VisitElement(visitor, element_name, depth, element_index, annotation_value);
+  switch (annotation_value.type_) {
+    case DexFile::kDexAnnotationArray: {
+      DCHECK(!is_consumed) << " unexpected consumption of array-typed element '" << element_name
+                           << "' annotating the class " << klass.GetRealClass()->PrettyClass();
+      SkipEncodedValueHeaderByte(annotation_ptr);
+      uint32_t array_size = DecodeUnsignedLeb128(annotation_ptr);
+      uint8_t next_depth = depth + 1;
+      VisitorStatus element_status = (status == VisitorStatus::kVisitInner) ?
+                                         VisitorStatus::kVisitNext :
+                                         VisitorStatus::kVisitBreak;
+      uint32_t i = 0;
+      for (; i < array_size && element_status != VisitorStatus::kVisitBreak; ++i) {
+        element_status = VisitEncodedValue(
+            klass, dex_file, annotation_ptr, visitor, element_name, next_depth, i);
+      }
+      for (; i < array_size; ++i) {
+        SkipAnnotationValue(dex_file, annotation_ptr);
+      }
+      break;
+    }
+    case DexFile::kDexAnnotationAnnotation: {
+      DCHECK(!is_consumed) << " unexpected consumption of annotation-typed element '"
+                           << element_name << "' annotating the class "
+                           << klass.GetRealClass()->PrettyClass();
+      SkipEncodedValueHeaderByte(annotation_ptr);
+      DecodeUnsignedLeb128(annotation_ptr);  // unused type_index
+      uint32_t size = DecodeUnsignedLeb128(annotation_ptr);
+      for (; size != 0u; --size) {
+        DecodeUnsignedLeb128(annotation_ptr);  // unused element_name_index
+        SkipAnnotationValue(dex_file, annotation_ptr);
+      }
+      break;
+    }
+    default: {
+      // kDexAnnotationArray and kDexAnnotationAnnotation are the only 2 known value_types causing
+      // ProcessAnnotationValue return false. For other value_types, we shouldn't need to iterate
+      // over annotation_ptr and skip the value here.
+      DCHECK(is_consumed) << StringPrintf(
+          "consumed annotation element type 0x%02x of %s for the class %s",
+          annotation_value.type_,
+          element_name,
+          klass.GetRealClass()->PrettyClass().c_str());
+      if (UNLIKELY(!is_consumed)) {
+        SkipAnnotationValue(dex_file, annotation_ptr);
+      }
+      break;
+    }
+  }
+
+  return status;
+}
+
+void VisitClassAnnotations(Handle<mirror::Class> klass, AnnotationVisitor* visitor) {
+  ClassData data(klass);
+  const AnnotationSetItem* annotation_set = FindAnnotationSetForClass(data);
+  if (annotation_set == nullptr) {
+    return;
+  }
+
+  const DexFile& dex_file = data.GetDexFile();
+  for (uint32_t i = 0; i < annotation_set->size_; ++i) {
+    const AnnotationItem* annotation_item = dex_file.GetAnnotationItem(annotation_set, i);
+    uint8_t visibility = annotation_item->visibility_;
+    const uint8_t* annotation = annotation_item->annotation_;
+    uint32_t type_index = DecodeUnsignedLeb128(&annotation);
+    const char* annotation_descriptor = dex_file.StringByTypeIdx(dex::TypeIndex(type_index));
+    VisitorStatus status = visitor->VisitAnnotation(annotation_descriptor, visibility);
+    switch (status) {
+      case VisitorStatus::kVisitBreak:
+        return;
+      case VisitorStatus::kVisitNext:
+        continue;
+      case VisitorStatus::kVisitInner:
+        // Visit the annotation elements
+        break;
+    }
+
+    uint32_t size = DecodeUnsignedLeb128(&annotation);
+    while (size != 0) {
+      uint32_t element_name_index = DecodeUnsignedLeb128(&annotation);
+      const char* element_name =
+          dex_file.GetStringData(dex_file.GetStringId(dex::StringIndex(element_name_index)));
+
+      status = VisitEncodedValue(
+          data, dex_file, &annotation, visitor, element_name, /*depth=*/0, /*ignored*/ 0);
+      if (status == VisitorStatus::kVisitBreak) {
+        break;
+      }
+      size--;
+    }
+  }
+}
+
 }  // namespace annotations
 
 }  // namespace art
diff --git a/runtime/dex/dex_file_annotations.h b/runtime/dex/dex_file_annotations.h
index 3ef67e5..cffeb25 100644
--- a/runtime/dex/dex_file_annotations.h
+++ b/runtime/dex/dex_file_annotations.h
@@ -91,6 +91,11 @@
 bool MethodIsNeverCompile(const DexFile& dex_file,
                           const dex::ClassDef& class_def,
                           uint32_t method_index);
+// Is the method from the `dex_file` with the given `field_index`
+// annotated with @dalvik.annotation.optimization.NeverInline?
+bool MethodIsNeverInline(const DexFile& dex_file,
+                         const dex::ClassDef& class_def,
+                         uint32_t method_index);
 // Is the field from the `dex_file` with the given `field_index`
 // annotated with @dalvik.annotation.optimization.ReachabilitySensitive?
 bool FieldIsReachabilitySensitive(const DexFile& dex_file,
@@ -136,6 +141,16 @@
     Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
 const char* GetSourceDebugExtension(Handle<mirror::Class> klass)
     REQUIRES_SHARED(Locks::mutator_lock_);
+ObjPtr<mirror::Class> GetNestHost(Handle<mirror::Class> klass)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+ObjPtr<mirror::ObjectArray<mirror::Class>> GetNestMembers(Handle<mirror::Class> klass)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+ObjPtr<mirror::Object> getRecordAnnotationElement(Handle<mirror::Class> klass,
+                                                  Handle<mirror::Class> array_class,
+                                                  const char* element_name)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+ObjPtr<mirror::ObjectArray<mirror::Class>> GetPermittedSubclasses(Handle<mirror::Class> klass)
+    REQUIRES_SHARED(Locks::mutator_lock_);
 bool IsClassAnnotationPresent(Handle<mirror::Class> klass,
                               Handle<mirror::Class> annotation_class)
     REQUIRES_SHARED(Locks::mutator_lock_);
@@ -169,6 +184,26 @@
   DISALLOW_IMPLICIT_CONSTRUCTORS(RuntimeEncodedStaticFieldValueIterator);
 };
 
+enum class VisitorStatus : uint8_t { kVisitBreak, kVisitNext, kVisitInner };
+
+class AnnotationVisitor {
+ public:
+  virtual ~AnnotationVisitor() {}
+  virtual VisitorStatus VisitAnnotation(const char* annotation_descriptor, uint8_t visibility) = 0;
+  virtual VisitorStatus VisitAnnotationElement(const char* element_name,
+                                               uint8_t type,
+                                               const JValue& value) = 0;
+  virtual VisitorStatus VisitArrayElement(uint8_t depth,
+                                          uint32_t index,
+                                          uint8_t type,
+                                          const JValue& value) = 0;
+};
+
+// Visit all annotation elements and array elements without creating
+// Arrays or Objects in the managed heap.
+void VisitClassAnnotations(Handle<mirror::Class> klass, AnnotationVisitor* visitor)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
 }  // namespace annotations
 
 }  // namespace art
diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h
index e867c4f..c7febcd 100644
--- a/runtime/dex2oat_environment_test.h
+++ b/runtime/dex2oat_environment_test.h
@@ -21,8 +21,6 @@
 #include <string>
 #include <vector>
 
-#include <gtest/gtest.h>
-
 #include "base/file_utils.h"
 #include "base/os.h"
 #include "base/stl_util.h"
@@ -34,8 +32,10 @@
 #include "exec_utils.h"
 #include "gc/heap.h"
 #include "gc/space/image_space.h"
+#include "gtest/gtest.h"
 #include "oat_file_assistant.h"
 #include "runtime.h"
+#include "ziparchive/zip_writer.h"
 
 namespace art {
 
@@ -97,8 +97,6 @@
     CommonRuntimeTest::SetUp();
     Dex2oatScratchDirs::SetUp(android_data_);
 
-    const ArtDexFileLoader dex_file_loader;
-
     // Verify the environment is as we expect
     std::vector<uint32_t> checksums;
     std::vector<std::string> dex_locations;
@@ -109,10 +107,9 @@
       << "Expected dex file to be at: " << GetDexSrc1();
     ASSERT_TRUE(OS::FileExists(GetResourceOnlySrc1().c_str()))
       << "Expected stripped dex file to be at: " << GetResourceOnlySrc1();
-    ASSERT_FALSE(
-        dex_file_loader.GetMultiDexChecksums(
-            GetResourceOnlySrc1().c_str(), &checksums, &dex_locations, &error_msg))
-      << "Expected stripped dex file to be stripped: " << GetResourceOnlySrc1();
+    ASSERT_TRUE(ArtDexFileLoader::GetMultiDexChecksums(
+        GetResourceOnlySrc1().c_str(), &checksums, &dex_locations, &error_msg))
+        << "Expected stripped dex file to be stripped: " << GetResourceOnlySrc1();
     ASSERT_TRUE(OS::FileExists(GetDexSrc2().c_str()))
       << "Expected dex file to be at: " << GetDexSrc2();
 
@@ -120,21 +117,15 @@
     // GetMultiDexSrc1, but a different secondary dex checksum.
     static constexpr bool kVerifyChecksum = true;
     std::vector<std::unique_ptr<const DexFile>> multi1;
-    ASSERT_TRUE(dex_file_loader.Open(GetMultiDexSrc1().c_str(),
-                                     GetMultiDexSrc1().c_str(),
-                                     /* verify= */ true,
-                                     kVerifyChecksum,
-                                     &error_msg,
-                                     &multi1)) << error_msg;
+    ArtDexFileLoader dex_file_loader1(GetMultiDexSrc1());
+    ASSERT_TRUE(dex_file_loader1.Open(/* verify= */ true, kVerifyChecksum, &error_msg, &multi1))
+        << error_msg;
     ASSERT_GT(multi1.size(), 1u);
 
     std::vector<std::unique_ptr<const DexFile>> multi2;
-    ASSERT_TRUE(dex_file_loader.Open(GetMultiDexSrc2().c_str(),
-                                     GetMultiDexSrc2().c_str(),
-                                     /* verify= */ true,
-                                     kVerifyChecksum,
-                                     &error_msg,
-                                     &multi2)) << error_msg;
+    ArtDexFileLoader dex_file_loader2(GetMultiDexSrc2());
+    ASSERT_TRUE(dex_file_loader2.Open(/* verify= */ true, kVerifyChecksum, &error_msg, &multi2))
+        << error_msg;
     ASSERT_GT(multi2.size(), 1u);
 
     ASSERT_EQ(multi1[0]->GetLocationChecksum(), multi2[0]->GetLocationChecksum());
@@ -178,6 +169,10 @@
     return GetTestDexFileName("MultiDex");
   }
 
+  std::string GetMultiDexUncompressedAlignedSrc1() const {
+    return GetTestDexFileName("MultiDexUncompressedAligned");
+  }
+
   // Returns the path to a multidex file equivalent to GetMultiDexSrc2, but
   // with the contents of the secondary dex file changed.
   std::string GetMultiDexSrc2() const {
@@ -243,6 +238,23 @@
 
     return res.status_code;
   }
+
+  void CreateDexMetadata(const std::string& vdex, const std::string& out_dm) {
+    // Read the vdex bytes.
+    std::unique_ptr<File> vdex_file(OS::OpenFileForReading(vdex.c_str()));
+    std::vector<uint8_t> data(vdex_file->GetLength());
+    ASSERT_TRUE(vdex_file->ReadFully(data.data(), data.size()));
+
+    // Zip the content.
+    FILE* file = fopen(out_dm.c_str(), "wbe");
+    ZipWriter writer(file);
+    writer.StartEntry("primary.vdex", ZipWriter::kAlign32);
+    writer.WriteBytes(data.data(), data.size());
+    writer.FinishEntry();
+    writer.Finish();
+    fflush(file);
+    fclose(file);
+  }
 };
 
 }  // namespace art
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
index eb5b149..3eee9e0 100644
--- a/runtime/dexopt_test.cc
+++ b/runtime/dexopt_test.cc
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 
-#include <string>
-#include <vector>
+#include "dexopt_test.h"
 
 #include <gtest/gtest.h>
 #include <procinfo/process_map.h>
 
+#include <string>
+#include <vector>
+
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
+#include "arch/instruction_set.h"
 #include "base/file_utils.h"
 #include "base/mem_map.h"
 #include "common_runtime_test.h"
@@ -29,10 +32,10 @@
 #include "dex/art_dex_file_loader.h"
 #include "dex/dex_file_loader.h"
 #include "dex2oat_environment_test.h"
-#include "dexopt_test.h"
 #include "gc/space/image_space.h"
 #include "hidden_api.h"
 #include "oat.h"
+#include "oat_file_assistant.h"
 #include "profile/profile_compilation_info.h"
 
 namespace art {
@@ -46,9 +49,7 @@
   UnreserveImageSpace();
 }
 
-void DexoptTest::PostRuntimeCreate() {
-  ReserveImageSpace();
-}
+void DexoptTest::PostRuntimeCreate() { ReserveImageSpace(); }
 
 bool DexoptTest::Dex2Oat(const std::vector<std::string>& args, std::string* error_msg) {
   std::vector<std::string> argv;
@@ -80,9 +81,9 @@
   int mkdir_result = mkdir(image_dir.c_str(), 0700);
   CHECK_EQ(0, mkdir_result) << image_dir.c_str();
 
-  std::vector<std::string> extra_args {
-    "--compiler-filter=verify",
-    android::base::StringPrintf("--base=0x%08x", ART_BASE_ADDRESS),
+  std::vector<std::string> extra_args{
+      "--compiler-filter=verify",
+      android::base::StringPrintf("--base=0x%08x", ART_BASE_ADDRESS),
   };
   std::string filename_prefix = image_dir + "/boot-interpreter";
   ArrayRef<const std::string> dex_files(libcore_dex_files);
@@ -117,10 +118,9 @@
     // doesn't get an empty profile and changes the filter to verify.
     std::string error_msg;
     std::vector<std::unique_ptr<const DexFile>> dex_files;
-    const ArtDexFileLoader dex_file_loader;
+    ArtDexFileLoader dex_file_loader(dex_location);
     ASSERT_TRUE(dex_file_loader.Open(
-        dex_location.c_str(), dex_location.c_str(), /*verify=*/ false, /*verify_checksum=*/ false,
-        &error_msg, &dex_files));
+        /*verify=*/false, /*verify_checksum=*/false, &error_msg, &dex_files));
     EXPECT_GE(dex_files.size(), 1U);
     std::unique_ptr<const DexFile>& dex_file = dex_files[0];
     ProfileCompilationInfo info;
@@ -152,33 +152,24 @@
   ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
 
   // Verify the odex file was generated as expected.
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   oat_location.c_str(),
-                                                   oat_location.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   oat_location,
+                                                   oat_location,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    dex_location,
                                                    &error_msg));
   ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
   EXPECT_EQ(filter, odex_file->GetCompilerFilter());
 
   if (CompilerFilter::DependsOnImageChecksum(filter)) {
-    const OatHeader& oat_header = odex_file->GetOatHeader();
-    const char* oat_bcp = oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey);
-    ASSERT_TRUE(oat_bcp != nullptr);
-    ASSERT_EQ(oat_bcp, android::base::Join(Runtime::Current()->GetBootClassPathLocations(), ':'));
-    const char* checksums = oat_header.GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey);
-    ASSERT_TRUE(checksums != nullptr);
+    std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(/*spec=*/"");
+    OatFileAssistant oat_file_assistant(dex_location.c_str(),
+                                        kRuntimeISA,
+                                        context.get(),
+                                        /*load_executable=*/false);
 
-    bool match = gc::space::ImageSpace::VerifyBootClassPathChecksums(
-        checksums,
-        oat_bcp,
-        ArrayRef<const std::string>(&image_location, 1),
-        ArrayRef<const std::string>(Runtime::Current()->GetBootClassPathLocations()),
-        ArrayRef<const std::string>(Runtime::Current()->GetBootClassPath()),
-        ArrayRef<const int>(Runtime::Current()->GetBootClassPathFds()),
-        kRuntimeISA,
-        &error_msg);
+    bool match = oat_file_assistant.ValidateBootClassPathChecksums(*odex_file);
     ASSERT_EQ(!with_alternate_image, match) << error_msg;
   }
 }
@@ -191,7 +182,7 @@
   GenerateOatForTest(dex_location,
                      odex_location,
                      filter,
-                     /*with_alternate_image=*/ false,
+                     /*with_alternate_image=*/false,
                      compilation_reason,
                      extra_args);
 }
@@ -202,15 +193,13 @@
   std::string oat_location;
   std::string error_msg;
   ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
-        dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
-  GenerateOatForTest(dex_location,
-                     oat_location,
-                     filter,
-                     with_alternate_image);
+      dex_location, kRuntimeISA, &oat_location, &error_msg))
+      << error_msg;
+  GenerateOatForTest(dex_location, oat_location, filter, with_alternate_image);
 }
 
 void DexoptTest::GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter) {
-  GenerateOatForTest(dex_location, filter, /*with_alternate_image=*/ false);
+  GenerateOatForTest(dex_location, filter, /*with_alternate_image=*/false);
 }
 
 void DexoptTest::ReserveImageSpace() {
@@ -239,19 +228,17 @@
                                                       reinterpret_cast<uint8_t*>(start),
                                                       end - start,
                                                       PROT_NONE,
-                                                      /*low_4gb=*/ false,
-                                                      /*reuse=*/ false,
-                                                      /*reservation=*/ nullptr,
+                                                      /*low_4gb=*/false,
+                                                      /*reuse=*/false,
+                                                      /*reservation=*/nullptr,
                                                       &error_msg));
     ASSERT_TRUE(image_reservation_.back().IsValid()) << error_msg;
-    LOG(INFO) << "Reserved space for image " <<
-      reinterpret_cast<void*>(image_reservation_.back().Begin()) << "-" <<
-      reinterpret_cast<void*>(image_reservation_.back().End());
+    LOG(INFO) << "Reserved space for image "
+              << reinterpret_cast<void*>(image_reservation_.back().Begin()) << "-"
+              << reinterpret_cast<void*>(image_reservation_.back().End());
   }
 }
 
-void DexoptTest::UnreserveImageSpace() {
-  image_reservation_.clear();
-}
+void DexoptTest::UnreserveImageSpace() { image_reservation_.clear(); }
 
 }  // namespace art
diff --git a/runtime/entrypoints/entrypoint_asm_constants.h b/runtime/entrypoints/entrypoint_asm_constants.h
new file mode 100644
index 0000000..bd7ebd3
--- /dev/null
+++ b/runtime/entrypoints/entrypoint_asm_constants.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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_RUNTIME_ENTRYPOINTS_ENTRYPOINT_ASM_CONSTANTS_H_
+#define ART_RUNTIME_ENTRYPOINTS_ENTRYPOINT_ASM_CONSTANTS_H_
+
+// Reserved area on stack for art_quick_generic_jni_trampoline:
+//           4    local state ref
+//           4    padding
+//        4096    4k scratch space, enough for 2x 256 8-byte parameters
+//   8*(32+32)    max 32 GPRs and 32 FPRs on each architecture, 8 bytes each
+// +         4    padding for 16-bytes alignment
+// -----------
+//        4616
+// Round up to 5k, total 5120
+#define GENERIC_JNI_TRAMPOLINE_RESERVED_AREA 5120
+
+#endif  // ART_RUNTIME_ENTRYPOINTS_ENTRYPOINT_ASM_CONSTANTS_H_
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index 4ee1013..4a1dfba 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -34,7 +34,6 @@
 #include "imt_conflict_table.h"
 #include "imtable-inl.h"
 #include "indirect_reference_table.h"
-#include "jni/jni_internal.h"
 #include "mirror/array-alloc-inl.h"
 #include "mirror/class-alloc-inl.h"
 #include "mirror/class-inl.h"
@@ -122,7 +121,7 @@
     uint32_t method_index = code_info.GetMethodIndexOf(inline_info);
     if (inline_info.GetDexPc() == static_cast<uint32_t>(-1)) {
       // "charAt" special case. It is the only non-leaf method we inline across dex files.
-      ArtMethod* inlined_method = jni::DecodeArtMethod(WellKnownClasses::java_lang_String_charAt);
+      ArtMethod* inlined_method = WellKnownClasses::java_lang_String_charAt;
       DCHECK_EQ(inlined_method->GetDexMethodIndex(), method_index);
       return inlined_method;
     }
@@ -290,7 +289,6 @@
 }
 
 
-template <bool kAccessCheck>
 ALWAYS_INLINE
 inline ObjPtr<mirror::Class> CheckArrayAlloc(dex::TypeIndex type_idx,
                                              int32_t component_count,
@@ -312,7 +310,7 @@
     }
     CHECK(klass->IsArrayClass()) << klass->PrettyClass();
   }
-  if (kAccessCheck) {
+  if (!method->SkipAccessChecks()) {
     ObjPtr<mirror::Class> referrer = method->GetDeclaringClass();
     if (UNLIKELY(!referrer->CanAccess(klass))) {
       ThrowIllegalAccessErrorClass(referrer, klass);
@@ -327,7 +325,7 @@
 // it cannot be resolved, throw an error. If it can, use it to create an array.
 // When verification/compiler hasn't been able to verify access, optionally perform an access
 // check.
-template <bool kAccessCheck, bool kInstrumented>
+template <bool kInstrumented>
 ALWAYS_INLINE
 inline ObjPtr<mirror::Array> AllocArrayFromCode(dex::TypeIndex type_idx,
                                                 int32_t component_count,
@@ -335,8 +333,7 @@
                                                 Thread* self,
                                                 gc::AllocatorType allocator_type) {
   bool slow_path = false;
-  ObjPtr<mirror::Class> klass =
-      CheckArrayAlloc<kAccessCheck>(type_idx, component_count, method, &slow_path);
+  ObjPtr<mirror::Class> klass = CheckArrayAlloc(type_idx, component_count, method, &slow_path);
   if (UNLIKELY(slow_path)) {
     if (klass == nullptr) {
       return nullptr;
@@ -376,76 +373,77 @@
                                              allocator_type);
 }
 
-template<FindFieldType type, bool access_check>
+FLATTEN
+inline ArtField* ResolveFieldWithAccessChecks(Thread* self,
+                                              ClassLinker* class_linker,
+                                              uint16_t field_index,
+                                              ArtMethod* caller,
+                                              bool is_static,
+                                              bool is_put,
+                                              size_t resolve_field_type)  // Resolve if not zero
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (caller->SkipAccessChecks()) {
+    return class_linker->ResolveField(field_index, caller, is_static);
+  }
+
+  caller = caller->GetInterfaceMethodIfProxy(class_linker->GetImagePointerSize());
+
+  StackHandleScope<2> hs(self);
+  Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(caller->GetDexCache()));
+  Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(caller->GetClassLoader()));
+
+  ArtField* resolved_field = class_linker->ResolveFieldJLS(field_index,
+                                                           h_dex_cache,
+                                                           h_class_loader);
+  if (resolved_field == nullptr) {
+    return nullptr;
+  }
+
+  ObjPtr<mirror::Class> fields_class = resolved_field->GetDeclaringClass();
+  if (UNLIKELY(resolved_field->IsStatic() != is_static)) {
+    ThrowIncompatibleClassChangeErrorField(resolved_field, is_static, caller);
+    return nullptr;
+  }
+  ObjPtr<mirror::Class> referring_class = caller->GetDeclaringClass();
+  if (UNLIKELY(!referring_class->CheckResolvedFieldAccess(fields_class,
+                                                          resolved_field,
+                                                          caller->GetDexCache(),
+                                                          field_index))) {
+    DCHECK(self->IsExceptionPending());
+    return nullptr;
+  }
+  if (UNLIKELY(is_put && !resolved_field->CanBeChangedBy(caller))) {
+    ThrowIllegalAccessErrorFinalField(caller, resolved_field);
+    return nullptr;
+  }
+
+  if (resolve_field_type != 0u) {
+    StackArtFieldHandleScope<1> rhs(self);
+    ReflectiveHandle<ArtField> field_handle(rhs.NewHandle(resolved_field));
+    if (resolved_field->ResolveType().IsNull()) {
+      DCHECK(self->IsExceptionPending());
+      return nullptr;
+    }
+    resolved_field = field_handle.Get();
+  }
+  return resolved_field;
+}
+
+template<FindFieldType type>
 inline ArtField* FindFieldFromCode(uint32_t field_idx,
                                    ArtMethod* referrer,
                                    Thread* self,
-                                   size_t expected_size) {
-  constexpr bool is_primitive = (type & FindFieldFlags::PrimitiveBit) != 0;
+                                   bool should_resolve_type = false) {
   constexpr bool is_set = (type & FindFieldFlags::WriteBit) != 0;
   constexpr bool is_static = (type & FindFieldFlags::StaticBit) != 0;
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-
-  ArtField* resolved_field;
-  if (access_check) {
-    // Slow path: According to JLS 13.4.8, a linkage error may occur if a compile-time
-    // qualifying type of a field and the resolved run-time qualifying type of a field differed
-    // in their static-ness.
-    //
-    // In particular, don't assume the dex instruction already correctly knows if the
-    // real field is static or not. The resolution must not be aware of this.
-    ArtMethod* method = referrer->GetInterfaceMethodIfProxy(kRuntimePointerSize);
-
-    StackHandleScope<2> hs(self);
-    Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(method->GetDexCache()));
-    Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(method->GetClassLoader()));
-
-    resolved_field = class_linker->ResolveFieldJLS(field_idx,
-                                                   h_dex_cache,
-                                                   h_class_loader);
-  } else {
-    // Fast path: Verifier already would've called ResolveFieldJLS and we wouldn't
-    // be executing here if there was a static/non-static mismatch.
-    resolved_field = class_linker->ResolveField(field_idx, referrer, is_static);
-  }
-
-  if (UNLIKELY(resolved_field == nullptr)) {
-    DCHECK(self->IsExceptionPending());  // Throw exception and unwind.
-    return nullptr;  // Failure.
-  }
-  ObjPtr<mirror::Class> fields_class = resolved_field->GetDeclaringClass();
-  if (access_check) {
-    if (UNLIKELY(resolved_field->IsStatic() != is_static)) {
-      ThrowIncompatibleClassChangeErrorField(resolved_field, is_static, referrer);
-      return nullptr;
-    }
-    ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
-    if (UNLIKELY(!referring_class->CheckResolvedFieldAccess(fields_class,
-                                                            resolved_field,
-                                                            referrer->GetDexCache(),
-                                                            field_idx))) {
-      DCHECK(self->IsExceptionPending());  // Throw exception and unwind.
-      return nullptr;  // Failure.
-    }
-    if (UNLIKELY(is_set && !resolved_field->CanBeChangedBy(referrer))) {
-      ThrowIllegalAccessErrorFinalField(referrer, resolved_field);
-      return nullptr;  // Failure.
-    } else {
-      if (UNLIKELY(resolved_field->IsPrimitiveType() != is_primitive ||
-                   resolved_field->FieldSize() != expected_size)) {
-        self->ThrowNewExceptionF("Ljava/lang/NoSuchFieldError;",
-                                 "Attempted read of %zd-bit %s on field '%s'",
-                                 expected_size * (32 / sizeof(int32_t)),
-                                 is_primitive ? "primitive" : "non-primitive",
-                                 resolved_field->PrettyField(true).c_str());
-        return nullptr;  // Failure.
-      }
-    }
-  }
-  if (!is_static) {
+  ArtField* resolved_field = ResolveFieldWithAccessChecks(
+      self, class_linker, field_idx, referrer, is_static, is_set, should_resolve_type ? 1u : 0u);
+  if (!is_static || resolved_field == nullptr) {
     // instance fields must be being accessed on an initialized class
     return resolved_field;
   } else {
+    ObjPtr<mirror::Class> fields_class = resolved_field->GetDeclaringClass();
     // If the class is initialized we're done.
     if (LIKELY(fields_class->IsVisiblyInitialized())) {
       return resolved_field;
@@ -463,28 +461,147 @@
   }
 }
 
+// NOLINTBEGIN(bugprone-macro-parentheses)
 // Explicit template declarations of FindFieldFromCode for all field access types.
-#define EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(_type, _access_check) \
+#define EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(_type) \
 template REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE \
-ArtField* FindFieldFromCode<_type, _access_check>(uint32_t field_idx, \
-                                                  ArtMethod* referrer, \
-                                                  Thread* self, size_t expected_size) \
+ArtField* FindFieldFromCode<_type>(uint32_t field_idx, \
+                                   ArtMethod* referrer, \
+                                   Thread* self, \
+                                   bool should_resolve_type = false) \
 
-#define EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL(_type) \
-    EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(_type, false); \
-    EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(_type, true)
+EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(InstanceObjectRead);
+EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(InstanceObjectWrite);
+EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(InstancePrimitiveRead);
+EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(InstancePrimitiveWrite);
+EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(StaticObjectRead);
+EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(StaticObjectWrite);
+EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(StaticPrimitiveRead);
+EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL(StaticPrimitiveWrite);
 
-EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL(InstanceObjectRead);
-EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL(InstanceObjectWrite);
-EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL(InstancePrimitiveRead);
-EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL(InstancePrimitiveWrite);
-EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL(StaticObjectRead);
-EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL(StaticObjectWrite);
-EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL(StaticPrimitiveRead);
-EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL(StaticPrimitiveWrite);
-
-#undef EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL
 #undef EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL
+// NOLINTEND(bugprone-macro-parentheses)
+
+static inline bool IsStringInit(const DexFile* dex_file, uint32_t method_idx)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  const dex::MethodId& method_id = dex_file->GetMethodId(method_idx);
+  const char* class_name = dex_file->StringByTypeIdx(method_id.class_idx_);
+  const char* method_name = dex_file->GetMethodName(method_id);
+  // Instead of calling ResolveMethod() which has suspend point and can trigger
+  // GC, look up the method symbolically.
+  // Compare method's class name and method name against string init.
+  // It's ok since it's not allowed to create your own java/lang/String.
+  // TODO: verify that assumption.
+  if ((strcmp(class_name, "Ljava/lang/String;") == 0) &&
+      (strcmp(method_name, "<init>") == 0)) {
+    return true;
+  }
+  return false;
+}
+
+static inline bool IsStringInit(const Instruction& instr, ArtMethod* caller)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (instr.Opcode() == Instruction::INVOKE_DIRECT ||
+      instr.Opcode() == Instruction::INVOKE_DIRECT_RANGE) {
+    uint16_t callee_method_idx = (instr.Opcode() == Instruction::INVOKE_DIRECT_RANGE) ?
+        instr.VRegB_3rc() : instr.VRegB_35c();
+    return IsStringInit(caller->GetDexFile(), callee_method_idx);
+  }
+  return false;
+}
+
+extern "C" size_t NterpGetMethod(Thread* self, ArtMethod* caller, const uint16_t* dex_pc_ptr);
+
+template <InvokeType type>
+ArtMethod* FindMethodToCall(Thread* self,
+                            ArtMethod* caller,
+                            ObjPtr<mirror::Object>* this_object,
+                            const Instruction& inst,
+                            bool only_lookup_tls_cache,
+                            /*out*/ bool* string_init)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+
+  // Try to find the method in thread-local cache.
+  size_t tls_value = 0u;
+  if (!self->GetInterpreterCache()->Get(self, &inst, &tls_value)) {
+    if (only_lookup_tls_cache) {
+      return nullptr;
+    }
+    DCHECK(!self->IsExceptionPending());
+    // NterpGetMethod can suspend, so save this_object.
+    StackHandleScope<1> hs(self);
+    HandleWrapperObjPtr<mirror::Object> h_this(hs.NewHandleWrapper(this_object));
+    tls_value = NterpGetMethod(self, caller, reinterpret_cast<const uint16_t*>(&inst));
+    if (self->IsExceptionPending()) {
+      return nullptr;
+    }
+  }
+
+  if (type != kStatic && UNLIKELY((*this_object) == nullptr)) {
+    if (UNLIKELY(IsStringInit(inst, caller))) {
+      // Hack for String init:
+      //
+      // We assume that the input of String.<init> in verified code is always
+      // an uninitialized reference. If it is a null constant, it must have been
+      // optimized out by the compiler and we arrive here after deoptimization.
+      // Do not throw NullPointerException.
+    } else {
+      // Maintain interpreter-like semantics where NullPointerException is thrown
+      // after potential NoSuchMethodError from class linker.
+      const uint32_t method_idx = inst.VRegB();
+      ThrowNullPointerExceptionForMethodAccess(method_idx, type);
+      return nullptr;
+    }
+  }
+
+  static constexpr size_t kStringInitMethodFlag = 0b1;
+  static constexpr size_t kInvokeInterfaceOnObjectMethodFlag = 0b1;
+  static constexpr size_t kMethodMask = ~0b11;
+
+  ArtMethod* called_method = nullptr;
+  switch (type) {
+    case kDirect:
+    case kSuper:
+    case kStatic:
+      // Note: for the interpreter, the String.<init> special casing for invocation is handled
+      // in DoCallCommon.
+      *string_init = ((tls_value & kStringInitMethodFlag) != 0);
+      DCHECK_EQ(*string_init, IsStringInit(inst, caller));
+      called_method = reinterpret_cast<ArtMethod*>(tls_value & kMethodMask);
+      break;
+    case kInterface:
+      if ((tls_value & kInvokeInterfaceOnObjectMethodFlag) != 0) {
+        // invokeinterface on a j.l.Object method.
+        uint16_t method_index = tls_value >> 16;
+        called_method = (*this_object)->GetClass()->GetVTableEntry(method_index, pointer_size);
+      } else {
+        ArtMethod* interface_method = reinterpret_cast<ArtMethod*>(tls_value & kMethodMask);
+        called_method = (*this_object)->GetClass()->GetImt(pointer_size)->Get(
+            interface_method->GetImtIndex(), pointer_size);
+        if (called_method->IsRuntimeMethod()) {
+          called_method = (*this_object)->GetClass()->FindVirtualMethodForInterface(
+              interface_method, pointer_size);
+          if (UNLIKELY(called_method == nullptr)) {
+            ThrowIncompatibleClassChangeErrorClassForInterfaceDispatch(
+                interface_method, *this_object, caller);
+            return nullptr;
+          }
+        }
+      }
+      break;
+    case kVirtual:
+      called_method = (*this_object)->GetClass()->GetVTableEntry(tls_value, pointer_size);
+      break;
+  }
+
+  if (UNLIKELY(!called_method->IsInvokable())) {
+    called_method->ThrowInvocationTimeError((type == kStatic) ? nullptr : *this_object);
+    return nullptr;
+  }
+  DCHECK(!called_method->IsRuntimeMethod()) << called_method->PrettyMethod();
+  return called_method;
+}
 
 template<bool access_check>
 ALWAYS_INLINE ArtMethod* FindSuperMethodToCall(uint32_t method_idx,
@@ -546,130 +663,6 @@
   return super_class->GetVTableEntry(vtable_index, linker->GetImagePointerSize());
 }
 
-// Follow virtual/interface indirections if applicable.
-// Will throw null-pointer exception the if the object is null.
-template<InvokeType type, bool access_check>
-ALWAYS_INLINE ArtMethod* FindMethodToCall(uint32_t method_idx,
-                                          ArtMethod* resolved_method,
-                                          ObjPtr<mirror::Object>* this_object,
-                                          ArtMethod* referrer,
-                                          Thread* self)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
-  // Null pointer check.
-  if (UNLIKELY(*this_object == nullptr && type != kStatic)) {
-    if (UNLIKELY(resolved_method->GetDeclaringClass()->IsStringClass() &&
-                 resolved_method->IsConstructor())) {
-      // Hack for String init:
-      //
-      // We assume that the input of String.<init> in verified code is always
-      // an unitialized reference. If it is a null constant, it must have been
-      // optimized out by the compiler. Do not throw NullPointerException.
-    } else {
-      // Maintain interpreter-like semantics where NullPointerException is thrown
-      // after potential NoSuchMethodError from class linker.
-      ThrowNullPointerExceptionForMethodAccess(method_idx, type);
-      return nullptr;  // Failure.
-    }
-  }
-  switch (type) {
-    case kStatic:
-    case kDirect:
-      return resolved_method;
-    case kVirtual: {
-      ObjPtr<mirror::Class> klass = (*this_object)->GetClass();
-      uint16_t vtable_index = resolved_method->GetMethodIndex();
-      if (access_check &&
-          (!klass->HasVTable() ||
-           vtable_index >= static_cast<uint32_t>(klass->GetVTableLength()))) {
-        // Behavior to agree with that of the verifier.
-        ThrowNoSuchMethodError(type, resolved_method->GetDeclaringClass(),
-                               resolved_method->GetName(), resolved_method->GetSignature());
-        return nullptr;  // Failure.
-      }
-      DCHECK(klass->HasVTable()) << klass->PrettyClass();
-      return klass->GetVTableEntry(vtable_index, class_linker->GetImagePointerSize());
-    }
-    case kSuper: {
-      return FindSuperMethodToCall<access_check>(method_idx, resolved_method, referrer, self);
-    }
-    case kInterface: {
-      size_t imt_index = resolved_method->GetImtIndex();
-      PointerSize pointer_size = class_linker->GetImagePointerSize();
-      ObjPtr<mirror::Class> klass = (*this_object)->GetClass();
-      ArtMethod* imt_method = klass->GetImt(pointer_size)->Get(imt_index, pointer_size);
-      if (!imt_method->IsRuntimeMethod()) {
-        if (kIsDebugBuild) {
-          ArtMethod* method = klass->FindVirtualMethodForInterface(
-              resolved_method, class_linker->GetImagePointerSize());
-          CHECK_EQ(imt_method, method) << ArtMethod::PrettyMethod(resolved_method) << " / "
-                                       << imt_method->PrettyMethod() << " / "
-                                       << ArtMethod::PrettyMethod(method) << " / "
-                                       << klass->PrettyClass();
-        }
-        return imt_method;
-      } else {
-        ArtMethod* interface_method = klass->FindVirtualMethodForInterface(
-            resolved_method, class_linker->GetImagePointerSize());
-        if (UNLIKELY(interface_method == nullptr)) {
-          ThrowIncompatibleClassChangeErrorClassForInterfaceDispatch(resolved_method,
-                                                                     *this_object, referrer);
-          return nullptr;  // Failure.
-        }
-        return interface_method;
-      }
-    }
-    default:
-      LOG(FATAL) << "Unknown invoke type " << type;
-      return nullptr;  // Failure.
-  }
-}
-
-template<InvokeType type, bool access_check>
-inline ArtMethod* FindMethodFromCode(uint32_t method_idx,
-                                     ObjPtr<mirror::Object>* this_object,
-                                     ArtMethod* referrer,
-                                     Thread* self) {
-  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
-  constexpr ClassLinker::ResolveMode resolve_mode =
-      access_check ? ClassLinker::ResolveMode::kCheckICCEAndIAE
-                   : ClassLinker::ResolveMode::kNoChecks;
-  ArtMethod* resolved_method;
-  if (type == kStatic) {
-    resolved_method = class_linker->ResolveMethod<resolve_mode>(self, method_idx, referrer, type);
-  } else {
-    StackHandleScope<1> hs(self);
-    HandleWrapperObjPtr<mirror::Object> h_this(hs.NewHandleWrapper(this_object));
-    resolved_method = class_linker->ResolveMethod<resolve_mode>(self, method_idx, referrer, type);
-  }
-  if (UNLIKELY(resolved_method == nullptr)) {
-    DCHECK(self->IsExceptionPending());  // Throw exception and unwind.
-    return nullptr;  // Failure.
-  }
-  return FindMethodToCall<type, access_check>(
-      method_idx, resolved_method, this_object, referrer, self);
-}
-
-// Explicit template declarations of FindMethodFromCode for all invoke types.
-#define EXPLICIT_FIND_METHOD_FROM_CODE_TEMPLATE_DECL(_type, _access_check)                 \
-  template REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE                       \
-  ArtMethod* FindMethodFromCode<_type, _access_check>(uint32_t method_idx,         \
-                                                      ObjPtr<mirror::Object>* this_object, \
-                                                      ArtMethod* referrer, \
-                                                      Thread* self)
-#define EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(_type) \
-    EXPLICIT_FIND_METHOD_FROM_CODE_TEMPLATE_DECL(_type, false);   \
-    EXPLICIT_FIND_METHOD_FROM_CODE_TEMPLATE_DECL(_type, true)
-
-EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(kStatic);
-EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(kDirect);
-EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(kVirtual);
-EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(kSuper);
-EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(kInterface);
-
-#undef EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL
-#undef EXPLICIT_FIND_METHOD_FROM_CODE_TEMPLATE_DECL
-
 inline ObjPtr<mirror::Class> ResolveVerifyAndClinit(dex::TypeIndex type_idx,
                                                     ArtMethod* referrer,
                                                     Thread* self,
@@ -724,13 +717,6 @@
   }
 }
 
-inline bool NeedsClinitCheckBeforeCall(ArtMethod* method) {
-  // The class needs to be visibly initialized before we can use entrypoints to
-  // compiled code for static methods. See b/18161648 . The class initializer is
-  // special as it is invoked during initialization and does not need the check.
-  return method->IsStatic() && !method->IsConstructor();
-}
-
 inline ObjPtr<mirror::Object> GetGenericJniSynchronizationObject(Thread* self, ArtMethod* called)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(!called->IsCriticalNative());
diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc
index 63d2aa4..aa27df4 100644
--- a/runtime/entrypoints/entrypoint_utils.cc
+++ b/runtime/entrypoints/entrypoint_utils.cc
@@ -22,6 +22,7 @@
 #include "base/mutex.h"
 #include "base/sdk_version.h"
 #include "class_linker-inl.h"
+#include "class_root-inl.h"
 #include "dex/dex_file-inl.h"
 #include "dex/method_reference.h"
 #include "entrypoints/entrypoint_utils-inl.h"
@@ -33,14 +34,14 @@
 #include "mirror/class-inl.h"
 #include "mirror/method.h"
 #include "mirror/object-inl.h"
-#include "mirror/object_array-inl.h"
+#include "mirror/object_array-alloc-inl.h"
 #include "nth_caller_visitor.h"
 #include "oat_file.h"
 #include "oat_file-inl.h"
 #include "oat_quick_method_header.h"
 #include "reflection.h"
 #include "scoped_thread_state_change-inl.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -65,60 +66,70 @@
                                     jobject rcvr_jobj,
                                     jobject interface_method_jobj,
                                     std::vector<jvalue>& args) {
-  DCHECK(soa.Env()->IsInstanceOf(rcvr_jobj, WellKnownClasses::java_lang_reflect_Proxy));
+  StackHandleScope<4u> hs(soa.Self());
+  DCHECK(rcvr_jobj != nullptr);
+  Handle<mirror::Object> h_receiver = hs.NewHandle(soa.Decode<mirror::Object>(rcvr_jobj));
+  DCHECK(h_receiver->InstanceOf(GetClassRoot(ClassRoot::kJavaLangReflectProxy)));
+  Handle<mirror::Method> h_interface_method =
+      hs.NewHandle(soa.Decode<mirror::Method>(interface_method_jobj));
 
   // Build argument array possibly triggering GC.
   soa.Self()->AssertThreadSuspensionIsAllowable();
-  jobjectArray args_jobj = nullptr;
+  auto h_args = hs.NewHandle<mirror::ObjectArray<mirror::Object>>(nullptr);
   const JValue zero;
-  uint32_t target_sdk_version = Runtime::Current()->GetTargetSdkVersion();
+  Runtime* runtime = Runtime::Current();
+  uint32_t target_sdk_version = runtime->GetTargetSdkVersion();
   // Do not create empty arrays unless needed to maintain Dalvik bug compatibility.
   if (args.size() > 0 || IsSdkVersionSetAndAtMost(target_sdk_version, SdkVersion::kL)) {
-    args_jobj = soa.Env()->NewObjectArray(args.size(), WellKnownClasses::java_lang_Object, nullptr);
-    if (args_jobj == nullptr) {
+    h_args.Assign(mirror::ObjectArray<mirror::Object>::Alloc(
+        soa.Self(), GetClassRoot<mirror::ObjectArray<mirror::Object>>(), args.size()));
+    if (h_args == nullptr) {
       CHECK(soa.Self()->IsExceptionPending());
       return zero;
     }
     for (size_t i = 0; i < args.size(); ++i) {
+      ObjPtr<mirror::Object> value;
       if (shorty[i + 1] == 'L') {
-        jobject val = args[i].l;
-        soa.Env()->SetObjectArrayElement(args_jobj, i, val);
+        value = soa.Decode<mirror::Object>(args[i].l);
       } else {
         JValue jv;
         jv.SetJ(args[i].j);
-        ObjPtr<mirror::Object> val = BoxPrimitive(Primitive::GetType(shorty[i + 1]), jv);
-        if (val == nullptr) {
+        value = BoxPrimitive(Primitive::GetType(shorty[i + 1]), jv);
+        if (value == nullptr) {
           CHECK(soa.Self()->IsExceptionPending());
           return zero;
         }
-        soa.Decode<mirror::ObjectArray<mirror::Object>>(args_jobj)->Set<false>(i, val);
       }
+      // We do not support `Proxy.invoke()` in a transaction.
+      h_args->SetWithoutChecks</*kActiveTransaction=*/ false>(i, value);
     }
   }
 
   // Call Proxy.invoke(Proxy proxy, Method method, Object[] args).
-  jvalue invocation_args[3];
-  invocation_args[0].l = rcvr_jobj;
-  invocation_args[1].l = interface_method_jobj;
-  invocation_args[2].l = args_jobj;
-  jobject result =
-      soa.Env()->CallStaticObjectMethodA(WellKnownClasses::java_lang_reflect_Proxy,
-                                         WellKnownClasses::java_lang_reflect_Proxy_invoke,
-                                         invocation_args);
+  Handle<mirror::Object> h_result = hs.NewHandle(
+      WellKnownClasses::java_lang_reflect_Proxy_invoke->InvokeStatic<'L', 'L', 'L', 'L'>(
+          soa.Self(), h_receiver.Get(), h_interface_method.Get(), h_args.Get()));
 
   // Unbox result and handle error conditions.
   if (LIKELY(!soa.Self()->IsExceptionPending())) {
-    if (shorty[0] == 'V' || (shorty[0] == 'L' && result == nullptr)) {
+    if (shorty[0] == 'V' || (shorty[0] == 'L' && h_result == nullptr)) {
       // Do nothing.
       return zero;
     } else {
-      ArtMethod* interface_method =
-          soa.Decode<mirror::Method>(interface_method_jobj)->GetArtMethod();
-      // This can cause thread suspension.
-      ObjPtr<mirror::Class> result_type = interface_method->ResolveReturnType();
-      ObjPtr<mirror::Object> result_ref = soa.Decode<mirror::Object>(result);
+      ObjPtr<mirror::Class> result_type;
+      if (shorty[0] == 'L') {
+        // This can cause thread suspension.
+        result_type = h_interface_method->GetArtMethod()->ResolveReturnType();
+        if (result_type == nullptr) {
+          DCHECK(soa.Self()->IsExceptionPending());
+          return zero;
+        }
+      } else {
+        result_type = runtime->GetClassLinker()->LookupPrimitiveClass(shorty[0]);
+        DCHECK(result_type != nullptr);
+      }
       JValue result_unboxed;
-      if (!UnboxPrimitiveForResult(result_ref, result_type, &result_unboxed)) {
+      if (!UnboxPrimitiveForResult(h_result.Get(), result_type, &result_unboxed)) {
         DCHECK(soa.Self()->IsExceptionPending());
         return zero;
       }
@@ -197,51 +208,62 @@
   return std::make_pair(outer_method, caller_pc);
 }
 
-static inline ArtMethod* DoGetCalleeSaveMethodCaller(ArtMethod* outer_method,
-                                                     uintptr_t caller_pc,
-                                                     bool do_caller_check)
+static inline ArtMethod* DoGetCalleeSaveMethodCallerAndDexPc(ArtMethod** sp,
+                                                             CalleeSaveType type,
+                                                             ArtMethod* outer_method,
+                                                             uintptr_t caller_pc,
+                                                             uint32_t* dex_pc,
+                                                             bool do_caller_check)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ArtMethod* caller = outer_method;
-  if (LIKELY(caller_pc != reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()))) {
-    if (outer_method != nullptr) {
-      const OatQuickMethodHeader* current_code = outer_method->GetOatQuickMethodHeader(caller_pc);
-      DCHECK(current_code != nullptr);
-      if (current_code->IsOptimized() &&
-          CodeInfo::HasInlineInfo(current_code->GetOptimizedCodeInfoPtr())) {
-        uintptr_t native_pc_offset = current_code->NativeQuickPcOffset(caller_pc);
-        CodeInfo code_info = CodeInfo::DecodeInlineInfoOnly(current_code);
-        StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset);
-        DCHECK(stack_map.IsValid());
-        BitTableRange<InlineInfo> inline_infos = code_info.GetInlineInfosOf(stack_map);
-        if (!inline_infos.empty()) {
-          caller = GetResolvedMethod(outer_method, code_info, inline_infos);
-        }
+  if (outer_method != nullptr) {
+    const OatQuickMethodHeader* current_code = outer_method->GetOatQuickMethodHeader(caller_pc);
+    DCHECK(current_code != nullptr);
+    if (current_code->IsOptimized() &&
+        CodeInfo::HasInlineInfo(current_code->GetOptimizedCodeInfoPtr())) {
+      uintptr_t native_pc_offset = current_code->NativeQuickPcOffset(caller_pc);
+      CodeInfo code_info = CodeInfo::DecodeInlineInfoOnly(current_code);
+      StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset);
+      DCHECK(stack_map.IsValid());
+      BitTableRange<InlineInfo> inline_infos = code_info.GetInlineInfosOf(stack_map);
+      if (!inline_infos.empty()) {
+        caller = GetResolvedMethod(outer_method, code_info, inline_infos);
+        *dex_pc = inline_infos.back().GetDexPc();
+      } else {
+        *dex_pc = stack_map.GetDexPc();
       }
+    } else {
+      size_t callee_frame_size = RuntimeCalleeSaveFrame::GetFrameSize(type);
+      ArtMethod** caller_sp = reinterpret_cast<ArtMethod**>(
+          reinterpret_cast<uintptr_t>(sp) + callee_frame_size);
+      *dex_pc = current_code->ToDexPc(caller_sp, caller_pc);
     }
-    if (kIsDebugBuild && do_caller_check) {
-      // Note that do_caller_check is optional, as this method can be called by
-      // stubs, and tests without a proper call stack.
-      NthCallerVisitor visitor(Thread::Current(), 1, true);
-      visitor.WalkStack();
-      CHECK_EQ(caller, visitor.caller);
-    }
-  } else {
-    // We're instrumenting, just use the StackVisitor which knows how to
-    // handle instrumented frames.
+  }
+  if (kIsDebugBuild && do_caller_check) {
+    // Note that do_caller_check is optional, as this method can be called by
+    // stubs, and tests without a proper call stack.
     NthCallerVisitor visitor(Thread::Current(), 1, true);
     visitor.WalkStack();
-    caller = visitor.caller;
+    CHECK_EQ(caller, visitor.caller);
   }
   return caller;
 }
 
-ArtMethod* GetCalleeSaveMethodCaller(ArtMethod** sp, CalleeSaveType type, bool do_caller_check)
+ArtMethod* GetCalleeSaveMethodCallerAndDexPc(ArtMethod** sp,
+                                             CalleeSaveType type,
+                                             uint32_t* dex_pc,
+                                             bool do_caller_check)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ScopedAssertNoThreadSuspension ants(__FUNCTION__);
   auto outer_caller_and_pc = DoGetCalleeSaveMethodOuterCallerAndPc(sp, type);
   ArtMethod* outer_method = outer_caller_and_pc.first;
   uintptr_t caller_pc = outer_caller_and_pc.second;
-  ArtMethod* caller = DoGetCalleeSaveMethodCaller(outer_method, caller_pc, do_caller_check);
+  ArtMethod* caller = DoGetCalleeSaveMethodCallerAndDexPc(sp,
+                                                          type,
+                                                          outer_method,
+                                                          caller_pc,
+                                                          dex_pc,
+                                                          do_caller_check);
   return caller;
 }
 
@@ -252,8 +274,13 @@
   auto outer_caller_and_pc = DoGetCalleeSaveMethodOuterCallerAndPc(sp, type);
   result.outer_method = outer_caller_and_pc.first;
   uintptr_t caller_pc = outer_caller_and_pc.second;
-  result.caller =
-      DoGetCalleeSaveMethodCaller(result.outer_method, caller_pc, /* do_caller_check= */ true);
+  uint32_t dex_pc;
+  result.caller = DoGetCalleeSaveMethodCallerAndDexPc(sp,
+                                                      type,
+                                                      result.outer_method,
+                                                      caller_pc,
+                                                      &dex_pc,
+                                                      /* do_caller_check= */ true);
   return result;
 }
 
diff --git a/runtime/entrypoints/entrypoint_utils.h b/runtime/entrypoints/entrypoint_utils.h
index 8b6fc69..cfa744d 100644
--- a/runtime/entrypoints/entrypoint_utils.h
+++ b/runtime/entrypoints/entrypoint_utils.h
@@ -77,7 +77,6 @@
     REQUIRES(!Roles::uninterruptible_);
 
 
-template <bool kAccessCheck>
 ALWAYS_INLINE inline ObjPtr<mirror::Class> CheckArrayAlloc(dex::TypeIndex type_idx,
                                                            int32_t component_count,
                                                            ArtMethod* method,
@@ -89,7 +88,7 @@
 // it cannot be resolved, throw an error. If it can, use it to create an array.
 // When verification/compiler hasn't been able to verify access, optionally perform an access
 // check.
-template <bool kAccessCheck, bool kInstrumented = true>
+template <bool kInstrumented = true>
 ALWAYS_INLINE inline ObjPtr<mirror::Array> AllocArrayFromCode(dex::TypeIndex type_idx,
                                                               int32_t component_count,
                                                               ArtMethod* method,
@@ -143,11 +142,13 @@
     REQUIRES_SHARED(Locks::mutator_lock_)
     REQUIRES(!Roles::uninterruptible_);
 
-template<InvokeType type, bool access_check>
-inline ArtMethod* FindMethodFromCode(uint32_t method_idx,
-                                     ObjPtr<mirror::Object>* this_object,
-                                     ArtMethod* referrer,
-                                     Thread* self)
+template<InvokeType type>
+inline ArtMethod* FindMethodToCall(Thread* self,
+                                   ArtMethod* referrer,
+                                   ObjPtr<mirror::Object>* this_object,
+                                   const Instruction& inst,
+                                   bool only_lookup_tls_cache,
+                                   /*out*/ bool* string_init)
     REQUIRES_SHARED(Locks::mutator_lock_)
     REQUIRES(!Roles::uninterruptible_);
 
@@ -187,9 +188,10 @@
 template <typename INT_TYPE, typename FLOAT_TYPE>
 inline INT_TYPE art_float_to_integral(FLOAT_TYPE f);
 
-ArtMethod* GetCalleeSaveMethodCaller(ArtMethod** sp,
-                                     CalleeSaveType type,
-                                     bool do_caller_check = false)
+ArtMethod* GetCalleeSaveMethodCallerAndDexPc(ArtMethod** sp,
+                                             CalleeSaveType type,
+                                             /* out */ uint32_t* dex_pc,
+                                             bool do_caller_check = false)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
 struct CallerAndOuterMethod {
@@ -203,10 +205,6 @@
 ArtMethod* GetCalleeSaveOuterMethod(Thread* self, CalleeSaveType type)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
-// Returns whether we need to do class initialization check before invoking the method.
-// The caller is responsible for performing that check.
-bool NeedsClinitCheckBeforeCall(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
-
 // Returns the synchronization object for a native method for a GenericJni frame
 // we have just created or are about to exit. The synchronization object is
 // the class object for static methods and the `this` object otherwise.
diff --git a/runtime/entrypoints/jni/jni_entrypoints.cc b/runtime/entrypoints/jni/jni_entrypoints.cc
index c78b604..e606c21 100644
--- a/runtime/entrypoints/jni/jni_entrypoints.cc
+++ b/runtime/entrypoints/jni/jni_entrypoints.cc
@@ -75,7 +75,7 @@
 
     // These calls do not have an explicit class initialization check, so do the check now.
     // (When going through the stub or GenericJNI, the check was already done.)
-    DCHECK(NeedsClinitCheckBeforeCall(target_method));
+    DCHECK(target_method->NeedsClinitCheckBeforeCall());
     ObjPtr<mirror::Class> declaring_class = target_method->GetDeclaringClass();
     if (UNLIKELY(!declaring_class->IsVisiblyInitialized())) {
       StackHandleScope<1> hs(self);
@@ -87,11 +87,11 @@
     }
 
     // Replace the runtime method on the stack with the target method.
-    DCHECK(!self->GetManagedStack()->GetTopQuickFrameTag());
+    DCHECK(!self->GetManagedStack()->GetTopQuickFrameGenericJniTag());
     ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrameKnownNotTagged();
     DCHECK(*sp == Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
     *sp = target_method;
-    self->SetTopOfStackTagged(sp);  // Fake GenericJNI frame.
+    self->SetTopOfStackGenericJniTagged(sp);  // Fake GenericJNI frame.
 
     // Continue with the target method.
     method = target_method;
diff --git a/runtime/entrypoints/math_entrypoints_test.cc b/runtime/entrypoints/math_entrypoints_test.cc
index f70a2da..fe61f5d 100644
--- a/runtime/entrypoints/math_entrypoints_test.cc
+++ b/runtime/entrypoints/math_entrypoints_test.cc
@@ -18,11 +18,11 @@
 
 #include <limits>
 
-#include "common_runtime_test.h"
+#include "base/common_art_test.h"
 
 namespace art {
 
-class MathEntrypointsTest : public CommonRuntimeTest {};
+class MathEntrypointsTest : public CommonArtTest {};
 
 TEST_F(MathEntrypointsTest, DoubleToLong) {
   EXPECT_EQ(std::numeric_limits<int64_t>::max(), art_d2l(1.85e19));
diff --git a/runtime/entrypoints/quick/callee_save_frame.h b/runtime/entrypoints/quick/callee_save_frame.h
index 1baccee..7d9b844 100644
--- a/runtime/entrypoints/quick/callee_save_frame.h
+++ b/runtime/entrypoints/quick/callee_save_frame.h
@@ -28,6 +28,7 @@
 // specialize the code.
 #include "arch/arm/callee_save_frame_arm.h"
 #include "arch/arm64/callee_save_frame_arm64.h"
+#include "arch/riscv64/callee_save_frame_riscv64.h"
 #include "arch/x86/callee_save_frame_x86.h"
 #include "arch/x86_64/callee_save_frame_x86_64.h"
 
@@ -73,13 +74,25 @@
 
 // Note: kThumb2 is never the kRuntimeISA.
 template <>
-struct CSFSelector<InstructionSet::kArm> { using type = arm::ArmCalleeSaveFrame; };
+struct CSFSelector<InstructionSet::kArm> {
+  using type = arm::ArmCalleeSaveFrame;
+};
 template <>
-struct CSFSelector<InstructionSet::kArm64> { using type = arm64::Arm64CalleeSaveFrame; };
+struct CSFSelector<InstructionSet::kArm64> {
+  using type = arm64::Arm64CalleeSaveFrame;
+};
 template <>
-struct CSFSelector<InstructionSet::kX86> { using type = x86::X86CalleeSaveFrame; };
+struct CSFSelector<InstructionSet::kRiscv64> {
+  using type = riscv64::Riscv64CalleeSaveFrame;
+};
 template <>
-struct CSFSelector<InstructionSet::kX86_64> { using type = x86_64::X86_64CalleeSaveFrame; };
+struct CSFSelector<InstructionSet::kX86> {
+  using type = x86::X86CalleeSaveFrame;
+};
+template <>
+struct CSFSelector<InstructionSet::kX86_64> {
+  using type = x86_64::X86_64CalleeSaveFrame;
+};
 
 }  // namespace detail
 
diff --git a/runtime/entrypoints/quick/quick_default_externs.h b/runtime/entrypoints/quick/quick_default_externs.h
index f8856d8..cb3caac 100644
--- a/runtime/entrypoints/quick/quick_default_externs.h
+++ b/runtime/entrypoints/quick/quick_default_externs.h
@@ -122,6 +122,7 @@
 extern "C" void art_jni_monitored_method_start();
 extern "C" void art_jni_method_end();
 extern "C" void art_jni_monitored_method_end();
+extern "C" void art_jni_method_entry_hook();
 
 // JNI lock/unlock entrypoints. Note: Custom calling convention.
 extern "C" void art_jni_lock_object(art::mirror::Object*);
diff --git a/runtime/entrypoints/quick/quick_default_init_entrypoints.h b/runtime/entrypoints/quick/quick_default_init_entrypoints.h
index 939feee..ea07788 100644
--- a/runtime/entrypoints/quick/quick_default_init_entrypoints.h
+++ b/runtime/entrypoints/quick/quick_default_init_entrypoints.h
@@ -79,6 +79,7 @@
   qpoints->SetQuickGenericJniTrampoline(art_quick_generic_jni_trampoline);
   qpoints->SetJniDecodeReferenceResult(JniDecodeReferenceResult);
   qpoints->SetJniReadBarrier(art_jni_read_barrier);
+  qpoints->SetJniMethodEntryHook(art_jni_method_entry_hook);
 
   // Locks
   if (UNLIKELY(VLOG_IS_ON(systrace_lock_logging))) {
diff --git a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc
index d06dbcb..277bc7b 100644
--- a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc
@@ -25,7 +25,10 @@
 
 namespace art {
 
-NO_RETURN static void artDeoptimizeImpl(Thread* self, DeoptimizationKind kind, bool single_frame)
+NO_RETURN static void artDeoptimizeImpl(Thread* self,
+                                        DeoptimizationKind kind,
+                                        bool single_frame,
+                                        bool skip_method_exit_callbacks)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   Runtime::Current()->IncrementDeoptimizationCount(kind);
   if (VLOG_IS_ON(deopt)) {
@@ -43,13 +46,12 @@
   if (single_frame) {
     exception_handler.DeoptimizeSingleFrame(kind);
   } else {
-    exception_handler.DeoptimizeStack();
+    exception_handler.DeoptimizeStack(skip_method_exit_callbacks);
   }
-  uintptr_t return_pc = exception_handler.UpdateInstrumentationStack();
   if (exception_handler.IsFullFragmentDone()) {
     exception_handler.DoLongJump(true);
   } else {
-    exception_handler.DeoptimizePartialFragmentFixup(return_pc);
+    exception_handler.DeoptimizePartialFragmentFixup();
     // We cannot smash the caller-saves, as we need the ArtMethod in a parameter register that would
     // be caller-saved. This has the downside that we cannot track incorrect register usage down the
     // line.
@@ -57,9 +59,10 @@
   }
 }
 
-extern "C" NO_RETURN void artDeoptimize(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
+extern "C" NO_RETURN void artDeoptimize(Thread* self, bool skip_method_exit_callbacks)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
   ScopedQuickEntrypointChecks sqec(self);
-  artDeoptimizeImpl(self, DeoptimizationKind::kFullFrame, false);
+  artDeoptimizeImpl(self, DeoptimizationKind::kFullFrame, false, skip_method_exit_callbacks);
 }
 
 // This is called directly from compiled code by an HDeoptimize.
@@ -74,7 +77,9 @@
                                   self->GetException(),
                                   /* from_code= */ true,
                                   DeoptimizationMethodType::kDefault);
-  artDeoptimizeImpl(self, kind, true);
+  // Deopting from compiled code, so method exit haven't run yet. Don't skip method exit callbacks
+  // if required.
+  artDeoptimizeImpl(self, kind, true, /* skip_method_exit_callbacks= */ false);
 }
 
 }  // namespace art
diff --git a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
index 60a5875..76bee21 100644
--- a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
@@ -23,6 +23,7 @@
 #include "dex/dex_file_types.h"
 #include "entrypoints/entrypoint_utils-inl.h"
 #include "gc/heap.h"
+#include "jvalue-inl.h"
 #include "mirror/class-inl.h"
 #include "mirror/class_loader.h"
 #include "mirror/object-inl.h"
diff --git a/runtime/entrypoints/quick/quick_entrypoints.h b/runtime/entrypoints/quick/quick_entrypoints.h
index 7af1a0b..0e73c63 100644
--- a/runtime/entrypoints/quick/quick_entrypoints.h
+++ b/runtime/entrypoints/quick/quick_entrypoints.h
@@ -67,6 +67,7 @@
 // JNI entrypoints when monitoring entry/exit.
 extern "C" void artJniMonitoredMethodStart(Thread* self) UNLOCK_FUNCTION(Locks::mutator_lock_);
 extern "C" void artJniMonitoredMethodEnd(Thread* self) SHARED_LOCK_FUNCTION(Locks::mutator_lock_);
+extern "C" void artJniMethodEntryHook(Thread* self);
 
 // StringAppend pattern entrypoint.
 extern "C" mirror::String* artStringBuilderAppend(uint32_t format,
diff --git a/runtime/entrypoints/quick/quick_entrypoints_list.h b/runtime/entrypoints/quick/quick_entrypoints_list.h
index dffaa4b..aa3360e 100644
--- a/runtime/entrypoints/quick/quick_entrypoints_list.h
+++ b/runtime/entrypoints/quick/quick_entrypoints_list.h
@@ -78,6 +78,7 @@
   V(JniLockObject, void, mirror::Object*) \
   V(JniUnlockObject, void, mirror::Object*) \
   V(QuickGenericJniTrampoline, void, ArtMethod*) \
+  V(JniMethodEntryHook, void) \
 \
   V(LockObject, void, mirror::Object*) \
   V(UnlockObject, void, mirror::Object*) \
@@ -150,6 +151,7 @@
 \
   V(NewEmptyString, void, void) \
   V(NewStringFromBytes_B, void, void) \
+  V(NewStringFromBytes_BB, void, void) \
   V(NewStringFromBytes_BI, void, void) \
   V(NewStringFromBytes_BII, void, void) \
   V(NewStringFromBytes_BIII, void, void) \
@@ -164,6 +166,7 @@
   V(NewStringFromString, void, void) \
   V(NewStringFromStringBuffer, void, void) \
   V(NewStringFromStringBuilder, void, void) \
+  V(NewStringFromUtf16Bytes_BII, void, void) \
 \
   V(StringBuilderAppend, void*, uint32_t) \
 \
diff --git a/runtime/entrypoints/quick/quick_field_entrypoints.cc b/runtime/entrypoints/quick/quick_field_entrypoints.cc
index d32aa39..e2fc232 100644
--- a/runtime/entrypoints/quick/quick_field_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_field_entrypoints.cc
@@ -32,7 +32,7 @@
 inline ArtField* FindFieldFast(uint32_t field_idx,
                                ArtMethod* referrer,
                                FindFieldType type,
-                               size_t expected_size)
+                               bool should_resolve_type = false)
     REQUIRES(!Roles::uninterruptible_)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ScopedAssertNoThreadSuspension ants(__FUNCTION__);
@@ -41,7 +41,6 @@
     return nullptr;
   }
   // Check for incompatible class change.
-  const bool is_primitive = (type & FindFieldFlags::PrimitiveBit) != 0;
   const bool is_set = (type & FindFieldFlags::WriteBit) != 0;
   const bool is_static = (type & FindFieldFlags::StaticBit) != 0;
   if (UNLIKELY(resolved_field->IsStatic() != is_static)) {
@@ -63,8 +62,7 @@
     // Illegal access.
     return nullptr;
   }
-  if (UNLIKELY(resolved_field->IsPrimitiveType() != is_primitive ||
-               resolved_field->FieldSize() != expected_size)) {
+  if (should_resolve_type && resolved_field->LookupResolvedType() == nullptr) {
     return nullptr;
   }
   return resolved_field;
@@ -73,17 +71,17 @@
 // Helper function to do a null check after trying to resolve the field. Not for statics since obj
 // does not exist there. There is a suspend check, object is a double pointer to update the value
 // in the caller in case it moves.
-template<FindFieldType type, bool kAccessCheck>
+template<FindFieldType type>
 ALWAYS_INLINE static inline ArtField* FindInstanceField(uint32_t field_idx,
                                                         ArtMethod* referrer,
                                                         Thread* self,
-                                                        size_t size,
-                                                        mirror::Object** obj)
+                                                        mirror::Object** obj,
+                                                        bool should_resolve_type = false)
     REQUIRES(!Roles::uninterruptible_)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   StackHandleScope<1> hs(self);
   HandleWrapper<mirror::Object> h(hs.NewHandleWrapper(obj));
-  ArtField* field = FindFieldFromCode<type, kAccessCheck>(field_idx, referrer, self, size);
+  ArtField* field = FindFieldFromCode<type>(field_idx, referrer, self, should_resolve_type);
   if (LIKELY(field != nullptr) && UNLIKELY(h == nullptr)) {
     ThrowNullPointerExceptionForFieldAccess(field, referrer, (type & FindFieldFlags::ReadBit) != 0);
     return nullptr;
@@ -116,13 +114,12 @@
       REQUIRES_SHARED(Locks::mutator_lock_) {                                  \
     ScopedQuickEntrypointChecks sqec(self);                                    \
     ArtField* field = FindFieldFast(                                           \
-        field_idx, referrer, Static ## PrimitiveOrObject ## Read,              \
-        sizeof(PrimitiveType));                                                \
+        field_idx, referrer, Static ## PrimitiveOrObject ## Read);             \
     if (LIKELY(field != nullptr)) {                                            \
       return field->Get ## Kind (field->GetDeclaringClass())Ptr;  /* NOLINT */ \
     }                                                                          \
-    field = FindFieldFromCode<Static ## PrimitiveOrObject ## Read, true>(      \
-        field_idx, referrer, self, sizeof(PrimitiveType));                     \
+    field = FindFieldFromCode<Static ## PrimitiveOrObject ## Read>(            \
+        field_idx, referrer, self);                                            \
     if (LIKELY(field != nullptr)) {                                            \
       return field->Get ## Kind (field->GetDeclaringClass())Ptr;  /* NOLINT */ \
     }                                                                          \
@@ -137,13 +134,12 @@
       REQUIRES_SHARED(Locks::mutator_lock_) {                                  \
     ScopedQuickEntrypointChecks sqec(self);                                    \
     ArtField* field = FindFieldFast(                                           \
-        field_idx, referrer, Instance ## PrimitiveOrObject ## Read,            \
-        sizeof(PrimitiveType));                                                \
+        field_idx, referrer, Instance ## PrimitiveOrObject ## Read);           \
     if (LIKELY(field != nullptr) && obj != nullptr) {                          \
       return field->Get ## Kind (obj)Ptr;  /* NOLINT */                        \
     }                                                                          \
-    field = FindInstanceField<Instance ## PrimitiveOrObject ## Read, true>(    \
-        field_idx, referrer, self, sizeof(PrimitiveType), &obj);               \
+    field = FindInstanceField<Instance ## PrimitiveOrObject ## Read>(          \
+        field_idx, referrer, self, &obj);                                      \
     if (LIKELY(field != nullptr)) {                                            \
       return field->Get ## Kind (obj)Ptr;  /* NOLINT */                        \
     }                                                                          \
@@ -157,33 +153,30 @@
                                                   Thread* self)                \
       REQUIRES_SHARED(Locks::mutator_lock_) {                                  \
     ScopedQuickEntrypointChecks sqec(self);                                    \
+    bool should_resolve_type = (IsObject) && new_value != 0;                   \
     ArtField* field = FindFieldFast(                                           \
-        field_idx, referrer, Static ## PrimitiveOrObject ## Write,             \
-        sizeof(PrimitiveType));                                                \
+        field_idx,                                                             \
+        referrer,                                                              \
+        Static ## PrimitiveOrObject ## Write,                                  \
+        should_resolve_type);                                                  \
     if (UNLIKELY(field == nullptr)) {                                          \
       if (IsObject) {                                                          \
         StackHandleScope<1> hs(self);                                          \
         HandleWrapper<mirror::Object> h_obj(hs.NewHandleWrapper(               \
             reinterpret_cast<mirror::Object**>(&new_value)));                  \
-        field = FindFieldFromCode<Static ## PrimitiveOrObject ## Write, true>( \
-            field_idx, referrer, self, sizeof(PrimitiveType));                 \
+        field = FindFieldFromCode<Static ## PrimitiveOrObject ## Write>(       \
+            field_idx,                                                         \
+            referrer,                                                          \
+            self,                                                              \
+            should_resolve_type);                                              \
       } else {                                                                 \
-        field = FindFieldFromCode<Static ## PrimitiveOrObject ## Write, true>( \
-            field_idx, referrer, self, sizeof(PrimitiveType));                 \
+        field = FindFieldFromCode<Static ## PrimitiveOrObject ## Write>(       \
+            field_idx, referrer, self);                                        \
       }                                                                        \
       if (UNLIKELY(field == nullptr)) {                                        \
         return -1;                                                             \
       }                                                                        \
     }                                                                          \
-    if (!referrer->SkipAccessChecks() && IsObject && new_value != 0) {         \
-      StackArtFieldHandleScope<1> rhs(self);                                   \
-      ReflectiveHandle<ArtField> field_handle(rhs.NewHandle(field));           \
-      if (field->ResolveType().IsNull()) {                                     \
-        self->AssertPendingException();                                        \
-        return -1;                                                             \
-      }                                                                        \
-      field = field_handle.Get();                                              \
-    }                                                                          \
     field->Set ## Kind <false>(field->GetDeclaringClass(), new_value);         \
     return 0;                                                                  \
   }                                                                            \
@@ -195,43 +188,31 @@
                                                     Thread* self)              \
     REQUIRES_SHARED(Locks::mutator_lock_) {                                    \
     ScopedQuickEntrypointChecks sqec(self);                                    \
+    bool should_resolve_type = (IsObject) && new_value != 0;                   \
     ArtField* field = FindFieldFast(                                           \
-        field_idx, referrer, Instance ## PrimitiveOrObject ## Write,           \
-        sizeof(PrimitiveType));                                                \
+        field_idx,                                                             \
+        referrer,                                                              \
+        Instance ## PrimitiveOrObject ## Write,                                \
+        should_resolve_type);                                                  \
     if (UNLIKELY(field == nullptr || obj == nullptr)) {                        \
       if (IsObject) {                                                          \
         StackHandleScope<1> hs(self);                                          \
         HandleWrapper<mirror::Object> h_obj(hs.NewHandleWrapper(               \
             reinterpret_cast<mirror::Object**>(&new_value)));                  \
-        field =                                                                \
-            FindInstanceField<Instance ## PrimitiveOrObject ## Write, true>(   \
-                field_idx,                                                     \
-                referrer,                                                      \
-                self,                                                          \
-                sizeof(PrimitiveType),                                         \
-                &obj);                                                         \
+        field = FindInstanceField<Instance ## PrimitiveOrObject ## Write>(     \
+            field_idx,                                                         \
+            referrer,                                                          \
+            self,                                                              \
+            &obj,                                                              \
+            should_resolve_type);                                              \
       } else {                                                                 \
-        field =                                                                \
-            FindInstanceField<Instance ## PrimitiveOrObject ## Write, true>(   \
-                field_idx,                                                     \
-                referrer,                                                      \
-                self,                                                          \
-                sizeof(PrimitiveType),                                         \
-                &obj);                                                         \
+        field = FindInstanceField<Instance ## PrimitiveOrObject ## Write>(     \
+            field_idx, referrer, self, &obj);                                  \
       }                                                                        \
       if (UNLIKELY(field == nullptr)) {                                        \
         return -1;                                                             \
       }                                                                        \
     }                                                                          \
-    if (!referrer->SkipAccessChecks() && IsObject && new_value != 0) {         \
-      StackArtFieldHandleScope<1> rhs(self);                                   \
-      ReflectiveHandle<ArtField> field_handle(rhs.NewHandle(field));           \
-      if (field->ResolveType().IsNull()) {                                     \
-        self->AssertPendingException();                                        \
-        return -1;                                                             \
-      }                                                                        \
-      field = field_handle.Get();                                              \
-    }                                                                          \
     field->Set ## Kind<false>(obj, new_value);                                 \
     return 0;                                                                  \
   }                                                                            \
@@ -435,7 +416,7 @@
 }
 
 extern "C" mirror::Object* artReadBarrierMark(mirror::Object* obj) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   return ReadBarrier::Mark(obj);
 }
 
@@ -443,14 +424,12 @@
                                               mirror::Object* obj,
                                               uint32_t offset) {
   // Used only in connection with non-volatile loads.
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   uint8_t* raw_addr = reinterpret_cast<uint8_t*>(obj) + offset;
   mirror::HeapReference<mirror::Object>* ref_addr =
      reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_addr);
-  constexpr ReadBarrierOption kReadBarrierOption =
-      kUseReadBarrier ? kWithReadBarrier : kWithoutReadBarrier;
   mirror::Object* result =
-      ReadBarrier::Barrier<mirror::Object, /* kIsVolatile= */ false, kReadBarrierOption>(
+      ReadBarrier::Barrier<mirror::Object, /* kIsVolatile= */ false, kWithReadBarrier>(
         obj,
         MemberOffset(offset),
         ref_addr);
@@ -458,7 +437,7 @@
 }
 
 extern "C" mirror::Object* artReadBarrierForRootSlow(GcRoot<mirror::Object>* root) {
-  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(gUseReadBarrier);
   return root->Read();
 }
 
diff --git a/runtime/entrypoints/quick/quick_jni_entrypoints.cc b/runtime/entrypoints/quick/quick_jni_entrypoints.cc
index ab13bd9..6f69001 100644
--- a/runtime/entrypoints/quick/quick_jni_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_jni_entrypoints.cc
@@ -38,11 +38,16 @@
 
 namespace art {
 
-static_assert(sizeof(IRTSegmentState) == sizeof(uint32_t), "IRTSegmentState size unexpected");
-static_assert(std::is_trivial<IRTSegmentState>::value, "IRTSegmentState not trivial");
+extern "C" int artMethodExitHook(Thread* self,
+                                 ArtMethod* method,
+                                 uint64_t* gpr_result,
+                                 uint64_t* fpr_result);
+
+static_assert(sizeof(jni::LRTSegmentState) == sizeof(uint32_t), "LRTSegmentState size unexpected");
+static_assert(std::is_trivial<jni::LRTSegmentState>::value, "LRTSegmentState not trivial");
 
 extern "C" void artJniReadBarrier(ArtMethod* method) {
-  DCHECK(kUseReadBarrier);
+  DCHECK(gUseReadBarrier);
   mirror::CompressedReference<mirror::Object>* declaring_class =
       method->GetDeclaringClassAddressWithoutBarrier();
   if (kUseBakerReadBarrier) {
@@ -77,7 +82,7 @@
     env->CheckNoHeldMonitors();
   }
   env->SetLocalSegmentState(env->GetLocalRefCookie());
-  env->SetLocalRefCookie(bit_cast<IRTSegmentState>(saved_local_ref_cookie));
+  env->SetLocalRefCookie(bit_cast<jni::LRTSegmentState>(saved_local_ref_cookie));
 }
 
 // TODO: annotalysis disabled as monitor semantics are maintained in Java code.
@@ -174,11 +179,11 @@
     artJniUnlockObject(lock.Ptr(), self);
   }
   char return_shorty_char = called->GetShorty()[0];
+  uint64_t ret;
   if (return_shorty_char == 'L') {
-    uint64_t ret = reinterpret_cast<uint64_t>(
+    ret = reinterpret_cast<uint64_t>(
         UNLIKELY(self->IsExceptionPending()) ? nullptr : JniDecodeReferenceResult(result.l, self));
     PopLocalReferences(saved_local_ref_cookie, self);
-    return ret;
   } else {
     if (LIKELY(!critical_native)) {
       PopLocalReferences(saved_local_ref_cookie, self);
@@ -188,32 +193,43 @@
         if (kRuntimeISA == InstructionSet::kX86) {
           // Convert back the result to float.
           double d = bit_cast<double, uint64_t>(result_f);
-          return bit_cast<uint32_t, float>(static_cast<float>(d));
+          ret = bit_cast<uint32_t, float>(static_cast<float>(d));
         } else {
-          return result_f;
+          ret = result_f;
         }
       }
+      break;
       case 'D':
-        return result_f;
+        ret = result_f;
+        break;
       case 'Z':
-        return result.z;
+        ret = result.z;
+        break;
       case 'B':
-        return result.b;
+        ret = result.b;
+        break;
       case 'C':
-        return result.c;
+        ret = result.c;
+        break;
       case 'S':
-        return result.s;
+        ret = result.s;
+        break;
       case 'I':
-        return result.i;
+        ret = result.i;
+        break;
       case 'J':
-        return result.j;
+        ret = result.j;
+        break;
       case 'V':
-        return 0;
+        ret = 0;
+        break;
       default:
         LOG(FATAL) << "Unexpected return shorty character " << return_shorty_char;
         UNREACHABLE();
     }
   }
+
+  return ret;
 }
 
 extern "C" void artJniMonitoredMethodStart(Thread* self) {
diff --git a/runtime/entrypoints/quick/quick_thread_entrypoints.cc b/runtime/entrypoints/quick/quick_thread_entrypoints.cc
index 93422cf..5dca58a 100644
--- a/runtime/entrypoints/quick/quick_thread_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_thread_entrypoints.cc
@@ -21,16 +21,46 @@
 
 namespace art {
 
+extern "C" void artDeoptimizeIfNeeded(Thread* self, uintptr_t result, bool is_ref)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+  DCHECK(!self->IsExceptionPending());
+
+  ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
+  DCHECK(sp != nullptr && (*sp)->IsRuntimeMethod());
+
+  DeoptimizationMethodType type = instr->GetDeoptimizationMethodType(*sp);
+  JValue jvalue;
+  jvalue.SetJ(result);
+  instr->DeoptimizeIfNeeded(self, sp, type, jvalue, is_ref);
+}
+
 extern "C" void artTestSuspendFromCode(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
   // Called when there is a pending checkpoint or suspend request.
   ScopedQuickEntrypointChecks sqec(self);
   self->CheckSuspend();
+
+  // We could have other dex instructions at the same dex pc as suspend and we need to execute
+  // those instructions. So we should start executing from the current dex pc.
+  ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
+  JValue result;
+  result.SetJ(0);
+  Runtime::Current()->GetInstrumentation()->DeoptimizeIfNeeded(
+      self, sp, DeoptimizationMethodType::kKeepDexPc, result, /* is_ref= */ false);
 }
 
 extern "C" void artImplicitSuspendFromCode(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
   // Called when there is a pending checkpoint or suspend request.
   ScopedQuickEntrypointChecks sqec(self);
   self->CheckSuspend(/*implicit=*/ true);
+
+  // We could have other dex instructions at the same dex pc as suspend and we need to execute
+  // those instructions. So we should start executing from the current dex pc.
+  ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
+  JValue result;
+  result.SetJ(0);
+  Runtime::Current()->GetInstrumentation()->DeoptimizeIfNeeded(
+      self, sp, DeoptimizationMethodType::kKeepDexPc, result, /* is_ref= */ false);
 }
 
 extern "C" void artCompileOptimized(ArtMethod* method, Thread* self)
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index b6ece4a..7e96f29 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -61,7 +61,7 @@
 namespace art {
 
 extern "C" NO_RETURN void artDeoptimizeFromCompiledCode(DeoptimizationKind kind, Thread* self);
-extern "C" NO_RETURN void artDeoptimize(Thread* self);
+extern "C" NO_RETURN void artDeoptimize(Thread* self, bool skip_method_exit_callbacks);
 
 // Visits the arguments as saved to the stack by a CalleeSaveType::kRefAndArgs callee save frame.
 class QuickArgumentVisitor {
@@ -141,6 +141,51 @@
   static size_t GprIndexToGprOffset(uint32_t gpr_index) {
     return gpr_index * GetBytesPerGprSpillLocation(kRuntimeISA);
   }
+#elif defined(__riscv)
+  // The callee save frame is pointed to by SP.
+  // | argN            |  |
+  // | ...             |  |
+  // | reg. arg spills |  |  Caller's frame
+  // | Method*         | ---
+  // | RA              |
+  // | S11/X27         |  callee-saved 11
+  // | S10/X26         |  callee-saved 10
+  // | S9/X25          |  callee-saved 9
+  // | S9/X24          |  callee-saved 8
+  // | S7/X23          |  callee-saved 7
+  // | S6/X22          |  callee-saved 6
+  // | S5/X21          |  callee-saved 5
+  // | S4/X20          |  callee-saved 4
+  // | S3/X19          |  callee-saved 3
+  // | S2/X18          |  callee-saved 2
+  // | A7/X17          |  arg 7
+  // | A6/X16          |  arg 6
+  // | A5/X15          |  arg 5
+  // | A4/X14          |  arg 4
+  // | A3/X13          |  arg 3
+  // | A2/X12          |  arg 2
+  // | A1/X11          |  arg 1 (A0 is the method => skipped)
+  // | S0/X8/FP        |  callee-saved 0 (S1 is TR => skipped)
+  // | FA7             |  float arg 8
+  // | FA6             |  float arg 7
+  // | FA5             |  float arg 6
+  // | FA4             |  float arg 5
+  // | FA3             |  float arg 4
+  // | FA2             |  float arg 3
+  // | FA1             |  float arg 2
+  // | FA0             |  float arg 1
+  // | A0/Method*      | <- sp
+  static constexpr bool kSplitPairAcrossRegisterAndStack = false;
+  static constexpr bool kAlignPairRegister = false;
+  static constexpr bool kQuickSoftFloatAbi = false;
+  static constexpr bool kQuickDoubleRegAlignedFloatBackFilled = false;
+  static constexpr bool kQuickSkipOddFpRegisters = false;
+  static constexpr size_t kNumQuickGprArgs = 7;
+  static constexpr size_t kNumQuickFprArgs = 8;
+  static constexpr bool kGprFprLockstep = false;
+  static size_t GprIndexToGprOffset(uint32_t gpr_index) {
+    return (gpr_index + 1) * GetBytesPerGprSpillLocation(kRuntimeISA);  // skip S0/X8/FP
+  }
 #elif defined(__i386__)
   // The callee save frame is pointed to by SP.
   // | argN        |  |
@@ -224,13 +269,8 @@
 #endif
 
  public:
-  // Special handling for proxy methods. Proxy methods are instance methods so the
-  // 'this' object is the 1st argument. They also have the same frame layout as the
-  // kRefAndArgs runtime method. Since 'this' is a reference, it is located in the
-  // 1st GPR.
-  static StackReference<mirror::Object>* GetProxyThisObjectReference(ArtMethod** sp)
+  static StackReference<mirror::Object>* GetThisObjectReference(ArtMethod** sp)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    CHECK((*sp)->IsProxyMethod());
     CHECK_GT(kNumQuickGprArgs, 0u);
     constexpr uint32_t kThisGprIndex = 0u;  // 'this' is in the 1st GPR.
     size_t this_arg_offset = kQuickCalleeSaveFrame_RefAndArgs_Gpr1Offset +
@@ -239,9 +279,15 @@
     return reinterpret_cast<StackReference<mirror::Object>*>(this_arg_address);
   }
 
-  static ArtMethod* GetCallingMethod(ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) {
+  static ArtMethod* GetCallingMethodAndDexPc(ArtMethod** sp, uint32_t* dex_pc)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
     DCHECK((*sp)->IsCalleeSaveMethod());
-    return GetCalleeSaveMethodCaller(sp, CalleeSaveType::kSaveRefsAndArgs);
+    return GetCalleeSaveMethodCallerAndDexPc(sp, CalleeSaveType::kSaveRefsAndArgs, dex_pc);
+  }
+
+  static ArtMethod* GetCallingMethod(ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) {
+    uint32_t dex_pc;
+    return GetCallingMethodAndDexPc(sp, &dex_pc);
   }
 
   static ArtMethod* GetOuterMethod(ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -251,31 +297,6 @@
     return *reinterpret_cast<ArtMethod**>(previous_sp);
   }
 
-  static uint32_t GetCallingDexPc(ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) {
-    DCHECK((*sp)->IsCalleeSaveMethod());
-    constexpr size_t callee_frame_size =
-        RuntimeCalleeSaveFrame::GetFrameSize(CalleeSaveType::kSaveRefsAndArgs);
-    ArtMethod** caller_sp = reinterpret_cast<ArtMethod**>(
-        reinterpret_cast<uintptr_t>(sp) + callee_frame_size);
-    uintptr_t outer_pc = QuickArgumentVisitor::GetCallingPc(sp);
-    const OatQuickMethodHeader* current_code = (*caller_sp)->GetOatQuickMethodHeader(outer_pc);
-    uintptr_t outer_pc_offset = current_code->NativeQuickPcOffset(outer_pc);
-
-    if (current_code->IsOptimized()) {
-      CodeInfo code_info = CodeInfo::DecodeInlineInfoOnly(current_code);
-      StackMap stack_map = code_info.GetStackMapForNativePcOffset(outer_pc_offset);
-      DCHECK(stack_map.IsValid());
-      BitTableRange<InlineInfo> inline_infos = code_info.GetInlineInfosOf(stack_map);
-      if (!inline_infos.empty()) {
-        return inline_infos.back().GetDexPc();
-      } else {
-        return stack_map.GetDexPc();
-      }
-    } else {
-      return current_code->ToDexPc(caller_sp, outer_pc);
-    }
-  }
-
   static uint8_t* GetCallingPcAddr(ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) {
     DCHECK((*sp)->IsCalleeSaveMethod());
     uint8_t* return_adress_spill =
@@ -529,7 +550,8 @@
 // allows to use the QuickArgumentVisitor constants without moving all the code in its own module.
 extern "C" mirror::Object* artQuickGetProxyThisObject(ArtMethod** sp)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  return QuickArgumentVisitor::GetProxyThisObjectReference(sp)->AsMirrorPtr();
+  DCHECK((*sp)->IsProxyMethod());
+  return QuickArgumentVisitor::GetThisObjectReference(sp)->AsMirrorPtr();
 }
 
 // Visits arguments on the stack placing them into the shadow frame.
@@ -647,6 +669,7 @@
                                               method_type);
 }
 
+NO_STACK_PROTECTOR
 extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, ArtMethod** sp)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   // Ensure we don't get thread suspension until the object arguments are safely in the shadow
@@ -654,100 +677,75 @@
   ScopedQuickEntrypointChecks sqec(self);
 
   if (UNLIKELY(!method->IsInvokable())) {
-    method->ThrowInvocationTimeError();
+    method->ThrowInvocationTimeError(
+        method->IsStatic()
+            ? nullptr
+            : QuickArgumentVisitor::GetThisObjectReference(sp)->AsMirrorPtr());
     return 0;
   }
 
-  JValue tmp_value;
-  ShadowFrame* deopt_frame = self->PopStackedShadowFrame(
-      StackedShadowFrameType::kDeoptimizationShadowFrame, false);
-  ManagedStack fragment;
-
   DCHECK(!method->IsNative()) << method->PrettyMethod();
-  uint32_t shorty_len = 0;
-  ArtMethod* non_proxy_method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
-  DCHECK(non_proxy_method->GetCodeItem() != nullptr) << method->PrettyMethod();
-  CodeItemDataAccessor accessor(non_proxy_method->DexInstructionData());
-  const char* shorty = non_proxy_method->GetShorty(&shorty_len);
 
   JValue result;
-  bool force_frame_pop = false;
 
+  ArtMethod* non_proxy_method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
+  DCHECK(non_proxy_method->GetCodeItem() != nullptr) << method->PrettyMethod();
+  uint32_t shorty_len = 0;
+  const char* shorty = non_proxy_method->GetShorty(&shorty_len);
+
+  ManagedStack fragment;
+  ShadowFrame* deopt_frame = self->MaybePopDeoptimizedStackedShadowFrame();
   if (UNLIKELY(deopt_frame != nullptr)) {
     HandleDeoptimization(&result, method, deopt_frame, &fragment);
   } else {
+    CodeItemDataAccessor accessor(non_proxy_method->DexInstructionData());
     const char* old_cause = self->StartAssertNoThreadSuspension(
         "Building interpreter shadow frame");
     uint16_t num_regs = accessor.RegistersSize();
     // No last shadow coming from quick.
     ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
-        CREATE_SHADOW_FRAME(num_regs, /* link= */ nullptr, method, /* dex_pc= */ 0);
+        CREATE_SHADOW_FRAME(num_regs, method, /* dex_pc= */ 0);
     ShadowFrame* shadow_frame = shadow_frame_unique_ptr.get();
     size_t first_arg_reg = accessor.RegistersSize() - accessor.InsSize();
     BuildQuickShadowFrameVisitor shadow_frame_builder(sp, method->IsStatic(), shorty, shorty_len,
                                                       shadow_frame, first_arg_reg);
     shadow_frame_builder.VisitArguments();
+    self->EndAssertNoThreadSuspension(old_cause);
+
+    // Potentially run <clinit> before pushing the shadow frame. We do not want
+    // to have the called method on the stack if there is an exception.
+    if (!EnsureInitialized(self, shadow_frame)) {
+      DCHECK(self->IsExceptionPending());
+      return 0;
+    }
+
     // Push a transition back into managed code onto the linked list in thread.
     self->PushManagedStackFragment(&fragment);
     self->PushShadowFrame(shadow_frame);
-    self->EndAssertNoThreadSuspension(old_cause);
-
-    if (NeedsClinitCheckBeforeCall(method)) {
-      ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass();
-      if (UNLIKELY(!declaring_class->IsVisiblyInitialized())) {
-        // Ensure static method's class is initialized.
-        StackHandleScope<1> hs(self);
-        Handle<mirror::Class> h_class(hs.NewHandle(declaring_class));
-        if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, h_class, true, true)) {
-          DCHECK(Thread::Current()->IsExceptionPending()) << method->PrettyMethod();
-          self->PopManagedStackFragment(fragment);
-          return 0;
-        }
-      }
-    }
-
     result = interpreter::EnterInterpreterFromEntryPoint(self, accessor, shadow_frame);
-    force_frame_pop = shadow_frame->GetForcePopFrame();
   }
 
   // Pop transition.
   self->PopManagedStackFragment(fragment);
 
-  // Request a stack deoptimization if needed
-  ArtMethod* caller = QuickArgumentVisitor::GetCallingMethod(sp);
-  uintptr_t caller_pc = QuickArgumentVisitor::GetCallingPc(sp);
-  // If caller_pc is the instrumentation exit stub, the stub will check to see if deoptimization
-  // should be done and it knows the real return pc. NB If the upcall is null we don't need to do
-  // anything. This can happen during shutdown or early startup.
-  if (UNLIKELY(
-          caller != nullptr &&
-          caller_pc != reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) &&
-          (self->IsForceInterpreter() || Dbg::IsForcedInterpreterNeededForUpcall(self, caller)))) {
-    if (!Runtime::Current()->IsAsyncDeoptimizeable(caller_pc)) {
-      LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
-                   << caller->PrettyMethod();
-    } else {
-      VLOG(deopt) << "Forcing deoptimization on return from method " << method->PrettyMethod()
-                  << " to " << caller->PrettyMethod()
-                  << (force_frame_pop ? " for frame-pop" : "");
-      DCHECK_IMPLIES(force_frame_pop, result.GetJ() == 0)
-          << "Force frame pop should have no result.";
-      if (force_frame_pop && self->GetException() != nullptr) {
-        LOG(WARNING) << "Suppressing exception for instruction-retry: "
-                     << self->GetException()->Dump();
-      }
-      // Push the context of the deoptimization stack so we can restore the return value and the
-      // exception before executing the deoptimized frames.
-      self->PushDeoptimizationContext(
-          result,
-          shorty[0] == 'L' || shorty[0] == '[',  /* class or array */
-          force_frame_pop ? nullptr : self->GetException(),
-          /* from_code= */ false,
-          DeoptimizationMethodType::kDefault);
+  // Check if caller needs to be deoptimized for instrumentation reasons.
+  instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+  if (UNLIKELY(instr->ShouldDeoptimizeCaller(self, sp))) {
+    ArtMethod* caller = QuickArgumentVisitor::GetOuterMethod(sp);
+    uintptr_t caller_pc = QuickArgumentVisitor::GetCallingPc(sp);
+    DCHECK(Runtime::Current()->IsAsyncDeoptimizeable(caller, caller_pc));
+    DCHECK(caller != nullptr);
+    DCHECK(self->GetException() != Thread::GetDeoptimizationException());
+    // Push the context of the deoptimization stack so we can restore the return value and the
+    // exception before executing the deoptimized frames.
+    self->PushDeoptimizationContext(result,
+                                    shorty[0] == 'L' || shorty[0] == '[', /* class or array */
+                                    self->GetException(),
+                                    /* from_code= */ false,
+                                    DeoptimizationMethodType::kDefault);
 
-      // Set special exception to cause deoptimization.
-      self->SetException(Thread::GetDeoptimizationException());
-    }
+    // Set special exception to cause deoptimization.
+    self->SetException(Thread::GetDeoptimizationException());
   }
 
   // No need to restore the args since the method has already been run by the interpreter.
@@ -862,7 +860,6 @@
     instr->MethodEnterEvent(soa.Self(), proxy_method);
     if (soa.Self()->IsExceptionPending()) {
       instr->MethodUnwindEvent(self,
-                               soa.Decode<mirror::Object>(rcvr_jobj),
                                proxy_method,
                                0);
       return 0;
@@ -872,7 +869,6 @@
   if (soa.Self()->IsExceptionPending()) {
     if (instr->HasMethodUnwindListeners()) {
       instr->MethodUnwindEvent(self,
-                               soa.Decode<mirror::Object>(rcvr_jobj),
                                proxy_method,
                                0);
     }
@@ -1023,99 +1019,10 @@
   }
 }
 
-extern "C" const void* artInstrumentationMethodEntryFromCode(ArtMethod* method,
-                                                             mirror::Object* this_object,
-                                                             Thread* self,
-                                                             ArtMethod** sp)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  const void* result;
-  // Instrumentation changes the stack. Thus, when exiting, the stack cannot be verified, so skip
-  // that part.
-  ScopedQuickEntrypointChecks sqec(self, kIsDebugBuild, false);
-  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
-  DCHECK(!method->IsProxyMethod())
-      << "Proxy method " << method->PrettyMethod()
-      << " (declaring class: " << method->GetDeclaringClass()->PrettyClass() << ")"
-      << " should not hit instrumentation entrypoint.";
-  DCHECK(!instrumentation->IsDeoptimized(method));
-  // This will get the entry point either from the oat file, the JIT or the appropriate bridge
-  // method if none of those can be found.
-  result = instrumentation->GetCodeForInvoke(method);
-  DCHECK_NE(result, GetQuickInstrumentationEntryPoint()) << method->PrettyMethod();
-  bool interpreter_entry = Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(result);
-  bool is_static = method->IsStatic();
-  uint32_t shorty_len;
-  const char* shorty =
-      method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty(&shorty_len);
-
-  ScopedObjectAccessUnchecked soa(self);
-  RememberForGcArgumentVisitor visitor(sp, is_static, shorty, shorty_len, &soa);
-  visitor.VisitArguments();
-
-  StackHandleScope<2> hs(self);
-  Handle<mirror::Object> h_object(hs.NewHandle(is_static ? nullptr : this_object));
-  Handle<mirror::Class> h_class(hs.NewHandle(method->GetDeclaringClass()));
-
-  // Ensure that the called method's class is initialized.
-  if (NeedsClinitCheckBeforeCall(method) && !h_class->IsVisiblyInitialized()) {
-    if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, h_class, true, true)) {
-      visitor.FixupReferences();
-      DCHECK(self->IsExceptionPending());
-      return nullptr;
-    }
-  }
-
-  instrumentation->PushInstrumentationStackFrame(self,
-                                                 is_static ? nullptr : h_object.Get(),
-                                                 method,
-                                                 reinterpret_cast<uintptr_t>(
-                                                     QuickArgumentVisitor::GetCallingPcAddr(sp)),
-                                                 QuickArgumentVisitor::GetCallingPc(sp),
-                                                 interpreter_entry);
-
-  visitor.FixupReferences();
-  if (UNLIKELY(self->IsExceptionPending())) {
-    return nullptr;
-  }
-  CHECK(result != nullptr) << method->PrettyMethod();
-  return result;
-}
-
-extern "C" TwoWordReturn artInstrumentationMethodExitFromCode(Thread* self,
-                                                              ArtMethod** sp,
-                                                              uint64_t* gpr_result,
-                                                              uint64_t* fpr_result)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  DCHECK_EQ(reinterpret_cast<uintptr_t>(self), reinterpret_cast<uintptr_t>(Thread::Current()));
-  CHECK(gpr_result != nullptr);
-  CHECK(fpr_result != nullptr);
-  // Instrumentation exit stub must not be entered with a pending exception.
-  CHECK(!self->IsExceptionPending()) << "Enter instrumentation exit stub with pending exception "
-                                     << self->GetException()->Dump();
-  // Compute address of return PC and check that it currently holds 0.
-  constexpr size_t return_pc_offset =
-      RuntimeCalleeSaveFrame::GetReturnPcOffset(CalleeSaveType::kSaveEverything);
-  uintptr_t* return_pc_addr = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) +
-                                                           return_pc_offset);
-  CHECK_EQ(*return_pc_addr, 0U);
-
-  // Pop the frame filling in the return pc. The low half of the return value is 0 when
-  // deoptimization shouldn't be performed with the high-half having the return address. When
-  // deoptimization should be performed the low half is zero and the high-half the address of the
-  // deoptimization entry point.
-  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
-  TwoWordReturn return_or_deoptimize_pc = instrumentation->PopInstrumentationStackFrame(
-      self, return_pc_addr, gpr_result, fpr_result);
-  if (self->IsExceptionPending() || self->ObserveAsyncException()) {
-    return GetTwoWordFailureValue();
-  }
-  return return_or_deoptimize_pc;
-}
-
 static std::string DumpInstruction(ArtMethod* method, uint32_t dex_pc)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   if (dex_pc == static_cast<uint32_t>(-1)) {
-    CHECK(method == jni::DecodeArtMethod(WellKnownClasses::java_lang_String_charAt));
+    CHECK(method == WellKnownClasses::java_lang_String_charAt);
     return "<native>";
   } else {
     CodeItemInstructionAccessor accessor = method->DexInstructions();
@@ -1154,12 +1061,6 @@
       (reinterpret_cast<uint8_t*>(sp) + callee_return_pc_offset));
   ArtMethod* outer_method = *caller_sp;
 
-  if (UNLIKELY(caller_pc == reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()))) {
-    LOG(FATAL_WITHOUT_ABORT) << "Method: " << outer_method->PrettyMethod()
-        << " native pc: " << caller_pc << " Instrumented!";
-    return;
-  }
-
   const OatQuickMethodHeader* current_code = outer_method->GetOatQuickMethodHeader(caller_pc);
   CHECK(current_code != nullptr);
   CHECK(current_code->IsOptimized());
@@ -1193,7 +1094,7 @@
       if (dex_pc == static_cast<uint32_t>(-1)) {
         tag = "special ";
         CHECK(inline_info.Equals(inline_infos.back()));
-        caller = jni::DecodeArtMethod(WellKnownClasses::java_lang_String_charAt);
+        caller = WellKnownClasses::java_lang_String_charAt;
         CHECK_EQ(caller->GetDexMethodIndex(), method_index);
       } else {
         ObjPtr<mirror::DexCache> dex_cache = caller->GetDexCache();
@@ -1234,11 +1135,11 @@
   const bool called_method_known_on_entry = !called->IsRuntimeMethod();
   ArtMethod* caller = nullptr;
   if (!called_method_known_on_entry) {
-    caller = QuickArgumentVisitor::GetCallingMethod(sp);
+    uint32_t dex_pc;
+    caller = QuickArgumentVisitor::GetCallingMethodAndDexPc(sp, &dex_pc);
     called_method.dex_file = caller->GetDexFile();
 
     {
-      uint32_t dex_pc = QuickArgumentVisitor::GetCallingDexPc(sp);
       CodeItemInstructionAccessor accessor(caller->DexInstructions());
       CHECK_LT(dex_pc, accessor.InsnsSizeInCodeUnits());
       const Instruction& instr = accessor.InstructionAt(dex_pc);
@@ -1371,21 +1272,25 @@
     // Static invokes need class initialization check but instance invokes can proceed even if
     // the class is erroneous, i.e. in the edge case of escaping instances of erroneous classes.
     bool success = true;
-    ObjPtr<mirror::Class> called_class = called->GetDeclaringClass();
-    if (NeedsClinitCheckBeforeCall(called) && !called_class->IsVisiblyInitialized()) {
+    if (called->StillNeedsClinitCheck()) {
       // Ensure that the called method's class is initialized.
       StackHandleScope<1> hs(soa.Self());
-      HandleWrapperObjPtr<mirror::Class> h_called_class(hs.NewHandleWrapper(&called_class));
+      Handle<mirror::Class> h_called_class = hs.NewHandle(called->GetDeclaringClass());
       success = linker->EnsureInitialized(soa.Self(), h_called_class, true, true);
     }
     if (success) {
+      // When the clinit check is at entry of the AOT/nterp code, we do the clinit check
+      // before doing the suspend check. To ensure the code sees the latest
+      // version of the class (the code doesn't do a read barrier to reduce
+      // size), do a suspend check now.
+      self->CheckSuspend();
       instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
       // Check if we need instrumented code here. Since resolution stubs could suspend, it is
       // possible that we instrumented the entry points after we started executing the resolution
       // stub.
       code = instrumentation->GetMaybeInstrumentedCodeForInvoke(called);
     } else {
-      DCHECK(called_class->IsErroneous());
+      DCHECK(called->GetDeclaringClass()->IsErroneous());
       DCHECK(self->IsExceptionPending());
     }
   }
@@ -1442,10 +1347,10 @@
   static constexpr size_t kRegistersNeededForLong = 2;
   static constexpr size_t kRegistersNeededForDouble = 2;
   static constexpr bool kMultiRegistersAligned = true;
-  static constexpr bool kMultiFPRegistersWidened = false;
   static constexpr bool kMultiGPRegistersWidened = false;
   static constexpr bool kAlignLongOnStack = true;
   static constexpr bool kAlignDoubleOnStack = true;
+  static constexpr bool kNaNBoxing = false;
 #elif defined(__aarch64__)
   static constexpr bool kNativeSoftFloatAbi = false;  // This is a hard float ABI.
   static constexpr size_t kNumNativeGprArgs = 8;  // 8 arguments passed in GPRs.
@@ -1454,10 +1359,22 @@
   static constexpr size_t kRegistersNeededForLong = 1;
   static constexpr size_t kRegistersNeededForDouble = 1;
   static constexpr bool kMultiRegistersAligned = false;
-  static constexpr bool kMultiFPRegistersWidened = false;
   static constexpr bool kMultiGPRegistersWidened = false;
   static constexpr bool kAlignLongOnStack = false;
   static constexpr bool kAlignDoubleOnStack = false;
+  static constexpr bool kNaNBoxing = false;
+#elif defined(__riscv)
+  static constexpr bool kNativeSoftFloatAbi = false;
+  static constexpr size_t kNumNativeGprArgs = 8;
+  static constexpr size_t kNumNativeFprArgs = 8;
+
+  static constexpr size_t kRegistersNeededForLong = 1;
+  static constexpr size_t kRegistersNeededForDouble = 1;
+  static constexpr bool kMultiRegistersAligned = false;
+  static constexpr bool kMultiGPRegistersWidened = true;
+  static constexpr bool kAlignLongOnStack = false;
+  static constexpr bool kAlignDoubleOnStack = false;
+  static constexpr bool kNaNBoxing = true;
 #elif defined(__i386__)
   static constexpr bool kNativeSoftFloatAbi = false;  // Not using int registers for fp
   static constexpr size_t kNumNativeGprArgs = 0;  // 0 arguments passed in GPRs.
@@ -1466,10 +1383,10 @@
   static constexpr size_t kRegistersNeededForLong = 2;
   static constexpr size_t kRegistersNeededForDouble = 2;
   static constexpr bool kMultiRegistersAligned = false;  // x86 not using regs, anyways
-  static constexpr bool kMultiFPRegistersWidened = false;
   static constexpr bool kMultiGPRegistersWidened = false;
   static constexpr bool kAlignLongOnStack = false;
   static constexpr bool kAlignDoubleOnStack = false;
+  static constexpr bool kNaNBoxing = false;
 #elif defined(__x86_64__)
   static constexpr bool kNativeSoftFloatAbi = false;  // This is a hard float ABI.
   static constexpr size_t kNumNativeGprArgs = 6;  // 6 arguments passed in GPRs.
@@ -1478,10 +1395,10 @@
   static constexpr size_t kRegistersNeededForLong = 1;
   static constexpr size_t kRegistersNeededForDouble = 1;
   static constexpr bool kMultiRegistersAligned = false;
-  static constexpr bool kMultiFPRegistersWidened = false;
   static constexpr bool kMultiGPRegistersWidened = false;
   static constexpr bool kAlignLongOnStack = false;
   static constexpr bool kAlignDoubleOnStack = false;
+  static constexpr bool kNaNBoxing = false;
 #else
 #error "Unsupported architecture"
 #endif
@@ -1597,8 +1514,10 @@
       if (HaveFloatFpr()) {
         fpr_index_--;
         if (kRegistersNeededForDouble == 1) {
-          if (kMultiFPRegistersWidened) {
-            PushFpr8(bit_cast<uint64_t, double>(val));
+          if (kNaNBoxing) {
+            // NaN boxing: no widening, just use the bits, but reset upper bits to 1s.
+            // See e.g. RISC-V manual, D extension, section "NaN Boxing of Narrower Values".
+            PushFpr8(0xFFFFFFFF00000000lu | static_cast<uint64_t>(bit_cast<uint32_t, float>(val)));
           } else {
             // No widening, just use the bits.
             PushFpr8(static_cast<uint64_t>(bit_cast<uint32_t, float>(val)));
@@ -1608,14 +1527,7 @@
         }
       } else {
         stack_entries_++;
-        if (kRegistersNeededForDouble == 1 && kMultiFPRegistersWidened) {
-          // Need to widen before storing: Note the "double" in the template instantiation.
-          // Note: We need to jump through those hoops to make the compiler happy.
-          DCHECK_EQ(sizeof(uintptr_t), sizeof(uint64_t));
-          PushStack(static_cast<uintptr_t>(bit_cast<uint64_t, double>(val)));
-        } else {
-          PushStack(static_cast<uintptr_t>(bit_cast<uint32_t, float>(val)));
-        }
+        PushStack(static_cast<uintptr_t>(bit_cast<uint32_t, float>(val)));
         fpr_index_ = 0;
       }
     }
@@ -1795,7 +1707,7 @@
 
     // Add space for cookie.
     DCHECK_ALIGNED(managed_sp, sizeof(uintptr_t));
-    static_assert(sizeof(uintptr_t) >= sizeof(IRTSegmentState));
+    static_assert(sizeof(uintptr_t) >= sizeof(jni::LRTSegmentState));
     uint8_t* sp8 = reinterpret_cast<uint8_t*>(managed_sp) - sizeof(uintptr_t);
 
     // Layout stack arguments.
@@ -1903,10 +1815,10 @@
                               uint32_t shorty_len,
                               ArtMethod** managed_sp,
                               uintptr_t* reserved_area)
-     : QuickArgumentVisitor(managed_sp, is_static, shorty, shorty_len),
-       jni_call_(nullptr, nullptr, nullptr, critical_native),
-       sm_(&jni_call_),
-       current_vreg_(nullptr) {
+      : QuickArgumentVisitor(managed_sp, is_static, shorty, shorty_len),
+        jni_call_(nullptr, nullptr, nullptr),
+        sm_(&jni_call_),
+        current_vreg_(nullptr) {
     DCHECK_ALIGNED(managed_sp, kStackAlignment);
     DCHECK_ALIGNED(reserved_area, sizeof(uintptr_t));
 
@@ -1944,7 +1856,7 @@
         // The declaring class must be marked.
         auto* declaring_class = reinterpret_cast<mirror::CompressedReference<mirror::Class>*>(
             method->GetDeclaringClassAddressWithoutBarrier());
-        if (kUseReadBarrier) {
+        if (gUseReadBarrier) {
           artJniReadBarrier(method);
         }
         sm_.AdvancePointer(declaring_class);
@@ -1955,33 +1867,8 @@
   void Visit() REQUIRES_SHARED(Locks::mutator_lock_) override;
 
  private:
-  // A class to fill a JNI call. Adds reference/handle-scope management to FillNativeCall.
-  class FillJniCall final : public FillNativeCall {
-   public:
-    FillJniCall(uintptr_t* gpr_regs,
-                uint32_t* fpr_regs,
-                uintptr_t* stack_args,
-                bool critical_native)
-        : FillNativeCall(gpr_regs, fpr_regs, stack_args),
-          cur_entry_(0),
-          critical_native_(critical_native) {}
-
-    void Reset(uintptr_t* gpr_regs, uint32_t* fpr_regs, uintptr_t* stack_args) {
-      FillNativeCall::Reset(gpr_regs, fpr_regs, stack_args);
-      cur_entry_ = 0U;
-    }
-
-    bool CriticalNative() const {
-      return critical_native_;
-    }
-
-   private:
-    size_t cur_entry_;
-    const bool critical_native_;
-  };
-
-  FillJniCall jni_call_;
-  BuildNativeCallFrameStateMachine<FillJniCall> sm_;
+  FillNativeCall jni_call_;
+  BuildNativeCallFrameStateMachine<FillNativeCall> sm_;
 
   // Pointer to the current vreg in caller's reserved out vreg area.
   // Used for spilling reference arguments.
@@ -2091,7 +1978,7 @@
   }
 
   // Fix up managed-stack things in Thread. After this we can walk the stack.
-  self->SetTopOfStackTagged(managed_sp);
+  self->SetTopOfStackGenericJniTagged(managed_sp);
 
   self->VerifyStack();
 
@@ -2104,16 +1991,21 @@
   // We can set the entrypoint of a native method to generic JNI even when the
   // class hasn't been initialized, so we need to do the initialization check
   // before invoking the native code.
-  if (NeedsClinitCheckBeforeCall(called)) {
-    ObjPtr<mirror::Class> declaring_class = called->GetDeclaringClass();
-    if (UNLIKELY(!declaring_class->IsVisiblyInitialized())) {
-      // Ensure static method's class is initialized.
-      StackHandleScope<1> hs(self);
-      Handle<mirror::Class> h_class(hs.NewHandle(declaring_class));
-      if (!runtime->GetClassLinker()->EnsureInitialized(self, h_class, true, true)) {
-        DCHECK(Thread::Current()->IsExceptionPending()) << called->PrettyMethod();
-        return nullptr;  // Report error.
-      }
+  if (called->StillNeedsClinitCheck()) {
+    // Ensure static method's class is initialized.
+    StackHandleScope<1> hs(self);
+    Handle<mirror::Class> h_class = hs.NewHandle(called->GetDeclaringClass());
+    if (!runtime->GetClassLinker()->EnsureInitialized(self, h_class, true, true)) {
+      DCHECK(Thread::Current()->IsExceptionPending()) << called->PrettyMethod();
+      return nullptr;  // Report error.
+    }
+  }
+
+  instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+  if (UNLIKELY(instr->HasMethodEntryListeners())) {
+    instr->MethodEnterEvent(self, called);
+    if (self->IsExceptionPending()) {
+      return nullptr;
     }
   }
 
@@ -2185,75 +2077,13 @@
   // anything that requires a mutator lock before that would cause problems as GC may have the
   // exclusive mutator lock and may be moving objects, etc.
   ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
-  DCHECK(self->GetManagedStack()->GetTopQuickFrameTag());
+  DCHECK(self->GetManagedStack()->GetTopQuickFrameGenericJniTag());
   uint32_t* sp32 = reinterpret_cast<uint32_t*>(sp);
   ArtMethod* called = *sp;
   uint32_t cookie = *(sp32 - 1);
   return GenericJniMethodEnd(self, cookie, result, result_f, called);
 }
 
-// Fast path method resolution that can't throw exceptions.
-template <InvokeType type>
-inline ArtMethod* FindMethodFast(uint32_t method_idx,
-                                 ObjPtr<mirror::Object> this_object,
-                                 ArtMethod* referrer)
-    REQUIRES_SHARED(Locks::mutator_lock_)
-    REQUIRES(!Roles::uninterruptible_) {
-  ScopedAssertNoThreadSuspension ants(__FUNCTION__);
-  if (UNLIKELY(this_object == nullptr && type != kStatic)) {
-    return nullptr;
-  }
-  ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
-  ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
-  constexpr ClassLinker::ResolveMode resolve_mode = ClassLinker::ResolveMode::kCheckICCEAndIAE;
-  ClassLinker* linker = Runtime::Current()->GetClassLinker();
-  ArtMethod* resolved_method = linker->GetResolvedMethod<type, resolve_mode>(method_idx, referrer);
-  if (UNLIKELY(resolved_method == nullptr)) {
-    return nullptr;
-  }
-  if (type == kInterface) {  // Most common form of slow path dispatch.
-    return this_object->GetClass()->FindVirtualMethodForInterface(resolved_method,
-                                                                  kRuntimePointerSize);
-  }
-  if (type == kStatic || type == kDirect) {
-    return resolved_method;
-  }
-
-  if (type == kSuper) {
-    // TODO This lookup is rather slow.
-    dex::TypeIndex method_type_idx = dex_cache->GetDexFile()->GetMethodId(method_idx).class_idx_;
-    ObjPtr<mirror::Class> method_reference_class = linker->LookupResolvedType(
-        method_type_idx, dex_cache, referrer->GetClassLoader());
-    if (method_reference_class == nullptr) {
-      // Need to do full type resolution...
-      return nullptr;
-    }
-
-    // If the referring class is in the class hierarchy of the
-    // referenced class in the bytecode, we use its super class. Otherwise, we cannot
-    // resolve the method.
-    if (!method_reference_class->IsAssignableFrom(referring_class)) {
-      return nullptr;
-    }
-
-    if (method_reference_class->IsInterface()) {
-      return method_reference_class->FindVirtualMethodForInterfaceSuper(
-          resolved_method, kRuntimePointerSize);
-    }
-
-    ObjPtr<mirror::Class> super_class = referring_class->GetSuperClass();
-    if (resolved_method->GetMethodIndex() >= super_class->GetVTableLength()) {
-      // The super class does not have the method.
-      return nullptr;
-    }
-    return super_class->GetVTableEntry(resolved_method->GetMethodIndex(), kRuntimePointerSize);
-  }
-
-  DCHECK(type == kVirtual);
-  return this_object->GetClass()->GetVTableEntry(
-      resolved_method->GetMethodIndex(), kRuntimePointerSize);
-}
-
 // We use TwoWordReturn to optimize scalar returns. We use the hi value for code, and the lo value
 // for the method pointer.
 //
@@ -2267,19 +2097,36 @@
                                      ArtMethod** sp) {
   ScopedQuickEntrypointChecks sqec(self);
   DCHECK_EQ(*sp, Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
-  ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethod(sp);
-  ArtMethod* method = FindMethodFast<type>(method_idx, this_object, caller_method);
+  uint32_t dex_pc;
+  ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethodAndDexPc(sp, &dex_pc);
+  CodeItemInstructionAccessor accessor(caller_method->DexInstructions());
+  DCHECK_LT(dex_pc, accessor.InsnsSizeInCodeUnits());
+  const Instruction& instr = accessor.InstructionAt(dex_pc);
+  bool string_init = false;
+  ArtMethod* method = FindMethodToCall<type>(
+      self, caller_method, &this_object, instr, /* only_lookup_tls_cache= */ true, &string_init);
+
   if (UNLIKELY(method == nullptr)) {
+    if (self->IsExceptionPending()) {
+      // Return a failure if the first lookup threw an exception.
+      return GetTwoWordFailureValue();  // Failure.
+    }
     const DexFile* dex_file = caller_method->GetDexFile();
     uint32_t shorty_len;
     const char* shorty = dex_file->GetMethodShorty(dex_file->GetMethodId(method_idx), &shorty_len);
     {
-      // Remember the args in case a GC happens in FindMethodFromCode.
+      // Remember the args in case a GC happens in FindMethodToCall.
       ScopedObjectAccessUnchecked soa(self->GetJniEnv());
       RememberForGcArgumentVisitor visitor(sp, type == kStatic, shorty, shorty_len, &soa);
       visitor.VisitArguments();
-      method = FindMethodFromCode<type, /*access_check=*/true>(
-          method_idx, &this_object, caller_method, self);
+
+      method = FindMethodToCall<type>(self,
+                                      caller_method,
+                                      &this_object,
+                                      instr,
+                                      /* only_lookup_tls_cache= */ false,
+                                      &string_init);
+
       visitor.FixupReferences();
     }
 
@@ -2364,9 +2211,9 @@
     // Fetch the dex_method_idx of the target interface method from the caller.
     StackHandleScope<1> hs(self);
     Handle<mirror::Object> this_object = hs.NewHandle(raw_this_object);
-    ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethod(sp);
+    uint32_t dex_pc;
+    ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethodAndDexPc(sp, &dex_pc);
     uint32_t dex_method_idx;
-    uint32_t dex_pc = QuickArgumentVisitor::GetCallingDexPc(sp);
     const Instruction& instr = caller_method->DexInstructions().InstructionAt(dex_pc);
     Instruction::Code instr_code = instr.Opcode();
     DCHECK(instr_code == Instruction::INVOKE_INTERFACE ||
@@ -2485,8 +2332,8 @@
   const char* old_cause = self->StartAssertNoThreadSuspension("Making stack arguments safe.");
 
   // From the instruction, get the |callsite_shorty| and expose arguments on the stack to the GC.
-  ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethod(sp);
-  uint32_t dex_pc = QuickArgumentVisitor::GetCallingDexPc(sp);
+  uint32_t dex_pc;
+  ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethodAndDexPc(sp, &dex_pc);
   const Instruction& inst = caller_method->DexInstructions().InstructionAt(dex_pc);
   DCHECK(inst.Opcode() == Instruction::INVOKE_POLYMORPHIC ||
          inst.Opcode() == Instruction::INVOKE_POLYMORPHIC_RANGE);
@@ -2527,10 +2374,9 @@
   const size_t num_vregs = is_range ? inst.VRegA_4rcc() : inst.VRegA_45cc();
   const size_t first_arg = 0;
   ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
-      CREATE_SHADOW_FRAME(num_vregs, /* link= */ nullptr, resolved_method, dex_pc);
+      CREATE_SHADOW_FRAME(num_vregs, resolved_method, dex_pc);
   ShadowFrame* shadow_frame = shadow_frame_unique_ptr.get();
-  ScopedStackedShadowFramePusher
-      frame_pusher(self, shadow_frame, StackedShadowFrameType::kShadowFrameUnderConstruction);
+  ScopedStackedShadowFramePusher frame_pusher(self, shadow_frame);
   BuildQuickShadowFrameVisitor shadow_frame_builder(sp,
                                                     kMethodIsStatic,
                                                     shorty,
@@ -2589,6 +2435,10 @@
   // Pop transition record.
   self->PopManagedStackFragment(fragment);
 
+  bool is_ref = (shorty[0] == 'L');
+  Runtime::Current()->GetInstrumentation()->PushDeoptContextIfNeeded(
+      self, DeoptimizationMethodType::kDefault, is_ref, result);
+
   return result.GetJ();
 }
 
@@ -2609,8 +2459,8 @@
   const char* old_cause = self->StartAssertNoThreadSuspension("Making stack arguments safe.");
 
   // From the instruction, get the |callsite_shorty| and expose arguments on the stack to the GC.
-  ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethod(sp);
-  uint32_t dex_pc = QuickArgumentVisitor::GetCallingDexPc(sp);
+  uint32_t dex_pc;
+  ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethodAndDexPc(sp, &dex_pc);
   const DexFile* dex_file = caller_method->GetDexFile();
   const dex::ProtoIndex proto_idx(dex_file->GetProtoIndexForCallSite(call_site_idx));
   const char* shorty = caller_method->GetDexFile()->GetShorty(proto_idx);
@@ -2620,10 +2470,9 @@
   const size_t first_arg = 0;
   const size_t num_vregs = ArtMethod::NumArgRegisters(shorty);
   ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
-      CREATE_SHADOW_FRAME(num_vregs, /* link= */ nullptr, caller_method, dex_pc);
+      CREATE_SHADOW_FRAME(num_vregs, caller_method, dex_pc);
   ShadowFrame* shadow_frame = shadow_frame_unique_ptr.get();
-  ScopedStackedShadowFramePusher
-      frame_pusher(self, shadow_frame, StackedShadowFrameType::kShadowFrameUnderConstruction);
+  ScopedStackedShadowFramePusher frame_pusher(self, shadow_frame);
   BuildQuickShadowFrameVisitor shadow_frame_builder(sp,
                                                     kMethodIsStatic,
                                                     shorty,
@@ -2647,40 +2496,61 @@
   // Pop transition record.
   self->PopManagedStackFragment(fragment);
 
+  bool is_ref = (shorty[0] == 'L');
+  Runtime::Current()->GetInstrumentation()->PushDeoptContextIfNeeded(
+      self, DeoptimizationMethodType::kDefault, is_ref, result);
+
   return result.GetJ();
 }
 
-extern "C" void artMethodEntryHook(ArtMethod* method, Thread* self, ArtMethod** sp ATTRIBUTE_UNUSED)
+extern "C" void artJniMethodEntryHook(Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+  ArtMethod* method = *self->GetManagedStack()->GetTopQuickFrame();
   instr->MethodEnterEvent(self, method);
-  if (instr->IsDeoptimized(method)) {
-    // Instrumentation can request deoptimizing only a particular method (for
-    // ex: when there are break points on the method). In such cases deoptimize
-    // only this method. FullFrame deoptimizations are handled on method exits.
-    artDeoptimizeFromCompiledCode(DeoptimizationKind::kDebugging, self);
+}
+
+extern "C" void artMethodEntryHook(ArtMethod* method, Thread* self, ArtMethod** sp)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+  if (instr->HasMethodEntryListeners()) {
+    instr->MethodEnterEvent(self, method);
+    // MethodEnter callback could have requested a deopt for ex: by setting a breakpoint, so
+    // check if we need a deopt here.
+    if (instr->ShouldDeoptimizeCaller(self, sp) || instr->IsDeoptimized(method)) {
+      // Instrumentation can request deoptimizing only a particular method (for ex: when
+      // there are break points on the method). In such cases deoptimize only this method.
+      // FullFrame deoptimizations are handled on method exits.
+      artDeoptimizeFromCompiledCode(DeoptimizationKind::kDebugging, self);
+    }
+  } else {
+    DCHECK(!instr->IsDeoptimized(method));
   }
 }
 
-extern "C" int artMethodExitHook(Thread* self,
-                                 ArtMethod* method,
-                                 uint64_t* gpr_result,
-                                 uint64_t* fpr_result)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
+extern "C" void artMethodExitHook(Thread* self,
+                                  ArtMethod** sp,
+                                  uint64_t* gpr_result,
+                                  uint64_t* fpr_result,
+                                  uint32_t frame_size)
+  REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK_EQ(reinterpret_cast<uintptr_t>(self), reinterpret_cast<uintptr_t>(Thread::Current()));
-  CHECK(gpr_result != nullptr);
-  CHECK(fpr_result != nullptr);
   // Instrumentation exit stub must not be entered with a pending exception.
   CHECK(!self->IsExceptionPending())
       << "Enter instrumentation exit stub with pending exception " << self->GetException()->Dump();
 
   instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
-  DCHECK(instr->AreExitStubsInstalled());
-  bool is_ref;
-  JValue return_value = instr->GetReturnValue(self, method, &is_ref, gpr_result, fpr_result);
-  bool deoptimize = false;
-  {
+  DCHECK(instr->RunExitHooks());
+
+  bool is_ref = false;
+  ArtMethod* method = *sp;
+  if (instr->HasMethodExitListeners()) {
     StackHandleScope<1> hs(self);
+
+    CHECK(gpr_result != nullptr);
+    CHECK(fpr_result != nullptr);
+
+    JValue return_value = instr->GetReturnValue(method, &is_ref, gpr_result, fpr_result);
     MutableHandle<mirror::Object> res(hs.NewHandle<mirror::Object>(nullptr));
     if (is_ref) {
       // Take a handle to the return value so we won't lose it if we suspend.
@@ -2688,20 +2558,10 @@
     }
     DCHECK(!method->IsRuntimeMethod());
 
-    // Deoptimize if the caller needs to continue execution in the interpreter. Do nothing if we get
-    // back to an upcall.
-    NthCallerVisitor visitor(self, 1, /*include_runtime_and_upcalls=*/false);
-    visitor.WalkStack(true);
-    deoptimize = instr->ShouldDeoptimizeMethod(self, visitor);
-
     // If we need a deoptimization MethodExitEvent will be called by the interpreter when it
-    // re-executes the return instruction.
-    if (!deoptimize) {
-      instr->MethodExitEvent(self,
-                             method,
-                             /* frame= */ {},
-                             return_value);
-    }
+    // re-executes the return instruction. For native methods we have to process method exit
+    // events here since deoptimization just removes the native frame.
+    instr->MethodExitEvent(self, method, /* frame= */ {}, return_value);
 
     if (is_ref) {
       // Restore the return value if it's a reference since it might have moved.
@@ -2711,17 +2571,27 @@
   }
 
   if (self->IsExceptionPending() || self->ObserveAsyncException()) {
-    return 1;
-  }
-
-  if (deoptimize) {
-    DeoptimizationMethodType deopt_method_type = instr->GetDeoptimizationMethodType(method);
-    self->PushDeoptimizationContext(return_value, is_ref, nullptr, false, deopt_method_type);
-    artDeoptimize(self);
+    // The exception was thrown from the method exit callback. We should not call method unwind
+    // callbacks for this case.
+    self->QuickDeliverException(/* is_method_exit_exception= */ true);
     UNREACHABLE();
   }
 
-  return 0;
+  // We should deoptimize here if the caller requires a deoptimization or if the current method
+  // needs a deoptimization. We may need deoptimization for the current method if method exit
+  // hooks requested this frame to be popped. IsForcedInterpreterNeededForUpcall checks for that.
+  const bool deoptimize = instr->ShouldDeoptimizeCaller(self, sp, frame_size) ||
+                          Dbg::IsForcedInterpreterNeededForUpcall(self, method);
+  if (deoptimize) {
+    JValue ret_val = instr->GetReturnValue(method, &is_ref, gpr_result, fpr_result);
+    DeoptimizationMethodType deopt_method_type = instr->GetDeoptimizationMethodType(method);
+    self->PushDeoptimizationContext(
+        ret_val, is_ref, self->GetException(), false, deopt_method_type);
+    // Method exit callback has already been run for this method. So tell the deoptimizer to skip
+    // callbacks for this frame.
+    artDeoptimize(self, /*skip_method_exit_callbacks = */ true);
+    UNREACHABLE();
+  }
 }
 
 }  // namespace art
diff --git a/runtime/entrypoints/runtime_asm_entrypoints.h b/runtime/entrypoints/runtime_asm_entrypoints.h
index c4e62e5..951398d 100644
--- a/runtime/entrypoints/runtime_asm_entrypoints.h
+++ b/runtime/entrypoints/runtime_asm_entrypoints.h
@@ -79,21 +79,9 @@
   return reinterpret_cast<const void*>(art_quick_deoptimize);
 }
 
-// Return address of instrumentation entry point used by non-interpreter based tracing.
-extern "C" void art_quick_instrumentation_entry(void*);
-static inline const void* GetQuickInstrumentationEntryPoint() {
-  return reinterpret_cast<const void*>(art_quick_instrumentation_entry);
-}
-
 // Stub to deoptimize from compiled code.
 extern "C" void art_quick_deoptimize_from_compiled_code(DeoptimizationKind);
 
-// The return_pc of instrumentation exit stub.
-extern "C" void art_quick_instrumentation_exit();
-static inline const void* GetQuickInstrumentationExitPc() {
-  return reinterpret_cast<const void*>(art_quick_instrumentation_exit);
-}
-
 extern "C" void* art_quick_string_builder_append(uint32_t format);
 extern "C" void art_quick_compile_optimized(ArtMethod*, Thread*);
 extern "C" void art_quick_method_entry_hook(ArtMethod*, Thread*);
diff --git a/runtime/entrypoints_order_test.cc b/runtime/entrypoints_order_test.cc
index 240ecbd..be1c58d 100644
--- a/runtime/entrypoints_order_test.cc
+++ b/runtime/entrypoints_order_test.cc
@@ -16,8 +16,8 @@
 
 #include <memory>
 
+#include "base/common_art_test.h"
 #include "base/macros.h"
-#include "common_runtime_test.h"
 #include "thread.h"
 
 // This test checks the offsets of values in the thread TLS and entrypoint structures. A failure
@@ -59,7 +59,7 @@
 #define EXPECT_OFFSET_DIFF_GT3(type, first_field, second_field, diff, name) \
   EXPECT_OFFSET_DIFF_GT(type, first_field, type, second_field, diff, name)
 
-class EntrypointsOrderTest : public CommonRuntimeTest {
+class EntrypointsOrderTest : public CommonArtTest {
  protected:
   void CheckThreadOffsets() {
     CHECKED(OFFSETOF_MEMBER(Thread, tls32_.state_and_flags) == 0, thread_flags_at_zero);
@@ -98,9 +98,8 @@
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, monitor_enter_object, top_handle_scope, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, top_handle_scope, class_loader_override, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, class_loader_override, long_jump_context, sizeof(void*));
-    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, long_jump_context, instrumentation_stack, sizeof(void*));
-    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, instrumentation_stack, stacked_shadow_frame_record,
-                        sizeof(void*));
+    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, long_jump_context,
+                        stacked_shadow_frame_record, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, stacked_shadow_frame_record,
                         deoptimization_context_stack, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, deoptimization_context_stack,
@@ -109,9 +108,7 @@
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, name, pthread_self, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, pthread_self, last_no_thread_suspension_cause,
                         sizeof(void*));
-    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, last_no_thread_suspension_cause, checkpoint_function,
-                        sizeof(void*));
-    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, checkpoint_function, active_suspend_barriers,
+    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, last_no_thread_suspension_cause, active_suspend_barriers,
                         sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, active_suspend_barriers, thread_local_start,
                         sizeof(Thread::tls_ptr_sized_values::active_suspend_barriers));
@@ -119,7 +116,9 @@
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_pos, thread_local_end, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_end, thread_local_limit, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_limit, thread_local_objects, sizeof(void*));
-    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_objects, jni_entrypoints, sizeof(size_t));
+    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_objects, checkpoint_function, sizeof(size_t));
+    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, checkpoint_function, jni_entrypoints,
+                        sizeof(void*));
 
     // Skip across the entrypoints structures.
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, rosalloc_runs, thread_local_alloc_stack_top,
@@ -135,10 +134,14 @@
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_mark_stack, async_exception, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, async_exception, top_reflective_handle_scope,
                         sizeof(void*));
+    EXPECT_OFFSET_DIFFP(
+        Thread, tlsPtr_, top_reflective_handle_scope, method_trace_buffer, sizeof(void*));
+    EXPECT_OFFSET_DIFFP(
+        Thread, tlsPtr_, method_trace_buffer, method_trace_buffer_index, sizeof(void*));
     // The first field after tlsPtr_ is forced to a 16 byte alignment so it might have some space.
     auto offset_tlsptr_end = OFFSETOF_MEMBER(Thread, tlsPtr_) +
         sizeof(decltype(reinterpret_cast<Thread*>(16)->tlsPtr_));
-    CHECKED(offset_tlsptr_end - OFFSETOF_MEMBER(Thread, tlsPtr_.top_reflective_handle_scope) ==
+    CHECKED(offset_tlsptr_end - OFFSETOF_MEMBER(Thread, tlsPtr_.method_trace_buffer_index) ==
                 sizeof(void*),
             "async_exception last field");
   }
@@ -225,7 +228,9 @@
                          pJniUnlockObject, sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pJniUnlockObject,
                          pQuickGenericJniTrampoline, sizeof(void*));
-    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pQuickGenericJniTrampoline, pLockObject, sizeof(void*));
+    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pQuickGenericJniTrampoline,
+                         pJniMethodEntryHook, sizeof(void*));
+    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pJniMethodEntryHook, pLockObject, sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pLockObject, pUnlockObject, sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pUnlockObject, pCmpgDouble, sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pCmpgDouble, pCmpgFloat, sizeof(void*));
@@ -299,7 +304,9 @@
 
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pA64Store, pNewEmptyString, sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pNewEmptyString, pNewStringFromBytes_B, sizeof(void*));
-    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pNewStringFromBytes_B, pNewStringFromBytes_BI,
+    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pNewStringFromBytes_B, pNewStringFromBytes_BB,
+                         sizeof(void*));
+    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pNewStringFromBytes_BB, pNewStringFromBytes_BI,
                          sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pNewStringFromBytes_BI, pNewStringFromBytes_BII,
                          sizeof(void*));
@@ -327,7 +334,9 @@
                          sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pNewStringFromStringBuffer, pNewStringFromStringBuilder,
                          sizeof(void*));
-    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pNewStringFromStringBuilder, pStringBuilderAppend,
+    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pNewStringFromStringBuilder, pNewStringFromUtf16Bytes_BII,
+                         sizeof(void*));
+    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pNewStringFromUtf16Bytes_BII, pStringBuilderAppend,
                          sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pStringBuilderAppend, pUpdateInlineCache,
                          sizeof(void*));
diff --git a/runtime/exec_utils.cc b/runtime/exec_utils.cc
index 463d458..1021429 100644
--- a/runtime/exec_utils.cc
+++ b/runtime/exec_utils.cc
@@ -16,22 +16,46 @@
 
 #include "exec_utils.h"
 
+#include <poll.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <unistd.h>
+
+#include <ctime>
+#include <string_view>
+
+#ifdef __BIONIC__
+#include <sys/pidfd.h>
+#endif
+
+#include <chrono>
+#include <climits>
+#include <condition_variable>
+#include <cstdint>
+#include <mutex>
 #include <string>
+#include <thread>
 #include <vector>
 
+#include "android-base/file.h"
+#include "android-base/parseint.h"
+#include "android-base/scopeguard.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
-
+#include "android-base/unique_fd.h"
+#include "base/macros.h"
+#include "base/utils.h"
 #include "runtime.h"
 
 namespace art {
 
-using android::base::StringPrintf;
-
 namespace {
 
+using ::android::base::ParseInt;
+using ::android::base::ReadFileToString;
+using ::android::base::StringPrintf;
+using ::android::base::unique_fd;
+
 std::string ToCommandLine(const std::vector<std::string>& args) {
   return android::base::Join(args, ' ');
 }
@@ -40,10 +64,11 @@
 // If there is a runtime (Runtime::Current != nullptr) then the subprocess is created with the
 // same environment that existed when the runtime was started.
 // Returns the process id of the child process on success, -1 otherwise.
-pid_t ExecWithoutWait(std::vector<std::string>& arg_vector) {
+pid_t ExecWithoutWait(const std::vector<std::string>& arg_vector, std::string* error_msg) {
   // Convert the args to char pointers.
   const char* program = arg_vector[0].c_str();
   std::vector<char*> args;
+  args.reserve(arg_vector.size() + 1);
   for (const auto& arg : arg_vector) {
     args.push_back(const_cast<char*>(arg.c_str()));
   }
@@ -65,110 +90,274 @@
     } else {
       execve(program, &args[0], envp);
     }
-    PLOG(ERROR) << "Failed to execve(" << ToCommandLine(arg_vector) << ")";
-    // _exit to avoid atexit handlers in child.
-    _exit(1);
+    // This should be regarded as a crash rather than a normal return.
+    PLOG(FATAL) << "Failed to execute (" << ToCommandLine(arg_vector) << ")";
+    UNREACHABLE();
+  } else if (pid == -1) {
+    *error_msg = StringPrintf("Failed to execute (%s) because fork failed: %s",
+                              ToCommandLine(arg_vector).c_str(),
+                              strerror(errno));
+    return -1;
   } else {
     return pid;
   }
 }
 
+ExecResult WaitChild(pid_t pid,
+                     const std::vector<std::string>& arg_vector,
+                     bool no_wait,
+                     std::string* error_msg) {
+  siginfo_t info;
+  // WNOWAIT leaves the child in a waitable state. The call is still blocking.
+  int options = WEXITED | (no_wait ? WNOWAIT : 0);
+  if (TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, options)) != 0) {
+    *error_msg = StringPrintf("waitid failed for (%s) pid %d: %s",
+                              ToCommandLine(arg_vector).c_str(),
+                              pid,
+                              strerror(errno));
+    return {.status = ExecResult::kUnknown};
+  }
+  if (info.si_pid != pid) {
+    *error_msg = StringPrintf("waitid failed for (%s): wanted pid %d, got %d: %s",
+                              ToCommandLine(arg_vector).c_str(),
+                              pid,
+                              info.si_pid,
+                              strerror(errno));
+    return {.status = ExecResult::kUnknown};
+  }
+  if (info.si_code != CLD_EXITED) {
+    *error_msg =
+        StringPrintf("Failed to execute (%s) because the child process is terminated by signal %d",
+                     ToCommandLine(arg_vector).c_str(),
+                     info.si_status);
+    return {.status = ExecResult::kSignaled, .signal = info.si_status};
+  }
+  return {.status = ExecResult::kExited, .exit_code = info.si_status};
+}
+
+// A fallback implementation of `WaitChildWithTimeout` that creates a thread to wait instead of
+// relying on `pidfd_open`.
+ExecResult WaitChildWithTimeoutFallback(pid_t pid,
+                                        const std::vector<std::string>& arg_vector,
+                                        int timeout_ms,
+                                        std::string* error_msg) {
+  bool child_exited = false;
+  bool timed_out = false;
+  std::condition_variable cv;
+  std::mutex m;
+
+  std::thread wait_thread([&]() {
+    std::unique_lock<std::mutex> lock(m);
+    if (!cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [&] { return child_exited; })) {
+      timed_out = true;
+      kill(pid, SIGKILL);
+    }
+  });
+
+  ExecResult result = WaitChild(pid, arg_vector, /*no_wait=*/true, error_msg);
+
+  {
+    std::unique_lock<std::mutex> lock(m);
+    child_exited = true;
+  }
+  cv.notify_all();
+  wait_thread.join();
+
+  // The timeout error should have a higher priority than any other error.
+  if (timed_out) {
+    *error_msg =
+        StringPrintf("Failed to execute (%s) because the child process timed out after %dms",
+                     ToCommandLine(arg_vector).c_str(),
+                     timeout_ms);
+    return ExecResult{.status = ExecResult::kTimedOut};
+  }
+
+  return result;
+}
+
+// Waits for the child process to finish and leaves the child in a waitable state.
+ExecResult WaitChildWithTimeout(pid_t pid,
+                                unique_fd pidfd,
+                                const std::vector<std::string>& arg_vector,
+                                int timeout_ms,
+                                std::string* error_msg) {
+  auto cleanup = android::base::make_scope_guard([&]() {
+    kill(pid, SIGKILL);
+    std::string ignored_error_msg;
+    WaitChild(pid, arg_vector, /*no_wait=*/true, &ignored_error_msg);
+  });
+
+  struct pollfd pfd;
+  pfd.fd = pidfd.get();
+  pfd.events = POLLIN;
+  int poll_ret = TEMP_FAILURE_RETRY(poll(&pfd, /*nfds=*/1, timeout_ms));
+
+  pidfd.reset();
+
+  if (poll_ret < 0) {
+    *error_msg = StringPrintf("poll failed for pid %d: %s", pid, strerror(errno));
+    return {.status = ExecResult::kUnknown};
+  }
+  if (poll_ret == 0) {
+    *error_msg =
+        StringPrintf("Failed to execute (%s) because the child process timed out after %dms",
+                     ToCommandLine(arg_vector).c_str(),
+                     timeout_ms);
+    return {.status = ExecResult::kTimedOut};
+  }
+
+  cleanup.Disable();
+  return WaitChild(pid, arg_vector, /*no_wait=*/true, error_msg);
+}
+
+bool ParseProcStat(const std::string& stat_content,
+                   int64_t uptime_ms,
+                   int64_t ticks_per_sec,
+                   /*out*/ ProcessStat* stat) {
+  size_t pos = stat_content.rfind(") ");
+  if (pos == std::string::npos) {
+    return false;
+  }
+  std::vector<std::string> stat_fields;
+  // Skip the first two fields. The second field is the parenthesized process filename, which can
+  // contain anything, including spaces.
+  Split(std::string_view(stat_content).substr(pos + 2), ' ', &stat_fields);
+  constexpr int kSkippedFields = 2;
+  int64_t utime, stime, cutime, cstime, starttime;
+  if (stat_fields.size() < 22 - kSkippedFields ||
+      !ParseInt(stat_fields[13 - kSkippedFields], &utime) ||
+      !ParseInt(stat_fields[14 - kSkippedFields], &stime) ||
+      !ParseInt(stat_fields[15 - kSkippedFields], &cutime) ||
+      !ParseInt(stat_fields[16 - kSkippedFields], &cstime) ||
+      !ParseInt(stat_fields[21 - kSkippedFields], &starttime)) {
+    return false;
+  }
+  stat->cpu_time_ms = (utime + stime + cutime + cstime) * 1000 / ticks_per_sec;
+  stat->wall_time_ms = uptime_ms - starttime * 1000 / ticks_per_sec;
+  return true;
+}
+
 }  // namespace
 
-int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_msg) {
-  pid_t pid = ExecWithoutWait(arg_vector);
-  if (pid == -1) {
-    *error_msg = StringPrintf("Failed to execv(%s) because fork failed: %s",
-                              ToCommandLine(arg_vector).c_str(), strerror(errno));
-    return -1;
-  }
-
-  // wait for subprocess to finish
-  int status = -1;
-  pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
-  if (got_pid != pid) {
-    *error_msg = StringPrintf("Failed after fork for execv(%s) because waitpid failed: "
-                              "wanted %d, got %d: %s",
-                              ToCommandLine(arg_vector).c_str(), pid, got_pid, strerror(errno));
-    return -1;
-  }
-  if (WIFEXITED(status)) {
-    return WEXITSTATUS(status);
-  }
-  return -1;
+int ExecUtils::ExecAndReturnCode(const std::vector<std::string>& arg_vector,
+                                 std::string* error_msg) const {
+  return ExecAndReturnResult(arg_vector, /*timeout_sec=*/-1, error_msg).exit_code;
 }
 
-int ExecAndReturnCode(std::vector<std::string>& arg_vector,
-                      time_t timeout_secs,
-                      bool* timed_out,
-                      std::string* error_msg) {
-  *timed_out = false;
+ExecResult ExecUtils::ExecAndReturnResult(const std::vector<std::string>& arg_vector,
+                                          int timeout_sec,
+                                          std::string* error_msg) const {
+  return ExecAndReturnResult(arg_vector, timeout_sec, ExecCallbacks(), /*stat=*/nullptr, error_msg);
+}
+
+ExecResult ExecUtils::ExecAndReturnResult(const std::vector<std::string>& arg_vector,
+                                          int timeout_sec,
+                                          const ExecCallbacks& callbacks,
+                                          /*out*/ ProcessStat* stat,
+                                          /*out*/ std::string* error_msg) const {
+  if (timeout_sec > INT_MAX / 1000) {
+    *error_msg = "Timeout too large";
+    return {.status = ExecResult::kStartFailed};
+  }
 
   // Start subprocess.
-  pid_t pid = ExecWithoutWait(arg_vector);
+  pid_t pid = ExecWithoutWait(arg_vector, error_msg);
   if (pid == -1) {
-    *error_msg = StringPrintf("Failed to execv(%s) because fork failed: %s",
-                              ToCommandLine(arg_vector).c_str(), strerror(errno));
-    return -1;
+    return {.status = ExecResult::kStartFailed};
   }
 
-  // Add SIGCHLD to the signal set.
-  sigset_t child_mask, original_mask;
-  sigemptyset(&child_mask);
-  sigaddset(&child_mask, SIGCHLD);
-  if (sigprocmask(SIG_BLOCK, &child_mask, &original_mask) == -1) {
-    *error_msg = StringPrintf("Failed to set sigprocmask(): %s", strerror(errno));
-    return -1;
-  }
-
-  // Wait for a SIGCHLD notification.
-  errno = 0;
-  timespec ts = {timeout_secs, 0};
-  int wait_result = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts));
-  int wait_errno = errno;
-
-  // Restore the original signal set.
-  if (sigprocmask(SIG_SETMASK, &original_mask, nullptr) == -1) {
-    *error_msg = StringPrintf("Fail to restore sigprocmask(): %s", strerror(errno));
-    if (wait_result == 0) {
-      return -1;
-    }
-  }
-
-  // Having restored the signal set, see if we need to terminate the subprocess.
-  if (wait_result == -1) {
-    if (wait_errno == EAGAIN) {
-      *error_msg = "Timed out.";
-      *timed_out = true;
-    } else {
-      *error_msg = StringPrintf("Failed to sigtimedwait(): %s", strerror(errno));
-    }
-    if (kill(pid, SIGKILL) != 0) {
-      PLOG(ERROR) << "Failed to kill() subprocess: ";
-    }
-  }
+  callbacks.on_start(pid);
 
   // Wait for subprocess to finish.
-  int status = -1;
-  pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
-  if (got_pid != pid) {
-    *error_msg = StringPrintf("Failed after fork for execv(%s) because waitpid failed: "
-                              "wanted %d, got %d: %s",
-                              ToCommandLine(arg_vector).c_str(), pid, got_pid, strerror(errno));
-    return -1;
+  ExecResult result;
+  if (timeout_sec >= 0) {
+    unique_fd pidfd = PidfdOpen(pid);
+    if (pidfd.get() >= 0) {
+      result =
+          WaitChildWithTimeout(pid, std::move(pidfd), arg_vector, timeout_sec * 1000, error_msg);
+    } else {
+      LOG(DEBUG) << StringPrintf(
+          "pidfd_open failed for pid %d: %s, falling back", pid, strerror(errno));
+      result = WaitChildWithTimeoutFallback(pid, arg_vector, timeout_sec * 1000, error_msg);
+    }
+  } else {
+    result = WaitChild(pid, arg_vector, /*no_wait=*/true, error_msg);
   }
-  if (WIFEXITED(status)) {
-    return WEXITSTATUS(status);
+
+  if (stat != nullptr) {
+    std::string local_error_msg;
+    if (!GetStat(pid, stat, &local_error_msg)) {
+      LOG(ERROR) << "Failed to get process stat: " << local_error_msg;
+    }
   }
-  return -1;
+
+  callbacks.on_end(pid);
+
+  std::string local_error_msg;
+  // TODO(jiakaiz): Use better logic to detect waitid failure.
+  if (WaitChild(pid, arg_vector, /*no_wait=*/false, &local_error_msg).status ==
+      ExecResult::kUnknown) {
+    LOG(ERROR) << "Failed to clean up child process '" << arg_vector[0] << "': " << local_error_msg;
+  }
+
+  return result;
 }
 
-
-bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {
+bool ExecUtils::Exec(const std::vector<std::string>& arg_vector, std::string* error_msg) const {
   int status = ExecAndReturnCode(arg_vector, error_msg);
-  if (status != 0) {
-    *error_msg = StringPrintf("Failed execv(%s) because non-0 exit status",
-                              ToCommandLine(arg_vector).c_str());
+  if (status < 0) {
+    // Internal error. The error message is already set.
+    return false;
+  }
+  if (status > 0) {
+    *error_msg =
+        StringPrintf("Failed to execute (%s) because the child process returns non-zero exit code",
+                     ToCommandLine(arg_vector).c_str());
+    return false;
+  }
+  return true;
+}
+
+unique_fd ExecUtils::PidfdOpen(pid_t pid) const {
+#ifdef __BIONIC__
+  return unique_fd(pidfd_open(pid, /*flags=*/0));
+#else
+  // There is no glibc wrapper for pidfd_open.
+#ifndef SYS_pidfd_open
+  constexpr int SYS_pidfd_open = 434;
+#endif
+  return unique_fd(syscall(SYS_pidfd_open, pid, /*flags=*/0));
+#endif
+}
+
+std::string ExecUtils::GetProcStat(pid_t pid) const {
+  std::string stat_content;
+  if (!ReadFileToString(StringPrintf("/proc/%d/stat", pid), &stat_content)) {
+    stat_content = "";
+  }
+  return stat_content;
+}
+
+int64_t ExecUtils::GetUptimeMs() const {
+  timespec t;
+  clock_gettime(CLOCK_MONOTONIC, &t);
+  return t.tv_sec * 1000 + t.tv_nsec / 1000000;
+}
+
+int64_t ExecUtils::GetTicksPerSec() const { return sysconf(_SC_CLK_TCK); }
+
+bool ExecUtils::GetStat(pid_t pid,
+                        /*out*/ ProcessStat* stat,
+                        /*out*/ std::string* error_msg) const {
+  int64_t uptime_ms = GetUptimeMs();
+  std::string stat_content = GetProcStat(pid);
+  if (stat_content.empty()) {
+    *error_msg = StringPrintf("Failed to read /proc/%d/stat: %s", pid, strerror(errno));
+    return false;
+  }
+  int64_t ticks_per_sec = GetTicksPerSec();
+  if (!ParseProcStat(stat_content, uptime_ms, ticks_per_sec, stat)) {
+    *error_msg = StringPrintf("Failed to parse /proc/%d/stat '%s'", pid, stat_content.c_str());
     return false;
   }
   return true;
diff --git a/runtime/exec_utils.h b/runtime/exec_utils.h
index 7ce0a9c..b83722f 100644
--- a/runtime/exec_utils.h
+++ b/runtime/exec_utils.h
@@ -19,48 +19,109 @@
 
 #include <time.h>
 
+#include <functional>
 #include <string>
 #include <vector>
 
+#include "android-base/unique_fd.h"
+
 namespace art {
 
+struct ProcessStat {
+  // The total wall time, in milliseconds, that the process spent, or 0 if failed to get the value.
+  int wall_time_ms = 0;
+  // The total CPU time, in milliseconds, that the process and any waited-for children spent, or 0
+  // if failed to get the value.
+  int cpu_time_ms = 0;
+};
+
+struct ExecCallbacks {
+  // Called in the parent process as soon as the child process is forked.
+  std::function<void(pid_t pid)> on_start = [](pid_t) {};
+  // Called in the parent process after the child process exits while still in a waitable state, no
+  // matter the child process succeeds or not.
+  std::function<void(pid_t pid)> on_end = [](pid_t) {};
+};
+
+struct ExecResult {
+  // This struct needs to be in sync with the ExecResultStatus enum contained within the
+  // OdrefreshReported atom in frameworks/proto_logging/atoms/art/odrefresh_extension_atoms.proto.
+  enum Status {
+    // Unable to get the status.
+    kUnknown = 0,
+    // Process exited normally with an exit code.
+    kExited = 1,
+    // Process terminated by a signal.
+    kSignaled = 2,
+    // Process timed out and killed.
+    kTimedOut = 3,
+    // Failed to start the process.
+    kStartFailed = 4,
+    kLast = kStartFailed
+  };
+
+  Status status = kUnknown;
+
+  // The process exit code, if `status` is `kExited`, or -1.
+  int exit_code = -1;
+
+  // The signal that terminated the process, if `status` is `kSignaled`, or 0.
+  int signal = 0;
+};
+
 // Wrapper on fork/execv to run a command in a subprocess.
 // These spawn child processes using the environment as it was set when the single instance
 // of the runtime (Runtime::Current()) was started.  If no instance of the runtime was started, it
 // will use the current environment settings.
-
-bool Exec(std::vector<std::string>& arg_vector, /*out*/ std::string* error_msg);
-int ExecAndReturnCode(std::vector<std::string>& arg_vector, /*out*/ std::string* error_msg);
-
-// Execute the command specified in `argv_vector` in a subprocess with a timeout.
-// Returns the process exit code on success, -1 otherwise.
-int ExecAndReturnCode(std::vector<std::string>& arg_vector,
-                      time_t timeout_secs,
-                      /*out*/ bool* timed_out,
-                      /*out*/ std::string* error_msg);
-
-// A wrapper class to make the functions above mockable.
 class ExecUtils {
  public:
   virtual ~ExecUtils() = default;
 
-  virtual bool Exec(std::vector<std::string>& arg_vector, /*out*/ std::string* error_msg) const {
-    return art::Exec(arg_vector, error_msg);
-  }
+  virtual bool Exec(const std::vector<std::string>& arg_vector,
+                    /*out*/ std::string* error_msg) const;
 
-  virtual int ExecAndReturnCode(std::vector<std::string>& arg_vector,
-                                /*out*/ std::string* error_msg) const {
-    return art::ExecAndReturnCode(arg_vector, error_msg);
-  }
+  virtual int ExecAndReturnCode(const std::vector<std::string>& arg_vector,
+                                /*out*/ std::string* error_msg) const;
 
-  virtual int ExecAndReturnCode(std::vector<std::string>& arg_vector,
-                                time_t timeout_secs,
-                                /*out*/ bool* timed_out,
-                                /*out*/ std::string* error_msg) const {
-    return art::ExecAndReturnCode(arg_vector, timeout_secs, timed_out, error_msg);
-  }
+  // Executes the command specified in `arg_vector` in a subprocess with a timeout.
+  // If `timeout_sec` is negative, blocks until the subprocess exits.
+  // Returns a structured result. If the status is not `kExited`, also returns a non-empty
+  // `error_msg`.
+  virtual ExecResult ExecAndReturnResult(const std::vector<std::string>& arg_vector,
+                                         int timeout_sec,
+                                         /*out*/ std::string* error_msg) const;
+
+  // Same as above, but also collects stat of the process and calls callbacks. The stat is collected
+  // no matter the child process succeeds or not.
+  virtual ExecResult ExecAndReturnResult(const std::vector<std::string>& arg_vector,
+                                         int timeout_sec,
+                                         const ExecCallbacks& callbacks,
+                                         /*out*/ ProcessStat* stat,
+                                         /*out*/ std::string* error_msg) const;
+
+ protected:
+  virtual android::base::unique_fd PidfdOpen(pid_t pid) const;
+
+  // Returns the content of `/proc/<pid>/stat`, or an empty string if failed.
+  virtual std::string GetProcStat(pid_t pid) const;
+
+  virtual int64_t GetUptimeMs() const;
+
+  virtual int64_t GetTicksPerSec() const;
+
+ private:
+  bool GetStat(pid_t pid, /*out*/ ProcessStat* stat, /*out*/ std::string* error_msg) const;
 };
 
+inline bool Exec(const std::vector<std::string>& arg_vector, /*out*/ std::string* error_msg) {
+  return ExecUtils().Exec(arg_vector, error_msg);
+}
+
+inline int ExecAndReturnCode(const std::vector<std::string>& arg_vector,
+                             /*out*/ std::string* error_msg) {
+  return ExecUtils().ExecAndReturnCode(arg_vector, error_msg);
+}
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_EXEC_UTILS_H_
diff --git a/runtime/exec_utils_test.cc b/runtime/exec_utils_test.cc
index dc789aa..e89180b 100644
--- a/runtime/exec_utils_test.cc
+++ b/runtime/exec_utils_test.cc
@@ -16,68 +16,134 @@
 
 #include "exec_utils.h"
 
+#include <sys/utsname.h>
+
+#include <csignal>
+#include <cstring>
+#include <filesystem>
+#include <memory>
+#include <tuple>
+
+#include "android-base/logging.h"
 #include "android-base/stringprintf.h"
 #include "base/file_utils.h"
 #include "base/memory_tool.h"
 #include "common_runtime_test.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
 
 namespace art {
 
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::Gt;
+using ::testing::HasSubstr;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+using ::testing::Ne;
+using ::testing::Return;
+
 std::string PrettyArguments(const char* signature);
 std::string PrettyReturnType(const char* signature);
 
-class ExecUtilsTest : public CommonRuntimeTest {};
-
-TEST_F(ExecUtilsTest, ExecSuccess) {
-  std::vector<std::string> command;
+std::string GetBin(const std::string& name) {
   if (kIsTargetBuild) {
     std::string android_root(GetAndroidRoot());
-    command.push_back(android_root + "/bin/id");
+    return android_root + "/bin/" + name;
+  } else if (std::filesystem::exists("/usr/bin/" + name)) {
+    return "/usr/bin/" + name;
   } else {
-    command.push_back("/usr/bin/id");
+    return "/bin/" + name;
   }
+}
+
+std::tuple<int, int> GetKernelVersion() {
+  std::tuple<int, int> version;
+  utsname uts;
+  CHECK_EQ(uname(&uts), 0);
+  CHECK_EQ(sscanf(uts.release, "%d.%d", &std::get<0>(version), &std::get<1>(version)), 2);
+  return version;
+}
+
+class TestingExecUtils : public ExecUtils {
+ public:
+  MOCK_METHOD(std::string, GetProcStat, (pid_t pid), (const, override));
+  MOCK_METHOD(int64_t, GetUptimeMs, (), (const, override));
+  MOCK_METHOD(int64_t, GetTicksPerSec, (), (const, override));
+};
+
+class AlwaysFallbackExecUtils : public TestingExecUtils {
+ protected:
+  android::base::unique_fd PidfdOpen(pid_t) const override { return android::base::unique_fd(-1); }
+};
+
+class NeverFallbackExecUtils : public TestingExecUtils {
+ protected:
+  android::base::unique_fd PidfdOpen(pid_t pid) const override {
+    android::base::unique_fd pidfd = ExecUtils::PidfdOpen(pid);
+    CHECK_GE(pidfd.get(), 0) << strerror(errno);
+    return pidfd;
+  }
+};
+
+class ExecUtilsTest : public CommonRuntimeTest, public testing::WithParamInterface<bool> {
+ protected:
+  void SetUp() override {
+    CommonRuntimeTest::SetUp();
+    bool always_fallback = GetParam();
+    if (always_fallback) {
+      exec_utils_ = std::make_unique<AlwaysFallbackExecUtils>();
+    } else {
+      if (GetKernelVersion() >= std::make_tuple(5, 4)) {
+        exec_utils_ = std::make_unique<NeverFallbackExecUtils>();
+      } else {
+        GTEST_SKIP() << "Kernel version older than 5.4";
+      }
+    }
+  }
+
+  std::unique_ptr<TestingExecUtils> exec_utils_;
+};
+
+TEST_P(ExecUtilsTest, ExecSuccess) {
+  std::vector<std::string> command;
+  command.push_back(GetBin("id"));
   std::string error_msg;
   // Historical note: Running on Valgrind failed due to some memory
   // that leaks in thread alternate signal stacks.
-  EXPECT_TRUE(Exec(command, &error_msg));
+  EXPECT_TRUE(exec_utils_->Exec(command, &error_msg));
   EXPECT_EQ(0U, error_msg.size()) << error_msg;
 }
 
-TEST_F(ExecUtilsTest, ExecError) {
-  // This will lead to error messages in the log.
-  ScopedLogSeverity sls(LogSeverity::FATAL);
-
+TEST_P(ExecUtilsTest, ExecError) {
   std::vector<std::string> command;
   command.push_back("bogus");
   std::string error_msg;
   // Historical note: Running on Valgrind failed due to some memory
   // that leaks in thread alternate signal stacks.
-  EXPECT_FALSE(Exec(command, &error_msg));
+  ExecResult result = exec_utils_->ExecAndReturnResult(command, /*timeout_sec=*/-1, &error_msg);
+  EXPECT_EQ(result.status, ExecResult::kSignaled);
+  EXPECT_EQ(result.signal, SIGABRT);
   EXPECT_FALSE(error_msg.empty());
 }
 
-TEST_F(ExecUtilsTest, EnvSnapshotAdditionsAreNotVisible) {
+TEST_P(ExecUtilsTest, EnvSnapshotAdditionsAreNotVisible) {
   static constexpr const char* kModifiedVariable = "EXEC_SHOULD_NOT_EXPORT_THIS";
   static constexpr int kOverwrite = 1;
   // Set an variable in the current environment.
   EXPECT_EQ(setenv(kModifiedVariable, "NEVER", kOverwrite), 0);
   // Test that it is not exported.
   std::vector<std::string> command;
-  if (kIsTargetBuild) {
-    std::string android_root(GetAndroidRoot());
-    command.push_back(android_root + "/bin/printenv");
-  } else {
-    command.push_back("/usr/bin/printenv");
-  }
+  command.push_back(GetBin("printenv"));
   command.push_back(kModifiedVariable);
   std::string error_msg;
   // Historical note: Running on Valgrind failed due to some memory
   // that leaks in thread alternate signal stacks.
-  EXPECT_FALSE(Exec(command, &error_msg));
+  EXPECT_FALSE(exec_utils_->Exec(command, &error_msg));
   EXPECT_NE(0U, error_msg.size()) << error_msg;
 }
 
-TEST_F(ExecUtilsTest, EnvSnapshotDeletionsAreNotVisible) {
+TEST_P(ExecUtilsTest, EnvSnapshotDeletionsAreNotVisible) {
   static constexpr const char* kDeletedVariable = "PATH";
   static constexpr int kOverwrite = 1;
   // Save the variable's value.
@@ -87,17 +153,12 @@
   EXPECT_EQ(unsetenv(kDeletedVariable), 0);
   // Test that it is not exported.
   std::vector<std::string> command;
-  if (kIsTargetBuild) {
-    std::string android_root(GetAndroidRoot());
-    command.push_back(android_root + "/bin/printenv");
-  } else {
-    command.push_back("/usr/bin/printenv");
-  }
+  command.push_back(GetBin("printenv"));
   command.push_back(kDeletedVariable);
   std::string error_msg;
   // Historical note: Running on Valgrind failed due to some memory
   // that leaks in thread alternate signal stacks.
-  EXPECT_TRUE(Exec(command, &error_msg));
+  EXPECT_TRUE(exec_utils_->Exec(command, &error_msg));
   EXPECT_EQ(0U, error_msg.size()) << error_msg;
   // Restore the variable's value.
   EXPECT_EQ(setenv(kDeletedVariable, save_value, kOverwrite), 0);
@@ -105,33 +166,106 @@
 
 static std::vector<std::string> SleepCommand(int sleep_seconds) {
   std::vector<std::string> command;
-  if (kIsTargetBuild) {
-    command.push_back(GetAndroidRoot() + "/bin/sleep");
-  } else {
-    command.push_back("/bin/sleep");
-  }
+  command.push_back(GetBin("sleep"));
   command.push_back(android::base::StringPrintf("%d", sleep_seconds));
   return command;
 }
 
-TEST_F(ExecUtilsTest, ExecTimeout) {
+TEST_P(ExecUtilsTest, ExecTimeout) {
   static constexpr int kSleepSeconds = 5;
   static constexpr int kWaitSeconds = 1;
   std::vector<std::string> command = SleepCommand(kSleepSeconds);
   std::string error_msg;
-  bool timed_out;
-  ASSERT_EQ(ExecAndReturnCode(command, kWaitSeconds, &timed_out, &error_msg), -1);
-  EXPECT_TRUE(timed_out);
+  EXPECT_EQ(exec_utils_->ExecAndReturnResult(command, kWaitSeconds, &error_msg).status,
+            ExecResult::kTimedOut)
+      << error_msg;
+  EXPECT_THAT(error_msg, HasSubstr("timed out"));
 }
 
-TEST_F(ExecUtilsTest, ExecNoTimeout) {
+TEST_P(ExecUtilsTest, ExecNoTimeout) {
   static constexpr int kSleepSeconds = 1;
   static constexpr int kWaitSeconds = 5;
   std::vector<std::string> command = SleepCommand(kSleepSeconds);
   std::string error_msg;
-  bool timed_out;
-  ASSERT_EQ(ExecAndReturnCode(command, kWaitSeconds, &timed_out, &error_msg), 0);
-  EXPECT_FALSE(timed_out);
+  EXPECT_EQ(exec_utils_->ExecAndReturnResult(command, kWaitSeconds, &error_msg).status,
+            ExecResult::kExited)
+      << error_msg;
 }
 
+TEST_P(ExecUtilsTest, ExecStat) {
+  std::vector<std::string> command;
+  command.push_back(GetBin("id"));
+
+  std::string error_msg;
+  ProcessStat stat;
+
+  // The process filename is "a) b".
+  EXPECT_CALL(*exec_utils_, GetProcStat(_))
+      .WillOnce(Return(
+          "14963 (a) b) Z 6067 14963 1 0 -1 4228108 105 0 0 0 94 5 0 0 39 19 1 0 162034388 0 0 "
+          "18446744073709551615 0 0 0 0 0 0 20999 0 0 1 0 0 17 71 0 0 0 0 0 0 0 0 0 0 0 0 9"));
+  EXPECT_CALL(*exec_utils_, GetUptimeMs()).WillOnce(Return(1620344887ll));
+  EXPECT_CALL(*exec_utils_, GetTicksPerSec()).WillOnce(Return(100));
+
+  ASSERT_EQ(
+      exec_utils_
+          ->ExecAndReturnResult(command, /*timeout_sec=*/-1, ExecCallbacks(), &stat, &error_msg)
+          .status,
+      ExecResult::kExited)
+      << error_msg;
+
+  EXPECT_EQ(stat.cpu_time_ms, 990);
+  EXPECT_EQ(stat.wall_time_ms, 1007);
+}
+
+TEST_P(ExecUtilsTest, ExecStatFailed) {
+  std::vector<std::string> command = SleepCommand(5);
+
+  std::string error_msg;
+  ProcessStat stat;
+
+  EXPECT_CALL(*exec_utils_, GetProcStat(_))
+      .WillOnce(Return(
+          "14963 (a) b) Z 6067 14963 1 0 -1 4228108 105 0 0 0 94 5 0 0 39 19 1 0 162034388 0 0 "
+          "18446744073709551615 0 0 0 0 0 0 20999 0 0 1 0 0 17 71 0 0 0 0 0 0 0 0 0 0 0 0 9"));
+  EXPECT_CALL(*exec_utils_, GetUptimeMs()).WillOnce(Return(1620344887ll));
+  EXPECT_CALL(*exec_utils_, GetTicksPerSec()).WillOnce(Return(100));
+
+  // This will always time out.
+  ASSERT_EQ(
+      exec_utils_
+          ->ExecAndReturnResult(command, /*timeout_sec=*/1, ExecCallbacks(), &stat, &error_msg)
+          .status,
+      ExecResult::kTimedOut);
+
+  EXPECT_EQ(stat.cpu_time_ms, 990);
+  EXPECT_EQ(stat.wall_time_ms, 1007);
+}
+
+TEST_P(ExecUtilsTest, ExecCallbacks) {
+  MockFunction<void(pid_t)> on_start;
+  MockFunction<void(pid_t)> on_end;
+
+  {
+    InSequence s;
+    EXPECT_CALL(on_start, Call(AllOf(Gt(0), Ne(getpid()))));
+    EXPECT_CALL(on_end, Call(AllOf(Gt(0), Ne(getpid()))));
+  }
+
+  std::vector<std::string> command;
+  command.push_back(GetBin("id"));
+
+  std::string error_msg;
+  exec_utils_->ExecAndReturnResult(command,
+                                   /*timeout_sec=*/-1,
+                                   ExecCallbacks{
+                                       .on_start = on_start.AsStdFunction(),
+                                       .on_end = on_end.AsStdFunction(),
+                                   },
+                                   /*stat=*/nullptr,
+                                   &error_msg);
+}
+
+INSTANTIATE_TEST_SUITE_P(AlwaysOrNeverFallback, ExecUtilsTest, testing::Values(true, false));
+
 }  // namespace art
diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc
index f8bd213..a3c1f3b 100644
--- a/runtime/fault_handler.cc
+++ b/runtime/fault_handler.cc
@@ -20,11 +20,15 @@
 #include <sys/mman.h>
 #include <sys/ucontext.h>
 
+#include <atomic>
+
 #include "art_method-inl.h"
 #include "base/logging.h"  // For VLOG
+#include "base/membarrier.h"
 #include "base/safe_copy.h"
 #include "base/stl_util.h"
 #include "dex/dex_file_types.h"
+#include "gc/heap.h"
 #include "jit/jit.h"
 #include "jit/jit_code_cache.h"
 #include "mirror/class.h"
@@ -47,115 +51,128 @@
 }
 
 // Signal handler called on SIGSEGV.
-static bool art_fault_handler(int sig, siginfo_t* info, void* context) {
-  return fault_manager.HandleFault(sig, info, context);
+static bool art_sigsegv_handler(int sig, siginfo_t* info, void* context) {
+  return fault_manager.HandleSigsegvFault(sig, info, context);
 }
 
-#if defined(__linux__)
-
-// Change to verify the safe implementations against the original ones.
-constexpr bool kVerifySafeImpls = false;
-
-// Provide implementations of ArtMethod::GetDeclaringClass and VerifyClassClass that use SafeCopy
-// to safely dereference pointers which are potentially garbage.
-// Only available on Linux due to availability of SafeCopy.
-
-static mirror::Class* SafeGetDeclaringClass(ArtMethod* method)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  char* method_declaring_class =
-      reinterpret_cast<char*>(method) + ArtMethod::DeclaringClassOffset().SizeValue();
-
-  // ArtMethod::declaring_class_ is a GcRoot<mirror::Class>.
-  // Read it out into as a CompressedReference directly for simplicity's sake.
-  mirror::CompressedReference<mirror::Class> cls;
-  ssize_t rc = SafeCopy(&cls, method_declaring_class, sizeof(cls));
-  CHECK_NE(-1, rc);
-
-  if (kVerifySafeImpls) {
-    ObjPtr<mirror::Class> actual_class = method->GetDeclaringClassUnchecked<kWithoutReadBarrier>();
-    CHECK_EQ(actual_class, cls.AsMirrorPtr());
-  }
-
-  if (rc != sizeof(cls)) {
-    return nullptr;
-  }
-
-  return cls.AsMirrorPtr();
+// Signal handler called on SIGBUS.
+static bool art_sigbus_handler(int sig, siginfo_t* info, void* context) {
+  return fault_manager.HandleSigbusFault(sig, info, context);
 }
 
-static mirror::Class* SafeGetClass(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
-  char* obj_cls = reinterpret_cast<char*>(obj) + mirror::Object::ClassOffset().SizeValue();
-
-  mirror::HeapReference<mirror::Class> cls;
-  ssize_t rc = SafeCopy(&cls, obj_cls, sizeof(cls));
-  CHECK_NE(-1, rc);
-
-  if (kVerifySafeImpls) {
-    mirror::Class* actual_class = obj->GetClass<kVerifyNone>();
-    CHECK_EQ(actual_class, cls.AsMirrorPtr());
-  }
-
-  if (rc != sizeof(cls)) {
-    return nullptr;
-  }
-
-  return cls.AsMirrorPtr();
-}
-
-static bool SafeVerifyClassClass(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) {
-  mirror::Class* c_c = SafeGetClass(cls);
-  bool result = c_c != nullptr && c_c == SafeGetClass(c_c);
-
-  if (kVerifySafeImpls) {
-    CHECK_EQ(VerifyClassClass(cls), result);
-  }
-
-  return result;
-}
-
-#else
-
-static mirror::Class* SafeGetDeclaringClass(ArtMethod* method_obj)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  return method_obj->GetDeclaringClassUnchecked<kWithoutReadBarrier>().Ptr();
-}
-
-static bool SafeVerifyClassClass(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) {
-  return VerifyClassClass(cls);
-}
-#endif
-
-
-FaultManager::FaultManager() : initialized_(false) {
-  sigaction(SIGSEGV, nullptr, &oldaction_);
-}
+FaultManager::FaultManager()
+    : generated_code_ranges_lock_("FaultHandler generated code ranges lock",
+                                  LockLevel::kGenericBottomLock),
+      initialized_(false) {}
 
 FaultManager::~FaultManager() {
 }
 
-void FaultManager::Init() {
+static const char* SignalCodeName(int sig, int code) {
+  if (sig == SIGSEGV) {
+    switch (code) {
+      case SEGV_MAPERR: return "SEGV_MAPERR";
+      case SEGV_ACCERR: return "SEGV_ACCERR";
+      case 8:           return "SEGV_MTEAERR";
+      case 9:           return "SEGV_MTESERR";
+      default:          return "SEGV_UNKNOWN";
+    }
+  } else if (sig == SIGBUS) {
+    switch (code) {
+      case BUS_ADRALN: return "BUS_ADRALN";
+      case BUS_ADRERR: return "BUS_ADRERR";
+      case BUS_OBJERR: return "BUS_OBJERR";
+      default:         return "BUS_UNKNOWN";
+    }
+  } else {
+    return "UNKNOWN";
+  }
+}
+
+static std::ostream& PrintSignalInfo(std::ostream& os, siginfo_t* info) {
+  os << "  si_signo: " << info->si_signo << " (" << strsignal(info->si_signo) << ")\n"
+     << "  si_code: " << info->si_code
+     << " (" << SignalCodeName(info->si_signo, info->si_code) << ")";
+  if (info->si_signo == SIGSEGV || info->si_signo == SIGBUS) {
+    os << "\n" << "  si_addr: " << info->si_addr;
+  }
+  return os;
+}
+
+static bool InstallSigbusHandler() {
+  return gUseUserfaultfd &&
+         Runtime::Current()->GetHeap()->MarkCompactCollector()->IsUsingSigbusFeature();
+}
+
+void FaultManager::Init(bool use_sig_chain) {
   CHECK(!initialized_);
-  sigset_t mask;
-  sigfillset(&mask);
-  sigdelset(&mask, SIGABRT);
-  sigdelset(&mask, SIGBUS);
-  sigdelset(&mask, SIGFPE);
-  sigdelset(&mask, SIGILL);
-  sigdelset(&mask, SIGSEGV);
+  if (use_sig_chain) {
+    sigset_t mask;
+    sigfillset(&mask);
+    sigdelset(&mask, SIGABRT);
+    sigdelset(&mask, SIGBUS);
+    sigdelset(&mask, SIGFPE);
+    sigdelset(&mask, SIGILL);
+    sigdelset(&mask, SIGSEGV);
 
-  SigchainAction sa = {
-    .sc_sigaction = art_fault_handler,
-    .sc_mask = mask,
-    .sc_flags = 0UL,
-  };
+    SigchainAction sa = {
+        .sc_sigaction = art_sigsegv_handler,
+        .sc_mask = mask,
+        .sc_flags = 0UL,
+    };
 
-  AddSpecialSignalHandlerFn(SIGSEGV, &sa);
-  initialized_ = true;
+    AddSpecialSignalHandlerFn(SIGSEGV, &sa);
+    if (InstallSigbusHandler()) {
+      sa.sc_sigaction = art_sigbus_handler;
+      AddSpecialSignalHandlerFn(SIGBUS, &sa);
+    }
+
+    // Notify the kernel that we intend to use a specific `membarrier()` command.
+    int result = art::membarrier(MembarrierCommand::kRegisterPrivateExpedited);
+    if (result != 0) {
+      LOG(WARNING) << "FaultHandler: MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED failed: "
+                   << errno << " " << strerror(errno);
+    }
+
+    {
+      MutexLock lock(Thread::Current(), generated_code_ranges_lock_);
+      for (size_t i = 0; i != kNumLocalGeneratedCodeRanges; ++i) {
+        GeneratedCodeRange* next = (i + 1u != kNumLocalGeneratedCodeRanges)
+            ? &generated_code_ranges_storage_[i + 1u]
+            : nullptr;
+        generated_code_ranges_storage_[i].next.store(next, std::memory_order_relaxed);
+        generated_code_ranges_storage_[i].start = nullptr;
+        generated_code_ranges_storage_[i].size = 0u;
+      }
+      free_generated_code_ranges_ = generated_code_ranges_storage_;
+    }
+
+    initialized_ = true;
+  } else if (InstallSigbusHandler()) {
+    struct sigaction act;
+    std::memset(&act, '\0', sizeof(act));
+    act.sa_flags = SA_SIGINFO | SA_RESTART;
+    act.sa_sigaction = [](int sig, siginfo_t* info, void* context) {
+      if (!art_sigbus_handler(sig, info, context)) {
+        std::ostringstream oss;
+        PrintSignalInfo(oss, info);
+        LOG(FATAL) << "Couldn't handle SIGBUS fault:"
+                   << "\n"
+                   << oss.str();
+      }
+    };
+    if (sigaction(SIGBUS, &act, nullptr)) {
+      LOG(FATAL) << "Fault handler for SIGBUS couldn't be setup: " << strerror(errno);
+    }
+  }
 }
 
 void FaultManager::Release() {
   if (initialized_) {
-    RemoveSpecialSignalHandlerFn(SIGSEGV, art_fault_handler);
+    RemoveSpecialSignalHandlerFn(SIGSEGV, art_sigsegv_handler);
+    if (InstallSigbusHandler()) {
+      RemoveSpecialSignalHandlerFn(SIGBUS, art_sigbus_handler);
+    }
     initialized_ = false;
   }
 }
@@ -167,6 +184,24 @@
     // Free all handlers.
     STLDeleteElements(&generated_code_handlers_);
     STLDeleteElements(&other_handlers_);
+
+    // Delete remaining code ranges if any (such as nterp code or oat code from
+    // oat files that have not been unloaded, including boot image oat files).
+    MutexLock lock(Thread::Current(), generated_code_ranges_lock_);
+    GeneratedCodeRange* range = generated_code_ranges_.load(std::memory_order_acquire);
+    generated_code_ranges_.store(nullptr, std::memory_order_release);
+    while (range != nullptr) {
+      GeneratedCodeRange* next_range = range->next.load(std::memory_order_relaxed);
+      std::less<GeneratedCodeRange*> less;
+      if (!less(range, generated_code_ranges_storage_) &&
+          less(range, generated_code_ranges_storage_ + kNumLocalGeneratedCodeRanges)) {
+        // Nothing to do - not adding `range` to the `free_generated_code_ranges_` anymore.
+      } else {
+        // Range is not in the `generated_code_ranges_storage_`.
+        delete range;
+      }
+      range = next_range;
+    }
   }
 }
 
@@ -188,32 +223,22 @@
   return false;
 }
 
-static const char* SignalCodeName(int sig, int code) {
-  if (sig != SIGSEGV) {
-    return "UNKNOWN";
-  } else {
-    switch (code) {
-      case SEGV_MAPERR: return "SEGV_MAPERR";
-      case SEGV_ACCERR: return "SEGV_ACCERR";
-      case 8:           return "SEGV_MTEAERR";
-      case 9:           return "SEGV_MTESERR";
-      default:          return "UNKNOWN";
-    }
+bool FaultManager::HandleSigbusFault(int sig, siginfo_t* info, void* context ATTRIBUTE_UNUSED) {
+  DCHECK_EQ(sig, SIGBUS);
+  if (VLOG_IS_ON(signals)) {
+    PrintSignalInfo(VLOG_STREAM(signals) << "Handling SIGBUS fault:\n", info);
   }
-}
-static std::ostream& PrintSignalInfo(std::ostream& os, siginfo_t* info) {
-  os << "  si_signo: " << info->si_signo << " (" << strsignal(info->si_signo) << ")\n"
-     << "  si_code: " << info->si_code
-     << " (" << SignalCodeName(info->si_signo, info->si_code) << ")";
-  if (info->si_signo == SIGSEGV) {
-    os << "\n" << "  si_addr: " << info->si_addr;
-  }
-  return os;
+
+#ifdef TEST_NESTED_SIGNAL
+  // Simulate a crash in a handler.
+  raise(SIGBUS);
+#endif
+  return Runtime::Current()->GetHeap()->MarkCompactCollector()->SigbusHandler(info);
 }
 
-bool FaultManager::HandleFault(int sig, siginfo_t* info, void* context) {
+bool FaultManager::HandleSigsegvFault(int sig, siginfo_t* info, void* context) {
   if (VLOG_IS_ON(signals)) {
-    PrintSignalInfo(VLOG_STREAM(signals) << "Handling fault:" << "\n", info);
+    PrintSignalInfo(VLOG_STREAM(signals) << "Handling SIGSEGV fault:\n", info);
   }
 
 #ifdef TEST_NESTED_SIGNAL
@@ -221,7 +246,7 @@
   raise(SIGSEGV);
 #endif
 
-  if (IsInGeneratedCode(info, context, true)) {
+  if (IsInGeneratedCode(info, context)) {
     VLOG(signals) << "in generated code, looking for handler";
     for (const auto& handler : generated_code_handlers_) {
       VLOG(signals) << "invoking Action on handler " << handler;
@@ -268,37 +293,143 @@
   LOG(FATAL) << "Attempted to remove non existent handler " << handler;
 }
 
-static bool IsKnownPc(uintptr_t pc, ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
-  // Check whether the pc is within nterp range.
-  if (OatQuickMethodHeader::IsNterpPc(pc)) {
-    return true;
+inline FaultManager::GeneratedCodeRange* FaultManager::CreateGeneratedCodeRange(
+    const void* start, size_t size) {
+  GeneratedCodeRange* range = free_generated_code_ranges_;
+  if (range != nullptr) {
+    std::less<GeneratedCodeRange*> less;
+    DCHECK(!less(range, generated_code_ranges_storage_));
+    DCHECK(less(range, generated_code_ranges_storage_ + kNumLocalGeneratedCodeRanges));
+    range->start = start;
+    range->size = size;
+    free_generated_code_ranges_ = range->next.load(std::memory_order_relaxed);
+    range->next.store(nullptr, std::memory_order_relaxed);
+    return range;
+  } else {
+    return new GeneratedCodeRange{nullptr, start, size};
   }
-
-  // Check whether the pc is in the JIT code cache.
-  jit::Jit* jit = Runtime::Current()->GetJit();
-  if (jit != nullptr && jit->GetCodeCache()->ContainsPc(reinterpret_cast<const void*>(pc))) {
-    return true;
-  }
-
-  if (method->IsObsolete()) {
-    // Obsolete methods never happen on AOT code.
-    return false;
-  }
-
-  // Note: at this point, we trust it's truly an ArtMethod we found at the bottom of the stack,
-  // and we can find its oat file through it.
-  const OatDexFile* oat_dex_file = method->GetDeclaringClass()->GetDexFile().GetOatDexFile();
-  if (oat_dex_file != nullptr &&
-      oat_dex_file->GetOatFile()->Contains(reinterpret_cast<const void*>(pc))) {
-    return true;
-  }
-
-  return false;
 }
 
-// This function is called within the signal handler.  It checks that
-// the mutator_lock is held (shared).  No annotalysis is done.
-bool FaultManager::IsInGeneratedCode(siginfo_t* siginfo, void* context, bool check_dex_pc) {
+inline void FaultManager::FreeGeneratedCodeRange(GeneratedCodeRange* range) {
+  std::less<GeneratedCodeRange*> less;
+  if (!less(range, generated_code_ranges_storage_) &&
+      less(range, generated_code_ranges_storage_ + kNumLocalGeneratedCodeRanges)) {
+    MutexLock lock(Thread::Current(), generated_code_ranges_lock_);
+    range->start = nullptr;
+    range->size = 0u;
+    range->next.store(free_generated_code_ranges_, std::memory_order_relaxed);
+    free_generated_code_ranges_ = range;
+  } else {
+    // Range is not in the `generated_code_ranges_storage_`.
+    delete range;
+  }
+}
+
+void FaultManager::AddGeneratedCodeRange(const void* start, size_t size) {
+  GeneratedCodeRange* new_range = nullptr;
+  {
+    MutexLock lock(Thread::Current(), generated_code_ranges_lock_);
+    new_range = CreateGeneratedCodeRange(start, size);
+    GeneratedCodeRange* old_head = generated_code_ranges_.load(std::memory_order_relaxed);
+    new_range->next.store(old_head, std::memory_order_relaxed);
+    generated_code_ranges_.store(new_range, std::memory_order_release);
+  }
+
+  // The above release operation on `generated_code_ranges_` with an acquire operation
+  // on the same atomic object in `IsInGeneratedCode()` ensures the correct memory
+  // visibility for the contents of `*new_range` for any thread that loads the value
+  // written above (or a value written by a release sequence headed by that write).
+  //
+  // However, we also need to ensure that any thread that encounters a segmentation
+  // fault in the provided range shall actually see the written value. For JIT code
+  // cache and nterp, the registration happens while the process is single-threaded
+  // but the synchronization is more complicated for code in oat files.
+  //
+  // Threads that load classes register dex files under the `Locks::dex_lock_` and
+  // the first one to register a dex file with a given oat file shall add the oat
+  // code range; the memory visibility for these threads is guaranteed by the lock.
+  // However a thread that did not try to load a class with oat code can execute the
+  // code if a direct or indirect reference to such class escapes from one of the
+  // threads that loaded it. Use `membarrier()` for memory visibility in this case.
+  art::membarrier(MembarrierCommand::kPrivateExpedited);
+}
+
+void FaultManager::RemoveGeneratedCodeRange(const void* start, size_t size) {
+  Thread* self = Thread::Current();
+  GeneratedCodeRange* range = nullptr;
+  {
+    MutexLock lock(self, generated_code_ranges_lock_);
+    std::atomic<GeneratedCodeRange*>* before = &generated_code_ranges_;
+    range = before->load(std::memory_order_relaxed);
+    while (range != nullptr && range->start != start) {
+      before = &range->next;
+      range = before->load(std::memory_order_relaxed);
+    }
+    if (range != nullptr) {
+      GeneratedCodeRange* next = range->next.load(std::memory_order_relaxed);
+      if (before == &generated_code_ranges_) {
+        // Relaxed store directly to `generated_code_ranges_` would not satisfy
+        // conditions for a release sequence, so we need to use store-release.
+        before->store(next, std::memory_order_release);
+      } else {
+        // In the middle of the list, we can use a relaxed store as we're not
+        // publishing any newly written memory to potential reader threads.
+        // Whether they see the removed node or not is unimportant as we should
+        // not execute that code anymore. We're keeping the `next` link of the
+        // removed node, so that concurrent walk can use it to reach remaining
+        // retained nodes, if any.
+        before->store(next, std::memory_order_relaxed);
+      }
+    }
+  }
+  CHECK(range != nullptr);
+  DCHECK_EQ(range->start, start);
+  CHECK_EQ(range->size, size);
+
+  Runtime* runtime = Runtime::Current();
+  CHECK(runtime != nullptr);
+  if (runtime->IsStarted() && runtime->GetThreadList() != nullptr) {
+    // Run a checkpoint before deleting the range to ensure that no thread holds a
+    // pointer to the removed range while walking the list in `IsInGeneratedCode()`.
+    // That walk is guarded by checking that the thread is `Runnable`, so any walk
+    // started before the removal shall be done when running the checkpoint and the
+    // checkpoint also ensures the correct memory visibility of `next` links,
+    // so the thread shall not see the pointer during future walks.
+
+    // This function is currently called in different mutex and thread states.
+    // Semi-space GC performs the cleanup during its `MarkingPhase()` while holding
+    // the mutator exclusively, so we do not need a checkpoint. All other GCs perform
+    // the cleanup in their `ReclaimPhase()` while holding the mutator lock as shared
+    // and it's safe to release and re-acquire the mutator lock. Despite holding the
+    // mutator lock as shared, the thread is not always marked as `Runnable`.
+    // TODO: Clean up state transitions in different GC implementations. b/259440389
+    if (Locks::mutator_lock_->IsExclusiveHeld(self)) {
+      // We do not need a checkpoint because no other thread is Runnable.
+    } else {
+      DCHECK(Locks::mutator_lock_->IsSharedHeld(self));
+      // Use explicit state transitions or unlock/lock.
+      bool runnable = (self->GetState() == ThreadState::kRunnable);
+      if (runnable) {
+        self->TransitionFromRunnableToSuspended(ThreadState::kNative);
+      } else {
+        Locks::mutator_lock_->SharedUnlock(self);
+      }
+      DCHECK(!Locks::mutator_lock_->IsSharedHeld(self));
+      runtime->GetThreadList()->RunEmptyCheckpoint();
+      if (runnable) {
+        self->TransitionFromSuspendedToRunnable();
+      } else {
+        Locks::mutator_lock_->SharedLock(self);
+      }
+    }
+  }
+  FreeGeneratedCodeRange(range);
+}
+
+// This function is called within the signal handler. It checks that the thread
+// is `Runnable`, the `mutator_lock_` is held (shared) and the fault PC is in one
+// of the registered generated code ranges. No annotalysis is done.
+bool FaultManager::IsInGeneratedCode(siginfo_t* siginfo, void* context) {
   // We can only be running Java code in the current thread if it
   // is in Runnable state.
   VLOG(signals) << "Checking for generated code";
@@ -321,76 +452,29 @@
     return false;
   }
 
-  ArtMethod* method_obj = nullptr;
-  uintptr_t return_pc = 0;
-  uintptr_t sp = 0;
-  bool is_stack_overflow = false;
-
-  // Get the architecture specific method address and return address.  These
-  // are in architecture specific files in arch/<arch>/fault_handler_<arch>.
-  GetMethodAndReturnPcAndSp(siginfo, context, &method_obj, &return_pc, &sp, &is_stack_overflow);
-
-  // If we don't have a potential method, we're outta here.
-  VLOG(signals) << "potential method: " << method_obj;
-  // TODO: Check linear alloc and image.
-  DCHECK_ALIGNED(ArtMethod::Size(kRuntimePointerSize), sizeof(void*))
-      << "ArtMethod is not pointer aligned";
-  if (method_obj == nullptr || !IsAligned<sizeof(void*)>(method_obj)) {
-    VLOG(signals) << "no method";
+  uintptr_t fault_pc = GetFaultPc(siginfo, context);
+  if (fault_pc == 0u) {
+    VLOG(signals) << "no fault PC";
     return false;
   }
 
-  // Verify that the potential method is indeed a method.
-  // TODO: check the GC maps to make sure it's an object.
-  // Check that the class pointer inside the object is not null and is aligned.
-  // No read barrier because method_obj may not be a real object.
-  mirror::Class* cls = SafeGetDeclaringClass(method_obj);
-  if (cls == nullptr) {
-    VLOG(signals) << "not a class";
-    return false;
+  // Walk over the list of registered code ranges.
+  GeneratedCodeRange* range = generated_code_ranges_.load(std::memory_order_acquire);
+  while (range != nullptr) {
+    if (fault_pc - reinterpret_cast<uintptr_t>(range->start) < range->size) {
+      return true;
+    }
+    // We may or may not see ranges that were concurrently removed, depending
+    // on when the relaxed writes of the `next` links become visible. However,
+    // even if we're currently at a node that is being removed, we shall visit
+    // all remaining ranges that are not being removed as the removed nodes
+    // retain the `next` link at the time of removal (which may lead to other
+    // removed nodes before reaching remaining retained nodes, if any). Correct
+    // memory visibility of `start` and `size` fields of the visited ranges is
+    // ensured by the release and acquire operations on `generated_code_ranges_`.
+    range = range->next.load(std::memory_order_relaxed);
   }
-
-  if (!IsAligned<kObjectAlignment>(cls)) {
-    VLOG(signals) << "not aligned";
-    return false;
-  }
-
-  if (!SafeVerifyClassClass(cls)) {
-    VLOG(signals) << "not a class class";
-    return false;
-  }
-
-  if (!IsKnownPc(return_pc, method_obj)) {
-    VLOG(signals) << "PC not in Java code";
-    return false;
-  }
-
-  const OatQuickMethodHeader* method_header = method_obj->GetOatQuickMethodHeader(return_pc);
-
-  if (method_header == nullptr) {
-    VLOG(signals) << "no compiled code";
-    return false;
-  }
-
-  // We can be certain that this is a method now.  Check if we have a GC map
-  // at the return PC address.
-  if (true || kIsDebugBuild) {
-    VLOG(signals) << "looking for dex pc for return pc " << std::hex << return_pc;
-    uint32_t sought_offset = return_pc -
-        reinterpret_cast<uintptr_t>(method_header->GetEntryPoint());
-    VLOG(signals) << "pc offset: " << std::hex << sought_offset;
-  }
-  uint32_t dexpc = dex::kDexNoIndex;
-  if (is_stack_overflow) {
-    // If it's an implicit stack overflow check, the frame is not setup, so we
-    // just infer the dex PC as zero.
-    dexpc = 0;
-  } else {
-    CHECK_EQ(*reinterpret_cast<ArtMethod**>(sp), method_obj);
-    dexpc = method_header->ToDexPc(reinterpret_cast<ArtMethod**>(sp), return_pc, false);
-  }
-  VLOG(signals) << "dexpc: " << dexpc;
-  return !check_dex_pc || dexpc != dex::kDexNoIndex;
+  return false;
 }
 
 FaultHandler::FaultHandler(FaultManager* manager) : manager_(manager) {
@@ -403,6 +487,76 @@
   manager_->AddHandler(this, true);
 }
 
+bool NullPointerHandler::IsValidMethod(ArtMethod* method) {
+  // At this point we know that the thread is `Runnable` and the PC is in one of
+  // the registered code ranges. The `method` was read from the top of the stack
+  // and should really point to an actual `ArtMethod`, unless we're crashing during
+  // prologue or epilogue, or somehow managed to jump to the compiled code by some
+  // unexpected path, other than method invoke or exception delivery. We do a few
+  // quick checks without guarding from another fault.
+  VLOG(signals) << "potential method: " << method;
+
+  static_assert(IsAligned<sizeof(void*)>(ArtMethod::Size(kRuntimePointerSize)));
+  if (method == nullptr || !IsAligned<sizeof(void*)>(method)) {
+    VLOG(signals) << ((method == nullptr) ? "null method" : "unaligned method");
+    return false;
+  }
+
+  // Check that the presumed method actually points to a class. Read barriers
+  // are not needed (and would be undesirable in a signal handler) when reading
+  // a chain of constant references to get to a non-movable `Class.class` object.
+
+  // Note: Allowing nested faults. Checking that the method is in one of the
+  // `LinearAlloc` spaces, or that objects we look at are in the `Heap` would be
+  // slow and require locking a mutex, which is undesirable in a signal handler.
+  // (Though we could register valid ranges similarly to the generated code ranges.)
+
+  mirror::Object* klass =
+      method->GetDeclaringClassAddressWithoutBarrier()->AsMirrorPtr();
+  if (klass == nullptr || !IsAligned<kObjectAlignment>(klass)) {
+    VLOG(signals) << ((klass == nullptr) ? "null class" : "unaligned class");
+    return false;
+  }
+
+  mirror::Class* class_class = klass->GetClass<kVerifyNone, kWithoutReadBarrier>();
+  if (class_class == nullptr || !IsAligned<kObjectAlignment>(class_class)) {
+    VLOG(signals) << ((klass == nullptr) ? "null class_class" : "unaligned class_class");
+    return false;
+  }
+
+  if (class_class != class_class->GetClass<kVerifyNone, kWithoutReadBarrier>()) {
+    VLOG(signals) << "invalid class_class";
+    return false;
+  }
+
+  return true;
+}
+
+bool NullPointerHandler::IsValidReturnPc(ArtMethod** sp, uintptr_t return_pc) {
+  // Check if we can associate a dex PC with the return PC, whether from Nterp,
+  // or with an existing stack map entry for a compiled method.
+  // Note: Allowing nested faults if `IsValidMethod()` returned a false positive.
+  // Note: The `ArtMethod::GetOatQuickMethodHeader()` can acquire locks (at least
+  // `Locks::jit_lock_`) and if the thread already held such a lock, the signal
+  // handler would deadlock. However, if a thread is holding one of the locks
+  // below the mutator lock, the PC should be somewhere in ART code and should
+  // not match any registered generated code range, so such as a deadlock is
+  // unlikely. If it happens anyway, the worst case is that an internal ART crash
+  // would be reported as ANR.
+  ArtMethod* method = *sp;
+  const OatQuickMethodHeader* method_header = method->GetOatQuickMethodHeader(return_pc);
+  if (method_header == nullptr) {
+    VLOG(signals) << "No method header.";
+    return false;
+  }
+  VLOG(signals) << "looking for dex pc for return pc 0x" << std::hex << return_pc
+                << " pc offset: 0x" << std::hex
+                << (return_pc - reinterpret_cast<uintptr_t>(method_header->GetEntryPoint()));
+  uint32_t dexpc = method_header->ToDexPc(reinterpret_cast<ArtMethod**>(sp), return_pc, false);
+  VLOG(signals) << "dexpc: " << dexpc;
+  return dexpc != dex::kDexNoIndex;
+}
+
 //
 // Suspension fault handler
 //
@@ -426,17 +580,13 @@
 
 bool JavaStackTraceHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* siginfo, void* context) {
   // Make sure that we are in the generated code, but we may not have a dex pc.
-  bool in_generated_code = manager_->IsInGeneratedCode(siginfo, context, false);
+  bool in_generated_code = manager_->IsInGeneratedCode(siginfo, context);
   if (in_generated_code) {
     LOG(ERROR) << "Dumping java stack trace for crash in generated code";
-    ArtMethod* method = nullptr;
-    uintptr_t return_pc = 0;
-    uintptr_t sp = 0;
-    bool is_stack_overflow = false;
     Thread* self = Thread::Current();
 
-    manager_->GetMethodAndReturnPcAndSp(
-        siginfo, context, &method, &return_pc, &sp, &is_stack_overflow);
+    uintptr_t sp = FaultManager::GetFaultSp(context);
+    CHECK_NE(sp, 0u);  // Otherwise we should not have reached this handler.
     // Inside of generated code, sp[0] is the method, so sp is the frame.
     self->SetTopOfStack(reinterpret_cast<ArtMethod**>(sp));
     self->DumpJavaStack(LOG_STREAM(ERROR));
diff --git a/runtime/fault_handler.h b/runtime/fault_handler.h
index 8b89c22..1ed6526 100644
--- a/runtime/fault_handler.h
+++ b/runtime/fault_handler.h
@@ -21,9 +21,11 @@
 #include <signal.h>
 #include <stdint.h>
 
+#include <atomic>
 #include <vector>
 
 #include "base/locks.h"  // For annotalysis.
+#include "base/mutex.h"
 #include "runtime_globals.h"  // For CanDoImplicitNullCheckOn.
 
 namespace art {
@@ -36,7 +38,9 @@
   FaultManager();
   ~FaultManager();
 
-  void Init();
+  // Use libsigchain if use_sig_chain is true. Otherwise, setup SIGBUS directly
+  // using sigaction().
+  void Init(bool use_sig_chain);
 
   // Unclaim signals.
   void Release();
@@ -44,36 +48,66 @@
   // Unclaim signals and delete registered handlers.
   void Shutdown();
 
-  // Try to handle a fault, returns true if successful.
-  bool HandleFault(int sig, siginfo_t* info, void* context);
+  // Try to handle a SIGSEGV fault, returns true if successful.
+  bool HandleSigsegvFault(int sig, siginfo_t* info, void* context);
+
+  // Try to handle a SIGBUS fault, returns true if successful.
+  bool HandleSigbusFault(int sig, siginfo_t* info, void* context);
 
   // Added handlers are owned by the fault handler and will be freed on Shutdown().
   void AddHandler(FaultHandler* handler, bool generated_code);
   void RemoveHandler(FaultHandler* handler);
 
-  // Note that the following two functions are called in the context of a signal handler.
-  // The IsInGeneratedCode() function checks that the mutator lock is held before it
-  // calls GetMethodAndReturnPCAndSP().
-  // TODO: think about adding lock assertions and fake lock and unlock functions.
-  void GetMethodAndReturnPcAndSp(siginfo_t* siginfo,
-                                 void* context,
-                                 ArtMethod** out_method,
-                                 uintptr_t* out_return_pc,
-                                 uintptr_t* out_sp,
-                                 bool* out_is_stack_overflow)
-                                 NO_THREAD_SAFETY_ANALYSIS;
-  bool IsInGeneratedCode(siginfo_t* siginfo, void *context, bool check_dex_pc)
-                         NO_THREAD_SAFETY_ANALYSIS;
+  void AddGeneratedCodeRange(const void* start, size_t size);
+  void RemoveGeneratedCodeRange(const void* start, size_t size)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Retrieves fault PC from architecture-dependent `context`, returns 0 on failure.
+  // Called in the context of a signal handler.
+  static uintptr_t GetFaultPc(siginfo_t* siginfo, void* context);
+
+  // Retrieves SP from architecture-dependent `context`.
+  // Called in the context of a signal handler.
+  static uintptr_t GetFaultSp(void* context);
+
+  // Checks if the fault happened while running generated code.
+  // Called in the context of a signal handler.
+  bool IsInGeneratedCode(siginfo_t* siginfo, void *context) NO_THREAD_SAFETY_ANALYSIS;
 
  private:
+  struct GeneratedCodeRange {
+    std::atomic<GeneratedCodeRange*> next;
+    const void* start;
+    size_t size;
+  };
+
+  GeneratedCodeRange* CreateGeneratedCodeRange(const void* start, size_t size)
+      REQUIRES(generated_code_ranges_lock_);
+  void FreeGeneratedCodeRange(GeneratedCodeRange* range) REQUIRES(!generated_code_ranges_lock_);
+
   // The HandleFaultByOtherHandlers function is only called by HandleFault function for generated code.
   bool HandleFaultByOtherHandlers(int sig, siginfo_t* info, void* context)
                                   NO_THREAD_SAFETY_ANALYSIS;
 
+  // Note: The lock guards modifications of the ranges but the function `IsInGeneratedCode()`
+  // walks the list in the context of a signal handler without holding the lock.
+  Mutex generated_code_ranges_lock_;
+  std::atomic<GeneratedCodeRange*> generated_code_ranges_ GUARDED_BY(generated_code_ranges_lock_);
+
   std::vector<FaultHandler*> generated_code_handlers_;
   std::vector<FaultHandler*> other_handlers_;
-  struct sigaction oldaction_;
   bool initialized_;
+
+  // We keep a certain number of generated code ranges locally to avoid too many
+  // cache misses while traversing the singly-linked list `generated_code_ranges_`.
+  // 16 should be enough for the boot image (assuming `--multi-image`; there is
+  // only one entry for `--single-image`), nterp, JIT code cache and a few other
+  // entries for the app or system server.
+  static constexpr size_t kNumLocalGeneratedCodeRanges = 16;
+  GeneratedCodeRange generated_code_ranges_storage_[kNumLocalGeneratedCodeRanges];
+  GeneratedCodeRange* free_generated_code_ranges_
+       GUARDED_BY(generated_code_ranges_lock_);
+
   DISALLOW_COPY_AND_ASSIGN(FaultManager);
 };
 
@@ -98,17 +132,29 @@
  public:
   explicit NullPointerHandler(FaultManager* manager);
 
-  bool Action(int sig, siginfo_t* siginfo, void* context) override;
-
-  static bool IsValidImplicitCheck(siginfo_t* siginfo) {
-    // Our implicit NPE checks always limit the range to a page.
-    // Note that the runtime will do more exhaustive checks (that we cannot
-    // reasonably do in signal processing code) based on the dex instruction
-    // faulting.
-    return CanDoImplicitNullCheckOn(reinterpret_cast<uintptr_t>(siginfo->si_addr));
-  }
+  // NO_THREAD_SAFETY_ANALYSIS: Called after the fault manager determined that
+  // the thread is `Runnable` and holds the mutator lock (shared) but without
+  // telling annotalysis that we actually hold the lock.
+  bool Action(int sig, siginfo_t* siginfo, void* context) override
+      NO_THREAD_SAFETY_ANALYSIS;
 
  private:
+  // Helper functions for checking whether the signal can be interpreted
+  // as implicit NPE check. Note that the runtime will do more exhaustive
+  // checks (that we cannot reasonably do in signal processing code) based
+  // on the dex instruction faulting.
+
+  static bool IsValidFaultAddress(uintptr_t fault_address) {
+    // Our implicit NPE checks always limit the range to a page.
+    return CanDoImplicitNullCheckOn(fault_address);
+  }
+
+  static bool IsValidMethod(ArtMethod* method)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  static bool IsValidReturnPc(ArtMethod** sp, uintptr_t return_pc)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   DISALLOW_COPY_AND_ASSIGN(NullPointerHandler);
 };
 
diff --git a/runtime/gc/accounting/atomic_stack.h b/runtime/gc/accounting/atomic_stack.h
index 5e6bd88..a90a319 100644
--- a/runtime/gc/accounting/atomic_stack.h
+++ b/runtime/gc/accounting/atomic_stack.h
@@ -130,6 +130,35 @@
     }
   }
 
+  // Bump the back index by the given number of slots. Returns false if this
+  // operation will overflow the stack. New elements should be written
+  // to [*start_address, *end_address).
+  bool BumpBack(size_t num_slots,
+                StackReference<T>** start_address,
+                StackReference<T>** end_address)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (kIsDebugBuild) {
+      debug_is_sorted_ = false;
+    }
+    const int32_t index = back_index_.load(std::memory_order_relaxed);
+    const int32_t new_index = index + num_slots;
+    if (UNLIKELY(static_cast<size_t>(new_index) >= growth_limit_)) {
+      // Stack overflow.
+      return false;
+    }
+    back_index_.store(new_index, std::memory_order_relaxed);
+    *start_address = begin_ + index;
+    *end_address = begin_ + new_index;
+    if (kIsDebugBuild) {
+      // Check the memory is zero.
+      for (int32_t i = index; i < new_index; i++) {
+        DCHECK_EQ(begin_[i].AsMirrorPtr(), static_cast<T*>(nullptr))
+            << "i=" << i << " index=" << index << " new_index=" << new_index;
+      }
+    }
+    return true;
+  }
+
   void PushBack(T* value) REQUIRES_SHARED(Locks::mutator_lock_) {
     if (kIsDebugBuild) {
       debug_is_sorted_ = false;
@@ -144,8 +173,16 @@
     DCHECK_GT(back_index_.load(std::memory_order_relaxed),
               front_index_.load(std::memory_order_relaxed));
     // Decrement the back index non atomically.
-    back_index_.store(back_index_.load(std::memory_order_relaxed) - 1, std::memory_order_relaxed);
-    return begin_[back_index_.load(std::memory_order_relaxed)].AsMirrorPtr();
+    const int32_t index = back_index_.load(std::memory_order_relaxed) - 1;
+    back_index_.store(index, std::memory_order_relaxed);
+    T* ret = begin_[index].AsMirrorPtr();
+    // In debug builds we expect the stack elements to be null, which may not
+    // always be the case if the stack is being reused without resetting it
+    // in-between.
+    if (kIsDebugBuild) {
+      begin_[index].Clear();
+    }
+    return ret;
   }
 
   // Take an item from the front of the stack.
diff --git a/runtime/gc/accounting/bitmap.cc b/runtime/gc/accounting/bitmap.cc
index 37646b3..bd10958 100644
--- a/runtime/gc/accounting/bitmap.cc
+++ b/runtime/gc/accounting/bitmap.cc
@@ -21,6 +21,7 @@
 #include "base/bit_utils.h"
 #include "base/mem_map.h"
 #include "card_table.h"
+#include "gc/collector/mark_compact.h"
 #include "jit/jit_memory_region.h"
 
 namespace art {
@@ -98,6 +99,7 @@
 
 template class MemoryRangeBitmap<CardTable::kCardSize>;
 template class MemoryRangeBitmap<jit::kJitCodeAccountingBytes>;
+template class MemoryRangeBitmap<collector::MarkCompact::kAlignment>;
 
 }  // namespace accounting
 }  // namespace gc
diff --git a/runtime/gc/accounting/bitmap.h b/runtime/gc/accounting/bitmap.h
index 68f2d04..06398d6 100644
--- a/runtime/gc/accounting/bitmap.h
+++ b/runtime/gc/accounting/bitmap.h
@@ -81,7 +81,7 @@
   void CopyFrom(Bitmap* source_bitmap);
 
   // Starting address of our internal storage.
-  uintptr_t* Begin() {
+  uintptr_t* Begin() const {
     return bitmap_begin_;
   }
 
@@ -98,7 +98,7 @@
   std::string Dump() const;
 
  protected:
-  static constexpr size_t kBitsPerBitmapWord = sizeof(uintptr_t) * kBitsPerByte;
+  static constexpr size_t kBitsPerBitmapWord = kBitsPerIntPtrT;
 
   Bitmap(MemMap&& mem_map, size_t bitmap_size);
   ~Bitmap();
@@ -109,7 +109,9 @@
   template<bool kSetBit>
   ALWAYS_INLINE bool ModifyBit(uintptr_t bit_index);
 
-  // Backing storage for bitmap.
+  // Backing storage for bitmap. This is interpreted as an array of
+  // kBitsPerBitmapWord-sized integers, with bits assigned in each word little
+  // endian first.
   MemMap mem_map_;
 
   // This bitmap itself, word sized for efficiency in scanning.
@@ -122,7 +124,7 @@
   DISALLOW_IMPLICIT_CONSTRUCTORS(Bitmap);
 };
 
-// One bit per kAlignment in range (start, end]
+// One bit per kAlignment in range [start, end)
 template<size_t kAlignment>
 class MemoryRangeBitmap : public Bitmap {
  public:
@@ -138,7 +140,7 @@
 
   // End of the memory range that the bitmap covers.
   ALWAYS_INLINE uintptr_t CoverEnd() const {
-    return cover_end_;
+    return cover_begin_ + kAlignment * BitmapSize();
   }
 
   // Return the address associated with a bit index.
@@ -150,39 +152,47 @@
 
   // Return the bit index associated with an address .
   ALWAYS_INLINE uintptr_t BitIndexFromAddr(uintptr_t addr) const {
-    DCHECK(HasAddress(addr)) << CoverBegin() << " <= " <<  addr << " < " << CoverEnd();
-    return (addr - CoverBegin()) / kAlignment;
+    uintptr_t result = (addr - CoverBegin()) / kAlignment;
+    DCHECK(result < BitmapSize()) << CoverBegin() << " <= " <<  addr << " < " << CoverEnd();
+    return result;
   }
 
   ALWAYS_INLINE bool HasAddress(const uintptr_t addr) const {
-    return cover_begin_ <= addr && addr < cover_end_;
+    // Don't use BitIndexFromAddr() here as the addr passed to this function
+    // could be outside the range. If addr < cover_begin_, then the result
+    // underflows to some very large value past the end of the bitmap.
+    // Therefore, all operations are unsigned here.
+    bool ret = (addr - CoverBegin()) / kAlignment < BitmapSize();
+    if (ret) {
+      DCHECK(CoverBegin() <= addr && addr < CoverEnd())
+          << CoverBegin() << " <= " <<  addr << " < " << CoverEnd();
+    }
+    return ret;
   }
 
   ALWAYS_INLINE bool Set(uintptr_t addr) {
     return SetBit(BitIndexFromAddr(addr));
   }
 
-  ALWAYS_INLINE bool Clear(size_t addr) {
+  ALWAYS_INLINE bool Clear(uintptr_t addr) {
     return ClearBit(BitIndexFromAddr(addr));
   }
 
-  ALWAYS_INLINE bool Test(size_t addr) const {
+  ALWAYS_INLINE bool Test(uintptr_t addr) const {
     return TestBit(BitIndexFromAddr(addr));
   }
 
   // Returns true if the object was previously set.
-  ALWAYS_INLINE bool AtomicTestAndSet(size_t addr) {
+  ALWAYS_INLINE bool AtomicTestAndSet(uintptr_t addr) {
     return AtomicTestAndSetBit(BitIndexFromAddr(addr));
   }
 
  private:
   MemoryRangeBitmap(MemMap&& mem_map, uintptr_t begin, size_t num_bits)
       : Bitmap(std::move(mem_map), num_bits),
-        cover_begin_(begin),
-        cover_end_(begin + kAlignment * num_bits) {}
+        cover_begin_(begin) {}
 
   uintptr_t const cover_begin_;
-  uintptr_t const cover_end_;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(MemoryRangeBitmap);
 };
diff --git a/runtime/gc/accounting/card_table.cc b/runtime/gc/accounting/card_table.cc
index fdf1615..b8b328c 100644
--- a/runtime/gc/accounting/card_table.cc
+++ b/runtime/gc/accounting/card_table.cc
@@ -31,11 +31,6 @@
 namespace gc {
 namespace accounting {
 
-constexpr size_t CardTable::kCardShift;
-constexpr size_t CardTable::kCardSize;
-constexpr uint8_t CardTable::kCardClean;
-constexpr uint8_t CardTable::kCardDirty;
-
 /*
  * Maintain a card table from the write barrier. All writes of
  * non-null values to heap addresses should go through an entry in
diff --git a/runtime/gc/accounting/card_table_test.cc b/runtime/gc/accounting/card_table_test.cc
index 12baaa4..b34a883 100644
--- a/runtime/gc/accounting/card_table_test.cc
+++ b/runtime/gc/accounting/card_table_test.cc
@@ -19,8 +19,8 @@
 #include <string>
 
 #include "base/atomic.h"
+#include "base/common_art_test.h"
 #include "base/utils.h"
-#include "common_runtime_test.h"
 #include "handle_scope-inl.h"
 #include "mirror/class-inl.h"
 #include "mirror/string-inl.h"  // Strings are easiest to allocate
@@ -36,7 +36,7 @@
 namespace gc {
 namespace accounting {
 
-class CardTableTest : public CommonRuntimeTest {
+class CardTableTest : public CommonArtTest {
  public:
   std::unique_ptr<CardTable> card_table_;
 
diff --git a/runtime/gc/accounting/mod_union_table.cc b/runtime/gc/accounting/mod_union_table.cc
index b4026fc..4a84799 100644
--- a/runtime/gc/accounting/mod_union_table.cc
+++ b/runtime/gc/accounting/mod_union_table.cc
@@ -388,6 +388,11 @@
 void ModUnionTableReferenceCache::VisitObjects(ObjectCallback callback, void* arg) {
   CardTable* const card_table = heap_->GetCardTable();
   ContinuousSpaceBitmap* live_bitmap = space_->GetLiveBitmap();
+  // Use an unordered_set for constant time search of card in the second loop.
+  // We don't want to change cleared_cards_ to unordered so that traversals are
+  // sequential in address order.
+  // TODO: Optimize this.
+  std::unordered_set<const uint8_t*> card_lookup_map;
   for (uint8_t* card : cleared_cards_) {
     uintptr_t start = reinterpret_cast<uintptr_t>(card_table->AddrFromCard(card));
     uintptr_t end = start + CardTable::kCardSize;
@@ -396,10 +401,13 @@
                                   [callback, arg](mirror::Object* obj) {
       callback(obj, arg);
     });
+    card_lookup_map.insert(card);
   }
-  // This may visit the same card twice, TODO avoid this.
   for (const auto& pair : references_) {
     const uint8_t* card = pair.first;
+    if (card_lookup_map.find(card) != card_lookup_map.end()) {
+      continue;
+    }
     uintptr_t start = reinterpret_cast<uintptr_t>(card_table->AddrFromCard(card));
     uintptr_t end = start + CardTable::kCardSize;
     live_bitmap->VisitMarkedRange(start,
diff --git a/runtime/gc/accounting/mod_union_table_test.cc b/runtime/gc/accounting/mod_union_table_test.cc
index e42682a..3f38f50 100644
--- a/runtime/gc/accounting/mod_union_table_test.cc
+++ b/runtime/gc/accounting/mod_union_table_test.cc
@@ -46,6 +46,7 @@
 class ModUnionTableTest : public CommonRuntimeTest {
  public:
   ModUnionTableTest() : java_lang_object_array_(nullptr) {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
   }
   mirror::ObjectArray<mirror::Object>* AllocObjectArray(
       Thread* self, space::ContinuousMemMapAllocSpace* space, size_t component_count)
diff --git a/runtime/gc/accounting/space_bitmap-inl.h b/runtime/gc/accounting/space_bitmap-inl.h
index d460e00..e7825e6 100644
--- a/runtime/gc/accounting/space_bitmap-inl.h
+++ b/runtime/gc/accounting/space_bitmap-inl.h
@@ -64,7 +64,44 @@
 }
 
 template<size_t kAlignment>
-template<typename Visitor>
+inline mirror::Object* SpaceBitmap<kAlignment>::FindPrecedingObject(uintptr_t visit_begin,
+                                                                    uintptr_t visit_end) const {
+  // Covers [visit_end, visit_begin].
+  visit_end = std::max(heap_begin_, visit_end);
+  DCHECK_LE(visit_end, visit_begin);
+  DCHECK_LT(visit_begin, HeapLimit());
+
+  const uintptr_t offset_start = visit_begin - heap_begin_;
+  const uintptr_t offset_end = visit_end - heap_begin_;
+  uintptr_t index_start = OffsetToIndex(offset_start);
+  const uintptr_t index_end = OffsetToIndex(offset_end);
+
+  // Start with the right edge
+  uintptr_t word = bitmap_begin_[index_start].load(std::memory_order_relaxed);
+  // visit_begin could be the first word of the object we are looking for.
+  const uintptr_t right_edge_mask = OffsetToMask(offset_start);
+  word &= right_edge_mask | (right_edge_mask - 1);
+  while (index_start > index_end) {
+    if (word != 0) {
+      const uintptr_t ptr_base = IndexToOffset(index_start) + heap_begin_;
+      size_t pos_leading_set_bit = kBitsPerIntPtrT - CLZ(word) - 1;
+      return reinterpret_cast<mirror::Object*>(ptr_base + pos_leading_set_bit * kAlignment);
+    }
+    word = bitmap_begin_[--index_start].load(std::memory_order_relaxed);
+  }
+
+  word &= ~(OffsetToMask(offset_end) - 1);
+  if (word != 0) {
+    const uintptr_t ptr_base = IndexToOffset(index_end) + heap_begin_;
+    size_t pos_leading_set_bit = kBitsPerIntPtrT - CLZ(word) - 1;
+    return reinterpret_cast<mirror::Object*>(ptr_base + pos_leading_set_bit * kAlignment);
+  } else {
+    return nullptr;
+  }
+}
+
+template<size_t kAlignment>
+template<bool kVisitOnce, typename Visitor>
 inline void SpaceBitmap<kAlignment>::VisitMarkedRange(uintptr_t visit_begin,
                                                       uintptr_t visit_end,
                                                       Visitor&& visitor) const {
@@ -114,6 +151,9 @@
         const size_t shift = CTZ(left_edge);
         mirror::Object* obj = reinterpret_cast<mirror::Object*>(ptr_base + shift * kAlignment);
         visitor(obj);
+        if (kVisitOnce) {
+          return;
+        }
         left_edge ^= (static_cast<uintptr_t>(1)) << shift;
       } while (left_edge != 0);
     }
@@ -128,6 +168,9 @@
           const size_t shift = CTZ(w);
           mirror::Object* obj = reinterpret_cast<mirror::Object*>(ptr_base + shift * kAlignment);
           visitor(obj);
+          if (kVisitOnce) {
+            return;
+          }
           w ^= (static_cast<uintptr_t>(1)) << shift;
         } while (w != 0);
       }
@@ -155,6 +198,9 @@
       const size_t shift = CTZ(right_edge);
       mirror::Object* obj = reinterpret_cast<mirror::Object*>(ptr_base + shift * kAlignment);
       visitor(obj);
+      if (kVisitOnce) {
+        return;
+      }
       right_edge ^= (static_cast<uintptr_t>(1)) << shift;
     } while (right_edge != 0);
   }
diff --git a/runtime/gc/accounting/space_bitmap.cc b/runtime/gc/accounting/space_bitmap.cc
index 3c5688d..a0458d2 100644
--- a/runtime/gc/accounting/space_bitmap.cc
+++ b/runtime/gc/accounting/space_bitmap.cc
@@ -16,6 +16,9 @@
 
 #include "space_bitmap-inl.h"
 
+#include <iomanip>
+#include <sstream>
+
 #include "android-base/stringprintf.h"
 
 #include "art_field-inl.h"
@@ -113,6 +116,37 @@
                       reinterpret_cast<void*>(HeapLimit()));
 }
 
+template <size_t kAlignment>
+std::string SpaceBitmap<kAlignment>::DumpMemAround(mirror::Object* obj) const {
+  uintptr_t addr = reinterpret_cast<uintptr_t>(obj);
+  DCHECK_GE(addr, heap_begin_);
+  DCHECK(HasAddress(obj)) << obj;
+  const uintptr_t offset = addr - heap_begin_;
+  const size_t index = OffsetToIndex(offset);
+  const uintptr_t mask = OffsetToMask(offset);
+  size_t num_entries = bitmap_size_ / sizeof(uintptr_t);
+  DCHECK_LT(index, num_entries) << " bitmap_size_ = " << bitmap_size_;
+  Atomic<uintptr_t>* atomic_entry = &bitmap_begin_[index];
+  uintptr_t prev = 0;
+  uintptr_t next = 0;
+  if (index > 0) {
+    prev = (atomic_entry - 1)->load(std::memory_order_relaxed);
+  }
+  uintptr_t curr = atomic_entry->load(std::memory_order_relaxed);
+  if (index < num_entries - 1) {
+    next = (atomic_entry + 1)->load(std::memory_order_relaxed);
+  }
+  std::ostringstream oss;
+  oss << " offset: " << offset
+      << " index: " << index
+      << " mask: " << std::hex << std::setfill('0') << std::setw(16) << mask
+      << " words {" << std::hex << std::setfill('0') << std::setw(16) << prev
+      << ", " << std::hex << std::setfill('0') << std::setw(16) << curr
+      << ", " << std::hex <<std::setfill('0') << std::setw(16) << next
+      << "}";
+  return oss.str();
+}
+
 template<size_t kAlignment>
 void SpaceBitmap<kAlignment>::Clear() {
   if (bitmap_begin_ != nullptr) {
diff --git a/runtime/gc/accounting/space_bitmap.h b/runtime/gc/accounting/space_bitmap.h
index 0d8ffa0..e318933 100644
--- a/runtime/gc/accounting/space_bitmap.h
+++ b/runtime/gc/accounting/space_bitmap.h
@@ -40,8 +40,8 @@
 template<size_t kAlignment>
 class SpaceBitmap {
  public:
-  typedef void ScanCallback(mirror::Object* obj, void* finger, void* arg);
-  typedef void SweepCallback(size_t ptr_count, mirror::Object** ptrs, void* arg);
+  using ScanCallback = void(mirror::Object* obj, void* finger, void* arg);
+  using SweepCallback = void(size_t ptr_count, mirror::Object** ptrs, void* arg);
 
   // Initialize a space bitmap so that it points to a bitmap large enough to cover a heap at
   // heap_begin of heap_capacity bytes, where objects are guaranteed to be kAlignment-aligned.
@@ -131,10 +131,15 @@
     }
   }
 
-  // Visit the live objects in the range [visit_begin, visit_end).
+  // Find first object while scanning bitmap backwards from visit_begin -> visit_end.
+  // Covers [visit_end, visit_begin] range.
+  mirror::Object* FindPrecedingObject(uintptr_t visit_begin, uintptr_t visit_end = 0) const;
+
+  // Visit the live objects in the range [visit_begin, visit_end). If kVisitOnce
+  // is true, then only the first live object will be visited.
   // TODO: Use lock annotations when clang is fixed.
   // REQUIRES(Locks::heap_bitmap_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-  template <typename Visitor>
+  template <bool kVisitOnce = false, typename Visitor>
   void VisitMarkedRange(uintptr_t visit_begin, uintptr_t visit_end, Visitor&& visitor) const
       NO_THREAD_SAFETY_ANALYSIS;
 
@@ -159,7 +164,7 @@
   void CopyFrom(SpaceBitmap* source_bitmap);
 
   // Starting address of our internal storage.
-  Atomic<uintptr_t>* Begin() {
+  Atomic<uintptr_t>* Begin() const {
     return bitmap_begin_;
   }
 
@@ -202,6 +207,9 @@
 
   std::string Dump() const;
 
+  // Dump three bitmap words around obj.
+  std::string DumpMemAround(mirror::Object* obj) const;
+
   // Helper function for computing bitmap size based on a 64 bit capacity.
   static size_t ComputeBitmapSize(uint64_t capacity);
   static size_t ComputeHeapSize(uint64_t bitmap_bytes);
diff --git a/runtime/gc/accounting/space_bitmap_test.cc b/runtime/gc/accounting/space_bitmap_test.cc
index 3a69865..8fcf102 100644
--- a/runtime/gc/accounting/space_bitmap_test.cc
+++ b/runtime/gc/accounting/space_bitmap_test.cc
@@ -19,8 +19,8 @@
 #include <stdint.h>
 #include <memory>
 
+#include "base/common_art_test.h"
 #include "base/mutex.h"
-#include "common_runtime_test.h"
 #include "runtime_globals.h"
 #include "space_bitmap-inl.h"
 
@@ -28,7 +28,7 @@
 namespace gc {
 namespace accounting {
 
-class SpaceBitmapTest : public CommonRuntimeTest {};
+class SpaceBitmapTest : public CommonArtTest {};
 
 TEST_F(SpaceBitmapTest, Init) {
   uint8_t* heap_begin = reinterpret_cast<uint8_t*>(0x10000000);
diff --git a/runtime/gc/allocation_record.cc b/runtime/gc/allocation_record.cc
index 7bcf375..f0d379f 100644
--- a/runtime/gc/allocation_record.cc
+++ b/runtime/gc/allocation_record.cc
@@ -59,6 +59,11 @@
 }
 
 void AllocRecordObjectMap::VisitRoots(RootVisitor* visitor) {
+  // When we are compacting in userfaultfd GC, the class GC-roots are already
+  // updated in SweepAllocationRecords()->SweepClassObject().
+  if (Runtime::Current()->GetHeap()->IsPerformingUffdCompaction()) {
+    return;
+  }
   CHECK_LE(recent_record_max_, alloc_record_max_);
   BufferedRootVisitor<kDefaultBufferedRootCount> buffered_visitor(visitor, RootInfo(kRootDebugger));
   size_t count = recent_record_max_;
@@ -92,7 +97,10 @@
     mirror::Object* new_object = visitor->IsMarked(old_object);
     DCHECK(new_object != nullptr);
     if (UNLIKELY(old_object != new_object)) {
-      klass = GcRoot<mirror::Class>(new_object->AsClass());
+      // We can't use AsClass() as it uses IsClass in a DCHECK, which expects
+      // the class' contents to be there. This is not the case in userfaultfd
+      // GC.
+      klass = GcRoot<mirror::Class>(ObjPtr<mirror::Class>::DownCast(new_object));
     }
   }
 }
@@ -131,13 +139,13 @@
 }
 
 void AllocRecordObjectMap::AllowNewAllocationRecords() {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   allow_new_record_ = true;
   new_record_condition_.Broadcast(Thread::Current());
 }
 
 void AllocRecordObjectMap::DisallowNewAllocationRecords() {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   allow_new_record_ = false;
 }
 
@@ -230,8 +238,8 @@
   // Since nobody seemed to really notice or care it might not be worth the trouble.
 
   // Wait for GC's sweeping to complete and allow new records.
-  while (UNLIKELY((!kUseReadBarrier && !allow_new_record_) ||
-                  (kUseReadBarrier && !self->GetWeakRefAccessEnabled()))) {
+  while (UNLIKELY((!gUseReadBarrier && !allow_new_record_) ||
+                  (gUseReadBarrier && !self->GetWeakRefAccessEnabled()))) {
     // Check and run the empty checkpoint before blocking so the empty checkpoint will work in the
     // presence of threads blocking for weak ref access.
     self->CheckEmptyCheckpointFromWeakRefAccess(Locks::alloc_tracker_lock_);
diff --git a/runtime/gc/allocator/art-dlmalloc.cc b/runtime/gc/allocator/art-dlmalloc.cc
new file mode 100644
index 0000000..de0c85a
--- /dev/null
+++ b/runtime/gc/allocator/art-dlmalloc.cc
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include "art-dlmalloc.h"
+
+#include <android-base/logging.h>
+
+#include "base/bit_utils.h"
+
+// ART specific morecore implementation defined in space.cc.
+static void* art_heap_morecore(void* m, intptr_t increment);
+#define MORECORE(x) art_heap_morecore(m, x)
+
+// Custom heap error handling.
+#define PROCEED_ON_ERROR 0
+static void art_heap_corruption(const char* function);
+static void art_heap_usage_error(const char* function, void* p);
+#define CORRUPTION_ERROR_ACTION(m) art_heap_corruption(__FUNCTION__)
+#define USAGE_ERROR_ACTION(m, p) art_heap_usage_error(__FUNCTION__, p)
+
+// Ugly inclusion of C file so that ART specific #defines configure dlmalloc for our use for
+// mspaces (regular dlmalloc is still declared in bionic).
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wredundant-decls"
+#pragma GCC diagnostic ignored "-Wempty-body"
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+#pragma GCC diagnostic ignored "-Wnull-pointer-arithmetic"
+#pragma GCC diagnostic ignored "-Wexpansion-to-defined"
+#include "dlmalloc.c"  // NOLINT
+// Note: dlmalloc.c uses a DEBUG define to drive debug code. This interferes with the DEBUG severity
+//       of libbase, so undefine it now.
+#undef DEBUG
+#pragma GCC diagnostic pop
+
+static void* art_heap_morecore(void* m, intptr_t increment) {
+  return ::art::gc::allocator::ArtDlMallocMoreCore(m, increment);
+}
+
+static void art_heap_corruption(const char* function) {
+  LOG(FATAL) << "Corrupt heap detected in: " << function;
+}
+
+static void art_heap_usage_error(const char* function, void* p) {
+  LOG(FATAL) << "Incorrect use of function '" << function << "' argument " << p
+      << " not expected";
+}
+
+#include <sys/mman.h>
+
+#include "base/utils.h"
+#include "runtime_globals.h"
+
+extern "C" void DlmallocMadviseCallback(void* start, void* end, size_t used_bytes, void* arg) {
+  // Is this chunk in use?
+  if (used_bytes != 0) {
+    return;
+  }
+  // Do we have any whole pages to give back?
+  start = reinterpret_cast<void*>(art::RoundUp(reinterpret_cast<uintptr_t>(start), art::kPageSize));
+  end = reinterpret_cast<void*>(art::RoundDown(reinterpret_cast<uintptr_t>(end), art::kPageSize));
+  if (end > start) {
+    size_t length = reinterpret_cast<uint8_t*>(end) - reinterpret_cast<uint8_t*>(start);
+    int rc = madvise(start, length, MADV_DONTNEED);
+    if (UNLIKELY(rc != 0)) {
+      errno = rc;
+      PLOG(FATAL) << "madvise failed during heap trimming";
+    }
+    size_t* reclaimed = reinterpret_cast<size_t*>(arg);
+    *reclaimed += length;
+  }
+}
+
+extern "C" void DlmallocBytesAllocatedCallback(void* start ATTRIBUTE_UNUSED,
+                                               void* end ATTRIBUTE_UNUSED,
+                                               size_t used_bytes,
+                                               void* arg) {
+  if (used_bytes == 0) {
+    return;
+  }
+  size_t* bytes_allocated = reinterpret_cast<size_t*>(arg);
+  *bytes_allocated += used_bytes + sizeof(size_t);
+}
+
+extern "C" void DlmallocObjectsAllocatedCallback(void* start ATTRIBUTE_UNUSED,
+                                                 void* end ATTRIBUTE_UNUSED,
+                                                 size_t used_bytes,
+                                                 void* arg) {
+  if (used_bytes == 0) {
+    return;
+  }
+  size_t* objects_allocated = reinterpret_cast<size_t*>(arg);
+  ++(*objects_allocated);
+}
diff --git a/runtime/gc/allocator/art-dlmalloc.h b/runtime/gc/allocator/art-dlmalloc.h
new file mode 100644
index 0000000..296de72
--- /dev/null
+++ b/runtime/gc/allocator/art-dlmalloc.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2011 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_RUNTIME_GC_ALLOCATOR_ART_DLMALLOC_H_
+#define ART_RUNTIME_GC_ALLOCATOR_ART_DLMALLOC_H_
+
+#include <cstdint>
+
+// Configure dlmalloc for mspaces.
+// Avoid a collision with one used in llvm.
+#undef HAVE_MMAP
+#define HAVE_MMAP 0
+#define HAVE_MREMAP 0
+#define HAVE_MORECORE 1
+#define MSPACES 1
+#define NO_MALLINFO 1
+#define ONLY_MSPACES 1
+#define MALLOC_INSPECT_ALL 1
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wredundant-decls"
+#pragma GCC diagnostic ignored "-Wnull-pointer-arithmetic"
+#include "dlmalloc.h"
+#pragma GCC diagnostic pop
+
+// Callback for dlmalloc_inspect_all or mspace_inspect_all that will madvise(2) unused
+// pages back to the kernel.
+extern "C" void DlmallocMadviseCallback(void* start, void* end, size_t used_bytes, void* /*arg*/);
+
+// Callbacks for dlmalloc_inspect_all or mspace_inspect_all that will
+// count the number of bytes allocated and objects allocated,
+// respectively.
+extern "C" void DlmallocBytesAllocatedCallback(void* start, void* end, size_t used_bytes, void* arg);
+extern "C" void DlmallocObjectsAllocatedCallback(void* start, void* end, size_t used_bytes, void* arg);
+
+namespace art {
+namespace gc {
+namespace allocator {
+
+// Callback from dlmalloc when it needs to increase the footprint. Must be implemented somewhere
+// else (currently dlmalloc_space.cc).
+void* ArtDlMallocMoreCore(void* mspace, intptr_t increment);
+
+}  // namespace allocator
+}  // namespace gc
+}  // namespace art
+
+#endif  // ART_RUNTIME_GC_ALLOCATOR_ART_DLMALLOC_H_
diff --git a/runtime/gc/allocator/dlmalloc.cc b/runtime/gc/allocator/dlmalloc.cc
deleted file mode 100644
index 79d4fbf..0000000
--- a/runtime/gc/allocator/dlmalloc.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "dlmalloc.h"
-
-#include <android-base/logging.h>
-
-#include "base/bit_utils.h"
-
-// ART specific morecore implementation defined in space.cc.
-static void* art_heap_morecore(void* m, intptr_t increment);
-#define MORECORE(x) art_heap_morecore(m, x)
-
-// Custom heap error handling.
-#define PROCEED_ON_ERROR 0
-static void art_heap_corruption(const char* function);
-static void art_heap_usage_error(const char* function, void* p);
-#define CORRUPTION_ERROR_ACTION(m) art_heap_corruption(__FUNCTION__)
-#define USAGE_ERROR_ACTION(m, p) art_heap_usage_error(__FUNCTION__, p)
-
-// Ugly inclusion of C file so that ART specific #defines configure dlmalloc for our use for
-// mspaces (regular dlmalloc is still declared in bionic).
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wredundant-decls"
-#pragma GCC diagnostic ignored "-Wempty-body"
-#pragma GCC diagnostic ignored "-Wstrict-aliasing"
-#pragma GCC diagnostic ignored "-Wnull-pointer-arithmetic"
-#pragma GCC diagnostic ignored "-Wexpansion-to-defined"
-#include "../../../external/dlmalloc/malloc.c"
-// Note: malloc.c uses a DEBUG define to drive debug code. This interferes with the DEBUG severity
-//       of libbase, so undefine it now.
-#undef DEBUG
-#pragma GCC diagnostic pop
-
-static void* art_heap_morecore(void* m, intptr_t increment) {
-  return ::art::gc::allocator::ArtDlMallocMoreCore(m, increment);
-}
-
-static void art_heap_corruption(const char* function) {
-  LOG(FATAL) << "Corrupt heap detected in: " << function;
-}
-
-static void art_heap_usage_error(const char* function, void* p) {
-  LOG(FATAL) << "Incorrect use of function '" << function << "' argument " << p
-      << " not expected";
-}
-
-#include <sys/mman.h>
-
-#include "base/utils.h"
-#include "runtime_globals.h"
-
-extern "C" void DlmallocMadviseCallback(void* start, void* end, size_t used_bytes, void* arg) {
-  // Is this chunk in use?
-  if (used_bytes != 0) {
-    return;
-  }
-  // Do we have any whole pages to give back?
-  start = reinterpret_cast<void*>(art::RoundUp(reinterpret_cast<uintptr_t>(start), art::kPageSize));
-  end = reinterpret_cast<void*>(art::RoundDown(reinterpret_cast<uintptr_t>(end), art::kPageSize));
-  if (end > start) {
-    size_t length = reinterpret_cast<uint8_t*>(end) - reinterpret_cast<uint8_t*>(start);
-    int rc = madvise(start, length, MADV_DONTNEED);
-    if (UNLIKELY(rc != 0)) {
-      errno = rc;
-      PLOG(FATAL) << "madvise failed during heap trimming";
-    }
-    size_t* reclaimed = reinterpret_cast<size_t*>(arg);
-    *reclaimed += length;
-  }
-}
-
-extern "C" void DlmallocBytesAllocatedCallback(void* start ATTRIBUTE_UNUSED,
-                                               void* end ATTRIBUTE_UNUSED,
-                                               size_t used_bytes,
-                                               void* arg) {
-  if (used_bytes == 0) {
-    return;
-  }
-  size_t* bytes_allocated = reinterpret_cast<size_t*>(arg);
-  *bytes_allocated += used_bytes + sizeof(size_t);
-}
-
-extern "C" void DlmallocObjectsAllocatedCallback(void* start ATTRIBUTE_UNUSED,
-                                                 void* end ATTRIBUTE_UNUSED,
-                                                 size_t used_bytes,
-                                                 void* arg) {
-  if (used_bytes == 0) {
-    return;
-  }
-  size_t* objects_allocated = reinterpret_cast<size_t*>(arg);
-  ++(*objects_allocated);
-}
diff --git a/runtime/gc/allocator/dlmalloc.h b/runtime/gc/allocator/dlmalloc.h
deleted file mode 100644
index b12691a..0000000
--- a/runtime/gc/allocator/dlmalloc.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2011 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_RUNTIME_GC_ALLOCATOR_DLMALLOC_H_
-#define ART_RUNTIME_GC_ALLOCATOR_DLMALLOC_H_
-
-#include <cstdint>
-
-// Configure dlmalloc for mspaces.
-// Avoid a collision with one used in llvm.
-#undef HAVE_MMAP
-#define HAVE_MMAP 0
-#define HAVE_MREMAP 0
-#define HAVE_MORECORE 1
-#define MSPACES 1
-#define NO_MALLINFO 1
-#define ONLY_MSPACES 1
-#define MALLOC_INSPECT_ALL 1
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wredundant-decls"
-#pragma GCC diagnostic ignored "-Wnull-pointer-arithmetic"
-#include "../../external/dlmalloc/malloc.h"
-#pragma GCC diagnostic pop
-
-// Callback for dlmalloc_inspect_all or mspace_inspect_all that will madvise(2) unused
-// pages back to the kernel.
-extern "C" void DlmallocMadviseCallback(void* start, void* end, size_t used_bytes, void* /*arg*/);
-
-// Callbacks for dlmalloc_inspect_all or mspace_inspect_all that will
-// count the number of bytes allocated and objects allocated,
-// respectively.
-extern "C" void DlmallocBytesAllocatedCallback(void* start, void* end, size_t used_bytes, void* arg);
-extern "C" void DlmallocObjectsAllocatedCallback(void* start, void* end, size_t used_bytes, void* arg);
-
-namespace art {
-namespace gc {
-namespace allocator {
-
-// Callback from dlmalloc when it needs to increase the footprint. Must be implemented somewhere
-// else (currently dlmalloc_space.cc).
-void* ArtDlMallocMoreCore(void* mspace, intptr_t increment);
-
-}  // namespace allocator
-}  // namespace gc
-}  // namespace art
-
-#endif  // ART_RUNTIME_GC_ALLOCATOR_DLMALLOC_H_
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 0de62fe..1f123aa 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -160,17 +160,31 @@
   if (young_gen_) {
     gc_time_histogram_ = metrics->YoungGcCollectionTime();
     metrics_gc_count_ = metrics->YoungGcCount();
+    metrics_gc_count_delta_ = metrics->YoungGcCountDelta();
     gc_throughput_histogram_ = metrics->YoungGcThroughput();
     gc_tracing_throughput_hist_ = metrics->YoungGcTracingThroughput();
     gc_throughput_avg_ = metrics->YoungGcThroughputAvg();
     gc_tracing_throughput_avg_ = metrics->YoungGcTracingThroughputAvg();
+    gc_scanned_bytes_ = metrics->YoungGcScannedBytes();
+    gc_scanned_bytes_delta_ = metrics->YoungGcScannedBytesDelta();
+    gc_freed_bytes_ = metrics->YoungGcFreedBytes();
+    gc_freed_bytes_delta_ = metrics->YoungGcFreedBytesDelta();
+    gc_duration_ = metrics->YoungGcDuration();
+    gc_duration_delta_ = metrics->YoungGcDurationDelta();
   } else {
     gc_time_histogram_ = metrics->FullGcCollectionTime();
     metrics_gc_count_ = metrics->FullGcCount();
+    metrics_gc_count_delta_ = metrics->FullGcCountDelta();
     gc_throughput_histogram_ = metrics->FullGcThroughput();
     gc_tracing_throughput_hist_ = metrics->FullGcTracingThroughput();
     gc_throughput_avg_ = metrics->FullGcThroughputAvg();
     gc_tracing_throughput_avg_ = metrics->FullGcTracingThroughputAvg();
+    gc_scanned_bytes_ = metrics->FullGcScannedBytes();
+    gc_scanned_bytes_delta_ = metrics->FullGcScannedBytesDelta();
+    gc_freed_bytes_ = metrics->FullGcFreedBytes();
+    gc_freed_bytes_delta_ = metrics->FullGcFreedBytesDelta();
+    gc_duration_ = metrics->FullGcDuration();
+    gc_duration_delta_ = metrics->FullGcDurationDelta();
   }
 }
 
@@ -575,10 +589,11 @@
     if (kIsDebugBuild && !cc->use_generational_cc_) {
       cc->region_space_->AssertAllRegionLiveBytesZeroOrCleared();
     }
-    if (UNLIKELY(Runtime::Current()->IsActiveTransaction())) {
-      CHECK(Runtime::Current()->IsAotCompiler());
+    Runtime* runtime = Runtime::Current();
+    if (UNLIKELY(runtime->IsActiveTransaction())) {
+      CHECK(runtime->IsAotCompiler());
       TimingLogger::ScopedTiming split3("(Paused)VisitTransactionRoots", cc->GetTimings());
-      Runtime::Current()->VisitTransactionRoots(cc);
+      runtime->VisitTransactionRoots(cc);
     }
     if (kUseBakerReadBarrier && kGrayDirtyImmuneObjects) {
       cc->GrayAllNewlyDirtyImmuneObjects();
@@ -587,15 +602,10 @@
         cc->VerifyGrayImmuneObjects();
       }
     }
-    // May be null during runtime creation, in this case leave java_lang_Object null.
-    // This is safe since single threaded behavior should mean FillWithFakeObject does not
-    // happen when java_lang_Object_ is null.
-    if (WellKnownClasses::java_lang_Object != nullptr) {
-      cc->java_lang_Object_ = down_cast<mirror::Class*>(cc->Mark(thread,
-          WellKnownClasses::ToClass(WellKnownClasses::java_lang_Object).Ptr()));
-    } else {
-      cc->java_lang_Object_ = nullptr;
-    }
+    ObjPtr<mirror::Class> java_lang_Object =
+        GetClassRoot<mirror::Object, kWithoutReadBarrier>(runtime->GetClassLinker());
+    DCHECK(java_lang_Object != nullptr);
+    cc->java_lang_Object_ = down_cast<mirror::Class*>(cc->Mark(thread, java_lang_Object.Ptr()));
   }
 
  private:
@@ -1692,8 +1702,6 @@
     if (kVerboseMode) {
       LOG(INFO) << "SweepSystemWeaks done";
     }
-    // Free data for class loaders that we unloaded.
-    Runtime::Current()->GetClassLinker()->CleanupClassLoaders();
     // Marking is done. Disable marking.
     DisableMarking();
     CheckEmptyMarkStack();
@@ -1739,6 +1747,10 @@
            thread->IsSuspended() ||
            thread->GetState() == ThreadState::kWaitingPerformingGc)
         << thread->GetState() << " thread " << thread << " self " << self;
+    // We sweep interpreter caches here so that it can be done after all
+    // reachable objects are marked and the mutators can sweep their caches
+    // without synchronization.
+    thread->SweepInterpreterCache(concurrent_copying_);
     // Disable the thread-local is_gc_marking flag.
     // Note a thread that has just started right before this checkpoint may have already this flag
     // set to false, which is ok.
@@ -1887,7 +1899,10 @@
         << " cc->is_marking=" << is_marking_;
     CHECK(self == thread_running_gc_)
         << "Only GC-running thread should access the mark stack "
-        << "in the GC exclusive mark stack mode";
+        << "in the GC exclusive mark stack mode. "
+        << "ref=" << to_ref
+        << " self->gc_marking=" << self->GetIsGcMarking()
+        << " cc->is_marking=" << is_marking_;
     // Access the GC mark stack without a lock.
     if (UNLIKELY(gc_mark_stack_->IsFull())) {
       ExpandGcMarkStack();
@@ -2716,6 +2731,11 @@
   }
   Thread* self = Thread::Current();
 
+  // Free data for class loaders that we unloaded. This includes removing
+  // dead methods from JIT's internal maps. This must be done before
+  // reclaiming the memory of the dead methods' declaring classes.
+  Runtime::Current()->GetClassLinker()->CleanupClassLoaders();
+
   {
     // Double-check that the mark stack is empty.
     // Note: need to set this after VerifyNoFromSpaceRef().
diff --git a/runtime/gc/collector/concurrent_copying.h b/runtime/gc/collector/concurrent_copying.h
index c274fed..888c38a 100644
--- a/runtime/gc/collector/concurrent_copying.h
+++ b/runtime/gc/collector/concurrent_copying.h
@@ -161,6 +161,10 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   void AssertNoThreadMarkStackMapping(Thread* thread) REQUIRES(!mark_stack_lock_);
+  // Dump information about reference `ref` and return it as a string.
+  // Use `ref_name` to name the reference in messages. Each message is prefixed with `indent`.
+  std::string DumpReferenceInfo(mirror::Object* ref, const char* ref_name, const char* indent = "")
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
  private:
   void PushOntoMarkStack(Thread* const self, mirror::Object* obj)
@@ -282,10 +286,6 @@
   void ComputeUnevacFromSpaceLiveRatio();
   void LogFromSpaceRefHolder(mirror::Object* obj, MemberOffset offset)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  // Dump information about reference `ref` and return it as a string.
-  // Use `ref_name` to name the reference in messages. Each message is prefixed with `indent`.
-  std::string DumpReferenceInfo(mirror::Object* ref, const char* ref_name, const char* indent = "")
-      REQUIRES_SHARED(Locks::mutator_lock_);
   // Dump information about heap reference `ref`, referenced from object `obj` at offset `offset`,
   // and return it as a string.
   std::string DumpHeapReference(mirror::Object* obj, MemberOffset offset, mirror::Object* ref)
diff --git a/runtime/gc/collector/garbage_collector.cc b/runtime/gc/collector/garbage_collector.cc
index 80b3982..03a432d 100644
--- a/runtime/gc/collector/garbage_collector.cc
+++ b/runtime/gc/collector/garbage_collector.cc
@@ -72,10 +72,17 @@
       freed_bytes_histogram_((name_ + " freed-bytes").c_str(), kMemBucketSize, kMemBucketCount),
       gc_time_histogram_(nullptr),
       metrics_gc_count_(nullptr),
+      metrics_gc_count_delta_(nullptr),
       gc_throughput_histogram_(nullptr),
       gc_tracing_throughput_hist_(nullptr),
       gc_throughput_avg_(nullptr),
       gc_tracing_throughput_avg_(nullptr),
+      gc_scanned_bytes_(nullptr),
+      gc_scanned_bytes_delta_(nullptr),
+      gc_freed_bytes_(nullptr),
+      gc_freed_bytes_delta_(nullptr),
+      gc_duration_(nullptr),
+      gc_duration_delta_(nullptr),
       cumulative_timings_(name),
       pause_histogram_lock_("pause histogram lock", kDefaultMutexLevel, true),
       is_transaction_active_(false),
@@ -189,19 +196,26 @@
     RegisterPause(duration_ns);
   }
   total_time_ns_ += duration_ns;
-  uint64_t total_pause_time = 0;
+  uint64_t total_pause_time_ns = 0;
   for (uint64_t pause_time : current_iteration->GetPauseTimes()) {
     MutexLock mu(self, pause_histogram_lock_);
     pause_histogram_.AdjustAndAddValue(pause_time);
-    total_pause_time += pause_time;
+    total_pause_time_ns += pause_time;
   }
   metrics::ArtMetrics* metrics = runtime->GetMetrics();
   // Report STW pause time in microseconds.
-  metrics->WorldStopTimeDuringGCAvg()->Add(total_pause_time / 1'000);
+  const uint64_t total_pause_time_us = total_pause_time_ns / 1'000;
+  metrics->WorldStopTimeDuringGCAvg()->Add(total_pause_time_us);
+  metrics->GcWorldStopTime()->Add(total_pause_time_us);
+  metrics->GcWorldStopTimeDelta()->Add(total_pause_time_us);
+  metrics->GcWorldStopCount()->AddOne();
+  metrics->GcWorldStopCountDelta()->AddOne();
   // Report total collection time of all GCs put together.
   metrics->TotalGcCollectionTime()->Add(NsToMs(duration_ns));
+  metrics->TotalGcCollectionTimeDelta()->Add(NsToMs(duration_ns));
   if (are_metrics_initialized_) {
     metrics_gc_count_->Add(1);
+    metrics_gc_count_delta_->Add(1);
     // Report GC time in milliseconds.
     gc_time_histogram_->Add(NsToMs(duration_ns));
     // Throughput in bytes/s. Add 1us to prevent possible division by 0.
@@ -216,6 +230,13 @@
     throughput = current_iteration->GetEstimatedThroughput() / MB;
     gc_throughput_histogram_->Add(throughput);
     gc_throughput_avg_->Add(throughput);
+
+    gc_scanned_bytes_->Add(current_iteration->GetScannedBytes());
+    gc_scanned_bytes_delta_->Add(current_iteration->GetScannedBytes());
+    gc_freed_bytes_->Add(current_iteration->GetFreedBytes());
+    gc_freed_bytes_delta_->Add(current_iteration->GetFreedBytes());
+    gc_duration_->Add(NsToMs(current_iteration->GetDurationNs()));
+    gc_duration_delta_->Add(NsToMs(current_iteration->GetDurationNs()));
   }
   is_transaction_active_ = false;
 }
diff --git a/runtime/gc/collector/garbage_collector.h b/runtime/gc/collector/garbage_collector.h
index d439914..948a868 100644
--- a/runtime/gc/collector/garbage_collector.h
+++ b/runtime/gc/collector/garbage_collector.h
@@ -162,10 +162,17 @@
   Histogram<size_t> freed_bytes_histogram_;
   metrics::MetricsBase<int64_t>* gc_time_histogram_;
   metrics::MetricsBase<uint64_t>* metrics_gc_count_;
+  metrics::MetricsBase<uint64_t>* metrics_gc_count_delta_;
   metrics::MetricsBase<int64_t>* gc_throughput_histogram_;
   metrics::MetricsBase<int64_t>* gc_tracing_throughput_hist_;
   metrics::MetricsBase<uint64_t>* gc_throughput_avg_;
   metrics::MetricsBase<uint64_t>* gc_tracing_throughput_avg_;
+  metrics::MetricsBase<uint64_t>* gc_scanned_bytes_;
+  metrics::MetricsBase<uint64_t>* gc_scanned_bytes_delta_;
+  metrics::MetricsBase<uint64_t>* gc_freed_bytes_;
+  metrics::MetricsBase<uint64_t>* gc_freed_bytes_delta_;
+  metrics::MetricsBase<uint64_t>* gc_duration_;
+  metrics::MetricsBase<uint64_t>* gc_duration_delta_;
   uint64_t total_thread_cpu_time_ns_;
   uint64_t total_time_ns_;
   uint64_t total_freed_objects_;
diff --git a/runtime/gc/collector/immune_spaces_test.cc b/runtime/gc/collector/immune_spaces_test.cc
index a0ea60d..caa8106 100644
--- a/runtime/gc/collector/immune_spaces_test.cc
+++ b/runtime/gc/collector/immune_spaces_test.cc
@@ -16,7 +16,8 @@
 
 #include <sys/mman.h>
 
-#include "common_runtime_test.h"
+#include "base/common_art_test.h"
+#include "base/utils.h"
 #include "gc/collector/immune_spaces.h"
 #include "gc/space/image_space.h"
 #include "gc/space/space-inl.h"
@@ -46,7 +47,7 @@
                  MemMap&& oat_map)
       : ImageSpace("FakeImageSpace",
                    /*image_location=*/"",
-                   /*profile_file=*/{},
+                   /*profile_files=*/{},
                    std::move(map),
                    std::move(live_bitmap),
                    map.End()),
@@ -59,7 +60,7 @@
   MemMap oat_map_;
 };
 
-class ImmuneSpacesTest : public CommonRuntimeTest {
+class ImmuneSpacesTest : public CommonArtTest {
   static constexpr size_t kMaxBitmaps = 10;
 
  public:
diff --git a/runtime/gc/collector/mark_compact-inl.h b/runtime/gc/collector/mark_compact-inl.h
new file mode 100644
index 0000000..c9b792e8
--- /dev/null
+++ b/runtime/gc/collector/mark_compact-inl.h
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2021 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_RUNTIME_GC_COLLECTOR_MARK_COMPACT_INL_H_
+#define ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_INL_H_
+
+#include "gc/space/bump_pointer_space.h"
+#include "mark_compact.h"
+#include "mirror/object-inl.h"
+
+namespace art {
+namespace gc {
+namespace collector {
+
+inline void MarkCompact::UpdateClassAfterObjectMap(mirror::Object* obj) {
+  mirror::Class* klass = obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
+  // Track a class if it needs walking super-classes for visiting references or
+  // if it's higher in address order than its objects and is in moving space.
+  if (UNLIKELY(
+          (std::less<mirror::Object*>{}(obj, klass) && bump_pointer_space_->HasAddress(klass)) ||
+          (klass->GetReferenceInstanceOffsets<kVerifyNone>() == mirror::Class::kClassWalkSuper &&
+           walk_super_class_cache_ != klass))) {
+    // Since this function gets invoked in the compaction pause as well, it is
+    // preferable to store such super class separately rather than updating key
+    // as the latter would require traversing the hierarchy for every object of 'klass'.
+    auto ret1 = class_after_obj_hash_map_.try_emplace(ObjReference::FromMirrorPtr(klass),
+                                                      ObjReference::FromMirrorPtr(obj));
+    if (ret1.second) {
+      if (klass->GetReferenceInstanceOffsets<kVerifyNone>() == mirror::Class::kClassWalkSuper) {
+        // In this case we require traversing through the super class hierarchy
+        // and find the super class at the highest address order.
+        mirror::Class* highest_klass = bump_pointer_space_->HasAddress(klass) ? klass : nullptr;
+        for (ObjPtr<mirror::Class> k = klass->GetSuperClass<kVerifyNone, kWithoutReadBarrier>();
+             k != nullptr;
+             k = k->GetSuperClass<kVerifyNone, kWithoutReadBarrier>()) {
+          // TODO: Can we break once we encounter a super class outside the moving space?
+          if (bump_pointer_space_->HasAddress(k.Ptr())) {
+            highest_klass = std::max(highest_klass, k.Ptr(), std::less<mirror::Class*>());
+          }
+        }
+        if (highest_klass != nullptr && highest_klass != klass) {
+          auto ret2 = super_class_after_class_hash_map_.try_emplace(
+              ObjReference::FromMirrorPtr(klass), ObjReference::FromMirrorPtr(highest_klass));
+          DCHECK(ret2.second);
+        } else {
+          walk_super_class_cache_ = klass;
+        }
+      }
+    } else if (std::less<mirror::Object*>{}(obj, ret1.first->second.AsMirrorPtr())) {
+      ret1.first->second = ObjReference::FromMirrorPtr(obj);
+    }
+  }
+}
+
+template <size_t kAlignment>
+inline uintptr_t MarkCompact::LiveWordsBitmap<kAlignment>::SetLiveWords(uintptr_t begin,
+                                                                        size_t size) {
+  const uintptr_t begin_bit_idx = MemRangeBitmap::BitIndexFromAddr(begin);
+  DCHECK(!Bitmap::TestBit(begin_bit_idx));
+  // Range to set bit: [begin, end]
+  uintptr_t end = begin + size - kAlignment;
+  const uintptr_t end_bit_idx = MemRangeBitmap::BitIndexFromAddr(end);
+  uintptr_t* begin_bm_address = Bitmap::Begin() + Bitmap::BitIndexToWordIndex(begin_bit_idx);
+  uintptr_t* end_bm_address = Bitmap::Begin() + Bitmap::BitIndexToWordIndex(end_bit_idx);
+  ptrdiff_t diff = end_bm_address - begin_bm_address;
+  uintptr_t mask = Bitmap::BitIndexToMask(begin_bit_idx);
+  // Bits that needs to be set in the first word, if it's not also the last word
+  mask = ~(mask - 1);
+  if (diff > 0) {
+    *begin_bm_address |= mask;
+    mask = ~0;
+    // Even though memset can handle the (diff == 1) case but we should avoid the
+    // overhead of a function call for this, highly likely (as most of the objects
+    // are small), case.
+    if (diff > 1) {
+      // Set all intermediate bits to 1.
+      std::memset(static_cast<void*>(begin_bm_address + 1), 0xff, (diff - 1) * sizeof(uintptr_t));
+    }
+  }
+  uintptr_t end_mask = Bitmap::BitIndexToMask(end_bit_idx);
+  *end_bm_address |= mask & (end_mask | (end_mask - 1));
+  return begin_bit_idx;
+}
+
+template <size_t kAlignment> template <typename Visitor>
+inline void MarkCompact::LiveWordsBitmap<kAlignment>::VisitLiveStrides(uintptr_t begin_bit_idx,
+                                                                       uint8_t* end,
+                                                                       const size_t bytes,
+                                                                       Visitor&& visitor) const {
+  // Range to visit [begin_bit_idx, end_bit_idx]
+  DCHECK(IsAligned<kAlignment>(end));
+  end -= kAlignment;
+  const uintptr_t end_bit_idx = MemRangeBitmap::BitIndexFromAddr(reinterpret_cast<uintptr_t>(end));
+  DCHECK_LE(begin_bit_idx, end_bit_idx);
+  uintptr_t begin_word_idx = Bitmap::BitIndexToWordIndex(begin_bit_idx);
+  const uintptr_t end_word_idx = Bitmap::BitIndexToWordIndex(end_bit_idx);
+  DCHECK(Bitmap::TestBit(begin_bit_idx));
+  size_t stride_size = 0;
+  size_t idx_in_word = 0;
+  size_t num_heap_words = bytes / kAlignment;
+  uintptr_t live_stride_start_idx;
+  uintptr_t word = Bitmap::Begin()[begin_word_idx];
+
+  // Setup the first word.
+  word &= ~(Bitmap::BitIndexToMask(begin_bit_idx) - 1);
+  begin_bit_idx = RoundDown(begin_bit_idx, Bitmap::kBitsPerBitmapWord);
+
+  do {
+    if (UNLIKELY(begin_word_idx == end_word_idx)) {
+      uintptr_t mask = Bitmap::BitIndexToMask(end_bit_idx);
+      word &= mask | (mask - 1);
+    }
+    if (~word == 0) {
+      // All bits in the word are marked.
+      if (stride_size == 0) {
+        live_stride_start_idx = begin_bit_idx;
+      }
+      stride_size += Bitmap::kBitsPerBitmapWord;
+      if (num_heap_words <= stride_size) {
+        break;
+      }
+    } else {
+      while (word != 0) {
+        // discard 0s
+        size_t shift = CTZ(word);
+        idx_in_word += shift;
+        word >>= shift;
+        if (stride_size > 0) {
+          if (shift > 0) {
+            if (num_heap_words <= stride_size) {
+              break;
+            }
+            visitor(live_stride_start_idx, stride_size, /*is_last*/ false);
+            num_heap_words -= stride_size;
+            live_stride_start_idx = begin_bit_idx + idx_in_word;
+            stride_size = 0;
+          }
+        } else {
+          live_stride_start_idx = begin_bit_idx + idx_in_word;
+        }
+        // consume 1s
+        shift = CTZ(~word);
+        DCHECK_NE(shift, 0u);
+        word >>= shift;
+        idx_in_word += shift;
+        stride_size += shift;
+      }
+      // If the whole word == 0 or the higher bits are 0s, then we exit out of
+      // the above loop without completely consuming the word, so call visitor,
+      // if needed.
+      if (idx_in_word < Bitmap::kBitsPerBitmapWord && stride_size > 0) {
+        if (num_heap_words <= stride_size) {
+          break;
+        }
+        visitor(live_stride_start_idx, stride_size, /*is_last*/ false);
+        num_heap_words -= stride_size;
+        stride_size = 0;
+      }
+      idx_in_word = 0;
+    }
+    begin_bit_idx += Bitmap::kBitsPerBitmapWord;
+    begin_word_idx++;
+    if (UNLIKELY(begin_word_idx > end_word_idx)) {
+      num_heap_words = std::min(stride_size, num_heap_words);
+      break;
+    }
+    word = Bitmap::Begin()[begin_word_idx];
+  } while (true);
+
+  if (stride_size > 0) {
+    visitor(live_stride_start_idx, num_heap_words, /*is_last*/ true);
+  }
+}
+
+template <size_t kAlignment>
+inline
+uint32_t MarkCompact::LiveWordsBitmap<kAlignment>::FindNthLiveWordOffset(size_t chunk_idx,
+                                                                         uint32_t n) const {
+  DCHECK_LT(n, kBitsPerVectorWord);
+  const size_t index = chunk_idx * kBitmapWordsPerVectorWord;
+  for (uint32_t i = 0; i < kBitmapWordsPerVectorWord; i++) {
+    uintptr_t word = Bitmap::Begin()[index + i];
+    if (~word == 0) {
+      if (n < Bitmap::kBitsPerBitmapWord) {
+        return i * Bitmap::kBitsPerBitmapWord + n;
+      }
+      n -= Bitmap::kBitsPerBitmapWord;
+    } else {
+      uint32_t j = 0;
+      while (word != 0) {
+        // count contiguous 0s
+        uint32_t shift = CTZ(word);
+        word >>= shift;
+        j += shift;
+        // count contiguous 1s
+        shift = CTZ(~word);
+        DCHECK_NE(shift, 0u);
+        if (shift > n) {
+          return i * Bitmap::kBitsPerBitmapWord + j + n;
+        }
+        n -= shift;
+        word >>= shift;
+        j += shift;
+      }
+    }
+  }
+  UNREACHABLE();
+}
+
+inline void MarkCompact::UpdateRef(mirror::Object* obj, MemberOffset offset) {
+  mirror::Object* old_ref = obj->GetFieldObject<
+      mirror::Object, kVerifyNone, kWithoutReadBarrier, /*kIsVolatile*/false>(offset);
+  if (kIsDebugBuild) {
+    if (live_words_bitmap_->HasAddress(old_ref)
+        && reinterpret_cast<uint8_t*>(old_ref) < black_allocations_begin_
+        && !moving_space_bitmap_->Test(old_ref)) {
+      mirror::Object* from_ref = GetFromSpaceAddr(old_ref);
+      std::ostringstream oss;
+      heap_->DumpSpaces(oss);
+      MemMap::DumpMaps(oss, /* terse= */ true);
+      LOG(FATAL) << "Not marked in the bitmap ref=" << old_ref
+                 << " from_ref=" << from_ref
+                 << " offset=" << offset
+                 << " obj=" << obj
+                 << " obj-validity=" << IsValidObject(obj)
+                 << " from-space=" << static_cast<void*>(from_space_begin_)
+                 << " bitmap= " << moving_space_bitmap_->DumpMemAround(old_ref)
+                 << " from_ref "
+                 << heap_->GetVerification()->DumpRAMAroundAddress(
+                     reinterpret_cast<uintptr_t>(from_ref), 128)
+                 << " obj "
+                 << heap_->GetVerification()->DumpRAMAroundAddress(
+                     reinterpret_cast<uintptr_t>(obj), 128)
+                 << " old_ref " << heap_->GetVerification()->DumpRAMAroundAddress(
+                     reinterpret_cast<uintptr_t>(old_ref), 128)
+                 << " maps\n" << oss.str();
+    }
+  }
+  mirror::Object* new_ref = PostCompactAddress(old_ref);
+  if (new_ref != old_ref) {
+    obj->SetFieldObjectWithoutWriteBarrier<
+        /*kTransactionActive*/false, /*kCheckTransaction*/false, kVerifyNone, /*kIsVolatile*/false>(
+            offset,
+            new_ref);
+  }
+}
+
+inline bool MarkCompact::VerifyRootSingleUpdate(void* root,
+                                                mirror::Object* old_ref,
+                                                const RootInfo& info) {
+  // ASAN promotes stack-frames to heap in order to detect
+  // stack-use-after-return issues. So skip using this double-root update
+  // detection on ASAN as well.
+  if (kIsDebugBuild && !kMemoryToolIsAvailable) {
+    void* stack_low_addr = stack_low_addr_;
+    void* stack_high_addr = stack_high_addr_;
+    if (!live_words_bitmap_->HasAddress(old_ref)) {
+      return false;
+    }
+    Thread* self = Thread::Current();
+    if (UNLIKELY(stack_low_addr == nullptr)) {
+      stack_low_addr = self->GetStackEnd();
+      stack_high_addr = reinterpret_cast<char*>(stack_low_addr) + self->GetStackSize();
+    }
+    if (root < stack_low_addr || root > stack_high_addr) {
+      MutexLock mu(self, lock_);
+      auto ret = updated_roots_->insert(root);
+      DCHECK(ret.second) << "root=" << root << " old_ref=" << old_ref
+                         << " stack_low_addr=" << stack_low_addr
+                         << " stack_high_addr=" << stack_high_addr;
+    }
+    DCHECK(reinterpret_cast<uint8_t*>(old_ref) >= black_allocations_begin_ ||
+           live_words_bitmap_->Test(old_ref))
+        << "ref=" << old_ref << " <" << mirror::Object::PrettyTypeOf(old_ref) << "> RootInfo ["
+        << info << "]";
+  }
+  return true;
+}
+
+inline void MarkCompact::UpdateRoot(mirror::CompressedReference<mirror::Object>* root,
+                                    const RootInfo& info) {
+  DCHECK(!root->IsNull());
+  mirror::Object* old_ref = root->AsMirrorPtr();
+  if (VerifyRootSingleUpdate(root, old_ref, info)) {
+    mirror::Object* new_ref = PostCompactAddress(old_ref);
+    if (old_ref != new_ref) {
+      root->Assign(new_ref);
+    }
+  }
+}
+
+inline void MarkCompact::UpdateRoot(mirror::Object** root, const RootInfo& info) {
+  mirror::Object* old_ref = *root;
+  if (VerifyRootSingleUpdate(root, old_ref, info)) {
+    mirror::Object* new_ref = PostCompactAddress(old_ref);
+    if (old_ref != new_ref) {
+      *root = new_ref;
+    }
+  }
+}
+
+template <size_t kAlignment>
+inline size_t MarkCompact::LiveWordsBitmap<kAlignment>::CountLiveWordsUpto(size_t bit_idx) const {
+  const size_t word_offset = Bitmap::BitIndexToWordIndex(bit_idx);
+  uintptr_t word;
+  size_t ret = 0;
+  // This is needed only if we decide to make chunks 128-bit but still
+  // choose to use 64-bit word for bitmap. Ideally we should use 128-bit
+  // SIMD instructions to compute popcount.
+  if (kBitmapWordsPerVectorWord > 1) {
+    for (size_t i = RoundDown(word_offset, kBitmapWordsPerVectorWord); i < word_offset; i++) {
+      word = Bitmap::Begin()[i];
+      ret += POPCOUNT(word);
+    }
+  }
+  word = Bitmap::Begin()[word_offset];
+  const uintptr_t mask = Bitmap::BitIndexToMask(bit_idx);
+  DCHECK_NE(word & mask, 0u)
+        << " word_offset:" << word_offset
+        << " bit_idx:" << bit_idx
+        << " bit_idx_in_word:" << (bit_idx % Bitmap::kBitsPerBitmapWord)
+        << std::hex << " word: 0x" << word
+        << " mask: 0x" << mask << std::dec;
+  ret += POPCOUNT(word & (mask - 1));
+  return ret;
+}
+
+inline mirror::Object* MarkCompact::PostCompactBlackObjAddr(mirror::Object* old_ref) const {
+  return reinterpret_cast<mirror::Object*>(reinterpret_cast<uint8_t*>(old_ref)
+                                           - black_objs_slide_diff_);
+}
+
+inline mirror::Object* MarkCompact::PostCompactOldObjAddr(mirror::Object* old_ref) const {
+  const uintptr_t begin = live_words_bitmap_->Begin();
+  const uintptr_t addr_offset = reinterpret_cast<uintptr_t>(old_ref) - begin;
+  const size_t vec_idx = addr_offset / kOffsetChunkSize;
+  const size_t live_bytes_in_bitmap_word =
+      live_words_bitmap_->CountLiveWordsUpto(addr_offset / kAlignment) * kAlignment;
+  return reinterpret_cast<mirror::Object*>(begin
+                                           + chunk_info_vec_[vec_idx]
+                                           + live_bytes_in_bitmap_word);
+}
+
+inline mirror::Object* MarkCompact::PostCompactAddressUnchecked(mirror::Object* old_ref) const {
+  if (reinterpret_cast<uint8_t*>(old_ref) >= black_allocations_begin_) {
+    return PostCompactBlackObjAddr(old_ref);
+  }
+  if (kIsDebugBuild) {
+    mirror::Object* from_ref = GetFromSpaceAddr(old_ref);
+    DCHECK(live_words_bitmap_->Test(old_ref))
+         << "ref=" << old_ref;
+    if (!moving_space_bitmap_->Test(old_ref)) {
+      std::ostringstream oss;
+      Runtime::Current()->GetHeap()->DumpSpaces(oss);
+      MemMap::DumpMaps(oss, /* terse= */ true);
+      LOG(FATAL) << "ref=" << old_ref
+                 << " from_ref=" << from_ref
+                 << " from-space=" << static_cast<void*>(from_space_begin_)
+                 << " bitmap= " << moving_space_bitmap_->DumpMemAround(old_ref)
+                 << heap_->GetVerification()->DumpRAMAroundAddress(
+                         reinterpret_cast<uintptr_t>(from_ref), 128)
+                 << " maps\n" << oss.str();
+    }
+  }
+  return PostCompactOldObjAddr(old_ref);
+}
+
+inline mirror::Object* MarkCompact::PostCompactAddress(mirror::Object* old_ref) const {
+  // TODO: To further speedup the check, maybe we should consider caching heap
+  // start/end in this object.
+  if (LIKELY(live_words_bitmap_->HasAddress(old_ref))) {
+    return PostCompactAddressUnchecked(old_ref);
+  }
+  return old_ref;
+}
+
+}  // namespace collector
+}  // namespace gc
+}  // namespace art
+
+#endif  // ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_INL_H_
diff --git a/runtime/gc/collector/mark_compact.cc b/runtime/gc/collector/mark_compact.cc
new file mode 100644
index 0000000..bb34068
--- /dev/null
+++ b/runtime/gc/collector/mark_compact.cc
@@ -0,0 +1,4300 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#include <fcntl.h>
+// Glibc v2.19 doesn't include these in fcntl.h so host builds will fail without.
+#if !defined(FALLOC_FL_PUNCH_HOLE) || !defined(FALLOC_FL_KEEP_SIZE)
+#include <linux/falloc.h>
+#endif
+#include <linux/userfaultfd.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <numeric>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "android-base/file.h"
+#include "android-base/parsebool.h"
+#include "android-base/parseint.h"
+#include "android-base/properties.h"
+#include "android-base/strings.h"
+#include "base/file_utils.h"
+#include "base/memfd.h"
+#include "base/quasi_atomic.h"
+#include "base/systrace.h"
+#include "base/utils.h"
+#include "gc/accounting/mod_union_table-inl.h"
+#include "gc/collector_type.h"
+#include "gc/reference_processor.h"
+#include "gc/space/bump_pointer_space.h"
+#include "gc/task_processor.h"
+#include "gc/verification-inl.h"
+#include "jit/jit_code_cache.h"
+#include "mark_compact-inl.h"
+#include "mirror/object-refvisitor-inl.h"
+#include "read_barrier_config.h"
+#include "scoped_thread_state_change-inl.h"
+#include "sigchain.h"
+#include "thread_list.h"
+
+#ifdef ART_TARGET_ANDROID
+#include "com_android_art.h"
+#endif
+
+#ifndef __BIONIC__
+#ifndef MREMAP_DONTUNMAP
+#define MREMAP_DONTUNMAP 4
+#endif
+#ifndef MAP_FIXED_NOREPLACE
+#define MAP_FIXED_NOREPLACE 0x100000
+#endif
+#ifndef __NR_userfaultfd
+#if defined(__x86_64__)
+#define __NR_userfaultfd 323
+#elif defined(__i386__)
+#define __NR_userfaultfd 374
+#elif defined(__aarch64__)
+#define __NR_userfaultfd 282
+#elif defined(__arm__)
+#define __NR_userfaultfd 388
+#else
+#error "__NR_userfaultfd undefined"
+#endif
+#endif  // __NR_userfaultfd
+#endif  // __BIONIC__
+
+namespace {
+
+using ::android::base::GetBoolProperty;
+using ::android::base::ParseBool;
+using ::android::base::ParseBoolResult;
+
+}  // namespace
+
+namespace art {
+
+static bool HaveMremapDontunmap() {
+  void* old = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
+  CHECK_NE(old, MAP_FAILED);
+  void* addr = mremap(old, kPageSize, kPageSize, MREMAP_MAYMOVE | MREMAP_DONTUNMAP, nullptr);
+  CHECK_EQ(munmap(old, kPageSize), 0);
+  if (addr != MAP_FAILED) {
+    CHECK_EQ(munmap(addr, kPageSize), 0);
+    return true;
+  } else {
+    return false;
+  }
+}
+// We require MREMAP_DONTUNMAP functionality of the mremap syscall, which was
+// introduced in 5.13 kernel version. But it was backported to GKI kernels.
+static bool gHaveMremapDontunmap = IsKernelVersionAtLeast(5, 13) || HaveMremapDontunmap();
+// Bitmap of features supported by userfaultfd. This is obtained via uffd API ioctl.
+static uint64_t gUffdFeatures = 0;
+// Both, missing and minor faults on shmem are needed only for minor-fault mode.
+static constexpr uint64_t kUffdFeaturesForMinorFault =
+    UFFD_FEATURE_MISSING_SHMEM | UFFD_FEATURE_MINOR_SHMEM;
+static constexpr uint64_t kUffdFeaturesForSigbus = UFFD_FEATURE_SIGBUS;
+// We consider SIGBUS feature necessary to enable this GC as it's superior than
+// threading-based implementation for janks. However, since we have the latter
+// already implemented, for testing purposes, we allow choosing either of the
+// two at boot time in the constructor below.
+// Note that having minor-fault feature implies having SIGBUS feature as the
+// latter was introduced earlier than the former. In other words, having
+// minor-fault feature implies having SIGBUS. We still want minor-fault to be
+// available for making jit-code-cache updation concurrent, which uses shmem.
+static constexpr uint64_t kUffdFeaturesRequired =
+    kUffdFeaturesForMinorFault | kUffdFeaturesForSigbus;
+
+bool KernelSupportsUffd() {
+#ifdef __linux__
+  if (gHaveMremapDontunmap) {
+    int fd = syscall(__NR_userfaultfd, O_CLOEXEC | UFFD_USER_MODE_ONLY);
+    // On non-android devices we may not have the kernel patches that restrict
+    // userfaultfd to user mode. But that is not a security concern as we are
+    // on host. Therefore, attempt one more time without UFFD_USER_MODE_ONLY.
+    if (!kIsTargetAndroid && fd == -1 && errno == EINVAL) {
+      fd = syscall(__NR_userfaultfd, O_CLOEXEC);
+    }
+    if (fd >= 0) {
+      // We are only fetching the available features, which is returned by the
+      // ioctl.
+      struct uffdio_api api = {.api = UFFD_API, .features = 0, .ioctls = 0};
+      CHECK_EQ(ioctl(fd, UFFDIO_API, &api), 0) << "ioctl_userfaultfd : API:" << strerror(errno);
+      gUffdFeatures = api.features;
+      close(fd);
+      // Allow this GC to be used only if minor-fault and sigbus feature is available.
+      return (api.features & kUffdFeaturesRequired) == kUffdFeaturesRequired;
+    }
+  }
+#endif
+  return false;
+}
+
+// The other cases are defined as constexpr in runtime/read_barrier_config.h
+#if !defined(ART_FORCE_USE_READ_BARRIER) && defined(ART_USE_READ_BARRIER)
+// Returns collector type asked to be used on the cmdline.
+static gc::CollectorType FetchCmdlineGcType() {
+  std::string argv;
+  gc::CollectorType gc_type = gc::CollectorType::kCollectorTypeNone;
+  if (android::base::ReadFileToString("/proc/self/cmdline", &argv)) {
+    if (argv.find("-Xgc:CMC") != std::string::npos) {
+      gc_type = gc::CollectorType::kCollectorTypeCMC;
+    } else if (argv.find("-Xgc:CC") != std::string::npos) {
+      gc_type = gc::CollectorType::kCollectorTypeCC;
+    }
+  }
+  return gc_type;
+}
+
+#ifdef ART_TARGET_ANDROID
+static int GetOverrideCacheInfoFd() {
+  std::string args_str;
+  if (!android::base::ReadFileToString("/proc/self/cmdline", &args_str)) {
+    LOG(WARNING) << "Failed to load /proc/self/cmdline";
+    return -1;
+  }
+  std::vector<std::string_view> args;
+  Split(std::string_view(args_str), /*separator=*/'\0', &args);
+  for (std::string_view arg : args) {
+    if (android::base::ConsumePrefix(&arg, "--cache-info-fd=")) {  // This is a dex2oat flag.
+      int fd;
+      if (!android::base::ParseInt(std::string(arg), &fd)) {
+        LOG(ERROR) << "Failed to parse --cache-info-fd (value: '" << arg << "')";
+        return -1;
+      }
+      return fd;
+    }
+  }
+  return -1;
+}
+
+static bool GetCachedBoolProperty(const std::string& key, bool default_value) {
+  // For simplicity, we don't handle multiple calls because otherwise we would have to reset the fd.
+  static bool called = false;
+  CHECK(!called) << "GetCachedBoolProperty can be called only once";
+  called = true;
+
+  std::string cache_info_contents;
+  int fd = GetOverrideCacheInfoFd();
+  if (fd >= 0) {
+    if (!android::base::ReadFdToString(fd, &cache_info_contents)) {
+      PLOG(ERROR) << "Failed to read cache-info from fd " << fd;
+      return default_value;
+    }
+  } else {
+    std::string path = GetApexDataDalvikCacheDirectory(InstructionSet::kNone) + "/cache-info.xml";
+    if (!android::base::ReadFileToString(path, &cache_info_contents)) {
+      // If the file is not found, then we are in chroot or in a standalone runtime process (e.g.,
+      // IncidentHelper), or odsign/odrefresh failed to generate and sign the cache info. There's
+      // nothing we can do.
+      if (errno != ENOENT) {
+        PLOG(ERROR) << "Failed to read cache-info from the default path";
+      }
+      return default_value;
+    }
+  }
+
+  std::optional<com::android::art::CacheInfo> cache_info =
+      com::android::art::parse(cache_info_contents.c_str());
+  if (!cache_info.has_value()) {
+    // This should never happen.
+    LOG(ERROR) << "Failed to parse cache-info";
+    return default_value;
+  }
+  const com::android::art::KeyValuePairList* list = cache_info->getFirstSystemProperties();
+  if (list == nullptr) {
+    // This should never happen.
+    LOG(ERROR) << "Missing system properties from cache-info";
+    return default_value;
+  }
+  const std::vector<com::android::art::KeyValuePair>& properties = list->getItem();
+  for (const com::android::art::KeyValuePair& pair : properties) {
+    if (pair.getK() == key) {
+      ParseBoolResult result = ParseBool(pair.getV());
+      switch (result) {
+        case ParseBoolResult::kTrue:
+          return true;
+        case ParseBoolResult::kFalse:
+          return false;
+        case ParseBoolResult::kError:
+          return default_value;
+      }
+    }
+  }
+  return default_value;
+}
+
+static bool SysPropSaysUffdGc() {
+  // The phenotype flag can change at time time after boot, but it shouldn't take effect until a
+  // reboot. Therefore, we read the phenotype flag from the cache info, which is generated on boot.
+  return GetCachedBoolProperty("persist.device_config.runtime_native_boot.enable_uffd_gc",
+                               GetBoolProperty("ro.dalvik.vm.enable_uffd_gc", false));
+}
+#else
+// Never called.
+static bool SysPropSaysUffdGc() { return false; }
+#endif
+
+static bool ShouldUseUserfaultfd() {
+  static_assert(kUseBakerReadBarrier || kUseTableLookupReadBarrier);
+#ifdef __linux__
+  // Use CMC/CC if that is being explicitly asked for on cmdline. Otherwise,
+  // always use CC on host. On target, use CMC only if system properties says so
+  // and the kernel supports it.
+  gc::CollectorType gc_type = FetchCmdlineGcType();
+  return gc_type == gc::CollectorType::kCollectorTypeCMC ||
+         (gc_type == gc::CollectorType::kCollectorTypeNone &&
+          kIsTargetAndroid &&
+          SysPropSaysUffdGc() &&
+          KernelSupportsUffd());
+#else
+  return false;
+#endif
+}
+
+const bool gUseUserfaultfd = ShouldUseUserfaultfd();
+const bool gUseReadBarrier = !gUseUserfaultfd;
+#endif
+
+namespace gc {
+namespace collector {
+
+// Turn off kCheckLocks when profiling the GC as it slows down the GC
+// significantly.
+static constexpr bool kCheckLocks = kDebugLocking;
+static constexpr bool kVerifyRootsMarked = kIsDebugBuild;
+// Two threads should suffice on devices.
+static constexpr size_t kMaxNumUffdWorkers = 2;
+// Number of compaction buffers reserved for mutator threads in SIGBUS feature
+// case. It's extremely unlikely that we will ever have more than these number
+// of mutator threads trying to access the moving-space during one compaction
+// phase. Using a lower number in debug builds to hopefully catch the issue
+// before it becomes a problem on user builds.
+static constexpr size_t kMutatorCompactionBufferCount = kIsDebugBuild ? 256 : 512;
+// Minimum from-space chunk to be madvised (during concurrent compaction) in one go.
+static constexpr ssize_t kMinFromSpaceMadviseSize = 1 * MB;
+// Concurrent compaction termination logic is different (and slightly more efficient) if the
+// kernel has the fault-retry feature (allowing repeated faults on the same page), which was
+// introduced in 5.7 (https://android-review.git.corp.google.com/c/kernel/common/+/1540088).
+// This allows a single page fault to be handled, in turn, by each worker thread, only waking
+// up the GC thread at the end.
+static const bool gKernelHasFaultRetry = IsKernelVersionAtLeast(5, 7);
+
+std::pair<bool, bool> MarkCompact::GetUffdAndMinorFault() {
+  bool uffd_available;
+  // In most cases the gUffdFeatures will already be initialized at boot time
+  // when libart is loaded. On very old kernels we may get '0' from the kernel,
+  // in which case we would be doing the syscalls each time this function is
+  // called. But that's very unlikely case. There are no correctness issues as
+  // the response from kernel never changes after boot.
+  if (UNLIKELY(gUffdFeatures == 0)) {
+    uffd_available = KernelSupportsUffd();
+  } else {
+    // We can have any uffd features only if uffd exists.
+    uffd_available = true;
+  }
+  bool minor_fault_available =
+      (gUffdFeatures & kUffdFeaturesForMinorFault) == kUffdFeaturesForMinorFault;
+  return std::pair<bool, bool>(uffd_available, minor_fault_available);
+}
+
+bool MarkCompact::CreateUserfaultfd(bool post_fork) {
+  if (post_fork || uffd_ == kFdUnused) {
+    // Check if we have MREMAP_DONTUNMAP here for cases where
+    // 'ART_USE_READ_BARRIER=false' is used. Additionally, this check ensures
+    // that userfaultfd isn't used on old kernels, which cause random ioctl
+    // failures.
+    if (gHaveMremapDontunmap) {
+      // Don't use O_NONBLOCK as we rely on read waiting on uffd_ if there isn't
+      // any read event available. We don't use poll.
+      uffd_ = syscall(__NR_userfaultfd, O_CLOEXEC | UFFD_USER_MODE_ONLY);
+      // On non-android devices we may not have the kernel patches that restrict
+      // userfaultfd to user mode. But that is not a security concern as we are
+      // on host. Therefore, attempt one more time without UFFD_USER_MODE_ONLY.
+      if (!kIsTargetAndroid && UNLIKELY(uffd_ == -1 && errno == EINVAL)) {
+        uffd_ = syscall(__NR_userfaultfd, O_CLOEXEC);
+      }
+      if (UNLIKELY(uffd_ == -1)) {
+        uffd_ = kFallbackMode;
+        LOG(WARNING) << "Userfaultfd isn't supported (reason: " << strerror(errno)
+                     << ") and therefore falling back to stop-the-world compaction.";
+      } else {
+        DCHECK(IsValidFd(uffd_));
+        // Initialize uffd with the features which are required and available.
+        struct uffdio_api api = {.api = UFFD_API, .features = gUffdFeatures, .ioctls = 0};
+        api.features &= use_uffd_sigbus_ ? kUffdFeaturesRequired : kUffdFeaturesForMinorFault;
+        CHECK_EQ(ioctl(uffd_, UFFDIO_API, &api), 0)
+            << "ioctl_userfaultfd: API: " << strerror(errno);
+      }
+    } else {
+      uffd_ = kFallbackMode;
+    }
+  }
+  uffd_initialized_ = !post_fork || uffd_ == kFallbackMode;
+  return IsValidFd(uffd_);
+}
+
+template <size_t kAlignment>
+MarkCompact::LiveWordsBitmap<kAlignment>* MarkCompact::LiveWordsBitmap<kAlignment>::Create(
+    uintptr_t begin, uintptr_t end) {
+  return static_cast<LiveWordsBitmap<kAlignment>*>(
+          MemRangeBitmap::Create("Concurrent Mark Compact live words bitmap", begin, end));
+}
+
+static bool IsSigbusFeatureAvailable() {
+  MarkCompact::GetUffdAndMinorFault();
+  return gUffdFeatures & UFFD_FEATURE_SIGBUS;
+}
+
+MarkCompact::MarkCompact(Heap* heap)
+    : GarbageCollector(heap, "concurrent mark compact"),
+      gc_barrier_(0),
+      lock_("mark compact lock", kGenericBottomLock),
+      bump_pointer_space_(heap->GetBumpPointerSpace()),
+      moving_space_bitmap_(bump_pointer_space_->GetMarkBitmap()),
+      moving_to_space_fd_(kFdUnused),
+      moving_from_space_fd_(kFdUnused),
+      uffd_(kFdUnused),
+      sigbus_in_progress_count_(kSigbusCounterCompactionDoneMask),
+      compaction_in_progress_count_(0),
+      thread_pool_counter_(0),
+      compacting_(false),
+      uffd_initialized_(false),
+      uffd_minor_fault_supported_(false),
+      use_uffd_sigbus_(IsSigbusFeatureAvailable()),
+      minor_fault_initialized_(false),
+      map_linear_alloc_shared_(false) {
+  if (kIsDebugBuild) {
+    updated_roots_.reset(new std::unordered_set<void*>());
+  }
+  // TODO: When using minor-fault feature, the first GC after zygote-fork
+  // requires mapping the linear-alloc again with MAP_SHARED. This leaves a
+  // gap for suspended threads to access linear-alloc when it's empty (after
+  // mremap) and not yet userfaultfd registered. This cannot be fixed by merely
+  // doing uffd registration first. For now, just assert that we are not using
+  // minor-fault. Eventually, a cleanup of linear-alloc update logic to only
+  // use private anonymous would be ideal.
+  CHECK(!uffd_minor_fault_supported_);
+
+  // TODO: Depending on how the bump-pointer space move is implemented. If we
+  // switch between two virtual memories each time, then we will have to
+  // initialize live_words_bitmap_ accordingly.
+  live_words_bitmap_.reset(LiveWordsBitmap<kAlignment>::Create(
+          reinterpret_cast<uintptr_t>(bump_pointer_space_->Begin()),
+          reinterpret_cast<uintptr_t>(bump_pointer_space_->Limit())));
+
+  // Create one MemMap for all the data structures
+  size_t moving_space_size = bump_pointer_space_->Capacity();
+  size_t chunk_info_vec_size = moving_space_size / kOffsetChunkSize;
+  size_t nr_moving_pages = moving_space_size / kPageSize;
+  size_t nr_non_moving_pages = heap->GetNonMovingSpace()->Capacity() / kPageSize;
+
+  std::string err_msg;
+  info_map_ = MemMap::MapAnonymous("Concurrent mark-compact chunk-info vector",
+                                   chunk_info_vec_size * sizeof(uint32_t)
+                                   + nr_non_moving_pages * sizeof(ObjReference)
+                                   + nr_moving_pages * sizeof(ObjReference)
+                                   + nr_moving_pages * sizeof(uint32_t),
+                                   PROT_READ | PROT_WRITE,
+                                   /*low_4gb=*/ false,
+                                   &err_msg);
+  if (UNLIKELY(!info_map_.IsValid())) {
+    LOG(FATAL) << "Failed to allocate concurrent mark-compact chunk-info vector: " << err_msg;
+  } else {
+    uint8_t* p = info_map_.Begin();
+    chunk_info_vec_ = reinterpret_cast<uint32_t*>(p);
+    vector_length_ = chunk_info_vec_size;
+
+    p += chunk_info_vec_size * sizeof(uint32_t);
+    first_objs_non_moving_space_ = reinterpret_cast<ObjReference*>(p);
+
+    p += nr_non_moving_pages * sizeof(ObjReference);
+    first_objs_moving_space_ = reinterpret_cast<ObjReference*>(p);
+
+    p += nr_moving_pages * sizeof(ObjReference);
+    pre_compact_offset_moving_space_ = reinterpret_cast<uint32_t*>(p);
+  }
+
+  size_t moving_space_alignment = BestPageTableAlignment(moving_space_size);
+  // The moving space is created at a fixed address, which is expected to be
+  // PMD-size aligned.
+  if (!IsAlignedParam(bump_pointer_space_->Begin(), moving_space_alignment)) {
+    LOG(WARNING) << "Bump pointer space is not aligned to " << PrettySize(moving_space_alignment)
+                 << ". This can lead to longer stop-the-world pauses for compaction";
+  }
+  // NOTE: PROT_NONE is used here as these mappings are for address space reservation
+  // only and will be used only after appropriately remapping them.
+  from_space_map_ = MemMap::MapAnonymousAligned("Concurrent mark-compact from-space",
+                                                moving_space_size,
+                                                PROT_NONE,
+                                                /*low_4gb=*/kObjPtrPoisoning,
+                                                moving_space_alignment,
+                                                &err_msg);
+  if (UNLIKELY(!from_space_map_.IsValid())) {
+    LOG(FATAL) << "Failed to allocate concurrent mark-compact from-space" << err_msg;
+  } else {
+    from_space_begin_ = from_space_map_.Begin();
+  }
+
+  // In some cases (32-bit or kObjPtrPoisoning) it's too much to ask for 3
+  // heap-sized mappings in low-4GB. So tolerate failure here by attempting to
+  // mmap again right before the compaction pause. And if even that fails, then
+  // running the GC cycle in copy-mode rather than minor-fault.
+  //
+  // This map doesn't have to be aligned to 2MB as we don't mremap on it.
+  if (!kObjPtrPoisoning && uffd_minor_fault_supported_) {
+    // We need this map only if minor-fault feature is supported. But in that case
+    // don't create the mapping if obj-ptr poisoning is enabled as then the mapping
+    // has to be created in low_4gb. Doing this here rather than later causes the
+    // Dex2oatImageTest.TestExtension gtest to fail in 64-bit platforms.
+    shadow_to_space_map_ = MemMap::MapAnonymous("Concurrent mark-compact moving-space shadow",
+                                                moving_space_size,
+                                                PROT_NONE,
+                                                /*low_4gb=*/false,
+                                                &err_msg);
+    if (!shadow_to_space_map_.IsValid()) {
+      LOG(WARNING) << "Failed to allocate concurrent mark-compact moving-space shadow: " << err_msg;
+    }
+  }
+  const size_t num_pages =
+      1 + (use_uffd_sigbus_ ? kMutatorCompactionBufferCount :
+                              std::min(heap_->GetParallelGCThreadCount(), kMaxNumUffdWorkers));
+  compaction_buffers_map_ = MemMap::MapAnonymous("Concurrent mark-compact compaction buffers",
+                                                 kPageSize * num_pages,
+                                                 PROT_READ | PROT_WRITE,
+                                                 /*low_4gb=*/kObjPtrPoisoning,
+                                                 &err_msg);
+  if (UNLIKELY(!compaction_buffers_map_.IsValid())) {
+    LOG(FATAL) << "Failed to allocate concurrent mark-compact compaction buffers" << err_msg;
+  }
+  // We also use the first page-sized buffer for the purpose of terminating concurrent compaction.
+  conc_compaction_termination_page_ = compaction_buffers_map_.Begin();
+  // Touch the page deliberately to avoid userfaults on it. We madvise it in
+  // CompactionPhase() before using it to terminate concurrent compaction.
+  ForceRead(conc_compaction_termination_page_);
+
+  // In most of the cases, we don't expect more than one LinearAlloc space.
+  linear_alloc_spaces_data_.reserve(1);
+
+  // Initialize GC metrics.
+  metrics::ArtMetrics* metrics = GetMetrics();
+  // The mark-compact collector supports only full-heap collections at the moment.
+  gc_time_histogram_ = metrics->FullGcCollectionTime();
+  metrics_gc_count_ = metrics->FullGcCount();
+  metrics_gc_count_delta_ = metrics->FullGcCountDelta();
+  gc_throughput_histogram_ = metrics->FullGcThroughput();
+  gc_tracing_throughput_hist_ = metrics->FullGcTracingThroughput();
+  gc_throughput_avg_ = metrics->FullGcThroughputAvg();
+  gc_tracing_throughput_avg_ = metrics->FullGcTracingThroughputAvg();
+  gc_scanned_bytes_ = metrics->FullGcScannedBytes();
+  gc_scanned_bytes_delta_ = metrics->FullGcScannedBytesDelta();
+  gc_freed_bytes_ = metrics->FullGcFreedBytes();
+  gc_freed_bytes_delta_ = metrics->FullGcFreedBytesDelta();
+  gc_duration_ = metrics->FullGcDuration();
+  gc_duration_delta_ = metrics->FullGcDurationDelta();
+  are_metrics_initialized_ = true;
+}
+
+void MarkCompact::AddLinearAllocSpaceData(uint8_t* begin, size_t len) {
+  DCHECK_ALIGNED(begin, kPageSize);
+  DCHECK_ALIGNED(len, kPageSize);
+  DCHECK_GE(len, kPMDSize);
+  size_t alignment = BestPageTableAlignment(len);
+  bool is_shared = false;
+  // We use MAP_SHARED on non-zygote processes for leveraging userfaultfd's minor-fault feature.
+  if (map_linear_alloc_shared_) {
+    void* ret = mmap(begin,
+                     len,
+                     PROT_READ | PROT_WRITE,
+                     MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED,
+                     /*fd=*/-1,
+                     /*offset=*/0);
+    CHECK_EQ(ret, begin) << "mmap failed: " << strerror(errno);
+    is_shared = true;
+  }
+  std::string err_msg;
+  MemMap shadow(MemMap::MapAnonymousAligned("linear-alloc shadow map",
+                                            len,
+                                            PROT_NONE,
+                                            /*low_4gb=*/false,
+                                            alignment,
+                                            &err_msg));
+  if (!shadow.IsValid()) {
+    LOG(FATAL) << "Failed to allocate linear-alloc shadow map: " << err_msg;
+    UNREACHABLE();
+  }
+
+  MemMap page_status_map(MemMap::MapAnonymous("linear-alloc page-status map",
+                                              len / kPageSize,
+                                              PROT_READ | PROT_WRITE,
+                                              /*low_4gb=*/false,
+                                              &err_msg));
+  if (!page_status_map.IsValid()) {
+    LOG(FATAL) << "Failed to allocate linear-alloc page-status shadow map: " << err_msg;
+    UNREACHABLE();
+  }
+  linear_alloc_spaces_data_.emplace_back(std::forward<MemMap>(shadow),
+                                         std::forward<MemMap>(page_status_map),
+                                         begin,
+                                         begin + len,
+                                         is_shared);
+}
+
+void MarkCompact::BindAndResetBitmaps() {
+  // TODO: We need to hold heap_bitmap_lock_ only for populating immune_spaces.
+  // The card-table and mod-union-table processing can be done without it. So
+  // change the logic below. Note that the bitmap clearing would require the
+  // lock.
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  accounting::CardTable* const card_table = heap_->GetCardTable();
+  // Mark all of the spaces we never collect as immune.
+  for (const auto& space : GetHeap()->GetContinuousSpaces()) {
+    if (space->GetGcRetentionPolicy() == space::kGcRetentionPolicyNeverCollect ||
+        space->GetGcRetentionPolicy() == space::kGcRetentionPolicyFullCollect) {
+      CHECK(space->IsZygoteSpace() || space->IsImageSpace());
+      immune_spaces_.AddSpace(space);
+      accounting::ModUnionTable* table = heap_->FindModUnionTableFromSpace(space);
+      if (table != nullptr) {
+        table->ProcessCards();
+      } else {
+        // Keep cards aged if we don't have a mod-union table since we may need
+        // to scan them in future GCs. This case is for app images.
+        // TODO: We could probably scan the objects right here to avoid doing
+        // another scan through the card-table.
+        card_table->ModifyCardsAtomic(
+            space->Begin(),
+            space->End(),
+            [](uint8_t card) {
+              return (card == gc::accounting::CardTable::kCardClean)
+                  ? card
+                  : gc::accounting::CardTable::kCardAged;
+            },
+            /* card modified visitor */ VoidFunctor());
+      }
+    } else {
+      CHECK(!space->IsZygoteSpace());
+      CHECK(!space->IsImageSpace());
+      // The card-table corresponding to bump-pointer and non-moving space can
+      // be cleared, because we are going to traverse all the reachable objects
+      // in these spaces. This card-table will eventually be used to track
+      // mutations while concurrent marking is going on.
+      card_table->ClearCardRange(space->Begin(), space->Limit());
+      if (space != bump_pointer_space_) {
+        CHECK_EQ(space, heap_->GetNonMovingSpace());
+        non_moving_space_ = space;
+        non_moving_space_bitmap_ = space->GetMarkBitmap();
+      }
+    }
+  }
+}
+
+void MarkCompact::MarkZygoteLargeObjects() {
+  Thread* self = thread_running_gc_;
+  DCHECK_EQ(self, Thread::Current());
+  space::LargeObjectSpace* const los = heap_->GetLargeObjectsSpace();
+  if (los != nullptr) {
+    // Pick the current live bitmap (mark bitmap if swapped).
+    accounting::LargeObjectBitmap* const live_bitmap = los->GetLiveBitmap();
+    accounting::LargeObjectBitmap* const mark_bitmap = los->GetMarkBitmap();
+    // Walk through all of the objects and explicitly mark the zygote ones so they don't get swept.
+    std::pair<uint8_t*, uint8_t*> range = los->GetBeginEndAtomic();
+    live_bitmap->VisitMarkedRange(reinterpret_cast<uintptr_t>(range.first),
+                                  reinterpret_cast<uintptr_t>(range.second),
+                                  [mark_bitmap, los, self](mirror::Object* obj)
+                                      REQUIRES(Locks::heap_bitmap_lock_)
+                                          REQUIRES_SHARED(Locks::mutator_lock_) {
+                                            if (los->IsZygoteLargeObject(self, obj)) {
+                                              mark_bitmap->Set(obj);
+                                            }
+                                          });
+  }
+}
+
+void MarkCompact::InitializePhase() {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  mark_stack_ = heap_->GetMarkStack();
+  CHECK(mark_stack_->IsEmpty());
+  immune_spaces_.Reset();
+  moving_first_objs_count_ = 0;
+  non_moving_first_objs_count_ = 0;
+  black_page_count_ = 0;
+  bytes_scanned_ = 0;
+  freed_objects_ = 0;
+  // The first buffer is used by gc-thread.
+  compaction_buffer_counter_.store(1, std::memory_order_relaxed);
+  from_space_slide_diff_ = from_space_begin_ - bump_pointer_space_->Begin();
+  black_allocations_begin_ = bump_pointer_space_->Limit();
+  walk_super_class_cache_ = nullptr;
+  // TODO: Would it suffice to read it once in the constructor, which is called
+  // in zygote process?
+  pointer_size_ = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+}
+
+class MarkCompact::ThreadFlipVisitor : public Closure {
+ public:
+  explicit ThreadFlipVisitor(MarkCompact* collector) : collector_(collector) {}
+
+  void Run(Thread* thread) override REQUIRES_SHARED(Locks::mutator_lock_) {
+    // Note: self is not necessarily equal to thread since thread may be suspended.
+    Thread* self = Thread::Current();
+    CHECK(thread == self || thread->GetState() != ThreadState::kRunnable)
+        << thread->GetState() << " thread " << thread << " self " << self;
+    thread->VisitRoots(collector_, kVisitRootFlagAllRoots);
+    // Interpreter cache is thread-local so it needs to be swept either in a
+    // flip, or a stop-the-world pause.
+    CHECK(collector_->compacting_);
+    thread->SweepInterpreterCache(collector_);
+    thread->AdjustTlab(collector_->black_objs_slide_diff_);
+    collector_->GetBarrier().Pass(self);
+  }
+
+ private:
+  MarkCompact* const collector_;
+};
+
+class MarkCompact::FlipCallback : public Closure {
+ public:
+  explicit FlipCallback(MarkCompact* collector) : collector_(collector) {}
+
+  void Run(Thread* thread ATTRIBUTE_UNUSED) override REQUIRES(Locks::mutator_lock_) {
+    collector_->CompactionPause();
+  }
+
+ private:
+  MarkCompact* const collector_;
+};
+
+void MarkCompact::RunPhases() {
+  Thread* self = Thread::Current();
+  thread_running_gc_ = self;
+  Runtime* runtime = Runtime::Current();
+  InitializePhase();
+  GetHeap()->PreGcVerification(this);
+  {
+    ReaderMutexLock mu(self, *Locks::mutator_lock_);
+    MarkingPhase();
+  }
+  {
+    // Marking pause
+    ScopedPause pause(this);
+    MarkingPause();
+    if (kIsDebugBuild) {
+      bump_pointer_space_->AssertAllThreadLocalBuffersAreRevoked();
+    }
+  }
+  // To increase likelihood of black allocations. For testing purposes only.
+  if (kIsDebugBuild && heap_->GetTaskProcessor()->GetRunningThread() == thread_running_gc_) {
+    usleep(500'000);
+  }
+  {
+    ReaderMutexLock mu(self, *Locks::mutator_lock_);
+    ReclaimPhase();
+    PrepareForCompaction();
+  }
+  if (uffd_ != kFallbackMode && !use_uffd_sigbus_) {
+    heap_->GetThreadPool()->WaitForWorkersToBeCreated();
+  }
+
+  {
+    // Compaction pause
+    gc_barrier_.Init(self, 0);
+    ThreadFlipVisitor visitor(this);
+    FlipCallback callback(this);
+    size_t barrier_count = runtime->GetThreadList()->FlipThreadRoots(
+        &visitor, &callback, this, GetHeap()->GetGcPauseListener());
+    {
+      ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
+      gc_barrier_.Increment(self, barrier_count);
+    }
+  }
+
+  if (IsValidFd(uffd_)) {
+    ReaderMutexLock mu(self, *Locks::mutator_lock_);
+    CompactionPhase();
+  }
+
+  FinishPhase();
+  thread_running_gc_ = nullptr;
+  GetHeap()->PostGcVerification(this);
+}
+
+void MarkCompact::InitMovingSpaceFirstObjects(const size_t vec_len) {
+  // Find the first live word first.
+  size_t to_space_page_idx = 0;
+  uint32_t offset_in_chunk_word;
+  uint32_t offset;
+  mirror::Object* obj;
+  const uintptr_t heap_begin = moving_space_bitmap_->HeapBegin();
+
+  size_t chunk_idx;
+  // Find the first live word in the space
+  for (chunk_idx = 0; chunk_info_vec_[chunk_idx] == 0; chunk_idx++) {
+    if (chunk_idx > vec_len) {
+      // We don't have any live data on the moving-space.
+      return;
+    }
+  }
+  // Use live-words bitmap to find the first word
+  offset_in_chunk_word = live_words_bitmap_->FindNthLiveWordOffset(chunk_idx, /*n*/ 0);
+  offset = chunk_idx * kBitsPerVectorWord + offset_in_chunk_word;
+  DCHECK(live_words_bitmap_->Test(offset)) << "offset=" << offset
+                                           << " chunk_idx=" << chunk_idx
+                                           << " N=0"
+                                           << " offset_in_word=" << offset_in_chunk_word
+                                           << " word=" << std::hex
+                                           << live_words_bitmap_->GetWord(chunk_idx);
+  // The first object doesn't require using FindPrecedingObject().
+  obj = reinterpret_cast<mirror::Object*>(heap_begin + offset * kAlignment);
+  // TODO: add a check to validate the object.
+
+  pre_compact_offset_moving_space_[to_space_page_idx] = offset;
+  first_objs_moving_space_[to_space_page_idx].Assign(obj);
+  to_space_page_idx++;
+
+  uint32_t page_live_bytes = 0;
+  while (true) {
+    for (; page_live_bytes <= kPageSize; chunk_idx++) {
+      if (chunk_idx > vec_len) {
+        moving_first_objs_count_ = to_space_page_idx;
+        return;
+      }
+      page_live_bytes += chunk_info_vec_[chunk_idx];
+    }
+    chunk_idx--;
+    page_live_bytes -= kPageSize;
+    DCHECK_LE(page_live_bytes, kOffsetChunkSize);
+    DCHECK_LE(page_live_bytes, chunk_info_vec_[chunk_idx])
+        << " chunk_idx=" << chunk_idx
+        << " to_space_page_idx=" << to_space_page_idx
+        << " vec_len=" << vec_len;
+    DCHECK(IsAligned<kAlignment>(chunk_info_vec_[chunk_idx] - page_live_bytes));
+    offset_in_chunk_word =
+            live_words_bitmap_->FindNthLiveWordOffset(
+                chunk_idx, (chunk_info_vec_[chunk_idx] - page_live_bytes) / kAlignment);
+    offset = chunk_idx * kBitsPerVectorWord + offset_in_chunk_word;
+    DCHECK(live_words_bitmap_->Test(offset))
+        << "offset=" << offset
+        << " chunk_idx=" << chunk_idx
+        << " N=" << ((chunk_info_vec_[chunk_idx] - page_live_bytes) / kAlignment)
+        << " offset_in_word=" << offset_in_chunk_word
+        << " word=" << std::hex << live_words_bitmap_->GetWord(chunk_idx);
+    // TODO: Can we optimize this for large objects? If we are continuing a
+    // large object that spans multiple pages, then we may be able to do without
+    // calling FindPrecedingObject().
+    //
+    // Find the object which encapsulates offset in it, which could be
+    // starting at offset itself.
+    obj = moving_space_bitmap_->FindPrecedingObject(heap_begin + offset * kAlignment);
+    // TODO: add a check to validate the object.
+    pre_compact_offset_moving_space_[to_space_page_idx] = offset;
+    first_objs_moving_space_[to_space_page_idx].Assign(obj);
+    to_space_page_idx++;
+    chunk_idx++;
+  }
+}
+
+void MarkCompact::InitNonMovingSpaceFirstObjects() {
+  accounting::ContinuousSpaceBitmap* bitmap = non_moving_space_->GetLiveBitmap();
+  uintptr_t begin = reinterpret_cast<uintptr_t>(non_moving_space_->Begin());
+  const uintptr_t end = reinterpret_cast<uintptr_t>(non_moving_space_->End());
+  mirror::Object* prev_obj;
+  size_t page_idx;
+  {
+    // Find first live object
+    mirror::Object* obj = nullptr;
+    bitmap->VisitMarkedRange</*kVisitOnce*/ true>(begin,
+                                                  end,
+                                                  [&obj] (mirror::Object* o) {
+                                                    obj = o;
+                                                  });
+    if (obj == nullptr) {
+      // There are no live objects in the non-moving space
+      return;
+    }
+    page_idx = (reinterpret_cast<uintptr_t>(obj) - begin) / kPageSize;
+    first_objs_non_moving_space_[page_idx++].Assign(obj);
+    prev_obj = obj;
+  }
+  // TODO: check obj is valid
+  uintptr_t prev_obj_end = reinterpret_cast<uintptr_t>(prev_obj)
+                           + RoundUp(prev_obj->SizeOf<kDefaultVerifyFlags>(), kAlignment);
+  // For every page find the object starting from which we need to call
+  // VisitReferences. It could either be an object that started on some
+  // preceding page, or some object starting within this page.
+  begin = RoundDown(reinterpret_cast<uintptr_t>(prev_obj) + kPageSize, kPageSize);
+  while (begin < end) {
+    // Utilize, if any, large object that started in some preceding page, but
+    // overlaps with this page as well.
+    if (prev_obj != nullptr && prev_obj_end > begin) {
+      DCHECK_LT(prev_obj, reinterpret_cast<mirror::Object*>(begin));
+      first_objs_non_moving_space_[page_idx].Assign(prev_obj);
+      mirror::Class* klass = prev_obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
+      if (bump_pointer_space_->HasAddress(klass)) {
+        LOG(WARNING) << "found inter-page object " << prev_obj
+                     << " in non-moving space with klass " << klass
+                     << " in moving space";
+      }
+    } else {
+      prev_obj_end = 0;
+      // It's sufficient to only search for previous object in the preceding page.
+      // If no live object started in that page and some object had started in
+      // the page preceding to that page, which was big enough to overlap with
+      // the current page, then we wouldn't be in the else part.
+      prev_obj = bitmap->FindPrecedingObject(begin, begin - kPageSize);
+      if (prev_obj != nullptr) {
+        prev_obj_end = reinterpret_cast<uintptr_t>(prev_obj)
+                        + RoundUp(prev_obj->SizeOf<kDefaultVerifyFlags>(), kAlignment);
+      }
+      if (prev_obj_end > begin) {
+        mirror::Class* klass = prev_obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
+        if (bump_pointer_space_->HasAddress(klass)) {
+          LOG(WARNING) << "found inter-page object " << prev_obj
+                       << " in non-moving space with klass " << klass
+                       << " in moving space";
+        }
+        first_objs_non_moving_space_[page_idx].Assign(prev_obj);
+      } else {
+        // Find the first live object in this page
+        bitmap->VisitMarkedRange</*kVisitOnce*/ true>(
+                begin,
+                begin + kPageSize,
+                [this, page_idx] (mirror::Object* obj) {
+                  first_objs_non_moving_space_[page_idx].Assign(obj);
+                });
+      }
+      // An empty entry indicates that the page has no live objects and hence
+      // can be skipped.
+    }
+    begin += kPageSize;
+    page_idx++;
+  }
+  non_moving_first_objs_count_ = page_idx;
+}
+
+bool MarkCompact::CanCompactMovingSpaceWithMinorFault() {
+  size_t min_size = (moving_first_objs_count_ + black_page_count_) * kPageSize;
+  return minor_fault_initialized_ && shadow_to_space_map_.IsValid() &&
+         shadow_to_space_map_.Size() >= min_size;
+}
+
+class MarkCompact::ConcurrentCompactionGcTask : public SelfDeletingTask {
+ public:
+  explicit ConcurrentCompactionGcTask(MarkCompact* collector, size_t idx)
+      : collector_(collector), index_(idx) {}
+
+  void Run(Thread* self ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (collector_->CanCompactMovingSpaceWithMinorFault()) {
+      collector_->ConcurrentCompaction<MarkCompact::kMinorFaultMode>(/*buf=*/nullptr);
+    } else {
+      // The passed page/buf to ConcurrentCompaction is used by the thread as a
+      // kPageSize buffer for compacting and updating objects into and then
+      // passing the buf to uffd ioctls.
+      uint8_t* buf = collector_->compaction_buffers_map_.Begin() + index_ * kPageSize;
+      collector_->ConcurrentCompaction<MarkCompact::kCopyMode>(buf);
+    }
+  }
+
+ private:
+  MarkCompact* const collector_;
+  size_t index_;
+};
+
+void MarkCompact::PrepareForCompaction() {
+  uint8_t* space_begin = bump_pointer_space_->Begin();
+  size_t vector_len = (black_allocations_begin_ - space_begin) / kOffsetChunkSize;
+  DCHECK_LE(vector_len, vector_length_);
+  for (size_t i = 0; i < vector_len; i++) {
+    DCHECK_LE(chunk_info_vec_[i], kOffsetChunkSize);
+    DCHECK_EQ(chunk_info_vec_[i], live_words_bitmap_->LiveBytesInBitmapWord(i));
+  }
+  InitMovingSpaceFirstObjects(vector_len);
+  InitNonMovingSpaceFirstObjects();
+
+  // TODO: We can do a lot of neat tricks with this offset vector to tune the
+  // compaction as we wish. Originally, the compaction algorithm slides all
+  // live objects towards the beginning of the heap. This is nice because it
+  // keeps the spatial locality of objects intact.
+  // However, sometimes it's desired to compact objects in certain portions
+  // of the heap. For instance, it is expected that, over time,
+  // objects towards the beginning of the heap are long lived and are always
+  // densely packed. In this case, it makes sense to only update references in
+  // there and not try to compact it.
+  // Furthermore, we might have some large objects and may not want to move such
+  // objects.
+  // We can adjust, without too much effort, the values in the chunk_info_vec_ such
+  // that the objects in the dense beginning area aren't moved. OTOH, large
+  // objects, which could be anywhere in the heap, could also be kept from
+  // moving by using a similar trick. The only issue is that by doing this we will
+  // leave an unused hole in the middle of the heap which can't be used for
+  // allocations until we do a *full* compaction.
+  //
+  // At this point every element in the chunk_info_vec_ contains the live-bytes
+  // of the corresponding chunk. For old-to-new address computation we need
+  // every element to reflect total live-bytes till the corresponding chunk.
+
+  // Live-bytes count is required to compute post_compact_end_ below.
+  uint32_t total;
+  // Update the vector one past the heap usage as it is required for black
+  // allocated objects' post-compact address computation.
+  if (vector_len < vector_length_) {
+    vector_len++;
+    total = 0;
+  } else {
+    // Fetch the value stored in the last element before it gets overwritten by
+    // std::exclusive_scan().
+    total = chunk_info_vec_[vector_len - 1];
+  }
+  std::exclusive_scan(chunk_info_vec_, chunk_info_vec_ + vector_len, chunk_info_vec_, 0);
+  total += chunk_info_vec_[vector_len - 1];
+
+  for (size_t i = vector_len; i < vector_length_; i++) {
+    DCHECK_EQ(chunk_info_vec_[i], 0u);
+  }
+  post_compact_end_ = AlignUp(space_begin + total, kPageSize);
+  CHECK_EQ(post_compact_end_, space_begin + moving_first_objs_count_ * kPageSize);
+  black_objs_slide_diff_ = black_allocations_begin_ - post_compact_end_;
+  // How do we handle compaction of heap portion used for allocations after the
+  // marking-pause?
+  // All allocations after the marking-pause are considered black (reachable)
+  // for this GC cycle. However, they need not be allocated contiguously as
+  // different mutators use TLABs. So we will compact the heap till the point
+  // where allocations took place before the marking-pause. And everything after
+  // that will be slid with TLAB holes, and then TLAB info in TLS will be
+  // appropriately updated in the pre-compaction pause.
+  // The chunk-info vector entries for the post marking-pause allocations will be
+  // also updated in the pre-compaction pause.
+
+  bool is_zygote = Runtime::Current()->IsZygote();
+  if (!uffd_initialized_ && CreateUserfaultfd(/*post_fork*/false)) {
+    if (!use_uffd_sigbus_) {
+      // Register the buffer that we use for terminating concurrent compaction
+      struct uffdio_register uffd_register;
+      uffd_register.range.start = reinterpret_cast<uintptr_t>(conc_compaction_termination_page_);
+      uffd_register.range.len = kPageSize;
+      uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+      CHECK_EQ(ioctl(uffd_, UFFDIO_REGISTER, &uffd_register), 0)
+          << "ioctl_userfaultfd: register compaction termination page: " << strerror(errno);
+    }
+    if (!uffd_minor_fault_supported_ && shadow_to_space_map_.IsValid()) {
+      // A valid shadow-map for moving space is only possible if we
+      // were able to map it in the constructor. That also means that its size
+      // matches the moving-space.
+      CHECK_EQ(shadow_to_space_map_.Size(), bump_pointer_space_->Capacity());
+      // Release the shadow map for moving-space if we don't support minor-fault
+      // as it's not required.
+      shadow_to_space_map_.Reset();
+    }
+  }
+  // For zygote we create the thread pool each time before starting compaction,
+  // and get rid of it when finished. This is expected to happen rarely as
+  // zygote spends most of the time in native fork loop.
+  if (uffd_ != kFallbackMode) {
+    if (!use_uffd_sigbus_) {
+      ThreadPool* pool = heap_->GetThreadPool();
+      if (UNLIKELY(pool == nullptr)) {
+        // On devices with 2 cores, GetParallelGCThreadCount() will return 1,
+        // which is desired number of workers on such devices.
+        heap_->CreateThreadPool(std::min(heap_->GetParallelGCThreadCount(), kMaxNumUffdWorkers));
+        pool = heap_->GetThreadPool();
+      }
+      size_t num_threads = pool->GetThreadCount();
+      thread_pool_counter_ = num_threads;
+      for (size_t i = 0; i < num_threads; i++) {
+        pool->AddTask(thread_running_gc_, new ConcurrentCompactionGcTask(this, i + 1));
+      }
+      CHECK_EQ(pool->GetTaskCount(thread_running_gc_), num_threads);
+    }
+    /*
+     * Possible scenarios for mappings:
+     * A) All zygote GCs (or if minor-fault feature isn't available): uses
+     * uffd's copy mode
+     *  1) For moving-space ('to' space is same as the moving-space):
+     *    a) Private-anonymous mappings for 'to' and 'from' space are created in
+     *    the constructor.
+     *    b) In the compaction pause, we mremap(dontunmap) from 'to' space to
+     *    'from' space. This results in moving all pages to 'from' space and
+     *    emptying the 'to' space, thereby preparing it for userfaultfd
+     *    registration.
+     *
+     *  2) For linear-alloc space:
+     *    a) Private-anonymous mappings for the linear-alloc and its 'shadow'
+     *    are created by the arena-pool.
+     *    b) In the compaction pause, we mremap(dontumap) with similar effect as
+     *    (A.1.b) above.
+     *
+     * B) First GC after zygote: uses uffd's copy-mode
+     *  1) For moving-space:
+     *    a) If the mmap for shadow-map has been successful in the constructor,
+     *    then we remap it (mmap with MAP_FIXED) to get a shared-anonymous
+     *    mapping.
+     *    b) Else, we create two memfd and ftruncate them to the moving-space
+     *    size.
+     *    c) Same as (A.1.b)
+     *    d) If (B.1.a), then mremap(dontunmap) from shadow-map to
+     *    'to' space. This will make both of them map to the same pages
+     *    e) If (B.1.b), then mmap with the first memfd in shared mode on the
+     *    'to' space.
+     *    f) At the end of compaction, we will have moved the moving-space
+     *    objects to a MAP_SHARED mapping, readying it for minor-fault from next
+     *    GC cycle.
+     *
+     *  2) For linear-alloc space:
+     *    a) Same as (A.2.b)
+     *    b) mmap a shared-anonymous mapping onto the linear-alloc space.
+     *    c) Same as (B.1.f)
+     *
+     * C) All subsequent GCs: preferable minor-fault mode. But may also require
+     * using copy-mode.
+     *  1) For moving-space:
+     *    a) If the shadow-map is created and no memfd was used, then that means
+     *    we are using shared-anonymous. Therefore, mmap a shared-anonymous on
+     *    the shadow-space.
+     *    b) If the shadow-map is not mapped yet, then mmap one with a size
+     *    big enough to hold the compacted moving space. This may fail, in which
+     *    case we will use uffd's copy-mode.
+     *    c) If (b) is successful, then mmap the free memfd onto shadow-map.
+     *    d) Same as (A.1.b)
+     *    e) In compaction pause, if the shadow-map was not created, then use
+     *    copy-mode.
+     *    f) Else, if the created map is smaller than the required-size, then
+     *    use mremap (without dontunmap) to expand the size. If failed, then use
+     *    copy-mode.
+     *    g) Otherwise, same as (B.1.d) and use minor-fault mode.
+     *
+     *  2) For linear-alloc space:
+     *    a) Same as (A.2.b)
+     *    b) Use minor-fault mode
+     */
+    auto mmap_shadow_map = [this](int flags, int fd) {
+      void* ret = mmap(shadow_to_space_map_.Begin(),
+                       shadow_to_space_map_.Size(),
+                       PROT_READ | PROT_WRITE,
+                       flags,
+                       fd,
+                       /*offset=*/0);
+      DCHECK_NE(ret, MAP_FAILED) << "mmap for moving-space shadow failed:" << strerror(errno);
+    };
+    // Setup all the virtual memory ranges required for concurrent compaction.
+    if (minor_fault_initialized_) {
+      DCHECK(!is_zygote);
+      if (UNLIKELY(!shadow_to_space_map_.IsValid())) {
+        // This case happens only once on the first GC in minor-fault mode, if
+        // we were unable to reserve shadow-map for moving-space in the
+        // beginning.
+        DCHECK_GE(moving_to_space_fd_, 0);
+        // Take extra 4MB to reduce the likelihood of requiring resizing this
+        // map in the pause due to black allocations.
+        size_t reqd_size = std::min(moving_first_objs_count_ * kPageSize + 4 * MB,
+                                    bump_pointer_space_->Capacity());
+        // We cannot support memory-tool with shadow-map (as it requires
+        // appending a redzone) in this case because the mapping may have to be expanded
+        // using mremap (in KernelPreparation()), which would ignore the redzone.
+        // MemMap::MapFile() appends a redzone, but MemMap::MapAnonymous() doesn't.
+        std::string err_msg;
+        shadow_to_space_map_ = MemMap::MapAnonymous("moving-space-shadow",
+                                                    reqd_size,
+                                                    PROT_NONE,
+                                                    /*low_4gb=*/kObjPtrPoisoning,
+                                                    &err_msg);
+
+        if (shadow_to_space_map_.IsValid()) {
+          CHECK(!kMemoryToolAddsRedzones || shadow_to_space_map_.GetRedzoneSize() == 0u);
+          // We want to use MemMap to get low-4GB mapping, if required, but then also
+          // want to have its ownership as we may grow it (in
+          // KernelPreparation()). If the ownership is not taken and we try to
+          // resize MemMap, then it unmaps the virtual range.
+          MemMap temp = shadow_to_space_map_.TakeReservedMemory(shadow_to_space_map_.Size(),
+                                                                /*reuse*/ true);
+          std::swap(temp, shadow_to_space_map_);
+          DCHECK(!temp.IsValid());
+        } else {
+          LOG(WARNING) << "Failed to create moving space's shadow map of " << PrettySize(reqd_size)
+                       << " size. " << err_msg;
+        }
+      }
+
+      if (LIKELY(shadow_to_space_map_.IsValid())) {
+        int fd = moving_to_space_fd_;
+        int mmap_flags = MAP_SHARED | MAP_FIXED;
+        if (fd == kFdUnused) {
+          // Unused moving-to-space fd means we are using anonymous shared
+          // mapping.
+          DCHECK_EQ(shadow_to_space_map_.Size(), bump_pointer_space_->Capacity());
+          mmap_flags |= MAP_ANONYMOUS;
+          fd = -1;
+        }
+        // If the map is smaller than required, then we'll do mremap in the
+        // compaction pause to increase the size.
+        mmap_shadow_map(mmap_flags, fd);
+      }
+
+      for (auto& data : linear_alloc_spaces_data_) {
+        DCHECK_EQ(mprotect(data.shadow_.Begin(), data.shadow_.Size(), PROT_READ | PROT_WRITE), 0)
+            << "mprotect failed: " << strerror(errno);
+      }
+    } else if (!is_zygote && uffd_minor_fault_supported_) {
+      // First GC after zygote-fork. We will still use uffd's copy mode but will
+      // use it to move objects to MAP_SHARED (to prepare for subsequent GCs, which
+      // will use uffd's minor-fault feature).
+      if (shadow_to_space_map_.IsValid() &&
+          shadow_to_space_map_.Size() == bump_pointer_space_->Capacity()) {
+        mmap_shadow_map(MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, /*fd=*/-1);
+      } else {
+        size_t size = bump_pointer_space_->Capacity();
+        DCHECK_EQ(moving_to_space_fd_, kFdUnused);
+        DCHECK_EQ(moving_from_space_fd_, kFdUnused);
+        const char* name = bump_pointer_space_->GetName();
+        moving_to_space_fd_ = memfd_create(name, MFD_CLOEXEC);
+        CHECK_NE(moving_to_space_fd_, -1)
+            << "memfd_create: failed for " << name << ": " << strerror(errno);
+        moving_from_space_fd_ = memfd_create(name, MFD_CLOEXEC);
+        CHECK_NE(moving_from_space_fd_, -1)
+            << "memfd_create: failed for " << name << ": " << strerror(errno);
+
+        // memfds are considered as files from resource limits point of view.
+        // And the moving space could be several hundred MBs. So increase the
+        // limit, if it's lower than moving-space size.
+        bool rlimit_changed = false;
+        rlimit rlim_read;
+        CHECK_EQ(getrlimit(RLIMIT_FSIZE, &rlim_read), 0) << "getrlimit failed: " << strerror(errno);
+        if (rlim_read.rlim_cur < size) {
+          rlimit_changed = true;
+          rlimit rlim = rlim_read;
+          rlim.rlim_cur = size;
+          CHECK_EQ(setrlimit(RLIMIT_FSIZE, &rlim), 0) << "setrlimit failed: " << strerror(errno);
+        }
+
+        // moving-space will map this fd so that we compact objects into it.
+        int ret = ftruncate(moving_to_space_fd_, size);
+        CHECK_EQ(ret, 0) << "ftruncate failed for moving-space:" << strerror(errno);
+        ret = ftruncate(moving_from_space_fd_, size);
+        CHECK_EQ(ret, 0) << "ftruncate failed for moving-space:" << strerror(errno);
+
+        if (rlimit_changed) {
+          // reset the rlimit to the original limits.
+          CHECK_EQ(setrlimit(RLIMIT_FSIZE, &rlim_read), 0)
+              << "setrlimit failed: " << strerror(errno);
+        }
+      }
+    }
+  }
+}
+
+class MarkCompact::VerifyRootMarkedVisitor : public SingleRootVisitor {
+ public:
+  explicit VerifyRootMarkedVisitor(MarkCompact* collector) : collector_(collector) { }
+
+  void VisitRoot(mirror::Object* root, const RootInfo& info) override
+      REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_) {
+    CHECK(collector_->IsMarked(root) != nullptr) << info.ToString();
+  }
+
+ private:
+  MarkCompact* const collector_;
+};
+
+void MarkCompact::ReMarkRoots(Runtime* runtime) {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  DCHECK_EQ(thread_running_gc_, Thread::Current());
+  Locks::mutator_lock_->AssertExclusiveHeld(thread_running_gc_);
+  MarkNonThreadRoots(runtime);
+  MarkConcurrentRoots(static_cast<VisitRootFlags>(kVisitRootFlagNewRoots
+                                                  | kVisitRootFlagStopLoggingNewRoots
+                                                  | kVisitRootFlagClearRootLog),
+                      runtime);
+
+  if (kVerifyRootsMarked) {
+    TimingLogger::ScopedTiming t2("(Paused)VerifyRoots", GetTimings());
+    VerifyRootMarkedVisitor visitor(this);
+    runtime->VisitRoots(&visitor);
+  }
+}
+
+void MarkCompact::MarkingPause() {
+  TimingLogger::ScopedTiming t("(Paused)MarkingPause", GetTimings());
+  Runtime* runtime = Runtime::Current();
+  Locks::mutator_lock_->AssertExclusiveHeld(thread_running_gc_);
+  {
+    // Handle the dirty objects as we are a concurrent GC
+    WriterMutexLock mu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+    {
+      MutexLock mu2(thread_running_gc_, *Locks::runtime_shutdown_lock_);
+      MutexLock mu3(thread_running_gc_, *Locks::thread_list_lock_);
+      std::list<Thread*> thread_list = runtime->GetThreadList()->GetList();
+      for (Thread* thread : thread_list) {
+        thread->VisitRoots(this, static_cast<VisitRootFlags>(0));
+        DCHECK_EQ(thread->GetThreadLocalGcBuffer(), nullptr);
+        // Need to revoke all the thread-local allocation stacks since we will
+        // swap the allocation stacks (below) and don't want anybody to allocate
+        // into the live stack.
+        thread->RevokeThreadLocalAllocationStack();
+        bump_pointer_space_->RevokeThreadLocalBuffers(thread);
+      }
+    }
+    // Fetch only the accumulated objects-allocated count as it is guaranteed to
+    // be up-to-date after the TLAB revocation above.
+    freed_objects_ += bump_pointer_space_->GetAccumulatedObjectsAllocated();
+    // Capture 'end' of moving-space at this point. Every allocation beyond this
+    // point will be considered as black.
+    // Align-up to page boundary so that black allocations happen from next page
+    // onwards. Also, it ensures that 'end' is aligned for card-table's
+    // ClearCardRange().
+    black_allocations_begin_ = bump_pointer_space_->AlignEnd(thread_running_gc_, kPageSize);
+    DCHECK(IsAligned<kAlignment>(black_allocations_begin_));
+    black_allocations_begin_ = AlignUp(black_allocations_begin_, kPageSize);
+
+    // Re-mark root set. Doesn't include thread-roots as they are already marked
+    // above.
+    ReMarkRoots(runtime);
+    // Scan dirty objects.
+    RecursiveMarkDirtyObjects(/*paused*/ true, accounting::CardTable::kCardDirty);
+    {
+      TimingLogger::ScopedTiming t2("SwapStacks", GetTimings());
+      heap_->SwapStacks();
+      live_stack_freeze_size_ = heap_->GetLiveStack()->Size();
+    }
+  }
+  // TODO: For PreSweepingGcVerification(), find correct strategy to visit/walk
+  // objects in bump-pointer space when we have a mark-bitmap to indicate live
+  // objects. At the same time we also need to be able to visit black allocations,
+  // even though they are not marked in the bitmap. Without both of these we fail
+  // pre-sweeping verification. As well as we leave windows open wherein a
+  // VisitObjects/Walk on the space would either miss some objects or visit
+  // unreachable ones. These windows are when we are switching from shared
+  // mutator-lock to exclusive and vice-versa starting from here till compaction pause.
+  // heap_->PreSweepingGcVerification(this);
+
+  // Disallow new system weaks to prevent a race which occurs when someone adds
+  // a new system weak before we sweep them. Since this new system weak may not
+  // be marked, the GC may incorrectly sweep it. This also fixes a race where
+  // interning may attempt to return a strong reference to a string that is
+  // about to be swept.
+  runtime->DisallowNewSystemWeaks();
+  // Enable the reference processing slow path, needs to be done with mutators
+  // paused since there is no lock in the GetReferent fast path.
+  heap_->GetReferenceProcessor()->EnableSlowPath();
+}
+
+void MarkCompact::SweepSystemWeaks(Thread* self, Runtime* runtime, const bool paused) {
+  TimingLogger::ScopedTiming t(paused ? "(Paused)SweepSystemWeaks" : "SweepSystemWeaks",
+                               GetTimings());
+  ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
+  runtime->SweepSystemWeaks(this);
+}
+
+void MarkCompact::ProcessReferences(Thread* self) {
+  WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
+  GetHeap()->GetReferenceProcessor()->ProcessReferences(self, GetTimings());
+}
+
+void MarkCompact::Sweep(bool swap_bitmaps) {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  // Ensure that nobody inserted objects in the live stack after we swapped the
+  // stacks.
+  CHECK_GE(live_stack_freeze_size_, GetHeap()->GetLiveStack()->Size());
+  {
+    TimingLogger::ScopedTiming t2("MarkAllocStackAsLive", GetTimings());
+    // Mark everything allocated since the last GC as live so that we can sweep
+    // concurrently, knowing that new allocations won't be marked as live.
+    accounting::ObjectStack* live_stack = heap_->GetLiveStack();
+    heap_->MarkAllocStackAsLive(live_stack);
+    live_stack->Reset();
+    DCHECK(mark_stack_->IsEmpty());
+  }
+  for (const auto& space : GetHeap()->GetContinuousSpaces()) {
+    if (space->IsContinuousMemMapAllocSpace() && space != bump_pointer_space_) {
+      space::ContinuousMemMapAllocSpace* alloc_space = space->AsContinuousMemMapAllocSpace();
+      TimingLogger::ScopedTiming split(
+          alloc_space->IsZygoteSpace() ? "SweepZygoteSpace" : "SweepMallocSpace",
+          GetTimings());
+      RecordFree(alloc_space->Sweep(swap_bitmaps));
+    }
+  }
+  SweepLargeObjects(swap_bitmaps);
+}
+
+void MarkCompact::SweepLargeObjects(bool swap_bitmaps) {
+  space::LargeObjectSpace* los = heap_->GetLargeObjectsSpace();
+  if (los != nullptr) {
+    TimingLogger::ScopedTiming split(__FUNCTION__, GetTimings());
+    RecordFreeLOS(los->Sweep(swap_bitmaps));
+  }
+}
+
+void MarkCompact::ReclaimPhase() {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  DCHECK(thread_running_gc_ == Thread::Current());
+  Runtime* const runtime = Runtime::Current();
+  // Process the references concurrently.
+  ProcessReferences(thread_running_gc_);
+  // TODO: Try to merge this system-weak sweeping with the one while updating
+  // references during the compaction pause.
+  SweepSystemWeaks(thread_running_gc_, runtime, /*paused*/ false);
+  runtime->AllowNewSystemWeaks();
+  // Clean up class loaders after system weaks are swept since that is how we know if class
+  // unloading occurred.
+  runtime->GetClassLinker()->CleanupClassLoaders();
+  {
+    WriterMutexLock mu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+    // Reclaim unmarked objects.
+    Sweep(false);
+    // Swap the live and mark bitmaps for each space which we modified space. This is an
+    // optimization that enables us to not clear live bits inside of the sweep. Only swaps unbound
+    // bitmaps.
+    SwapBitmaps();
+    // Unbind the live and mark bitmaps.
+    GetHeap()->UnBindBitmaps();
+  }
+}
+
+// We want to avoid checking for every reference if it's within the page or
+// not. This can be done if we know where in the page the holder object lies.
+// If it doesn't overlap either boundaries then we can skip the checks.
+template <bool kCheckBegin, bool kCheckEnd>
+class MarkCompact::RefsUpdateVisitor {
+ public:
+  explicit RefsUpdateVisitor(MarkCompact* collector,
+                             mirror::Object* obj,
+                             uint8_t* begin,
+                             uint8_t* end)
+      : collector_(collector), obj_(obj), begin_(begin), end_(end) {
+    DCHECK(!kCheckBegin || begin != nullptr);
+    DCHECK(!kCheckEnd || end != nullptr);
+  }
+
+  void operator()(mirror::Object* old ATTRIBUTE_UNUSED, MemberOffset offset, bool /* is_static */)
+      const ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES_SHARED(Locks::heap_bitmap_lock_) {
+    bool update = true;
+    if (kCheckBegin || kCheckEnd) {
+      uint8_t* ref = reinterpret_cast<uint8_t*>(obj_) + offset.Int32Value();
+      update = (!kCheckBegin || ref >= begin_) && (!kCheckEnd || ref < end_);
+    }
+    if (update) {
+      collector_->UpdateRef(obj_, offset);
+    }
+  }
+
+  // For object arrays we don't need to check boundaries here as it's done in
+  // VisitReferenes().
+  // TODO: Optimize reference updating using SIMD instructions. Object arrays
+  // are perfect as all references are tightly packed.
+  void operator()(mirror::Object* old ATTRIBUTE_UNUSED,
+                  MemberOffset offset,
+                  bool /*is_static*/,
+                  bool /*is_obj_array*/)
+      const ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES_SHARED(Locks::heap_bitmap_lock_) {
+    collector_->UpdateRef(obj_, offset);
+  }
+
+  void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+      ALWAYS_INLINE
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (!root->IsNull()) {
+      VisitRoot(root);
+    }
+  }
+
+  void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+      ALWAYS_INLINE
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    collector_->UpdateRoot(root);
+  }
+
+ private:
+  MarkCompact* const collector_;
+  mirror::Object* const obj_;
+  uint8_t* const begin_;
+  uint8_t* const end_;
+};
+
+bool MarkCompact::IsValidObject(mirror::Object* obj) const {
+  mirror::Class* klass = obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
+  if (!heap_->GetVerification()->IsValidHeapObjectAddress(klass)) {
+    return false;
+  }
+  return heap_->GetVerification()->IsValidClassUnchecked<kWithFromSpaceBarrier>(
+          obj->GetClass<kVerifyNone, kWithFromSpaceBarrier>());
+}
+
+template <typename Callback>
+void MarkCompact::VerifyObject(mirror::Object* ref, Callback& callback) const {
+  if (kIsDebugBuild) {
+    mirror::Class* klass = ref->GetClass<kVerifyNone, kWithFromSpaceBarrier>();
+    mirror::Class* pre_compact_klass = ref->GetClass<kVerifyNone, kWithoutReadBarrier>();
+    mirror::Class* klass_klass = klass->GetClass<kVerifyNone, kWithFromSpaceBarrier>();
+    mirror::Class* klass_klass_klass = klass_klass->GetClass<kVerifyNone, kWithFromSpaceBarrier>();
+    if (bump_pointer_space_->HasAddress(pre_compact_klass) &&
+        reinterpret_cast<uint8_t*>(pre_compact_klass) < black_allocations_begin_) {
+      CHECK(moving_space_bitmap_->Test(pre_compact_klass))
+          << "ref=" << ref
+          << " post_compact_end=" << static_cast<void*>(post_compact_end_)
+          << " pre_compact_klass=" << pre_compact_klass
+          << " black_allocations_begin=" << static_cast<void*>(black_allocations_begin_);
+      CHECK(live_words_bitmap_->Test(pre_compact_klass));
+    }
+    if (!IsValidObject(ref)) {
+      std::ostringstream oss;
+      oss << "Invalid object: "
+          << "ref=" << ref
+          << " klass=" << klass
+          << " klass_klass=" << klass_klass
+          << " klass_klass_klass=" << klass_klass_klass
+          << " pre_compact_klass=" << pre_compact_klass
+          << " from_space_begin=" << static_cast<void*>(from_space_begin_)
+          << " pre_compact_begin=" << static_cast<void*>(bump_pointer_space_->Begin())
+          << " post_compact_end=" << static_cast<void*>(post_compact_end_)
+          << " black_allocations_begin=" << static_cast<void*>(black_allocations_begin_);
+
+      // Call callback before dumping larger data like RAM and space dumps.
+      callback(oss);
+
+      oss << " \nobject="
+          << heap_->GetVerification()->DumpRAMAroundAddress(reinterpret_cast<uintptr_t>(ref), 128)
+          << " \nklass(from)="
+          << heap_->GetVerification()->DumpRAMAroundAddress(reinterpret_cast<uintptr_t>(klass), 128)
+          << "spaces:\n";
+      heap_->DumpSpaces(oss);
+      LOG(FATAL) << oss.str();
+    }
+  }
+}
+
+void MarkCompact::CompactPage(mirror::Object* obj,
+                              uint32_t offset,
+                              uint8_t* addr,
+                              bool needs_memset_zero) {
+  DCHECK(moving_space_bitmap_->Test(obj)
+         && live_words_bitmap_->Test(obj));
+  DCHECK(live_words_bitmap_->Test(offset)) << "obj=" << obj
+                                           << " offset=" << offset
+                                           << " addr=" << static_cast<void*>(addr)
+                                           << " black_allocs_begin="
+                                           << static_cast<void*>(black_allocations_begin_)
+                                           << " post_compact_addr="
+                                           << static_cast<void*>(post_compact_end_);
+  uint8_t* const start_addr = addr;
+  // How many distinct live-strides do we have.
+  size_t stride_count = 0;
+  uint8_t* last_stride = addr;
+  uint32_t last_stride_begin = 0;
+  auto verify_obj_callback = [&] (std::ostream& os) {
+                               os << " stride_count=" << stride_count
+                                  << " last_stride=" << static_cast<void*>(last_stride)
+                                  << " offset=" << offset
+                                  << " start_addr=" << static_cast<void*>(start_addr);
+                             };
+  obj = GetFromSpaceAddr(obj);
+  live_words_bitmap_->VisitLiveStrides(offset,
+                                       black_allocations_begin_,
+                                       kPageSize,
+                                       [&addr,
+                                        &last_stride,
+                                        &stride_count,
+                                        &last_stride_begin,
+                                        verify_obj_callback,
+                                        this] (uint32_t stride_begin,
+                                               size_t stride_size,
+                                               bool /*is_last*/)
+                                        REQUIRES_SHARED(Locks::mutator_lock_) {
+                                         const size_t stride_in_bytes = stride_size * kAlignment;
+                                         DCHECK_LE(stride_in_bytes, kPageSize);
+                                         last_stride_begin = stride_begin;
+                                         DCHECK(IsAligned<kAlignment>(addr));
+                                         memcpy(addr,
+                                                from_space_begin_ + stride_begin * kAlignment,
+                                                stride_in_bytes);
+                                         if (kIsDebugBuild) {
+                                           uint8_t* space_begin = bump_pointer_space_->Begin();
+                                           // We can interpret the first word of the stride as an
+                                           // obj only from second stride onwards, as the first
+                                           // stride's first-object may have started on previous
+                                           // page. The only exception is the first page of the
+                                           // moving space.
+                                           if (stride_count > 0
+                                               || stride_begin * kAlignment < kPageSize) {
+                                             mirror::Object* o =
+                                                reinterpret_cast<mirror::Object*>(space_begin
+                                                                                  + stride_begin
+                                                                                  * kAlignment);
+                                             CHECK(live_words_bitmap_->Test(o)) << "ref=" << o;
+                                             CHECK(moving_space_bitmap_->Test(o))
+                                                 << "ref=" << o
+                                                 << " bitmap: "
+                                                 << moving_space_bitmap_->DumpMemAround(o);
+                                             VerifyObject(reinterpret_cast<mirror::Object*>(addr),
+                                                          verify_obj_callback);
+                                           }
+                                         }
+                                         last_stride = addr;
+                                         addr += stride_in_bytes;
+                                         stride_count++;
+                                       });
+  DCHECK_LT(last_stride, start_addr + kPageSize);
+  DCHECK_GT(stride_count, 0u);
+  size_t obj_size = 0;
+  uint32_t offset_within_obj = offset * kAlignment
+                               - (reinterpret_cast<uint8_t*>(obj) - from_space_begin_);
+  // First object
+  if (offset_within_obj > 0) {
+    mirror::Object* to_ref = reinterpret_cast<mirror::Object*>(start_addr - offset_within_obj);
+    if (stride_count > 1) {
+      RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/false> visitor(this,
+                                                                         to_ref,
+                                                                         start_addr,
+                                                                         nullptr);
+      obj_size = obj->VisitRefsForCompaction</*kFetchObjSize*/true, /*kVisitNativeRoots*/false>(
+              visitor, MemberOffset(offset_within_obj), MemberOffset(-1));
+    } else {
+      RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/true> visitor(this,
+                                                                        to_ref,
+                                                                        start_addr,
+                                                                        start_addr + kPageSize);
+      obj_size = obj->VisitRefsForCompaction</*kFetchObjSize*/true, /*kVisitNativeRoots*/false>(
+              visitor, MemberOffset(offset_within_obj), MemberOffset(offset_within_obj
+                                                                     + kPageSize));
+    }
+    obj_size = RoundUp(obj_size, kAlignment);
+    DCHECK_GT(obj_size, offset_within_obj)
+        << "obj:" << obj
+        << " class:"
+        << obj->GetClass<kDefaultVerifyFlags, kWithFromSpaceBarrier>()
+        << " to_addr:" << to_ref
+        << " black-allocation-begin:" << reinterpret_cast<void*>(black_allocations_begin_)
+        << " post-compact-end:" << reinterpret_cast<void*>(post_compact_end_)
+        << " offset:" << offset * kAlignment
+        << " class-after-obj-iter:"
+        << (class_after_obj_iter_ != class_after_obj_ordered_map_.rend() ?
+            class_after_obj_iter_->first.AsMirrorPtr() : nullptr)
+        << " last-reclaimed-page:" << reinterpret_cast<void*>(last_reclaimed_page_)
+        << " last-checked-reclaim-page-idx:" << last_checked_reclaim_page_idx_
+        << " offset-of-last-idx:"
+        << pre_compact_offset_moving_space_[last_checked_reclaim_page_idx_] * kAlignment
+        << " first-obj-of-last-idx:"
+        << first_objs_moving_space_[last_checked_reclaim_page_idx_].AsMirrorPtr();
+
+    obj_size -= offset_within_obj;
+    // If there is only one stride, then adjust last_stride_begin to the
+    // end of the first object.
+    if (stride_count == 1) {
+      last_stride_begin += obj_size / kAlignment;
+    }
+  }
+
+  // Except for the last page being compacted, the pages will have addr ==
+  // start_addr + kPageSize.
+  uint8_t* const end_addr = addr;
+  addr = start_addr;
+  size_t bytes_done = obj_size;
+  // All strides except the last one can be updated without any boundary
+  // checks.
+  DCHECK_LE(addr, last_stride);
+  size_t bytes_to_visit = last_stride - addr;
+  DCHECK_LE(bytes_to_visit, kPageSize);
+  while (bytes_to_visit > bytes_done) {
+    mirror::Object* ref = reinterpret_cast<mirror::Object*>(addr + bytes_done);
+    VerifyObject(ref, verify_obj_callback);
+    RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/false>
+            visitor(this, ref, nullptr, nullptr);
+    obj_size = ref->VisitRefsForCompaction(visitor, MemberOffset(0), MemberOffset(-1));
+    obj_size = RoundUp(obj_size, kAlignment);
+    bytes_done += obj_size;
+  }
+  // Last stride may have multiple objects in it and we don't know where the
+  // last object which crosses the page boundary starts, therefore check
+  // page-end in all of these objects. Also, we need to call
+  // VisitRefsForCompaction() with from-space object as we fetch object size,
+  // which in case of klass requires 'class_size_'.
+  uint8_t* from_addr = from_space_begin_ + last_stride_begin * kAlignment;
+  bytes_to_visit = end_addr - addr;
+  DCHECK_LE(bytes_to_visit, kPageSize);
+  while (bytes_to_visit > bytes_done) {
+    mirror::Object* ref = reinterpret_cast<mirror::Object*>(addr + bytes_done);
+    obj = reinterpret_cast<mirror::Object*>(from_addr);
+    VerifyObject(ref, verify_obj_callback);
+    RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/true>
+            visitor(this, ref, nullptr, start_addr + kPageSize);
+    obj_size = obj->VisitRefsForCompaction(visitor,
+                                           MemberOffset(0),
+                                           MemberOffset(end_addr - (addr + bytes_done)));
+    obj_size = RoundUp(obj_size, kAlignment);
+    DCHECK_GT(obj_size, 0u)
+        << "from_addr:" << obj
+        << " from-space-class:"
+        << obj->GetClass<kDefaultVerifyFlags, kWithFromSpaceBarrier>()
+        << " to_addr:" << ref
+        << " black-allocation-begin:" << reinterpret_cast<void*>(black_allocations_begin_)
+        << " post-compact-end:" << reinterpret_cast<void*>(post_compact_end_)
+        << " offset:" << offset * kAlignment
+        << " bytes_done:" << bytes_done
+        << " class-after-obj-iter:"
+        << (class_after_obj_iter_ != class_after_obj_ordered_map_.rend() ?
+            class_after_obj_iter_->first.AsMirrorPtr() : nullptr)
+        << " last-reclaimed-page:" << reinterpret_cast<void*>(last_reclaimed_page_)
+        << " last-checked-reclaim-page-idx:" << last_checked_reclaim_page_idx_
+        << " offset-of-last-idx:"
+        << pre_compact_offset_moving_space_[last_checked_reclaim_page_idx_] * kAlignment
+        << " first-obj-of-last-idx:"
+        << first_objs_moving_space_[last_checked_reclaim_page_idx_].AsMirrorPtr();
+
+    from_addr += obj_size;
+    bytes_done += obj_size;
+  }
+  // The last page that we compact may have some bytes left untouched in the
+  // end, we should zero them as the kernel copies at page granularity.
+  if (needs_memset_zero && UNLIKELY(bytes_done < kPageSize)) {
+    std::memset(addr + bytes_done, 0x0, kPageSize - bytes_done);
+  }
+}
+
+// We store the starting point (pre_compact_page - first_obj) and first-chunk's
+// size. If more TLAB(s) started in this page, then those chunks are identified
+// using mark bitmap. All this info is prepared in UpdateMovingSpaceBlackAllocations().
+// If we find a set bit in the bitmap, then we copy the remaining page and then
+// use the bitmap to visit each object for updating references.
+void MarkCompact::SlideBlackPage(mirror::Object* first_obj,
+                                 const size_t page_idx,
+                                 uint8_t* const pre_compact_page,
+                                 uint8_t* dest,
+                                 bool needs_memset_zero) {
+  DCHECK(IsAligned<kPageSize>(pre_compact_page));
+  size_t bytes_copied;
+  const uint32_t first_chunk_size = black_alloc_pages_first_chunk_size_[page_idx];
+  mirror::Object* next_page_first_obj = first_objs_moving_space_[page_idx + 1].AsMirrorPtr();
+  uint8_t* src_addr = reinterpret_cast<uint8_t*>(GetFromSpaceAddr(first_obj));
+  uint8_t* pre_compact_addr = reinterpret_cast<uint8_t*>(first_obj);
+  uint8_t* const pre_compact_page_end = pre_compact_page + kPageSize;
+  uint8_t* const dest_page_end = dest + kPageSize;
+
+  auto verify_obj_callback = [&] (std::ostream& os) {
+                               os << " first_obj=" << first_obj
+                                  << " next_page_first_obj=" << next_page_first_obj
+                                  << " first_chunk_sie=" << first_chunk_size
+                                  << " dest=" << static_cast<void*>(dest)
+                                  << " pre_compact_page="
+                                  << static_cast<void* const>(pre_compact_page);
+                             };
+  // We have empty portion at the beginning of the page. Zero it.
+  if (pre_compact_addr > pre_compact_page) {
+    bytes_copied = pre_compact_addr - pre_compact_page;
+    DCHECK_LT(bytes_copied, kPageSize);
+    if (needs_memset_zero) {
+      std::memset(dest, 0x0, bytes_copied);
+    }
+    dest += bytes_copied;
+  } else {
+    bytes_copied = 0;
+    size_t offset = pre_compact_page - pre_compact_addr;
+    pre_compact_addr = pre_compact_page;
+    src_addr += offset;
+    DCHECK(IsAligned<kPageSize>(src_addr));
+  }
+  // Copy the first chunk of live words
+  std::memcpy(dest, src_addr, first_chunk_size);
+  // Update references in the first chunk. Use object size to find next object.
+  {
+    size_t bytes_to_visit = first_chunk_size;
+    size_t obj_size;
+    // The first object started in some previous page. So we need to check the
+    // beginning.
+    DCHECK_LE(reinterpret_cast<uint8_t*>(first_obj), pre_compact_addr);
+    size_t offset = pre_compact_addr - reinterpret_cast<uint8_t*>(first_obj);
+    if (bytes_copied == 0 && offset > 0) {
+      mirror::Object* to_obj = reinterpret_cast<mirror::Object*>(dest - offset);
+      mirror::Object* from_obj = reinterpret_cast<mirror::Object*>(src_addr - offset);
+      // If the next page's first-obj is in this page or nullptr, then we don't
+      // need to check end boundary
+      if (next_page_first_obj == nullptr
+          || (first_obj != next_page_first_obj
+              && reinterpret_cast<uint8_t*>(next_page_first_obj) <= pre_compact_page_end)) {
+        RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/false> visitor(this,
+                                                                           to_obj,
+                                                                           dest,
+                                                                           nullptr);
+        obj_size = from_obj->VisitRefsForCompaction<
+                /*kFetchObjSize*/true, /*kVisitNativeRoots*/false>(visitor,
+                                                                   MemberOffset(offset),
+                                                                   MemberOffset(-1));
+      } else {
+        RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/true> visitor(this,
+                                                                          to_obj,
+                                                                          dest,
+                                                                          dest_page_end);
+        obj_size = from_obj->VisitRefsForCompaction<
+                /*kFetchObjSize*/true, /*kVisitNativeRoots*/false>(visitor,
+                                                                   MemberOffset(offset),
+                                                                   MemberOffset(offset
+                                                                                + kPageSize));
+        if (first_obj == next_page_first_obj) {
+          // First object is the only object on this page. So there's nothing else left to do.
+          return;
+        }
+      }
+      obj_size = RoundUp(obj_size, kAlignment);
+      obj_size -= offset;
+      dest += obj_size;
+      bytes_to_visit -= obj_size;
+    }
+    bytes_copied += first_chunk_size;
+    // If the last object in this page is next_page_first_obj, then we need to check end boundary
+    bool check_last_obj = false;
+    if (next_page_first_obj != nullptr
+        && reinterpret_cast<uint8_t*>(next_page_first_obj) < pre_compact_page_end
+        && bytes_copied == kPageSize) {
+      size_t diff = pre_compact_page_end - reinterpret_cast<uint8_t*>(next_page_first_obj);
+      DCHECK_LE(diff, kPageSize);
+      DCHECK_LE(diff, bytes_to_visit);
+      bytes_to_visit -= diff;
+      check_last_obj = true;
+    }
+    while (bytes_to_visit > 0) {
+      mirror::Object* dest_obj = reinterpret_cast<mirror::Object*>(dest);
+      VerifyObject(dest_obj, verify_obj_callback);
+      RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/false> visitor(this,
+                                                                          dest_obj,
+                                                                          nullptr,
+                                                                          nullptr);
+      obj_size = dest_obj->VisitRefsForCompaction(visitor, MemberOffset(0), MemberOffset(-1));
+      obj_size = RoundUp(obj_size, kAlignment);
+      bytes_to_visit -= obj_size;
+      dest += obj_size;
+    }
+    DCHECK_EQ(bytes_to_visit, 0u);
+    if (check_last_obj) {
+      mirror::Object* dest_obj = reinterpret_cast<mirror::Object*>(dest);
+      VerifyObject(dest_obj, verify_obj_callback);
+      RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/true> visitor(this,
+                                                                         dest_obj,
+                                                                         nullptr,
+                                                                         dest_page_end);
+      mirror::Object* obj = GetFromSpaceAddr(next_page_first_obj);
+      obj->VisitRefsForCompaction</*kFetchObjSize*/false>(visitor,
+                                                          MemberOffset(0),
+                                                          MemberOffset(dest_page_end - dest));
+      return;
+    }
+  }
+
+  // Probably a TLAB finished on this page and/or a new TLAB started as well.
+  if (bytes_copied < kPageSize) {
+    src_addr += first_chunk_size;
+    pre_compact_addr += first_chunk_size;
+    // Use mark-bitmap to identify where objects are. First call
+    // VisitMarkedRange for only the first marked bit. If found, zero all bytes
+    // until that object and then call memcpy on the rest of the page.
+    // Then call VisitMarkedRange for all marked bits *after* the one found in
+    // this invocation. This time to visit references.
+    uintptr_t start_visit = reinterpret_cast<uintptr_t>(pre_compact_addr);
+    uintptr_t page_end = reinterpret_cast<uintptr_t>(pre_compact_page_end);
+    mirror::Object* found_obj = nullptr;
+    moving_space_bitmap_->VisitMarkedRange</*kVisitOnce*/true>(start_visit,
+                                                                page_end,
+                                                                [&found_obj](mirror::Object* obj) {
+                                                                  found_obj = obj;
+                                                                });
+    size_t remaining_bytes = kPageSize - bytes_copied;
+    if (found_obj == nullptr) {
+      if (needs_memset_zero) {
+        // No more black objects in this page. Zero the remaining bytes and return.
+        std::memset(dest, 0x0, remaining_bytes);
+      }
+      return;
+    }
+    // Copy everything in this page, which includes any zeroed regions
+    // in-between.
+    std::memcpy(dest, src_addr, remaining_bytes);
+    DCHECK_LT(reinterpret_cast<uintptr_t>(found_obj), page_end);
+    moving_space_bitmap_->VisitMarkedRange(
+            reinterpret_cast<uintptr_t>(found_obj) + mirror::kObjectHeaderSize,
+            page_end,
+            [&found_obj, pre_compact_addr, dest, this, verify_obj_callback] (mirror::Object* obj)
+            REQUIRES_SHARED(Locks::mutator_lock_) {
+              ptrdiff_t diff = reinterpret_cast<uint8_t*>(found_obj) - pre_compact_addr;
+              mirror::Object* ref = reinterpret_cast<mirror::Object*>(dest + diff);
+              VerifyObject(ref, verify_obj_callback);
+              RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/false>
+                      visitor(this, ref, nullptr, nullptr);
+              ref->VisitRefsForCompaction</*kFetchObjSize*/false>(visitor,
+                                                                  MemberOffset(0),
+                                                                  MemberOffset(-1));
+              // Remember for next round.
+              found_obj = obj;
+            });
+    // found_obj may have been updated in VisitMarkedRange. Visit the last found
+    // object.
+    DCHECK_GT(reinterpret_cast<uint8_t*>(found_obj), pre_compact_addr);
+    DCHECK_LT(reinterpret_cast<uintptr_t>(found_obj), page_end);
+    ptrdiff_t diff = reinterpret_cast<uint8_t*>(found_obj) - pre_compact_addr;
+    mirror::Object* ref = reinterpret_cast<mirror::Object*>(dest + diff);
+    VerifyObject(ref, verify_obj_callback);
+    RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/true> visitor(this,
+                                                                       ref,
+                                                                       nullptr,
+                                                                       dest_page_end);
+    ref->VisitRefsForCompaction</*kFetchObjSize*/false>(
+            visitor, MemberOffset(0), MemberOffset(page_end -
+                                                   reinterpret_cast<uintptr_t>(found_obj)));
+  }
+}
+
+template <bool kFirstPageMapping>
+void MarkCompact::MapProcessedPages(uint8_t* to_space_start,
+                                    Atomic<PageState>* state_arr,
+                                    size_t arr_idx,
+                                    size_t arr_len) {
+  DCHECK(minor_fault_initialized_);
+  DCHECK_LT(arr_idx, arr_len);
+  DCHECK_ALIGNED(to_space_start, kPageSize);
+  // Claim all the contiguous pages, which are ready to be mapped, and then do
+  // so in a single ioctl. This helps avoid the overhead of invoking syscall
+  // several times and also maps the already-processed pages, avoiding
+  // unnecessary faults on them.
+  size_t length = kFirstPageMapping ? kPageSize : 0;
+  if (kFirstPageMapping) {
+    arr_idx++;
+  }
+  // We need to guarantee that we don't end up sucsessfully marking a later
+  // page 'mapping' and then fail to mark an earlier page. To guarantee that
+  // we use acq_rel order.
+  for (; arr_idx < arr_len; arr_idx++, length += kPageSize) {
+    PageState expected_state = PageState::kProcessed;
+    if (!state_arr[arr_idx].compare_exchange_strong(
+            expected_state, PageState::kProcessedAndMapping, std::memory_order_acq_rel)) {
+      break;
+    }
+  }
+  if (length > 0) {
+    // Note: We need the first page to be attempted (to be mapped) by the ioctl
+    // as this function is called due to some mutator thread waiting on the
+    // 'to_space_start' page. Therefore, the ioctl must always be called
+    // with 'to_space_start' as the 'start' address because it can bail out in
+    // the middle (not attempting to map the subsequent pages) if it finds any
+    // page either already mapped in between, or missing on the shadow-map.
+    struct uffdio_continue uffd_continue;
+    uffd_continue.range.start = reinterpret_cast<uintptr_t>(to_space_start);
+    uffd_continue.range.len = length;
+    uffd_continue.mode = 0;
+    int ret = ioctl(uffd_, UFFDIO_CONTINUE, &uffd_continue);
+    if (UNLIKELY(ret == -1 && errno == EAGAIN)) {
+      // This can happen only in linear-alloc.
+      DCHECK(linear_alloc_spaces_data_.end() !=
+             std::find_if(linear_alloc_spaces_data_.begin(),
+                          linear_alloc_spaces_data_.end(),
+                          [to_space_start](const LinearAllocSpaceData& data) {
+                            return data.begin_ <= to_space_start && to_space_start < data.end_;
+                          }));
+
+      // This could happen if userfaultfd couldn't find any pages mapped in the
+      // shadow map. For instance, if there are certain (contiguous) pages on
+      // linear-alloc which are allocated and have first-object set-up but have
+      // not been accessed yet.
+      // Bail out by setting the remaining pages' state back to kProcessed and
+      // then waking up any waiting threads.
+      DCHECK_GE(uffd_continue.mapped, 0);
+      DCHECK_ALIGNED(uffd_continue.mapped, kPageSize);
+      DCHECK_LT(uffd_continue.mapped, static_cast<ssize_t>(length));
+      if (kFirstPageMapping) {
+        // In this case the first page must be mapped.
+        DCHECK_GE(uffd_continue.mapped, static_cast<ssize_t>(kPageSize));
+      }
+      // Nobody would modify these pages' state simultaneously so only atomic
+      // store is sufficient. Use 'release' order to ensure that all states are
+      // modified sequentially.
+      for (size_t remaining_len = length - uffd_continue.mapped; remaining_len > 0;
+           remaining_len -= kPageSize) {
+        arr_idx--;
+        DCHECK_EQ(state_arr[arr_idx].load(std::memory_order_relaxed),
+                  PageState::kProcessedAndMapping);
+        state_arr[arr_idx].store(PageState::kProcessed, std::memory_order_release);
+      }
+      uffd_continue.range.start =
+          reinterpret_cast<uintptr_t>(to_space_start) + uffd_continue.mapped;
+      uffd_continue.range.len = length - uffd_continue.mapped;
+      ret = ioctl(uffd_, UFFDIO_WAKE, &uffd_continue.range);
+      CHECK_EQ(ret, 0) << "ioctl_userfaultfd: wake failed: " << strerror(errno);
+    } else {
+      // We may receive ENOENT if gc-thread unregisters the
+      // range behind our back, which is fine because that
+      // happens only when it knows compaction is done.
+      CHECK(ret == 0 || !kFirstPageMapping || errno == ENOENT)
+          << "ioctl_userfaultfd: continue failed: " << strerror(errno);
+      if (ret == 0) {
+        DCHECK_EQ(uffd_continue.mapped, static_cast<ssize_t>(length));
+      }
+    }
+    if (use_uffd_sigbus_) {
+      // Nobody else would modify these pages' state simultaneously so atomic
+      // store is sufficient.
+      for (; uffd_continue.mapped > 0; uffd_continue.mapped -= kPageSize) {
+        arr_idx--;
+        DCHECK_EQ(state_arr[arr_idx].load(std::memory_order_relaxed),
+                  PageState::kProcessedAndMapping);
+        state_arr[arr_idx].store(PageState::kProcessedAndMapped, std::memory_order_release);
+      }
+    }
+  }
+}
+
+void MarkCompact::ZeropageIoctl(void* addr, bool tolerate_eexist, bool tolerate_enoent) {
+  struct uffdio_zeropage uffd_zeropage;
+  DCHECK(IsAligned<kPageSize>(addr));
+  uffd_zeropage.range.start = reinterpret_cast<uintptr_t>(addr);
+  uffd_zeropage.range.len = kPageSize;
+  uffd_zeropage.mode = 0;
+  int ret = ioctl(uffd_, UFFDIO_ZEROPAGE, &uffd_zeropage);
+  if (LIKELY(ret == 0)) {
+    DCHECK_EQ(uffd_zeropage.zeropage, static_cast<ssize_t>(kPageSize));
+  } else {
+    CHECK((tolerate_enoent && errno == ENOENT) || (tolerate_eexist && errno == EEXIST))
+        << "ioctl_userfaultfd: zeropage failed: " << strerror(errno) << ". addr:" << addr;
+  }
+}
+
+void MarkCompact::CopyIoctl(void* dst, void* buffer) {
+  struct uffdio_copy uffd_copy;
+  uffd_copy.src = reinterpret_cast<uintptr_t>(buffer);
+  uffd_copy.dst = reinterpret_cast<uintptr_t>(dst);
+  uffd_copy.len = kPageSize;
+  uffd_copy.mode = 0;
+  CHECK_EQ(ioctl(uffd_, UFFDIO_COPY, &uffd_copy), 0)
+      << "ioctl_userfaultfd: copy failed: " << strerror(errno) << ". src:" << buffer
+      << " dst:" << dst;
+  DCHECK_EQ(uffd_copy.copy, static_cast<ssize_t>(kPageSize));
+}
+
+template <int kMode, typename CompactionFn>
+void MarkCompact::DoPageCompactionWithStateChange(size_t page_idx,
+                                                  size_t status_arr_len,
+                                                  uint8_t* to_space_page,
+                                                  uint8_t* page,
+                                                  CompactionFn func) {
+  PageState expected_state = PageState::kUnprocessed;
+  PageState desired_state =
+      kMode == kCopyMode ? PageState::kProcessingAndMapping : PageState::kProcessing;
+  // In the concurrent case (kMode != kFallbackMode) we need to ensure that the update
+  // to moving_spaces_status_[page_idx] is released before the contents of the page are
+  // made accessible to other threads.
+  //
+  // We need acquire ordering here to ensure that when the CAS fails, another thread
+  // has completed processing the page, which is guaranteed by the release below.
+  if (kMode == kFallbackMode || moving_pages_status_[page_idx].compare_exchange_strong(
+                                    expected_state, desired_state, std::memory_order_acquire)) {
+    func();
+    if (kMode == kCopyMode) {
+      CopyIoctl(to_space_page, page);
+      if (use_uffd_sigbus_) {
+        // Store is sufficient as no other thread would modify the status at this point.
+        moving_pages_status_[page_idx].store(PageState::kProcessedAndMapped,
+                                             std::memory_order_release);
+      }
+    } else if (kMode == kMinorFaultMode) {
+      expected_state = PageState::kProcessing;
+      desired_state = PageState::kProcessed;
+      // the CAS needs to be with release order to ensure that stores to the
+      // page makes it to memory *before* other threads observe that it's
+      // ready to be mapped.
+      if (!moving_pages_status_[page_idx].compare_exchange_strong(
+              expected_state, desired_state, std::memory_order_release)) {
+        // Some mutator has requested to map the page after processing it.
+        DCHECK_EQ(expected_state, PageState::kProcessingAndMapping);
+        MapProcessedPages</*kFirstPageMapping=*/true>(
+            to_space_page, moving_pages_status_, page_idx, status_arr_len);
+      }
+    }
+  } else {
+    DCHECK_GT(expected_state, PageState::kProcessed);
+  }
+}
+
+void MarkCompact::FreeFromSpacePages(size_t cur_page_idx, int mode) {
+  // Thanks to sliding compaction, bump-pointer allocations, and reverse
+  // compaction (see CompactMovingSpace) the logic here is pretty simple: find
+  // the to-space page up to which compaction has finished, all the from-space
+  // pages corresponding to this onwards can be freed. There are some corner
+  // cases to be taken care of, which are described below.
+  size_t idx = last_checked_reclaim_page_idx_;
+  // Find the to-space page up to which the corresponding from-space pages can be
+  // freed.
+  for (; idx > cur_page_idx; idx--) {
+    PageState state = moving_pages_status_[idx - 1].load(std::memory_order_acquire);
+    if (state == PageState::kMutatorProcessing) {
+      // Some mutator is working on the page.
+      break;
+    }
+    DCHECK(state >= PageState::kProcessed ||
+           (state == PageState::kUnprocessed &&
+            (mode == kFallbackMode || idx > moving_first_objs_count_)));
+  }
+  DCHECK_LE(idx, last_checked_reclaim_page_idx_);
+  if (idx == last_checked_reclaim_page_idx_) {
+    // Nothing to do.
+    return;
+  }
+
+  uint8_t* reclaim_begin;
+  uint8_t* idx_addr;
+  // Calculate the first from-space page to be freed using 'idx'. If the
+  // first-object of the idx'th to-space page started before the corresponding
+  // from-space page, which is almost always the case in the compaction portion
+  // of the moving-space, then it indicates that the subsequent pages that are
+  // yet to be compacted will need the from-space pages. Therefore, find the page
+  // (from the already compacted pages) whose first-object is different from
+  // ours. All the from-space pages starting from that one are safe to be
+  // removed. Please note that this iteration is not expected to be long in
+  // normal cases as objects are smaller than page size.
+  if (idx >= moving_first_objs_count_) {
+    // black-allocated portion of the moving-space
+    idx_addr = black_allocations_begin_ + (idx - moving_first_objs_count_) * kPageSize;
+    reclaim_begin = idx_addr;
+    mirror::Object* first_obj = first_objs_moving_space_[idx].AsMirrorPtr();
+    if (first_obj != nullptr && reinterpret_cast<uint8_t*>(first_obj) < reclaim_begin) {
+      size_t idx_len = moving_first_objs_count_ + black_page_count_;
+      for (size_t i = idx + 1; i < idx_len; i++) {
+        mirror::Object* obj = first_objs_moving_space_[i].AsMirrorPtr();
+        // A null first-object indicates that the corresponding to-space page is
+        // not used yet. So we can compute its from-space page and use that.
+        if (obj != first_obj) {
+          reclaim_begin = obj != nullptr
+                          ? AlignUp(reinterpret_cast<uint8_t*>(obj), kPageSize)
+                          : (black_allocations_begin_ + (i - moving_first_objs_count_) * kPageSize);
+          break;
+        }
+      }
+    }
+  } else {
+    DCHECK_GE(pre_compact_offset_moving_space_[idx], 0u);
+    idx_addr = bump_pointer_space_->Begin() + pre_compact_offset_moving_space_[idx] * kAlignment;
+    reclaim_begin = idx_addr;
+    DCHECK_LE(reclaim_begin, black_allocations_begin_);
+    mirror::Object* first_obj = first_objs_moving_space_[idx].AsMirrorPtr();
+    if (reinterpret_cast<uint8_t*>(first_obj) < reclaim_begin) {
+      DCHECK_LT(idx, moving_first_objs_count_);
+      mirror::Object* obj = first_obj;
+      for (size_t i = idx + 1; i < moving_first_objs_count_; i++) {
+        obj = first_objs_moving_space_[i].AsMirrorPtr();
+        if (first_obj != obj) {
+          DCHECK_LT(first_obj, obj);
+          DCHECK_LT(reclaim_begin, reinterpret_cast<uint8_t*>(obj));
+          reclaim_begin = reinterpret_cast<uint8_t*>(obj);
+          break;
+        }
+      }
+      if (obj == first_obj) {
+        reclaim_begin = black_allocations_begin_;
+      }
+    }
+    reclaim_begin = AlignUp(reclaim_begin, kPageSize);
+  }
+
+  DCHECK_NE(reclaim_begin, nullptr);
+  DCHECK_ALIGNED(reclaim_begin, kPageSize);
+  DCHECK_ALIGNED(last_reclaimed_page_, kPageSize);
+  // Check if the 'class_after_obj_map_' map allows pages to be freed.
+  for (; class_after_obj_iter_ != class_after_obj_ordered_map_.rend(); class_after_obj_iter_++) {
+    mirror::Object* klass = class_after_obj_iter_->first.AsMirrorPtr();
+    mirror::Class* from_klass = static_cast<mirror::Class*>(GetFromSpaceAddr(klass));
+    // Check with class' end to ensure that, if required, the entire class survives.
+    uint8_t* klass_end = reinterpret_cast<uint8_t*>(klass) + from_klass->SizeOf<kVerifyNone>();
+    DCHECK_LE(klass_end, last_reclaimed_page_);
+    if (reinterpret_cast<uint8_t*>(klass_end) >= reclaim_begin) {
+      // Found a class which is in the reclaim range.
+      uint8_t* obj_addr = reinterpret_cast<uint8_t*>(class_after_obj_iter_->second.AsMirrorPtr());
+      // NOTE: Don't assert that obj is of 'klass' type as klass could instead
+      // be its super-class.
+      if (obj_addr < idx_addr) {
+        // Its lowest-address object is not compacted yet. Reclaim starting from
+        // the end of this class.
+        reclaim_begin = AlignUp(klass_end, kPageSize);
+      } else {
+        // Continue consuming pairs wherein the lowest address object has already
+        // been compacted.
+        continue;
+      }
+    }
+    // All the remaining class (and thereby corresponding object) addresses are
+    // lower than the reclaim range.
+    break;
+  }
+
+  ssize_t size = last_reclaimed_page_ - reclaim_begin;
+  if (size >= kMinFromSpaceMadviseSize) {
+    int behavior = minor_fault_initialized_ ? MADV_REMOVE : MADV_DONTNEED;
+    CHECK_EQ(madvise(reclaim_begin + from_space_slide_diff_, size, behavior), 0)
+        << "madvise of from-space failed: " << strerror(errno);
+    last_reclaimed_page_ = reclaim_begin;
+  }
+  last_checked_reclaim_page_idx_ = idx;
+}
+
+void MarkCompact::UpdateClassAfterObjMap() {
+  CHECK(class_after_obj_ordered_map_.empty());
+  for (const auto& pair : class_after_obj_hash_map_) {
+    auto super_class_iter = super_class_after_class_hash_map_.find(pair.first);
+    ObjReference key = super_class_iter != super_class_after_class_hash_map_.end()
+                       ? super_class_iter->second
+                       : pair.first;
+    if (std::less<mirror::Object*>{}(pair.second.AsMirrorPtr(), key.AsMirrorPtr()) &&
+        bump_pointer_space_->HasAddress(key.AsMirrorPtr())) {
+      auto [ret_iter, success] = class_after_obj_ordered_map_.try_emplace(key, pair.second);
+      // It could fail only if the class 'key' has objects of its own, which are lower in
+      // address order, as well of some of its derived class. In this case
+      // choose the lowest address object.
+      if (!success &&
+          std::less<mirror::Object*>{}(pair.second.AsMirrorPtr(), ret_iter->second.AsMirrorPtr())) {
+        ret_iter->second = pair.second;
+      }
+    }
+  }
+  class_after_obj_hash_map_.clear();
+  super_class_after_class_hash_map_.clear();
+}
+
+template <int kMode>
+void MarkCompact::CompactMovingSpace(uint8_t* page) {
+  // For every page we have a starting object, which may have started in some
+  // preceding page, and an offset within that object from where we must start
+  // copying.
+  // Consult the live-words bitmap to copy all contiguously live words at a
+  // time. These words may constitute multiple objects. To avoid the need for
+  // consulting mark-bitmap to find where does the next live object start, we
+  // use the object-size returned by VisitRefsForCompaction.
+  //
+  // We do the compaction in reverse direction so that the pages containing
+  // TLAB and latest allocations are processed first.
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  size_t page_status_arr_len = moving_first_objs_count_ + black_page_count_;
+  size_t idx = page_status_arr_len;
+  uint8_t* to_space_end = bump_pointer_space_->Begin() + page_status_arr_len * kPageSize;
+  uint8_t* shadow_space_end = nullptr;
+  if (kMode == kMinorFaultMode) {
+    shadow_space_end = shadow_to_space_map_.Begin() + page_status_arr_len * kPageSize;
+  }
+  uint8_t* pre_compact_page = black_allocations_begin_ + (black_page_count_ * kPageSize);
+
+  DCHECK(IsAligned<kPageSize>(pre_compact_page));
+
+  UpdateClassAfterObjMap();
+  // These variables are maintained by FreeFromSpacePages().
+  last_reclaimed_page_ = pre_compact_page;
+  last_checked_reclaim_page_idx_ = idx;
+  class_after_obj_iter_ = class_after_obj_ordered_map_.rbegin();
+  // Allocated-black pages
+  while (idx > moving_first_objs_count_) {
+    idx--;
+    pre_compact_page -= kPageSize;
+    to_space_end -= kPageSize;
+    if (kMode == kMinorFaultMode) {
+      shadow_space_end -= kPageSize;
+      page = shadow_space_end;
+    } else if (kMode == kFallbackMode) {
+      page = to_space_end;
+    }
+    mirror::Object* first_obj = first_objs_moving_space_[idx].AsMirrorPtr();
+    if (first_obj != nullptr) {
+      DoPageCompactionWithStateChange<kMode>(
+          idx,
+          page_status_arr_len,
+          to_space_end,
+          page,
+          [&]() REQUIRES_SHARED(Locks::mutator_lock_) {
+            SlideBlackPage(first_obj, idx, pre_compact_page, page, kMode == kCopyMode);
+          });
+      // We are sliding here, so no point attempting to madvise for every
+      // page. Wait for enough pages to be done.
+      if (idx % (kMinFromSpaceMadviseSize / kPageSize) == 0) {
+        FreeFromSpacePages(idx, kMode);
+      }
+    }
+  }
+  DCHECK_EQ(pre_compact_page, black_allocations_begin_);
+
+  while (idx > 0) {
+    idx--;
+    to_space_end -= kPageSize;
+    if (kMode == kMinorFaultMode) {
+      shadow_space_end -= kPageSize;
+      page = shadow_space_end;
+    } else if (kMode == kFallbackMode) {
+      page = to_space_end;
+    }
+    mirror::Object* first_obj = first_objs_moving_space_[idx].AsMirrorPtr();
+    DoPageCompactionWithStateChange<kMode>(
+        idx, page_status_arr_len, to_space_end, page, [&]() REQUIRES_SHARED(Locks::mutator_lock_) {
+          CompactPage(first_obj, pre_compact_offset_moving_space_[idx], page, kMode == kCopyMode);
+        });
+    FreeFromSpacePages(idx, kMode);
+  }
+  DCHECK_EQ(to_space_end, bump_pointer_space_->Begin());
+}
+
+void MarkCompact::UpdateNonMovingPage(mirror::Object* first, uint8_t* page) {
+  DCHECK_LT(reinterpret_cast<uint8_t*>(first), page + kPageSize);
+  // For every object found in the page, visit the previous object. This ensures
+  // that we can visit without checking page-end boundary.
+  // Call VisitRefsForCompaction with from-space read-barrier as the klass object and
+  // super-class loads require it.
+  // TODO: Set kVisitNativeRoots to false once we implement concurrent
+  // compaction
+  mirror::Object* curr_obj = first;
+  non_moving_space_bitmap_->VisitMarkedRange(
+          reinterpret_cast<uintptr_t>(first) + mirror::kObjectHeaderSize,
+          reinterpret_cast<uintptr_t>(page + kPageSize),
+          [&](mirror::Object* next_obj) {
+            // TODO: Once non-moving space update becomes concurrent, we'll
+            // require fetching the from-space address of 'curr_obj' and then call
+            // visitor on that.
+            if (reinterpret_cast<uint8_t*>(curr_obj) < page) {
+              RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/false>
+                      visitor(this, curr_obj, page, page + kPageSize);
+              MemberOffset begin_offset(page - reinterpret_cast<uint8_t*>(curr_obj));
+              // Native roots shouldn't be visited as they are done when this
+              // object's beginning was visited in the preceding page.
+              curr_obj->VisitRefsForCompaction</*kFetchObjSize*/false, /*kVisitNativeRoots*/false>(
+                      visitor, begin_offset, MemberOffset(-1));
+            } else {
+              RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/false>
+                      visitor(this, curr_obj, page, page + kPageSize);
+              curr_obj->VisitRefsForCompaction</*kFetchObjSize*/false>(visitor,
+                                                                       MemberOffset(0),
+                                                                       MemberOffset(-1));
+            }
+            curr_obj = next_obj;
+          });
+
+  MemberOffset end_offset(page + kPageSize - reinterpret_cast<uint8_t*>(curr_obj));
+  if (reinterpret_cast<uint8_t*>(curr_obj) < page) {
+    RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/true>
+            visitor(this, curr_obj, page, page + kPageSize);
+    curr_obj->VisitRefsForCompaction</*kFetchObjSize*/false, /*kVisitNativeRoots*/false>(
+            visitor, MemberOffset(page - reinterpret_cast<uint8_t*>(curr_obj)), end_offset);
+  } else {
+    RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/true>
+            visitor(this, curr_obj, page, page + kPageSize);
+    curr_obj->VisitRefsForCompaction</*kFetchObjSize*/false>(visitor, MemberOffset(0), end_offset);
+  }
+}
+
+void MarkCompact::UpdateNonMovingSpace() {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  // Iterating in reverse ensures that the class pointer in objects which span
+  // across more than one page gets updated in the end. This is necessary for
+  // VisitRefsForCompaction() to work correctly.
+  // TODO: If and when we make non-moving space update concurrent, implement a
+  // mechanism to remember class pointers for such objects off-heap and pass it
+  // to VisitRefsForCompaction().
+  uint8_t* page = non_moving_space_->Begin() + non_moving_first_objs_count_ * kPageSize;
+  for (ssize_t i = non_moving_first_objs_count_ - 1; i >= 0; i--) {
+    mirror::Object* obj = first_objs_non_moving_space_[i].AsMirrorPtr();
+    page -= kPageSize;
+    // null means there are no objects on the page to update references.
+    if (obj != nullptr) {
+      UpdateNonMovingPage(obj, page);
+    }
+  }
+}
+
+void MarkCompact::UpdateMovingSpaceBlackAllocations() {
+  // For sliding black pages, we need the first-object, which overlaps with the
+  // first byte of the page. Additionally, we compute the size of first chunk of
+  // black objects. This will suffice for most black pages. Unlike, compaction
+  // pages, here we don't need to pre-compute the offset within first-obj from
+  // where sliding has to start. That can be calculated using the pre-compact
+  // address of the page. Therefore, to save space, we store the first chunk's
+  // size in black_alloc_pages_first_chunk_size_ array.
+  // For the pages which may have holes after the first chunk, which could happen
+  // if a new TLAB starts in the middle of the page, we mark the objects in
+  // the mark-bitmap. So, if the first-chunk size is smaller than kPageSize,
+  // then we use the mark-bitmap for the remainder of the page.
+  uint8_t* const begin = bump_pointer_space_->Begin();
+  uint8_t* black_allocs = black_allocations_begin_;
+  DCHECK_LE(begin, black_allocs);
+  size_t consumed_blocks_count = 0;
+  size_t first_block_size;
+  // Get the list of all blocks allocated in the bump-pointer space.
+  std::vector<size_t>* block_sizes = bump_pointer_space_->GetBlockSizes(thread_running_gc_,
+                                                                        &first_block_size);
+  DCHECK_LE(first_block_size, (size_t)(black_allocs - begin));
+  if (block_sizes != nullptr) {
+    size_t black_page_idx = moving_first_objs_count_;
+    uint8_t* block_end = begin + first_block_size;
+    uint32_t remaining_chunk_size = 0;
+    uint32_t first_chunk_size = 0;
+    mirror::Object* first_obj = nullptr;
+    for (size_t block_size : *block_sizes) {
+      block_end += block_size;
+      // Skip the blocks that are prior to the black allocations. These will be
+      // merged with the main-block later.
+      if (black_allocs >= block_end) {
+        consumed_blocks_count++;
+        continue;
+      }
+      mirror::Object* obj = reinterpret_cast<mirror::Object*>(black_allocs);
+      bool set_mark_bit = remaining_chunk_size > 0;
+      // We don't know how many objects are allocated in the current block. When we hit
+      // a null assume it's the end. This works as every block is expected to
+      // have objects allocated linearly using bump-pointer.
+      // BumpPointerSpace::Walk() also works similarly.
+      while (black_allocs < block_end
+             && obj->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>() != nullptr) {
+        // Try to keep instructions which access class instance together to
+        // avoid reloading the pointer from object.
+        size_t obj_size = obj->SizeOf();
+        bytes_scanned_ += obj_size;
+        obj_size = RoundUp(obj_size, kAlignment);
+        UpdateClassAfterObjectMap(obj);
+        if (first_obj == nullptr) {
+          first_obj = obj;
+        }
+        // We only need the mark-bitmap in the pages wherein a new TLAB starts in
+        // the middle of the page.
+        if (set_mark_bit) {
+          moving_space_bitmap_->Set(obj);
+        }
+        // Handle objects which cross page boundary, including objects larger
+        // than page size.
+        if (remaining_chunk_size + obj_size >= kPageSize) {
+          set_mark_bit = false;
+          first_chunk_size += kPageSize - remaining_chunk_size;
+          remaining_chunk_size += obj_size;
+          // We should not store first-object and remaining_chunk_size if there were
+          // unused bytes before this TLAB, in which case we must have already
+          // stored the values (below).
+          if (black_alloc_pages_first_chunk_size_[black_page_idx] == 0) {
+            black_alloc_pages_first_chunk_size_[black_page_idx] = first_chunk_size;
+            first_objs_moving_space_[black_page_idx].Assign(first_obj);
+          }
+          black_page_idx++;
+          remaining_chunk_size -= kPageSize;
+          // Consume an object larger than page size.
+          while (remaining_chunk_size >= kPageSize) {
+            black_alloc_pages_first_chunk_size_[black_page_idx] = kPageSize;
+            first_objs_moving_space_[black_page_idx].Assign(obj);
+            black_page_idx++;
+            remaining_chunk_size -= kPageSize;
+          }
+          first_obj = remaining_chunk_size > 0 ? obj : nullptr;
+          first_chunk_size = remaining_chunk_size;
+        } else {
+          DCHECK_LE(first_chunk_size, remaining_chunk_size);
+          first_chunk_size += obj_size;
+          remaining_chunk_size += obj_size;
+        }
+        black_allocs += obj_size;
+        obj = reinterpret_cast<mirror::Object*>(black_allocs);
+      }
+      DCHECK_LE(black_allocs, block_end);
+      DCHECK_LT(remaining_chunk_size, kPageSize);
+      // consume the unallocated portion of the block
+      if (black_allocs < block_end) {
+        // first-chunk of the current page ends here. Store it.
+        if (first_chunk_size > 0 && black_alloc_pages_first_chunk_size_[black_page_idx] == 0) {
+          black_alloc_pages_first_chunk_size_[black_page_idx] = first_chunk_size;
+          first_objs_moving_space_[black_page_idx].Assign(first_obj);
+        }
+        first_chunk_size = 0;
+        first_obj = nullptr;
+        size_t page_remaining = kPageSize - remaining_chunk_size;
+        size_t block_remaining = block_end - black_allocs;
+        if (page_remaining <= block_remaining) {
+          block_remaining -= page_remaining;
+          // current page and the subsequent empty pages in the block
+          black_page_idx += 1 + block_remaining / kPageSize;
+          remaining_chunk_size = block_remaining % kPageSize;
+        } else {
+          remaining_chunk_size += block_remaining;
+        }
+        black_allocs = block_end;
+      }
+    }
+    if (black_page_idx < bump_pointer_space_->Size() / kPageSize) {
+      // Store the leftover first-chunk, if any, and update page index.
+      if (black_alloc_pages_first_chunk_size_[black_page_idx] > 0) {
+        black_page_idx++;
+      } else if (first_chunk_size > 0) {
+        black_alloc_pages_first_chunk_size_[black_page_idx] = first_chunk_size;
+        first_objs_moving_space_[black_page_idx].Assign(first_obj);
+        black_page_idx++;
+      }
+    }
+    black_page_count_ = black_page_idx - moving_first_objs_count_;
+    delete block_sizes;
+  }
+  // Update bump-pointer space by consuming all the pre-black blocks into the
+  // main one.
+  bump_pointer_space_->SetBlockSizes(thread_running_gc_,
+                                     post_compact_end_ - begin,
+                                     consumed_blocks_count);
+}
+
+void MarkCompact::UpdateNonMovingSpaceBlackAllocations() {
+  accounting::ObjectStack* stack = heap_->GetAllocationStack();
+  const StackReference<mirror::Object>* limit = stack->End();
+  uint8_t* const space_begin = non_moving_space_->Begin();
+  for (StackReference<mirror::Object>* it = stack->Begin(); it != limit; ++it) {
+    mirror::Object* obj = it->AsMirrorPtr();
+    if (obj != nullptr && non_moving_space_bitmap_->HasAddress(obj)) {
+      non_moving_space_bitmap_->Set(obj);
+      // Clear so that we don't try to set the bit again in the next GC-cycle.
+      it->Clear();
+      size_t idx = (reinterpret_cast<uint8_t*>(obj) - space_begin) / kPageSize;
+      uint8_t* page_begin = AlignDown(reinterpret_cast<uint8_t*>(obj), kPageSize);
+      mirror::Object* first_obj = first_objs_non_moving_space_[idx].AsMirrorPtr();
+      if (first_obj == nullptr
+          || (obj < first_obj && reinterpret_cast<uint8_t*>(first_obj) > page_begin)) {
+        first_objs_non_moving_space_[idx].Assign(obj);
+      }
+      mirror::Object* next_page_first_obj = first_objs_non_moving_space_[++idx].AsMirrorPtr();
+      uint8_t* next_page_begin = page_begin + kPageSize;
+      if (next_page_first_obj == nullptr
+          || reinterpret_cast<uint8_t*>(next_page_first_obj) > next_page_begin) {
+        size_t obj_size = RoundUp(obj->SizeOf<kDefaultVerifyFlags>(), kAlignment);
+        uint8_t* obj_end = reinterpret_cast<uint8_t*>(obj) + obj_size;
+        while (next_page_begin < obj_end) {
+          first_objs_non_moving_space_[idx++].Assign(obj);
+          next_page_begin += kPageSize;
+        }
+      }
+      // update first_objs count in case we went past non_moving_first_objs_count_
+      non_moving_first_objs_count_ = std::max(non_moving_first_objs_count_, idx);
+    }
+  }
+}
+
+class MarkCompact::ImmuneSpaceUpdateObjVisitor {
+ public:
+  ImmuneSpaceUpdateObjVisitor(MarkCompact* collector, bool visit_native_roots)
+      : collector_(collector), visit_native_roots_(visit_native_roots) {}
+
+  ALWAYS_INLINE void operator()(mirror::Object* obj) const REQUIRES(Locks::mutator_lock_) {
+    RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/false> visitor(collector_,
+                                                                        obj,
+                                                                        /*begin_*/nullptr,
+                                                                        /*end_*/nullptr);
+    if (visit_native_roots_) {
+      obj->VisitRefsForCompaction</*kFetchObjSize*/ false, /*kVisitNativeRoots*/ true>(
+          visitor, MemberOffset(0), MemberOffset(-1));
+    } else {
+      obj->VisitRefsForCompaction</*kFetchObjSize*/ false>(
+          visitor, MemberOffset(0), MemberOffset(-1));
+    }
+  }
+
+  static void Callback(mirror::Object* obj, void* arg) REQUIRES(Locks::mutator_lock_) {
+    reinterpret_cast<ImmuneSpaceUpdateObjVisitor*>(arg)->operator()(obj);
+  }
+
+ private:
+  MarkCompact* const collector_;
+  const bool visit_native_roots_;
+};
+
+class MarkCompact::ClassLoaderRootsUpdater : public ClassLoaderVisitor {
+ public:
+  explicit ClassLoaderRootsUpdater(MarkCompact* collector) : collector_(collector) {}
+
+  void Visit(ObjPtr<mirror::ClassLoader> class_loader) override
+      REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_) {
+    ClassTable* const class_table = class_loader->GetClassTable();
+    if (class_table != nullptr) {
+      class_table->VisitRoots(*this);
+    }
+  }
+
+  void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+      REQUIRES(Locks::heap_bitmap_lock_) REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (!root->IsNull()) {
+      VisitRoot(root);
+    }
+  }
+
+  void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+      REQUIRES(Locks::heap_bitmap_lock_) REQUIRES_SHARED(Locks::mutator_lock_) {
+    collector_->VisitRoots(&root, 1, RootInfo(RootType::kRootVMInternal));
+  }
+
+ private:
+  MarkCompact* collector_;
+};
+
+class MarkCompact::LinearAllocPageUpdater {
+ public:
+  explicit LinearAllocPageUpdater(MarkCompact* collector) : collector_(collector) {}
+
+  void operator()(uint8_t* page_begin, uint8_t* first_obj) ALWAYS_INLINE
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK_ALIGNED(page_begin, kPageSize);
+    uint8_t* page_end = page_begin + kPageSize;
+    uint32_t obj_size;
+    for (uint8_t* byte = first_obj; byte < page_end;) {
+      TrackingHeader* header = reinterpret_cast<TrackingHeader*>(byte);
+      obj_size = header->GetSize();
+      if (UNLIKELY(obj_size == 0)) {
+        // No more objects in this page to visit.
+        last_page_touched_ = byte >= page_begin;
+        return;
+      }
+      uint8_t* obj = byte + sizeof(TrackingHeader);
+      uint8_t* obj_end = byte + obj_size;
+      if (header->Is16Aligned()) {
+        obj = AlignUp(obj, 16);
+      }
+      uint8_t* begin_boundary = std::max(obj, page_begin);
+      uint8_t* end_boundary = std::min(obj_end, page_end);
+      if (begin_boundary < end_boundary) {
+        VisitObject(header->GetKind(), obj, begin_boundary, end_boundary);
+      }
+      if (ArenaAllocator::IsRunningOnMemoryTool()) {
+        obj_size += ArenaAllocator::kMemoryToolRedZoneBytes;
+      }
+      byte += RoundUp(obj_size, LinearAlloc::kAlignment);
+    }
+    last_page_touched_ = true;
+  }
+
+  bool WasLastPageTouched() const { return last_page_touched_; }
+
+  void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+      ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (!root->IsNull()) {
+      VisitRoot(root);
+    }
+  }
+
+  void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+      ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
+    mirror::Object* old_ref = root->AsMirrorPtr();
+    DCHECK_NE(old_ref, nullptr);
+    if (collector_->live_words_bitmap_->HasAddress(old_ref)) {
+      mirror::Object* new_ref = old_ref;
+      if (reinterpret_cast<uint8_t*>(old_ref) >= collector_->black_allocations_begin_) {
+        new_ref = collector_->PostCompactBlackObjAddr(old_ref);
+      } else if (collector_->live_words_bitmap_->Test(old_ref)) {
+        DCHECK(collector_->moving_space_bitmap_->Test(old_ref)) << old_ref;
+        new_ref = collector_->PostCompactOldObjAddr(old_ref);
+      }
+      if (old_ref != new_ref) {
+        root->Assign(new_ref);
+      }
+    }
+  }
+
+ private:
+  void VisitObject(LinearAllocKind kind,
+                   void* obj,
+                   uint8_t* start_boundary,
+                   uint8_t* end_boundary) const REQUIRES_SHARED(Locks::mutator_lock_) {
+    switch (kind) {
+      case LinearAllocKind::kNoGCRoots:
+        break;
+      case LinearAllocKind::kGCRootArray:
+        {
+          GcRoot<mirror::Object>* root = reinterpret_cast<GcRoot<mirror::Object>*>(start_boundary);
+          GcRoot<mirror::Object>* last = reinterpret_cast<GcRoot<mirror::Object>*>(end_boundary);
+          for (; root < last; root++) {
+            VisitRootIfNonNull(root->AddressWithoutBarrier());
+          }
+        }
+        break;
+      case LinearAllocKind::kArtMethodArray:
+        {
+          LengthPrefixedArray<ArtMethod>* array = static_cast<LengthPrefixedArray<ArtMethod>*>(obj);
+          // Old methods are clobbered in debug builds. Check size to confirm if the array
+          // has any GC roots to visit. See ClassLinker::LinkMethodsHelper::ClobberOldMethods()
+          if (array->size() > 0) {
+            if (collector_->pointer_size_ == PointerSize::k64) {
+              ArtMethod::VisitArrayRoots<PointerSize::k64>(
+                  *this, start_boundary, end_boundary, array);
+            } else {
+              DCHECK_EQ(collector_->pointer_size_, PointerSize::k32);
+              ArtMethod::VisitArrayRoots<PointerSize::k32>(
+                  *this, start_boundary, end_boundary, array);
+            }
+          }
+        }
+        break;
+      case LinearAllocKind::kArtMethod:
+        ArtMethod::VisitRoots(*this, start_boundary, end_boundary, static_cast<ArtMethod*>(obj));
+        break;
+      case LinearAllocKind::kArtFieldArray:
+        ArtField::VisitArrayRoots(*this,
+                                  start_boundary,
+                                  end_boundary,
+                                  static_cast<LengthPrefixedArray<ArtField>*>(obj));
+        break;
+      case LinearAllocKind::kDexCacheArray:
+        {
+          mirror::DexCachePair<mirror::Object>* first =
+              reinterpret_cast<mirror::DexCachePair<mirror::Object>*>(start_boundary);
+          mirror::DexCachePair<mirror::Object>* last =
+              reinterpret_cast<mirror::DexCachePair<mirror::Object>*>(end_boundary);
+          mirror::DexCache::VisitDexCachePairRoots(*this, first, last);
+      }
+    }
+  }
+
+  MarkCompact* const collector_;
+  // Whether the last page was touched or not.
+  bool last_page_touched_;
+};
+
+void MarkCompact::CompactionPause() {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  Runtime* runtime = Runtime::Current();
+  non_moving_space_bitmap_ = non_moving_space_->GetLiveBitmap();
+  if (kIsDebugBuild) {
+    DCHECK_EQ(thread_running_gc_, Thread::Current());
+    stack_low_addr_ = thread_running_gc_->GetStackEnd();
+    stack_high_addr_ =
+        reinterpret_cast<char*>(stack_low_addr_) + thread_running_gc_->GetStackSize();
+  }
+  {
+    TimingLogger::ScopedTiming t2("(Paused)UpdateCompactionDataStructures", GetTimings());
+    ReaderMutexLock rmu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+    // Refresh data-structures to catch-up on allocations that may have
+    // happened since marking-phase pause.
+    // There could be several TLABs that got allocated since marking pause. We
+    // don't want to compact them and instead update the TLAB info in TLS and
+    // let mutators continue to use the TLABs.
+    // We need to set all the bits in live-words bitmap corresponding to allocated
+    // objects. Also, we need to find the objects that are overlapping with
+    // page-begin boundaries. Unlike objects allocated before
+    // black_allocations_begin_, which can be identified via mark-bitmap, we can get
+    // this info only via walking the space past black_allocations_begin_, which
+    // involves fetching object size.
+    // TODO: We can reduce the time spent on this in a pause by performing one
+    // round of this concurrently prior to the pause.
+    UpdateMovingSpaceBlackAllocations();
+    // TODO: If we want to avoid this allocation in a pause then we will have to
+    // allocate an array for the entire moving-space size, which can be made
+    // part of info_map_.
+    moving_pages_status_ = new Atomic<PageState>[moving_first_objs_count_ + black_page_count_];
+    if (kIsDebugBuild) {
+      size_t len = moving_first_objs_count_ + black_page_count_;
+      for (size_t i = 0; i < len; i++) {
+          CHECK_EQ(moving_pages_status_[i].load(std::memory_order_relaxed),
+                   PageState::kUnprocessed);
+      }
+    }
+    // Iterate over the allocation_stack_, for every object in the non-moving
+    // space:
+    // 1. Mark the object in live bitmap
+    // 2. Erase the object from allocation stack
+    // 3. In the corresponding page, if the first-object vector needs updating
+    // then do so.
+    UpdateNonMovingSpaceBlackAllocations();
+
+    // This store is visible to mutator (or uffd worker threads) as the mutator
+    // lock's unlock guarantees that.
+    compacting_ = true;
+    // Start updating roots and system weaks now.
+    heap_->GetReferenceProcessor()->UpdateRoots(this);
+  }
+  {
+    TimingLogger::ScopedTiming t2("(Paused)UpdateClassLoaderRoots", GetTimings());
+    ReaderMutexLock rmu(thread_running_gc_, *Locks::classlinker_classes_lock_);
+    {
+      ClassLoaderRootsUpdater updater(this);
+      runtime->GetClassLinker()->VisitClassLoaders(&updater);
+    }
+  }
+
+  bool has_zygote_space = heap_->HasZygoteSpace();
+  // TODO: Find out why it's not sufficient to visit native roots of immune
+  // spaces, and why all the pre-zygote fork arenas have to be linearly updated.
+  // Is it possible that some native root starts getting pointed to by some object
+  // in moving space after fork? Or are we missing a write-barrier somewhere
+  // when a native root is updated?
+  GcVisitedArenaPool* arena_pool =
+      static_cast<GcVisitedArenaPool*>(runtime->GetLinearAllocArenaPool());
+  if (uffd_ == kFallbackMode || (!has_zygote_space && runtime->IsZygote())) {
+    // Besides fallback-mode, visit linear-alloc space in the pause for zygote
+    // processes prior to first fork (that's when zygote space gets created).
+    if (kIsDebugBuild && IsValidFd(uffd_)) {
+      // All arenas allocated so far are expected to be pre-zygote fork.
+      arena_pool->ForEachAllocatedArena(
+          [](const TrackedArena& arena)
+              REQUIRES_SHARED(Locks::mutator_lock_) { CHECK(arena.IsPreZygoteForkArena()); });
+    }
+    LinearAllocPageUpdater updater(this);
+    arena_pool->VisitRoots(updater);
+  } else {
+    // Clear the flag as we care about this only if arenas are freed during
+    // concurrent compaction.
+    arena_pool->ClearArenasFreed();
+    arena_pool->ForEachAllocatedArena(
+        [this](const TrackedArena& arena) REQUIRES_SHARED(Locks::mutator_lock_) {
+          // The pre-zygote fork arenas are not visited concurrently in the
+          // zygote children processes. The native roots of the dirty objects
+          // are visited during immune space visit below.
+          if (!arena.IsPreZygoteForkArena()) {
+            uint8_t* last_byte = arena.GetLastUsedByte();
+            CHECK(linear_alloc_arenas_.insert({&arena, last_byte}).second);
+          } else {
+            LinearAllocPageUpdater updater(this);
+            arena.VisitRoots(updater);
+          }
+        });
+  }
+
+  SweepSystemWeaks(thread_running_gc_, runtime, /*paused*/ true);
+
+  {
+    TimingLogger::ScopedTiming t2("(Paused)UpdateConcurrentRoots", GetTimings());
+    runtime->VisitConcurrentRoots(this, kVisitRootFlagAllRoots);
+  }
+  {
+    // TODO: don't visit the transaction roots if it's not active.
+    TimingLogger::ScopedTiming t2("(Paused)UpdateNonThreadRoots", GetTimings());
+    runtime->VisitNonThreadRoots(this);
+  }
+
+  {
+    // TODO: Immune space updation has to happen either before or after
+    // remapping pre-compact pages to from-space. And depending on when it's
+    // done, we have to invoke VisitRefsForCompaction() with or without
+    // read-barrier.
+    TimingLogger::ScopedTiming t2("(Paused)UpdateImmuneSpaces", GetTimings());
+    accounting::CardTable* const card_table = heap_->GetCardTable();
+    for (auto& space : immune_spaces_.GetSpaces()) {
+      DCHECK(space->IsImageSpace() || space->IsZygoteSpace());
+      accounting::ContinuousSpaceBitmap* live_bitmap = space->GetLiveBitmap();
+      accounting::ModUnionTable* table = heap_->FindModUnionTableFromSpace(space);
+      // Having zygote-space indicates that the first zygote fork has taken
+      // place and that the classes/dex-caches in immune-spaces may have allocations
+      // (ArtMethod/ArtField arrays, dex-cache array, etc.) in the
+      // non-userfaultfd visited private-anonymous mappings. Visit them here.
+      ImmuneSpaceUpdateObjVisitor visitor(this, /*visit_native_roots=*/false);
+      if (table != nullptr) {
+        table->ProcessCards();
+        table->VisitObjects(ImmuneSpaceUpdateObjVisitor::Callback, &visitor);
+      } else {
+        WriterMutexLock wmu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+        card_table->Scan<false>(
+            live_bitmap,
+            space->Begin(),
+            space->Limit(),
+            visitor,
+            accounting::CardTable::kCardDirty - 1);
+      }
+    }
+  }
+
+  if (use_uffd_sigbus_) {
+    // Release order wrt to mutator threads' SIGBUS handler load.
+    sigbus_in_progress_count_.store(0, std::memory_order_release);
+  }
+  KernelPreparation();
+  UpdateNonMovingSpace();
+  // fallback mode
+  if (uffd_ == kFallbackMode) {
+    CompactMovingSpace<kFallbackMode>(nullptr);
+
+    int32_t freed_bytes = black_objs_slide_diff_;
+    bump_pointer_space_->RecordFree(freed_objects_, freed_bytes);
+    RecordFree(ObjectBytePair(freed_objects_, freed_bytes));
+  } else {
+    DCHECK_EQ(compaction_in_progress_count_.load(std::memory_order_relaxed), 0u);
+    DCHECK_EQ(compaction_buffer_counter_.load(std::memory_order_relaxed), 1);
+    if (!use_uffd_sigbus_) {
+      // We must start worker threads before resuming mutators to avoid deadlocks.
+      heap_->GetThreadPool()->StartWorkers(thread_running_gc_);
+    }
+  }
+  stack_low_addr_ = nullptr;
+}
+
+void MarkCompact::KernelPrepareRangeForUffd(uint8_t* to_addr,
+                                            uint8_t* from_addr,
+                                            size_t map_size,
+                                            int fd,
+                                            uint8_t* shadow_addr) {
+  int mremap_flags = MREMAP_MAYMOVE | MREMAP_FIXED;
+  if (gHaveMremapDontunmap) {
+    mremap_flags |= MREMAP_DONTUNMAP;
+  }
+
+  void* ret = mremap(to_addr, map_size, map_size, mremap_flags, from_addr);
+  CHECK_EQ(ret, static_cast<void*>(from_addr))
+      << "mremap to move pages failed: " << strerror(errno)
+      << ". space-addr=" << reinterpret_cast<void*>(to_addr) << " size=" << PrettySize(map_size);
+
+  if (shadow_addr != nullptr) {
+    DCHECK_EQ(fd, kFdUnused);
+    DCHECK(gHaveMremapDontunmap);
+    ret = mremap(shadow_addr, map_size, map_size, mremap_flags, to_addr);
+    CHECK_EQ(ret, static_cast<void*>(to_addr))
+        << "mremap from shadow to to-space map failed: " << strerror(errno);
+  } else if (!gHaveMremapDontunmap || fd > kFdUnused) {
+    // Without MREMAP_DONTUNMAP the source mapping is unmapped by mremap. So mmap
+    // the moving space again.
+    int mmap_flags = MAP_FIXED;
+    if (fd == kFdUnused) {
+      // Use MAP_FIXED_NOREPLACE so that if someone else reserves 'to_addr'
+      // mapping in meantime, which can happen when MREMAP_DONTUNMAP isn't
+      // available, to avoid unmapping someone else' mapping and then causing
+      // crashes elsewhere.
+      mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE;
+      // On some platforms MAP_ANONYMOUS expects fd to be -1.
+      fd = -1;
+    } else if (IsValidFd(fd)) {
+      mmap_flags |= MAP_SHARED;
+    } else {
+      DCHECK_EQ(fd, kFdSharedAnon);
+      mmap_flags |= MAP_SHARED | MAP_ANONYMOUS;
+    }
+    ret = mmap(to_addr, map_size, PROT_READ | PROT_WRITE, mmap_flags, fd, 0);
+    CHECK_EQ(ret, static_cast<void*>(to_addr))
+        << "mmap for moving space failed: " << strerror(errno);
+  }
+}
+
+void MarkCompact::KernelPreparation() {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  uint8_t* moving_space_begin = bump_pointer_space_->Begin();
+  size_t moving_space_size = bump_pointer_space_->Capacity();
+  int mode = kCopyMode;
+  size_t moving_space_register_sz;
+  if (minor_fault_initialized_) {
+    moving_space_register_sz = (moving_first_objs_count_ + black_page_count_) * kPageSize;
+    if (shadow_to_space_map_.IsValid()) {
+      size_t shadow_size = shadow_to_space_map_.Size();
+      void* addr = shadow_to_space_map_.Begin();
+      if (shadow_size < moving_space_register_sz) {
+        addr = mremap(addr,
+                      shadow_size,
+                      moving_space_register_sz,
+                      // Don't allow moving with obj-ptr poisoning as the
+                      // mapping needs to be in <4GB address space.
+                      kObjPtrPoisoning ? 0 : MREMAP_MAYMOVE,
+                      /*new_address=*/nullptr);
+        if (addr != MAP_FAILED) {
+          // Succeeded in expanding the mapping. Update the MemMap entry for shadow map.
+          MemMap temp = MemMap::MapPlaceholder(
+              "moving-space-shadow", static_cast<uint8_t*>(addr), moving_space_register_sz);
+          std::swap(shadow_to_space_map_, temp);
+        }
+      }
+      if (addr != MAP_FAILED) {
+        mode = kMinorFaultMode;
+      } else {
+        // We are not going to use shadow map. So protect it to catch any
+        // potential bugs.
+        DCHECK_EQ(mprotect(shadow_to_space_map_.Begin(), shadow_to_space_map_.Size(), PROT_NONE), 0)
+            << "mprotect failed: " << strerror(errno);
+      }
+    }
+  } else {
+    moving_space_register_sz = moving_space_size;
+  }
+
+  bool map_shared =
+      minor_fault_initialized_ || (!Runtime::Current()->IsZygote() && uffd_minor_fault_supported_);
+  uint8_t* shadow_addr = nullptr;
+  if (moving_to_space_fd_ == kFdUnused && map_shared) {
+    DCHECK(gHaveMremapDontunmap);
+    DCHECK(shadow_to_space_map_.IsValid());
+    DCHECK_EQ(shadow_to_space_map_.Size(), moving_space_size);
+    shadow_addr = shadow_to_space_map_.Begin();
+  }
+
+  KernelPrepareRangeForUffd(moving_space_begin,
+                            from_space_begin_,
+                            moving_space_size,
+                            moving_to_space_fd_,
+                            shadow_addr);
+
+  if (IsValidFd(uffd_)) {
+    // Register the moving space with userfaultfd.
+    RegisterUffd(moving_space_begin, moving_space_register_sz, mode);
+    // Prepare linear-alloc for concurrent compaction.
+    for (auto& data : linear_alloc_spaces_data_) {
+      bool mmap_again = map_shared && !data.already_shared_;
+      DCHECK_EQ(static_cast<ssize_t>(data.shadow_.Size()), data.end_ - data.begin_);
+      // There could be threads running in suspended mode when the compaction
+      // pause is being executed. In order to make the userfaultfd setup atomic,
+      // the registration has to be done *before* moving the pages to shadow map.
+      if (!mmap_again) {
+        // See the comment in the constructor as to why it's conditionally done.
+        RegisterUffd(data.begin_,
+                     data.shadow_.Size(),
+                     minor_fault_initialized_ ? kMinorFaultMode : kCopyMode);
+      }
+      KernelPrepareRangeForUffd(data.begin_,
+                                data.shadow_.Begin(),
+                                data.shadow_.Size(),
+                                mmap_again ? kFdSharedAnon : kFdUnused);
+      if (mmap_again) {
+        data.already_shared_ = true;
+        RegisterUffd(data.begin_,
+                     data.shadow_.Size(),
+                     minor_fault_initialized_ ? kMinorFaultMode : kCopyMode);
+      }
+    }
+  }
+  if (map_shared) {
+    // Start mapping linear-alloc MAP_SHARED only after the compaction pause of
+    // the first GC in non-zygote processes. This is the GC which sets up
+    // mappings for using minor-fault in future. Up to this point we run
+    // userfaultfd in copy-mode, which requires the mappings (of linear-alloc)
+    // to be MAP_PRIVATE.
+    map_linear_alloc_shared_ = true;
+  }
+}
+
+template <int kMode>
+void MarkCompact::ConcurrentCompaction(uint8_t* buf) {
+  DCHECK_NE(kMode, kFallbackMode);
+  DCHECK(kMode != kCopyMode || buf != nullptr);
+  size_t nr_moving_space_used_pages = moving_first_objs_count_ + black_page_count_;
+  while (true) {
+    struct uffd_msg msg;
+    ssize_t nread = read(uffd_, &msg, sizeof(msg));
+    CHECK_GT(nread, 0);
+    CHECK_EQ(msg.event, UFFD_EVENT_PAGEFAULT);
+    DCHECK_EQ(nread, static_cast<ssize_t>(sizeof(msg)));
+    uint8_t* fault_addr = reinterpret_cast<uint8_t*>(msg.arg.pagefault.address);
+    if (fault_addr == conc_compaction_termination_page_) {
+      // The counter doesn't need to be updated atomically as only one thread
+      // would wake up against the gc-thread's load to this fault_addr. In fact,
+      // the other threads would wake up serially because every exiting thread
+      // will wake up gc-thread, which would retry load but again would find the
+      // page missing. Also, the value will be flushed to caches due to the ioctl
+      // syscall below.
+      uint8_t ret = thread_pool_counter_--;
+      // If 'gKernelHasFaultRetry == true' then only the last thread should map the
+      // zeropage so that the gc-thread can proceed. Otherwise, each thread does
+      // it and the gc-thread will repeat this fault until thread_pool_counter == 0.
+      if (!gKernelHasFaultRetry || ret == 1) {
+        ZeropageIoctl(fault_addr, /*tolerate_eexist=*/false, /*tolerate_enoent=*/false);
+      } else {
+        struct uffdio_range uffd_range;
+        uffd_range.start = msg.arg.pagefault.address;
+        uffd_range.len = kPageSize;
+        CHECK_EQ(ioctl(uffd_, UFFDIO_WAKE, &uffd_range), 0)
+            << "ioctl_userfaultfd: wake failed for concurrent-compaction termination page: "
+            << strerror(errno);
+      }
+      break;
+    }
+    uint8_t* fault_page = AlignDown(fault_addr, kPageSize);
+    if (bump_pointer_space_->HasAddress(reinterpret_cast<mirror::Object*>(fault_addr))) {
+      ConcurrentlyProcessMovingPage<kMode>(fault_page, buf, nr_moving_space_used_pages);
+    } else if (minor_fault_initialized_) {
+      ConcurrentlyProcessLinearAllocPage<kMinorFaultMode>(
+          fault_page, (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_MINOR) != 0);
+    } else {
+      ConcurrentlyProcessLinearAllocPage<kCopyMode>(
+          fault_page, (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_MINOR) != 0);
+    }
+  }
+}
+
+bool MarkCompact::SigbusHandler(siginfo_t* info) {
+  class ScopedInProgressCount {
+   public:
+    explicit ScopedInProgressCount(MarkCompact* collector) : collector_(collector) {
+      // Increment the count only if compaction is not done yet.
+      SigbusCounterType prev =
+          collector_->sigbus_in_progress_count_.load(std::memory_order_relaxed);
+      while ((prev & kSigbusCounterCompactionDoneMask) == 0) {
+        if (collector_->sigbus_in_progress_count_.compare_exchange_strong(
+                prev, prev + 1, std::memory_order_acquire)) {
+          DCHECK_LT(prev, kSigbusCounterCompactionDoneMask - 1);
+          compaction_done_ = false;
+          return;
+        }
+      }
+      compaction_done_ = true;
+    }
+
+    bool IsCompactionDone() const {
+      return compaction_done_;
+    }
+
+    ~ScopedInProgressCount() {
+      if (!IsCompactionDone()) {
+        collector_->sigbus_in_progress_count_.fetch_sub(1, std::memory_order_release);
+      }
+    }
+
+   private:
+    MarkCompact* const collector_;
+    bool compaction_done_;
+  };
+
+  DCHECK(use_uffd_sigbus_);
+  if (info->si_code != BUS_ADRERR) {
+    // Userfaultfd raises SIGBUS with BUS_ADRERR. All other causes can't be
+    // handled here.
+    return false;
+  }
+
+  ScopedInProgressCount spc(this);
+  uint8_t* fault_page = AlignDown(reinterpret_cast<uint8_t*>(info->si_addr), kPageSize);
+  if (!spc.IsCompactionDone()) {
+    if (bump_pointer_space_->HasAddress(reinterpret_cast<mirror::Object*>(fault_page))) {
+      Thread* self = Thread::Current();
+      Locks::mutator_lock_->AssertSharedHeld(self);
+      size_t nr_moving_space_used_pages = moving_first_objs_count_ + black_page_count_;
+      if (minor_fault_initialized_) {
+        ConcurrentlyProcessMovingPage<kMinorFaultMode>(
+            fault_page, nullptr, nr_moving_space_used_pages);
+      } else {
+        ConcurrentlyProcessMovingPage<kCopyMode>(
+            fault_page, self->GetThreadLocalGcBuffer(), nr_moving_space_used_pages);
+      }
+      return true;
+    } else {
+      // Find the linear-alloc space containing fault-addr
+      for (auto& data : linear_alloc_spaces_data_) {
+        if (data.begin_ <= fault_page && data.end_ > fault_page) {
+          if (minor_fault_initialized_) {
+            ConcurrentlyProcessLinearAllocPage<kMinorFaultMode>(fault_page, false);
+          } else {
+            ConcurrentlyProcessLinearAllocPage<kCopyMode>(fault_page, false);
+          }
+          return true;
+        }
+      }
+      // Fault address doesn't belong to either moving-space or linear-alloc.
+      return false;
+    }
+  } else {
+    // We may spuriously get SIGBUS fault, which was initiated before the
+    // compaction was finished, but ends up here. In that case, if the fault
+    // address is valid then consider it handled.
+    return bump_pointer_space_->HasAddress(reinterpret_cast<mirror::Object*>(fault_page)) ||
+           linear_alloc_spaces_data_.end() !=
+               std::find_if(linear_alloc_spaces_data_.begin(),
+                            linear_alloc_spaces_data_.end(),
+                            [fault_page](const LinearAllocSpaceData& data) {
+                              return data.begin_ <= fault_page && data.end_ > fault_page;
+                            });
+  }
+}
+
+static void BackOff(uint32_t i) {
+  static constexpr uint32_t kYieldMax = 5;
+  // TODO: Consider adding x86 PAUSE and/or ARM YIELD here.
+  if (i <= kYieldMax) {
+    sched_yield();
+  } else {
+    // nanosleep is not in the async-signal-safe list, but bionic implements it
+    // with a pure system call, so it should be fine.
+    NanoSleep(10000ull * (i - kYieldMax));
+  }
+}
+
+template <int kMode>
+void MarkCompact::ConcurrentlyProcessMovingPage(uint8_t* fault_page,
+                                                uint8_t* buf,
+                                                size_t nr_moving_space_used_pages) {
+  class ScopedInProgressCount {
+   public:
+    explicit ScopedInProgressCount(MarkCompact* collector) : collector_(collector) {
+      collector_->compaction_in_progress_count_.fetch_add(1, std::memory_order_relaxed);
+    }
+
+    ~ScopedInProgressCount() {
+      collector_->compaction_in_progress_count_.fetch_sub(1, std::memory_order_relaxed);
+    }
+
+   private:
+    MarkCompact* collector_;
+  };
+
+  uint8_t* unused_space_begin =
+      bump_pointer_space_->Begin() + nr_moving_space_used_pages * kPageSize;
+  DCHECK(IsAligned<kPageSize>(unused_space_begin));
+  DCHECK(kMode == kCopyMode || fault_page < unused_space_begin);
+  if (kMode == kCopyMode && fault_page >= unused_space_begin) {
+    // There is a race which allows more than one thread to install a
+    // zero-page. But we can tolerate that. So absorb the EEXIST returned by
+    // the ioctl and move on.
+    ZeropageIoctl(fault_page, /*tolerate_eexist=*/true, /*tolerate_enoent=*/true);
+    return;
+  }
+  size_t page_idx = (fault_page - bump_pointer_space_->Begin()) / kPageSize;
+  mirror::Object* first_obj = first_objs_moving_space_[page_idx].AsMirrorPtr();
+  if (first_obj == nullptr) {
+    // We should never have a case where two workers are trying to install a
+    // zeropage in this range as we synchronize using moving_pages_status_[page_idx].
+    PageState expected_state = PageState::kUnprocessed;
+    if (moving_pages_status_[page_idx].compare_exchange_strong(
+            expected_state, PageState::kProcessedAndMapping, std::memory_order_relaxed)) {
+      // Note: ioctl acts as an acquire fence.
+      ZeropageIoctl(fault_page, /*tolerate_eexist=*/false, /*tolerate_enoent=*/true);
+    } else {
+      DCHECK_EQ(expected_state, PageState::kProcessedAndMapping);
+    }
+    return;
+  }
+
+  PageState state = moving_pages_status_[page_idx].load(
+      use_uffd_sigbus_ ? std::memory_order_acquire : std::memory_order_relaxed);
+  uint32_t backoff_count = 0;
+  while (true) {
+    switch (state) {
+      case PageState::kUnprocessed: {
+        // The increment to the in-progress counter must be done before updating
+        // the page's state. Otherwise, we will end up leaving a window wherein
+        // the GC-thread could observe that no worker is working on compaction
+        // and could end up unregistering the moving space from userfaultfd.
+        ScopedInProgressCount spc(this);
+        // Acquire order to ensure we don't start writing to shadow map, which is
+        // shared, before the CAS is successful. Release order to ensure that the
+        // increment to moving_compactions_in_progress above is not re-ordered
+        // after the CAS.
+        if (moving_pages_status_[page_idx].compare_exchange_strong(
+                state, PageState::kMutatorProcessing, std::memory_order_acq_rel)) {
+          if (kMode == kMinorFaultMode) {
+            DCHECK_EQ(buf, nullptr);
+            buf = shadow_to_space_map_.Begin() + page_idx * kPageSize;
+          } else if (UNLIKELY(buf == nullptr)) {
+            DCHECK_EQ(kMode, kCopyMode);
+            uint16_t idx = compaction_buffer_counter_.fetch_add(1, std::memory_order_relaxed);
+            // The buffer-map is one page bigger as the first buffer is used by GC-thread.
+            CHECK_LE(idx, kMutatorCompactionBufferCount);
+            buf = compaction_buffers_map_.Begin() + idx * kPageSize;
+            DCHECK(compaction_buffers_map_.HasAddress(buf));
+            Thread::Current()->SetThreadLocalGcBuffer(buf);
+          }
+
+          if (fault_page < post_compact_end_) {
+            // The page has to be compacted.
+            CompactPage(
+                first_obj, pre_compact_offset_moving_space_[page_idx], buf, kMode == kCopyMode);
+          } else {
+            DCHECK_NE(first_obj, nullptr);
+            DCHECK_GT(pre_compact_offset_moving_space_[page_idx], 0u);
+            uint8_t* pre_compact_page = black_allocations_begin_ + (fault_page - post_compact_end_);
+            DCHECK(IsAligned<kPageSize>(pre_compact_page));
+            SlideBlackPage(first_obj, page_idx, pre_compact_page, buf, kMode == kCopyMode);
+          }
+          // Nobody else would simultaneously modify this page's state so an
+          // atomic store is sufficient. Use 'release' order to guarantee that
+          // loads/stores to the page are finished before this store.
+          moving_pages_status_[page_idx].store(PageState::kProcessedAndMapping,
+                                               std::memory_order_release);
+          if (kMode == kCopyMode) {
+            CopyIoctl(fault_page, buf);
+            if (use_uffd_sigbus_) {
+              // Store is sufficient as no other thread modifies the status at this stage.
+              moving_pages_status_[page_idx].store(PageState::kProcessedAndMapped,
+                                                   std::memory_order_release);
+            }
+            return;
+          } else {
+            break;
+          }
+        }
+      }
+        continue;
+      case PageState::kProcessing:
+        DCHECK_EQ(kMode, kMinorFaultMode);
+        if (moving_pages_status_[page_idx].compare_exchange_strong(
+                state, PageState::kProcessingAndMapping, std::memory_order_relaxed) &&
+            !use_uffd_sigbus_) {
+          // Somebody else took or will take care of finishing the compaction and
+          // then mapping the page.
+          return;
+        }
+        continue;
+      case PageState::kProcessed:
+        // The page is processed but not mapped. We should map it.
+        break;
+      case PageState::kProcessingAndMapping:
+      case PageState::kMutatorProcessing:
+      case PageState::kProcessedAndMapping:
+        if (use_uffd_sigbus_) {
+          // Wait for the page to be mapped before returning.
+          BackOff(backoff_count++);
+          state = moving_pages_status_[page_idx].load(std::memory_order_acquire);
+          continue;
+        }
+        return;
+      case PageState::kProcessedAndMapped:
+        // Somebody else took care of the page.
+        return;
+    }
+    break;
+  }
+
+  DCHECK_EQ(kMode, kMinorFaultMode);
+  if (state == PageState::kUnprocessed) {
+    MapProcessedPages</*kFirstPageMapping=*/true>(
+        fault_page, moving_pages_status_, page_idx, nr_moving_space_used_pages);
+  } else {
+    DCHECK_EQ(state, PageState::kProcessed);
+    MapProcessedPages</*kFirstPageMapping=*/false>(
+        fault_page, moving_pages_status_, page_idx, nr_moving_space_used_pages);
+  }
+}
+
+void MarkCompact::MapUpdatedLinearAllocPage(uint8_t* page,
+                                            uint8_t* shadow_page,
+                                            Atomic<PageState>& state,
+                                            bool page_touched) {
+  DCHECK(!minor_fault_initialized_);
+  if (page_touched) {
+    CopyIoctl(page, shadow_page);
+  } else {
+    // If the page wasn't touched, then it means it is empty and
+    // is most likely not present on the shadow-side. Furthermore,
+    // since the shadow is also userfaultfd registered doing copy
+    // ioctl fail as the copy-from-user in the kernel will cause
+    // userfault. Instead, just map a zeropage, which is not only
+    // correct but also efficient as it avoids unnecessary memcpy
+    // in the kernel.
+    ZeropageIoctl(page, /*tolerate_eexist=*/false, /*tolerate_enoent=*/false);
+  }
+  if (use_uffd_sigbus_) {
+    // Store is sufficient as no other thread can modify the
+    // status of this page at this point.
+    state.store(PageState::kProcessedAndMapped, std::memory_order_release);
+  }
+}
+
+template <int kMode>
+void MarkCompact::ConcurrentlyProcessLinearAllocPage(uint8_t* fault_page, bool is_minor_fault) {
+  DCHECK(!is_minor_fault || kMode == kMinorFaultMode);
+  auto arena_iter = linear_alloc_arenas_.end();
+  {
+    TrackedArena temp_arena(fault_page);
+    arena_iter = linear_alloc_arenas_.upper_bound(&temp_arena);
+    arena_iter = arena_iter != linear_alloc_arenas_.begin() ? std::prev(arena_iter)
+                                                            : linear_alloc_arenas_.end();
+  }
+  if (arena_iter == linear_alloc_arenas_.end() || arena_iter->second <= fault_page) {
+    // Fault page isn't in any of the arenas that existed before we started
+    // compaction. So map zeropage and return.
+    ZeropageIoctl(fault_page, /*tolerate_eexist=*/true, /*tolerate_enoent=*/false);
+  } else {
+    // fault_page should always belong to some arena.
+    DCHECK(arena_iter != linear_alloc_arenas_.end())
+        << "fault_page:" << static_cast<void*>(fault_page) << "is_minor_fault:" << is_minor_fault;
+    // Find the linear-alloc space containing fault-page
+    LinearAllocSpaceData* space_data = nullptr;
+    for (auto& data : linear_alloc_spaces_data_) {
+      if (data.begin_ <= fault_page && fault_page < data.end_) {
+        space_data = &data;
+        break;
+      }
+    }
+    DCHECK_NE(space_data, nullptr);
+    ptrdiff_t diff = space_data->shadow_.Begin() - space_data->begin_;
+    size_t page_idx = (fault_page - space_data->begin_) / kPageSize;
+    Atomic<PageState>* state_arr =
+        reinterpret_cast<Atomic<PageState>*>(space_data->page_status_map_.Begin());
+    PageState state = state_arr[page_idx].load(use_uffd_sigbus_ ? std::memory_order_acquire :
+                                                                  std::memory_order_relaxed);
+    uint32_t backoff_count = 0;
+    while (true) {
+      switch (state) {
+        case PageState::kUnprocessed: {
+          // Acquire order to ensure we don't start writing to shadow map, which is
+          // shared, before the CAS is successful.
+          if (state_arr[page_idx].compare_exchange_strong(
+                  state, PageState::kProcessingAndMapping, std::memory_order_acquire)) {
+            if (kMode == kCopyMode || is_minor_fault) {
+              uint8_t* first_obj = arena_iter->first->GetFirstObject(fault_page);
+              DCHECK_NE(first_obj, nullptr);
+              LinearAllocPageUpdater updater(this);
+              updater(fault_page + diff, first_obj + diff);
+              if (kMode == kCopyMode) {
+                MapUpdatedLinearAllocPage(fault_page,
+                                          fault_page + diff,
+                                          state_arr[page_idx],
+                                          updater.WasLastPageTouched());
+                return;
+              }
+            } else {
+              // Don't touch the page in this case (there is no reason to do so
+              // anyways) as it would mean reading from first_obj, which could be on
+              // another missing page and hence may cause this thread to block, leading
+              // to deadlocks.
+              // Force read the page if it is missing so that a zeropage gets mapped on
+              // the shadow map and then CONTINUE ioctl will map it on linear-alloc.
+              ForceRead(fault_page + diff);
+            }
+            MapProcessedPages</*kFirstPageMapping=*/true>(
+                fault_page, state_arr, page_idx, space_data->page_status_map_.Size());
+            return;
+          }
+        }
+          continue;
+        case PageState::kProcessing:
+          DCHECK_EQ(kMode, kMinorFaultMode);
+          if (state_arr[page_idx].compare_exchange_strong(
+                  state, PageState::kProcessingAndMapping, std::memory_order_relaxed) &&
+              !use_uffd_sigbus_) {
+            // Somebody else took or will take care of finishing the updates and
+            // then mapping the page.
+            return;
+          }
+          continue;
+        case PageState::kProcessed:
+          // The page is processed but not mapped. We should map it.
+          break;
+        case PageState::kMutatorProcessing:
+          UNREACHABLE();
+        case PageState::kProcessingAndMapping:
+        case PageState::kProcessedAndMapping:
+          if (use_uffd_sigbus_) {
+            // Wait for the page to be mapped before returning.
+            BackOff(backoff_count++);
+            state = state_arr[page_idx].load(std::memory_order_acquire);
+            continue;
+          }
+          return;
+        case PageState::kProcessedAndMapped:
+          // Somebody else took care of the page.
+          return;
+      }
+      break;
+    }
+
+    DCHECK_EQ(kMode, kMinorFaultMode);
+    DCHECK_EQ(state, PageState::kProcessed);
+    if (!is_minor_fault) {
+      // Force read the page if it is missing so that a zeropage gets mapped on
+      // the shadow map and then CONTINUE ioctl will map it on linear-alloc.
+      ForceRead(fault_page + diff);
+    }
+    MapProcessedPages</*kFirstPageMapping=*/false>(
+        fault_page, state_arr, page_idx, space_data->page_status_map_.Size());
+  }
+}
+
+void MarkCompact::ProcessLinearAlloc() {
+  GcVisitedArenaPool* arena_pool =
+      static_cast<GcVisitedArenaPool*>(Runtime::Current()->GetLinearAllocArenaPool());
+  for (auto& pair : linear_alloc_arenas_) {
+    const TrackedArena* arena = pair.first;
+    size_t arena_size;
+    uint8_t* arena_begin;
+    ptrdiff_t diff;
+    bool others_processing;
+    {
+      // Acquire arena-pool's lock so that the arena being worked cannot be
+      // deallocated at the same time.
+      std::lock_guard<std::mutex> lock(arena_pool->GetLock());
+      // If any arenas were freed since compaction pause then skip them from
+      // visiting.
+      if (arena_pool->AreArenasFreed() && !arena_pool->FindAllocatedArena(arena)) {
+        continue;
+      }
+      uint8_t* last_byte = pair.second;
+      DCHECK_ALIGNED(last_byte, kPageSize);
+      others_processing = false;
+      arena_begin = arena->Begin();
+      arena_size = arena->Size();
+      // Find the linear-alloc space containing the arena
+      LinearAllocSpaceData* space_data = nullptr;
+      for (auto& data : linear_alloc_spaces_data_) {
+        if (data.begin_ <= arena_begin && arena_begin < data.end_) {
+          space_data = &data;
+          break;
+        }
+      }
+      DCHECK_NE(space_data, nullptr);
+      diff = space_data->shadow_.Begin() - space_data->begin_;
+      auto visitor = [space_data, last_byte, diff, this, &others_processing](
+                         uint8_t* page_begin,
+                         uint8_t* first_obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+        // No need to process pages past last_byte as they already have updated
+        // gc-roots, if any.
+        if (page_begin >= last_byte) {
+          return;
+        }
+        LinearAllocPageUpdater updater(this);
+        size_t page_idx = (page_begin - space_data->begin_) / kPageSize;
+        DCHECK_LT(page_idx, space_data->page_status_map_.Size());
+        Atomic<PageState>* state_arr =
+            reinterpret_cast<Atomic<PageState>*>(space_data->page_status_map_.Begin());
+        PageState expected_state = PageState::kUnprocessed;
+        PageState desired_state =
+            minor_fault_initialized_ ? PageState::kProcessing : PageState::kProcessingAndMapping;
+        // Acquire order to ensure that we don't start accessing the shadow page,
+        // which is shared with other threads, prior to CAS. Also, for same
+        // reason, we used 'release' order for changing the state to 'processed'.
+        if (state_arr[page_idx].compare_exchange_strong(
+                expected_state, desired_state, std::memory_order_acquire)) {
+          updater(page_begin + diff, first_obj + diff);
+          expected_state = PageState::kProcessing;
+          if (!minor_fault_initialized_) {
+            MapUpdatedLinearAllocPage(
+                page_begin, page_begin + diff, state_arr[page_idx], updater.WasLastPageTouched());
+          } else if (!state_arr[page_idx].compare_exchange_strong(
+                         expected_state, PageState::kProcessed, std::memory_order_release)) {
+            DCHECK_EQ(expected_state, PageState::kProcessingAndMapping);
+            // Force read in case the page was missing and updater didn't touch it
+            // as there was nothing to do. This will ensure that a zeropage is
+            // faulted on the shadow map.
+            ForceRead(page_begin + diff);
+            MapProcessedPages</*kFirstPageMapping=*/true>(
+                page_begin, state_arr, page_idx, space_data->page_status_map_.Size());
+          }
+        } else {
+          others_processing = true;
+        }
+      };
+
+      arena->VisitRoots(visitor);
+    }
+    // If we are not in minor-fault mode and if no other thread was found to be
+    // processing any pages in this arena, then we can madvise the shadow size.
+    // Otherwise, we will double the memory use for linear-alloc.
+    if (!minor_fault_initialized_ && !others_processing) {
+      ZeroAndReleasePages(arena_begin + diff, arena_size);
+    }
+  }
+}
+
+void MarkCompact::RegisterUffd(void* addr, size_t size, int mode) {
+  DCHECK(IsValidFd(uffd_));
+  struct uffdio_register uffd_register;
+  uffd_register.range.start = reinterpret_cast<uintptr_t>(addr);
+  uffd_register.range.len = size;
+  uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+  if (mode == kMinorFaultMode) {
+    uffd_register.mode |= UFFDIO_REGISTER_MODE_MINOR;
+  }
+  CHECK_EQ(ioctl(uffd_, UFFDIO_REGISTER, &uffd_register), 0)
+      << "ioctl_userfaultfd: register failed: " << strerror(errno)
+      << ". start:" << static_cast<void*>(addr) << " len:" << PrettySize(size);
+}
+
+void MarkCompact::UnregisterUffd(uint8_t* start, size_t len) {
+  DCHECK(IsValidFd(uffd_));
+  struct uffdio_range range;
+  range.start = reinterpret_cast<uintptr_t>(start);
+  range.len = len;
+  CHECK_EQ(ioctl(uffd_, UFFDIO_UNREGISTER, &range), 0)
+      << "ioctl_userfaultfd: unregister failed: " << strerror(errno)
+      << ". addr:" << static_cast<void*>(start) << " len:" << PrettySize(len);
+  // Due to an oversight in the kernel implementation of 'unregister', the
+  // waiting threads are woken up only for copy uffds. Therefore, for now, we
+  // have to explicitly wake up the threads in minor-fault case.
+  // TODO: The fix in the kernel is being worked on. Once the kernel version
+  // containing the fix is known, make it conditional on that as well.
+  if (minor_fault_initialized_) {
+    CHECK_EQ(ioctl(uffd_, UFFDIO_WAKE, &range), 0)
+        << "ioctl_userfaultfd: wake failed: " << strerror(errno)
+        << ". addr:" << static_cast<void*>(start) << " len:" << PrettySize(len);
+  }
+}
+
+void MarkCompact::CompactionPhase() {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  {
+    int32_t freed_bytes = black_objs_slide_diff_;
+    bump_pointer_space_->RecordFree(freed_objects_, freed_bytes);
+    RecordFree(ObjectBytePair(freed_objects_, freed_bytes));
+  }
+
+  if (CanCompactMovingSpaceWithMinorFault()) {
+    CompactMovingSpace<kMinorFaultMode>(/*page=*/nullptr);
+  } else {
+    CompactMovingSpace<kCopyMode>(compaction_buffers_map_.Begin());
+  }
+
+  // Make sure no mutator is reading from the from-space before unregistering
+  // userfaultfd from moving-space and then zapping from-space. The mutator
+  // and GC may race to set a page state to processing or further along. The two
+  // attempts are ordered. If the collector wins, then the mutator will see that
+  // and not access the from-space page. If the muator wins, then the
+  // compaction_in_progress_count_ increment by the mutator happens-before the test
+  // here, and we will not see a zero value until the mutator has completed.
+  for (uint32_t i = 0; compaction_in_progress_count_.load(std::memory_order_acquire) > 0; i++) {
+    BackOff(i);
+  }
+
+  size_t moving_space_size = bump_pointer_space_->Capacity();
+  UnregisterUffd(bump_pointer_space_->Begin(),
+                 minor_fault_initialized_ ?
+                     (moving_first_objs_count_ + black_page_count_) * kPageSize :
+                     moving_space_size);
+
+  // Release all of the memory taken by moving-space's from-map
+  if (minor_fault_initialized_) {
+    if (IsValidFd(moving_from_space_fd_)) {
+      // A strange behavior is observed wherein between GC cycles the from-space'
+      // first page is accessed. But the memfd that is mapped on from-space, is
+      // used on to-space in next GC cycle, causing issues with userfaultfd as the
+      // page isn't missing. A possible reason for this could be prefetches. The
+      // mprotect ensures that such accesses don't succeed.
+      int ret = mprotect(from_space_begin_, moving_space_size, PROT_NONE);
+      CHECK_EQ(ret, 0) << "mprotect(PROT_NONE) for from-space failed: " << strerror(errno);
+      // madvise(MADV_REMOVE) needs PROT_WRITE. Use fallocate() instead, which
+      // does the same thing.
+      ret = fallocate(moving_from_space_fd_,
+                      FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+                      /*offset=*/0,
+                      moving_space_size);
+      CHECK_EQ(ret, 0) << "fallocate for from-space failed: " << strerror(errno);
+    } else {
+      // We don't have a valid fd, so use madvise(MADV_REMOVE) instead. mprotect
+      // is not required in this case as we create fresh
+      // MAP_SHARED+MAP_ANONYMOUS mapping in each GC cycle.
+      int ret = madvise(from_space_begin_, moving_space_size, MADV_REMOVE);
+      CHECK_EQ(ret, 0) << "madvise(MADV_REMOVE) failed for from-space map:" << strerror(errno);
+    }
+  } else {
+    from_space_map_.MadviseDontNeedAndZero();
+  }
+  // mprotect(PROT_NONE) all maps except to-space in debug-mode to catch any unexpected accesses.
+  if (shadow_to_space_map_.IsValid()) {
+    DCHECK_EQ(mprotect(shadow_to_space_map_.Begin(), shadow_to_space_map_.Size(), PROT_NONE), 0)
+        << "mprotect(PROT_NONE) for shadow-map failed:" << strerror(errno);
+  }
+  if (!IsValidFd(moving_from_space_fd_)) {
+    // The other case is already mprotected above.
+    DCHECK_EQ(mprotect(from_space_begin_, moving_space_size, PROT_NONE), 0)
+        << "mprotect(PROT_NONE) for from-space failed: " << strerror(errno);
+  }
+
+  ProcessLinearAlloc();
+
+  if (use_uffd_sigbus_) {
+    // Set compaction-done bit so that no new mutator threads start compaction
+    // process in the SIGBUS handler.
+    SigbusCounterType count = sigbus_in_progress_count_.fetch_or(kSigbusCounterCompactionDoneMask,
+                                                                 std::memory_order_acq_rel);
+    // Wait for SIGBUS handlers already in play.
+    for (uint32_t i = 0; count > 0; i++) {
+      BackOff(i);
+      count = sigbus_in_progress_count_.load(std::memory_order_acquire);
+      count &= ~kSigbusCounterCompactionDoneMask;
+    }
+  } else {
+    DCHECK(IsAligned<kPageSize>(conc_compaction_termination_page_));
+    // We will only iterate once if gKernelHasFaultRetry is true.
+    do {
+      // madvise the page so that we can get userfaults on it.
+      ZeroAndReleasePages(conc_compaction_termination_page_, kPageSize);
+      // The following load triggers 'special' userfaults. When received by the
+      // thread-pool workers, they will exit out of the compaction task. This fault
+      // happens because we madvised the page.
+      ForceRead(conc_compaction_termination_page_);
+    } while (thread_pool_counter_ > 0);
+  }
+  // Unregister linear-alloc spaces
+  for (auto& data : linear_alloc_spaces_data_) {
+    DCHECK_EQ(data.end_ - data.begin_, static_cast<ssize_t>(data.shadow_.Size()));
+    UnregisterUffd(data.begin_, data.shadow_.Size());
+    // madvise linear-allocs's page-status array
+    data.page_status_map_.MadviseDontNeedAndZero();
+    // Madvise the entire linear-alloc space's shadow. In copy-mode it gets rid
+    // of the pages which are still mapped. In minor-fault mode this unmaps all
+    // pages, which is good in reducing the mremap (done in STW pause) time in
+    // next GC cycle.
+    data.shadow_.MadviseDontNeedAndZero();
+    if (minor_fault_initialized_) {
+      DCHECK_EQ(mprotect(data.shadow_.Begin(), data.shadow_.Size(), PROT_NONE), 0)
+          << "mprotect failed: " << strerror(errno);
+    }
+  }
+
+  if (!use_uffd_sigbus_) {
+    heap_->GetThreadPool()->StopWorkers(thread_running_gc_);
+  }
+}
+
+template <size_t kBufferSize>
+class MarkCompact::ThreadRootsVisitor : public RootVisitor {
+ public:
+  explicit ThreadRootsVisitor(MarkCompact* mark_compact, Thread* const self)
+        : mark_compact_(mark_compact), self_(self) {}
+
+  ~ThreadRootsVisitor() {
+    Flush();
+  }
+
+  void VisitRoots(mirror::Object*** roots, size_t count, const RootInfo& info ATTRIBUTE_UNUSED)
+      override REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_) {
+    for (size_t i = 0; i < count; i++) {
+      mirror::Object* obj = *roots[i];
+      if (mark_compact_->MarkObjectNonNullNoPush</*kParallel*/true>(obj)) {
+        Push(obj);
+      }
+    }
+  }
+
+  void VisitRoots(mirror::CompressedReference<mirror::Object>** roots,
+                  size_t count,
+                  const RootInfo& info ATTRIBUTE_UNUSED)
+      override REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_) {
+    for (size_t i = 0; i < count; i++) {
+      mirror::Object* obj = roots[i]->AsMirrorPtr();
+      if (mark_compact_->MarkObjectNonNullNoPush</*kParallel*/true>(obj)) {
+        Push(obj);
+      }
+    }
+  }
+
+ private:
+  void Flush() REQUIRES_SHARED(Locks::mutator_lock_)
+               REQUIRES(Locks::heap_bitmap_lock_) {
+    StackReference<mirror::Object>* start;
+    StackReference<mirror::Object>* end;
+    {
+      MutexLock mu(self_, mark_compact_->lock_);
+      // Loop here because even after expanding once it may not be sufficient to
+      // accommodate all references. It's almost impossible, but there is no harm
+      // in implementing it this way.
+      while (!mark_compact_->mark_stack_->BumpBack(idx_, &start, &end)) {
+        mark_compact_->ExpandMarkStack();
+      }
+    }
+    while (idx_ > 0) {
+      *start++ = roots_[--idx_];
+    }
+    DCHECK_EQ(start, end);
+  }
+
+  void Push(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_)
+                                 REQUIRES(Locks::heap_bitmap_lock_) {
+    if (UNLIKELY(idx_ >= kBufferSize)) {
+      Flush();
+    }
+    roots_[idx_++].Assign(obj);
+  }
+
+  StackReference<mirror::Object> roots_[kBufferSize];
+  size_t idx_ = 0;
+  MarkCompact* const mark_compact_;
+  Thread* const self_;
+};
+
+class MarkCompact::CheckpointMarkThreadRoots : public Closure {
+ public:
+  explicit CheckpointMarkThreadRoots(MarkCompact* mark_compact) : mark_compact_(mark_compact) {}
+
+  void Run(Thread* thread) override NO_THREAD_SAFETY_ANALYSIS {
+    ScopedTrace trace("Marking thread roots");
+    // Note: self is not necessarily equal to thread since thread may be
+    // suspended.
+    Thread* const self = Thread::Current();
+    CHECK(thread == self
+          || thread->IsSuspended()
+          || thread->GetState() == ThreadState::kWaitingPerformingGc)
+        << thread->GetState() << " thread " << thread << " self " << self;
+    {
+      ThreadRootsVisitor</*kBufferSize*/ 20> visitor(mark_compact_, self);
+      thread->VisitRoots(&visitor, kVisitRootFlagAllRoots);
+    }
+    // Clear page-buffer to prepare for compaction phase.
+    thread->SetThreadLocalGcBuffer(nullptr);
+
+    // If thread is a running mutator, then act on behalf of the garbage
+    // collector. See the code in ThreadList::RunCheckpoint.
+    mark_compact_->GetBarrier().Pass(self);
+  }
+
+ private:
+  MarkCompact* const mark_compact_;
+};
+
+void MarkCompact::MarkRootsCheckpoint(Thread* self, Runtime* runtime) {
+  // We revote TLABs later during paused round of marking.
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  CheckpointMarkThreadRoots check_point(this);
+  ThreadList* thread_list = runtime->GetThreadList();
+  gc_barrier_.Init(self, 0);
+  // Request the check point is run on all threads returning a count of the threads that must
+  // run through the barrier including self.
+  size_t barrier_count = thread_list->RunCheckpoint(&check_point);
+  // Release locks then wait for all mutator threads to pass the barrier.
+  // If there are no threads to wait which implys that all the checkpoint functions are finished,
+  // then no need to release locks.
+  if (barrier_count == 0) {
+    return;
+  }
+  Locks::heap_bitmap_lock_->ExclusiveUnlock(self);
+  Locks::mutator_lock_->SharedUnlock(self);
+  {
+    ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
+    gc_barrier_.Increment(self, barrier_count);
+  }
+  Locks::mutator_lock_->SharedLock(self);
+  Locks::heap_bitmap_lock_->ExclusiveLock(self);
+}
+
+void MarkCompact::MarkNonThreadRoots(Runtime* runtime) {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  runtime->VisitNonThreadRoots(this);
+}
+
+void MarkCompact::MarkConcurrentRoots(VisitRootFlags flags, Runtime* runtime) {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  runtime->VisitConcurrentRoots(this, flags);
+}
+
+void MarkCompact::RevokeAllThreadLocalBuffers() {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  bump_pointer_space_->RevokeAllThreadLocalBuffers();
+}
+
+class MarkCompact::ScanObjectVisitor {
+ public:
+  explicit ScanObjectVisitor(MarkCompact* const mark_compact) ALWAYS_INLINE
+      : mark_compact_(mark_compact) {}
+
+  void operator()(ObjPtr<mirror::Object> obj) const
+      ALWAYS_INLINE
+      REQUIRES(Locks::heap_bitmap_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    mark_compact_->ScanObject</*kUpdateLiveWords*/ false>(obj.Ptr());
+  }
+
+ private:
+  MarkCompact* const mark_compact_;
+};
+
+void MarkCompact::UpdateAndMarkModUnion() {
+  accounting::CardTable* const card_table = heap_->GetCardTable();
+  for (const auto& space : immune_spaces_.GetSpaces()) {
+    const char* name = space->IsZygoteSpace()
+        ? "UpdateAndMarkZygoteModUnionTable"
+        : "UpdateAndMarkImageModUnionTable";
+    DCHECK(space->IsZygoteSpace() || space->IsImageSpace()) << *space;
+    TimingLogger::ScopedTiming t(name, GetTimings());
+    accounting::ModUnionTable* table = heap_->FindModUnionTableFromSpace(space);
+    if (table != nullptr) {
+      // UpdateAndMarkReferences() doesn't visit Reference-type objects. But
+      // that's fine because these objects are immutable enough (referent can
+      // only be cleared) and hence the only referents they can have are intra-space.
+      table->UpdateAndMarkReferences(this);
+    } else {
+      // No mod-union table, scan all dirty/aged cards in the corresponding
+      // card-table. This can only occur for app images.
+      card_table->Scan</*kClearCard*/ false>(space->GetMarkBitmap(),
+                                             space->Begin(),
+                                             space->End(),
+                                             ScanObjectVisitor(this),
+                                             gc::accounting::CardTable::kCardAged);
+    }
+  }
+}
+
+void MarkCompact::MarkReachableObjects() {
+  UpdateAndMarkModUnion();
+  // Recursively mark all the non-image bits set in the mark bitmap.
+  ProcessMarkStack();
+}
+
+class MarkCompact::CardModifiedVisitor {
+ public:
+  explicit CardModifiedVisitor(MarkCompact* const mark_compact,
+                               accounting::ContinuousSpaceBitmap* const bitmap,
+                               accounting::CardTable* const card_table)
+      : visitor_(mark_compact), bitmap_(bitmap), card_table_(card_table) {}
+
+  void operator()(uint8_t* card,
+                  uint8_t expected_value,
+                  uint8_t new_value ATTRIBUTE_UNUSED) const {
+    if (expected_value == accounting::CardTable::kCardDirty) {
+      uintptr_t start = reinterpret_cast<uintptr_t>(card_table_->AddrFromCard(card));
+      bitmap_->VisitMarkedRange(start, start + accounting::CardTable::kCardSize, visitor_);
+    }
+  }
+
+ private:
+  ScanObjectVisitor visitor_;
+  accounting::ContinuousSpaceBitmap* bitmap_;
+  accounting::CardTable* const card_table_;
+};
+
+void MarkCompact::ScanDirtyObjects(bool paused, uint8_t minimum_age) {
+  accounting::CardTable* card_table = heap_->GetCardTable();
+  for (const auto& space : heap_->GetContinuousSpaces()) {
+    const char* name = nullptr;
+    switch (space->GetGcRetentionPolicy()) {
+    case space::kGcRetentionPolicyNeverCollect:
+      name = paused ? "(Paused)ScanGrayImmuneSpaceObjects" : "ScanGrayImmuneSpaceObjects";
+      break;
+    case space::kGcRetentionPolicyFullCollect:
+      name = paused ? "(Paused)ScanGrayZygoteSpaceObjects" : "ScanGrayZygoteSpaceObjects";
+      break;
+    case space::kGcRetentionPolicyAlwaysCollect:
+      name = paused ? "(Paused)ScanGrayAllocSpaceObjects" : "ScanGrayAllocSpaceObjects";
+      break;
+    default:
+      LOG(FATAL) << "Unreachable";
+      UNREACHABLE();
+    }
+    TimingLogger::ScopedTiming t(name, GetTimings());
+    ScanObjectVisitor visitor(this);
+    const bool is_immune_space = space->IsZygoteSpace() || space->IsImageSpace();
+    if (paused) {
+      DCHECK_EQ(minimum_age, gc::accounting::CardTable::kCardDirty);
+      // We can clear the card-table for any non-immune space.
+      if (is_immune_space) {
+        card_table->Scan</*kClearCard*/false>(space->GetMarkBitmap(),
+                                              space->Begin(),
+                                              space->End(),
+                                              visitor,
+                                              minimum_age);
+      } else {
+        card_table->Scan</*kClearCard*/true>(space->GetMarkBitmap(),
+                                             space->Begin(),
+                                             space->End(),
+                                             visitor,
+                                             minimum_age);
+      }
+    } else {
+      DCHECK_EQ(minimum_age, gc::accounting::CardTable::kCardAged);
+      accounting::ModUnionTable* table = heap_->FindModUnionTableFromSpace(space);
+      if (table) {
+        table->ProcessCards();
+        card_table->Scan</*kClearCard*/false>(space->GetMarkBitmap(),
+                                              space->Begin(),
+                                              space->End(),
+                                              visitor,
+                                              minimum_age);
+      } else {
+        CardModifiedVisitor card_modified_visitor(this, space->GetMarkBitmap(), card_table);
+        // For the alloc spaces we should age the dirty cards and clear the rest.
+        // For image and zygote-space without mod-union-table, age the dirty
+        // cards but keep the already aged cards unchanged.
+        // In either case, visit the objects on the cards that were changed from
+        // dirty to aged.
+        if (is_immune_space) {
+          card_table->ModifyCardsAtomic(space->Begin(),
+                                        space->End(),
+                                        [](uint8_t card) {
+                                          return (card == gc::accounting::CardTable::kCardClean)
+                                                  ? card
+                                                  : gc::accounting::CardTable::kCardAged;
+                                        },
+                                        card_modified_visitor);
+        } else {
+          card_table->ModifyCardsAtomic(space->Begin(),
+                                        space->End(),
+                                        AgeCardVisitor(),
+                                        card_modified_visitor);
+        }
+      }
+    }
+  }
+}
+
+void MarkCompact::RecursiveMarkDirtyObjects(bool paused, uint8_t minimum_age) {
+  ScanDirtyObjects(paused, minimum_age);
+  ProcessMarkStack();
+}
+
+void MarkCompact::MarkRoots(VisitRootFlags flags) {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  Runtime* runtime = Runtime::Current();
+  // Make sure that the checkpoint which collects the stack roots is the first
+  // one capturning GC-roots. As this one is supposed to find the address
+  // everything allocated after that (during this marking phase) will be
+  // considered 'marked'.
+  MarkRootsCheckpoint(thread_running_gc_, runtime);
+  MarkNonThreadRoots(runtime);
+  MarkConcurrentRoots(flags, runtime);
+}
+
+void MarkCompact::PreCleanCards() {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  CHECK(!Locks::mutator_lock_->IsExclusiveHeld(thread_running_gc_));
+  MarkRoots(static_cast<VisitRootFlags>(kVisitRootFlagClearRootLog | kVisitRootFlagNewRoots));
+  RecursiveMarkDirtyObjects(/*paused*/ false, accounting::CardTable::kCardDirty - 1);
+}
+
+// In a concurrent marking algorithm, if we are not using a write/read barrier, as
+// in this case, then we need a stop-the-world (STW) round in the end to mark
+// objects which were written into concurrently while concurrent marking was
+// performed.
+// In order to minimize the pause time, we could take one of the two approaches:
+// 1. Keep repeating concurrent marking of dirty cards until the time spent goes
+// below a threshold.
+// 2. Do two rounds concurrently and then attempt a paused one. If we figure
+// that it's taking too long, then resume mutators and retry.
+//
+// Given the non-trivial fixed overhead of running a round (card table and root
+// scan), it might be better to go with approach 2.
+void MarkCompact::MarkingPhase() {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  DCHECK_EQ(thread_running_gc_, Thread::Current());
+  WriterMutexLock mu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+  BindAndResetBitmaps();
+  MarkZygoteLargeObjects();
+  MarkRoots(
+        static_cast<VisitRootFlags>(kVisitRootFlagAllRoots | kVisitRootFlagStartLoggingNewRoots));
+  MarkReachableObjects();
+  // Pre-clean dirtied cards to reduce pauses.
+  PreCleanCards();
+
+  // Setup reference processing and forward soft references once before enabling
+  // slow path (in MarkingPause)
+  ReferenceProcessor* rp = GetHeap()->GetReferenceProcessor();
+  bool clear_soft_references = GetCurrentIteration()->GetClearSoftReferences();
+  rp->Setup(thread_running_gc_, this, /*concurrent=*/ true, clear_soft_references);
+  if (!clear_soft_references) {
+    // Forward as many SoftReferences as possible before inhibiting reference access.
+    rp->ForwardSoftReferences(GetTimings());
+  }
+}
+
+class MarkCompact::RefFieldsVisitor {
+ public:
+  ALWAYS_INLINE explicit RefFieldsVisitor(MarkCompact* const mark_compact)
+    : mark_compact_(mark_compact) {}
+
+  ALWAYS_INLINE void operator()(mirror::Object* obj,
+                                MemberOffset offset,
+                                bool is_static ATTRIBUTE_UNUSED) const
+      REQUIRES(Locks::heap_bitmap_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (kCheckLocks) {
+      Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+      Locks::heap_bitmap_lock_->AssertExclusiveHeld(Thread::Current());
+    }
+    mark_compact_->MarkObject(obj->GetFieldObject<mirror::Object>(offset), obj, offset);
+  }
+
+  void operator()(ObjPtr<mirror::Class> klass, ObjPtr<mirror::Reference> ref) const
+      REQUIRES(Locks::heap_bitmap_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    mark_compact_->DelayReferenceReferent(klass, ref);
+  }
+
+  void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+      REQUIRES(Locks::heap_bitmap_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (!root->IsNull()) {
+      VisitRoot(root);
+    }
+  }
+
+  void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+      REQUIRES(Locks::heap_bitmap_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (kCheckLocks) {
+      Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+      Locks::heap_bitmap_lock_->AssertExclusiveHeld(Thread::Current());
+    }
+    mark_compact_->MarkObject(root->AsMirrorPtr());
+  }
+
+ private:
+  MarkCompact* const mark_compact_;
+};
+
+template <size_t kAlignment>
+size_t MarkCompact::LiveWordsBitmap<kAlignment>::LiveBytesInBitmapWord(size_t chunk_idx) const {
+  const size_t index = chunk_idx * kBitmapWordsPerVectorWord;
+  size_t words = 0;
+  for (uint32_t i = 0; i < kBitmapWordsPerVectorWord; i++) {
+    words += POPCOUNT(Bitmap::Begin()[index + i]);
+  }
+  return words * kAlignment;
+}
+
+void MarkCompact::UpdateLivenessInfo(mirror::Object* obj, size_t obj_size) {
+  DCHECK(obj != nullptr);
+  DCHECK_EQ(obj_size, obj->SizeOf<kDefaultVerifyFlags>());
+  uintptr_t obj_begin = reinterpret_cast<uintptr_t>(obj);
+  UpdateClassAfterObjectMap(obj);
+  size_t size = RoundUp(obj_size, kAlignment);
+  uintptr_t bit_index = live_words_bitmap_->SetLiveWords(obj_begin, size);
+  size_t chunk_idx = (obj_begin - live_words_bitmap_->Begin()) / kOffsetChunkSize;
+  // Compute the bit-index within the chunk-info vector word.
+  bit_index %= kBitsPerVectorWord;
+  size_t first_chunk_portion = std::min(size, (kBitsPerVectorWord - bit_index) * kAlignment);
+
+  chunk_info_vec_[chunk_idx++] += first_chunk_portion;
+  DCHECK_LE(first_chunk_portion, size);
+  for (size -= first_chunk_portion; size > kOffsetChunkSize; size -= kOffsetChunkSize) {
+    DCHECK_EQ(chunk_info_vec_[chunk_idx], 0u);
+    chunk_info_vec_[chunk_idx++] = kOffsetChunkSize;
+  }
+  chunk_info_vec_[chunk_idx] += size;
+  freed_objects_--;
+}
+
+template <bool kUpdateLiveWords>
+void MarkCompact::ScanObject(mirror::Object* obj) {
+  // The size of `obj` is used both here (to update `bytes_scanned_`) and in
+  // `UpdateLivenessInfo`. As fetching this value can be expensive, do it once
+  // here and pass that information to `UpdateLivenessInfo`.
+  size_t obj_size = obj->SizeOf<kDefaultVerifyFlags>();
+  bytes_scanned_ += obj_size;
+
+  RefFieldsVisitor visitor(this);
+  DCHECK(IsMarked(obj)) << "Scanning marked object " << obj << "\n" << heap_->DumpSpaces();
+  if (kUpdateLiveWords && moving_space_bitmap_->HasAddress(obj)) {
+    UpdateLivenessInfo(obj, obj_size);
+  }
+  obj->VisitReferences(visitor, visitor);
+}
+
+// Scan anything that's on the mark stack.
+void MarkCompact::ProcessMarkStack() {
+  TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+  // TODO: try prefetch like in CMS
+  while (!mark_stack_->IsEmpty()) {
+    mirror::Object* obj = mark_stack_->PopBack();
+    DCHECK(obj != nullptr);
+    ScanObject</*kUpdateLiveWords*/ true>(obj);
+  }
+}
+
+void MarkCompact::ExpandMarkStack() {
+  const size_t new_size = mark_stack_->Capacity() * 2;
+  std::vector<StackReference<mirror::Object>> temp(mark_stack_->Begin(),
+                                                   mark_stack_->End());
+  mark_stack_->Resize(new_size);
+  for (auto& ref : temp) {
+    mark_stack_->PushBack(ref.AsMirrorPtr());
+  }
+  DCHECK(!mark_stack_->IsFull());
+}
+
+inline void MarkCompact::PushOnMarkStack(mirror::Object* obj) {
+  if (UNLIKELY(mark_stack_->IsFull())) {
+    ExpandMarkStack();
+  }
+  mark_stack_->PushBack(obj);
+}
+
+inline void MarkCompact::MarkObjectNonNull(mirror::Object* obj,
+                                           mirror::Object* holder,
+                                           MemberOffset offset) {
+  DCHECK(obj != nullptr);
+  if (MarkObjectNonNullNoPush</*kParallel*/false>(obj, holder, offset)) {
+    PushOnMarkStack(obj);
+  }
+}
+
+template <bool kParallel>
+inline bool MarkCompact::MarkObjectNonNullNoPush(mirror::Object* obj,
+                                                 mirror::Object* holder,
+                                                 MemberOffset offset) {
+  // We expect most of the referenes to be in bump-pointer space, so try that
+  // first to keep the cost of this function minimal.
+  if (LIKELY(moving_space_bitmap_->HasAddress(obj))) {
+    return kParallel ? !moving_space_bitmap_->AtomicTestAndSet(obj)
+                     : !moving_space_bitmap_->Set(obj);
+  } else if (non_moving_space_bitmap_->HasAddress(obj)) {
+    return kParallel ? !non_moving_space_bitmap_->AtomicTestAndSet(obj)
+                     : !non_moving_space_bitmap_->Set(obj);
+  } else if (immune_spaces_.ContainsObject(obj)) {
+    DCHECK(IsMarked(obj) != nullptr);
+    return false;
+  } else {
+    // Must be a large-object space, otherwise it's a case of heap corruption.
+    if (!IsAligned<kPageSize>(obj)) {
+      // Objects in large-object space are page aligned. So if we have an object
+      // which doesn't belong to any space and is not page-aligned as well, then
+      // it's memory corruption.
+      // TODO: implement protect/unprotect in bump-pointer space.
+      heap_->GetVerification()->LogHeapCorruption(holder, offset, obj, /*fatal*/ true);
+    }
+    DCHECK_NE(heap_->GetLargeObjectsSpace(), nullptr)
+        << "ref=" << obj
+        << " doesn't belong to any of the spaces and large object space doesn't exist";
+    accounting::LargeObjectBitmap* los_bitmap = heap_->GetLargeObjectsSpace()->GetMarkBitmap();
+    DCHECK(los_bitmap->HasAddress(obj));
+    if (kParallel) {
+      los_bitmap->AtomicTestAndSet(obj);
+    } else {
+      los_bitmap->Set(obj);
+    }
+    // We only have primitive arrays in large object space. So there is no
+    // reason to push into mark-stack.
+    DCHECK(obj->IsString() || (obj->IsArrayInstance() && !obj->IsObjectArray()));
+    return false;
+  }
+}
+
+inline void MarkCompact::MarkObject(mirror::Object* obj,
+                                    mirror::Object* holder,
+                                    MemberOffset offset) {
+  if (obj != nullptr) {
+    MarkObjectNonNull(obj, holder, offset);
+  }
+}
+
+mirror::Object* MarkCompact::MarkObject(mirror::Object* obj) {
+  MarkObject(obj, nullptr, MemberOffset(0));
+  return obj;
+}
+
+void MarkCompact::MarkHeapReference(mirror::HeapReference<mirror::Object>* obj,
+                                    bool do_atomic_update ATTRIBUTE_UNUSED) {
+  MarkObject(obj->AsMirrorPtr(), nullptr, MemberOffset(0));
+}
+
+void MarkCompact::VisitRoots(mirror::Object*** roots,
+                             size_t count,
+                             const RootInfo& info) {
+  if (compacting_) {
+    for (size_t i = 0; i < count; ++i) {
+      UpdateRoot(roots[i], info);
+    }
+  } else {
+    for (size_t i = 0; i < count; ++i) {
+      MarkObjectNonNull(*roots[i]);
+    }
+  }
+}
+
+void MarkCompact::VisitRoots(mirror::CompressedReference<mirror::Object>** roots,
+                             size_t count,
+                             const RootInfo& info) {
+  // TODO: do we need to check if the root is null or not?
+  if (compacting_) {
+    for (size_t i = 0; i < count; ++i) {
+      UpdateRoot(roots[i], info);
+    }
+  } else {
+    for (size_t i = 0; i < count; ++i) {
+      MarkObjectNonNull(roots[i]->AsMirrorPtr());
+    }
+  }
+}
+
+mirror::Object* MarkCompact::IsMarked(mirror::Object* obj) {
+  if (moving_space_bitmap_->HasAddress(obj)) {
+    const bool is_black = reinterpret_cast<uint8_t*>(obj) >= black_allocations_begin_;
+    if (compacting_) {
+      if (is_black) {
+        return PostCompactBlackObjAddr(obj);
+      } else if (live_words_bitmap_->Test(obj)) {
+        return PostCompactOldObjAddr(obj);
+      } else {
+        return nullptr;
+      }
+    }
+    return (is_black || moving_space_bitmap_->Test(obj)) ? obj : nullptr;
+  } else if (non_moving_space_bitmap_->HasAddress(obj)) {
+    return non_moving_space_bitmap_->Test(obj) ? obj : nullptr;
+  } else if (immune_spaces_.ContainsObject(obj)) {
+    return obj;
+  } else {
+    DCHECK(heap_->GetLargeObjectsSpace())
+        << "ref=" << obj
+        << " doesn't belong to any of the spaces and large object space doesn't exist";
+    accounting::LargeObjectBitmap* los_bitmap = heap_->GetLargeObjectsSpace()->GetMarkBitmap();
+    if (los_bitmap->HasAddress(obj)) {
+      DCHECK(IsAligned<kPageSize>(obj));
+      return los_bitmap->Test(obj) ? obj : nullptr;
+    } else {
+      // The given obj is not in any of the known spaces, so return null. This could
+      // happen for instance in interpreter caches wherein a concurrent updation
+      // to the cache could result in obj being a non-reference. This is
+      // tolerable because SweepInterpreterCaches only updates if the given
+      // object has moved, which can't be the case for the non-reference.
+      return nullptr;
+    }
+  }
+}
+
+bool MarkCompact::IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* obj,
+                                              bool do_atomic_update ATTRIBUTE_UNUSED) {
+  mirror::Object* ref = obj->AsMirrorPtr();
+  if (ref == nullptr) {
+    return true;
+  }
+  return IsMarked(ref);
+}
+
+// Process the 'referent' field in a java.lang.ref.Reference. If the referent
+// has not yet been marked, put it on the appropriate list in the heap for later
+// processing.
+void MarkCompact::DelayReferenceReferent(ObjPtr<mirror::Class> klass,
+                                         ObjPtr<mirror::Reference> ref) {
+  heap_->GetReferenceProcessor()->DelayReferenceReferent(klass, ref, this);
+}
+
+void MarkCompact::FinishPhase() {
+  GetCurrentIteration()->SetScannedBytes(bytes_scanned_);
+  bool is_zygote = Runtime::Current()->IsZygote();
+  compacting_ = false;
+  minor_fault_initialized_ = !is_zygote && uffd_minor_fault_supported_;
+  // Madvise compaction buffers. When using threaded implementation, skip the first page,
+  // which is used by the gc-thread for the next iteration. Otherwise, we get into a
+  // deadlock due to userfault on it in the next iteration. This page is not consuming any
+  // physical memory because we already madvised it above and then we triggered a read
+  // userfault, which maps a special zero-page.
+  if (use_uffd_sigbus_ || !minor_fault_initialized_ || !shadow_to_space_map_.IsValid() ||
+      shadow_to_space_map_.Size() < (moving_first_objs_count_ + black_page_count_) * kPageSize) {
+    size_t adjustment = use_uffd_sigbus_ ? 0 : kPageSize;
+    ZeroAndReleasePages(compaction_buffers_map_.Begin() + adjustment,
+                        compaction_buffers_map_.Size() - adjustment);
+  } else if (shadow_to_space_map_.Size() == bump_pointer_space_->Capacity()) {
+    // Now that we are going to use minor-faults from next GC cycle, we can
+    // unmap the buffers used by worker threads.
+    compaction_buffers_map_.SetSize(kPageSize);
+  }
+  info_map_.MadviseDontNeedAndZero();
+  live_words_bitmap_->ClearBitmap();
+  // TODO: We can clear this bitmap right before compaction pause. But in that
+  // case we need to ensure that we don't assert on this bitmap afterwards.
+  // Also, we would still need to clear it here again as we may have to use the
+  // bitmap for black-allocations (see UpdateMovingSpaceBlackAllocations()).
+  moving_space_bitmap_->Clear();
+
+  if (UNLIKELY(is_zygote && IsValidFd(uffd_))) {
+    heap_->DeleteThreadPool();
+    // This unregisters all ranges as a side-effect.
+    close(uffd_);
+    uffd_ = kFdUnused;
+    uffd_initialized_ = false;
+  }
+  CHECK(mark_stack_->IsEmpty());  // Ensure that the mark stack is empty.
+  mark_stack_->Reset();
+  DCHECK_EQ(thread_running_gc_, Thread::Current());
+  if (kIsDebugBuild) {
+    MutexLock mu(thread_running_gc_, lock_);
+    if (updated_roots_.get() != nullptr) {
+      updated_roots_->clear();
+    }
+  }
+  class_after_obj_ordered_map_.clear();
+  delete[] moving_pages_status_;
+  linear_alloc_arenas_.clear();
+  {
+    ReaderMutexLock mu(thread_running_gc_, *Locks::mutator_lock_);
+    WriterMutexLock mu2(thread_running_gc_, *Locks::heap_bitmap_lock_);
+    heap_->ClearMarkedObjects();
+  }
+  std::swap(moving_to_space_fd_, moving_from_space_fd_);
+  if (IsValidFd(moving_to_space_fd_)) {
+    // Confirm that the memfd to be used on to-space in next GC cycle is empty.
+    struct stat buf;
+    DCHECK_EQ(fstat(moving_to_space_fd_, &buf), 0) << "fstat failed: " << strerror(errno);
+    DCHECK_EQ(buf.st_blocks, 0u);
+  }
+}
+
+}  // namespace collector
+}  // namespace gc
+}  // namespace art
diff --git a/runtime/gc/collector/mark_compact.h b/runtime/gc/collector/mark_compact.h
new file mode 100644
index 0000000..d73f40d
--- /dev/null
+++ b/runtime/gc/collector/mark_compact.h
@@ -0,0 +1,789 @@
+/*
+ * Copyright 2021 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_RUNTIME_GC_COLLECTOR_MARK_COMPACT_H_
+#define ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_H_
+
+#include <signal.h>
+
+#include <map>
+#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "barrier.h"
+#include "base/atomic.h"
+#include "base/gc_visited_arena_pool.h"
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "garbage_collector.h"
+#include "gc/accounting/atomic_stack.h"
+#include "gc/accounting/bitmap-inl.h"
+#include "gc/accounting/heap_bitmap.h"
+#include "gc_root.h"
+#include "immune_spaces.h"
+#include "offsets.h"
+
+namespace art {
+
+bool KernelSupportsUffd();
+
+namespace mirror {
+class DexCache;
+}  // namespace mirror
+
+namespace gc {
+
+class Heap;
+
+namespace space {
+class BumpPointerSpace;
+}  // namespace space
+
+namespace collector {
+class MarkCompact final : public GarbageCollector {
+ public:
+  using SigbusCounterType = uint32_t;
+
+  static constexpr size_t kAlignment = kObjectAlignment;
+  static constexpr int kCopyMode = -1;
+  static constexpr int kMinorFaultMode = -2;
+  // Fake file descriptor for fall back mode (when uffd isn't available)
+  static constexpr int kFallbackMode = -3;
+  static constexpr int kFdSharedAnon = -1;
+  static constexpr int kFdUnused = -2;
+
+  // Bitmask for the compaction-done bit in the sigbus_in_progress_count_.
+  static constexpr SigbusCounterType kSigbusCounterCompactionDoneMask =
+      1u << (BitSizeOf<SigbusCounterType>() - 1);
+
+  explicit MarkCompact(Heap* heap);
+
+  ~MarkCompact() {}
+
+  void RunPhases() override REQUIRES(!Locks::mutator_lock_, !lock_);
+
+  // Updated before (or in) pre-compaction pause and is accessed only in the
+  // pause or during concurrent compaction. The flag is reset in next GC cycle's
+  // InitializePhase(). Therefore, it's safe to update without any memory ordering.
+  bool IsCompacting() const { return compacting_; }
+
+  bool IsUsingSigbusFeature() const { return use_uffd_sigbus_; }
+
+  // Called by SIGBUS handler. NO_THREAD_SAFETY_ANALYSIS for mutator-lock, which
+  // is asserted in the function.
+  bool SigbusHandler(siginfo_t* info) REQUIRES(!lock_) NO_THREAD_SAFETY_ANALYSIS;
+
+  GcType GetGcType() const override {
+    return kGcTypeFull;
+  }
+
+  CollectorType GetCollectorType() const override {
+    return kCollectorTypeCMC;
+  }
+
+  Barrier& GetBarrier() {
+    return gc_barrier_;
+  }
+
+  mirror::Object* MarkObject(mirror::Object* obj) override
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj,
+                         bool do_atomic_update) override
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  void VisitRoots(mirror::Object*** roots,
+                  size_t count,
+                  const RootInfo& info) override
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  void VisitRoots(mirror::CompressedReference<mirror::Object>** roots,
+                  size_t count,
+                  const RootInfo& info) override
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  bool IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* obj,
+                                   bool do_atomic_update) override
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  void RevokeAllThreadLocalBuffers() override;
+
+  void DelayReferenceReferent(ObjPtr<mirror::Class> klass,
+                              ObjPtr<mirror::Reference> reference) override
+      REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+
+  mirror::Object* IsMarked(mirror::Object* obj) override
+      REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+
+  mirror::Object* GetFromSpaceAddrFromBarrier(mirror::Object* old_ref) {
+    CHECK(compacting_);
+    if (live_words_bitmap_->HasAddress(old_ref)) {
+      return GetFromSpaceAddr(old_ref);
+    }
+    return old_ref;
+  }
+  // Called from Heap::PostForkChildAction() for non-zygote processes and from
+  // PrepareForCompaction() for zygote processes. Returns true if uffd was
+  // created or was already done.
+  bool CreateUserfaultfd(bool post_fork);
+
+  // Returns a pair indicating if userfaultfd itself is available (first) and if
+  // so then whether its minor-fault feature is available or not (second).
+  static std::pair<bool, bool> GetUffdAndMinorFault();
+
+  // Add linear-alloc space data when a new space is added to
+  // GcVisitedArenaPool, which mostly happens only once.
+  void AddLinearAllocSpaceData(uint8_t* begin, size_t len);
+
+  // In copy-mode of userfaultfd, we don't need to reach a 'processed' state as
+  // it's given that processing thread also copies the page, thereby mapping it.
+  // The order is important as we may treat them as integers.
+  enum class PageState : uint8_t {
+    kUnprocessed = 0,           // Not processed yet
+    kProcessing = 1,            // Being processed by GC thread and will not be mapped
+    kProcessed = 2,             // Processed but not mapped
+    kProcessingAndMapping = 3,  // Being processed by GC or mutator and will be mapped
+    kMutatorProcessing = 4,     // Being processed by mutator thread
+    kProcessedAndMapping = 5,   // Processed and will be mapped
+    kProcessedAndMapped = 6     // Processed and mapped. For SIGBUS.
+  };
+
+ private:
+  using ObjReference = mirror::CompressedReference<mirror::Object>;
+  // Number of bits (live-words) covered by a single chunk-info (below)
+  // entry/word.
+  // TODO: Since popcount is performed usomg SIMD instructions, we should
+  // consider using 128-bit in order to halve the chunk-info size.
+  static constexpr uint32_t kBitsPerVectorWord = kBitsPerIntPtrT;
+  static constexpr uint32_t kOffsetChunkSize = kBitsPerVectorWord * kAlignment;
+  static_assert(kOffsetChunkSize < kPageSize);
+  // Bitmap with bits corresponding to every live word set. For an object
+  // which is 4 words in size will have the corresponding 4 bits set. This is
+  // required for efficient computation of new-address (post-compaction) from
+  // the given old-address (pre-compaction).
+  template <size_t kAlignment>
+  class LiveWordsBitmap : private accounting::MemoryRangeBitmap<kAlignment> {
+    using Bitmap = accounting::Bitmap;
+    using MemRangeBitmap = accounting::MemoryRangeBitmap<kAlignment>;
+
+   public:
+    static_assert(IsPowerOfTwo(kBitsPerVectorWord));
+    static_assert(IsPowerOfTwo(Bitmap::kBitsPerBitmapWord));
+    static_assert(kBitsPerVectorWord >= Bitmap::kBitsPerBitmapWord);
+    static constexpr uint32_t kBitmapWordsPerVectorWord =
+            kBitsPerVectorWord / Bitmap::kBitsPerBitmapWord;
+    static_assert(IsPowerOfTwo(kBitmapWordsPerVectorWord));
+    static LiveWordsBitmap* Create(uintptr_t begin, uintptr_t end);
+
+    // Return offset (within the indexed chunk-info) of the nth live word.
+    uint32_t FindNthLiveWordOffset(size_t chunk_idx, uint32_t n) const;
+    // Sets all bits in the bitmap corresponding to the given range. Also
+    // returns the bit-index of the first word.
+    ALWAYS_INLINE uintptr_t SetLiveWords(uintptr_t begin, size_t size);
+    // Count number of live words upto the given bit-index. This is to be used
+    // to compute the post-compact address of an old reference.
+    ALWAYS_INLINE size_t CountLiveWordsUpto(size_t bit_idx) const;
+    // Call 'visitor' for every stride of contiguous marked bits in the live-words
+    // bitmap, starting from begin_bit_idx. Only visit 'bytes' live bytes or
+    // until 'end', whichever comes first.
+    // Visitor is called with index of the first marked bit in the stride,
+    // stride size and whether it's the last stride in the given range or not.
+    template <typename Visitor>
+    ALWAYS_INLINE void VisitLiveStrides(uintptr_t begin_bit_idx,
+                                        uint8_t* end,
+                                        const size_t bytes,
+                                        Visitor&& visitor) const
+        REQUIRES_SHARED(Locks::mutator_lock_);
+    // Count the number of live bytes in the given vector entry.
+    size_t LiveBytesInBitmapWord(size_t chunk_idx) const;
+    void ClearBitmap() { Bitmap::Clear(); }
+    ALWAYS_INLINE uintptr_t Begin() const { return MemRangeBitmap::CoverBegin(); }
+    ALWAYS_INLINE bool HasAddress(mirror::Object* obj) const {
+      return MemRangeBitmap::HasAddress(reinterpret_cast<uintptr_t>(obj));
+    }
+    ALWAYS_INLINE bool Test(uintptr_t bit_index) const {
+      return Bitmap::TestBit(bit_index);
+    }
+    ALWAYS_INLINE bool Test(mirror::Object* obj) const {
+      return MemRangeBitmap::Test(reinterpret_cast<uintptr_t>(obj));
+    }
+    ALWAYS_INLINE uintptr_t GetWord(size_t index) const {
+      static_assert(kBitmapWordsPerVectorWord == 1);
+      return Bitmap::Begin()[index * kBitmapWordsPerVectorWord];
+    }
+  };
+
+  // For a given object address in pre-compact space, return the corresponding
+  // address in the from-space, where heap pages are relocated in the compaction
+  // pause.
+  mirror::Object* GetFromSpaceAddr(mirror::Object* obj) const {
+    DCHECK(live_words_bitmap_->HasAddress(obj)) << " obj=" << obj;
+    return reinterpret_cast<mirror::Object*>(reinterpret_cast<uintptr_t>(obj)
+                                             + from_space_slide_diff_);
+  }
+
+  // Verifies that that given object reference refers to a valid object.
+  // Otherwise fataly dumps logs, including those from callback.
+  template <typename Callback>
+  void VerifyObject(mirror::Object* ref, Callback& callback) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Check if the obj is within heap and has a klass which is likely to be valid
+  // mirror::Class.
+  bool IsValidObject(mirror::Object* obj) const REQUIRES_SHARED(Locks::mutator_lock_);
+  void InitializePhase();
+  void FinishPhase() REQUIRES(!Locks::mutator_lock_, !Locks::heap_bitmap_lock_, !lock_);
+  void MarkingPhase() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::heap_bitmap_lock_);
+  void CompactionPhase() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void SweepSystemWeaks(Thread* self, Runtime* runtime, const bool paused)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::heap_bitmap_lock_);
+  // Update the reference at given offset in the given object with post-compact
+  // address.
+  ALWAYS_INLINE void UpdateRef(mirror::Object* obj, MemberOffset offset)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Verify that the gc-root is updated only once. Returns false if the update
+  // shouldn't be done.
+  ALWAYS_INLINE bool VerifyRootSingleUpdate(void* root,
+                                            mirror::Object* old_ref,
+                                            const RootInfo& info)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Update the given root with post-compact address.
+  ALWAYS_INLINE void UpdateRoot(mirror::CompressedReference<mirror::Object>* root,
+                                const RootInfo& info = RootInfo(RootType::kRootUnknown))
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  ALWAYS_INLINE void UpdateRoot(mirror::Object** root,
+                                const RootInfo& info = RootInfo(RootType::kRootUnknown))
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Given the pre-compact address, the function returns the post-compact
+  // address of the given object.
+  ALWAYS_INLINE mirror::Object* PostCompactAddress(mirror::Object* old_ref) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Compute post-compact address of an object in moving space. This function
+  // assumes that old_ref is in moving space.
+  ALWAYS_INLINE mirror::Object* PostCompactAddressUnchecked(mirror::Object* old_ref) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Compute the new address for an object which was allocated prior to starting
+  // this GC cycle.
+  ALWAYS_INLINE mirror::Object* PostCompactOldObjAddr(mirror::Object* old_ref) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Compute the new address for an object which was black allocated during this
+  // GC cycle.
+  ALWAYS_INLINE mirror::Object* PostCompactBlackObjAddr(mirror::Object* old_ref) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Identify immune spaces and reset card-table, mod-union-table, and mark
+  // bitmaps.
+  void BindAndResetBitmaps() REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  // Perform one last round of marking, identifying roots from dirty cards
+  // during a stop-the-world (STW) pause.
+  void MarkingPause() REQUIRES(Locks::mutator_lock_, !Locks::heap_bitmap_lock_);
+  // Perform stop-the-world pause prior to concurrent compaction.
+  // Updates GC-roots and protects heap so that during the concurrent
+  // compaction phase we can receive faults and compact the corresponding pages
+  // on the fly.
+  void CompactionPause() REQUIRES(Locks::mutator_lock_);
+  // Compute offsets (in chunk_info_vec_) and other data structures required
+  // during concurrent compaction.
+  void PrepareForCompaction() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Copy kPageSize live bytes starting from 'offset' (within the moving space),
+  // which must be within 'obj', into the kPageSize sized memory pointed by 'addr'.
+  // Then update the references within the copied objects. The boundary objects are
+  // partially updated such that only the references that lie in the page are updated.
+  // This is necessary to avoid cascading userfaults.
+  void CompactPage(mirror::Object* obj, uint32_t offset, uint8_t* addr, bool needs_memset_zero)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Compact the bump-pointer space. Pass page that should be used as buffer for
+  // userfaultfd.
+  template <int kMode>
+  void CompactMovingSpace(uint8_t* page) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Compact the given page as per func and change its state. Also map/copy the
+  // page, if required.
+  template <int kMode, typename CompactionFn>
+  ALWAYS_INLINE void DoPageCompactionWithStateChange(size_t page_idx,
+                                                     size_t status_arr_len,
+                                                     uint8_t* to_space_page,
+                                                     uint8_t* page,
+                                                     CompactionFn func)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Update all the objects in the given non-moving space page. 'first' object
+  // could have started in some preceding page.
+  void UpdateNonMovingPage(mirror::Object* first, uint8_t* page)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Update all the references in the non-moving space.
+  void UpdateNonMovingSpace() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // For all the pages in non-moving space, find the first object that overlaps
+  // with the pages' start address, and store in first_objs_non_moving_space_ array.
+  void InitNonMovingSpaceFirstObjects() REQUIRES_SHARED(Locks::mutator_lock_);
+  // In addition to the first-objects for every post-compact moving space page,
+  // also find offsets within those objects from where the contents should be
+  // copied to the page. The offsets are relative to the moving-space's
+  // beginning. Store the computed first-object and offset in first_objs_moving_space_
+  // and pre_compact_offset_moving_space_ respectively.
+  void InitMovingSpaceFirstObjects(const size_t vec_len) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Gather the info related to black allocations from bump-pointer space to
+  // enable concurrent sliding of these pages.
+  void UpdateMovingSpaceBlackAllocations() REQUIRES(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+  // Update first-object info from allocation-stack for non-moving space black
+  // allocations.
+  void UpdateNonMovingSpaceBlackAllocations() REQUIRES(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+
+  // Slides (retain the empty holes, which are usually part of some in-use TLAB)
+  // black page in the moving space. 'first_obj' is the object that overlaps with
+  // the first byte of the page being slid. pre_compact_page is the pre-compact
+  // address of the page being slid. 'page_idx' is used to fetch the first
+  // allocated chunk's size and next page's first_obj. 'dest' is the kPageSize
+  // sized memory where the contents would be copied.
+  void SlideBlackPage(mirror::Object* first_obj,
+                      const size_t page_idx,
+                      uint8_t* const pre_compact_page,
+                      uint8_t* dest,
+                      bool needs_memset_zero) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Perform reference-processing and the likes before sweeping the non-movable
+  // spaces.
+  void ReclaimPhase() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::heap_bitmap_lock_);
+
+  // Mark GC-roots (except from immune spaces and thread-stacks) during a STW pause.
+  void ReMarkRoots(Runtime* runtime) REQUIRES(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+  // Concurrently mark GC-roots, except from immune spaces.
+  void MarkRoots(VisitRootFlags flags) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  // Collect thread stack roots via a checkpoint.
+  void MarkRootsCheckpoint(Thread* self, Runtime* runtime) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  // Second round of concurrent marking. Mark all gray objects that got dirtied
+  // since the first round.
+  void PreCleanCards() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
+
+  void MarkNonThreadRoots(Runtime* runtime) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  void MarkConcurrentRoots(VisitRootFlags flags, Runtime* runtime)
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
+
+  // Traverse through the reachable objects and mark them.
+  void MarkReachableObjects() REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  // Scan (only) immune spaces looking for references into the garbage collected
+  // spaces.
+  void UpdateAndMarkModUnion() REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  // Scan mod-union and card tables, covering all the spaces, to identify dirty objects.
+  // These are in 'minimum age' cards, which is 'kCardAged' in case of concurrent (second round)
+  // marking and kCardDirty during the STW pause.
+  void ScanDirtyObjects(bool paused, uint8_t minimum_age) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  // Recursively mark dirty objects. Invoked both concurrently as well in a STW
+  // pause in PausePhase().
+  void RecursiveMarkDirtyObjects(bool paused, uint8_t minimum_age)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  // Go through all the objects in the mark-stack until it's empty.
+  void ProcessMarkStack() override REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  void ExpandMarkStack() REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  // Scan object for references. If kUpdateLivewords is true then set bits in
+  // the live-words bitmap and add size to chunk-info.
+  template <bool kUpdateLiveWords>
+  void ScanObject(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  // Push objects to the mark-stack right after successfully marking objects.
+  void PushOnMarkStack(mirror::Object* obj)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  // Update the live-words bitmap as well as add the object size to the
+  // chunk-info vector. Both are required for computation of post-compact addresses.
+  // Also updates freed_objects_ counter.
+  void UpdateLivenessInfo(mirror::Object* obj, size_t obj_size)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void ProcessReferences(Thread* self)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::heap_bitmap_lock_);
+
+  void MarkObjectNonNull(mirror::Object* obj,
+                         mirror::Object* holder = nullptr,
+                         MemberOffset offset = MemberOffset(0))
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  void MarkObject(mirror::Object* obj, mirror::Object* holder, MemberOffset offset)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  template <bool kParallel>
+  bool MarkObjectNonNullNoPush(mirror::Object* obj,
+                               mirror::Object* holder = nullptr,
+                               MemberOffset offset = MemberOffset(0))
+      REQUIRES(Locks::heap_bitmap_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void Sweep(bool swap_bitmaps) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+  void SweepLargeObjects(bool swap_bitmaps) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  // Perform all kernel operations required for concurrent compaction. Includes
+  // mremap to move pre-compact pages to from-space, followed by userfaultfd
+  // registration on the moving space and linear-alloc.
+  void KernelPreparation();
+  // Called by KernelPreparation() for every memory range being prepared for
+  // userfaultfd registration.
+  void KernelPrepareRangeForUffd(uint8_t* to_addr,
+                                 uint8_t* from_addr,
+                                 size_t map_size,
+                                 int fd,
+                                 uint8_t* shadow_addr = nullptr);
+
+  void RegisterUffd(void* addr, size_t size, int mode);
+  void UnregisterUffd(uint8_t* start, size_t len);
+
+  // Called by thread-pool workers to read uffd_ and process fault events.
+  template <int kMode>
+  void ConcurrentCompaction(uint8_t* buf) REQUIRES_SHARED(Locks::mutator_lock_);
+  // Called by thread-pool workers to compact and copy/map the fault page in
+  // moving space.
+  template <int kMode>
+  void ConcurrentlyProcessMovingPage(uint8_t* fault_page,
+                                     uint8_t* buf,
+                                     size_t nr_moving_space_used_pages)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Called by thread-pool workers to process and copy/map the fault page in
+  // linear-alloc.
+  template <int kMode>
+  void ConcurrentlyProcessLinearAllocPage(uint8_t* fault_page, bool is_minor_fault)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Process concurrently all the pages in linear-alloc. Called by gc-thread.
+  void ProcessLinearAlloc() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Returns true if the moving space can be compacted using uffd's minor-fault
+  // feature.
+  bool CanCompactMovingSpaceWithMinorFault();
+
+  void FreeFromSpacePages(size_t cur_page_idx, int mode) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Maps processed pages (from moving space and linear-alloc) for uffd's
+  // minor-fault feature. We try to 'claim' all processed (and unmapped) pages
+  // contiguous to 'to_space_start'.
+  // kFirstPageMapping indicates if the first page is already claimed or not. It
+  // also indicates that the ioctl must succeed in mapping the first page.
+  template <bool kFirstPageMapping>
+  void MapProcessedPages(uint8_t* to_space_start,
+                         Atomic<PageState>* state_arr,
+                         size_t arr_idx,
+                         size_t arr_len) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  bool IsValidFd(int fd) const { return fd >= 0; }
+  // Add/update <class, obj> pair if class > obj and obj is the lowest address
+  // object of class.
+  ALWAYS_INLINE void UpdateClassAfterObjectMap(mirror::Object* obj)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Updates 'class_after_obj_map_' map by updating the keys (class) with its
+  // highest-address super-class (obtained from 'super_class_after_class_map_'),
+  // if there is any. This is to ensure we don't free from-space pages before
+  // the lowest-address obj is compacted.
+  void UpdateClassAfterObjMap();
+
+  void MarkZygoteLargeObjects() REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::heap_bitmap_lock_);
+
+  void ZeropageIoctl(void* addr, bool tolerate_eexist, bool tolerate_enoent);
+  void CopyIoctl(void* dst, void* buffer);
+  // Called after updating a linear-alloc page to either map a zero-page if the
+  // page wasn't touched during updation, or map the page via copy-ioctl. And
+  // then updates the page's state to indicate the page is mapped.
+  void MapUpdatedLinearAllocPage(uint8_t* page,
+                                 uint8_t* shadow_page,
+                                 Atomic<PageState>& state,
+                                 bool page_touched);
+
+  // For checkpoints
+  Barrier gc_barrier_;
+  // Every object inside the immune spaces is assumed to be marked.
+  ImmuneSpaces immune_spaces_;
+  // Required only when mark-stack is accessed in shared mode, which happens
+  // when collecting thread-stack roots using checkpoint. Otherwise, we use it
+  // to synchronize on updated_roots_ in debug-builds.
+  Mutex lock_;
+  accounting::ObjectStack* mark_stack_;
+  // Special bitmap wherein all the bits corresponding to an object are set.
+  // TODO: make LiveWordsBitmap encapsulated in this class rather than a
+  // pointer. We tend to access its members in performance-sensitive
+  // code-path. Also, use a single MemMap for all the GC's data structures,
+  // which we will clear in the end. This would help in limiting the number of
+  // VMAs that get created in the kernel.
+  std::unique_ptr<LiveWordsBitmap<kAlignment>> live_words_bitmap_;
+  // Track GC-roots updated so far in a GC-cycle. This is to confirm that no
+  // GC-root is updated twice.
+  // TODO: Must be replaced with an efficient mechanism eventually. Or ensure
+  // that double updation doesn't happen in the first place.
+  std::unique_ptr<std::unordered_set<void*>> updated_roots_ GUARDED_BY(lock_);
+  MemMap from_space_map_;
+  MemMap shadow_to_space_map_;
+  // Any array of live-bytes in logical chunks of kOffsetChunkSize size
+  // in the 'to-be-compacted' space.
+  MemMap info_map_;
+  // Set of page-sized buffers used for compaction. The first page is used by
+  // the GC thread. Subdequent pages are used by mutator threads in case of
+  // SIGBUS feature, and by uffd-worker threads otherwise. In the latter case
+  // the first page is also used for termination of concurrent compaction by
+  // making worker threads terminate the userfaultfd read loop.
+  MemMap compaction_buffers_map_;
+
+  class LessByArenaAddr {
+   public:
+    bool operator()(const TrackedArena* a, const TrackedArena* b) const {
+      return std::less<uint8_t*>{}(a->Begin(), b->Begin());
+    }
+  };
+
+  // Map of arenas allocated in LinearAlloc arena-pool and last non-zero page,
+  // captured during compaction pause for concurrent updates.
+  std::map<const TrackedArena*, uint8_t*, LessByArenaAddr> linear_alloc_arenas_;
+  // Set of PageStatus arrays, one per arena-pool space. It's extremely rare to
+  // have more than one, but this is to be ready for the worst case.
+  class LinearAllocSpaceData {
+   public:
+    LinearAllocSpaceData(MemMap&& shadow,
+                         MemMap&& page_status_map,
+                         uint8_t* begin,
+                         uint8_t* end,
+                         bool already_shared)
+        : shadow_(std::move(shadow)),
+          page_status_map_(std::move(page_status_map)),
+          begin_(begin),
+          end_(end),
+          already_shared_(already_shared) {}
+
+    MemMap shadow_;
+    MemMap page_status_map_;
+    uint8_t* begin_;
+    uint8_t* end_;
+    // Indicates if the linear-alloc is already MAP_SHARED.
+    bool already_shared_;
+  };
+
+  std::vector<LinearAllocSpaceData> linear_alloc_spaces_data_;
+
+  class ObjReferenceHash {
+   public:
+    uint32_t operator()(const ObjReference& ref) const {
+      return ref.AsVRegValue() >> kObjectAlignmentShift;
+    }
+  };
+
+  class ObjReferenceEqualFn {
+   public:
+    bool operator()(const ObjReference& a, const ObjReference& b) const {
+      return a.AsMirrorPtr() == b.AsMirrorPtr();
+    }
+  };
+
+  class LessByObjReference {
+   public:
+    bool operator()(const ObjReference& a, const ObjReference& b) const {
+      return std::less<mirror::Object*>{}(a.AsMirrorPtr(), b.AsMirrorPtr());
+    }
+  };
+
+  // Data structures used to track objects whose layout information is stored in later
+  // allocated classes (at higher addresses). We must be careful not to free the
+  // corresponding from-space pages prematurely.
+  using ObjObjOrderedMap = std::map<ObjReference, ObjReference, LessByObjReference>;
+  using ObjObjUnorderedMap =
+      std::unordered_map<ObjReference, ObjReference, ObjReferenceHash, ObjReferenceEqualFn>;
+  // Unordered map of <K, S> such that the class K (in moving space) has kClassWalkSuper
+  // in reference bitmap and S is its highest address super class.
+  ObjObjUnorderedMap super_class_after_class_hash_map_;
+  // Unordered map of <K, V> such that the class K (in moving space) is after its objects
+  // or would require iterating super-class hierarchy when visiting references. And V is
+  // its lowest address object (in moving space).
+  ObjObjUnorderedMap class_after_obj_hash_map_;
+  // Ordered map constructed before starting compaction using the above two maps. Key is a
+  // class (or super-class) which is higher in address order than some of its object(s) and
+  // value is the corresponding object with lowest address.
+  ObjObjOrderedMap class_after_obj_ordered_map_;
+  // Since the compaction is done in reverse, we use a reverse iterator. It is maintained
+  // either at the pair whose class is lower than the first page to be freed, or at the
+  // pair whose object is not yet compacted.
+  ObjObjOrderedMap::const_reverse_iterator class_after_obj_iter_;
+  // Cached reference to the last class which has kClassWalkSuper in reference
+  // bitmap but has all its super classes lower address order than itself.
+  mirror::Class* walk_super_class_cache_;
+  // Used by FreeFromSpacePages() for maintaining markers in the moving space for
+  // how far the pages have been reclaimed/checked.
+  size_t last_checked_reclaim_page_idx_;
+  uint8_t* last_reclaimed_page_;
+
+  space::ContinuousSpace* non_moving_space_;
+  space::BumpPointerSpace* const bump_pointer_space_;
+  // The main space bitmap
+  accounting::ContinuousSpaceBitmap* const moving_space_bitmap_;
+  accounting::ContinuousSpaceBitmap* non_moving_space_bitmap_;
+  Thread* thread_running_gc_;
+  // Array of moving-space's pages' compaction status.
+  Atomic<PageState>* moving_pages_status_;
+  size_t vector_length_;
+  size_t live_stack_freeze_size_;
+
+  uint64_t bytes_scanned_;
+
+  // For every page in the to-space (post-compact heap) we need to know the
+  // first object from which we must compact and/or update references. This is
+  // for both non-moving and moving space. Additionally, for the moving-space,
+  // we also need the offset within the object from where we need to start
+  // copying.
+  // chunk_info_vec_ holds live bytes for chunks during marking phase. After
+  // marking we perform an exclusive scan to compute offset for every chunk.
+  uint32_t* chunk_info_vec_;
+  // For pages before black allocations, pre_compact_offset_moving_space_[i]
+  // holds offset within the space from where the objects need to be copied in
+  // the ith post-compact page.
+  // Otherwise, black_alloc_pages_first_chunk_size_[i] holds the size of first
+  // non-empty chunk in the ith black-allocations page.
+  union {
+    uint32_t* pre_compact_offset_moving_space_;
+    uint32_t* black_alloc_pages_first_chunk_size_;
+  };
+  // first_objs_moving_space_[i] is the pre-compact address of the object which
+  // would overlap with the starting boundary of the ith post-compact page.
+  ObjReference* first_objs_moving_space_;
+  // First object for every page. It could be greater than the page's start
+  // address, or null if the page is empty.
+  ObjReference* first_objs_non_moving_space_;
+  size_t non_moving_first_objs_count_;
+  // Length of first_objs_moving_space_ and pre_compact_offset_moving_space_
+  // arrays. Also the number of pages which are to be compacted.
+  size_t moving_first_objs_count_;
+  // Number of pages containing black-allocated objects, indicating number of
+  // pages to be slid.
+  size_t black_page_count_;
+
+  uint8_t* from_space_begin_;
+  // moving-space's end pointer at the marking pause. All allocations beyond
+  // this will be considered black in the current GC cycle. Aligned up to page
+  // size.
+  uint8_t* black_allocations_begin_;
+  // End of compacted space. Use for computing post-compact addr of black
+  // allocated objects. Aligned up to page size.
+  uint8_t* post_compact_end_;
+  // Cache (black_allocations_begin_ - post_compact_end_) for post-compact
+  // address computations.
+  ptrdiff_t black_objs_slide_diff_;
+  // Cache (from_space_begin_ - bump_pointer_space_->Begin()) so that we can
+  // compute from-space address of a given pre-comapct addr efficiently.
+  ptrdiff_t from_space_slide_diff_;
+
+  // TODO: Remove once an efficient mechanism to deal with double root updation
+  // is incorporated.
+  void* stack_high_addr_;
+  void* stack_low_addr_;
+
+  uint8_t* conc_compaction_termination_page_;
+
+  PointerSize pointer_size_;
+  // Number of objects freed during this GC in moving space. It is decremented
+  // every time an object is discovered. And total-object count is added to it
+  // in MarkingPause(). It reaches the correct count only once the marking phase
+  // is completed.
+  int32_t freed_objects_;
+  // memfds for moving space for using userfaultfd's minor-fault feature.
+  // Initialized to kFdUnused to indicate that mmap should be MAP_PRIVATE in
+  // KernelPrepareRange().
+  int moving_to_space_fd_;
+  int moving_from_space_fd_;
+  // Userfault file descriptor, accessed only by the GC itself.
+  // kFallbackMode value indicates that we are in the fallback mode.
+  int uffd_;
+  // Number of mutator-threads currently executing SIGBUS handler. When the
+  // GC-thread is done with compaction, it set the most significant bit to
+  // indicate that. Mutator threads check for the flag when incrementing in the
+  // handler.
+  std::atomic<SigbusCounterType> sigbus_in_progress_count_;
+  // Number of mutator-threads/uffd-workers working on moving-space page. It
+  // must be 0 before gc-thread can unregister the space after it's done
+  // sequentially compacting all pages of the space.
+  std::atomic<uint16_t> compaction_in_progress_count_;
+  // When using SIGBUS feature, this counter is used by mutators to claim a page
+  // out of compaction buffers to be used for the entire compaction cycle.
+  std::atomic<uint16_t> compaction_buffer_counter_;
+  // Used to exit from compaction loop at the end of concurrent compaction
+  uint8_t thread_pool_counter_;
+  // True while compacting.
+  bool compacting_;
+  // Flag indicating whether one-time uffd initialization has been done. It will
+  // be false on the first GC for non-zygote processes, and always for zygote.
+  // Its purpose is to minimize the userfaultfd overhead to the minimal in
+  // Heap::PostForkChildAction() as it's invoked in app startup path. With
+  // this, we register the compaction-termination page on the first GC.
+  bool uffd_initialized_;
+  // Flag indicating if userfaultfd supports minor-faults. Set appropriately in
+  // CreateUserfaultfd(), where we get this information from the kernel.
+  const bool uffd_minor_fault_supported_;
+  // Flag indicating if we should use sigbus signals instead of threads to
+  // handle userfaults.
+  const bool use_uffd_sigbus_;
+  // For non-zygote processes this flag indicates if the spaces are ready to
+  // start using userfaultfd's minor-fault feature. This initialization involves
+  // starting to use shmem (memfd_create) for the userfaultfd protected spaces.
+  bool minor_fault_initialized_;
+  // Set to true when linear-alloc can start mapping with MAP_SHARED. Set on
+  // non-zygote processes during first GC, which sets up everyting for using
+  // minor-fault from next GC.
+  bool map_linear_alloc_shared_;
+
+  class FlipCallback;
+  class ThreadFlipVisitor;
+  class VerifyRootMarkedVisitor;
+  class ScanObjectVisitor;
+  class CheckpointMarkThreadRoots;
+  template<size_t kBufferSize> class ThreadRootsVisitor;
+  class CardModifiedVisitor;
+  class RefFieldsVisitor;
+  template <bool kCheckBegin, bool kCheckEnd> class RefsUpdateVisitor;
+  class ArenaPoolPageUpdater;
+  class ClassLoaderRootsUpdater;
+  class LinearAllocPageUpdater;
+  class ImmuneSpaceUpdateObjVisitor;
+  class ConcurrentCompactionGcTask;
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(MarkCompact);
+};
+
+std::ostream& operator<<(std::ostream& os, MarkCompact::PageState value);
+
+}  // namespace collector
+}  // namespace gc
+}  // namespace art
+
+#endif  // ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_H_
diff --git a/runtime/gc/collector/mark_sweep.cc b/runtime/gc/collector/mark_sweep.cc
index bd5ce37..4fefe65 100644
--- a/runtime/gc/collector/mark_sweep.cc
+++ b/runtime/gc/collector/mark_sweep.cc
@@ -340,6 +340,8 @@
   Thread* const self = Thread::Current();
   // Process the references concurrently.
   ProcessReferences(self);
+  // There is no need to sweep interpreter caches as this GC doesn't move
+  // objects and hence would be a nop.
   SweepSystemWeaks(self);
   Runtime* const runtime = Runtime::Current();
   runtime->AllowNewSystemWeaks();
@@ -1127,7 +1129,8 @@
   TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
   // Verify system weaks, uses a special object visitor which returns the input object.
   VerifySystemWeakVisitor visitor(this);
-  Runtime::Current()->SweepSystemWeaks(&visitor);
+  Runtime* runtime = Runtime::Current();
+  runtime->SweepSystemWeaks(&visitor);
 }
 
 class MarkSweep::CheckpointMarkThreadRoots : public Closure, public RootVisitor {
@@ -1455,6 +1458,8 @@
   if (current_space_bitmap_->HasAddress(object)) {
     return current_space_bitmap_->Test(object) ? object : nullptr;
   }
+  // This function returns nullptr for objects allocated after marking phase as
+  // they are not marked in the bitmap.
   return mark_bitmap_->Test(object) ? object : nullptr;
 }
 
diff --git a/runtime/gc/collector/mark_sweep.h b/runtime/gc/collector/mark_sweep.h
index 6af7c54..12fd7f9 100644
--- a/runtime/gc/collector/mark_sweep.h
+++ b/runtime/gc/collector/mark_sweep.h
@@ -181,7 +181,7 @@
       REQUIRES_SHARED(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
 
   void VerifySystemWeaks()
-      REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+      REQUIRES(Locks::mutator_lock_) REQUIRES_SHARED(Locks::heap_bitmap_lock_);
 
   // Verify that an object is live, either in a live bitmap or in the allocation stack.
   void VerifyIsLive(const mirror::Object* obj)
diff --git a/runtime/gc/collector/partial_mark_sweep.cc b/runtime/gc/collector/partial_mark_sweep.cc
index f6ca867..e283a95 100644
--- a/runtime/gc/collector/partial_mark_sweep.cc
+++ b/runtime/gc/collector/partial_mark_sweep.cc
@@ -18,7 +18,6 @@
 
 #include "gc/heap.h"
 #include "gc/space/space.h"
-#include "partial_mark_sweep.h"
 #include "thread-current-inl.h"
 
 namespace art {
diff --git a/runtime/gc/collector/semi_space.cc b/runtime/gc/collector/semi_space.cc
index 53b0604..acd4807 100644
--- a/runtime/gc/collector/semi_space.cc
+++ b/runtime/gc/collector/semi_space.cc
@@ -500,7 +500,9 @@
 
 void SemiSpace::SweepSystemWeaks() {
   TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
-  Runtime::Current()->SweepSystemWeaks(this);
+  Runtime* runtime = Runtime::Current();
+  runtime->SweepSystemWeaks(this);
+  runtime->GetThreadList()->SweepInterpreterCaches(this);
 }
 
 bool SemiSpace::ShouldSweepSpace(space::ContinuousSpace* space) const {
diff --git a/runtime/gc/collector/semi_space.h b/runtime/gc/collector/semi_space.h
index 245ea10..6d3ac08 100644
--- a/runtime/gc/collector/semi_space.h
+++ b/runtime/gc/collector/semi_space.h
@@ -143,7 +143,7 @@
   void SweepLargeObjects(bool swap_bitmaps) REQUIRES(Locks::heap_bitmap_lock_);
 
   void SweepSystemWeaks()
-      REQUIRES_SHARED(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
+      REQUIRES_SHARED(Locks::heap_bitmap_lock_) REQUIRES(Locks::mutator_lock_);
 
   void VisitRoots(mirror::Object*** roots, size_t count, const RootInfo& info) override
       REQUIRES(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
diff --git a/runtime/gc/collector_type.h b/runtime/gc/collector_type.h
index 9c99964..c20e3a73 100644
--- a/runtime/gc/collector_type.h
+++ b/runtime/gc/collector_type.h
@@ -30,6 +30,8 @@
   kCollectorTypeMS,
   // Concurrent mark-sweep.
   kCollectorTypeCMS,
+  // Concurrent mark-compact.
+  kCollectorTypeCMC,
   // Semi-space / mark-sweep hybrid, enables compaction.
   kCollectorTypeSS,
   // Heap trimming collector, doesn't do any actual collecting.
@@ -63,12 +65,13 @@
 std::ostream& operator<<(std::ostream& os, CollectorType collector_type);
 
 static constexpr CollectorType kCollectorTypeDefault =
-#if ART_DEFAULT_GC_TYPE_IS_CMS
-    kCollectorTypeCMS
+#if ART_DEFAULT_GC_TYPE_IS_CMC
+    kCollectorTypeCMC
 #elif ART_DEFAULT_GC_TYPE_IS_SS
     kCollectorTypeSS
-#else
+#elif ART_DEFAULT_GC_TYPE_IS_CMS
     kCollectorTypeCMS
+#else
 #error "ART default GC type must be set"
 #endif
     ;  // NOLINT [whitespace/semicolon] [5]
diff --git a/runtime/gc/gc_cause.cc b/runtime/gc/gc_cause.cc
index b197a99..02fe2f9 100644
--- a/runtime/gc/gc_cause.cc
+++ b/runtime/gc/gc_cause.cc
@@ -46,7 +46,7 @@
     case kGcCauseHprof: return "Hprof";
     case kGcCauseGetObjectsAllocated: return "ObjectsAllocated";
     case kGcCauseProfileSaver: return "ProfileSaver";
-    case kGcCauseRunEmptyCheckpoint: return "RunEmptyCheckpoint";
+    case kGcCauseDeletingDexCacheArrays: return "DeletingDexCacheArrays";
   }
   LOG(FATAL) << "Unreachable";
   UNREACHABLE();
diff --git a/runtime/gc/gc_cause.h b/runtime/gc/gc_cause.h
index 4dae585..5c039b3 100644
--- a/runtime/gc/gc_cause.h
+++ b/runtime/gc/gc_cause.h
@@ -62,8 +62,8 @@
   kGcCauseGetObjectsAllocated,
   // GC cause for the profile saver.
   kGcCauseProfileSaver,
-  // GC cause for running an empty checkpoint.
-  kGcCauseRunEmptyCheckpoint,
+  // GC cause for deleting dex cache arrays at startup.
+  kGcCauseDeletingDexCacheArrays,
 };
 
 const char* PrettyCause(GcCause cause);
diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h
index 9e1524e..922b588 100644
--- a/runtime/gc/heap-inl.h
+++ b/runtime/gc/heap-inl.h
@@ -209,13 +209,12 @@
       }
       // IsGcConcurrent() isn't known at compile time so we can optimize by not checking it for the
       // BumpPointer or TLAB allocators. This is nice since it allows the entire if statement to be
-      // optimized out. And for the other allocators, AllocatorMayHaveConcurrentGC is a constant
-      // since the allocator_type should be constant propagated.
-      if (AllocatorMayHaveConcurrentGC(allocator) && IsGcConcurrent()
-          && UNLIKELY(ShouldConcurrentGCForJava(new_num_bytes_allocated))) {
+      // optimized out.
+      if (IsGcConcurrent() && UNLIKELY(ShouldConcurrentGCForJava(new_num_bytes_allocated))) {
         need_gc = true;
       }
       GetMetrics()->TotalBytesAllocated()->Add(bytes_tl_bulk_allocated);
+      GetMetrics()->TotalBytesAllocatedDelta()->Add(bytes_tl_bulk_allocated);
     }
   }
   if (kIsDebugBuild && Runtime::Current()->IsStarted()) {
@@ -442,7 +441,7 @@
   return byte_count >= large_object_threshold_ && (c->IsPrimitiveArray() || c->IsStringClass());
 }
 
-inline bool Heap::IsOutOfMemoryOnAllocation(AllocatorType allocator_type,
+inline bool Heap::IsOutOfMemoryOnAllocation(AllocatorType allocator_type ATTRIBUTE_UNUSED,
                                             size_t alloc_size,
                                             bool grow) {
   size_t old_target = target_footprint_.load(std::memory_order_relaxed);
@@ -457,7 +456,7 @@
       return true;
     }
     // We are between target_footprint_ and growth_limit_ .
-    if (AllocatorMayHaveConcurrentGC(allocator_type) && IsGcConcurrent()) {
+    if (IsGcConcurrent()) {
       return false;
     } else {
       if (grow) {
diff --git a/runtime/gc/heap-visit-objects-inl.h b/runtime/gc/heap-visit-objects-inl.h
index e20d981..a235c44 100644
--- a/runtime/gc/heap-visit-objects-inl.h
+++ b/runtime/gc/heap-visit-objects-inl.h
@@ -118,7 +118,7 @@
       // For speed reasons, only perform it when Rosalloc could possibly be used.
       // (Disabled for read barriers because it never uses Rosalloc).
       // (See the DCHECK in RosAllocSpace constructor).
-      if (!kUseReadBarrier) {
+      if (!gUseReadBarrier) {
         // Rosalloc has a race in allocation. Objects can be written into the allocation
         // stack before their header writes are visible to this thread.
         // See b/28790624 for more details.
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 8407ba4..f27bddb 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -21,10 +21,6 @@
 #if defined(__BIONIC__) || defined(__GLIBC__)
 #include <malloc.h>  // For mallinfo()
 #endif
-#if defined(__BIONIC__) && defined(ART_TARGET)
-#include <linux/userfaultfd.h>
-#include <sys/ioctl.h>
-#endif
 #include <memory>
 #include <random>
 #include <unistd.h>
@@ -61,6 +57,7 @@
 #include "gc/accounting/remembered_set.h"
 #include "gc/accounting/space_bitmap-inl.h"
 #include "gc/collector/concurrent_copying.h"
+#include "gc/collector/mark_compact.h"
 #include "gc/collector/mark_sweep.h"
 #include "gc/collector/partial_mark_sweep.h"
 #include "gc/collector/semi_space.h"
@@ -106,6 +103,7 @@
 #include "runtime.h"
 #include "javaheapprof/javaheapsampler.h"
 #include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
 #include "thread_list.h"
 #include "verify_object-inl.h"
 #include "well_known_classes.h"
@@ -339,6 +337,7 @@
       // this one.
       process_state_update_lock_("process state update lock", kPostMonitorLock),
       min_foreground_target_footprint_(0),
+      min_foreground_concurrent_start_bytes_(0),
       concurrent_start_bytes_(std::numeric_limits<size_t>::max()),
       total_bytes_freed_ever_(0),
       total_objects_freed_ever_(0),
@@ -410,7 +409,6 @@
       backtrace_lock_(nullptr),
       seen_backtrace_count_(0u),
       unique_backtrace_count_(0u),
-      uffd_(-1),
       gc_disabled_for_shutdown_(false),
       dump_region_info_before_gc_(dump_region_info_before_gc),
       dump_region_info_after_gc_(dump_region_info_after_gc),
@@ -421,7 +419,19 @@
   if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
     LOG(INFO) << "Heap() entering";
   }
-  if (kUseReadBarrier) {
+
+  LOG(INFO) << "Using " << foreground_collector_type_ << " GC.";
+  if (!gUseUserfaultfd) {
+    // This ensures that userfaultfd syscall is done before any seccomp filter is installed.
+    // TODO(b/266731037): Remove this when we no longer need to collect metric on userfaultfd
+    // support.
+    auto [uffd_supported, minor_fault_supported] = collector::MarkCompact::GetUffdAndMinorFault();
+    // The check is just to ensure that compiler doesn't eliminate the function call above.
+    // Userfaultfd support is certain to be there if its minor-fault feature is supported.
+    CHECK_IMPLIES(minor_fault_supported, uffd_supported);
+  }
+
+  if (gUseReadBarrier) {
     CHECK_EQ(foreground_collector_type_, kCollectorTypeCC);
     CHECK_EQ(background_collector_type_, kCollectorTypeCCBackground);
   } else if (background_collector_type_ != gc::kCollectorTypeHomogeneousSpaceCompact) {
@@ -448,7 +458,8 @@
   mark_bitmap_.reset(new accounting::HeapBitmap(this));
 
   // We don't have hspace compaction enabled with CC.
-  if (foreground_collector_type_ == kCollectorTypeCC) {
+  if (foreground_collector_type_ == kCollectorTypeCC
+      || foreground_collector_type_ == kCollectorTypeCMC) {
     use_homogeneous_space_compaction_for_oom_ = false;
   }
   bool support_homogeneous_space_compaction =
@@ -486,6 +497,7 @@
                                        runtime->ShouldRelocate(),
                                        /*executable=*/ !runtime->IsAotCompiler(),
                                        heap_reservation_size,
+                                       runtime->AllowInMemoryCompilation(),
                                        &boot_image_spaces,
                                        &heap_reservation)) {
     DCHECK_EQ(heap_reservation_size, heap_reservation.IsValid() ? heap_reservation.Size() : 0u);
@@ -629,10 +641,14 @@
                                                                     std::move(main_mem_map_1));
     CHECK(bump_pointer_space_ != nullptr) << "Failed to create bump pointer space";
     AddSpace(bump_pointer_space_);
-    temp_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 2",
-                                                            std::move(main_mem_map_2));
-    CHECK(temp_space_ != nullptr) << "Failed to create bump pointer space";
-    AddSpace(temp_space_);
+    // For Concurrent Mark-compact GC we don't need the temp space to be in
+    // lower 4GB. So its temp space will be created by the GC itself.
+    if (foreground_collector_type_ != kCollectorTypeCMC) {
+      temp_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 2",
+                                                              std::move(main_mem_map_2));
+      CHECK(temp_space_ != nullptr) << "Failed to create bump pointer space";
+      AddSpace(temp_space_);
+    }
     CHECK(separate_non_moving_space);
   } else {
     CreateMainMallocSpace(std::move(main_mem_map_1), initial_size, growth_limit_, capacity_);
@@ -758,6 +774,10 @@
       semi_space_collector_ = new collector::SemiSpace(this);
       garbage_collectors_.push_back(semi_space_collector_);
     }
+    if (MayUseCollector(kCollectorTypeCMC)) {
+      mark_compact_ = new collector::MarkCompact(this);
+      garbage_collectors_.push_back(mark_compact_);
+    }
     if (MayUseCollector(kCollectorTypeCC)) {
       concurrent_copying_collector_ = new collector::ConcurrentCopying(this,
                                                                        /*young_gen=*/false,
@@ -963,7 +983,6 @@
 
 void Heap::IncrementDisableThreadFlip(Thread* self) {
   // Supposed to be called by mutators. If thread_flip_running_ is true, block. Otherwise, go ahead.
-  CHECK(kUseReadBarrier);
   bool is_nested = self->GetDisableThreadFlipCount() > 0;
   self->IncrementDisableThreadFlipCount();
   if (is_nested) {
@@ -994,10 +1013,23 @@
   }
 }
 
+void Heap::EnsureObjectUserfaulted(ObjPtr<mirror::Object> obj) {
+  if (gUseUserfaultfd) {
+    // Use volatile to ensure that compiler loads from memory to trigger userfaults, if required.
+    const uint8_t* start = reinterpret_cast<uint8_t*>(obj.Ptr());
+    const uint8_t* end = AlignUp(start + obj->SizeOf(), kPageSize);
+    // The first page is already touched by SizeOf().
+    start += kPageSize;
+    while (start < end) {
+      ForceRead(start);
+      start += kPageSize;
+    }
+  }
+}
+
 void Heap::DecrementDisableThreadFlip(Thread* self) {
   // Supposed to be called by mutators. Decrement disable_thread_flip_count_ and potentially wake up
   // the GC waiting before doing a thread flip.
-  CHECK(kUseReadBarrier);
   self->DecrementDisableThreadFlipCount();
   bool is_outermost = self->GetDisableThreadFlipCount() == 0;
   if (!is_outermost) {
@@ -1017,7 +1049,6 @@
 void Heap::ThreadFlipBegin(Thread* self) {
   // Supposed to be called by GC. Set thread_flip_running_ to be true. If disable_thread_flip_count_
   // > 0, block. Otherwise, go ahead.
-  CHECK(kUseReadBarrier);
   ScopedThreadStateChange tsc(self, ThreadState::kWaitingForGcThreadFlip);
   MutexLock mu(self, *thread_flip_lock_);
   thread_flip_cond_->CheckSafeToWait(self);
@@ -1043,7 +1074,6 @@
 void Heap::ThreadFlipEnd(Thread* self) {
   // Supposed to be called by GC. Set thread_flip_running_ to false and potentially wake up mutators
   // waiting before doing a JNI critical.
-  CHECK(kUseReadBarrier);
   MutexLock mu(self, *thread_flip_lock_);
   CHECK(thread_flip_running_);
   thread_flip_running_ = false;
@@ -1059,7 +1089,9 @@
                                               min_foreground_target_footprint_,
                                               std::memory_order_relaxed);
   }
-  min_foreground_target_footprint_ = 0;
+  if (IsGcConcurrent() && concurrent_start_bytes_ < min_foreground_concurrent_start_bytes_) {
+    concurrent_start_bytes_ = min_foreground_concurrent_start_bytes_;
+  }
 }
 
 void Heap::UpdateProcessState(ProcessState old_process_state, ProcessState new_process_state) {
@@ -1070,26 +1102,32 @@
       RequestCollectorTransition(foreground_collector_type_, 0);
       GrowHeapOnJankPerceptibleSwitch();
     } else {
-      // Don't delay for debug builds since we may want to stress test the GC.
       // If background_collector_type_ is kCollectorTypeHomogeneousSpaceCompact then we have
       // special handling which does a homogenous space compaction once but then doesn't transition
       // the collector. Similarly, we invoke a full compaction for kCollectorTypeCC but don't
       // transition the collector.
-      RequestCollectorTransition(background_collector_type_,
-                                 kStressCollectorTransition
-                                     ? 0
-                                     : kCollectorTransitionWait);
+      RequestCollectorTransition(background_collector_type_, 0);
     }
   }
 }
 
-void Heap::CreateThreadPool() {
-  const size_t num_threads = std::max(parallel_gc_threads_, conc_gc_threads_);
+void Heap::CreateThreadPool(size_t num_threads) {
+  if (num_threads == 0) {
+    num_threads = std::max(parallel_gc_threads_, conc_gc_threads_);
+  }
   if (num_threads != 0) {
     thread_pool_.reset(new ThreadPool("Heap thread pool", num_threads));
   }
 }
 
+void Heap::WaitForWorkersToBeCreated() {
+  DCHECK(!Runtime::Current()->IsShuttingDown(Thread::Current()))
+      << "Cannot create new threads during runtime shutdown";
+  if (thread_pool_ != nullptr) {
+    thread_pool_->WaitForWorkersToBeCreated();
+  }
+}
+
 void Heap::MarkAllocStackAsLive(accounting::ObjectStack* stack) {
   space::ContinuousSpace* space1 = main_space_ != nullptr ? main_space_ : non_moving_space_;
   space::ContinuousSpace* space2 = non_moving_space_;
@@ -1451,6 +1489,8 @@
         Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow());
     return;
   }
+  // Allow plugins to intercept out of memory errors.
+  Runtime::Current()->OutOfMemoryErrorHook();
 
   std::ostringstream oss;
   size_t total_bytes_free = GetFreeMemory();
@@ -1497,6 +1537,23 @@
 
 void Heap::DoPendingCollectorTransition() {
   CollectorType desired_collector_type = desired_collector_type_;
+
+  if (collector_type_ == kCollectorTypeCC || collector_type_ == kCollectorTypeCMC) {
+    // App's allocations (since last GC) more than the threshold then do TransitionGC
+    // when the app was in background. If not then don't do TransitionGC.
+    // num_bytes_allocated_since_gc should always be positive even if initially
+    // num_bytes_alive_after_gc_ is coming from Zygote. This gives positive or zero value.
+    size_t num_bytes_allocated_since_gc =
+        UnsignedDifference(GetBytesAllocated(), num_bytes_alive_after_gc_);
+    if (num_bytes_allocated_since_gc <
+        (UnsignedDifference(target_footprint_.load(std::memory_order_relaxed),
+                            num_bytes_alive_after_gc_)/4)
+        && !kStressCollectorTransition
+        && !IsLowMemoryMode()) {
+      return;
+    }
+  }
+
   // Launch homogeneous space compaction if it is desired.
   if (desired_collector_type == kCollectorTypeHomogeneousSpaceCompact) {
     if (!CareAboutPauseTimes()) {
@@ -1504,15 +1561,15 @@
     } else {
       VLOG(gc) << "Homogeneous compaction ignored due to jank perceptible process state";
     }
-  } else if (desired_collector_type == kCollectorTypeCCBackground) {
-    DCHECK(kUseReadBarrier);
+  } else if (desired_collector_type == kCollectorTypeCCBackground ||
+             desired_collector_type == kCollectorTypeCMC) {
     if (!CareAboutPauseTimes()) {
-      // Invoke CC full compaction.
+      // Invoke full compaction.
       CollectGarbageInternal(collector::kGcTypeFull,
                              kGcCauseCollectorTransition,
-                             /*clear_soft_references=*/false, GC_NUM_ANY);
+                             /*clear_soft_references=*/false, GetCurrentGcNum() + 1);
     } else {
-      VLOG(gc) << "CC background compaction ignored due to jank perceptible process state";
+      VLOG(gc) << "background compaction ignored due to jank perceptible process state";
     }
   } else {
     CHECK_EQ(desired_collector_type, collector_type_) << "Unsupported collector transition";
@@ -1761,7 +1818,7 @@
 
 void Heap::VerifyHeap() {
   ReaderMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
-  auto visitor = [&](mirror::Object* obj) {
+  auto visitor = [&](mirror::Object* obj) NO_THREAD_SAFETY_ANALYSIS {
     VerifyObjectBody(obj);
   };
   // Technically we need the mutator lock here to call Visit. However, VerifyObjectBody is already
@@ -2199,6 +2256,15 @@
         }
         break;
       }
+      case kCollectorTypeCMC: {
+        gc_plan_.push_back(collector::kGcTypeFull);
+        if (use_tlab_) {
+          ChangeAllocator(kAllocatorTypeTLAB);
+        } else {
+          ChangeAllocator(kAllocatorTypeBumpPointer);
+        }
+        break;
+      }
       case kCollectorTypeSS: {
         gc_plan_.push_back(collector::kGcTypeFull);
         if (use_tlab_) {
@@ -2368,18 +2434,16 @@
   }
   // We need to close userfaultfd fd for app/webview zygotes to avoid getattr
   // (stat) on the fd during fork.
-  if (uffd_ >= 0) {
-    close(uffd_);
-    uffd_ = -1;
-  }
   Thread* self = Thread::Current();
   MutexLock mu(self, zygote_creation_lock_);
   // Try to see if we have any Zygote spaces.
   if (HasZygoteSpace()) {
     return;
   }
-  Runtime::Current()->GetInternTable()->AddNewTable();
-  Runtime::Current()->GetClassLinker()->MoveClassTableToPreZygote();
+  Runtime* runtime = Runtime::Current();
+  runtime->GetInternTable()->AddNewTable();
+  runtime->GetClassLinker()->MoveClassTableToPreZygote();
+  runtime->SetupLinearAllocForPostZygoteFork(self);
   VLOG(heap) << "Starting PreZygoteFork";
   // The end of the non-moving space may be protected, unprotect it so that we can copy the zygote
   // there.
@@ -2488,7 +2552,7 @@
       new accounting::ModUnionTableCardCache("zygote space mod-union table", this, zygote_space_);
   CHECK(mod_union_table != nullptr) << "Failed to create zygote space mod-union table";
 
-  if (collector_type_ != kCollectorTypeCC) {
+  if (collector_type_ != kCollectorTypeCC && collector_type_ != kCollectorTypeCMC) {
     // Set all the cards in the mod-union table since we don't know which objects contain references
     // to large objects.
     mod_union_table->SetCards();
@@ -2500,10 +2564,10 @@
     mod_union_table->ProcessCards();
     mod_union_table->ClearTable();
 
-    // For CC we never collect zygote large objects. This means we do not need to set the cards for
-    // the zygote mod-union table and we can also clear all of the existing image mod-union tables.
-    // The existing mod-union tables are only for image spaces and may only reference zygote and
-    // image objects.
+    // For CC and CMC we never collect zygote large objects. This means we do not need to set the
+    // cards for the zygote mod-union table and we can also clear all of the existing image
+    // mod-union tables. The existing mod-union tables are only for image spaces and may only
+    // reference zygote and image objects.
     for (auto& pair : mod_union_tables_) {
       CHECK(pair.first->IsImageSpace());
       CHECK(!pair.first->AsImageSpace()->GetImageHeader().IsAppImage());
@@ -2710,6 +2774,9 @@
         semi_space_collector_->SetSwapSemiSpaces(true);
         collector = semi_space_collector_;
         break;
+      case kCollectorTypeCMC:
+        collector = mark_compact_;
+        break;
       case kCollectorTypeCC:
         collector::ConcurrentCopying* active_cc_collector;
         if (use_generational_cc_) {
@@ -2728,7 +2795,9 @@
       default:
         LOG(FATAL) << "Invalid collector type " << static_cast<size_t>(collector_type_);
     }
-    if (collector != active_concurrent_copying_collector_.load(std::memory_order_relaxed)) {
+    // temp_space_ will be null for kCollectorTypeCMC.
+    if (temp_space_ != nullptr
+        && collector != active_concurrent_copying_collector_.load(std::memory_order_relaxed)) {
       temp_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
       if (kIsDebugBuild) {
         // Try to read each page of the memory map in case mprotect didn't work properly b/19894268.
@@ -3561,6 +3630,15 @@
 void Heap::DumpForSigQuit(std::ostream& os) {
   os << "Heap: " << GetPercentFree() << "% free, " << PrettySize(GetBytesAllocated()) << "/"
      << PrettySize(GetTotalMemory()) << "; " << GetObjectsAllocated() << " objects\n";
+  {
+    os << "Image spaces:\n";
+    ScopedObjectAccess soa(Thread::Current());
+    for (const auto& space : continuous_spaces_) {
+      if (space->IsImageSpace()) {
+        os << space->GetName() << "\n";
+      }
+    }
+  }
   DumpGcPerformanceInfo(os);
 }
 
@@ -3680,7 +3758,9 @@
     // process-state switch.
     min_foreground_target_footprint_ =
         (multiplier <= 1.0 && grow_bytes > 0)
-        ? bytes_allocated + static_cast<size_t>(grow_bytes * foreground_heap_growth_multiplier_)
+        ? std::min(
+          bytes_allocated + static_cast<size_t>(grow_bytes * foreground_heap_growth_multiplier_),
+          GetMaxMemory())
         : 0;
 
     if (IsGcConcurrent()) {
@@ -3712,6 +3792,12 @@
       // allocation rate is very high, remaining_bytes could tell us that we should start a GC
       // right away.
       concurrent_start_bytes_ = std::max(target_footprint - remaining_bytes, bytes_allocated);
+      // Store concurrent_start_bytes_ (computed with foreground heap growth multiplier) for update
+      // itself when process state switches to foreground.
+      min_foreground_concurrent_start_bytes_ =
+          min_foreground_target_footprint_ != 0
+          ? std::max(min_foreground_target_footprint_ - remaining_bytes, bytes_allocated)
+          : 0;
     }
   }
 }
@@ -3762,12 +3848,11 @@
 
 void Heap::AddFinalizerReference(Thread* self, ObjPtr<mirror::Object>* object) {
   ScopedObjectAccess soa(self);
-  ScopedLocalRef<jobject> arg(self->GetJniEnv(), soa.AddLocalReference<jobject>(*object));
-  jvalue args[1];
-  args[0].l = arg.get();
-  InvokeWithJValues(soa, nullptr, WellKnownClasses::java_lang_ref_FinalizerReference_add, args);
-  // Restore object in case it gets moved.
-  *object = soa.Decode<mirror::Object>(arg.get());
+  StackHandleScope<1u> hs(self);
+  // Use handle wrapper to update the `*object` if the object gets moved.
+  HandleWrapperObjPtr<mirror::Object> h_object = hs.NewHandleWrapper(object);
+  WellKnownClasses::java_lang_ref_FinalizerReference_add->InvokeStatic<'V', 'L'>(
+      self, h_object.Get());
 }
 
 void Heap::RequestConcurrentGCAndSaveObject(Thread* self,
@@ -3829,70 +3914,6 @@
   return true;  // Vacuously.
 }
 
-#if defined(__BIONIC__) && defined(ART_TARGET)
-void Heap::MaybePerformUffdIoctls(GcCause cause, uint32_t requested_gc_num) const {
-  if (uffd_ >= 0
-      && cause == kGcCauseBackground
-      && (requested_gc_num < 5 || requested_gc_num % 5 == 0)) {
-    // Attempt to use all userfaultfd ioctls that we intend to use.
-    // Register ioctl
-    {
-      struct uffdio_register uffd_register;
-      uffd_register.range.start = 0;
-      uffd_register.range.len = 0;
-      uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING;
-      int ret = ioctl(uffd_, UFFDIO_REGISTER, &uffd_register);
-      CHECK_EQ(ret, -1);
-      CHECK_EQ(errno, EINVAL);
-    }
-    // Copy ioctl
-    {
-      struct uffdio_copy uffd_copy = {.src = 0, .dst = 0, .len = 0, .mode = 0};
-      int ret = ioctl(uffd_, UFFDIO_COPY, &uffd_copy);
-      CHECK_EQ(ret, -1);
-      CHECK_EQ(errno, EINVAL);
-    }
-    // Zeropage ioctl
-    {
-      struct uffdio_zeropage uffd_zeropage;
-      uffd_zeropage.range.start = 0;
-      uffd_zeropage.range.len = 0;
-      uffd_zeropage.mode = 0;
-      int ret = ioctl(uffd_, UFFDIO_ZEROPAGE, &uffd_zeropage);
-      CHECK_EQ(ret, -1);
-      CHECK_EQ(errno, EINVAL);
-    }
-    // Continue ioctl
-    {
-      struct uffdio_continue uffd_continue;
-      uffd_continue.range.start = 0;
-      uffd_continue.range.len = 0;
-      uffd_continue.mode = 0;
-      int ret = ioctl(uffd_, UFFDIO_CONTINUE, &uffd_continue);
-      CHECK_EQ(ret, -1);
-      CHECK_EQ(errno, EINVAL);
-    }
-    // Wake ioctl
-    {
-      struct uffdio_range uffd_range = {.start = 0, .len = 0};
-      int ret = ioctl(uffd_, UFFDIO_WAKE, &uffd_range);
-      CHECK_EQ(ret, -1);
-      CHECK_EQ(errno, EINVAL);
-    }
-    // Unregister ioctl
-    {
-      struct uffdio_range uffd_range = {.start = 0, .len = 0};
-      int ret = ioctl(uffd_, UFFDIO_UNREGISTER, &uffd_range);
-      CHECK_EQ(ret, -1);
-      CHECK_EQ(errno, EINVAL);
-    }
-  }
-}
-#else
-void Heap::MaybePerformUffdIoctls(GcCause cause ATTRIBUTE_UNUSED,
-                                  uint32_t requested_gc_num ATTRIBUTE_UNUSED) const {}
-#endif
-
 void Heap::ConcurrentGC(Thread* self, GcCause cause, bool force_full, uint32_t requested_gc_num) {
   if (!Runtime::Current()->IsShuttingDown(self)) {
     // Wait for any GCs currently running to finish. If this incremented GC number, we're done.
@@ -3905,7 +3926,7 @@
       }
       // If we can't run the GC type we wanted to run, find the next appropriate one and try
       // that instead. E.g. can't do partial, so do full instead.
-      // We must ensure that we run something that ends up inrementing gcs_completed_.
+      // We must ensure that we run something that ends up incrementing gcs_completed_.
       // In the kGcTypePartial case, the initial CollectGarbageInternal call may not have that
       // effect, but the subsequent KGcTypeFull call will.
       if (CollectGarbageInternal(next_gc_type, cause, false, requested_gc_num)
@@ -3919,12 +3940,9 @@
           if (gc_type > next_gc_type &&
               CollectGarbageInternal(gc_type, cause, false, requested_gc_num)
               != collector::kGcTypeNone) {
-            MaybePerformUffdIoctls(cause, requested_gc_num);
             break;
           }
         }
-      } else {
-        MaybePerformUffdIoctls(cause, requested_gc_num);
       }
     }
   }
@@ -3956,16 +3974,6 @@
     // For CC, we invoke a full compaction when going to the background, but the collector type
     // doesn't change.
     DCHECK_EQ(desired_collector_type_, kCollectorTypeCCBackground);
-    // App's allocations (since last GC) more than the threshold then do TransitionGC
-    // when the app was in background. If not then don't do TransitionGC.
-    size_t num_bytes_allocated_since_gc = GetBytesAllocated() - num_bytes_alive_after_gc_;
-    if (num_bytes_allocated_since_gc <
-        (UnsignedDifference(target_footprint_.load(std::memory_order_relaxed),
-                            num_bytes_alive_after_gc_)/4)
-        && !kStressCollectorTransition
-        && !IsLowMemoryMode()) {
-      return;
-    }
   }
   DCHECK_NE(collector_type_, kCollectorTypeCCBackground);
   CollectorTransitionTask* added_task = nullptr;
@@ -4076,12 +4084,6 @@
   }
 }
 
-void Heap::RunFinalization(JNIEnv* env, uint64_t timeout) {
-  env->CallStaticVoidMethod(WellKnownClasses::dalvik_system_VMRuntime,
-                            WellKnownClasses::dalvik_system_VMRuntime_runFinalization,
-                            static_cast<jlong>(timeout));
-}
-
 // For GC triggering purposes, we count old (pre-last-GC) and new native allocations as
 // different fractions of Java allocations.
 // For now, we essentially do not count old native allocations at all, so that we can preserve the
@@ -4167,7 +4169,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);
-  CheckGCForNative(ThreadForEnv(env));
+  CheckGCForNative(Thread::ForEnv(env));
 }
 
 // Register a native allocation with an explicit size.
@@ -4181,7 +4183,7 @@
       native_objects_notified_.fetch_add(1, std::memory_order_relaxed);
   if (objects_notified % kNotifyNativeInterval == kNotifyNativeInterval - 1
       || bytes > kCheckImmediatelyThreshold) {
-    CheckGCForNative(ThreadForEnv(env));
+    CheckGCForNative(Thread::ForEnv(env));
   }
   // Heap profiler treats this as a Java allocation with a null object.
   JHPCheckNonTlabSampleAllocation(Thread::Current(), nullptr, bytes);
@@ -4280,7 +4282,7 @@
 }
 
 void Heap::AllowNewAllocationRecords() const {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   MutexLock mu(Thread::Current(), *Locks::alloc_tracker_lock_);
   AllocRecordObjectMap* allocation_records = GetAllocationRecords();
   if (allocation_records != nullptr) {
@@ -4289,7 +4291,7 @@
 }
 
 void Heap::DisallowNewAllocationRecords() const {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   MutexLock mu(Thread::Current(), *Locks::alloc_tracker_lock_);
   AllocRecordObjectMap* allocation_records = GetAllocationRecords();
   if (allocation_records != nullptr) {
@@ -4412,12 +4414,15 @@
 }
 
 void Heap::DisableGCForShutdown() {
-  Thread* const self = Thread::Current();
-  CHECK(Runtime::Current()->IsShuttingDown(self));
-  MutexLock mu(self, *gc_complete_lock_);
+  MutexLock mu(Thread::Current(), *gc_complete_lock_);
   gc_disabled_for_shutdown_ = true;
 }
 
+bool Heap::IsGCDisabledForShutdown() const {
+  MutexLock mu(Thread::Current(), *gc_complete_lock_);
+  return gc_disabled_for_shutdown_;
+}
+
 bool Heap::ObjectIsInBootImageSpace(ObjPtr<mirror::Object> obj) const {
   DCHECK_EQ(IsBootImageAddress(obj.Ptr()),
             any_of(boot_image_spaces_.begin(),
@@ -4494,8 +4499,13 @@
     DCHECK_LE(alloc_size, self->TlabSize());
   } else if (allocator_type == kAllocatorTypeTLAB) {
     DCHECK(bump_pointer_space_ != nullptr);
+    // Try to allocate a page-aligned TLAB (not necessary though).
+    // TODO: for large allocations, which are rare, maybe we should allocate
+    // that object and return. There is no need to revoke the current TLAB,
+    // particularly if it's mostly unutilized.
+    size_t def_pr_tlab_size = RoundDown(alloc_size + kDefaultTLABSize, kPageSize) - alloc_size;
     size_t next_tlab_size = JHPCalculateNextTlabSize(self,
-                                                     kDefaultTLABSize,
+                                                     def_pr_tlab_size,
                                                      alloc_size,
                                                      &take_sample,
                                                      &bytes_until_sample);
@@ -4658,42 +4668,33 @@
   uint64_t last_adj_time = NanoTime();
   next_gc_type_ = NonStickyGcType();  // Always start with a full gc.
 
-#if defined(__BIONIC__) && defined(ART_TARGET)
-  uffd_ = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY);
-  if (uffd_ >= 0) {
-    struct uffdio_api api = {.api = UFFD_API, .features = 0};
-    int ret = ioctl(uffd_, UFFDIO_API, &api);
-    CHECK_EQ(ret, 0) << "ioctl_userfaultfd: API: " << strerror(errno);
-  } else {
-    // The syscall should fail only if it doesn't exist in the kernel or if it's
-    // denied by SELinux.
-    CHECK(errno == ENOSYS || errno == EACCES) << "userfaultfd: " << strerror(errno);
+  LOG(INFO) << "Using " << foreground_collector_type_ << " GC.";
+  if (gUseUserfaultfd) {
+    DCHECK_NE(mark_compact_, nullptr);
+    mark_compact_->CreateUserfaultfd(/*post_fork*/true);
   }
-#endif
 
   // Temporarily increase target_footprint_ and concurrent_start_bytes_ to
   // max values to avoid GC during app launch.
-  if (!IsLowMemoryMode()) {
-    // Set target_footprint_ to the largest allowed value.
-    SetIdealFootprint(growth_limit_);
-    SetDefaultConcurrentStartBytes();
+  // Set target_footprint_ to the largest allowed value.
+  SetIdealFootprint(growth_limit_);
+  SetDefaultConcurrentStartBytes();
 
-    // Shrink heap after kPostForkMaxHeapDurationMS, to force a memory hog process to GC.
-    // This remains high enough that many processes will continue without a GC.
-    if (initial_heap_size_ < growth_limit_) {
-      size_t first_shrink_size = std::max(growth_limit_ / 4, initial_heap_size_);
-      last_adj_time += MsToNs(kPostForkMaxHeapDurationMS);
+  // Shrink heap after kPostForkMaxHeapDurationMS, to force a memory hog process to GC.
+  // This remains high enough that many processes will continue without a GC.
+  if (initial_heap_size_ < growth_limit_) {
+    size_t first_shrink_size = std::max(growth_limit_ / 4, initial_heap_size_);
+    last_adj_time += MsToNs(kPostForkMaxHeapDurationMS);
+    GetTaskProcessor()->AddTask(
+        self, new ReduceTargetFootprintTask(last_adj_time, first_shrink_size, starting_gc_num));
+    // Shrink to a small value after a substantial time period. This will typically force a
+    // GC if none has occurred yet. Has no effect if there was a GC before this anyway, which
+    // is commonly the case, e.g. because of a process transition.
+    if (initial_heap_size_ < first_shrink_size) {
+      last_adj_time += MsToNs(4 * kPostForkMaxHeapDurationMS);
       GetTaskProcessor()->AddTask(
-          self, new ReduceTargetFootprintTask(last_adj_time, first_shrink_size, starting_gc_num));
-      // Shrink to a small value after a substantial time period. This will typically force a
-      // GC if none has occurred yet. Has no effect if there was a GC before this anyway, which
-      // is commonly the case, e.g. because of a process transition.
-      if (initial_heap_size_ < first_shrink_size) {
-        last_adj_time += MsToNs(4 * kPostForkMaxHeapDurationMS);
-        GetTaskProcessor()->AddTask(
-            self,
-            new ReduceTargetFootprintTask(last_adj_time, initial_heap_size_, starting_gc_num));
-      }
+          self,
+          new ReduceTargetFootprintTask(last_adj_time, initial_heap_size_, starting_gc_num));
     }
   }
   // Schedule a GC after a substantial period of time. This will become a no-op if another GC is
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 232c96b..31a1b2b 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -34,6 +34,7 @@
 #include "base/time_utils.h"
 #include "gc/collector/gc_type.h"
 #include "gc/collector/iteration.h"
+#include "gc/collector/mark_compact.h"
 #include "gc/collector_type.h"
 #include "gc/gc_cause.h"
 #include "gc/space/large_object_space.h"
@@ -150,7 +151,7 @@
   static constexpr size_t kMinLargeObjectThreshold = 3 * kPageSize;
   static constexpr size_t kDefaultLargeObjectThreshold = kMinLargeObjectThreshold;
   // Whether or not parallel GC is enabled. If not, then we never create the thread pool.
-  static constexpr bool kDefaultEnableParallelGC = false;
+  static constexpr bool kDefaultEnableParallelGC = true;
   static uint8_t* const kPreferredAllocSpaceBegin;
 
   // Whether or not we use the free list large object space. Only use it if USE_ART_LOW_4G_ALLOCATOR
@@ -181,10 +182,8 @@
 
   // How often we allow heap trimming to happen (nanoseconds).
   static constexpr uint64_t kHeapTrimWait = MsToNs(5000);
-  // How long we wait after a transition request to perform a collector transition (nanoseconds).
-  static constexpr uint64_t kCollectorTransitionWait = MsToNs(5000);
-  // Whether the transition-wait applies or not. Zero wait will stress the
-  // transition code and collector, but increases jank probability.
+  // Whether the transition-GC heap threshold condition applies or not for non-low memory devices.
+  // Stressing GC will bypass the heap threshold condition.
   DECLARE_RUNTIME_DEBUG_FLAG(kStressCollectorTransition);
 
   // Create a heap with the requested sizes. The possible empty
@@ -385,6 +384,9 @@
   void ThreadFlipBegin(Thread* self) REQUIRES(!*thread_flip_lock_);
   void ThreadFlipEnd(Thread* self) REQUIRES(!*thread_flip_lock_);
 
+  // Ensures that the obj doesn't cause userfaultfd in JNI critical calls.
+  void EnsureObjectUserfaulted(ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Clear all of the mark bits, doesn't clear bitmaps which have the same live bits as mark bits.
   // Mutator lock is required for GetContinuousSpaces.
   void ClearMarkedObjects()
@@ -578,6 +580,9 @@
     return region_space_;
   }
 
+  space::BumpPointerSpace* GetBumpPointerSpace() const {
+    return bump_pointer_space_;
+  }
   // Implements java.lang.Runtime.maxMemory, returning the maximum amount of memory a program can
   // consume. For a regular VM this would relate to the -Xmx option and would return -1 if no Xmx
   // were specified. Android apps start with a growth limit (small heap size) which is
@@ -661,6 +666,10 @@
     return live_stack_.get();
   }
 
+  accounting::ObjectStack* GetAllocationStack() REQUIRES_SHARED(Locks::heap_bitmap_lock_) {
+    return allocation_stack_.get();
+  }
+
   void PreZygoteFork() NO_THREAD_SAFETY_ANALYSIS;
 
   // Mark and empty stack.
@@ -760,8 +769,10 @@
       REQUIRES(!*gc_complete_lock_);
   void ResetGcPerformanceInfo() REQUIRES(!*gc_complete_lock_);
 
-  // Thread pool.
-  void CreateThreadPool();
+  // Thread pool. Create either the given number of threads, or as per the
+  // values of conc_gc_threads_ and parallel_gc_threads_.
+  void CreateThreadPool(size_t num_threads = 0);
+  void WaitForWorkersToBeCreated();
   void DeleteThreadPool();
   ThreadPool* GetThreadPool() {
     return thread_pool_.get();
@@ -812,10 +823,22 @@
     return active_collector;
   }
 
-  CollectorType CurrentCollectorType() {
+  collector::MarkCompact* MarkCompactCollector() {
+    DCHECK(!gUseUserfaultfd || mark_compact_ != nullptr);
+    return mark_compact_;
+  }
+
+  bool IsPerformingUffdCompaction() { return gUseUserfaultfd && mark_compact_->IsCompacting(); }
+
+  CollectorType CurrentCollectorType() const {
+    DCHECK(!gUseUserfaultfd || collector_type_ == kCollectorTypeCMC);
     return collector_type_;
   }
 
+  bool IsMovingGc() const { return IsMovingGc(CurrentCollectorType()); }
+
+  CollectorType GetForegroundCollectorType() const { return foreground_collector_type_; }
+
   bool IsGcConcurrentAndMoving() const {
     if (IsGcConcurrent() && IsMovingGc(collector_type_)) {
       // Assume no transition when a concurrent moving collector is used.
@@ -939,6 +962,7 @@
       REQUIRES(!Locks::alloc_tracker_lock_);
 
   void DisableGCForShutdown() REQUIRES(!*gc_complete_lock_);
+  bool IsGCDisabledForShutdown() const REQUIRES(!*gc_complete_lock_);
 
   // Create a new alloc space and compact default alloc space to it.
   HomogeneousSpaceCompactResult PerformHomogeneousSpaceCompact()
@@ -1001,9 +1025,6 @@
     return main_space_backup_ != nullptr;
   }
 
-  // Attempt to use all the userfaultfd related ioctls.
-  void MaybePerformUffdIoctls(GcCause cause, uint32_t requested_gc_num) const;
-
   // Size_t saturating arithmetic
   static ALWAYS_INLINE size_t UnsignedDifference(size_t x, size_t y) {
     return x > y ? x - y : 0;
@@ -1019,19 +1040,11 @@
         allocator_type != kAllocatorTypeTLAB &&
         allocator_type != kAllocatorTypeRegion;
   }
-  static ALWAYS_INLINE bool AllocatorMayHaveConcurrentGC(AllocatorType allocator_type) {
-    if (kUseReadBarrier) {
-      // Read barrier may have the TLAB allocator but is always concurrent. TODO: clean this up.
-      return true;
-    }
-    return
-        allocator_type != kAllocatorTypeTLAB &&
-        allocator_type != kAllocatorTypeBumpPointer;
-  }
   static bool IsMovingGc(CollectorType collector_type) {
     return
         collector_type == kCollectorTypeCC ||
         collector_type == kCollectorTypeSS ||
+        collector_type == kCollectorTypeCMC ||
         collector_type == kCollectorTypeCCBackground ||
         collector_type == kCollectorTypeHomogeneousSpaceCompact;
   }
@@ -1117,9 +1130,6 @@
                                                size_t alloc_size,
                                                bool grow);
 
-  // Run the finalizers. If timeout is non zero, then we use the VMRuntime version.
-  void RunFinalization(JNIEnv* env, uint64_t timeout);
-
   // Blocks the caller until the garbage collector becomes idle and returns the type of GC we
   // waited for.
   collector::GcType WaitForGcToCompleteLocked(GcCause cause, Thread* self)
@@ -1223,6 +1233,7 @@
   // sweep GC, false for other GC types.
   bool IsGcConcurrent() const ALWAYS_INLINE {
     return collector_type_ == kCollectorTypeCC ||
+        collector_type_ == kCollectorTypeCMC ||
         collector_type_ == kCollectorTypeCMS ||
         collector_type_ == kCollectorTypeCCBackground;
   }
@@ -1326,7 +1337,7 @@
   // The current collector type.
   CollectorType collector_type_;
   // Which collector we use when the app is in the foreground.
-  CollectorType foreground_collector_type_;
+  const CollectorType foreground_collector_type_;
   // Which collector we will use when the app is notified of a transition to background.
   CollectorType background_collector_type_;
   // Desired collector type, heap trimming daemon transitions the heap if it is != collector_type_.
@@ -1437,8 +1448,9 @@
 
   // Computed with foreground-multiplier in GrowForUtilization() when run in
   // jank non-perceptible state. On update to process state from background to
-  // foreground we set target_footprint_ to this value.
+  // foreground we set target_footprint_ and concurrent_start_bytes_ to the corresponding value.
   size_t min_foreground_target_footprint_ GUARDED_BY(process_state_update_lock_);
+  size_t min_foreground_concurrent_start_bytes_ GUARDED_BY(process_state_update_lock_);
 
   // When num_bytes_allocated_ exceeds this amount then a concurrent GC should be requested so that
   // it completes ahead of an allocation failing.
@@ -1588,6 +1600,7 @@
 
   std::vector<collector::GarbageCollector*> garbage_collectors_;
   collector::SemiSpace* semi_space_collector_;
+  collector::MarkCompact* mark_compact_;
   Atomic<collector::ConcurrentCopying*> active_concurrent_copying_collector_;
   collector::ConcurrentCopying* young_concurrent_copying_collector_;
   collector::ConcurrentCopying* concurrent_copying_collector_;
@@ -1680,9 +1693,6 @@
   // Stack trace hashes that we already saw,
   std::unordered_set<uint64_t> seen_backtraces_ GUARDED_BY(backtrace_lock_);
 
-  // Userfaultfd file descriptor.
-  // TODO (lokeshgidra): remove this when the userfaultfd-based GC is in use.
-  int uffd_;
   // We disable GC when we are shutting down the runtime in case there are daemon threads still
   // allocating.
   bool gc_disabled_for_shutdown_ GUARDED_BY(gc_complete_lock_);
@@ -1712,6 +1722,7 @@
   friend class CollectorTransitionTask;
   friend class collector::GarbageCollector;
   friend class collector::ConcurrentCopying;
+  friend class collector::MarkCompact;
   friend class collector::MarkSweep;
   friend class collector::SemiSpace;
   friend class GCCriticalSection;
diff --git a/runtime/gc/heap_test.cc b/runtime/gc/heap_test.cc
index 5e8c1e3..b569241 100644
--- a/runtime/gc/heap_test.cc
+++ b/runtime/gc/heap_test.cc
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include <algorithm>
+
+#include "base/metrics/metrics.h"
 #include "class_linker-inl.h"
 #include "common_runtime_test.h"
 #include "gc/accounting/card_table-inl.h"
@@ -30,6 +33,10 @@
 
 class HeapTest : public CommonRuntimeTest {
  public:
+  HeapTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
   void SetUp() override {
     MemMap::Init();
     std::string error_msg;
@@ -99,7 +106,160 @@
   Runtime::Current()->SetDumpGCPerformanceOnShutdown(true);
 }
 
+bool AnyIsFalse(bool x, bool y) { return !x || !y; }
+
+TEST_F(HeapTest, GCMetrics) {
+  // Allocate a few string objects (to be collected), then trigger garbage
+  // collection, and check that GC metrics are updated (where applicable).
+  {
+    constexpr const size_t kNumObj = 128;
+    ScopedObjectAccess soa(Thread::Current());
+    StackHandleScope<kNumObj> hs(soa.Self());
+    for (size_t i = 0u; i < kNumObj; ++i) {
+      Handle<mirror::String> string [[maybe_unused]] (
+          hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "test")));
+    }
+  }
+  Heap* heap = Runtime::Current()->GetHeap();
+  heap->CollectGarbage(/* clear_soft_references= */ false);
+
+  // ART Metrics.
+  metrics::ArtMetrics* metrics = Runtime::Current()->GetMetrics();
+  // ART full-heap GC metrics.
+  metrics::MetricsBase<int64_t>* full_gc_collection_time = metrics->FullGcCollectionTime();
+  metrics::MetricsBase<uint64_t>* full_gc_count = metrics->FullGcCount();
+  metrics::MetricsBase<uint64_t>* full_gc_count_delta = metrics->FullGcCountDelta();
+  metrics::MetricsBase<int64_t>* full_gc_throughput = metrics->FullGcThroughput();
+  metrics::MetricsBase<int64_t>* full_gc_tracing_throughput = metrics->FullGcTracingThroughput();
+  metrics::MetricsBase<uint64_t>* full_gc_throughput_avg = metrics->FullGcThroughputAvg();
+  metrics::MetricsBase<uint64_t>* full_gc_tracing_throughput_avg =
+      metrics->FullGcTracingThroughputAvg();
+  metrics::MetricsBase<uint64_t>* full_gc_scanned_bytes = metrics->FullGcScannedBytes();
+  metrics::MetricsBase<uint64_t>* full_gc_scanned_bytes_delta = metrics->FullGcScannedBytesDelta();
+  metrics::MetricsBase<uint64_t>* full_gc_freed_bytes = metrics->FullGcFreedBytes();
+  metrics::MetricsBase<uint64_t>* full_gc_freed_bytes_delta = metrics->FullGcFreedBytesDelta();
+  metrics::MetricsBase<uint64_t>* full_gc_duration = metrics->FullGcDuration();
+  metrics::MetricsBase<uint64_t>* full_gc_duration_delta = metrics->FullGcDurationDelta();
+  // ART young-generation GC metrics.
+  metrics::MetricsBase<int64_t>* young_gc_collection_time = metrics->YoungGcCollectionTime();
+  metrics::MetricsBase<uint64_t>* young_gc_count = metrics->YoungGcCount();
+  metrics::MetricsBase<uint64_t>* young_gc_count_delta = metrics->YoungGcCountDelta();
+  metrics::MetricsBase<int64_t>* young_gc_throughput = metrics->YoungGcThroughput();
+  metrics::MetricsBase<int64_t>* young_gc_tracing_throughput = metrics->YoungGcTracingThroughput();
+  metrics::MetricsBase<uint64_t>* young_gc_throughput_avg = metrics->YoungGcThroughputAvg();
+  metrics::MetricsBase<uint64_t>* young_gc_tracing_throughput_avg =
+      metrics->YoungGcTracingThroughputAvg();
+  metrics::MetricsBase<uint64_t>* young_gc_scanned_bytes = metrics->YoungGcScannedBytes();
+  metrics::MetricsBase<uint64_t>* young_gc_scanned_bytes_delta =
+      metrics->YoungGcScannedBytesDelta();
+  metrics::MetricsBase<uint64_t>* young_gc_freed_bytes = metrics->YoungGcFreedBytes();
+  metrics::MetricsBase<uint64_t>* young_gc_freed_bytes_delta = metrics->YoungGcFreedBytesDelta();
+  metrics::MetricsBase<uint64_t>* young_gc_duration = metrics->YoungGcDuration();
+  metrics::MetricsBase<uint64_t>* young_gc_duration_delta = metrics->YoungGcDurationDelta();
+
+  CollectorType fg_collector_type = heap->GetForegroundCollectorType();
+  if (fg_collector_type == kCollectorTypeCC || fg_collector_type == kCollectorTypeCMC) {
+    // Only the Concurrent Copying and Concurrent Mark-Compact collectors enable
+    // GC metrics at the moment.
+    if (heap->GetUseGenerationalCC()) {
+      // Check that full-heap and/or young-generation GC metrics are non-null
+      // after trigerring the collection.
+      EXPECT_PRED2(
+          AnyIsFalse, full_gc_collection_time->IsNull(), young_gc_collection_time->IsNull());
+      EXPECT_PRED2(AnyIsFalse, full_gc_count->IsNull(), young_gc_count->IsNull());
+      EXPECT_PRED2(AnyIsFalse, full_gc_count_delta->IsNull(), young_gc_count_delta->IsNull());
+      EXPECT_PRED2(AnyIsFalse, full_gc_throughput->IsNull(), young_gc_throughput->IsNull());
+      EXPECT_PRED2(
+          AnyIsFalse, full_gc_tracing_throughput->IsNull(), young_gc_tracing_throughput->IsNull());
+      EXPECT_PRED2(AnyIsFalse, full_gc_throughput_avg->IsNull(), young_gc_throughput_avg->IsNull());
+      EXPECT_PRED2(AnyIsFalse,
+                   full_gc_tracing_throughput_avg->IsNull(),
+                   young_gc_tracing_throughput_avg->IsNull());
+      EXPECT_PRED2(AnyIsFalse, full_gc_scanned_bytes->IsNull(), young_gc_scanned_bytes->IsNull());
+      EXPECT_PRED2(AnyIsFalse,
+                   full_gc_scanned_bytes_delta->IsNull(),
+                   young_gc_scanned_bytes_delta->IsNull());
+      EXPECT_PRED2(AnyIsFalse, full_gc_freed_bytes->IsNull(), young_gc_freed_bytes->IsNull());
+      EXPECT_PRED2(
+          AnyIsFalse, full_gc_freed_bytes_delta->IsNull(), young_gc_freed_bytes_delta->IsNull());
+      // We have observed that sometimes the GC duration (both for full-heap and
+      // young-generation collections) is null (b/271112044). Temporarily
+      // suspend the following checks while we investigate.
+      //
+      // TODO(b/271112044): Investigate and adjust these expectations and/or the
+      // corresponding metric logic.
+#if 0
+      EXPECT_PRED2(AnyIsFalse, full_gc_duration->IsNull(), young_gc_duration->IsNull());
+      EXPECT_PRED2(AnyIsFalse, full_gc_duration_delta->IsNull(), young_gc_duration_delta->IsNull());
+#endif
+    } else {
+      // Check that only full-heap GC metrics are non-null after trigerring the collection.
+      EXPECT_FALSE(full_gc_collection_time->IsNull());
+      EXPECT_FALSE(full_gc_count->IsNull());
+      EXPECT_FALSE(full_gc_count_delta->IsNull());
+      EXPECT_FALSE(full_gc_throughput->IsNull());
+      EXPECT_FALSE(full_gc_tracing_throughput->IsNull());
+      EXPECT_FALSE(full_gc_throughput_avg->IsNull());
+      EXPECT_FALSE(full_gc_tracing_throughput_avg->IsNull());
+      EXPECT_FALSE(full_gc_scanned_bytes->IsNull());
+      EXPECT_FALSE(full_gc_scanned_bytes_delta->IsNull());
+      EXPECT_FALSE(full_gc_freed_bytes->IsNull());
+      EXPECT_FALSE(full_gc_freed_bytes_delta->IsNull());
+      EXPECT_FALSE(full_gc_duration->IsNull());
+      EXPECT_FALSE(full_gc_duration_delta->IsNull());
+
+      EXPECT_TRUE(young_gc_collection_time->IsNull());
+      EXPECT_TRUE(young_gc_count->IsNull());
+      EXPECT_TRUE(young_gc_count_delta->IsNull());
+      EXPECT_TRUE(young_gc_throughput->IsNull());
+      EXPECT_TRUE(young_gc_tracing_throughput->IsNull());
+      EXPECT_TRUE(young_gc_throughput_avg->IsNull());
+      EXPECT_TRUE(young_gc_tracing_throughput_avg->IsNull());
+      EXPECT_TRUE(young_gc_scanned_bytes->IsNull());
+      EXPECT_TRUE(young_gc_scanned_bytes_delta->IsNull());
+      EXPECT_TRUE(young_gc_freed_bytes->IsNull());
+      EXPECT_TRUE(young_gc_freed_bytes_delta->IsNull());
+      EXPECT_TRUE(young_gc_duration->IsNull());
+      EXPECT_TRUE(young_gc_duration_delta->IsNull());
+    }
+  } else {
+    // Check that all metrics are null after trigerring the collection.
+    EXPECT_TRUE(full_gc_collection_time->IsNull());
+    EXPECT_TRUE(full_gc_count->IsNull());
+    EXPECT_TRUE(full_gc_count_delta->IsNull());
+    EXPECT_TRUE(full_gc_throughput->IsNull());
+    EXPECT_TRUE(full_gc_tracing_throughput->IsNull());
+    EXPECT_TRUE(full_gc_throughput_avg->IsNull());
+    EXPECT_TRUE(full_gc_tracing_throughput_avg->IsNull());
+    EXPECT_TRUE(full_gc_scanned_bytes->IsNull());
+    EXPECT_TRUE(full_gc_scanned_bytes_delta->IsNull());
+    EXPECT_TRUE(full_gc_freed_bytes->IsNull());
+    EXPECT_TRUE(full_gc_freed_bytes_delta->IsNull());
+    EXPECT_TRUE(full_gc_duration->IsNull());
+    EXPECT_TRUE(full_gc_duration_delta->IsNull());
+
+    EXPECT_TRUE(young_gc_collection_time->IsNull());
+    EXPECT_TRUE(young_gc_count->IsNull());
+    EXPECT_TRUE(young_gc_count_delta->IsNull());
+    EXPECT_TRUE(young_gc_throughput->IsNull());
+    EXPECT_TRUE(young_gc_tracing_throughput->IsNull());
+    EXPECT_TRUE(young_gc_throughput_avg->IsNull());
+    EXPECT_TRUE(young_gc_tracing_throughput_avg->IsNull());
+    EXPECT_TRUE(young_gc_scanned_bytes->IsNull());
+    EXPECT_TRUE(young_gc_scanned_bytes_delta->IsNull());
+    EXPECT_TRUE(young_gc_freed_bytes->IsNull());
+    EXPECT_TRUE(young_gc_freed_bytes_delta->IsNull());
+    EXPECT_TRUE(young_gc_duration->IsNull());
+    EXPECT_TRUE(young_gc_duration_delta->IsNull());
+  }
+}
+
 class ZygoteHeapTest : public CommonRuntimeTest {
+ public:
+  ZygoteHeapTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
   void SetUpRuntimeOptions(RuntimeOptions* options) override {
     CommonRuntimeTest::SetUpRuntimeOptions(options);
     options->push_back(std::make_pair("-Xzygote", nullptr));
diff --git a/runtime/gc/heap_verification_test.cc b/runtime/gc/heap_verification_test.cc
index ca6a30b..a7583fe 100644
--- a/runtime/gc/heap_verification_test.cc
+++ b/runtime/gc/heap_verification_test.cc
@@ -26,14 +26,16 @@
 #include "mirror/string.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
-#include "verification.h"
+#include "verification-inl.h"
 
 namespace art {
 namespace gc {
 
 class VerificationTest : public CommonRuntimeTest {
  protected:
-  VerificationTest() {}
+  VerificationTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
 
   template <class T>
   ObjPtr<mirror::ObjectArray<T>> AllocObjectArray(Thread* self, size_t length)
@@ -76,11 +78,11 @@
   Handle<mirror::String> string(
       hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "test")));
   const Verification* const v = Runtime::Current()->GetHeap()->GetVerification();
-  EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(1)));
-  EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(4)));
+  EXPECT_FALSE(v->IsValidClass(reinterpret_cast<mirror::Class*>(1)));
+  EXPECT_FALSE(v->IsValidClass(reinterpret_cast<mirror::Class*>(4)));
   EXPECT_FALSE(v->IsValidClass(nullptr));
   EXPECT_TRUE(v->IsValidClass(string->GetClass()));
-  EXPECT_FALSE(v->IsValidClass(string.Get()));
+  EXPECT_FALSE(v->IsValidClass(reinterpret_cast<mirror::Class*>(string.Get())));
 }
 
 TEST_F(VerificationTest, IsValidClassInHeap) {
@@ -95,9 +97,9 @@
   Handle<mirror::String> string(
       hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "test")));
   const Verification* const v = Runtime::Current()->GetHeap()->GetVerification();
-  const uintptr_t uint_klass = reinterpret_cast<uintptr_t>(string->GetClass());
-  EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(uint_klass - kObjectAlignment)));
-  EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(&uint_klass)));
+  uintptr_t uint_klass = reinterpret_cast<uintptr_t>(string->GetClass());
+  EXPECT_FALSE(v->IsValidClass(reinterpret_cast<mirror::Class*>(uint_klass - kObjectAlignment)));
+  EXPECT_FALSE(v->IsValidClass(reinterpret_cast<mirror::Class*>(&uint_klass)));
 }
 
 TEST_F(VerificationTest, DumpInvalidObjectInfo) {
diff --git a/runtime/gc/reference_processor.cc b/runtime/gc/reference_processor.cc
index 5e41ee4..f24c942 100644
--- a/runtime/gc/reference_processor.cc
+++ b/runtime/gc/reference_processor.cc
@@ -90,7 +90,7 @@
 ObjPtr<mirror::Object> ReferenceProcessor::GetReferent(Thread* self,
                                                        ObjPtr<mirror::Reference> reference) {
   auto slow_path_required = [this, self]() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return kUseReadBarrier ? !self->GetWeakRefAccessEnabled() : SlowPathEnabled();
+    return gUseReadBarrier ? !self->GetWeakRefAccessEnabled() : SlowPathEnabled();
   };
   if (!slow_path_required()) {
     return reference->GetReferent();
@@ -118,10 +118,10 @@
   // Keeping reference_processor_lock_ blocks the broadcast when we try to reenable the fast path.
   while (slow_path_required()) {
     DCHECK(collector_ != nullptr);
-    constexpr bool kOtherReadBarrier = kUseReadBarrier && !kUseBakerReadBarrier;
+    const bool other_read_barrier = !kUseBakerReadBarrier && gUseReadBarrier;
     if (UNLIKELY(reference->IsFinalizerReferenceInstance()
                  || rp_state_ == RpState::kStarting /* too early to determine mark state */
-                 || (kOtherReadBarrier && reference->IsPhantomReferenceInstance()))) {
+                 || (other_read_barrier && reference->IsPhantomReferenceInstance()))) {
       // Odd cases in which it doesn't hurt to just wait, or the wait is likely to be very brief.
 
       // Check and run the empty checkpoint before blocking so the empty checkpoint will work in the
@@ -210,7 +210,7 @@
   }
   {
     MutexLock mu(self, *Locks::reference_processor_lock_);
-    if (!kUseReadBarrier) {
+    if (!gUseReadBarrier) {
       CHECK_EQ(SlowPathEnabled(), concurrent_) << "Slow path must be enabled iff concurrent";
     } else {
       // Weak ref access is enabled at Zygote compaction by SemiSpace (concurrent_ == false).
@@ -305,7 +305,7 @@
     // could result in a stale is_marked_callback_ being called before the reference processing
     // starts since there is a small window of time where slow_path_enabled_ is enabled but the
     // callback isn't yet set.
-    if (!kUseReadBarrier && concurrent_) {
+    if (!gUseReadBarrier && concurrent_) {
       // Done processing, disable the slow path and broadcast to the waiters.
       DisableSlowPath(self);
     }
@@ -363,9 +363,8 @@
   }
   void Run(Thread* thread) override {
     ScopedObjectAccess soa(thread);
-    jvalue args[1];
-    args[0].l = cleared_references_;
-    InvokeWithJValues(soa, nullptr, WellKnownClasses::java_lang_ref_ReferenceQueue_add, args);
+    WellKnownClasses::java_lang_ref_ReferenceQueue_add->InvokeStatic<'V', 'L'>(
+        thread, soa.Decode<mirror::Object>(cleared_references_));
     soa.Env()->DeleteGlobalRef(cleared_references_);
   }
 
@@ -418,8 +417,8 @@
 
 void ReferenceProcessor::WaitUntilDoneProcessingReferences(Thread* self) {
   // Wait until we are done processing reference.
-  while ((!kUseReadBarrier && SlowPathEnabled()) ||
-         (kUseReadBarrier && !self->GetWeakRefAccessEnabled())) {
+  while ((!gUseReadBarrier && SlowPathEnabled()) ||
+         (gUseReadBarrier && !self->GetWeakRefAccessEnabled())) {
     // Check and run the empty checkpoint before blocking so the empty checkpoint will work in the
     // presence of threads blocking for weak ref access.
     self->CheckEmptyCheckpointFromWeakRefAccess(Locks::reference_processor_lock_);
diff --git a/runtime/gc/reference_queue_test.cc b/runtime/gc/reference_queue_test.cc
index c680fb5..c8e71b0 100644
--- a/runtime/gc/reference_queue_test.cc
+++ b/runtime/gc/reference_queue_test.cc
@@ -26,7 +26,12 @@
 namespace art {
 namespace gc {
 
-class ReferenceQueueTest : public CommonRuntimeTest {};
+class ReferenceQueueTest : public CommonRuntimeTest {
+ protected:
+  ReferenceQueueTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+};
 
 TEST_F(ReferenceQueueTest, EnqueueDequeue) {
   Thread* self = Thread::Current();
diff --git a/runtime/gc/scoped_gc_critical_section.cc b/runtime/gc/scoped_gc_critical_section.cc
index eaede43..7a0a6e8 100644
--- a/runtime/gc/scoped_gc_critical_section.cc
+++ b/runtime/gc/scoped_gc_critical_section.cc
@@ -58,17 +58,5 @@
   critical_section_.Exit(old_no_suspend_reason_);
 }
 
-ScopedInterruptibleGCCriticalSection::ScopedInterruptibleGCCriticalSection(
-    Thread* self,
-    GcCause cause,
-    CollectorType type) : self_(self) {
-  DCHECK(self != nullptr);
-  Runtime::Current()->GetHeap()->StartGC(self_, cause, type);
-}
-
-ScopedInterruptibleGCCriticalSection::~ScopedInterruptibleGCCriticalSection() {
-  Runtime::Current()->GetHeap()->FinishGC(self_, collector::kGcTypeNone);
-}
-
 }  // namespace gc
 }  // namespace art
diff --git a/runtime/gc/scoped_gc_critical_section.h b/runtime/gc/scoped_gc_critical_section.h
index b3a897c..8ad0158 100644
--- a/runtime/gc/scoped_gc_critical_section.h
+++ b/runtime/gc/scoped_gc_critical_section.h
@@ -59,19 +59,6 @@
   const char* old_no_suspend_reason_;
 };
 
-// The use of ScopedGCCriticalSection should be preferred whenever possible.
-// This class allows thread suspension but should never be used with allocations because of the
-// deadlock risk. TODO: Add a new thread role for "no allocations" that still allows suspension.
-class ScopedInterruptibleGCCriticalSection {
- public:
-  ScopedInterruptibleGCCriticalSection(Thread* self, GcCause cause, CollectorType type);
-  ~ScopedInterruptibleGCCriticalSection();
-
- private:
-  Thread* const self_;
-};
-
-
 }  // namespace gc
 }  // namespace art
 
diff --git a/runtime/gc/space/bump_pointer_space-inl.h b/runtime/gc/space/bump_pointer_space-inl.h
index 20f7a93..2774b9e 100644
--- a/runtime/gc/space/bump_pointer_space-inl.h
+++ b/runtime/gc/space/bump_pointer_space-inl.h
@@ -20,6 +20,7 @@
 #include "bump_pointer_space.h"
 
 #include "base/bit_utils.h"
+#include "mirror/object-inl.h"
 
 namespace art {
 namespace gc {
@@ -89,6 +90,11 @@
   return ret;
 }
 
+inline mirror::Object* BumpPointerSpace::GetNextObject(mirror::Object* obj) {
+  const uintptr_t position = reinterpret_cast<uintptr_t>(obj) + obj->SizeOf();
+  return reinterpret_cast<mirror::Object*>(RoundUp(position, kAlignment));
+}
+
 }  // namespace space
 }  // namespace gc
 }  // namespace art
diff --git a/runtime/gc/space/bump_pointer_space-walk-inl.h b/runtime/gc/space/bump_pointer_space-walk-inl.h
index 5d05ea2..a978f62 100644
--- a/runtime/gc/space/bump_pointer_space-walk-inl.h
+++ b/runtime/gc/space/bump_pointer_space-walk-inl.h
@@ -17,12 +17,14 @@
 #ifndef ART_RUNTIME_GC_SPACE_BUMP_POINTER_SPACE_WALK_INL_H_
 #define ART_RUNTIME_GC_SPACE_BUMP_POINTER_SPACE_WALK_INL_H_
 
-#include "bump_pointer_space.h"
+#include "bump_pointer_space-inl.h"
 
 #include "base/bit_utils.h"
 #include "mirror/object-inl.h"
 #include "thread-current-inl.h"
 
+#include <memory>
+
 namespace art {
 namespace gc {
 namespace space {
@@ -32,6 +34,7 @@
   uint8_t* pos = Begin();
   uint8_t* end = End();
   uint8_t* main_end = pos;
+  std::unique_ptr<std::vector<size_t>> block_sizes_copy;
   // Internal indirection w/ NO_THREAD_SAFETY_ANALYSIS. Optimally, we'd like to have an annotation
   // like
   //   REQUIRES_AS(visitor.operator(mirror::Object*))
@@ -49,15 +52,17 @@
     MutexLock mu(Thread::Current(), block_lock_);
     // If we have 0 blocks then we need to update the main header since we have bump pointer style
     // allocation into an unbounded region (actually bounded by Capacity()).
-    if (num_blocks_ == 0) {
+    if (block_sizes_.empty()) {
       UpdateMainBlock();
     }
     main_end = Begin() + main_block_size_;
-    if (num_blocks_ == 0) {
+    if (block_sizes_.empty()) {
       // We don't have any other blocks, this means someone else may be allocating into the main
       // block. In this case, we don't want to try and visit the other blocks after the main block
       // since these could actually be part of the main block.
       end = main_end;
+    } else {
+      block_sizes_copy.reset(new std::vector<size_t>(block_sizes_.begin(), block_sizes_.end()));
     }
   }
   // Walk all of the objects in the main block first.
@@ -66,31 +71,33 @@
     // No read barrier because obj may not be a valid object.
     if (obj->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>() == nullptr) {
       // There is a race condition where a thread has just allocated an object but not set the
-      // class. We can't know the size of this object, so we don't visit it and exit the function
-      // since there is guaranteed to be not other blocks.
-      return;
+      // class. We can't know the size of this object, so we don't visit it and break the loop
+      pos = main_end;
+      break;
     } else {
       no_thread_safety_analysis_visit(obj);
       pos = reinterpret_cast<uint8_t*>(GetNextObject(obj));
     }
   }
   // Walk the other blocks (currently only TLABs).
-  while (pos < end) {
-    BlockHeader* header = reinterpret_cast<BlockHeader*>(pos);
-    size_t block_size = header->size_;
-    pos += sizeof(BlockHeader);  // Skip the header so that we know where the objects
-    mirror::Object* obj = reinterpret_cast<mirror::Object*>(pos);
-    const mirror::Object* end_obj = reinterpret_cast<const mirror::Object*>(pos + block_size);
-    CHECK_LE(reinterpret_cast<const uint8_t*>(end_obj), End());
-    // We don't know how many objects are allocated in the current block. When we hit a null class
-    // assume its the end. TODO: Have a thread update the header when it flushes the block?
-    // No read barrier because obj may not be a valid object.
-    while (obj < end_obj && obj->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>() != nullptr) {
-      no_thread_safety_analysis_visit(obj);
-      obj = GetNextObject(obj);
+  if (block_sizes_copy != nullptr) {
+    for (size_t block_size : *block_sizes_copy) {
+      mirror::Object* obj = reinterpret_cast<mirror::Object*>(pos);
+      const mirror::Object* end_obj = reinterpret_cast<const mirror::Object*>(pos + block_size);
+      CHECK_LE(reinterpret_cast<const uint8_t*>(end_obj), End());
+      // We don't know how many objects are allocated in the current block. When we hit a null class
+      // assume it's the end. TODO: Have a thread update the header when it flushes the block?
+      // No read barrier because obj may not be a valid object.
+      while (obj < end_obj && obj->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>() != nullptr) {
+        no_thread_safety_analysis_visit(obj);
+        obj = GetNextObject(obj);
+      }
+      pos += block_size;
     }
-    pos += block_size;
+  } else {
+    CHECK_EQ(end, main_end);
   }
+  CHECK_EQ(pos, end);
 }
 
 }  // namespace space
diff --git a/runtime/gc/space/bump_pointer_space.cc b/runtime/gc/space/bump_pointer_space.cc
index 3a0155a..7753f73 100644
--- a/runtime/gc/space/bump_pointer_space.cc
+++ b/runtime/gc/space/bump_pointer_space.cc
@@ -54,8 +54,9 @@
       growth_end_(limit),
       objects_allocated_(0), bytes_allocated_(0),
       block_lock_("Block lock"),
-      main_block_size_(0),
-      num_blocks_(0) {
+      main_block_size_(0) {
+  // This constructor gets called only from Heap::PreZygoteFork(), which
+  // doesn't require a mark_bitmap.
 }
 
 BumpPointerSpace::BumpPointerSpace(const std::string& name, MemMap&& mem_map)
@@ -68,8 +69,11 @@
       growth_end_(mem_map_.End()),
       objects_allocated_(0), bytes_allocated_(0),
       block_lock_("Block lock", kBumpPointerSpaceBlockLock),
-      main_block_size_(0),
-      num_blocks_(0) {
+      main_block_size_(0) {
+  mark_bitmap_ =
+      accounting::ContinuousSpaceBitmap::Create("bump-pointer space live bitmap",
+                                                Begin(),
+                                                Capacity());
 }
 
 void BumpPointerSpace::Clear() {
@@ -86,7 +90,7 @@
   growth_end_ = Limit();
   {
     MutexLock mu(Thread::Current(), block_lock_);
-    num_blocks_ = 0;
+    block_sizes_.clear();
     main_block_size_ = 0;
   }
 }
@@ -97,11 +101,6 @@
       << reinterpret_cast<void*>(Limit());
 }
 
-mirror::Object* BumpPointerSpace::GetNextObject(mirror::Object* obj) {
-  const uintptr_t position = reinterpret_cast<uintptr_t>(obj) + obj->SizeOf();
-  return reinterpret_cast<mirror::Object*>(RoundUp(position, kAlignment));
-}
-
 size_t BumpPointerSpace::RevokeThreadLocalBuffers(Thread* thread) {
   MutexLock mu(Thread::Current(), block_lock_);
   RevokeThreadLocalBuffersLocked(thread);
@@ -141,23 +140,19 @@
 }
 
 void BumpPointerSpace::UpdateMainBlock() {
-  DCHECK_EQ(num_blocks_, 0U);
+  DCHECK(block_sizes_.empty());
   main_block_size_ = Size();
 }
 
 // Returns the start of the storage.
 uint8_t* BumpPointerSpace::AllocBlock(size_t bytes) {
   bytes = RoundUp(bytes, kAlignment);
-  if (!num_blocks_) {
+  if (block_sizes_.empty()) {
     UpdateMainBlock();
   }
-  uint8_t* storage = reinterpret_cast<uint8_t*>(
-      AllocNonvirtualWithoutAccounting(bytes + sizeof(BlockHeader)));
+  uint8_t* storage = reinterpret_cast<uint8_t*>(AllocNonvirtualWithoutAccounting(bytes));
   if (LIKELY(storage != nullptr)) {
-    BlockHeader* header = reinterpret_cast<BlockHeader*>(storage);
-    header->size_ = bytes;  // Write out the block header.
-    storage += sizeof(BlockHeader);
-    ++num_blocks_;
+    block_sizes_.push_back(bytes);
   }
   return storage;
 }
@@ -177,7 +172,7 @@
   MutexLock mu3(Thread::Current(), block_lock_);
   // If we don't have any blocks, we don't have any thread local buffers. This check is required
   // since there can exist multiple bump pointer spaces which exist at the same time.
-  if (num_blocks_ > 0) {
+  if (!block_sizes_.empty()) {
     for (Thread* thread : thread_list) {
       total += thread->GetThreadLocalBytesAllocated();
     }
@@ -195,7 +190,7 @@
   MutexLock mu3(Thread::Current(), block_lock_);
   // If we don't have any blocks, we don't have any thread local buffers. This check is required
   // since there can exist multiple bump pointer spaces which exist at the same time.
-  if (num_blocks_ > 0) {
+  if (!block_sizes_.empty()) {
     for (Thread* thread : thread_list) {
       total += thread->GetThreadLocalObjectsAllocated();
     }
@@ -240,6 +235,52 @@
   return num_bytes;
 }
 
+uint8_t* BumpPointerSpace::AlignEnd(Thread* self, size_t alignment) {
+  Locks::mutator_lock_->AssertExclusiveHeld(self);
+  DCHECK(IsAligned<kAlignment>(alignment));
+  uint8_t* end = end_.load(std::memory_order_relaxed);
+  uint8_t* aligned_end = AlignUp(end, alignment);
+  ptrdiff_t diff = aligned_end - end;
+  if (diff > 0) {
+    end_.store(aligned_end, std::memory_order_relaxed);
+    // If we have blocks after the main one. Then just add the diff to the last
+    // block.
+    MutexLock mu(self, block_lock_);
+    if (!block_sizes_.empty()) {
+      block_sizes_.back() += diff;
+    }
+  }
+  return end;
+}
+
+std::vector<size_t>* BumpPointerSpace::GetBlockSizes(Thread* self, size_t* main_block_size) {
+  std::vector<size_t>* block_sizes = nullptr;
+  MutexLock mu(self, block_lock_);
+  if (!block_sizes_.empty()) {
+    block_sizes = new std::vector<size_t>(block_sizes_.begin(), block_sizes_.end());
+  } else {
+    UpdateMainBlock();
+  }
+  *main_block_size = main_block_size_;
+  return block_sizes;
+}
+
+void BumpPointerSpace::SetBlockSizes(Thread* self,
+                                     const size_t main_block_size,
+                                     const size_t first_valid_idx) {
+  MutexLock mu(self, block_lock_);
+  main_block_size_ = main_block_size;
+  if (!block_sizes_.empty()) {
+    block_sizes_.erase(block_sizes_.begin(), block_sizes_.begin() + first_valid_idx);
+  }
+  size_t size = main_block_size;
+  for (size_t block_size : block_sizes_) {
+    size += block_size;
+  }
+  DCHECK(IsAligned<kAlignment>(size));
+  end_.store(Begin() + size, std::memory_order_relaxed);
+}
+
 }  // namespace space
 }  // namespace gc
 }  // namespace art
diff --git a/runtime/gc/space/bump_pointer_space.h b/runtime/gc/space/bump_pointer_space.h
index 08ed503..bba1711 100644
--- a/runtime/gc/space/bump_pointer_space.h
+++ b/runtime/gc/space/bump_pointer_space.h
@@ -17,9 +17,10 @@
 #ifndef ART_RUNTIME_GC_SPACE_BUMP_POINTER_SPACE_H_
 #define ART_RUNTIME_GC_SPACE_BUMP_POINTER_SPACE_H_
 
+#include "base/mutex.h"
 #include "space.h"
 
-#include "base/mutex.h"
+#include <deque>
 
 namespace art {
 
@@ -30,6 +31,7 @@
 namespace gc {
 
 namespace collector {
+class MarkCompact;
 class MarkSweep;
 }  // namespace collector
 
@@ -39,7 +41,7 @@
 // implementation as its intended to be evacuated.
 class BumpPointerSpace final : public ContinuousMemMapAllocSpace {
  public:
-  typedef void(*WalkCallback)(void *start, void *end, size_t num_bytes, void* callback_arg);
+  using WalkCallback = void (*)(void *, void *, int, void *);
 
   SpaceType GetType() const override {
     return kSpaceTypeBumpPointerSpace;
@@ -100,10 +102,6 @@
     return nullptr;
   }
 
-  accounting::ContinuousSpaceBitmap* GetMarkBitmap() override {
-    return nullptr;
-  }
-
   // Reset the space to empty.
   void Clear() override REQUIRES(!block_lock_);
 
@@ -120,6 +118,11 @@
       REQUIRES(!*Locks::runtime_shutdown_lock_, !*Locks::thread_list_lock_, !block_lock_);
   uint64_t GetObjectsAllocated() override REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!*Locks::runtime_shutdown_lock_, !*Locks::thread_list_lock_, !block_lock_);
+  // Return the pre-determined allocated object count. This could be beneficial
+  // when we know that all the TLABs are revoked.
+  int32_t GetAccumulatedObjectsAllocated() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return objects_allocated_.load(std::memory_order_relaxed);
+  }
   bool IsEmpty() const {
     return Begin() == End();
   }
@@ -128,18 +131,9 @@
     return true;
   }
 
-  bool Contains(const mirror::Object* obj) const override {
-    const uint8_t* byte_obj = reinterpret_cast<const uint8_t*>(obj);
-    return byte_obj >= Begin() && byte_obj < End();
-  }
-
   // TODO: Change this? Mainly used for compacting to a particular region of memory.
   BumpPointerSpace(const std::string& name, uint8_t* begin, uint8_t* limit);
 
-  // Return the object which comes after obj, while ensuring alignment.
-  static mirror::Object* GetNextObject(mirror::Object* obj)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   // Allocate a new TLAB, returns false if the allocation failed.
   bool AllocNewTlab(Thread* self, size_t bytes) REQUIRES(!block_lock_);
 
@@ -165,7 +159,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Object alignment within the space.
-  static constexpr size_t kAlignment = 8;
+  static constexpr size_t kAlignment = kObjectAlignment;
 
  protected:
   BumpPointerSpace(const std::string& name, MemMap&& mem_map);
@@ -183,23 +177,40 @@
   AtomicInteger objects_allocated_;  // Accumulated from revoked thread local regions.
   AtomicInteger bytes_allocated_;  // Accumulated from revoked thread local regions.
   Mutex block_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
-  // The objects at the start of the space are stored in the main block. The main block doesn't
-  // have a header, this lets us walk empty spaces which are mprotected.
+  // The objects at the start of the space are stored in the main block.
   size_t main_block_size_ GUARDED_BY(block_lock_);
-  // The number of blocks in the space, if it is 0 then the space has one long continuous block
-  // which doesn't have an updated header.
-  size_t num_blocks_ GUARDED_BY(block_lock_);
+  // List of block sizes (in bytes) after the main-block. Needed for Walk().
+  // If empty then the space has only one long continuous block. Each TLAB
+  // allocation has one entry in this deque.
+  // Keeping block-sizes off-heap simplifies sliding compaction algorithms.
+  // The compaction algorithm should ideally compact all objects into the main
+  // block, thereby enabling erasing corresponding entries from here.
+  std::deque<size_t> block_sizes_ GUARDED_BY(block_lock_);
 
  private:
-  struct BlockHeader {
-    size_t size_;  // Size of the block in bytes, does not include the header.
-    size_t unused_;  // Ensures alignment of kAlignment.
-  };
+  // Return the object which comes after obj, while ensuring alignment.
+  static mirror::Object* GetNextObject(mirror::Object* obj)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
-  static_assert(sizeof(BlockHeader) % kAlignment == 0,
-                "continuous block must be kAlignment aligned");
+  // Return a vector of block sizes on the space. Required by MarkCompact GC for
+  // walking black objects allocated after marking phase.
+  std::vector<size_t>* GetBlockSizes(Thread* self, size_t* main_block_size) REQUIRES(!block_lock_);
+
+  // Once the MarkCompact decides the post-compact layout of the space in the
+  // pre-compaction pause, it calls this function to update the block sizes. It is
+  // done by passing the new main-block size, which consumes a bunch of blocks
+  // into itself, and the index of first unconsumed block. This works as all the
+  // block sizes are ordered. Also updates 'end_' to reflect the change.
+  void SetBlockSizes(Thread* self, const size_t main_block_size, const size_t first_valid_idx)
+      REQUIRES(!block_lock_, Locks::mutator_lock_);
+
+  // Align end to the given alignment. This is done in MarkCompact GC when
+  // mutators are suspended so that upcoming TLAB allocations start with a new
+  // page. Returns the pre-alignment end.
+  uint8_t* AlignEnd(Thread* self, size_t alignment) REQUIRES(Locks::mutator_lock_);
 
   friend class collector::MarkSweep;
+  friend class collector::MarkCompact;
   DISALLOW_COPY_AND_ASSIGN(BumpPointerSpace);
 };
 
diff --git a/runtime/gc/space/dlmalloc_space-inl.h b/runtime/gc/space/dlmalloc_space-inl.h
index 4fc4ada..6041fd0 100644
--- a/runtime/gc/space/dlmalloc_space-inl.h
+++ b/runtime/gc/space/dlmalloc_space-inl.h
@@ -18,7 +18,7 @@
 #define ART_RUNTIME_GC_SPACE_DLMALLOC_SPACE_INL_H_
 
 #include "dlmalloc_space.h"
-#include "gc/allocator/dlmalloc.h"
+#include "gc/allocator/art-dlmalloc.h"
 #include "thread.h"
 
 namespace art {
diff --git a/runtime/gc/space/dlmalloc_space.cc b/runtime/gc/space/dlmalloc_space.cc
index 25cac7e..1edcdbd 100644
--- a/runtime/gc/space/dlmalloc_space.cc
+++ b/runtime/gc/space/dlmalloc_space.cc
@@ -350,11 +350,18 @@
 }
 #endif
 
+struct MspaceCbArgs {
+  size_t max_contiguous;
+  size_t used;
+};
+
 static void MSpaceChunkCallback(void* start, void* end, size_t used_bytes, void* arg) {
   size_t chunk_size = reinterpret_cast<uint8_t*>(end) - reinterpret_cast<uint8_t*>(start);
+  MspaceCbArgs* mspace_cb_args = reinterpret_cast<MspaceCbArgs*>(arg);
+  mspace_cb_args->used += used_bytes;
   if (used_bytes < chunk_size) {
     size_t chunk_free_bytes = chunk_size - used_bytes;
-    size_t& max_contiguous_allocation = *reinterpret_cast<size_t*>(arg);
+    size_t& max_contiguous_allocation = mspace_cb_args->max_contiguous;
     max_contiguous_allocation = std::max(max_contiguous_allocation, chunk_free_bytes);
   }
 }
@@ -362,16 +369,17 @@
 bool DlMallocSpace::LogFragmentationAllocFailure(std::ostream& os,
                                                  size_t failed_alloc_bytes) {
   Thread* const self = Thread::Current();
-  size_t max_contiguous_allocation = 0;
+  MspaceCbArgs mspace_cb_args = {0, 0};
   // To allow the Walk/InspectAll() to exclusively-lock the mutator
   // lock, temporarily release the shared access to the mutator
   // lock here by transitioning to the suspended state.
   Locks::mutator_lock_->AssertSharedHeld(self);
   ScopedThreadSuspension sts(self, ThreadState::kSuspended);
-  Walk(MSpaceChunkCallback, &max_contiguous_allocation);
-  if (failed_alloc_bytes > max_contiguous_allocation) {
-    os << "; failed due to fragmentation (largest possible contiguous allocation "
-       <<  max_contiguous_allocation << " bytes)";
+  Walk(MSpaceChunkCallback, &mspace_cb_args);
+  if (failed_alloc_bytes > mspace_cb_args.max_contiguous) {
+    os << "; failed due to malloc_space fragmentation (largest possible contiguous allocation "
+       << mspace_cb_args.max_contiguous << " bytes, space in use " << mspace_cb_args.used
+       << " bytes, capacity = " << Capacity() << ")";
     return true;
   }
   return false;
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 6afd63e..13966d8 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -23,7 +23,9 @@
 #include <memory>
 #include <random>
 #include <string>
+#include <vector>
 
+#include "android-base/logging.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "android-base/unique_fd.h"
@@ -49,6 +51,7 @@
 #include "dex/art_dex_file_loader.h"
 #include "dex/dex_file_loader.h"
 #include "exec_utils.h"
+#include "fmt/format.h"
 #include "gc/accounting/space_bitmap-inl.h"
 #include "gc/task_processor.h"
 #include "image-inl.h"
@@ -69,14 +72,20 @@
 namespace gc {
 namespace space {
 
-using android::base::Join;
-using android::base::StringAppendF;
-using android::base::StringPrintf;
+namespace {
+
+using ::android::base::Join;
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+
+using ::fmt::literals::operator""_format;  // NOLINT
 
 // We do not allow the boot image and extensions to take more than 1GiB. They are
 // supposed to be much smaller and allocating more that this would likely fail anyway.
 static constexpr size_t kMaxTotalImageReservationSize = 1 * GB;
 
+}  // namespace
+
 Atomic<uint32_t> ImageSpace::bitmap_index_(0);
 
 ImageSpace::ImageSpace(const std::string& image_filename,
@@ -198,7 +207,6 @@
 // Helper class for relocating from one range of memory to another.
 class RelocationRange {
  public:
-  RelocationRange() = default;
   RelocationRange(const RelocationRange&) = default;
   RelocationRange(uintptr_t source, uintptr_t dest, uintptr_t length)
       : source_(source),
@@ -372,6 +380,64 @@
       const {}
   void VisitRoot(mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) const {}
 
+  template <typename T> void VisitNativeDexCacheArray(mirror::NativeArray<T>* array)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (array == nullptr) {
+      return;
+    }
+    DCHECK_ALIGNED(array, static_cast<size_t>(kPointerSize));
+    uint32_t size = (kPointerSize == PointerSize::k32)
+        ? reinterpret_cast<uint32_t*>(array)[-1]
+        : dchecked_integral_cast<uint32_t>(reinterpret_cast<uint64_t*>(array)[-1]);
+    for (uint32_t i = 0; i < size; ++i) {
+      PatchNativePointer(array->GetPtrEntryPtrSize(i, kPointerSize));
+    }
+  }
+
+  template <typename T> void VisitGcRootDexCacheArray(mirror::GcRootArray<T>* array)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (array == nullptr) {
+      return;
+    }
+    DCHECK_ALIGNED(array, sizeof(GcRoot<T>));
+    static_assert(sizeof(GcRoot<T>) == sizeof(uint32_t));
+    uint32_t size = reinterpret_cast<uint32_t*>(array)[-1];
+    for (uint32_t i = 0; i < size; ++i) {
+      PatchGcRoot(array->GetGcRootAddress(i));
+    }
+  }
+
+  void VisitDexCacheArrays(ObjPtr<mirror::DexCache> dex_cache)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    mirror::NativeArray<ArtMethod>* old_resolved_methods = dex_cache->GetResolvedMethodsArray();
+    if (old_resolved_methods != nullptr) {
+      mirror::NativeArray<ArtMethod>* resolved_methods = native_visitor_(old_resolved_methods);
+      dex_cache->SetResolvedMethodsArray(resolved_methods);
+      VisitNativeDexCacheArray(resolved_methods);
+    }
+
+    mirror::NativeArray<ArtField>* old_resolved_fields = dex_cache->GetResolvedFieldsArray();
+    if (old_resolved_fields != nullptr) {
+      mirror::NativeArray<ArtField>* resolved_fields = native_visitor_(old_resolved_fields);
+      dex_cache->SetResolvedFieldsArray(resolved_fields);
+      VisitNativeDexCacheArray(resolved_fields);
+    }
+
+    mirror::GcRootArray<mirror::String>* old_strings = dex_cache->GetStringsArray();
+    if (old_strings != nullptr) {
+      mirror::GcRootArray<mirror::String>* strings = native_visitor_(old_strings);
+      dex_cache->SetStringsArray(strings);
+      VisitGcRootDexCacheArray(strings);
+    }
+
+    mirror::GcRootArray<mirror::Class>* old_types = dex_cache->GetResolvedTypesArray();
+    if (old_types != nullptr) {
+      mirror::GcRootArray<mirror::Class>* types = native_visitor_(old_types);
+      dex_cache->SetResolvedTypesArray(types);
+      VisitGcRootDexCacheArray(types);
+    }
+  }
+
   template <bool kMayBeNull = true, typename T>
   ALWAYS_INLINE void PatchGcRoot(/*inout*/GcRoot<T>* root) const
       REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -513,7 +579,8 @@
       // Check the oat file checksum.
       const uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum();
       const uint32_t image_oat_checksum = image_header.GetOatChecksum();
-      if (oat_checksum != image_oat_checksum) {
+      // Note image_oat_checksum is 0 for images generated by the runtime.
+      if (image_oat_checksum != 0u && oat_checksum != image_oat_checksum) {
         *error_msg = StringPrintf("Oat checksum 0x%x does not match the image one 0x%x in image %s",
                                   oat_checksum,
                                   image_oat_checksum,
@@ -1299,6 +1366,16 @@
       image_header->RelocateImageReferences(app_image_objects.Delta());
       image_header->RelocateBootImageReferences(boot_image.Delta());
       CHECK_EQ(image_header->GetImageBegin(), target_base);
+
+      // Fix up dex cache arrays.
+      ObjPtr<mirror::ObjectArray<mirror::DexCache>> dex_caches =
+          image_header->GetImageRoot<kWithoutReadBarrier>(ImageHeader::kDexCaches)
+              ->AsObjectArray<mirror::DexCache, kVerifyNone>();
+      for (int32_t i = 0, count = dex_caches->GetLength(); i < count; ++i) {
+        ObjPtr<mirror::DexCache> dex_cache =
+            dex_caches->GetWithoutChecks<kVerifyNone, kWithoutReadBarrier>(i);
+        patch_object_visitor.VisitDexCacheArrays(dex_cache);
+      }
     }
     {
       // Only touches objects in the app image, no need for mutator lock.
@@ -1366,9 +1443,9 @@
   }
 };
 
-static void AppendImageChecksum(uint32_t component_count,
-                                uint32_t checksum,
-                                /*inout*/std::string* checksums) {
+void ImageSpace::AppendImageChecksum(uint32_t component_count,
+                                     uint32_t checksum,
+                                     /*inout*/ std::string* checksums) {
   static_assert(ImageSpace::kImageChecksumPrefix == 'i', "Format prefix check.");
   StringAppendF(checksums, "i;%u/%08x", component_count, checksum);
 }
@@ -1378,7 +1455,7 @@
                                         /*inout*/std::string_view* oat_checksums,
                                         /*out*/std::string* error_msg) {
   std::string image_checksum;
-  AppendImageChecksum(component_count, checksum, &image_checksum);
+  ImageSpace::AppendImageChecksum(component_count, checksum, &image_checksum);
   if (!StartsWith(*oat_checksums, image_checksum)) {
     *error_msg = StringPrintf("Image checksum mismatch, expected %s to start with %s",
                               std::string(*oat_checksums).c_str(),
@@ -1389,182 +1466,6 @@
   return true;
 }
 
-// Helper class to find the primary boot image and boot image extensions
-// and determine the boot image layout.
-class ImageSpace::BootImageLayout {
- public:
-  // Description of a "chunk" of the boot image, i.e. either primary boot image
-  // or a boot image extension, used in conjunction with the boot class path to
-  // load boot image components.
-  struct ImageChunk {
-    std::string base_location;
-    std::string base_filename;
-    std::vector<std::string> profile_files;
-    size_t start_index;
-    uint32_t component_count;
-    uint32_t image_space_count;
-    uint32_t reservation_size;
-    uint32_t checksum;
-    uint32_t boot_image_component_count;
-    uint32_t boot_image_checksum;
-    uint32_t boot_image_size;
-
-    // The following file descriptors hold the memfd files for extensions compiled
-    // in memory and described by the above fields. We want to use them to mmap()
-    // the contents and then close them while treating the ImageChunk description
-    // as immutable (const), so make these fields explicitly mutable.
-    mutable android::base::unique_fd art_fd;
-    mutable android::base::unique_fd vdex_fd;
-    mutable android::base::unique_fd oat_fd;
-  };
-
-  BootImageLayout(ArrayRef<const std::string> image_locations,
-                  ArrayRef<const std::string> boot_class_path,
-                  ArrayRef<const std::string> boot_class_path_locations,
-                  ArrayRef<const int> boot_class_path_fds,
-                  ArrayRef<const int> boot_class_path_image_fds,
-                  ArrayRef<const int> boot_class_path_vdex_fds,
-                  ArrayRef<const int> boot_class_path_oat_fds)
-     : image_locations_(image_locations),
-       boot_class_path_(boot_class_path),
-       boot_class_path_locations_(boot_class_path_locations),
-       boot_class_path_fds_(boot_class_path_fds),
-       boot_class_path_image_fds_(boot_class_path_image_fds),
-       boot_class_path_vdex_fds_(boot_class_path_vdex_fds),
-       boot_class_path_oat_fds_(boot_class_path_oat_fds) {}
-
-  std::string GetPrimaryImageLocation();
-
-  bool LoadFromSystem(InstructionSet image_isa, /*out*/std::string* error_msg) {
-    return LoadOrValidateFromSystem(image_isa, /*oat_checksums=*/ nullptr, error_msg);
-  }
-
-  bool ValidateFromSystem(InstructionSet image_isa,
-                          /*inout*/std::string_view* oat_checksums,
-                          /*out*/std::string* error_msg) {
-    DCHECK(oat_checksums != nullptr);
-    return LoadOrValidateFromSystem(image_isa, oat_checksums, error_msg);
-  }
-
-  ArrayRef<const ImageChunk> GetChunks() const {
-    return ArrayRef<const ImageChunk>(chunks_);
-  }
-
-  uint32_t GetBaseAddress() const {
-    return base_address_;
-  }
-
-  size_t GetNextBcpIndex() const {
-    return next_bcp_index_;
-  }
-
-  size_t GetTotalComponentCount() const {
-    return total_component_count_;
-  }
-
-  size_t GetTotalReservationSize() const {
-    return total_reservation_size_;
-  }
-
- private:
-  struct NamedComponentLocation {
-    std::string base_location;
-    size_t bcp_index;
-    std::vector<std::string> profile_filenames;
-  };
-
-  std::string ExpandLocationImpl(const std::string& location,
-                                 size_t bcp_index,
-                                 bool boot_image_extension) {
-    std::vector<std::string> expanded = ExpandMultiImageLocations(
-        ArrayRef<const std::string>(boot_class_path_).SubArray(bcp_index, 1u),
-        location,
-        boot_image_extension);
-    DCHECK_EQ(expanded.size(), 1u);
-    return expanded[0];
-  }
-
-  std::string ExpandLocation(const std::string& location, size_t bcp_index) {
-    if (bcp_index == 0u) {
-      DCHECK_EQ(location, ExpandLocationImpl(location, bcp_index, /*boot_image_extension=*/ false));
-      return location;
-    } else {
-      return ExpandLocationImpl(location, bcp_index, /*boot_image_extension=*/ true);
-    }
-  }
-
-  std::string GetBcpComponentPath(size_t bcp_index) {
-    DCHECK_LE(bcp_index, boot_class_path_.size());
-    size_t bcp_slash_pos = boot_class_path_[bcp_index].rfind('/');
-    DCHECK_NE(bcp_slash_pos, std::string::npos);
-    return boot_class_path_[bcp_index].substr(0u, bcp_slash_pos + 1u);
-  }
-
-  bool VerifyImageLocation(ArrayRef<const std::string> components,
-                           /*out*/size_t* named_components_count,
-                           /*out*/std::string* error_msg);
-
-  bool MatchNamedComponents(
-      ArrayRef<const std::string> named_components,
-      /*out*/std::vector<NamedComponentLocation>* named_component_locations,
-      /*out*/std::string* error_msg);
-
-  bool ValidateBootImageChecksum(const char* file_description,
-                                 const ImageHeader& header,
-                                 /*out*/std::string* error_msg);
-
-  bool ValidateHeader(const ImageHeader& header,
-                      size_t bcp_index,
-                      const char* file_description,
-                      /*out*/std::string* error_msg);
-
-  bool ValidateOatFile(const std::string& base_location,
-                       const std::string& base_filename,
-                       size_t bcp_index,
-                       size_t component_count,
-                       /*out*/std::string* error_msg);
-
-  bool ReadHeader(const std::string& base_location,
-                  const std::string& base_filename,
-                  size_t bcp_index,
-                  /*out*/std::string* error_msg);
-
-  // Compiles a consecutive subsequence of bootclasspath dex files, whose contents are included in
-  // the profiles specified by `profile_filenames`, starting from `bcp_index`.
-  bool CompileBootclasspathElements(const std::string& base_location,
-                                    const std::string& base_filename,
-                                    size_t bcp_index,
-                                    const std::vector<std::string>& profile_filenames,
-                                    ArrayRef<const std::string> dependencies,
-                                    /*out*/std::string* error_msg);
-
-  bool CheckAndRemoveLastChunkChecksum(/*inout*/std::string_view* oat_checksums,
-                                       /*out*/std::string* error_msg);
-
-  template <typename FilenameFn>
-  bool LoadOrValidate(FilenameFn&& filename_fn,
-                      /*inout*/std::string_view* oat_checksums,
-                      /*out*/std::string* error_msg);
-
-  bool LoadOrValidateFromSystem(InstructionSet image_isa,
-                                /*inout*/std::string_view* oat_checksums,
-                                /*out*/std::string* error_msg);
-
-  ArrayRef<const std::string> image_locations_;
-  ArrayRef<const std::string> boot_class_path_;
-  ArrayRef<const std::string> boot_class_path_locations_;
-  ArrayRef<const int> boot_class_path_fds_;
-  ArrayRef<const int> boot_class_path_image_fds_;
-  ArrayRef<const int> boot_class_path_vdex_fds_;
-  ArrayRef<const int> boot_class_path_oat_fds_;
-
-  std::vector<ImageChunk> chunks_;
-  uint32_t base_address_ = 0u;
-  size_t next_bcp_index_ = 0u;
-  size_t total_component_count_ = 0u;
-  size_t total_reservation_size_ = 0u;
-};
-
 std::string ImageSpace::BootImageLayout::GetPrimaryImageLocation() {
   DCHECK(!image_locations_.empty());
   std::string location = image_locations_[0];
@@ -1886,7 +1787,7 @@
                               error_msg->c_str());
     return false;
   }
-  if (!ImageSpace::ValidateOatFile(*oat_file, error_msg, dex_filenames, dex_fds)) {
+  if (!ImageSpace::ValidateOatFile(*oat_file, error_msg, dex_filenames, dex_fds, apex_versions_)) {
     return false;
   }
   return true;
@@ -2151,48 +2052,12 @@
   return true;
 }
 
-bool ImageSpace::BootImageLayout::CheckAndRemoveLastChunkChecksum(
-    /*inout*/std::string_view* oat_checksums,
-    /*out*/std::string* error_msg) {
-  DCHECK(oat_checksums != nullptr);
-  DCHECK(!chunks_.empty());
-  const ImageChunk& chunk = chunks_.back();
-  size_t component_count = chunk.component_count;
-  size_t checksum = chunk.checksum;
-  if (!CheckAndRemoveImageChecksum(component_count, checksum, oat_checksums, error_msg)) {
-    DCHECK(!error_msg->empty());
-    return false;
-  }
-  if (oat_checksums->empty()) {
-    if (next_bcp_index_ != boot_class_path_.size()) {
-      *error_msg = StringPrintf("Checksum too short, missing %zu components.",
-                                boot_class_path_.size() - next_bcp_index_);
-      return false;
-    }
-    return true;
-  }
-  if (!StartsWith(*oat_checksums, ":")) {
-    *error_msg = StringPrintf("Missing ':' separator at start of %s",
-                              std::string(*oat_checksums).c_str());
-    return false;
-  }
-  oat_checksums->remove_prefix(1u);
-  if (oat_checksums->empty()) {
-    *error_msg = "Missing checksums after the ':' separator.";
-    return false;
-  }
-  return true;
-}
-
 template <typename FilenameFn>
-bool ImageSpace::BootImageLayout::LoadOrValidate(FilenameFn&& filename_fn,
-                                                 /*inout*/std::string_view* oat_checksums,
-                                                 /*out*/std::string* error_msg) {
+bool ImageSpace::BootImageLayout::Load(FilenameFn&& filename_fn,
+                                       bool allow_in_memory_compilation,
+                                       /*out*/ std::string* error_msg) {
   DCHECK(GetChunks().empty());
   DCHECK_EQ(GetBaseAddress(), 0u);
-  bool validate = (oat_checksums != nullptr);
-  static_assert(ImageSpace::kImageChecksumPrefix == 'i', "Format prefix check.");
-  DCHECK_IMPLIES(validate, StartsWith(*oat_checksums, "i"));
 
   ArrayRef<const std::string> components = image_locations_;
   size_t named_components_count = 0u;
@@ -2223,24 +2088,21 @@
       LOG(ERROR) << "Named image component already covered by previous image: " << base_location;
       continue;
     }
-    if (validate && bcp_index > bcp_pos) {
-      *error_msg = StringPrintf("End of contiguous boot class path images, remaining checksum: %s",
-                                std::string(*oat_checksums).c_str());
-      return false;
-    }
     std::string local_error_msg;
-    std::string* err_msg = validate ? error_msg : &local_error_msg;
     std::string base_filename;
-    if (!filename_fn(base_location, &base_filename, err_msg) ||
-        !ReadHeader(base_location, base_filename, bcp_index, err_msg)) {
-      if (validate) {
-        return false;
-      }
+    if (!filename_fn(base_location, &base_filename, &local_error_msg) ||
+        !ReadHeader(base_location, base_filename, bcp_index, &local_error_msg)) {
       LOG(ERROR) << "Error reading named image component header for " << base_location
                  << ", error: " << local_error_msg;
       // If the primary boot image is invalid, we generate a single full image. This is faster than
       // generating the primary boot image and the extension separately.
       if (bcp_index == 0) {
+        if (!allow_in_memory_compilation) {
+          // The boot image is unusable and we can't continue by generating a boot image in memory.
+          // All we can do is to return.
+          *error_msg = std::move(local_error_msg);
+          return false;
+        }
         // We must at least have profiles for the core libraries.
         if (profile_filenames.empty()) {
           *error_msg = "Full boot image cannot be compiled because no profile is provided.";
@@ -2264,14 +2126,15 @@
         // No extensions are needed.
         return true;
       }
-      if (profile_filenames.empty() ||
+      bool should_compile_extension = allow_in_memory_compilation && !profile_filenames.empty();
+      if (!should_compile_extension ||
           !CompileBootclasspathElements(base_location,
                                         base_filename,
                                         bcp_index,
                                         profile_filenames,
                                         components.SubArray(/*pos=*/ 0, /*length=*/ 1),
                                         &local_error_msg)) {
-        if (!profile_filenames.empty()) {
+        if (should_compile_extension) {
           LOG(ERROR) << "Error compiling boot image extension for " << boot_class_path_[bcp_index]
                      << ", error: " << local_error_msg;
         }
@@ -2280,14 +2143,6 @@
         continue;
       }
     }
-    if (validate) {
-      if (!CheckAndRemoveLastChunkChecksum(oat_checksums, error_msg)) {
-        return false;
-      }
-      if (oat_checksums->empty() || !StartsWith(*oat_checksums, "i")) {
-        return true;  // Let the caller deal with the dex file checksums if any.
-      }
-    }
     bcp_pos = GetNextBcpIndex();
   }
 
@@ -2320,24 +2175,10 @@
           VLOG(image) << "Found image extension for " << ExpandLocation(base_location, bcp_pos);
           bcp_pos = GetNextBcpIndex();
           found = true;
-          if (validate) {
-            if (!CheckAndRemoveLastChunkChecksum(oat_checksums, error_msg)) {
-              return false;
-            }
-            if (oat_checksums->empty() || !StartsWith(*oat_checksums, "i")) {
-              return true;  // Let the caller deal with the dex file checksums if any.
-            }
-          }
           break;
         }
       }
       if (!found) {
-        if (validate) {
-          *error_msg = StringPrintf("Missing extension for %s, remaining checksum: %s",
-                                    bcp_component.c_str(),
-                                    std::string(*oat_checksums).c_str());
-          return false;
-        }
         ++bcp_pos;
       }
     }
@@ -2346,16 +2187,16 @@
   return true;
 }
 
-bool ImageSpace::BootImageLayout::LoadOrValidateFromSystem(InstructionSet image_isa,
-                                                           /*inout*/std::string_view* oat_checksums,
-                                                           /*out*/std::string* error_msg) {
+bool ImageSpace::BootImageLayout::LoadFromSystem(InstructionSet image_isa,
+                                                 bool allow_in_memory_compilation,
+                                                 /*out*/ std::string* error_msg) {
   auto filename_fn = [image_isa](const std::string& location,
                                  /*out*/std::string* filename,
                                  /*out*/std::string* err_msg ATTRIBUTE_UNUSED) {
     *filename = GetSystemImageFilename(location.c_str(), image_isa);
     return true;
   };
-  return LoadOrValidate(filename_fn, oat_checksums, error_msg);
+  return Load(filename_fn, allow_in_memory_compilation, error_msg);
 }
 
 class ImageSpace::BootImageLoader {
@@ -2403,6 +2244,7 @@
   bool HasSystem() const { return has_system_; }
 
   bool LoadFromSystem(size_t extra_reservation_size,
+                      bool allow_in_memory_compilation,
                       /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces,
                       /*out*/MemMap* extra_reservation,
                       /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -2697,7 +2539,8 @@
       int32_t class_roots_index = enum_cast<int32_t>(ImageHeader::kClassRoots);
       DCHECK_LT(class_roots_index, image_roots->GetLength<kVerifyNone>());
       class_roots = ObjPtr<mirror::ObjectArray<mirror::Class>>::DownCast(base_relocate_visitor(
-          image_roots->GetWithoutChecks<kVerifyNone>(class_roots_index).Ptr()));
+          image_roots->GetWithoutChecks<kVerifyNone,
+                                        kWithoutReadBarrier>(class_roots_index).Ptr()));
       if (kExtension) {
         // Class roots must have been visited if we relocated the primary boot image.
         DCHECK(base_diff == 0 || patched_objects->Test(class_roots.Ptr()));
@@ -2863,6 +2706,14 @@
       DCHECK_EQ(base_diff64, 0);
     }
 
+    // While `Thread::Current()` is null, the `ScopedDebugDisallowReadBarriers`
+    // cannot be used but the class `ReadBarrier` shall not allow read barriers anyway.
+    // For some gtests we actually have an initialized `Thread:Current()`.
+    std::optional<ScopedDebugDisallowReadBarriers> sddrb(std::nullopt);
+    if (kCheckDebugDisallowReadBarrierCount && Thread::Current() != nullptr) {
+      sddrb.emplace(Thread::Current());
+    }
+
     ArrayRef<const std::unique_ptr<ImageSpace>> spaces_ref(spaces);
     PointerSize pointer_size = first_space_header.GetPointerSize();
     if (pointer_size == PointerSize::k64) {
@@ -3111,8 +2962,24 @@
         return false;
       }
     }
+
+    // As an optimization, madvise the oat file into memory if it's being used
+    // for execution with an active runtime. This can significantly improve
+    // ZygoteInit class preload performance.
+    if (executable_) {
+      Runtime* runtime = Runtime::Current();
+      if (runtime != nullptr) {
+        Runtime::MadviseFileForRange(runtime->GetMadviseWillNeedSizeOdex(),
+                                     oat_file->Size(),
+                                     oat_file->Begin(),
+                                     oat_file->End(),
+                                     oat_file->GetLocation());
+      }
+    }
+
     space->oat_file_ = std::move(oat_file);
     space->oat_file_non_owned_ = space->oat_file_.get();
+
     return true;
   }
 
@@ -3345,6 +3212,7 @@
 
 bool ImageSpace::BootImageLoader::LoadFromSystem(
     size_t extra_reservation_size,
+    bool allow_in_memory_compilation,
     /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces,
     /*out*/MemMap* extra_reservation,
     /*out*/std::string* error_msg) {
@@ -3357,7 +3225,7 @@
                          boot_class_path_image_fds_,
                          boot_class_path_vdex_fds_,
                          boot_class_path_oat_fds_);
-  if (!layout.LoadFromSystem(image_isa_, error_msg)) {
+  if (!layout.LoadFromSystem(image_isa_, allow_in_memory_compilation, error_msg)) {
     return false;
   }
 
@@ -3420,6 +3288,7 @@
     bool relocate,
     bool executable,
     size_t extra_reservation_size,
+    bool allow_in_memory_compilation,
     /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces,
     /*out*/MemMap* extra_reservation) {
   ScopedTrace trace(__FUNCTION__);
@@ -3450,8 +3319,11 @@
   std::vector<std::string> error_msgs;
 
   std::string error_msg;
-  if (loader.LoadFromSystem(
-          extra_reservation_size, boot_image_spaces, extra_reservation, &error_msg)) {
+  if (loader.LoadFromSystem(extra_reservation_size,
+                            allow_in_memory_compilation,
+                            boot_image_spaces,
+                            extra_reservation,
+                            &error_msg)) {
     return true;
   }
   error_msgs.push_back(error_msg);
@@ -3519,54 +3391,66 @@
       << ",name=\"" << GetName() << "\"]";
 }
 
-bool ImageSpace::ValidateApexVersions(const OatFile& oat_file, std::string* error_msg) {
+bool ImageSpace::ValidateApexVersions(const OatHeader& oat_header,
+                                      const std::string& apex_versions,
+                                      const std::string& file_location,
+                                      std::string* error_msg) {
   // For a boot image, the key value store only exists in the first OAT file. Skip other OAT files.
-  if (oat_file.GetOatHeader().GetKeyValueStoreSize() == 0) {
+  if (oat_header.GetKeyValueStoreSize() == 0) {
     return true;
   }
 
-  // The OAT files in the ART APEX is built on host, so they don't have the right APEX versions. It
-  // is safe to assume that they are always up-to-date because they are shipped along with the
-  // runtime and the dex files.
-  if (kIsTargetAndroid && android::base::StartsWith(oat_file.GetLocation(), GetArtRoot())) {
-    return true;
-  }
-
-  const char* oat_apex_versions =
-      oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kApexVersionsKey);
+  const char* oat_apex_versions = oat_header.GetStoreValueByKey(OatHeader::kApexVersionsKey);
   if (oat_apex_versions == nullptr) {
     *error_msg = StringPrintf("ValidateApexVersions failed to get APEX versions from oat file '%s'",
-                              oat_file.GetLocation().c_str());
+                              file_location.c_str());
     return false;
   }
   // For a boot image, it can be generated from a subset of the bootclasspath.
   // For an app image, some dex files get compiled with a subset of the bootclasspath.
   // For such cases, the OAT APEX versions will be a prefix of the runtime APEX versions.
-  if (!android::base::StartsWith(Runtime::Current()->GetApexVersions(), oat_apex_versions)) {
+  if (!android::base::StartsWith(apex_versions, oat_apex_versions)) {
     *error_msg = StringPrintf(
         "ValidateApexVersions found APEX versions mismatch between oat file '%s' and the runtime "
         "(Oat file: '%s', Runtime: '%s')",
-        oat_file.GetLocation().c_str(),
+        file_location.c_str(),
         oat_apex_versions,
-        Runtime::Current()->GetApexVersions().c_str());
+        apex_versions.c_str());
     return false;
   }
   return true;
 }
 
 bool ImageSpace::ValidateOatFile(const OatFile& oat_file, std::string* error_msg) {
-  return ValidateOatFile(oat_file, error_msg, ArrayRef<const std::string>(), ArrayRef<const int>());
+  DCHECK(Runtime::Current() != nullptr);
+  return ValidateOatFile(oat_file,
+                         error_msg,
+                         ArrayRef<const std::string>(),
+                         ArrayRef<const int>(),
+                         Runtime::Current()->GetApexVersions());
 }
 
 bool ImageSpace::ValidateOatFile(const OatFile& oat_file,
                                  std::string* error_msg,
                                  ArrayRef<const std::string> dex_filenames,
-                                 ArrayRef<const int> dex_fds) {
-  if (!ValidateApexVersions(oat_file, error_msg)) {
+                                 ArrayRef<const int> dex_fds,
+                                 const std::string& apex_versions) {
+  if (!ValidateApexVersions(oat_file.GetOatHeader(),
+                            apex_versions,
+                            oat_file.GetLocation(),
+                            error_msg)) {
     return false;
   }
 
-  const ArtDexFileLoader dex_file_loader;
+  // For a boot image, the key value store only exists in the first OAT file. Skip other OAT files.
+  if (oat_file.GetOatHeader().GetKeyValueStoreSize() != 0 &&
+      oat_file.GetOatHeader().IsConcurrentCopying() != gUseReadBarrier) {
+    *error_msg =
+        "ValidateOatFile found read barrier state mismatch (oat file: {}, runtime: {})"_format(
+            oat_file.GetOatHeader().IsConcurrentCopying(), gUseReadBarrier);
+    return false;
+  }
+
   size_t dex_file_index = 0;
   for (const OatDexFile* oat_dex_file : oat_file.GetOatDexFiles()) {
     // Skip multidex locations - These will be checked when we visit their
@@ -3583,7 +3467,7 @@
 
     std::vector<uint32_t> checksums;
     std::vector<std::string> dex_locations_ignored;
-    if (!dex_file_loader.GetMultiDexChecksums(
+    if (!ArtDexFileLoader::GetMultiDexChecksums(
             dex_file_location.c_str(), &checksums, &dex_locations_ignored, error_msg, dex_fd)) {
       *error_msg = StringPrintf("ValidateOatFile failed to get checksums of dex file '%s' "
                                 "referenced by oat file %s: %s",
@@ -3695,7 +3579,7 @@
   return n;
 }
 
-static size_t CheckAndCountBCPComponents(std::string_view oat_boot_class_path,
+size_t ImageSpace::CheckAndCountBCPComponents(std::string_view oat_boot_class_path,
                                          ArrayRef<const std::string> boot_class_path,
                                          /*out*/std::string* error_msg) {
   // Check that the oat BCP is a prefix of current BCP locations and count components.
@@ -3727,110 +3611,6 @@
   return component_count;
 }
 
-bool ImageSpace::VerifyBootClassPathChecksums(std::string_view oat_checksums,
-                                              std::string_view oat_boot_class_path,
-                                              ArrayRef<const std::string> image_locations,
-                                              ArrayRef<const std::string> boot_class_path_locations,
-                                              ArrayRef<const std::string> boot_class_path,
-                                              ArrayRef<const int> boot_class_path_fds,
-                                              InstructionSet image_isa,
-                                              /*out*/std::string* error_msg) {
-  if (oat_checksums.empty() || oat_boot_class_path.empty()) {
-    *error_msg = oat_checksums.empty() ? "Empty checksums." : "Empty boot class path.";
-    return false;
-  }
-
-  DCHECK_EQ(boot_class_path_locations.size(), boot_class_path.size());
-  size_t bcp_size =
-      CheckAndCountBCPComponents(oat_boot_class_path, boot_class_path_locations, error_msg);
-  if (bcp_size == static_cast<size_t>(-1)) {
-    DCHECK(!error_msg->empty());
-    return false;
-  }
-
-  size_t bcp_pos = 0u;
-  if (StartsWith(oat_checksums, "i")) {
-    // Use only the matching part of the BCP for validation.  FDs are optional, so only pass the
-    // sub-array if provided.
-    ArrayRef<const int> bcp_fds = boot_class_path_fds.empty()
-        ? ArrayRef<const int>()
-        : boot_class_path_fds.SubArray(/*pos=*/ 0u, bcp_size);
-    BootImageLayout layout(image_locations,
-                           boot_class_path.SubArray(/*pos=*/ 0u, bcp_size),
-                           boot_class_path_locations.SubArray(/*pos=*/ 0u, bcp_size),
-                           bcp_fds,
-                           /*boot_class_path_image_fds=*/ ArrayRef<const int>(),
-                           /*boot_class_path_vdex_fds=*/ ArrayRef<const int>(),
-                           /*boot_class_path_oat_fds=*/ ArrayRef<const int>());
-    std::string primary_image_location = layout.GetPrimaryImageLocation();
-    std::string system_filename;
-    bool has_system = false;
-    if (!FindImageFilename(primary_image_location.c_str(),
-                           image_isa,
-                           &system_filename,
-                           &has_system)) {
-      *error_msg = StringPrintf("Unable to find image file for %s and %s",
-                                android::base::Join(image_locations, kComponentSeparator).c_str(),
-                                GetInstructionSetString(image_isa));
-      return false;
-    }
-
-    DCHECK(has_system);
-    if (!layout.ValidateFromSystem(image_isa, &oat_checksums, error_msg)) {
-      return false;
-    }
-    bcp_pos = layout.GetNextBcpIndex();
-  }
-
-  for ( ; bcp_pos != bcp_size; ++bcp_pos) {
-    static_assert(ImageSpace::kDexFileChecksumPrefix == 'd', "Format prefix check.");
-    if (!StartsWith(oat_checksums, "d")) {
-      *error_msg = StringPrintf("Missing dex checksums, expected %s to start with 'd'",
-                                std::string(oat_checksums).c_str());
-      return false;
-    }
-    oat_checksums.remove_prefix(1u);
-
-    const std::string& bcp_filename = boot_class_path[bcp_pos];
-    std::vector<uint32_t> checksums;
-    std::vector<std::string> dex_locations;
-    const ArtDexFileLoader dex_file_loader;
-    if (!dex_file_loader.GetMultiDexChecksums(bcp_filename.c_str(),
-                                              &checksums,
-                                              &dex_locations,
-                                              error_msg)) {
-      return false;
-    }
-    DCHECK(!checksums.empty());
-    for (uint32_t checksum : checksums) {
-      std::string dex_file_checksum = StringPrintf("/%08x", checksum);
-      if (!StartsWith(oat_checksums, dex_file_checksum)) {
-        *error_msg = StringPrintf(
-            "Dex checksum mismatch for bootclasspath file %s, expected %s to start with %s",
-            bcp_filename.c_str(),
-            std::string(oat_checksums).c_str(),
-            dex_file_checksum.c_str());
-        return false;
-      }
-      oat_checksums.remove_prefix(dex_file_checksum.size());
-    }
-    if (bcp_pos + 1u != bcp_size) {
-      if (!StartsWith(oat_checksums, ":")) {
-        *error_msg = StringPrintf("Missing ':' separator at start of %s",
-                                  std::string(oat_checksums).c_str());
-        return false;
-      }
-      oat_checksums.remove_prefix(1u);
-    }
-  }
-  if (!oat_checksums.empty()) {
-    *error_msg = StringPrintf("Checksum too long, unexpected tail %s",
-                              std::string(oat_checksums).c_str());
-    return false;
-  }
-  return true;
-}
-
 bool ImageSpace::VerifyBootClassPathChecksums(
     std::string_view oat_checksums,
     std::string_view oat_boot_class_path,
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index 8a93f2b..1a85456 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -17,16 +17,19 @@
 #ifndef ART_RUNTIME_GC_SPACE_IMAGE_SPACE_H_
 #define ART_RUNTIME_GC_SPACE_IMAGE_SPACE_H_
 
+#include "android-base/unique_fd.h"
+#include "base/array_ref.h"
 #include "gc/accounting/space_bitmap.h"
 #include "image.h"
+#include "runtime.h"
 #include "space.h"
 
 namespace art {
 
-template <typename T> class ArrayRef;
 class DexFile;
 enum class InstructionSet;
 class OatFile;
+class OatHeader;
 
 namespace gc {
 namespace space {
@@ -142,6 +145,7 @@
       bool relocate,
       bool executable,
       size_t extra_reservation_size,
+      bool allow_in_memory_compilation,
       /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces,
       /*out*/MemMap* extra_reservation) REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -239,18 +243,6 @@
   // Returns the total number of components (jar files) associated with the image spaces.
   static size_t GetNumberOfComponents(ArrayRef<gc::space::ImageSpace* const> image_spaces);
 
-  // Returns whether the checksums are valid for the given boot class path,
-  // image location and ISA (may differ from the ISA of an initialized Runtime).
-  // The boot image and dex files do not need to be loaded in memory.
-  static bool VerifyBootClassPathChecksums(std::string_view oat_checksums,
-                                           std::string_view oat_boot_class_path,
-                                           ArrayRef<const std::string> image_locations,
-                                           ArrayRef<const std::string> boot_class_path_locations,
-                                           ArrayRef<const std::string> boot_class_path,
-                                           ArrayRef<const int> boot_class_path_fds,
-                                           InstructionSet image_isa,
-                                           /*out*/std::string* error_msg);
-
   // Returns whether the oat checksums and boot class path description are valid
   // for the given boot image spaces and boot class path. Used for boot image extensions.
   static bool VerifyBootClassPathChecksums(
@@ -267,8 +259,11 @@
       const std::string& image_location,
       bool boot_image_extension = false);
 
-  // Returns true if the APEX versions in the OAT file match the current APEX versions.
-  static bool ValidateApexVersions(const OatFile& oat_file, std::string* error_msg);
+  // Returns true if the APEX versions in the OAT header match the given APEX versions.
+  static bool ValidateApexVersions(const OatHeader& oat_header,
+                                   const std::string& apex_versions,
+                                   const std::string& file_location,
+                                   std::string* error_msg);
 
   // Returns true if the dex checksums in the given oat file match the
   // checksums of the original dex files on disk. This is intended to be used
@@ -279,17 +274,23 @@
   // oat and odex file.
   //
   // This function is exposed for testing purposes.
+  //
+  // Calling this function requires an active runtime.
   static bool ValidateOatFile(const OatFile& oat_file, std::string* error_msg);
 
   // Same as above, but allows to use `dex_filenames` and `dex_fds` to find the dex files instead of
-  // using the dex filenames in the header of the oat file. This overload is useful when the actual
-  // dex filenames are different from what's in the header (e.g., when we run dex2oat on host), or
-  // when the runtime can only access files through FDs (e.g., when we run dex2oat on target in a
-  // restricted SELinux domain).
+  // using the dex filenames in the header of the oat file, and also takes `apex_versions` from the
+  // input. This overload is useful when the actual dex filenames are different from what's in the
+  // header (e.g., when we run dex2oat on host), when the runtime can only access files through FDs
+  // (e.g., when we run dex2oat on target in a restricted SELinux domain), or when there is no
+  // active runtime.
+  //
+  // Calling this function does not require an active runtime.
   static bool ValidateOatFile(const OatFile& oat_file,
                               std::string* error_msg,
                               ArrayRef<const std::string> dex_filenames,
-                              ArrayRef<const int> dex_fds);
+                              ArrayRef<const int> dex_fds,
+                              const std::string& apex_versions);
 
   // Return the end of the image which includes non-heap objects such as ArtMethods and ArtFields.
   uint8_t* GetImageEnd() const {
@@ -303,6 +304,182 @@
 
   void ReleaseMetadata() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  static void AppendImageChecksum(uint32_t component_count,
+                                  uint32_t checksum,
+                                  /*inout*/ std::string* checksums);
+
+  static size_t CheckAndCountBCPComponents(std::string_view oat_boot_class_path,
+                                           ArrayRef<const std::string> boot_class_path,
+                                           /*out*/ std::string* error_msg);
+
+  // Helper class to find the primary boot image and boot image extensions
+  // and determine the boot image layout.
+  class BootImageLayout {
+   public:
+    // Description of a "chunk" of the boot image, i.e. either primary boot image
+    // or a boot image extension, used in conjunction with the boot class path to
+    // load boot image components.
+    struct ImageChunk {
+      std::string base_location;
+      std::string base_filename;
+      std::vector<std::string> profile_files;
+      size_t start_index;
+      uint32_t component_count;
+      uint32_t image_space_count;
+      uint32_t reservation_size;
+      uint32_t checksum;
+      uint32_t boot_image_component_count;
+      uint32_t boot_image_checksum;
+      uint32_t boot_image_size;
+
+      // The following file descriptors hold the memfd files for extensions compiled
+      // in memory and described by the above fields. We want to use them to mmap()
+      // the contents and then close them while treating the ImageChunk description
+      // as immutable (const), so make these fields explicitly mutable.
+      mutable android::base::unique_fd art_fd;
+      mutable android::base::unique_fd vdex_fd;
+      mutable android::base::unique_fd oat_fd;
+    };
+
+    BootImageLayout(ArrayRef<const std::string> image_locations,
+                    ArrayRef<const std::string> boot_class_path,
+                    ArrayRef<const std::string> boot_class_path_locations,
+                    ArrayRef<const int> boot_class_path_fds,
+                    ArrayRef<const int> boot_class_path_image_fds,
+                    ArrayRef<const int> boot_class_path_vdex_fds,
+                    ArrayRef<const int> boot_class_path_oat_fds,
+                    const std::string* apex_versions = nullptr)
+        : image_locations_(image_locations),
+          boot_class_path_(boot_class_path),
+          boot_class_path_locations_(boot_class_path_locations),
+          boot_class_path_fds_(boot_class_path_fds),
+          boot_class_path_image_fds_(boot_class_path_image_fds),
+          boot_class_path_vdex_fds_(boot_class_path_vdex_fds),
+          boot_class_path_oat_fds_(boot_class_path_oat_fds),
+          apex_versions_(GetApexVersions(apex_versions)) {}
+
+    std::string GetPrimaryImageLocation();
+
+    bool LoadFromSystem(InstructionSet image_isa,
+                        bool allow_in_memory_compilation,
+                        /*out*/ std::string* error_msg);
+
+    ArrayRef<const ImageChunk> GetChunks() const { return ArrayRef<const ImageChunk>(chunks_); }
+
+    uint32_t GetBaseAddress() const { return base_address_; }
+
+    size_t GetNextBcpIndex() const { return next_bcp_index_; }
+
+    size_t GetTotalComponentCount() const { return total_component_count_; }
+
+    size_t GetTotalReservationSize() const { return total_reservation_size_; }
+
+   private:
+    struct NamedComponentLocation {
+      std::string base_location;
+      size_t bcp_index;
+      std::vector<std::string> profile_filenames;
+    };
+
+    std::string ExpandLocationImpl(const std::string& location,
+                                   size_t bcp_index,
+                                   bool boot_image_extension) {
+      std::vector<std::string> expanded = ExpandMultiImageLocations(
+          ArrayRef<const std::string>(boot_class_path_).SubArray(bcp_index, 1u),
+          location,
+          boot_image_extension);
+      DCHECK_EQ(expanded.size(), 1u);
+      return expanded[0];
+    }
+
+    std::string ExpandLocation(const std::string& location, size_t bcp_index) {
+      if (bcp_index == 0u) {
+        DCHECK_EQ(location,
+                  ExpandLocationImpl(location, bcp_index, /*boot_image_extension=*/false));
+        return location;
+      } else {
+        return ExpandLocationImpl(location, bcp_index, /*boot_image_extension=*/true);
+      }
+    }
+
+    std::string GetBcpComponentPath(size_t bcp_index) {
+      DCHECK_LE(bcp_index, boot_class_path_.size());
+      size_t bcp_slash_pos = boot_class_path_[bcp_index].rfind('/');
+      DCHECK_NE(bcp_slash_pos, std::string::npos);
+      return boot_class_path_[bcp_index].substr(0u, bcp_slash_pos + 1u);
+    }
+
+    bool VerifyImageLocation(ArrayRef<const std::string> components,
+                             /*out*/ size_t* named_components_count,
+                             /*out*/ std::string* error_msg);
+
+    bool MatchNamedComponents(
+        ArrayRef<const std::string> named_components,
+        /*out*/ std::vector<NamedComponentLocation>* named_component_locations,
+        /*out*/ std::string* error_msg);
+
+    bool ValidateBootImageChecksum(const char* file_description,
+                                   const ImageHeader& header,
+                                   /*out*/ std::string* error_msg);
+
+    bool ValidateHeader(const ImageHeader& header,
+                        size_t bcp_index,
+                        const char* file_description,
+                        /*out*/ std::string* error_msg);
+
+    bool ValidateOatFile(const std::string& base_location,
+                         const std::string& base_filename,
+                         size_t bcp_index,
+                         size_t component_count,
+                         /*out*/ std::string* error_msg);
+
+    bool ReadHeader(const std::string& base_location,
+                    const std::string& base_filename,
+                    size_t bcp_index,
+                    /*out*/ std::string* error_msg);
+
+    // Compiles a consecutive subsequence of bootclasspath dex files, whose contents are included in
+    // the profiles specified by `profile_filenames`, starting from `bcp_index`.
+    bool CompileBootclasspathElements(const std::string& base_location,
+                                      const std::string& base_filename,
+                                      size_t bcp_index,
+                                      const std::vector<std::string>& profile_filenames,
+                                      ArrayRef<const std::string> dependencies,
+                                      /*out*/ std::string* error_msg);
+
+    // Returns true if a least one chuck has been loaded.
+    template <typename FilenameFn>
+    bool Load(FilenameFn&& filename_fn,
+              bool allow_in_memory_compilation,
+              /*out*/ std::string* error_msg);
+
+    // This function prefers taking APEX versions from the input instead of from the runtime if
+    // possible. If the input is present, `ValidateFromSystem` can work without an active runtime.
+    static const std::string& GetApexVersions(const std::string* apex_versions) {
+      if (apex_versions == nullptr) {
+        DCHECK(Runtime::Current() != nullptr);
+        return Runtime::Current()->GetApexVersions();
+      } else {
+        return *apex_versions;
+      }
+    }
+
+    ArrayRef<const std::string> image_locations_;
+    ArrayRef<const std::string> boot_class_path_;
+    ArrayRef<const std::string> boot_class_path_locations_;
+    ArrayRef<const int> boot_class_path_fds_;
+    ArrayRef<const int> boot_class_path_image_fds_;
+    ArrayRef<const int> boot_class_path_vdex_fds_;
+    ArrayRef<const int> boot_class_path_oat_fds_;
+
+    std::vector<ImageChunk> chunks_;
+    uint32_t base_address_ = 0u;
+    size_t next_bcp_index_ = 0u;
+    size_t total_component_count_ = 0u;
+    size_t total_reservation_size_ = 0u;
+    const std::string& apex_versions_;
+  };
+
  protected:
   // Tries to initialize an ImageSpace from the given image path, returning null on error.
   //
@@ -342,7 +519,6 @@
   friend class Space;
 
  private:
-  class BootImageLayout;
   class BootImageLoader;
   template <typename ReferenceVisitor>
   class ClassTableVisitor;
diff --git a/runtime/gc/space/image_space_test.cc b/runtime/gc/space/image_space_test.cc
index 3a6d0e1..d6bb86b 100644
--- a/runtime/gc/space/image_space_test.cc
+++ b/runtime/gc/space/image_space_test.cc
@@ -50,7 +50,7 @@
 };
 
 TEST_F(ImageSpaceTest, StringDeduplication) {
-  const char* const kBaseNames[] = { "Extension1", "Extension2" };
+  const char* const kBaseNames[] = {"Extension1", "Extension2"};
 
   ScratchDir scratch;
   const std::string& scratch_dir = scratch.GetPath();
@@ -77,7 +77,7 @@
   std::vector<std::string> extension_image_locations;
   for (const char* base_name : kBaseNames) {
     std::string jar_name = GetTestDexFileName(base_name);
-    ArrayRef<const std::string> dex_files(&jar_name, /*size=*/ 1u);
+    ArrayRef<const std::string> dex_files(&jar_name, /*size=*/1u);
     ScratchFile profile_file;
     GenerateBootProfile(dex_files, profile_file.GetFile());
     std::vector<std::string> extra_args = {
@@ -94,8 +94,8 @@
     ASSERT_TRUE(success) << error_msg;
     bcp.push_back(jar_name);
     bcp_locations.push_back(jar_name);
-    extension_image_locations.push_back(
-        scratch_dir + prefix + '-' + GetFilenameBase(jar_name) + ".art");
+    extension_image_locations.push_back(scratch_dir + prefix + '-' + GetFilenameBase(jar_name) +
+                                        ".art");
   }
 
   // Also compile the second extension as an app with app image.
@@ -104,26 +104,27 @@
   std::string app_odex_name = scratch_dir + app_base_name + ".odex";
   std::string app_image_name = scratch_dir + app_base_name + ".art";
   {
-    ArrayRef<const std::string> dex_files(&app_jar_name, /*size=*/ 1u);
+    ArrayRef<const std::string> dex_files(&app_jar_name, /*size=*/1u);
     ScratchFile profile_file;
     GenerateProfile(dex_files, profile_file.GetFile());
     std::vector<std::string> argv;
     std::string error_msg;
-    bool success = StartDex2OatCommandLine(&argv, &error_msg, /*use_runtime_bcp_and_image=*/ false);
+    bool success = StartDex2OatCommandLine(&argv, &error_msg, /*use_runtime_bcp_and_image=*/false);
     ASSERT_TRUE(success) << error_msg;
-    argv.insert(argv.end(), {
-        "--profile-file=" + profile_file.GetFilename(),
-        "--runtime-arg",
-        "-Xbootclasspath:" + base_bcp_string,
-        "--runtime-arg",
-        "-Xbootclasspath-locations:" + base_bcp_locations_string,
-        "--boot-image=" + base_image_location,
-        "--dex-file=" + app_jar_name,
-        "--dex-location=" + app_jar_name,
-        "--oat-file=" + app_odex_name,
-        "--app-image-file=" + app_image_name,
-        "--initialize-app-image-classes=true",
-    });
+    argv.insert(argv.end(),
+                {
+                    "--profile-file=" + profile_file.GetFilename(),
+                    "--runtime-arg",
+                    "-Xbootclasspath:" + base_bcp_string,
+                    "--runtime-arg",
+                    "-Xbootclasspath-locations:" + base_bcp_locations_string,
+                    "--boot-image=" + base_image_location,
+                    "--dex-file=" + app_jar_name,
+                    "--dex-location=" + app_jar_name,
+                    "--oat-file=" + app_odex_name,
+                    "--app-image-file=" + app_image_name,
+                    "--initialize-app-image-classes=true",
+                });
     success = RunDex2Oat(argv, &error_msg);
     ASSERT_TRUE(success) << error_msg;
   }
@@ -136,15 +137,16 @@
     extra_reservation = MemMap::Invalid();
     return ImageSpace::LoadBootImage(bcp,
                                      bcp_locations,
-                                     /*boot_class_path_fds=*/ std::vector<int>(),
-                                     /*boot_class_path_image_fds=*/ std::vector<int>(),
-                                     /*boot_class_path_vdex_fds=*/ std::vector<int>(),
-                                     /*boot_class_path_oat_fds=*/ std::vector<int>(),
+                                     /*boot_class_path_fds=*/std::vector<int>(),
+                                     /*boot_class_path_image_fds=*/std::vector<int>(),
+                                     /*boot_class_path_vdex_fds=*/std::vector<int>(),
+                                     /*boot_class_path_oat_fds=*/std::vector<int>(),
                                      full_image_locations,
                                      kRuntimeISA,
-                                     /*relocate=*/ false,
-                                     /*executable=*/ true,
-                                     /*extra_reservation_size=*/ 0u,
+                                     /*relocate=*/false,
+                                     /*executable=*/true,
+                                     /*extra_reservation_size=*/0u,
+                                     /*allow_in_memory_compilation=*/false,
                                      &boot_image_spaces,
                                      &extra_reservation);
   };
@@ -153,13 +155,13 @@
   size_t test_string_length = std::size(test_string) - 1u;  // Equals UTF-16 length.
   uint32_t hash = InternTable::Utf8String::Hash(test_string_length, test_string);
   InternTable::Utf8String utf8_test_string(test_string_length, test_string);
-  auto contains_test_string = [utf8_test_string, hash](ImageSpace* space)
-      REQUIRES_SHARED(Locks::mutator_lock_) {
+  auto contains_test_string = [utf8_test_string,
+                               hash](ImageSpace* space) REQUIRES_SHARED(Locks::mutator_lock_) {
     const ImageHeader& image_header = space->GetImageHeader();
     if (image_header.GetInternedStringsSection().Size() != 0u) {
       const uint8_t* data = space->Begin() + image_header.GetInternedStringsSection().Offset();
       size_t read_count;
-      InternTable::UnorderedSet temp_set(data, /*make_copy_of_data=*/ false, &read_count);
+      InternTable::UnorderedSet temp_set(data, /*make_copy_of_data=*/false, &read_count);
       return temp_set.FindWithHash(utf8_test_string, hash) != temp_set.end();
     } else {
       return false;
@@ -170,8 +172,7 @@
   ScopedObjectAccess soa(Thread::Current());
   ASSERT_EQ(2u, extension_image_locations.size());
   full_image_locations = {
-    base_image_location, extension_image_locations[0], extension_image_locations[1]
-  };
+      base_image_location, extension_image_locations[0], extension_image_locations[1]};
   bool success = load_boot_image();
   ASSERT_TRUE(success);
   ASSERT_EQ(bcp.size(), boot_image_spaces.size());
@@ -183,8 +184,7 @@
   std::swap(bcp[bcp.size() - 2u], bcp[bcp.size() - 1u]);
   std::swap(bcp_locations[bcp_locations.size() - 2u], bcp_locations[bcp_locations.size() - 1u]);
   full_image_locations = {
-    base_image_location, extension_image_locations[1], extension_image_locations[0]
-  };
+      base_image_location, extension_image_locations[1], extension_image_locations[0]};
   success = load_boot_image();
   ASSERT_TRUE(success);
   ASSERT_EQ(bcp.size(), boot_image_spaces.size());
@@ -203,21 +203,21 @@
 
   // Load the app odex file and app image.
   std::string error_msg;
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   app_odex_name.c_str(),
-                                                   app_odex_name.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   app_odex_name,
+                                                   app_odex_name,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    app_jar_name,
                                                    &error_msg));
   ASSERT_TRUE(odex_file != nullptr) << error_msg;
   std::vector<ImageSpace*> non_owning_boot_image_spaces =
       MakeNonOwningPointerVector(boot_image_spaces);
-  std::unique_ptr<ImageSpace> app_image_space = ImageSpace::CreateFromAppImage(
-      app_image_name.c_str(),
-      odex_file.get(),
-      ArrayRef<ImageSpace* const>(non_owning_boot_image_spaces),
-      &error_msg);
+  std::unique_ptr<ImageSpace> app_image_space =
+      ImageSpace::CreateFromAppImage(app_image_name.c_str(),
+                                     odex_file.get(),
+                                     ArrayRef<ImageSpace* const>(non_owning_boot_image_spaces),
+                                     &error_msg);
   ASSERT_TRUE(app_image_space != nullptr) << error_msg;
 
   // The string in the app image should be replaced and removed from interned string section.
@@ -242,25 +242,25 @@
   args.push_back("--oat-file=" + oat_location);
   ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
 
-  std::unique_ptr<OatFile> oat(OatFile::Open(/*zip_fd=*/ -1,
-                                             oat_location.c_str(),
-                                             oat_location.c_str(),
-                                             /*executable=*/ false,
-                                             /*low_4gb=*/ false,
+  std::unique_ptr<OatFile> oat(OatFile::Open(/*zip_fd=*/-1,
+                                             oat_location,
+                                             oat_location,
+                                             /*executable=*/false,
+                                             /*low_4gb=*/false,
                                              &error_msg));
   ASSERT_TRUE(oat != nullptr) << error_msg;
 
   {
     // Test opening the oat file also with explicit dex filenames.
-    std::vector<std::string> dex_filenames{ dex1, multidex1, dex2 };
-    std::unique_ptr<OatFile> oat2(OatFile::Open(/*zip_fd=*/ -1,
-                                                oat_location.c_str(),
-                                                oat_location.c_str(),
-                                                /*executable=*/ false,
-                                                /*low_4gb=*/ false,
+    std::vector<std::string> dex_filenames{dex1, multidex1, dex2};
+    std::unique_ptr<OatFile> oat2(OatFile::Open(/*zip_fd=*/-1,
+                                                oat_location,
+                                                oat_location,
+                                                /*executable=*/false,
+                                                /*low_4gb=*/false,
                                                 ArrayRef<const std::string>(dex_filenames),
-                                                /*dex_fds=*/ ArrayRef<const int>(),
-                                                /*reservation=*/ nullptr,
+                                                /*dex_fds=*/ArrayRef<const int>(),
+                                                /*reservation=*/nullptr,
                                                 &error_msg));
     ASSERT_TRUE(oat2 != nullptr) << error_msg;
   }
@@ -321,56 +321,6 @@
   EXPECT_FALSE(ImageSpace::ValidateOatFile(*oat, &error_msg));
 }
 
-TEST_F(DexoptTest, Checksums) {
-  Runtime* runtime = Runtime::Current();
-  ASSERT_TRUE(runtime != nullptr);
-  ASSERT_FALSE(runtime->GetHeap()->GetBootImageSpaces().empty());
-
-  std::vector<std::string> bcp = runtime->GetBootClassPath();
-  std::vector<std::string> bcp_locations = runtime->GetBootClassPathLocations();
-  std::vector<const DexFile*> dex_files = runtime->GetClassLinker()->GetBootClassPath();
-
-  std::string error_msg;
-  auto create_and_verify = [&]() {
-    std::string checksums = gc::space::ImageSpace::GetBootClassPathChecksums(
-        ArrayRef<gc::space::ImageSpace* const>(runtime->GetHeap()->GetBootImageSpaces()),
-        ArrayRef<const DexFile* const>(dex_files));
-    return gc::space::ImageSpace::VerifyBootClassPathChecksums(
-        checksums,
-        android::base::Join(bcp_locations, ':'),
-        ArrayRef<const std::string>(runtime->GetImageLocations()),
-        ArrayRef<const std::string>(bcp_locations),
-        ArrayRef<const std::string>(bcp),
-        /*boot_class_path_fds=*/ ArrayRef<const int>(),
-        kRuntimeISA,
-        &error_msg);
-  };
-
-  ASSERT_TRUE(create_and_verify()) << error_msg;
-
-  std::vector<std::unique_ptr<const DexFile>> opened_dex_files;
-  for (const std::string& src : { GetDexSrc1(), GetDexSrc2() }) {
-    std::vector<std::unique_ptr<const DexFile>> new_dex_files;
-    const ArtDexFileLoader dex_file_loader;
-    ASSERT_TRUE(dex_file_loader.Open(src.c_str(),
-                                     src,
-                                     /*verify=*/ true,
-                                     /*verify_checksum=*/ false,
-                                     &error_msg,
-                                     &new_dex_files))
-        << error_msg;
-
-    bcp.push_back(src);
-    bcp_locations.push_back(src);
-    for (std::unique_ptr<const DexFile>& df : new_dex_files) {
-      dex_files.push_back(df.get());
-      opened_dex_files.push_back(std::move(df));
-    }
-
-    ASSERT_TRUE(create_and_verify()) << error_msg;
-  }
-}
-
 template <bool kImage, bool kRelocate>
 class ImageSpaceLoadingTest : public CommonRuntimeTest {
  protected:
@@ -380,6 +330,7 @@
     options->emplace_back(android::base::StringPrintf("-Ximage:%s", image_location.c_str()),
                           nullptr);
     options->emplace_back(kRelocate ? "-Xrelocate" : "-Xnorelocate", nullptr);
+    options->emplace_back("-Xallowinmemorycompilation", nullptr);
 
     // We want to test the relocation behavior of ImageSpace. As such, don't pretend we're a
     // compiler.
diff --git a/runtime/gc/space/large_object_space.cc b/runtime/gc/space/large_object_space.cc
index 2d17a18..f1df45f 100644
--- a/runtime/gc/space/large_object_space.cc
+++ b/runtime/gc/space/large_object_space.cc
@@ -336,7 +336,7 @@
 
 size_t FreeListSpace::GetSlotIndexForAllocationInfo(const AllocationInfo* info) const {
   DCHECK_GE(info, allocation_info_);
-  DCHECK_LT(info, reinterpret_cast<AllocationInfo*>(allocation_info_map_.End()));
+  DCHECK_LE(info, reinterpret_cast<AllocationInfo*>(allocation_info_map_.End()));
   return info - allocation_info_;
 }
 
@@ -457,6 +457,10 @@
     // The previous allocation info must not be free since we are supposed to always coalesce.
     DCHECK_EQ(info->GetPrevFreeBytes(), 0U) << "Previous allocation was free";
   }
+  // NOTE: next_info could be pointing right after the allocation_info_map_
+  // when freeing object in the very end of the space. But that's safe
+  // as we don't dereference it in that case. We only use it to calculate
+  // next_addr using offset within the map.
   uintptr_t next_addr = GetAddressForAllocationInfo(next_info);
   if (next_addr >= free_end_start) {
     // Easy case, the next chunk is the end free region.
diff --git a/runtime/gc/space/malloc_space.h b/runtime/gc/space/malloc_space.h
index 5000656..59ab3f3 100644
--- a/runtime/gc/space/malloc_space.h
+++ b/runtime/gc/space/malloc_space.h
@@ -38,7 +38,7 @@
 // A common parent of DlMallocSpace and RosAllocSpace.
 class MallocSpace : public ContinuousMemMapAllocSpace {
  public:
-  typedef void(*WalkCallback)(void *start, void *end, size_t num_bytes, void* callback_arg);
+  using WalkCallback = void (*)(void *start, void *end, size_t num_bytes, void* callback_arg);
 
   SpaceType GetType() const override {
     return kSpaceTypeMallocSpace;
diff --git a/runtime/gc/space/region_space-inl.h b/runtime/gc/space/region_space-inl.h
index 901568e..1026f42 100644
--- a/runtime/gc/space/region_space-inl.h
+++ b/runtime/gc/space/region_space-inl.h
@@ -17,8 +17,6 @@
 #ifndef ART_RUNTIME_GC_SPACE_REGION_SPACE_INL_H_
 #define ART_RUNTIME_GC_SPACE_REGION_SPACE_INL_H_
 
-#include "region_space.h"
-
 #include "base/mutex-inl.h"
 #include "mirror/object-inl.h"
 #include "region_space.h"
diff --git a/runtime/gc/space/region_space.cc b/runtime/gc/space/region_space.cc
index 171c5cd..60141d6 100644
--- a/runtime/gc/space/region_space.cc
+++ b/runtime/gc/space/region_space.cc
@@ -36,7 +36,7 @@
 static constexpr bool kProtectClearedRegions = kIsDebugBuild;
 
 // Wether we poison memory areas occupied by dead objects in unevacuated regions.
-static constexpr bool kPoisonDeadObjectsInUnevacuatedRegions = true;
+static constexpr bool kPoisonDeadObjectsInUnevacuatedRegions = kIsDebugBuild;
 
 // Special 32-bit value used to poison memory areas occupied by dead
 // objects in unevacuated regions. Dereferencing this value is expected
@@ -741,10 +741,19 @@
   max_contiguous_allocation = std::min(max_contiguous_allocation,
                                        regions_free_for_alloc * kRegionSize);
   if (failed_alloc_bytes > max_contiguous_allocation) {
+    // Region space does not normally fragment in the conventional sense. However we can run out
+    // of region space prematurely if we have many threads, each with a partially committed TLAB.
+    // The whole TLAB uses up region address space, but we only count the section that was
+    // actually given to the thread so far as allocated. For unlikely allocation request sequences
+    // involving largish objects that don't qualify for large objects space, we may also be unable
+    // to fully utilize entire TLABs, and thus generate enough actual fragmentation to get
+    // here. This appears less likely, since we usually reuse sufficiently large TLAB "tails"
+    // that are no longer needed.
     os << "; failed due to fragmentation (largest possible contiguous allocation "
-       <<  max_contiguous_allocation << " bytes). Number of "
-       << PrettySize(kRegionSize)
-       << " sized free regions are: " << regions_free_for_alloc;
+       << max_contiguous_allocation << " bytes). Number of " << PrettySize(kRegionSize)
+       << " sized free regions are: " << regions_free_for_alloc
+       << ". Likely cause: (1) Too much memory in use, and "
+       << "(2) many threads or many larger objects of the wrong kind";
     return true;
   }
   // Caller's job to print failed_alloc_bytes.
diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h
index 1463eb7..27b9e9c 100644
--- a/runtime/gc/space/region_space.h
+++ b/runtime/gc/space/region_space.h
@@ -46,7 +46,7 @@
 // A space that consists of equal-sized regions.
 class RegionSpace final : public ContinuousMemMapAllocSpace {
  public:
-  typedef void(*WalkCallback)(void *start, void *end, size_t num_bytes, void* callback_arg);
+  using WalkCallback = void (*)(void *start, void *end, size_t num_bytes, void* callback_arg);
 
   enum EvacMode {
     kEvacModeNewlyAllocated,
diff --git a/runtime/gc/system_weak.h b/runtime/gc/system_weak.h
index ef85b39..77b9548 100644
--- a/runtime/gc/system_weak.h
+++ b/runtime/gc/system_weak.h
@@ -48,7 +48,7 @@
   void Allow() override
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!allow_disallow_lock_) {
-    CHECK(!kUseReadBarrier);
+    CHECK(!gUseReadBarrier);
     MutexLock mu(Thread::Current(), allow_disallow_lock_);
     allow_new_system_weak_ = true;
     new_weak_condition_.Broadcast(Thread::Current());
@@ -57,7 +57,7 @@
   void Disallow() override
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!allow_disallow_lock_) {
-    CHECK(!kUseReadBarrier);
+    CHECK(!gUseReadBarrier);
     MutexLock mu(Thread::Current(), allow_disallow_lock_);
     allow_new_system_weak_ = false;
   }
@@ -78,8 +78,8 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(allow_disallow_lock_) {
     // Wait for GC's sweeping to complete and allow new records
-    while (UNLIKELY((!kUseReadBarrier && !allow_new_system_weak_) ||
-                    (kUseReadBarrier && !self->GetWeakRefAccessEnabled()))) {
+    while (UNLIKELY((!gUseReadBarrier && !allow_new_system_weak_) ||
+                    (gUseReadBarrier && !self->GetWeakRefAccessEnabled()))) {
       // Check and run the empty checkpoint before blocking so the empty checkpoint will work in the
       // presence of threads blocking for weak ref access.
       self->CheckEmptyCheckpointFromWeakRefAccess(&allow_disallow_lock_);
diff --git a/runtime/gc/system_weak_test.cc b/runtime/gc/system_weak_test.cc
index ca11297..dd93653 100644
--- a/runtime/gc/system_weak_test.cc
+++ b/runtime/gc/system_weak_test.cc
@@ -35,6 +35,10 @@
 namespace gc {
 
 class SystemWeakTest : public CommonRuntimeTest {
+ protected:
+  SystemWeakTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
 };
 
 struct CountingSystemWeakHolder : public SystemWeakHolder {
@@ -111,6 +115,7 @@
   CollectorType type = Runtime::Current()->GetHeap()->CurrentCollectorType();
   switch (type) {
     case CollectorType::kCollectorTypeCMS:
+    case CollectorType::kCollectorTypeCMC:
     case CollectorType::kCollectorTypeCC:
     case CollectorType::kCollectorTypeSS:
       return true;
@@ -124,6 +129,7 @@
   CollectorType type = Runtime::Current()->GetHeap()->CurrentCollectorType();
   switch (type) {
     case CollectorType::kCollectorTypeCMS:
+    case CollectorType::kCollectorTypeCMC:
       return true;
 
     default:
@@ -149,7 +155,12 @@
   // Expect the holder to have been called.
   EXPECT_EQ(CollectorDoesAllowOrBroadcast() ? 1U : 0U, cswh.allow_count_);
   EXPECT_EQ(CollectorDoesDisallow() ? 1U : 0U, cswh.disallow_count_);
-  EXPECT_EQ(1U, cswh.sweep_count_);
+  // Userfaultfd GC uses SweepSystemWeaks also for concurrent updation.
+  // TODO: Explore this can be reverted back to unconditionally compare with 1
+  // once concurrent updation of native roots is full implemented in userfaultfd
+  // GC.
+  size_t expected_sweep_count = gUseUserfaultfd ? 2U : 1U;
+  EXPECT_EQ(expected_sweep_count, cswh.sweep_count_);
 
   // Expect the weak to not be cleared.
   EXPECT_FALSE(cswh.Get().IsNull());
@@ -170,7 +181,12 @@
   // Expect the holder to have been called.
   EXPECT_EQ(CollectorDoesAllowOrBroadcast() ? 1U : 0U, cswh.allow_count_);
   EXPECT_EQ(CollectorDoesDisallow() ? 1U : 0U, cswh.disallow_count_);
-  EXPECT_EQ(1U, cswh.sweep_count_);
+  // Userfaultfd GC uses SweepSystemWeaks also for concurrent updation.
+  // TODO: Explore this can be reverted back to unconditionally compare with 1
+  // once concurrent updation of native roots is full implemented in userfaultfd
+  // GC.
+  size_t expected_sweep_count = gUseUserfaultfd ? 2U : 1U;
+  EXPECT_EQ(expected_sweep_count, cswh.sweep_count_);
 
   // Expect the weak to be cleared.
   EXPECT_TRUE(cswh.Get().IsNull());
@@ -194,7 +210,12 @@
   // Expect the holder to have been called.
   ASSERT_EQ(CollectorDoesAllowOrBroadcast() ? 1U : 0U, cswh.allow_count_);
   ASSERT_EQ(CollectorDoesDisallow() ? 1U : 0U, cswh.disallow_count_);
-  ASSERT_EQ(1U, cswh.sweep_count_);
+  // Userfaultfd GC uses SweepSystemWeaks also for concurrent updation.
+  // TODO: Explore this can be reverted back to unconditionally compare with 1
+  // once concurrent updation of native roots is full implemented in userfaultfd
+  // GC.
+  size_t expected_sweep_count = gUseUserfaultfd ? 2U : 1U;
+  EXPECT_EQ(expected_sweep_count, cswh.sweep_count_);
 
   // Expect the weak to not be cleared.
   ASSERT_FALSE(cswh.Get().IsNull());
@@ -209,7 +230,7 @@
   // Expectation: no change in the numbers.
   EXPECT_EQ(CollectorDoesAllowOrBroadcast() ? 1U : 0U, cswh.allow_count_);
   EXPECT_EQ(CollectorDoesDisallow() ? 1U : 0U, cswh.disallow_count_);
-  EXPECT_EQ(1U, cswh.sweep_count_);
+  EXPECT_EQ(expected_sweep_count, cswh.sweep_count_);
 }
 
 }  // namespace gc
diff --git a/runtime/gc/verification-inl.h b/runtime/gc/verification-inl.h
new file mode 100644
index 0000000..1ef96e2
--- /dev/null
+++ b/runtime/gc/verification-inl.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2021 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_RUNTIME_GC_VERIFICATION_INL_H_
+#define ART_RUNTIME_GC_VERIFICATION_INL_H_
+
+#include "verification.h"
+
+#include "mirror/class-inl.h"
+
+namespace art {
+namespace gc {
+
+template <ReadBarrierOption kReadBarrierOption>
+bool Verification::IsValidClassUnchecked(mirror::Class* klass) const {
+  mirror::Class* k1 = klass->GetClass<kVerifyNone, kReadBarrierOption>();
+  if (!IsValidHeapObjectAddress(k1)) {
+    return false;
+  }
+  // `k1` should be class class, take the class again to verify.
+  // Note that this check may not be valid for the no image space
+  // since the class class might move around from moving GC.
+  mirror::Class* k2 = k1->GetClass<kVerifyNone, kReadBarrierOption>();
+  if (!IsValidHeapObjectAddress(k2)) {
+    return false;
+  }
+  return k1 == k2;
+}
+
+template <ReadBarrierOption kReadBarrierOption>
+bool Verification::IsValidClass(mirror::Class* klass) const {
+  if (!IsValidHeapObjectAddress(klass)) {
+    return false;
+  }
+  return IsValidClassUnchecked<kReadBarrierOption>(klass);
+}
+
+template <ReadBarrierOption kReadBarrierOption>
+bool Verification::IsValidObject(mirror::Object* obj) const {
+  if (!IsValidHeapObjectAddress(obj)) {
+    return false;
+  }
+  mirror::Class* klass = obj->GetClass<kVerifyNone, kReadBarrierOption>();
+  return IsValidClass(klass);
+}
+
+}  // namespace gc
+}  // namespace art
+
+#endif  // ART_RUNTIME_GC_VERIFICATION_INL_H_
diff --git a/runtime/gc/verification.cc b/runtime/gc/verification.cc
index 9e0b8a2..195986f 100644
--- a/runtime/gc/verification.cc
+++ b/runtime/gc/verification.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "verification.h"
+#include "verification-inl.h"
 
 #include <iomanip>
 #include <sstream>
@@ -29,23 +29,16 @@
 namespace gc {
 
 std::string Verification::DumpRAMAroundAddress(uintptr_t addr, uintptr_t bytes) const {
-  const uintptr_t dump_start = addr - bytes;
-  const uintptr_t dump_end = addr + bytes;
+  uintptr_t* dump_start = reinterpret_cast<uintptr_t*>(addr - bytes);
+  uintptr_t* dump_end = reinterpret_cast<uintptr_t*>(addr + bytes);
   std::ostringstream oss;
-  if (dump_start < dump_end &&
-      IsAddressInHeapSpace(reinterpret_cast<const void*>(dump_start)) &&
-      IsAddressInHeapSpace(reinterpret_cast<const void*>(dump_end - 1))) {
-    oss << " adjacent_ram=";
-    for (uintptr_t p = dump_start; p < dump_end; ++p) {
-      if (p == addr) {
-        // Marker of where the address is.
-        oss << "|";
-      }
-      uint8_t* ptr = reinterpret_cast<uint8_t*>(p);
-      oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<uintptr_t>(*ptr);
+  oss << " adjacent_ram=";
+  for (const uintptr_t* p = dump_start; p < dump_end; ++p) {
+    if (p == reinterpret_cast<uintptr_t*>(addr)) {
+      // Marker of where the address is.
+      oss << "|";
     }
-  } else {
-    oss << " <invalid address>";
+    oss << std::hex << std::setfill('0') << std::setw(sizeof(uintptr_t) * 2) << *p << " ";
   }
   return oss.str();
 }
@@ -93,7 +86,7 @@
   std::ostringstream oss;
   oss << "GC tried to mark invalid reference " << ref << std::endl;
   oss << DumpObjectInfo(ref, "ref") << "\n";
-  oss << DumpObjectInfo(holder.Ptr(), "holder");
+  oss << DumpObjectInfo(holder.Ptr(), "holder") << "\n";
   if (holder != nullptr) {
     mirror::Class* holder_klass = holder->GetClass<kVerifyNone, kWithoutReadBarrier>();
     if (IsValidClass(holder_klass)) {
@@ -132,25 +125,6 @@
   return IsAligned<kObjectAlignment>(addr) && IsAddressInHeapSpace(addr, out_space);
 }
 
-bool Verification::IsValidClass(const void* addr) const {
-  if (!IsValidHeapObjectAddress(addr)) {
-    return false;
-  }
-  mirror::Class* klass = reinterpret_cast<mirror::Class*>(const_cast<void*>(addr));
-  mirror::Class* k1 = klass->GetClass<kVerifyNone, kWithoutReadBarrier>();
-  if (!IsValidHeapObjectAddress(k1)) {
-    return false;
-  }
-  // `k1` should be class class, take the class again to verify.
-  // Note that this check may not be valid for the no image space since the class class might move
-  // around from moving GC.
-  mirror::Class* k2 = k1->GetClass<kVerifyNone, kWithoutReadBarrier>();
-  if (!IsValidHeapObjectAddress(k2)) {
-    return false;
-  }
-  return k1 == k2;
-}
-
 using ObjectSet = std::set<mirror::Object*>;
 using WorkQueue = std::deque<std::pair<mirror::Object*, std::string>>;
 
diff --git a/runtime/gc/verification.h b/runtime/gc/verification.h
index 6b456fd..7a5d01a 100644
--- a/runtime/gc/verification.h
+++ b/runtime/gc/verification.h
@@ -19,6 +19,7 @@
 
 #include "obj_ptr.h"
 #include "offsets.h"
+#include "read_barrier_option.h"
 
 namespace art {
 
@@ -50,7 +51,16 @@
                          bool fatal) const REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Return true if the klass is likely to be a valid mirror::Class.
-  bool IsValidClass(const void* klass) const REQUIRES_SHARED(Locks::mutator_lock_);
+  // Returns true if the class is a valid mirror::Class or possibly spuriously.
+  template <ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
+  bool IsValidClassUnchecked(mirror::Class* klass) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Return true if the klass is likely to be a valid mirror::Class.
+  template <ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
+  bool IsValidClass(mirror::Class* klass) const REQUIRES_SHARED(Locks::mutator_lock_);
+  // Return true if the obj is likely to be a valid obj with valid mirror::Class.
+  template <ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
+  bool IsValidObject(mirror::Object* obj) const REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Does not allow null, checks alignment.
   bool IsValidHeapObjectAddress(const void* addr, space::Space** out_space = nullptr) const
diff --git a/runtime/gc_root-inl.h b/runtime/gc_root-inl.h
index 7795c66..e561d29 100644
--- a/runtime/gc_root-inl.h
+++ b/runtime/gc_root-inl.h
@@ -34,8 +34,12 @@
 }
 
 template<class MirrorType>
+inline GcRoot<MirrorType>::GcRoot(mirror::CompressedReference<mirror::Object> ref)
+    : root_(ref) { }
+
+template<class MirrorType>
 inline GcRoot<MirrorType>::GcRoot(MirrorType* ref)
-    : root_(mirror::CompressedReference<mirror::Object>::FromMirrorPtr(ref)) { }
+    : GcRoot(mirror::CompressedReference<mirror::Object>::FromMirrorPtr(ref)) { }
 
 template<class MirrorType>
 inline GcRoot<MirrorType>::GcRoot(ObjPtr<MirrorType> ref)
diff --git a/runtime/gc_root.h b/runtime/gc_root.h
index 553f3d6..19e2786 100644
--- a/runtime/gc_root.h
+++ b/runtime/gc_root.h
@@ -214,7 +214,12 @@
     return root_.IsNull();
   }
 
-  ALWAYS_INLINE GcRoot() {}
+  ALWAYS_INLINE GcRoot() : GcRoot(nullptr) {}
+  ALWAYS_INLINE GcRoot(std::nullptr_t) : root_() {
+    DCHECK(IsNull());
+  }
+  explicit ALWAYS_INLINE GcRoot(mirror::CompressedReference<mirror::Object> ref)
+      REQUIRES_SHARED(Locks::mutator_lock_);
   explicit ALWAYS_INLINE GcRoot(MirrorType* ref)
       REQUIRES_SHARED(Locks::mutator_lock_);
   explicit ALWAYS_INLINE GcRoot(ObjPtr<MirrorType> ref)
diff --git a/runtime/handle.cc b/runtime/handle.cc
index af77e23..e9c9113 100644
--- a/runtime/handle.cc
+++ b/runtime/handle.cc
@@ -42,6 +42,7 @@
 
 namespace art {
 
+// NOLINTBEGIN(bugprone-macro-parentheses)
 #define MAKE_OBJECT_FOR_GDB(ROOT, NAME, MIRROR)                 \
   template <> MIRROR* Handle<MIRROR>::GetFromGdb() {            \
     return Get();                                               \
@@ -53,5 +54,6 @@
 CLASS_MIRROR_ROOT_LIST(MAKE_OBJECT_FOR_GDB)
 
 #undef MAKE_OBJECT_FOR_GDB
+// NOLINTEND(bugprone-macro-parentheses)
 
 }  // namespace art
diff --git a/runtime/handle_scope-inl.h b/runtime/handle_scope-inl.h
index 3aa9e52..60a82a2 100644
--- a/runtime/handle_scope-inl.h
+++ b/runtime/handle_scope-inl.h
@@ -121,6 +121,15 @@
   }
 }
 
+template <typename Visitor>
+inline void HandleScope::VisitHandles(Visitor& visitor) {
+  for (size_t i = 0, count = NumberOfReferences(); i < count; ++i) {
+    if (GetHandle(i) != nullptr) {
+      visitor.Visit(GetHandle(i));
+    }
+  }
+}
+
 template<size_t kNumReferences> template<class T>
 inline MutableHandle<T> FixedSizeHandleScope<kNumReferences>::NewHandle(T* object) {
   return NewHandle(ObjPtr<T>(object));
@@ -179,6 +188,15 @@
   }
 }
 
+template <typename Visitor>
+inline void BaseHandleScope::VisitHandles(Visitor& visitor) {
+  if (LIKELY(!IsVariableSized())) {
+    AsHandleScope()->VisitHandles(visitor);
+  } else {
+    AsVariableSized()->VisitHandles(visitor);
+  }
+}
+
 inline VariableSizedHandleScope* BaseHandleScope::AsVariableSized() {
   DCHECK(IsVariableSized());
   return down_cast<VariableSizedHandleScope*>(this);
@@ -269,6 +287,15 @@
   }
 }
 
+template <typename Visitor>
+inline void VariableSizedHandleScope::VisitHandles(Visitor& visitor) {
+  LocalScopeType* cur = current_scope_;
+  while (cur != nullptr) {
+    cur->VisitHandles(visitor);
+    cur = reinterpret_cast<LocalScopeType*>(cur->GetLink());
+  }
+}
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_HANDLE_SCOPE_INL_H_
diff --git a/runtime/handle_scope.h b/runtime/handle_scope.h
index 89127e4..a43e889 100644
--- a/runtime/handle_scope.h
+++ b/runtime/handle_scope.h
@@ -56,6 +56,9 @@
   template <typename Visitor>
   ALWAYS_INLINE void VisitRoots(Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template <typename Visitor>
+  ALWAYS_INLINE void VisitHandles(Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Link to previous BaseHandleScope or null.
   BaseHandleScope* GetLink() const {
     return link_;
@@ -148,6 +151,9 @@
   template <typename Visitor>
   ALWAYS_INLINE void VisitRoots(Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template <typename Visitor>
+  ALWAYS_INLINE void VisitHandles(Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
  protected:
   // Return backing storage used for references.
   ALWAYS_INLINE StackReference<mirror::Object>* GetReferences() const {
@@ -261,6 +267,9 @@
   template <typename Visitor>
   void VisitRoots(Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template <typename Visitor>
+  ALWAYS_INLINE void VisitHandles(Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   static constexpr size_t kLocalScopeSize = 64u;
   static constexpr size_t kSizeOfReferencesPerScope =
diff --git a/runtime/handle_scope_test.cc b/runtime/handle_scope_test.cc
index d72dbe6..9207303 100644
--- a/runtime/handle_scope_test.cc
+++ b/runtime/handle_scope_test.cc
@@ -38,7 +38,12 @@
 static_assert(std::is_trivially_copyable<ScopedNullHandle<mirror::Object>>::value,
               "ScopedNullHandle should be trivially copyable");
 
-class HandleScopeTest : public CommonRuntimeTest {};
+class HandleScopeTest : public CommonRuntimeTest {
+ protected:
+  HandleScopeTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+};
 
 // Test the offsets computed for members of HandleScope. Because of cross-compiling
 // it is impossible the use OFFSETOF_MEMBER, so we do some reasonable computations ourselves. This
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index 54acbb4..9d32e51 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -16,19 +16,19 @@
 
 #include "hidden_api.h"
 
-#include <nativehelper/scoped_local_ref.h>
 #include <atomic>
 
 #include "art_field-inl.h"
 #include "art_method-inl.h"
-#include "class_root-inl.h"
-#include "compat_framework.h"
 #include "base/dumpable.h"
 #include "base/file_utils.h"
+#include "class_root-inl.h"
+#include "compat_framework.h"
 #include "dex/class_accessor-inl.h"
 #include "dex/dex_file_loader.h"
 #include "mirror/class_ext.h"
 #include "mirror/proxy.h"
+#include "nativehelper/scoped_local_ref.h"
 #include "oat_file.h"
 #include "scoped_thread_state_change.h"
 #include "stack.h"
@@ -105,22 +105,21 @@
   // These checks will be skipped on target buildbots where ANDROID_ART_ROOT
   // is set to "/system".
   if (ArtModuleRootDistinctFromAndroidRoot()) {
-    if (LocationIsOnArtModule(dex_location.c_str()) ||
-        LocationIsOnConscryptModule(dex_location.c_str()) ||
-        LocationIsOnI18nModule(dex_location.c_str())) {
+    if (LocationIsOnArtModule(dex_location) || LocationIsOnConscryptModule(dex_location) ||
+        LocationIsOnI18nModule(dex_location)) {
       return Domain::kCorePlatform;
     }
 
-    if (LocationIsOnApex(dex_location.c_str())) {
+    if (LocationIsOnApex(dex_location)) {
       return Domain::kPlatform;
     }
   }
 
-  if (LocationIsOnSystemFramework(dex_location.c_str())) {
+  if (LocationIsOnSystemFramework(dex_location)) {
     return Domain::kPlatform;
   }
 
-  if (LocationIsOnSystemExtFramework(dex_location.c_str())) {
+  if (LocationIsOnSystemExtFramework(dex_location)) {
     return Domain::kPlatform;
   }
 
@@ -128,7 +127,7 @@
     if (kIsTargetBuild && !kIsTargetLinux) {
       // This is unexpected only when running on Android.
       LOG(WARNING) << "DexFile " << dex_location
-          << " is in boot class path but is not in a known location";
+                   << " is in boot class path but is not in a known location";
     }
     return Domain::kPlatform;
   }
@@ -149,17 +148,16 @@
 void InitializeCorePlatformApiPrivateFields() {
   // The following fields in WellKnownClasses correspond to private fields in the Core Platform
   // API that cannot be otherwise expressed and propagated through tooling (b/144502743).
-  jfieldID private_core_platform_api_fields[] = {
-    WellKnownClasses::java_io_FileDescriptor_descriptor,
-    WellKnownClasses::java_nio_Buffer_address,
-    WellKnownClasses::java_nio_Buffer_elementSizeShift,
-    WellKnownClasses::java_nio_Buffer_limit,
-    WellKnownClasses::java_nio_Buffer_position,
+  ArtField* private_core_platform_api_fields[] = {
+      WellKnownClasses::java_io_FileDescriptor_descriptor,
+      WellKnownClasses::java_nio_Buffer_address,
+      WellKnownClasses::java_nio_Buffer_elementSizeShift,
+      WellKnownClasses::java_nio_Buffer_limit,
+      WellKnownClasses::java_nio_Buffer_position,
   };
 
   ScopedObjectAccess soa(Thread::Current());
-  for (const auto private_core_platform_api_field : private_core_platform_api_fields) {
-    ArtField* field = jni::DecodeArtField(private_core_platform_api_field);
+  for (ArtField* field : private_core_platform_api_fields) {
     const uint32_t access_flags = field->GetAccessFlags();
     uint32_t new_access_flags = access_flags | kAccCorePlatformApi;
     DCHECK(new_access_flags != access_flags);
@@ -175,11 +173,10 @@
   struct FirstExternalCallerVisitor : public StackVisitor {
     explicit FirstExternalCallerVisitor(Thread* thread)
         : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
-          caller(nullptr) {
-    }
+          caller(nullptr) {}
 
     bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
-      ArtMethod *m = GetMethod();
+      ArtMethod* m = GetMethod();
       if (m == nullptr) {
         // Attached native thread. Assume this is *not* boot class path.
         caller = nullptr;
@@ -200,8 +197,8 @@
         // NB Static initializers within java.lang.invoke are permitted and do not
         // need further stack inspection.
         ObjPtr<mirror::Class> lookup_class = GetClassRoot<mirror::MethodHandlesLookup>();
-        if ((declaring_class == lookup_class || declaring_class->IsInSamePackage(lookup_class))
-            && !m->IsClassInitializer()) {
+        if ((declaring_class == lookup_class || declaring_class->IsInSamePackage(lookup_class)) &&
+            !m->IsClassInitializer()) {
           return true;
         }
         // Check for classes in the java.lang.reflect package, except for java.lang.reflect.Proxy.
@@ -230,10 +227,9 @@
   // Construct AccessContext from the calling class found on the stack.
   // If the calling class cannot be determined, e.g. unattached threads,
   // we conservatively assume the caller is trusted.
-  ObjPtr<mirror::Class> caller = (visitor.caller == nullptr)
-      ? nullptr : visitor.caller->GetDeclaringClass();
-  return caller.IsNull() ? AccessContext(/* is_trusted= */ true)
-                         : AccessContext(caller);
+  ObjPtr<mirror::Class> caller =
+      (visitor.caller == nullptr) ? nullptr : visitor.caller->GetDeclaringClass();
+  return caller.IsNull() ? AccessContext(/* is_trusted= */ true) : AccessContext(caller);
 }
 
 namespace detail {
@@ -244,7 +240,7 @@
   // Accessed member is a field if this bit is set, else a method
   kMemberIsField = 1 << 0,
   // Indicates if access was denied to the member, instead of just printing a warning.
-  kAccessDenied  = 1 << 1,
+  kAccessDenied = 1 << 1,
 };
 
 MemberSignature::MemberSignature(ArtField* field) {
@@ -283,10 +279,10 @@
 
 inline std::vector<const char*> MemberSignature::GetSignatureParts() const {
   if (type_ == kField) {
-    return { class_name_.c_str(), "->", member_name_.c_str(), ":", type_signature_.c_str() };
+    return {class_name_.c_str(), "->", member_name_.c_str(), ":", type_signature_.c_str()};
   } else {
     DCHECK_EQ(type_, kMethod);
-    return { class_name_.c_str(), "->", member_name_.c_str(), type_signature_.c_str() };
+    return {class_name_.c_str(), "->", member_name_.c_str(), type_signature_.c_str()};
   }
 }
 
@@ -342,10 +338,8 @@
 }
 
 bool MemberSignature::Equals(const MemberSignature& other) {
-  return type_ == other.type_ &&
-         class_name_ == other.class_name_ &&
-         member_name_ == other.member_name_ &&
-         type_signature_ == other.type_signature_;
+  return type_ == other.type_ && class_name_ == other.class_name_ &&
+         member_name_ == other.member_name_ && type_signature_ == other.type_signature_;
 }
 
 bool MemberSignature::MemberNameAndTypeMatch(const MemberSignature& other) {
@@ -367,30 +361,34 @@
   if (runtime->IsAotCompiler()) {
     return;
   }
-  JNIEnvExt* env = Thread::Current()->GetJniEnv();
-  const std::string& package_name = Runtime::Current()->GetProcessPackageName();
-  ScopedLocalRef<jstring> package_str(env, env->NewStringUTF(package_name.c_str()));
-  if (env->ExceptionCheck()) {
-    env->ExceptionClear();
-    LOG(ERROR) << "Unable to allocate string for package name which called hidden api";
-  }
+
+  const std::string& package_name = runtime->GetProcessPackageName();
   std::ostringstream signature_str;
   Dump(signature_str);
-  ScopedLocalRef<jstring> signature_jstr(env,
-      env->NewStringUTF(signature_str.str().c_str()));
-  if (env->ExceptionCheck()) {
-    env->ExceptionClear();
+
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<2u> hs(soa.Self());
+  Handle<mirror::String> package_str =
+      hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), package_name.c_str()));
+  if (soa.Self()->IsExceptionPending()) {
+    soa.Self()->ClearException();
+    LOG(ERROR) << "Unable to allocate string for package name which called hidden api";
+  }
+  Handle<mirror::String> signature_jstr =
+      hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), signature_str.str().c_str()));
+  if (soa.Self()->IsExceptionPending()) {
+    soa.Self()->ClearException();
     LOG(ERROR) << "Unable to allocate string for hidden api method signature";
   }
-  env->CallStaticVoidMethod(WellKnownClasses::dalvik_system_VMRuntime,
-      WellKnownClasses::dalvik_system_VMRuntime_hiddenApiUsed,
-      sampled_value,
-      package_str.get(),
-      signature_jstr.get(),
-      static_cast<jint>(access_method),
-      access_denied);
-  if (env->ExceptionCheck()) {
-    env->ExceptionClear();
+  WellKnownClasses::dalvik_system_VMRuntime_hiddenApiUsed
+      ->InvokeStatic<'V', 'I', 'L', 'L', 'I', 'Z'>(soa.Self(),
+                                                   static_cast<jint>(sampled_value),
+                                                   package_str.Get(),
+                                                   signature_jstr.Get(),
+                                                   static_cast<jint>(access_method),
+                                                   access_denied);
+  if (soa.Self()->IsExceptionPending()) {
+    soa.Self()->ClearException();
     LOG(ERROR) << "Unable to report hidden api usage";
   }
 #else
@@ -408,47 +406,46 @@
 
   Runtime* runtime = Runtime::Current();
   if (!runtime->IsAotCompiler()) {
-    ScopedObjectAccessUnchecked soa(Thread::Current());
+    ScopedObjectAccess soa(Thread::Current());
+    StackHandleScope<2u> hs(soa.Self());
 
-    ScopedLocalRef<jobject> consumer_object(soa.Env(),
-        soa.Env()->GetStaticObjectField(
-            WellKnownClasses::dalvik_system_VMRuntime,
-            WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer));
+    ArtField* consumer_field = WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer;
+    DCHECK(consumer_field->GetDeclaringClass()->IsInitialized());
+    Handle<mirror::Object> consumer_object =
+        hs.NewHandle(consumer_field->GetObject(consumer_field->GetDeclaringClass()));
+
     // If the consumer is non-null, we call back to it to let it know that we
     // have encountered an API that's in one of our lists.
     if (consumer_object != nullptr) {
       std::ostringstream member_signature_str;
       Dump(member_signature_str);
 
-      ScopedLocalRef<jobject> signature_str(
-          soa.Env(),
-          soa.Env()->NewStringUTF(member_signature_str.str().c_str()));
+      Handle<mirror::String> signature_str = hs.NewHandle(
+          mirror::String::AllocFromModifiedUtf8(soa.Self(), member_signature_str.str().c_str()));
+      // FIXME: Handle OOME. For now, crash immediatelly (do not continue with a pending exception).
+      CHECK(signature_str != nullptr);
 
       // Call through to Consumer.accept(String memberSignature);
-      soa.Env()->CallVoidMethod(consumer_object.get(),
-                                WellKnownClasses::java_util_function_Consumer_accept,
-                                signature_str.get());
+      WellKnownClasses::java_util_function_Consumer_accept->InvokeInterface<'V', 'L'>(
+          soa.Self(), consumer_object.Get(), signature_str.Get());
     }
   }
 }
 
-static ALWAYS_INLINE bool CanUpdateRuntimeFlags(ArtField*) {
-  return true;
-}
+static ALWAYS_INLINE bool CanUpdateRuntimeFlags(ArtField*) { return true; }
 
 static ALWAYS_INLINE bool CanUpdateRuntimeFlags(ArtMethod* method) {
   return !method->IsIntrinsic();
 }
 
-template<typename T>
+template <typename T>
 static ALWAYS_INLINE void MaybeUpdateAccessFlags(Runtime* runtime, T* member, uint32_t flag)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   // Update the access flags unless:
   // (a) `member` is an intrinsic
   // (b) this is AOT compiler, as we do not want the updated access flags in the boot/app image
   // (c) deduping warnings has been explicitly switched off.
-  if (CanUpdateRuntimeFlags(member) &&
-      !runtime->IsAotCompiler() &&
+  if (CanUpdateRuntimeFlags(member) && !runtime->IsAotCompiler() &&
       runtime->ShouldDedupeHiddenApiWarnings()) {
     member->SetAccessFlags(member->GetAccessFlags() | flag);
   }
@@ -479,12 +476,13 @@
   accessor.VisitMethods(fn_visit, fn_visit);
 }
 
-template<typename T>
+template <typename T>
 uint32_t GetDexFlags(T* member) REQUIRES_SHARED(Locks::mutator_lock_) {
   static_assert(std::is_same<T, ArtField>::value || std::is_same<T, ArtMethod>::value);
   constexpr bool kMemberIsField = std::is_same<T, ArtField>::value;
   using AccessorType = typename std::conditional<std::is_same<T, ArtField>::value,
-      ClassAccessor::Field, ClassAccessor::Method>::type;
+                                                 ClassAccessor::Field,
+                                                 ClassAccessor::Method>::type;
 
   ObjPtr<mirror::Class> declaring_class = member->GetDeclaringClass();
   DCHECK(!declaring_class.IsNull()) << "Attempting to access a runtime method";
@@ -539,11 +537,11 @@
   }
 
   CHECK(flags.IsValid()) << "Could not find hiddenapi flags for "
-      << Dumpable<MemberSignature>(MemberSignature(member));
+                         << Dumpable<MemberSignature>(MemberSignature(member));
   return flags.GetDexFlags();
 }
 
-template<typename T>
+template <typename T>
 bool HandleCorePlatformApiViolation(T* member,
                                     const AccessContext& caller_context,
                                     AccessMethod access_method,
@@ -553,8 +551,8 @@
 
   if (access_method != AccessMethod::kNone) {
     LOG(WARNING) << "Core platform API violation: "
-        << Dumpable<MemberSignature>(MemberSignature(member))
-        << " from " << caller_context << " using " << access_method;
+                 << Dumpable<MemberSignature>(MemberSignature(member)) << " from " << caller_context
+                 << " using " << access_method;
 
     // If policy is set to just warn, add kAccCorePlatformApi to access flags of
     // `member` to avoid reporting the violation again next time.
@@ -567,7 +565,7 @@
   return policy == EnforcementPolicy::kEnabled;
 }
 
-template<typename T>
+template <typename T>
 bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) {
   DCHECK(member != nullptr);
   Runtime* runtime = Runtime::Current();
@@ -592,9 +590,8 @@
 
   bool deny_access = false;
   if (hiddenApiPolicy == EnforcementPolicy::kEnabled) {
-    if (api_list.IsTestApi() &&
-      (testApiPolicy == EnforcementPolicy::kDisabled ||
-        compatFramework.IsChangeEnabled(kAllowTestApiAccess))) {
+    if (api_list.IsTestApi() && (testApiPolicy == EnforcementPolicy::kDisabled ||
+                                 compatFramework.IsChangeEnabled(kAllowTestApiAccess))) {
       deny_access = false;
     } else {
       switch (api_list.GetMaxAllowedSdkVersion()) {
@@ -667,7 +664,7 @@
                                                       AccessMethod access_method);
 }  // namespace detail
 
-template<typename T>
+template <typename T>
 bool ShouldDenyAccessToMember(T* member,
                               const std::function<AccessContext()>& fn_get_access_context,
                               AccessMethod access_method) {
@@ -760,10 +757,7 @@
       // Access checks are not disabled, report the violation.
       // This may also add kAccCorePlatformApi to the access flags of `member`
       // so as to not warn again on next access.
-      return detail::HandleCorePlatformApiViolation(member,
-                                                    caller_context,
-                                                    access_method,
-                                                    policy);
+      return detail::HandleCorePlatformApiViolation(member, caller_context, access_method, policy);
     }
 
     case Domain::kCorePlatform: {
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h
index 1eb5e17..281c1f3 100644
--- a/runtime/hidden_api.h
+++ b/runtime/hidden_api.h
@@ -29,7 +29,6 @@
 #include "mirror/class_loader.h"
 #include "reflection.h"
 #include "runtime.h"
-#include "well_known_classes.h"
 
 namespace art {
 namespace hiddenapi {
@@ -123,7 +122,7 @@
 
     if (domain == Domain::kApplication &&
         klass->ShouldSkipHiddenApiChecks() &&
-        Runtime::Current()->IsJavaDebuggable()) {
+        Runtime::Current()->IsJavaDebuggableAtInit()) {
       // Class is known, it is marked trusted and we are in debuggable mode.
       domain = ComputeDomain(/* is_trusted= */ true);
     }
diff --git a/runtime/hidden_api_test.cc b/runtime/hidden_api_test.cc
index e204c57..3fb447a 100644
--- a/runtime/hidden_api_test.cc
+++ b/runtime/hidden_api_test.cc
@@ -26,7 +26,7 @@
 #include "common_runtime_test.h"
 #include "jni/jni_internal.h"
 #include "proxy_test.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -59,26 +59,25 @@
 }
 
 static bool LoadDexFiles(const std::string& path,
-                         ScopedObjectAccess& soa,
+                         Thread* self,
                          /* out */ std::vector<std::unique_ptr<const DexFile>>* dex_files,
                          /* out */ ObjPtr<mirror::ClassLoader>* class_loader,
                          /* out */ std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) {
-  if (!ArtDexFileLoader().Open(path.c_str(),
-                               path,
-                               /* verify= */ true,
-                               /* verify_checksum= */ true,
-                               error_msg,
-                               dex_files)) {
+  ArtDexFileLoader dex_file_loader(path);
+  if (!dex_file_loader.Open(/* verify= */ true,
+                            /* verify_checksum= */ true,
+                            error_msg,
+                            dex_files)) {
     return false;
   }
 
   ClassLinker* const linker = Runtime::Current()->GetClassLinker();
 
-  StackHandleScope<2> hs(soa.Self());
-  Handle<mirror::Class> h_class = hs.NewHandle(soa.Decode<mirror::Class>(
-      WellKnownClasses::dalvik_system_PathClassLoader));
+  StackHandleScope<2> hs(self);
+  Handle<mirror::Class> h_class =
+      hs.NewHandle(WellKnownClasses::dalvik_system_PathClassLoader.Get());
   Handle<mirror::ClassLoader> h_loader = hs.NewHandle(linker->CreateWellKnownClassLoader(
-      soa.Self(),
+      self,
       MakeNonOwningPointerVector(*dex_files),
       h_class,
       /* parent_loader= */ ScopedNullHandle<mirror::ClassLoader>(),
@@ -198,7 +197,7 @@
     ObjPtr<mirror::ClassLoader> class_loader;
 
     ASSERT_TRUE(Copy(GetTestDexFileName("Main"), location, &error_msg)) << error_msg;
-    ASSERT_TRUE(LoadDexFiles(location, soa, &dex_files, &class_loader, &error_msg))
+    ASSERT_TRUE(LoadDexFiles(location, soa.Self(), &dex_files, &class_loader, &error_msg))
         << error_msg;
     ASSERT_GE(dex_files.size(), 1u);
     ASSERT_TRUE(CheckAllDexFilesInDomain(class_loader,
@@ -639,14 +638,14 @@
 TEST_F(HiddenApiTest, DexDomain_DataDir) {
   // Load file from a non-system directory and check that it is not flagged as framework.
   std::string data_location_path = android_data_ + "/foo.jar";
-  ASSERT_FALSE(LocationIsOnSystemFramework(data_location_path.c_str()));
+  ASSERT_FALSE(LocationIsOnSystemFramework(data_location_path));
   TestLocation(data_location_path, hiddenapi::Domain::kApplication);
 }
 
 TEST_F(HiddenApiTest, DexDomain_SystemDir) {
   // Load file from a system, non-framework directory and check that it is not flagged as framework.
   std::string system_location_path = GetAndroidRoot() + "/foo.jar";
-  ASSERT_FALSE(LocationIsOnSystemFramework(system_location_path.c_str()));
+  ASSERT_FALSE(LocationIsOnSystemFramework(system_location_path));
   TestLocation(system_location_path, hiddenapi::Domain::kApplication);
 }
 
@@ -654,7 +653,7 @@
   // Load file from a system_ext, non-framework directory and check that it is not flagged as
   // framework.
   std::string system_ext_location_path = android_system_ext_ + "/foo.jar";
-  ASSERT_FALSE(LocationIsOnSystemExtFramework(system_ext_location_path.c_str()));
+  ASSERT_FALSE(LocationIsOnSystemExtFramework(system_ext_location_path));
   TestLocation(system_ext_location_path, hiddenapi::Domain::kApplication);
 }
 
@@ -663,7 +662,7 @@
   // framework.
   std::filesystem::create_directory(GetAndroidRoot() + "/system_ext");
   std::string system_ext_location_path =  GetAndroidRoot() + "/system_ext/foo.jar";
-  ASSERT_FALSE(LocationIsOnSystemExtFramework(system_ext_location_path.c_str()));
+  ASSERT_FALSE(LocationIsOnSystemExtFramework(system_ext_location_path));
   TestLocation(system_ext_location_path, hiddenapi::Domain::kApplication);
 }
 
@@ -671,7 +670,7 @@
   // Load file from a system/framework directory and check that it is flagged as a framework dex.
   std::filesystem::create_directory(GetAndroidRoot() + "/framework");
   std::string system_framework_location_path = GetAndroidRoot() + "/framework/foo.jar";
-  ASSERT_TRUE(LocationIsOnSystemFramework(system_framework_location_path.c_str()));
+  ASSERT_TRUE(LocationIsOnSystemFramework(system_framework_location_path));
   TestLocation(system_framework_location_path, hiddenapi::Domain::kPlatform);
 }
 
@@ -679,7 +678,7 @@
   // Load file from a system_ext/framework directory and check that it is flagged as a framework dex.
   std::filesystem::create_directory(android_system_ext_ + "/framework");
   std::string system_ext_framework_location_path = android_system_ext_ + "/framework/foo.jar";
-  ASSERT_TRUE(LocationIsOnSystemExtFramework(system_ext_framework_location_path.c_str()));
+  ASSERT_TRUE(LocationIsOnSystemExtFramework(system_ext_framework_location_path));
   TestLocation(system_ext_framework_location_path, hiddenapi::Domain::kPlatform);
 }
 
@@ -690,14 +689,14 @@
   std::filesystem::create_directory(GetAndroidRoot() + "/system_ext/framework");
   std::string system_ext_framework_location_path =
        GetAndroidRoot() + "/system_ext/framework/foo.jar";
-  ASSERT_TRUE(LocationIsOnSystemExtFramework(system_ext_framework_location_path.c_str()));
+  ASSERT_TRUE(LocationIsOnSystemExtFramework(system_ext_framework_location_path));
   TestLocation(system_ext_framework_location_path, hiddenapi::Domain::kPlatform);
 }
 
 TEST_F(HiddenApiTest, DexDomain_DataDir_MultiDex) {
   // Load multidex file from a non-system directory and check that it is not flagged as framework.
   std::string data_multi_location_path = android_data_ + "/multifoo.jar";
-  ASSERT_FALSE(LocationIsOnSystemFramework(data_multi_location_path.c_str()));
+  ASSERT_FALSE(LocationIsOnSystemFramework(data_multi_location_path));
   TestLocation(data_multi_location_path, hiddenapi::Domain::kApplication);
 }
 
@@ -705,7 +704,7 @@
   // Load multidex file from a system, non-framework directory and check that it is not flagged
   // as framework.
   std::string system_multi_location_path = GetAndroidRoot() + "/multifoo.jar";
-  ASSERT_FALSE(LocationIsOnSystemFramework(system_multi_location_path.c_str()));
+  ASSERT_FALSE(LocationIsOnSystemFramework(system_multi_location_path));
   TestLocation(system_multi_location_path, hiddenapi::Domain::kApplication);
 }
 
@@ -713,7 +712,7 @@
   // Load multidex file from a system_ext, non-framework directory and check that it is not flagged
   // as framework.
   std::string system_ext_multi_location_path = android_system_ext_ + "/multifoo.jar";
-  ASSERT_FALSE(LocationIsOnSystemExtFramework(system_ext_multi_location_path.c_str()));
+  ASSERT_FALSE(LocationIsOnSystemExtFramework(system_ext_multi_location_path));
   TestLocation(system_ext_multi_location_path, hiddenapi::Domain::kApplication);
 }
 
@@ -723,7 +722,7 @@
   std::filesystem::create_directory(GetAndroidRoot() + "/system_ext");
   std::string system_ext_multi_location_path =
       GetAndroidRoot() + "/system_ext/multifoo.jar";
-  ASSERT_FALSE(LocationIsOnSystemExtFramework(system_ext_multi_location_path.c_str()));
+  ASSERT_FALSE(LocationIsOnSystemExtFramework(system_ext_multi_location_path));
   TestLocation(system_ext_multi_location_path, hiddenapi::Domain::kApplication);
 }
 
@@ -732,7 +731,7 @@
   // framework dex.
   std::filesystem::create_directory(GetAndroidRoot() + "/framework");
   std::string system_framework_multi_location_path = GetAndroidRoot() + "/framework/multifoo.jar";
-  ASSERT_TRUE(LocationIsOnSystemFramework(system_framework_multi_location_path.c_str()));
+  ASSERT_TRUE(LocationIsOnSystemFramework(system_framework_multi_location_path));
   TestLocation(system_framework_multi_location_path, hiddenapi::Domain::kPlatform);
 }
 
@@ -742,7 +741,7 @@
   std::filesystem::create_directory(android_system_ext_ + "/framework");
   std::string system_ext_framework_multi_location_path =
       android_system_ext_ + "/framework/multifoo.jar";
-  ASSERT_TRUE(LocationIsOnSystemExtFramework(system_ext_framework_multi_location_path.c_str()));
+  ASSERT_TRUE(LocationIsOnSystemExtFramework(system_ext_framework_multi_location_path));
   TestLocation(system_ext_framework_multi_location_path, hiddenapi::Domain::kPlatform);
 }
 
@@ -753,7 +752,7 @@
   std::filesystem::create_directory(GetAndroidRoot() + "/system_ext/framework");
   std::string system_ext_framework_multi_location_path =
        GetAndroidRoot() + "/system_ext/framework/multifoo.jar";
-  ASSERT_TRUE(LocationIsOnSystemExtFramework(system_ext_framework_multi_location_path.c_str()));
+  ASSERT_TRUE(LocationIsOnSystemExtFramework(system_ext_framework_multi_location_path));
   TestLocation(system_ext_framework_multi_location_path, hiddenapi::Domain::kPlatform);
 }
 
diff --git a/runtime/image.cc b/runtime/image.cc
index b39ffe5..bb1701f 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -17,7 +17,12 @@
 #include "image.h"
 
 #include <lz4.h>
+#include <lz4hc.h>
 #include <sstream>
+#include <sys/stat.h>
+#include <zlib.h>
+
+#include "android-base/stringprintf.h"
 
 #include "base/bit_utils.h"
 #include "base/length_prefixed_array.h"
@@ -29,8 +34,8 @@
 namespace art {
 
 const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-// Last change: Math.fma(double, double, double) intrinsic.
-const uint8_t ImageHeader::kImageVersion[] = { '1', '0', '6', '\0' };
+// Last change: Add DexCacheSection.
+const uint8_t ImageHeader::kImageVersion[] = { '1', '0', '8', '\0' };
 
 ImageHeader::ImageHeader(uint32_t image_reservation_size,
                          uint32_t component_count,
@@ -65,12 +70,14 @@
     image_roots_(image_roots),
     pointer_size_(pointer_size) {
   CHECK_EQ(image_begin, RoundUp(image_begin, kPageSize));
-  CHECK_EQ(oat_file_begin, RoundUp(oat_file_begin, kPageSize));
-  CHECK_EQ(oat_data_begin, RoundUp(oat_data_begin, kPageSize));
-  CHECK_LT(image_roots, oat_file_begin);
-  CHECK_LE(oat_file_begin, oat_data_begin);
-  CHECK_LT(oat_data_begin, oat_data_end);
-  CHECK_LE(oat_data_end, oat_file_end);
+  if (oat_checksum != 0u) {
+    CHECK_EQ(oat_file_begin, RoundUp(oat_file_begin, kPageSize));
+    CHECK_EQ(oat_data_begin, RoundUp(oat_data_begin, kPageSize));
+    CHECK_LT(image_roots, oat_file_begin);
+    CHECK_LE(oat_file_begin, oat_data_begin);
+    CHECK_LT(oat_data_begin, oat_data_end);
+    CHECK_LE(oat_data_end, oat_file_end);
+  }
   CHECK(ValidPointerSize(pointer_size_)) << pointer_size_;
   memcpy(magic_, kImageMagic, sizeof(kImageMagic));
   memcpy(version_, kImageVersion, sizeof(kImageVersion));
@@ -127,14 +134,16 @@
   if (image_begin_ >= image_begin_ + image_size_) {
     return false;
   }
-  if (oat_file_begin_ > oat_file_end_) {
-    return false;
-  }
-  if (oat_data_begin_ > oat_data_end_) {
-    return false;
-  }
-  if (oat_file_begin_ >= oat_data_begin_) {
-    return false;
+  if (oat_checksum_ != 0u) {
+    if (oat_file_begin_ > oat_file_end_) {
+      return false;
+    }
+    if (oat_data_begin_ > oat_data_end_) {
+      return false;
+    }
+    if (oat_file_begin_ >= oat_data_begin_) {
+      return false;
+    }
   }
   return true;
 }
@@ -170,6 +179,23 @@
   return ConvertToPointerSize(pointer_size_);
 }
 
+bool LZ4_decompress_safe_checked(const char* source,
+                                 char* dest,
+                                 int compressed_size,
+                                 int max_decompressed_size,
+                                 /*out*/ size_t* decompressed_size_checked,
+                                 /*out*/ std::string* error_msg) {
+  int decompressed_size = LZ4_decompress_safe(source, dest, compressed_size, max_decompressed_size);
+  if (UNLIKELY(decompressed_size < 0)) {
+    *error_msg = android::base::StringPrintf("LZ4_decompress_safe() returned negative size: %d",
+                                             decompressed_size);
+    return false;
+  } else {
+    *decompressed_size_checked = static_cast<size_t>(decompressed_size);
+    return true;
+  }
+}
+
 bool ImageHeader::Block::Decompress(uint8_t* out_ptr,
                                     const uint8_t* in_ptr,
                                     std::string* error_msg) const {
@@ -182,11 +208,17 @@
     case kStorageModeLZ4:
     case kStorageModeLZ4HC: {
       // LZ4HC and LZ4 have same internal format, both use LZ4_decompress.
-      const size_t decompressed_size = LZ4_decompress_safe(
+      size_t decompressed_size;
+      bool ok = LZ4_decompress_safe_checked(
           reinterpret_cast<const char*>(in_ptr) + data_offset_,
           reinterpret_cast<char*>(out_ptr) + image_offset_,
           data_size_,
-          image_size_);
+          image_size_,
+          &decompressed_size,
+          error_msg);
+      if (!ok) {
+        return false;
+      }
       CHECK_EQ(decompressed_size, image_size_);
       break;
     }
@@ -211,10 +243,204 @@
     case kSectionInternedStrings: return "InternedStrings";
     case kSectionClassTable: return "ClassTable";
     case kSectionStringReferenceOffsets: return "StringReferenceOffsets";
+    case kSectionDexCacheArrays: return "DexCacheArrays";
     case kSectionMetadata: return "Metadata";
     case kSectionImageBitmap: return "ImageBitmap";
     case kSectionCount: return nullptr;
   }
 }
 
+// If `image_storage_mode` is compressed, compress data from `source`
+// into `storage`, and return an array pointing to the compressed.
+// If the mode is uncompressed, just return an array pointing to `source`.
+static ArrayRef<const uint8_t> MaybeCompressData(ArrayRef<const uint8_t> source,
+                                                 ImageHeader::StorageMode image_storage_mode,
+                                                 /*out*/ dchecked_vector<uint8_t>* storage) {
+  const uint64_t compress_start_time = NanoTime();
+
+  switch (image_storage_mode) {
+    case ImageHeader::kStorageModeLZ4: {
+      storage->resize(LZ4_compressBound(source.size()));
+      size_t data_size = LZ4_compress_default(
+          reinterpret_cast<char*>(const_cast<uint8_t*>(source.data())),
+          reinterpret_cast<char*>(storage->data()),
+          source.size(),
+          storage->size());
+      storage->resize(data_size);
+      break;
+    }
+    case ImageHeader::kStorageModeLZ4HC: {
+      // Bound is same as non HC.
+      storage->resize(LZ4_compressBound(source.size()));
+      size_t data_size = LZ4_compress_HC(
+          reinterpret_cast<const char*>(const_cast<uint8_t*>(source.data())),
+          reinterpret_cast<char*>(storage->data()),
+          source.size(),
+          storage->size(),
+          LZ4HC_CLEVEL_MAX);
+      storage->resize(data_size);
+      break;
+    }
+    case ImageHeader::kStorageModeUncompressed: {
+      return source;
+    }
+    default: {
+      LOG(FATAL) << "Unsupported";
+      UNREACHABLE();
+    }
+  }
+
+  DCHECK(image_storage_mode == ImageHeader::kStorageModeLZ4 ||
+         image_storage_mode == ImageHeader::kStorageModeLZ4HC);
+  VLOG(image) << "Compressed from " << source.size() << " to " << storage->size() << " in "
+              << PrettyDuration(NanoTime() - compress_start_time);
+  if (kIsDebugBuild) {
+    dchecked_vector<uint8_t> decompressed(source.size());
+    size_t decompressed_size;
+    std::string error_msg;
+    bool ok = LZ4_decompress_safe_checked(
+        reinterpret_cast<char*>(storage->data()),
+        reinterpret_cast<char*>(decompressed.data()),
+        storage->size(),
+        decompressed.size(),
+        &decompressed_size,
+        &error_msg);
+    if (!ok) {
+      LOG(FATAL) << error_msg;
+      UNREACHABLE();
+    }
+    CHECK_EQ(decompressed_size, decompressed.size());
+    CHECK_EQ(memcmp(source.data(), decompressed.data(), source.size()), 0) << image_storage_mode;
+  }
+  return ArrayRef<const uint8_t>(*storage);
+}
+
+bool ImageHeader::WriteData(const ImageFileGuard& image_file,
+                            const uint8_t* data,
+                            const uint8_t* bitmap_data,
+                            ImageHeader::StorageMode image_storage_mode,
+                            uint32_t max_image_block_size,
+                            bool update_checksum,
+                            std::string* error_msg) {
+  const bool is_compressed = image_storage_mode != ImageHeader::kStorageModeUncompressed;
+  dchecked_vector<std::pair<uint32_t, uint32_t>> block_sources;
+  dchecked_vector<ImageHeader::Block> blocks;
+
+  // Add a set of solid blocks such that no block is larger than the maximum size. A solid block
+  // is a block that must be decompressed all at once.
+  auto add_blocks = [&](uint32_t offset, uint32_t size) {
+    while (size != 0u) {
+      const uint32_t cur_size = std::min(size, max_image_block_size);
+      block_sources.emplace_back(offset, cur_size);
+      offset += cur_size;
+      size -= cur_size;
+    }
+  };
+
+  add_blocks(sizeof(ImageHeader), this->GetImageSize() - sizeof(ImageHeader));
+
+  // Checksum of compressed image data and header.
+  uint32_t image_checksum = 0u;
+  if (update_checksum) {
+    image_checksum = adler32(0L, Z_NULL, 0);
+    image_checksum = adler32(image_checksum,
+                             reinterpret_cast<const uint8_t*>(this),
+                             sizeof(ImageHeader));
+  }
+
+  // Copy and compress blocks.
+  uint32_t out_offset = sizeof(ImageHeader);
+  for (const std::pair<uint32_t, uint32_t> block : block_sources) {
+    ArrayRef<const uint8_t> raw_image_data(data + block.first, block.second);
+    dchecked_vector<uint8_t> compressed_data;
+    ArrayRef<const uint8_t> image_data =
+        MaybeCompressData(raw_image_data, image_storage_mode, &compressed_data);
+
+    if (!is_compressed) {
+      // For uncompressed, preserve alignment since the image will be directly mapped.
+      out_offset = block.first;
+    }
+
+    // Fill in the compressed location of the block.
+    blocks.emplace_back(ImageHeader::Block(
+        image_storage_mode,
+        /*data_offset=*/ out_offset,
+        /*data_size=*/ image_data.size(),
+        /*image_offset=*/ block.first,
+        /*image_size=*/ block.second));
+
+    if (!image_file->PwriteFully(image_data.data(), image_data.size(), out_offset)) {
+      *error_msg = "Failed to write image file data " +
+          image_file->GetPath() + ": " + std::string(strerror(errno));
+      return false;
+    }
+    out_offset += image_data.size();
+    if (update_checksum) {
+      image_checksum = adler32(image_checksum, image_data.data(), image_data.size());
+    }
+  }
+
+  if (is_compressed) {
+    // Align up since the compressed data is not necessarily aligned.
+    out_offset = RoundUp(out_offset, alignof(ImageHeader::Block));
+    CHECK(!blocks.empty());
+    const size_t blocks_bytes = blocks.size() * sizeof(blocks[0]);
+    if (!image_file->PwriteFully(&blocks[0], blocks_bytes, out_offset)) {
+      *error_msg = "Failed to write image blocks " +
+          image_file->GetPath() + ": " + std::string(strerror(errno));
+      return false;
+    }
+    this->blocks_offset_ = out_offset;
+    this->blocks_count_ = blocks.size();
+    out_offset += blocks_bytes;
+  }
+
+  // Data size includes everything except the bitmap.
+  this->data_size_ = out_offset - sizeof(ImageHeader);
+
+  // Update and write the bitmap section. Note that the bitmap section is relative to the
+  // possibly compressed image.
+  ImageSection& bitmap_section = GetImageSection(ImageHeader::kSectionImageBitmap);
+  // Align up since data size may be unaligned if the image is compressed.
+  out_offset = RoundUp(out_offset, kPageSize);
+  bitmap_section = ImageSection(out_offset, bitmap_section.Size());
+
+  if (!image_file->PwriteFully(bitmap_data,
+                               bitmap_section.Size(),
+                               bitmap_section.Offset())) {
+    *error_msg = "Failed to write image file bitmap " +
+        image_file->GetPath() + ": " + std::string(strerror(errno));
+    return false;
+  }
+
+  int err = image_file->Flush();
+  if (err < 0) {
+    *error_msg = "Failed to flush image file " + image_file->GetPath() + ": " + std::to_string(err);
+    return false;
+  }
+
+  if (update_checksum) {
+      // Calculate the image checksum of the remaining data.
+    image_checksum = adler32(GetImageChecksum(),
+                             reinterpret_cast<const uint8_t*>(bitmap_data),
+                             bitmap_section.Size());
+    this->SetImageChecksum(image_checksum);
+  }
+
+  if (VLOG_IS_ON(image)) {
+    const size_t separately_written_section_size = bitmap_section.Size();
+    const size_t total_uncompressed_size = image_size_ + separately_written_section_size;
+    const size_t total_compressed_size = out_offset + separately_written_section_size;
+
+    VLOG(compiler) << "UncompressedImageSize = " << total_uncompressed_size;
+    if (total_uncompressed_size != total_compressed_size) {
+      VLOG(compiler) << "CompressedImageSize = " << total_compressed_size;
+    }
+  }
+
+  DCHECK_EQ(bitmap_section.End(), static_cast<size_t>(image_file->GetLength()))
+      << "Bitmap should be at the end of the file";
+  return true;
+}
+
 }  // namespace art
diff --git a/runtime/image.h b/runtime/image.h
index 8f045e9..324cd3c 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -21,6 +21,8 @@
 
 #include "base/enums.h"
 #include "base/iteration_range.h"
+#include "base/os.h"
+#include "base/unix_file/fd_file.h"
 #include "mirror/object.h"
 #include "runtime_globals.h"
 
@@ -28,6 +30,8 @@
 
 class ArtField;
 class ArtMethod;
+class ImageFileGuard;
+
 template <class MirrorType> class ObjPtr;
 
 namespace linker {
@@ -230,6 +234,9 @@
     // Aliases.
     kAppImageClassLoader = kSpecialRoots,   // The class loader used to build the app image.
     kBootImageLiveObjects = kSpecialRoots,  // Array of boot image objects that must be kept live.
+    kAppImageOatHeader = kSpecialRoots,     // A byte array containing 1) a fake OatHeader to check
+                                            // if the image can be loaded against the current
+                                            // runtime, and 2) the dex checksums.
   };
 
   enum BootImageLiveObjects {
@@ -261,6 +268,7 @@
     kSectionInternedStrings,
     kSectionClassTable,
     kSectionStringReferenceOffsets,
+    kSectionDexCacheArrays,
     kSectionMetadata,
     kSectionImageBitmap,
     kSectionCount,  // Number of elements in enum.
@@ -414,6 +422,16 @@
     return blocks_count_;
   }
 
+  // Helper for writing `data` and `bitmap_data` into `image_file`, following
+  // the information stored in this header and passed as arguments.
+  bool WriteData(const ImageFileGuard& image_file,
+                 const uint8_t* data,
+                 const uint8_t* bitmap_data,
+                 ImageHeader::StorageMode image_storage_mode,
+                 uint32_t max_image_block_size,
+                 bool update_checksum,
+                 std::string* error_msg);
+
  private:
   static const uint8_t kImageMagic[4];
   static const uint8_t kImageVersion[4];
@@ -502,8 +520,66 @@
   uint32_t blocks_count_ = 0u;
 
   friend class linker::ImageWriter;
+  friend class RuntimeImageHelper;
 };
 
+// Helper class that erases the image file if it isn't properly flushed and closed.
+class ImageFileGuard {
+ public:
+  ImageFileGuard() noexcept = default;
+  ImageFileGuard(ImageFileGuard&& other) noexcept = default;
+  ImageFileGuard& operator=(ImageFileGuard&& other) noexcept = default;
+
+  ~ImageFileGuard() {
+    if (image_file_ != nullptr) {
+      // Failure, erase the image file.
+      image_file_->Erase();
+    }
+  }
+
+  void reset(File* image_file) {
+    image_file_.reset(image_file);
+  }
+
+  bool operator==(std::nullptr_t) {
+    return image_file_ == nullptr;
+  }
+
+  bool operator!=(std::nullptr_t) {
+    return image_file_ != nullptr;
+  }
+
+  File* operator->() const {
+    return image_file_.get();
+  }
+
+  bool WriteHeaderAndClose(const std::string& image_filename,
+                           const ImageHeader* image_header,
+                           std::string* error_msg) {
+    // The header is uncompressed since it contains whether the image is compressed or not.
+    if (!image_file_->PwriteFully(image_header, sizeof(ImageHeader), 0)) {
+      *error_msg = "Failed to write image file header "
+          + image_filename + ": " + std::string(strerror(errno));
+      return false;
+    }
+
+    // FlushCloseOrErase() takes care of erasing, so the destructor does not need
+    // to do that whether the FlushCloseOrErase() succeeds or fails.
+    std::unique_ptr<File> image_file = std::move(image_file_);
+    if (image_file->FlushCloseOrErase() != 0) {
+      *error_msg = "Failed to flush and close image file "
+          + image_filename + ": " + std::string(strerror(errno));
+      return false;
+    }
+
+    return true;
+  }
+
+ private:
+  std::unique_ptr<File> image_file_;
+};
+
+
 /*
  * This type holds the information necessary to fix up AppImage string
  * references.
@@ -520,6 +596,14 @@
 
 std::ostream& operator<<(std::ostream& os, const ImageSection& section);
 
+// Wrapper over LZ4_decompress_safe() that checks if return value is negative. See b/242914915.
+bool LZ4_decompress_safe_checked(const char* source,
+                                 char* dest,
+                                 int compressed_size,
+                                 int max_decompressed_size,
+                                 /*out*/ size_t* decompressed_size_checked,
+                                 /*out*/ std::string* error_msg);
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_IMAGE_H_
diff --git a/runtime/index_bss_mapping.cc b/runtime/index_bss_mapping.cc
index 8d9d8cf..f6e083d 100644
--- a/runtime/index_bss_mapping.cc
+++ b/runtime/index_bss_mapping.cc
@@ -44,8 +44,6 @@
   }
 }
 
-constexpr size_t IndexBssMappingLookup::npos;
-
 size_t IndexBssMappingLookup::GetBssOffset(const IndexBssMapping* mapping,
                                            uint32_t index,
                                            uint32_t number_of_indexes,
diff --git a/runtime/indirect_reference_table-inl.h b/runtime/indirect_reference_table-inl.h
index 6ea035b..23df2c8 100644
--- a/runtime/indirect_reference_table-inl.h
+++ b/runtime/indirect_reference_table-inl.h
@@ -37,7 +37,7 @@
                                                      /*out*/std::string* error_msg) const {
   DCHECK(iref != nullptr);
   DCHECK_EQ(GetIndirectRefKind(iref), kind_);
-  const uint32_t top_index = segment_state_.top_index;
+  const uint32_t top_index = top_index_;
   uint32_t idx = ExtractIndex(iref);
   if (UNLIKELY(idx >= top_index)) {
     *error_msg = android::base::StringPrintf("deleted reference at index %u in a table of size %u",
@@ -82,7 +82,7 @@
 inline ObjPtr<mirror::Object> IndirectReferenceTable::Get(IndirectRef iref) const {
   DCHECK_EQ(GetIndirectRefKind(iref), kind_);
   uint32_t idx = ExtractIndex(iref);
-  DCHECK_LT(idx, segment_state_.top_index);
+  DCHECK_LT(idx, top_index_);
   DCHECK_EQ(DecodeSerial(reinterpret_cast<uintptr_t>(iref)), table_[idx].GetSerial());
   DCHECK(!table_[idx].GetReference()->IsNull());
   ObjPtr<mirror::Object> obj = table_[idx].GetReference()->Read<kReadBarrierOption>();
@@ -93,7 +93,7 @@
 inline void IndirectReferenceTable::Update(IndirectRef iref, ObjPtr<mirror::Object> obj) {
   DCHECK_EQ(GetIndirectRefKind(iref), kind_);
   uint32_t idx = ExtractIndex(iref);
-  DCHECK_LT(idx, segment_state_.top_index);
+  DCHECK_LT(idx, top_index_);
   DCHECK_EQ(DecodeSerial(reinterpret_cast<uintptr_t>(iref)), table_[idx].GetSerial());
   DCHECK(!table_[idx].GetReference()->IsNull());
   table_[idx].SetReference(obj);
diff --git a/runtime/indirect_reference_table.cc b/runtime/indirect_reference_table.cc
index ebf382f..67010f3 100644
--- a/runtime/indirect_reference_table.cc
+++ b/runtime/indirect_reference_table.cc
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include "base/bit_utils.h"
-#include "base/globals.h"
 #include "indirect_reference_table-inl.h"
 
+#include "base/bit_utils.h"
+#include "base/globals.h"
 #include "base/mutator_locked_dumpable.h"
 #include "base/systrace.h"
 #include "base/utils.h"
@@ -26,8 +26,9 @@
 #include "jni/jni_internal.h"
 #include "mirror/object-inl.h"
 #include "nth_caller_visitor.h"
+#include "object_callbacks.h"
 #include "reference_table.h"
-#include "runtime.h"
+#include "runtime-inl.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
 
@@ -35,16 +36,15 @@
 
 namespace art {
 
-static constexpr bool kDumpStackOnNonLocalReference = false;
 static constexpr bool kDebugIRT = false;
 
 // Maximum table size we allow.
 static constexpr size_t kMaxTableSizeInBytes = 128 * MB;
 
-const char* GetIndirectRefKindString(const IndirectRefKind& kind) {
+const char* GetIndirectRefKindString(IndirectRefKind kind) {
   switch (kind) {
-    case kJniTransitionOrInvalid:
-      return "JniTransitionOrInvalid";
+    case kJniTransition:
+      return "JniTransition";
     case kLocal:
       return "Local";
     case kGlobal:
@@ -79,86 +79,37 @@
   return result;
 }
 
-SmallIrtAllocator::SmallIrtAllocator()
-    : small_irt_freelist_(nullptr), lock_("Small IRT table lock", LockLevel::kGenericBottomLock) {
-}
-
-// Allocate an IRT table for kSmallIrtEntries.
-IrtEntry* SmallIrtAllocator::Allocate(std::string* error_msg) {
-  MutexLock lock(Thread::Current(), lock_);
-  if (small_irt_freelist_ == nullptr) {
-    // Refill.
-    MemMap map = NewIRTMap(kPageSize, error_msg);
-    if (map.IsValid()) {
-      small_irt_freelist_ = reinterpret_cast<IrtEntry*>(map.Begin());
-      for (uint8_t* p = map.Begin(); p + kInitialIrtBytes < map.End(); p += kInitialIrtBytes) {
-        *reinterpret_cast<IrtEntry**>(p) = reinterpret_cast<IrtEntry*>(p + kInitialIrtBytes);
-      }
-      shared_irt_maps_.emplace_back(std::move(map));
-    }
-  }
-  if (small_irt_freelist_ == nullptr) {
-    return nullptr;
-  }
-  IrtEntry* result = small_irt_freelist_;
-  small_irt_freelist_ = *reinterpret_cast<IrtEntry**>(small_irt_freelist_);
-  // Clear pointer in first entry.
-  new(result) IrtEntry();
-  return result;
-}
-
-void SmallIrtAllocator::Deallocate(IrtEntry* unneeded) {
-  MutexLock lock(Thread::Current(), lock_);
-  *reinterpret_cast<IrtEntry**>(unneeded) = small_irt_freelist_;
-  small_irt_freelist_ = unneeded;
-}
-
-IndirectReferenceTable::IndirectReferenceTable(size_t max_count,
-                                               IndirectRefKind desired_kind,
-                                               ResizableCapacity resizable,
-                                               std::string* error_msg)
-    : segment_state_(kIRTFirstSegment),
+IndirectReferenceTable::IndirectReferenceTable(IndirectRefKind kind)
+    : table_mem_map_(),
       table_(nullptr),
-      kind_(desired_kind),
-      max_entries_(max_count),
-      current_num_holes_(0),
-      resizable_(resizable) {
+      kind_(kind),
+      top_index_(0u),
+      max_entries_(0u),
+      current_num_holes_(0) {
+  CHECK_NE(kind, kJniTransition);
+  CHECK_NE(kind, kLocal);
+}
+
+bool IndirectReferenceTable::Initialize(size_t max_count, std::string* error_msg) {
   CHECK(error_msg != nullptr);
-  CHECK_NE(desired_kind, kJniTransitionOrInvalid);
 
   // Overflow and maximum check.
   CHECK_LE(max_count, kMaxTableSizeInBytes / sizeof(IrtEntry));
 
-  if (max_entries_ <= kSmallIrtEntries) {
-    table_ = Runtime::Current()->GetSmallIrtAllocator()->Allocate(error_msg);
-    if (table_ != nullptr) {
-      max_entries_ = kSmallIrtEntries;
-      // table_mem_map_ remains invalid.
-    }
+  const size_t table_bytes = RoundUp(max_count * sizeof(IrtEntry), kPageSize);
+  table_mem_map_ = NewIRTMap(table_bytes, error_msg);
+  if (!table_mem_map_.IsValid()) {
+    DCHECK(!error_msg->empty());
+    return false;
   }
-  if (table_ == nullptr) {
-    const size_t table_bytes = RoundUp(max_count * sizeof(IrtEntry), kPageSize);
-    table_mem_map_ = NewIRTMap(table_bytes, error_msg);
-    if (!table_mem_map_.IsValid() && error_msg->empty()) {
-      *error_msg = "Unable to map memory for indirect ref table";
-    }
 
-    if (table_mem_map_.IsValid()) {
-      table_ = reinterpret_cast<IrtEntry*>(table_mem_map_.Begin());
-    } else {
-      table_ = nullptr;
-    }
-    // Take into account the actual length.
-    max_entries_ = table_bytes / sizeof(IrtEntry);
-  }
-  segment_state_ = kIRTFirstSegment;
-  last_known_previous_state_ = kIRTFirstSegment;
+  table_ = reinterpret_cast<IrtEntry*>(table_mem_map_.Begin());
+  // Take into account the actual length.
+  max_entries_ = table_bytes / sizeof(IrtEntry);
+  return true;
 }
 
 IndirectReferenceTable::~IndirectReferenceTable() {
-  if (table_ != nullptr && !table_mem_map_.IsValid()) {
-    Runtime::Current()->GetSmallIrtAllocator()->Deallocate(table_);
-  }
 }
 
 void IndirectReferenceTable::ConstexprChecks() {
@@ -187,10 +138,12 @@
   static_assert(DecodeIndex(EncodeIndex(1u)) == 1u, "Index encoding error");
   static_assert(DecodeIndex(EncodeIndex(2u)) == 2u, "Index encoding error");
   static_assert(DecodeIndex(EncodeIndex(3u)) == 3u, "Index encoding error");
-}
 
-bool IndirectReferenceTable::IsValid() const {
-  return table_ != nullptr;
+  // Distinguishing between local and (weak) global references.
+  static_assert((GetGlobalOrWeakGlobalMask() & EncodeIndirectRefKind(kJniTransition)) == 0u);
+  static_assert((GetGlobalOrWeakGlobalMask() & EncodeIndirectRefKind(kLocal)) == 0u);
+  static_assert((GetGlobalOrWeakGlobalMask() & EncodeIndirectRefKind(kGlobal)) != 0u);
+  static_assert((GetGlobalOrWeakGlobalMask() & EncodeIndirectRefKind(kWeakGlobal)) != 0u);
 }
 
 // Holes:
@@ -200,37 +153,10 @@
 // similar. Instead, we scan for holes, with the expectation that we will find holes fast as they
 // are usually near the end of the table (see the header, TODO: verify this assumption). To avoid
 // scans when there are no holes, the number of known holes should be tracked.
-//
-// A previous implementation stored the top index and the number of holes as the segment state.
-// This constraints the maximum number of references to 16-bit. We want to relax this, as it
-// is easy to require more references (e.g., to list all classes in large applications). Thus,
-// the implicitly stack-stored state, the IRTSegmentState, is only the top index.
-//
-// Thus, hole count is a local property of the current segment, and needs to be recovered when
-// (or after) a frame is pushed or popped. To keep JNI transitions simple (and inlineable), we
-// cannot do work when the segment changes. Thus, Add and Remove need to ensure the current
-// hole count is correct.
-//
-// To be able to detect segment changes, we require an additional local field that can describe
-// the known segment. This is last_known_previous_state_. The requirement will become clear with
-// the following (some non-trivial) cases that have to be supported:
-//
-// 1) Segment with holes (current_num_holes_ > 0), push new segment, add/remove reference
-// 2) Segment with holes (current_num_holes_ > 0), pop segment, add/remove reference
-// 3) Segment with holes (current_num_holes_ > 0), push new segment, pop segment, add/remove
-//    reference
-// 4) Empty segment, push new segment, create a hole, pop a segment, add/remove a reference
-// 5) Base segment, push new segment, create a hole, pop a segment, push new segment, add/remove
-//    reference
-//
-// Storing the last known *previous* state (bottom index) allows conservatively detecting all the
-// segment changes above. The condition is simply that the last known state is greater than or
-// equal to the current previous state, and smaller than the current state (top index). The
-// condition is conservative as it adds O(1) overhead to operations on an empty segment.
 
-static size_t CountNullEntries(const IrtEntry* table, size_t from, size_t to) {
+static size_t CountNullEntries(const IrtEntry* table, size_t to) {
   size_t count = 0;
-  for (size_t index = from; index != to; ++index) {
+  for (size_t index = 0u; index != to; ++index) {
     if (table[index].GetReference()->IsNull()) {
       count++;
     }
@@ -238,121 +164,37 @@
   return count;
 }
 
-void IndirectReferenceTable::RecoverHoles(IRTSegmentState prev_state) {
-  if (last_known_previous_state_.top_index >= segment_state_.top_index ||
-      last_known_previous_state_.top_index < prev_state.top_index) {
-    const size_t top_index = segment_state_.top_index;
-    size_t count = CountNullEntries(table_, prev_state.top_index, top_index);
-
-    if (kDebugIRT) {
-      LOG(INFO) << "+++ Recovered holes: "
-                << " Current prev=" << prev_state.top_index
-                << " Current top_index=" << top_index
-                << " Old num_holes=" << current_num_holes_
-                << " New num_holes=" << count;
-    }
-
-    current_num_holes_ = count;
-    last_known_previous_state_ = prev_state;
-  } else if (kDebugIRT) {
-    LOG(INFO) << "No need to recover holes";
-  }
-}
-
 ALWAYS_INLINE
 static inline void CheckHoleCount(IrtEntry* table,
                                   size_t exp_num_holes,
-                                  IRTSegmentState prev_state,
-                                  IRTSegmentState cur_state) {
+                                  size_t top_index) {
   if (kIsDebugBuild) {
-    size_t count = CountNullEntries(table, prev_state.top_index, cur_state.top_index);
-    CHECK_EQ(exp_num_holes, count) << "prevState=" << prev_state.top_index
-                                   << " topIndex=" << cur_state.top_index;
+    size_t count = CountNullEntries(table, top_index);
+    CHECK_EQ(exp_num_holes, count) << " topIndex=" << top_index;
   }
 }
 
-bool IndirectReferenceTable::Resize(size_t new_size, std::string* error_msg) {
-  CHECK_GT(new_size, max_entries_);
-
-  constexpr size_t kMaxEntries = kMaxTableSizeInBytes / sizeof(IrtEntry);
-  if (new_size > kMaxEntries) {
-    *error_msg = android::base::StringPrintf("Requested size exceeds maximum: %zu", new_size);
-    return false;
-  }
-  // Note: the above check also ensures that there is no overflow below.
-
-  const size_t table_bytes = RoundUp(new_size * sizeof(IrtEntry), kPageSize);
-
-  MemMap new_map = NewIRTMap(table_bytes, error_msg);
-  if (!new_map.IsValid()) {
-    return false;
-  }
-
-  memcpy(new_map.Begin(), table_, max_entries_ * sizeof(IrtEntry));
-  if (!table_mem_map_.IsValid()) {
-    // Didn't have its own map; deallocate old table.
-    Runtime::Current()->GetSmallIrtAllocator()->Deallocate(table_);
-  }
-  table_mem_map_ = std::move(new_map);
-  table_ = reinterpret_cast<IrtEntry*>(table_mem_map_.Begin());
-  const size_t real_new_size = table_bytes / sizeof(IrtEntry);
-  DCHECK_GE(real_new_size, new_size);
-  max_entries_ = real_new_size;
-
-  return true;
-}
-
-IndirectRef IndirectReferenceTable::Add(IRTSegmentState previous_state,
-                                        ObjPtr<mirror::Object> obj,
-                                        std::string* error_msg) {
+IndirectRef IndirectReferenceTable::Add(ObjPtr<mirror::Object> obj, std::string* error_msg) {
   if (kDebugIRT) {
-    LOG(INFO) << "+++ Add: previous_state=" << previous_state.top_index
-              << " top_index=" << segment_state_.top_index
-              << " last_known_prev_top_index=" << last_known_previous_state_.top_index
+    LOG(INFO) << "+++ Add: top_index=" << top_index_
               << " holes=" << current_num_holes_;
   }
 
-  size_t top_index = segment_state_.top_index;
-
   CHECK(obj != nullptr);
   VerifyObject(obj);
   DCHECK(table_ != nullptr);
 
-  if (top_index == max_entries_) {
-    if (resizable_ == ResizableCapacity::kNo) {
-      std::ostringstream oss;
-      oss << "JNI ERROR (app bug): " << kind_ << " table overflow "
-          << "(max=" << max_entries_ << ")"
-          << MutatorLockedDumpable<IndirectReferenceTable>(*this);
-      *error_msg = oss.str();
-      return nullptr;
-    }
-
-    // Try to double space.
-    if (std::numeric_limits<size_t>::max() / 2 < max_entries_) {
-      std::ostringstream oss;
-      oss << "JNI ERROR (app bug): " << kind_ << " table overflow "
-          << "(max=" << max_entries_ << ")" << std::endl
-          << MutatorLockedDumpable<IndirectReferenceTable>(*this)
-          << " Resizing failed: exceeds size_t";
-      *error_msg = oss.str();
-      return nullptr;
-    }
-
-    std::string inner_error_msg;
-    if (!Resize(max_entries_ * 2, &inner_error_msg)) {
-      std::ostringstream oss;
-      oss << "JNI ERROR (app bug): " << kind_ << " table overflow "
-          << "(max=" << max_entries_ << ")" << std::endl
-          << MutatorLockedDumpable<IndirectReferenceTable>(*this)
-          << " Resizing failed: " << inner_error_msg;
-      *error_msg = oss.str();
-      return nullptr;
-    }
+  if (top_index_ == max_entries_) {
+    // TODO: Fill holes before reporting error.
+    std::ostringstream oss;
+    oss << "JNI ERROR (app bug): " << kind_ << " table overflow "
+        << "(max=" << max_entries_ << ")"
+        << MutatorLockedDumpable<IndirectReferenceTable>(*this);
+    *error_msg = oss.str();
+    return nullptr;
   }
 
-  RecoverHoles(previous_state);
-  CheckHoleCount(table_, current_num_holes_, previous_state, segment_state_);
+  CheckHoleCount(table_, current_num_holes_, top_index_);
 
   // We know there's enough room in the table.  Now we just need to find
   // the right spot.  If there's a hole, find it and fill it; otherwise,
@@ -360,26 +202,26 @@
   IndirectRef result;
   size_t index;
   if (current_num_holes_ > 0) {
-    DCHECK_GT(top_index, 1U);
+    DCHECK_GT(top_index_, 1U);
     // Find the first hole; likely to be near the end of the list.
-    IrtEntry* p_scan = &table_[top_index - 1];
+    IrtEntry* p_scan = &table_[top_index_ - 1];
     DCHECK(!p_scan->GetReference()->IsNull());
     --p_scan;
     while (!p_scan->GetReference()->IsNull()) {
-      DCHECK_GE(p_scan, table_ + previous_state.top_index);
+      DCHECK_GT(p_scan, table_);
       --p_scan;
     }
     index = p_scan - table_;
     current_num_holes_--;
   } else {
     // Add to the end.
-    index = top_index++;
-    segment_state_.top_index = top_index;
+    index = top_index_;
+    ++top_index_;
   }
   table_[index].Add(obj);
   result = ToIndirectRef(index);
   if (kDebugIRT) {
-    LOG(INFO) << "+++ added at " << ExtractIndex(result) << " top=" << segment_state_.top_index
+    LOG(INFO) << "+++ added at " << ExtractIndex(result) << " top=" << top_index_
               << " holes=" << current_num_holes_;
   }
 
@@ -387,72 +229,31 @@
   return result;
 }
 
-void IndirectReferenceTable::AssertEmpty() {
-  for (size_t i = 0; i < Capacity(); ++i) {
-    if (!table_[i].GetReference()->IsNull()) {
-      LOG(FATAL) << "Internal Error: non-empty local reference table\n"
-                 << MutatorLockedDumpable<IndirectReferenceTable>(*this);
-      UNREACHABLE();
-    }
-  }
-}
-
 // Removes an object. We extract the table offset bits from "iref"
 // and zap the corresponding entry, leaving a hole if it's not at the top.
-// If the entry is not between the current top index and the bottom index
-// specified by the cookie, we don't remove anything. This is the behavior
-// required by JNI's DeleteLocalRef function.
-// This method is not called when a local frame is popped; this is only used
-// for explicit single removals.
 // Returns "false" if nothing was removed.
-bool IndirectReferenceTable::Remove(IRTSegmentState previous_state, IndirectRef iref) {
+bool IndirectReferenceTable::Remove(IndirectRef iref) {
   if (kDebugIRT) {
-    LOG(INFO) << "+++ Remove: previous_state=" << previous_state.top_index
-              << " top_index=" << segment_state_.top_index
-              << " last_known_prev_top_index=" << last_known_previous_state_.top_index
+    LOG(INFO) << "+++ Remove: top_index=" << top_index_
               << " holes=" << current_num_holes_;
   }
 
-  const uint32_t top_index = segment_state_.top_index;
-  const uint32_t bottom_index = previous_state.top_index;
+  // TODO: We should eagerly check the ref kind against the `kind_` instead of postponing until
+  // `CheckEntry()` below. Passing the wrong kind shall currently result in misleading warnings.
+
+  const uint32_t top_index = top_index_;
 
   DCHECK(table_ != nullptr);
 
-  // TODO: We should eagerly check the ref kind against the `kind_` instead of
-  // relying on this weak check and postponing the rest until `CheckEntry()` below.
-  // Passing the wrong kind shall currently result in misleading warnings.
-  if (GetIndirectRefKind(iref) == kJniTransitionOrInvalid) {
-    auto* self = Thread::Current();
-    ScopedObjectAccess soa(self);
-    if (self->IsJniTransitionReference(reinterpret_cast<jobject>(iref))) {
-      auto* env = self->GetJniEnv();
-      DCHECK(env != nullptr);
-      if (env->IsCheckJniEnabled()) {
-        LOG(WARNING) << "Attempt to remove non-JNI local reference, dumping thread";
-        if (kDumpStackOnNonLocalReference) {
-          self->Dump(LOG_STREAM(WARNING));
-        }
-      }
-      return true;
-    }
-  }
-
   const uint32_t idx = ExtractIndex(iref);
-  if (idx < bottom_index) {
-    // Wrong segment.
-    LOG(WARNING) << "Attempt to remove index outside index area (" << idx
-                 << " vs " << bottom_index << "-" << top_index << ")";
-    return false;
-  }
   if (idx >= top_index) {
     // Bad --- stale reference?
     LOG(WARNING) << "Attempt to remove invalid index " << idx
-                 << " (bottom=" << bottom_index << " top=" << top_index << ")";
+                 << " (top=" << top_index << ")";
     return false;
   }
 
-  RecoverHoles(previous_state);
-  CheckHoleCount(table_, current_num_holes_, previous_state, segment_state_);
+  CheckHoleCount(table_, current_num_holes_, top_index_);
 
   if (idx == top_index - 1) {
     // Top-most entry.  Scan up and consume holes.
@@ -464,11 +265,10 @@
     *table_[idx].GetReference() = GcRoot<mirror::Object>(nullptr);
     if (current_num_holes_ != 0) {
       uint32_t collapse_top_index = top_index;
-      while (--collapse_top_index > bottom_index && current_num_holes_ != 0) {
+      while (--collapse_top_index > 0u && current_num_holes_ != 0) {
         if (kDebugIRT) {
           ScopedObjectAccess soa(Thread::Current());
-          LOG(INFO) << "+++ checking for hole at " << collapse_top_index - 1
-                    << " (previous_state=" << bottom_index << ") val="
+          LOG(INFO) << "+++ checking for hole at " << collapse_top_index - 1 << " val="
                     << table_[collapse_top_index - 1].GetReference()->Read<kWithoutReadBarrier>();
         }
         if (!table_[collapse_top_index - 1].GetReference()->IsNull()) {
@@ -479,11 +279,11 @@
         }
         current_num_holes_--;
       }
-      segment_state_.top_index = collapse_top_index;
+      top_index_ = collapse_top_index;
 
-      CheckHoleCount(table_, current_num_holes_, previous_state, segment_state_);
+      CheckHoleCount(table_, current_num_holes_, top_index_);
     } else {
-      segment_state_.top_index = top_index - 1;
+      top_index_ = top_index - 1;
       if (kDebugIRT) {
         LOG(INFO) << "+++ ate last entry " << top_index - 1;
       }
@@ -501,7 +301,7 @@
 
     *table_[idx].GetReference() = GcRoot<mirror::Object>(nullptr);
     current_num_holes_++;
-    CheckHoleCount(table_, current_num_holes_, previous_state, segment_state_);
+    CheckHoleCount(table_, current_num_holes_, top_index_);
     if (kDebugIRT) {
       LOG(INFO) << "+++ left hole at " << idx << ", holes=" << current_num_holes_;
     }
@@ -512,10 +312,7 @@
 
 void IndirectReferenceTable::Trim() {
   ScopedTrace trace(__PRETTY_FUNCTION__);
-  if (!table_mem_map_.IsValid()) {
-    // Small table; nothing to do here.
-    return;
-  }
+  DCHECK(table_mem_map_.IsValid());
   const size_t top_index = Capacity();
   uint8_t* release_start = AlignUp(reinterpret_cast<uint8_t*>(&table_[top_index]), kPageSize);
   uint8_t* release_end = static_cast<uint8_t*>(table_mem_map_.BaseEnd());
@@ -529,7 +326,8 @@
 
 void IndirectReferenceTable::VisitRoots(RootVisitor* visitor, const RootInfo& root_info) {
   BufferedRootVisitor<kDefaultBufferedRootCount> root_visitor(visitor, root_info);
-  for (auto ref : *this) {
+  for (size_t i = 0, capacity = Capacity(); i != capacity; ++i) {
+    GcRoot<mirror::Object>* ref = table_[i].GetReference();
     if (!ref->IsNull()) {
       root_visitor.VisitRoot(*ref);
       DCHECK(!ref->IsNull());
@@ -537,6 +335,24 @@
   }
 }
 
+void IndirectReferenceTable::SweepJniWeakGlobals(IsMarkedVisitor* visitor) {
+  CHECK_EQ(kind_, kWeakGlobal);
+  MutexLock mu(Thread::Current(), *Locks::jni_weak_globals_lock_);
+  Runtime* const runtime = Runtime::Current();
+  for (size_t i = 0, capacity = Capacity(); i != capacity; ++i) {
+    GcRoot<mirror::Object>* entry = table_[i].GetReference();
+    // Need to skip null here to distinguish between null entries and cleared weak ref entries.
+    if (!entry->IsNull()) {
+      mirror::Object* obj = entry->Read<kWithoutReadBarrier>();
+      mirror::Object* new_obj = visitor->IsMarked(obj);
+      if (new_obj == nullptr) {
+        new_obj = runtime->GetClearedJniWeakGlobal();
+      }
+      *entry = GcRoot<mirror::Object>(new_obj);
+    }
+  }
+}
+
 void IndirectReferenceTable::Dump(std::ostream& os) const {
   os << kind_ << " table dump:\n";
   ReferenceTable::Table entries;
@@ -550,47 +366,8 @@
   ReferenceTable::Dump(os, entries);
 }
 
-void IndirectReferenceTable::SetSegmentState(IRTSegmentState new_state) {
-  if (kDebugIRT) {
-    LOG(INFO) << "Setting segment state: "
-              << segment_state_.top_index
-              << " -> "
-              << new_state.top_index;
-  }
-  segment_state_ = new_state;
-}
-
-bool IndirectReferenceTable::EnsureFreeCapacity(size_t free_capacity, std::string* error_msg) {
-  DCHECK_GE(free_capacity, static_cast<size_t>(1));
-  if (free_capacity > kMaxTableSizeInBytes) {
-    // Arithmetic might even overflow.
-    *error_msg = "Requested table size implausibly large";
-    return false;
-  }
-  size_t top_index = segment_state_.top_index;
-  if (top_index + free_capacity <= max_entries_) {
-    return true;
-  }
-
-  // We're only gonna do a simple best-effort here, ensuring the asked-for capacity at the end.
-  if (resizable_ == ResizableCapacity::kNo) {
-    *error_msg = "Table is not resizable";
-    return false;
-  }
-
-  // Try to increase the table size.
-  if (!Resize(top_index + free_capacity, error_msg)) {
-    LOG(WARNING) << "JNI ERROR: Unable to reserve space in EnsureFreeCapacity (" << free_capacity
-                 << "): " << std::endl
-                 << MutatorLockedDumpable<IndirectReferenceTable>(*this)
-                 << " Resizing failed: " << *error_msg;
-    return false;
-  }
-  return true;
-}
-
 size_t IndirectReferenceTable::FreeCapacity() const {
-  return max_entries_ - segment_state_.top_index;
+  return max_entries_ - top_index_;
 }
 
 }  // namespace art
diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h
index e279422..ded6bcd 100644
--- a/runtime/indirect_reference_table.h
+++ b/runtime/indirect_reference_table.h
@@ -37,75 +37,65 @@
 
 namespace art {
 
+class IsMarkedVisitor;
 class RootInfo;
 
 namespace mirror {
 class Object;
 }  // namespace mirror
 
-// Maintain a table of indirect references.  Used for local/global JNI references.
-//
-// The table contains object references, where the strong (local/global) references are part of the
-// GC root set (but not the weak global references). When an object is added we return an
-// IndirectRef that is not a valid pointer but can be used to find the original value in O(1) time.
-// Conversions to and from indirect references are performed on upcalls and downcalls, so they need
-// to be very fast.
-//
-// To be efficient for JNI local variable storage, we need to provide operations that allow us to
-// operate on segments of the table, where segments are pushed and popped as if on a stack. For
-// example, deletion of an entry should only succeed if it appears in the current segment, and we
-// want to be able to strip off the current segment quickly when a method returns. Additions to the
-// table must be made in the current segment even if space is available in an earlier area.
-//
-// A new segment is created when we call into native code from interpreted code, or when we handle
-// the JNI PushLocalFrame function.
-//
-// The GC must be able to scan the entire table quickly.
-//
-// In summary, these must be very fast:
-//  - adding or removing a segment
-//  - adding references to a new segment
-//  - converting an indirect reference back to an Object
-// These can be a little slower, but must still be pretty quick:
-//  - adding references to a "mature" segment
-//  - removing individual references
-//  - scanning the entire table straight through
-//
-// If there's more than one segment, we don't guarantee that the table will fill completely before
-// we fail due to lack of space. We do ensure that the current segment will pack tightly, which
-// should satisfy JNI requirements (e.g. EnsureLocalCapacity).
-//
-// Only SynchronizedGet is synchronized.
-
 // Indirect reference definition.  This must be interchangeable with JNI's jobject, and it's
 // convenient to let null be null, so we use void*.
 //
-// We need a (potentially) large table index and a 2-bit reference type (global, local, weak
-// global). We also reserve some bits to be used to detect stale indirect references: we put a
-// serial number in the extra bits, and keep a copy of the serial number in the table. This requires
-// more memory and additional memory accesses on add/get, but is moving-GC safe. It will catch
-// additional problems, e.g.: create iref1 for obj, delete iref1, create iref2 for same obj,
-// lookup iref1. A pattern based on object bits will miss this.
+// We need a 2-bit reference kind (global, local, weak global) and the rest of the `IndirectRef`
+// is used to locate the actual reference storage.
+//
+// For global and weak global references, we need a (potentially) large table index and we also
+// reserve some bits to be used to detect stale indirect references: we put a serial number in
+// the extra bits, and keep a copy of the serial number in the table. This requires more memory
+// and additional memory accesses on add/get, but is moving-GC safe. It will catch additional
+// problems, e.g.: create iref1 for obj, delete iref1, create iref2 for same obj, lookup iref1.
+// A pattern based on object bits will miss this.
+//
+// Local references use the same bits for the reference kind but the rest of their `IndirectRef`
+// encoding is different, see `LocalReferenceTable` for details.
 using IndirectRef = void*;
 
 // Indirect reference kind, used as the two low bits of IndirectRef.
 //
-// For convenience these match up with enum jobjectRefType from jni.h.
+// For convenience these match up with enum jobjectRefType from jni.h, except that
+// we use value 0 for JNI transitions instead of marking invalid reference type.
 enum IndirectRefKind {
-  kJniTransitionOrInvalid = 0,  // <<JNI transition frame reference or invalid reference>>
-  kLocal                  = 1,  // <<local reference>>
-  kGlobal                 = 2,  // <<global reference>>
-  kWeakGlobal             = 3,  // <<weak global reference>>
-  kLastKind               = kWeakGlobal
+  kJniTransition = 0,  // <<JNI transition frame reference>>
+  kLocal         = 1,  // <<local reference>>
+  kGlobal        = 2,  // <<global reference>>
+  kWeakGlobal    = 3,  // <<weak global reference>>
+  kLastKind      = kWeakGlobal
 };
 std::ostream& operator<<(std::ostream& os, IndirectRefKind rhs);
-const char* GetIndirectRefKindString(const IndirectRefKind& kind);
+const char* GetIndirectRefKindString(IndirectRefKind kind);
+
+// Maintain a table of indirect references.  Used for global and weak global JNI references.
+//
+// The table contains object references, where the strong global references are part of the
+// GC root set (but not the weak global references). When an object is added we return an
+// `IndirectRef` that is not a valid pointer but can be used to find the original value in O(1)
+// time. Conversions to and from indirect references are performed in JNI functions and when
+// returning from native methods to managed code, so they need to be very fast.
+//
+// The GC must be able to scan the entire table quickly.
+//
+// In summary, these must be very fast:
+//  - adding references
+//  - removing references
+//  - converting an indirect reference back to an Object
+// These can be a little slower, but must still be pretty quick:
+//  - scanning the entire table straight through
 
 // Table definition.
 //
-// For the global reference table, the expected common operations are adding a new entry and
-// removing a recently-added entry (usually the most-recently-added entry).  For JNI local
-// references, the common operations are adding a new entry and removing an entire table segment.
+// For the global reference tables, the expected common operations are adding a new entry and
+// removing a recently-added entry (usually the most-recently-added entry).
 //
 // If we delete entries from the middle of the list, we will be left with "holes".  We track the
 // number of holes so that, when adding new elements, we can quickly decide to do a trivial append
@@ -114,18 +104,6 @@
 // When the top-most entry is removed, any holes immediately below it are also removed. Thus,
 // deletion of an entry may reduce "top_index" by more than one.
 //
-// To get the desired behavior for JNI locals, we need to know the bottom and top of the current
-// "segment". The top is managed internally, and the bottom is passed in as a function argument.
-// When we call a native method or push a local frame, the current top index gets pushed on, and
-// serves as the new bottom. When we pop a frame off, the value from the stack becomes the new top
-// index, and the value stored in the previous frame becomes the new bottom.
-//
-// Holes are being locally cached for the segment. Otherwise we'd have to pass bottom index and
-// number of holes, which restricts us to 16 bits for the top index. The value is cached within the
-// table. To avoid code in generated JNI transitions, which implicitly form segments, the code for
-// adding and removing references needs to detect the change of a segment. Helper fields are used
-// for this detection.
-//
 // Common alternative implementation: make IndirectRef a pointer to the actual reference slot.
 // Instead of getting a table and doing a lookup, the lookup can be done instantly. Operations like
 // determining the type and deleting the reference are more expensive because the table must be
@@ -135,20 +113,7 @@
 // approaches).
 //
 // TODO: consider a "lastDeleteIndex" for quick hole-filling when an add immediately follows a
-// delete; must invalidate after segment pop might be worth only using it for JNI globals.
-//
-// TODO: may want completely different add/remove algorithms for global and local refs to improve
-// performance.  A large circular buffer might reduce the amortized cost of adding global
-// references.
-
-// The state of the current segment. We only store the index. Splitting it for index and hole
-// count restricts the range too much.
-struct IRTSegmentState {
-  uint32_t top_index;
-};
-
-// Use as initial value for "cookie", and when table has only one segment.
-static constexpr IRTSegmentState kIRTFirstSegment = { 0 };
+// delete.
 
 // We associate a few bits of serial number with each reference, for error checking.
 static constexpr unsigned int kIRTSerialBits = 3;
@@ -181,110 +146,22 @@
 static_assert(sizeof(IrtEntry) == 2 * sizeof(uint32_t), "Unexpected sizeof(IrtEntry)");
 static_assert(IsPowerOfTwo(sizeof(IrtEntry)), "Unexpected sizeof(IrtEntry)");
 
-class IrtIterator {
- public:
-  IrtIterator(IrtEntry* table, size_t i, size_t capacity) REQUIRES_SHARED(Locks::mutator_lock_)
-      : table_(table), i_(i), capacity_(capacity) {
-    // capacity_ is used in some target; has warning with unused attribute.
-    UNUSED(capacity_);
-  }
-
-  IrtIterator& operator++() REQUIRES_SHARED(Locks::mutator_lock_) {
-    ++i_;
-    return *this;
-  }
-
-  GcRoot<mirror::Object>* operator*() REQUIRES_SHARED(Locks::mutator_lock_) {
-    // This does not have a read barrier as this is used to visit roots.
-    return table_[i_].GetReference();
-  }
-
-  bool equals(const IrtIterator& rhs) const {
-    return (i_ == rhs.i_ && table_ == rhs.table_);
-  }
-
- private:
-  IrtEntry* const table_;
-  size_t i_;
-  const size_t capacity_;
-};
-
-bool inline operator==(const IrtIterator& lhs, const IrtIterator& rhs) {
-  return lhs.equals(rhs);
-}
-
-bool inline operator!=(const IrtIterator& lhs, const IrtIterator& rhs) {
-  return !lhs.equals(rhs);
-}
-
-// We initially allocate local reference tables with a very small number of entries, packing
-// multiple tables into a single page. If we need to expand one, we allocate them in units of
-// pages.
-// TODO: We should allocate all IRT tables as nonmovable Java objects, That in turn works better
-// if we break up each table into 2 parallel arrays, one for the Java reference, and one for the
-// serial number. The current scheme page-aligns regions containing IRT tables, and so allows them
-// to be identified and page-protected in the future.
-constexpr size_t kInitialIrtBytes = 512;  // Number of bytes in an initial local table.
-constexpr size_t kSmallIrtEntries = kInitialIrtBytes / sizeof(IrtEntry);
-static_assert(kPageSize % kInitialIrtBytes == 0);
-static_assert(kInitialIrtBytes % sizeof(IrtEntry) == 0);
-static_assert(kInitialIrtBytes % sizeof(void *) == 0);
-
-// A minimal stopgap allocator for initial small local IRT tables.
-class SmallIrtAllocator {
- public:
-  SmallIrtAllocator();
-
-  // Allocate an IRT table for kSmallIrtEntries.
-  IrtEntry* Allocate(std::string* error_msg) REQUIRES(!lock_);
-
-  void Deallocate(IrtEntry* unneeded) REQUIRES(!lock_);
-
- private:
-  // A free list of kInitialIrtBytes chunks linked through the first word.
-  IrtEntry* small_irt_freelist_;
-
-  // Repository of MemMaps used for small IRT tables.
-  std::vector<MemMap> shared_irt_maps_;
-
-  Mutex lock_;  // Level kGenericBottomLock; acquired before mem_map_lock_, which is a C++ mutex.
-};
-
 class IndirectReferenceTable {
  public:
-  enum class ResizableCapacity {
-    kNo,
-    kYes
-  };
+  // Constructs an uninitialized indirect reference table. Use `Initialize()` to initialize it.
+  explicit IndirectReferenceTable(IndirectRefKind kind);
 
-  // WARNING: Construction of the IndirectReferenceTable may fail.
-  // error_msg must not be null. If error_msg is set by the constructor, then
-  // construction has failed and the IndirectReferenceTable will be in an
-  // invalid state. Use IsValid to check whether the object is in an invalid
-  // state.
-  // Max_count is the minimum initial capacity (resizable), or minimum total capacity
-  // (not resizable). A value of 1 indicates an implementation-convenient small size.
-  IndirectReferenceTable(size_t max_count,
-                         IndirectRefKind kind,
-                         ResizableCapacity resizable,
-                         std::string* error_msg);
+  // Initialize the indirect reference table.
+  //
+  // Max_count is the requested total capacity (not resizable). The actual total capacity
+  // can be higher to utilize all allocated memory (rounding up to whole pages).
+  bool Initialize(size_t max_count, std::string* error_msg);
 
   ~IndirectReferenceTable();
 
-  /*
-   * Checks whether construction of the IndirectReferenceTable succeeded.
-   *
-   * This object must only be used if IsValid() returns true. It is safe to
-   * call IsValid from multiple threads without locking or other explicit
-   * synchronization.
-   */
-  bool IsValid() const;
-
   // Add a new entry. "obj" must be a valid non-null object reference. This function will
   // return null if an error happened (with an appropriate error message set).
-  IndirectRef Add(IRTSegmentState previous_state,
-                  ObjPtr<mirror::Object> obj,
-                  std::string* error_msg)
+  IndirectRef Add(ObjPtr<mirror::Object> obj, std::string* error_msg)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Given an IndirectRef in the table, return the Object it refers to.
@@ -294,13 +171,6 @@
   ObjPtr<mirror::Object> Get(IndirectRef iref) const REQUIRES_SHARED(Locks::mutator_lock_)
       ALWAYS_INLINE;
 
-  // Synchronized get which reads a reference, acquiring a lock if necessary.
-  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
-  ObjPtr<mirror::Object> SynchronizedGet(IndirectRef iref) const
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    return Get<kReadBarrierOption>(iref);
-  }
-
   // Updates an existing indirect reference to point to a new object.
   void Update(IndirectRef iref, ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -311,9 +181,7 @@
   // required by JNI's DeleteLocalRef function.
   //
   // Returns "false" if nothing was removed.
-  bool Remove(IRTSegmentState previous_state, IndirectRef iref);
-
-  void AssertEmpty() REQUIRES_SHARED(Locks::mutator_lock_);
+  bool Remove(IndirectRef iref);
 
   void Dump(std::ostream& os) const
       REQUIRES_SHARED(Locks::mutator_lock_)
@@ -326,48 +194,22 @@
   // Return the #of entries in the entire table.  This includes holes, and
   // so may be larger than the actual number of "live" entries.
   size_t Capacity() const {
-    return segment_state_.top_index;
+    return top_index_;
   }
 
   // Return the number of non-null entries in the table. Only reliable for a
   // single segment table.
   int32_t NEntriesForGlobal() {
-    return segment_state_.top_index - current_num_holes_;
+    return top_index_ - current_num_holes_;
   }
 
-  // Ensure that at least free_capacity elements are available, or return false.
-  // Caller ensures free_capacity > 0.
-  bool EnsureFreeCapacity(size_t free_capacity, std::string* error_msg)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-  // See implementation of EnsureFreeCapacity. We'll only state here how much is trivially free,
-  // without recovering holes. Thus this is a conservative estimate.
+  // We'll only state here how much is trivially free, without recovering holes.
+  // Thus this is a conservative estimate.
   size_t FreeCapacity() const;
 
-  // Note IrtIterator does not have a read barrier as it's used to visit roots.
-  IrtIterator begin() {
-    return IrtIterator(table_, 0, Capacity());
-  }
-
-  IrtIterator end() {
-    return IrtIterator(table_, Capacity(), Capacity());
-  }
-
   void VisitRoots(RootVisitor* visitor, const RootInfo& root_info)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  IRTSegmentState GetSegmentState() const {
-    return segment_state_;
-  }
-
-  void SetSegmentState(IRTSegmentState new_state);
-
-  static Offset SegmentStateOffset(size_t pointer_size ATTRIBUTE_UNUSED) {
-    // Note: Currently segment_state_ is at offset 0. We're testing the expected value in
-    //       jni_internal_test to make sure it stays correct. It is not OFFSETOF_MEMBER, as that
-    //       is not pointer-size-safe.
-    return Offset(0);
-  }
-
   // Release pages past the end of the table that may have previously held references.
   void Trim() REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -376,10 +218,43 @@
     return DecodeIndirectRefKind(reinterpret_cast<uintptr_t>(iref));
   }
 
+  static constexpr uintptr_t GetGlobalOrWeakGlobalMask() {
+    constexpr uintptr_t mask = enum_cast<uintptr_t>(kGlobal);
+    static_assert(IsPowerOfTwo(mask));
+    static_assert((mask & kJniTransition) == 0u);
+    static_assert((mask & kLocal) == 0u);
+    static_assert((mask & kGlobal) != 0u);
+    static_assert((mask & kWeakGlobal) != 0u);
+    return mask;
+  }
+
+  static bool IsGlobalOrWeakGlobalReference(IndirectRef iref) {
+    return (reinterpret_cast<uintptr_t>(iref) & GetGlobalOrWeakGlobalMask()) != 0u;
+  }
+
+  static bool IsJniTransitionOrLocalReference(IndirectRef iref) {
+    return !IsGlobalOrWeakGlobalReference(iref);
+  }
+
+  template <typename T>
+  static T ClearIndirectRefKind(IndirectRef iref) {
+    static_assert(std::is_pointer_v<T>);
+    return reinterpret_cast<T>(
+        reinterpret_cast<uintptr_t>(iref) & ~static_cast<uintptr_t>(kKindMask));
+  }
+
+  static constexpr uintptr_t GetIndirectRefKindMask() {
+    return kKindMask;
+  }
+
   /* Reference validation for CheckJNI. */
   bool IsValidReference(IndirectRef, /*out*/std::string* error_msg) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void SweepJniWeakGlobals(IsMarkedVisitor* visitor)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::jni_weak_globals_lock_);
+
  private:
   static constexpr uint32_t kShiftedSerialMask = (1u << kIRTSerialBits) - 1;
 
@@ -429,42 +304,30 @@
     return reinterpret_cast<IndirectRef>(EncodeIndirectRef(table_index, serial));
   }
 
-  // Resize the backing table to be at least new_size elements long. Currently
-  // must be larger than the current size. After return max_entries_ >= new_size.
-  bool Resize(size_t new_size, std::string* error_msg);
-
-  void RecoverHoles(IRTSegmentState from);
-
   // Abort if check_jni is not enabled. Otherwise, just log as an error.
   static void AbortIfNoCheckJNI(const std::string& msg);
 
   /* extra debugging checks */
   bool CheckEntry(const char*, IndirectRef, uint32_t) const;
 
-  /// semi-public - read/write by jni down calls.
-  IRTSegmentState segment_state_;
-
-  // Mem map where we store the indirect refs. If it's invalid, and table_ is non-null, then
-  // table_ is valid, but was allocated via allocSmallIRT();
+  // Mem map where we store the indirect refs.
   MemMap table_mem_map_;
-  // bottom of the stack. Do not directly access the object references
+  // Bottom of the stack. Do not directly access the object references
   // in this as they are roots. Use Get() that has a read barrier.
   IrtEntry* table_;
-  // bit mask, ORed into all irefs.
+  // Bit mask, ORed into all irefs.
   const IndirectRefKind kind_;
 
-  // max #of entries allowed (modulo resizing).
+  // The "top of stack" index where new references are added.
+  size_t top_index_;
+
+  // Maximum number of entries allowed.
   size_t max_entries_;
 
-  // Some values to retain old behavior with holes. Description of the algorithm is in the .cc
-  // file.
+  // Some values to retain old behavior with holes.
+  // Description of the algorithm is in the .cc file.
   // TODO: Consider other data structures for compact tables, e.g., free lists.
   size_t current_num_holes_;  // Number of holes in the current / top segment.
-  IRTSegmentState last_known_previous_state_;
-
-  // Whether the table's capacity may be resized. As there are no locks used, it is the caller's
-  // responsibility to ensure thread-safety.
-  ResizableCapacity resizable_;
 };
 
 }  // namespace art
diff --git a/runtime/indirect_reference_table_test.cc b/runtime/indirect_reference_table_test.cc
index 5da7a30..ac22f3f 100644
--- a/runtime/indirect_reference_table_test.cc
+++ b/runtime/indirect_reference_table_test.cc
@@ -28,7 +28,12 @@
 
 using android::base::StringPrintf;
 
-class IndirectReferenceTableTest : public CommonRuntimeTest {};
+class IndirectReferenceTableTest : public CommonRuntimeTest {
+ protected:
+  IndirectReferenceTableTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+};
 
 static void CheckDump(IndirectReferenceTable* irt, size_t num_objects, size_t num_unique)
     REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -54,12 +59,10 @@
 
   ScopedObjectAccess soa(Thread::Current());
   static const size_t kTableMax = 20;
+  IndirectReferenceTable irt(kGlobal);
   std::string error_msg;
-  IndirectReferenceTable irt(kTableMax,
-                             kGlobal,
-                             IndirectReferenceTable::ResizableCapacity::kNo,
-                             &error_msg);
-  ASSERT_TRUE(irt.IsValid()) << error_msg;
+  bool success = irt.Initialize(kTableMax, &error_msg);
+  ASSERT_TRUE(success) << error_msg;
 
   StackHandleScope<5> hs(soa.Self());
   Handle<mirror::Class> c =
@@ -74,21 +77,19 @@
   Handle<mirror::Object> obj3 = hs.NewHandle(c->AllocObject(soa.Self()));
   ASSERT_TRUE(obj3 != nullptr);
 
-  const IRTSegmentState cookie = kIRTFirstSegment;
-
   CheckDump(&irt, 0, 0);
 
   IndirectRef iref0 = (IndirectRef) 0x11110;
-  EXPECT_FALSE(irt.Remove(cookie, iref0)) << "unexpectedly successful removal";
+  EXPECT_FALSE(irt.Remove(iref0)) << "unexpectedly successful removal";
 
   // Add three, check, remove in the order in which they were added.
-  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
+  iref0 = irt.Add(obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
   CheckDump(&irt, 1, 1);
-  IndirectRef iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
+  IndirectRef iref1 = irt.Add(obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
   CheckDump(&irt, 2, 2);
-  IndirectRef iref2 = irt.Add(cookie, obj2.Get(), &error_msg);
+  IndirectRef iref2 = irt.Add(obj2.Get(), &error_msg);
   EXPECT_TRUE(iref2 != nullptr);
   CheckDump(&irt, 3, 3);
 
@@ -96,11 +97,11 @@
   EXPECT_OBJ_PTR_EQ(obj1.Get(), irt.Get(iref1));
   EXPECT_OBJ_PTR_EQ(obj2.Get(), irt.Get(iref2));
 
-  EXPECT_TRUE(irt.Remove(cookie, iref0));
+  EXPECT_TRUE(irt.Remove(iref0));
   CheckDump(&irt, 2, 2);
-  EXPECT_TRUE(irt.Remove(cookie, iref1));
+  EXPECT_TRUE(irt.Remove(iref1));
   CheckDump(&irt, 1, 1);
-  EXPECT_TRUE(irt.Remove(cookie, iref2));
+  EXPECT_TRUE(irt.Remove(iref2));
   CheckDump(&irt, 0, 0);
 
   // Table should be empty now.
@@ -111,19 +112,19 @@
   EXPECT_FALSE(irt.IsValidReference(iref0, &error_msg));
 
   // Add three, remove in the opposite order.
-  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
+  iref0 = irt.Add(obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
-  iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
+  iref1 = irt.Add(obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
-  iref2 = irt.Add(cookie, obj2.Get(), &error_msg);
+  iref2 = irt.Add(obj2.Get(), &error_msg);
   EXPECT_TRUE(iref2 != nullptr);
   CheckDump(&irt, 3, 3);
 
-  ASSERT_TRUE(irt.Remove(cookie, iref2));
+  ASSERT_TRUE(irt.Remove(iref2));
   CheckDump(&irt, 2, 2);
-  ASSERT_TRUE(irt.Remove(cookie, iref1));
+  ASSERT_TRUE(irt.Remove(iref1));
   CheckDump(&irt, 1, 1);
-  ASSERT_TRUE(irt.Remove(cookie, iref0));
+  ASSERT_TRUE(irt.Remove(iref0));
   CheckDump(&irt, 0, 0);
 
   // Table should be empty now.
@@ -131,27 +132,27 @@
 
   // Add three, remove middle / middle / bottom / top.  (Second attempt
   // to remove middle should fail.)
-  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
+  iref0 = irt.Add(obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
-  iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
+  iref1 = irt.Add(obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
-  iref2 = irt.Add(cookie, obj2.Get(), &error_msg);
+  iref2 = irt.Add(obj2.Get(), &error_msg);
   EXPECT_TRUE(iref2 != nullptr);
   CheckDump(&irt, 3, 3);
 
   ASSERT_EQ(3U, irt.Capacity());
 
-  ASSERT_TRUE(irt.Remove(cookie, iref1));
+  ASSERT_TRUE(irt.Remove(iref1));
   CheckDump(&irt, 2, 2);
-  ASSERT_FALSE(irt.Remove(cookie, iref1));
+  ASSERT_FALSE(irt.Remove(iref1));
   CheckDump(&irt, 2, 2);
 
   // Check that the reference to the hole is not valid.
   EXPECT_FALSE(irt.IsValidReference(iref1, &error_msg));
 
-  ASSERT_TRUE(irt.Remove(cookie, iref2));
+  ASSERT_TRUE(irt.Remove(iref2));
   CheckDump(&irt, 1, 1);
-  ASSERT_TRUE(irt.Remove(cookie, iref0));
+  ASSERT_TRUE(irt.Remove(iref0));
   CheckDump(&irt, 0, 0);
 
   // Table should be empty now.
@@ -160,35 +161,35 @@
   // Add four entries.  Remove #1, add new entry, verify that table size
   // is still 4 (i.e. holes are getting filled).  Remove #1 and #3, verify
   // that we delete one and don't hole-compact the other.
-  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
+  iref0 = irt.Add(obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
-  iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
+  iref1 = irt.Add(obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
-  iref2 = irt.Add(cookie, obj2.Get(), &error_msg);
+  iref2 = irt.Add(obj2.Get(), &error_msg);
   EXPECT_TRUE(iref2 != nullptr);
-  IndirectRef iref3 = irt.Add(cookie, obj3.Get(), &error_msg);
+  IndirectRef iref3 = irt.Add(obj3.Get(), &error_msg);
   EXPECT_TRUE(iref3 != nullptr);
   CheckDump(&irt, 4, 4);
 
-  ASSERT_TRUE(irt.Remove(cookie, iref1));
+  ASSERT_TRUE(irt.Remove(iref1));
   CheckDump(&irt, 3, 3);
 
-  iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
+  iref1 = irt.Add(obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
 
   ASSERT_EQ(4U, irt.Capacity()) << "hole not filled";
   CheckDump(&irt, 4, 4);
 
-  ASSERT_TRUE(irt.Remove(cookie, iref1));
+  ASSERT_TRUE(irt.Remove(iref1));
   CheckDump(&irt, 3, 3);
-  ASSERT_TRUE(irt.Remove(cookie, iref3));
+  ASSERT_TRUE(irt.Remove(iref3));
   CheckDump(&irt, 2, 2);
 
   ASSERT_EQ(3U, irt.Capacity()) << "should be 3 after two deletions";
 
-  ASSERT_TRUE(irt.Remove(cookie, iref2));
+  ASSERT_TRUE(irt.Remove(iref2));
   CheckDump(&irt, 1, 1);
-  ASSERT_TRUE(irt.Remove(cookie, iref0));
+  ASSERT_TRUE(irt.Remove(iref0));
   CheckDump(&irt, 0, 0);
 
   ASSERT_EQ(0U, irt.Capacity()) << "not empty after split remove";
@@ -196,320 +197,72 @@
   // Add an entry, remove it, add a new entry, and try to use the original
   // iref.  They have the same slot number but are for different objects.
   // With the extended checks in place, this should fail.
-  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
+  iref0 = irt.Add(obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
   CheckDump(&irt, 1, 1);
-  ASSERT_TRUE(irt.Remove(cookie, iref0));
+  ASSERT_TRUE(irt.Remove(iref0));
   CheckDump(&irt, 0, 0);
-  iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
+  iref1 = irt.Add(obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
   CheckDump(&irt, 1, 1);
-  ASSERT_FALSE(irt.Remove(cookie, iref0)) << "mismatched del succeeded";
+  ASSERT_FALSE(irt.Remove(iref0)) << "mismatched del succeeded";
   CheckDump(&irt, 1, 1);
-  ASSERT_TRUE(irt.Remove(cookie, iref1)) << "switched del failed";
+  ASSERT_TRUE(irt.Remove(iref1)) << "switched del failed";
   ASSERT_EQ(0U, irt.Capacity()) << "switching del not empty";
   CheckDump(&irt, 0, 0);
 
   // Same as above, but with the same object.  A more rigorous checker
   // (e.g. with slot serialization) will catch this.
-  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
+  iref0 = irt.Add(obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
   CheckDump(&irt, 1, 1);
-  ASSERT_TRUE(irt.Remove(cookie, iref0));
+  ASSERT_TRUE(irt.Remove(iref0));
   CheckDump(&irt, 0, 0);
-  iref1 = irt.Add(cookie, obj0.Get(), &error_msg);
+  iref1 = irt.Add(obj0.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
   CheckDump(&irt, 1, 1);
   if (iref0 != iref1) {
     // Try 0, should not work.
-    ASSERT_FALSE(irt.Remove(cookie, iref0)) << "temporal del succeeded";
+    ASSERT_FALSE(irt.Remove(iref0)) << "temporal del succeeded";
   }
-  ASSERT_TRUE(irt.Remove(cookie, iref1)) << "temporal cleanup failed";
+  ASSERT_TRUE(irt.Remove(iref1)) << "temporal cleanup failed";
   ASSERT_EQ(0U, irt.Capacity()) << "temporal del not empty";
   CheckDump(&irt, 0, 0);
 
   // Stale reference is not valid.
-  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
+  iref0 = irt.Add(obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
   CheckDump(&irt, 1, 1);
-  ASSERT_TRUE(irt.Remove(cookie, iref0));
+  ASSERT_TRUE(irt.Remove(iref0));
   EXPECT_FALSE(irt.IsValidReference(iref0, &error_msg)) << "stale lookup succeeded";
   CheckDump(&irt, 0, 0);
 
-  // Test table resizing.
-  // These ones fit...
+  // Test deleting all but the last entry.
+  // We shall delete these.
   static const size_t kTableInitial = kTableMax / 2;
   IndirectRef manyRefs[kTableInitial];
   for (size_t i = 0; i < kTableInitial; i++) {
-    manyRefs[i] = irt.Add(cookie, obj0.Get(), &error_msg);
+    manyRefs[i] = irt.Add(obj0.Get(), &error_msg);
     ASSERT_TRUE(manyRefs[i] != nullptr) << "Failed adding " << i;
     CheckDump(&irt, i + 1, 1);
   }
-  // ...this one causes overflow.
-  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
+  // We shall keep this one.
+  iref0 = irt.Add(obj0.Get(), &error_msg);
   ASSERT_TRUE(iref0 != nullptr);
   ASSERT_EQ(kTableInitial + 1, irt.Capacity());
   CheckDump(&irt, kTableInitial + 1, 1);
-
+  // Delete all but the last entry.
   for (size_t i = 0; i < kTableInitial; i++) {
-    ASSERT_TRUE(irt.Remove(cookie, manyRefs[i])) << "failed removing " << i;
+    ASSERT_TRUE(irt.Remove(manyRefs[i])) << "failed removing " << i;
     CheckDump(&irt, kTableInitial - i, 1);
   }
   // Because of removal order, should have 11 entries, 10 of them holes.
   ASSERT_EQ(kTableInitial + 1, irt.Capacity());
 
-  ASSERT_TRUE(irt.Remove(cookie, iref0)) << "multi-remove final failed";
+  ASSERT_TRUE(irt.Remove(iref0)) << "multi-remove final failed";
 
   ASSERT_EQ(0U, irt.Capacity()) << "multi-del not empty";
   CheckDump(&irt, 0, 0);
 }
 
-TEST_F(IndirectReferenceTableTest, Holes) {
-  // Test the explicitly named cases from the IRT implementation:
-  //
-  // 1) Segment with holes (current_num_holes_ > 0), push new segment, add/remove reference
-  // 2) Segment with holes (current_num_holes_ > 0), pop segment, add/remove reference
-  // 3) Segment with holes (current_num_holes_ > 0), push new segment, pop segment, add/remove
-  //    reference
-  // 4) Empty segment, push new segment, create a hole, pop a segment, add/remove a reference
-  // 5) Base segment, push new segment, create a hole, pop a segment, push new segment, add/remove
-  //    reference
-
-  ScopedObjectAccess soa(Thread::Current());
-  static const size_t kTableMax = 10;
-
-  StackHandleScope<6> hs(soa.Self());
-  Handle<mirror::Class> c = hs.NewHandle(
-      class_linker_->FindSystemClass(soa.Self(), "Ljava/lang/Object;"));
-  ASSERT_TRUE(c != nullptr);
-  Handle<mirror::Object> obj0 = hs.NewHandle(c->AllocObject(soa.Self()));
-  ASSERT_TRUE(obj0 != nullptr);
-  Handle<mirror::Object> obj1 = hs.NewHandle(c->AllocObject(soa.Self()));
-  ASSERT_TRUE(obj1 != nullptr);
-  Handle<mirror::Object> obj2 = hs.NewHandle(c->AllocObject(soa.Self()));
-  ASSERT_TRUE(obj2 != nullptr);
-  Handle<mirror::Object> obj3 = hs.NewHandle(c->AllocObject(soa.Self()));
-  ASSERT_TRUE(obj3 != nullptr);
-  Handle<mirror::Object> obj4 = hs.NewHandle(c->AllocObject(soa.Self()));
-  ASSERT_TRUE(obj4 != nullptr);
-
-  std::string error_msg;
-
-  // 1) Segment with holes (current_num_holes_ > 0), push new segment, add/remove reference.
-  {
-    IndirectReferenceTable irt(kTableMax,
-                               kGlobal,
-                               IndirectReferenceTable::ResizableCapacity::kNo,
-                               &error_msg);
-    ASSERT_TRUE(irt.IsValid()) << error_msg;
-
-    const IRTSegmentState cookie0 = kIRTFirstSegment;
-
-    CheckDump(&irt, 0, 0);
-
-    IndirectRef iref0 = irt.Add(cookie0, obj0.Get(), &error_msg);
-    IndirectRef iref1 = irt.Add(cookie0, obj1.Get(), &error_msg);
-    IndirectRef iref2 = irt.Add(cookie0, obj2.Get(), &error_msg);
-
-    EXPECT_TRUE(irt.Remove(cookie0, iref1));
-
-    // New segment.
-    const IRTSegmentState cookie1 = irt.GetSegmentState();
-
-    IndirectRef iref3 = irt.Add(cookie1, obj3.Get(), &error_msg);
-
-    // Must not have filled the previous hole.
-    EXPECT_EQ(irt.Capacity(), 4u);
-    EXPECT_FALSE(irt.IsValidReference(iref1, &error_msg));
-    CheckDump(&irt, 3, 3);
-
-    UNUSED(iref0, iref1, iref2, iref3);
-  }
-
-  // 2) Segment with holes (current_num_holes_ > 0), pop segment, add/remove reference
-  {
-    IndirectReferenceTable irt(kTableMax,
-                               kGlobal,
-                               IndirectReferenceTable::ResizableCapacity::kNo,
-                               &error_msg);
-    ASSERT_TRUE(irt.IsValid()) << error_msg;
-
-    const IRTSegmentState cookie0 = kIRTFirstSegment;
-
-    CheckDump(&irt, 0, 0);
-
-    IndirectRef iref0 = irt.Add(cookie0, obj0.Get(), &error_msg);
-
-    // New segment.
-    const IRTSegmentState cookie1 = irt.GetSegmentState();
-
-    IndirectRef iref1 = irt.Add(cookie1, obj1.Get(), &error_msg);
-    IndirectRef iref2 = irt.Add(cookie1, obj2.Get(), &error_msg);
-    IndirectRef iref3 = irt.Add(cookie1, obj3.Get(), &error_msg);
-
-    EXPECT_TRUE(irt.Remove(cookie1, iref2));
-
-    // Pop segment.
-    irt.SetSegmentState(cookie1);
-
-    IndirectRef iref4 = irt.Add(cookie1, obj4.Get(), &error_msg);
-
-    EXPECT_EQ(irt.Capacity(), 2u);
-    EXPECT_FALSE(irt.IsValidReference(iref2, &error_msg));
-    CheckDump(&irt, 2, 2);
-
-    UNUSED(iref0, iref1, iref2, iref3, iref4);
-  }
-
-  // 3) Segment with holes (current_num_holes_ > 0), push new segment, pop segment, add/remove
-  //    reference.
-  {
-    IndirectReferenceTable irt(kTableMax,
-                               kGlobal,
-                               IndirectReferenceTable::ResizableCapacity::kNo,
-                               &error_msg);
-    ASSERT_TRUE(irt.IsValid()) << error_msg;
-
-    const IRTSegmentState cookie0 = kIRTFirstSegment;
-
-    CheckDump(&irt, 0, 0);
-
-    IndirectRef iref0 = irt.Add(cookie0, obj0.Get(), &error_msg);
-
-    // New segment.
-    const IRTSegmentState cookie1 = irt.GetSegmentState();
-
-    IndirectRef iref1 = irt.Add(cookie1, obj1.Get(), &error_msg);
-    IndirectRef iref2 = irt.Add(cookie1, obj2.Get(), &error_msg);
-
-    EXPECT_TRUE(irt.Remove(cookie1, iref1));
-
-    // New segment.
-    const IRTSegmentState cookie2 = irt.GetSegmentState();
-
-    IndirectRef iref3 = irt.Add(cookie2, obj3.Get(), &error_msg);
-
-    // Pop segment.
-    irt.SetSegmentState(cookie2);
-
-    IndirectRef iref4 = irt.Add(cookie1, obj4.Get(), &error_msg);
-
-    EXPECT_EQ(irt.Capacity(), 3u);
-    EXPECT_FALSE(irt.IsValidReference(iref1, &error_msg));
-    CheckDump(&irt, 3, 3);
-
-    UNUSED(iref0, iref1, iref2, iref3, iref4);
-  }
-
-  // 4) Empty segment, push new segment, create a hole, pop a segment, add/remove a reference.
-  {
-    IndirectReferenceTable irt(kTableMax,
-                               kGlobal,
-                               IndirectReferenceTable::ResizableCapacity::kNo,
-                               &error_msg);
-    ASSERT_TRUE(irt.IsValid()) << error_msg;
-
-    const IRTSegmentState cookie0 = kIRTFirstSegment;
-
-    CheckDump(&irt, 0, 0);
-
-    IndirectRef iref0 = irt.Add(cookie0, obj0.Get(), &error_msg);
-
-    // New segment.
-    const IRTSegmentState cookie1 = irt.GetSegmentState();
-
-    IndirectRef iref1 = irt.Add(cookie1, obj1.Get(), &error_msg);
-    EXPECT_TRUE(irt.Remove(cookie1, iref1));
-
-    // Emptied segment, push new one.
-    const IRTSegmentState cookie2 = irt.GetSegmentState();
-
-    IndirectRef iref2 = irt.Add(cookie1, obj1.Get(), &error_msg);
-    IndirectRef iref3 = irt.Add(cookie1, obj2.Get(), &error_msg);
-    IndirectRef iref4 = irt.Add(cookie1, obj3.Get(), &error_msg);
-
-    EXPECT_TRUE(irt.Remove(cookie1, iref3));
-
-    // Pop segment.
-    UNUSED(cookie2);
-    irt.SetSegmentState(cookie1);
-
-    IndirectRef iref5 = irt.Add(cookie1, obj4.Get(), &error_msg);
-
-    EXPECT_EQ(irt.Capacity(), 2u);
-    EXPECT_FALSE(irt.IsValidReference(iref3, &error_msg));
-    CheckDump(&irt, 2, 2);
-
-    UNUSED(iref0, iref1, iref2, iref3, iref4, iref5);
-  }
-
-  // 5) Base segment, push new segment, create a hole, pop a segment, push new segment, add/remove
-  //    reference
-  {
-    IndirectReferenceTable irt(kTableMax,
-                               kGlobal,
-                               IndirectReferenceTable::ResizableCapacity::kNo,
-                               &error_msg);
-    ASSERT_TRUE(irt.IsValid()) << error_msg;
-
-    const IRTSegmentState cookie0 = kIRTFirstSegment;
-
-    CheckDump(&irt, 0, 0);
-
-    IndirectRef iref0 = irt.Add(cookie0, obj0.Get(), &error_msg);
-
-    // New segment.
-    const IRTSegmentState cookie1 = irt.GetSegmentState();
-
-    IndirectRef iref1 = irt.Add(cookie1, obj1.Get(), &error_msg);
-    IndirectRef iref2 = irt.Add(cookie1, obj1.Get(), &error_msg);
-    IndirectRef iref3 = irt.Add(cookie1, obj2.Get(), &error_msg);
-
-    EXPECT_TRUE(irt.Remove(cookie1, iref2));
-
-    // Pop segment.
-    irt.SetSegmentState(cookie1);
-
-    // Push segment.
-    const IRTSegmentState cookie1_second = irt.GetSegmentState();
-    UNUSED(cookie1_second);
-
-    IndirectRef iref4 = irt.Add(cookie1, obj3.Get(), &error_msg);
-
-    EXPECT_EQ(irt.Capacity(), 2u);
-    EXPECT_FALSE(irt.IsValidReference(iref3, &error_msg));
-    CheckDump(&irt, 2, 2);
-
-    UNUSED(iref0, iref1, iref2, iref3, iref4);
-  }
-}
-
-TEST_F(IndirectReferenceTableTest, Resize) {
-  ScopedObjectAccess soa(Thread::Current());
-  static const size_t kTableMax = 512;
-
-  StackHandleScope<2> hs(soa.Self());
-  Handle<mirror::Class> c = hs.NewHandle(
-      class_linker_->FindSystemClass(soa.Self(), "Ljava/lang/Object;"));
-  ASSERT_TRUE(c != nullptr);
-  Handle<mirror::Object> obj0 = hs.NewHandle(c->AllocObject(soa.Self()));
-  ASSERT_TRUE(obj0 != nullptr);
-
-  std::string error_msg;
-  IndirectReferenceTable irt(kTableMax,
-                             kLocal,
-                             IndirectReferenceTable::ResizableCapacity::kYes,
-                             &error_msg);
-  ASSERT_TRUE(irt.IsValid()) << error_msg;
-
-  CheckDump(&irt, 0, 0);
-  const IRTSegmentState cookie = kIRTFirstSegment;
-
-  for (size_t i = 0; i != kTableMax + 1; ++i) {
-    irt.Add(cookie, obj0.Get(), &error_msg);
-  }
-
-  EXPECT_EQ(irt.Capacity(), kTableMax + 1);
-}
-
 }  // namespace art
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 6ec98ff..e4be577 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -55,6 +55,9 @@
 #include "thread_list.h"
 
 namespace art {
+extern "C" NO_RETURN void artDeoptimize(Thread* self, bool skip_method_exit_callbacks);
+extern "C" NO_RETURN void artDeliverPendingExceptionFromCode(Thread* self);
+
 namespace instrumentation {
 
 constexpr bool kVerboseInstrumentation = false;
@@ -104,72 +107,8 @@
   Instrumentation* const instrumentation_;
 };
 
-InstrumentationStackPopper::InstrumentationStackPopper(Thread* self)
-      : self_(self),
-        instrumentation_(Runtime::Current()->GetInstrumentation()),
-        pop_until_(0u) {}
-
-InstrumentationStackPopper::~InstrumentationStackPopper() {
-  std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
-      self_->GetInstrumentationStack();
-  for (auto i = stack->begin(); i != stack->end() && i->first <= pop_until_;) {
-    i = stack->erase(i);
-  }
-}
-
-bool InstrumentationStackPopper::PopFramesTo(uintptr_t stack_pointer,
-                                             MutableHandle<mirror::Throwable>& exception) {
-  std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
-      self_->GetInstrumentationStack();
-  DCHECK(!self_->IsExceptionPending());
-  if (!instrumentation_->HasMethodUnwindListeners()) {
-    pop_until_ = stack_pointer;
-    return true;
-  }
-  if (kVerboseInstrumentation) {
-    LOG(INFO) << "Popping frames for exception " << exception->Dump();
-  }
-  // The instrumentation events expect the exception to be set.
-  self_->SetException(exception.Get());
-  bool new_exception_thrown = false;
-  auto i = stack->upper_bound(pop_until_);
-
-  // Now pop all frames until reaching stack_pointer, or a new exception is
-  // thrown. Note that `stack_pointer` doesn't need to be a return PC address
-  // (in fact the exception handling code passes the start of the frame where
-  // the catch handler is).
-  for (; i != stack->end() && i->first <= stack_pointer; i++) {
-    const InstrumentationStackFrame& frame = i->second;
-    ArtMethod* method = frame.method_;
-    // Notify listeners of method unwind.
-    // TODO: improve the dex_pc information here.
-    uint32_t dex_pc = dex::kDexNoIndex;
-    if (kVerboseInstrumentation) {
-      LOG(INFO) << "Popping for unwind " << method->PrettyMethod();
-    }
-    if (!method->IsRuntimeMethod() && !frame.interpreter_entry_) {
-      instrumentation_->MethodUnwindEvent(self_, frame.this_object_, method, dex_pc);
-      new_exception_thrown = self_->GetException() != exception.Get();
-      if (new_exception_thrown) {
-        pop_until_ = i->first;
-        break;
-      }
-    }
-  }
-  if (!new_exception_thrown) {
-    pop_until_ = stack_pointer;
-  }
-  exception.Assign(self_->GetException());
-  self_->ClearException();
-  if (kVerboseInstrumentation && new_exception_thrown) {
-    LOG(INFO) << "Did partial pop of frames due to new exception";
-  }
-  return !new_exception_thrown;
-}
-
 Instrumentation::Instrumentation()
-    : current_force_deopt_id_(0),
-      instrumentation_stubs_installed_(false),
+    : run_exit_hooks_(false),
       instrumentation_level_(InstrumentationLevel::kInstrumentNothing),
       forced_interpret_only_(false),
       have_method_entry_listeners_(false),
@@ -182,10 +121,51 @@
       have_watched_frame_pop_listeners_(false),
       have_branch_listeners_(false),
       have_exception_handled_listeners_(false),
-      deoptimized_methods_lock_(new ReaderWriterMutex("deoptimized methods lock",
-                                                      kGenericBottomLock)),
       quick_alloc_entry_points_instrumentation_counter_(0),
-      alloc_entrypoints_instrumented_(false) {
+      alloc_entrypoints_instrumented_(false) {}
+
+bool Instrumentation::ProcessMethodUnwindCallbacks(Thread* self,
+                                                   std::queue<ArtMethod*>& methods,
+                                                   MutableHandle<mirror::Throwable>& exception) {
+  DCHECK(!self->IsExceptionPending());
+  if (!HasMethodUnwindListeners()) {
+    return true;
+  }
+  if (kVerboseInstrumentation) {
+    LOG(INFO) << "Popping frames for exception " << exception->Dump();
+  }
+  // The instrumentation events expect the exception to be set.
+  self->SetException(exception.Get());
+  bool new_exception_thrown = false;
+
+  // Process callbacks for all methods that would be unwound until a new exception is thrown.
+  while (!methods.empty()) {
+    ArtMethod* method = methods.front();
+    methods.pop();
+    if (kVerboseInstrumentation) {
+      LOG(INFO) << "Popping for unwind " << method->PrettyMethod();
+    }
+
+    if (method->IsRuntimeMethod()) {
+      continue;
+    }
+
+    // Notify listeners of method unwind.
+    // TODO: improve the dex_pc information here.
+    uint32_t dex_pc = dex::kDexNoIndex;
+    MethodUnwindEvent(self, method, dex_pc);
+    new_exception_thrown = self->GetException() != exception.Get();
+    if (new_exception_thrown) {
+      break;
+    }
+  }
+
+  exception.Assign(self->GetException());
+  self->ClearException();
+  if (kVerboseInstrumentation && new_exception_thrown) {
+    LOG(INFO) << "Did partial pop of frames due to new exception";
+  }
+  return !new_exception_thrown;
 }
 
 void Instrumentation::InstallStubsForClass(ObjPtr<mirror::Class> klass) {
@@ -206,15 +186,14 @@
   return class_linker->IsQuickResolutionStub(code) ||
          class_linker->IsQuickToInterpreterBridge(code) ||
          class_linker->IsQuickGenericJniStub(code) ||
-         (code == GetQuickInstrumentationEntryPoint());
+         (code == interpreter::GetNterpWithClinitEntryPoint());
 }
 
 static bool IsProxyInit(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
   // Annoyingly this can be called before we have actually initialized WellKnownClasses so therefore
   // we also need to check this based on the declaring-class descriptor. The check is valid because
   // Proxy only has a single constructor.
-  ArtMethod* well_known_proxy_init = jni::DecodeArtMethod(
-      WellKnownClasses::java_lang_reflect_Proxy_init);
+  ArtMethod* well_known_proxy_init = WellKnownClasses::java_lang_reflect_Proxy_init;
   if (well_known_proxy_init == method) {
     return true;
   }
@@ -227,11 +206,53 @@
       method->GetDeclaringClass()->DescriptorEquals("Ljava/lang/reflect/Proxy;");
 }
 
+// Returns true if we need entry exit stub to call entry hooks. JITed code
+// directly call entry / exit hooks and don't need the stub.
+static bool CodeSupportsEntryExitHooks(const void* entry_point, ArtMethod* method)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  // Proxy.init should always run with the switch interpreter where entry / exit hooks are
+  // supported.
+  if (IsProxyInit(method)) {
+    return true;
+  }
+
+  // In some tests runtime isn't setup fully and hence the entry points could be nullptr.
+  // just be conservative and return false here.
+  if (entry_point == nullptr) {
+    return false;
+  }
+
+  ClassLinker* linker = Runtime::Current()->GetClassLinker();
+  // Interpreter supports entry / exit hooks. Resolution stubs fetch code that supports entry / exit
+  // hooks when required. So return true for both cases.
+  if (linker->IsQuickToInterpreterBridge(entry_point) ||
+      linker->IsQuickResolutionStub(entry_point)) {
+    return true;
+  }
+
+  // When jiting code for debuggable runtimes / instrumentation is active  we generate the code to
+  // call method entry / exit hooks when required.
+  jit::Jit* jit = Runtime::Current()->GetJit();
+  if (jit != nullptr && jit->GetCodeCache()->ContainsPc(entry_point)) {
+    // If JITed code was compiled with instrumentation support we support entry / exit hooks.
+    OatQuickMethodHeader* header = OatQuickMethodHeader::FromEntryPoint(entry_point);
+    return CodeInfo::IsDebuggable(header->GetOptimizedCodeInfoPtr());
+  }
+
+  // GenericJni trampoline can handle entry / exit hooks.
+  if (linker->IsQuickGenericJniStub(entry_point)) {
+    return true;
+  }
+
+  // The remaining cases are nterp / oat code / JIT code that isn't compiled with instrumentation
+  // support.
+  return false;
+}
+
 static void UpdateEntryPoints(ArtMethod* method, const void* quick_code)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   if (kIsDebugBuild) {
-    if (NeedsClinitCheckBeforeCall(method) &&
-        !method->GetDeclaringClass()->IsVisiblyInitialized()) {
+    if (method->StillNeedsClinitCheckMayBeDead()) {
       CHECK(CanHandleInitializationCheck(quick_code));
     }
     jit::Jit* jit = Runtime::Current()->GetJit();
@@ -241,8 +262,9 @@
         CHECK_EQ(reinterpret_cast<uintptr_t>(quick_code) & 1, 1u);
       }
     }
-    if (IsProxyInit(method)) {
-      CHECK_NE(quick_code, GetQuickInstrumentationEntryPoint());
+    const Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+    if (instr->EntryExitStubsInstalled()) {
+      DCHECK(CodeSupportsEntryExitHooks(quick_code, method));
     }
   }
   // If the method is from a boot image, don't dirty it if the entrypoint
@@ -252,64 +274,22 @@
   }
 }
 
-bool Instrumentation::CodeNeedsEntryExitStub(const void* code, ArtMethod* method)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  // Proxy.init should never have entry/exit stubs.
-  if (IsProxyInit(method)) {
-    return false;
-  }
-
-  // In some tests runtime isn't setup fully and hence the entry points could
-  // be nullptr.
-  if (code == nullptr) {
-    return true;
-  }
-
-  // Code running in the interpreter doesn't need entry/exit stubs.
-  if (Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(code)) {
-    return false;
-  }
-
-  // When jiting code for debuggable apps we generate the code to call method
-  // entry / exit hooks when required. Hence it is not required to update
-  // to instrumentation entry point for JITed code in debuggable mode.
-  if (!Runtime::Current()->IsJavaDebuggable()) {
-    return true;
-  }
-
-  // Native functions can have JITed entry points but we don't include support
-  // for calling entry / exit hooks directly from the JITed code for native
-  // functions. So we still have to install entry exit stubs for such cases.
-  if (method->IsNative()) {
-    return true;
-  }
-
-  jit::Jit* jit = Runtime::Current()->GetJit();
-  if (jit != nullptr && jit->GetCodeCache()->ContainsPc(code)) {
-    return false;
-  }
-  return true;
+bool Instrumentation::NeedsDexPcEvents(ArtMethod* method, Thread* thread) {
+  return (InterpretOnly(method) || thread->IsForceInterpreter()) && HasDexPcListeners();
 }
 
 bool Instrumentation::InterpretOnly(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
   if (method->IsNative()) {
     return false;
   }
-  return InterpretOnly() ||
-         IsDeoptimized(method) ||
-         Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method);
+  return InterpretOnly() || IsDeoptimized(method);
 }
 
-static bool CanUseAotCode(ArtMethod* method, const void* quick_code)
+static bool CanUseAotCode(const void* quick_code)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   if (quick_code == nullptr) {
     return false;
   }
-  if (method->IsNative()) {
-    // AOT code for native methods can always be used.
-    return true;
-  }
-
   Runtime* runtime = Runtime::Current();
   // For simplicity, we never use AOT code for debuggable.
   if (runtime->IsJavaDebuggable()) {
@@ -332,7 +312,7 @@
 static bool CanUseNterp(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
   return interpreter::CanRuntimeUseNterp() &&
       CanMethodUseNterp(method) &&
-      method->GetDeclaringClass()->IsVerified();
+      method->IsDeclaringClassVerifiedMayBeDead();
 }
 
 static const void* GetOptimizedCodeFor(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -345,7 +325,7 @@
   // In debuggable mode, we can only use AOT code for native methods.
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   const void* aot_code = method->GetOatMethodQuickCode(class_linker->GetImagePointerSize());
-  if (CanUseAotCode(method, aot_code)) {
+  if (CanUseAotCode(aot_code)) {
     return aot_code;
   }
 
@@ -370,24 +350,24 @@
 
 void Instrumentation::InitializeMethodsCode(ArtMethod* method, const void* aot_code)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  // Use instrumentation entrypoints if instrumentation is installed.
-  if (UNLIKELY(EntryExitStubsInstalled()) && !IsProxyInit(method)) {
-    if (!method->IsNative() && InterpretOnly(method)) {
-      UpdateEntryPoints(method, GetQuickToInterpreterBridge());
-    } else {
-      UpdateEntryPoints(method, GetQuickInstrumentationEntryPoint());
-    }
+  if (!method->IsInvokable()) {
+    DCHECK(method->GetEntryPointFromQuickCompiledCode() == nullptr ||
+           Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(
+               method->GetEntryPointFromQuickCompiledCode()));
+    UpdateEntryPoints(method, GetQuickToInterpreterBridge());
     return;
   }
 
-  if (UNLIKELY(IsForcedInterpretOnly() || IsDeoptimized(method))) {
+  // Use instrumentation entrypoints if instrumentation is installed.
+  if (UNLIKELY(EntryExitStubsInstalled() || IsForcedInterpretOnly() || IsDeoptimized(method))) {
     UpdateEntryPoints(
         method, method->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge());
     return;
   }
 
   // Special case if we need an initialization check.
-  if (NeedsClinitCheckBeforeCall(method) && !method->GetDeclaringClass()->IsVisiblyInitialized()) {
+  // The method and its declaring class may be dead when starting JIT GC during managed heap GC.
+  if (method->StillNeedsClinitCheckMayBeDead()) {
     // If we have code but the method needs a class initialization check before calling
     // that code, install the resolution stub that will perform the check.
     // It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines
@@ -396,7 +376,12 @@
     // stub only if we have compiled code or we can execute nterp, and the method needs a class
     // initialization check.
     if (aot_code != nullptr || method->IsNative() || CanUseNterp(method)) {
-      UpdateEntryPoints(method, GetQuickResolutionStub());
+      if (kIsDebugBuild && CanUseNterp(method)) {
+        // Adds some test coverage for the nterp clinit entrypoint.
+        UpdateEntryPoints(method, interpreter::GetNterpWithClinitEntryPoint());
+      } else {
+        UpdateEntryPoints(method, GetQuickResolutionStub());
+      }
     } else {
       UpdateEntryPoints(method, GetQuickToInterpreterBridge());
     }
@@ -404,7 +389,7 @@
   }
 
   // Use the provided AOT code if possible.
-  if (CanUseAotCode(method, aot_code)) {
+  if (CanUseAotCode(aot_code)) {
     UpdateEntryPoints(method, aot_code);
     return;
   }
@@ -442,9 +427,11 @@
   }
 
   if (EntryExitStubsInstalled()) {
-    // Install the instrumentation entry point if needed.
-    if (CodeNeedsEntryExitStub(method->GetEntryPointFromQuickCompiledCode(), method)) {
-      UpdateEntryPoints(method, GetQuickInstrumentationEntryPoint());
+    // Install interpreter bridge / GenericJni stub if the existing code doesn't support
+    // entry / exit hooks.
+    if (!CodeSupportsEntryExitHooks(method->GetEntryPointFromQuickCompiledCode(), method)) {
+      UpdateEntryPoints(
+          method, method->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge());
     }
     return;
   }
@@ -452,138 +439,110 @@
   // We're being asked to restore the entrypoints after instrumentation.
   CHECK_EQ(instrumentation_level_, InstrumentationLevel::kInstrumentNothing);
   // We need to have the resolution stub still if the class is not initialized.
-  if (NeedsClinitCheckBeforeCall(method) && !method->GetDeclaringClass()->IsVisiblyInitialized()) {
+  if (method->StillNeedsClinitCheck()) {
     UpdateEntryPoints(method, GetQuickResolutionStub());
     return;
   }
   UpdateEntryPoints(method, GetOptimizedCodeFor(method));
 }
 
-// Places the instrumentation exit pc as the return PC for every quick frame. This also allows
-// deoptimization of quick frames to interpreter frames. When force_deopt is
-// true the frames have to be deoptimized. If the frame has a deoptimization
-// stack slot (all Jited frames), it is set to true to indicate this. For frames
-// that do not have this slot, the force_deopt_id on the InstrumentationStack is
-// used to check if the frame needs to be deoptimized. When force_deopt is false
-// we just instrument the stack for method entry / exit hooks.
-// Since we may already have done this previously, we need to push new instrumentation frame before
-// existing instrumentation frames.
-void InstrumentationInstallStack(Thread* thread, void* arg, bool deopt_all_frames)
+void Instrumentation::UpdateEntrypointsForDebuggable() {
+  Runtime* runtime = Runtime::Current();
+  // If we are transitioning from non-debuggable to debuggable, we patch
+  // entry points of methods to remove any aot / JITed entry points.
+  InstallStubsClassVisitor visitor(this);
+  runtime->GetClassLinker()->VisitClasses(&visitor);
+}
+
+bool Instrumentation::MethodSupportsExitEvents(ArtMethod* method,
+                                               const OatQuickMethodHeader* header) {
+  if (header == nullptr) {
+    // Header can be a nullptr for runtime / proxy methods that doesn't support method exit hooks
+    // or for native methods that use generic jni stubs. Generic jni stubs support method exit
+    // hooks.
+    return method->IsNative();
+  }
+
+  if (header->IsNterpMethodHeader()) {
+    // Nterp doesn't support method exit events
+    return false;
+  }
+
+  DCHECK(header->IsOptimized());
+  if (CodeInfo::IsDebuggable(header->GetOptimizedCodeInfoPtr())) {
+    // For optimized code, we only support method entry / exit hooks if they are compiled as
+    // debuggable.
+    return true;
+  }
+
+  return false;
+}
+
+// Updates on stack frames to support any changes related to instrumentation.
+// For JITed frames, DeoptimizeFlag is updated to enable deoptimization of
+// methods when necessary. Shadow frames are updated if dex pc event
+// notification has changed. When force_deopt is true then DeoptimizationFlag is
+// updated to force a deoptimization.
+void InstrumentationInstallStack(Thread* thread, bool deopt_all_frames)
     REQUIRES(Locks::mutator_lock_) {
   Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
   struct InstallStackVisitor final : public StackVisitor {
     InstallStackVisitor(Thread* thread_in,
                         Context* context,
-                        uintptr_t instrumentation_exit_pc,
-                        uint64_t force_deopt_id,
                         bool deopt_all_frames)
         : StackVisitor(thread_in, context, kInstrumentationStackWalk),
-          instrumentation_stack_(thread_in->GetInstrumentationStack()),
-          instrumentation_exit_pc_(instrumentation_exit_pc),
-          reached_existing_instrumentation_frames_(false),
-          force_deopt_id_(force_deopt_id),
-          deopt_all_frames_(deopt_all_frames) {}
+          deopt_all_frames_(deopt_all_frames),
+          runtime_methods_need_deopt_check_(false) {}
 
     bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
       ArtMethod* m = GetMethod();
-      if (m == nullptr) {
+      if (m == nullptr || m->IsRuntimeMethod()) {
         if (kVerboseInstrumentation) {
-          LOG(INFO) << "  Skipping upcall. Frame " << GetFrameId();
+          LOG(INFO) << "  Skipping upcall / runtime method. Frame " << GetFrameId();
         }
-        return true;  // Ignore upcalls.
+        return true;  // Ignore upcalls and runtime methods.
       }
-      if (GetCurrentQuickFrame() == nullptr) {
-        if (kVerboseInstrumentation) {
-          LOG(INFO) << "Pushing shadow frame method " << m->PrettyMethod();
-        }
-        stack_methods_.push_back(m);
+
+      bool is_shadow_frame = GetCurrentQuickFrame() == nullptr;
+      if (kVerboseInstrumentation) {
+        LOG(INFO) << "Processing frame: method: " << m->PrettyMethod()
+                  << " is_shadow_frame: " << is_shadow_frame;
+      }
+
+      // Handle interpreter frame.
+      if (is_shadow_frame) {
+        // Since we are updating the instrumentation related information we have to recalculate
+        // NeedsDexPcEvents. For example, when a new method or thread is deoptimized / interpreter
+        // stubs are installed the NeedsDexPcEvents could change for the shadow frames on the stack.
+        // If we don't update it here we would miss reporting dex pc events which is incorrect.
+        ShadowFrame* shadow_frame = GetCurrentShadowFrame();
+        DCHECK(shadow_frame != nullptr);
+        shadow_frame->SetNotifyDexPcMoveEvents(
+            Runtime::Current()->GetInstrumentation()->NeedsDexPcEvents(GetMethod(), GetThread()));
         return true;  // Continue.
       }
-      uintptr_t return_pc = GetReturnPc();
-      if (kVerboseInstrumentation) {
-        LOG(INFO) << "  Installing exit stub in " << DescribeLocation();
+
+      DCHECK(!m->IsRuntimeMethod());
+      const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();
+      // If it is a JITed frame then just set the deopt bit if required otherwise continue.
+      // We need kForceDeoptForRedefinition to ensure we don't use any JITed code after a
+      // redefinition. We support redefinition only if the runtime has started off as a
+      // debuggable runtime which makes sure we don't use any AOT or Nterp code.
+      // The CheckCallerForDeopt is an optimization which we only do for non-native JITed code for
+      // now. We can extend it to native methods but that needs reserving an additional stack slot.
+      // We don't do it currently since that wasn't important for debugger performance.
+      if (method_header != nullptr && method_header->HasShouldDeoptimizeFlag()) {
+        if (deopt_all_frames_) {
+          runtime_methods_need_deopt_check_ = true;
+          SetShouldDeoptimizeFlag(DeoptimizeFlagValue::kForceDeoptForRedefinition);
+        }
+        SetShouldDeoptimizeFlag(DeoptimizeFlagValue::kCheckCallerForDeopt);
       }
-      if (return_pc == instrumentation_exit_pc_) {
-        auto it = instrumentation_stack_->find(GetReturnPcAddr());
-        CHECK(it != instrumentation_stack_->end());
-        const InstrumentationStackFrame& frame = it->second;
-        if (m->IsRuntimeMethod()) {
-          if (frame.interpreter_entry_) {
-            return true;
-          }
-        }
 
-        // We've reached a frame which has already been installed with instrumentation exit stub.
-        // We should have already installed instrumentation or be interpreter on previous frames.
-        reached_existing_instrumentation_frames_ = true;
-
-        // Trampolines get replaced with their actual method in the stack,
-        // so don't do the check below for runtime methods.
-        if (!frame.method_->IsRuntimeMethod()) {
-          CHECK_EQ(m->GetNonObsoleteMethod(), frame.method_->GetNonObsoleteMethod())
-              << "Expected " << ArtMethod::PrettyMethod(m)
-              << ", Found " << ArtMethod::PrettyMethod(frame.method_);
-        }
-        return_pc = frame.return_pc_;
-        if (kVerboseInstrumentation) {
-          LOG(INFO) << "Ignoring already instrumented " << frame.Dump();
-        }
-      } else {
-        // If it is a JITed frame then just set the deopt bit if required
-        // otherwise continue
-        const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();
-        if (method_header != nullptr && method_header->HasShouldDeoptimizeFlag()) {
-          if (deopt_all_frames_) {
-            SetShouldDeoptimizeFlag(DeoptimizeFlagValue::kDebug);
-          }
-          return true;
-        }
-        CHECK_NE(return_pc, 0U);
-        if (UNLIKELY(reached_existing_instrumentation_frames_ && !m->IsRuntimeMethod())) {
-          // We already saw an existing instrumentation frame so this should be a runtime-method
-          // inserted by the interpreter or runtime.
-          std::string thread_name;
-          GetThread()->GetThreadName(thread_name);
-          LOG(FATAL) << "While walking " << thread_name << " found unexpected non-runtime method"
-                     << " without instrumentation exit return or interpreter frame."
-                     << " method is " << GetMethod()->PrettyMethod()
-                     << " return_pc is " << std::hex << return_pc;
-          UNREACHABLE();
-        }
-        if (m->IsRuntimeMethod()) {
-          size_t frame_size = GetCurrentQuickFrameInfo().FrameSizeInBytes();
-          ArtMethod** caller_frame = reinterpret_cast<ArtMethod**>(
-              reinterpret_cast<uint8_t*>(GetCurrentQuickFrame()) + frame_size);
-          if (*caller_frame != nullptr && (*caller_frame)->IsNative()) {
-            // Do not install instrumentation exit on return to JNI stubs.
-            return true;
-          }
-        }
-        InstrumentationStackFrame instrumentation_frame(
-            m->IsRuntimeMethod() ? nullptr : GetThisObject().Ptr(),
-            m,
-            return_pc,
-            false,
-            force_deopt_id_);
-        if (kVerboseInstrumentation) {
-          LOG(INFO) << "Pushing frame " << instrumentation_frame.Dump();
-        }
-
-        if (!m->IsRuntimeMethod()) {
-          // Runtime methods don't need to run method entry callbacks.
-          stack_methods_.push_back(m);
-        }
-        instrumentation_stack_->insert({GetReturnPcAddr(), instrumentation_frame});
-        SetReturnPc(instrumentation_exit_pc_);
-      }
       return true;  // Continue.
     }
-    std::map<uintptr_t, InstrumentationStackFrame>* const instrumentation_stack_;
-    std::vector<ArtMethod*> stack_methods_;
-    const uintptr_t instrumentation_exit_pc_;
-    bool reached_existing_instrumentation_frames_;
-    uint64_t force_deopt_id_;
     bool deopt_all_frames_;
+    bool runtime_methods_need_deopt_check_;
   };
   if (kVerboseInstrumentation) {
     std::string thread_name;
@@ -591,127 +550,177 @@
     LOG(INFO) << "Installing exit stubs in " << thread_name;
   }
 
-  Instrumentation* instrumentation = reinterpret_cast<Instrumentation*>(arg);
   std::unique_ptr<Context> context(Context::Create());
-  uintptr_t instrumentation_exit_pc = reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc());
   InstallStackVisitor visitor(thread,
                               context.get(),
-                              instrumentation_exit_pc,
-                              instrumentation->current_force_deopt_id_,
                               deopt_all_frames);
   visitor.WalkStack(true);
 
-  if (instrumentation->ShouldNotifyMethodEnterExitEvents()) {
-    // Create method enter events for all methods currently on the thread's stack. We only do this
-    // if we haven't already processed the method enter events.
-    for (auto smi = visitor.stack_methods_.rbegin(); smi != visitor.stack_methods_.rend(); smi++) {
-      instrumentation->MethodEnterEvent(thread,  *smi);
-    }
+  if (visitor.runtime_methods_need_deopt_check_) {
+    thread->SetDeoptCheckRequired(true);
   }
+
   thread->VerifyStack();
 }
 
-void Instrumentation::InstrumentThreadStack(Thread* thread, bool force_deopt) {
-  instrumentation_stubs_installed_ = true;
-  InstrumentationInstallStack(thread, this, force_deopt);
+void UpdateNeedsDexPcEventsOnStack(Thread* thread) REQUIRES(Locks::mutator_lock_) {
+  Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
+
+  struct InstallStackVisitor final : public StackVisitor {
+    InstallStackVisitor(Thread* thread_in, Context* context)
+        : StackVisitor(thread_in, context, kInstrumentationStackWalk) {}
+
+    bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
+      ShadowFrame* shadow_frame = GetCurrentShadowFrame();
+      if (shadow_frame != nullptr) {
+        shadow_frame->SetNotifyDexPcMoveEvents(
+            Runtime::Current()->GetInstrumentation()->NeedsDexPcEvents(GetMethod(), GetThread()));
+      }
+      return true;
+    }
+  };
+
+  std::unique_ptr<Context> context(Context::Create());
+  InstallStackVisitor visitor(thread, context.get());
+  visitor.WalkStack(true);
 }
 
-// Removes the instrumentation exit pc as the return PC for every quick frame.
-static void InstrumentationRestoreStack(Thread* thread, void* arg)
+void ReportMethodEntryForOnStackMethods(InstrumentationListener* listener, Thread* thread)
     REQUIRES(Locks::mutator_lock_) {
   Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
 
-  struct RestoreStackVisitor final : public StackVisitor {
-    RestoreStackVisitor(Thread* thread_in, uintptr_t instrumentation_exit_pc,
-                        Instrumentation* instrumentation)
-        : StackVisitor(thread_in, nullptr, kInstrumentationStackWalk),
-          thread_(thread_in),
-          instrumentation_exit_pc_(instrumentation_exit_pc),
-          instrumentation_(instrumentation),
-          instrumentation_stack_(thread_in->GetInstrumentationStack()),
-          frames_removed_(0) {}
+  struct InstallStackVisitor final : public StackVisitor {
+    InstallStackVisitor(Thread* thread_in, Context* context)
+        : StackVisitor(thread_in, context, kInstrumentationStackWalk) {}
 
     bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
-      if (instrumentation_stack_->size() == 0) {
-        return false;  // Stop.
-      }
       ArtMethod* m = GetMethod();
-      if (GetCurrentQuickFrame() == nullptr) {
-        if (kVerboseInstrumentation) {
-          LOG(INFO) << "  Ignoring a shadow frame. Frame " << GetFrameId()
-              << " Method=" << ArtMethod::PrettyMethod(m);
-        }
-        return true;  // Ignore shadow frames.
+      if (m == nullptr || m->IsRuntimeMethod()) {
+        // Skip upcall / runtime methods
+        return true;
       }
-      if (m == nullptr) {
-        if (kVerboseInstrumentation) {
-          LOG(INFO) << "  Skipping upcall. Frame " << GetFrameId();
-        }
-        return true;  // Ignore upcalls.
-      }
-      auto it = instrumentation_stack_->find(GetReturnPcAddr());
-      if (it != instrumentation_stack_->end()) {
-        const InstrumentationStackFrame& instrumentation_frame = it->second;
-        if (kVerboseInstrumentation) {
-          LOG(INFO) << "  Removing exit stub in " << DescribeLocation();
-        }
-        if (instrumentation_frame.interpreter_entry_) {
-          CHECK(m == Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
-        } else {
-          CHECK_EQ(m->GetNonObsoleteMethod(),
-                   instrumentation_frame.method_->GetNonObsoleteMethod())
-              << ArtMethod::PrettyMethod(m)
-              << " and " << instrumentation_frame.method_->GetNonObsoleteMethod()->PrettyMethod();
-        }
-        SetReturnPc(instrumentation_frame.return_pc_);
-        if (instrumentation_->ShouldNotifyMethodEnterExitEvents() &&
-            !m->IsRuntimeMethod()) {
-          // Create the method exit events. As the methods didn't really exit the result is 0.
-          // We only do this if no debugger is attached to prevent from posting events twice.
-          JValue val;
-          instrumentation_->MethodExitEvent(thread_, m, OptionalFrame{}, val);
-        }
-        frames_removed_++;
+
+      if (GetCurrentShadowFrame() != nullptr) {
+        stack_methods_.push_back(m);
       } else {
-        if (kVerboseInstrumentation) {
-          LOG(INFO) << "  No exit stub in " << DescribeLocation();
+        const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();
+        if (Runtime::Current()->GetInstrumentation()->MethodSupportsExitEvents(m, method_header)) {
+          // It is unexpected to see a method enter event but not a method exit event so record
+          // stack methods only for frames that support method exit events. Even if we deoptimize we
+          // make sure that we only call method exit event if the frame supported it in the first
+          // place. For ex: deoptimizing from JITed code with debug support calls a method exit hook
+          // but deoptimizing from nterp doesn't.
+          stack_methods_.push_back(m);
+        }
+      }
+      return true;
+    }
+
+    std::vector<ArtMethod*> stack_methods_;
+  };
+
+  if (kVerboseInstrumentation) {
+    std::string thread_name;
+    thread->GetThreadName(thread_name);
+    LOG(INFO) << "Updating DexPcMoveEvents on shadow frames on stack  " << thread_name;
+  }
+
+  std::unique_ptr<Context> context(Context::Create());
+  InstallStackVisitor visitor(thread, context.get());
+  visitor.WalkStack(true);
+
+  // Create method enter events for all methods currently on the thread's stack.
+  for (auto smi = visitor.stack_methods_.rbegin(); smi != visitor.stack_methods_.rend(); smi++) {
+    listener->MethodEntered(thread, *smi);
+  }
+}
+
+void Instrumentation::InstrumentThreadStack(Thread* thread, bool force_deopt) {
+  run_exit_hooks_ = true;
+  InstrumentationInstallStack(thread, force_deopt);
+}
+
+void Instrumentation::InstrumentAllThreadStacks(bool force_deopt) {
+  run_exit_hooks_ = true;
+  MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+  for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) {
+    InstrumentThreadStack(thread, force_deopt);
+  }
+}
+
+static void InstrumentationRestoreStack(Thread* thread) REQUIRES(Locks::mutator_lock_) {
+  Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
+
+  struct RestoreStackVisitor final : public StackVisitor {
+    RestoreStackVisitor(Thread* thread)
+        : StackVisitor(thread, nullptr, kInstrumentationStackWalk), thread_(thread) {}
+
+    bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
+      if (GetCurrentQuickFrame() == nullptr) {
+        return true;
+      }
+
+      const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();
+      if (method_header != nullptr && method_header->HasShouldDeoptimizeFlag()) {
+        // We shouldn't restore stack if any of the frames need a force deopt
+        DCHECK(!ShouldForceDeoptForRedefinition());
+        UnsetShouldDeoptimizeFlag(DeoptimizeFlagValue::kCheckCallerForDeopt);
+      }
+      return true;  // Continue.
+    }
+    Thread* const thread_;
+  };
+
+  if (kVerboseInstrumentation) {
+    std::string thread_name;
+    thread->GetThreadName(thread_name);
+    LOG(INFO) << "Restoring stack for " << thread_name;
+  }
+  DCHECK(!thread->IsDeoptCheckRequired());
+  RestoreStackVisitor visitor(thread);
+  visitor.WalkStack(true);
+}
+
+static bool HasFramesNeedingForceDeopt(Thread* thread) REQUIRES(Locks::mutator_lock_) {
+  Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
+
+  struct CheckForForceDeoptStackVisitor final : public StackVisitor {
+    CheckForForceDeoptStackVisitor(Thread* thread)
+        : StackVisitor(thread, nullptr, kInstrumentationStackWalk),
+          thread_(thread),
+          force_deopt_check_needed_(false) {}
+
+    bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
+      if (GetCurrentQuickFrame() == nullptr) {
+        return true;
+      }
+
+      const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();
+      if (method_header != nullptr && method_header->HasShouldDeoptimizeFlag()) {
+        if (ShouldForceDeoptForRedefinition()) {
+          force_deopt_check_needed_ = true;
+          return false;
         }
       }
       return true;  // Continue.
     }
     Thread* const thread_;
-    const uintptr_t instrumentation_exit_pc_;
-    Instrumentation* const instrumentation_;
-    std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* const instrumentation_stack_;
-    size_t frames_removed_;
+    bool force_deopt_check_needed_;
   };
-  if (kVerboseInstrumentation) {
-    std::string thread_name;
-    thread->GetThreadName(thread_name);
-    LOG(INFO) << "Removing exit stubs in " << thread_name;
-  }
-  std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
-      thread->GetInstrumentationStack();
-  if (stack->size() > 0) {
-    Instrumentation* instrumentation = reinterpret_cast<Instrumentation*>(arg);
-    uintptr_t instrumentation_exit_pc =
-        reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc());
-    RestoreStackVisitor visitor(thread, instrumentation_exit_pc, instrumentation);
-    visitor.WalkStack(true);
-    CHECK_EQ(visitor.frames_removed_, stack->size());
-    stack->clear();
-  }
+
+  CheckForForceDeoptStackVisitor visitor(thread);
+  visitor.WalkStack(true);
+  // If there is a frame that requires a force deopt we should have set the IsDeoptCheckRequired
+  // bit. We don't check if the bit needs to be reset on every method exit / deoptimization. We
+  // only check when we no longer need instrumentation support. So it is possible that the bit is
+  // set but we don't find any frames that need a force deopt on the stack so reverse implication
+  // doesn't hold.
+  DCHECK_IMPLIES(visitor.force_deopt_check_needed_, thread->IsDeoptCheckRequired());
+  return visitor.force_deopt_check_needed_;
 }
 
 void Instrumentation::DeoptimizeAllThreadFrames() {
-  Thread* self = Thread::Current();
-  MutexLock mu(self, *Locks::thread_list_lock_);
-  ThreadList* tl = Runtime::Current()->GetThreadList();
-  tl->ForEach([&](Thread* t) {
-    Locks::mutator_lock_->AssertExclusiveHeld(self);
-    InstrumentThreadStack(t, /* deopt_all_frames= */ true);
-  });
-  current_force_deopt_id_++;
+  InstrumentAllThreadStacks(/* force_deopt= */ true);
 }
 
 static bool HasEvent(Instrumentation::InstrumentationEvent expected, uint32_t events) {
@@ -791,6 +800,12 @@
                            exception_handled_listeners_,
                            listener,
                            &have_exception_handled_listeners_);
+  if (HasEvent(kDexPcMoved, events)) {
+    MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+    for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) {
+      UpdateNeedsDexPcEventsOnStack(thread);
+    }
+  }
 }
 
 static void PotentiallyRemoveListenerFrom(Instrumentation::InstrumentationEvent event,
@@ -872,6 +887,12 @@
                                 exception_handled_listeners_,
                                 listener,
                                 &have_exception_handled_listeners_);
+  if (HasEvent(kDexPcMoved, events)) {
+    MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+    for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) {
+      UpdateNeedsDexPcEventsOnStack(thread);
+    }
+  }
 }
 
 Instrumentation::InstrumentationLevel Instrumentation::GetCurrentInstrumentationLevel() const {
@@ -900,6 +921,11 @@
   instrumentation_level_ = requested_level;
 }
 
+void Instrumentation::EnableEntryExitHooks(const char* key) {
+  DCHECK(Runtime::Current()->IsJavaDebuggable());
+  ConfigureStubs(key, InstrumentationLevel::kInstrumentWithEntryExitHooks);
+}
+
 void Instrumentation::MaybeRestoreInstrumentationStack() {
   // Restore stack only if there is no method currently deoptimized.
   if (!IsDeoptimizedMethodsEmpty()) {
@@ -916,21 +942,21 @@
   // exclusively at this point.
   Locks::mutator_lock_->AssertExclusiveHeld(self);
   Runtime::Current()->GetThreadList()->ForEach([&](Thread* t) NO_THREAD_SAFETY_ANALYSIS {
+    bool has_force_deopt_frames = HasFramesNeedingForceDeopt(t);
+    if (!has_force_deopt_frames) {
+      // We no longer have any frames that require a force deopt check. If the bit was true then we
+      // had some frames earlier but they already got deoptimized and are no longer on stack.
+      t->SetDeoptCheckRequired(false);
+    }
     no_remaining_deopts =
         no_remaining_deopts &&
         !t->IsForceInterpreter() &&
         !t->HasDebuggerShadowFrames() &&
-        std::all_of(t->GetInstrumentationStack()->cbegin(),
-                    t->GetInstrumentationStack()->cend(),
-                    [&](const auto& frame) REQUIRES_SHARED(Locks::mutator_lock_) {
-                      return frame.second.force_deopt_id_ == current_force_deopt_id_;
-                    });
+        !has_force_deopt_frames;
   });
   if (no_remaining_deopts) {
-    Runtime::Current()->GetThreadList()->ForEach(InstrumentationRestoreStack, this);
-    // Only do this after restoring, as walking the stack when restoring will see
-    // the instrumentation exit pc.
-    instrumentation_stubs_installed_ = false;
+    Runtime::Current()->GetThreadList()->ForEach(InstrumentationRestoreStack);
+    run_exit_hooks_ = false;
   }
 }
 
@@ -950,17 +976,11 @@
   Locks::mutator_lock_->AssertExclusiveHeld(self);
   Locks::thread_list_lock_->AssertNotHeld(self);
   UpdateInstrumentationLevel(requested_level);
+  InstallStubsClassVisitor visitor(this);
+  runtime->GetClassLinker()->VisitClasses(&visitor);
   if (requested_level > InstrumentationLevel::kInstrumentNothing) {
-    InstallStubsClassVisitor visitor(this);
-    runtime->GetClassLinker()->VisitClasses(&visitor);
-    instrumentation_stubs_installed_ = true;
-    MutexLock mu(self, *Locks::thread_list_lock_);
-    for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) {
-      InstrumentThreadStack(thread, /* deopt_all_frames= */ false);
-    }
+    InstrumentAllThreadStacks(/* force_deopt= */ false);
   } else {
-    InstallStubsClassVisitor visitor(this);
-    runtime->GetClassLinker()->VisitClasses(&visitor);
     MaybeRestoreInstrumentationStack();
   }
 }
@@ -1038,14 +1058,14 @@
     return "interpreter";
   } else if (class_linker->IsQuickResolutionStub(code)) {
     return "resolution";
-  } else if (code == GetQuickInstrumentationEntryPoint()) {
-    return "instrumentation";
   } else if (jit != nullptr && jit->GetCodeCache()->ContainsPc(code)) {
     return "jit";
   } else if (code == GetInvokeObsoleteMethodStub()) {
     return "obsolete";
   } else if (code == interpreter::GetNterpEntryPoint()) {
     return "nterp";
+  } else if (code == interpreter::GetNterpWithClinitEntryPoint()) {
+    return "nterp with clinit";
   } else if (class_linker->IsQuickGenericJniStub(code)) {
     return "generic jni";
   } else if (Runtime::Current()->GetOatFileManager().ContainsPc(code)) {
@@ -1055,7 +1075,7 @@
 }
 
 void Instrumentation::UpdateMethodsCodeImpl(ArtMethod* method, const void* new_code) {
-  if (!AreExitStubsInstalled()) {
+  if (!EntryExitStubsInstalled()) {
     // Fast path: no instrumentation.
     DCHECK(!IsDeoptimized(method));
     UpdateEntryPoints(method, new_code);
@@ -1076,12 +1096,11 @@
     return;
   }
 
-  if (EntryExitStubsInstalled() && CodeNeedsEntryExitStub(new_code, method)) {
-    DCHECK(method->GetEntryPointFromQuickCompiledCode() == GetQuickInstrumentationEntryPoint() ||
-        class_linker->IsQuickToInterpreterBridge(method->GetEntryPointFromQuickCompiledCode()))
-              << EntryPointString(method->GetEntryPointFromQuickCompiledCode())
-              << " " << method->PrettyMethod();
-    // If the code we want to update the method with still needs entry/exit stub, just skip.
+  if (EntryExitStubsInstalled() && !CodeSupportsEntryExitHooks(new_code, method)) {
+    DCHECK(CodeSupportsEntryExitHooks(method->GetEntryPointFromQuickCompiledCode(), method))
+        << EntryPointString(method->GetEntryPointFromQuickCompiledCode()) << " "
+        << method->PrettyMethod();
+    // If we need entry / exit stubs but the new_code doesn't support entry / exit hooks just skip.
     return;
   }
 
@@ -1093,8 +1112,9 @@
   // We don't do any read barrier on `method`'s declaring class in this code, as the JIT might
   // enter here on a soon-to-be deleted ArtMethod. Updating the entrypoint is OK though, as
   // the ArtMethod is still in memory.
-  if (EntryExitStubsInstalled()) {
-    // If stubs are installed don't update.
+  if (EntryExitStubsInstalled() && !CodeSupportsEntryExitHooks(new_code, method)) {
+    // If the new code doesn't support entry exit hooks but we need them don't update with the new
+    // code.
     return;
   }
   UpdateEntryPoints(method, new_code);
@@ -1119,14 +1139,6 @@
   return deoptimized_methods_.find(method) != deoptimized_methods_.end();
 }
 
-ArtMethod* Instrumentation::BeginDeoptimizedMethod() {
-  if (deoptimized_methods_.empty()) {
-    // Empty.
-    return nullptr;
-  }
-  return *deoptimized_methods_.begin();
-}
-
 bool Instrumentation::RemoveDeoptimizedMethod(ArtMethod* method) {
   auto it = deoptimized_methods_.find(method);
   if (it == deoptimized_methods_.end()) {
@@ -1136,18 +1148,13 @@
   return true;
 }
 
-bool Instrumentation::IsDeoptimizedMethodsEmptyLocked() const {
-  return deoptimized_methods_.empty();
-}
-
 void Instrumentation::Deoptimize(ArtMethod* method) {
   CHECK(!method->IsNative());
   CHECK(!method->IsProxyMethod());
   CHECK(method->IsInvokable());
 
-  Thread* self = Thread::Current();
   {
-    WriterMutexLock mu(self, *GetDeoptimizedMethodsLock());
+    Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
     bool has_not_been_deoptimized = AddDeoptimizedMethod(method);
     CHECK(has_not_been_deoptimized) << "Method " << ArtMethod::PrettyMethod(method)
         << " is already deoptimized";
@@ -1155,16 +1162,10 @@
   if (!InterpreterStubsInstalled()) {
     UpdateEntryPoints(method, GetQuickToInterpreterBridge());
 
-    // Install instrumentation exit stub and instrumentation frames. We may already have installed
-    // these previously so it will only cover the newly created frames.
-    instrumentation_stubs_installed_ = true;
-    MutexLock mu(self, *Locks::thread_list_lock_);
-    for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) {
-      // This isn't a strong deopt. We deopt this method if it is still in the
-      // deopt methods list. If by the time we hit this frame we no longer need
-      // a deopt it is safe to continue. So we don't mark the frame.
-      InstrumentThreadStack(thread, /* deopt_all_frames= */ false);
-    }
+    // Instrument thread stacks to request a check if the caller needs a deoptimization.
+    // This isn't a strong deopt. We deopt this method if it is still in the deopt methods list.
+    // If by the time we hit this frame we no longer need a deopt it is safe to continue.
+    InstrumentAllThreadStacks(/* force_deopt= */ false);
   }
 }
 
@@ -1173,9 +1174,8 @@
   CHECK(!method->IsProxyMethod());
   CHECK(method->IsInvokable());
 
-  Thread* self = Thread::Current();
   {
-    WriterMutexLock mu(self, *GetDeoptimizedMethodsLock());
+    Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
     bool found_and_erased = RemoveDeoptimizedMethod(method);
     CHECK(found_and_erased) << "Method " << ArtMethod::PrettyMethod(method)
         << " is not deoptimized";
@@ -1186,12 +1186,19 @@
     return;
   }
 
+  if (method->IsObsolete()) {
+    // Don't update entry points for obsolete methods. The entrypoint should
+    // have been set to InvokeObsoleteMethoStub.
+    DCHECK_EQ(method->GetEntryPointFromQuickCompiledCodePtrSize(kRuntimePointerSize),
+              GetInvokeObsoleteMethodStub());
+    return;
+  }
+
   // We are not using interpreter stubs for deoptimization. Restore the code of the method.
   // We still retain interpreter bridge if we need it for other reasons.
   if (InterpretOnly(method)) {
     UpdateEntryPoints(method, GetQuickToInterpreterBridge());
-  } else if (NeedsClinitCheckBeforeCall(method) &&
-             !method->GetDeclaringClass()->IsVisiblyInitialized()) {
+  } else if (method->StillNeedsClinitCheck()) {
     UpdateEntryPoints(method, GetQuickResolutionStub());
   } else {
     UpdateEntryPoints(method, GetMaybeInstrumentedCodeForInvoke(method));
@@ -1204,41 +1211,51 @@
 }
 
 bool Instrumentation::IsDeoptimizedMethodsEmpty() const {
-  ReaderMutexLock mu(Thread::Current(), *GetDeoptimizedMethodsLock());
   return deoptimized_methods_.empty();
 }
 
 bool Instrumentation::IsDeoptimized(ArtMethod* method) {
   DCHECK(method != nullptr);
-  ReaderMutexLock mu(Thread::Current(), *GetDeoptimizedMethodsLock());
   return IsDeoptimizedMethod(method);
 }
 
-
 void Instrumentation::DisableDeoptimization(const char* key) {
   // Remove any instrumentation support added for deoptimization.
   ConfigureStubs(key, InstrumentationLevel::kInstrumentNothing);
+  Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
   // Undeoptimized selected methods.
   while (true) {
     ArtMethod* method;
     {
-      ReaderMutexLock mu(Thread::Current(), *GetDeoptimizedMethodsLock());
-      if (IsDeoptimizedMethodsEmptyLocked()) {
+      if (deoptimized_methods_.empty()) {
         break;
       }
-      method = BeginDeoptimizedMethod();
+      method = *deoptimized_methods_.begin();
       CHECK(method != nullptr);
     }
     Undeoptimize(method);
   }
 }
 
-// Indicates if instrumentation should notify method enter/exit events to the listeners.
-bool Instrumentation::ShouldNotifyMethodEnterExitEvents() const {
-  if (!HasMethodEntryListeners() && !HasMethodExitListeners()) {
-    return false;
+void Instrumentation::MaybeSwitchRuntimeDebugState(Thread* self) {
+  Runtime* runtime = Runtime::Current();
+  // Return early if runtime is shutting down.
+  if (runtime->IsShuttingDown(self)) {
+    return;
   }
-  return !InterpreterStubsInstalled();
+
+  // Don't switch the state if we started off as JavaDebuggable or if we still need entry / exit
+  // hooks for other reasons.
+  if (EntryExitStubsInstalled() || runtime->IsJavaDebuggableAtInit()) {
+    return;
+  }
+
+  art::jit::Jit* jit = runtime->GetJit();
+  if (jit != nullptr) {
+    jit->GetCodeCache()->InvalidateAllCompiledCode();
+    jit->GetJitCompiler()->SetDebuggableCompilerOption(false);
+  }
+  runtime->SetRuntimeDebugState(art::Runtime::RuntimeDebugState::kNonJavaDebuggable);
 }
 
 void Instrumentation::DeoptimizeEverything(const char* key) {
@@ -1250,14 +1267,21 @@
   ConfigureStubs(key, InstrumentationLevel::kInstrumentNothing);
 }
 
-void Instrumentation::EnableMethodTracing(const char* key, bool needs_interpreter) {
+void Instrumentation::EnableMethodTracing(const char* key,
+                                          InstrumentationListener* listener,
+                                          bool needs_interpreter) {
   InstrumentationLevel level;
   if (needs_interpreter) {
     level = InstrumentationLevel::kInstrumentWithInterpreter;
   } else {
-    level = InstrumentationLevel::kInstrumentWithInstrumentationStubs;
+    level = InstrumentationLevel::kInstrumentWithEntryExitHooks;
   }
   ConfigureStubs(key, level);
+
+  MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+  for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) {
+    ReportMethodEntryForOnStackMethods(listener, thread);
+  }
 }
 
 void Instrumentation::DisableMethodTracing(const char* key) {
@@ -1271,10 +1295,9 @@
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   const void* code = method->GetEntryPointFromQuickCompiledCodePtrSize(kRuntimePointerSize);
   // If we don't have the instrumentation, the resolution stub, or the
-  // interpreter as entrypoint, just return the current entrypoint, assuming
-  // it's the most optimized.
-  if (code != GetQuickInstrumentationEntryPoint() &&
-      !class_linker->IsQuickResolutionStub(code) &&
+  // interpreter, just return the current entrypoint,
+  // assuming it's the most optimized.
+  if (!class_linker->IsQuickResolutionStub(code) &&
       !class_linker->IsQuickToInterpreterBridge(code)) {
     return code;
   }
@@ -1291,8 +1314,8 @@
   // This is called by resolution trampolines and that should never be getting proxy methods.
   DCHECK(!method->IsProxyMethod()) << method->PrettyMethod();
   const void* code = GetCodeForInvoke(method);
-  if (EntryExitStubsInstalled() && CodeNeedsEntryExitStub(code, method)) {
-    return GetQuickInstrumentationEntryPoint();
+  if (EntryExitStubsInstalled() && !CodeSupportsEntryExitHooks(code, method)) {
+    return method->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge();
   }
   return code;
 }
@@ -1345,16 +1368,12 @@
 }
 
 void Instrumentation::MethodUnwindEvent(Thread* thread,
-                                        ObjPtr<mirror::Object> this_object,
                                         ArtMethod* method,
                                         uint32_t dex_pc) const {
   if (HasMethodUnwindListeners()) {
-    Thread* self = Thread::Current();
-    StackHandleScope<1> hs(self);
-    Handle<mirror::Object> thiz(hs.NewHandle(this_object));
     for (InstrumentationListener* listener : method_unwind_listeners_) {
       if (listener != nullptr) {
-        listener->MethodUnwind(thread, thiz, method, dex_pc);
+        listener->MethodUnwind(thread, method, dex_pc);
       }
     }
   }
@@ -1468,40 +1487,6 @@
   }
 }
 
-void Instrumentation::PushInstrumentationStackFrame(Thread* self,
-                                                    ObjPtr<mirror::Object> this_object,
-                                                    ArtMethod* method,
-                                                    uintptr_t stack_ptr,
-                                                    uintptr_t lr,
-                                                    bool interpreter_entry) {
-  DCHECK(!self->IsExceptionPending());
-  std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
-      self->GetInstrumentationStack();
-  if (kVerboseInstrumentation) {
-    LOG(INFO) << "Entering " << ArtMethod::PrettyMethod(method) << " from PC "
-              << reinterpret_cast<void*>(lr);
-  }
-
-  // We send the enter event before pushing the instrumentation frame to make cleanup easier. If the
-  // event causes an exception we can simply send the unwind event and return.
-  StackHandleScope<1> hs(self);
-  Handle<mirror::Object> h_this(hs.NewHandle(this_object));
-  if (!interpreter_entry) {
-    MethodEnterEvent(self, method);
-    if (self->IsExceptionPending()) {
-      MethodUnwindEvent(self, h_this.Get(), method, 0);
-      return;
-    }
-  }
-
-  // We have a callee-save frame meaning this value is guaranteed to never be 0.
-  DCHECK(!self->IsExceptionPending());
-
-  instrumentation::InstrumentationStackFrame instrumentation_frame(
-      h_this.Get(), method, lr, interpreter_entry, current_force_deopt_id_);
-  stack->insert({stack_ptr, instrumentation_frame});
-}
-
 DeoptimizationMethodType Instrumentation::GetDeoptimizationMethodType(ArtMethod* method) {
   if (method->IsRuntimeMethod()) {
     // Certain methods have strict requirement on whether the dex instruction
@@ -1518,83 +1503,18 @@
   return DeoptimizationMethodType::kDefault;
 }
 
-// Try to get the shorty of a runtime method if it's an invocation stub.
-static char GetRuntimeMethodShorty(Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_) {
-  char shorty = 'V';
-  StackVisitor::WalkStack(
-      [&shorty](const art::StackVisitor* stack_visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
-        ArtMethod* m = stack_visitor->GetMethod();
-        if (m == nullptr || m->IsRuntimeMethod()) {
-          return true;
-        }
-        // The first Java method.
-        if (m->IsNative()) {
-          // Use JNI method's shorty for the jni stub.
-          shorty = m->GetShorty()[0];
-        } else if (m->IsProxyMethod()) {
-          // Proxy method just invokes its proxied method via
-          // art_quick_proxy_invoke_handler.
-          shorty = m->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty()[0];
-        } else {
-          const Instruction& instr = m->DexInstructions().InstructionAt(stack_visitor->GetDexPc());
-          if (instr.IsInvoke()) {
-            uint16_t method_index = static_cast<uint16_t>(instr.VRegB());
-            const DexFile* dex_file = m->GetDexFile();
-            if (interpreter::IsStringInit(dex_file, method_index)) {
-              // Invoking string init constructor is turned into invoking
-              // StringFactory.newStringFromChars() which returns a string.
-              shorty = 'L';
-            } else {
-              shorty = dex_file->GetMethodShorty(method_index)[0];
-            }
-
-          } else {
-            // It could be that a non-invoke opcode invokes a stub, which in turn
-            // invokes Java code. In such cases, we should never expect a return
-            // value from the stub.
-          }
-        }
-        // Stop stack walking since we've seen a Java frame.
-        return false;
-      },
-      thread,
-      /* context= */ nullptr,
-      art::StackVisitor::StackWalkKind::kIncludeInlinedFrames);
-  return shorty;
-}
-
-JValue Instrumentation::GetReturnValue(
-    Thread* self, ArtMethod* method, bool* is_ref, uint64_t* gpr_result, uint64_t* fpr_result) {
+JValue Instrumentation::GetReturnValue(ArtMethod* method,
+                                       bool* is_ref,
+                                       uint64_t* gpr_result,
+                                       uint64_t* fpr_result) {
   uint32_t length;
   const PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
-  char return_shorty;
 
   // Runtime method does not call into MethodExitEvent() so there should not be
   // suspension point below.
   ScopedAssertNoThreadSuspension ants(__FUNCTION__, method->IsRuntimeMethod());
-  if (method->IsRuntimeMethod()) {
-    Runtime* runtime = Runtime::Current();
-    if (method != runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForClinit) &&
-        method != runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForSuspendCheck)) {
-      // If the caller is at an invocation point and the runtime method is not
-      // for clinit, we need to pass return results to the caller.
-      // We need the correct shorty to decide whether we need to pass the return
-      // result for deoptimization below.
-      return_shorty = GetRuntimeMethodShorty(self);
-    } else {
-      // Some runtime methods such as allocations, unresolved field getters, etc.
-      // have return value. We don't need to set return_value since MethodExitEvent()
-      // below isn't called for runtime methods. Deoptimization doesn't need the
-      // value either since the dex instruction will be re-executed by the
-      // interpreter, except these two cases:
-      // (1) For an invoke, which is handled above to get the correct shorty.
-      // (2) For MONITOR_ENTER/EXIT, which cannot be re-executed since it's not
-      //     idempotent. However there is no return value for it anyway.
-      return_shorty = 'V';
-    }
-  } else {
-    return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
-  }
+  DCHECK(!method->IsRuntimeMethod());
+  char return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
 
   *is_ref = return_shorty == '[' || return_shorty == 'L';
   JValue return_value;
@@ -1608,136 +1528,121 @@
   return return_value;
 }
 
-bool Instrumentation::ShouldDeoptimizeMethod(Thread* self, const NthCallerVisitor& visitor) {
-  bool should_deoptimize_frame = false;
-  const OatQuickMethodHeader* header = visitor.GetCurrentOatQuickMethodHeader();
-  if (header != nullptr && header->HasShouldDeoptimizeFlag()) {
-    uint8_t should_deopt_flag = visitor.GetShouldDeoptimizeFlag();
-    // DeoptimizeFlag could be set for debugging or for CHA invalidations.
-    // Deoptimize here only if it was requested for debugging. CHA
-    // invalidations are handled in the JITed code.
-    if ((should_deopt_flag & static_cast<uint8_t>(DeoptimizeFlagValue::kDebug)) != 0) {
-      should_deoptimize_frame = true;
-    }
+bool Instrumentation::PushDeoptContextIfNeeded(Thread* self,
+                                               DeoptimizationMethodType deopt_type,
+                                               bool is_ref,
+                                               const JValue& return_value)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (self->IsExceptionPending()) {
+    return false;
   }
-  return (visitor.caller != nullptr) &&
-         (InterpreterStubsInstalled() || IsDeoptimized(visitor.caller) ||
+
+  ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
+  DCHECK(sp != nullptr && (*sp)->IsRuntimeMethod());
+  if (!ShouldDeoptimizeCaller(self, sp)) {
+    return false;
+  }
+
+  // TODO(mythria): The current deopt behaviour is we just re-execute the
+  // alloc instruction so we don't need the return value. For instrumentation
+  // related deopts, we actually don't need to and can use the result we got
+  // here. Since this is a debug only feature it is not very important but
+  // consider reusing the result in future.
+  self->PushDeoptimizationContext(
+      return_value, is_ref, nullptr, /* from_code= */ false, deopt_type);
+  self->SetException(Thread::GetDeoptimizationException());
+  return true;
+}
+
+void Instrumentation::DeoptimizeIfNeeded(Thread* self,
+                                         ArtMethod** sp,
+                                         DeoptimizationMethodType type,
+                                         JValue return_value,
+                                         bool is_reference) {
+  if (self->IsAsyncExceptionPending() || ShouldDeoptimizeCaller(self, sp)) {
+    self->PushDeoptimizationContext(return_value,
+                                    is_reference,
+                                    nullptr,
+                                    /* from_code= */ false,
+                                    type);
+    // This is requested from suspend points or when returning from runtime methods so exit
+    // callbacks wouldn't be run yet. So don't skip method callbacks.
+    artDeoptimize(self, /* skip_method_exit_callbacks= */ false);
+  }
+}
+
+bool Instrumentation::NeedsSlowInterpreterForMethod(Thread* self, ArtMethod* method) {
+  return (method != nullptr) &&
+         (InterpreterStubsInstalled() ||
+          IsDeoptimized(method) ||
           self->IsForceInterpreter() ||
           // NB Since structurally obsolete compiled methods might have the offsets of
           // methods/fields compiled in we need to go back to interpreter whenever we hit
           // them.
-          visitor.caller->GetDeclaringClass()->IsObsoleteObject() ||
-          Dbg::IsForcedInterpreterNeededForUpcall(self, visitor.caller) ||
-          should_deoptimize_frame);
+          method->GetDeclaringClass()->IsObsoleteObject() ||
+          Dbg::IsForcedInterpreterNeededForUpcall(self, method));
 }
 
-TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self,
-                                                            uintptr_t* return_pc_addr,
-                                                            uint64_t* gpr_result,
-                                                            uint64_t* fpr_result) {
-  DCHECK(gpr_result != nullptr);
-  DCHECK(fpr_result != nullptr);
-  // Do the pop.
-  std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
-      self->GetInstrumentationStack();
-  CHECK_GT(stack->size(), 0U);
-  auto it = stack->find(reinterpret_cast<uintptr_t>(return_pc_addr));
-  CHECK(it != stack->end());
-  InstrumentationStackFrame instrumentation_frame = it->second;
-  stack->erase(it);
-
-  // Set return PC and check the consistency of the stack.
-  // We don't cache the return pc value in a local as it may change after
-  // sending a method exit event.
-  *return_pc_addr = instrumentation_frame.return_pc_;
-  self->VerifyStack();
-
-  ArtMethod* method = instrumentation_frame.method_;
-
-  bool is_ref;
-  JValue return_value = GetReturnValue(self, method, &is_ref, gpr_result, fpr_result);
-  StackHandleScope<1> hs(self);
-  MutableHandle<mirror::Object> res(hs.NewHandle<mirror::Object>(nullptr));
-  if (is_ref) {
-    // Take a handle to the return value so we won't lose it if we suspend.
-    // FIXME: The `is_ref` is often guessed wrong, so even object aligment
-    // assertion would fail for some tests. See b/204766614 .
-    // DCHECK_ALIGNED(return_value.GetL(), kObjectAlignment);
-    res.Assign(return_value.GetL());
-  }
-  if (!method->IsRuntimeMethod() && !instrumentation_frame.interpreter_entry_) {
-    // Note that sending the event may change the contents of *return_pc_addr.
-    MethodExitEvent(self, instrumentation_frame.method_, OptionalFrame{}, return_value);
+bool Instrumentation::ShouldDeoptimizeCaller(Thread* self, ArtMethod** sp) {
+  // When exit stubs aren't called we don't need to check for any instrumentation related
+  // deoptimizations.
+  if (!RunExitHooks()) {
+    return false;
   }
 
-  // Deoptimize if the caller needs to continue execution in the interpreter. Do nothing if we get
-  // back to an upcall.
-  NthCallerVisitor visitor(self, 1, true);
-  visitor.WalkStack(true);
-  // Check if we forced all threads to deoptimize in the time between this frame being created and
-  // now.
-  bool should_deoptimize_frame = instrumentation_frame.force_deopt_id_ != current_force_deopt_id_;
-  bool deoptimize = ShouldDeoptimizeMethod(self, visitor) || should_deoptimize_frame;
-
-  if (is_ref) {
-    // Restore the return value if it's a reference since it might have moved.
-    *reinterpret_cast<mirror::Object**>(gpr_result) = res.Get();
-  }
-  if (deoptimize && Runtime::Current()->IsAsyncDeoptimizeable(*return_pc_addr)) {
-    if (kVerboseInstrumentation) {
-      LOG(INFO) << "Deoptimizing "
-                << visitor.caller->PrettyMethod()
-                << " by returning from "
-                << method->PrettyMethod()
-                << " with result "
-                << std::hex << return_value.GetJ() << std::dec
-                << " in "
-                << *self;
-    }
-    DeoptimizationMethodType deopt_method_type = GetDeoptimizationMethodType(method);
-    self->PushDeoptimizationContext(return_value,
-                                    is_ref,
-                                    /* exception= */ nullptr,
-                                    /* from_code= */ false,
-                                    deopt_method_type);
-    return GetTwoWordSuccessValue(*return_pc_addr,
-                                  reinterpret_cast<uintptr_t>(GetQuickDeoptimizationEntryPoint()));
-  } else {
-    if (deoptimize && !Runtime::Current()->IsAsyncDeoptimizeable(*return_pc_addr)) {
-      VLOG(deopt) << "Got a deoptimization request on un-deoptimizable " << method->PrettyMethod()
-                  << " at PC " << reinterpret_cast<void*>(*return_pc_addr);
-    }
-    if (kVerboseInstrumentation) {
-      LOG(INFO) << "Returning from " << method->PrettyMethod()
-                << " to PC " << reinterpret_cast<void*>(*return_pc_addr);
-    }
-    return GetTwoWordSuccessValue(0, *return_pc_addr);
-  }
+  ArtMethod* runtime_method = *sp;
+  DCHECK(runtime_method->IsRuntimeMethod());
+  QuickMethodFrameInfo frame_info = Runtime::Current()->GetRuntimeMethodFrameInfo(runtime_method);
+  return ShouldDeoptimizeCaller(self, sp, frame_info.FrameSizeInBytes());
 }
 
-uintptr_t Instrumentation::PopFramesForDeoptimization(Thread* self, uintptr_t pop_until) const {
-  std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
-      self->GetInstrumentationStack();
-  // Pop all instrumentation frames below `pop_until`.
-  uintptr_t return_pc = 0u;
-  for (auto i = stack->begin(); i != stack->end() && i->first <= pop_until;) {
-    auto e = i;
-    ++i;
-    if (kVerboseInstrumentation) {
-      LOG(INFO) << "Popping for deoptimization " << e->second.method_->PrettyMethod();
-    }
-    return_pc = e->second.return_pc_;
-    stack->erase(e);
-  }
-  return return_pc;
-}
+bool Instrumentation::ShouldDeoptimizeCaller(Thread* self, ArtMethod** sp, size_t frame_size) {
+  uintptr_t caller_sp = reinterpret_cast<uintptr_t>(sp) + frame_size;
+  ArtMethod* caller = *(reinterpret_cast<ArtMethod**>(caller_sp));
+  uintptr_t caller_pc_addr = reinterpret_cast<uintptr_t>(sp) + (frame_size - sizeof(void*));
+  uintptr_t caller_pc = *reinterpret_cast<uintptr_t*>(caller_pc_addr);
 
-std::string InstrumentationStackFrame::Dump() const {
-  std::ostringstream os;
-  os << ArtMethod::PrettyMethod(method_) << ":"
-      << reinterpret_cast<void*>(return_pc_) << " this=" << reinterpret_cast<void*>(this_object_)
-      << " force_deopt_id=" << force_deopt_id_;
-  return os.str();
+  if (caller == nullptr ||
+      caller->IsNative() ||
+      caller->IsRuntimeMethod()) {
+    // We need to check for a deoptimization here because when a redefinition happens it is
+    // not safe to use any compiled code because the field offsets might change. For native
+    // methods, we don't embed any field offsets so no need to check for a deoptimization.
+    // If the caller is null we don't need to do anything. This can happen when the caller
+    // is being interpreted by the switch interpreter (when called from
+    // artQuickToInterpreterBridge) / during shutdown / early startup.
+    return false;
+  }
+
+  bool needs_deopt = NeedsSlowInterpreterForMethod(self, caller);
+
+  // Non java debuggable apps don't support redefinition and hence it isn't required to check if
+  // frame needs to be deoptimized. Even in debuggable apps, we only need this check when a
+  // redefinition has actually happened. This is indicated by IsDeoptCheckRequired flag. We also
+  // want to avoid getting method header when we need a deopt anyway.
+  if (Runtime::Current()->IsJavaDebuggable() && !needs_deopt && self->IsDeoptCheckRequired()) {
+    const OatQuickMethodHeader* header = caller->GetOatQuickMethodHeader(caller_pc);
+    if (header != nullptr && header->HasShouldDeoptimizeFlag()) {
+      DCHECK(header->IsOptimized());
+      uint8_t* should_deopt_flag_addr =
+          reinterpret_cast<uint8_t*>(caller_sp) + header->GetShouldDeoptimizeFlagOffset();
+      if ((*should_deopt_flag_addr &
+           static_cast<uint8_t>(DeoptimizeFlagValue::kForceDeoptForRedefinition)) != 0) {
+        needs_deopt = true;
+      }
+    }
+  }
+
+  if (needs_deopt) {
+    if (!Runtime::Current()->IsAsyncDeoptimizeable(caller, caller_pc)) {
+      LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
+                   << caller->PrettyMethod();
+      return false;
+    }
+    return true;
+  }
+
+  return false;
 }
 
 }  // namespace instrumentation
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index c811935..b31d0da 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -23,6 +23,7 @@
 #include <list>
 #include <memory>
 #include <optional>
+#include <queue>
 #include <unordered_set>
 
 #include "arch/instruction_set.h"
@@ -31,6 +32,7 @@
 #include "base/macros.h"
 #include "base/safe_map.h"
 #include "gc_root.h"
+#include "jvalue.h"
 #include "offsets.h"
 
 namespace art {
@@ -45,6 +47,7 @@
 template <typename T> class MutableHandle;
 struct NthCallerVisitor;
 union JValue;
+class OatQuickMethodHeader;
 class SHARED_LOCKABLE ReaderWriterMutex;
 class ShadowFrame;
 class Thread;
@@ -92,7 +95,6 @@
   // Call-back for when a method is popped due to an exception throw. A method will either cause a
   // MethodExited call-back or a MethodUnwind call-back when its activation is removed.
   virtual void MethodUnwind(Thread* thread,
-                            Handle<mirror::Object> this_object,
                             ArtMethod* method,
                             uint32_t dex_pc)
       REQUIRES_SHARED(Locks::mutator_lock_) = 0;
@@ -192,20 +194,37 @@
   };
 
   enum class InstrumentationLevel {
-    kInstrumentNothing,                   // execute without instrumentation
-    kInstrumentWithInstrumentationStubs,  // execute with instrumentation entry/exit stubs
-    kInstrumentWithInterpreter            // execute with interpreter
+    kInstrumentNothing,             // execute without instrumentation
+    kInstrumentWithEntryExitHooks,  // execute with entry/exit hooks
+    kInstrumentWithInterpreter      // execute with interpreter
   };
 
   Instrumentation();
 
-  static constexpr MemberOffset NeedsEntryExitHooksOffset() {
-    // Assert that instrumentation_stubs_installed_ is 8bits wide. If the size changes
+  static constexpr MemberOffset RunExitHooksOffset() {
+    // Assert that run_entry_exit_hooks_ is 8bits wide. If the size changes
     // update the compare instructions in the code generator when generating checks for
     // MethodEntryExitHooks.
-    static_assert(sizeof(instrumentation_stubs_installed_) == 1,
-                  "instrumentation_stubs_installed_ isn't expected size");
-    return MemberOffset(OFFSETOF_MEMBER(Instrumentation, instrumentation_stubs_installed_));
+    static_assert(sizeof(run_exit_hooks_) == 1, "run_exit_hooks_ isn't expected size");
+    return MemberOffset(OFFSETOF_MEMBER(Instrumentation, run_exit_hooks_));
+  }
+
+  static constexpr MemberOffset HaveMethodEntryListenersOffset() {
+    // Assert that have_method_entry_listeners_ is 8bits wide. If the size changes
+    // update the compare instructions in the code generator when generating checks for
+    // MethodEntryExitHooks.
+    static_assert(sizeof(have_method_entry_listeners_) == 1,
+                  "have_method_entry_listeners_ isn't expected size");
+    return MemberOffset(OFFSETOF_MEMBER(Instrumentation, have_method_entry_listeners_));
+  }
+
+  static constexpr MemberOffset HaveMethodExitListenersOffset() {
+    // Assert that have_method_exit_listeners_ is 8bits wide. If the size changes
+    // update the compare instructions in the code generator when generating checks for
+    // MethodEntryExitHooks.
+    static_assert(sizeof(have_method_exit_listeners_) == 1,
+                  "have_method_exit_listeners_ isn't expected size");
+    return MemberOffset(OFFSETOF_MEMBER(Instrumentation, have_method_exit_listeners_));
   }
 
   // Add a listener to be notified of the masked together sent of instrumentation events. This
@@ -215,14 +234,23 @@
   void AddListener(InstrumentationListener* listener, uint32_t events)
       REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_, !Locks::classlinker_classes_lock_);
 
-  // Removes a listener possibly removing instrumentation stubs.
+  // Removes listeners for the specified events.
   void RemoveListener(InstrumentationListener* listener, uint32_t events)
       REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_, !Locks::classlinker_classes_lock_);
 
   // Calls UndeoptimizeEverything which may visit class linker classes through ConfigureStubs.
   void DisableDeoptimization(const char* key)
-      REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
-      REQUIRES(!GetDeoptimizedMethodsLock());
+      REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_);
+
+  // Enables entry exit hooks support. This is called in preparation for debug requests that require
+  // calling method entry / exit hooks.
+  void EnableEntryExitHooks(const char* key)
+      REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_);
+
+  // Switches the runtime state to non-java debuggable if entry / exit hooks are no longer required
+  // and the runtime did not start off as java debuggable.
+  void MaybeSwitchRuntimeDebugState(Thread* self)
+      REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_);
 
   bool AreAllMethodsDeoptimized() const {
     return InterpreterStubsInstalled();
@@ -233,52 +261,44 @@
   void DeoptimizeEverything(const char* key)
       REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
       REQUIRES(!Locks::thread_list_lock_,
-               !Locks::classlinker_classes_lock_,
-               !GetDeoptimizedMethodsLock());
+               !Locks::classlinker_classes_lock_);
 
   // Executes everything with compiled code (or interpreter if there is no code). May visit class
   // linker classes through ConfigureStubs.
   void UndeoptimizeEverything(const char* key)
       REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
       REQUIRES(!Locks::thread_list_lock_,
-               !Locks::classlinker_classes_lock_,
-               !GetDeoptimizedMethodsLock());
+               !Locks::classlinker_classes_lock_);
 
   // Deoptimize a method by forcing its execution with the interpreter. Nevertheless, a static
   // method (except a class initializer) set to the resolution trampoline will be deoptimized only
   // once its declaring class is initialized.
-  void Deoptimize(ArtMethod* method)
-      REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_, !GetDeoptimizedMethodsLock());
+  void Deoptimize(ArtMethod* method) REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_);
 
   // Undeoptimze the method by restoring its entrypoints. Nevertheless, a static method
   // (except a class initializer) set to the resolution trampoline will be updated only once its
   // declaring class is initialized.
-  void Undeoptimize(ArtMethod* method)
-      REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_, !GetDeoptimizedMethodsLock());
+  void Undeoptimize(ArtMethod* method) REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_);
 
   // Indicates whether the method has been deoptimized so it is executed with the interpreter.
-  bool IsDeoptimized(ArtMethod* method)
-      REQUIRES(!GetDeoptimizedMethodsLock()) REQUIRES_SHARED(Locks::mutator_lock_);
+  bool IsDeoptimized(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Indicates if any method needs to be deoptimized. This is used to avoid walking the stack to
   // determine if a deoptimization is required.
-  bool IsDeoptimizedMethodsEmpty() const
-      REQUIRES(!GetDeoptimizedMethodsLock()) REQUIRES_SHARED(Locks::mutator_lock_);
+  bool IsDeoptimizedMethodsEmpty() const REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Enable method tracing by installing instrumentation entry/exit stubs or interpreter.
   void EnableMethodTracing(const char* key,
+                           InstrumentationListener* listener,
                            bool needs_interpreter = kDeoptimizeForAccurateMethodEntryExitListeners)
       REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
-      REQUIRES(!Locks::thread_list_lock_,
-               !Locks::classlinker_classes_lock_,
-               !GetDeoptimizedMethodsLock());
+          REQUIRES(!Locks::thread_list_lock_, !Locks::classlinker_classes_lock_);
 
   // Disable method tracing by uninstalling instrumentation entry/exit stubs or interpreter.
   void DisableMethodTracing(const char* key)
       REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
       REQUIRES(!Locks::thread_list_lock_,
-               !Locks::classlinker_classes_lock_,
-               !GetDeoptimizedMethodsLock());
+               !Locks::classlinker_classes_lock_);
 
 
   void InstrumentQuickAllocEntryPoints() REQUIRES(!Locks::instrument_entrypoints_lock_);
@@ -300,11 +320,11 @@
 
   // Update the code of a method respecting any installed stubs.
   void UpdateMethodsCode(ArtMethod* method, const void* new_code)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Update the code of a native method to a JITed stub.
   void UpdateNativeMethodsCodeToJitCode(ArtMethod* method, const void* new_code)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Return the code that we can execute for an invoke including from the JIT.
   const void* GetCodeForInvoke(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -321,7 +341,7 @@
   }
 
   bool EntryExitStubsInstalled() const {
-    return instrumentation_level_ == InstrumentationLevel::kInstrumentWithInstrumentationStubs ||
+    return instrumentation_level_ == InstrumentationLevel::kInstrumentWithEntryExitHooks ||
            instrumentation_level_ == InstrumentationLevel::kInstrumentWithInterpreter;
   }
 
@@ -339,8 +359,8 @@
     return forced_interpret_only_;
   }
 
-  bool AreExitStubsInstalled() const {
-    return instrumentation_stubs_installed_;
+  bool RunExitHooks() const {
+    return run_exit_hooks_;
   }
 
   bool HasMethodEntryListeners() const REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -383,6 +403,20 @@
     return have_exception_handled_listeners_;
   }
 
+  // Returns if dex pc events need to be reported for the specified method.
+  // These events are reported when DexPCListeners are installed and at least one of the
+  // following conditions hold:
+  // 1. The method is deoptimized. This is done when there is a breakpoint on method.
+  // 2. When the thread is deoptimized. This is used when single stepping a single thread.
+  // 3. When interpreter stubs are installed. In this case no additional information is maintained
+  //    about which methods need dex pc move events. This is usually used for features which need
+  //    them for several methods across threads or need expensive processing. So it is OK to not
+  //    further optimize this case.
+  // DexPCListeners are installed when there is a breakpoint on any method / single stepping
+  // on any of thread. These are removed when the last breakpoint was removed. See AddListener and
+  // RemoveListener for more details.
+  bool NeedsDexPcEvents(ArtMethod* method, Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_);
+
   bool NeedsSlowInterpreterForListeners() const REQUIRES_SHARED(Locks::mutator_lock_) {
     return have_field_read_listeners_ ||
            have_field_write_listeners_ ||
@@ -413,7 +447,6 @@
 
   // Inform listeners that a method has been exited due to an exception.
   void MethodUnwindEvent(Thread* thread,
-                         ObjPtr<mirror::Object> this_object,
                          ArtMethod* method,
                          uint32_t dex_pc) const
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -479,50 +512,37 @@
   void ExceptionHandledEvent(Thread* thread, ObjPtr<mirror::Throwable> exception_object) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  JValue GetReturnValue(Thread* self,
-                        ArtMethod* method,
-                        bool* is_ref,
-                        uint64_t* gpr_result,
-                        uint64_t* fpr_result) REQUIRES_SHARED(Locks::mutator_lock_);
-  bool ShouldDeoptimizeMethod(Thread* self, const NthCallerVisitor& visitor)
+  JValue GetReturnValue(ArtMethod* method, bool* is_ref, uint64_t* gpr_result, uint64_t* fpr_result)
       REQUIRES_SHARED(Locks::mutator_lock_);
-
-  // Called when an instrumented method is entered. The intended link register (lr) is saved so
-  // that returning causes a branch to the method exit stub. Generates method enter events.
-  void PushInstrumentationStackFrame(Thread* self,
-                                     ObjPtr<mirror::Object> this_object,
-                                     ArtMethod* method,
-                                     uintptr_t stack_pointer,
-                                     uintptr_t lr,
-                                     bool interpreter_entry)
+  bool PushDeoptContextIfNeeded(Thread* self,
+                                DeoptimizationMethodType deopt_type,
+                                bool is_ref,
+                                const JValue& result) REQUIRES_SHARED(Locks::mutator_lock_);
+  void DeoptimizeIfNeeded(Thread* self,
+                          ArtMethod** sp,
+                          DeoptimizationMethodType type,
+                          JValue result,
+                          bool is_ref) REQUIRES_SHARED(Locks::mutator_lock_);
+  // This returns if the caller of runtime method requires a deoptimization. This checks both if the
+  // method requires a deopt or if this particular frame needs a deopt because of a class
+  // redefinition.
+  bool ShouldDeoptimizeCaller(Thread* self, ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_);
+  bool ShouldDeoptimizeCaller(Thread* self, ArtMethod** sp, size_t frame_size)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // This returns if the specified method requires a deoptimization. This doesn't account if a stack
+  // frame involving this method requires a deoptimization.
+  bool NeedsSlowInterpreterForMethod(Thread* self, ArtMethod* method)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   DeoptimizationMethodType GetDeoptimizationMethodType(ArtMethod* method)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Called when an instrumented method is exited. Removes the pushed instrumentation frame
-  // returning the intended link register. Generates method exit events. The gpr_result and
-  // fpr_result pointers are pointers to the locations where the integer/pointer and floating point
-  // result values of the function are stored. Both pointers must always be valid but the values
-  // held there will only be meaningful if interpreted as the appropriate type given the function
-  // being returned from.
-  TwoWordReturn PopInstrumentationStackFrame(Thread* self,
-                                             uintptr_t* return_pc_addr,
-                                             uint64_t* gpr_result,
-                                             uint64_t* fpr_result)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
-
-  // Pops nframes instrumentation frames from the current thread. Returns the return pc for the last
-  // instrumentation frame that's popped.
-  uintptr_t PopFramesForDeoptimization(Thread* self, uintptr_t stack_pointer) const
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   // Call back for configure stubs.
-  void InstallStubsForClass(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_)
-      REQUIRES(!GetDeoptimizedMethodsLock());
+  void InstallStubsForClass(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void InstallStubsForMethod(ArtMethod* method)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+  void InstallStubsForMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void UpdateEntrypointsForDebuggable() REQUIRES(art::Locks::mutator_lock_);
 
   // Install instrumentation exit stub on every method of the stack of the given thread.
   // This is used by:
@@ -532,6 +552,8 @@
   //    the stack frame to run entry / exit hooks but we don't need to deoptimize.
   // deopt_all_frames indicates whether the frames need to deoptimize or not.
   void InstrumentThreadStack(Thread* thread, bool deopt_all_frames) REQUIRES(Locks::mutator_lock_);
+  void InstrumentAllThreadStacks(bool deopt_all_frames) REQUIRES(Locks::mutator_lock_)
+      REQUIRES(!Locks::thread_list_lock_);
 
   // Force all currently running frames to be deoptimized back to interpreter. This should only be
   // used in cases where basically all compiled code has been invalidated.
@@ -548,18 +570,21 @@
     return alloc_entrypoints_instrumented_;
   }
 
+  bool ProcessMethodUnwindCallbacks(Thread* self,
+                                    std::queue<ArtMethod*>& methods,
+                                    MutableHandle<mirror::Throwable>& exception)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   InstrumentationLevel GetCurrentInstrumentationLevel() const;
 
+  bool MethodSupportsExitEvents(ArtMethod* method, const OatQuickMethodHeader* header)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   // Returns true if moving to the given instrumentation level requires the installation of stubs.
   // False otherwise.
   bool RequiresInstrumentationInstallation(InstrumentationLevel new_level) const;
 
-  // Returns true if we need entry exit stub to call entry hooks. JITed code
-  // directly call entry / exit hooks and don't need the stub.
-  static bool CodeNeedsEntryExitStub(const void* code, ArtMethod* method)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   // Update the current instrumentation_level_.
   void UpdateInstrumentationLevel(InstrumentationLevel level);
 
@@ -570,12 +595,10 @@
   // becomes the highest instrumentation level required by a client.
   void ConfigureStubs(const char* key, InstrumentationLevel desired_instrumentation_level)
       REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
-      REQUIRES(!GetDeoptimizedMethodsLock(),
-               !Locks::thread_list_lock_,
+      REQUIRES(!Locks::thread_list_lock_,
                !Locks::classlinker_classes_lock_);
   void UpdateStubs() REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
-      REQUIRES(!GetDeoptimizedMethodsLock(),
-               !Locks::thread_list_lock_,
+      REQUIRES(!Locks::thread_list_lock_,
                !Locks::classlinker_classes_lock_);
 
   // If there are no pending deoptimizations restores the stack to the normal state by updating the
@@ -619,34 +642,36 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Read barrier-aware utility functions for accessing deoptimized_methods_
-  bool AddDeoptimizedMethod(ArtMethod* method)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(GetDeoptimizedMethodsLock());
-  bool IsDeoptimizedMethod(ArtMethod* method)
-      REQUIRES_SHARED(Locks::mutator_lock_, GetDeoptimizedMethodsLock());
-  bool RemoveDeoptimizedMethod(ArtMethod* method)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(GetDeoptimizedMethodsLock());
-  ArtMethod* BeginDeoptimizedMethod()
-      REQUIRES_SHARED(Locks::mutator_lock_, GetDeoptimizedMethodsLock());
-  bool IsDeoptimizedMethodsEmptyLocked() const
-      REQUIRES_SHARED(Locks::mutator_lock_, GetDeoptimizedMethodsLock());
+  bool AddDeoptimizedMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_);
+  bool IsDeoptimizedMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
+  bool RemoveDeoptimizedMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_);
   void UpdateMethodsCodeImpl(ArtMethod* method, const void* new_code)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
-  ReaderWriterMutex* GetDeoptimizedMethodsLock() const {
-    return deoptimized_methods_lock_.get();
-  }
-
-  // A counter that's incremented every time a DeoptimizeAllFrames. We check each
-  // InstrumentationStackFrames creation id against this number and if they differ we deopt even if
-  // we could otherwise continue running.
-  uint64_t current_force_deopt_id_ GUARDED_BY(Locks::mutator_lock_);
-
-  // Have we hijacked ArtMethod::code_ so that it calls instrumentation/interpreter code?
-  bool instrumentation_stubs_installed_;
+  // We need to run method exit hooks for two reasons:
+  // 1. When method exit listeners are installed
+  // 2. When we need to check if the caller of this method needs a deoptimization. This is needed
+  // only for deoptimizing the currently active invocations on stack when we deoptimize a method or
+  // invalidate the JITed code when redefining the classes. So future invocations don't need to do
+  // this check.
+  //
+  // For JITed code of non-native methods we already have a stack slot reserved for deoptimizing
+  // on demand and we use that stack slot to check if the caller needs a deoptimization. JITed code
+  // checks if there are any method exit listeners or if the stack slot is set to determine if
+  // method exit hooks need to be executed.
+  //
+  // For JITed JNI stubs there is no reserved stack slot for this and we just use this variable to
+  // check if we need to run method entry / exit hooks. This variable would be set when either of
+  // the above conditions are true. If we need method exit hooks only for case 2, we would call exit
+  // hooks for any future invocations which aren't necessary.
+  // QuickToInterpreterBridge and GenericJniStub also use this for same reasons.
+  // If calling entry / exit hooks becomes expensive we could do the same optimization we did for
+  // JITed code by having a reserved stack slot.
+  bool run_exit_hooks_;
 
   // The required level of instrumentation. This could be one of the following values:
   // kInstrumentNothing: no instrumentation support is needed
-  // kInstrumentWithInstrumentationStubs: needs support to call method entry/exit stubs.
+  // kInstrumentWithEntryExitHooks: needs support to call method entry/exit stubs.
   // kInstrumentWithInterpreter: only execute with interpreter
   Instrumentation::InstrumentationLevel instrumentation_level_;
 
@@ -718,8 +743,7 @@
 
   // The set of methods being deoptimized (by the debugger) which must be executed with interpreter
   // only.
-  mutable std::unique_ptr<ReaderWriterMutex> deoptimized_methods_lock_ BOTTOM_MUTEX_ACQUIRED_AFTER;
-  std::unordered_set<ArtMethod*> deoptimized_methods_ GUARDED_BY(GetDeoptimizedMethodsLock());
+  std::unordered_set<ArtMethod*> deoptimized_methods_ GUARDED_BY(Locks::mutator_lock_);
 
   // Current interpreter handler table. This is updated each time the thread state flags are
   // modified.
@@ -734,36 +758,13 @@
 
   friend class InstrumentationTest;  // For GetCurrentInstrumentationLevel and ConfigureStubs.
   friend class InstrumentationStackPopper;  // For popping instrumentation frames.
-  friend void InstrumentationInstallStack(Thread*, void*, bool);
+  friend void InstrumentationInstallStack(Thread*, bool);
 
   DISALLOW_COPY_AND_ASSIGN(Instrumentation);
 };
 std::ostream& operator<<(std::ostream& os, Instrumentation::InstrumentationEvent rhs);
 std::ostream& operator<<(std::ostream& os, Instrumentation::InstrumentationLevel rhs);
 
-// An element in the instrumentation side stack maintained in art::Thread.
-struct InstrumentationStackFrame {
-  InstrumentationStackFrame(mirror::Object* this_object,
-                            ArtMethod* method,
-                            uintptr_t return_pc,
-                            bool interpreter_entry,
-                            uint64_t force_deopt_id)
-      : this_object_(this_object),
-        method_(method),
-        return_pc_(return_pc),
-        interpreter_entry_(interpreter_entry),
-        force_deopt_id_(force_deopt_id) {
-  }
-
-  std::string Dump() const REQUIRES_SHARED(Locks::mutator_lock_);
-
-  mirror::Object* this_object_;
-  ArtMethod* method_;
-  uintptr_t return_pc_;
-  bool interpreter_entry_;
-  uint64_t force_deopt_id_;
-};
-
 }  // namespace instrumentation
 }  // namespace art
 
diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc
index b0a81b6..fc05298 100644
--- a/runtime/instrumentation_test.cc
+++ b/runtime/instrumentation_test.cc
@@ -77,7 +77,6 @@
   }
 
   void MethodUnwind(Thread* thread ATTRIBUTE_UNUSED,
-                    Handle<mirror::Object> this_object ATTRIBUTE_UNUSED,
                     ArtMethod* method ATTRIBUTE_UNUSED,
                     uint32_t dex_pc ATTRIBUTE_UNUSED)
       override REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -184,6 +183,10 @@
   static constexpr const char* kClientOneKey = "TestClient1";
   static constexpr const char* kClientTwoKey = "TestClient2";
 
+  InstrumentationTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
   void CheckConfigureStubs(const char* key, Instrumentation::InstrumentationLevel level) {
     ScopedObjectAccess soa(Thread::Current());
     instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
@@ -223,7 +226,7 @@
 
     mirror::Object* const event_obj = nullptr;
     const uint32_t event_dex_pc = 0;
-    ShadowFrameAllocaUniquePtr test_frame = CREATE_SHADOW_FRAME(0, nullptr, event_method, 0);
+    ShadowFrameAllocaUniquePtr test_frame = CREATE_SHADOW_FRAME(0, event_method, 0);
 
     // Check the listener is registered and is notified of the event.
     EXPECT_TRUE(HasEventListener(instr, instrumentation_event));
@@ -318,12 +321,13 @@
         REQUIRES_SHARED(Locks::mutator_lock_) {
     Runtime* runtime = Runtime::Current();
     instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
+    TestInstrumentationListener listener;
     ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     gc::ScopedGCCriticalSection gcs(self,
                                     gc::kGcCauseInstrumentation,
                                     gc::kCollectorTypeInstrumentation);
     ScopedSuspendAll ssa("EnableMethodTracing");
-    instrumentation->EnableMethodTracing(key, needs_interpreter);
+    instrumentation->EnableMethodTracing(key, &listener, needs_interpreter);
   }
 
   void DisableMethodTracing(Thread* self, const char* key)
@@ -387,7 +391,7 @@
         break;
       }
       case instrumentation::Instrumentation::kMethodUnwind:
-        instr->MethodUnwindEvent(self, obj, method, dex_pc);
+        instr->MethodUnwindEvent(self, method, dex_pc);
         break;
       case instrumentation::Instrumentation::kDexPcMoved:
         instr->DexPcMovedEvent(self, obj, method, dex_pc);
@@ -464,11 +468,10 @@
   instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
   ASSERT_NE(instr, nullptr);
 
-  EXPECT_FALSE(instr->AreExitStubsInstalled());
+  EXPECT_FALSE(instr->RunExitHooks());
+  EXPECT_FALSE(instr->EntryExitStubsInstalled());
   EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
   EXPECT_FALSE(instr->NeedsSlowInterpreterForListeners());
-  EXPECT_FALSE(instr->ShouldNotifyMethodEnterExitEvents());
-
 
   // Check there is no registered listener.
   EXPECT_FALSE(instr->HasDexPcListeners());
@@ -627,7 +630,7 @@
   DeoptimizeMethod(soa.Self(), method_to_deoptimize);
 
   EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
-  EXPECT_TRUE(instr->AreExitStubsInstalled());
+  EXPECT_TRUE(instr->RunExitHooks());
   EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize));
 
   constexpr const char* instrumentation_key = "DeoptimizeDirectMethod";
@@ -647,7 +650,8 @@
   DeoptimizeEverything(soa.Self(), instrumentation_key);
 
   EXPECT_TRUE(instr->AreAllMethodsDeoptimized());
-  EXPECT_TRUE(instr->AreExitStubsInstalled());
+  EXPECT_TRUE(instr->RunExitHooks());
+  EXPECT_TRUE(instr->InterpreterStubsInstalled());
 
   UndeoptimizeEverything(soa.Self(), instrumentation_key, true);
 
@@ -678,7 +682,7 @@
   EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing,
             GetCurrentInstrumentationLevel());
   EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
-  EXPECT_TRUE(instr->AreExitStubsInstalled());
+  EXPECT_TRUE(instr->RunExitHooks());
   EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize));
 
   constexpr const char* instrumentation_key = "MixedDeoptimization";
@@ -686,14 +690,14 @@
   EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter,
             GetCurrentInstrumentationLevel());
   EXPECT_TRUE(instr->AreAllMethodsDeoptimized());
-  EXPECT_TRUE(instr->AreExitStubsInstalled());
+  EXPECT_TRUE(instr->RunExitHooks());
   EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize));
 
   UndeoptimizeEverything(soa.Self(), instrumentation_key, false);
   EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing,
             GetCurrentInstrumentationLevel());
   EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
-  EXPECT_TRUE(instr->AreExitStubsInstalled());
+  EXPECT_TRUE(instr->RunExitHooks());
   EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize));
 
   UndeoptimizeMethod(soa.Self(), method_to_deoptimize, instrumentation_key, true);
@@ -714,7 +718,7 @@
   EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter,
             GetCurrentInstrumentationLevel());
   EXPECT_TRUE(instr->AreAllMethodsDeoptimized());
-  EXPECT_TRUE(instr->AreExitStubsInstalled());
+  EXPECT_TRUE(instr->RunExitHooks());
 
   DisableMethodTracing(soa.Self(), instrumentation_key);
   EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing,
@@ -730,10 +734,10 @@
 
   constexpr const char* instrumentation_key = "MethodTracing";
   EnableMethodTracing(soa.Self(), instrumentation_key, false);
-  EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+  EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks,
             GetCurrentInstrumentationLevel());
   EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
-  EXPECT_TRUE(instr->AreExitStubsInstalled());
+  EXPECT_TRUE(instr->RunExitHooks());
 
   DisableMethodTracing(soa.Self(), instrumentation_key);
   EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing,
@@ -776,9 +780,8 @@
 
   // Check we can switch to instrumentation stubs
   CheckConfigureStubs(kClientOneKey,
-                      Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
-  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
-                        1U);
+                      Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks);
+  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks, 1U);
 
   // Check we can disable instrumentation.
   CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
@@ -803,9 +806,8 @@
 
   // Configure stubs with instrumentation stubs.
   CheckConfigureStubs(kClientOneKey,
-                      Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
-  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
-                        1U);
+                      Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks);
+  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks, 1U);
 
   // Configure stubs with interpreter.
   CheckConfigureStubs(kClientOneKey,
@@ -827,9 +829,8 @@
 
   // Configure stubs with instrumentation stubs.
   CheckConfigureStubs(kClientOneKey,
-                      Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
-  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
-                        1U);
+                      Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks);
+  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks, 1U);
 
   // Check we can disable instrumentation.
   CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
@@ -842,9 +843,8 @@
 
   // Configure stubs with instrumentation stubs.
   CheckConfigureStubs(kClientOneKey,
-                      Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
-  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
-                        1U);
+                      Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks);
+  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks, 1U);
 
   // Configure stubs with interpreter.
   CheckConfigureStubs(kClientOneKey,
@@ -853,9 +853,8 @@
 
   // Configure stubs with instrumentation stubs again.
   CheckConfigureStubs(kClientOneKey,
-                      Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
-  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
-                        1U);
+                      Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks);
+  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks, 1U);
 
   // Check we can disable instrumentation.
   CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
@@ -878,21 +877,18 @@
 
   // Configure stubs with instrumentation stubs for 1st client.
   CheckConfigureStubs(kClientOneKey,
-                      Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
-  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
-                        1U);
+                      Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks);
+  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks, 1U);
 
   // Configure stubs with instrumentation stubs for 2nd client.
   CheckConfigureStubs(kClientTwoKey,
-                      Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
-  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
-                        2U);
+                      Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks);
+  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks, 2U);
 
   // 1st client requests instrumentation deactivation but 2nd client still needs
   // instrumentation stubs.
   CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
-  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
-                        1U);
+  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks, 1U);
 
   // 2nd client requests instrumentation deactivation
   CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
@@ -926,9 +922,8 @@
 
   // Configure stubs with instrumentation stubs for 1st client.
   CheckConfigureStubs(kClientOneKey,
-                      Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
-  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
-                        1U);
+                      Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks);
+  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks, 1U);
 
   // Configure stubs with interpreter for 2nd client.
   CheckConfigureStubs(kClientTwoKey,
@@ -954,14 +949,13 @@
 
   // Configure stubs with instrumentation stubs for 2nd client.
   CheckConfigureStubs(kClientTwoKey,
-                      Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+                      Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks);
   CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U);
 
   // 1st client requests instrumentation deactivation but 2nd client still needs
   // instrumentation stubs.
   CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
-  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
-                        1U);
+  CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithEntryExitHooks, 1U);
 
   // 2nd client requests instrumentation deactivation
   CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc
index f587d01..10b2d65 100644
--- a/runtime/intern_table.cc
+++ b/runtime/intern_table.cc
@@ -190,8 +190,8 @@
   {
     ScopedThreadSuspension sts(self, ThreadState::kWaitingWeakGcRootRead);
     MutexLock mu(self, *Locks::intern_table_lock_);
-    while ((!kUseReadBarrier && weak_root_state_ == gc::kWeakRootStateNoReadsOrWrites) ||
-           (kUseReadBarrier && !self->GetWeakRefAccessEnabled())) {
+    while ((!gUseReadBarrier && weak_root_state_ == gc::kWeakRootStateNoReadsOrWrites) ||
+           (gUseReadBarrier && !self->GetWeakRefAccessEnabled())) {
       weak_intern_condition_.Wait(self);
     }
   }
@@ -218,7 +218,7 @@
     if (strong != nullptr) {
       return strong;
     }
-    if (kUseReadBarrier ? self->GetWeakRefAccessEnabled()
+    if (gUseReadBarrier ? self->GetWeakRefAccessEnabled()
                         : weak_root_state_ != gc::kWeakRootStateNoReadsOrWrites) {
       break;
     }
@@ -230,7 +230,7 @@
     auto h = hs.NewHandleWrapper(&s);
     WaitUntilAccessible(self);
   }
-  if (!kUseReadBarrier) {
+  if (!gUseReadBarrier) {
     CHECK_EQ(weak_root_state_, gc::kWeakRootStateNormal);
   } else {
     CHECK(self->GetWeakRefAccessEnabled());
@@ -405,7 +405,10 @@
     if (new_object == nullptr) {
       it = set->erase(it);
     } else {
-      *it = GcRoot<mirror::String>(new_object->AsString());
+      // Don't use AsString as it does IsString check in debug builds which, in
+      // case of userfaultfd GC, is called when the object's content isn't
+      // thereyet.
+      *it = GcRoot<mirror::String>(ObjPtr<mirror::String>::DownCast(new_object));
       ++it;
     }
   }
@@ -426,7 +429,7 @@
 }
 
 void InternTable::ChangeWeakRootStateLocked(gc::WeakRootState new_state) {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   weak_root_state_ = new_state;
   if (new_state != gc::kWeakRootStateNoReadsOrWrites) {
     weak_intern_condition_.Broadcast(Thread::Current());
diff --git a/runtime/intern_table_test.cc b/runtime/intern_table_test.cc
index 9900c2a..d77ffcb 100644
--- a/runtime/intern_table_test.cc
+++ b/runtime/intern_table_test.cc
@@ -27,7 +27,12 @@
 
 namespace art {
 
-class InternTableTest : public CommonRuntimeTest {};
+class InternTableTest : public CommonRuntimeTest {
+ protected:
+  InternTableTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+};
 
 TEST_F(InternTableTest, Intern) {
   ScopedObjectAccess soa(Thread::Current());
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index 38c94ab..3ca531f 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -231,30 +231,22 @@
   }
 }
 
+NO_STACK_PROTECTOR
 static JValue ExecuteSwitch(Thread* self,
                             const CodeItemDataAccessor& accessor,
                             ShadowFrame& shadow_frame,
                             JValue result_register,
                             bool interpret_one_instruction) REQUIRES_SHARED(Locks::mutator_lock_) {
   if (Runtime::Current()->IsActiveTransaction()) {
-    if (shadow_frame.GetMethod()->SkipAccessChecks()) {
-      return ExecuteSwitchImpl<false, true>(
-          self, accessor, shadow_frame, result_register, interpret_one_instruction);
-    } else {
-      return ExecuteSwitchImpl<true, true>(
-          self, accessor, shadow_frame, result_register, interpret_one_instruction);
-    }
+    return ExecuteSwitchImpl<true>(
+        self, accessor, shadow_frame, result_register, interpret_one_instruction);
   } else {
-    if (shadow_frame.GetMethod()->SkipAccessChecks()) {
-      return ExecuteSwitchImpl<false, false>(
-          self, accessor, shadow_frame, result_register, interpret_one_instruction);
-    } else {
-      return ExecuteSwitchImpl<true, false>(
-          self, accessor, shadow_frame, result_register, interpret_one_instruction);
-    }
+    return ExecuteSwitchImpl<false>(
+        self, accessor, shadow_frame, result_register, interpret_one_instruction);
   }
 }
 
+NO_STACK_PROTECTOR
 static inline JValue Execute(
     Thread* self,
     const CodeItemDataAccessor& accessor,
@@ -265,41 +257,22 @@
   DCHECK(!shadow_frame.GetMethod()->IsAbstract());
   DCHECK(!shadow_frame.GetMethod()->IsNative());
 
+  // We cache the result of NeedsDexPcEvents in the shadow frame so we don't need to call
+  // NeedsDexPcEvents on every instruction for better performance. NeedsDexPcEvents only gets
+  // updated asynchronoulsy in a SuspendAll scope and any existing shadow frames are updated with
+  // new value. So it is safe to cache it here.
+  shadow_frame.SetNotifyDexPcMoveEvents(
+      Runtime::Current()->GetInstrumentation()->NeedsDexPcEvents(shadow_frame.GetMethod(), self));
+
   if (LIKELY(!from_deoptimize)) {  // Entering the method, but not via deoptimization.
     if (kIsDebugBuild) {
       CHECK_EQ(shadow_frame.GetDexPC(), 0u);
       self->AssertNoPendingException();
     }
-    instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
     ArtMethod *method = shadow_frame.GetMethod();
 
-    if (UNLIKELY(instrumentation->HasMethodEntryListeners())) {
-      instrumentation->MethodEnterEvent(self, method);
-      if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
-        // The caller will retry this invoke or ignore the result. Just return immediately without
-        // any value.
-        DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
-        JValue ret = JValue();
-        PerformNonStandardReturn<MonitorState::kNoMonitorsLocked>(
-            self, shadow_frame, ret, instrumentation, accessor.InsSize());
-        return ret;
-      }
-      if (UNLIKELY(self->IsExceptionPending())) {
-        instrumentation->MethodUnwindEvent(self,
-                                           shadow_frame.GetThisObject(accessor.InsSize()),
-                                           method,
-                                           0);
-        JValue ret = JValue();
-        if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
-          DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
-          PerformNonStandardReturn<MonitorState::kNoMonitorsLocked>(
-              self, shadow_frame, ret, instrumentation, accessor.InsSize());
-        }
-        return ret;
-      }
-    }
-
-    if (!stay_in_interpreter && !self->IsForceInterpreter()) {
+    // If we can continue in JIT and have JITed code available execute JITed code.
+    if (!stay_in_interpreter && !self->IsForceInterpreter() && !shadow_frame.GetForcePopFrame()) {
       jit::Jit* jit = Runtime::Current()->GetJit();
       if (jit != nullptr) {
         jit->MethodEntered(self, shadow_frame.GetMethod());
@@ -320,6 +293,40 @@
         }
       }
     }
+
+    instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+    if (UNLIKELY(instrumentation->HasMethodEntryListeners() || shadow_frame.GetForcePopFrame())) {
+      instrumentation->MethodEnterEvent(self, method);
+      if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
+        // The caller will retry this invoke or ignore the result. Just return immediately without
+        // any value.
+        DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
+        JValue ret = JValue();
+        PerformNonStandardReturn(self,
+                                 shadow_frame,
+                                 ret,
+                                 instrumentation,
+                                 accessor.InsSize(),
+                                 /* unlock_monitors= */ false);
+        return ret;
+      }
+      if (UNLIKELY(self->IsExceptionPending())) {
+        instrumentation->MethodUnwindEvent(self,
+                                           method,
+                                           0);
+        JValue ret = JValue();
+        if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
+          DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
+          PerformNonStandardReturn(self,
+                                   shadow_frame,
+                                   ret,
+                                   instrumentation,
+                                   accessor.InsSize(),
+                                   /* unlock_monitors= */ false);
+        }
+        return ret;
+      }
+    }
   }
 
   ArtMethod* method = shadow_frame.GetMethod();
@@ -366,7 +373,7 @@
     num_ins = accessor.InsSize();
   } else if (!method->IsInvokable()) {
     self->EndAssertNoThreadSuspension(old_cause);
-    method->ThrowInvocationTimeError();
+    method->ThrowInvocationTimeError(receiver);
     return;
   } else {
     DCHECK(method->IsNative()) << method->PrettyMethod();
@@ -377,11 +384,9 @@
     }
   }
   // Set up shadow frame with matching number of reference slots to vregs.
-  ShadowFrame* last_shadow_frame = self->GetManagedStack()->GetTopShadowFrame();
   ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
-      CREATE_SHADOW_FRAME(num_regs, last_shadow_frame, method, /* dex pc */ 0);
+      CREATE_SHADOW_FRAME(num_regs, method, /* dex pc */ 0);
   ShadowFrame* shadow_frame = shadow_frame_unique_ptr.get();
-  self->PushShadowFrame(shadow_frame);
 
   size_t cur_reg = num_regs - num_ins;
   if (!method->IsStatic()) {
@@ -413,21 +418,10 @@
     }
   }
   self->EndAssertNoThreadSuspension(old_cause);
-  // Do this after populating the shadow frame in case EnsureInitialized causes a GC.
-  if (method->IsStatic()) {
-    ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass();
-    if (UNLIKELY(!declaring_class->IsVisiblyInitialized())) {
-      StackHandleScope<1> hs(self);
-      Handle<mirror::Class> h_class(hs.NewHandle(declaring_class));
-      if (UNLIKELY(!Runtime::Current()->GetClassLinker()->EnsureInitialized(
-                        self, h_class, /*can_init_fields=*/ true, /*can_init_parents=*/ true))) {
-        CHECK(self->IsExceptionPending());
-        self->PopShadowFrame();
-        return;
-      }
-      DCHECK(h_class->IsInitializing());
-    }
+  if (!EnsureInitialized(self, shadow_frame)) {
+    return;
   }
+  self->PushShadowFrame(shadow_frame);
   if (LIKELY(!method->IsNative())) {
     JValue r = Execute(self, accessor, *shadow_frame, JValue(), stay_in_interpreter);
     if (result != nullptr) {
@@ -476,6 +470,7 @@
     const uint32_t dex_pc = shadow_frame->GetDexPC();
     uint32_t new_dex_pc = dex_pc;
     if (UNLIKELY(self->IsExceptionPending())) {
+      DCHECK(self->GetException() != Thread::GetDeoptimizationException());
       // If we deoptimize from the QuickExceptionHandler, we already reported the exception throw
       // event to the instrumentation. Skip throw listeners for the first frame. The deopt check
       // should happen after the throw listener is called as throw listener can trigger a
@@ -514,7 +509,7 @@
         new_dex_pc = dex_pc + instr->SizeInCodeUnits();
       } else if (instr->IsInvoke()) {
         DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault);
-        if (IsStringInit(instr, shadow_frame->GetMethod())) {
+        if (IsStringInit(*instr, shadow_frame->GetMethod())) {
           uint16_t this_obj_vreg = GetReceiverRegisterForStringInit(instr);
           // Move the StringFactory.newStringFromChars() result into the register representing
           // "this object" when invoking the string constructor in the original dex instruction.
@@ -569,6 +564,7 @@
   ret_val->SetJ(value.GetJ());
 }
 
+NO_STACK_PROTECTOR
 JValue EnterInterpreterFromEntryPoint(Thread* self, const CodeItemDataAccessor& accessor,
                                       ShadowFrame* shadow_frame) {
   DCHECK_EQ(self, Thread::Current());
@@ -585,6 +581,7 @@
   return Execute(self, accessor, *shadow_frame, JValue());
 }
 
+NO_STACK_PROTECTOR
 void ArtInterpreterToInterpreterBridge(Thread* self,
                                        const CodeItemDataAccessor& accessor,
                                        ShadowFrame* shadow_frame,
@@ -596,23 +593,6 @@
   }
 
   self->PushShadowFrame(shadow_frame);
-  ArtMethod* method = shadow_frame->GetMethod();
-  // Ensure static methods are initialized.
-  const bool is_static = method->IsStatic();
-  if (is_static) {
-    ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass();
-    if (UNLIKELY(!declaring_class->IsVisiblyInitialized())) {
-      StackHandleScope<1> hs(self);
-      Handle<mirror::Class> h_class(hs.NewHandle(declaring_class));
-      if (UNLIKELY(!Runtime::Current()->GetClassLinker()->EnsureInitialized(
-                        self, h_class, /*can_init_fields=*/ true, /*can_init_parents=*/ true))) {
-        DCHECK(self->IsExceptionPending());
-        self->PopShadowFrame();
-        return;
-      }
-      DCHECK(h_class->IsInitializing());
-    }
-  }
 
   if (LIKELY(!shadow_frame->GetMethod()->IsNative())) {
     result->SetJ(Execute(self, accessor, *shadow_frame, JValue()).GetJ());
@@ -620,6 +600,7 @@
     // We don't expect to be asked to interpret native code (which is entered via a JNI compiler
     // generated stub) except during testing and image writing.
     CHECK(!Runtime::Current()->IsStarted());
+    bool is_static = shadow_frame->GetMethod()->IsStatic();
     ObjPtr<mirror::Object> receiver = is_static ? nullptr : shadow_frame->GetVRegReference(0);
     uint32_t* args = shadow_frame->GetVRegArgs(is_static ? 0 : 1);
     UnstartedRuntime::Jni(self, shadow_frame->GetMethod(), receiver.Ptr(), args, result);
diff --git a/runtime/interpreter/interpreter_cache-inl.h b/runtime/interpreter/interpreter_cache-inl.h
index cea8157..804d382 100644
--- a/runtime/interpreter/interpreter_cache-inl.h
+++ b/runtime/interpreter/interpreter_cache-inl.h
@@ -35,13 +35,9 @@
 
 inline void InterpreterCache::Set(Thread* self, const void* key, size_t value) {
   DCHECK(self->GetInterpreterCache() == this) << "Must be called from owning thread";
-
-  // For simplicity, only update the cache if weak ref accesses are enabled. If
-  // they are disabled, this means the GC is processing the cache, and is
-  // reading it concurrently.
-  if (kUseReadBarrier && self->GetWeakRefAccessEnabled()) {
-    data_[IndexOf(key)] = Entry{key, value};
-  }
+  // Simple store works here as the cache is always read/written by the owning
+  // thread only (or in a stop-the-world pause).
+  data_[IndexOf(key)] = Entry{key, value};
 }
 
 }  // namespace art
diff --git a/runtime/interpreter/interpreter_cache.cc b/runtime/interpreter/interpreter_cache.cc
index 450edba..7e7b294 100644
--- a/runtime/interpreter/interpreter_cache.cc
+++ b/runtime/interpreter/interpreter_cache.cc
@@ -22,7 +22,13 @@
 void InterpreterCache::Clear(Thread* owning_thread) {
   DCHECK(owning_thread->GetInterpreterCache() == this);
   DCHECK(owning_thread == Thread::Current() || owning_thread->IsSuspended());
-  data_.fill(Entry{});
+  // Avoid using std::fill (or its variant) as there could be a concurrent sweep
+  // happening by the GC thread and these functions may clear partially.
+  for (Entry& entry : data_) {
+    std::atomic<const void*>* atomic_key_addr =
+        reinterpret_cast<std::atomic<const void*>*>(&entry.first);
+    atomic_key_addr->store(nullptr, std::memory_order_relaxed);
+  }
 }
 
 }  // namespace art
diff --git a/runtime/interpreter/interpreter_cache.h b/runtime/interpreter/interpreter_cache.h
index c57d023..8714bc6 100644
--- a/runtime/interpreter/interpreter_cache.h
+++ b/runtime/interpreter/interpreter_cache.h
@@ -47,7 +47,7 @@
 class ALIGNED(16) InterpreterCache {
  public:
   // Aligned since we load the whole entry in single assembly instruction.
-  typedef std::pair<const void*, size_t> Entry ALIGNED(2 * sizeof(size_t));
+  using Entry ALIGNED(2 * sizeof(size_t)) = std::pair<const void*, size_t>;
 
   // 2x size increase/decrease corresponds to ~0.5% interpreter performance change.
   // Value of 256 has around 75% cache hit rate.
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index c8a87c1..ac9980f 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -92,10 +92,6 @@
   }
 
   const void* code = method->GetEntryPointFromQuickCompiledCode();
-  if (code == GetQuickInstrumentationEntryPoint()) {
-    code = Runtime::Current()->GetInstrumentation()->GetCodeForInvoke(method);
-  }
-
   return Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(code);
 }
 
@@ -185,7 +181,6 @@
       // Exception is not caught by the current method. We will unwind to the
       // caller. Notify any instrumentation listener.
       instrumentation->MethodUnwindEvent(self,
-                                         shadow_frame.GetThisObject(),
                                          shadow_frame.GetMethod(),
                                          shadow_frame.GetDexPC());
     }
@@ -236,14 +231,16 @@
 // about ALWAYS_INLINE (-Werror, -Wgcc-compat) in definitions.
 //
 
-template <bool is_range, bool do_assignability_check>
+template <bool is_range>
+NO_STACK_PROTECTOR
 static ALWAYS_INLINE bool DoCallCommon(ArtMethod* called_method,
                                        Thread* self,
                                        ShadowFrame& shadow_frame,
                                        JValue* result,
                                        uint16_t number_of_inputs,
                                        uint32_t (&arg)[Instruction::kMaxVarArgRegs],
-                                       uint32_t vregC) REQUIRES_SHARED(Locks::mutator_lock_);
+                                       uint32_t vregC,
+                                       bool string_init) REQUIRES_SHARED(Locks::mutator_lock_);
 
 template <bool is_range>
 ALWAYS_INLINE void CopyRegisters(ShadowFrame& caller_frame,
@@ -255,6 +252,7 @@
 
 // END DECLARATIONS.
 
+NO_STACK_PROTECTOR
 void ArtInterpreterToCompiledCodeBridge(Thread* self,
                                         ArtMethod* caller,
                                         ShadowFrame* shadow_frame,
@@ -262,25 +260,6 @@
                                         JValue* result)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ArtMethod* method = shadow_frame->GetMethod();
-  // Ensure static methods are initialized.
-  if (method->IsStatic()) {
-    ObjPtr<mirror::Class> declaringClass = method->GetDeclaringClass();
-    if (UNLIKELY(!declaringClass->IsVisiblyInitialized())) {
-      self->PushShadowFrame(shadow_frame);
-      StackHandleScope<1> hs(self);
-      Handle<mirror::Class> h_class(hs.NewHandle(declaringClass));
-      if (UNLIKELY(!Runtime::Current()->GetClassLinker()->EnsureInitialized(
-                        self, h_class, /*can_init_fields=*/ true, /*can_init_parents=*/ true))) {
-        self->PopShadowFrame();
-        DCHECK(self->IsExceptionPending());
-        return;
-      }
-      self->PopShadowFrame();
-      DCHECK(h_class->IsInitializing());
-      // Reload from shadow frame in case the method moved, this is faster than adding a handle.
-      method = shadow_frame->GetMethod();
-    }
-  }
   // Basic checks for the arg_offset. If there's no code item, the arg_offset must be 0. Otherwise,
   // check that the arg_offset isn't greater than the number of registers. A stronger check is
   // difficult since the frame may contain space for all the registers in the method, or only enough
@@ -1018,11 +997,9 @@
   // Set-up a shadow frame for invoking the bootstrap method handle.
   ShadowFrameAllocaUniquePtr bootstrap_frame =
       CREATE_SHADOW_FRAME(call_site_type->NumberOfVRegs(),
-                          nullptr,
                           referrer,
                           shadow_frame.GetDexPC());
-  ScopedStackedShadowFramePusher pusher(
-      self, bootstrap_frame.get(), StackedShadowFrameType::kShadowFrameUnderConstruction);
+  ScopedStackedShadowFramePusher pusher(self, bootstrap_frame.get());
   ShadowFrameSetter setter(bootstrap_frame.get(), 0u);
 
   // The first parameter is a MethodHandles lookup instance.
@@ -1205,23 +1182,15 @@
   }
 }
 
-template <bool is_range,
-          bool do_assignability_check>
+template <bool is_range>
 static inline bool DoCallCommon(ArtMethod* called_method,
                                 Thread* self,
                                 ShadowFrame& shadow_frame,
                                 JValue* result,
                                 uint16_t number_of_inputs,
                                 uint32_t (&arg)[Instruction::kMaxVarArgRegs],
-                                uint32_t vregC) {
-  bool string_init = false;
-  // Replace calls to String.<init> with equivalent StringFactory call.
-  if (UNLIKELY(called_method->GetDeclaringClass()->IsStringClass()
-               && called_method->IsConstructor())) {
-    called_method = WellKnownClasses::StringInitToStringFactory(called_method);
-    string_init = true;
-  }
-
+                                uint32_t vregC,
+                                bool string_init) {
   // Compute method information.
   CodeItemDataAccessor accessor(called_method->DexInstructionData());
   // Number of registers for the callee's call frame.
@@ -1288,16 +1257,15 @@
   // Allocate shadow frame on the stack.
   const char* old_cause = self->StartAssertNoThreadSuspension("DoCallCommon");
   ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
-      CREATE_SHADOW_FRAME(num_regs, &shadow_frame, called_method, /* dex pc */ 0);
+      CREATE_SHADOW_FRAME(num_regs, called_method, /* dex pc */ 0);
   ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
 
   // Initialize new shadow frame by copying the registers from the callee shadow frame.
-  if (do_assignability_check) {
+  if (!shadow_frame.GetMethod()->SkipAccessChecks()) {
     // Slow path.
     // We might need to do class loading, which incurs a thread state change to kNative. So
     // register the shadow frame as under construction and allow suspension again.
-    ScopedStackedShadowFramePusher pusher(
-        self, new_shadow_frame, StackedShadowFrameType::kShadowFrameUnderConstruction);
+    ScopedStackedShadowFramePusher pusher(self, new_shadow_frame);
     self->EndAssertNoThreadSuspension(old_cause);
 
     // ArtMethod here is needed to check type information of the call site against the callee.
@@ -1336,7 +1304,7 @@
         // Handle Object references. 1 virtual register slot.
         case 'L': {
           ObjPtr<mirror::Object> o = shadow_frame.GetVRegReference(src_reg);
-          if (do_assignability_check && o != nullptr) {
+          if (o != nullptr) {
             const dex::TypeIndex type_idx = params->GetTypeItem(shorty_pos).type_idx_;
             ObjPtr<mirror::Class> arg_type = method->GetDexCache()->GetResolvedType(type_idx);
             if (arg_type == nullptr) {
@@ -1410,9 +1378,15 @@
   return !self->IsExceptionPending();
 }
 
-template<bool is_range, bool do_assignability_check>
-bool DoCall(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame,
-            const Instruction* inst, uint16_t inst_data, JValue* result) {
+template<bool is_range>
+NO_STACK_PROTECTOR
+bool DoCall(ArtMethod* called_method,
+            Thread* self,
+            ShadowFrame& shadow_frame,
+            const Instruction* inst,
+            uint16_t inst_data,
+            bool is_string_init,
+            JValue* result) {
   // Argument word count.
   const uint16_t number_of_inputs =
       (is_range) ? inst->VRegA_3rc(inst_data) : inst->VRegA_35c(inst_data);
@@ -1428,12 +1402,18 @@
     inst->GetVarArgs(arg, inst_data);
   }
 
-  return DoCallCommon<is_range, do_assignability_check>(
-      called_method, self, shadow_frame,
-      result, number_of_inputs, arg, vregC);
+  return DoCallCommon<is_range>(
+      called_method,
+      self,
+      shadow_frame,
+      result,
+      number_of_inputs,
+      arg,
+      vregC,
+      is_string_init);
 }
 
-template <bool is_range, bool do_access_check, bool transaction_active>
+template <bool is_range, bool transaction_active>
 bool DoFilledNewArray(const Instruction* inst,
                       const ShadowFrame& shadow_frame,
                       Thread* self,
@@ -1450,6 +1430,7 @@
     return false;
   }
   uint16_t type_idx = is_range ? inst->VRegB_3rc() : inst->VRegB_35c();
+  bool do_access_check = !shadow_frame.GetMethod()->SkipAccessChecks();
   ObjPtr<mirror::Class> array_class = ResolveVerifyAndClinit(dex::TypeIndex(type_idx),
                                                              shadow_frame.GetMethod(),
                                                              self,
@@ -1554,17 +1535,50 @@
   }
 }
 
+void UnlockHeldMonitors(Thread* self, ShadowFrame* shadow_frame)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  DCHECK(shadow_frame->GetForcePopFrame() || Runtime::Current()->IsTransactionAborted());
+  // Unlock all monitors.
+  if (shadow_frame->GetMethod()->MustCountLocks()) {
+    DCHECK(!shadow_frame->GetMethod()->SkipAccessChecks());
+    // Get the monitors from the shadow-frame monitor-count data.
+    shadow_frame->GetLockCountData().VisitMonitors(
+      [&](mirror::Object** obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+        // Since we don't use the 'obj' pointer after the DoMonitorExit everything should be fine
+        // WRT suspension.
+        DoMonitorExit(self, shadow_frame, *obj);
+      });
+  } else {
+    std::vector<verifier::MethodVerifier::DexLockInfo> locks;
+    verifier::MethodVerifier::FindLocksAtDexPc(shadow_frame->GetMethod(),
+                                               shadow_frame->GetDexPC(),
+                                               &locks,
+                                               Runtime::Current()->GetTargetSdkVersion());
+    for (const auto& reg : locks) {
+      if (UNLIKELY(reg.dex_registers.empty())) {
+        LOG(ERROR) << "Unable to determine reference locked by "
+                   << shadow_frame->GetMethod()->PrettyMethod() << " at pc "
+                   << shadow_frame->GetDexPC();
+      } else {
+        DoMonitorExit(
+            self, shadow_frame, shadow_frame->GetVRegReference(*reg.dex_registers.begin()));
+      }
+    }
+  }
+}
+
 // Explicit DoCall template function declarations.
-#define EXPLICIT_DO_CALL_TEMPLATE_DECL(_is_range, _do_assignability_check)                      \
-  template REQUIRES_SHARED(Locks::mutator_lock_)                                                \
-  bool DoCall<_is_range, _do_assignability_check>(ArtMethod* method, Thread* self,              \
-                                                  ShadowFrame& shadow_frame,                    \
-                                                  const Instruction* inst, uint16_t inst_data,  \
-                                                  JValue* result)
-EXPLICIT_DO_CALL_TEMPLATE_DECL(false, false);
-EXPLICIT_DO_CALL_TEMPLATE_DECL(false, true);
-EXPLICIT_DO_CALL_TEMPLATE_DECL(true, false);
-EXPLICIT_DO_CALL_TEMPLATE_DECL(true, true);
+#define EXPLICIT_DO_CALL_TEMPLATE_DECL(_is_range)                      \
+  template REQUIRES_SHARED(Locks::mutator_lock_)                       \
+  bool DoCall<_is_range>(ArtMethod* method,                            \
+                         Thread* self,                                 \
+                         ShadowFrame& shadow_frame,                    \
+                         const Instruction* inst,                      \
+                         uint16_t inst_data,                           \
+                         bool string_init,                             \
+                         JValue* result)
+EXPLICIT_DO_CALL_TEMPLATE_DECL(false);
+EXPLICIT_DO_CALL_TEMPLATE_DECL(true);
 #undef EXPLICIT_DO_CALL_TEMPLATE_DECL
 
 // Explicit DoInvokePolymorphic template function declarations.
@@ -1578,16 +1592,15 @@
 #undef EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL
 
 // Explicit DoFilledNewArray template function declarations.
-#define EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(_is_range_, _check, _transaction_active)       \
+#define EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(_is_range_, _transaction_active)               \
   template REQUIRES_SHARED(Locks::mutator_lock_)                                                  \
-  bool DoFilledNewArray<_is_range_, _check, _transaction_active>(const Instruction* inst,         \
-                                                                 const ShadowFrame& shadow_frame, \
-                                                                 Thread* self, JValue* result)
-#define EXPLICIT_DO_FILLED_NEW_ARRAY_ALL_TEMPLATE_DECL(_transaction_active)       \
-  EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(false, false, _transaction_active);  \
-  EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(false, true, _transaction_active);   \
-  EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(true, false, _transaction_active);   \
-  EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(true, true, _transaction_active)
+  bool DoFilledNewArray<_is_range_, _transaction_active>(const Instruction* inst,                 \
+                                                         const ShadowFrame& shadow_frame,         \
+                                                         Thread* self,                            \
+                                                         JValue* result)
+#define EXPLICIT_DO_FILLED_NEW_ARRAY_ALL_TEMPLATE_DECL(_transaction_active)                       \
+  EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(false, _transaction_active);                         \
+  EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(true, _transaction_active)
 EXPLICIT_DO_FILLED_NEW_ARRAY_ALL_TEMPLATE_DECL(false);
 EXPLICIT_DO_FILLED_NEW_ARRAY_ALL_TEMPLATE_DECL(true);
 #undef EXPLICIT_DO_FILLED_NEW_ARRAY_ALL_TEMPLATE_DECL
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 0b91120..b8d68179 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -20,7 +20,6 @@
 #include "android-base/macros.h"
 #include "instrumentation.h"
 #include "interpreter.h"
-#include "interpreter_intrinsics.h"
 #include "transaction.h"
 
 #include <math.h>
@@ -71,7 +70,6 @@
 void ThrowNullPointerExceptionFromInterpreter()
     REQUIRES_SHARED(Locks::mutator_lock_);
 
-template <bool kMonitorCounting>
 static inline void DoMonitorEnter(Thread* self, ShadowFrame* frame, ObjPtr<mirror::Object> ref)
     NO_THREAD_SAFETY_ANALYSIS
     REQUIRES(!Roles::uninterruptible_) {
@@ -85,28 +83,29 @@
     DCHECK(unlocked);
     return;
   }
-  if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) {
+  if (frame->GetMethod()->MustCountLocks()) {
+    DCHECK(!frame->GetMethod()->SkipAccessChecks());
     frame->GetLockCountData().AddMonitor(self, h_ref.Get());
   }
 }
 
-template <bool kMonitorCounting>
 static inline void DoMonitorExit(Thread* self, ShadowFrame* frame, ObjPtr<mirror::Object> ref)
     NO_THREAD_SAFETY_ANALYSIS
     REQUIRES(!Roles::uninterruptible_) {
   StackHandleScope<1> hs(self);
   Handle<mirror::Object> h_ref(hs.NewHandle(ref));
   h_ref->MonitorExit(self);
-  if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) {
+  if (frame->GetMethod()->MustCountLocks()) {
+    DCHECK(!frame->GetMethod()->SkipAccessChecks());
     frame->GetLockCountData().RemoveMonitorOrThrow(self, h_ref.Get());
   }
 }
 
-template <bool kMonitorCounting>
 static inline bool DoMonitorCheckOnExit(Thread* self, ShadowFrame* frame)
     NO_THREAD_SAFETY_ANALYSIS
     REQUIRES(!Roles::uninterruptible_) {
-  if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) {
+  if (frame->GetMethod()->MustCountLocks()) {
+    DCHECK(!frame->GetMethod()->SkipAccessChecks());
     return frame->GetLockCountData().CheckAllMonitorsReleasedOrThrow(self);
   }
   return true;
@@ -125,9 +124,14 @@
 // Invokes the given method. This is part of the invocation support and is used by DoInvoke,
 // DoFastInvoke and DoInvokeVirtualQuick functions.
 // Returns true on success, otherwise throws an exception and returns false.
-template<bool is_range, bool do_assignability_check>
-bool DoCall(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame,
-            const Instruction* inst, uint16_t inst_data, JValue* result);
+template<bool is_range>
+bool DoCall(ArtMethod* called_method,
+            Thread* self,
+            ShadowFrame& shadow_frame,
+            const Instruction* inst,
+            uint16_t inst_data,
+            bool string_init,
+            JValue* result);
 
 // Called by the switch interpreter to know if we can stay in it.
 bool ShouldStayInSwitchInterpreter(ArtMethod* method)
@@ -153,54 +157,16 @@
   return ins->HasMethodExitListeners() || ins->HasWatchedFramePopListeners();
 }
 
-// NO_INLINE so we won't bloat the interpreter with this very cold lock-release code.
-template <bool kMonitorCounting>
-static NO_INLINE void UnlockHeldMonitors(Thread* self, ShadowFrame* shadow_frame)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  DCHECK(shadow_frame->GetForcePopFrame() ||
-         Runtime::Current()->IsTransactionAborted());
-  // Unlock all monitors.
-  if (kMonitorCounting && shadow_frame->GetMethod()->MustCountLocks()) {
-    // Get the monitors from the shadow-frame monitor-count data.
-    shadow_frame->GetLockCountData().VisitMonitors(
-      [&](mirror::Object** obj) REQUIRES_SHARED(Locks::mutator_lock_) {
-        // Since we don't use the 'obj' pointer after the DoMonitorExit everything should be fine
-        // WRT suspension.
-        DoMonitorExit<kMonitorCounting>(self, shadow_frame, *obj);
-      });
-  } else {
-    std::vector<verifier::MethodVerifier::DexLockInfo> locks;
-    verifier::MethodVerifier::FindLocksAtDexPc(shadow_frame->GetMethod(),
-                                                shadow_frame->GetDexPC(),
-                                                &locks,
-                                                Runtime::Current()->GetTargetSdkVersion());
-    for (const auto& reg : locks) {
-      if (UNLIKELY(reg.dex_registers.empty())) {
-        LOG(ERROR) << "Unable to determine reference locked by "
-                    << shadow_frame->GetMethod()->PrettyMethod() << " at pc "
-                    << shadow_frame->GetDexPC();
-      } else {
-        DoMonitorExit<kMonitorCounting>(
-            self, shadow_frame, shadow_frame->GetVRegReference(*reg.dex_registers.begin()));
-      }
-    }
-  }
-}
+COLD_ATTR void UnlockHeldMonitors(Thread* self, ShadowFrame* shadow_frame)
+    REQUIRES_SHARED(Locks::mutator_lock_);
 
-enum class MonitorState {
-  kNoMonitorsLocked,
-  kCountingMonitors,
-  kNormalMonitors,
-};
-
-template<MonitorState kMonitorState>
 static inline ALWAYS_INLINE void PerformNonStandardReturn(
       Thread* self,
       ShadowFrame& frame,
       JValue& result,
       const instrumentation::Instrumentation* instrumentation,
-      uint16_t num_dex_inst) REQUIRES_SHARED(Locks::mutator_lock_) {
-  static constexpr bool kMonitorCounting = (kMonitorState == MonitorState::kCountingMonitors);
+      uint16_t num_dex_inst,
+      bool unlock_monitors = true) REQUIRES_SHARED(Locks::mutator_lock_) {
   ObjPtr<mirror::Object> thiz(frame.GetThisObject(num_dex_inst));
   StackHandleScope<1u> hs(self);
   if (UNLIKELY(self->IsExceptionPending())) {
@@ -208,10 +174,10 @@
                  << self->GetException()->Dump();
     self->ClearException();
   }
-  if (kMonitorState != MonitorState::kNoMonitorsLocked) {
-    UnlockHeldMonitors<kMonitorCounting>(self, &frame);
+  if (unlock_monitors) {
+    UnlockHeldMonitors(self, &frame);
+    DoMonitorCheckOnExit(self, &frame);
   }
-  DoMonitorCheckOnExit<kMonitorCounting>(self, &frame);
   result = JValue();
   if (UNLIKELY(NeedsMethodExitEvent(instrumentation))) {
     SendMethodExitEvents(self, instrumentation, frame, frame.GetMethod(), result);
@@ -220,7 +186,7 @@
 
 // Handles all invoke-XXX/range instructions except for invoke-polymorphic[/range].
 // Returns true on success, otherwise throws an exception and returns false.
-template<InvokeType type, bool is_range, bool do_access_check, bool is_mterp>
+template<InvokeType type, bool is_range>
 static ALWAYS_INLINE bool DoInvoke(Thread* self,
                                    ShadowFrame& shadow_frame,
                                    const Instruction* inst,
@@ -231,63 +197,20 @@
   if (UNLIKELY(self->ObserveAsyncException())) {
     return false;
   }
-  const uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
-  const uint32_t vregC = (is_range) ? inst->VRegC_3rc() : inst->VRegC_35c();
+  const uint32_t vregC = is_range ? inst->VRegC_3rc() : inst->VRegC_35c();
+  ObjPtr<mirror::Object> obj = type == kStatic ? nullptr : shadow_frame.GetVRegReference(vregC);
   ArtMethod* sf_method = shadow_frame.GetMethod();
-
-  // Try to find the method in small thread-local cache first (only used when
-  // nterp is not used as mterp and nterp use the cache in an incompatible way).
-  InterpreterCache* tls_cache = self->GetInterpreterCache();
-  size_t tls_value;
-  ArtMethod* resolved_method;
-  if (!IsNterpSupported() && LIKELY(tls_cache->Get(self, inst, &tls_value))) {
-    resolved_method = reinterpret_cast<ArtMethod*>(tls_value);
-  } else {
-    ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
-    constexpr ClassLinker::ResolveMode resolve_mode =
-        do_access_check ? ClassLinker::ResolveMode::kCheckICCEAndIAE
-                        : ClassLinker::ResolveMode::kNoChecks;
-    resolved_method = class_linker->ResolveMethod<resolve_mode>(self, method_idx, sf_method, type);
-    if (UNLIKELY(resolved_method == nullptr)) {
-      CHECK(self->IsExceptionPending());
-      result->SetJ(0);
-      return false;
-    }
-    if (!IsNterpSupported()) {
-      tls_cache->Set(self, inst, reinterpret_cast<size_t>(resolved_method));
-    }
-  }
-
-  // Null pointer check and virtual method resolution.
-  ObjPtr<mirror::Object> receiver =
-      (type == kStatic) ? nullptr : shadow_frame.GetVRegReference(vregC);
-  ArtMethod* called_method;
-  called_method = FindMethodToCall<type, do_access_check>(
-      method_idx, resolved_method, &receiver, sf_method, self);
-  if (UNLIKELY(called_method == nullptr)) {
-    CHECK(self->IsExceptionPending());
-    result->SetJ(0);
-    return false;
-  }
-  if (UNLIKELY(!called_method->IsInvokable())) {
-    called_method->ThrowInvocationTimeError();
+  bool string_init = false;
+  ArtMethod* called_method = FindMethodToCall<type>(
+      self, sf_method, &obj, *inst, /* only_lookup_tls_cache= */ false, &string_init);
+  if (called_method == nullptr) {
+    DCHECK(self->IsExceptionPending());
     result->SetJ(0);
     return false;
   }
 
-  jit::Jit* jit = Runtime::Current()->GetJit();
-  if (is_mterp && !is_range && called_method->IsIntrinsic()) {
-    if (MterpHandleIntrinsic(&shadow_frame, called_method, inst, inst_data,
-                             shadow_frame.GetResultRegister())) {
-      if (jit != nullptr && sf_method != nullptr) {
-        jit->NotifyInterpreterToCompiledCodeTransition(self, sf_method);
-      }
-      return !self->IsExceptionPending();
-    }
-  }
-
-  return DoCall<is_range, do_access_check>(called_method, self, shadow_frame, inst, inst_data,
-                                           result);
+  return DoCall<is_range>(
+      called_method, self, shadow_frame, inst, inst_data, string_init, result);
 }
 
 static inline ObjPtr<mirror::MethodHandle> ResolveMethodHandle(Thread* self,
@@ -386,24 +309,77 @@
   return field_value;
 }
 
+extern "C" size_t NterpGetStaticField(Thread* self,
+                                      ArtMethod* caller,
+                                      const uint16_t* dex_pc_ptr,
+                                      size_t resolve_field_type);
+
+extern "C" uint32_t NterpGetInstanceFieldOffset(Thread* self,
+                                                ArtMethod* caller,
+                                                const uint16_t* dex_pc_ptr,
+                                                size_t resolve_field_type);
+
+static inline void GetFieldInfo(Thread* self,
+                                ArtMethod* caller,
+                                const uint16_t* dex_pc_ptr,
+                                bool is_static,
+                                bool resolve_field_type,
+                                ArtField** field,
+                                bool* is_volatile,
+                                MemberOffset* offset) {
+  size_t tls_value = 0u;
+  if (!self->GetInterpreterCache()->Get(self, dex_pc_ptr, &tls_value)) {
+    if (is_static) {
+      tls_value = NterpGetStaticField(self, caller, dex_pc_ptr, resolve_field_type);
+    } else {
+      tls_value = NterpGetInstanceFieldOffset(self, caller, dex_pc_ptr, resolve_field_type);
+    }
+
+    if (self->IsExceptionPending()) {
+      return;
+    }
+  }
+
+  if (is_static) {
+    DCHECK_NE(tls_value, 0u);
+    *is_volatile = ((tls_value & 1) != 0);
+    *field = reinterpret_cast<ArtField*>(tls_value & ~static_cast<size_t>(1u));
+    *offset = (*field)->GetOffset();
+  } else {
+    *is_volatile = (static_cast<int32_t>(tls_value) < 0);
+    *offset = MemberOffset(std::abs(static_cast<int32_t>(tls_value)));
+  }
+}
+
 // Handles iget-XXX and sget-XXX instructions.
 // Returns true on success, otherwise throws an exception and returns false.
-template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check,
+template<FindFieldType find_type,
+         Primitive::Type field_type,
          bool transaction_active = false>
-ALWAYS_INLINE bool DoFieldGet(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,
+ALWAYS_INLINE bool DoFieldGet(Thread* self,
+                              ShadowFrame& shadow_frame,
+                              const Instruction* inst,
                               uint16_t inst_data) REQUIRES_SHARED(Locks::mutator_lock_) {
   const bool is_static = (find_type == StaticObjectRead) || (find_type == StaticPrimitiveRead);
-  const uint32_t field_idx = is_static ? inst->VRegB_21c() : inst->VRegC_22c();
-  ArtMethod* method = shadow_frame.GetMethod();
-  ArtField* f = FindFieldFromCode<find_type, do_access_check>(
-      field_idx, method, self, Primitive::ComponentSize(field_type));
-  if (UNLIKELY(f == nullptr)) {
-    CHECK(self->IsExceptionPending());
+  bool should_report = Runtime::Current()->GetInstrumentation()->HasFieldReadListeners();
+  ArtField* field = nullptr;
+  MemberOffset offset(0u);
+  bool is_volatile;
+  GetFieldInfo(self,
+               shadow_frame.GetMethod(),
+               reinterpret_cast<const uint16_t*>(inst),
+               is_static,
+               /*resolve_field_type=*/ false,
+               &field,
+               &is_volatile,
+               &offset);
+  if (self->IsExceptionPending()) {
     return false;
   }
+
   ObjPtr<mirror::Object> obj;
   if (is_static) {
-    obj = f->GetDeclaringClass();
+    obj = field->GetDeclaringClass();
     if (transaction_active) {
       if (Runtime::Current()->GetTransaction()->ReadConstraint(obj)) {
         Runtime::Current()->AbortTransactionAndThrowAbortError(self, "Can't read static fields of "
@@ -413,40 +389,57 @@
     }
   } else {
     obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
-    if (UNLIKELY(obj == nullptr)) {
-      ThrowNullPointerExceptionForFieldAccess(f, method, true);
+    if (should_report || obj == nullptr) {
+      field = ResolveFieldWithAccessChecks(self,
+                                           Runtime::Current()->GetClassLinker(),
+                                           inst->VRegC_22c(),
+                                           shadow_frame.GetMethod(),
+                                           /* is_static= */ false,
+                                           /* is_put= */ false,
+                                           /* resolve_field_type= */ false);
+      if (obj == nullptr) {
+        ThrowNullPointerExceptionForFieldAccess(
+            field, shadow_frame.GetMethod(), /* is_read= */ true);
+        return false;
+      }
+      // Reload in case suspension happened during field resolution.
+      obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
+    }
+  }
+
+  uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
+  JValue result;
+  if (should_report) {
+    DCHECK(field != nullptr);
+    if (UNLIKELY(!DoFieldGetCommon<field_type>(self, shadow_frame, obj, field, &result))) {
+      // Instrumentation threw an error!
+      CHECK(self->IsExceptionPending());
       return false;
     }
   }
 
-  JValue result;
-  if (UNLIKELY(!DoFieldGetCommon<field_type>(self, shadow_frame, obj, f, &result))) {
-    // Instrumentation threw an error!
-    CHECK(self->IsExceptionPending());
-    return false;
-  }
-  uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
+#define FIELD_GET(prim, type, jtype, vreg)                                      \
+  case Primitive::kPrim ##prim:                                                 \
+    shadow_frame.SetVReg ##vreg(vregA,                                          \
+        should_report ? result.Get ##jtype()                                    \
+                      : is_volatile ? obj->GetField ## type ## Volatile(offset) \
+                                    : obj->GetField ##type(offset));            \
+    break;
+
   switch (field_type) {
-    case Primitive::kPrimBoolean:
-      shadow_frame.SetVReg(vregA, result.GetZ());
-      break;
-    case Primitive::kPrimByte:
-      shadow_frame.SetVReg(vregA, result.GetB());
-      break;
-    case Primitive::kPrimChar:
-      shadow_frame.SetVReg(vregA, result.GetC());
-      break;
-    case Primitive::kPrimShort:
-      shadow_frame.SetVReg(vregA, result.GetS());
-      break;
-    case Primitive::kPrimInt:
-      shadow_frame.SetVReg(vregA, result.GetI());
-      break;
-    case Primitive::kPrimLong:
-      shadow_frame.SetVRegLong(vregA, result.GetJ());
-      break;
+    FIELD_GET(Boolean, Boolean, Z, )
+    FIELD_GET(Byte, Byte, B, )
+    FIELD_GET(Char, Char, C, )
+    FIELD_GET(Short, Short, S, )
+    FIELD_GET(Int, 32, I, )
+    FIELD_GET(Long, 64, J, Long)
+#undef FIELD_GET
     case Primitive::kPrimNot:
-      shadow_frame.SetVRegReference(vregA, result.GetL());
+      shadow_frame.SetVRegReference(
+          vregA,
+          should_report ? result.GetL()
+                        : is_volatile ? obj->GetFieldObjectVolatile<mirror::Object>(offset)
+                                      : obj->GetFieldObject<mirror::Object>(offset));
       break;
     default:
       LOG(FATAL) << "Unreachable: " << field_type;
@@ -485,36 +478,57 @@
 
 // Handles iput-XXX and sput-XXX instructions.
 // Returns true on success, otherwise throws an exception and returns false.
-template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check,
-         bool transaction_active>
-ALWAYS_INLINE bool DoFieldPut(Thread* self, const ShadowFrame& shadow_frame,
-                              const Instruction* inst, uint16_t inst_data)
+template<FindFieldType find_type, Primitive::Type field_type, bool transaction_active>
+ALWAYS_INLINE bool DoFieldPut(Thread* self,
+                              const ShadowFrame& shadow_frame,
+                              const Instruction* inst,
+                              uint16_t inst_data)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  const bool do_assignability_check = do_access_check;
+  bool should_report = Runtime::Current()->GetInstrumentation()->HasFieldWriteListeners();
   bool is_static = (find_type == StaticObjectWrite) || (find_type == StaticPrimitiveWrite);
-  uint32_t field_idx = is_static ? inst->VRegB_21c() : inst->VRegC_22c();
-  ArtMethod* method = shadow_frame.GetMethod();
-  ArtField* f = FindFieldFromCode<find_type, do_access_check>(
-      field_idx, method, self, Primitive::ComponentSize(field_type));
-  if (UNLIKELY(f == nullptr)) {
-    CHECK(self->IsExceptionPending());
+  uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
+  bool resolve_field_type = (shadow_frame.GetVRegReference(vregA) != nullptr);
+  ArtField* field = nullptr;
+  MemberOffset offset(0u);
+  bool is_volatile;
+  GetFieldInfo(self,
+               shadow_frame.GetMethod(),
+               reinterpret_cast<const uint16_t*>(inst),
+               is_static,
+               resolve_field_type,
+               &field,
+               &is_volatile,
+               &offset);
+  if (self->IsExceptionPending()) {
     return false;
   }
+
   ObjPtr<mirror::Object> obj;
   if (is_static) {
-    obj = f->GetDeclaringClass();
+    obj = field->GetDeclaringClass();
   } else {
     obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
-    if (UNLIKELY(obj == nullptr)) {
-      ThrowNullPointerExceptionForFieldAccess(f, method, false);
-      return false;
+    if (should_report || obj == nullptr) {
+      field = ResolveFieldWithAccessChecks(self,
+                                           Runtime::Current()->GetClassLinker(),
+                                           inst->VRegC_22c(),
+                                           shadow_frame.GetMethod(),
+                                           /* is_static= */ false,
+                                           /* is_put= */ true,
+                                           resolve_field_type);
+      if (UNLIKELY(obj == nullptr)) {
+        ThrowNullPointerExceptionForFieldAccess(
+            field, shadow_frame.GetMethod(), /* is_read= */ false);
+        return false;
+      }
+      // Reload in case suspension happened during field resolution.
+      obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
     }
   }
   if (transaction_active && !CheckWriteConstraint(self, obj)) {
     return false;
   }
 
-  uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
   JValue value = GetFieldValue<field_type>(shadow_frame, vregA);
 
   if (transaction_active &&
@@ -522,12 +536,43 @@
       !CheckWriteValueConstraint(self, value.GetL())) {
     return false;
   }
+  if (should_report) {
+    return DoFieldPutCommon<field_type, transaction_active>(self,
+                                                            shadow_frame,
+                                                            obj,
+                                                            field,
+                                                            value);
+  }
+#define FIELD_SET(prim, type, jtype) \
+  case Primitive::kPrim ## prim: \
+    if (is_volatile) { \
+      obj->SetField ## type ## Volatile<transaction_active>(offset, value.Get ## jtype()); \
+    } else { \
+      obj->SetField ## type<transaction_active>(offset, value.Get ## jtype()); \
+    } \
+    break;
 
-  return DoFieldPutCommon<field_type, do_assignability_check, transaction_active>(self,
-                                                                                  shadow_frame,
-                                                                                  obj,
-                                                                                  f,
-                                                                                  value);
+  switch (field_type) {
+    FIELD_SET(Boolean, Boolean, Z)
+    FIELD_SET(Byte, Byte, B)
+    FIELD_SET(Char, Char, C)
+    FIELD_SET(Short, Short, S)
+    FIELD_SET(Int, 32, I)
+    FIELD_SET(Long, 64, J)
+    FIELD_SET(Not, Object, L)
+    case Primitive::kPrimVoid: {
+      LOG(FATAL) << "Unreachable " << field_type;
+      break;
+    }
+  }
+#undef FIELD_SET
+
+  if (transaction_active) {
+    if (UNLIKELY(self->IsExceptionPending())) {
+      return false;
+    }
+  }
+  return true;
 }
 
 // Handles string resolution for const-string and const-string-jumbo instructions. Also ensures the
@@ -631,13 +676,16 @@
 
 // Handles filled-new-array and filled-new-array-range instructions.
 // Returns true on success, otherwise throws an exception and returns false.
-template <bool is_range, bool do_access_check, bool transaction_active>
-bool DoFilledNewArray(const Instruction* inst, const ShadowFrame& shadow_frame,
-                      Thread* self, JValue* result);
+template <bool is_range, bool transaction_active>
+bool DoFilledNewArray(const Instruction* inst,
+                      const ShadowFrame& shadow_frame,
+                      Thread* self,
+                      JValue* result);
 
 // Handles packed-switch instruction.
 // Returns the branch offset to the next instruction to execute.
-static inline int32_t DoPackedSwitch(const Instruction* inst, const ShadowFrame& shadow_frame,
+static inline int32_t DoPackedSwitch(const Instruction* inst,
+                                     const ShadowFrame& shadow_frame,
                                      uint16_t inst_data)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(inst->Opcode() == Instruction::PACKED_SWITCH);
@@ -755,34 +803,6 @@
                                         uint16_t arg_offset,
                                         JValue* result);
 
-static inline bool IsStringInit(const DexFile* dex_file, uint32_t method_idx)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  const dex::MethodId& method_id = dex_file->GetMethodId(method_idx);
-  const char* class_name = dex_file->StringByTypeIdx(method_id.class_idx_);
-  const char* method_name = dex_file->GetMethodName(method_id);
-  // Instead of calling ResolveMethod() which has suspend point and can trigger
-  // GC, look up the method symbolically.
-  // Compare method's class name and method name against string init.
-  // It's ok since it's not allowed to create your own java/lang/String.
-  // TODO: verify that assumption.
-  if ((strcmp(class_name, "Ljava/lang/String;") == 0) &&
-      (strcmp(method_name, "<init>") == 0)) {
-    return true;
-  }
-  return false;
-}
-
-static inline bool IsStringInit(const Instruction* instr, ArtMethod* caller)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  if (instr->Opcode() == Instruction::INVOKE_DIRECT ||
-      instr->Opcode() == Instruction::INVOKE_DIRECT_RANGE) {
-    uint16_t callee_method_idx = (instr->Opcode() == Instruction::INVOKE_DIRECT_RANGE) ?
-        instr->VRegB_3rc() : instr->VRegB_35c();
-    return IsStringInit(caller->GetDexFile(), callee_method_idx);
-  }
-  return false;
-}
-
 // Set string value created from StringFactory.newStringFromXXX() into all aliases of
 // StringFactory.newEmptyString().
 void SetStringInitValueToAllAliases(ShadowFrame* shadow_frame,
diff --git a/runtime/interpreter/interpreter_intrinsics.cc b/runtime/interpreter/interpreter_intrinsics.cc
deleted file mode 100644
index c8344bc..0000000
--- a/runtime/interpreter/interpreter_intrinsics.cc
+++ /dev/null
@@ -1,678 +0,0 @@
-/*
- * 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.
- */
-
-#include "interpreter/interpreter_intrinsics.h"
-
-#include "dex/dex_instruction.h"
-#include "intrinsics_enum.h"
-#include "interpreter/interpreter_common.h"
-
-namespace art {
-namespace interpreter {
-
-
-#define BINARY_INTRINSIC(name, op, get1, get2, set)                 \
-static ALWAYS_INLINE bool name(ShadowFrame* shadow_frame,           \
-                               const Instruction* inst,             \
-                               uint16_t inst_data,                  \
-                               JValue* result_register)             \
-    REQUIRES_SHARED(Locks::mutator_lock_) {                         \
-  uint32_t arg[Instruction::kMaxVarArgRegs] = {};                   \
-  inst->GetVarArgs(arg, inst_data);                                 \
-  result_register->set(op(shadow_frame->get1, shadow_frame->get2)); \
-  return true;                                                      \
-}
-
-#define BINARY_II_INTRINSIC(name, op, set) \
-    BINARY_INTRINSIC(name, op, GetVReg(arg[0]), GetVReg(arg[1]), set)
-
-#define BINARY_JJ_INTRINSIC(name, op, set) \
-    BINARY_INTRINSIC(name, op, GetVRegLong(arg[0]), GetVRegLong(arg[2]), set)
-
-#define BINARY_JI_INTRINSIC(name, op, set) \
-    BINARY_INTRINSIC(name, op, GetVRegLong(arg[0]), GetVReg(arg[2]), set)
-
-#define UNARY_INTRINSIC(name, op, get, set)                  \
-static ALWAYS_INLINE bool name(ShadowFrame* shadow_frame,    \
-                               const Instruction* inst,      \
-                               uint16_t inst_data,           \
-                               JValue* result_register)      \
-    REQUIRES_SHARED(Locks::mutator_lock_) {                  \
-  uint32_t arg[Instruction::kMaxVarArgRegs] = {};            \
-  inst->GetVarArgs(arg, inst_data);                          \
-  result_register->set(op(shadow_frame->get(arg[0])));       \
-  return true;                                               \
-}
-
-
-// java.lang.Integer.reverse(I)I
-UNARY_INTRINSIC(MterpIntegerReverse, ReverseBits32, GetVReg, SetI);
-
-// java.lang.Integer.reverseBytes(I)I
-UNARY_INTRINSIC(MterpIntegerReverseBytes, BSWAP, GetVReg, SetI);
-
-// java.lang.Integer.bitCount(I)I
-UNARY_INTRINSIC(MterpIntegerBitCount, POPCOUNT, GetVReg, SetI);
-
-// java.lang.Integer.compare(II)I
-BINARY_II_INTRINSIC(MterpIntegerCompare, Compare, SetI);
-
-// java.lang.Integer.highestOneBit(I)I
-UNARY_INTRINSIC(MterpIntegerHighestOneBit, HighestOneBitValue, GetVReg, SetI);
-
-// java.lang.Integer.LowestOneBit(I)I
-UNARY_INTRINSIC(MterpIntegerLowestOneBit, LowestOneBitValue, GetVReg, SetI);
-
-// java.lang.Integer.numberOfLeadingZeros(I)I
-UNARY_INTRINSIC(MterpIntegerNumberOfLeadingZeros, JAVASTYLE_CLZ, GetVReg, SetI);
-
-// java.lang.Integer.numberOfTrailingZeros(I)I
-UNARY_INTRINSIC(MterpIntegerNumberOfTrailingZeros, JAVASTYLE_CTZ, GetVReg, SetI);
-
-// java.lang.Integer.rotateRight(II)I
-BINARY_II_INTRINSIC(MterpIntegerRotateRight, (Rot<int32_t, false>), SetI);
-
-// java.lang.Integer.rotateLeft(II)I
-BINARY_II_INTRINSIC(MterpIntegerRotateLeft, (Rot<int32_t, true>), SetI);
-
-// java.lang.Integer.signum(I)I
-UNARY_INTRINSIC(MterpIntegerSignum, Signum, GetVReg, SetI);
-
-// java.lang.Long.reverse(J)J
-UNARY_INTRINSIC(MterpLongReverse, ReverseBits64, GetVRegLong, SetJ);
-
-// java.lang.Long.reverseBytes(J)J
-UNARY_INTRINSIC(MterpLongReverseBytes, BSWAP, GetVRegLong, SetJ);
-
-// java.lang.Long.bitCount(J)I
-UNARY_INTRINSIC(MterpLongBitCount, POPCOUNT, GetVRegLong, SetI);
-
-// java.lang.Long.compare(JJ)I
-BINARY_JJ_INTRINSIC(MterpLongCompare, Compare, SetI);
-
-// java.lang.Long.highestOneBit(J)J
-UNARY_INTRINSIC(MterpLongHighestOneBit, HighestOneBitValue, GetVRegLong, SetJ);
-
-// java.lang.Long.lowestOneBit(J)J
-UNARY_INTRINSIC(MterpLongLowestOneBit, LowestOneBitValue, GetVRegLong, SetJ);
-
-// java.lang.Long.numberOfLeadingZeros(J)I
-UNARY_INTRINSIC(MterpLongNumberOfLeadingZeros, JAVASTYLE_CLZ, GetVRegLong, SetJ);
-
-// java.lang.Long.numberOfTrailingZeros(J)I
-UNARY_INTRINSIC(MterpLongNumberOfTrailingZeros, JAVASTYLE_CTZ, GetVRegLong, SetJ);
-
-// java.lang.Long.rotateRight(JI)J
-BINARY_JI_INTRINSIC(MterpLongRotateRight, (Rot<int64_t, false>), SetJ);
-
-// java.lang.Long.rotateLeft(JI)J
-BINARY_JI_INTRINSIC(MterpLongRotateLeft, (Rot<int64_t, true>), SetJ);
-
-// java.lang.Long.signum(J)I
-UNARY_INTRINSIC(MterpLongSignum, Signum, GetVRegLong, SetI);
-
-// java.lang.Short.reverseBytes(S)S
-UNARY_INTRINSIC(MterpShortReverseBytes, BSWAP, GetVRegShort, SetS);
-
-// java.lang.Math.min(II)I
-BINARY_II_INTRINSIC(MterpMathMinIntInt, std::min, SetI);
-
-// java.lang.Math.min(JJ)J
-BINARY_JJ_INTRINSIC(MterpMathMinLongLong, std::min, SetJ);
-
-// java.lang.Math.max(II)I
-BINARY_II_INTRINSIC(MterpMathMaxIntInt, std::max, SetI);
-
-// java.lang.Math.max(JJ)J
-BINARY_JJ_INTRINSIC(MterpMathMaxLongLong, std::max, SetJ);
-
-// java.lang.Math.abs(I)I
-UNARY_INTRINSIC(MterpMathAbsInt, std::abs, GetVReg, SetI);
-
-// java.lang.Math.abs(J)J
-UNARY_INTRINSIC(MterpMathAbsLong, std::abs, GetVRegLong, SetJ);
-
-// java.lang.Math.abs(F)F
-UNARY_INTRINSIC(MterpMathAbsFloat, 0x7fffffff&, GetVReg, SetI);
-
-// java.lang.Math.abs(D)D
-UNARY_INTRINSIC(MterpMathAbsDouble, INT64_C(0x7fffffffffffffff)&, GetVRegLong, SetJ);
-
-// java.lang.Math.sqrt(D)D
-UNARY_INTRINSIC(MterpMathSqrt, std::sqrt, GetVRegDouble, SetD);
-
-// java.lang.Math.ceil(D)D
-UNARY_INTRINSIC(MterpMathCeil, std::ceil, GetVRegDouble, SetD);
-
-// java.lang.Math.floor(D)D
-UNARY_INTRINSIC(MterpMathFloor, std::floor, GetVRegDouble, SetD);
-
-// java.lang.Math.sin(D)D
-UNARY_INTRINSIC(MterpMathSin, std::sin, GetVRegDouble, SetD);
-
-// java.lang.Math.cos(D)D
-UNARY_INTRINSIC(MterpMathCos, std::cos, GetVRegDouble, SetD);
-
-// java.lang.Math.tan(D)D
-UNARY_INTRINSIC(MterpMathTan, std::tan, GetVRegDouble, SetD);
-
-// java.lang.Math.asin(D)D
-UNARY_INTRINSIC(MterpMathAsin, std::asin, GetVRegDouble, SetD);
-
-// java.lang.Math.acos(D)D
-UNARY_INTRINSIC(MterpMathAcos, std::acos, GetVRegDouble, SetD);
-
-// java.lang.Math.atan(D)D
-UNARY_INTRINSIC(MterpMathAtan, std::atan, GetVRegDouble, SetD);
-
-// java.lang.String.charAt(I)C
-static ALWAYS_INLINE bool MterpStringCharAt(ShadowFrame* shadow_frame,
-                                            const Instruction* inst,
-                                            uint16_t inst_data,
-                                            JValue* result_register)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  uint32_t arg[Instruction::kMaxVarArgRegs] = {};
-  inst->GetVarArgs(arg, inst_data);
-  ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString();
-  int length = str->GetLength();
-  int index = shadow_frame->GetVReg(arg[1]);
-  uint16_t res;
-  if (UNLIKELY(index < 0) || (index >= length)) {
-    return false;  // Punt and let non-intrinsic version deal with the throw.
-  }
-  if (str->IsCompressed()) {
-    res = str->GetValueCompressed()[index];
-  } else {
-    res = str->GetValue()[index];
-  }
-  result_register->SetC(res);
-  return true;
-}
-
-// java.lang.String.compareTo(Ljava/lang/string)I
-static ALWAYS_INLINE bool MterpStringCompareTo(ShadowFrame* shadow_frame,
-                                               const Instruction* inst,
-                                               uint16_t inst_data,
-                                               JValue* result_register)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  uint32_t arg[Instruction::kMaxVarArgRegs] = {};
-  inst->GetVarArgs(arg, inst_data);
-  ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString();
-  ObjPtr<mirror::Object> arg1 = shadow_frame->GetVRegReference(arg[1]);
-  if (arg1 == nullptr) {
-    return false;
-  }
-  result_register->SetI(str->CompareTo(arg1->AsString()));
-  return true;
-}
-
-#define STRING_INDEXOF_INTRINSIC(name, starting_pos)             \
-static ALWAYS_INLINE bool Mterp##name(ShadowFrame* shadow_frame, \
-                                      const Instruction* inst,   \
-                                      uint16_t inst_data,        \
-                                      JValue* result_register)   \
-    REQUIRES_SHARED(Locks::mutator_lock_) {                      \
-  uint32_t arg[Instruction::kMaxVarArgRegs] = {};                \
-  inst->GetVarArgs(arg, inst_data);                              \
-  ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString(); \
-  int ch = shadow_frame->GetVReg(arg[1]);                        \
-  if (ch >= 0x10000) {                                           \
-    /* Punt if supplementary char. */                            \
-    return false;                                                \
-  }                                                              \
-  result_register->SetI(str->FastIndexOf(ch, starting_pos));     \
-  return true;                                                   \
-}
-
-// java.lang.String.indexOf(I)I
-STRING_INDEXOF_INTRINSIC(StringIndexOf, 0);
-
-// java.lang.String.indexOf(II)I
-STRING_INDEXOF_INTRINSIC(StringIndexOfAfter, shadow_frame->GetVReg(arg[2]));
-
-#define SIMPLE_STRING_INTRINSIC(name, operation)                 \
-static ALWAYS_INLINE bool Mterp##name(ShadowFrame* shadow_frame, \
-                                      const Instruction* inst,   \
-                                      uint16_t inst_data,        \
-                                      JValue* result_register)   \
-    REQUIRES_SHARED(Locks::mutator_lock_) {                      \
-  uint32_t arg[Instruction::kMaxVarArgRegs] = {};                \
-  inst->GetVarArgs(arg, inst_data);                              \
-  ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString(); \
-  result_register->operation;                                    \
-  return true;                                                   \
-}
-
-// java.lang.String.isEmpty()Z
-SIMPLE_STRING_INTRINSIC(StringIsEmpty, SetZ(str->GetLength() == 0))
-
-// java.lang.String.length()I
-SIMPLE_STRING_INTRINSIC(StringLength, SetI(str->GetLength()))
-
-// java.lang.String.getCharsNoCheck(II[CI)V
-static ALWAYS_INLINE bool MterpStringGetCharsNoCheck(ShadowFrame* shadow_frame,
-                                                     const Instruction* inst,
-                                                     uint16_t inst_data,
-                                                     JValue* result_register ATTRIBUTE_UNUSED)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  // Start, end & index already checked by caller - won't throw.  Destination is uncompressed.
-  uint32_t arg[Instruction::kMaxVarArgRegs] = {};
-  inst->GetVarArgs(arg, inst_data);
-  ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString();
-  int32_t start = shadow_frame->GetVReg(arg[1]);
-  int32_t end = shadow_frame->GetVReg(arg[2]);
-  int32_t index = shadow_frame->GetVReg(arg[4]);
-  ObjPtr<mirror::CharArray> array = shadow_frame->GetVRegReference(arg[3])->AsCharArray();
-  uint16_t* dst = array->GetData() + index;
-  int32_t len = (end - start);
-  if (str->IsCompressed()) {
-    const uint8_t* src_8 = str->GetValueCompressed() + start;
-    for (int i = 0; i < len; i++) {
-      dst[i] = src_8[i];
-    }
-  } else {
-    uint16_t* src_16 = str->GetValue() + start;
-    memcpy(dst, src_16, len * sizeof(uint16_t));
-  }
-  return true;
-}
-
-// java.lang.String.equalsLjava/lang/Object;)Z
-static ALWAYS_INLINE bool MterpStringEquals(ShadowFrame* shadow_frame,
-                                            const Instruction* inst,
-                                            uint16_t inst_data,
-                                            JValue* result_register)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  uint32_t arg[Instruction::kMaxVarArgRegs] = {};
-  inst->GetVarArgs(arg, inst_data);
-  ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString();
-  ObjPtr<mirror::Object> obj = shadow_frame->GetVRegReference(arg[1]);
-  bool res = false;  // Assume not equal.
-  if ((obj != nullptr) && obj->IsString()) {
-    ObjPtr<mirror::String> str2 = obj->AsString();
-    if (str->GetCount() == str2->GetCount()) {
-      // Length & compression status are same.  Can use block compare.
-      void* bytes1;
-      void* bytes2;
-      int len = str->GetLength();
-      if (str->IsCompressed()) {
-        bytes1 = str->GetValueCompressed();
-        bytes2 = str2->GetValueCompressed();
-      } else {
-        len *= sizeof(uint16_t);
-        bytes1 = str->GetValue();
-        bytes2 = str2->GetValue();
-      }
-      res = (memcmp(bytes1, bytes2, len) == 0);
-    }
-  }
-  result_register->SetZ(res);
-  return true;
-}
-
-#define VARHANDLE_FENCE_INTRINSIC(name, std_memory_operation)              \
-static ALWAYS_INLINE bool name(ShadowFrame* shadow_frame ATTRIBUTE_UNUSED, \
-                               const Instruction* inst ATTRIBUTE_UNUSED,   \
-                               uint16_t inst_data ATTRIBUTE_UNUSED,        \
-                               JValue* result_register ATTRIBUTE_UNUSED)   \
-    REQUIRES_SHARED(Locks::mutator_lock_) {                                \
-  std::atomic_thread_fence(std_memory_operation);                          \
-  return true;                                                             \
-}
-
-// The VarHandle fence methods are static (unlike jdk.internal.misc.Unsafe versions).
-// The fences for the LoadLoadFence and StoreStoreFence are stronger
-// than strictly required, but the impact should be marginal.
-VARHANDLE_FENCE_INTRINSIC(MterpVarHandleFullFence, std::memory_order_seq_cst)
-VARHANDLE_FENCE_INTRINSIC(MterpVarHandleAcquireFence, std::memory_order_acquire)
-VARHANDLE_FENCE_INTRINSIC(MterpVarHandleReleaseFence, std::memory_order_release)
-VARHANDLE_FENCE_INTRINSIC(MterpVarHandleLoadLoadFence, std::memory_order_acquire)
-VARHANDLE_FENCE_INTRINSIC(MterpVarHandleStoreStoreFence, std::memory_order_release)
-
-#define METHOD_HANDLE_INVOKE_INTRINSIC(name)                                                      \
-static ALWAYS_INLINE bool Mterp##name(ShadowFrame* shadow_frame,                                  \
-                               const Instruction* inst,                                           \
-                               uint16_t inst_data,                                                \
-                               JValue* result)                                                    \
-    REQUIRES_SHARED(Locks::mutator_lock_) {                                                       \
-  if (inst->Opcode() == Instruction::INVOKE_POLYMORPHIC) {                                        \
-    return DoInvokePolymorphic<false>(Thread::Current(), *shadow_frame, inst, inst_data, result); \
-  } else {                                                                                        \
-    return DoInvokePolymorphic<true>(Thread::Current(), *shadow_frame, inst, inst_data, result);  \
-  }                                                                                               \
-}
-
-METHOD_HANDLE_INVOKE_INTRINSIC(MethodHandleInvokeExact)
-METHOD_HANDLE_INVOKE_INTRINSIC(MethodHandleInvoke)
-
-#define VAR_HANDLE_ACCESSOR_INTRINSIC(name)                                   \
-static ALWAYS_INLINE bool Mterp##name(ShadowFrame* shadow_frame,              \
-                               const Instruction* inst,                       \
-                               uint16_t inst_data,                            \
-                               JValue* result)                                \
-    REQUIRES_SHARED(Locks::mutator_lock_) {                                   \
-  return Do##name(Thread::Current(), *shadow_frame, inst, inst_data, result); \
-}
-
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleCompareAndExchange)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleCompareAndExchangeAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleCompareAndExchangeRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleCompareAndSet)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGet);
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndAdd)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndAddAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndAddRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseAnd)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseAndAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseAndRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseOr)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseOrAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseOrRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseXor)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseXorAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseXorRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndSet)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndSetAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndSetRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetOpaque)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetVolatile)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleSet)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleSetOpaque)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleSetRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleSetVolatile)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleWeakCompareAndSet)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleWeakCompareAndSetAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleWeakCompareAndSetPlain)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleWeakCompareAndSetRelease)
-
-static ALWAYS_INLINE bool MterpReachabilityFence(ShadowFrame* shadow_frame ATTRIBUTE_UNUSED,
-                                                 const Instruction* inst ATTRIBUTE_UNUSED,
-                                                 uint16_t inst_data ATTRIBUTE_UNUSED,
-                                                 JValue* result_register ATTRIBUTE_UNUSED)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  // Do nothing; Its only purpose is to keep the argument reference live
-  // at preceding suspend points. That's automatic in the interpreter.
-  return true;
-}
-
-// Macro to help keep track of what's left to implement.
-#define UNIMPLEMENTED_CASE(name)    \
-    case Intrinsics::k##name:       \
-      res = false;                  \
-      break;
-
-#define INTRINSIC_CASE(name)                                           \
-    case Intrinsics::k##name:                                          \
-      res = Mterp##name(shadow_frame, inst, inst_data, result_register); \
-      break;
-
-bool MterpHandleIntrinsic(ShadowFrame* shadow_frame,
-                          ArtMethod* const called_method,
-                          const Instruction* inst,
-                          uint16_t inst_data,
-                          JValue* result_register)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  Intrinsics intrinsic = static_cast<Intrinsics>(called_method->GetIntrinsic());
-  bool res = false;  // Assume failure
-  switch (intrinsic) {
-    UNIMPLEMENTED_CASE(DoubleDoubleToRawLongBits /* (D)J */)
-    UNIMPLEMENTED_CASE(DoubleDoubleToLongBits /* (D)J */)
-    UNIMPLEMENTED_CASE(DoubleIsInfinite /* (D)Z */)
-    UNIMPLEMENTED_CASE(DoubleIsNaN /* (D)Z */)
-    UNIMPLEMENTED_CASE(DoubleLongBitsToDouble /* (J)D */)
-    UNIMPLEMENTED_CASE(FloatFloatToRawIntBits /* (F)I */)
-    UNIMPLEMENTED_CASE(FloatFloatToIntBits /* (F)I */)
-    UNIMPLEMENTED_CASE(FloatIsInfinite /* (F)Z */)
-    UNIMPLEMENTED_CASE(FloatIsNaN /* (F)Z */)
-    UNIMPLEMENTED_CASE(FloatIntBitsToFloat /* (I)F */)
-    UNIMPLEMENTED_CASE(IntegerDivideUnsigned /* (II)I */)
-    UNIMPLEMENTED_CASE(LongDivideUnsigned /* (JJ)J */)
-    INTRINSIC_CASE(IntegerReverse)
-    INTRINSIC_CASE(IntegerReverseBytes)
-    INTRINSIC_CASE(IntegerBitCount)
-    INTRINSIC_CASE(IntegerCompare)
-    INTRINSIC_CASE(IntegerHighestOneBit)
-    INTRINSIC_CASE(IntegerLowestOneBit)
-    INTRINSIC_CASE(IntegerNumberOfLeadingZeros)
-    INTRINSIC_CASE(IntegerNumberOfTrailingZeros)
-    INTRINSIC_CASE(IntegerRotateRight)
-    INTRINSIC_CASE(IntegerRotateLeft)
-    INTRINSIC_CASE(IntegerSignum)
-    INTRINSIC_CASE(LongReverse)
-    INTRINSIC_CASE(LongReverseBytes)
-    INTRINSIC_CASE(LongBitCount)
-    INTRINSIC_CASE(LongCompare)
-    INTRINSIC_CASE(LongHighestOneBit)
-    INTRINSIC_CASE(LongLowestOneBit)
-    INTRINSIC_CASE(LongNumberOfLeadingZeros)
-    INTRINSIC_CASE(LongNumberOfTrailingZeros)
-    INTRINSIC_CASE(LongRotateRight)
-    INTRINSIC_CASE(LongRotateLeft)
-    INTRINSIC_CASE(LongSignum)
-    INTRINSIC_CASE(ShortReverseBytes)
-    INTRINSIC_CASE(MathAbsDouble)
-    INTRINSIC_CASE(MathAbsFloat)
-    INTRINSIC_CASE(MathAbsLong)
-    INTRINSIC_CASE(MathAbsInt)
-    UNIMPLEMENTED_CASE(MathFmaDouble /* (DDD)D */)
-    UNIMPLEMENTED_CASE(MathFmaFloat /* (FFF)F */)
-    UNIMPLEMENTED_CASE(MathMinDoubleDouble /* (DD)D */)
-    UNIMPLEMENTED_CASE(MathMinFloatFloat /* (FF)F */)
-    INTRINSIC_CASE(MathMinLongLong)
-    INTRINSIC_CASE(MathMinIntInt)
-    UNIMPLEMENTED_CASE(MathMaxDoubleDouble /* (DD)D */)
-    UNIMPLEMENTED_CASE(MathMaxFloatFloat /* (FF)F */)
-    INTRINSIC_CASE(MathMaxLongLong)
-    INTRINSIC_CASE(MathMaxIntInt)
-    INTRINSIC_CASE(MathCos)
-    INTRINSIC_CASE(MathSin)
-    INTRINSIC_CASE(MathAcos)
-    INTRINSIC_CASE(MathAsin)
-    INTRINSIC_CASE(MathAtan)
-    UNIMPLEMENTED_CASE(MathAtan2 /* (DD)D */)
-    UNIMPLEMENTED_CASE(MathCbrt /* (D)D */)
-    UNIMPLEMENTED_CASE(MathCosh /* (D)D */)
-    UNIMPLEMENTED_CASE(MathExp /* (D)D */)
-    UNIMPLEMENTED_CASE(MathExpm1 /* (D)D */)
-    UNIMPLEMENTED_CASE(MathHypot /* (DD)D */)
-    UNIMPLEMENTED_CASE(MathLog /* (D)D */)
-    UNIMPLEMENTED_CASE(MathLog10 /* (D)D */)
-    UNIMPLEMENTED_CASE(MathNextAfter /* (DD)D */)
-    UNIMPLEMENTED_CASE(MathPow /* (DD)D */)
-    UNIMPLEMENTED_CASE(MathSinh /* (D)D */)
-    INTRINSIC_CASE(MathTan)
-    UNIMPLEMENTED_CASE(MathTanh /* (D)D */)
-    INTRINSIC_CASE(MathSqrt)
-    INTRINSIC_CASE(MathCeil)
-    INTRINSIC_CASE(MathFloor)
-    UNIMPLEMENTED_CASE(MathRint /* (D)D */)
-    UNIMPLEMENTED_CASE(MathRoundDouble /* (D)J */)
-    UNIMPLEMENTED_CASE(MathRoundFloat /* (F)I */)
-    UNIMPLEMENTED_CASE(MathMultiplyHigh /* (JJ)J */)
-    UNIMPLEMENTED_CASE(SystemArrayCopyByte /* ([BI[BII)V */)
-    UNIMPLEMENTED_CASE(SystemArrayCopyChar /* ([CI[CII)V */)
-    UNIMPLEMENTED_CASE(SystemArrayCopyInt /* ([II[III)V */)
-    UNIMPLEMENTED_CASE(SystemArrayCopy /* (Ljava/lang/Object;ILjava/lang/Object;II)V */)
-    UNIMPLEMENTED_CASE(ThreadCurrentThread /* ()Ljava/lang/Thread; */)
-    UNIMPLEMENTED_CASE(MemoryPeekByte /* (J)B */)
-    UNIMPLEMENTED_CASE(MemoryPeekIntNative /* (J)I */)
-    UNIMPLEMENTED_CASE(MemoryPeekLongNative /* (J)J */)
-    UNIMPLEMENTED_CASE(MemoryPeekShortNative /* (J)S */)
-    UNIMPLEMENTED_CASE(MemoryPokeByte /* (JB)V */)
-    UNIMPLEMENTED_CASE(MemoryPokeIntNative /* (JI)V */)
-    UNIMPLEMENTED_CASE(MemoryPokeLongNative /* (JJ)V */)
-    UNIMPLEMENTED_CASE(MemoryPokeShortNative /* (JS)V */)
-    INTRINSIC_CASE(ReachabilityFence /* (Ljava/lang/Object;)V */)
-    INTRINSIC_CASE(StringCharAt)
-    INTRINSIC_CASE(StringCompareTo)
-    INTRINSIC_CASE(StringEquals)
-    INTRINSIC_CASE(StringGetCharsNoCheck)
-    INTRINSIC_CASE(StringIndexOf)
-    INTRINSIC_CASE(StringIndexOfAfter)
-    UNIMPLEMENTED_CASE(StringStringIndexOf /* (Ljava/lang/String;)I */)
-    UNIMPLEMENTED_CASE(StringStringIndexOfAfter /* (Ljava/lang/String;I)I */)
-    INTRINSIC_CASE(StringIsEmpty)
-    INTRINSIC_CASE(StringLength)
-    UNIMPLEMENTED_CASE(StringNewStringFromBytes /* ([BIII)Ljava/lang/String; */)
-    UNIMPLEMENTED_CASE(StringNewStringFromChars /* (II[C)Ljava/lang/String; */)
-    UNIMPLEMENTED_CASE(StringNewStringFromString /* (Ljava/lang/String;)Ljava/lang/String; */)
-    UNIMPLEMENTED_CASE(StringBufferAppend /* (Ljava/lang/String;)Ljava/lang/StringBuffer; */)
-    UNIMPLEMENTED_CASE(StringBufferLength /* ()I */)
-    UNIMPLEMENTED_CASE(StringBufferToString /* ()Ljava/lang/String; */)
-    UNIMPLEMENTED_CASE(
-        StringBuilderAppendObject /* (Ljava/lang/Object;)Ljava/lang/StringBuilder; */)
-    UNIMPLEMENTED_CASE(
-        StringBuilderAppendString /* (Ljava/lang/String;)Ljava/lang/StringBuilder; */)
-    UNIMPLEMENTED_CASE(
-        StringBuilderAppendCharSequence /* (Ljava/lang/CharSequence;)Ljava/lang/StringBuilder; */)
-    UNIMPLEMENTED_CASE(StringBuilderAppendCharArray /* ([C)Ljava/lang/StringBuilder; */)
-    UNIMPLEMENTED_CASE(StringBuilderAppendBoolean /* (Z)Ljava/lang/StringBuilder; */)
-    UNIMPLEMENTED_CASE(StringBuilderAppendChar /* (C)Ljava/lang/StringBuilder; */)
-    UNIMPLEMENTED_CASE(StringBuilderAppendInt /* (I)Ljava/lang/StringBuilder; */)
-    UNIMPLEMENTED_CASE(StringBuilderAppendLong /* (J)Ljava/lang/StringBuilder; */)
-    UNIMPLEMENTED_CASE(StringBuilderAppendFloat /* (F)Ljava/lang/StringBuilder; */)
-    UNIMPLEMENTED_CASE(StringBuilderAppendDouble /* (D)Ljava/lang/StringBuilder; */)
-    UNIMPLEMENTED_CASE(StringBuilderLength /* ()I */)
-    UNIMPLEMENTED_CASE(StringBuilderToString /* ()Ljava/lang/String; */)
-    UNIMPLEMENTED_CASE(UnsafeCASInt /* (Ljava/lang/Object;JII)Z */)
-    UNIMPLEMENTED_CASE(UnsafeCASLong /* (Ljava/lang/Object;JJJ)Z */)
-    UNIMPLEMENTED_CASE(UnsafeCASObject /* (Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z */)
-    UNIMPLEMENTED_CASE(UnsafeGet /* (Ljava/lang/Object;J)I */)
-    UNIMPLEMENTED_CASE(UnsafeGetVolatile /* (Ljava/lang/Object;J)I */)
-    UNIMPLEMENTED_CASE(UnsafeGetObject /* (Ljava/lang/Object;J)Ljava/lang/Object; */)
-    UNIMPLEMENTED_CASE(UnsafeGetObjectVolatile /* (Ljava/lang/Object;J)Ljava/lang/Object; */)
-    UNIMPLEMENTED_CASE(UnsafeGetLong /* (Ljava/lang/Object;J)J */)
-    UNIMPLEMENTED_CASE(UnsafeGetLongVolatile /* (Ljava/lang/Object;J)J */)
-    UNIMPLEMENTED_CASE(UnsafePut /* (Ljava/lang/Object;JI)V */)
-    UNIMPLEMENTED_CASE(UnsafePutOrdered /* (Ljava/lang/Object;JI)V */)
-    UNIMPLEMENTED_CASE(UnsafePutVolatile /* (Ljava/lang/Object;JI)V */)
-    UNIMPLEMENTED_CASE(UnsafePutObject /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
-    UNIMPLEMENTED_CASE(UnsafePutObjectOrdered /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
-    UNIMPLEMENTED_CASE(UnsafePutObjectVolatile /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
-    UNIMPLEMENTED_CASE(UnsafePutLong /* (Ljava/lang/Object;JJ)V */)
-    UNIMPLEMENTED_CASE(UnsafePutLongOrdered /* (Ljava/lang/Object;JJ)V */)
-    UNIMPLEMENTED_CASE(UnsafePutLongVolatile /* (Ljava/lang/Object;JJ)V */)
-    UNIMPLEMENTED_CASE(UnsafeGetAndAddInt /* (Ljava/lang/Object;JI)I */)
-    UNIMPLEMENTED_CASE(UnsafeGetAndAddLong /* (Ljava/lang/Object;JJ)J */)
-    UNIMPLEMENTED_CASE(UnsafeGetAndSetInt /* (Ljava/lang/Object;JI)I */)
-    UNIMPLEMENTED_CASE(UnsafeGetAndSetLong /* (Ljava/lang/Object;JJ)J */)
-    UNIMPLEMENTED_CASE(UnsafeGetAndSetObject /* (Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object; */)
-    UNIMPLEMENTED_CASE(UnsafeLoadFence /* ()V */)
-    UNIMPLEMENTED_CASE(UnsafeStoreFence /* ()V */)
-    UNIMPLEMENTED_CASE(UnsafeFullFence /* ()V */)
-    UNIMPLEMENTED_CASE(JdkUnsafeCASInt /* (Ljava/lang/Object;JII)Z */)
-    UNIMPLEMENTED_CASE(JdkUnsafeCASLong /* (Ljava/lang/Object;JJJ)Z */)
-    UNIMPLEMENTED_CASE(JdkUnsafeCASObject /* (Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z */)
-    UNIMPLEMENTED_CASE(JdkUnsafeCompareAndSetInt /* (Ljava/lang/Object;JII)Z */)
-    UNIMPLEMENTED_CASE(JdkUnsafeCompareAndSetLong /* (Ljava/lang/Object;JJJ)Z */)
-    UNIMPLEMENTED_CASE(JdkUnsafeCompareAndSetObject /* (Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGet /* (Ljava/lang/Object;J)I */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetVolatile /* (Ljava/lang/Object;J)I */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetAcquire /* (Ljava/lang/Object;J)I */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetObject /* (Ljava/lang/Object;J)Ljava/lang/Object; */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetObjectVolatile /* (Ljava/lang/Object;J)Ljava/lang/Object; */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetObjectAcquire /* (Ljava/lang/Object;J)Ljava/lang/Object; */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetLong /* (Ljava/lang/Object;J)J */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetLongVolatile /* (Ljava/lang/Object;J)J */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetLongAcquire /* (Ljava/lang/Object;J)J */)
-    UNIMPLEMENTED_CASE(JdkUnsafePut /* (Ljava/lang/Object;JI)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutOrdered /* (Ljava/lang/Object;JI)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutVolatile /* (Ljava/lang/Object;JI)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutRelease /* (Ljava/lang/Object;JI)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutObject /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutObjectOrdered /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutObjectVolatile /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutObjectRelease /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutLong /* (Ljava/lang/Object;JJ)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutLongOrdered /* (Ljava/lang/Object;JJ)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutLongVolatile /* (Ljava/lang/Object;JJ)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafePutLongRelease /* (Ljava/lang/Object;JJ)V */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetAndAddInt /* (Ljava/lang/Object;JI)I */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetAndAddLong /* (Ljava/lang/Object;JJ)J */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetAndSetInt /* (Ljava/lang/Object;JI)I */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetAndSetLong /* (Ljava/lang/Object;JJ)J */)
-    UNIMPLEMENTED_CASE(JdkUnsafeGetAndSetObject /* (Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object; */)
-    UNIMPLEMENTED_CASE(JdkUnsafeLoadFence /* ()V */)
-    UNIMPLEMENTED_CASE(JdkUnsafeStoreFence /* ()V */)
-    UNIMPLEMENTED_CASE(JdkUnsafeFullFence /* ()V */)
-    UNIMPLEMENTED_CASE(ReferenceGetReferent /* ()Ljava/lang/Object; */)
-    UNIMPLEMENTED_CASE(ReferenceRefersTo /* (Ljava/lang/Object;)Z */)
-    UNIMPLEMENTED_CASE(IntegerValueOf /* (I)Ljava/lang/Integer; */)
-    UNIMPLEMENTED_CASE(ThreadInterrupted /* ()Z */)
-    UNIMPLEMENTED_CASE(CRC32Update /* (II)I */)
-    UNIMPLEMENTED_CASE(CRC32UpdateBytes /* (I[BII)I */)
-    UNIMPLEMENTED_CASE(CRC32UpdateByteBuffer /* (IJII)I */)
-    UNIMPLEMENTED_CASE(FP16Compare /* (SS)I */)
-    UNIMPLEMENTED_CASE(FP16ToFloat /* (S)F */)
-    UNIMPLEMENTED_CASE(FP16ToHalf /* (F)S */)
-    UNIMPLEMENTED_CASE(FP16Floor /* (S)S */)
-    UNIMPLEMENTED_CASE(FP16Ceil /* (S)S */)
-    UNIMPLEMENTED_CASE(FP16Rint /* (S)S */)
-    UNIMPLEMENTED_CASE(FP16Greater /* (SS)Z */)
-    UNIMPLEMENTED_CASE(FP16GreaterEquals /* (SS)Z */)
-    UNIMPLEMENTED_CASE(FP16Less /* (SS)Z */)
-    UNIMPLEMENTED_CASE(FP16LessEquals /* (SS)Z */)
-    UNIMPLEMENTED_CASE(FP16Min /* (SS)S */)
-    UNIMPLEMENTED_CASE(FP16Max /* (SS)S */)
-    INTRINSIC_CASE(VarHandleFullFence)
-    INTRINSIC_CASE(VarHandleAcquireFence)
-    INTRINSIC_CASE(VarHandleReleaseFence)
-    INTRINSIC_CASE(VarHandleLoadLoadFence)
-    INTRINSIC_CASE(VarHandleStoreStoreFence)
-    INTRINSIC_CASE(MethodHandleInvokeExact)
-    INTRINSIC_CASE(MethodHandleInvoke)
-    INTRINSIC_CASE(VarHandleCompareAndExchange)
-    INTRINSIC_CASE(VarHandleCompareAndExchangeAcquire)
-    INTRINSIC_CASE(VarHandleCompareAndExchangeRelease)
-    INTRINSIC_CASE(VarHandleCompareAndSet)
-    INTRINSIC_CASE(VarHandleGet)
-    INTRINSIC_CASE(VarHandleGetAcquire)
-    INTRINSIC_CASE(VarHandleGetAndAdd)
-    INTRINSIC_CASE(VarHandleGetAndAddAcquire)
-    INTRINSIC_CASE(VarHandleGetAndAddRelease)
-    INTRINSIC_CASE(VarHandleGetAndBitwiseAnd)
-    INTRINSIC_CASE(VarHandleGetAndBitwiseAndAcquire)
-    INTRINSIC_CASE(VarHandleGetAndBitwiseAndRelease)
-    INTRINSIC_CASE(VarHandleGetAndBitwiseOr)
-    INTRINSIC_CASE(VarHandleGetAndBitwiseOrAcquire)
-    INTRINSIC_CASE(VarHandleGetAndBitwiseOrRelease)
-    INTRINSIC_CASE(VarHandleGetAndBitwiseXor)
-    INTRINSIC_CASE(VarHandleGetAndBitwiseXorAcquire)
-    INTRINSIC_CASE(VarHandleGetAndBitwiseXorRelease)
-    INTRINSIC_CASE(VarHandleGetAndSet)
-    INTRINSIC_CASE(VarHandleGetAndSetAcquire)
-    INTRINSIC_CASE(VarHandleGetAndSetRelease)
-    INTRINSIC_CASE(VarHandleGetOpaque)
-    INTRINSIC_CASE(VarHandleGetVolatile)
-    INTRINSIC_CASE(VarHandleSet)
-    INTRINSIC_CASE(VarHandleSetOpaque)
-    INTRINSIC_CASE(VarHandleSetRelease)
-    INTRINSIC_CASE(VarHandleSetVolatile)
-    INTRINSIC_CASE(VarHandleWeakCompareAndSet)
-    INTRINSIC_CASE(VarHandleWeakCompareAndSetAcquire)
-    INTRINSIC_CASE(VarHandleWeakCompareAndSetPlain)
-    INTRINSIC_CASE(VarHandleWeakCompareAndSetRelease)
-    case Intrinsics::kNone:
-      res = false;
-      break;
-    // Note: no default case to ensure we catch any newly added intrinsics.
-  }
-  return res;
-}
-
-}  // namespace interpreter
-}  // namespace art
diff --git a/runtime/interpreter/interpreter_intrinsics.h b/runtime/interpreter/interpreter_intrinsics.h
deleted file mode 100644
index 2a23002..0000000
--- a/runtime/interpreter/interpreter_intrinsics.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ART_RUNTIME_INTERPRETER_INTERPRETER_INTRINSICS_H_
-#define ART_RUNTIME_INTERPRETER_INTERPRETER_INTRINSICS_H_
-
-#include "jvalue.h"
-
-namespace art {
-
-class ArtMethod;
-class Instruction;
-class ShadowFrame;
-
-namespace interpreter {
-
-// Invokes to methods identified as intrinics are routed here.  If there is
-// no interpreter implementation, return false and a normal invoke will proceed.
-bool MterpHandleIntrinsic(ShadowFrame* shadow_frame,
-                          ArtMethod* const called_method,
-                          const Instruction* inst,
-                          uint16_t inst_data,
-                          JValue* result_register);
-
-}  // namespace interpreter
-}  // namespace art
-
-#endif  // ART_RUNTIME_INTERPRETER_INTERPRETER_INTRINSICS_H_
diff --git a/runtime/interpreter/interpreter_switch_impl-inl.h b/runtime/interpreter/interpreter_switch_impl-inl.h
index d95c507..ddde26d 100644
--- a/runtime/interpreter/interpreter_switch_impl-inl.h
+++ b/runtime/interpreter/interpreter_switch_impl-inl.h
@@ -50,7 +50,7 @@
 // The function names must match the names from dex_instruction_list.h and have no arguments.
 // Return value: The handlers must return false if the instruction throws or returns (exits).
 //
-template<bool do_access_check, bool transaction_active, Instruction::Format kFormat>
+template<bool transaction_active, Instruction::Format kFormat>
 class InstructionHandler {
  public:
 #define HANDLER_ATTRIBUTES ALWAYS_INLINE FLATTEN WARN_UNUSED REQUIRES_SHARED(Locks::mutator_lock_)
@@ -64,7 +64,7 @@
       DCHECK(abort_exception != nullptr);
       DCHECK(abort_exception->GetClass()->DescriptorEquals(Transaction::kAbortExceptionDescriptor));
       Self()->ClearException();
-      PerformNonStandardReturn<kMonitorState>(
+      PerformNonStandardReturn(
           Self(), shadow_frame_, ctx_->result, Instrumentation(), Accessor().InsSize());
       Self()->SetException(abort_exception.Get());
       ExitInterpreterLoop();
@@ -76,7 +76,7 @@
   HANDLER_ATTRIBUTES bool CheckForceReturn() {
     if (shadow_frame_.GetForcePopFrame()) {
       DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
-      PerformNonStandardReturn<kMonitorState>(
+      PerformNonStandardReturn(
           Self(), shadow_frame_, ctx_->result, Instrumentation(), Accessor().InsSize());
       ExitInterpreterLoop();
       return false;
@@ -100,7 +100,7 @@
                                 /* skip_listeners= */ skip_event,
                                 /* skip_throw_listener= */ skip_event)) {
       // Structured locking is to be enforced for abnormal termination, too.
-      DoMonitorCheckOnExit<do_assignability_check>(Self(), &shadow_frame_);
+      DoMonitorCheckOnExit(Self(), &shadow_frame_);
       ctx_->result = JValue(); /* Handled in caller. */
       ExitInterpreterLoop();
       return false;  // Return to caller.
@@ -144,7 +144,7 @@
     if (!CheckForceReturn()) {
       return false;
     }
-    if (UNLIKELY(Instrumentation()->HasDexPcListeners())) {
+    if (UNLIKELY(shadow_frame_.GetNotifyDexPcMoveEvents())) {
       uint8_t opcode = inst_->Opcode(inst_data_);
       bool is_move_result_object = (opcode == Instruction::MOVE_RESULT_OBJECT);
       JValue* save_ref = is_move_result_object ? &ctx_->result_register : nullptr;
@@ -204,7 +204,7 @@
 
   HANDLER_ATTRIBUTES bool HandleReturn(JValue result) {
     Self()->AllowThreadSuspension();
-    if (!DoMonitorCheckOnExit<do_assignability_check>(Self(), &shadow_frame_)) {
+    if (!DoMonitorCheckOnExit(Self(), &shadow_frame_)) {
       return false;
     }
     if (UNLIKELY(NeedsMethodExitEvent(Instrumentation()) &&
@@ -341,19 +341,19 @@
 
   template<FindFieldType find_type, Primitive::Type field_type>
   HANDLER_ATTRIBUTES bool HandleGet() {
-    return DoFieldGet<find_type, field_type, do_access_check, transaction_active>(
+    return DoFieldGet<find_type, field_type, transaction_active>(
         Self(), shadow_frame_, inst_, inst_data_);
   }
 
   template<FindFieldType find_type, Primitive::Type field_type>
   HANDLER_ATTRIBUTES bool HandlePut() {
-    return DoFieldPut<find_type, field_type, do_access_check, transaction_active>(
+    return DoFieldPut<find_type, field_type, transaction_active>(
         Self(), shadow_frame_, inst_, inst_data_);
   }
 
   template<InvokeType type, bool is_range>
   HANDLER_ATTRIBUTES bool HandleInvoke() {
-    bool success = DoInvoke<type, is_range, do_access_check, /*is_mterp=*/ false>(
+    bool success = DoInvoke<type, is_range>(
         Self(), shadow_frame_, inst_, inst_data_, ResultRegister());
     return PossiblyHandlePendingExceptionOnInvoke(!success);
   }
@@ -457,12 +457,12 @@
   HANDLER_ATTRIBUTES bool RETURN_OBJECT() {
     JValue result;
     Self()->AllowThreadSuspension();
-    if (!DoMonitorCheckOnExit<do_assignability_check>(Self(), &shadow_frame_)) {
+    if (!DoMonitorCheckOnExit(Self(), &shadow_frame_)) {
       return false;
     }
     const size_t ref_idx = A();
     ObjPtr<mirror::Object> obj_result = GetVRegReference(ref_idx);
-    if (do_assignability_check && obj_result != nullptr) {
+    if (obj_result != nullptr && UNLIKELY(DoAssignabilityChecks())) {
       ObjPtr<mirror::Class> return_type = shadow_frame_.GetMethod()->ResolveReturnType();
       // Re-load since it might have moved.
       obj_result = GetVRegReference(ref_idx);
@@ -481,22 +481,23 @@
         return false;  // Pending exception.
       }
     }
-    StackHandleScope<1> hs(Self());
-    MutableHandle<mirror::Object> h_result(hs.NewHandle(obj_result));
     result.SetL(obj_result);
-    if (UNLIKELY(NeedsMethodExitEvent(Instrumentation()) &&
-                 !SendMethodExitEvents(Self(),
-                                       Instrumentation(),
-                                       shadow_frame_,
-                                       shadow_frame_.GetMethod(),
-                                       h_result))) {
-      DCHECK(Self()->IsExceptionPending());
-      // Do not raise exception event if it is caused by other instrumentation event.
-      shadow_frame_.SetSkipNextExceptionEvent(true);
-      return false;  // Pending exception.
+    if (UNLIKELY(NeedsMethodExitEvent(Instrumentation()))) {
+      StackHandleScope<1> hs(Self());
+      MutableHandle<mirror::Object> h_result(hs.NewHandle(obj_result));
+      if (!SendMethodExitEvents(Self(),
+                                Instrumentation(),
+                                shadow_frame_,
+                                shadow_frame_.GetMethod(),
+                                h_result)) {
+        DCHECK(Self()->IsExceptionPending());
+        // Do not raise exception event if it is caused by other instrumentation event.
+        shadow_frame_.SetSkipNextExceptionEvent(true);
+        return false;  // Pending exception.
+      }
+      // Re-load since it might have moved or been replaced during the MethodExitEvent.
+      result.SetL(h_result.Get());
     }
-    // Re-load since it might have moved or been replaced during the MethodExitEvent.
-    result.SetL(h_result.Get());
     ctx_->result = result;
     ExitInterpreterLoop();
     return false;
@@ -551,11 +552,12 @@
   }
 
   HANDLER_ATTRIBUTES bool CONST_CLASS() {
-    ObjPtr<mirror::Class> c = ResolveVerifyAndClinit(dex::TypeIndex(B()),
-                                                     shadow_frame_.GetMethod(),
-                                                     Self(),
-                                                     false,
-                                                     do_access_check);
+    ObjPtr<mirror::Class> c =
+        ResolveVerifyAndClinit(dex::TypeIndex(B()),
+                               shadow_frame_.GetMethod(),
+                               Self(),
+                               false,
+                               !shadow_frame_.GetMethod()->SkipAccessChecks());
     if (UNLIKELY(c == nullptr)) {
       return false;  // Pending exception.
     }
@@ -596,7 +598,7 @@
       ThrowNullPointerExceptionFromInterpreter();
       return false;  // Pending exception.
     }
-    DoMonitorEnter<do_assignability_check>(Self(), &shadow_frame_, obj);
+    DoMonitorEnter(Self(), &shadow_frame_, obj);
     return !Self()->IsExceptionPending();
   }
 
@@ -609,16 +611,17 @@
       ThrowNullPointerExceptionFromInterpreter();
       return false;  // Pending exception.
     }
-    DoMonitorExit<do_assignability_check>(Self(), &shadow_frame_, obj);
+    DoMonitorExit(Self(), &shadow_frame_, obj);
     return !Self()->IsExceptionPending();
   }
 
   HANDLER_ATTRIBUTES bool CHECK_CAST() {
-    ObjPtr<mirror::Class> c = ResolveVerifyAndClinit(dex::TypeIndex(B()),
-                                                     shadow_frame_.GetMethod(),
-                                                     Self(),
-                                                     false,
-                                                     do_access_check);
+    ObjPtr<mirror::Class> c =
+        ResolveVerifyAndClinit(dex::TypeIndex(B()),
+                               shadow_frame_.GetMethod(),
+                               Self(),
+                               false,
+                               !shadow_frame_.GetMethod()->SkipAccessChecks());
     if (UNLIKELY(c == nullptr)) {
       return false;  // Pending exception.
     }
@@ -631,11 +634,12 @@
   }
 
   HANDLER_ATTRIBUTES bool INSTANCE_OF() {
-    ObjPtr<mirror::Class> c = ResolveVerifyAndClinit(dex::TypeIndex(C()),
-                                                     shadow_frame_.GetMethod(),
-                                                     Self(),
-                                                     false,
-                                                     do_access_check);
+    ObjPtr<mirror::Class> c =
+        ResolveVerifyAndClinit(dex::TypeIndex(C()),
+                               shadow_frame_.GetMethod(),
+                               Self(),
+                               false,
+                               !shadow_frame_.GetMethod()->SkipAccessChecks());
     if (UNLIKELY(c == nullptr)) {
       return false;  // Pending exception.
     }
@@ -656,11 +660,12 @@
 
   HANDLER_ATTRIBUTES bool NEW_INSTANCE() {
     ObjPtr<mirror::Object> obj = nullptr;
-    ObjPtr<mirror::Class> c = ResolveVerifyAndClinit(dex::TypeIndex(B()),
-                                                     shadow_frame_.GetMethod(),
-                                                     Self(),
-                                                     false,
-                                                     do_access_check);
+    ObjPtr<mirror::Class> c =
+        ResolveVerifyAndClinit(dex::TypeIndex(B()),
+                               shadow_frame_.GetMethod(),
+                               Self(),
+                               false,
+                               !shadow_frame_.GetMethod()->SkipAccessChecks());
     if (LIKELY(c != nullptr)) {
       // Don't allow finalizable objects to be allocated during a transaction since these can't
       // be finalized without a started runtime.
@@ -687,7 +692,7 @@
 
   HANDLER_ATTRIBUTES bool NEW_ARRAY() {
     int32_t length = GetVReg(B());
-    ObjPtr<mirror::Object> obj = AllocArrayFromCode<do_access_check>(
+    ObjPtr<mirror::Object> obj = AllocArrayFromCode(
         dex::TypeIndex(C()),
         length,
         shadow_frame_.GetMethod(),
@@ -701,12 +706,12 @@
   }
 
   HANDLER_ATTRIBUTES bool FILLED_NEW_ARRAY() {
-    return DoFilledNewArray<false, do_access_check, transaction_active>(
+    return DoFilledNewArray<false, transaction_active>(
         inst_, shadow_frame_, Self(), ResultRegister());
   }
 
   HANDLER_ATTRIBUTES bool FILLED_NEW_ARRAY_RANGE() {
-    return DoFilledNewArray<true, do_access_check, transaction_active>(
+    return DoFilledNewArray<true, transaction_active>(
         inst_, shadow_frame_, Self(), ResultRegister());
   }
 
@@ -731,7 +736,7 @@
     ObjPtr<mirror::Object> exception = GetVRegReference(A());
     if (UNLIKELY(exception == nullptr)) {
       ThrowNullPointerException();
-    } else if (do_assignability_check && !exception->GetClass()->IsThrowableClass()) {
+    } else if (DoAssignabilityChecks() && !exception->GetClass()->IsThrowableClass()) {
       // This should never happen.
       std::string temp;
       Self()->ThrowNewExceptionF("Ljava/lang/InternalError;",
@@ -1741,9 +1746,9 @@
   }
 
  private:
-  static constexpr bool do_assignability_check = do_access_check;
-  static constexpr MonitorState kMonitorState =
-      do_assignability_check ? MonitorState::kCountingMonitors : MonitorState::kNormalMonitors;
+  bool DoAssignabilityChecks() const REQUIRES_SHARED(Locks::mutator_lock_) {
+    return !shadow_frame_.GetMethod()->SkipAccessChecks();
+  }
 
   ALWAYS_INLINE const CodeItemDataAccessor& Accessor() { return ctx_->accessor; }
   ALWAYS_INLINE const uint16_t* Insns() { return ctx_->accessor.Insns(); }
@@ -1815,8 +1820,8 @@
 #endif
 
 #define OPCODE_CASE(OPCODE, OPCODE_NAME, NAME, FORMAT, i, a, e, v)                                \
-template<bool do_access_check, bool transaction_active>                                           \
-ASAN_NO_INLINE static bool OP_##OPCODE_NAME(                                                      \
+template<bool transaction_active>                                                                 \
+ASAN_NO_INLINE NO_STACK_PROTECTOR static bool OP_##OPCODE_NAME(                                   \
     SwitchImplContext* ctx,                                                                       \
     const instrumentation::Instrumentation* instrumentation,                                      \
     Thread* self,                                                                                 \
@@ -1826,14 +1831,15 @@
     uint16_t inst_data,                                                                           \
     const Instruction*& next,                                                                     \
     bool& exit) REQUIRES_SHARED(Locks::mutator_lock_) {                                           \
-  InstructionHandler<do_access_check, transaction_active, Instruction::FORMAT> handler(           \
+  InstructionHandler<transaction_active, Instruction::FORMAT> handler(                            \
       ctx, instrumentation, self, shadow_frame, dex_pc, inst, inst_data, next, exit);             \
   return LIKELY(handler.OPCODE_NAME());                                                           \
 }
 DEX_INSTRUCTION_LIST(OPCODE_CASE)
 #undef OPCODE_CASE
 
-template<bool do_access_check, bool transaction_active>
+template<bool transaction_active>
+NO_STACK_PROTECTOR
 void ExecuteSwitchImplCpp(SwitchImplContext* ctx) {
   Thread* self = ctx->self;
   const CodeItemDataAccessor& accessor = ctx->accessor;
@@ -1857,7 +1863,7 @@
     uint16_t inst_data = inst->Fetch16(0);
     bool exit = false;
     bool success;  // Moved outside to keep frames small under asan.
-    if (InstructionHandler<do_access_check, transaction_active, Instruction::kInvalidFormat>(
+    if (InstructionHandler<transaction_active, Instruction::kInvalidFormat>(
             ctx, instrumentation, self, shadow_frame, dex_pc, inst, inst_data, next, exit).
             Preamble()) {
       DCHECK_EQ(self->IsExceptionPending(), inst->Opcode(inst_data) == Instruction::MOVE_EXCEPTION);
@@ -1865,7 +1871,7 @@
 #define OPCODE_CASE(OPCODE, OPCODE_NAME, NAME, FORMAT, i, a, e, v)                                \
         case OPCODE: {                                                                            \
           next = inst->RelativeAt(Instruction::SizeInCodeUnits(Instruction::FORMAT));             \
-          success = OP_##OPCODE_NAME<do_access_check, transaction_active>(                        \
+          success = OP_##OPCODE_NAME<transaction_active>(                                         \
               ctx, instrumentation, self, shadow_frame, dex_pc, inst, inst_data, next, exit);     \
           if (success && LIKELY(!interpret_one_instruction)) {                                    \
             continue;                                                                             \
@@ -1881,7 +1887,7 @@
       return;  // Return statement or debugger forced exit.
     }
     if (self->IsExceptionPending()) {
-      if (!InstructionHandler<do_access_check, transaction_active, Instruction::kInvalidFormat>(
+      if (!InstructionHandler<transaction_active, Instruction::kInvalidFormat>(
               ctx, instrumentation, self, shadow_frame, dex_pc, inst, inst_data, next, exit).
               HandlePendingException()) {
         shadow_frame.SetDexPC(dex::kDexNoIndex);
diff --git a/runtime/interpreter/interpreter_switch_impl.h b/runtime/interpreter/interpreter_switch_impl.h
index d4dca11..3a42c21 100644
--- a/runtime/interpreter/interpreter_switch_impl.h
+++ b/runtime/interpreter/interpreter_switch_impl.h
@@ -45,7 +45,7 @@
 };
 
 // The actual internal implementation of the switch interpreter.
-template<bool do_access_check, bool transaction_active>
+template<bool transaction_active>
 void ExecuteSwitchImplCpp(SwitchImplContext* ctx)
   REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -55,9 +55,11 @@
   REQUIRES_SHARED(Locks::mutator_lock_);
 
 // Wrapper around the switch interpreter which ensures we can unwind through it.
-template<bool do_access_check, bool transaction_active>
-ALWAYS_INLINE JValue ExecuteSwitchImpl(Thread* self, const CodeItemDataAccessor& accessor,
-                                       ShadowFrame& shadow_frame, JValue result_register,
+template<bool transaction_active>
+ALWAYS_INLINE JValue ExecuteSwitchImpl(Thread* self,
+                                       const CodeItemDataAccessor& accessor,
+                                       ShadowFrame& shadow_frame,
+                                       JValue result_register,
                                        bool interpret_one_instruction)
   REQUIRES_SHARED(Locks::mutator_lock_) {
   SwitchImplContext ctx {
@@ -68,7 +70,7 @@
     .interpret_one_instruction = interpret_one_instruction,
     .result = JValue(),
   };
-  void* impl = reinterpret_cast<void*>(&ExecuteSwitchImplCpp<do_access_check, transaction_active>);
+  void* impl = reinterpret_cast<void*>(&ExecuteSwitchImplCpp<transaction_active>);
   const uint16_t* dex_pc = ctx.accessor.Insns();
   ExecuteSwitchImplAsm(&ctx, impl, dex_pc);
   return ctx.result;
diff --git a/runtime/interpreter/interpreter_switch_impl0.cc b/runtime/interpreter/interpreter_switch_impl0.cc
index 00159ec..b4e5f50 100644
--- a/runtime/interpreter/interpreter_switch_impl0.cc
+++ b/runtime/interpreter/interpreter_switch_impl0.cc
@@ -24,7 +24,7 @@
 
 // Explicit definition of ExecuteSwitchImplCpp.
 template HOT_ATTR
-void ExecuteSwitchImplCpp<false, false>(SwitchImplContext* ctx);
+void ExecuteSwitchImplCpp<false>(SwitchImplContext* ctx);
 
 }  // namespace interpreter
 }  // namespace art
diff --git a/runtime/interpreter/interpreter_switch_impl1.cc b/runtime/interpreter/interpreter_switch_impl1.cc
index 3a86765..f8f9fcc 100644
--- a/runtime/interpreter/interpreter_switch_impl1.cc
+++ b/runtime/interpreter/interpreter_switch_impl1.cc
@@ -24,7 +24,7 @@
 
 // Explicit definition of ExecuteSwitchImplCpp.
 template
-void ExecuteSwitchImplCpp<false, true>(SwitchImplContext* ctx);
+void ExecuteSwitchImplCpp<true>(SwitchImplContext* ctx);
 
 }  // namespace interpreter
 }  // namespace art
diff --git a/runtime/interpreter/interpreter_switch_impl2.cc b/runtime/interpreter/interpreter_switch_impl2.cc
deleted file mode 100644
index c2739c1..0000000
--- a/runtime/interpreter/interpreter_switch_impl2.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-// The interpreter function takes considerable time to compile and link.
-// We compile the explicit definitions separately to speed up the build.
-
-#include "interpreter_switch_impl-inl.h"
-
-namespace art {
-namespace interpreter {
-
-// Explicit definition of ExecuteSwitchImplCpp.
-template HOT_ATTR
-void ExecuteSwitchImplCpp<true, false>(SwitchImplContext* ctx);
-
-}  // namespace interpreter
-}  // namespace art
diff --git a/runtime/interpreter/interpreter_switch_impl3.cc b/runtime/interpreter/interpreter_switch_impl3.cc
deleted file mode 100644
index 808e4bc..0000000
--- a/runtime/interpreter/interpreter_switch_impl3.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-// The interpreter function takes considerable time to compile and link.
-// We compile the explicit definitions separately to speed up the build.
-
-#include "interpreter_switch_impl-inl.h"
-
-namespace art {
-namespace interpreter {
-
-// Explicit definition of ExecuteSwitchImplCpp.
-template
-void ExecuteSwitchImplCpp<true, true>(SwitchImplContext* ctx);
-
-}  // namespace interpreter
-}  // namespace art
diff --git a/runtime/interpreter/mterp/README.txt b/runtime/interpreter/mterp/README.txt
index c550911..9b6c845 100644
--- a/runtime/interpreter/mterp/README.txt
+++ b/runtime/interpreter/mterp/README.txt
@@ -51,26 +51,13 @@
 message and abort during startup.
 
 
-==== Rebuilding ====
-
-If you change any of the source file fragments, you need to rebuild the
-combined source files in the "out" directory.  Make sure the files in
-"out" are editable, then:
-
-    $ cd mterp
-    $ ./gen_mterp.py
-
-The ultimate goal is to have the build system generate the necessary
-output files without requiring this separate step, but we're not yet
-ready to require Python in the build.
-
 ==== Interpreter Control ====
 
-The mterp fast interpreter achieves much of its performance advantage
-over the C++ interpreter through its efficient mechanism of
-transitioning from one Dalvik bytecode to the next.  Mterp for ARM targets
-uses a computed-goto mechanism, in which the handler entrypoints are
-located at the base of the handler table + (opcode * 128).
+The nterp fast interpreter achieves much of its performance advantage
+over the C++ "switch" interpreter through its efficient mechanism of
+transitioning from one Dalvik bytecode to the next.  Nterp uses a computed-goto
+mechanism, in which the handler entrypoints are located at the base of the
+handler table + (opcode * 128).
 
 In normal operation, the dedicated register rIBASE
 (r8 for ARM, edx for x86) holds a mainHandlerTable.  If we need to switch
diff --git a/runtime/interpreter/mterp/arm64ng/array.S b/runtime/interpreter/mterp/arm64ng/array.S
index 6863662..1689b01 100644
--- a/runtime/interpreter/mterp/arm64ng/array.S
+++ b/runtime/interpreter/mterp/arm64ng/array.S
@@ -170,6 +170,7 @@
    GET_VREG w1, w1                     // w1<- vB (array length)
    ldr lr, [xSELF, #THREAD_ALLOC_ARRAY_ENTRYPOINT_OFFSET]
    blr lr
+   dmb ishst                           // need fence for making array's class visible
    ubfx    w1, wINST, #8, #4           // w1<- A
    SET_VREG_OBJECT w0, w1
    FETCH_ADVANCE_INST 2
@@ -179,7 +180,7 @@
    mov x0, xSELF
    ldr x1, [sp, 0]
    mov x2, xPC
-   bl nterp_get_class_or_allocate_object
+   bl nterp_get_class
    b 1b
 3:
    bl art_quick_read_barrier_mark_reg00
diff --git a/runtime/interpreter/mterp/arm64ng/main.S b/runtime/interpreter/mterp/arm64ng/main.S
index 89de81f..424d060 100644
--- a/runtime/interpreter/mterp/arm64ng/main.S
+++ b/runtime/interpreter/mterp/arm64ng/main.S
@@ -238,7 +238,7 @@
     .hidden \name
     .global \name
     .balign 16
-    // Padding of 3 * 8 bytes to get 16 bytes alignment of code entry.
+    // Padding of 3 * 4 bytes to get 16 bytes alignment of code entry.
     .long 0
     .long 0
     .long 0
@@ -273,7 +273,9 @@
   bl \helper
   RESTORE_SAVE_REFS_ONLY_FRAME
   REFRESH_MARKING_REGISTER
-  RETURN_OR_DELIVER_PENDING_EXCEPTION
+  ldr xIP0, [xSELF, # THREAD_EXCEPTION_OFFSET]   // Get exception field.
+  cbnz xIP0, nterp_deliver_pending_exception
+  ret
 END \name
 .endm
 
@@ -1590,6 +1592,29 @@
  *  rest  method parameters
  */
 
+OAT_ENTRY ExecuteNterpWithClinitImpl, EndExecuteNterpWithClinitImpl
+    // For simplicity, we don't do a read barrier here, but instead rely
+    // on art_quick_resolution_trampoline to always have a suspend point before
+    // calling back here.
+    ldr wip, [x0, #ART_METHOD_DECLARING_CLASS_OFFSET]
+    ldrb wip2, [ip, #MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET]
+    cmp ip2, #MIRROR_CLASS_IS_VISIBLY_INITIALIZED_VALUE
+    b.hs ExecuteNterpImpl
+    cmp ip2, #MIRROR_CLASS_IS_INITIALIZED_VALUE
+    b.lo .Linitializing_check
+    dmb ish
+    b ExecuteNterpImpl
+.Linitializing_check:
+    cmp ip2, #MIRROR_CLASS_IS_INITIALIZING_VALUE
+    b.lo .Lresolution_trampoline
+    ldr wip2, [ip, #MIRROR_CLASS_CLINIT_THREAD_ID_OFFSET]
+    ldr wip, [xSELF, #THREAD_TID_OFFSET]
+    cmp wip, wip2
+    b.eq ExecuteNterpImpl
+.Lresolution_trampoline:
+    b art_quick_resolution_trampoline
+EndExecuteNterpWithClinitImpl:
+
 OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl
     .cfi_startproc
     sub x16, sp, #STACK_OVERFLOW_RESERVED_BYTES
@@ -1887,11 +1912,16 @@
 NTERP_TRAMPOLINE nterp_get_instance_field_offset, NterpGetInstanceFieldOffset
 NTERP_TRAMPOLINE nterp_filled_new_array, NterpFilledNewArray
 NTERP_TRAMPOLINE nterp_filled_new_array_range, NterpFilledNewArrayRange
-NTERP_TRAMPOLINE nterp_get_class_or_allocate_object, NterpGetClassOrAllocateObject
+NTERP_TRAMPOLINE nterp_get_class, NterpGetClass
+NTERP_TRAMPOLINE nterp_allocate_object, NterpAllocateObject
 NTERP_TRAMPOLINE nterp_get_method, NterpGetMethod
 NTERP_TRAMPOLINE nterp_hot_method, NterpHotMethod
 NTERP_TRAMPOLINE nterp_load_object, NterpLoadObject
 
+ENTRY nterp_deliver_pending_exception
+    DELIVER_PENDING_EXCEPTION
+END nterp_deliver_pending_exception
+
 // gen_mterp.py will inline the following definitions
 // within [ExecuteNterpImpl, EndExecuteNterpImpl).
 %def instruction_end():
diff --git a/runtime/interpreter/mterp/arm64ng/object.S b/runtime/interpreter/mterp/arm64ng/object.S
index df044d9..b6a6e24 100644
--- a/runtime/interpreter/mterp/arm64ng/object.S
+++ b/runtime/interpreter/mterp/arm64ng/object.S
@@ -19,7 +19,7 @@
    mov     x0, xSELF
    ldr     x1, [sp]
    mov     x2, xPC
-   bl      nterp_get_class_or_allocate_object
+   bl      nterp_get_class
    mov     x1, x0
    b       1b
 
@@ -90,7 +90,7 @@
    mov     x0, xSELF
    ldr     x1, [sp]
    mov     x2, xPC
-   bl      nterp_get_class_or_allocate_object
+   bl      nterp_get_class
    mov     x1, x0
    b       1b
 
@@ -466,6 +466,7 @@
 4:
    ldr     lr, [xSELF, #THREAD_ALLOC_OBJECT_ENTRYPOINT_OFFSET]
    blr     lr
+   dmb     ishst                       // need fence for making object's class visible
 1:
    lsr     w1, wINST, #8               // w1 <- A
    SET_VREG_OBJECT w0, w1              // fp[A] <- value
@@ -476,7 +477,7 @@
    mov     x0, xSELF
    ldr     x1, [sp]
    mov     x2, xPC
-   bl      nterp_get_class_or_allocate_object
+   bl      nterp_allocate_object
    b       1b
 3:
    bl      art_quick_read_barrier_mark_reg00
diff --git a/runtime/interpreter/mterp/arm64ng/other.S b/runtime/interpreter/mterp/arm64ng/other.S
index 1feafd5..3470ee8 100644
--- a/runtime/interpreter/mterp/arm64ng/other.S
+++ b/runtime/interpreter/mterp/arm64ng/other.S
@@ -66,7 +66,7 @@
    b 1b
 
 %def op_const_class():
-%  op_const_object(jumbo="0", helper="nterp_get_class_or_allocate_object")
+%  op_const_object(jumbo="0", helper="nterp_get_class")
 
 %def op_const_method_handle():
 %  op_const_object(jumbo="0")
diff --git a/runtime/interpreter/mterp/armng/array.S b/runtime/interpreter/mterp/armng/array.S
index 4ab418c..e685cbf 100644
--- a/runtime/interpreter/mterp/armng/array.S
+++ b/runtime/interpreter/mterp/armng/array.S
@@ -177,6 +177,7 @@
    GET_VREG r1, r1                     // r1<- vB (array length)
    ldr lr, [rSELF, #THREAD_ALLOC_ARRAY_ENTRYPOINT_OFFSET]
    blx lr
+   dmb ishst                           // need fence for making array's class visible
    ubfx    r1, rINST, #8, #4           // r1<- A
    SET_VREG_OBJECT r0, r1
    FETCH_ADVANCE_INST 2
@@ -186,7 +187,7 @@
    mov r0, rSELF
    ldr r1, [sp]
    mov r2, rPC
-   bl nterp_get_class_or_allocate_object
+   bl nterp_get_class
    b 1b
 3:
    bl art_quick_read_barrier_mark_reg00
diff --git a/runtime/interpreter/mterp/armng/main.S b/runtime/interpreter/mterp/armng/main.S
index 310a3fd..3647f3e 100644
--- a/runtime/interpreter/mterp/armng/main.S
+++ b/runtime/interpreter/mterp/armng/main.S
@@ -248,7 +248,7 @@
     .hidden \name
     .global \name
     .balign 16
-    // Padding of 3 * 8 bytes to get 16 bytes alignment of code entry.
+    // Padding of 3 * 4 bytes to get 16 bytes alignment of code entry.
     .long 0
     .long 0
     .long 0
@@ -284,7 +284,10 @@
   bl \helper
   RESTORE_SAVE_REFS_ONLY_FRAME
   REFRESH_MARKING_REGISTER
-  RETURN_OR_DELIVER_PENDING_EXCEPTION
+  ldr ip, [rSELF, #THREAD_EXCEPTION_OFFSET]  @ Get exception field.
+  cmp ip, #0
+  bne nterp_deliver_pending_exception
+  bx lr
 END \name
 .endm
 
@@ -1608,6 +1611,28 @@
  *  rest  method parameters
  */
 
+OAT_ENTRY ExecuteNterpWithClinitImpl, EndExecuteNterpWithClinitImpl
+    // For simplicity, we don't do a read barrier here, but instead rely
+    // on art_quick_resolution_trampoline to always have a suspend point before
+    // calling back here.
+    ldr r4, [r0, ART_METHOD_DECLARING_CLASS_OFFSET]
+    ldrb ip, [r4, MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET]
+    cmp ip, #MIRROR_CLASS_IS_VISIBLY_INITIALIZED_VALUE
+    bcs ExecuteNterpImpl
+    cmp ip, #MIRROR_CLASS_IS_INITIALIZED_VALUE
+    blo .Linitializing_check
+    dmb ish
+    b ExecuteNterpImpl
+.Linitializing_check:
+    cmp ip, #MIRROR_CLASS_IS_INITIALIZING_VALUE
+    blo art_quick_resolution_trampoline
+    ldr r4, [r4, #MIRROR_CLASS_CLINIT_THREAD_ID_OFFSET]
+    ldr ip, [rSELF, #THREAD_TID_OFFSET]
+    cmp r4, ip
+    beq ExecuteNterpImpl
+    b art_quick_resolution_trampoline
+EndExecuteNterpWithClinitImpl:
+
 OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl
     .cfi_startproc
     sub ip, sp, #STACK_OVERFLOW_RESERVED_BYTES
@@ -1969,11 +1994,16 @@
 NTERP_TRAMPOLINE nterp_get_instance_field_offset, NterpGetInstanceFieldOffset
 NTERP_TRAMPOLINE nterp_filled_new_array, NterpFilledNewArray
 NTERP_TRAMPOLINE nterp_filled_new_array_range, NterpFilledNewArrayRange
-NTERP_TRAMPOLINE nterp_get_class_or_allocate_object, NterpGetClassOrAllocateObject
+NTERP_TRAMPOLINE nterp_get_class, NterpGetClass
+NTERP_TRAMPOLINE nterp_allocate_object, NterpAllocateObject
 NTERP_TRAMPOLINE nterp_get_method, NterpGetMethod
 NTERP_TRAMPOLINE nterp_hot_method, NterpHotMethod
 NTERP_TRAMPOLINE nterp_load_object, NterpLoadObject
 
+ENTRY nterp_deliver_pending_exception
+    DELIVER_PENDING_EXCEPTION
+END nterp_deliver_pending_exception
+
 // gen_mterp.py will inline the following definitions
 // within [ExecuteNterpImpl, EndExecuteNterpImpl).
 %def instruction_end():
diff --git a/runtime/interpreter/mterp/armng/object.S b/runtime/interpreter/mterp/armng/object.S
index 7deffaf..cde8cf9 100644
--- a/runtime/interpreter/mterp/armng/object.S
+++ b/runtime/interpreter/mterp/armng/object.S
@@ -20,7 +20,7 @@
    mov     r0, rSELF
    ldr     r1, [sp]
    mov     r2, rPC
-   bl      nterp_get_class_or_allocate_object
+   bl      nterp_get_class
    mov     r1, r0
    b       1b
 
@@ -91,7 +91,7 @@
    mov     r0, rSELF
    ldr     r1, [sp]
    mov     r2, rPC
-   bl      nterp_get_class_or_allocate_object
+   bl      nterp_get_class
    mov     r1, r0
    b       1b
 
@@ -513,8 +513,9 @@
 4:
    ldr     lr, [rSELF, #THREAD_ALLOC_OBJECT_ENTRYPOINT_OFFSET]
    blx     lr
+   dmb     ishst                        // need fence for making object's class visible
 1:
-   lsr     r1, rINST, #8                    // r1 <- A
+   lsr     r1, rINST, #8                // r1 <- A
    SET_VREG_OBJECT r0, r1               // fp[A] <- value
    FETCH_ADVANCE_INST 2
    GET_INST_OPCODE ip
@@ -523,7 +524,7 @@
    mov     r0, rSELF
    ldr     r1, [sp]
    mov     r2, rPC
-   bl      nterp_get_class_or_allocate_object
+   bl      nterp_allocate_object
    b       1b
 3:
    bl      art_quick_read_barrier_mark_reg00
diff --git a/runtime/interpreter/mterp/armng/other.S b/runtime/interpreter/mterp/armng/other.S
index 3376808..7dfed62 100644
--- a/runtime/interpreter/mterp/armng/other.S
+++ b/runtime/interpreter/mterp/armng/other.S
@@ -67,7 +67,7 @@
    b 1b
 
 %def op_const_class():
-%  op_const_object(jumbo="0", helper="nterp_get_class_or_allocate_object")
+%  op_const_object(jumbo="0", helper="nterp_get_class")
 
 %def op_const_method_handle():
 %  op_const_object(jumbo="0")
diff --git a/runtime/interpreter/mterp/nterp.cc b/runtime/interpreter/mterp/nterp.cc
index d70a846..81e80ed 100644
--- a/runtime/interpreter/mterp/nterp.cc
+++ b/runtime/interpreter/mterp/nterp.cc
@@ -26,7 +26,6 @@
 #include "entrypoints/entrypoint_utils-inl.h"
 #include "interpreter/interpreter_cache-inl.h"
 #include "interpreter/interpreter_common.h"
-#include "interpreter/interpreter_intrinsics.h"
 #include "interpreter/shadow_frame-inl.h"
 #include "mirror/string-alloc-inl.h"
 #include "nterp_helpers.h"
@@ -34,53 +33,6 @@
 namespace art {
 namespace interpreter {
 
-bool IsNterpSupported() {
-  return !kPoisonHeapReferences && kUseReadBarrier;
-}
-
-bool CanRuntimeUseNterp() REQUIRES_SHARED(Locks::mutator_lock_) {
-  Runtime* runtime = Runtime::Current();
-  instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
-  // If the runtime is interpreter only, we currently don't use nterp as some
-  // parts of the runtime (like instrumentation) make assumption on an
-  // interpreter-only runtime to always be in a switch-like interpreter.
-  return IsNterpSupported() &&
-      !instr->InterpretOnly() &&
-      !runtime->IsAotCompiler() &&
-      !runtime->GetInstrumentation()->NeedsSlowInterpreterForListeners() &&
-      // An async exception has been thrown. We need to go to the switch interpreter. nterp doesn't
-      // know how to deal with these so we could end up never dealing with it if we are in an
-      // infinite loop.
-      !runtime->AreAsyncExceptionsThrown() &&
-      (runtime->GetJit() == nullptr || !runtime->GetJit()->JitAtFirstUse());
-}
-
-// The entrypoint for nterp, which ArtMethods can directly point to.
-extern "C" void ExecuteNterpImpl() REQUIRES_SHARED(Locks::mutator_lock_);
-
-const void* GetNterpEntryPoint() {
-  return reinterpret_cast<const void*>(interpreter::ExecuteNterpImpl);
-}
-
-/*
- * Verify some constants used by the nterp interpreter.
- */
-void CheckNterpAsmConstants() {
-  /*
-   * If we're using computed goto instruction transitions, make sure
-   * none of the handlers overflows the byte limit.  This won't tell
-   * which one did, but if any one is too big the total size will
-   * overflow.
-   */
-  const int width = kNterpHandlerSize;
-  ptrdiff_t interp_size = reinterpret_cast<uintptr_t>(artNterpAsmInstructionEnd) -
-                          reinterpret_cast<uintptr_t>(artNterpAsmInstructionStart);
-  if ((interp_size == 0) || (interp_size != (art::kNumPackedOpcodes * width))) {
-      LOG(FATAL) << "ERROR: unexpected asm interp size " << interp_size
-                 << "(did an instruction handler exceed " << width << " bytes?)";
-  }
-}
-
 inline void UpdateHotness(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
   // The hotness we will add to a method when we perform a
   // field/method/class/string lookup.
@@ -89,13 +41,12 @@
 }
 
 template<typename T>
-inline void UpdateCache(Thread* self, uint16_t* dex_pc_ptr, T value) {
-  DCHECK(kUseReadBarrier) << "Nterp only works with read barriers";
+inline void UpdateCache(Thread* self, const uint16_t* dex_pc_ptr, T value) {
   self->GetInterpreterCache()->Set(self, dex_pc_ptr, value);
 }
 
 template<typename T>
-inline void UpdateCache(Thread* self, uint16_t* dex_pc_ptr, T* value) {
+inline void UpdateCache(Thread* self, const uint16_t* dex_pc_ptr, T* value) {
   UpdateCache(self, dex_pc_ptr, reinterpret_cast<size_t>(value));
 }
 
@@ -244,76 +195,57 @@
   return dex_file->GetShorty(proto_idx);
 }
 
+static constexpr uint8_t kInvalidInvokeType = 255u;
+static_assert(static_cast<uint8_t>(kMaxInvokeType) < kInvalidInvokeType);
+
+static constexpr uint8_t GetOpcodeInvokeType(uint8_t opcode) {
+  switch (opcode) {
+    case Instruction::INVOKE_DIRECT:
+    case Instruction::INVOKE_DIRECT_RANGE:
+      return static_cast<uint8_t>(kDirect);
+    case Instruction::INVOKE_INTERFACE:
+    case Instruction::INVOKE_INTERFACE_RANGE:
+      return static_cast<uint8_t>(kInterface);
+    case Instruction::INVOKE_STATIC:
+    case Instruction::INVOKE_STATIC_RANGE:
+      return static_cast<uint8_t>(kStatic);
+    case Instruction::INVOKE_SUPER:
+    case Instruction::INVOKE_SUPER_RANGE:
+      return static_cast<uint8_t>(kSuper);
+    case Instruction::INVOKE_VIRTUAL:
+    case Instruction::INVOKE_VIRTUAL_RANGE:
+      return static_cast<uint8_t>(kVirtual);
+
+    default:
+      return kInvalidInvokeType;
+  }
+}
+
+static constexpr std::array<uint8_t, 256u> GenerateOpcodeInvokeTypes() {
+  std::array<uint8_t, 256u> opcode_invoke_types{};
+  for (size_t opcode = 0u; opcode != opcode_invoke_types.size(); ++opcode) {
+    opcode_invoke_types[opcode] = GetOpcodeInvokeType(opcode);
+  }
+  return opcode_invoke_types;
+}
+
+static constexpr std::array<uint8_t, 256u> kOpcodeInvokeTypes = GenerateOpcodeInvokeTypes();
+
 FLATTEN
-extern "C" size_t NterpGetMethod(Thread* self, ArtMethod* caller, uint16_t* dex_pc_ptr)
+extern "C" size_t NterpGetMethod(Thread* self, ArtMethod* caller, const uint16_t* dex_pc_ptr)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   UpdateHotness(caller);
   const Instruction* inst = Instruction::At(dex_pc_ptr);
-  InvokeType invoke_type = kStatic;
-  uint16_t method_index = 0;
-  switch (inst->Opcode()) {
-    case Instruction::INVOKE_DIRECT: {
-      method_index = inst->VRegB_35c();
-      invoke_type = kDirect;
-      break;
-    }
+  Instruction::Code opcode = inst->Opcode();
+  DCHECK(IsUint<8>(static_cast<std::underlying_type_t<Instruction::Code>>(opcode)));
+  uint8_t raw_invoke_type = kOpcodeInvokeTypes[opcode];
+  DCHECK_LE(raw_invoke_type, kMaxInvokeType);
+  InvokeType invoke_type = static_cast<InvokeType>(raw_invoke_type);
 
-    case Instruction::INVOKE_INTERFACE: {
-      method_index = inst->VRegB_35c();
-      invoke_type = kInterface;
-      break;
-    }
-
-    case Instruction::INVOKE_STATIC: {
-      method_index = inst->VRegB_35c();
-      invoke_type = kStatic;
-      break;
-    }
-
-    case Instruction::INVOKE_SUPER: {
-      method_index = inst->VRegB_35c();
-      invoke_type = kSuper;
-      break;
-    }
-    case Instruction::INVOKE_VIRTUAL: {
-      method_index = inst->VRegB_35c();
-      invoke_type = kVirtual;
-      break;
-    }
-
-    case Instruction::INVOKE_DIRECT_RANGE: {
-      method_index = inst->VRegB_3rc();
-      invoke_type = kDirect;
-      break;
-    }
-
-    case Instruction::INVOKE_INTERFACE_RANGE: {
-      method_index = inst->VRegB_3rc();
-      invoke_type = kInterface;
-      break;
-    }
-
-    case Instruction::INVOKE_STATIC_RANGE: {
-      method_index = inst->VRegB_3rc();
-      invoke_type = kStatic;
-      break;
-    }
-
-    case Instruction::INVOKE_SUPER_RANGE: {
-      method_index = inst->VRegB_3rc();
-      invoke_type = kSuper;
-      break;
-    }
-
-    case Instruction::INVOKE_VIRTUAL_RANGE: {
-      method_index = inst->VRegB_3rc();
-      invoke_type = kVirtual;
-      break;
-    }
-
-    default:
-      LOG(FATAL) << "Unknown instruction " << inst->Opcode();
-  }
+  // In release mode, this is just a simple load.
+  // In debug mode, this checks that we're using the correct instruction format.
+  uint16_t method_index =
+      (opcode >= Instruction::INVOKE_VIRTUAL_RANGE) ? inst->VRegB_3rc() : inst->VRegB_35c();
 
   ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
   ArtMethod* resolved_method = caller->SkipAccessChecks()
@@ -355,9 +287,7 @@
     }
     UpdateCache(self, dex_pc_ptr, result);
     return result;
-  } else if (resolved_method->GetDeclaringClass()->IsStringClass()
-             && !resolved_method->IsStatic()
-             && resolved_method->IsConstructor()) {
+  } else if (resolved_method->IsStringConstructor()) {
     CHECK_NE(invoke_type, kSuper);
     resolved_method = WellKnownClasses::StringInitToStringFactory(resolved_method);
     // Or the result with 1 to notify to nterp this is a string init method. We
@@ -374,71 +304,23 @@
   }
 }
 
-FLATTEN
-static ArtField* ResolveFieldWithAccessChecks(Thread* self,
-                                              ClassLinker* class_linker,
-                                              uint16_t field_index,
-                                              ArtMethod* caller,
-                                              bool is_static,
-                                              bool is_put,
-                                              size_t resolve_field_type)  // Resolve if not zero
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  if (caller->SkipAccessChecks()) {
-    return class_linker->ResolveField(field_index, caller, is_static);
-  }
-
-  caller = caller->GetInterfaceMethodIfProxy(kRuntimePointerSize);
-
-  StackHandleScope<2> hs(self);
-  Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(caller->GetDexCache()));
-  Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(caller->GetClassLoader()));
-
-  ArtField* resolved_field = class_linker->ResolveFieldJLS(field_index,
-                                                           h_dex_cache,
-                                                           h_class_loader);
-  if (resolved_field == nullptr) {
-    return nullptr;
-  }
-
-  ObjPtr<mirror::Class> fields_class = resolved_field->GetDeclaringClass();
-  if (UNLIKELY(resolved_field->IsStatic() != is_static)) {
-    ThrowIncompatibleClassChangeErrorField(resolved_field, is_static, caller);
-    return nullptr;
-  }
-  ObjPtr<mirror::Class> referring_class = caller->GetDeclaringClass();
-  if (UNLIKELY(!referring_class->CheckResolvedFieldAccess(fields_class,
-                                                          resolved_field,
-                                                          caller->GetDexCache(),
-                                                          field_index))) {
-    return nullptr;
-  }
-  if (UNLIKELY(is_put && resolved_field->IsFinal() && (fields_class != referring_class))) {
-    ThrowIllegalAccessErrorFinalField(caller, resolved_field);
-    return nullptr;
-  }
-  if (resolve_field_type != 0u && resolved_field->ResolveType() == nullptr) {
-    DCHECK(self->IsExceptionPending());
-    return nullptr;
-  }
-  return resolved_field;
-}
-
 extern "C" size_t NterpGetStaticField(Thread* self,
                                       ArtMethod* caller,
-                                      uint16_t* dex_pc_ptr,
+                                      const uint16_t* dex_pc_ptr,
                                       size_t resolve_field_type)  // Resolve if not zero
     REQUIRES_SHARED(Locks::mutator_lock_) {
   UpdateHotness(caller);
   const Instruction* inst = Instruction::At(dex_pc_ptr);
   uint16_t field_index = inst->VRegB_21c();
   ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+  Instruction::Code opcode = inst->Opcode();
   ArtField* resolved_field = ResolveFieldWithAccessChecks(
       self,
       class_linker,
       field_index,
       caller,
-      /* is_static */ true,
-      /* is_put */ IsInstructionSPut(inst->Opcode()),
+      /*is_static=*/ true,
+      /*is_put=*/ IsInstructionSPut(opcode),
       resolve_field_type);
 
   if (resolved_field == nullptr) {
@@ -461,27 +343,39 @@
     // check for it.
     return reinterpret_cast<size_t>(resolved_field) | 1;
   } else {
-    UpdateCache(self, dex_pc_ptr, resolved_field);
+    // For sput-object, try to resolve the field type even if we were not requested to.
+    // Only if the field type is successfully resolved can we update the cache. If we
+    // fail to resolve the type, we clear the exception to keep interpreter
+    // semantics of not throwing when null is stored.
+    if (opcode == Instruction::SPUT_OBJECT &&
+        resolve_field_type == 0 &&
+        resolved_field->ResolveType() == nullptr) {
+      DCHECK(self->IsExceptionPending());
+      self->ClearException();
+    } else {
+      UpdateCache(self, dex_pc_ptr, resolved_field);
+    }
     return reinterpret_cast<size_t>(resolved_field);
   }
 }
 
 extern "C" uint32_t NterpGetInstanceFieldOffset(Thread* self,
                                                 ArtMethod* caller,
-                                                uint16_t* dex_pc_ptr,
+                                                const uint16_t* dex_pc_ptr,
                                                 size_t resolve_field_type)  // Resolve if not zero
     REQUIRES_SHARED(Locks::mutator_lock_) {
   UpdateHotness(caller);
   const Instruction* inst = Instruction::At(dex_pc_ptr);
   uint16_t field_index = inst->VRegC_22c();
   ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+  Instruction::Code opcode = inst->Opcode();
   ArtField* resolved_field = ResolveFieldWithAccessChecks(
       self,
       class_linker,
       field_index,
       caller,
-      /* is_static */ false,
-      /* is_put */ IsInstructionIPut(inst->Opcode()),
+      /*is_static=*/ false,
+      /*is_put=*/ IsInstructionIPut(opcode),
       resolve_field_type);
   if (resolved_field == nullptr) {
     DCHECK(self->IsExceptionPending());
@@ -492,67 +386,86 @@
     // of volatile.
     return -resolved_field->GetOffset().Uint32Value();
   }
-  UpdateCache(self, dex_pc_ptr, resolved_field->GetOffset().Uint32Value());
+  // For iput-object, try to resolve the field type even if we were not requested to.
+  // Only if the field type is successfully resolved can we update the cache. If we
+  // fail to resolve the type, we clear the exception to keep interpreter
+  // semantics of not throwing when null is stored.
+  if (opcode == Instruction::IPUT_OBJECT &&
+      resolve_field_type == 0 &&
+      resolved_field->ResolveType() == nullptr) {
+    DCHECK(self->IsExceptionPending());
+    self->ClearException();
+  } else {
+    UpdateCache(self, dex_pc_ptr, resolved_field->GetOffset().Uint32Value());
+  }
   return resolved_field->GetOffset().Uint32Value();
 }
 
-extern "C" mirror::Object* NterpGetClassOrAllocateObject(Thread* self,
-                                                         ArtMethod* caller,
-                                                         uint16_t* dex_pc_ptr)
+extern "C" mirror::Object* NterpGetClass(Thread* self, ArtMethod* caller, uint16_t* dex_pc_ptr)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   UpdateHotness(caller);
   const Instruction* inst = Instruction::At(dex_pc_ptr);
-  dex::TypeIndex index;
-  switch (inst->Opcode()) {
-    case Instruction::NEW_INSTANCE:
-      index = dex::TypeIndex(inst->VRegB_21c());
-      break;
-    case Instruction::CHECK_CAST:
-      index = dex::TypeIndex(inst->VRegB_21c());
-      break;
-    case Instruction::INSTANCE_OF:
-      index = dex::TypeIndex(inst->VRegC_22c());
-      break;
-    case Instruction::CONST_CLASS:
-      index = dex::TypeIndex(inst->VRegB_21c());
-      break;
-    case Instruction::NEW_ARRAY:
-      index = dex::TypeIndex(inst->VRegC_22c());
-      break;
-    default:
-      LOG(FATAL) << "Unreachable";
-  }
+  Instruction::Code opcode = inst->Opcode();
+  DCHECK(opcode == Instruction::CHECK_CAST ||
+         opcode == Instruction::INSTANCE_OF ||
+         opcode == Instruction::CONST_CLASS ||
+         opcode == Instruction::NEW_ARRAY);
+
+  // In release mode, this is just a simple load.
+  // In debug mode, this checks that we're using the correct instruction format.
+  dex::TypeIndex index = dex::TypeIndex(
+      (opcode == Instruction::CHECK_CAST || opcode == Instruction::CONST_CLASS)
+          ? inst->VRegB_21c()
+          : inst->VRegC_22c());
+
   ObjPtr<mirror::Class> c =
       ResolveVerifyAndClinit(index,
                              caller,
                              self,
                              /* can_run_clinit= */ false,
                              /* verify_access= */ !caller->SkipAccessChecks());
-  if (c == nullptr) {
+  if (UNLIKELY(c == nullptr)) {
     DCHECK(self->IsExceptionPending());
     return nullptr;
   }
 
-  if (inst->Opcode() == Instruction::NEW_INSTANCE) {
-    gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
-    if (UNLIKELY(c->IsStringClass())) {
-      // We don't cache the class for strings as we need to special case their
-      // allocation.
-      return mirror::String::AllocEmptyString(self, allocator_type).Ptr();
-    } else {
-      if (!c->IsFinalizable() && c->IsInstantiable()) {
-        // Cache non-finalizable classes for next calls.
-        UpdateCache(self, dex_pc_ptr, c.Ptr());
-      }
-      return AllocObjectFromCode(c, self, allocator_type).Ptr();
-    }
-  } else {
-    // For all other cases, cache the class.
-    UpdateCache(self, dex_pc_ptr, c.Ptr());
-  }
+  UpdateCache(self, dex_pc_ptr, c.Ptr());
   return c.Ptr();
 }
 
+extern "C" mirror::Object* NterpAllocateObject(Thread* self,
+                                               ArtMethod* caller,
+                                               uint16_t* dex_pc_ptr)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  UpdateHotness(caller);
+  const Instruction* inst = Instruction::At(dex_pc_ptr);
+  DCHECK_EQ(inst->Opcode(), Instruction::NEW_INSTANCE);
+  dex::TypeIndex index = dex::TypeIndex(inst->VRegB_21c());
+  ObjPtr<mirror::Class> c =
+      ResolveVerifyAndClinit(index,
+                             caller,
+                             self,
+                             /* can_run_clinit= */ false,
+                             /* verify_access= */ !caller->SkipAccessChecks());
+  if (UNLIKELY(c == nullptr)) {
+    DCHECK(self->IsExceptionPending());
+    return nullptr;
+  }
+
+  gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
+  if (UNLIKELY(c->IsStringClass())) {
+    // We don't cache the class for strings as we need to special case their
+    // allocation.
+    return mirror::String::AllocEmptyString(self, allocator_type).Ptr();
+  } else {
+    if (!c->IsFinalizable() && c->IsInstantiable()) {
+      // Cache non-finalizable classes for next calls.
+      UpdateCache(self, dex_pc_ptr, c.Ptr());
+    }
+    return AllocObjectFromCode(c, self, allocator_type).Ptr();
+  }
+}
+
 extern "C" mirror::Object* NterpLoadObject(Thread* self, ArtMethod* caller, uint16_t* dex_pc_ptr)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   const Instruction* inst = Instruction::At(dex_pc_ptr);
diff --git a/runtime/interpreter/mterp/nterp.h b/runtime/interpreter/mterp/nterp.h
index 1590b28..4d5af39 100644
--- a/runtime/interpreter/mterp/nterp.h
+++ b/runtime/interpreter/mterp/nterp.h
@@ -32,6 +32,7 @@
 bool IsNterpSupported();
 bool CanRuntimeUseNterp();
 const void* GetNterpEntryPoint();
+const void* GetNterpWithClinitEntryPoint();
 
 constexpr uint16_t kNterpHotnessValue = 0;
 
diff --git a/runtime/interpreter/mterp/nterp_impl.cc b/runtime/interpreter/mterp/nterp_impl.cc
new file mode 100644
index 0000000..f2a9855
--- /dev/null
+++ b/runtime/interpreter/mterp/nterp_impl.cc
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "arch/instruction_set.h"
+#include "interpreter/interpreter_common.h"
+#include "nterp.h"
+
+/*
+ * Definitions for targets that support nterp.
+ */
+
+namespace art {
+
+namespace interpreter {
+
+bool IsNterpSupported() {
+  return !kPoisonHeapReferences && kReserveMarkingRegister &&
+         kRuntimeISA != InstructionSet::kRiscv64;
+}
+
+bool CanRuntimeUseNterp() REQUIRES_SHARED(Locks::mutator_lock_) {
+  Runtime* runtime = Runtime::Current();
+  instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
+  // If the runtime is interpreter only, we currently don't use nterp as some
+  // parts of the runtime (like instrumentation) make assumption on an
+  // interpreter-only runtime to always be in a switch-like interpreter.
+  return IsNterpSupported() && !runtime->IsJavaDebuggable() && !instr->EntryExitStubsInstalled() &&
+         !instr->InterpretOnly() && !runtime->IsAotCompiler() &&
+         !instr->NeedsSlowInterpreterForListeners() &&
+         // An async exception has been thrown. We need to go to the switch interpreter. nterp
+         // doesn't know how to deal with these so we could end up never dealing with it if we are
+         // in an infinite loop.
+         !runtime->AreAsyncExceptionsThrown() &&
+         (runtime->GetJit() == nullptr || !runtime->GetJit()->JitAtFirstUse());
+}
+
+// The entrypoint for nterp, which ArtMethods can directly point to.
+extern "C" void ExecuteNterpImpl() REQUIRES_SHARED(Locks::mutator_lock_);
+
+const void* GetNterpEntryPoint() {
+  return reinterpret_cast<const void*>(interpreter::ExecuteNterpImpl);
+}
+
+// Another entrypoint, which does a clinit check at entry.
+extern "C" void ExecuteNterpWithClinitImpl() REQUIRES_SHARED(Locks::mutator_lock_);
+
+const void* GetNterpWithClinitEntryPoint() {
+  return reinterpret_cast<const void*>(interpreter::ExecuteNterpWithClinitImpl);
+}
+
+/*
+ * Verify some constants used by the nterp interpreter.
+ */
+void CheckNterpAsmConstants() {
+  /*
+   * If we're using computed goto instruction transitions, make sure
+   * none of the handlers overflows the byte limit.  This won't tell
+   * which one did, but if any one is too big the total size will
+   * overflow.
+   */
+  const int width = kNterpHandlerSize;
+  ptrdiff_t interp_size = reinterpret_cast<uintptr_t>(artNterpAsmInstructionEnd) -
+                          reinterpret_cast<uintptr_t>(artNterpAsmInstructionStart);
+  if ((interp_size == 0) || (interp_size != (art::kNumPackedOpcodes * width))) {
+    LOG(FATAL) << "ERROR: unexpected asm interp size " << interp_size
+               << "(did an instruction handler exceed " << width << " bytes?)";
+  }
+}
+
+}  // namespace interpreter
+}  // namespace art
diff --git a/runtime/interpreter/mterp/nterp_stub.cc b/runtime/interpreter/mterp/nterp_stub.cc
deleted file mode 100644
index 95d11c2..0000000
--- a/runtime/interpreter/mterp/nterp_stub.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#include "base/enums.h"
-#include "base/locks.h"
-
-/*
- * Stub definitions for targets without nterp implementations.
- */
-
-namespace art {
-
-class ArtMethod;
-
-namespace interpreter {
-
-bool IsNterpSupported() {
-  return false;
-}
-
-bool CanRuntimeUseNterp() {
-  return false;
-}
-
-const void* GetNterpEntryPoint() {
-  return nullptr;
-}
-
-void CheckNterpAsmConstants() {
-}
-
-extern "C" void ExecuteNterpImpl() REQUIRES_SHARED(Locks::mutator_lock_) {
-  UNIMPLEMENTED(FATAL);
-}
-
-extern "C" void* artNterpAsmInstructionStart[] = { nullptr };
-extern "C" void* artNterpAsmInstructionEnd[] = { nullptr };
-
-}  // namespace interpreter
-}  // namespace art
diff --git a/runtime/interpreter/mterp/riscv64/arithmetic.S b/runtime/interpreter/mterp/riscv64/arithmetic.S
new file mode 100644
index 0000000..30cb903
--- /dev/null
+++ b/runtime/interpreter/mterp/riscv64/arithmetic.S
@@ -0,0 +1,248 @@
+%def binop(preinstr="", result="r0", chkzero="0", instr=""):
+    unimp
+
+%def binop2addr(preinstr="", result="r0", chkzero="0", instr=""):
+    unimp
+
+%def binopLit16(result="r0", chkzero="0", instr=""):
+    unimp
+
+%def binopLit8(extract="unimp", result="r0", chkzero="0", instr=""):
+    unimp
+
+%def binopWide(preinstr="", result0="r0", result1="r1", chkzero="0", instr=""):
+    unimp
+
+%def binopWide2addr(preinstr="", result0="r0", result1="r1", chkzero="0", instr=""):
+    unimp
+
+%def unop(preinstr="", instr=""):
+    unimp
+
+%def unopNarrower(preinstr="", instr=""):
+    unimp
+
+%def unopWide(preinstr="", instr=""):
+    unimp
+
+%def unopWider(preinstr="", instr=""):
+    unimp
+
+%def op_add_int():
+    unimp
+
+%def op_add_int_2addr():
+    unimp
+
+%def op_add_int_lit16():
+    unimp
+
+%def op_add_int_lit8():
+    unimp
+
+%def op_add_long():
+    unimp
+
+%def op_add_long_2addr():
+    unimp
+
+%def op_and_int():
+    unimp
+
+%def op_and_int_2addr():
+    unimp
+
+%def op_and_int_lit16():
+    unimp
+
+%def op_and_int_lit8():
+    unimp
+
+%def op_and_long():
+    unimp
+
+%def op_and_long_2addr():
+    unimp
+
+%def op_cmp_long():
+    unimp
+
+%def op_div_int():
+    unimp
+
+%def op_div_int_2addr():
+    unimp
+
+%def op_div_int_lit16():
+    unimp
+
+%def op_div_int_lit8():
+    unimp
+
+%def op_div_long():
+    unimp
+
+%def op_div_long_2addr():
+    unimp
+
+%def op_int_to_byte():
+    unimp
+
+%def op_int_to_char():
+    unimp
+
+%def op_int_to_long():
+    unimp
+
+%def op_int_to_short():
+    unimp
+
+%def op_long_to_int():
+    unimp
+
+%def op_mul_int():
+    unimp
+
+%def op_mul_int_2addr():
+    unimp
+
+%def op_mul_int_lit16():
+    unimp
+
+%def op_mul_int_lit8():
+    unimp
+
+%def op_mul_long():
+    unimp
+
+%def op_mul_long_2addr():
+    unimp
+
+%def op_neg_int():
+    unimp
+
+%def op_neg_long():
+    unimp
+
+%def op_not_int():
+    unimp
+
+%def op_not_long():
+    unimp
+
+%def op_or_int():
+    unimp
+
+%def op_or_int_2addr():
+    unimp
+
+%def op_or_int_lit16():
+    unimp
+
+%def op_or_int_lit8():
+    unimp
+
+%def op_or_long():
+    unimp
+
+%def op_or_long_2addr():
+    unimp
+
+%def op_rem_int():
+    unimp
+
+%def op_rem_int_2addr():
+    unimp
+
+%def op_rem_int_lit16():
+    unimp
+
+%def op_rem_int_lit8():
+    unimp
+
+%def op_rem_long():
+    unimp
+
+%def op_rem_long_2addr():
+    unimp
+
+%def op_rsub_int():
+    unimp
+
+%def op_rsub_int_lit8():
+    unimp
+
+%def op_shl_int():
+    unimp
+
+%def op_shl_int_2addr():
+    unimp
+
+%def op_shl_int_lit8():
+    unimp
+
+%def op_shl_long():
+    unimp
+
+%def op_shl_long_2addr():
+    unimp
+
+%def op_shr_int():
+    unimp
+
+%def op_shr_int_2addr():
+    unimp
+
+%def op_shr_int_lit8():
+    unimp
+
+%def op_shr_long():
+    unimp
+
+%def op_shr_long_2addr():
+    unimp
+
+%def op_sub_int():
+    unimp
+
+%def op_sub_int_2addr():
+    unimp
+
+%def op_sub_long():
+    unimp
+
+%def op_sub_long_2addr():
+    unimp
+
+%def op_ushr_int():
+    unimp
+
+%def op_ushr_int_2addr():
+    unimp
+
+%def op_ushr_int_lit8():
+    unimp
+
+%def op_ushr_long():
+    unimp
+
+%def op_ushr_long_2addr():
+    unimp
+
+%def op_xor_int():
+    unimp
+
+%def op_xor_int_2addr():
+    unimp
+
+%def op_xor_int_lit16():
+    unimp
+
+%def op_xor_int_lit8():
+    unimp
+
+%def op_xor_long():
+    unimp
+
+%def op_xor_long_2addr():
+    unimp
diff --git a/runtime/interpreter/mterp/riscv64/array.S b/runtime/interpreter/mterp/riscv64/array.S
new file mode 100644
index 0000000..e58f384
--- /dev/null
+++ b/runtime/interpreter/mterp/riscv64/array.S
@@ -0,0 +1,57 @@
+%def op_aget(load="unimp", shift="2", data_offset="MIRROR_INT_ARRAY_DATA_OFFSET", wide="0", is_object="0"):
+    unimp
+
+%def op_aget_boolean():
+%  op_aget(load="unimp", shift="0", data_offset="MIRROR_BOOLEAN_ARRAY_DATA_OFFSET", wide="0", is_object="0")
+
+%def op_aget_byte():
+%  op_aget(load="unimp", shift="0", data_offset="MIRROR_BYTE_ARRAY_DATA_OFFSET", wide="0", is_object="0")
+
+%def op_aget_char():
+%  op_aget(load="unimp", shift="1", data_offset="MIRROR_CHAR_ARRAY_DATA_OFFSET", wide="0", is_object="0")
+
+%def op_aget_object():
+%  op_aget(load="unimp", shift="2", data_offset="MIRROR_OBJECT_ARRAY_DATA_OFFSET", wide="0", is_object="1")
+
+%def op_aget_short():
+%  op_aget(load="unimp", shift="1", data_offset="MIRROR_SHORT_ARRAY_DATA_OFFSET", wide="0", is_object="0")
+
+%def op_aget_wide():
+%  op_aget(load="unimp", shift="3", data_offset="MIRROR_WIDE_ARRAY_DATA_OFFSET", wide="1", is_object="0")
+
+%def op_aput(store="unimp", shift="2", data_offset="MIRROR_INT_ARRAY_DATA_OFFSET", wide="0", is_object="0"):
+    unimp
+
+%def op_aput_boolean():
+%  op_aput(store="unimp", shift="0", data_offset="MIRROR_BOOLEAN_ARRAY_DATA_OFFSET", wide="0", is_object="0")
+
+%def op_aput_byte():
+%  op_aput(store="unimp", shift="0", data_offset="MIRROR_BYTE_ARRAY_DATA_OFFSET", wide="0", is_object="0")
+
+%def op_aput_char():
+%  op_aput(store="unimp", shift="1", data_offset="MIRROR_CHAR_ARRAY_DATA_OFFSET", wide="0", is_object="0")
+
+%def op_aput_short():
+%  op_aput(store="unimp", shift="1", data_offset="MIRROR_SHORT_ARRAY_DATA_OFFSET", wide="0", is_object="0")
+
+%def op_aput_wide():
+%  op_aput(store="unimp", shift="3", data_offset="MIRROR_WIDE_ARRAY_DATA_OFFSET", wide="1", is_object="0")
+
+%def op_aput_object():
+%  op_aput(store="unimp", shift="2", data_offset="MIRROR_OBJECT_ARRAY_DATA_OFFSET", wide="0", is_object="1")
+
+%def op_array_length():
+    unimp
+
+%def op_fill_array_data():
+    unimp
+
+%def op_filled_new_array(helper="nterp_filled_new_array"):
+    unimp
+
+%def op_filled_new_array_range():
+%  op_filled_new_array(helper="nterp_filled_new_array_range")
+
+%def op_new_array():
+    unimp
+
diff --git a/runtime/interpreter/mterp/riscv64/control_flow.S b/runtime/interpreter/mterp/riscv64/control_flow.S
new file mode 100644
index 0000000..6e263f3
--- /dev/null
+++ b/runtime/interpreter/mterp/riscv64/control_flow.S
@@ -0,0 +1,74 @@
+%def bincmp(condition=""):
+    unimp
+
+%def zcmp(condition=""):
+    unimp
+
+%def op_goto():
+    unimp
+
+%def op_goto_16():
+    unimp
+
+%def op_goto_32():
+    unimp
+
+%def op_if_eq():
+%  bincmp(condition="eq")
+
+%def op_if_eqz():
+%  zcmp(condition="eq")
+
+%def op_if_ge():
+%  bincmp(condition="ge")
+
+%def op_if_gez():
+%  zcmp(condition="ge")
+
+%def op_if_gt():
+%  bincmp(condition="gt")
+
+%def op_if_gtz():
+%  zcmp(condition="gt")
+
+%def op_if_le():
+%  bincmp(condition="le")
+
+%def op_if_lez():
+%  zcmp(condition="le")
+
+%def op_if_lt():
+%  bincmp(condition="lt")
+
+%def op_if_ltz():
+%  zcmp(condition="lt")
+
+%def op_if_ne():
+%  bincmp(condition="ne")
+
+%def op_if_nez():
+%  zcmp(condition="ne")
+
+%def op_packed_switch(func="NterpDoPackedSwitch"):
+    unimp
+
+%def op_sparse_switch():
+%  op_packed_switch(func="NterpDoSparseSwitch")
+
+/*
+ * Return a 32-bit value.
+ */
+%def op_return(is_object="0", is_void="0", is_wide="0"):
+    unimp
+
+%def op_return_object():
+%  op_return(is_object="1", is_void="0", is_wide="0")
+
+%def op_return_void():
+%  op_return(is_object="0", is_void="1", is_wide="0")
+
+%def op_return_wide():
+%  op_return(is_object="0", is_void="0", is_wide="1")
+
+%def op_throw():
+    unimp
diff --git a/runtime/interpreter/mterp/riscv64/floating_point.S b/runtime/interpreter/mterp/riscv64/floating_point.S
new file mode 100644
index 0000000..cd6c82a
--- /dev/null
+++ b/runtime/interpreter/mterp/riscv64/floating_point.S
@@ -0,0 +1,128 @@
+%def fbinop(instr=""):
+    unimp
+
+%def fbinop2addr(instr=""):
+    unimp
+
+%def fbinopWide(instr=""):
+    unimp
+
+%def fbinopWide2addr(instr=""):
+    unimp
+
+%def funop(instr=""):
+    unimp
+
+%def funopNarrower(instr=""):
+    unimp
+
+%def funopWider(instr=""):
+    unimp
+
+%def op_add_double():
+    unimp
+
+%def op_add_double_2addr():
+    unimp
+
+%def op_add_float():
+    unimp
+
+%def op_add_float_2addr():
+    unimp
+
+%def op_cmpg_double():
+    unimp
+
+%def op_cmpg_float():
+    unimp
+
+%def op_cmpl_double():
+    unimp
+
+%def op_cmpl_float():
+    unimp
+
+%def op_div_double():
+    unimp
+
+%def op_div_double_2addr():
+    unimp
+
+%def op_div_float():
+    unimp
+
+%def op_div_float_2addr():
+    unimp
+
+%def op_double_to_float():
+    unimp
+
+%def op_double_to_int():
+    unimp
+
+%def op_double_to_long():
+    unimp
+
+%def op_float_to_double():
+    unimp
+
+%def op_float_to_int():
+    unimp
+
+%def op_float_to_long():
+    unimp
+
+%def op_int_to_double():
+    unimp
+
+%def op_int_to_float():
+    unimp
+
+%def op_long_to_double():
+    unimp
+
+%def op_long_to_float():
+    unimp
+
+%def op_mul_double():
+    unimp
+
+%def op_mul_double_2addr():
+    unimp
+
+%def op_mul_float():
+    unimp
+
+%def op_mul_float_2addr():
+    unimp
+
+%def op_neg_double():
+    unimp
+
+%def op_neg_float():
+    unimp
+
+%def op_rem_double():
+    unimp
+
+%def op_rem_double_2addr():
+    unimp
+
+%def op_rem_float():
+    unimp
+
+%def op_rem_float_2addr():
+    unimp
+
+%def op_sub_double():
+    unimp
+
+%def op_sub_double_2addr():
+    unimp
+
+%def op_sub_float():
+    unimp
+
+%def op_sub_float_2addr():
+    unimp
diff --git a/runtime/interpreter/mterp/riscv64/invoke.S b/runtime/interpreter/mterp/riscv64/invoke.S
new file mode 100644
index 0000000..8d3b522
--- /dev/null
+++ b/runtime/interpreter/mterp/riscv64/invoke.S
@@ -0,0 +1,56 @@
+%def op_invoke_custom():
+    unimp
+
+%def op_invoke_custom_range():
+    unimp
+
+%def invoke_direct_or_super(helper="", range="", is_super=""):
+    unimp
+
+%def op_invoke_direct():
+    unimp
+
+%def op_invoke_direct_range():
+    unimp
+
+%def op_invoke_super():
+    unimp
+
+%def op_invoke_super_range():
+    unimp
+
+%def op_invoke_polymorphic():
+    unimp
+
+%def op_invoke_polymorphic_range():
+    unimp
+
+%def invoke_interface(range=""):
+    unimp
+
+%def op_invoke_interface_slow_path():
+    unimp
+
+%def op_invoke_interface():
+    unimp
+
+%def op_invoke_interface_range():
+    unimp
+
+%def invoke_static(helper=""):
+    unimp
+
+%def op_invoke_static():
+    unimp
+
+%def op_invoke_static_range():
+    unimp
+
+%def invoke_virtual(helper="", range=""):
+    unimp
+
+%def op_invoke_virtual():
+    unimp
+
+%def op_invoke_virtual_range():
+    unimp
diff --git a/runtime/interpreter/mterp/riscv64/main.S b/runtime/interpreter/mterp/riscv64/main.S
new file mode 100644
index 0000000..b2ca460
--- /dev/null
+++ b/runtime/interpreter/mterp/riscv64/main.S
@@ -0,0 +1,132 @@
+%def header():
+/*
+ * Copyright (C) 2023 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.
+ */
+
+/*
+ * This is a #include, not a %include, because we want the C pre-processor
+ * to expand the macros into assembler assignment statements.
+ */
+#include "asm_support.h"
+#include "arch/riscv64/asm_support_riscv64.S"
+
+// An assembly entry that has a OatQuickMethodHeader prefix.
+.macro OAT_ENTRY name, end
+    .type \name, @function
+    .hidden \name
+    .global \name
+    .balign 16
+    // Padding of 3 * 4 bytes to get 16 bytes alignment of code entry.
+    .4byte 0, 0, 0
+    // OatQuickMethodHeader `data_` field. Note that the top two bits must be clear.
+    .4byte (\end - \name)
+\name:
+.endm
+
+.macro SIZE name
+    .size \name, .-\name
+.endm
+
+// Similar to ENTRY but without the CFI directives.
+.macro NAME_START name
+    .type \name, @function
+    .hidden \name  // Hide this as a global symbol, so we do not incur plt calls.
+    .global \name
+    /* XXX Cache alignment for function entry */
+    .balign 16
+\name:
+.endm
+
+.macro NAME_END name
+  SIZE \name
+.endm
+
+%def entry():
+/*
+ * ArtMethod entry point.
+ *
+ * On entry:
+ *  XXX   ArtMethod* callee
+ *  rest  method parameters
+ */
+
+OAT_ENTRY ExecuteNterpWithClinitImpl, EndExecuteNterpWithClinitImpl
+    // For simplicity, we don't do a read barrier here, but instead rely
+    // on art_quick_resolution_trampoline to always have a suspend point before
+    // calling back here.
+    unimp
+EndExecuteNterpWithClinitImpl:
+
+OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl
+    .cfi_startproc
+    unimp
+
+%def fetch_from_thread_cache(dest_reg, miss_label):
+
+%def footer():
+/*
+ * ===========================================================================
+ *  Common subroutines and data
+ * ===========================================================================
+ */
+
+    .text
+    .align  2
+
+
+// Enclose all code below in a symbol (which gets printed in backtraces).
+NAME_START nterp_helper
+// This is the logical end of ExecuteNterpImpl, where the frame info applies.
+// EndExecuteNterpImpl includes the methods below as we want the runtime to
+// see them as part of the Nterp PCs.
+.cfi_endproc
+NAME_END nterp_helper
+
+// This is the end of PCs contained by the OatQuickMethodHeader created for the interpreter
+// entry point.
+    .type EndExecuteNterpImpl, @function
+    .hidden EndExecuteNterpImpl
+    .global EndExecuteNterpImpl
+EndExecuteNterpImpl:
+
+// gen_mterp.py will inline the following definitions
+// within [ExecuteNterpImpl, EndExecuteNterpImpl).
+%def instruction_start():
+    .type artNterpAsmInstructionStart, @function
+    .hidden artNterpAsmInstructionStart
+    .global artNterpAsmInstructionStart
+artNterpAsmInstructionStart = .L_op_nop
+    .text
+
+%def instruction_end():
+    .type artNterpAsmInstructionEnd, @function
+    .hidden artNterpAsmInstructionEnd
+    .global artNterpAsmInstructionEnd
+artNterpAsmInstructionEnd:
+    unimp
+
+%def opcode_pre():
+%   pass
+%def opcode_name_prefix():
+%   return "nterp_"
+%def opcode_start():
+    NAME_START nterp_${opcode}
+%def opcode_end():
+    NAME_END nterp_${opcode}
+    unimp
+%def opcode_slow_path_start(name):
+    NAME_START ${name}
+%def opcode_slow_path_end(name):
+    NAME_END ${name}
diff --git a/runtime/interpreter/mterp/riscv64/object.S b/runtime/interpreter/mterp/riscv64/object.S
new file mode 100644
index 0000000..449df1e
--- /dev/null
+++ b/runtime/interpreter/mterp/riscv64/object.S
@@ -0,0 +1,109 @@
+%def op_check_cast():
+    unimp
+
+%def op_check_cast_slow_path():
+    unimp
+
+%def op_instance_of():
+    unimp
+
+%def op_instance_of_slow_path():
+    unimp
+
+%def op_iget_boolean():
+%  op_iget(load="ldrb", wide="0", is_object="0")
+
+%def op_iget_byte():
+%  op_iget(load="ldrsb", wide="0", is_object="0")
+
+%def op_iget_char():
+%  op_iget(load="ldrh", wide="0", is_object="0")
+
+%def op_iget_short():
+%  op_iget(load="ldrsh", wide="0", is_object="0")
+
+%def op_iget(load="ldr", wide="0", is_object="0"):
+    unimp
+
+%def op_iget_slow_path(load, wide, is_object):
+
+%def op_iget_wide():
+%  op_iget(load="ldr", wide="1", is_object="0")
+
+%def op_iget_object():
+%  op_iget(load="ldr", wide="0", is_object="1")
+
+%def op_iput_boolean():
+%  op_iput(store="strb", wide="0", is_object="0")
+
+%def op_iput_byte():
+%  op_iput(store="strb", wide="0", is_object="0")
+
+%def op_iput_char():
+%  op_iput(store="strh", wide="0", is_object="0")
+
+%def op_iput_short():
+%  op_iput(store="strh", wide="0", is_object="0")
+
+%def op_iput(store="str", wide="0", is_object="0"):
+    unimp
+
+%def op_iput_slow_path(store, wide, is_object):
+    unimp
+
+%def op_iput_wide():
+%  op_iput(store="str", wide="1", is_object="0")
+
+%def op_iput_object():
+%  op_iput(store="str", wide="0", is_object="1")
+
+%def op_sget_boolean():
+%  op_sget(load="ldrb", wide="0", is_object="0")
+
+%def op_sget_byte():
+%  op_sget(load="ldrsb", wide="0", is_object="0")
+
+%def op_sget_char():
+%  op_sget(load="ldrh", wide="0", is_object="0")
+
+%def op_sget_short():
+%  op_sget(load="ldrsh", wide="0", is_object="0")
+
+%def op_sget(load="ldr", wide="0", is_object="0"):
+    unimp
+
+%def op_sget_slow_path(load="ldr", wide="0", is_object="0"):
+    unimp
+
+%def op_sget_wide():
+%  op_sget(load="ldr", wide="1", is_object="0")
+
+%def op_sget_object():
+%  op_sget(load="ldr", wide="0", is_object="1")
+
+%def op_sput_boolean():
+%  op_sput(store="strb", wide="0", is_object="0")
+
+%def op_sput_byte():
+%  op_sput(store="strb", wide="0", is_object="0")
+
+%def op_sput_char():
+%  op_sput(store="strh", wide="0", is_object="0")
+
+%def op_sput_short():
+%  op_sput(store="strh", wide="0", is_object="0")
+
+%def op_sput(store="str", wide="0", is_object="0"):
+    unimp
+
+%def op_sput_slow_path(store, wide, is_object):
+    unimp
+
+%def op_sput_wide():
+%  op_sput(store="str", wide="1", is_object="0")
+
+%def op_sput_object():
+%  op_sput(store="str", wide="0", is_object="1")
+
+%def op_new_instance():
+    unimp
diff --git a/runtime/interpreter/mterp/riscv64/other.S b/runtime/interpreter/mterp/riscv64/other.S
new file mode 100644
index 0000000..0e7ba95
--- /dev/null
+++ b/runtime/interpreter/mterp/riscv64/other.S
@@ -0,0 +1,167 @@
+%def unused():
+    ebreak
+%def op_const():
+    unimp
+
+%def op_const_16():
+    unimp
+%def op_const_4():
+    unimp
+%def op_const_high16():
+    unimp
+%def op_const_object(jumbo="0", helper="nterp_load_object"):
+    unimp
+%def op_const_class():
+    unimp
+%def op_const_method_handle():
+    unimp
+%def op_const_method_type():
+    unimp
+%def op_const_string():
+    unimp
+%def op_const_string_jumbo():
+    unimp
+%def op_const_wide():
+    unimp
+%def op_const_wide_16():
+    unimp
+%def op_const_wide_32():
+    unimp
+%def op_const_wide_high16():
+    unimp
+%def op_monitor_enter():
+    unimp
+%def op_monitor_exit():
+    unimp
+%def op_move(is_object="0"):
+    unimp
+%def op_move_16(is_object="0"):
+    unimp
+%def op_move_exception():
+    unimp
+%def op_move_from16(is_object="0"):
+    unimp
+%def op_move_object():
+    unimp
+%def op_move_object_16():
+    unimp
+%def op_move_object_from16():
+    unimp
+%def op_move_result(is_object="0"):
+    unimp
+%def op_move_result_object():
+    unimp
+%def op_move_result_wide():
+    unimp
+%def op_move_wide():
+    unimp
+%def op_move_wide_16():
+    unimp
+%def op_move_wide_from16():
+    unimp
+
+%def op_nop():
+    unimp
+
+%def op_unused_3e():
+%  unused()
+
+%def op_unused_3f():
+%  unused()
+
+%def op_unused_40():
+%  unused()
+
+%def op_unused_41():
+%  unused()
+
+%def op_unused_42():
+%  unused()
+
+%def op_unused_43():
+%  unused()
+
+%def op_unused_73():
+%  unused()
+
+%def op_unused_79():
+%  unused()
+
+%def op_unused_7a():
+%  unused()
+
+%def op_unused_e3():
+%  unused()
+
+%def op_unused_e4():
+%  unused()
+
+%def op_unused_e5():
+%  unused()
+
+%def op_unused_e6():
+%  unused()
+
+%def op_unused_e7():
+%  unused()
+
+%def op_unused_e8():
+%  unused()
+
+%def op_unused_e9():
+%  unused()
+
+%def op_unused_ea():
+%  unused()
+
+%def op_unused_eb():
+%  unused()
+
+%def op_unused_ec():
+%  unused()
+
+%def op_unused_ed():
+%  unused()
+
+%def op_unused_ee():
+%  unused()
+
+%def op_unused_ef():
+%  unused()
+
+%def op_unused_f0():
+%  unused()
+
+%def op_unused_f1():
+%  unused()
+
+%def op_unused_f2():
+%  unused()
+
+%def op_unused_f3():
+%  unused()
+
+%def op_unused_f4():
+%  unused()
+
+%def op_unused_f5():
+%  unused()
+
+%def op_unused_f6():
+%  unused()
+
+%def op_unused_f7():
+%  unused()
+
+%def op_unused_f8():
+%  unused()
+
+%def op_unused_f9():
+%  unused()
+
+%def op_unused_fc():
+%  unused()
+
+%def op_unused_fd():
+%  unused()
+
diff --git a/runtime/interpreter/mterp/x86_64ng/main.S b/runtime/interpreter/mterp/x86_64ng/main.S
index bd191c0..9ad7efa 100644
--- a/runtime/interpreter/mterp/x86_64ng/main.S
+++ b/runtime/interpreter/mterp/x86_64ng/main.S
@@ -208,7 +208,7 @@
     ASM_HIDDEN SYMBOL(\name)
     .global SYMBOL(\name)
     .balign 16
-    // Padding of 3 * 8 bytes to get 16 bytes alignment of code entry.
+    // Padding of 3 * 4 bytes to get 16 bytes alignment of code entry.
     .long 0
     .long 0
     .long 0
@@ -237,7 +237,9 @@
   SETUP_SAVE_REFS_ONLY_FRAME
   call \helper
   RESTORE_SAVE_REFS_ONLY_FRAME
-  RETURN_OR_DELIVER_PENDING_EXCEPTION
+  cmpq LITERAL(0), %gs:THREAD_EXCEPTION_OFFSET
+  jne nterp_deliver_pending_exception
+  ret
 END_FUNCTION \name
 .endm
 
@@ -1694,6 +1696,21 @@
  *  rest  method parameters
  */
 
+OAT_ENTRY ExecuteNterpWithClinitImpl, EndExecuteNterpWithClinitImpl
+    // For simplicity, we don't do a read barrier here, but instead rely
+    // on art_quick_resolution_trampoline to always have a suspend point before
+    // calling back here.
+    movl ART_METHOD_DECLARING_CLASS_OFFSET(%rdi), %r10d
+    cmpb  $$(MIRROR_CLASS_IS_VISIBLY_INITIALIZED_VALUE), MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET(%r10d)
+    jae ExecuteNterpImpl
+    cmpb  $$(MIRROR_CLASS_IS_INITIALIZING_VALUE), MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET(%r10d)
+    jb art_quick_resolution_trampoline
+    movl MIRROR_CLASS_CLINIT_THREAD_ID_OFFSET(%r10d), %r10d
+    cmpl %r10d, rSELF:THREAD_TID_OFFSET
+    je ExecuteNterpImpl
+    jmp art_quick_resolution_trampoline
+EndExecuteNterpWithClinitImpl:
+
 OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl
     .cfi_startproc
     .cfi_def_cfa rsp, 8
@@ -1923,7 +1940,7 @@
    movq rSELF:THREAD_SELF_OFFSET, %rdi
    movq 0(%rsp), %rsi
    movq rPC, %rdx
-   call nterp_get_class_or_allocate_object
+   call nterp_allocate_object
    jmp 1b
 3:
    // 07 is %rdi
@@ -1949,7 +1966,7 @@
    movq rSELF:THREAD_SELF_OFFSET, %rdi
    movq 0(%rsp), %rsi
    movq rPC, %rdx
-   call nterp_get_class_or_allocate_object
+   call nterp_get_class
    movq %rax, %rdi
    jmp 1b
 3:
@@ -2295,11 +2312,16 @@
 NTERP_TRAMPOLINE nterp_get_instance_field_offset, NterpGetInstanceFieldOffset
 NTERP_TRAMPOLINE nterp_filled_new_array, NterpFilledNewArray
 NTERP_TRAMPOLINE nterp_filled_new_array_range, NterpFilledNewArrayRange
-NTERP_TRAMPOLINE nterp_get_class_or_allocate_object, NterpGetClassOrAllocateObject
+NTERP_TRAMPOLINE nterp_get_class, NterpGetClass
+NTERP_TRAMPOLINE nterp_allocate_object, NterpAllocateObject
 NTERP_TRAMPOLINE nterp_get_method, NterpGetMethod
 NTERP_TRAMPOLINE nterp_hot_method, NterpHotMethod
 NTERP_TRAMPOLINE nterp_load_object, NterpLoadObject
 
+DEFINE_FUNCTION nterp_deliver_pending_exception
+    DELIVER_PENDING_EXCEPTION
+END_FUNCTION nterp_deliver_pending_exception
+
 // gen_mterp.py will inline the following definitions
 // within [ExecuteNterpImpl, EndExecuteNterpImpl).
 %def instruction_end():
diff --git a/runtime/interpreter/mterp/x86_64ng/object.S b/runtime/interpreter/mterp/x86_64ng/object.S
index 140ea75..21a6e67 100644
--- a/runtime/interpreter/mterp/x86_64ng/object.S
+++ b/runtime/interpreter/mterp/x86_64ng/object.S
@@ -16,7 +16,7 @@
    movq rSELF:THREAD_SELF_OFFSET, %rdi
    movq 0(%rsp), %rsi
    movq rPC, %rdx
-   call nterp_get_class_or_allocate_object
+   call nterp_get_class
    movq %rax, %rsi
    jmp 1b
 
@@ -149,7 +149,7 @@
    movq rSELF:THREAD_SELF_OFFSET, %rdi
    movq 0(%rsp), %rsi
    movq rPC, %rdx
-   call nterp_get_class_or_allocate_object
+   call nterp_get_class
    movq %rax, %rsi
    jmp .L${opcode}_start
 
diff --git a/runtime/interpreter/mterp/x86_64ng/other.S b/runtime/interpreter/mterp/x86_64ng/other.S
index a72ee58..f789086 100644
--- a/runtime/interpreter/mterp/x86_64ng/other.S
+++ b/runtime/interpreter/mterp/x86_64ng/other.S
@@ -53,7 +53,7 @@
    jmp 1b
 
 %def op_const_class():
-%  op_const_object(jumbo="0", helper="nterp_get_class_or_allocate_object")
+%  op_const_object(jumbo="0", helper="nterp_get_class")
 
 %def op_const_method_handle():
 %  op_const_object(jumbo="0")
diff --git a/runtime/interpreter/mterp/x86ng/main.S b/runtime/interpreter/mterp/x86ng/main.S
index db8519b..5b0edd4 100644
--- a/runtime/interpreter/mterp/x86ng/main.S
+++ b/runtime/interpreter/mterp/x86ng/main.S
@@ -275,7 +275,9 @@
   RESTORE_IBASE
   FETCH_INST_CLEAR_OPCODE
   RESTORE_SAVE_REFS_ONLY_FRAME
-  RETURN_OR_DELIVER_PENDING_EXCEPTION
+  cmpl LITERAL(0), %fs:THREAD_EXCEPTION_OFFSET
+  jne nterp_deliver_pending_exception
+  ret
 END_FUNCTION \name
 .endm
 
@@ -1757,6 +1759,27 @@
  *  rest  method parameters
  */
 
+OAT_ENTRY ExecuteNterpWithClinitImpl, EndExecuteNterpWithClinitImpl
+    push %esi
+    // For simplicity, we don't do a read barrier here, but instead rely
+    // on art_quick_resolution_trampoline to always have a suspend point before
+    // calling back here.
+    movl ART_METHOD_DECLARING_CLASS_OFFSET(%eax), %esi
+    cmpb $$(MIRROR_CLASS_IS_VISIBLY_INITIALIZED_VALUE), MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET(%esi)
+    jae .Lcontinue_execute_nterp
+    cmpb  $$(MIRROR_CLASS_IS_INITIALIZING_VALUE), MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET(%esi)
+    jb .Linvoke_trampoline
+    movl MIRROR_CLASS_CLINIT_THREAD_ID_OFFSET(%esi), %esi
+    cmpl %esi, rSELF:THREAD_TID_OFFSET
+    je .Lcontinue_execute_nterp
+.Linvoke_trampoline:
+    pop %esi
+    jmp art_quick_resolution_trampoline
+.Lcontinue_execute_nterp:
+    pop %esi
+    jmp ExecuteNterpImpl
+EndExecuteNterpWithClinitImpl:
+
 OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl
     .cfi_startproc
     .cfi_def_cfa esp, 4
@@ -1980,7 +2003,7 @@
    movl rSELF:THREAD_SELF_OFFSET, ARG0
    movl 0(%esp), ARG1
    movl rPC, ARG2
-   call nterp_get_class_or_allocate_object
+   call nterp_allocate_object
    jmp 1b
 3:
    // 00 is %eax
@@ -2008,7 +2031,7 @@
    movl rSELF:THREAD_SELF_OFFSET, ARG0
    movl 0(%esp), ARG1
    movl rPC, ARG2
-   call nterp_get_class_or_allocate_object
+   call nterp_get_class
    jmp 1b
 3:
    // 00 is %eax
@@ -2339,11 +2362,16 @@
 NTERP_TRAMPOLINE nterp_get_instance_field_offset, NterpGetInstanceFieldOffset
 NTERP_TRAMPOLINE nterp_filled_new_array, NterpFilledNewArray
 NTERP_TRAMPOLINE nterp_filled_new_array_range, NterpFilledNewArrayRange
-NTERP_TRAMPOLINE nterp_get_class_or_allocate_object, NterpGetClassOrAllocateObject
+NTERP_TRAMPOLINE nterp_get_class, NterpGetClass
+NTERP_TRAMPOLINE nterp_allocate_object, NterpAllocateObject
 NTERP_TRAMPOLINE nterp_get_method, NterpGetMethod
 NTERP_TRAMPOLINE nterp_hot_method, NterpHotMethod
 NTERP_TRAMPOLINE nterp_load_object, NterpLoadObject
 
+DEFINE_FUNCTION nterp_deliver_pending_exception
+    DELIVER_PENDING_EXCEPTION
+END_FUNCTION nterp_deliver_pending_exception
+
 // gen_mterp.py will inline the following definitions
 // within [ExecuteNterpImpl, EndExecuteNterpImpl).
 %def instruction_end():
diff --git a/runtime/interpreter/mterp/x86ng/object.S b/runtime/interpreter/mterp/x86ng/object.S
index 1d11e10..39091ce 100644
--- a/runtime/interpreter/mterp/x86ng/object.S
+++ b/runtime/interpreter/mterp/x86ng/object.S
@@ -16,7 +16,7 @@
    movl rSELF:THREAD_SELF_OFFSET, ARG0
    movl 0(%esp), ARG1
    movl rPC, ARG2
-   call nterp_get_class_or_allocate_object
+   call nterp_get_class
    movl %eax, %ecx
    jmp 1b
 
@@ -58,7 +58,7 @@
    movl rSELF:THREAD_SELF_OFFSET, ARG0
    movl 0(%esp), ARG1
    movl rPC, ARG2
-   call nterp_get_class_or_allocate_object
+   call nterp_get_class
    movl %eax, %ecx
    jmp 1b
 
diff --git a/runtime/interpreter/mterp/x86ng/other.S b/runtime/interpreter/mterp/x86ng/other.S
index 4cf982c..6dd1ce3 100644
--- a/runtime/interpreter/mterp/x86ng/other.S
+++ b/runtime/interpreter/mterp/x86ng/other.S
@@ -53,7 +53,7 @@
    jmp 1b
 
 %def op_const_class():
-%  op_const_object(jumbo="0", helper="nterp_get_class_or_allocate_object")
+%  op_const_object(jumbo="0", helper="nterp_get_class")
 
 %def op_const_method_handle():
 %  op_const_object(jumbo="0")
diff --git a/runtime/interpreter/shadow_frame.h b/runtime/interpreter/shadow_frame.h
index 8cb2b33..7ca2423 100644
--- a/runtime/interpreter/shadow_frame.h
+++ b/runtime/interpreter/shadow_frame.h
@@ -54,7 +54,7 @@
     // We have been requested to notify when this frame gets popped.
     kNotifyFramePop = 1 << 0,
     // We have been asked to pop this frame off the stack as soon as possible.
-    kForcePopFrame  = 1 << 1,
+    kForcePopFrame = 1 << 1,
     // We have been asked to re-execute the last instruction.
     kForceRetryInst = 1 << 2,
     // Mark that we expect the next frame to retry the last instruction (used by instrumentation and
@@ -62,6 +62,9 @@
     kSkipMethodExitEvents = 1 << 3,
     // Used to suppress exception events caused by other instrumentation events.
     kSkipNextExceptionEvent = 1 << 4,
+    // Used to specify if DexPCMoveEvents have to be reported. These events will
+    // only be reported if the method has a breakpoint set.
+    kNotifyDexPcMoveEvents = 1 << 5,
   };
 
  public:
@@ -72,10 +75,11 @@
   }
 
   // Create ShadowFrame in heap for deoptimization.
-  static ShadowFrame* CreateDeoptimizedFrame(uint32_t num_vregs, ShadowFrame* link,
-                                             ArtMethod* method, uint32_t dex_pc) {
+  static ShadowFrame* CreateDeoptimizedFrame(uint32_t num_vregs,
+                                             ArtMethod* method,
+                                             uint32_t dex_pc) {
     uint8_t* memory = new uint8_t[ComputeSize(num_vregs)];
-    return CreateShadowFrameImpl(num_vregs, link, method, dex_pc, memory);
+    return CreateShadowFrameImpl(num_vregs, method, dex_pc, memory);
   }
 
   // Delete a ShadowFrame allocated on the heap for deoptimization.
@@ -87,12 +91,11 @@
 
   // Create a shadow frame in a fresh alloca. This needs to be in the context of the caller.
   // Inlining doesn't work, the compiler will still undo the alloca. So this needs to be a macro.
-#define CREATE_SHADOW_FRAME(num_vregs, link, method, dex_pc) ({                              \
+#define CREATE_SHADOW_FRAME(num_vregs, method, dex_pc) ({                                    \
     size_t frame_size = ShadowFrame::ComputeSize(num_vregs);                                 \
     void* alloca_mem = alloca(frame_size);                                                   \
     ShadowFrameAllocaUniquePtr(                                                              \
-        ShadowFrame::CreateShadowFrameImpl((num_vregs), (link), (method), (dex_pc),          \
-                                           (alloca_mem)));                                   \
+        ShadowFrame::CreateShadowFrameImpl((num_vregs), (method), (dex_pc), (alloca_mem)));  \
     })
 
   ~ShadowFrame() {}
@@ -132,9 +135,14 @@
 
   void SetLink(ShadowFrame* frame) {
     DCHECK_NE(this, frame);
+    DCHECK_EQ(link_, nullptr);
     link_ = frame;
   }
 
+  void ClearLink() {
+    link_ = nullptr;
+  }
+
   int32_t GetVReg(size_t i) const {
     DCHECK_LT(i, NumberOfVRegs());
     const uint32_t* vreg = &vregs_[i];
@@ -169,14 +177,14 @@
   int64_t GetVRegLong(size_t i) const {
     DCHECK_LT(i + 1, NumberOfVRegs());
     const uint32_t* vreg = &vregs_[i];
-    typedef const int64_t unaligned_int64 __attribute__ ((aligned (4)));
+    using unaligned_int64 __attribute__((aligned(4))) = const int64_t;
     return *reinterpret_cast<unaligned_int64*>(vreg);
   }
 
   double GetVRegDouble(size_t i) const {
     DCHECK_LT(i + 1, NumberOfVRegs());
     const uint32_t* vreg = &vregs_[i];
-    typedef const double unaligned_double __attribute__ ((aligned (4)));
+    using unaligned_double __attribute__((aligned(4))) = const double;
     return *reinterpret_cast<unaligned_double*>(vreg);
   }
 
@@ -221,7 +229,7 @@
   void SetVRegLong(size_t i, int64_t val) {
     DCHECK_LT(i + 1, NumberOfVRegs());
     uint32_t* vreg = &vregs_[i];
-    typedef int64_t unaligned_int64 __attribute__ ((aligned (4)));
+    using unaligned_int64 __attribute__((aligned(4))) = int64_t;
     *reinterpret_cast<unaligned_int64*>(vreg) = val;
     // This is needed for moving collectors since these can update the vreg references if they
     // happen to agree with references in the reference array.
@@ -232,7 +240,7 @@
   void SetVRegDouble(size_t i, double val) {
     DCHECK_LT(i + 1, NumberOfVRegs());
     uint32_t* vreg = &vregs_[i];
-    typedef double unaligned_double __attribute__ ((aligned (4)));
+    using unaligned_double __attribute__((aligned(4))) = double;
     *reinterpret_cast<unaligned_double*>(vreg) = val;
     // This is needed for moving collectors since these can update the vreg references if they
     // happen to agree with references in the reference array.
@@ -314,11 +322,10 @@
 
   // Create ShadowFrame for interpreter using provided memory.
   static ShadowFrame* CreateShadowFrameImpl(uint32_t num_vregs,
-                                            ShadowFrame* link,
                                             ArtMethod* method,
                                             uint32_t dex_pc,
                                             void* memory) {
-    return new (memory) ShadowFrame(num_vregs, link, method, dex_pc);
+    return new (memory) ShadowFrame(num_vregs, method, dex_pc);
   }
 
   const uint16_t* GetDexPCPtr() {
@@ -373,6 +380,14 @@
     UpdateFrameFlag(enable, FrameFlags::kSkipNextExceptionEvent);
   }
 
+  bool GetNotifyDexPcMoveEvents() const {
+    return GetFrameFlag(FrameFlags::kNotifyDexPcMoveEvents);
+  }
+
+  void SetNotifyDexPcMoveEvents(bool enable) {
+    UpdateFrameFlag(enable, FrameFlags::kNotifyDexPcMoveEvents);
+  }
+
   void CheckConsistentVRegs() const {
     if (kIsDebugBuild) {
       // A shadow frame visible to GC requires the following rule: for a given vreg,
@@ -385,8 +400,8 @@
   }
 
  private:
-  ShadowFrame(uint32_t num_vregs, ShadowFrame* link, ArtMethod* method, uint32_t dex_pc)
-      : link_(link),
+  ShadowFrame(uint32_t num_vregs, ArtMethod* method, uint32_t dex_pc)
+      : link_(nullptr),
         method_(method),
         result_register_(nullptr),
         dex_pc_ptr_(nullptr),
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 62051ee..32ed430 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -47,6 +47,7 @@
 #include "mirror/array-alloc-inl.h"
 #include "mirror/array-inl.h"
 #include "mirror/class-alloc-inl.h"
+#include "mirror/class.h"
 #include "mirror/executable-inl.h"
 #include "mirror/field.h"
 #include "mirror/method.h"
@@ -61,7 +62,7 @@
 #include "thread-inl.h"
 #include "transaction.h"
 #include "unstarted_runtime_list.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 namespace interpreter {
@@ -138,6 +139,10 @@
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
 
   ObjPtr<mirror::Class> found = class_linker->FindClass(self, descriptor.c_str(), class_loader);
+  if (found != nullptr && !found->CheckIsVisibleWithTargetSdk(self)) {
+    CHECK(self->IsExceptionPending());
+    return;
+  }
   if (found != nullptr && initialize_class) {
     StackHandleScope<1> hs(self);
     HandleWrapperObjPtr<mirror::Class> h_class = hs.NewHandleWrapper(&found);
@@ -231,8 +236,7 @@
     class_loader = nullptr;
   }
 
-  ScopedObjectAccessUnchecked soa(self);
-  if (class_loader != nullptr && !ClassLinker::IsBootClassLoader(soa, class_loader)) {
+  if (class_loader != nullptr && !ClassLinker::IsBootClassLoader(class_loader)) {
     AbortTransactionOrFail(self,
                            "Only the boot classloader is supported: %s",
                            mirror::Object::PrettyTypeOf(class_loader).c_str());
@@ -659,8 +663,7 @@
     StackHandleScope<1> hs(self);
     Handle<mirror::Class> this_classloader_class(hs.NewHandle(this_obj->GetClass()));
 
-    if (self->DecodeJObject(WellKnownClasses::java_lang_BootClassLoader) !=
-            this_classloader_class.Get()) {
+    if (WellKnownClasses::java_lang_BootClassLoader != this_classloader_class.Get()) {
       AbortTransactionOrFail(self,
                              "Unsupported classloader type %s for getResourceAsStream",
                              mirror::Class::PrettyClass(this_classloader_class.Get()).c_str());
@@ -1113,18 +1116,14 @@
     // thread as unstarted to the ThreadGroup. A faked-up main thread peer is good enough for
     // these purposes.
     Runtime::Current()->InitThreadGroups(self);
-    jobject main_peer =
-        self->CreateCompileTimePeer(self->GetJniEnv(),
-                                    "main",
-                                    false,
-                                    Runtime::Current()->GetMainThreadGroup());
+    ObjPtr<mirror::Object> main_peer = self->CreateCompileTimePeer(
+        "main", /*as_daemon=*/ false, Runtime::Current()->GetMainThreadGroup());
     if (main_peer == nullptr) {
       AbortTransactionOrFail(self, "Failed allocating peer");
       return;
     }
 
-    result->SetL(self->DecodeJObject(main_peer));
-    self->GetJniEnv()->DeleteLocalRef(main_peer);
+    result->SetL(main_peer);
   } else {
     AbortTransactionOrFail(self,
                            "Thread.currentThread() does not support %s",
@@ -1277,7 +1276,7 @@
   if (offset < 0 || offset + count > array->GetLength()) {
     std::string error_msg(StringPrintf("Array out of bounds in peekArray: %d/%d vs %d",
                                        offset, count, array->GetLength()));
-    Runtime::Current()->AbortTransactionAndThrowAbortError(self, error_msg.c_str());
+    Runtime::Current()->AbortTransactionAndThrowAbortError(self, error_msg);
     return;
   }
 
@@ -1367,6 +1366,22 @@
 }
 
 // This allows creating the new style of String objects during compilation.
+void UnstartedRuntime::UnstartedStringFactoryNewStringFromBytes(
+    Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+  jint high = shadow_frame->GetVReg(arg_offset + 1);
+  jint offset = shadow_frame->GetVReg(arg_offset + 2);
+  jint byte_count = shadow_frame->GetVReg(arg_offset + 3);
+  DCHECK_GE(byte_count, 0);
+  StackHandleScope<1> hs(self);
+  Handle<mirror::ByteArray> h_byte_array(
+      hs.NewHandle(shadow_frame->GetVRegReference(arg_offset)->AsByteArray()));
+  Runtime* runtime = Runtime::Current();
+  gc::AllocatorType allocator = runtime->GetHeap()->GetCurrentAllocator();
+  result->SetL(
+      mirror::String::AllocFromByteArray(self, byte_count, h_byte_array, offset, high, allocator));
+}
+
+// This allows creating the new style of String objects during compilation.
 void UnstartedRuntime::UnstartedStringFactoryNewStringFromChars(
     Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
   jint offset = shadow_frame->GetVReg(arg_offset);
@@ -1557,7 +1572,7 @@
   mirror::Object* new_value = shadow_frame->GetVRegReference(arg_offset + 5);
 
   // Must use non transactional mode.
-  if (kUseReadBarrier) {
+  if (gUseReadBarrier) {
     // Need to make sure the reference stored in the field is a to-space one before attempting the
     // CAS or the CAS could fail incorrectly.
     mirror::HeapReference<mirror::Object>* field_addr =
@@ -1921,6 +1936,30 @@
   result->SetI(receiver->AsString()->CompareTo(rhs->AsString()));
 }
 
+void UnstartedRuntime::UnstartedJNIStringFillBytesLatin1(
+    Thread* self, ArtMethod* method ATTRIBUTE_UNUSED,
+    mirror::Object* receiver, uint32_t* args, JValue* ATTRIBUTE_UNUSED) {
+  StackHandleScope<2> hs(self);
+  Handle<mirror::String> h_receiver(hs.NewHandle(
+      reinterpret_cast<mirror::String*>(receiver)->AsString()));
+  Handle<mirror::ByteArray> h_buffer(hs.NewHandle(
+      reinterpret_cast<mirror::ByteArray*>(args[0])->AsByteArray()));
+  int32_t index = static_cast<int32_t>(args[1]);
+  h_receiver->FillBytesLatin1(h_buffer, index);
+}
+
+void UnstartedRuntime::UnstartedJNIStringFillBytesUTF16(
+    Thread* self, ArtMethod* method ATTRIBUTE_UNUSED,
+    mirror::Object* receiver, uint32_t* args, JValue* ATTRIBUTE_UNUSED) {
+  StackHandleScope<2> hs(self);
+  Handle<mirror::String> h_receiver(hs.NewHandle(
+      reinterpret_cast<mirror::String*>(receiver)->AsString()));
+  Handle<mirror::ByteArray> h_buffer(hs.NewHandle(
+      reinterpret_cast<mirror::ByteArray*>(args[0])->AsByteArray()));
+  int32_t index = static_cast<int32_t>(args[1]);
+  h_receiver->FillBytesUTF16(h_buffer, index);
+}
+
 void UnstartedRuntime::UnstartedJNIStringIntern(
     Thread* self ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, mirror::Object* receiver,
     uint32_t* args ATTRIBUTE_UNUSED, JValue* result) {
@@ -2163,6 +2202,7 @@
                            uint32_t* args,
                            JValue* result);
 
+// NOLINTNEXTLINE
 #define ONE_PLUS(ShortNameIgnored, DescriptorIgnored, NameIgnored, SignatureIgnored) 1 +
 static constexpr size_t kInvokeHandlersSize = UNSTARTED_RUNTIME_DIRECT_LIST(ONE_PLUS) 0;
 static constexpr size_t kJniHandlersSize = UNSTARTED_RUNTIME_JNI_LIST(ONE_PLUS) 0;
@@ -2262,6 +2302,9 @@
 
   const auto& iter = invoke_handlers_.find(shadow_frame->GetMethod());
   if (iter != invoke_handlers_.end()) {
+    // Note: When we special case the method, we do not ensure initialization.
+    // This has been the behavior since implementation of this feature.
+
     // Clear out the result in case it's not zeroed out.
     result->SetL(nullptr);
 
@@ -2272,6 +2315,9 @@
 
     self->PopShadowFrame();
   } else {
+    if (!EnsureInitialized(self, shadow_frame)) {
+      return;
+    }
     // Not special, continue with regular interpreter execution.
     ArtInterpreterToInterpreterBridge(self, accessor, shadow_frame, result);
   }
diff --git a/runtime/interpreter/unstarted_runtime_list.h b/runtime/interpreter/unstarted_runtime_list.h
index 5f8add0..dd2028a 100644
--- a/runtime/interpreter/unstarted_runtime_list.h
+++ b/runtime/interpreter/unstarted_runtime_list.h
@@ -67,6 +67,7 @@
   V(StringGetCharsNoCheck, "Ljava/lang/String;", "getCharsNoCheck", "(II[CI)V") \
   V(StringCharAt, "Ljava/lang/String;", "charAt", "(I)C") \
   V(StringDoReplace, "Ljava/lang/String;", "doReplace", "(CC)Ljava/lang/String;") \
+  V(StringFactoryNewStringFromBytes, "Ljava/lang/StringFactory;", "newStringFromBytes", "([BIII)Ljava/lang/String;") \
   V(StringFactoryNewStringFromChars, "Ljava/lang/StringFactory;", "newStringFromChars", "(II[C)Ljava/lang/String;") \
   V(StringFactoryNewStringFromString, "Ljava/lang/StringFactory;", "newStringFromString", "(Ljava/lang/String;)Ljava/lang/String;") \
   V(StringFastSubstring, "Ljava/lang/String;", "fastSubstring", "(II)Ljava/lang/String;") \
@@ -105,6 +106,8 @@
   V(ObjectInternalClone, "Ljava/lang/Object;", "internalClone", "()Ljava/lang/Object;") \
   V(ObjectNotifyAll, "Ljava/lang/Object;", "notifyAll", "()V") \
   V(StringCompareTo, "Ljava/lang/String;", "compareTo", "(Ljava/lang/String;)I") \
+  V(StringFillBytesLatin1, "Ljava/lang/String;", "fillBytesLatin1", "([BI)V") \
+  V(StringFillBytesUTF16, "Ljava/lang/String;", "fillBytesUTF16", "([BI)V") \
   V(StringIntern, "Ljava/lang/String;", "intern", "()Ljava/lang/String;") \
   V(ArrayCreateMultiArray, "Ljava/lang/reflect/Array;", "createMultiArray", "(Ljava/lang/Class;[I)Ljava/lang/Object;") \
   V(ArrayCreateObjectArray, "Ljava/lang/reflect/Array;", "createObjectArray", "(Ljava/lang/Class;I)Ljava/lang/Object;") \
diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc
index 75a692e..3227ef7 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -62,11 +62,6 @@
 
 class UnstartedRuntimeTest : public CommonRuntimeTest {
  protected:
-  void SetUp() override {
-    CommonRuntimeTest::SetUp();
-    InitializeIntrinsics();
-  }
-
   // Re-expose all UnstartedRuntime implementations so we don't need to declare a million
   // test friends.
 
@@ -96,11 +91,10 @@
 #undef UNSTARTED_JNI
 
   UniqueDeoptShadowFramePtr CreateShadowFrame(uint32_t num_vregs,
-                                              ShadowFrame* link,
                                               ArtMethod* method,
                                               uint32_t dex_pc) {
     return UniqueDeoptShadowFramePtr(
-        ShadowFrame::CreateDeoptimizedFrame(num_vregs, link, method, dex_pc));
+        ShadowFrame::CreateDeoptimizedFrame(num_vregs, method, dex_pc));
   }
 
   // Helpers for ArrayCopy.
@@ -237,7 +231,7 @@
   const uint8_t* base_ptr = base_array;
 
   JValue result;
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   for (int32_t i = 0; i < kBaseLen; ++i) {
     tmp->SetVRegLong(0, static_cast<int64_t>(reinterpret_cast<intptr_t>(base_ptr + i)));
@@ -257,7 +251,7 @@
   const uint8_t* base_ptr = base_array;
 
   JValue result;
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   int32_t adjusted_length = kBaseLen - sizeof(int16_t);
   for (int32_t i = 0; i < adjusted_length; ++i) {
@@ -280,7 +274,7 @@
   const uint8_t* base_ptr = base_array;
 
   JValue result;
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   int32_t adjusted_length = kBaseLen - sizeof(int32_t);
   for (int32_t i = 0; i < adjusted_length; ++i) {
@@ -303,7 +297,7 @@
   const uint8_t* base_ptr = base_array;
 
   JValue result;
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   int32_t adjusted_length = kBaseLen - sizeof(int64_t);
   for (int32_t i = 0; i < adjusted_length; ++i) {
@@ -333,7 +327,7 @@
   uint16_t buf[kBaseLen];
 
   JValue result;
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   for (int32_t start_index = 0; start_index < kBaseLen; ++start_index) {
     for (int32_t count = 0; count <= kBaseLen; ++count) {
@@ -385,7 +379,7 @@
   ObjPtr<mirror::String> test_string = mirror::String::AllocFromModifiedUtf8(self, base_string);
 
   JValue result;
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   for (int32_t i = 0; i < base_len; ++i) {
     tmp->SetVRegReference(0, test_string);
@@ -410,7 +404,7 @@
   uint16_t inst_data[3] = { 0x2070, 0x0000, 0x0010 };
 
   JValue result;
-  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, method, 0);
+  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, method, 0);
   const char* base_string = "hello_world";
   StackHandleScope<2> hs(self);
   Handle<mirror::String> string_arg =
@@ -420,12 +414,14 @@
   shadow_frame->SetVRegReference(0, reference_empty_string.Get());
   shadow_frame->SetVRegReference(1, string_arg.Get());
 
-  interpreter::DoCall<false, false>(method,
-                                    self,
-                                    *shadow_frame,
-                                    Instruction::At(inst_data),
-                                    inst_data[0],
-                                    &result);
+  ArtMethod* factory = WellKnownClasses::StringInitToStringFactory(method);
+  interpreter::DoCall<false>(factory,
+                             self,
+                             *shadow_frame,
+                             Instruction::At(inst_data),
+                             inst_data[0],
+                             /* string_init= */ true,
+                             &result);
   ObjPtr<mirror::String> string_result = down_cast<mirror::String*>(result.GetL());
   EXPECT_EQ(string_arg->GetLength(), string_result->GetLength());
 
@@ -453,7 +449,7 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
   JValue result;
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   // Note: all tests are not GC safe. Assume there's no GC running here with the few objects we
   //       allocate.
@@ -485,7 +481,7 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
   JValue result;
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   StackHandleScope<1> hs_object(self);
   Handle<mirror::Class> object_class(hs_object.NewHandle(GetClassRoot<mirror::Object>()));
@@ -588,7 +584,7 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
 
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   // Test string. Should be valid, and between minimal values of LONG_MIN and LONG_MAX (for all
   // suffixes).
@@ -634,7 +630,7 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
 
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   // Test string. Should be valid, and between minimal values of LONG_MIN and LONG_MAX (for all
   // suffixes).
@@ -679,7 +675,7 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
 
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   constexpr double nan = std::numeric_limits<double>::quiet_NaN();
   constexpr double inf = std::numeric_limits<double>::infinity();
@@ -706,7 +702,7 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
 
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   constexpr double nan = std::numeric_limits<double>::quiet_NaN();
   constexpr double inf = std::numeric_limits<double>::infinity();
@@ -733,7 +729,7 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
 
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   std::locale c_locale("C");
 
@@ -828,7 +824,7 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
 
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   // Test an important value, PI/6. That's the one we see in practice.
   constexpr uint64_t lvalue = UINT64_C(0x3fe0c152382d7365);
@@ -845,7 +841,7 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
 
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   // Test an important value, PI/6. That's the one we see in practice.
   constexpr uint64_t lvalue = UINT64_C(0x3fe0c152382d7365);
@@ -862,7 +858,7 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
 
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   // Test an important pair.
   constexpr uint64_t lvalue1 = UINT64_C(0x4079000000000000);
@@ -883,7 +879,7 @@
   ScopedObjectAccess soa(self);
 
   JValue result;
-  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
 
   ObjPtr<mirror::Class> class_klass = GetClassRoot<mirror::Class>();
   shadow_frame->SetVRegReference(0, class_klass);
@@ -906,7 +902,7 @@
   ScopedObjectAccess soa(self);
 
   JValue result;
-  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
 
   jobject class_loader = LoadDex("Nested");
   StackHandleScope<4> hs(self);
@@ -938,7 +934,7 @@
   ScopedObjectAccess soa(self);
 
   JValue result;
-  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
 
   StackHandleScope<1> hs(self);
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
@@ -960,14 +956,14 @@
     ASSERT_TRUE(caller_method != nullptr);
     ASSERT_TRUE(caller_method->IsDirect());
     ASSERT_TRUE(caller_method->GetDeclaringClass() == floating_decimal.Get());
-    UniqueDeoptShadowFramePtr caller_frame = CreateShadowFrame(10, nullptr, caller_method, 0);
+    UniqueDeoptShadowFramePtr caller_frame = CreateShadowFrame(10, caller_method, 0);
     shadow_frame->SetLink(caller_frame.get());
 
     UnstartedThreadLocalGet(self, shadow_frame.get(), &result, 0);
     EXPECT_TRUE(result.GetL() != nullptr);
     EXPECT_FALSE(self->IsExceptionPending());
 
-    shadow_frame->SetLink(nullptr);
+    shadow_frame->ClearLink();
   }
 
   // Negative test.
@@ -978,7 +974,7 @@
     ObjPtr<mirror::Class> class_class = GetClassRoot<mirror::Class>();
     ArtMethod* caller_method =
         &*class_class->GetDeclaredMethods(class_linker->GetImagePointerSize()).begin();
-    UniqueDeoptShadowFramePtr caller_frame = CreateShadowFrame(10, nullptr, caller_method, 0);
+    UniqueDeoptShadowFramePtr caller_frame = CreateShadowFrame(10, caller_method, 0);
     shadow_frame->SetLink(caller_frame.get());
 
     EnterTransactionMode();
@@ -988,7 +984,7 @@
     ASSERT_TRUE(self->IsExceptionPending());
     self->ClearException();
 
-    shadow_frame->SetLink(nullptr);
+    shadow_frame->ClearLink();
   }
 }
 
@@ -1016,15 +1012,16 @@
   uint16_t inst_data[3] = { 0x2070, 0x0000, 0x0010 };
 
   JValue result;
-  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, method, 0);
+  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, method, 0);
 
   shadow_frame->SetVRegDouble(0, 1.23);
-  interpreter::DoCall<false, false>(method,
-                                    self,
-                                    *shadow_frame,
-                                    Instruction::At(inst_data),
-                                    inst_data[0],
-                                    &result);
+  interpreter::DoCall<false>(method,
+                             self,
+                             *shadow_frame,
+                             Instruction::At(inst_data),
+                             inst_data[0],
+                             /* string_init= */ false,
+                             &result);
   ObjPtr<mirror::String> string_result = down_cast<mirror::String*>(result.GetL());
   ASSERT_TRUE(string_result != nullptr);
 
@@ -1037,7 +1034,7 @@
   ScopedObjectAccess soa(self);
 
   JValue result;
-  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
 
   StackHandleScope<1> hs(self);
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
@@ -1114,13 +1111,15 @@
     }
 
     JValue result;
-    UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0);
+    UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
 
     for (const char* name : kTestCases) {
       ObjPtr<mirror::String> name_string = mirror::String::AllocFromModifiedUtf8(self, name);
       CHECK(name_string != nullptr);
 
       if (in_transaction) {
+        StackHandleScope<1> hs(self);
+        HandleWrapperObjPtr<mirror::String> h(hs.NewHandleWrapper(&name_string));
         EnterTransactionMode();
       }
       CHECK(!self->IsExceptionPending());
@@ -1168,18 +1167,19 @@
       CHECK(boot_cp_init != nullptr);
 
       JValue result;
-      UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, boot_cp_init, 0);
+      UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, boot_cp_init, 0);
       shadow_frame->SetVRegReference(0, boot_cp.Get());
 
       // create instruction data for invoke-direct {v0} of method with fake index
       uint16_t inst_data[3] = { 0x1070, 0x0000, 0x0010 };
 
-      interpreter::DoCall<false, false>(boot_cp_init,
-                                        self,
-                                        *shadow_frame,
-                                        Instruction::At(inst_data),
-                                        inst_data[0],
-                                        &result);
+      interpreter::DoCall<false>(boot_cp_init,
+                                 self,
+                                 *shadow_frame,
+                                 Instruction::At(inst_data),
+                                 inst_data[0],
+                                 /* string_init= */ false,
+                                 &result);
       CHECK(!self->IsExceptionPending());
     }
 
@@ -1287,7 +1287,7 @@
   ASSERT_TRUE(class_linker->EnsureInitialized(self, list_class, true, true));
 
   JValue result;
-  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
 
   shadow_frame->SetVRegReference(0, list_class.Get());
   UnstartedClassGetSignatureAnnotation(self, shadow_frame.get(), &result, 0);
@@ -1339,7 +1339,7 @@
 
   // OK, we're ready now.
   JValue result;
-  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
   shadow_frame->SetVRegReference(0, cons.Get());
   shadow_frame->SetVRegReference(1, args.Get());
   UnstartedConstructorNewInstance0(self, shadow_frame.get(), &result, 0);
@@ -1360,7 +1360,7 @@
 TEST_F(UnstartedRuntimeTest, IdentityHashCode) {
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
-  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, nullptr, 0);
+  UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
 
   JValue result;
   UnstartedSystemIdentityHashCode(self, tmp.get(), &result, 0);
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index 6d634ae..b231cce 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -208,7 +208,6 @@
   // Jit GC for now (b/147208992).
   if (code_cache->GetGarbageCollectCode()) {
     code_cache->SetGarbageCollectCode(!jit_compiler_->GenerateDebugInfo() &&
-        !Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled() &&
         !jit->JitAtFirstUse());
   }
 
@@ -259,10 +258,14 @@
   return true;
 }
 
-bool Jit::CompileMethod(ArtMethod* method,
-                        Thread* self,
-                        CompilationKind compilation_kind,
-                        bool prejit) {
+bool Jit::CompileMethodInternal(ArtMethod* method,
+                                Thread* self,
+                                CompilationKind compilation_kind,
+                                bool prejit) {
+  if (kIsDebugBuild) {
+    MutexLock mu(self, *Locks::jit_lock_);
+    CHECK(GetCodeCache()->IsMethodBeingCompiled(method, compilation_kind));
+  }
   DCHECK(Runtime::Current()->UseJitCompilation());
   DCHECK(!method->IsRuntimeMethod());
 
@@ -279,9 +282,8 @@
     compilation_kind = CompilationKind::kOptimized;
   }
 
-  RuntimeCallbacks* cb = Runtime::Current()->GetRuntimeCallbacks();
   // Don't compile the method if it has breakpoints.
-  if (cb->IsMethodBeingInspected(method)) {
+  if (Runtime::Current()->GetInstrumentation()->IsDeoptimized(method)) {
     VLOG(jit) << "JIT not compiling " << method->PrettyMethod()
               << " due to not being safe to jit according to runtime-callbacks. For example, there"
               << " could be breakpoints in this method.";
@@ -323,7 +325,7 @@
             << ArtMethod::PrettyMethod(method_to_compile)
             << " kind=" << compilation_kind;
   bool success = jit_compiler_->CompileMethod(self, region, method_to_compile, compilation_kind);
-  code_cache_->DoneCompiling(method_to_compile, self, compilation_kind);
+  code_cache_->DoneCompiling(method_to_compile, self);
   if (!success) {
     VLOG(jit) << "Failed to compile method "
               << ArtMethod::PrettyMethod(method_to_compile)
@@ -568,12 +570,11 @@
   // Before allowing the jump, make sure no code is actively inspecting the method to avoid
   // jumping from interpreter to OSR while e.g. single stepping. Note that we could selectively
   // disable OSR when single stepping, but that's currently hard to know at this point.
-  if (Runtime::Current()->GetInstrumentation()->InterpreterStubsInstalled() ||
-      Runtime::Current()->GetInstrumentation()->IsDeoptimized(method) ||
-      thread->IsForceInterpreter() ||
-      method->GetDeclaringClass()->IsObsoleteObject() ||
-      Dbg::IsForcedInterpreterNeededForUpcall(thread, method) ||
-      Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method)) {
+  // Currently, HaveLocalsChanged is not frame specific. It is possible to make it frame specific
+  // to allow OSR of frames that don't have any locals changed but it isn't worth the additional
+  // complexity.
+  if (Runtime::Current()->GetInstrumentation()->NeedsSlowInterpreterForMethod(thread, method) ||
+      Runtime::Current()->GetRuntimeCallbacks()->HaveLocalsChanged()) {
     return false;
   }
 
@@ -748,6 +749,51 @@
   child_mapping_methods.Reset();
 }
 
+class ScopedCompilation {
+ public:
+  ScopedCompilation(ScopedCompilation&& other) noexcept :
+      jit_(other.jit_),
+      method_(other.method_),
+      compilation_kind_(other.compilation_kind_),
+      owns_compilation_(other.owns_compilation_) {
+    other.owns_compilation_ = false;
+  }
+
+  ScopedCompilation(Jit* jit, ArtMethod* method, CompilationKind compilation_kind)
+      : jit_(jit),
+        method_(method),
+        compilation_kind_(compilation_kind),
+        owns_compilation_(true) {
+    MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+    // We don't want to enqueue any new tasks when thread pool has stopped. This simplifies
+    // the implementation of redefinition feature in jvmti.
+    if (jit_->GetThreadPool() == nullptr ||
+        !jit_->GetThreadPool()->HasStarted(Thread::Current()) ||
+        jit_->GetCodeCache()->IsMethodBeingCompiled(method_, compilation_kind_)) {
+      owns_compilation_ = false;
+      return;
+    }
+    jit_->GetCodeCache()->AddMethodBeingCompiled(method_, compilation_kind_);
+  }
+
+  bool OwnsCompilation() const {
+    return owns_compilation_;
+  }
+
+  ~ScopedCompilation() {
+    if (owns_compilation_) {
+      MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+      jit_->GetCodeCache()->RemoveMethodBeingCompiled(method_, compilation_kind_);
+    }
+  }
+
+ private:
+  Jit* const jit_;
+  ArtMethod* const method_;
+  const CompilationKind compilation_kind_;
+  bool owns_compilation_;
+};
+
 class JitCompileTask final : public Task {
  public:
   enum class TaskKind {
@@ -755,25 +801,16 @@
     kPreCompile,
   };
 
-  JitCompileTask(ArtMethod* method, TaskKind task_kind, CompilationKind compilation_kind)
-      : method_(method), kind_(task_kind), compilation_kind_(compilation_kind), klass_(nullptr) {
-    ScopedObjectAccess soa(Thread::Current());
-    // For a non-bootclasspath class, add a global ref to the class to prevent class unloading
-    // until compilation is done.
-    // When we precompile, this is either with boot classpath methods, or main
-    // class loader methods, so we don't need to keep a global reference.
-    if (method->GetDeclaringClass()->GetClassLoader() != nullptr &&
-        kind_ != TaskKind::kPreCompile) {
-      klass_ = soa.Vm()->AddGlobalRef(soa.Self(), method_->GetDeclaringClass());
-      CHECK(klass_ != nullptr);
-    }
-  }
-
-  ~JitCompileTask() {
-    if (klass_ != nullptr) {
-      ScopedObjectAccess soa(Thread::Current());
-      soa.Vm()->DeleteGlobalRef(soa.Self(), klass_);
-    }
+  JitCompileTask(ArtMethod* method,
+                 TaskKind task_kind,
+                 CompilationKind compilation_kind,
+                 ScopedCompilation&& sc)
+      : method_(method),
+        kind_(task_kind),
+        compilation_kind_(compilation_kind),
+        scoped_compilation_(std::move(sc)) {
+    DCHECK(scoped_compilation_.OwnsCompilation());
+    DCHECK(!sc.OwnsCompilation());
   }
 
   void Run(Thread* self) override {
@@ -782,7 +819,7 @@
       switch (kind_) {
         case TaskKind::kCompile:
         case TaskKind::kPreCompile: {
-          Runtime::Current()->GetJit()->CompileMethod(
+          Runtime::Current()->GetJit()->CompileMethodInternal(
               method_,
               self,
               compilation_kind_,
@@ -802,7 +839,7 @@
   ArtMethod* const method_;
   const TaskKind kind_;
   const CompilationKind compilation_kind_;
-  jobject klass_;
+  ScopedCompilation scoped_compilation_;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(JitCompileTask);
 };
@@ -888,22 +925,16 @@
     uint64_t start_ns = ThreadCpuNanoTime();
     uint64_t number_of_classes = 0;
     for (const DexFile* dex_file : boot_class_path) {
-      if (dex_file->GetOatDexFile() != nullptr &&
-          dex_file->GetOatDexFile()->GetOatFile() != nullptr) {
-        // If backed by an .oat file, we have already run verification at
-        // compile-time. Note that some classes may still have failed
-        // verification there if they reference updatable mainline module
-        // classes.
-        continue;
-      }
       for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) {
         const dex::ClassDef& class_def = dex_file->GetClassDef(i);
         const char* descriptor = dex_file->GetClassDescriptor(class_def);
-        ScopedNullHandle<mirror::ClassLoader> null_loader;
-        klass.Assign(linker->FindClass(self, descriptor, null_loader));
+        klass.Assign(linker->LookupResolvedType(descriptor, /* class_loader= */ nullptr));
         if (klass == nullptr) {
-          self->ClearException();
-          LOG(WARNING) << "Could not find " << descriptor;
+          // Class not loaded yet.
+          DCHECK(!self->IsExceptionPending());
+          continue;
+        }
+        if (klass->IsVerified()) {
           continue;
         }
         if (linker->VerifyClass(self, /* verifier_deps= */ nullptr, klass) ==
@@ -918,9 +949,9 @@
         CHECK(!self->IsExceptionPending());
       }
     }
-    LOG(INFO) << "Verified "
+    LOG(INFO) << "Background verification of "
               << number_of_classes
-              << " classes from mainline modules in "
+              << " classes from boot classpath took "
               << PrettyDuration(ThreadCpuNanoTime() - start_ns);
   }
 };
@@ -1280,16 +1311,40 @@
     return;
   }
   Runtime* runtime = Runtime::Current();
-  // If the runtime is debuggable, no need to precompile methods.
+  // If the runtime is debuggable, don't bother precompiling methods.
+  // If system server is being profiled, don't precompile as we are going to use
+  // the JIT to count hotness. Note that --count-hotness-in-compiled-code is
+  // only forced when we also profile the boot classpath, see
+  // AndroidRuntime.cpp.
   if (runtime->IsSystemServer() &&
       UseJitCompilation() &&
       options_->UseProfiledJitCompilation() &&
       runtime->HasImageWithProfile() &&
+      !runtime->IsSystemServerProfiled() &&
       !runtime->IsJavaDebuggable()) {
+    // Note: this precompilation is currently not running in production because:
+    // - UseProfiledJitCompilation() is not set by default.
+    // - System server dex files are registered *before* we set the runtime as
+    //   system server (though we are in the system server process).
     thread_pool_->AddTask(Thread::Current(), new JitProfileTask(dex_files, class_loader));
   }
 }
 
+void Jit::AddCompileTask(Thread* self,
+                         ArtMethod* method,
+                         CompilationKind compilation_kind,
+                         bool precompile) {
+  ScopedCompilation sc(this, method, compilation_kind);
+  if (!sc.OwnsCompilation()) {
+    return;
+  }
+  JitCompileTask::TaskKind task_kind = precompile
+      ? JitCompileTask::TaskKind::kPreCompile
+      : JitCompileTask::TaskKind::kCompile;
+  thread_pool_->AddTask(
+      self, new JitCompileTask(method, task_kind, compilation_kind, std::move(sc)));
+}
+
 bool Jit::CompileMethodFromProfile(Thread* self,
                                    ClassLinker* class_linker,
                                    uint32_t method_idx,
@@ -1310,21 +1365,27 @@
     // Already seen by another profile.
     return false;
   }
+  CompilationKind compilation_kind = CompilationKind::kOptimized;
   const void* entry_point = method->GetEntryPointFromQuickCompiledCode();
   if (class_linker->IsQuickToInterpreterBridge(entry_point) ||
       class_linker->IsQuickGenericJniStub(entry_point) ||
-      (entry_point == interpreter::GetNterpEntryPoint()) ||
-      // We explicitly check for the stub. The trampoline is for methods backed by
-      // a .oat file that has a compiled version of the method.
+      class_linker->IsNterpEntryPoint(entry_point) ||
+      // We explicitly check for the resolution stub, and not the resolution trampoline.
+      // The trampoline is for methods backed by a .oat file that has a compiled version of
+      // the method.
       (entry_point == GetQuickResolutionStub())) {
     VLOG(jit) << "JIT Zygote processing method " << ArtMethod::PrettyMethod(method)
               << " from profile";
     method->SetPreCompiled();
+    ScopedCompilation sc(this, method, compilation_kind);
+    if (!sc.OwnsCompilation()) {
+      return false;
+    }
     if (!add_to_queue) {
-      CompileMethod(method, self, CompilationKind::kOptimized, /* prejit= */ true);
+      CompileMethodInternal(method, self, compilation_kind, /* prejit= */ true);
     } else {
       Task* task = new JitCompileTask(
-          method, JitCompileTask::TaskKind::kPreCompile, CompilationKind::kOptimized);
+          method, JitCompileTask::TaskKind::kPreCompile, compilation_kind, std::move(sc));
       if (compile_after_boot) {
         AddPostBootTask(self, task);
       } else {
@@ -1342,7 +1403,7 @@
     const std::string& profile_file,
     Handle<mirror::ClassLoader> class_loader,
     bool add_to_queue) {
-  unix_file::FdFile profile(profile_file.c_str(), O_RDONLY, true);
+  unix_file::FdFile profile(profile_file, O_RDONLY, true);
 
   if (profile.Fd() == -1) {
     PLOG(WARNING) << "No boot profile: " << profile_file;
@@ -1392,7 +1453,7 @@
 
   // We don't generate boot profiles on device, therefore we don't
   // need to lock the file.
-  unix_file::FdFile profile(profile_file.c_str(), O_RDONLY, true);
+  unix_file::FdFile profile(profile_file, O_RDONLY, true);
 
   if (profile.Fd() == -1) {
     PLOG(WARNING) << "No profile: " << profile_file;
@@ -1475,11 +1536,7 @@
   // hotness threshold. If we're not only using the baseline compiler, enqueue a compilation
   // task that will compile optimize the method.
   if (!options_->UseBaselineCompiler()) {
-    thread_pool_->AddTask(
-        self,
-        new JitCompileTask(method,
-                           JitCompileTask::TaskKind::kCompile,
-                           CompilationKind::kOptimized));
+    AddCompileTask(self, method, CompilationKind::kOptimized);
   }
 }
 
@@ -1499,23 +1556,17 @@
   bool was_runtime_thread_;
 };
 
-void Jit::MethodEntered(Thread* thread, ArtMethod* method) {
+void Jit::MethodEntered(Thread* self, ArtMethod* method) {
   Runtime* runtime = Runtime::Current();
   if (UNLIKELY(runtime->UseJitCompilation() && JitAtFirstUse())) {
     ArtMethod* np_method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
     if (np_method->IsCompilable()) {
-      // TODO(ngeoffray): For JIT at first use, use kPreCompile. Currently we don't due to
-      // conflicts with jitzygote optimizations.
-      JitCompileTask compile_task(
-          method, JitCompileTask::TaskKind::kCompile, CompilationKind::kOptimized);
-      // Fake being in a runtime thread so that class-load behavior will be the same as normal jit.
-      ScopedSetRuntimeThread ssrt(thread);
-      compile_task.Run(thread);
+      CompileMethod(method, self, CompilationKind::kOptimized, /* prejit= */ false);
     }
     return;
   }
 
-  AddSamples(thread, method);
+  AddSamples(self, method);
 }
 
 void Jit::WaitForCompilationToFinish(Thread* self) {
@@ -1620,7 +1671,6 @@
   // Jit GC for now (b/147208992).
   code_cache_->SetGarbageCollectCode(
       !jit_compiler_->GenerateDebugInfo() &&
-      !runtime->GetInstrumentation()->AreExitStubsInstalled() &&
       !JitAtFirstUse());
 
   if (is_system_server && runtime->HasImageWithProfile()) {
@@ -1745,17 +1795,14 @@
     if (!method->IsNative() && !code_cache_->IsOsrCompiled(method)) {
       // If we already have compiled code for it, nterp may be stuck in a loop.
       // Compile OSR.
-      thread_pool_->AddTask(
-          self,
-          new JitCompileTask(method, JitCompileTask::TaskKind::kCompile, CompilationKind::kOsr));
+      AddCompileTask(self, method, CompilationKind::kOsr);
     }
     return;
   }
 
   // Check if we have precompiled this method.
   if (UNLIKELY(method->IsPreCompiled())) {
-    if (!NeedsClinitCheckBeforeCall(method) ||
-        method->GetDeclaringClass()->IsVisiblyInitialized()) {
+    if (!method->StillNeedsClinitCheck()) {
       const void* entry_point = code_cache_->GetSavedEntryPointOfPreCompiledMethod(method);
       if (entry_point != nullptr) {
         Runtime::Current()->GetInstrumentation()->UpdateMethodsCode(method, entry_point);
@@ -1764,7 +1811,7 @@
     return;
   }
 
-  static constexpr size_t kIndividualSharedMethodHotnessThreshold = 0xff;
+  static constexpr size_t kIndividualSharedMethodHotnessThreshold = 0x3f;
   if (method->IsMemorySharedMethod()) {
     MutexLock mu(self, lock_);
     auto it = shared_method_counters_.find(method);
@@ -1781,17 +1828,27 @@
   }
 
   if (!method->IsNative() && GetCodeCache()->CanAllocateProfilingInfo()) {
-    thread_pool_->AddTask(
-        self,
-        new JitCompileTask(method, JitCompileTask::TaskKind::kCompile, CompilationKind::kBaseline));
+    AddCompileTask(self, method, CompilationKind::kBaseline);
   } else {
-    thread_pool_->AddTask(
-        self,
-        new JitCompileTask(method,
-                           JitCompileTask::TaskKind::kCompile,
-                           CompilationKind::kOptimized));
+    AddCompileTask(self, method, CompilationKind::kOptimized);
   }
 }
 
+bool Jit::CompileMethod(ArtMethod* method,
+                        Thread* self,
+                        CompilationKind compilation_kind,
+                        bool prejit) {
+  ScopedCompilation sc(this, method, compilation_kind);
+  // TODO: all current users of this method expect us to wait if it is being compiled.
+  if (!sc.OwnsCompilation()) {
+    return false;
+  }
+  // Fake being in a runtime thread so that class-load behavior will be the same as normal jit.
+  ScopedSetRuntimeThread ssrt(self);
+  // TODO(ngeoffray): For JIT at first use, use kPreCompile. Currently we don't due to
+  // conflicts with jitzygote optimizations.
+  return CompileMethodInternal(method, self, compilation_kind, prejit);
+}
+
 }  // namespace jit
 }  // namespace art
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index b439c8e..c95fd9d 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -53,6 +53,7 @@
 namespace jit {
 
 class JitCodeCache;
+class JitCompileTask;
 class JitMemoryRegion;
 class JitOptions;
 
@@ -195,6 +196,7 @@
   virtual bool GenerateDebugInfo() = 0;
   virtual void ParseCompilerOptions() = 0;
   virtual bool IsBaselineCompiler() const = 0;
+  virtual void SetDebuggableCompilerOption(bool value) = 0;
 
   virtual std::vector<uint8_t> PackElfFileForJIT(ArrayRef<const JITCodeEntry*> elf_files,
                                                  ArrayRef<const void*> removed_symbols,
@@ -461,6 +463,17 @@
 
   static bool BindCompilerMethods(std::string* error_msg);
 
+  void AddCompileTask(Thread* self,
+                      ArtMethod* method,
+                      CompilationKind compilation_kind,
+                      bool precompile = false);
+
+  bool CompileMethodInternal(ArtMethod* method,
+                             Thread* self,
+                             CompilationKind compilation_kind,
+                             bool prejit)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   // JIT compiler
   static void* jit_library_handle_;
   static JitCompilerInterface* jit_compiler_;
@@ -507,6 +520,8 @@
   // between the zygote and apps.
   std::map<ArtMethod*, uint16_t> shared_method_counters_;
 
+  friend class art::jit::JitCompileTask;
+
   DISALLOW_COPY_AND_ASSIGN(Jit);
 };
 
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 0b34688..34f9045 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -40,7 +40,7 @@
 #include "entrypoints/entrypoint_utils-inl.h"
 #include "entrypoints/runtime_asm_entrypoints.h"
 #include "gc/accounting/bitmap-inl.h"
-#include "gc/allocator/dlmalloc.h"
+#include "gc/allocator/art-dlmalloc.h"
 #include "gc/scoped_gc_critical_section.h"
 #include "handle.h"
 #include "handle_scope-inl.h"
@@ -127,31 +127,19 @@
     DCHECK(entrypoint == OatQuickMethodHeader::FromCodePointer(GetCode())->GetEntryPoint());
     instrumentation::Instrumentation* instrum = Runtime::Current()->GetInstrumentation();
     for (ArtMethod* m : GetMethods()) {
-      // Because `m` might be in the process of being deleted:
-      // - Call the dedicated method instead of the more generic UpdateMethodsCode
-      // - Check the class status without a full read barrier; use ReadBarrier::IsMarked().
-      bool can_set_entrypoint = true;
-      if (NeedsClinitCheckBeforeCall(m)) {
-        // To avoid resurrecting an unreachable object, we must not use a full read
-        // barrier but we do not want to miss updating an entrypoint under common
-        // circumstances, i.e. during a GC the class becomes visibly initialized,
-        // the method becomes hot, we compile the thunk and want to update the
-        // entrypoint while the method's declaring class field still points to the
-        // from-space class object with the old status. Therefore we read the
-        // declaring class without a read barrier and check if it's already marked.
-        // If yes, we check the status of the to-space class object as intended.
-        // Otherwise, there is no to-space object and the from-space class object
-        // contains the most recent value of the status field; even if this races
-        // with another thread doing a read barrier and updating the status, that's
-        // no different from a race with a thread that just updates the status.
-        // Such race can happen only for the zygote method pre-compilation, as we
-        // otherwise compile only thunks for methods of visibly initialized classes.
-        ObjPtr<mirror::Class> klass = m->GetDeclaringClass<kWithoutReadBarrier>();
-        ObjPtr<mirror::Class> marked = ReadBarrier::IsMarked(klass.Ptr());
-        ObjPtr<mirror::Class> checked_klass = (marked != nullptr) ? marked : klass;
-        can_set_entrypoint = checked_klass->IsVisiblyInitialized();
-      }
-      if (can_set_entrypoint) {
+      // Because `m` might be in the process of being deleted,
+      //   - use the `ArtMethod::StillNeedsClinitCheckMayBeDead()` to check if
+      //     we can update the entrypoint, and
+      //   - call `Instrumentation::UpdateNativeMethodsCodeToJitCode` instead of the
+      //     more generic function `Instrumentation::UpdateMethodsCode()`.
+      // The `ArtMethod::StillNeedsClinitCheckMayBeDead()` checks the class status
+      // in the to-space object if any even if the method's declaring class points to
+      // the from-space class object. This way we do not miss updating an entrypoint
+      // even under uncommon circumstances, when during a GC the class becomes visibly
+      // initialized, the method becomes hot, we compile the thunk and want to update
+      // the entrypoint while the method's declaring class field still points to the
+      // from-space class object with the old status.
+      if (!m->StillNeedsClinitCheckMayBeDead()) {
         instrum->UpdateNativeMethodsCodeToJitCode(m, entrypoint);
       }
     }
@@ -220,9 +208,10 @@
     }
   }
 
-  size_t initial_capacity = Runtime::Current()->GetJITOptions()->GetCodeCacheInitialCapacity();
+  Runtime* runtime = Runtime::Current();
+  size_t initial_capacity = runtime->GetJITOptions()->GetCodeCacheInitialCapacity();
   // Check whether the provided max capacity in options is below 1GB.
-  size_t max_capacity = Runtime::Current()->GetJITOptions()->GetCodeCacheMaxCapacity();
+  size_t max_capacity = runtime->GetJITOptions()->GetCodeCacheMaxCapacity();
   // We need to have 32 bit offsets from method headers in code cache which point to things
   // in the data cache. If the maps are more than 4G apart, having multiple maps wouldn't work.
   // Ensure we're below 1 GB to be safe.
@@ -244,6 +233,11 @@
     return nullptr;
   }
 
+  if (region.HasCodeMapping()) {
+    const MemMap* exec_pages = region.GetExecPages();
+    runtime->AddGeneratedCodeRange(exec_pages->Begin(), exec_pages->Size());
+  }
+
   std::unique_ptr<JitCodeCache> jit_code_cache(new JitCodeCache());
   if (is_zygote) {
     // Zygote should never collect code to share the memory with the children.
@@ -278,7 +272,16 @@
       histogram_profiling_info_memory_use_("Memory used for profiling info", 16) {
 }
 
-JitCodeCache::~JitCodeCache() {}
+JitCodeCache::~JitCodeCache() {
+  if (private_region_.HasCodeMapping()) {
+    const MemMap* exec_pages = private_region_.GetExecPages();
+    Runtime::Current()->RemoveGeneratedCodeRange(exec_pages->Begin(), exec_pages->Size());
+  }
+  if (shared_region_.HasCodeMapping()) {
+    const MemMap* exec_pages = shared_region_.GetExecPages();
+    Runtime::Current()->RemoveGeneratedCodeRange(exec_pages->Begin(), exec_pages->Size());
+  }
+}
 
 bool JitCodeCache::PrivateRegionContainsPc(const void* ptr) const {
   return private_region_.IsInExecSpace(ptr);
@@ -289,7 +292,9 @@
 }
 
 bool JitCodeCache::ContainsMethod(ArtMethod* method) {
-  MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
+  MutexLock mu(self, *Locks::jit_lock_);
   if (UNLIKELY(method->IsNative())) {
     auto it = jni_stubs_map_.find(JniStubKey(method));
     if (it != jni_stubs_map_.end() &&
@@ -312,7 +317,9 @@
 
 const void* JitCodeCache::GetJniStubCode(ArtMethod* method) {
   DCHECK(method->IsNative());
-  MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
+  MutexLock mu(self, *Locks::jit_lock_);
   auto it = jni_stubs_map_.find(JniStubKey(method));
   if (it != jni_stubs_map_.end()) {
     JniStubData& data = it->second;
@@ -324,12 +331,14 @@
 }
 
 const void* JitCodeCache::GetSavedEntryPointOfPreCompiledMethod(ArtMethod* method) {
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
   if (method->IsPreCompiled()) {
     const void* code_ptr = nullptr;
-    if (method->GetDeclaringClass()->IsBootStrapClassLoaded()) {
+    if (method->GetDeclaringClass<kWithoutReadBarrier>()->IsBootStrapClassLoaded()) {
       code_ptr = zygote_map_.GetCodeFor(method);
     } else {
-      MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+      MutexLock mu(self, *Locks::jit_lock_);
       auto it = saved_compiled_methods_map_.find(method);
       if (it != saved_compiled_methods_map_.end()) {
         code_ptr = it->second;
@@ -353,12 +362,12 @@
 }
 
 static uintptr_t FromCodeToAllocation(const void* code) {
-  size_t alignment = GetInstructionSetAlignment(kRuntimeISA);
+  size_t alignment = GetInstructionSetCodeAlignment(kRuntimeISA);
   return reinterpret_cast<uintptr_t>(code) - RoundUp(sizeof(OatQuickMethodHeader), alignment);
 }
 
 static const void* FromAllocationToCode(const uint8_t* alloc) {
-  size_t alignment = GetInstructionSetAlignment(kRuntimeISA);
+  size_t alignment = GetInstructionSetCodeAlignment(kRuntimeISA);
   return reinterpret_cast<const void*>(alloc + RoundUp(sizeof(OatQuickMethodHeader), alignment));
 }
 
@@ -400,7 +409,9 @@
 }
 
 void JitCodeCache::SweepRootTables(IsMarkedVisitor* visitor) {
-  MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
+  MutexLock mu(self, *Locks::jit_lock_);
   for (const auto& entry : method_code_map_) {
     uint32_t number_of_roots = 0;
     const uint8_t* root_table = GetRootTable(entry.first, &number_of_roots);
@@ -416,20 +427,20 @@
       } else if (object->IsString<kDefaultVerifyFlags>()) {
         mirror::Object* new_object = visitor->IsMarked(object);
         // We know the string is marked because it's a strongly-interned string that
-        // is always alive. The IsMarked implementation of the CMS collector returns
-        // null for newly allocated objects, but we know those haven't moved. Therefore,
-        // only update the entry if we get a different non-null string.
+        // is always alive.
         // TODO: Do not use IsMarked for j.l.Class, and adjust once we move this method
         // out of the weak access/creation pause. b/32167580
-        if (new_object != nullptr && new_object != object) {
-          DCHECK(new_object->IsString());
+        DCHECK_NE(new_object, nullptr) << "old-string:" << object;
+        if (new_object != object) {
           roots[i] = GcRoot<mirror::Object>(new_object);
         }
       } else {
-        Runtime::ProcessWeakClass(
-            reinterpret_cast<GcRoot<mirror::Class>*>(&roots[i]),
-            visitor,
-            Runtime::GetWeakClassSentinel());
+        mirror::Object* new_klass = visitor->IsMarked(object);
+        if (new_klass == nullptr) {
+          roots[i] = GcRoot<mirror::Object>(Runtime::GetWeakClassSentinel());
+        } else if (new_klass != object) {
+          roots[i] = GcRoot<mirror::Object>(new_klass);
+        }
       }
     }
   }
@@ -439,7 +450,13 @@
     for (size_t i = 0; i < info->number_of_inline_caches_; ++i) {
       InlineCache* cache = &info->cache_[i];
       for (size_t j = 0; j < InlineCache::kIndividualCacheSize; ++j) {
-        Runtime::ProcessWeakClass(&cache->classes_[j], visitor, nullptr);
+        mirror::Class* klass = cache->classes_[j].Read<kWithoutReadBarrier>();
+        if (klass != nullptr) {
+          mirror::Class* new_klass = down_cast<mirror::Class*>(visitor->IsMarked(klass));
+          if (new_klass != klass) {
+            cache->classes_[j] = GcRoot<mirror::Class>(new_klass);
+          }
+        }
       }
     }
   }
@@ -506,6 +523,7 @@
 
 void JitCodeCache::RemoveMethodsIn(Thread* self, const LinearAlloc& alloc) {
   ScopedTrace trace(__PRETTY_FUNCTION__);
+  ScopedDebugDisallowReadBarriers sddrb(self);
   // We use a set to first collect all method_headers whose code need to be
   // removed. We need to free the underlying code after we remove CHA dependencies
   // for entries in this set. And it's more efficient to iterate through
@@ -560,7 +578,7 @@
 }
 
 bool JitCodeCache::IsWeakAccessEnabled(Thread* self) const {
-  return kUseReadBarrier
+  return gUseReadBarrier
       ? self->GetWeakRefAccessEnabled()
       : is_weak_access_enabled_.load(std::memory_order_seq_cst);
 }
@@ -583,13 +601,13 @@
 }
 
 void JitCodeCache::AllowInlineCacheAccess() {
-  DCHECK(!kUseReadBarrier);
+  DCHECK(!gUseReadBarrier);
   is_weak_access_enabled_.store(true, std::memory_order_seq_cst);
   BroadcastForInlineCacheAccess();
 }
 
 void JitCodeCache::DisallowInlineCacheAccess() {
-  DCHECK(!kUseReadBarrier);
+  DCHECK(!gUseReadBarrier);
   is_weak_access_enabled_.store(false, std::memory_order_seq_cst);
 }
 
@@ -700,32 +718,37 @@
     // compiled code is considered invalidated by some class linking, but below we still make the
     // compiled code valid for the method.  Need cha_lock_ for checking all single-implementation
     // flags and register dependencies.
-    MutexLock cha_mu(self, *Locks::cha_lock_);
-    bool single_impl_still_valid = true;
-    for (ArtMethod* single_impl : cha_single_implementation_list) {
-      if (!single_impl->HasSingleImplementation()) {
-        // Simply discard the compiled code. Clear the counter so that it may be recompiled later.
-        // Hopefully the class hierarchy will be more stable when compilation is retried.
-        single_impl_still_valid = false;
-        ClearMethodCounter(method, /*was_warm=*/ false);
-        break;
+    {
+      ScopedDebugDisallowReadBarriers sddrb(self);
+      MutexLock cha_mu(self, *Locks::cha_lock_);
+      bool single_impl_still_valid = true;
+      for (ArtMethod* single_impl : cha_single_implementation_list) {
+        if (!single_impl->HasSingleImplementation()) {
+          // Simply discard the compiled code. Clear the counter so that it may be recompiled later.
+          // Hopefully the class hierarchy will be more stable when compilation is retried.
+          single_impl_still_valid = false;
+          ClearMethodCounter(method, /*was_warm=*/ false);
+          break;
+        }
+      }
+
+      // Discard the code if any single-implementation assumptions are now invalid.
+      if (UNLIKELY(!single_impl_still_valid)) {
+        VLOG(jit) << "JIT discarded jitted code due to invalid single-implementation assumptions.";
+        return false;
+      }
+      DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsJavaDebuggable())
+          << "Should not be using cha on debuggable apps/runs!";
+
+      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+      for (ArtMethod* single_impl : cha_single_implementation_list) {
+        class_linker->GetClassHierarchyAnalysis()->AddDependency(
+            single_impl, method, method_header);
       }
     }
 
-    // Discard the code if any single-implementation assumptions are now invalid.
-    if (UNLIKELY(!single_impl_still_valid)) {
-      VLOG(jit) << "JIT discarded jitted code due to invalid single-implementation assumptions.";
-      return false;
-    }
-    DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsJavaDebuggable())
-        << "Should not be using cha on debuggable apps/runs!";
-
-    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-    for (ArtMethod* single_impl : cha_single_implementation_list) {
-      class_linker->GetClassHierarchyAnalysis()->AddDependency(single_impl, method, method_header);
-    }
-
     if (UNLIKELY(method->IsNative())) {
+      ScopedDebugDisallowReadBarriers sddrb(self);
       auto it = jni_stubs_map_.find(JniStubKey(method));
       DCHECK(it != jni_stubs_map_.end())
           << "Entry inserted in NotifyCompilationOf() should be alive.";
@@ -736,14 +759,17 @@
       data->UpdateEntryPoints(method_header->GetEntryPoint());
     } else {
       if (method->IsPreCompiled() && IsSharedRegion(*region)) {
+        ScopedDebugDisallowReadBarriers sddrb(self);
         zygote_map_.Put(code_ptr, method);
       } else {
+        ScopedDebugDisallowReadBarriers sddrb(self);
         method_code_map_.Put(code_ptr, method);
       }
       if (compilation_kind == CompilationKind::kOsr) {
+        ScopedDebugDisallowReadBarriers sddrb(self);
         osr_code_map_.Put(method, code_ptr);
-      } else if (NeedsClinitCheckBeforeCall(method) &&
-                 !method->GetDeclaringClass()->IsVisiblyInitialized()) {
+      } else if (method->StillNeedsClinitCheck()) {
+        ScopedDebugDisallowReadBarriers sddrb(self);
         // This situation currently only occurs in the jit-zygote mode.
         DCHECK(!garbage_collect_code_);
         DCHECK(method->IsPreCompiled());
@@ -784,7 +810,9 @@
   // This function is used only for testing and only with non-native methods.
   CHECK(!method->IsNative());
 
-  MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
+  MutexLock mu(self, *Locks::jit_lock_);
 
   bool osr = osr_code_map_.find(method) != osr_code_map_.end();
   bool in_cache = RemoveMethodLocked(method, release_memory);
@@ -853,7 +881,9 @@
 // any cached information it has on the method. All threads must be suspended before calling this
 // method. The compiled code for the method (if there is any) must not be in any threads call stack.
 void JitCodeCache::NotifyMethodRedefined(ArtMethod* method) {
-  MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
+  MutexLock mu(self, *Locks::jit_lock_);
   RemoveMethodLocked(method, /* release_memory= */ true);
 }
 
@@ -864,7 +894,9 @@
 // shouldn't be used since it is no longer logically in the jit code cache.
 // TODO We should add DCHECKS that validate that the JIT is paused when this method is entered.
 void JitCodeCache::MoveObsoleteMethod(ArtMethod* old_method, ArtMethod* new_method) {
-  MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
+  MutexLock mu(self, *Locks::jit_lock_);
   if (old_method->IsNative()) {
     // Update methods in jni_stubs_map_.
     for (auto& entry : jni_stubs_map_) {
@@ -891,11 +923,14 @@
   // Check that none of our methods have an entrypoint in the zygote exec
   // space (this should be taken care of by
   // ClassLinker::UpdateEntryPointsClassVisitor.
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
   {
-    MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+    MutexLock mu(self, *Locks::jit_lock_);
     if (kIsDebugBuild) {
-      for (const auto& it : method_code_map_) {
-        ArtMethod* method = it.second;
+      // TODO: Check `jni_stubs_map_`?
+      for (const auto& entry : method_code_map_) {
+        ArtMethod* method = entry.second;
         DCHECK(!method->IsPreCompiled());
         DCHECK(!IsInZygoteExecSpace(method->GetEntryPointFromQuickCompiledCode()));
       }
@@ -1032,22 +1067,6 @@
         /* context= */ nullptr,
         art::StackVisitor::StackWalkKind::kSkipInlinedFrames);
 
-    if (kIsDebugBuild) {
-      // The stack walking code queries the side instrumentation stack if it
-      // sees an instrumentation exit pc, so the JIT code of methods in that stack
-      // must have been seen. We check this below.
-      for (const auto& it : *thread->GetInstrumentationStack()) {
-        // The 'method_' in InstrumentationStackFrame is the one that has return_pc_ in
-        // its stack frame, it is not the method owning return_pc_. We just pass null to
-        // LookupMethodHeader: the method is only checked against in debug builds.
-        OatQuickMethodHeader* method_header =
-            code_cache_->LookupMethodHeader(it.second.return_pc_, /* method= */ nullptr);
-        if (method_header != nullptr) {
-          const void* code = method_header->GetCode();
-          CHECK(bitmap_->Test(FromCodeToAllocation(code)));
-        }
-      }
-    }
     barrier_->Pass(Thread::Current());
   }
 
@@ -1156,6 +1175,7 @@
 
       // Start polling the liveness of compiled code to prepare for the next full collection.
       if (next_collection_will_be_full) {
+        ScopedDebugDisallowReadBarriers sddrb(self);
         for (auto it : profiling_infos_) {
           it.second->ResetCounter();
         }
@@ -1187,6 +1207,7 @@
 
 void JitCodeCache::RemoveUnmarkedCode(Thread* self) {
   ScopedTrace trace(__FUNCTION__);
+  ScopedDebugDisallowReadBarriers sddrb(self);
   std::unordered_set<OatQuickMethodHeader*> method_headers;
   {
     MutexLock mu(self, *Locks::jit_lock_);
@@ -1234,6 +1255,7 @@
 }
 
 void JitCodeCache::RemoveMethodBeingCompiled(ArtMethod* method, CompilationKind kind) {
+  ScopedDebugDisallowReadBarriers sddrb(Thread::Current());
   DCHECK(IsMethodBeingCompiled(method, kind));
   switch (kind) {
     case CompilationKind::kOsr:
@@ -1249,6 +1271,7 @@
 }
 
 void JitCodeCache::AddMethodBeingCompiled(ArtMethod* method, CompilationKind kind) {
+  ScopedDebugDisallowReadBarriers sddrb(Thread::Current());
   DCHECK(!IsMethodBeingCompiled(method, kind));
   switch (kind) {
     case CompilationKind::kOsr:
@@ -1264,6 +1287,7 @@
 }
 
 bool JitCodeCache::IsMethodBeingCompiled(ArtMethod* method, CompilationKind kind) {
+  ScopedDebugDisallowReadBarriers sddrb(Thread::Current());
   switch (kind) {
     case CompilationKind::kOsr:
       return ContainsElement(current_osr_compilations_, method);
@@ -1275,12 +1299,14 @@
 }
 
 bool JitCodeCache::IsMethodBeingCompiled(ArtMethod* method) {
+  ScopedDebugDisallowReadBarriers sddrb(Thread::Current());
   return ContainsElement(current_optimized_compilations_, method) ||
       ContainsElement(current_osr_compilations_, method) ||
       ContainsElement(current_baseline_compilations_, method);
 }
 
 ProfilingInfo* JitCodeCache::GetProfilingInfo(ArtMethod* method, Thread* self) {
+  ScopedDebugDisallowReadBarriers sddrb(self);
   MutexLock mu(self, *Locks::jit_lock_);
   DCHECK(IsMethodBeingCompiled(method))
       << "GetProfilingInfo should only be called when the method is being compiled";
@@ -1292,6 +1318,7 @@
 }
 
 void JitCodeCache::ResetHotnessCounter(ArtMethod* method, Thread* self) {
+  ScopedDebugDisallowReadBarriers sddrb(self);
   MutexLock mu(self, *Locks::jit_lock_);
   auto it = profiling_infos_.find(method);
   DCHECK(it != profiling_infos_.end());
@@ -1302,6 +1329,7 @@
 void JitCodeCache::DoCollection(Thread* self, bool collect_profiling_info) {
   ScopedTrace trace(__FUNCTION__);
   {
+    ScopedDebugDisallowReadBarriers sddrb(self);
     MutexLock mu(self, *Locks::jit_lock_);
 
     // Update to interpreter the methods that have baseline entrypoints and whose baseline
@@ -1390,7 +1418,9 @@
     CHECK(method != nullptr);
   }
 
-  MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
+  MutexLock mu(self, *Locks::jit_lock_);
   OatQuickMethodHeader* method_header = nullptr;
   ArtMethod* found_method = nullptr;  // Only for DCHECK(), not for JNI stubs.
   if (method != nullptr && UNLIKELY(method->IsNative())) {
@@ -1445,7 +1475,9 @@
 }
 
 OatQuickMethodHeader* JitCodeCache::LookupOsrMethodHeader(ArtMethod* method) {
-  MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
+  MutexLock mu(self, *Locks::jit_lock_);
   auto it = osr_code_map_.find(method);
   if (it == osr_code_map_.end()) {
     return nullptr;
@@ -1471,9 +1503,10 @@
   return info;
 }
 
-ProfilingInfo* JitCodeCache::AddProfilingInfoInternal(Thread* self ATTRIBUTE_UNUSED,
+ProfilingInfo* JitCodeCache::AddProfilingInfoInternal(Thread* self,
                                                       ArtMethod* method,
                                                       const std::vector<uint32_t>& entries) {
+  ScopedDebugDisallowReadBarriers sddrb(self);
   // Check whether some other thread has concurrently created it.
   auto it = profiling_infos_.find(method);
   if (it != profiling_infos_.end()) {
@@ -1506,11 +1539,14 @@
                                       std::vector<ProfileMethodInfo>& methods) {
   Thread* self = Thread::Current();
   WaitUntilInlineCacheAccessible(self);
+  // TODO: Avoid read barriers for potentially dead methods.
+  // ScopedDebugDisallowReadBarriers sddrb(self);
   MutexLock mu(self, *Locks::jit_lock_);
   ScopedTrace trace(__FUNCTION__);
-  for (auto it : profiling_infos_) {
-    ProfilingInfo* info = it.second;
-    ArtMethod* method = info->GetMethod();
+  for (const auto& entry : profiling_infos_) {
+    ArtMethod* method = entry.first;
+    ProfilingInfo* info = entry.second;
+    DCHECK_EQ(method, info->GetMethod());
     const DexFile* dex_file = method->GetDexFile();
     const std::string base_location = DexFileLoader::GetBaseLocation(dex_file->GetLocation());
     if (!ContainsElement(dex_base_locations, base_location)) {
@@ -1590,14 +1626,41 @@
 }
 
 bool JitCodeCache::IsOsrCompiled(ArtMethod* method) {
-  MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
+  MutexLock mu(self, *Locks::jit_lock_);
   return osr_code_map_.find(method) != osr_code_map_.end();
 }
 
+void JitCodeCache::VisitRoots(RootVisitor* visitor) {
+  if (Runtime::Current()->GetHeap()->IsPerformingUffdCompaction()) {
+    // In case of userfaultfd compaction, ArtMethods are updated concurrently
+    // via linear-alloc.
+    return;
+  }
+  MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  UnbufferedRootVisitor root_visitor(visitor, RootInfo(kRootStickyClass));
+  for (ArtMethod* method : current_optimized_compilations_) {
+    method->VisitRoots(root_visitor, kRuntimePointerSize);
+  }
+  for (ArtMethod* method : current_baseline_compilations_) {
+    method->VisitRoots(root_visitor, kRuntimePointerSize);
+  }
+  for (ArtMethod* method : current_osr_compilations_) {
+    method->VisitRoots(root_visitor, kRuntimePointerSize);
+  }
+}
+
 bool JitCodeCache::NotifyCompilationOf(ArtMethod* method,
                                        Thread* self,
                                        CompilationKind compilation_kind,
                                        bool prejit) {
+  if (kIsDebugBuild) {
+    MutexLock mu(self, *Locks::jit_lock_);
+    // Note: the compilation kind may have been adjusted after what was passed initially.
+    // We really just want to check that the method is indeed being compiled.
+    CHECK(IsMethodBeingCompiled(method));
+  }
   const void* existing_entry_point = method->GetEntryPointFromQuickCompiledCode();
   if (compilation_kind != CompilationKind::kOsr && ContainsPc(existing_entry_point)) {
     OatQuickMethodHeader* method_header =
@@ -1612,7 +1675,7 @@
     }
   }
 
-  if (NeedsClinitCheckBeforeCall(method) && !prejit) {
+  if (method->NeedsClinitCheckBeforeCall() && !prejit) {
     // We do not need a synchronization barrier for checking the visibly initialized status
     // or checking the initialized status just for requesting visible initialization.
     ClassStatus status = method->GetDeclaringClass()
@@ -1635,6 +1698,7 @@
     }
   }
 
+  ScopedDebugDisallowReadBarriers sddrb(self);
   if (compilation_kind == CompilationKind::kOsr) {
     MutexLock mu(self, *Locks::jit_lock_);
     if (osr_code_map_.find(method) != osr_code_map_.end()) {
@@ -1686,16 +1750,12 @@
         }
       }
     }
-    MutexLock mu(self, *Locks::jit_lock_);
-    if (IsMethodBeingCompiled(method, compilation_kind)) {
-      return false;
-    }
-    AddMethodBeingCompiled(method, compilation_kind);
-    return true;
   }
+  return true;
 }
 
 ProfilingInfo* JitCodeCache::NotifyCompilerUse(ArtMethod* method, Thread* self) {
+  ScopedDebugDisallowReadBarriers sddrb(self);
   MutexLock mu(self, *Locks::jit_lock_);
   auto it = profiling_infos_.find(method);
   if (it == profiling_infos_.end()) {
@@ -1709,16 +1769,16 @@
 }
 
 void JitCodeCache::DoneCompilerUse(ArtMethod* method, Thread* self) {
+  ScopedDebugDisallowReadBarriers sddrb(self);
   MutexLock mu(self, *Locks::jit_lock_);
   auto it = profiling_infos_.find(method);
   DCHECK(it != profiling_infos_.end());
   it->second->DecrementInlineUse();
 }
 
-void JitCodeCache::DoneCompiling(ArtMethod* method,
-                                 Thread* self,
-                                 CompilationKind compilation_kind) {
+void JitCodeCache::DoneCompiling(ArtMethod* method, Thread* self) {
   DCHECK_EQ(Thread::Current(), self);
+  ScopedDebugDisallowReadBarriers sddrb(self);
   MutexLock mu(self, *Locks::jit_lock_);
   if (UNLIKELY(method->IsNative())) {
     auto it = jni_stubs_map_.find(JniStubKey(method));
@@ -1729,25 +1789,40 @@
       // Failed to compile; the JNI compiler never fails, but the cache may be full.
       jni_stubs_map_.erase(it);  // Remove the entry added in NotifyCompilationOf().
     }  // else Commit() updated entrypoints of all methods in the JniStubData.
-  } else {
-    RemoveMethodBeingCompiled(method, compilation_kind);
   }
 }
 
 void JitCodeCache::InvalidateAllCompiledCode() {
-  art::MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  Thread* self = Thread::Current();
+  ScopedDebugDisallowReadBarriers sddrb(self);
+  art::MutexLock mu(self, *Locks::jit_lock_);
   VLOG(jit) << "Invalidating all compiled code";
-  ClassLinker* linker = Runtime::Current()->GetClassLinker();
-  for (auto it : method_code_map_) {
-    ArtMethod* meth = it.second;
+  Runtime* runtime = Runtime::Current();
+  ClassLinker* linker = runtime->GetClassLinker();
+  instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
+  // TODO: Clear `jni_stubs_map_`?
+  for (const auto& entry : method_code_map_) {
+    ArtMethod* meth = entry.second;
     // We were compiled, so we must be warm.
     ClearMethodCounter(meth, /*was_warm=*/true);
-    if (meth->IsObsolete()) {
+    if (UNLIKELY(meth->IsObsolete())) {
       linker->SetEntryPointsForObsoleteMethod(meth);
     } else {
-      Runtime::Current()->GetInstrumentation()->InitializeMethodsCode(meth, /*aot_code=*/ nullptr);
+      instr->InitializeMethodsCode(meth, /*aot_code=*/ nullptr);
     }
   }
+
+  for (const auto& entry : zygote_map_) {
+    if (entry.method == nullptr) {
+      continue;
+    }
+    if (entry.method->IsPreCompiled()) {
+      entry.method->ClearPreCompiled();
+    }
+    Runtime::Current()->GetInstrumentation()->InitializeMethodsCode(entry.method,
+                                                                    /*aot_code=*/nullptr);
+  }
+
   saved_compiled_methods_map_.clear();
   osr_code_map_.clear();
 }
@@ -1765,7 +1840,9 @@
     Runtime::Current()->GetInstrumentation()->InitializeMethodsCode(method, /*aot_code=*/ nullptr);
     ClearMethodCounter(method, /*was_warm=*/ true);
   } else {
-    MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+    Thread* self = Thread::Current();
+    ScopedDebugDisallowReadBarriers sddrb(self);
+    MutexLock mu(self, *Locks::jit_lock_);
     auto it = osr_code_map_.find(method);
     if (it != osr_code_map_.end() && OatQuickMethodHeader::FromCodePointer(it->second) == header) {
       // Remove the OSR method, to avoid using it again.
@@ -1817,7 +1894,8 @@
   // We do this now and not in Jit::PostForkChildAction, as system server calls
   // JitCodeCache::PostForkChildAction first, and then does some code loading
   // that may result in new JIT tasks that we want to keep.
-  ThreadPool* pool = Runtime::Current()->GetJit()->GetThreadPool();
+  Runtime* runtime = Runtime::Current();
+  ThreadPool* pool = runtime->GetJit()->GetThreadPool();
   if (pool != nullptr) {
     pool->RemoveAllTasks(self);
   }
@@ -1828,7 +1906,7 @@
   // to write to them.
   shared_region_.ResetWritableMappings();
 
-  if (is_zygote || Runtime::Current()->IsSafeMode()) {
+  if (is_zygote || runtime->IsSafeMode()) {
     // Don't create a private region for a child zygote. Regions are usually map shared
     // (to satisfy dual-view), and we don't want children of a child zygote to inherit it.
     return;
@@ -1843,8 +1921,8 @@
   histogram_code_memory_use_.Reset();
   histogram_profiling_info_memory_use_.Reset();
 
-  size_t initial_capacity = Runtime::Current()->GetJITOptions()->GetCodeCacheInitialCapacity();
-  size_t max_capacity = Runtime::Current()->GetJITOptions()->GetCodeCacheMaxCapacity();
+  size_t initial_capacity = runtime->GetJITOptions()->GetCodeCacheInitialCapacity();
+  size_t max_capacity = runtime->GetJITOptions()->GetCodeCacheMaxCapacity();
   std::string error_msg;
   if (!private_region_.Initialize(initial_capacity,
                                   max_capacity,
@@ -1853,6 +1931,10 @@
                                   &error_msg)) {
     LOG(WARNING) << "Could not create private region after zygote fork: " << error_msg;
   }
+  if (private_region_.HasCodeMapping()) {
+    const MemMap* exec_pages = private_region_.GetExecPages();
+    runtime->AddGeneratedCodeRange(exec_pages->Begin(), exec_pages->Size());
+  }
 }
 
 JitMemoryRegion* JitCodeCache::GetCurrentRegion() {
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index fb861a4..a6b101b 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -215,7 +215,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::jit_lock_);
 
-  void DoneCompiling(ArtMethod* method, Thread* self, CompilationKind compilation_kind)
+  void DoneCompiling(ArtMethod* method, Thread* self)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::jit_lock_);
 
@@ -230,10 +230,12 @@
   bool PrivateRegionContainsPc(const void* pc) const;
 
   // Return true if the code cache contains this method.
-  bool ContainsMethod(ArtMethod* method) REQUIRES(!Locks::jit_lock_);
+  bool ContainsMethod(ArtMethod* method)
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::jit_lock_);
 
   // Return the code pointer for a JNI-compiled stub if the method is in the cache, null otherwise.
-  const void* GetJniStubCode(ArtMethod* method) REQUIRES(!Locks::jit_lock_);
+  const void* GetJniStubCode(ArtMethod* method)
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::jit_lock_);
 
   // Allocate a region for both code and data in the JIT code cache.
   // The reserved memory is left completely uninitialized.
@@ -403,6 +405,20 @@
   ProfilingInfo* GetProfilingInfo(ArtMethod* method, Thread* self);
   void ResetHotnessCounter(ArtMethod* method, Thread* self);
 
+  void VisitRoots(RootVisitor* visitor);
+
+  // Return whether `method` is being compiled with the given mode.
+  bool IsMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
+      REQUIRES(Locks::jit_lock_);
+
+  // Remove `method` from the list of methods meing compiled with the given mode.
+  void RemoveMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
+      REQUIRES(Locks::jit_lock_);
+
+  // Record that `method` is being compiled with the given mode.
+  void AddMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
+      REQUIRES(Locks::jit_lock_);
+
  private:
   JitCodeCache();
 
@@ -492,18 +508,6 @@
       REQUIRES(!Locks::jit_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Record that `method` is being compiled with the given mode.
-  void AddMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
-      REQUIRES(Locks::jit_lock_);
-
-  // Remove `method` from the list of methods meing compiled with the given mode.
-  void RemoveMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
-      REQUIRES(Locks::jit_lock_);
-
-  // Return whether `method` is being compiled with the given mode.
-  bool IsMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
-      REQUIRES(Locks::jit_lock_);
-
   // Return whether `method` is being compiled in any mode.
   bool IsMethodBeingCompiled(ArtMethod* method) REQUIRES(Locks::jit_lock_);
 
@@ -528,6 +532,14 @@
 
   // -------------- Global JIT maps --------------------------------------- //
 
+  // Note: The methods held in these maps may be dead, so we must ensure that we do not use
+  // read barriers on their declaring classes as that could unnecessarily keep them alive or
+  // crash the GC, depending on the GC phase and particular GC's details. Asserting that we
+  // do not emit read barriers for these methods can be tricky as we're allowed to emit read
+  // barriers for other methods that are known to be alive, such as the method being compiled.
+  // The GC must ensure that methods in these maps are cleaned up with `RemoveMethodsIn()`
+  // before the declaring class memory is freed.
+
   // Holds compiled code associated with the shorty for a JNI stub.
   SafeMap<JniStubKey, JniStubData> jni_stubs_map_ GUARDED_BY(Locks::jit_lock_);
 
diff --git a/runtime/jit/jit_load_test.cc b/runtime/jit/jit_load_test.cc
new file mode 100644
index 0000000..4b080a5
--- /dev/null
+++ b/runtime/jit/jit_load_test.cc
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "common_runtime_test.h"
+#include "compiler_callbacks.h"
+
+namespace art {
+
+class JitLoadTest : public CommonRuntimeTest {
+ protected:
+  void SetUpRuntimeOptions(RuntimeOptions *options) override {
+    callbacks_.reset();
+    CommonRuntimeTest::SetUpRuntimeOptions(options);
+    options->push_back(std::make_pair("-Xusejit:true", nullptr));
+  }
+};
+
+
+TEST_F(JitLoadTest, JitLoad) {
+  Thread::Current()->TransitionFromSuspendedToRunnable();
+  runtime_->Start();
+  ASSERT_NE(runtime_->GetJit(), nullptr);
+}
+
+}  // namespace art
diff --git a/runtime/jit/jit_memory_region.cc b/runtime/jit/jit_memory_region.cc
index 56407f5..410bf70 100644
--- a/runtime/jit/jit_memory_region.cc
+++ b/runtime/jit/jit_memory_region.cc
@@ -27,7 +27,7 @@
 #include "base/membarrier.h"
 #include "base/memfd.h"
 #include "base/systrace.h"
-#include "gc/allocator/dlmalloc.h"
+#include "gc/allocator/art-dlmalloc.h"
 #include "jit/jit_scoped_code_cache_write.h"
 #include "oat_quick_method_header.h"
 #include "palette/palette.h"
@@ -360,7 +360,7 @@
   DCHECK(IsInExecSpace(reserved_code.data()));
   ScopedCodeCacheWrite scc(*this);
 
-  size_t alignment = GetInstructionSetAlignment(kRuntimeISA);
+  size_t alignment = GetInstructionSetCodeAlignment(kRuntimeISA);
   size_t header_size = OatQuickMethodHeader::InstructionAlignedSize();
   size_t total_size = header_size + code.size();
 
@@ -468,7 +468,7 @@
 }
 
 const uint8_t* JitMemoryRegion::AllocateCode(size_t size) {
-  size_t alignment = GetInstructionSetAlignment(kRuntimeISA);
+  size_t alignment = GetInstructionSetCodeAlignment(kRuntimeISA);
   void* result = mspace_memalign(exec_mspace_, alignment, size);
   if (UNLIKELY(result == nullptr)) {
     return nullptr;
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index cea654f..3321636 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -23,7 +23,6 @@
 #include <unistd.h>
 
 #include "android-base/strings.h"
-
 #include "art_method-inl.h"
 #include "base/compiler_filter.h"
 #include "base/enums.h"
@@ -32,6 +31,7 @@
 #include "base/stl_util.h"
 #include "base/systrace.h"
 #include "base/time_utils.h"
+#include "base/unix_file/fd_file.h"
 #include "class_table-inl.h"
 #include "dex/dex_file_loader.h"
 #include "dex_reference_collection.h"
@@ -136,21 +136,20 @@
   {
     MutexLock mu(self, wait_lock_);
 
-    const uint64_t end_time = NanoTime() + MsToNs(force_early_first_save
+    const uint64_t sleep_time = MsToNs(force_early_first_save
       ? options_.GetMinFirstSaveMs()
       : options_.GetSaveResolvedClassesDelayMs());
-    while (!Runtime::Current()->GetStartupCompleted()) {
+    const uint64_t start_time = NanoTime();
+    const uint64_t end_time = start_time + sleep_time;
+    while (!Runtime::Current()->GetStartupCompleted() || force_early_first_save) {
       const uint64_t current_time = NanoTime();
       if (current_time >= end_time) {
         break;
       }
       period_condition_.TimedWait(self, NsToMs(end_time - current_time), 0);
     }
-    total_ms_of_sleep_ += options_.GetSaveResolvedClassesDelayMs();
+    total_ms_of_sleep_ += NsToMs(NanoTime() - start_time);
   }
-  // Tell the runtime that startup is completed if it has not already been notified.
-  // TODO: We should use another thread to do this in case the profile saver is not running.
-  Runtime::Current()->NotifyStartupCompleted();
 
   FetchAndCacheResolvedClassesAndMethods(/*startup=*/ true);
 
@@ -869,11 +868,17 @@
     }
     {
       ProfileCompilationInfo info(Runtime::Current()->GetArenaPool(),
-                                  /*for_boot_image=*/ options_.GetProfileBootClassPath());
-      if (!info.Load(filename, /*clear_if_invalid=*/ true)) {
+                                  /*for_boot_image=*/options_.GetProfileBootClassPath());
+      // Load the existing profile before saving.
+      // If the file is updated between `Load` and `Save`, the update will be lost. This is
+      // acceptable. The main reason is that the lost entries will eventually come back if the user
+      // keeps using the same methods, or they won't be needed if the user doesn't use the same
+      // methods again.
+      if (!info.Load(filename, /*clear_if_invalid=*/true)) {
         LOG(WARNING) << "Could not forcefully load profile " << filename;
         continue;
       }
+
       uint64_t last_save_number_of_methods = info.GetNumberOfMethods();
       uint64_t last_save_number_of_classes = info.GetNumberOfResolvedClasses();
       VLOG(profiler) << "last_save_number_of_methods=" << last_save_number_of_methods
diff --git a/runtime/jit/profiling_info_test.cc b/runtime/jit/profiling_info_test.cc
index ce0a30f..021bebf 100644
--- a/runtime/jit/profiling_info_test.cc
+++ b/runtime/jit/profiling_info_test.cc
@@ -72,6 +72,7 @@
       Hotness::Flag flags) {
     ProfileCompilationInfo info;
     std::vector<ProfileMethodInfo> profile_methods;
+    profile_methods.reserve(methods.size());
     ScopedObjectAccess soa(Thread::Current());
     for (ArtMethod* method : methods) {
       profile_methods.emplace_back(
@@ -188,7 +189,7 @@
 
   // Check that what we saved is in the profile.
   ProfileCompilationInfo info1;
-  ASSERT_TRUE(info1.Load(GetFd(profile)));
+  ASSERT_TRUE(info1.Load(profile.GetFilename(), /*clear_if_invalid=*/false));
   ASSERT_EQ(info1.GetNumberOfMethods(), main_methods.size());
   {
     ScopedObjectAccess soa(self);
@@ -208,7 +209,7 @@
 
   // Check that what we saved is in the profile (methods form Main and Second).
   ProfileCompilationInfo info2;
-  ASSERT_TRUE(info2.Load(GetFd(profile)));
+  ASSERT_TRUE(info2.Load(profile.GetFilename(), /*clear_if_invalid=*/false));
   ASSERT_EQ(info2.GetNumberOfMethods(), main_methods.size() + second_methods.size());
   {
     ScopedObjectAccess soa(self);
@@ -248,7 +249,7 @@
 
   // Check that what we saved is in the profile.
   ProfileCompilationInfo info;
-  ASSERT_TRUE(info.Load(GetFd(profile)));
+  ASSERT_TRUE(info.Load(profile.GetFilename(), /*clear_if_invalid=*/false));
   ASSERT_EQ(info.GetNumberOfMethods(), main_methods.size());
   {
     ScopedObjectAccess soa(self);
diff --git a/runtime/jni/check_jni.cc b/runtime/jni/check_jni.cc
index 4c7b1aa..eb54f98 100644
--- a/runtime/jni/check_jni.cc
+++ b/runtime/jni/check_jni.cc
@@ -38,6 +38,7 @@
 #include "indirect_reference_table-inl.h"
 #include "java_vm_ext.h"
 #include "jni_internal.h"
+#include "local_reference_table-inl.h"
 #include "mirror/class-inl.h"
 #include "mirror/field.h"
 #include "mirror/method.h"
@@ -56,15 +57,20 @@
 // declared as a friend by JniVmExt and JniEnvExt.
 inline IndirectReferenceTable* GetIndirectReferenceTable(ScopedObjectAccess& soa,
                                                          IndirectRefKind kind) {
-  DCHECK_NE(kind, kJniTransitionOrInvalid);
-  JNIEnvExt* env = soa.Env();
-  IndirectReferenceTable* irt =
-      (kind == kLocal) ? &env->locals_
-                       : ((kind == kGlobal) ? &env->vm_->globals_ : &env->vm_->weak_globals_);
+  DCHECK_NE(kind, kJniTransition);
+  DCHECK_NE(kind, kLocal);
+  JavaVMExt* vm = soa.Env()->GetVm();
+  IndirectReferenceTable* irt = (kind == kGlobal) ? &vm->globals_ : &vm->weak_globals_;
   DCHECK_EQ(irt->GetKind(), kind);
   return irt;
 }
 
+// This helper cannot be in the anonymous namespace because it needs to be
+// declared as a friend by JniEnvExt.
+inline jni::LocalReferenceTable* GetLocalReferenceTable(ScopedObjectAccess& soa) {
+  return &soa.Env()->locals_;
+}
+
 namespace {
 
 using android::base::StringAppendF;
@@ -723,7 +729,7 @@
     IndirectRefKind found_kind;
     if (expected_kind == kLocal) {
       found_kind = IndirectReferenceTable::GetIndirectRefKind(obj);
-      if (found_kind == kJniTransitionOrInvalid &&
+      if (found_kind == kJniTransition &&
           obj != nullptr &&
           self->IsJniTransitionReference(obj)) {
         found_kind = kLocal;
@@ -866,13 +872,19 @@
     bool expect_null = false;
     bool okay = true;
     std::string error_msg;
-    if (ref_kind == kJniTransitionOrInvalid) {
+    if (ref_kind == kJniTransition) {
       if (!soa.Self()->IsJniTransitionReference(java_object)) {
         okay = false;
         error_msg = "use of invalid jobject";
       } else {
         obj = soa.Decode<mirror::Object>(java_object);
       }
+    } else if (ref_kind == kLocal) {
+      jni::LocalReferenceTable* lrt = GetLocalReferenceTable(soa);
+      okay = lrt->IsValidReference(java_object, &error_msg);
+      if (okay) {
+        obj = lrt->Get(ref);
+      }
     } else {
       IndirectReferenceTable* irt = GetIndirectReferenceTable(soa, ref_kind);
       okay = irt->IsValidReference(java_object, &error_msg);
@@ -881,10 +893,7 @@
         // Note: The `IsValidReference()` checks for null but we do not prevent races,
         // so the null check below can still fail. Even if it succeeds, another thread
         // could delete the global or weak global before it's used by JNI.
-        if (ref_kind == kLocal) {
-          // Local references do not need a read barrier.
-          obj = irt->Get<kWithoutReadBarrier>(ref);
-        } else if (ref_kind == kGlobal) {
+        if (ref_kind == kGlobal) {
           obj = soa.Env()->GetVm()->DecodeGlobal(ref);
         } else {
           obj = soa.Env()->GetVm()->DecodeWeakGlobal(soa.Self(), ref);
@@ -2340,6 +2349,7 @@
     CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, Primitive::kPrimVoid, kDirect);
   }
 
+  NO_STACK_PROTECTOR
   static void CallStaticVoidMethodV(JNIEnv* env, jclass c, jmethodID mid, va_list vargs) {
     CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, Primitive::kPrimVoid, kStatic);
   }
@@ -3304,6 +3314,7 @@
     return result;
   }
 
+  NO_STACK_PROTECTOR
   static JniValueType CallMethodV(const char* function_name, JNIEnv* env, jobject obj, jclass c,
                                   jmethodID mid, va_list vargs, Primitive::Type type,
                                   InvokeType invoke) {
diff --git a/runtime/jni/java_vm_ext-inl.h b/runtime/jni/java_vm_ext-inl.h
index 29cdf1b..c98a553 100644
--- a/runtime/jni/java_vm_ext-inl.h
+++ b/runtime/jni/java_vm_ext-inl.h
@@ -26,7 +26,7 @@
 
 inline bool JavaVMExt::MayAccessWeakGlobals(Thread* self) const {
   DCHECK(self != nullptr);
-  return kUseReadBarrier
+  return gUseReadBarrier
       ? self->GetWeakRefAccessEnabled()
       : allow_accessing_weak_globals_.load(std::memory_order_seq_cst);
 }
diff --git a/runtime/jni/java_vm_ext.cc b/runtime/jni/java_vm_ext.cc
index f41b6c0..7ae6c99 100644
--- a/runtime/jni/java_vm_ext.cc
+++ b/runtime/jni/java_vm_ext.cc
@@ -44,7 +44,6 @@
 #include "nativehelper/scoped_local_ref.h"
 #include "nativehelper/scoped_utf_chars.h"
 #include "nativeloader/native_loader.h"
-#include "object_callbacks.h"
 #include "parsed_options.h"
 #include "runtime-inl.h"
 #include "runtime_options.h"
@@ -53,7 +52,7 @@
 #include "thread-inl.h"
 #include "thread_list.h"
 #include "ti/agent.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -306,6 +305,7 @@
       *detail += "No implementation found for ";
       *detail += m->PrettyMethod();
       *detail += " (tried " + jni_short_name + " and " + jni_long_name + ")";
+      *detail += " - is the library loaded, e.g. System.loadLibrary?";
     }
     return nullptr;
   }
@@ -495,9 +495,7 @@
   JII::AttachCurrentThreadAsDaemon
 };
 
-JavaVMExt::JavaVMExt(Runtime* runtime,
-                     const RuntimeArgumentMap& runtime_options,
-                     std::string* error_msg)
+JavaVMExt::JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options)
     : runtime_(runtime),
       check_jni_abort_hook_(nullptr),
       check_jni_abort_hook_data_(nullptr),
@@ -506,13 +504,10 @@
       tracing_enabled_(runtime_options.Exists(RuntimeArgumentMap::JniTrace)
                        || VLOG_IS_ON(third_party_jni)),
       trace_(runtime_options.GetOrDefault(RuntimeArgumentMap::JniTrace)),
-      globals_(kGlobalsMax, kGlobal, IndirectReferenceTable::ResizableCapacity::kNo, error_msg),
+      globals_(kGlobal),
       libraries_(new Libraries),
       unchecked_functions_(&gJniInvokeInterface),
-      weak_globals_(kWeakGlobalsMax,
-                    kWeakGlobal,
-                    IndirectReferenceTable::ResizableCapacity::kNo,
-                    error_msg),
+      weak_globals_(kWeakGlobal),
       allow_accessing_weak_globals_(true),
       weak_globals_add_condition_("weak globals add condition",
                                   (CHECK(Locks::jni_weak_globals_lock_ != nullptr),
@@ -527,21 +522,23 @@
   SetCheckJniEnabled(runtime_options.Exists(RuntimeArgumentMap::CheckJni) || kIsDebugBuild);
 }
 
+bool JavaVMExt::Initialize(std::string* error_msg) {
+  return globals_.Initialize(kGlobalsMax, error_msg) &&
+         weak_globals_.Initialize(kWeakGlobalsMax, error_msg);
+}
+
 JavaVMExt::~JavaVMExt() {
   UnloadBootNativeLibraries();
 }
 
-// Checking "globals" and "weak_globals" usually requires locks, but we
-// don't need the locks to check for validity when constructing the
-// object. Use NO_THREAD_SAFETY_ANALYSIS for this.
 std::unique_ptr<JavaVMExt> JavaVMExt::Create(Runtime* runtime,
                                              const RuntimeArgumentMap& runtime_options,
-                                             std::string* error_msg) NO_THREAD_SAFETY_ANALYSIS {
-  std::unique_ptr<JavaVMExt> java_vm(new JavaVMExt(runtime, runtime_options, error_msg));
-  if (java_vm && java_vm->globals_.IsValid() && java_vm->weak_globals_.IsValid()) {
-    return java_vm;
+                                             std::string* error_msg) {
+  std::unique_ptr<JavaVMExt> java_vm(new JavaVMExt(runtime, runtime_options));
+  if (!java_vm->Initialize(error_msg)) {
+    return nullptr;
   }
-  return nullptr;
+  return java_vm;
 }
 
 jint JavaVMExt::HandleGetEnv(/*out*/void** env, jint version) {
@@ -698,7 +695,7 @@
   std::string error_msg;
   {
     WriterMutexLock mu(self, *Locks::jni_globals_lock_);
-    ref = globals_.Add(kIRTFirstSegment, obj, &error_msg);
+    ref = globals_.Add(obj, &error_msg);
     MaybeTraceGlobals();
   }
   if (UNLIKELY(ref == nullptr)) {
@@ -729,12 +726,12 @@
   MutexLock mu(self, *Locks::jni_weak_globals_lock_);
   // CMS needs this to block for concurrent reference processing because an object allocated during
   // the GC won't be marked and concurrent reference processing would incorrectly clear the JNI weak
-  // ref. But CC (kUseReadBarrier == true) doesn't because of the to-space invariant.
-  if (!kUseReadBarrier) {
+  // ref. But CC (gUseReadBarrier == true) doesn't because of the to-space invariant.
+  if (!gUseReadBarrier) {
     WaitForWeakGlobalsAccess(self);
   }
   std::string error_msg;
-  IndirectRef ref = weak_globals_.Add(kIRTFirstSegment, obj, &error_msg);
+  IndirectRef ref = weak_globals_.Add(obj, &error_msg);
   MaybeTraceWeakGlobals();
   if (UNLIKELY(ref == nullptr)) {
     LOG(FATAL) << error_msg;
@@ -749,7 +746,7 @@
   }
   {
     WriterMutexLock mu(self, *Locks::jni_globals_lock_);
-    if (!globals_.Remove(kIRTFirstSegment, obj)) {
+    if (!globals_.Remove(obj)) {
       LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") "
                    << "failed to find entry";
     }
@@ -763,7 +760,7 @@
     return;
   }
   MutexLock mu(self, *Locks::jni_weak_globals_lock_);
-  if (!weak_globals_.Remove(kIRTFirstSegment, obj)) {
+  if (!weak_globals_.Remove(obj)) {
     LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") "
                  << "failed to find entry";
   }
@@ -809,7 +806,7 @@
 }
 
 void JavaVMExt::DisallowNewWeakGlobals() {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   Thread* const self = Thread::Current();
   MutexLock mu(self, *Locks::jni_weak_globals_lock_);
   // DisallowNewWeakGlobals is only called by CMS during the pause. It is required to have the
@@ -820,7 +817,7 @@
 }
 
 void JavaVMExt::AllowNewWeakGlobals() {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   Thread* self = Thread::Current();
   MutexLock mu(self, *Locks::jni_weak_globals_lock_);
   allow_accessing_weak_globals_.store(true, std::memory_order_seq_cst);
@@ -834,7 +831,7 @@
 }
 
 ObjPtr<mirror::Object> JavaVMExt::DecodeGlobal(IndirectRef ref) {
-  return globals_.SynchronizedGet(ref);
+  return globals_.Get(ref);
 }
 
 void JavaVMExt::UpdateGlobal(Thread* self, IndirectRef ref, ObjPtr<mirror::Object> result) {
@@ -851,7 +848,7 @@
   // if MayAccessWeakGlobals is false.
   DCHECK_EQ(IndirectReferenceTable::GetIndirectRefKind(ref), kWeakGlobal);
   if (LIKELY(MayAccessWeakGlobals(self))) {
-    return weak_globals_.SynchronizedGet(ref);
+    return weak_globals_.Get(ref);
   }
   MutexLock mu(self, *Locks::jni_weak_globals_lock_);
   return DecodeWeakGlobalLocked(self, ref);
@@ -869,6 +866,11 @@
   return weak_globals_.Get(ref);
 }
 
+ObjPtr<mirror::Object> JavaVMExt::DecodeWeakGlobalAsStrong(IndirectRef ref) {
+  // The target is known to be alive. Simple `Get()` with read barrier is enough.
+  return weak_globals_.Get(ref);
+}
+
 ObjPtr<mirror::Object> JavaVMExt::DecodeWeakGlobalDuringShutdown(Thread* self, IndirectRef ref) {
   DCHECK_EQ(IndirectReferenceTable::GetIndirectRefKind(ref), kWeakGlobal);
   DCHECK(Runtime::Current()->IsShuttingDown(self));
@@ -876,10 +878,10 @@
     return DecodeWeakGlobal(self, ref);
   }
   // self can be null during a runtime shutdown. ~Runtime()->~ClassLinker()->DecodeWeakGlobal().
-  if (!kUseReadBarrier) {
+  if (!gUseReadBarrier) {
     DCHECK(allow_accessing_weak_globals_.load(std::memory_order_seq_cst));
   }
-  return weak_globals_.SynchronizedGet(ref);
+  return weak_globals_.Get(ref);
 }
 
 bool JavaVMExt::IsWeakGlobalCleared(Thread* self, IndirectRef ref) {
@@ -945,7 +947,7 @@
     ObjPtr<mirror::ClassLoader> loader = soa.Decode<mirror::ClassLoader>(class_loader);
 
     ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-    if (class_linker->IsBootClassLoader(soa, loader.Ptr())) {
+    if (class_linker->IsBootClassLoader(loader)) {
       loader = nullptr;
       class_loader = nullptr;
       if (caller_class != nullptr) {
@@ -957,7 +959,7 @@
       }
     }
 
-    class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader.Ptr());
+    class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader);
     CHECK(class_loader_allocator != nullptr);
   }
   if (library != nullptr) {
@@ -1160,7 +1162,7 @@
   CHECK(m->IsNative());
   ObjPtr<mirror::Class> c = m->GetDeclaringClass();
   // If this is a static method, it could be called before the class has been initialized.
-  CHECK(c->IsInitializing() || !NeedsClinitCheckBeforeCall(m))
+  CHECK(c->IsInitializing() || !m->NeedsClinitCheckBeforeCall())
       << c->GetStatus() << " " << m->PrettyMethod();
   Thread* const self = Thread::Current();
   void* native_method = libraries_->FindNativeMethod(self, m, error_msg, can_suspend);
@@ -1172,23 +1174,6 @@
   return native_method;
 }
 
-void JavaVMExt::SweepJniWeakGlobals(IsMarkedVisitor* visitor) {
-  MutexLock mu(Thread::Current(), *Locks::jni_weak_globals_lock_);
-  Runtime* const runtime = Runtime::Current();
-  for (auto* entry : weak_globals_) {
-    // Need to skip null here to distinguish between null entries and cleared weak ref entries.
-    if (!entry->IsNull()) {
-      // Since this is called by the GC, we don't need a read barrier.
-      mirror::Object* obj = entry->Read<kWithoutReadBarrier>();
-      mirror::Object* new_obj = visitor->IsMarked(obj);
-      if (new_obj == nullptr) {
-        new_obj = runtime->GetClearedJniWeakGlobal();
-      }
-      *entry = GcRoot<mirror::Object>(new_obj);
-    }
-  }
-}
-
 void JavaVMExt::TrimGlobals() {
   WriterMutexLock mu(Thread::Current(), *Locks::jni_globals_lock_);
   globals_.Trim();
@@ -1205,12 +1190,14 @@
   if (class_loader == nullptr) {
     return nullptr;
   }
-  if (!env->IsInstanceOf(class_loader, WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
+  ScopedObjectAccess soa(env);
+  ObjPtr<mirror::Object> mirror_class_loader = soa.Decode<mirror::Object>(class_loader);
+  if (!mirror_class_loader->InstanceOf(WellKnownClasses::dalvik_system_BaseDexClassLoader.Get())) {
     return nullptr;
   }
-  return reinterpret_cast<jstring>(env->CallObjectMethod(
-      class_loader,
-      WellKnownClasses::dalvik_system_BaseDexClassLoader_getLdLibraryPath));
+  return soa.AddLocalReference<jstring>(
+      WellKnownClasses::dalvik_system_BaseDexClassLoader_getLdLibraryPath->InvokeVirtual<'L'>(
+          soa.Self(), mirror_class_loader));
 }
 
 // JNI Invocation interface.
diff --git a/runtime/jni/java_vm_ext.h b/runtime/jni/java_vm_ext.h
index 08de18b..7f4f548 100644
--- a/runtime/jni/java_vm_ext.h
+++ b/runtime/jni/java_vm_ext.h
@@ -165,7 +165,9 @@
 
   void SweepJniWeakGlobals(IsMarkedVisitor* visitor)
       REQUIRES_SHARED(Locks::mutator_lock_)
-      REQUIRES(!Locks::jni_weak_globals_lock_);
+      REQUIRES(!Locks::jni_weak_globals_lock_) {
+    weak_globals_.SweepJniWeakGlobals(visitor);
+  }
 
   ObjPtr<mirror::Object> DecodeGlobal(IndirectRef ref)
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -182,6 +184,11 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(Locks::jni_weak_globals_lock_);
 
+  // Decode weak global as strong. Use only if the target object is known to be alive.
+  ObjPtr<mirror::Object> DecodeWeakGlobalAsStrong(IndirectRef ref)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::jni_weak_globals_lock_);
+
   // Like DecodeWeakGlobal() but to be used only during a runtime shutdown where self may be
   // null.
   ObjPtr<mirror::Object> DecodeWeakGlobalDuringShutdown(Thread* self, IndirectRef ref)
@@ -218,9 +225,12 @@
   static jstring GetLibrarySearchPath(JNIEnv* env, jobject class_loader);
 
  private:
-  // The constructor should not be called directly. It may leave the object in
-  // an erroneous state, and the result needs to be checked.
-  JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options, std::string* error_msg);
+  // The constructor should not be called directly. Use `Create()` that initializes
+  // the new `JavaVMExt` object by calling `Initialize()`.
+  JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options);
+
+  // Initialize the `JavaVMExt` object.
+  bool Initialize(std::string* error_msg);
 
   // Return true if self can currently access weak globals.
   bool MayAccessWeakGlobals(Thread* self) const REQUIRES_SHARED(Locks::mutator_lock_);
@@ -248,7 +258,6 @@
   // Extra diagnostics.
   const std::string trace_;
 
-  // Not guarded by globals_lock since we sometimes use SynchronizedGet in Thread::DecodeJObject.
   IndirectReferenceTable globals_;
 
   // No lock annotation since UnloadNativeLibraries is called on libraries_ but locks the
@@ -260,10 +269,8 @@
 
   // Since weak_globals_ contain weak roots, be careful not to
   // directly access the object references in it. Use Get() with the
-  // read barrier enabled.
-  // Not guarded by weak_globals_lock since we may use SynchronizedGet in DecodeWeakGlobal.
+  // read barrier enabled or disabled based on the use case.
   IndirectReferenceTable weak_globals_;
-  // Not guarded by weak_globals_lock since we may use SynchronizedGet in DecodeWeakGlobal.
   Atomic<bool> allow_accessing_weak_globals_;
   ConditionVariable weak_globals_add_condition_ GUARDED_BY(Locks::jni_weak_globals_lock_);
 
diff --git a/runtime/jni/jni_env_ext-inl.h b/runtime/jni/jni_env_ext-inl.h
index 7609a9e..0c04192 100644
--- a/runtime/jni/jni_env_ext-inl.h
+++ b/runtime/jni/jni_env_ext-inl.h
@@ -19,6 +19,7 @@
 
 #include "jni_env_ext.h"
 
+#include "local_reference_table-inl.h"
 #include "mirror/object.h"
 
 namespace art {
@@ -49,6 +50,10 @@
   return reinterpret_cast<T>(ref);
 }
 
+inline void JNIEnvExt::UpdateLocal(IndirectRef iref, ObjPtr<mirror::Object> obj) {
+  locals_.Update(iref, obj);
+}
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_JNI_JNI_ENV_EXT_INL_H_
diff --git a/runtime/jni/jni_env_ext.cc b/runtime/jni/jni_env_ext.cc
index 4510b37..bef0fd3 100644
--- a/runtime/jni/jni_env_ext.cc
+++ b/runtime/jni/jni_env_ext.cc
@@ -33,6 +33,7 @@
 #include "nth_caller_visitor.h"
 #include "scoped_thread_state_change.h"
 #include "thread-current-inl.h"
+#include "thread-inl.h"
 #include "thread_list.h"
 
 namespace art {
@@ -44,13 +45,6 @@
 
 const JNINativeInterface* JNIEnvExt::table_override_ = nullptr;
 
-bool JNIEnvExt::CheckLocalsValid(JNIEnvExt* in) NO_THREAD_SAFETY_ANALYSIS {
-  if (in == nullptr) {
-    return false;
-  }
-  return in->locals_.IsValid();
-}
-
 jint JNIEnvExt::GetEnvHandler(JavaVMExt* vm, /*out*/void** env, jint version) {
   UNUSED(vm);
   // GetEnv always returns a JNIEnv* for the most current supported JNI version,
@@ -66,18 +60,18 @@
 }
 
 JNIEnvExt* JNIEnvExt::Create(Thread* self_in, JavaVMExt* vm_in, std::string* error_msg) {
-  std::unique_ptr<JNIEnvExt> ret(new JNIEnvExt(self_in, vm_in, error_msg));
-  if (CheckLocalsValid(ret.get())) {
-    return ret.release();
+  std::unique_ptr<JNIEnvExt> ret(new JNIEnvExt(self_in, vm_in));
+  if (!ret->Initialize(error_msg)) {
+    return nullptr;
   }
-  return nullptr;
+  return ret.release();
 }
 
-JNIEnvExt::JNIEnvExt(Thread* self_in, JavaVMExt* vm_in, std::string* error_msg)
+JNIEnvExt::JNIEnvExt(Thread* self_in, JavaVMExt* vm_in)
     : self_(self_in),
       vm_(vm_in),
-      local_ref_cookie_(kIRTFirstSegment),
-      locals_(1, kLocal, IndirectReferenceTable::ResizableCapacity::kYes, error_msg),
+      local_ref_cookie_(jni::kLRTFirstSegment),
+      locals_(vm_in->IsCheckJniEnabled()),
       monitors_("monitors", kMonitorsInitial, kMonitorsMax),
       critical_(0),
       check_jni_(false),
@@ -88,6 +82,10 @@
   unchecked_functions_ = GetJniNativeInterface();
 }
 
+bool JNIEnvExt::Initialize(std::string* error_msg) {
+  return locals_.Initialize(/*max_count=*/ 1u, error_msg);
+}
+
 void JNIEnvExt::SetFunctionsToRuntimeShutdownFunctions() {
   functions = GetRuntimeShutdownNativeInterface();
 }
@@ -117,6 +115,7 @@
 
 void JNIEnvExt::SetCheckJniEnabled(bool enabled) {
   check_jni_ = enabled;
+  locals_.SetCheckJniEnabled(enabled);
   MutexLock mu(Thread::Current(), *Locks::jni_function_table_lock_);
   functions = GetFunctionTable(enabled);
   // Check whether this is a no-op because of override.
@@ -157,7 +156,7 @@
                          4 +                         // local_ref_cookie.
                          (pointer_size - 4);         // Padding.
   size_t irt_segment_state_offset =
-      IndirectReferenceTable::SegmentStateOffset(pointer_size).Int32Value();
+      jni::LocalReferenceTable::SegmentStateOffset(pointer_size).Int32Value();
   return MemberOffset(locals_offset + irt_segment_state_offset);
 }
 
diff --git a/runtime/jni/jni_env_ext.h b/runtime/jni/jni_env_ext.h
index bdde5f8..1f57658 100644
--- a/runtime/jni/jni_env_ext.h
+++ b/runtime/jni/jni_env_ext.h
@@ -21,7 +21,7 @@
 
 #include "base/locks.h"
 #include "base/macros.h"
-#include "indirect_reference_table.h"
+#include "local_reference_table.h"
 #include "obj_ptr.h"
 #include "reference_table.h"
 
@@ -63,9 +63,8 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::alloc_tracker_lock_);
 
-  void UpdateLocal(IndirectRef iref, ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_) {
-    locals_.Update(iref, obj);
-  }
+  void UpdateLocal(IndirectRef iref, ObjPtr<mirror::Object> obj)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   jobject NewLocalRef(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_);
   void DeleteLocalRef(jobject obj) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -80,13 +79,13 @@
     return locals_.Capacity();
   }
 
-  IRTSegmentState GetLocalRefCookie() const { return local_ref_cookie_; }
-  void SetLocalRefCookie(IRTSegmentState new_cookie) { local_ref_cookie_ = new_cookie; }
+  jni::LRTSegmentState GetLocalRefCookie() const { return local_ref_cookie_; }
+  void SetLocalRefCookie(jni::LRTSegmentState new_cookie) { local_ref_cookie_ = new_cookie; }
 
-  IRTSegmentState GetLocalsSegmentState() const REQUIRES_SHARED(Locks::mutator_lock_) {
+  jni::LRTSegmentState GetLocalsSegmentState() const REQUIRES_SHARED(Locks::mutator_lock_) {
     return locals_.GetSegmentState();
   }
-  void SetLocalSegmentState(IRTSegmentState new_state) REQUIRES_SHARED(Locks::mutator_lock_) {
+  void SetLocalSegmentState(jni::LRTSegmentState new_state) REQUIRES_SHARED(Locks::mutator_lock_) {
     locals_.SetSegmentState(new_state);
   }
 
@@ -151,20 +150,18 @@
       REQUIRES(!Locks::thread_list_lock_, !Locks::jni_function_table_lock_);
 
  private:
-  // Checking "locals" requires the mutator lock, but at creation time we're
-  // really only interested in validity, which isn't changing. To avoid grabbing
-  // the mutator lock, factored out and tagged with NO_THREAD_SAFETY_ANALYSIS.
-  static bool CheckLocalsValid(JNIEnvExt* in) NO_THREAD_SAFETY_ANALYSIS;
-
   // Override of function tables. This applies to both default as well as instrumented (CheckJNI)
   // function tables.
   static const JNINativeInterface* table_override_ GUARDED_BY(Locks::jni_function_table_lock_);
 
-  // The constructor should not be called directly. It may leave the object in an erroneous state,
-  // and the result needs to be checked.
-  JNIEnvExt(Thread* self, JavaVMExt* vm, std::string* error_msg)
+  // The constructor should not be called directly. Use `Create()` that initializes
+  // the new `JNIEnvExt` object by calling `Initialize()`.
+  JNIEnvExt(Thread* self, JavaVMExt* vm)
       REQUIRES(!Locks::jni_function_table_lock_);
 
+  // Initialize the `JNIEnvExt` object.
+  bool Initialize(std::string* error_msg);
+
   // Link to Thread::Current().
   Thread* const self_;
 
@@ -172,15 +169,15 @@
   JavaVMExt* const vm_;
 
   // Cookie used when using the local indirect reference table.
-  IRTSegmentState local_ref_cookie_;
+  jni::LRTSegmentState local_ref_cookie_;
 
   // JNI local references.
-  IndirectReferenceTable locals_ GUARDED_BY(Locks::mutator_lock_);
+  jni::LocalReferenceTable locals_;
 
   // Stack of cookies corresponding to PushLocalFrame/PopLocalFrame calls.
   // TODO: to avoid leaks (and bugs), we need to clear this vector on entry (or return)
   // to a native method.
-  std::vector<IRTSegmentState> stacked_local_ref_cookies_;
+  std::vector<jni::LRTSegmentState> stacked_local_ref_cookies_;
 
   // Entered JNI monitors, for bulk exit on thread detach.
   ReferenceTable monitors_;
@@ -211,6 +208,7 @@
   friend class Thread;
   friend IndirectReferenceTable* GetIndirectReferenceTable(ScopedObjectAccess& soa,
                                                            IndirectRefKind kind);
+  friend jni::LocalReferenceTable* GetLocalReferenceTable(ScopedObjectAccess& soa);
   friend void ThreadResetFunctionTable(Thread* thread, void* arg);
   ART_FRIEND_TEST(JniInternalTest, JNIEnvExtOffsets);
 };
@@ -232,7 +230,7 @@
 
  private:
   JNIEnvExt* const env_;
-  const IRTSegmentState saved_local_ref_cookie_;
+  const jni::LRTSegmentState saved_local_ref_cookie_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedJniEnvLocalRefState);
 };
diff --git a/runtime/jni/jni_id_manager.cc b/runtime/jni/jni_id_manager.cc
index 402259b..a668fe5 100644
--- a/runtime/jni/jni_id_manager.cc
+++ b/runtime/jni/jni_id_manager.cc
@@ -64,6 +64,17 @@
   return (index << 1) + 1;
 }
 
+static bool CanUseIdArrays(ArtMethod* t) {
+  // We cannot use ID arrays from the ClassExt object for obsolete and default conflict methods. The
+  // ID arrays hold an ID corresponding to the methods in the methods_list. Obsolete methods aren't
+  // in the method list. For default conflicting methods it is difficult to find the class that
+  // contains the copied method, so we omit using ID arrays. For Default conflicting methods we
+  // cannot use the canonical method because canonicalizing would return a method from one of the
+  // interface classes. If we use that method ID and invoke it via the CallNonVirtual JNI interface,
+  // it wouldn't throw the expected ICCE.
+  return !(t->IsObsolete() || t->IsDefaultConflicting());
+}
+
 template <typename ArtType>
 ObjPtr<mirror::PointerArray> GetIds(ObjPtr<mirror::Class> k, ArtType* t)
     REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -71,7 +82,7 @@
   if constexpr (std::is_same_v<ArtType, ArtField>) {
     ret = t->IsStatic() ? k->GetStaticFieldIds() : k->GetInstanceFieldIds();
   } else {
-    ret = t->IsObsolete() ? nullptr : k->GetMethodIds();
+    ret = CanUseIdArrays(t) ? k->GetMethodIds() : nullptr;
   }
   DCHECK(ret.IsNull() || ret->IsArrayInstance()) << "Should have bailed out early!";
   if (kIsDebugBuild && !ret.IsNull()) {
@@ -138,9 +149,10 @@
 
 template <>
 bool EnsureIdsArray(Thread* self, ObjPtr<mirror::Class> k, ArtMethod* method) {
-  if (method->IsObsolete()) {
+  if (!CanUseIdArrays(method)) {
     if (kTraceIds) {
-      LOG(INFO) << "jmethodID for Obsolete method " << method->PrettyMethod() << " requested!";
+      LOG(INFO) << "jmethodID for Obsolete / Default conflicting method " << method->PrettyMethod()
+                << " requested!";
     }
     // No ids array for obsolete methods. Just do a linear scan.
     return false;
@@ -169,7 +181,7 @@
 }
 template <>
 size_t GetIdOffset(ObjPtr<mirror::Class> k, ArtMethod* method, PointerSize pointer_size) {
-  return method->IsObsolete() ? -1 : k->GetMethodIdOffset(method, pointer_size);
+  return CanUseIdArrays(method) ? k->GetMethodIdOffset(method, pointer_size) : -1;
 }
 
 // Calls the relevant PrettyMethod/PrettyField on the input.
@@ -192,16 +204,16 @@
   return f->PrettyField();
 }
 
-// Checks if the field or method is obsolete.
+// Checks if the field or method can use the ID array from class extension.
 template <typename ArtType>
-bool IsObsolete(ReflectiveHandle<ArtType> t) REQUIRES_SHARED(Locks::mutator_lock_);
+bool CanUseIdArrays(ReflectiveHandle<ArtType> t) REQUIRES_SHARED(Locks::mutator_lock_);
 template <>
-bool IsObsolete(ReflectiveHandle<ArtField> t ATTRIBUTE_UNUSED) {
-  return false;
+bool CanUseIdArrays(ReflectiveHandle<ArtField> t ATTRIBUTE_UNUSED) {
+  return true;
 }
 template <>
-bool IsObsolete(ReflectiveHandle<ArtMethod> t) {
-  return t->IsObsolete();
+bool CanUseIdArrays(ReflectiveHandle<ArtMethod> t) {
+  return CanUseIdArrays(t.Get());
 }
 
 // Get the canonical (non-copied) version of the field or method. Only relevant for methods.
@@ -258,10 +270,14 @@
 
 template <>
 size_t JniIdManager::GetLinearSearchStartId<ArtMethod>(ReflectiveHandle<ArtMethod> m) {
-  if (m->IsObsolete()) {
-    return 1;
-  } else {
+  if (CanUseIdArrays(m)) {
+    // If we are searching because we couldn't allocate because of defer allocate scope, then we
+    // should only look from deferred_allocation_method_id_start_. Once we exit the deferred scope
+    // all these method ids will be updated to the id arrays in the respective ClassExt objects.
     return deferred_allocation_method_id_start_;
+  } else {
+    // If we cannot use ID arrays, then the method can be anywhere in the list.
+    return 1;
   }
 }
 
@@ -278,14 +294,26 @@
   Thread* self = Thread::Current();
   ScopedExceptionStorage ses(self);
   DCHECK(!t->GetDeclaringClass().IsNull()) << "Null declaring class " << PrettyGeneric(t);
-  size_t off = GetIdOffset(t->GetDeclaringClass(), Canonicalize(t), kRuntimePointerSize);
-  // Here is the earliest point we can suspend.
-  bool allocation_failure = EnsureIdsArray(self, t->GetDeclaringClass(), t.Get());
+  size_t off = -1;
+  bool allocation_failure = false;
+  // When we cannot use ID arrays, we just fallback to looking through the list to obtain the ID.
+  // These are rare cases so shouldn't be a problem for performance. See CanUseIdArrays for more
+  // information.
+  if (CanUseIdArrays(t)) {
+    off = GetIdOffset(t->GetDeclaringClass(), Canonicalize(t), kRuntimePointerSize);
+    // Here is the earliest point we can suspend.
+    allocation_failure = EnsureIdsArray(self, t->GetDeclaringClass(), t.Get());
+  }
   if (allocation_failure) {
     self->AssertPendingOOMException();
     ses.SuppressOldException("OOM exception while trying to allocate JNI ids.");
     return 0u;
   } else if (ShouldReturnPointer(t->GetDeclaringClass(), t.Get())) {
+    // TODO(mythria): Check why we return a pointer here instead of falling back
+    // to the slow path of finding the ID by looping through the ID -> method
+    // map. This seem incorrect. For example, if we are in ScopedEnableSuspendAllJniIdQueries
+    // scope, we don't allocate ID arrays. We would then incorrectly return a
+    // pointer here.
     return reinterpret_cast<uintptr_t>(t.Get());
   }
   ObjPtr<mirror::Class> klass = t->GetDeclaringClass();
@@ -321,7 +349,7 @@
     }
   } else {
     // We cannot allocate anything here or don't have an ids array (we might be an obsolete method).
-    DCHECK(IsObsolete(t) || deferred_allocation_refcount_ > 0u)
+    DCHECK(!CanUseIdArrays(t) || deferred_allocation_refcount_ > 0u)
         << "deferred_allocation_refcount_: " << deferred_allocation_refcount_
         << " t: " << PrettyGeneric(t);
     // Check to see if we raced and lost to another thread.
@@ -354,7 +382,7 @@
   vec.resize(std::max(vec.size(), cur_index + 1), nullptr);
   vec[cur_index] = t.Get();
   if (ids.IsNull()) {
-    if (kIsDebugBuild && !IsObsolete(t)) {
+    if (kIsDebugBuild && CanUseIdArrays(t)) {
       CHECK_NE(deferred_allocation_refcount_, 0u)
           << "Failed to allocate ids array despite not being forbidden from doing so!";
       Locks::mutator_lock_->AssertExclusiveHeld(self);
diff --git a/runtime/jni/jni_internal.cc b/runtime/jni/jni_internal.cc
index e3153fd..ad2efc5 100644
--- a/runtime/jni/jni_internal.cc
+++ b/runtime/jni/jni_internal.cc
@@ -22,7 +22,7 @@
 #include <utility>
 
 #include "art_field-inl.h"
-#include "art_method-inl.h"
+#include "art_method-alloc-inl.h"
 #include "base/allocator.h"
 #include "base/atomic.h"
 #include "base/casts.h"
@@ -37,6 +37,7 @@
 #include "dex/dex_file-inl.h"
 #include "dex/utf-inl.h"
 #include "fault_handler.h"
+#include "handle_scope.h"
 #include "hidden_api.h"
 #include "gc/accounting/card_table-inl.h"
 #include "gc_root.h"
@@ -63,7 +64,7 @@
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -392,8 +393,7 @@
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ArtMethod* method = soa.Self()->GetCurrentMethod(nullptr);
   // If we are running Runtime.nativeLoad, use the overriding ClassLoader it set.
-  if (method ==
-      jni::DecodeArtMethod<kEnableIndexIds>(WellKnownClasses::java_lang_Runtime_nativeLoad)) {
+  if (method == WellKnownClasses::java_lang_Runtime_nativeLoad) {
     return soa.Decode<mirror::ClassLoader>(soa.Self()->GetClassLoaderOverride());
   }
   // If we have a method, use its ClassLoader for context.
@@ -954,16 +954,15 @@
           WellKnownClasses::StringInitToStringFactory(jni::DecodeArtMethod(mid)));
       return CallStaticObjectMethodV(env, WellKnownClasses::java_lang_StringFactory, sf_mid, args);
     }
-    ObjPtr<mirror::Object> result = c->AllocObject(soa.Self());
+    ScopedLocalRef<jobject> result(env, soa.AddLocalReference<jobject>(c->AllocObject(soa.Self())));
     if (result == nullptr) {
       return nullptr;
     }
-    jobject local_result = soa.AddLocalReference<jobject>(result);
-    CallNonvirtualVoidMethodV(env, local_result, java_class, mid, args);
+    CallNonvirtualVoidMethodV(env, result.get(), java_class, mid, args);
     if (soa.Self()->IsExceptionPending()) {
       return nullptr;
     }
-    return local_result;
+    return result.release();
   }
 
   static jobject NewObjectA(JNIEnv* env, jclass java_class, jmethodID mid, const jvalue* args) {
@@ -981,16 +980,15 @@
           WellKnownClasses::StringInitToStringFactory(jni::DecodeArtMethod(mid)));
       return CallStaticObjectMethodA(env, WellKnownClasses::java_lang_StringFactory, sf_mid, args);
     }
-    ObjPtr<mirror::Object> result = c->AllocObject(soa.Self());
+    ScopedLocalRef<jobject> result(env, soa.AddLocalReference<jobject>(c->AllocObject(soa.Self())));
     if (result == nullptr) {
       return nullptr;
     }
-    jobject local_result = soa.AddLocalReference<jobjectArray>(result);
-    CallNonvirtualVoidMethodA(env, local_result, java_class, mid, args);
+    CallNonvirtualVoidMethodA(env, result.get(), java_class, mid, args);
     if (soa.Self()->IsExceptionPending()) {
       return nullptr;
     }
-    return local_result;
+    return result.release();
   }
 
   static jmethodID GetMethodID(JNIEnv* env, jclass java_class, const char* name, const char* sig) {
@@ -1269,8 +1267,7 @@
     CHECK_NON_NULL_ARGUMENT(mid);
     ScopedObjectAccess soa(env);
     JValue result(InvokeWithVarArgs(soa, obj, mid, ap));
-    jobject local_result = soa.AddLocalReference<jobject>(result.GetL());
-    return local_result;
+    return soa.AddLocalReference<jobject>(result.GetL());
   }
 
   static jobject CallNonvirtualObjectMethodV(JNIEnv* env, jobject obj, jclass, jmethodID mid,
@@ -1756,8 +1753,7 @@
     CHECK_NON_NULL_ARGUMENT(mid);
     ScopedObjectAccess soa(env);
     JValue result(InvokeWithVarArgs(soa, nullptr, mid, ap));
-    jobject local_result = soa.AddLocalReference<jobject>(result.GetL());
-    return local_result;
+    return soa.AddLocalReference<jobject>(result.GetL());
   }
 
   static jobject CallStaticObjectMethodV(JNIEnv* env, jclass, jmethodID mid, va_list args) {
@@ -1950,6 +1946,7 @@
     return InvokeWithJValues(soa, nullptr, mid, args).GetD();
   }
 
+  NO_STACK_PROTECTOR
   static void CallStaticVoidMethod(JNIEnv* env, jclass, jmethodID mid, ...) {
     va_list ap;
     va_start(ap, mid);
@@ -1959,6 +1956,7 @@
     InvokeWithVarArgs(soa, nullptr, mid, ap);
   }
 
+  NO_STACK_PROTECTOR
   static void CallStaticVoidMethodV(JNIEnv* env, jclass, jmethodID mid, va_list args) {
     CHECK_NON_NULL_ARGUMENT_RETURN_VOID(mid);
     ScopedObjectAccess soa(env);
@@ -2176,14 +2174,17 @@
       if (heap->IsMovableObject(s)) {
         StackHandleScope<1> hs(soa.Self());
         HandleWrapperObjPtr<mirror::String> h(hs.NewHandleWrapper(&s));
-        if (!kUseReadBarrier) {
+        if (!gUseReadBarrier && !gUseUserfaultfd) {
           heap->IncrementDisableMovingGC(soa.Self());
         } else {
-          // For the CC collector, we only need to wait for the thread flip rather
+          // For the CC and CMC collector, we only need to wait for the thread flip rather
           // than the whole GC to occur thanks to the to-space invariant.
           heap->IncrementDisableThreadFlip(soa.Self());
         }
       }
+      // Ensure that the string doesn't cause userfaults in case passed on to
+      // the kernel.
+      heap->EnsureObjectUserfaulted(s);
       if (is_copy != nullptr) {
         *is_copy = JNI_FALSE;
       }
@@ -2199,7 +2200,7 @@
     gc::Heap* heap = Runtime::Current()->GetHeap();
     ObjPtr<mirror::String> s = soa.Decode<mirror::String>(java_string);
     if (!s->IsCompressed() && heap->IsMovableObject(s)) {
-      if (!kUseReadBarrier) {
+      if (!gUseReadBarrier && !gUseUserfaultfd) {
         heap->DecrementDisableMovingGC(soa.Self());
       } else {
         heap->DecrementDisableThreadFlip(soa.Self());
@@ -2366,16 +2367,18 @@
     }
     gc::Heap* heap = Runtime::Current()->GetHeap();
     if (heap->IsMovableObject(array)) {
-      if (!kUseReadBarrier) {
+      if (!gUseReadBarrier && !gUseUserfaultfd) {
         heap->IncrementDisableMovingGC(soa.Self());
       } else {
-        // For the CC collector, we only need to wait for the thread flip rather than the whole GC
-        // to occur thanks to the to-space invariant.
+        // For the CC and CMC collector, we only need to wait for the thread flip rather
+        // than the whole GC to occur thanks to the to-space invariant.
         heap->IncrementDisableThreadFlip(soa.Self());
       }
       // Re-decode in case the object moved since IncrementDisableGC waits for GC to complete.
       array = soa.Decode<mirror::Array>(java_array);
     }
+    // Ensure that the array doesn't cause userfaults in case passed on to the kernel.
+    heap->EnsureObjectUserfaulted(array);
     if (is_copy != nullptr) {
       *is_copy = JNI_FALSE;
     }
@@ -2772,10 +2775,10 @@
     jlong address_arg = reinterpret_cast<jlong>(address);
     jint capacity_arg = static_cast<jint>(capacity);
 
-    jobject result = env->NewObject(WellKnownClasses::java_nio_DirectByteBuffer,
-                                    WellKnownClasses::java_nio_DirectByteBuffer_init,
-                                    address_arg, capacity_arg);
-    return static_cast<JNIEnvExt*>(env)->self_->IsExceptionPending() ? nullptr : result;
+    ScopedObjectAccess soa(env);
+    return soa.AddLocalReference<jobject>(
+        WellKnownClasses::java_nio_DirectByteBuffer_init->NewObject<'J', 'I'>(
+            soa.Self(), address_arg, capacity_arg));
   }
 
   static void* GetDirectBufferAddress(JNIEnv* env, jobject java_buffer) {
@@ -2784,14 +2787,16 @@
       return nullptr;
     }
 
+    ScopedObjectAccess soa(env);
+    ObjPtr<mirror::Object> buffer = soa.Decode<mirror::Object>(java_buffer);
+
     // Return null if |java_buffer| is not a java.nio.Buffer instance.
-    if (!IsInstanceOf(env, java_buffer, WellKnownClasses::java_nio_Buffer)) {
+    if (!buffer->InstanceOf(WellKnownClasses::java_nio_Buffer.Get())) {
       return nullptr;
     }
 
     // Buffer.address is non-null when the |java_buffer| is direct.
-    return reinterpret_cast<void*>(env->GetLongField(
-        java_buffer, WellKnownClasses::java_nio_Buffer_address));
+    return reinterpret_cast<void*>(WellKnownClasses::java_nio_Buffer_address->GetLong(buffer));
   }
 
   static jlong GetDirectBufferCapacity(JNIEnv* env, jobject java_buffer) {
@@ -2799,7 +2804,10 @@
       return -1;
     }
 
-    if (!IsInstanceOf(env, java_buffer, WellKnownClasses::java_nio_Buffer)) {
+    ScopedObjectAccess soa(env);
+    StackHandleScope<1u> hs(soa.Self());
+    Handle<mirror::Object> buffer = hs.NewHandle(soa.Decode<mirror::Object>(java_buffer));
+    if (!buffer->InstanceOf(WellKnownClasses::java_nio_Buffer.Get())) {
       return -1;
     }
 
@@ -2808,16 +2816,18 @@
     // We therefore call Buffer.isDirect(). One path that creates such a buffer is
     // FileChannel.map() if the file size is zero.
     //
-    // NB GetDirectBufferAddress() does not need to call Buffer.isDirect() since it is only
+    // NB GetDirectBufferAddress() does not need to call `Buffer.isDirect()` since it is only
     // able return a valid address if the Buffer address field is not-null.
-    jboolean direct = env->CallBooleanMethod(java_buffer,
-                                             WellKnownClasses::java_nio_Buffer_isDirect);
-    if (!direct) {
+    //
+    // Note: We can hit a `StackOverflowError` during the invocation but `Buffer.isDirect()`
+    // implementations should not otherwise throw any exceptions.
+    bool direct = WellKnownClasses::java_nio_Buffer_isDirect->InvokeVirtual<'Z'>(
+        soa.Self(), buffer.Get());
+    if (UNLIKELY(soa.Self()->IsExceptionPending()) || !direct) {
       return -1;
     }
 
-    return static_cast<jlong>(env->GetIntField(
-        java_buffer, WellKnownClasses::java_nio_Buffer_capacity));
+    return static_cast<jlong>(WellKnownClasses::java_nio_Buffer_capacity->GetInt(buffer.Get()));
   }
 
   static jobjectRefType GetObjectRefType(JNIEnv* env ATTRIBUTE_UNUSED, jobject java_object) {
@@ -2835,7 +2845,7 @@
       return JNIGlobalRefType;
     case kWeakGlobal:
       return JNIWeakGlobalRefType;
-    case kJniTransitionOrInvalid:
+    case kJniTransition:
       // Assume value is in a JNI transition frame.
       return JNILocalRefType;
     }
@@ -2967,7 +2977,7 @@
         delete[] reinterpret_cast<uint64_t*>(elements);
       } else if (heap->IsMovableObject(array)) {
         // Non copy to a movable object must means that we had disabled the moving GC.
-        if (!kUseReadBarrier) {
+        if (!gUseReadBarrier && !gUseUserfaultfd) {
           heap->DecrementDisableMovingGC(soa.Self());
         } else {
           heap->DecrementDisableThreadFlip(soa.Self());
diff --git a/runtime/jni/jni_internal.h b/runtime/jni/jni_internal.h
index 1616ee5..cfe8208 100644
--- a/runtime/jni/jni_internal.h
+++ b/runtime/jni/jni_internal.h
@@ -115,8 +115,12 @@
     REQUIRES_SHARED(Locks::mutator_lock_) {
   if (kEnableIndexIds && Runtime::Current()->GetJniIdType() != JniIdType::kPointer) {
     return Runtime::Current()->GetJniIdManager()->EncodeMethodId(art_method);
-  } else {
+  } else if (art_method == nullptr ||
+             !art_method->IsCopied() ||
+             art_method->IsDefaultConflicting()) {
     return reinterpret_cast<jmethodID>(art_method.Get());
+  } else {
+    return reinterpret_cast<jmethodID>(art_method->GetCanonicalMethod());
   }
 }
 
@@ -126,8 +130,12 @@
     REQUIRES_SHARED(Locks::mutator_lock_) {
   if (kEnableIndexIds && Runtime::Current()->GetJniIdType() != JniIdType::kPointer) {
     return Runtime::Current()->GetJniIdManager()->EncodeMethodId(art_method);
-  } else {
+  } else if (art_method == nullptr ||
+             !art_method->IsCopied() ||
+             art_method->IsDefaultConflicting()) {
     return reinterpret_cast<jmethodID>(art_method);
+  } else {
+    return reinterpret_cast<jmethodID>(art_method->GetCanonicalMethod());
   }
 }
 
diff --git a/runtime/jni/jni_internal_test.cc b/runtime/jni/jni_internal_test.cc
index eca5aec..3d7f7c4 100644
--- a/runtime/jni/jni_internal_test.cc
+++ b/runtime/jni/jni_internal_test.cc
@@ -21,7 +21,7 @@
 #include "art_method-inl.h"
 #include "base/mem_map.h"
 #include "common_runtime_test.h"
-#include "indirect_reference_table.h"
+#include "local_reference_table.h"
 #include "java_vm_ext.h"
 #include "jni_env_ext.h"
 #include "mirror/string-inl.h"
@@ -2580,25 +2580,23 @@
   // by modifying memory.
   // The parameters don't really matter here.
   std::string error_msg;
-  IndirectReferenceTable irt(5,
-                             IndirectRefKind::kGlobal,
-                             IndirectReferenceTable::ResizableCapacity::kNo,
-                             &error_msg);
-  ASSERT_TRUE(irt.IsValid()) << error_msg;
-  IRTSegmentState old_state = irt.GetSegmentState();
+  jni::LocalReferenceTable lrt(/*check_jni=*/ true);
+  bool success = lrt.Initialize(/*max_count=*/ 5, &error_msg);
+  ASSERT_TRUE(success) << error_msg;
+  jni::LRTSegmentState old_state = lrt.GetSegmentState();
 
   // Write some new state directly. We invert parts of old_state to ensure a new value.
-  IRTSegmentState new_state;
+  jni::LRTSegmentState new_state;
   new_state.top_index = old_state.top_index ^ 0x07705005;
   ASSERT_NE(old_state.top_index, new_state.top_index);
 
-  uint8_t* base = reinterpret_cast<uint8_t*>(&irt);
+  uint8_t* base = reinterpret_cast<uint8_t*>(&lrt);
   int32_t segment_state_offset =
-      IndirectReferenceTable::SegmentStateOffset(sizeof(void*)).Int32Value();
-  *reinterpret_cast<IRTSegmentState*>(base + segment_state_offset) = new_state;
+      jni::LocalReferenceTable::SegmentStateOffset(sizeof(void*)).Int32Value();
+  *reinterpret_cast<jni::LRTSegmentState*>(base + segment_state_offset) = new_state;
 
   // Read and compare.
-  EXPECT_EQ(new_state.top_index, irt.GetSegmentState().top_index);
+  EXPECT_EQ(new_state.top_index, lrt.GetSegmentState().top_index);
 }
 
 // Test the offset computation of JNIEnvExt offsets. b/26071368.
@@ -2612,7 +2610,7 @@
   // hope it to be.
   uint32_t segment_state_now =
       OFFSETOF_MEMBER(JNIEnvExt, locals_) +
-      IndirectReferenceTable::SegmentStateOffset(sizeof(void*)).Uint32Value();
+      jni::LocalReferenceTable::SegmentStateOffset(sizeof(void*)).Uint32Value();
   uint32_t segment_state_computed = JNIEnvExt::SegmentStateOffset(sizeof(void*)).Uint32Value();
   EXPECT_EQ(segment_state_now, segment_state_computed);
 }
diff --git a/runtime/jni/local_reference_table-inl.h b/runtime/jni/local_reference_table-inl.h
new file mode 100644
index 0000000..8b46049
--- /dev/null
+++ b/runtime/jni/local_reference_table-inl.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 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_RUNTIME_JNI_LOCAL_REFERENCE_TABLE_INL_H_
+#define ART_RUNTIME_JNI_LOCAL_REFERENCE_TABLE_INL_H_
+
+#include "local_reference_table.h"
+
+#include "android-base/stringprintf.h"
+
+#include "base/casts.h"
+#include "gc_root-inl.h"
+#include "obj_ptr-inl.h"
+#include "mirror/object_reference.h"
+#include "verify_object.h"
+
+namespace art {
+namespace jni {
+
+inline void LrtEntry::SetReference(ObjPtr<mirror::Object> ref) {
+  root_ = GcRoot<mirror::Object>(
+      mirror::CompressedReference<mirror::Object>::FromMirrorPtr(ref.Ptr()));
+  DCHECK(!IsFree());
+  DCHECK(!IsSerialNumber());
+}
+
+inline ObjPtr<mirror::Object> LrtEntry::GetReference() {
+  DCHECK(!IsFree());
+  DCHECK(!IsSerialNumber());
+  DCHECK(!IsNull());
+  // Local references do not need read barriers. They are marked during the thread root flip.
+  return root_.Read<kWithoutReadBarrier>();
+}
+
+inline void LrtEntry::SetNextFree(uint32_t next_free) {
+  SetVRegValue(NextFreeField::Update(next_free, 1u << kFlagFree));
+  DCHECK(IsFree());
+  DCHECK(!IsSerialNumber());
+}
+
+inline void LrtEntry::SetSerialNumber(uint32_t serial_number) {
+  SetVRegValue(SerialNumberField::Update(serial_number, 1u << kFlagSerialNumber));
+  DCHECK(!IsFree());
+  DCHECK(IsSerialNumber());
+}
+
+inline void LrtEntry::SetVRegValue(uint32_t value) {
+  root_ = GcRoot<mirror::Object>(
+      mirror::CompressedReference<mirror::Object>::FromVRegValue(value));
+}
+
+inline uint32_t LocalReferenceTable::GetReferenceEntryIndex(IndirectRef iref) const {
+  DCHECK_EQ(IndirectReferenceTable::GetIndirectRefKind(iref), kLocal);
+  LrtEntry* entry = ToLrtEntry(iref);
+
+  if (LIKELY(small_table_ != nullptr)) {
+    DCHECK(tables_.empty());
+    if (!std::less<const LrtEntry*>()(entry, small_table_) &&
+        std::less<const LrtEntry*>()(entry, small_table_ + kSmallLrtEntries)) {
+      return dchecked_integral_cast<uint32_t>(entry - small_table_);
+    }
+  } else {
+    for (size_t i = 0, size = tables_.size(); i != size; ++i) {
+      LrtEntry* table = tables_[i];
+      size_t table_size = GetTableSize(i);
+      if (!std::less<const LrtEntry*>()(entry, table) &&
+          std::less<const LrtEntry*>()(entry, table + table_size)) {
+        return dchecked_integral_cast<size_t>(i != 0u ? table_size : 0u) +
+               dchecked_integral_cast<size_t>(entry - table);
+      }
+    }
+  }
+  return std::numeric_limits<uint32_t>::max();
+}
+
+inline bool LocalReferenceTable::IsValidReference(IndirectRef iref,
+                                                  /*out*/std::string* error_msg) const {
+  uint32_t entry_index = GetReferenceEntryIndex(iref);
+  if (UNLIKELY(entry_index == std::numeric_limits<uint32_t>::max())) {
+    *error_msg = android::base::StringPrintf("reference outside the table: %p", iref);
+    return false;
+  }
+  if (UNLIKELY(entry_index >= segment_state_.top_index)) {
+    *error_msg = android::base::StringPrintf("popped reference at index %u in a table of size %u",
+                                             entry_index,
+                                             segment_state_.top_index);
+    return false;
+  }
+  LrtEntry* entry = ToLrtEntry(iref);
+  LrtEntry* serial_number_entry = GetCheckJniSerialNumberEntry(entry);
+  if (serial_number_entry->IsSerialNumber()) {
+    // This reference was created with CheckJNI enabled.
+    uint32_t expected_serial_number = serial_number_entry->GetSerialNumber();
+    uint32_t serial_number = entry - serial_number_entry;
+    DCHECK_LT(serial_number, kCheckJniEntriesPerReference);
+    if (serial_number != expected_serial_number || serial_number == 0u) {
+      *error_msg = android::base::StringPrintf(
+          "reference at index %u with bad serial number %u v. %u (valid 1 - %u)",
+          entry_index,
+          serial_number,
+          expected_serial_number,
+          dchecked_integral_cast<uint32_t>(kCheckJniEntriesPerReference - 1u));
+      return false;
+    }
+  }
+  if (UNLIKELY(entry->IsFree())) {
+    *error_msg = android::base::StringPrintf("deleted reference at index %u", entry_index);
+    return false;
+  }
+  if (UNLIKELY(entry->IsNull())) {
+    // This should never really happen and may indicate memory coruption.
+    *error_msg = android::base::StringPrintf("null reference at index %u", entry_index);
+    return false;
+  }
+  return true;
+}
+
+inline void LocalReferenceTable::DCheckValidReference(IndirectRef iref) const {
+  // If CheckJNI is performing the checks, we should not reach this point with an invalid
+  // reference with the exception of gtests that intercept the CheckJNI abort and proceed
+  // to decode the reference anyway and we do not want to abort again in this case.
+  if (kIsDebugBuild && !IsCheckJniEnabled()) {
+    std::string error_msg;
+    CHECK(IsValidReference(iref, &error_msg)) << error_msg;
+  }
+}
+
+inline ObjPtr<mirror::Object> LocalReferenceTable::Get(IndirectRef iref) const {
+  DCheckValidReference(iref);
+  return ToLrtEntry(iref)->GetReference();
+}
+
+inline void LocalReferenceTable::Update(IndirectRef iref, ObjPtr<mirror::Object> obj) {
+  DCheckValidReference(iref);
+  ToLrtEntry(iref)->SetReference(obj);
+}
+
+}  // namespace jni
+}  // namespace art
+
+#endif  // ART_RUNTIME_JNI_LOCAL_REFERENCE_TABLE_INL_H_
diff --git a/runtime/jni/local_reference_table.cc b/runtime/jni/local_reference_table.cc
new file mode 100644
index 0000000..59c4e31
--- /dev/null
+++ b/runtime/jni/local_reference_table.cc
@@ -0,0 +1,750 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "local_reference_table-inl.h"
+
+#include "base/bit_utils.h"
+#include "base/casts.h"
+#include "base/globals.h"
+#include "base/mutator_locked_dumpable.h"
+#include "base/systrace.h"
+#include "base/utils.h"
+#include "indirect_reference_table.h"
+#include "jni/java_vm_ext.h"
+#include "jni/jni_internal.h"
+#include "mirror/object-inl.h"
+#include "nth_caller_visitor.h"
+#include "reference_table.h"
+#include "runtime-inl.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread.h"
+
+#include <cstdlib>
+
+namespace art {
+namespace jni {
+
+static constexpr bool kDumpStackOnNonLocalReference = false;
+static constexpr bool kDebugLRT = false;
+
+// Mmap an "indirect ref table region. Table_bytes is a multiple of a page size.
+static inline MemMap NewLRTMap(size_t table_bytes, std::string* error_msg) {
+  return MemMap::MapAnonymous("local ref table",
+                              table_bytes,
+                              PROT_READ | PROT_WRITE,
+                              /*low_4gb=*/ false,
+                              error_msg);
+}
+
+SmallLrtAllocator::SmallLrtAllocator()
+    : free_lists_(kNumSlots, nullptr),
+      shared_lrt_maps_(),
+      lock_("Small LRT allocator lock", LockLevel::kGenericBottomLock) {
+}
+
+inline size_t SmallLrtAllocator::GetIndex(size_t size) {
+  DCHECK_GE(size, kSmallLrtEntries);
+  DCHECK_LT(size, kPageSize / sizeof(LrtEntry));
+  DCHECK(IsPowerOfTwo(size));
+  size_t index = WhichPowerOf2(size / kSmallLrtEntries);
+  DCHECK_LT(index, kNumSlots);
+  return index;
+}
+
+LrtEntry* SmallLrtAllocator::Allocate(size_t size, std::string* error_msg) {
+  size_t index = GetIndex(size);
+  MutexLock lock(Thread::Current(), lock_);
+  size_t fill_from = index;
+  while (fill_from != kNumSlots && free_lists_[fill_from] == nullptr) {
+    ++fill_from;
+  }
+  void* result = nullptr;
+  if (fill_from != kNumSlots) {
+    // We found a slot with enough memory.
+    result = free_lists_[fill_from];
+    free_lists_[fill_from] = *reinterpret_cast<void**>(result);
+  } else {
+    // We need to allocate a new page and split it into smaller pieces.
+    MemMap map = NewLRTMap(kPageSize, error_msg);
+    if (!map.IsValid()) {
+      return nullptr;
+    }
+    result = map.Begin();
+    shared_lrt_maps_.emplace_back(std::move(map));
+  }
+  while (fill_from != index) {
+    --fill_from;
+    // Store the second half of the current buffer in appropriate free list slot.
+    void* mid = reinterpret_cast<uint8_t*>(result) + (kInitialLrtBytes << fill_from);
+    DCHECK(free_lists_[fill_from] == nullptr);
+    *reinterpret_cast<void**>(mid) = nullptr;
+    free_lists_[fill_from] = mid;
+  }
+  // Clear the memory we return to the caller.
+  std::memset(result, 0, kInitialLrtBytes << index);
+  return reinterpret_cast<LrtEntry*>(result);
+}
+
+void SmallLrtAllocator::Deallocate(LrtEntry* unneeded, size_t size) {
+  size_t index = GetIndex(size);
+  MutexLock lock(Thread::Current(), lock_);
+  while (index < kNumSlots) {
+    // Check if we can merge this free block with another block with the same size.
+    void** other = reinterpret_cast<void**>(
+        reinterpret_cast<uintptr_t>(unneeded) ^ (kInitialLrtBytes << index));
+    void** before = &free_lists_[index];
+    if (index + 1u == kNumSlots && *before == other && *other == nullptr) {
+      // Do not unmap the page if we do not have other free blocks with index `kNumSlots - 1`.
+      // (Keep at least one free block to avoid a situation where creating and destroying a single
+      // thread with no local references would map and unmap a page in the `SmallLrtAllocator`.)
+      break;
+    }
+    while (*before != nullptr && *before != other) {
+      before = reinterpret_cast<void**>(*before);
+    }
+    if (*before == nullptr) {
+      break;
+    }
+    // Remove `other` from the free list and merge it with the `unneeded` block.
+    DCHECK(*before == other);
+    *before = *reinterpret_cast<void**>(other);
+    ++index;
+    unneeded = reinterpret_cast<LrtEntry*>(
+        reinterpret_cast<uintptr_t>(unneeded) & reinterpret_cast<uintptr_t>(other));
+  }
+  if (index == kNumSlots) {
+    // Free the entire page.
+    DCHECK(free_lists_[kNumSlots - 1u] != nullptr);
+    auto match = [=](MemMap& map) { return unneeded == reinterpret_cast<LrtEntry*>(map.Begin()); };
+    auto it = std::find_if(shared_lrt_maps_.begin(), shared_lrt_maps_.end(), match);
+    DCHECK(it != shared_lrt_maps_.end());
+    shared_lrt_maps_.erase(it);
+    DCHECK(!shared_lrt_maps_.empty());
+    return;
+  }
+  *reinterpret_cast<void**>(unneeded) = free_lists_[index];
+  free_lists_[index] = unneeded;
+}
+
+LocalReferenceTable::LocalReferenceTable(bool check_jni)
+    : segment_state_(kLRTFirstSegment),
+      max_entries_(0u),
+      free_entries_list_(
+          FirstFreeField::Update(kFreeListEnd, check_jni ? 1u << kFlagCheckJni : 0u)),
+      small_table_(nullptr),
+      tables_(),
+      table_mem_maps_() {
+}
+
+void LocalReferenceTable::SetCheckJniEnabled(bool enabled) {
+  free_entries_list_ =
+      (free_entries_list_ & ~(1u << kFlagCheckJni)) | (enabled ? 1u << kFlagCheckJni : 0u);
+}
+
+bool LocalReferenceTable::Initialize(size_t max_count, std::string* error_msg) {
+  CHECK(error_msg != nullptr);
+
+  // Overflow and maximum check.
+  CHECK_LE(max_count, kMaxTableSizeInBytes / sizeof(LrtEntry));
+  if (IsCheckJniEnabled()) {
+    CHECK_LE(max_count, kMaxTableSizeInBytes / sizeof(LrtEntry) / kCheckJniEntriesPerReference);
+    max_count *= kCheckJniEntriesPerReference;
+  }
+
+  SmallLrtAllocator* small_lrt_allocator = Runtime::Current()->GetSmallLrtAllocator();
+  LrtEntry* first_table = small_lrt_allocator->Allocate(kSmallLrtEntries, error_msg);
+  if (first_table == nullptr) {
+    DCHECK(!error_msg->empty());
+    return false;
+  }
+  DCHECK_ALIGNED(first_table, kCheckJniEntriesPerReference * sizeof(LrtEntry));
+  small_table_ = first_table;
+  max_entries_ = kSmallLrtEntries;
+  return (max_count <= kSmallLrtEntries) || Resize(max_count, error_msg);
+}
+
+LocalReferenceTable::~LocalReferenceTable() {
+  SmallLrtAllocator* small_lrt_allocator =
+      max_entries_ != 0u ? Runtime::Current()->GetSmallLrtAllocator() : nullptr;
+  if (small_table_ != nullptr) {
+    small_lrt_allocator->Deallocate(small_table_, kSmallLrtEntries);
+    DCHECK(tables_.empty());
+  } else {
+    size_t num_small_tables = std::min(tables_.size(), MaxSmallTables());
+    for (size_t i = 0; i != num_small_tables; ++i) {
+      small_lrt_allocator->Deallocate(tables_[i], GetTableSize(i));
+    }
+  }
+}
+
+bool LocalReferenceTable::Resize(size_t new_size, std::string* error_msg) {
+  DCHECK_GE(max_entries_, kSmallLrtEntries);
+  DCHECK(IsPowerOfTwo(max_entries_));
+  DCHECK_GT(new_size, max_entries_);
+  DCHECK_LE(new_size, kMaxTableSizeInBytes / sizeof(LrtEntry));
+  size_t required_size = RoundUpToPowerOfTwo(new_size);
+  size_t num_required_tables = NumTablesForSize(required_size);
+  DCHECK_GE(num_required_tables, 2u);
+  // Delay moving the `small_table_` to `tables_` until after the next table allocation succeeds.
+  size_t num_tables = (small_table_ != nullptr) ? 1u : tables_.size();
+  DCHECK_EQ(num_tables, NumTablesForSize(max_entries_));
+  for (; num_tables != num_required_tables; ++num_tables) {
+    size_t new_table_size = GetTableSize(num_tables);
+    if (num_tables < MaxSmallTables()) {
+      SmallLrtAllocator* small_lrt_allocator = Runtime::Current()->GetSmallLrtAllocator();
+      LrtEntry* new_table = small_lrt_allocator->Allocate(new_table_size, error_msg);
+      if (new_table == nullptr) {
+        DCHECK(!error_msg->empty());
+        return false;
+      }
+      DCHECK_ALIGNED(new_table, kCheckJniEntriesPerReference * sizeof(LrtEntry));
+      tables_.push_back(new_table);
+    } else {
+      MemMap new_map = NewLRTMap(new_table_size * sizeof(LrtEntry), error_msg);
+      if (!new_map.IsValid()) {
+        DCHECK(!error_msg->empty());
+        return false;
+      }
+      DCHECK_ALIGNED(new_map.Begin(), kCheckJniEntriesPerReference * sizeof(LrtEntry));
+      tables_.push_back(reinterpret_cast<LrtEntry*>(new_map.Begin()));
+      table_mem_maps_.push_back(std::move(new_map));
+    }
+    DCHECK_EQ(num_tables == 1u, small_table_ != nullptr);
+    if (num_tables == 1u) {
+      tables_.insert(tables_.begin(), small_table_);
+      small_table_ = nullptr;
+    }
+    // Record the new available capacity after each successful allocation.
+    DCHECK_EQ(max_entries_, new_table_size);
+    max_entries_ = 2u * new_table_size;
+  }
+  DCHECK_EQ(num_required_tables, tables_.size());
+  return true;
+}
+
+template <typename EntryGetter>
+inline void LocalReferenceTable::PrunePoppedFreeEntries(EntryGetter&& get_entry) {
+  const uint32_t top_index = segment_state_.top_index;
+  uint32_t free_entries_list = free_entries_list_;
+  uint32_t free_entry_index = FirstFreeField::Decode(free_entries_list);
+  DCHECK_NE(free_entry_index, kFreeListEnd);
+  DCHECK_GE(free_entry_index, top_index);
+  do {
+    free_entry_index = get_entry(free_entry_index)->GetNextFree();
+  } while (free_entry_index != kFreeListEnd && free_entry_index >= top_index);
+  free_entries_list_ = FirstFreeField::Update(free_entry_index, free_entries_list);
+}
+
+inline uint32_t LocalReferenceTable::IncrementSerialNumber(LrtEntry* serial_number_entry) {
+  DCHECK_EQ(serial_number_entry, GetCheckJniSerialNumberEntry(serial_number_entry));
+  // The old serial number can be 0 if it was not used before. It can also be bits from the
+  // representation of an object reference, or a link to the next free entry written in this
+  // slot before enabling the CheckJNI. (Some gtests repeatedly enable and disable CheckJNI.)
+  uint32_t old_serial_number =
+      serial_number_entry->GetSerialNumberUnchecked() % kCheckJniEntriesPerReference;
+  uint32_t new_serial_number =
+      (old_serial_number + 1u) != kCheckJniEntriesPerReference ? old_serial_number + 1u : 1u;
+  DCHECK(IsValidSerialNumber(new_serial_number));
+  serial_number_entry->SetSerialNumber(new_serial_number);
+  return new_serial_number;
+}
+
+IndirectRef LocalReferenceTable::Add(LRTSegmentState previous_state,
+                                     ObjPtr<mirror::Object> obj,
+                                     std::string* error_msg) {
+  if (kDebugLRT) {
+    LOG(INFO) << "+++ Add: previous_state=" << previous_state.top_index
+              << " top_index=" << segment_state_.top_index;
+  }
+
+  DCHECK(obj != nullptr);
+  VerifyObject(obj);
+
+  DCHECK_LE(previous_state.top_index, segment_state_.top_index);
+  DCHECK(max_entries_ == kSmallLrtEntries ? small_table_ != nullptr : !tables_.empty());
+
+  auto store_obj = [obj, this](LrtEntry* free_entry, const char* tag)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    free_entry->SetReference(obj);
+    IndirectRef result = ToIndirectRef(free_entry);
+    if (kDebugLRT) {
+      LOG(INFO) << "+++ " << tag << ": added at index " << GetReferenceEntryIndex(result)
+                << ", top=" << segment_state_.top_index;
+    }
+    return result;
+  };
+
+  // Fast-path for small table with CheckJNI disabled.
+  uint32_t top_index = segment_state_.top_index;
+  LrtEntry* const small_table = small_table_;
+  if (LIKELY(small_table != nullptr)) {
+    DCHECK_EQ(max_entries_, kSmallLrtEntries);
+    DCHECK_LE(segment_state_.top_index, kSmallLrtEntries);
+    auto get_entry = [small_table](uint32_t index) ALWAYS_INLINE {
+      DCHECK_LT(index, kSmallLrtEntries);
+      return &small_table[index];
+    };
+    if (LIKELY(free_entries_list_ == kEmptyFreeListAndCheckJniDisabled)) {
+      if (LIKELY(top_index != kSmallLrtEntries)) {
+        LrtEntry* free_entry = get_entry(top_index);
+        segment_state_.top_index = top_index + 1u;
+        return store_obj(free_entry, "small_table/empty-free-list");
+      }
+    } else if (LIKELY(!IsCheckJniEnabled())) {
+      uint32_t first_free_index = GetFirstFreeIndex();
+      DCHECK_NE(first_free_index, kFreeListEnd);
+      if (UNLIKELY(first_free_index >= top_index)) {
+        PrunePoppedFreeEntries(get_entry);
+        first_free_index = GetFirstFreeIndex();
+      }
+      if (first_free_index != kFreeListEnd && first_free_index >= previous_state.top_index) {
+        DCHECK_LT(first_free_index, segment_state_.top_index);  // Popped entries pruned above.
+        LrtEntry* free_entry = get_entry(first_free_index);
+        // Use the `free_entry` only if it was created with CheckJNI disabled.
+        LrtEntry* serial_number_entry = GetCheckJniSerialNumberEntry(free_entry);
+        if (!serial_number_entry->IsSerialNumber()) {
+          free_entries_list_ = FirstFreeField::Update(free_entry->GetNextFree(), 0u);
+          return store_obj(free_entry, "small_table/reuse-empty-slot");
+        }
+      }
+      if (top_index != kSmallLrtEntries) {
+        LrtEntry* free_entry = get_entry(top_index);
+        segment_state_.top_index = top_index + 1u;
+        return store_obj(free_entry, "small_table/pruned-free-list");
+      }
+    }
+  }
+  DCHECK(IsCheckJniEnabled() || small_table == nullptr || top_index == kSmallLrtEntries);
+
+  // Process free list: prune, reuse free entry or pad for CheckJNI.
+  uint32_t first_free_index = GetFirstFreeIndex();
+  if (first_free_index != kFreeListEnd && first_free_index >= top_index) {
+    PrunePoppedFreeEntries([&](size_t index) { return GetEntry(index); });
+    first_free_index = GetFirstFreeIndex();
+  }
+  if (first_free_index != kFreeListEnd && first_free_index >= previous_state.top_index) {
+    // Reuse the free entry if it was created with the same CheckJNI setting.
+    DCHECK_LT(first_free_index, top_index);  // Popped entries have been pruned above.
+    LrtEntry* free_entry = GetEntry(first_free_index);
+    LrtEntry* serial_number_entry = GetCheckJniSerialNumberEntry(free_entry);
+    if (serial_number_entry->IsSerialNumber() == IsCheckJniEnabled()) {
+      free_entries_list_ = FirstFreeField::Update(free_entry->GetNextFree(), free_entries_list_);
+      if (UNLIKELY(IsCheckJniEnabled())) {
+        DCHECK_NE(free_entry, serial_number_entry);
+        uint32_t serial_number = IncrementSerialNumber(serial_number_entry);
+        free_entry = serial_number_entry + serial_number;
+        DCHECK_EQ(
+            free_entry,
+            GetEntry(RoundDown(first_free_index, kCheckJniEntriesPerReference) + serial_number));
+      }
+      return store_obj(free_entry, "reuse-empty-slot");
+    }
+  }
+  if (UNLIKELY(IsCheckJniEnabled()) && !IsAligned<kCheckJniEntriesPerReference>(top_index)) {
+    // Add non-CheckJNI holes up to the next serial number entry.
+    for (; !IsAligned<kCheckJniEntriesPerReference>(top_index); ++top_index) {
+      GetEntry(top_index)->SetNextFree(first_free_index);
+      first_free_index = top_index;
+    }
+    free_entries_list_ = FirstFreeField::Update(first_free_index, 1u << kFlagCheckJni);
+    segment_state_.top_index = top_index;
+  }
+
+  // Resize (double the space) if needed.
+  if (UNLIKELY(top_index == max_entries_)) {
+    static_assert(IsPowerOfTwo(kMaxTableSizeInBytes));
+    static_assert(IsPowerOfTwo(sizeof(LrtEntry)));
+    DCHECK(IsPowerOfTwo(max_entries_));
+    if (kMaxTableSizeInBytes == max_entries_ * sizeof(LrtEntry)) {
+      std::ostringstream oss;
+      oss << "JNI ERROR (app bug): " << kLocal << " table overflow "
+          << "(max=" << max_entries_ << ")" << std::endl
+          << MutatorLockedDumpable<LocalReferenceTable>(*this)
+          << " Resizing failed: Cannot resize over the maximum permitted size.";
+      *error_msg = oss.str();
+      return nullptr;
+    }
+
+    std::string inner_error_msg;
+    if (!Resize(max_entries_ * 2u, &inner_error_msg)) {
+      std::ostringstream oss;
+      oss << "JNI ERROR (app bug): " << kLocal << " table overflow "
+          << "(max=" << max_entries_ << ")" << std::endl
+          << MutatorLockedDumpable<LocalReferenceTable>(*this)
+          << " Resizing failed: " << inner_error_msg;
+      *error_msg = oss.str();
+      return nullptr;
+    }
+  }
+
+  // Use the next entry.
+  if (UNLIKELY(IsCheckJniEnabled())) {
+    DCHECK_ALIGNED(top_index, kCheckJniEntriesPerReference);
+    DCHECK_ALIGNED(previous_state.top_index, kCheckJniEntriesPerReference);
+    DCHECK_ALIGNED(max_entries_, kCheckJniEntriesPerReference);
+    LrtEntry* serial_number_entry = GetEntry(top_index);
+    uint32_t serial_number = IncrementSerialNumber(serial_number_entry);
+    LrtEntry* free_entry = serial_number_entry + serial_number;
+    DCHECK_EQ(free_entry, GetEntry(top_index + serial_number));
+    segment_state_.top_index = top_index + kCheckJniEntriesPerReference;
+    return store_obj(free_entry, "slow-path/check-jni");
+  }
+  LrtEntry* free_entry = GetEntry(top_index);
+  segment_state_.top_index = top_index + 1u;
+  return store_obj(free_entry, "slow-path");
+}
+
+// Removes an object.
+//
+// This method is not called when a local frame is popped; this is only used
+// for explicit single removals.
+//
+// If the entry is not at the top, we just add it to the free entry list.
+// If the entry is at the top, we pop it from the top and check if there are
+// free entries under it to remove in order to reduce the size of the table.
+//
+// Returns "false" if nothing was removed.
+bool LocalReferenceTable::Remove(LRTSegmentState previous_state, IndirectRef iref) {
+  if (kDebugLRT) {
+    LOG(INFO) << "+++ Remove: previous_state=" << previous_state.top_index
+              << " top_index=" << segment_state_.top_index;
+  }
+
+  IndirectRefKind kind = IndirectReferenceTable::GetIndirectRefKind(iref);
+  if (UNLIKELY(kind != kLocal)) {
+    Thread* self = Thread::Current();
+    if (kind == kJniTransition) {
+      if (self->IsJniTransitionReference(reinterpret_cast<jobject>(iref))) {
+        // Transition references count as local but they cannot be deleted.
+        // TODO: They could actually be cleared on the stack, except for the `jclass`
+        // reference for static methods that points to the method's declaring class.
+        JNIEnvExt* env = self->GetJniEnv();
+        DCHECK(env != nullptr);
+        if (env->IsCheckJniEnabled()) {
+          const char* msg = kDumpStackOnNonLocalReference
+              ? "Attempt to remove non-JNI local reference, dumping thread"
+              : "Attempt to remove non-JNI local reference";
+          LOG(WARNING) << msg;
+          if (kDumpStackOnNonLocalReference) {
+            self->Dump(LOG_STREAM(WARNING));
+          }
+        }
+        return true;
+      }
+    }
+    if (kDumpStackOnNonLocalReference && IsCheckJniEnabled()) {
+      // Log the error message and stack. Repeat the message as FATAL later.
+      LOG(ERROR) << "Attempt to delete " << kind
+                 << " reference as local JNI reference, dumping stack";
+      self->Dump(LOG_STREAM(ERROR));
+    }
+    LOG(IsCheckJniEnabled() ? ERROR : FATAL)
+        << "Attempt to delete " << kind << " reference as local JNI reference";
+    return false;
+  }
+
+  DCHECK_LE(previous_state.top_index, segment_state_.top_index);
+  DCHECK(max_entries_ == kSmallLrtEntries ? small_table_ != nullptr : !tables_.empty());
+  DCheckValidReference(iref);
+
+  LrtEntry* entry = ToLrtEntry(iref);
+  uint32_t entry_index = GetReferenceEntryIndex(iref);
+  uint32_t top_index = segment_state_.top_index;
+  const uint32_t bottom_index = previous_state.top_index;
+
+  if (entry_index < bottom_index) {
+    // Wrong segment.
+    LOG(WARNING) << "Attempt to remove index outside index area (" << entry_index
+                 << " vs " << bottom_index << "-" << top_index << ")";
+    return false;
+  }
+
+  if (UNLIKELY(IsCheckJniEnabled())) {
+    // Ignore invalid references. CheckJNI should have aborted before passing this reference
+    // to `LocalReferenceTable::Remove()` but gtests intercept the abort and proceed anyway.
+    std::string error_msg;
+    if (!IsValidReference(iref, &error_msg)) {
+      LOG(WARNING) << "Attempt to remove invalid reference: " << error_msg;
+      return false;
+    }
+  }
+  DCHECK_LT(entry_index, top_index);
+
+  // Prune the free entry list if a segment with holes was popped before the `Remove()` call.
+  uint32_t first_free_index = GetFirstFreeIndex();
+  if (first_free_index != kFreeListEnd && first_free_index >= top_index) {
+    PrunePoppedFreeEntries([&](size_t index) { return GetEntry(index); });
+  }
+
+  // Check if we're removing the top entry (created with any CheckJNI setting).
+  bool is_top_entry = false;
+  uint32_t prune_end = entry_index;
+  if (GetCheckJniSerialNumberEntry(entry)->IsSerialNumber()) {
+    LrtEntry* serial_number_entry = GetCheckJniSerialNumberEntry(entry);
+    uint32_t serial_number = dchecked_integral_cast<uint32_t>(entry - serial_number_entry);
+    DCHECK_EQ(serial_number, serial_number_entry->GetSerialNumber());
+    prune_end = entry_index - serial_number;
+    is_top_entry = (prune_end == top_index - kCheckJniEntriesPerReference);
+  } else {
+    is_top_entry = (entry_index == top_index - 1u);
+  }
+  if (is_top_entry) {
+    // Top-most entry. Scan up and consume holes created with the current CheckJNI setting.
+    constexpr uint32_t kDeadLocalValue = 0xdead10c0;
+    entry->SetReference(reinterpret_cast32<mirror::Object*>(kDeadLocalValue));
+
+    // TODO: Maybe we should not prune free entries from the top of the segment
+    // because it has quadratic worst-case complexity. We could still prune while
+    // the first free list entry is at the top.
+    uint32_t prune_start = prune_end;
+    size_t prune_count;
+    auto find_prune_range = [&](size_t chunk_size, auto is_prev_entry_free) {
+      while (prune_start > bottom_index && is_prev_entry_free(prune_start)) {
+        prune_start -= chunk_size;
+      }
+      prune_count = (prune_end - prune_start) / chunk_size;
+    };
+
+    if (UNLIKELY(IsCheckJniEnabled())) {
+      auto is_prev_entry_free = [&](size_t index) {
+        DCHECK_ALIGNED(index, kCheckJniEntriesPerReference);
+        LrtEntry* serial_number_entry = GetEntry(index - kCheckJniEntriesPerReference);
+        DCHECK_ALIGNED(serial_number_entry, kCheckJniEntriesPerReference * sizeof(LrtEntry));
+        if (!serial_number_entry->IsSerialNumber()) {
+          return false;
+        }
+        uint32_t serial_number = serial_number_entry->GetSerialNumber();
+        DCHECK(IsValidSerialNumber(serial_number));
+        LrtEntry* entry = serial_number_entry + serial_number;
+        DCHECK_EQ(entry, GetEntry(prune_start - kCheckJniEntriesPerReference + serial_number));
+        return entry->IsFree();
+      };
+      find_prune_range(kCheckJniEntriesPerReference, is_prev_entry_free);
+    } else {
+      auto is_prev_entry_free = [&](size_t index) {
+        LrtEntry* entry = GetEntry(index - 1u);
+        return entry->IsFree() && !GetCheckJniSerialNumberEntry(entry)->IsSerialNumber();
+      };
+      find_prune_range(1u, is_prev_entry_free);
+    }
+
+    if (prune_count != 0u) {
+      // Remove pruned entries from the free list.
+      size_t remaining = prune_count;
+      uint32_t free_index = GetFirstFreeIndex();
+      while (remaining != 0u && free_index >= prune_start) {
+        DCHECK_NE(free_index, kFreeListEnd);
+        LrtEntry* pruned_entry = GetEntry(free_index);
+        free_index = pruned_entry->GetNextFree();
+        pruned_entry->SetReference(reinterpret_cast32<mirror::Object*>(kDeadLocalValue));
+        --remaining;
+      }
+      free_entries_list_ = FirstFreeField::Update(free_index, free_entries_list_);
+      while (remaining != 0u) {
+        DCHECK_NE(free_index, kFreeListEnd);
+        DCHECK_LT(free_index, prune_start);
+        DCHECK_GE(free_index, bottom_index);
+        LrtEntry* free_entry = GetEntry(free_index);
+        while (free_entry->GetNextFree() < prune_start) {
+          free_index = free_entry->GetNextFree();
+          DCHECK_GE(free_index, bottom_index);
+          free_entry = GetEntry(free_index);
+        }
+        LrtEntry* pruned_entry = GetEntry(free_entry->GetNextFree());
+        free_entry->SetNextFree(pruned_entry->GetNextFree());
+        pruned_entry->SetReference(reinterpret_cast32<mirror::Object*>(kDeadLocalValue));
+        --remaining;
+      }
+      DCHECK(free_index == kFreeListEnd || free_index < prune_start)
+          << "free_index=" << free_index << ", prune_start=" << prune_start;
+    }
+    segment_state_.top_index = prune_start;
+    if (kDebugLRT) {
+      LOG(INFO) << "+++ removed last entry, pruned " << prune_count
+                << ", new top= " << segment_state_.top_index;
+    }
+  } else {
+    // Not the top-most entry. This creates a hole.
+    entry->SetNextFree(GetFirstFreeIndex());
+    free_entries_list_ = FirstFreeField::Update(entry_index, free_entries_list_);
+    if (kDebugLRT) {
+      LOG(INFO) << "+++ removed entry and left hole at " << entry_index;
+    }
+  }
+
+  return true;
+}
+
+void LocalReferenceTable::AssertEmpty() {
+  CHECK_EQ(Capacity(), 0u) << "Internal Error: non-empty local reference table.";
+}
+
+void LocalReferenceTable::Trim() {
+  ScopedTrace trace(__PRETTY_FUNCTION__);
+  const size_t num_mem_maps = table_mem_maps_.size();
+  if (num_mem_maps == 0u) {
+    // Only small tables; nothing to do here. (Do not unnecessarily prune popped free entries.)
+    return;
+  }
+  DCHECK_EQ(tables_.size(), num_mem_maps + MaxSmallTables());
+  const size_t top_index = segment_state_.top_index;
+  // Prune popped free entries before potentially losing their memory.
+  if (UNLIKELY(GetFirstFreeIndex() != kFreeListEnd) &&
+      UNLIKELY(GetFirstFreeIndex() >= segment_state_.top_index)) {
+    PrunePoppedFreeEntries([&](size_t index) { return GetEntry(index); });
+  }
+  // Small tables can hold as many entries as the next table.
+  constexpr size_t kSmallTablesCapacity = GetTableSize(MaxSmallTables());
+  size_t mem_map_index = 0u;
+  if (top_index > kSmallTablesCapacity) {
+    const size_t table_size = TruncToPowerOfTwo(top_index);
+    const size_t table_index = NumTablesForSize(table_size);
+    const size_t start_index = top_index - table_size;
+    mem_map_index = table_index - MaxSmallTables();
+    if (start_index != 0u) {
+      ++mem_map_index;
+      LrtEntry* table = tables_[table_index];
+      uint8_t* release_start = AlignUp(reinterpret_cast<uint8_t*>(&table[start_index]), kPageSize);
+      uint8_t* release_end = reinterpret_cast<uint8_t*>(&table[table_size]);
+      DCHECK_GE(reinterpret_cast<uintptr_t>(release_end),
+                reinterpret_cast<uintptr_t>(release_start));
+      DCHECK_ALIGNED(release_end, kPageSize);
+      DCHECK_ALIGNED(release_end - release_start, kPageSize);
+      if (release_start != release_end) {
+        madvise(release_start, release_end - release_start, MADV_DONTNEED);
+      }
+    }
+  }
+  for (MemMap& mem_map : ArrayRef<MemMap>(table_mem_maps_).SubArray(mem_map_index)) {
+    madvise(mem_map.Begin(), mem_map.Size(), MADV_DONTNEED);
+  }
+}
+
+template <typename Visitor>
+void LocalReferenceTable::VisitRootsInternal(Visitor&& visitor) const {
+  auto visit_table = [&](LrtEntry* table, size_t count) REQUIRES_SHARED(Locks::mutator_lock_) {
+    for (size_t i = 0; i != count; ) {
+      LrtEntry* entry;
+      if (i % kCheckJniEntriesPerReference == 0u && table[i].IsSerialNumber()) {
+        entry = &table[i + table[i].GetSerialNumber()];
+        i += kCheckJniEntriesPerReference;
+        DCHECK_LE(i, count);
+      } else {
+        entry = &table[i];
+        i += 1u;
+      }
+      DCHECK(!entry->IsSerialNumber());
+      if (!entry->IsFree()) {
+        GcRoot<mirror::Object>* root = entry->GetRootAddress();
+        DCHECK(!root->IsNull());
+        visitor(root);
+      }
+    }
+  };
+
+  if (small_table_ != nullptr) {
+    visit_table(small_table_, segment_state_.top_index);
+  } else {
+    uint32_t remaining = segment_state_.top_index;
+    size_t table_index = 0u;
+    while (remaining != 0u) {
+      size_t count = std::min<size_t>(remaining, GetTableSize(table_index));
+      visit_table(tables_[table_index], count);
+      ++table_index;
+      remaining -= count;
+    }
+  }
+}
+
+void LocalReferenceTable::VisitRoots(RootVisitor* visitor, const RootInfo& root_info) {
+  BufferedRootVisitor<kDefaultBufferedRootCount> root_visitor(visitor, root_info);
+  VisitRootsInternal([&](GcRoot<mirror::Object>* root) REQUIRES_SHARED(Locks::mutator_lock_) {
+                       root_visitor.VisitRoot(*root);
+                     });
+}
+
+void LocalReferenceTable::Dump(std::ostream& os) const {
+  os << kLocal << " table dump:\n";
+  ReferenceTable::Table entries;
+  VisitRootsInternal([&](GcRoot<mirror::Object>* root) REQUIRES_SHARED(Locks::mutator_lock_) {
+                       entries.push_back(*root);
+                     });
+  ReferenceTable::Dump(os, entries);
+}
+
+void LocalReferenceTable::SetSegmentState(LRTSegmentState new_state) {
+  if (kDebugLRT) {
+    LOG(INFO) << "Setting segment state: "
+              << segment_state_.top_index
+              << " -> "
+              << new_state.top_index;
+  }
+  segment_state_ = new_state;
+}
+
+bool LocalReferenceTable::EnsureFreeCapacity(size_t free_capacity, std::string* error_msg) {
+  // TODO: Pass `previous_state` so that we can check holes.
+  DCHECK_GE(free_capacity, static_cast<size_t>(1));
+  size_t top_index = segment_state_.top_index;
+  DCHECK_LE(top_index, max_entries_);
+
+  if (IsCheckJniEnabled()) {
+    // High values lead to the maximum size check failing below.
+    if (free_capacity >= std::numeric_limits<size_t>::max() / kCheckJniEntriesPerReference) {
+      free_capacity = std::numeric_limits<size_t>::max();
+    } else {
+      free_capacity *= kCheckJniEntriesPerReference;
+    }
+  }
+
+  // TODO: Include holes from the current segment in the calculation.
+  if (free_capacity <= max_entries_ - top_index) {
+    return true;
+  }
+
+  if (free_capacity > kMaxTableSize - top_index) {
+    *error_msg = android::base::StringPrintf(
+        "Requested size exceeds maximum: %zu > %zu (%zu used)",
+        free_capacity,
+        kMaxTableSize - top_index,
+        top_index);
+    return false;
+  }
+
+  // Try to increase the table size.
+  if (!Resize(top_index + free_capacity, error_msg)) {
+    LOG(WARNING) << "JNI ERROR: Unable to reserve space in EnsureFreeCapacity (" << free_capacity
+                 << "): " << std::endl
+                 << MutatorLockedDumpable<LocalReferenceTable>(*this)
+                 << " Resizing failed: " << *error_msg;
+    return false;
+  }
+  return true;
+}
+
+size_t LocalReferenceTable::FreeCapacity() const {
+  // TODO: Include holes in current segment.
+  if (IsCheckJniEnabled()) {
+    DCHECK_ALIGNED(max_entries_, kCheckJniEntriesPerReference);
+    // The `segment_state_.top_index` is not necessarily aligned; rounding down.
+    return (max_entries_ - segment_state_.top_index) / kCheckJniEntriesPerReference;
+  } else {
+    return max_entries_ - segment_state_.top_index;
+  }
+}
+
+}  // namespace jni
+}  // namespace art
diff --git a/runtime/jni/local_reference_table.h b/runtime/jni/local_reference_table.h
new file mode 100644
index 0000000..900e4c3
--- /dev/null
+++ b/runtime/jni/local_reference_table.h
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2022 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_RUNTIME_JNI_LOCAL_REFERENCE_TABLE_H_
+#define ART_RUNTIME_JNI_LOCAL_REFERENCE_TABLE_H_
+
+#include <stdint.h>
+
+#include <iosfwd>
+#include <limits>
+#include <string>
+
+#include <android-base/logging.h>
+
+#include "base/bit_field.h"
+#include "base/bit_utils.h"
+#include "base/casts.h"
+#include "base/dchecked_vector.h"
+#include "base/locks.h"
+#include "base/macros.h"
+#include "base/mem_map.h"
+#include "base/mutex.h"
+#include "gc_root.h"
+#include "indirect_reference_table.h"
+#include "mirror/object_reference.h"
+#include "obj_ptr.h"
+#include "offsets.h"
+
+namespace art {
+
+class RootInfo;
+
+namespace mirror {
+class Object;
+}  // namespace mirror
+
+namespace jni {
+
+// Maintain a table of local JNI references.
+//
+// The table contains object references that are part of the GC root set. When an object is
+// added we return an `IndirectRef` that is not a valid pointer but can be used to find the
+// original value in O(1) time. Conversions to and from local JNI references are performed
+// on upcalls and downcalls as well as in JNI functions, so they need to be very fast.
+//
+// To be efficient for JNI local variable storage, we need to provide operations that allow us to
+// operate on segments of the table, where segments are pushed and popped as if on a stack. For
+// example, deletion of an entry should only succeed if it appears in the current segment, and we
+// want to be able to strip off the current segment quickly when a method returns. Additions to the
+// table must be made in the current segment even if space is available in an earlier area.
+//
+// A new segment is created when we call into native code from managed code, or when we handle
+// the JNI PushLocalFrame function.
+//
+// The GC must be able to scan the entire table quickly.
+//
+// In summary, these must be very fast:
+//  - adding or removing a segment
+//  - adding references (always adding to the current segment)
+//  - converting a local reference back to an Object
+// These can be a little slower, but must still be pretty quick:
+//  - removing individual references
+//  - scanning the entire table straight through
+//
+// If there's more than one segment, we don't guarantee that the table will fill completely before
+// we fail due to lack of space. We do ensure that the current segment will pack tightly, which
+// should satisfy JNI requirements (e.g. EnsureLocalCapacity).
+
+// To get the desired behavior for JNI locals, we need to know the bottom and top of the current
+// "segment". The top is managed internally, and the bottom is passed in as a function argument.
+// When we call a native method or push a local frame, the current top index gets pushed on, and
+// serves as the new bottom. When we pop a frame off, the value from the stack becomes the new top
+// index, and the value stored in the previous frame becomes the new bottom.
+// TODO: Move the bottom index from `JniEnvExt` to the `LocalReferenceTable`. Use this in the JNI
+// compiler to improve the emitted local frame push/pop code by using two-register loads/stores
+// where available (LDRD/STRD on arm, LDP/STP on arm64).
+//
+// If we delete entries from the middle of the list, we will be left with "holes" which we track
+// with a singly-linked list, so that they can be reused quickly. After a segment has been removed,
+// we need to prune removed free entries from the front of this singly-linked list before we can
+// reuse a free entry from the current segment. This is linear in the number of entries removed
+// and may appear as a slow reference addition but this slow down is attributable to the previous
+// removals with a constant time per removal.
+//
+// Without CheckJNI, we aim for the fastest possible implementation, so there is no error checking
+// (in release build) and stale references can be erroneously used, especially after the same slot
+// has been reused for another reference which we cannot easily detect (even in debug build).
+//
+// With CheckJNI, we rotate the slots that we use based on a "serial number".
+// This increases the memory use but it allows for decent error detection.
+//
+// We allow switching between CheckJNI enabled and disabled but entries created with CheckJNI
+// disabled shall have weaker checking even after enabling CheckJNI and the switch can also
+// prevent reusing a hole that held a reference created with a different CheckJNI setting.
+
+// The state of the current segment contains the top index.
+struct LRTSegmentState {
+  uint32_t top_index;
+};
+
+// Use as initial value for "cookie", and when table has only one segment.
+static constexpr LRTSegmentState kLRTFirstSegment = { 0 };
+
+// Each entry in the `LocalReferenceTable` can contain a null (initially or after a `Trim()`)
+// or reference, or it can be marked as free and hold the index of the next free entry.
+// If CheckJNI is (or was) enabled, some entries can contain serial numbers instead and
+// only one other entry in a CheckJNI chunk starting with a serial number is active.
+//
+// Valid bit patterns:
+//                   33222222222211111111110000000000
+//                   10987654321098765432109876543210
+//   null:           00000000000000000000000000000000  // Only above the top index.
+//   reference:      <----- reference value ----->000  // See also `kObjectAlignment`.
+//   free:           <-------- next free --------->01
+//   serial number:  <------ serial number ------->10  // CheckJNI entry.
+// Note that serial number entries can appear only as the first entry of a 16-byte aligned
+// chunk of four entries and the serial number in the range [1, 3] specifies which of the
+// other three entries in the chunk is currently used.
+class LrtEntry {
+ public:
+  void SetReference(ObjPtr<mirror::Object> ref) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  ObjPtr<mirror::Object> GetReference() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  bool IsNull() const {
+    return root_.IsNull();
+  }
+
+  void SetNextFree(uint32_t next_free) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  uint32_t GetNextFree() {
+    DCHECK(IsFree());
+    DCHECK(!IsSerialNumber());
+    return NextFreeField::Decode(GetRawValue());
+  }
+
+  bool IsFree() {
+    return (GetRawValue() & (1u << kFlagFree)) != 0u;
+  }
+
+  void SetSerialNumber(uint32_t serial_number) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  uint32_t GetSerialNumber() {
+    DCHECK(IsSerialNumber());
+    DCHECK(!IsFree());
+    return GetSerialNumberUnchecked();
+  }
+
+  uint32_t GetSerialNumberUnchecked() {
+    return SerialNumberField::Decode(GetRawValue());
+  }
+
+  bool IsSerialNumber() {
+    return (GetRawValue() & (1u << kFlagSerialNumber)) != 0u;
+  }
+
+  GcRoot<mirror::Object>* GetRootAddress() {
+    return &root_;
+  }
+
+  static constexpr uint32_t FreeListEnd() {
+    return MaxInt<uint32_t>(kFieldNextFreeBits);
+  }
+
+ private:
+  // Definitions of bit fields and flags.
+  static constexpr size_t kFlagFree = 0u;
+  static constexpr size_t kFlagSerialNumber = kFlagFree + 1u;
+  static constexpr size_t kFieldNextFree = kFlagSerialNumber + 1u;
+  static constexpr size_t kFieldNextFreeBits = BitSizeOf<uint32_t>() - kFieldNextFree;
+
+  using NextFreeField = BitField<uint32_t, kFieldNextFree, kFieldNextFreeBits>;
+  using SerialNumberField = NextFreeField;
+
+  static_assert(kObjectAlignment > (1u << kFlagFree));
+  static_assert(kObjectAlignment > (1u << kFlagSerialNumber));
+
+  void SetVRegValue(uint32_t value) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  uint32_t GetRawValue() {
+    return root_.AddressWithoutBarrier()->AsVRegValue();
+  }
+
+  // We record the contents as a `GcRoot<>` but it is an actual `GcRoot<>` only if it's below
+  // the current segment's top index, it's not a "serial number" or inactive entry in a CheckJNI
+  // chunk, and it's not marked as "free". Such entries are never null.
+  GcRoot<mirror::Object> root_;
+};
+static_assert(sizeof(LrtEntry) == sizeof(mirror::CompressedReference<mirror::Object>));
+// Assert that the low bits of an `LrtEntry*` are sufficient for encoding the reference kind.
+static_assert(enum_cast<uint32_t>(IndirectRefKind::kLastKind) < alignof(LrtEntry));
+
+
+// We initially allocate local reference tables with a small number of entries, packing
+// multiple tables into a single page. If we need to expand, we double the capacity,
+// first allocating another chunk with the same number of entries as the first chunk
+// and then allocating twice as big chunk on each subsequent expansion.
+static constexpr size_t kInitialLrtBytes = 512;  // Number of bytes in an initial local table.
+static constexpr size_t kSmallLrtEntries = kInitialLrtBytes / sizeof(LrtEntry);
+static_assert(IsPowerOfTwo(kInitialLrtBytes));
+static_assert(kPageSize % kInitialLrtBytes == 0);
+static_assert(kInitialLrtBytes % sizeof(LrtEntry) == 0);
+
+// A minimal stopgap allocator for initial small local LRT tables.
+class SmallLrtAllocator {
+ public:
+  SmallLrtAllocator();
+
+  // Allocate a small block of `LrtEntries` for the `LocalReferenceTable` table. The `size`
+  // must be a power of 2, at least `kSmallLrtEntries`, and requiring less than a page of memory.
+  LrtEntry* Allocate(size_t size, std::string* error_msg) REQUIRES(!lock_);
+
+  void Deallocate(LrtEntry* unneeded, size_t size) REQUIRES(!lock_);
+
+ private:
+  static constexpr size_t kNumSlots = WhichPowerOf2(kPageSize / kInitialLrtBytes);
+
+  static size_t GetIndex(size_t size);
+
+  // Free lists of small chunks linked through the first word.
+  dchecked_vector<void*> free_lists_;
+
+  // Repository of MemMaps used for small LRT tables.
+  dchecked_vector<MemMap> shared_lrt_maps_;
+
+  Mutex lock_;  // Level kGenericBottomLock; acquired before mem_map_lock_, which is a C++ mutex.
+};
+
+class LocalReferenceTable {
+ public:
+  explicit LocalReferenceTable(bool check_jni);
+  ~LocalReferenceTable();
+
+  // Set the CheckJNI enabled status.
+  // Called only from the Zygote post-fork callback while the process is single-threaded.
+  // Enabling CheckJNI reduces the number of entries that can be stored, thus invalidating
+  // guarantees provided by a previous call to `EnsureFreeCapacity()`.
+  void SetCheckJniEnabled(bool enabled);
+
+  // Returns whether the CheckJNI is enabled for this `LocalReferenceTable`.
+  bool IsCheckJniEnabled() const {
+    return (free_entries_list_ & (1u << kFlagCheckJni)) != 0u;
+  }
+
+  // Initialize the `LocalReferenceTable`.
+  //
+  // Max_count is the requested minimum initial capacity (resizable). The actual initial
+  // capacity can be higher to utilize all allocated memory.
+  //
+  // Returns true on success.
+  // On failure, returns false and reports error in `*error_msg`.
+  bool Initialize(size_t max_count, std::string* error_msg);
+
+  // Add a new entry. The `obj` must be a valid non-null object reference. This function
+  // will return null if an error happened (with an appropriate error message set).
+  IndirectRef Add(LRTSegmentState previous_state,
+                  ObjPtr<mirror::Object> obj,
+                  std::string* error_msg)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Given an `IndirectRef` in the table, return the `Object` it refers to.
+  //
+  // This function may abort under error conditions in debug build.
+  // In release builds, error conditions are unchecked and the function can
+  // return old or invalid references from popped segments and deleted entries.
+  ObjPtr<mirror::Object> Get(IndirectRef iref) const
+      REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE;
+
+  // Updates an existing indirect reference to point to a new object.
+  // Used exclusively for updating `String` references after calling a `String` constructor.
+  void Update(IndirectRef iref, ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Remove an existing entry.
+  //
+  // If the entry is not between the current top index and the bottom index
+  // specified by the cookie, we don't remove anything.  This is the behavior
+  // required by JNI's DeleteLocalRef function.
+  //
+  // Returns "false" if nothing was removed.
+  bool Remove(LRTSegmentState previous_state, IndirectRef iref)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void AssertEmpty();
+
+  void Dump(std::ostream& os) const
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::alloc_tracker_lock_);
+
+  IndirectRefKind GetKind() const {
+    return kLocal;
+  }
+
+  // Return the number of entries in the entire table. This includes holes,
+  // and so may be larger than the actual number of "live" entries.
+  // The value corresponds to the number of entries for the current CheckJNI setting
+  // and may be wrong if there are entries created with a different CheckJNI setting.
+  size_t Capacity() const {
+    if (IsCheckJniEnabled()) {
+      DCHECK_ALIGNED(segment_state_.top_index, kCheckJniEntriesPerReference);
+      return segment_state_.top_index / kCheckJniEntriesPerReference;
+    } else {
+      return segment_state_.top_index;
+    }
+  }
+
+  // Ensure that at least free_capacity elements are available, or return false.
+  // Caller ensures free_capacity > 0.
+  bool EnsureFreeCapacity(size_t free_capacity, std::string* error_msg)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // See implementation of EnsureFreeCapacity. We'll only state here how much is trivially free,
+  // without recovering holes. Thus this is a conservative estimate.
+  size_t FreeCapacity() const;
+
+  void VisitRoots(RootVisitor* visitor, const RootInfo& root_info)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  LRTSegmentState GetSegmentState() const {
+    return segment_state_;
+  }
+
+  void SetSegmentState(LRTSegmentState new_state);
+
+  static Offset SegmentStateOffset(size_t pointer_size ATTRIBUTE_UNUSED) {
+    // Note: Currently segment_state_ is at offset 0. We're testing the expected value in
+    //       jni_internal_test to make sure it stays correct. It is not OFFSETOF_MEMBER, as that
+    //       is not pointer-size-safe.
+    return Offset(0);
+  }
+
+  // Release pages past the end of the table that may have previously held references.
+  void Trim() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  /* Reference validation for CheckJNI and debug build. */
+  bool IsValidReference(IndirectRef, /*out*/std::string* error_msg) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+ private:
+  // Flags and fields in the `free_entries_list_`.
+  static constexpr size_t kFlagCheckJni = 0u;
+  // Skip a bit to have the same value range for the "first free" as the "next free" in `LrtEntry`.
+  static constexpr size_t kFlagPadding = kFlagCheckJni + 1u;
+  static constexpr size_t kFieldFirstFree = kFlagPadding + 1u;
+  static constexpr size_t kFieldFirstFreeSize = BitSizeOf<uint32_t>() - kFieldFirstFree;
+
+  using FirstFreeField = BitField<uint32_t, kFieldFirstFree, kFieldFirstFreeSize>;
+
+  // The value of `FirstFreeField` in `free_entries_list_` indicating the end of the free list.
+  static constexpr uint32_t kFreeListEnd = LrtEntry::FreeListEnd();
+  static_assert(kFreeListEnd == MaxInt<uint32_t>(kFieldFirstFreeSize));
+
+  // The value of `free_entries_list_` indicating empty free list and disabled CheckJNI.
+  static constexpr uint32_t kEmptyFreeListAndCheckJniDisabled =
+      FirstFreeField::Update(kFreeListEnd, 0u);  // kFlagCheckJni not set.
+
+  // The number of entries per reference to detect obsolete reference uses with CheckJNI enabled.
+  // The first entry serves as a serial number, one of the remaining entries can hold the actual
+  // reference or the next free index.
+  static constexpr size_t kCheckJniEntriesPerReference = 4u;
+  static_assert(IsPowerOfTwo(kCheckJniEntriesPerReference));
+
+  // The maximum total table size we allow.
+  static constexpr size_t kMaxTableSizeInBytes = 128 * MB;
+  static_assert(IsPowerOfTwo(kMaxTableSizeInBytes));
+  static_assert(IsPowerOfTwo(sizeof(LrtEntry)));
+  static constexpr size_t kMaxTableSize = kMaxTableSizeInBytes / sizeof(LrtEntry);
+
+  static IndirectRef ToIndirectRef(LrtEntry* entry) {
+    // The `IndirectRef` can be used to directly access the underlying `GcRoot<>`.
+    DCHECK_EQ(reinterpret_cast<GcRoot<mirror::Object>*>(entry), entry->GetRootAddress());
+    return reinterpret_cast<IndirectRef>(
+        reinterpret_cast<uintptr_t>(entry) | static_cast<uintptr_t>(kLocal));
+  }
+
+  static LrtEntry* ToLrtEntry(IndirectRef iref) {
+    DCHECK_EQ(IndirectReferenceTable::GetIndirectRefKind(iref), kLocal);
+    return IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(iref);
+  }
+
+  static constexpr size_t GetTableSize(size_t table_index) {
+    // First two tables have size `kSmallLrtEntries`, then it doubles for subsequent tables.
+    return kSmallLrtEntries << (table_index != 0u ? table_index - 1u : 0u);
+  }
+
+  static constexpr size_t NumTablesForSize(size_t size) {
+    DCHECK_GE(size, kSmallLrtEntries);
+    DCHECK(IsPowerOfTwo(size));
+    return 1u + WhichPowerOf2(size / kSmallLrtEntries);
+  }
+
+  static constexpr size_t MaxSmallTables() {
+    return NumTablesForSize(kPageSize / sizeof(LrtEntry));
+  }
+
+  LrtEntry* GetEntry(size_t entry_index) const {
+    DCHECK_LT(entry_index, max_entries_);
+    if (LIKELY(small_table_ != nullptr)) {
+      DCHECK_LT(entry_index, kSmallLrtEntries);
+      DCHECK_EQ(max_entries_, kSmallLrtEntries);
+      return &small_table_[entry_index];
+    }
+    size_t table_start_index =
+        (entry_index < kSmallLrtEntries) ? 0u : TruncToPowerOfTwo(entry_index);
+    size_t table_index =
+        (entry_index < kSmallLrtEntries) ? 0u : NumTablesForSize(table_start_index);
+    LrtEntry* table = tables_[table_index];
+    return &table[entry_index - table_start_index];
+  }
+
+  // Get the entry index for a local reference. Note that this may be higher than
+  // the current segment state. Returns maximum uint32 value if the reference does not
+  // point to one of the internal tables.
+  uint32_t GetReferenceEntryIndex(IndirectRef iref) const;
+
+  static LrtEntry* GetCheckJniSerialNumberEntry(LrtEntry* entry) {
+    return AlignDown(entry, kCheckJniEntriesPerReference * sizeof(LrtEntry));
+  }
+
+  static uint32_t IncrementSerialNumber(LrtEntry* serial_number_entry)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  static bool IsValidSerialNumber(uint32_t serial_number) {
+    return serial_number != 0u && serial_number < kCheckJniEntriesPerReference;
+  }
+
+  // Debug mode check that the reference is valid.
+  void DCheckValidReference(IndirectRef iref) const REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Resize the backing table to be at least `new_size` elements long. The `new_size`
+  // must be larger than the current size. After return max_entries_ >= new_size.
+  bool Resize(size_t new_size, std::string* error_msg);
+
+  // Extract the first free index from `free_entries_list_`.
+  uint32_t GetFirstFreeIndex() const {
+    return FirstFreeField::Decode(free_entries_list_);
+  }
+
+  // Remove popped free entries from the list.
+  // Called only if `free_entries_list_` points to a popped entry.
+  template <typename EntryGetter>
+  void PrunePoppedFreeEntries(EntryGetter&& get_entry);
+
+  // Helper template function for visiting roots.
+  template <typename Visitor>
+  void VisitRootsInternal(Visitor&& visitor) const REQUIRES_SHARED(Locks::mutator_lock_);
+
+  /// semi-public - read/write by jni down calls.
+  LRTSegmentState segment_state_;
+
+  // The maximum number of entries (modulo resizing).
+  uint32_t max_entries_;
+
+  // The singly-linked list of free nodes.
+  // We use entry indexes instead of pointers and `kFreeListEnd` instead of null indicates
+  // the end of the list. See `LocalReferenceTable::GetEntry()` and `LrtEntry::GetNextFree().
+  //
+  // We use the lowest bit to record whether CheckJNI is enabled. This helps us
+  // check that the list is empty and CheckJNI is disabled in a single comparison.
+  uint32_t free_entries_list_;
+
+  // Individual tables.
+  // As long as we have only one small table, we use `small_table_` to avoid an extra load
+  // from another heap allocated location, otherwise we set it to null and use `tables_`.
+  LrtEntry* small_table_;  // For optimizing the fast-path.
+  dchecked_vector<LrtEntry*> tables_;
+
+  // Mem maps where we store tables allocated directly with `MemMap`
+  // rather than the `SmallLrtAllocator`.
+  dchecked_vector<MemMap> table_mem_maps_;
+};
+
+}  // namespace jni
+}  // namespace art
+
+#endif  // ART_RUNTIME_JNI_LOCAL_REFERENCE_TABLE_H_
diff --git a/runtime/jni/local_reference_table_test.cc b/runtime/jni/local_reference_table_test.cc
new file mode 100644
index 0000000..32c6d48
--- /dev/null
+++ b/runtime/jni/local_reference_table_test.cc
@@ -0,0 +1,1014 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "local_reference_table-inl.h"
+
+#include "android-base/stringprintf.h"
+
+#include "class_root-inl.h"
+#include "common_runtime_test.h"
+#include "mirror/class-alloc-inl.h"
+#include "mirror/object-inl.h"
+#include "scoped_thread_state_change-inl.h"
+
+namespace art {
+namespace jni {
+
+using android::base::StringPrintf;
+
+class LocalReferenceTableTest : public CommonRuntimeTest {
+ protected:
+  LocalReferenceTableTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
+  static void CheckDump(LocalReferenceTable* lrt, size_t num_objects, size_t num_unique)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void BasicTest(bool check_jni, size_t max_count);
+  void BasicHolesTest(bool check_jni, size_t max_count);
+  void BasicResizeTest(bool check_jni, size_t max_count);
+  void TestAddRemove(bool check_jni, size_t max_count, size_t fill_count = 0u);
+  void TestAddRemoveMixed(bool start_check_jni);
+};
+
+void LocalReferenceTableTest::CheckDump(
+    LocalReferenceTable* lrt, size_t num_objects, size_t num_unique) {
+  std::ostringstream oss;
+  lrt->Dump(oss);
+  if (num_objects == 0) {
+    EXPECT_EQ(oss.str().find("java.lang.Object"), std::string::npos) << oss.str();
+  } else if (num_objects == 1) {
+    EXPECT_NE(oss.str().find("1 of java.lang.Object"), std::string::npos) << oss.str();
+  } else {
+    EXPECT_NE(oss.str().find(StringPrintf("%zd of java.lang.Object (%zd unique instances)",
+                                          num_objects, num_unique)),
+              std::string::npos)
+                  << "\n Expected number of objects: " << num_objects
+                  << "\n Expected unique objects: " << num_unique << "\n"
+                  << oss.str();
+  }
+}
+
+void LocalReferenceTableTest::BasicTest(bool check_jni, size_t max_count) {
+  // This will lead to error messages in the log.
+  ScopedLogSeverity sls(LogSeverity::FATAL);
+
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<5> hs(soa.Self());
+  Handle<mirror::Class> c = hs.NewHandle(GetClassRoot<mirror::Object>());
+  ASSERT_TRUE(c != nullptr);
+  Handle<mirror::Object> obj0 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj0 != nullptr);
+  Handle<mirror::Object> obj1 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj1 != nullptr);
+  Handle<mirror::Object> obj2 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj2 != nullptr);
+  Handle<mirror::Object> obj3 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj3 != nullptr);
+
+  std::string error_msg;
+  LocalReferenceTable lrt(check_jni);
+  bool success = lrt.Initialize(max_count, &error_msg);
+  ASSERT_TRUE(success) << error_msg;
+
+  const LRTSegmentState cookie = kLRTFirstSegment;
+
+  CheckDump(&lrt, 0, 0);
+
+  if (check_jni) {
+    IndirectRef bad_iref = (IndirectRef) 0x11110;
+    EXPECT_FALSE(lrt.Remove(cookie, bad_iref)) << "unexpectedly successful removal";
+  }
+
+  // Add three, check, remove in the order in which they were added.
+  IndirectRef iref0 = lrt.Add(cookie, obj0.Get(), &error_msg);
+  EXPECT_TRUE(iref0 != nullptr);
+  CheckDump(&lrt, 1, 1);
+  IndirectRef iref1 = lrt.Add(cookie, obj1.Get(), &error_msg);
+  EXPECT_TRUE(iref1 != nullptr);
+  CheckDump(&lrt, 2, 2);
+  IndirectRef iref2 = lrt.Add(cookie, obj2.Get(), &error_msg);
+  EXPECT_TRUE(iref2 != nullptr);
+  CheckDump(&lrt, 3, 3);
+
+  EXPECT_OBJ_PTR_EQ(obj0.Get(), lrt.Get(iref0));
+  EXPECT_OBJ_PTR_EQ(obj1.Get(), lrt.Get(iref1));
+  EXPECT_OBJ_PTR_EQ(obj2.Get(), lrt.Get(iref2));
+
+  EXPECT_TRUE(lrt.Remove(cookie, iref0));
+  CheckDump(&lrt, 2, 2);
+  EXPECT_TRUE(lrt.Remove(cookie, iref1));
+  CheckDump(&lrt, 1, 1);
+  EXPECT_TRUE(lrt.Remove(cookie, iref2));
+  CheckDump(&lrt, 0, 0);
+
+  // Table should be empty now.
+  EXPECT_EQ(0U, lrt.Capacity());
+
+  // Check that the entry off the end of the list is not valid.
+  // (CheckJNI shall abort for such entries.)
+  EXPECT_FALSE(lrt.IsValidReference(iref0, &error_msg));
+
+  // Add three, remove in the opposite order.
+  iref0 = lrt.Add(cookie, obj0.Get(), &error_msg);
+  EXPECT_TRUE(iref0 != nullptr);
+  iref1 = lrt.Add(cookie, obj1.Get(), &error_msg);
+  EXPECT_TRUE(iref1 != nullptr);
+  iref2 = lrt.Add(cookie, obj2.Get(), &error_msg);
+  EXPECT_TRUE(iref2 != nullptr);
+  CheckDump(&lrt, 3, 3);
+
+  ASSERT_TRUE(lrt.Remove(cookie, iref2));
+  CheckDump(&lrt, 2, 2);
+  ASSERT_TRUE(lrt.Remove(cookie, iref1));
+  CheckDump(&lrt, 1, 1);
+  ASSERT_TRUE(lrt.Remove(cookie, iref0));
+  CheckDump(&lrt, 0, 0);
+
+  // Table should be empty now.
+  ASSERT_EQ(0U, lrt.Capacity());
+
+  // Add three, remove middle / middle / bottom / top.  (Second attempt
+  // to remove middle should fail.)
+  iref0 = lrt.Add(cookie, obj0.Get(), &error_msg);
+  EXPECT_TRUE(iref0 != nullptr);
+  iref1 = lrt.Add(cookie, obj1.Get(), &error_msg);
+  EXPECT_TRUE(iref1 != nullptr);
+  iref2 = lrt.Add(cookie, obj2.Get(), &error_msg);
+  EXPECT_TRUE(iref2 != nullptr);
+  CheckDump(&lrt, 3, 3);
+
+  ASSERT_EQ(3U, lrt.Capacity());
+
+  ASSERT_TRUE(lrt.Remove(cookie, iref1));
+  CheckDump(&lrt, 2, 2);
+  if (check_jni) {
+    ASSERT_FALSE(lrt.Remove(cookie, iref1));
+    CheckDump(&lrt, 2, 2);
+  }
+
+  // Check that the reference to the hole is not valid.
+  EXPECT_FALSE(lrt.IsValidReference(iref1, &error_msg));
+
+  ASSERT_TRUE(lrt.Remove(cookie, iref2));
+  CheckDump(&lrt, 1, 1);
+  ASSERT_TRUE(lrt.Remove(cookie, iref0));
+  CheckDump(&lrt, 0, 0);
+
+  // Table should be empty now.
+  ASSERT_EQ(0U, lrt.Capacity());
+
+  // Add four entries.  Remove #1, add new entry, verify that table size
+  // is still 4 (i.e. holes are getting filled).  Remove #1 and #3, verify
+  // that we delete one and don't hole-compact the other.
+  iref0 = lrt.Add(cookie, obj0.Get(), &error_msg);
+  EXPECT_TRUE(iref0 != nullptr);
+  iref1 = lrt.Add(cookie, obj1.Get(), &error_msg);
+  EXPECT_TRUE(iref1 != nullptr);
+  iref2 = lrt.Add(cookie, obj2.Get(), &error_msg);
+  EXPECT_TRUE(iref2 != nullptr);
+  IndirectRef iref3 = lrt.Add(cookie, obj3.Get(), &error_msg);
+  EXPECT_TRUE(iref3 != nullptr);
+  CheckDump(&lrt, 4, 4);
+
+  ASSERT_TRUE(lrt.Remove(cookie, iref1));
+  CheckDump(&lrt, 3, 3);
+
+  iref1 = lrt.Add(cookie, obj1.Get(), &error_msg);
+  EXPECT_TRUE(iref1 != nullptr);
+
+  ASSERT_EQ(4U, lrt.Capacity()) << "hole not filled";
+  CheckDump(&lrt, 4, 4);
+
+  ASSERT_TRUE(lrt.Remove(cookie, iref1));
+  CheckDump(&lrt, 3, 3);
+  ASSERT_TRUE(lrt.Remove(cookie, iref3));
+  CheckDump(&lrt, 2, 2);
+
+  ASSERT_EQ(3U, lrt.Capacity()) << "should be 3 after two deletions";
+
+  ASSERT_TRUE(lrt.Remove(cookie, iref2));
+  CheckDump(&lrt, 1, 1);
+  ASSERT_TRUE(lrt.Remove(cookie, iref0));
+  CheckDump(&lrt, 0, 0);
+
+  ASSERT_EQ(0U, lrt.Capacity()) << "not empty after split remove";
+
+  // Add an entry, remove it, add a new entry, and try to use the original
+  // iref.  They have the same slot number but are for different objects.
+  // With the extended checks in place, this should fail.
+  iref0 = lrt.Add(cookie, obj0.Get(), &error_msg);
+  EXPECT_TRUE(iref0 != nullptr);
+  CheckDump(&lrt, 1, 1);
+  ASSERT_TRUE(lrt.Remove(cookie, iref0));
+  CheckDump(&lrt, 0, 0);
+  iref1 = lrt.Add(cookie, obj1.Get(), &error_msg);
+  EXPECT_TRUE(iref1 != nullptr);
+  CheckDump(&lrt, 1, 1);
+  if (check_jni) {
+    ASSERT_FALSE(lrt.Remove(cookie, iref0)) << "mismatched del succeeded";
+    CheckDump(&lrt, 1, 1);
+  }
+  ASSERT_TRUE(lrt.Remove(cookie, iref1)) << "switched del failed";
+  ASSERT_EQ(0U, lrt.Capacity()) << "switching del not empty";
+  CheckDump(&lrt, 0, 0);
+
+  // Same as above, but with the same object.  A more rigorous checker
+  // (e.g. with slot serialization) will catch this.
+  iref0 = lrt.Add(cookie, obj0.Get(), &error_msg);
+  EXPECT_TRUE(iref0 != nullptr);
+  CheckDump(&lrt, 1, 1);
+  ASSERT_TRUE(lrt.Remove(cookie, iref0));
+  CheckDump(&lrt, 0, 0);
+  iref1 = lrt.Add(cookie, obj0.Get(), &error_msg);
+  EXPECT_TRUE(iref1 != nullptr);
+  CheckDump(&lrt, 1, 1);
+  if (iref0 != iref1) {
+    // Try 0, should not work.
+    ASSERT_FALSE(lrt.Remove(cookie, iref0)) << "temporal del succeeded";
+  }
+  ASSERT_TRUE(lrt.Remove(cookie, iref1)) << "temporal cleanup failed";
+  ASSERT_EQ(0U, lrt.Capacity()) << "temporal del not empty";
+  CheckDump(&lrt, 0, 0);
+
+  // Stale reference is not valid.
+  iref0 = lrt.Add(cookie, obj0.Get(), &error_msg);
+  EXPECT_TRUE(iref0 != nullptr);
+  CheckDump(&lrt, 1, 1);
+  ASSERT_TRUE(lrt.Remove(cookie, iref0));
+  EXPECT_FALSE(lrt.IsValidReference(iref0, &error_msg)) << "stale lookup succeeded";
+  CheckDump(&lrt, 0, 0);
+
+  // Test table resizing.
+  // These ones fit...
+  static const size_t kTableInitial = max_count / 2;
+  IndirectRef manyRefs[kTableInitial];
+  for (size_t i = 0; i < kTableInitial; i++) {
+    manyRefs[i] = lrt.Add(cookie, obj0.Get(), &error_msg);
+    ASSERT_TRUE(manyRefs[i] != nullptr) << "Failed adding " << i;
+    CheckDump(&lrt, i + 1, 1);
+  }
+  // ...this one causes overflow.
+  iref0 = lrt.Add(cookie, obj0.Get(), &error_msg);
+  ASSERT_TRUE(iref0 != nullptr);
+  ASSERT_EQ(kTableInitial + 1, lrt.Capacity());
+  CheckDump(&lrt, kTableInitial + 1, 1);
+
+  for (size_t i = 0; i < kTableInitial; i++) {
+    ASSERT_TRUE(lrt.Remove(cookie, manyRefs[i])) << "failed removing " << i;
+    CheckDump(&lrt, kTableInitial - i, 1);
+  }
+  // Because of removal order, should have 11 entries, 10 of them holes.
+  ASSERT_EQ(kTableInitial + 1, lrt.Capacity());
+
+  ASSERT_TRUE(lrt.Remove(cookie, iref0)) << "multi-remove final failed";
+
+  ASSERT_EQ(0U, lrt.Capacity()) << "multi-del not empty";
+  CheckDump(&lrt, 0, 0);
+}
+
+TEST_F(LocalReferenceTableTest, BasicTest) {
+  BasicTest(/*check_jni=*/ false, /*max_count=*/ 20u);
+  BasicTest(/*check_jni=*/ false, /*max_count=*/ kSmallLrtEntries);
+  BasicTest(/*check_jni=*/ false, /*max_count=*/ 2u * kSmallLrtEntries);
+}
+
+TEST_F(LocalReferenceTableTest, BasicTestCheckJNI) {
+  BasicTest(/*check_jni=*/ true, /*max_count=*/ 20u);
+  BasicTest(/*check_jni=*/ true, /*max_count=*/ kSmallLrtEntries);
+  BasicTest(/*check_jni=*/ true, /*max_count=*/ 2u * kSmallLrtEntries);
+}
+
+void LocalReferenceTableTest::BasicHolesTest(bool check_jni, size_t max_count) {
+  // Test the explicitly named cases from the LRT implementation:
+  //
+  // 1) Segment with holes (current_num_holes_ > 0), push new segment, add/remove reference
+  // 2) Segment with holes (current_num_holes_ > 0), pop segment, add/remove reference
+  // 3) Segment with holes (current_num_holes_ > 0), push new segment, pop segment, add/remove
+  //    reference
+  // 4) Empty segment, push new segment, create a hole, pop a segment, add/remove a reference
+  // 5) Base segment, push new segment, create a hole, pop a segment, push new segment, add/remove
+  //    reference
+
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<6> hs(soa.Self());
+  Handle<mirror::Class> c = hs.NewHandle(GetClassRoot<mirror::Object>());
+  ASSERT_TRUE(c != nullptr);
+  Handle<mirror::Object> obj0 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj0 != nullptr);
+  Handle<mirror::Object> obj1 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj1 != nullptr);
+  Handle<mirror::Object> obj2 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj2 != nullptr);
+  Handle<mirror::Object> obj3 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj3 != nullptr);
+  Handle<mirror::Object> obj4 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj4 != nullptr);
+
+  std::string error_msg;
+
+  // 1) Segment with holes (current_num_holes_ > 0), push new segment, add/remove reference.
+  {
+    LocalReferenceTable lrt(check_jni);
+    bool success = lrt.Initialize(max_count, &error_msg);
+    ASSERT_TRUE(success) << error_msg;
+
+    const LRTSegmentState cookie0 = kLRTFirstSegment;
+
+    CheckDump(&lrt, 0, 0);
+
+    IndirectRef iref0 = lrt.Add(cookie0, obj0.Get(), &error_msg);
+    IndirectRef iref1 = lrt.Add(cookie0, obj1.Get(), &error_msg);
+    IndirectRef iref2 = lrt.Add(cookie0, obj2.Get(), &error_msg);
+
+    EXPECT_TRUE(lrt.Remove(cookie0, iref1));
+
+    // New segment.
+    const LRTSegmentState cookie1 = lrt.GetSegmentState();
+
+    IndirectRef iref3 = lrt.Add(cookie1, obj3.Get(), &error_msg);
+
+    // Must not have filled the previous hole.
+    EXPECT_EQ(lrt.Capacity(), 4u);
+    EXPECT_FALSE(lrt.IsValidReference(iref1, &error_msg));
+    CheckDump(&lrt, 3, 3);
+
+    UNUSED(iref0, iref1, iref2, iref3);
+  }
+
+  // 2) Segment with holes (current_num_holes_ > 0), pop segment, add/remove reference
+  {
+    LocalReferenceTable lrt(check_jni);
+    bool success = lrt.Initialize(max_count, &error_msg);
+    ASSERT_TRUE(success) << error_msg;
+
+    const LRTSegmentState cookie0 = kLRTFirstSegment;
+
+    CheckDump(&lrt, 0, 0);
+
+    IndirectRef iref0 = lrt.Add(cookie0, obj0.Get(), &error_msg);
+
+    // New segment.
+    const LRTSegmentState cookie1 = lrt.GetSegmentState();
+
+    IndirectRef iref1 = lrt.Add(cookie1, obj1.Get(), &error_msg);
+    IndirectRef iref2 = lrt.Add(cookie1, obj2.Get(), &error_msg);
+    IndirectRef iref3 = lrt.Add(cookie1, obj3.Get(), &error_msg);
+
+    EXPECT_TRUE(lrt.Remove(cookie1, iref2));
+
+    // Pop segment.
+    lrt.SetSegmentState(cookie1);
+
+    IndirectRef iref4 = lrt.Add(cookie1, obj4.Get(), &error_msg);
+
+    EXPECT_EQ(lrt.Capacity(), 2u);
+    EXPECT_FALSE(lrt.IsValidReference(iref2, &error_msg));
+    CheckDump(&lrt, 2, 2);
+
+    UNUSED(iref0, iref1, iref2, iref3, iref4);
+  }
+
+  // 3) Segment with holes (current_num_holes_ > 0), push new segment, pop segment, add/remove
+  //    reference.
+  {
+    LocalReferenceTable lrt(check_jni);
+    bool success = lrt.Initialize(max_count, &error_msg);
+    ASSERT_TRUE(success) << error_msg;
+
+    const LRTSegmentState cookie0 = kLRTFirstSegment;
+
+    CheckDump(&lrt, 0, 0);
+
+    IndirectRef iref0 = lrt.Add(cookie0, obj0.Get(), &error_msg);
+
+    // New segment.
+    const LRTSegmentState cookie1 = lrt.GetSegmentState();
+
+    IndirectRef iref1 = lrt.Add(cookie1, obj1.Get(), &error_msg);
+    IndirectRef iref2 = lrt.Add(cookie1, obj2.Get(), &error_msg);
+
+    EXPECT_TRUE(lrt.Remove(cookie1, iref1));
+
+    // New segment.
+    const LRTSegmentState cookie2 = lrt.GetSegmentState();
+
+    IndirectRef iref3 = lrt.Add(cookie2, obj3.Get(), &error_msg);
+
+    // Pop segment.
+    lrt.SetSegmentState(cookie2);
+
+    IndirectRef iref4 = lrt.Add(cookie1, obj4.Get(), &error_msg);
+
+    EXPECT_EQ(lrt.Capacity(), 3u);
+    if (check_jni) {
+      EXPECT_FALSE(lrt.IsValidReference(iref1, &error_msg));
+    }
+    CheckDump(&lrt, 3, 3);
+
+    UNUSED(iref0, iref1, iref2, iref3, iref4);
+  }
+
+  // 4) Empty segment, push new segment, create a hole, pop a segment, add/remove a reference.
+  {
+    LocalReferenceTable lrt(check_jni);
+    bool success = lrt.Initialize(max_count, &error_msg);
+    ASSERT_TRUE(success) << error_msg;
+
+    const LRTSegmentState cookie0 = kLRTFirstSegment;
+
+    CheckDump(&lrt, 0, 0);
+
+    IndirectRef iref0 = lrt.Add(cookie0, obj0.Get(), &error_msg);
+
+    // New segment.
+    const LRTSegmentState cookie1 = lrt.GetSegmentState();
+
+    IndirectRef iref1 = lrt.Add(cookie1, obj1.Get(), &error_msg);
+    EXPECT_TRUE(lrt.Remove(cookie1, iref1));
+
+    // Emptied segment, push new one.
+    const LRTSegmentState cookie2 = lrt.GetSegmentState();
+
+    IndirectRef iref2 = lrt.Add(cookie1, obj1.Get(), &error_msg);
+    IndirectRef iref3 = lrt.Add(cookie1, obj2.Get(), &error_msg);
+    IndirectRef iref4 = lrt.Add(cookie1, obj3.Get(), &error_msg);
+
+    EXPECT_TRUE(lrt.Remove(cookie1, iref3));
+
+    // Pop segment.
+    UNUSED(cookie2);
+    lrt.SetSegmentState(cookie1);
+
+    IndirectRef iref5 = lrt.Add(cookie1, obj4.Get(), &error_msg);
+
+    EXPECT_EQ(lrt.Capacity(), 2u);
+    EXPECT_FALSE(lrt.IsValidReference(iref3, &error_msg));
+    CheckDump(&lrt, 2, 2);
+
+    UNUSED(iref0, iref1, iref2, iref3, iref4, iref5);
+  }
+
+  // 5) Base segment, push new segment, create a hole, pop a segment, push new segment, add/remove
+  //    reference
+  {
+    LocalReferenceTable lrt(check_jni);
+    bool success = lrt.Initialize(max_count, &error_msg);
+    ASSERT_TRUE(success) << error_msg;
+
+    const LRTSegmentState cookie0 = kLRTFirstSegment;
+
+    CheckDump(&lrt, 0, 0);
+
+    IndirectRef iref0 = lrt.Add(cookie0, obj0.Get(), &error_msg);
+
+    // New segment.
+    const LRTSegmentState cookie1 = lrt.GetSegmentState();
+
+    IndirectRef iref1 = lrt.Add(cookie1, obj1.Get(), &error_msg);
+    IndirectRef iref2 = lrt.Add(cookie1, obj1.Get(), &error_msg);
+    IndirectRef iref3 = lrt.Add(cookie1, obj2.Get(), &error_msg);
+
+    EXPECT_TRUE(lrt.Remove(cookie1, iref2));
+
+    // Pop segment.
+    lrt.SetSegmentState(cookie1);
+
+    // Push segment.
+    const LRTSegmentState cookie1_second = lrt.GetSegmentState();
+    UNUSED(cookie1_second);
+
+    IndirectRef iref4 = lrt.Add(cookie1, obj3.Get(), &error_msg);
+
+    EXPECT_EQ(lrt.Capacity(), 2u);
+    EXPECT_FALSE(lrt.IsValidReference(iref3, &error_msg));
+    CheckDump(&lrt, 2, 2);
+
+    UNUSED(iref0, iref1, iref2, iref3, iref4);
+  }
+}
+
+TEST_F(LocalReferenceTableTest, BasicHolesTest) {
+  BasicHolesTest(/*check_jni=*/ false, 20u);
+  BasicHolesTest(/*check_jni=*/ false, /*max_count=*/ kSmallLrtEntries);
+  BasicHolesTest(/*check_jni=*/ false, /*max_count=*/ 2u * kSmallLrtEntries);
+}
+
+TEST_F(LocalReferenceTableTest, BasicHolesTestCheckJNI) {
+  BasicHolesTest(/*check_jni=*/ true, 20u);
+  BasicHolesTest(/*check_jni=*/ true, /*max_count=*/ kSmallLrtEntries);
+  BasicHolesTest(/*check_jni=*/ true, /*max_count=*/ 2u * kSmallLrtEntries);
+}
+
+void LocalReferenceTableTest::BasicResizeTest(bool check_jni, size_t max_count) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<2> hs(soa.Self());
+  Handle<mirror::Class> c = hs.NewHandle(GetClassRoot<mirror::Object>());
+  ASSERT_TRUE(c != nullptr);
+  Handle<mirror::Object> obj0 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj0 != nullptr);
+
+  std::string error_msg;
+  LocalReferenceTable lrt(check_jni);
+  bool success = lrt.Initialize(max_count, &error_msg);
+  ASSERT_TRUE(success) << error_msg;
+
+  CheckDump(&lrt, 0, 0);
+  const LRTSegmentState cookie = kLRTFirstSegment;
+
+  for (size_t i = 0; i != max_count + 1; ++i) {
+    lrt.Add(cookie, obj0.Get(), &error_msg);
+  }
+
+  EXPECT_EQ(lrt.Capacity(), max_count + 1);
+}
+
+TEST_F(LocalReferenceTableTest, BasicResizeTest) {
+  BasicResizeTest(/*check_jni=*/ false, 20u);
+  BasicResizeTest(/*check_jni=*/ false, /*max_count=*/ kSmallLrtEntries);
+  BasicResizeTest(/*check_jni=*/ false, /*max_count=*/ 2u * kSmallLrtEntries);
+  BasicResizeTest(/*check_jni=*/ false, /*max_count=*/ kPageSize / sizeof(LrtEntry));
+}
+
+TEST_F(LocalReferenceTableTest, BasicResizeTestCheckJNI) {
+  BasicResizeTest(/*check_jni=*/ true, 20u);
+  BasicResizeTest(/*check_jni=*/ true, /*max_count=*/ kSmallLrtEntries);
+  BasicResizeTest(/*check_jni=*/ true, /*max_count=*/ 2u * kSmallLrtEntries);
+  BasicResizeTest(/*check_jni=*/ true, /*max_count=*/ kPageSize / sizeof(LrtEntry));
+}
+
+void LocalReferenceTableTest::TestAddRemove(bool check_jni, size_t max_count, size_t fill_count) {
+  // This will lead to error messages in the log.
+  ScopedLogSeverity sls(LogSeverity::FATAL);
+
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<9> hs(soa.Self());
+  Handle<mirror::Class> c = hs.NewHandle(GetClassRoot<mirror::Object>());
+  ASSERT_TRUE(c != nullptr);
+  Handle<mirror::Object> obj0 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj0 != nullptr);
+  Handle<mirror::Object> obj0x = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj0x != nullptr);
+  Handle<mirror::Object> obj1 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj1 != nullptr);
+  Handle<mirror::Object> obj1x = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj1x != nullptr);
+  Handle<mirror::Object> obj2 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj2 != nullptr);
+  Handle<mirror::Object> obj2x = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj2x != nullptr);
+  Handle<mirror::Object> obj3 = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj3 != nullptr);
+  Handle<mirror::Object> obj3x = hs.NewHandle(c->AllocObject(soa.Self()));
+  ASSERT_TRUE(obj3x != nullptr);
+
+  std::string error_msg;
+  LocalReferenceTable lrt(check_jni);
+  bool success = lrt.Initialize(max_count, &error_msg);
+  ASSERT_TRUE(success) << error_msg;
+
+  const LRTSegmentState cookie0 = kLRTFirstSegment;
+  for (size_t i = 0; i != fill_count; ++i) {
+    IndirectRef iref = lrt.Add(cookie0, c.Get(), &error_msg);
+    ASSERT_TRUE(iref != nullptr) << error_msg;
+    ASSERT_EQ(i + 1u, lrt.Capacity());
+    EXPECT_OBJ_PTR_EQ(c.Get(), lrt.Get(iref));
+  }
+
+  IndirectRef iref0, iref1, iref2, iref3;
+
+#define ADD_REF(iref, cookie, obj, expected_capacity)             \
+  do {                                                            \
+    (iref) = lrt.Add(cookie, (obj).Get(), &error_msg);            \
+    ASSERT_TRUE((iref) != nullptr) << error_msg;                  \
+    ASSERT_EQ(fill_count + (expected_capacity), lrt.Capacity());  \
+    EXPECT_OBJ_PTR_EQ((obj).Get(), lrt.Get(iref));                \
+  } while (false)
+#define REMOVE_REF(cookie, iref, expected_capacity)               \
+  do {                                                            \
+    ASSERT_TRUE(lrt.Remove(cookie, iref));                        \
+    ASSERT_EQ(fill_count + (expected_capacity), lrt.Capacity());  \
+  } while (false)
+#define POP_SEGMENT(cookie, expected_capacity)                    \
+  do {                                                            \
+    lrt.SetSegmentState(cookie);                                  \
+    ASSERT_EQ(fill_count + (expected_capacity), lrt.Capacity());  \
+  } while (false)
+
+  const LRTSegmentState cookie1 = lrt.GetSegmentState();
+  ADD_REF(iref0, cookie1, obj0, 1u);
+  ADD_REF(iref1, cookie1, obj1, 2u);
+  REMOVE_REF(cookie1, iref1, 1u);  // Remove top entry.
+  if (check_jni) {
+    ASSERT_FALSE(lrt.Remove(cookie1, iref1));
+  }
+  ADD_REF(iref1, cookie1, obj1x, 2u);
+  REMOVE_REF(cookie1, iref0, 2u);  // Create hole.
+  IndirectRef obsolete_iref0 = iref0;
+  if (check_jni) {
+    ASSERT_FALSE(lrt.Remove(cookie1, iref0));
+  }
+  ADD_REF(iref0, cookie1, obj0x, 2u);  // Reuse hole
+  if (check_jni) {
+    ASSERT_FALSE(lrt.Remove(cookie1, obsolete_iref0));
+  }
+
+  // Test addition to the second segment without a hole in the first segment.
+  // Also test removal from the wrong segment here.
+  LRTSegmentState cookie2 = lrt.GetSegmentState();  // Create second segment.
+  ASSERT_FALSE(lrt.Remove(cookie2, iref0));  // Cannot remove from inactive segment.
+  ADD_REF(iref2, cookie2, obj2, 3u);
+  POP_SEGMENT(cookie2, 2u);  // Pop the second segment.
+  if (check_jni) {
+    ASSERT_FALSE(lrt.Remove(cookie1, iref2));  // Cannot remove from popped segment.
+  }
+
+  // Test addition to the second segment with a hole in the first.
+  // Use one more reference in the first segment to allow hitting the small table
+  // overflow path either above or here, based on the provided `fill_count`.
+  ADD_REF(iref2, cookie2, obj2x, 3u);
+  REMOVE_REF(cookie1, iref1, 3u);  // Create hole.
+  cookie2 = lrt.GetSegmentState();  // Create second segment.
+  ADD_REF(iref3, cookie2, obj3, 4u);
+  POP_SEGMENT(cookie2, 3u);  // Pop the second segment.
+  REMOVE_REF(cookie1, iref2, 1u);  // Remove top entry, prune previous entry.
+  ADD_REF(iref1, cookie1, obj1, 2u);
+
+  cookie2 = lrt.GetSegmentState();  // Create second segment.
+  ADD_REF(iref2, cookie2, obj2, 3u);
+  ADD_REF(iref3, cookie2, obj3, 4u);
+  REMOVE_REF(cookie2, iref2, 4u);  // Create hole in second segment.
+  POP_SEGMENT(cookie2, 2u);  // Pop the second segment with hole.
+  ADD_REF(iref2, cookie1, obj2x, 3u);  // Prune free list, use new entry.
+  REMOVE_REF(cookie1, iref2, 2u);
+
+  REMOVE_REF(cookie1, iref0, 2u);  // Create hole.
+  cookie2 = lrt.GetSegmentState();  // Create second segment.
+  ADD_REF(iref2, cookie2, obj2, 3u);
+  ADD_REF(iref3, cookie2, obj3x, 4u);
+  REMOVE_REF(cookie2, iref2, 4u);  // Create hole in second segment.
+  POP_SEGMENT(cookie2, 2u);  // Pop the second segment with hole.
+  ADD_REF(iref0, cookie1, obj0, 2u);  // Prune free list, use remaining entry from free list.
+
+  REMOVE_REF(cookie1, iref0, 2u);  // Create hole.
+  cookie2 = lrt.GetSegmentState();  // Create second segment.
+  ADD_REF(iref2, cookie2, obj2x, 3u);
+  ADD_REF(iref3, cookie2, obj3, 4u);
+  REMOVE_REF(cookie2, iref2, 4u);  // Create hole in second segment.
+  REMOVE_REF(cookie2, iref3, 2u);  // Remove top entry, prune previous entry, keep hole above.
+  POP_SEGMENT(cookie2, 2u);  // Pop the empty second segment.
+  ADD_REF(iref0, cookie1, obj0x, 2u);  // Reuse hole.
+
+#undef REMOVE_REF
+#undef ADD_REF
+}
+
+TEST_F(LocalReferenceTableTest, TestAddRemove) {
+  TestAddRemove(/*check_jni=*/ false, /*max_count=*/ 20u);
+  TestAddRemove(/*check_jni=*/ false, /*max_count=*/ kSmallLrtEntries);
+  TestAddRemove(/*check_jni=*/ false, /*max_count=*/ 2u * kSmallLrtEntries);
+  static_assert(kSmallLrtEntries >= 4u);
+  for (size_t fill_count = kSmallLrtEntries - 4u; fill_count != kSmallLrtEntries; ++fill_count) {
+    TestAddRemove(/*check_jni=*/ false, /*max_count=*/ kSmallLrtEntries, fill_count);
+  }
+}
+
+TEST_F(LocalReferenceTableTest, TestAddRemoveCheckJNI) {
+  TestAddRemove(/*check_jni=*/ true, /*max_count=*/ 20u);
+  TestAddRemove(/*check_jni=*/ true, /*max_count=*/ kSmallLrtEntries);
+  TestAddRemove(/*check_jni=*/ true, /*max_count=*/ 2u * kSmallLrtEntries);
+  static_assert(kSmallLrtEntries >= 4u);
+  for (size_t fill_count = kSmallLrtEntries - 4u; fill_count != kSmallLrtEntries; ++fill_count) {
+    TestAddRemove(/*check_jni=*/ true, /*max_count=*/ kSmallLrtEntries, fill_count);
+  }
+}
+
+void LocalReferenceTableTest::TestAddRemoveMixed(bool start_check_jni) {
+  // This will lead to error messages in the log.
+  ScopedLogSeverity sls(LogSeverity::FATAL);
+
+  ScopedObjectAccess soa(Thread::Current());
+  static constexpr size_t kMaxUniqueRefs = 16;
+  StackHandleScope<kMaxUniqueRefs + 1u> hs(soa.Self());
+  Handle<mirror::Class> c = hs.NewHandle(GetClassRoot<mirror::Object>());
+  ASSERT_TRUE(c != nullptr);
+  std::array<Handle<mirror::Object>, kMaxUniqueRefs> objs;
+  for (size_t i = 0u; i != kMaxUniqueRefs; ++i) {
+    objs[i] = hs.NewHandle(c->AllocObject(soa.Self()));
+    ASSERT_TRUE(objs[i] != nullptr);
+  }
+
+  std::string error_msg;
+  std::array<IndirectRef, kMaxUniqueRefs> irefs;
+  const LRTSegmentState cookie0 = kLRTFirstSegment;
+
+#define ADD_REF(iref, cookie, obj)                                \
+  do {                                                            \
+    (iref) = lrt.Add(cookie, (obj).Get(), &error_msg);            \
+    ASSERT_TRUE((iref) != nullptr) << error_msg;                  \
+    EXPECT_OBJ_PTR_EQ((obj).Get(), lrt.Get(iref));                \
+  } while (false)
+
+  for (size_t split = 1u; split < kMaxUniqueRefs - 1u; ++split) {
+    for (size_t total = split + 1u; total < kMaxUniqueRefs; ++total) {
+      for (size_t deleted_at_start = 0u; deleted_at_start + 1u < split; ++deleted_at_start) {
+        LocalReferenceTable lrt(/*check_jni=*/ start_check_jni);
+        bool success = lrt.Initialize(kSmallLrtEntries, &error_msg);
+        ASSERT_TRUE(success) << error_msg;
+        for (size_t i = 0; i != split; ++i) {
+          ADD_REF(irefs[i], cookie0, objs[i]);
+          ASSERT_EQ(i + 1u, lrt.Capacity());
+        }
+        for (size_t i = 0; i != deleted_at_start; ++i) {
+          ASSERT_TRUE(lrt.Remove(cookie0, irefs[i]));
+          if (lrt.IsCheckJniEnabled()) {
+            ASSERT_FALSE(lrt.Remove(cookie0, irefs[i]));
+          }
+          ASSERT_EQ(split, lrt.Capacity());
+        }
+        lrt.SetCheckJniEnabled(!start_check_jni);
+        // Check top index instead of `Capacity()` after changing the CheckJNI setting.
+        uint32_t split_top_index = lrt.GetSegmentState().top_index;
+        uint32_t last_top_index = split_top_index;
+        for (size_t i = split; i != total; ++i) {
+          ADD_REF(irefs[i], cookie0, objs[i]);
+          ASSERT_LT(last_top_index, lrt.GetSegmentState().top_index);
+          last_top_index = lrt.GetSegmentState().top_index;
+        }
+        for (size_t i = split; i != total; ++i) {
+          ASSERT_TRUE(lrt.Remove(cookie0, irefs[i]));
+          if (lrt.IsCheckJniEnabled()) {
+            ASSERT_FALSE(lrt.Remove(cookie0, irefs[i]));
+          }
+          if (i + 1u != total) {
+            ASSERT_LE(last_top_index, lrt.GetSegmentState().top_index);
+          } else {
+            ASSERT_GT(last_top_index, lrt.GetSegmentState().top_index);
+            ASSERT_LE(split_top_index, lrt.GetSegmentState().top_index);
+          }
+        }
+      }
+    }
+  }
+
+#undef ADD_REF
+}
+
+TEST_F(LocalReferenceTableTest, TestAddRemoveMixed) {
+  TestAddRemoveMixed(/*start_check_jni=*/ false);
+  TestAddRemoveMixed(/*start_check_jni=*/ true);
+}
+
+TEST_F(LocalReferenceTableTest, RegressionTestB276210372) {
+  LocalReferenceTable lrt(/*check_jni=*/ false);
+  std::string error_msg;
+  bool success = lrt.Initialize(kSmallLrtEntries, &error_msg);
+  ASSERT_TRUE(success) << error_msg;
+  ScopedObjectAccess soa(Thread::Current());
+  ObjPtr<mirror::Class> c = GetClassRoot<mirror::Object>();
+
+  // Create the first segment with two references.
+  const LRTSegmentState cookie0 = kLRTFirstSegment;
+  IndirectRef ref0 = lrt.Add(cookie0, c, &error_msg);
+  ASSERT_TRUE(ref0 != nullptr);
+  IndirectRef ref1 = lrt.Add(cookie0, c, &error_msg);
+  ASSERT_TRUE(ref1 != nullptr);
+
+  // Create a second segment with a hole, then pop it.
+  const LRTSegmentState cookieA = lrt.GetSegmentState();
+  IndirectRef ref2a = lrt.Add(cookieA, c, &error_msg);
+  ASSERT_TRUE(ref2a != nullptr);
+  IndirectRef ref3a = lrt.Add(cookieA, c, &error_msg);
+  ASSERT_TRUE(ref3a != nullptr);
+  EXPECT_TRUE(lrt.Remove(cookieA, ref2a));
+  lrt.SetSegmentState(cookieA);
+
+  // Create a hole in the first segment.
+  // There was previously a bug that `Remove()` would not prune the popped free entries,
+  // so the new free entry would point to the hole in the popped segment. The code below
+  // would then overwrite that hole with a new segment, pop that segment, reuse the good
+  // free entry and then crash trying to prune the overwritten hole. b/276210372
+  EXPECT_TRUE(lrt.Remove(cookie0, ref0));
+
+  // Create a second segment again and overwite the old hole, then pop the segment.
+  const LRTSegmentState cookieB = lrt.GetSegmentState();
+  ASSERT_EQ(cookieB.top_index, cookieA.top_index);
+  IndirectRef ref2b = lrt.Add(cookieB, c, &error_msg);
+  ASSERT_TRUE(ref2b != nullptr);
+  lrt.SetSegmentState(cookieB);
+
+  // Reuse the hole in first segment.
+  IndirectRef reused0 = lrt.Add(cookie0, c, &error_msg);
+  ASSERT_TRUE(reused0 != nullptr);
+
+  // Add a new reference.
+  IndirectRef new_ref = lrt.Add(cookie0, c, &error_msg);
+  ASSERT_TRUE(new_ref != nullptr);
+}
+
+TEST_F(LocalReferenceTableTest, RegressionTestB276864369) {
+  LocalReferenceTable lrt(/*check_jni=*/ false);
+  std::string error_msg;
+  bool success = lrt.Initialize(kSmallLrtEntries, &error_msg);
+  ASSERT_TRUE(success) << error_msg;
+  ScopedObjectAccess soa(Thread::Current());
+  ObjPtr<mirror::Class> c = GetClassRoot<mirror::Object>();
+
+  // Add refs to fill all small tables and one bigger table.
+  const LRTSegmentState cookie0 = kLRTFirstSegment;
+  constexpr size_t kRefsPerPage = kPageSize / sizeof(LrtEntry);
+  std::vector<IndirectRef> refs;
+  for (size_t i = 0; i != 2 * kRefsPerPage; ++i) {
+    refs.push_back(lrt.Add(cookie0, c, &error_msg));
+    ASSERT_TRUE(refs.back() != nullptr);
+  }
+
+  // We had a bug in `Trim()` where we would try to skip one more table than available
+  // if the capacity was exactly at the end of table. If the next table was not allocated,
+  // we would hit a `DCHECK()` in `dchecked_vector<>` in debug mode but in release
+  // mode we would proceed to use memory outside the allocated chunk. b/276864369
+  lrt.Trim();
+}
+
+TEST_F(LocalReferenceTableTest, Trim) {
+  LocalReferenceTable lrt(/*check_jni=*/ false);
+  std::string error_msg;
+  bool success = lrt.Initialize(kSmallLrtEntries, &error_msg);
+  ASSERT_TRUE(success) << error_msg;
+  ScopedObjectAccess soa(Thread::Current());
+  ObjPtr<mirror::Class> c = GetClassRoot<mirror::Object>();
+
+  // Add refs to fill all small tables.
+  const LRTSegmentState cookie0 = kLRTFirstSegment;
+  constexpr size_t kRefsPerPage = kPageSize / sizeof(LrtEntry);
+  std::vector<IndirectRef> refs0;
+  for (size_t i = 0; i != kRefsPerPage; ++i) {
+    refs0.push_back(lrt.Add(cookie0, c, &error_msg));
+    ASSERT_TRUE(refs0.back() != nullptr);
+  }
+
+  // Nothing to trim.
+  lrt.Trim();
+  ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs0.back())->IsNull());
+
+  // Add refs to fill the next, page-sized table.
+  std::vector<IndirectRef> refs1;
+  LRTSegmentState cookie1 = lrt.GetSegmentState();
+  for (size_t i = 0; i != kRefsPerPage; ++i) {
+    refs1.push_back(lrt.Add(cookie1, c, &error_msg));
+    ASSERT_TRUE(refs1.back() != nullptr);
+  }
+
+  // Nothing to trim.
+  lrt.Trim();
+  ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs1.back())->IsNull());
+
+  // Pop one reference and try to trim, there is no page to trim.
+  ASSERT_TRUE(lrt.Remove(cookie1, refs1.back()));
+  lrt.Trim();
+  ASSERT_FALSE(
+      IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs1[refs1.size() - 2u])->IsNull());
+
+  // Pop the entire segment with the page-sized table and trim, clearing the page.
+  lrt.SetSegmentState(cookie1);
+  lrt.Trim();
+  for (IndirectRef ref : refs1) {
+    ASSERT_TRUE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
+  }
+  refs1.clear();
+
+  // Add refs to fill the page-sized table and half of the next one.
+  cookie1 = lrt.GetSegmentState();  // Push a new segment.
+  for (size_t i = 0; i != 2 * kRefsPerPage; ++i) {
+    refs1.push_back(lrt.Add(cookie1, c, &error_msg));
+    ASSERT_TRUE(refs1.back() != nullptr);
+  }
+
+  // Add refs to fill the other half of the table with two pages.
+  std::vector<IndirectRef> refs2;
+  const LRTSegmentState cookie2 = lrt.GetSegmentState();
+  for (size_t i = 0; i != kRefsPerPage; ++i) {
+    refs2.push_back(lrt.Add(cookie2, c, &error_msg));
+    ASSERT_TRUE(refs2.back() != nullptr);
+  }
+
+  // Nothing to trim.
+  lrt.Trim();
+  ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs1.back())->IsNull());
+
+  // Pop the last segment with one page worth of references and trim that page.
+  lrt.SetSegmentState(cookie2);
+  lrt.Trim();
+  for (IndirectRef ref : refs2) {
+    ASSERT_TRUE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
+  }
+  refs2.clear();
+  for (IndirectRef ref : refs1) {
+    ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
+  }
+
+  // Pop the middle segment with two pages worth of references, and trim those pages.
+  lrt.SetSegmentState(cookie1);
+  lrt.Trim();
+  for (IndirectRef ref : refs1) {
+    ASSERT_TRUE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
+  }
+  refs1.clear();
+
+  // Pop the first segment with small tables and try to trim. Small tables are never trimmed.
+  lrt.SetSegmentState(cookie0);
+  lrt.Trim();
+  for (IndirectRef ref : refs0) {
+    ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
+  }
+  refs0.clear();
+
+  // Fill small tables and one more reference, then another segment up to 4 pages.
+  for (size_t i = 0; i != kRefsPerPage + 1u; ++i) {
+    refs0.push_back(lrt.Add(cookie0, c, &error_msg));
+    ASSERT_TRUE(refs0.back() != nullptr);
+  }
+  cookie1 = lrt.GetSegmentState();  // Push a new segment.
+  for (size_t i = 0; i != 3u * kRefsPerPage - 1u; ++i) {
+    refs1.push_back(lrt.Add(cookie1, c, &error_msg));
+    ASSERT_TRUE(refs1.back() != nullptr);
+  }
+
+  // Nothing to trim.
+  lrt.Trim();
+  ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs1.back())->IsNull());
+
+  // Pop the middle segment, trim two pages.
+  lrt.SetSegmentState(cookie1);
+  lrt.Trim();
+  for (IndirectRef ref : refs0) {
+    ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
+  }
+  ASSERT_EQ(refs0.size(), lrt.Capacity());
+  for (IndirectRef ref : ArrayRef<IndirectRef>(refs1).SubArray(0u, kRefsPerPage - 1u)) {
+    // Popped but not trimmed as these are at the same page as the last entry in `refs0`.
+    ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
+  }
+  for (IndirectRef ref : ArrayRef<IndirectRef>(refs1).SubArray(kRefsPerPage - 1u)) {
+    ASSERT_TRUE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
+  }
+}
+
+TEST_F(LocalReferenceTableTest, PruneBeforeTrim) {
+  LocalReferenceTable lrt(/*check_jni=*/ false);
+  std::string error_msg;
+  bool success = lrt.Initialize(kSmallLrtEntries, &error_msg);
+  ASSERT_TRUE(success) << error_msg;
+  ScopedObjectAccess soa(Thread::Current());
+  ObjPtr<mirror::Class> c = GetClassRoot<mirror::Object>();
+
+  // Add refs to fill all small tables and one bigger table.
+  const LRTSegmentState cookie0 = kLRTFirstSegment;
+  constexpr size_t kRefsPerPage = kPageSize / sizeof(LrtEntry);
+  std::vector<IndirectRef> refs;
+  for (size_t i = 0; i != 2 * kRefsPerPage; ++i) {
+    refs.push_back(lrt.Add(cookie0, c, &error_msg));
+    ASSERT_TRUE(refs.back() != nullptr);
+  }
+
+  // Nothing to trim.
+  lrt.Trim();
+  ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(refs.back())->IsNull());
+
+  // Create a hole in the last page.
+  IndirectRef removed = refs[refs.size() - 2u];
+  ASSERT_TRUE(lrt.Remove(cookie0, removed));
+
+  // Pop the entire segment and trim. Small tables are not pruned.
+  lrt.SetSegmentState(cookie0);
+  lrt.Trim();
+  for (IndirectRef ref : ArrayRef<IndirectRef>(refs).SubArray(0u, kRefsPerPage)) {
+    ASSERT_FALSE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
+  }
+  for (IndirectRef ref : ArrayRef<IndirectRef>(refs).SubArray(kRefsPerPage)) {
+    ASSERT_TRUE(IndirectReferenceTable::ClearIndirectRefKind<LrtEntry*>(ref)->IsNull());
+  }
+
+  // Add a new reference and check that it reused the first slot rather than the old hole.
+  IndirectRef new_ref = lrt.Add(cookie0, c, &error_msg);
+  ASSERT_TRUE(new_ref != nullptr);
+  ASSERT_NE(new_ref, removed);
+  ASSERT_EQ(new_ref, refs[0]);
+}
+
+}  // namespace jni
+}  // namespace art
diff --git a/runtime/linear_alloc-inl.h b/runtime/linear_alloc-inl.h
new file mode 100644
index 0000000..13dbea1
--- /dev/null
+++ b/runtime/linear_alloc-inl.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 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_RUNTIME_LINEAR_ALLOC_INL_H_
+#define ART_RUNTIME_LINEAR_ALLOC_INL_H_
+
+#include "linear_alloc.h"
+
+#include "base/gc_visited_arena_pool.h"
+#include "thread-current-inl.h"
+
+namespace art {
+
+inline void LinearAlloc::SetFirstObject(void* begin, size_t bytes) const {
+  DCHECK(track_allocations_);
+  if (ArenaAllocator::IsRunningOnMemoryTool()) {
+    bytes += ArenaAllocator::kMemoryToolRedZoneBytes;
+  }
+  uint8_t* end = static_cast<uint8_t*>(begin) + bytes;
+  Arena* arena = allocator_.GetHeadArena();
+  DCHECK_NE(arena, nullptr);
+  // The object would either be in the head arena or the next one.
+  if (UNLIKELY(begin < arena->Begin() || begin >= arena->End())) {
+    arena = arena->Next();
+  }
+  DCHECK(begin >= arena->Begin() && end <= arena->End());
+  down_cast<TrackedArena*>(arena)->SetFirstObject(static_cast<uint8_t*>(begin), end);
+}
+
+inline void LinearAlloc::SetupForPostZygoteFork(Thread* self) {
+  MutexLock mu(self, lock_);
+  DCHECK(track_allocations_);
+  allocator_.ResetCurrentArena();
+}
+
+inline void* LinearAlloc::Realloc(Thread* self,
+                                  void* ptr,
+                                  size_t old_size,
+                                  size_t new_size,
+                                  LinearAllocKind kind) {
+  MutexLock mu(self, lock_);
+  if (track_allocations_) {
+    if (ptr != nullptr) {
+      // Realloc cannot be called on 16-byte aligned as Realloc doesn't guarantee
+      // that. So the header must be immediately prior to ptr.
+      TrackingHeader* header = reinterpret_cast<TrackingHeader*>(ptr) - 1;
+      DCHECK_EQ(header->GetKind(), kind);
+      old_size += sizeof(TrackingHeader);
+      DCHECK_EQ(header->GetSize(), old_size);
+      ptr = header;
+    } else {
+      DCHECK_EQ(old_size, 0u);
+    }
+    new_size += sizeof(TrackingHeader);
+    void* ret = allocator_.Realloc(ptr, old_size, new_size);
+    new (ret) TrackingHeader(new_size, kind);
+    SetFirstObject(ret, new_size);
+    return static_cast<TrackingHeader*>(ret) + 1;
+  } else {
+    return allocator_.Realloc(ptr, old_size, new_size);
+  }
+}
+
+inline void* LinearAlloc::Alloc(Thread* self, size_t size, LinearAllocKind kind) {
+  MutexLock mu(self, lock_);
+  if (track_allocations_) {
+    size += sizeof(TrackingHeader);
+    TrackingHeader* storage = new (allocator_.Alloc(size)) TrackingHeader(size, kind);
+    SetFirstObject(storage, size);
+    return storage + 1;
+  } else {
+    return allocator_.Alloc(size);
+  }
+}
+
+inline void* LinearAlloc::AllocAlign16(Thread* self, size_t size, LinearAllocKind kind) {
+  MutexLock mu(self, lock_);
+  DCHECK_ALIGNED(size, 16);
+  if (track_allocations_) {
+    size_t mem_tool_bytes = ArenaAllocator::IsRunningOnMemoryTool()
+                            ? ArenaAllocator::kMemoryToolRedZoneBytes : 0;
+    uint8_t* ptr = allocator_.CurrentPtr() + sizeof(TrackingHeader);
+    uintptr_t padding =
+        RoundUp(reinterpret_cast<uintptr_t>(ptr), 16) - reinterpret_cast<uintptr_t>(ptr);
+    DCHECK_LT(padding, 16u);
+    size_t required_size = size + sizeof(TrackingHeader) + padding;
+
+    if (allocator_.CurrentArenaUnusedBytes() < required_size + mem_tool_bytes) {
+      // The allocator will require a new arena, which is expected to be
+      // 16-byte aligned.
+      static_assert(ArenaAllocator::kArenaAlignment >= 16,
+                    "Expecting sufficient alignment for new Arena.");
+      required_size = size + RoundUp(sizeof(TrackingHeader), 16);
+    }
+    // Using ArenaAllocator's AllocAlign16 now would disturb the alignment by
+    // trying to make header 16-byte aligned. The alignment requirements are
+    // already addressed here. Now we want allocator to just bump the pointer.
+    ptr = static_cast<uint8_t*>(allocator_.Alloc(required_size));
+    new (ptr) TrackingHeader(required_size, kind, /*is_16_aligned=*/true);
+    SetFirstObject(ptr, required_size);
+    return AlignUp(ptr + sizeof(TrackingHeader), 16);
+  } else {
+    return allocator_.AllocAlign16(size);
+  }
+}
+
+inline size_t LinearAlloc::GetUsedMemory() const {
+  MutexLock mu(Thread::Current(), lock_);
+  return allocator_.BytesUsed();
+}
+
+inline ArenaPool* LinearAlloc::GetArenaPool() {
+  MutexLock mu(Thread::Current(), lock_);
+  return allocator_.GetArenaPool();
+}
+
+inline bool LinearAlloc::Contains(void* ptr) const {
+  MutexLock mu(Thread::Current(), lock_);
+  return allocator_.Contains(ptr);
+}
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_LINEAR_ALLOC_INL_H_
diff --git a/runtime/linear_alloc.cc b/runtime/linear_alloc.cc
deleted file mode 100644
index 3f01fc3..0000000
--- a/runtime/linear_alloc.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 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.
- */
-
-#include "linear_alloc.h"
-
-#include "thread-current-inl.h"
-
-namespace art {
-
-LinearAlloc::LinearAlloc(ArenaPool* pool) : lock_("linear alloc"), allocator_(pool) {
-}
-
-void* LinearAlloc::Realloc(Thread* self, void* ptr, size_t old_size, size_t new_size) {
-  MutexLock mu(self, lock_);
-  return allocator_.Realloc(ptr, old_size, new_size);
-}
-
-void* LinearAlloc::Alloc(Thread* self, size_t size) {
-  MutexLock mu(self, lock_);
-  return allocator_.Alloc(size);
-}
-
-void* LinearAlloc::AllocAlign16(Thread* self, size_t size) {
-  MutexLock mu(self, lock_);
-  return allocator_.AllocAlign16(size);
-}
-
-size_t LinearAlloc::GetUsedMemory() const {
-  MutexLock mu(Thread::Current(), lock_);
-  return allocator_.BytesUsed();
-}
-
-ArenaPool* LinearAlloc::GetArenaPool() {
-  MutexLock mu(Thread::Current(), lock_);
-  return allocator_.GetArenaPool();
-}
-
-bool LinearAlloc::Contains(void* ptr) const {
-  MutexLock mu(Thread::Current(), lock_);
-  return allocator_.Contains(ptr);
-}
-
-bool LinearAlloc::ContainsUnsafe(void* ptr) const {
-  return allocator_.Contains(ptr);
-}
-
-}  // namespace art
diff --git a/runtime/linear_alloc.h b/runtime/linear_alloc.h
index 1d01f84..c40af8a 100644
--- a/runtime/linear_alloc.h
+++ b/runtime/linear_alloc.h
@@ -18,44 +18,99 @@
 #define ART_RUNTIME_LINEAR_ALLOC_H_
 
 #include "base/arena_allocator.h"
+#include "base/casts.h"
 #include "base/mutex.h"
 
 namespace art {
 
 class ArenaPool;
 
-// TODO: Support freeing if we add class unloading.
+enum class LinearAllocKind : uint32_t {
+  kNoGCRoots = 0,  // No GC-root kind should always be 0.
+  kGCRootArray,
+  kArtMethodArray,
+  kArtFieldArray,
+  kDexCacheArray,
+  kArtMethod
+};
+
+// Header for every allocation in LinearAlloc. The header provides the type
+// and size information to the GC for invoking the right visitor.
+class TrackingHeader final {
+ public:
+  static constexpr uint32_t kIs16Aligned = 1;
+  TrackingHeader(size_t size, LinearAllocKind kind, bool is_16_aligned = false)
+      : kind_(kind), size_(dchecked_integral_cast<uint32_t>(size)) {
+    // We need the last bit to store 16-byte alignment flag.
+    CHECK_EQ(size_ & kIs16Aligned, 0u);
+    if (is_16_aligned) {
+      size_ |= kIs16Aligned;
+    }
+  }
+
+  LinearAllocKind GetKind() const { return kind_; }
+  // Since we are linearly allocating and hop from one object to the next during
+  // visits, reading 'size_ == 0' indicates that there are no more objects to
+  // visit in the given page. But ASAN detects it as use-after-poison access.
+  ATTRIBUTE_NO_SANITIZE_ADDRESS size_t GetSize() const { return size_ & ~kIs16Aligned; }
+  bool Is16Aligned() const { return size_ & kIs16Aligned; }
+
+ private:
+  LinearAllocKind kind_;
+  uint32_t size_;
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(TrackingHeader);
+};
+
+std::ostream& operator<<(std::ostream& os, LinearAllocKind value);
+
 class LinearAlloc {
  public:
-  explicit LinearAlloc(ArenaPool* pool);
+  static constexpr size_t kAlignment = 8u;
+  static_assert(kAlignment >= ArenaAllocator::kAlignment);
+  static_assert(sizeof(TrackingHeader) == ArenaAllocator::kAlignment);
 
-  void* Alloc(Thread* self, size_t size) REQUIRES(!lock_);
-  void* AllocAlign16(Thread* self, size_t size) REQUIRES(!lock_);
+  explicit LinearAlloc(ArenaPool* pool, bool track_allocs)
+      : lock_("linear alloc"), allocator_(pool), track_allocations_(track_allocs) {}
+
+  void* Alloc(Thread* self, size_t size, LinearAllocKind kind) REQUIRES(!lock_);
+  void* AllocAlign16(Thread* self, size_t size, LinearAllocKind kind) REQUIRES(!lock_);
 
   // Realloc never frees the input pointer, it is the caller's job to do this if necessary.
-  void* Realloc(Thread* self, void* ptr, size_t old_size, size_t new_size) REQUIRES(!lock_);
+  void* Realloc(Thread* self, void* ptr, size_t old_size, size_t new_size, LinearAllocKind kind)
+      REQUIRES(!lock_);
 
   // Allocate an array of structs of type T.
   template<class T>
-  T* AllocArray(Thread* self, size_t elements) REQUIRES(!lock_) {
-    return reinterpret_cast<T*>(Alloc(self, elements * sizeof(T)));
+  T* AllocArray(Thread* self, size_t elements, LinearAllocKind kind) REQUIRES(!lock_) {
+    return reinterpret_cast<T*>(Alloc(self, elements * sizeof(T), kind));
   }
 
   // Return the number of bytes used in the allocator.
   size_t GetUsedMemory() const REQUIRES(!lock_);
 
   ArenaPool* GetArenaPool() REQUIRES(!lock_);
+  // Force arena allocator to ask for a new arena on next allocation. This
+  // is to preserve private/shared clean pages across zygote fork.
+  void SetupForPostZygoteFork(Thread* self) REQUIRES(!lock_);
 
-  // Return true if the linear alloc contrains an address.
+  // Return true if the linear alloc contains an address.
   bool Contains(void* ptr) const REQUIRES(!lock_);
 
   // Unsafe version of 'Contains' only to be used when the allocator is going
   // to be deleted.
-  bool ContainsUnsafe(void* ptr) const NO_THREAD_SAFETY_ANALYSIS;
+  bool ContainsUnsafe(void* ptr) const NO_THREAD_SAFETY_ANALYSIS {
+    return allocator_.Contains(ptr);
+  }
+
+  // Set the given object as the first object for all the pages where the
+  // page-beginning overlaps with the object.
+  void SetFirstObject(void* begin, size_t bytes) const REQUIRES(lock_);
 
  private:
   mutable Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
   ArenaAllocator allocator_ GUARDED_BY(lock_);
+  const bool track_allocations_;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(LinearAlloc);
 };
diff --git a/runtime/lock_word.h b/runtime/lock_word.h
index 84f45c2..21b40b7 100644
--- a/runtime/lock_word.h
+++ b/runtime/lock_word.h
@@ -183,22 +183,24 @@
 
   LockState GetState() const {
     CheckReadBarrierState();
-    if ((!kUseReadBarrier && UNLIKELY(value_ == 0)) ||
-        (kUseReadBarrier && UNLIKELY((value_ & kGCStateMaskShiftedToggled) == 0))) {
-      return kUnlocked;
-    } else {
-      uint32_t internal_state = (value_ >> kStateShift) & kStateMask;
-      switch (internal_state) {
-        case kStateThinOrUnlocked:
-          return kThinLocked;
-        case kStateHash:
-          return kHashCode;
-        case kStateForwardingAddress:
-          return kForwardingAddress;
-        default:
-          DCHECK_EQ(internal_state, static_cast<uint32_t>(kStateFat));
-          return kFatLocked;
+    if (gUseReadBarrier || gUseUserfaultfd) {
+      if ((value_ & kGCStateMaskShiftedToggled) == 0) {
+        return kUnlocked;
       }
+    } else if (value_ == 0) {
+      return kUnlocked;
+    }
+    uint32_t internal_state = (value_ >> kStateShift) & kStateMask;
+    switch (internal_state) {
+      case kStateThinOrUnlocked:
+        return kThinLocked;
+      case kStateHash:
+        return kHashCode;
+      case kStateForwardingAddress:
+        return kForwardingAddress;
+      default:
+        DCHECK_EQ(internal_state, static_cast<uint32_t>(kStateFat));
+        return kFatLocked;
     }
   }
 
@@ -288,7 +290,7 @@
   void CheckReadBarrierState() const {
     if (kIsDebugBuild && ((value_ >> kStateShift) & kStateMask) != kStateForwardingAddress) {
       uint32_t rb_state = ReadBarrierState();
-      if (!kUseReadBarrier) {
+      if (!gUseReadBarrier) {
         DCHECK_EQ(rb_state, 0U);
       } else {
         DCHECK(rb_state == ReadBarrier::NonGrayState() ||
diff --git a/runtime/managed_stack-inl.h b/runtime/managed_stack-inl.h
index 678be8e..ca983ea 100644
--- a/runtime/managed_stack-inl.h
+++ b/runtime/managed_stack-inl.h
@@ -36,6 +36,7 @@
   CHECK(top_shadow_frame_ != nullptr);
   ShadowFrame* frame = top_shadow_frame_;
   top_shadow_frame_ = frame->GetLink();
+  frame->ClearLink();
   return frame;
 }
 
diff --git a/runtime/managed_stack.h b/runtime/managed_stack.h
index 04a27fe..0e7dfe3 100644
--- a/runtime/managed_stack.h
+++ b/runtime/managed_stack.h
@@ -43,6 +43,8 @@
 // code.
 class PACKED(4) ManagedStack {
  public:
+  static size_t constexpr kTaggedJniSpMask = 0x3;
+
   ManagedStack()
       : tagged_top_quick_frame_(TaggedTopQuickFrame::CreateNotTagged(nullptr)),
         link_(nullptr),
@@ -75,8 +77,12 @@
     return tagged_top_quick_frame_.GetSp();
   }
 
-  bool GetTopQuickFrameTag() const {
-    return tagged_top_quick_frame_.GetTag();
+  bool GetTopQuickFrameGenericJniTag() const {
+    return tagged_top_quick_frame_.GetGenericJniTag();
+  }
+
+  bool GetTopQuickFrameJitJniTag() const {
+    return tagged_top_quick_frame_.GetJitJniTag();
   }
 
   bool HasTopQuickFrame() const {
@@ -89,10 +95,10 @@
     tagged_top_quick_frame_ = TaggedTopQuickFrame::CreateNotTagged(top);
   }
 
-  void SetTopQuickFrameTagged(ArtMethod** top) {
+  void SetTopQuickFrameGenericJniTagged(ArtMethod** top) {
     DCHECK(top_shadow_frame_ == nullptr);
     DCHECK_ALIGNED(top, 4u);
-    tagged_top_quick_frame_ = TaggedTopQuickFrame::CreateTagged(top);
+    tagged_top_quick_frame_ = TaggedTopQuickFrame::CreateGenericJniTagged(top);
   }
 
   static constexpr size_t TaggedTopQuickFrameOffset() {
@@ -129,26 +135,30 @@
       return TaggedTopQuickFrame(reinterpret_cast<uintptr_t>(sp));
     }
 
-    static TaggedTopQuickFrame CreateTagged(ArtMethod** sp) {
+    static TaggedTopQuickFrame CreateGenericJniTagged(ArtMethod** sp) {
       DCHECK_ALIGNED(sp, 4u);
       return TaggedTopQuickFrame(reinterpret_cast<uintptr_t>(sp) | 1u);
     }
 
     // Get SP known to be not tagged and non-null.
     ArtMethod** GetSpKnownNotTagged() const {
-      DCHECK(!GetTag());
+      DCHECK(!GetGenericJniTag() && !GetJitJniTag());
       DCHECK_NE(tagged_sp_, 0u);
       return reinterpret_cast<ArtMethod**>(tagged_sp_);
     }
 
     ArtMethod** GetSp() const {
-      return reinterpret_cast<ArtMethod**>(tagged_sp_ & ~static_cast<uintptr_t>(1u));
+      return reinterpret_cast<ArtMethod**>(tagged_sp_ & ~static_cast<uintptr_t>(kTaggedJniSpMask));
     }
 
-    bool GetTag() const {
+    bool GetGenericJniTag() const {
       return (tagged_sp_ & 1u) != 0u;
     }
 
+    bool GetJitJniTag() const {
+      return (tagged_sp_ & 2u) != 0u;
+    }
+
     uintptr_t GetTaggedSp() const {
       return tagged_sp_;
     }
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
index 1327a24..c8c6ef9 100644
--- a/runtime/method_handles.cc
+++ b/runtime/method_handles.cc
@@ -81,7 +81,7 @@
 ObjPtr<mirror::Class> GetBoxedPrimitiveClass(Primitive::Type type)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ScopedAssertNoThreadSuspension ants(__FUNCTION__);
-  jmethodID m = nullptr;
+  ArtMethod* m = nullptr;
   switch (type) {
 #define CASE_PRIMITIVE(primitive, _, java_name, __)              \
     case primitive:                                              \
@@ -93,7 +93,7 @@
     case Primitive::Type::kPrimVoid:
       return nullptr;
   }
-  return jni::DecodeArtMethod(m)->GetDeclaringClass();
+  return m->GetDeclaringClass();
 }
 
 bool GetUnboxedTypeAndValue(ObjPtr<mirror::Object> o, Primitive::Type* type, JValue* value)
@@ -290,6 +290,13 @@
       return false;
     }
 
+    ObjPtr<mirror::Class> from_obj_type = from_obj->GetClass();
+    Primitive::Type from_primitive_type;
+    if (!GetUnboxedPrimitiveType(from_obj_type, &from_primitive_type)) {
+      ThrowClassCastException(from, to);
+      return false;
+    }
+
     Primitive::Type unboxed_type;
     JValue unboxed_value;
     if (UNLIKELY(!GetUnboxedTypeAndValue(from_obj, &unboxed_type, &unboxed_value))) {
@@ -393,7 +400,7 @@
 
   const char* old_cause = self->StartAssertNoThreadSuspension("MethodHandleInvokeTransform");
   ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
-      CREATE_SHADOW_FRAME(kNumRegsForTransform, &shadow_frame, called_method, /* dex pc */ 0);
+      CREATE_SHADOW_FRAME(kNumRegsForTransform, called_method, /* dex pc */ 0);
   ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
   new_shadow_frame->SetVRegReference(0, method_handle.Get());
   new_shadow_frame->SetVRegReference(1, sf.Get());
@@ -459,7 +466,7 @@
   } else if (handle_kind == mirror::MethodHandle::Kind::kInvokeDirect) {
     // String constructors are a special case, they are replaced with
     // StringFactory methods.
-    if (target_method->IsConstructor() && target_method->GetDeclaringClass()->IsStringClass()) {
+    if (target_method->IsStringConstructor()) {
       DCHECK(handle_type->GetRType()->IsStringClass());
       return WellKnownClasses::StringInitToStringFactory(target_method);
     }
@@ -544,31 +551,30 @@
                                  JValue& value) REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(!Runtime::Current()->IsActiveTransaction());
   static const bool kTransaction = false;         // Not in a transaction.
-  static const bool kAssignabilityCheck = false;  // No access check.
   switch (field_type) {
     case Primitive::kPrimBoolean:
       return
-          DoFieldPutCommon<Primitive::kPrimBoolean, kAssignabilityCheck, kTransaction>(
+          DoFieldPutCommon<Primitive::kPrimBoolean, kTransaction>(
               self, shadow_frame, obj, field, value);
     case Primitive::kPrimByte:
-      return DoFieldPutCommon<Primitive::kPrimByte, kAssignabilityCheck, kTransaction>(
+      return DoFieldPutCommon<Primitive::kPrimByte, kTransaction>(
           self, shadow_frame, obj, field, value);
     case Primitive::kPrimChar:
-      return DoFieldPutCommon<Primitive::kPrimChar, kAssignabilityCheck, kTransaction>(
+      return DoFieldPutCommon<Primitive::kPrimChar, kTransaction>(
           self, shadow_frame, obj, field, value);
     case Primitive::kPrimShort:
-      return DoFieldPutCommon<Primitive::kPrimShort, kAssignabilityCheck, kTransaction>(
+      return DoFieldPutCommon<Primitive::kPrimShort, kTransaction>(
           self, shadow_frame, obj, field, value);
     case Primitive::kPrimInt:
     case Primitive::kPrimFloat:
-      return DoFieldPutCommon<Primitive::kPrimInt, kAssignabilityCheck, kTransaction>(
+      return DoFieldPutCommon<Primitive::kPrimInt, kTransaction>(
           self, shadow_frame, obj, field, value);
     case Primitive::kPrimLong:
     case Primitive::kPrimDouble:
-      return DoFieldPutCommon<Primitive::kPrimLong, kAssignabilityCheck, kTransaction>(
+      return DoFieldPutCommon<Primitive::kPrimLong, kTransaction>(
           self, shadow_frame, obj, field, value);
     case Primitive::kPrimNot:
-      return DoFieldPutCommon<Primitive::kPrimNot, kAssignabilityCheck, kTransaction>(
+      return DoFieldPutCommon<Primitive::kPrimNot, kTransaction>(
           self, shadow_frame, obj, field, value);
     case Primitive::kPrimVoid:
       LOG(FATAL) << "Unreachable: " << field_type;
@@ -625,6 +631,10 @@
     case mirror::MethodHandle::kInstanceGet: {
       size_t obj_reg = operands->GetOperand(0);
       ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(obj_reg);
+      if (obj == nullptr) {
+        ThrowNullPointerException("Receiver is null");
+        return false;
+      }
       MethodHandleFieldGet(self, shadow_frame, obj, field, field_type, result);
       return true;
     }
@@ -648,6 +658,10 @@
           callsite_type->GetPTypes()->Get(kPTypeIndex)->GetPrimitiveType(),
           value_reg);
       ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(obj_reg);
+      if (obj == nullptr) {
+        ThrowNullPointerException("Receiver is null");
+        return false;
+      }
       return MethodHandleFieldPut(self, shadow_frame, obj, field, field_type, value);
     }
     case mirror::MethodHandle::kStaticPut: {
@@ -769,7 +783,7 @@
 
   const char* old_cause = self->StartAssertNoThreadSuspension("DoMethodHandleInvokeMethod");
   ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
-      CREATE_SHADOW_FRAME(num_regs, &shadow_frame, called_method, /* dex pc */ 0);
+      CREATE_SHADOW_FRAME(num_regs, called_method, /* dex pc */ 0);
   ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
   CopyArgumentsFromCallerFrame(shadow_frame, new_shadow_frame, operands, first_dest_reg);
   self->EndAssertNoThreadSuspension(old_cause);
@@ -856,19 +870,13 @@
   if (atc == nullptr || !callsite_type->IsExactMatch(atc->GetMethodType())) {
     // Cached asType adapter does not exist or is for another call site. Call
     // MethodHandle::asType() to get an appropriate adapter.
-    ArtMethod* as_type =
-        jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandle_asType);
-    uint32_t as_type_args[] = {
-        static_cast<uint32_t>(reinterpret_cast<uintptr_t>(method_handle.Get())),
-        static_cast<uint32_t>(reinterpret_cast<uintptr_t>(callsite_type.Get()))};
-    JValue atc_result;
-    as_type->Invoke(self, as_type_args, sizeof(as_type_args), &atc_result, "LL");
-    if (atc_result.GetL() == nullptr) {
+    ArtMethod* as_type = WellKnownClasses::java_lang_invoke_MethodHandle_asType;
+    ObjPtr<mirror::MethodHandle> atc_method_handle = ObjPtr<mirror::MethodHandle>::DownCast(
+        as_type->InvokeVirtual<'L', 'L'>(self, method_handle.Get(), callsite_type.Get()));
+    if (atc_method_handle == nullptr) {
       DCHECK(self->IsExceptionPending());
       return false;
     }
-    ObjPtr<mirror::MethodHandle> atc_method_handle =
-        down_cast<mirror::MethodHandle*>(atc_result.GetL());
     atc.Assign(atc_method_handle);
     DCHECK(!atc.IsNull());
   }
@@ -909,10 +917,9 @@
   const uint16_t num_vregs = callsite_type->NumberOfVRegs();
 
   const char* old_cause = self->StartAssertNoThreadSuspension("EmulatedStackFrame to ShadowFrame");
-  ArtMethod* invoke_exact =
-      jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandle_invokeExact);
+  ArtMethod* invoke_exact = WellKnownClasses::java_lang_invoke_MethodHandle_invokeExact;
   ShadowFrameAllocaUniquePtr shadow_frame =
-      CREATE_SHADOW_FRAME(num_vregs, /*link*/ nullptr, invoke_exact, /*dex_pc*/ 0);
+      CREATE_SHADOW_FRAME(num_vregs, invoke_exact, /*dex_pc*/ 0);
   emulated_frame->WriteToShadowFrame(self, callsite_type, 0, shadow_frame.get());
   self->EndAssertNoThreadSuspension(old_cause);
 
diff --git a/runtime/method_handles_test.cc b/runtime/method_handles_test.cc
index eb3f2ad..588f861 100644
--- a/runtime/method_handles_test.cc
+++ b/runtime/method_handles_test.cc
@@ -71,7 +71,12 @@
   }
 }  // namespace
 
-class MethodHandlesTest : public CommonRuntimeTest {};
+class MethodHandlesTest : public CommonRuntimeTest {
+ protected:
+  MethodHandlesTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+};
 
 //
 // Primitive -> Primitive Conversions
@@ -342,7 +347,7 @@
   value.SetL(cl->FindPrimitiveClass('V'));
   ASSERT_FALSE(TryConversion(soa.Self(), from, to, &value));
   ASSERT_TRUE(soa.Self()->IsExceptionPending());
-  ASSERT_TRUE(IsWrongMethodTypeException(soa.Self()->GetException()));
+  ASSERT_TRUE(IsClassCastException(soa.Self()->GetException()));
   soa.Self()->ClearException();
 }
 
diff --git a/runtime/metrics/reporter.cc b/runtime/metrics/reporter.cc
index a44066e..6fc1a14 100644
--- a/runtime/metrics/reporter.cc
+++ b/runtime/metrics/reporter.cc
@@ -16,11 +16,12 @@
 
 #include "reporter.h"
 
-#include <algorithm>
-
 #include <android-base/parseint.h>
 
+#include <algorithm>
+
 #include "base/flags.h"
+#include "base/stl_util.h"
 #include "oat_file_manager.h"
 #include "runtime.h"
 #include "runtime_options.h"
@@ -126,10 +127,17 @@
 
   // Configure the backends
   if (config_.dump_to_logcat) {
-    backends_.emplace_back(new LogBackend(LogSeverity::INFO));
+    backends_.emplace_back(new LogBackend(std::make_unique<TextFormatter>(), LogSeverity::INFO));
   }
   if (config_.dump_to_file.has_value()) {
-    backends_.emplace_back(new FileBackend(config_.dump_to_file.value()));
+    std::unique_ptr<MetricsFormatter> formatter;
+    if (config_.metrics_format == "xml") {
+      formatter = std::make_unique<XmlFormatter>();
+    } else {
+      formatter = std::make_unique<TextFormatter>();
+    }
+
+    backends_.emplace_back(new FileBackend(std::move(formatter), config_.dump_to_file.value()));
   }
   if (config_.dump_to_statsd) {
     auto backend = CreateStatsdBackend();
@@ -189,12 +197,10 @@
   }
 }
 
-const ArtMetrics* MetricsReporter::GetMetrics() {
-  return runtime_->GetMetrics();
-}
+ArtMetrics* MetricsReporter::GetMetrics() { return runtime_->GetMetrics(); }
 
 void MetricsReporter::ReportMetrics() {
-  const ArtMetrics* metrics = GetMetrics();
+  ArtMetrics* metrics = GetMetrics();
 
   if (!session_started_) {
     for (auto& backend : backends_) {
@@ -203,9 +209,7 @@
     session_started_ = true;
   }
 
-  for (auto& backend : backends_) {
-    metrics->ReportAllMetrics(backend.get());
-  }
+  metrics->ReportAllMetricsAndResetValueMetrics(MakeNonOwningPointerVector(backends_));
 }
 
 void MetricsReporter::UpdateSessionInBackends() {
@@ -291,6 +295,7 @@
       .dump_to_logcat = gFlags.MetricsWriteToLogcat(),
       .dump_to_file = gFlags.MetricsWriteToFile.GetValueOptional(),
       .dump_to_statsd = gFlags.MetricsWriteToStatsd(),
+      .metrics_format = gFlags.MetricsFormat(),
       .period_spec = period_spec,
       .reporting_num_mods = reporting_num_mods,
       .reporting_mods = reporting_mods,
diff --git a/runtime/metrics/reporter.h b/runtime/metrics/reporter.h
index daeaf1f..865815e 100644
--- a/runtime/metrics/reporter.h
+++ b/runtime/metrics/reporter.h
@@ -78,6 +78,9 @@
   // If set, provides a file name to enable metrics logging to a file.
   std::optional<std::string> dump_to_file;
 
+  // Provides the desired output format for metrics written to a file.
+  std::string metrics_format;
+
   // The reporting period configuration.
   std::optional<ReportingPeriodSpec> period_spec;
 
@@ -133,7 +136,7 @@
   // Returns the metrics to be reported.
   // This exists only for testing purposes so that we can verify reporting with minimum
   // runtime interference.
-  virtual const ArtMetrics* GetMetrics();
+  virtual ArtMetrics* GetMetrics();
 
   MetricsReporter(const ReportingConfig& config, Runtime* runtime);
 
diff --git a/runtime/metrics/reporter_test.cc b/runtime/metrics/reporter_test.cc
index 3807c77..848a74e 100644
--- a/runtime/metrics/reporter_test.cc
+++ b/runtime/metrics/reporter_test.cc
@@ -34,13 +34,10 @@
 // other runtime setup logic.
 class MockMetricsReporter : public MetricsReporter {
  protected:
-  MockMetricsReporter(const ReportingConfig& config, Runtime* runtime) :
-      MetricsReporter(config, runtime),
-      art_metrics_(new ArtMetrics()) {}
+  MockMetricsReporter(const ReportingConfig& config, Runtime* runtime)
+      : MetricsReporter(config, runtime), art_metrics_(std::make_unique<ArtMetrics>()) {}
 
-  const ArtMetrics* GetMetrics() override {
-    return art_metrics_.get();
-  }
+  ArtMetrics* GetMetrics() override { return art_metrics_.get(); }
 
   std::unique_ptr<ArtMetrics> art_metrics_;
 
@@ -174,9 +171,9 @@
         CompilationReason reason = CompilationReason::kUnknown) {
     // TODO: we should iterate through all the other metrics to make sure they were not
     // reported. However, we don't have an easy to use iteration mechanism over metrics yet.
-    // We should ads one
+    // We should add one
     ASSERT_EQ(backend_->GetReports().size(), size);
-    for (auto report : backend_->GetReports()) {
+    for (const TestBackend::Report& report : backend_->GetReports()) {
       ASSERT_EQ(report.data.Get(DatumId::kClassVerificationCount), with_metrics ? 2u : 0u);
       ASSERT_EQ(report.data.Get(DatumId::kJitMethodCompileCount), with_metrics ? 1u : 0u);
     }
@@ -220,7 +217,7 @@
   WaitForReport(/*report_count=*/ 1, /*sleep_period_ms=*/ 50);
   VerifyReports(/*size=*/ 1, /*with_metrics*/ true);
 
-  // We still should not report at period.
+  // We should still not report continuously.
   ASSERT_FALSE(ShouldContinueReporting());
 }
 
@@ -241,11 +238,11 @@
   WaitForReport(/*report_count=*/ 2, /*sleep_period_ms=*/ 500);
   VerifyReports(/*size=*/ 2, /*with_metrics*/ true);
 
-  // We should not longer report at period.
+  // We should no longer report continuously.
   ASSERT_FALSE(ShouldContinueReporting());
 }
 
-// LARGE TEST: This takes take 2s to run.
+// LARGE TEST: This test take 2s to run.
 // Verifies startup reporting, followed by continuous reporting.
 TEST_F(MetricsReporterTest, StartupAndPeriodContinuous) {
   SetupReporter("S,1,*");
@@ -262,7 +259,7 @@
   WaitForReport(/*report_count=*/ 3, /*sleep_period_ms=*/ 500);
   VerifyReports(/*size=*/ 3, /*with_metrics*/ true);
 
-  // We should keep reporting at period.
+  // We should keep reporting continuously.
   ASSERT_TRUE(ShouldContinueReporting());
 }
 
@@ -282,11 +279,11 @@
   WaitForReport(/*report_count=*/ 1, /*sleep_period_ms=*/ 500);
   VerifyReports(/*size=*/ 1, /*with_metrics*/ true);
 
-  // We should not longer report at period.
+  // We should no longer report continuously.
   ASSERT_FALSE(ShouldContinueReporting());
 }
 
-// LARGE TEST: This takes take 5s to run.
+// LARGE TEST: This test takes 5s to run.
 // Verifies a sequence of reporting, at different interval of times.
 TEST_F(MetricsReporterTest, PeriodContinuous) {
   SetupReporter("1,2,*");
@@ -303,7 +300,7 @@
   WaitForReport(/*report_count=*/ 3, /*sleep_period_ms=*/ 500);
   VerifyReports(/*size=*/ 3, /*with_metrics*/ true);
 
-  // We should keep reporting at period.
+  // We should keep reporting continuously.
   ASSERT_TRUE(ShouldContinueReporting());
 }
 
@@ -323,13 +320,13 @@
   WaitForReport(/*report_count=*/ 1, /*sleep_period_ms=*/ 500);
   VerifyReports(/*size=*/ 1, /*with_metrics*/ false);
 
-  // We should not longer report at period.
+  // We should no longer report continuously.
   ASSERT_FALSE(ShouldContinueReporting());
 }
 
 // Verify we don't start reporting if the sample rate is set to 0.
 TEST_F(MetricsReporterTest, SampleRateDisable) {
-  SetupReporter("1", /*session_id=*/ 1, /*reporting_mods=*/ 0);
+  SetupReporter("1,*", /*session_id=*/ 1, /*reporting_mods=*/ 0);
 
   // The background thread should not start.
   ASSERT_FALSE(MaybeStartBackgroundThread(/*add_metrics=*/ false));
@@ -341,7 +338,7 @@
 // Verify we don't start reporting if the sample rate is low and the session does
 // not meet conditions.
 TEST_F(MetricsReporterTest, SampleRateDisable24) {
-  SetupReporter("1", /*session_id=*/ 125, /*reporting_mods=*/ 24);
+  SetupReporter("1,*", /*session_id=*/ 125, /*reporting_mods=*/ 24);
 
   // The background thread should not start.
   ASSERT_FALSE(MaybeStartBackgroundThread(/*add_metrics=*/ false));
@@ -353,9 +350,9 @@
 // Verify we start reporting if the sample rate and the session meet
 // reporting conditions
 TEST_F(MetricsReporterTest, SampleRateEnable50) {
-  SetupReporter("1", /*session_id=*/ 125, /*reporting_mods=*/ 50);
+  SetupReporter("1,*", /*session_id=*/ 125, /*reporting_mods=*/ 50);
 
-  // The background thread should not start.
+  // The background thread should start.
   ASSERT_TRUE(MaybeStartBackgroundThread(/*add_metrics=*/ false));
 
   ASSERT_FALSE(ShouldReportAtStartup());
@@ -365,7 +362,7 @@
 // Verify we start reporting if the sample rate and the session meet
 // reporting conditions
 TEST_F(MetricsReporterTest, SampleRateEnableAll) {
-  SetupReporter("1", /*session_id=*/ 1099, /*reporting_mods=*/ 100);
+  SetupReporter("1,*", /*session_id=*/ 1099, /*reporting_mods=*/ 100);
 
   // The background thread should start.
   ASSERT_TRUE(MaybeStartBackgroundThread(/*add_metrics=*/ false));
@@ -411,7 +408,7 @@
       const std::string& spec_str,
       bool startup_first,
       bool continuous,
-      std::vector<uint32_t> periods) {
+      const std::vector<uint32_t>& periods) {
     Verify(spec_str, true, startup_first, continuous, periods);
   }
 
@@ -420,7 +417,7 @@
       bool valid,
       bool startup_first,
       bool continuous,
-      std::vector<uint32_t> periods) {
+      const std::vector<uint32_t>& periods) {
     std::string error_msg;
     std::optional<ReportingPeriodSpec> spec = ReportingPeriodSpec::Parse(spec_str, &error_msg);
 
diff --git a/runtime/metrics/statsd.cc b/runtime/metrics/statsd.cc
index f68d507..7002f22 100644
--- a/runtime/metrics/statsd.cc
+++ b/runtime/metrics/statsd.cc
@@ -19,6 +19,10 @@
 #include "arch/instruction_set.h"
 #include "base/compiler_filter.h"
 #include "base/metrics/metrics.h"
+#include "gc/collector/mark_compact.h"
+#include "gc/heap.h"
+#include "gc/space/image_space.h"
+#include "runtime.h"
 #include "statslog_art.h"
 
 #pragma clang diagnostic push
@@ -44,27 +48,49 @@
     case DatumId::kClassVerificationTotalTime:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_CLASS_VERIFICATION_TIME_COUNTER_MICROS);
+    case DatumId::kClassVerificationTotalTimeDelta:
+      return std::make_optional(
+          statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_CLASS_VERIFICATION_TIME_MICROS);
     case DatumId::kJitMethodCompileTotalTime:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_JIT_METHOD_COMPILE_TIME_MICROS);
+    case DatumId::kJitMethodCompileTotalTimeDelta:
+      return std::make_optional(
+          statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_JIT_METHOD_COMPILE_TIME_MICROS);
     case DatumId::kClassLoadingTotalTime:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_CLASS_LOADING_TIME_COUNTER_MICROS);
+    case DatumId::kClassLoadingTotalTimeDelta:
+      return std::make_optional(
+          statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_CLASS_LOADING_TIME_MICROS);
     case DatumId::kClassVerificationCount:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_CLASS_VERIFICATION_COUNT);
+    case DatumId::kClassVerificationCountDelta:
+      return std::make_optional(
+          statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_CLASS_VERIFICATION_COUNT);
     case DatumId::kWorldStopTimeDuringGCAvg:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_WORLD_STOP_TIME_AVG_MICROS);
     case DatumId::kYoungGcCount:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_COUNT);
+    case DatumId::kYoungGcCountDelta:
+      return std::make_optional(
+          statsd::
+              ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_YOUNG_GENERATION_COLLECTION_COUNT);
     case DatumId::kFullGcCount:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_COUNT);
+    case DatumId::kFullGcCountDelta:
+      return std::make_optional(
+          statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_FULL_HEAP_COLLECTION_COUNT);
     case DatumId::kTotalBytesAllocated:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_TOTAL_BYTES_ALLOCATED);
+    case DatumId::kTotalBytesAllocatedDelta:
+      return std::make_optional(
+          statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_TOTAL_BYTES_ALLOCATED);
     case DatumId::kYoungGcCollectionTime:
       return std::make_optional(
           statsd::
@@ -83,6 +109,9 @@
     case DatumId::kJitMethodCompileCount:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_JIT_METHOD_COMPILE_COUNT);
+    case DatumId::kJitMethodCompileCountDelta:
+      return std::make_optional(
+          statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_JIT_METHOD_COMPILE_COUNT);
     case DatumId::kYoungGcTracingThroughput:
       return std::make_optional(
           statsd::
@@ -94,6 +123,9 @@
     case DatumId::kTotalGcCollectionTime:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_TOTAL_COLLECTION_TIME_MS);
+    case DatumId::kTotalGcCollectionTimeDelta:
+      return std::make_optional(
+          statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_TOTAL_COLLECTION_TIME_MS);
     case DatumId::kYoungGcThroughputAvg:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_THROUGHPUT_AVG_MB_PER_SEC);
@@ -106,6 +138,60 @@
     case DatumId::kFullGcTracingThroughputAvg:
       return std::make_optional(
           statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_TRACING_THROUGHPUT_AVG_MB_PER_SEC);
+    case DatumId::kGcWorldStopTime:
+      return std::make_optional(
+          statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_WORLD_STOP_TIME_US);
+    case DatumId::kGcWorldStopTimeDelta:
+      return std::make_optional(
+          statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_WORLD_STOP_TIME_US);
+    case DatumId::kGcWorldStopCount:
+      return std::make_optional(
+          statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_WORLD_STOP_COUNT);
+    case DatumId::kGcWorldStopCountDelta:
+      return std::make_optional(
+          statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_WORLD_STOP_COUNT);
+    case DatumId::kYoungGcScannedBytes:
+      return std::make_optional(
+          statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_SCANNED_BYTES);
+    case DatumId::kYoungGcScannedBytesDelta:
+      return std::make_optional(
+          statsd::
+              ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_YOUNG_GENERATION_COLLECTION_SCANNED_BYTES);
+    case DatumId::kYoungGcFreedBytes:
+      return std::make_optional(
+          statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_FREED_BYTES);
+    case DatumId::kYoungGcFreedBytesDelta:
+      return std::make_optional(
+          statsd::
+              ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_YOUNG_GENERATION_COLLECTION_FREED_BYTES);
+    case DatumId::kYoungGcDuration:
+      return std::make_optional(
+          statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_DURATION_MS);
+    case DatumId::kYoungGcDurationDelta:
+      return std::make_optional(
+          statsd::
+              ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_YOUNG_GENERATION_COLLECTION_DURATION_MS);
+    case DatumId::kFullGcScannedBytes:
+      return std::make_optional(
+          statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_SCANNED_BYTES);
+    case DatumId::kFullGcScannedBytesDelta:
+      return std::make_optional(
+          statsd::
+              ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_FULL_HEAP_COLLECTION_SCANNED_BYTES);
+    case DatumId::kFullGcFreedBytes:
+      return std::make_optional(
+          statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_FREED_BYTES);
+    case DatumId::kFullGcFreedBytesDelta:
+      return std::make_optional(
+          statsd::
+              ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_FULL_HEAP_COLLECTION_FREED_BYTES);
+    case DatumId::kFullGcDuration:
+      return std::make_optional(
+          statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_DURATION_MS);
+    case DatumId::kFullGcDurationDelta:
+      return std::make_optional(
+          statsd::
+              ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_FULL_HEAP_COLLECTION_DURATION_MS);
   }
 }
 
@@ -185,6 +271,9 @@
       return statsd::ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_CMDLINE;
     case CompilationReason::kVdex:
       return statsd::ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_VDEX;
+    case CompilationReason::kBootAfterMainlineUpdate:
+      return statsd::
+          ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE;
   }
 }
 
@@ -196,6 +285,8 @@
       return statsd::ART_DATUM_REPORTED__ISA__ART_ISA_ARM;
     case InstructionSet::kArm64:
       return statsd::ART_DATUM_REPORTED__ISA__ART_ISA_ARM64;
+    case InstructionSet::kRiscv64:
+      return statsd::ART_DATUM_REPORTED__ISA__ART_ISA_RISCV64;
     case InstructionSet::kX86:
       return statsd::ART_DATUM_REPORTED__ISA__ART_ISA_X86;
     case InstructionSet::kX86_64:
@@ -205,6 +296,51 @@
   }
 }
 
+constexpr int32_t EncodeGcCollectorType(gc::CollectorType collector_type) {
+  switch (collector_type) {
+    case gc::CollectorType::kCollectorTypeMS:
+      return statsd::ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_MARK_SWEEP;
+    case gc::CollectorType::kCollectorTypeCMS:
+      return statsd::ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_CONCURRENT_MARK_SWEEP;
+    case gc::CollectorType::kCollectorTypeCMC:
+      return statsd::ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_CONCURRENT_MARK_COMPACT;
+    case gc::CollectorType::kCollectorTypeSS:
+      return statsd::ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_SEMI_SPACE;
+    case gc::kCollectorTypeCC:
+      return statsd::ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_CONCURRENT_COPYING;
+    case gc::kCollectorTypeCCBackground:
+      return statsd::ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_CONCURRENT_COPYING_BACKGROUND;
+    case gc::kCollectorTypeNone:
+    case gc::kCollectorTypeInstrumentation:
+    case gc::kCollectorTypeAddRemoveAppImageSpace:
+    case gc::kCollectorTypeDebugger:
+    case gc::kCollectorTypeHomogeneousSpaceCompact:
+    case gc::kCollectorTypeClassLinker:
+    case gc::kCollectorTypeJitCodeCache:
+    case gc::kCollectorTypeHprof:
+    case gc::kCollectorTypeAddRemoveSystemWeakHolder:
+    case gc::kCollectorTypeGetObjectsAllocated:
+    case gc::kCollectorTypeCriticalSection:
+    case gc::kCollectorTypeHeapTrim:
+      return statsd::ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_UNKNOWN;
+  }
+}
+
+int32_t EncodeUffdMinorFaultSupport() {
+  auto [uffd_supported, minor_fault_supported] = gc::collector::MarkCompact::GetUffdAndMinorFault();
+
+  if (uffd_supported) {
+    if (minor_fault_supported) {
+      return statsd::ART_DATUM_REPORTED__UFFD_SUPPORT__ART_UFFD_SUPPORT_MINOR_FAULT_MODE_SUPPORTED;
+    } else {
+      return statsd::
+          ART_DATUM_REPORTED__UFFD_SUPPORT__ART_UFFD_SUPPORT_MINOR_FAULT_MODE_NOT_SUPPORTED;
+    }
+  } else {
+    return statsd::ART_DATUM_REPORTED__UFFD_SUPPORT__ART_UFFD_SUPPORT_UFFD_NOT_SUPPORTED;
+  }
+}
+
 class StatsdBackend : public MetricsBackend {
  public:
   void BeginOrUpdateSession(const SessionData& session_data) override {
@@ -218,22 +354,41 @@
 
   void ReportCounter(DatumId counter_type, uint64_t value) override {
     std::optional<int32_t> datum_id = EncodeDatumId(counter_type);
-    if (datum_id.has_value()) {
-      statsd::stats_write(
-          statsd::ART_DATUM_REPORTED,
-          session_data_.session_id,
-          session_data_.uid,
-          EncodeCompileFilter(session_data_.compiler_filter),
-          EncodeCompilationReason(session_data_.compilation_reason),
-          current_timestamp_,
-          /*thread_type=*/0,  // TODO: collect and report thread type (0 means UNKNOWN, but that
-                              // constant is not present in all branches)
-          datum_id.value(),
-          static_cast<int64_t>(value),
-          statsd::ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN,
-          statsd::ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_UNKNOWN,
-          EncodeInstructionSet(kRuntimeISA));
+    if (!datum_id.has_value()) {
+      return;
     }
+
+    int32_t atom;
+    switch (counter_type) {
+#define EVENT_METRIC_CASE(name, ...) case DatumId::k##name:
+      ART_EVENT_METRICS(EVENT_METRIC_CASE)
+#undef EVENT_METRIC_CASE
+      atom = statsd::ART_DATUM_REPORTED;
+      break;
+
+#define VALUE_METRIC_CASE(name, type, ...) case DatumId::k##name:
+      ART_VALUE_METRICS(VALUE_METRIC_CASE)
+#undef VALUE_METRIC_CASE
+      atom = statsd::ART_DATUM_DELTA_REPORTED;
+      break;
+    }
+
+    statsd::stats_write(
+        atom,
+        session_data_.session_id,
+        session_data_.uid,
+        EncodeCompileFilter(session_data_.compiler_filter),
+        EncodeCompilationReason(session_data_.compilation_reason),
+        current_timestamp_,
+        0,  // TODO: collect and report thread type (0 means UNKNOWN, but that
+            // constant is not present in all branches)
+        datum_id.value(),
+        static_cast<int64_t>(value),
+        statsd::ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN,
+        statsd::ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_UNKNOWN,
+        EncodeInstructionSet(kRuntimeISA),
+        EncodeGcCollectorType(Runtime::Current()->GetHeap()->GetForegroundCollectorType()),
+        EncodeUffdMinorFaultSupport());
   }
 
   void ReportHistogram(DatumId /*histogram_type*/,
@@ -256,6 +411,20 @@
 
 std::unique_ptr<MetricsBackend> CreateStatsdBackend() { return std::make_unique<StatsdBackend>(); }
 
+void ReportDeviceMetrics() {
+  Runtime* runtime = Runtime::Current();
+  int32_t boot_image_status;
+  if (runtime->GetHeap()->HasBootImageSpace() && !runtime->HasImageWithProfile()) {
+    boot_image_status = statsd::ART_DEVICE_DATUM_REPORTED__BOOT_IMAGE_STATUS__STATUS_FULL;
+  } else if (runtime->GetHeap()->HasBootImageSpace() &&
+             runtime->GetHeap()->GetBootImageSpaces()[0]->GetProfileFiles().empty()) {
+    boot_image_status = statsd::ART_DEVICE_DATUM_REPORTED__BOOT_IMAGE_STATUS__STATUS_MINIMAL;
+  } else {
+    boot_image_status = statsd::ART_DEVICE_DATUM_REPORTED__BOOT_IMAGE_STATUS__STATUS_NONE;
+  }
+  statsd::stats_write(statsd::ART_DEVICE_DATUM_REPORTED, boot_image_status);
+}
+
 }  // namespace metrics
 }  // namespace art
 
diff --git a/runtime/metrics/statsd.h b/runtime/metrics/statsd.h
index a99d510..cb84825 100644
--- a/runtime/metrics/statsd.h
+++ b/runtime/metrics/statsd.h
@@ -27,8 +27,10 @@
 // Statsd is only supported on Android
 #ifdef __ANDROID__
 std::unique_ptr<MetricsBackend> CreateStatsdBackend();
+void ReportDeviceMetrics();
 #else
 inline std::unique_ptr<MetricsBackend> CreateStatsdBackend() { return nullptr; }
+inline void ReportDeviceMetrics() {}
 #endif
 
 }  // namespace metrics
diff --git a/runtime/mirror/array-inl.h b/runtime/mirror/array-inl.h
index b0e77b4..8f81ae5 100644
--- a/runtime/mirror/array-inl.h
+++ b/runtime/mirror/array-inl.h
@@ -36,12 +36,15 @@
   return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 0, 0, pointer_size);
 }
 
-template<VerifyObjectFlags kVerifyFlags>
+template <VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption, bool kIsObjArray>
 inline size_t Array::SizeOf() {
-  // No read barrier is needed for reading a constant primitive field through
-  // constant reference field chain. See ReadBarrierOption.
-  size_t component_size_shift =
-      GetClass<kVerifyFlags, kWithoutReadBarrier>()->GetComponentSizeShift();
+  // When we are certain that this is a object array, then don't fetch shift
+  // from component_type_ as that doesn't work well with userfaultfd GC as the
+  // component-type class may be allocated at a higher address than the array.
+  size_t component_size_shift = kIsObjArray ?
+                                    Primitive::ComponentSizeShift(Primitive::kPrimNot) :
+                                    GetClass<kVerifyFlags, kReadBarrierOption>()
+                                        ->template GetComponentSizeShift<kReadBarrierOption>();
   // Don't need to check this since we already check this in GetClass.
   int32_t component_count =
       GetLength<static_cast<VerifyObjectFlags>(kVerifyFlags & ~kVerifyThis)>();
@@ -98,7 +101,7 @@
   if (kTransactionActive) {
     Runtime::Current()->RecordWriteArray(this, i, GetWithoutChecks(i));
   }
-  DCHECK(CheckIsValidIndex<kVerifyFlags>(i));
+  DCHECK(CheckIsValidIndex<kVerifyFlags>(i)) << i << " " << GetLength<kVerifyFlags>();
   GetData()[i] = value;
 }
 // Backward copy where elements are of aligned appropriately for T. Count is in T sized units.
@@ -241,13 +244,16 @@
   // C style casts here since we sometimes have T be a pointer, or sometimes an integer
   // (for stack traces).
   using ConversionType = typename std::conditional_t<std::is_pointer_v<T>, uintptr_t, T>;
+  // Note: we cast the array directly when unchecked as this code gets called by
+  // runtime_image, which can pass a 64bit pointer and therefore cannot be held
+  // by an ObjPtr.
   if (kPointerSize == PointerSize::k64) {
     uint64_t value =
-        static_cast<uint64_t>(AsLongArrayUnchecked<kVerifyFlags>()->GetWithoutChecks(idx));
+        static_cast<uint64_t>(reinterpret_cast<LongArray*>(this)->GetWithoutChecks(idx));
     return (T) dchecked_integral_cast<ConversionType>(value);
   } else {
     uint32_t value =
-        static_cast<uint32_t>(AsIntArrayUnchecked<kVerifyFlags>()->GetWithoutChecks(idx));
+        static_cast<uint32_t>(reinterpret_cast<IntArray*>(this)->GetWithoutChecks(idx));
     return (T) dchecked_integral_cast<ConversionType>(value);
   }
 }
@@ -262,12 +268,15 @@
 
 template<bool kTransactionActive, bool kCheckTransaction, bool kUnchecked>
 inline void PointerArray::SetElementPtrSize(uint32_t idx, uint64_t element, PointerSize ptr_size) {
+  // Note: we cast the array directly when unchecked as this code gets called by
+  // runtime_image, which can pass a 64bit pointer and therefore cannot be held
+  // by an ObjPtr.
   if (ptr_size == PointerSize::k64) {
-    (kUnchecked ? ObjPtr<LongArray>::DownCast(ObjPtr<Object>(this)) : AsLongArray())->
+    (kUnchecked ? reinterpret_cast<LongArray*>(this) : AsLongArray().Ptr())->
         SetWithoutChecks<kTransactionActive, kCheckTransaction>(idx, element);
   } else {
     uint32_t element32 = dchecked_integral_cast<uint32_t>(element);
-    (kUnchecked ? ObjPtr<IntArray>::DownCast(ObjPtr<Object>(this)) : AsIntArray())
+    (kUnchecked ? reinterpret_cast<IntArray*>(this) : AsIntArray().Ptr())
         ->SetWithoutChecks<kTransactionActive, kCheckTransaction>(idx, element32);
   }
 }
@@ -279,7 +288,7 @@
 }
 
 template <VerifyObjectFlags kVerifyFlags, typename Visitor>
-inline void PointerArray::Fixup(ObjPtr<mirror::PointerArray> dest,
+inline void PointerArray::Fixup(mirror::PointerArray* dest,
                                 PointerSize pointer_size,
                                 const Visitor& visitor) {
   for (size_t i = 0, count = GetLength(); i < count; ++i) {
diff --git a/runtime/mirror/array.h b/runtime/mirror/array.h
index 4bf9dee..5d64167 100644
--- a/runtime/mirror/array.h
+++ b/runtime/mirror/array.h
@@ -58,7 +58,9 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Roles::uninterruptible_);
 
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  template <VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+            ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier,
+            bool kIsObjArray = false>
   size_t SizeOf() REQUIRES_SHARED(Locks::mutator_lock_);
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   ALWAYS_INLINE int32_t GetLength() REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -263,7 +265,7 @@
   // Fixup the pointers in the dest arrays by passing our pointers through the visitor. Only copies
   // to dest if visitor(source_ptr) != source_ptr.
   template <VerifyObjectFlags kVerifyFlags = kVerifyNone, typename Visitor>
-  void Fixup(ObjPtr<mirror::PointerArray> dest, PointerSize pointer_size, const Visitor& visitor)
+  void Fixup(mirror::PointerArray* dest, PointerSize pointer_size, const Visitor& visitor)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Works like memcpy(), except we guarantee not to allow tearing of array values (ie using smaller
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index b6bd22e..e2fd82f 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -498,47 +498,6 @@
   return false;
 }
 
-template <bool throw_on_failure>
-inline bool Class::ResolvedMethodAccessTest(ObjPtr<Class> access_to,
-                                            ArtMethod* method,
-                                            ObjPtr<DexCache> dex_cache,
-                                            uint32_t method_idx,
-                                            InvokeType throw_invoke_type) {
-  DCHECK(throw_on_failure || throw_invoke_type == kStatic);
-  DCHECK(dex_cache != nullptr);
-  if (UNLIKELY(!this->CanAccess(access_to))) {
-    // The referrer class can't access the method's declaring class but may still be able
-    // to access the method if the MethodId specifies an accessible subclass of the declaring
-    // class rather than the declaring class itself.
-    dex::TypeIndex class_idx = dex_cache->GetDexFile()->GetMethodId(method_idx).class_idx_;
-    // The referenced class has already been resolved with the method, but may not be in the dex
-    // cache.
-    ObjPtr<Class> dex_access_to = Runtime::Current()->GetClassLinker()->LookupResolvedType(
-        class_idx,
-        dex_cache,
-        GetClassLoader());
-    DCHECK(dex_access_to != nullptr)
-        << " Could not resolve " << dex_cache->GetDexFile()->StringByTypeIdx(class_idx)
-        << " when checking access to " << method->PrettyMethod() << " from " << PrettyDescriptor();
-    if (UNLIKELY(!this->CanAccess(dex_access_to))) {
-      if (throw_on_failure) {
-        ThrowIllegalAccessErrorClassForMethodDispatch(this,
-                                                      dex_access_to,
-                                                      method,
-                                                      throw_invoke_type);
-      }
-      return false;
-    }
-  }
-  if (LIKELY(this->CanAccessMember(access_to, method->GetAccessFlags()))) {
-    return true;
-  }
-  if (throw_on_failure) {
-    ThrowIllegalAccessErrorMethod(this, method);
-  }
-  return false;
-}
-
 inline bool Class::CanAccessResolvedField(ObjPtr<Class> access_to,
                                           ArtField* field,
                                           ObjPtr<DexCache> dex_cache,
@@ -553,22 +512,6 @@
   return ResolvedFieldAccessTest<true>(access_to, field, dex_cache, field_idx);
 }
 
-inline bool Class::CanAccessResolvedMethod(ObjPtr<Class> access_to,
-                                           ArtMethod* method,
-                                           ObjPtr<DexCache> dex_cache,
-                                           uint32_t method_idx) {
-  return ResolvedMethodAccessTest<false>(access_to, method, dex_cache, method_idx, kStatic);
-}
-
-inline bool Class::CheckResolvedMethodAccess(ObjPtr<Class> access_to,
-                                             ArtMethod* method,
-                                             ObjPtr<DexCache> dex_cache,
-                                             uint32_t method_idx,
-                                             InvokeType throw_invoke_type) {
-  return ResolvedMethodAccessTest<true>(
-      access_to, method, dex_cache, method_idx, throw_invoke_type);
-}
-
 inline bool Class::IsObsoleteVersionOf(ObjPtr<Class> klass) {
   DCHECK(!klass->IsObsoleteObject()) << klass->PrettyClass() << " is obsolete!";
   if (LIKELY(!IsObsoleteObject())) {
@@ -1077,10 +1020,9 @@
   return 1U << GetComponentSizeShift();
 }
 
+template <ReadBarrierOption kReadBarrierOption>
 inline size_t Class::GetComponentSizeShift() {
-  // No read barrier is needed for reading a constant primitive field through
-  // constant reference field. See ReadBarrierOption.
-  return GetComponentType<kDefaultVerifyFlags, kWithoutReadBarrier>()->GetPrimitiveTypeSizeShift();
+  return GetComponentType<kDefaultVerifyFlags, kReadBarrierOption>()->GetPrimitiveTypeSizeShift();
 }
 
 inline bool Class::IsObjectClass() {
@@ -1106,11 +1048,9 @@
   return GetComponentType<kVerifyFlags, kWithoutReadBarrier>() != nullptr;
 }
 
-template<VerifyObjectFlags kVerifyFlags>
+template<VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption>
 inline bool Class::IsObjectArrayClass() {
-  // We do not need a read barrier here as the primitive type is constant,
-  // both from-space and to-space component type classes shall yield the same result.
-  const ObjPtr<Class> component_type = GetComponentType<kVerifyFlags, kWithoutReadBarrier>();
+  const ObjPtr<Class> component_type = GetComponentType<kVerifyFlags, kReadBarrierOption>();
   constexpr VerifyObjectFlags kNewFlags = RemoveThisFlags(kVerifyFlags);
   return component_type != nullptr && !component_type->IsPrimitive<kNewFlags>();
 }
@@ -1255,6 +1195,17 @@
   SetAccessFlagsDuringLinking(flags | kAccHasDefaultMethod);
 }
 
+inline ImTable* Class::FindSuperImt(PointerSize pointer_size) {
+  ObjPtr<mirror::Class> klass = this;
+  while (klass->HasSuperClass()) {
+    klass = klass->GetSuperClass();
+    if (klass->ShouldHaveImt()) {
+      return klass->GetImt(pointer_size);
+    }
+  }
+  return nullptr;
+}
+
 }  // namespace mirror
 }  // namespace art
 
diff --git a/runtime/mirror/class-refvisitor-inl.h b/runtime/mirror/class-refvisitor-inl.h
index 8c85387..ee5c11f 100644
--- a/runtime/mirror/class-refvisitor-inl.h
+++ b/runtime/mirror/class-refvisitor-inl.h
@@ -51,22 +51,39 @@
   }
 }
 
-template<ReadBarrierOption kReadBarrierOption, class Visitor>
+template<ReadBarrierOption kReadBarrierOption, bool kVisitProxyMethod, class Visitor>
 void Class::VisitNativeRoots(Visitor& visitor, PointerSize pointer_size) {
   VisitFields<kReadBarrierOption>([&](ArtField* field) REQUIRES_SHARED(art::Locks::mutator_lock_) {
     field->VisitRoots(visitor);
-    if (kIsDebugBuild && IsResolved()) {
+    if (kIsDebugBuild && !gUseUserfaultfd && IsResolved()) {
       CHECK_EQ(field->GetDeclaringClass<kReadBarrierOption>(), this)
           << GetStatus() << field->GetDeclaringClass()->PrettyClass() << " != " << PrettyClass();
     }
   });
   // Don't use VisitMethods because we don't want to hit the class-ext methods twice.
   for (ArtMethod& method : GetMethods(pointer_size)) {
-    method.VisitRoots<kReadBarrierOption>(visitor, pointer_size);
+    method.VisitRoots<kReadBarrierOption, kVisitProxyMethod>(visitor, pointer_size);
   }
   ObjPtr<ClassExt> ext(GetExtData<kDefaultVerifyFlags, kReadBarrierOption>());
   if (!ext.IsNull()) {
-    ext->VisitNativeRoots<kReadBarrierOption, Visitor>(visitor, pointer_size);
+    ext->VisitNativeRoots<kReadBarrierOption, kVisitProxyMethod>(visitor, pointer_size);
+  }
+}
+
+template<ReadBarrierOption kReadBarrierOption>
+void Class::VisitObsoleteDexCaches(DexCacheVisitor& visitor) {
+  ObjPtr<ClassExt> ext(GetExtData<kDefaultVerifyFlags, kReadBarrierOption>());
+  if (!ext.IsNull()) {
+    ext->VisitDexCaches<kDefaultVerifyFlags, kReadBarrierOption>(visitor);
+  }
+}
+
+template<ReadBarrierOption kReadBarrierOption, class Visitor>
+void Class::VisitObsoleteClass(Visitor& visitor) {
+  ObjPtr<ClassExt> ext(GetExtData<kDefaultVerifyFlags, kReadBarrierOption>());
+  if (!ext.IsNull()) {
+    ObjPtr<Class> klass = ext->GetObsoleteClass<kDefaultVerifyFlags, kReadBarrierOption>();
+    visitor(klass);
   }
 }
 
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 37a4197..6458613 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -27,6 +27,7 @@
 #include "art_method-inl.h"
 #include "base/enums.h"
 #include "base/logging.h"  // For VLOG.
+#include "base/sdk_version.h"
 #include "base/utils.h"
 #include "class-inl.h"
 #include "class_ext-inl.h"
@@ -59,10 +60,6 @@
 
 namespace art {
 
-// TODO: move to own CC file?
-constexpr size_t BitString::kBitSizeAtPosition[BitString::kCapacity];
-constexpr size_t BitString::kCapacity;
-
 namespace mirror {
 
 using android::base::StringPrintf;
@@ -1436,7 +1433,7 @@
 void Class::ClearSkipAccessChecksFlagOnAllMethods(PointerSize pointer_size) {
   DCHECK(IsVerified());
   for (auto& m : GetMethods(pointer_size)) {
-    if (!m.IsNative() && m.IsInvokable()) {
+    if (m.IsManagedAndInvokable()) {
       m.ClearSkipAccessChecks();
     }
   }
@@ -1445,7 +1442,7 @@
 void Class::ClearMustCountLocksFlagOnAllMethods(PointerSize pointer_size) {
   DCHECK(IsVerified());
   for (auto& m : GetMethods(pointer_size)) {
-    if (!m.IsNative() && m.IsInvokable()) {
+    if (m.IsManagedAndInvokable()) {
       m.ClearMustCountLocks();
     }
   }
@@ -1454,7 +1451,7 @@
 void Class::ClearDontCompileFlagOnAllMethods(PointerSize pointer_size) {
   DCHECK(IsVerified());
   for (auto& m : GetMethods(pointer_size)) {
-    if (!m.IsNative() && m.IsInvokable()) {
+    if (m.IsManagedAndInvokable()) {
       m.ClearDontCompile();
     }
   }
@@ -1463,7 +1460,7 @@
 void Class::SetSkipAccessChecksFlagOnAllMethods(PointerSize pointer_size) {
   DCHECK(IsVerified());
   for (auto& m : GetMethods(pointer_size)) {
-    if (!m.IsNative() && m.IsInvokable()) {
+    if (m.IsManagedAndInvokable()) {
       m.SetSkipAccessChecks();
     }
   }
@@ -2132,6 +2129,19 @@
   return res;
 }
 
+bool Class::CheckIsVisibleWithTargetSdk(Thread* self) {
+  uint32_t targetSdkVersion = Runtime::Current()->GetTargetSdkVersion();
+  if (IsSdkVersionSetAndAtMost(targetSdkVersion, SdkVersion::kT)) {
+    ObjPtr<mirror::Class> java_lang_ClassValue =
+        WellKnownClasses::ToClass(WellKnownClasses::java_lang_ClassValue);
+    if (this == java_lang_ClassValue.Ptr()) {
+      self->ThrowNewException("Ljava/lang/ClassNotFoundException;", "java.lang.ClassValue");
+      return false;
+    }
+  }
+  return true;
+}
+
 ArtMethod* Class::FindAccessibleInterfaceMethod(ArtMethod* implementation_method,
                                                 PointerSize pointer_size)
     REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 90efce5..232d683 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -64,6 +64,8 @@
 template<typename T> class StrideIterator;
 template<size_t kNumReferences> class PACKED(4) StackHandleScope;
 class Thread;
+class DexCacheVisitor;
+class RuntimeImageHelper;
 
 namespace mirror {
 
@@ -236,6 +238,15 @@
   // Set access flags, recording the change if running inside a Transaction.
   void SetAccessFlags(uint32_t new_access_flags) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void SetInBootImageAndNotInPreloadedClasses() REQUIRES_SHARED(Locks::mutator_lock_) {
+    uint32_t flags = GetAccessFlags();
+    SetAccessFlags(flags | kAccInBootImageAndNotInPreloadedClasses);
+  }
+
+  ALWAYS_INLINE bool IsInBootImageAndNotInPreloadedClasses() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return (GetAccessFlags() & kAccInBootImageAndNotInPreloadedClasses) != 0;
+  }
+
   // Returns true if the class is an enum.
   ALWAYS_INLINE bool IsEnum() REQUIRES_SHARED(Locks::mutator_lock_) {
     return (GetAccessFlags() & kAccEnum) != 0;
@@ -306,6 +317,15 @@
     SetClassFlags(GetClassFlags() | kClassFlagDexCache);
   }
 
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  ALWAYS_INLINE bool IsRecordClass() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return (GetClassFlags<kVerifyFlags>() & kClassFlagRecord) != 0;
+  }
+
+  ALWAYS_INLINE void SetRecordClass() REQUIRES_SHARED(Locks::mutator_lock_) {
+    SetClassFlags(GetClassFlags() | kClassFlagRecord);
+  }
+
   // Returns true if the class is abstract.
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   ALWAYS_INLINE bool IsAbstract() REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -486,6 +506,7 @@
 
   size_t GetComponentSize() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template<ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
   size_t GetComponentSizeShift() REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool IsObjectClass() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -495,7 +516,8 @@
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsInstantiable() REQUIRES_SHARED(Locks::mutator_lock_);
 
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+           ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
   ALWAYS_INLINE bool IsObjectArrayClass() REQUIRES_SHARED(Locks::mutator_lock_);
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
@@ -553,7 +575,7 @@
   // The size of java.lang.Class.class.
   static uint32_t ClassClassSize(PointerSize pointer_size) {
     // The number of vtable entries in java.lang.Class.
-    uint32_t vtable_entries = Object::kVTableLength + 67;
+    uint32_t vtable_entries = Object::kVTableLength + 81;
     return ComputeClassSize(true, vtable_entries, 0, 0, 4, 1, 0, pointer_size);
   }
 
@@ -570,6 +592,9 @@
   static constexpr MemberOffset ObjectSizeAllocFastPathOffset() {
     return OFFSET_OF_OBJECT_MEMBER(Class, object_size_alloc_fast_path_);
   }
+  static constexpr MemberOffset ClinitThreadIdOffset() {
+    return OFFSET_OF_OBJECT_MEMBER(Class, clinit_thread_id_);
+  }
 
   ALWAYS_INLINE void SetObjectSize(uint32_t new_object_size) REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -623,21 +648,6 @@
                                 uint32_t field_idx)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Can this class access a resolved method?
-  // Note that access to methods's class is checked and this may require looking up the class
-  // referenced by the MethodId in the DexFile in case the declaring class is inaccessible.
-  bool CanAccessResolvedMethod(ObjPtr<Class> access_to,
-                               ArtMethod* resolved_method,
-                               ObjPtr<DexCache> dex_cache,
-                               uint32_t method_idx)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-  bool CheckResolvedMethodAccess(ObjPtr<Class> access_to,
-                                 ArtMethod* resolved_method,
-                                 ObjPtr<DexCache> dex_cache,
-                                 uint32_t method_idx,
-                                 InvokeType throw_invoke_type)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   bool IsSubClass(ObjPtr<Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Can src be assigned to this class? For example, String can be assigned to Object (by an
@@ -866,6 +876,8 @@
 
   void SetImt(ImTable* imt, PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  ImTable* FindSuperImt(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_);
+
   ArtMethod* GetEmbeddedVTableEntry(uint32_t i, PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -1170,10 +1182,19 @@
 
   // Visit native roots visits roots which are keyed off the native pointers such as ArtFields and
   // ArtMethods.
-  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
+  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier,
+           bool kVisitProxyMethod = true,
+           class Visitor>
   void VisitNativeRoots(Visitor& visitor, PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Visit obsolete dex caches possibly stored in ext_data_
+  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
+  void VisitObsoleteDexCaches(DexCacheVisitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
+  void VisitObsoleteClass(Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Visit ArtMethods directly owned by this class.
   template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
   void VisitMethods(Visitor visitor, PointerSize pointer_size)
@@ -1347,6 +1368,13 @@
   size_t GetMethodIdOffset(ArtMethod* method, PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Returns whether the class should be visible to an app.
+  // Notorious example is java.lang.ClassValue, which was added in Android U and proguarding tools
+  // used that as justification to remove computeValue method implementation. Such an app running
+  // on U+ will fail with AbstractMethodError as computeValue is not implemented.
+  // See b/259501764.
+  bool CheckIsVisibleWithTargetSdk(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   template <typename T, VerifyObjectFlags kVerifyFlags, typename Visitor>
   void FixupNativePointer(
@@ -1367,14 +1395,6 @@
                                uint32_t field_idx)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  template <bool throw_on_failure>
-  bool ResolvedMethodAccessTest(ObjPtr<Class> access_to,
-                                ArtMethod* resolved_method,
-                                ObjPtr<DexCache> dex_cache,
-                                uint32_t method_idx,
-                                InvokeType throw_invoke_type)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   bool IsArrayAssignableFromArray(ObjPtr<Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
   bool IsAssignableFromArray(ObjPtr<Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -1417,7 +1437,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_);
 
   // 'Class' Object Fields
-  // Order governed by java field ordering. See art::ClassLinker::LinkFields.
+  // Order governed by java field ordering. See art::ClassLinker::LinkFieldsHelper::LinkFields.
 
   // Defining class loader, or null for the "bootstrap" system loader.
   HeapReference<ClassLoader> class_loader_;
@@ -1571,6 +1591,7 @@
   friend struct art::ClassOffsets;  // for verifying offset information
   friend class Object;  // For VisitReferences
   friend class linker::ImageWriter;  // For SetStatusInternal
+  friend class art::RuntimeImageHelper;  // For SetStatusInternal
   DISALLOW_IMPLICIT_CONSTRUCTORS(Class);
 };
 
diff --git a/runtime/mirror/class_ext-inl.h b/runtime/mirror/class_ext-inl.h
index ddd46b9..9d6ac43 100644
--- a/runtime/mirror/class_ext-inl.h
+++ b/runtime/mirror/class_ext-inl.h
@@ -23,6 +23,7 @@
 #include "art_method-inl.h"
 #include "base/enums.h"
 #include "base/globals.h"
+#include "class_linker.h"
 #include "handle_scope.h"
 #include "jni/jni_internal.h"
 #include "jni_id_type.h"
@@ -148,8 +149,9 @@
   return GetFieldObject<Throwable>(OFFSET_OF_OBJECT_MEMBER(ClassExt, erroneous_state_error_));
 }
 
+template<VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption>
 inline ObjPtr<ObjectArray<DexCache>> ClassExt::GetObsoleteDexCaches() {
-  return GetFieldObject<ObjectArray<DexCache>>(
+  return GetFieldObject<ObjectArray<DexCache>, kVerifyFlags, kReadBarrierOption>(
       OFFSET_OF_OBJECT_MEMBER(ClassExt, obsolete_dex_caches_));
 }
 
@@ -164,13 +166,25 @@
   return GetFieldObject<Object>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_));
 }
 
-template<ReadBarrierOption kReadBarrierOption, class Visitor>
+template<ReadBarrierOption kReadBarrierOption, bool kVisitProxyMethod, class Visitor>
 void ClassExt::VisitNativeRoots(Visitor& visitor, PointerSize pointer_size) {
   VisitMethods<kReadBarrierOption>([&](ArtMethod* method) {
-    method->VisitRoots<kReadBarrierOption>(visitor, pointer_size);
+    method->VisitRoots<kReadBarrierOption, kVisitProxyMethod>(visitor, pointer_size);
   }, pointer_size);
 }
 
+template<VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption>
+void ClassExt::VisitDexCaches(DexCacheVisitor& visitor) {
+  ObjPtr<ObjectArray<DexCache>> arr(GetObsoleteDexCaches<kVerifyFlags, kReadBarrierOption>());
+  if (!arr.IsNull()) {
+    int32_t len = arr->GetLength();
+    for (int32_t i = 0; i < len; i++) {
+      ObjPtr<mirror::DexCache> dex_cache = arr->Get<kVerifyFlags, kReadBarrierOption>(i);
+      visitor.Visit(dex_cache);
+    }
+  }
+}
+
 template<ReadBarrierOption kReadBarrierOption, class Visitor>
 void ClassExt::VisitMethods(Visitor visitor, PointerSize pointer_size) {
   ObjPtr<PointerArray> arr(GetObsoleteMethods<kDefaultVerifyFlags, kReadBarrierOption>());
diff --git a/runtime/mirror/class_ext.h b/runtime/mirror/class_ext.h
index 4ce3b10..b025eb2 100644
--- a/runtime/mirror/class_ext.h
+++ b/runtime/mirror/class_ext.h
@@ -27,6 +27,7 @@
 namespace art {
 
 struct ClassExtOffsets;
+class DexCacheVisitor;
 
 namespace mirror {
 
@@ -46,6 +47,8 @@
 
   ObjPtr<Throwable> GetErroneousStateError() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+           ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ObjPtr<ObjectArray<DexCache>> GetObsoleteDexCaches() REQUIRES_SHARED(Locks::mutator_lock_);
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
@@ -126,10 +129,21 @@
   static bool ExtendObsoleteArrays(Handle<ClassExt> h_this, Thread* self, uint32_t increase)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
+  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier,
+           bool kVisitProxyMethod = true,
+           class Visitor>
   inline void VisitNativeRoots(Visitor& visitor, PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // NO_THREAD_SAFETY_ANALYSIS for dex_lock and heap_bitmap_lock_ as both are at
+  // higher lock-level than class-table's lock, which is already acquired and
+  // is at lower (kClassLoaderClassesLock) level.
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+           ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
+  inline void VisitDexCaches(DexCacheVisitor& visitor)
+      NO_THREAD_SAFETY_ANALYSIS
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
   inline void VisitMethods(Visitor visitor, PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -156,6 +170,10 @@
   bool EnsureJniIdsArrayPresent(MemberOffset off, size_t count)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Backing store of user-defined values pertaining to a class.
+  // Maintained by the ClassValue class.
+  HeapReference<Object> class_value_map_;
+
   // The saved error for this class being erroneous.
   HeapReference<Throwable> erroneous_state_error_;
 
@@ -181,9 +199,10 @@
   // classes sfields_ array or '0' if no id has been assigned to that field yet.
   HeapReference<PointerArray> static_jfield_ids_;
 
+  int32_t pre_redefine_class_def_index_;
+
   // Native pointer to DexFile and ClassDef index of this class before it was JVMTI-redefined.
   int64_t pre_redefine_dex_file_ptr_;
-  int32_t pre_redefine_class_def_index_;
 
   friend struct art::ClassExtOffsets;  // for verifying offset information
   DISALLOW_IMPLICIT_CONSTRUCTORS(ClassExt);
diff --git a/runtime/mirror/class_flags.h b/runtime/mirror/class_flags.h
index 139c4cb..c85b9e0 100644
--- a/runtime/mirror/class_flags.h
+++ b/runtime/mirror/class_flags.h
@@ -56,6 +56,9 @@
 // Class is the phantom reference class.
 static constexpr uint32_t kClassFlagPhantomReference   = 0x00000400;
 
+// Class is a record class. See doc at java.lang.Class#isRecord().
+static constexpr uint32_t kClassFlagRecord             = 0x00000800;
+
 // Combination of flags to figure out if the class is either the weak/soft/phantom/finalizer
 // reference class.
 static constexpr uint32_t kClassFlagReference =
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 2791fe3..8b8eecc 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -29,7 +29,7 @@
 #include "class_linker.h"
 #include "dex/dex_file.h"
 #include "gc_root-inl.h"
-#include "linear_alloc.h"
+#include "linear_alloc-inl.h"
 #include "mirror/call_site.h"
 #include "mirror/class.h"
 #include "mirror/method_type.h"
@@ -49,34 +49,36 @@
 }
 
 template<typename T>
-static void InitializeArray(GcRoot<T>*) {
-  // No special initialization is needed.
+static void InitializeArray(T*) {
+  // Nothing to do.
 }
 
-template<typename T, size_t kMaxCacheSize>
-T* DexCache::AllocArray(MemberOffset obj_offset, MemberOffset num_offset, size_t num) {
-  num = std::min<size_t>(num, kMaxCacheSize);
-  if (num == 0) {
-    return nullptr;
-  }
+template<typename T>
+T* DexCache::AllocArray(MemberOffset obj_offset, size_t num, LinearAllocKind kind, bool startup) {
+  Thread* self = Thread::Current();
   mirror::DexCache* dex_cache = this;
-  if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
+  if (gUseReadBarrier && self->GetIsGcMarking()) {
     // Several code paths use DexCache without read-barrier for performance.
     // We have to check the "to-space" object here to avoid allocating twice.
-    dex_cache = reinterpret_cast<DexCache*>(ReadBarrier::Mark(dex_cache));
+    dex_cache = reinterpret_cast<DexCache*>(ReadBarrier::Mark(this));
   }
-  Thread* self = Thread::Current();
-  ClassLinker* linker = Runtime::Current()->GetClassLinker();
-  LinearAlloc* alloc = linker->GetOrCreateAllocatorForClassLoader(GetClassLoader());
+  // DON'T USE 'this' from now on.
+  Runtime* runtime = Runtime::Current();
+  // Note: in the 1002-notify-startup test, the startup linear alloc can become null
+  // concurrently, even if the runtime is marked at startup. Therefore we should only
+  // fetch it once here.
+  LinearAlloc* startup_linear_alloc = runtime->GetStartupLinearAlloc();
+  LinearAlloc* alloc = (startup && startup_linear_alloc != nullptr)
+      ? startup_linear_alloc
+      : runtime->GetClassLinker()->GetOrCreateAllocatorForClassLoader(GetClassLoader());
   MutexLock mu(self, *Locks::dex_cache_lock_);  // Avoid allocation by multiple threads.
   T* array = dex_cache->GetFieldPtr64<T*>(obj_offset);
   if (array != nullptr) {
     DCHECK(alloc->Contains(array));
     return array;  // Other thread just allocated the array.
   }
-  array = reinterpret_cast<T*>(alloc->AllocAlign16(self, RoundUp(num * sizeof(T), 16)));
+  array = reinterpret_cast<T*>(alloc->AllocAlign16(self, RoundUp(num * sizeof(T), 16), kind));
   InitializeArray(array);  // Ensure other threads see the array initialized.
-  dex_cache->SetField32Volatile<false, false>(num_offset, num);
   dex_cache->SetField64Volatile<false, false>(obj_offset, reinterpret_cast64<uint64_t>(array));
   return array;
 }
@@ -86,14 +88,6 @@
     : object(object), index(index) {}
 
 template <typename T>
-inline void DexCachePair<T>::Initialize(std::atomic<DexCachePair<T>>* dex_cache) {
-  DexCachePair<T> first_elem;
-  first_elem.object = GcRoot<T>(nullptr);
-  first_elem.index = InvalidIndexForSlot(0);
-  dex_cache[0].store(first_elem, std::memory_order_relaxed);
-}
-
-template <typename T>
 inline T* DexCachePair<T>::GetObjectForIndex(uint32_t idx) {
   if (idx != index) {
     return nullptr;
@@ -103,11 +97,33 @@
 }
 
 template <typename T>
+inline void DexCachePair<T>::Initialize(std::atomic<DexCachePair<T>>* dex_cache) {
+  DexCachePair<T> first_elem;
+  first_elem.object = GcRoot<T>(nullptr);
+  first_elem.index = InvalidIndexForSlot(0);
+  dex_cache[0].store(first_elem, std::memory_order_relaxed);
+}
+
+template <typename T>
 inline void NativeDexCachePair<T>::Initialize(std::atomic<NativeDexCachePair<T>>* dex_cache) {
   NativeDexCachePair<T> first_elem;
   first_elem.object = nullptr;
   first_elem.index = InvalidIndexForSlot(0);
-  DexCache::SetNativePair(dex_cache, 0, first_elem);
+
+  auto* array = reinterpret_cast<std::atomic<AtomicPair<uintptr_t>>*>(dex_cache);
+  AtomicPair<uintptr_t> v(reinterpret_cast<size_t>(first_elem.object), first_elem.index);
+  AtomicPairStoreRelease(&array[0], v);
+}
+
+template <typename T>
+inline void GcRootArray<T>::Set(uint32_t index, T* value) {
+  GcRoot<T> root(value);
+  entries_[index].store(root, std::memory_order_relaxed);
+}
+
+template <typename T>
+inline T* GcRootArray<T>::Get(uint32_t index) {
+  return entries_[index].load(std::memory_order_relaxed).Read();
 }
 
 inline uint32_t DexCache::ClassSize(PointerSize pointer_size) {
@@ -115,31 +131,13 @@
   return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 0, 0, pointer_size);
 }
 
-inline uint32_t DexCache::StringSlotIndex(dex::StringIndex string_idx) {
-  DCHECK_LT(string_idx.index_, GetDexFile()->NumStringIds());
-  const uint32_t slot_idx = string_idx.index_ % kDexCacheStringCacheSize;
-  DCHECK_LT(slot_idx, NumStrings());
-  return slot_idx;
-}
-
 inline String* DexCache::GetResolvedString(dex::StringIndex string_idx) {
-  StringDexCacheType* strings = GetStrings();
-  if (UNLIKELY(strings == nullptr)) {
-    return nullptr;
-  }
-  return strings[StringSlotIndex(string_idx)].load(
-      std::memory_order_relaxed).GetObjectForIndex(string_idx.index_);
+  return GetStringsEntry(string_idx.index_);
 }
 
 inline void DexCache::SetResolvedString(dex::StringIndex string_idx, ObjPtr<String> resolved) {
   DCHECK(resolved != nullptr);
-  StringDexCacheType* strings = GetStrings();
-  if (UNLIKELY(strings == nullptr)) {
-    strings = AllocArray<StringDexCacheType, kDexCacheStringCacheSize>(
-        StringsOffset(), NumStringsOffset(), GetDexFile()->NumStringIds());
-  }
-  strings[StringSlotIndex(string_idx)].store(
-      StringDexCachePair(resolved, string_idx.index_), std::memory_order_relaxed);
+  SetStringsEntry(string_idx.index_, resolved.Ptr());
   Runtime* const runtime = Runtime::Current();
   if (UNLIKELY(runtime->IsActiveTransaction())) {
     DCHECK(runtime->IsAotCompiler());
@@ -151,96 +149,42 @@
 
 inline void DexCache::ClearString(dex::StringIndex string_idx) {
   DCHECK(Runtime::Current()->IsAotCompiler());
-  uint32_t slot_idx = StringSlotIndex(string_idx);
-  StringDexCacheType* strings = GetStrings();
+  auto* array = GetStringsArray();
+  if (array != nullptr) {
+    array->Set(string_idx.index_, nullptr);
+  }
+  auto* strings = GetStrings();
   if (UNLIKELY(strings == nullptr)) {
     return;
   }
-  StringDexCacheType* slot = &strings[slot_idx];
-  // This is racy but should only be called from the transactional interpreter.
-  if (slot->load(std::memory_order_relaxed).index == string_idx.index_) {
-    StringDexCachePair cleared(nullptr, StringDexCachePair::InvalidIndexForSlot(slot_idx));
-    slot->store(cleared, std::memory_order_relaxed);
-  }
-}
-
-inline uint32_t DexCache::TypeSlotIndex(dex::TypeIndex type_idx) {
-  DCHECK_LT(type_idx.index_, GetDexFile()->NumTypeIds());
-  const uint32_t slot_idx = type_idx.index_ % kDexCacheTypeCacheSize;
-  DCHECK_LT(slot_idx, NumResolvedTypes());
-  return slot_idx;
+  strings->Clear(string_idx.index_);
 }
 
 inline Class* DexCache::GetResolvedType(dex::TypeIndex type_idx) {
-  // It is theorized that a load acquire is not required since obtaining the resolved class will
-  // always have an address dependency or a lock.
-  TypeDexCacheType* resolved_types = GetResolvedTypes();
-  if (UNLIKELY(resolved_types == nullptr)) {
-    return nullptr;
-  }
-  return resolved_types[TypeSlotIndex(type_idx)].load(
-      std::memory_order_relaxed).GetObjectForIndex(type_idx.index_);
-}
-
-inline void DexCache::SetResolvedType(dex::TypeIndex type_idx, ObjPtr<Class> resolved) {
-  DCHECK(resolved != nullptr);
-  DCHECK(resolved->IsResolved()) << resolved->GetStatus();
-  TypeDexCacheType* resolved_types = GetResolvedTypes();
-  if (UNLIKELY(resolved_types == nullptr)) {
-    resolved_types = AllocArray<TypeDexCacheType, kDexCacheTypeCacheSize>(
-        ResolvedTypesOffset(), NumResolvedTypesOffset(), GetDexFile()->NumTypeIds());
-  }
-  // TODO default transaction support.
-  // Use a release store for SetResolvedType. This is done to prevent other threads from seeing a
-  // class but not necessarily seeing the loaded members like the static fields array.
-  // See b/32075261.
-  resolved_types[TypeSlotIndex(type_idx)].store(
-      TypeDexCachePair(resolved, type_idx.index_), std::memory_order_release);
-  // TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
-  WriteBarrier::ForEveryFieldWrite(this);
+  return GetResolvedTypesEntry(type_idx.index_);
 }
 
 inline void DexCache::ClearResolvedType(dex::TypeIndex type_idx) {
   DCHECK(Runtime::Current()->IsAotCompiler());
-  TypeDexCacheType* resolved_types = GetResolvedTypes();
+  auto* array = GetResolvedTypesArray();
+  if (array != nullptr) {
+    array->Set(type_idx.index_, nullptr);
+  }
+  auto* resolved_types = GetResolvedTypes();
   if (UNLIKELY(resolved_types == nullptr)) {
     return;
   }
-  uint32_t slot_idx = TypeSlotIndex(type_idx);
-  TypeDexCacheType* slot = &resolved_types[slot_idx];
-  // This is racy but should only be called from the single-threaded ImageWriter and tests.
-  if (slot->load(std::memory_order_relaxed).index == type_idx.index_) {
-    TypeDexCachePair cleared(nullptr, TypeDexCachePair::InvalidIndexForSlot(slot_idx));
-    slot->store(cleared, std::memory_order_relaxed);
-  }
-}
-
-inline uint32_t DexCache::MethodTypeSlotIndex(dex::ProtoIndex proto_idx) {
-  DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
-  DCHECK_LT(proto_idx.index_, GetDexFile()->NumProtoIds());
-  const uint32_t slot_idx = proto_idx.index_ % kDexCacheMethodTypeCacheSize;
-  DCHECK_LT(slot_idx, NumResolvedMethodTypes());
-  return slot_idx;
+  resolved_types->Clear(type_idx.index_);
 }
 
 inline MethodType* DexCache::GetResolvedMethodType(dex::ProtoIndex proto_idx) {
-  MethodTypeDexCacheType* methods = GetResolvedMethodTypes();
-  if (UNLIKELY(methods == nullptr)) {
-    return nullptr;
-  }
-  return methods[MethodTypeSlotIndex(proto_idx)].load(
-      std::memory_order_relaxed).GetObjectForIndex(proto_idx.index_);
+  return GetResolvedMethodTypesEntry(proto_idx.index_);
 }
 
 inline void DexCache::SetResolvedMethodType(dex::ProtoIndex proto_idx, MethodType* resolved) {
   DCHECK(resolved != nullptr);
-  MethodTypeDexCacheType* methods = GetResolvedMethodTypes();
-  if (UNLIKELY(methods == nullptr)) {
-    methods = AllocArray<MethodTypeDexCacheType, kDexCacheMethodTypeCacheSize>(
-        ResolvedMethodTypesOffset(), NumResolvedMethodTypesOffset(), GetDexFile()->NumProtoIds());
-  }
-  methods[MethodTypeSlotIndex(proto_idx)].store(
-      MethodTypeDexCachePair(resolved, proto_idx.index_), std::memory_order_relaxed);
+  SetResolvedMethodTypesEntry(proto_idx.index_, resolved);
+
   Runtime* const runtime = Runtime::Current();
   if (UNLIKELY(runtime->IsActiveTransaction())) {
     DCHECK(runtime->IsAotCompiler());
@@ -252,27 +196,26 @@
 
 inline void DexCache::ClearMethodType(dex::ProtoIndex proto_idx) {
   DCHECK(Runtime::Current()->IsAotCompiler());
-  uint32_t slot_idx = MethodTypeSlotIndex(proto_idx);
-  MethodTypeDexCacheType* slot = &GetResolvedMethodTypes()[slot_idx];
-  // This is racy but should only be called from the transactional interpreter.
-  if (slot->load(std::memory_order_relaxed).index == proto_idx.index_) {
-    MethodTypeDexCachePair cleared(nullptr,
-                                   MethodTypeDexCachePair::InvalidIndexForSlot(proto_idx.index_));
-    slot->store(cleared, std::memory_order_relaxed);
+  auto* array = GetResolvedMethodTypesArray();
+  if (array != nullptr) {
+    array->Set(proto_idx.index_, nullptr);
   }
+  auto* methods = GetResolvedMethodTypes();
+  if (methods == nullptr) {
+    return;
+  }
+  methods->Clear(proto_idx.index_);
 }
 
 inline CallSite* DexCache::GetResolvedCallSite(uint32_t call_site_idx) {
   DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
   DCHECK_LT(call_site_idx, GetDexFile()->NumCallSiteIds());
-  GcRoot<CallSite>* call_sites = GetResolvedCallSites();
+  GcRootArray<CallSite>* call_sites = GetResolvedCallSites();
   if (UNLIKELY(call_sites == nullptr)) {
     return nullptr;
   }
-  GcRoot<mirror::CallSite>& target = call_sites[call_site_idx];
-  Atomic<GcRoot<mirror::CallSite>>& ref =
-      reinterpret_cast<Atomic<GcRoot<mirror::CallSite>>&>(target);
-  return ref.load(std::memory_order_seq_cst).Read();
+  Atomic<GcRoot<mirror::CallSite>>* target = call_sites->GetGcRoot(call_site_idx);
+  return target->load(std::memory_order_seq_cst).Read();
 }
 
 inline ObjPtr<CallSite> DexCache::SetResolvedCallSite(uint32_t call_site_idx,
@@ -282,120 +225,71 @@
 
   GcRoot<mirror::CallSite> null_call_site(nullptr);
   GcRoot<mirror::CallSite> candidate(call_site);
-  GcRoot<CallSite>* call_sites = GetResolvedCallSites();
+  GcRootArray<CallSite>* call_sites = GetResolvedCallSites();
   if (UNLIKELY(call_sites == nullptr)) {
-    call_sites = AllocArray<GcRoot<CallSite>, std::numeric_limits<size_t>::max()>(
-        ResolvedCallSitesOffset(), NumResolvedCallSitesOffset(), GetDexFile()->NumCallSiteIds());
+    call_sites = AllocateResolvedCallSites();
   }
-  GcRoot<mirror::CallSite>& target = call_sites[call_site_idx];
+  Atomic<GcRoot<mirror::CallSite>>* target = call_sites->GetGcRoot(call_site_idx);
 
   // The first assignment for a given call site wins.
-  Atomic<GcRoot<mirror::CallSite>>& ref =
-      reinterpret_cast<Atomic<GcRoot<mirror::CallSite>>&>(target);
-  if (ref.CompareAndSetStrongSequentiallyConsistent(null_call_site, candidate)) {
+  if (target->CompareAndSetStrongSequentiallyConsistent(null_call_site, candidate)) {
     // TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
     WriteBarrier::ForEveryFieldWrite(this);
     return call_site;
   } else {
-    return target.Read();
+    return target->load(std::memory_order_relaxed).Read();
   }
 }
 
-inline uint32_t DexCache::FieldSlotIndex(uint32_t field_idx) {
-  DCHECK_LT(field_idx, GetDexFile()->NumFieldIds());
-  const uint32_t slot_idx = field_idx % kDexCacheFieldCacheSize;
-  DCHECK_LT(slot_idx, NumResolvedFields());
-  return slot_idx;
-}
-
 inline ArtField* DexCache::GetResolvedField(uint32_t field_idx) {
-  FieldDexCacheType* fields = GetResolvedFields();
-  if (UNLIKELY(fields == nullptr)) {
-    return nullptr;
-  }
-  auto pair = GetNativePair(fields, FieldSlotIndex(field_idx));
-  return pair.GetObjectForIndex(field_idx);
+  return GetResolvedFieldsEntry(field_idx);
 }
 
 inline void DexCache::SetResolvedField(uint32_t field_idx, ArtField* field) {
-  DCHECK(field != nullptr);
-  FieldDexCachePair pair(field, field_idx);
-  FieldDexCacheType* fields = GetResolvedFields();
-  if (UNLIKELY(fields == nullptr)) {
-    fields = AllocArray<FieldDexCacheType, kDexCacheFieldCacheSize>(
-        ResolvedFieldsOffset(), NumResolvedFieldsOffset(), GetDexFile()->NumFieldIds());
-  }
-  SetNativePair(fields, FieldSlotIndex(field_idx), pair);
-}
-
-inline uint32_t DexCache::MethodSlotIndex(uint32_t method_idx) {
-  DCHECK_LT(method_idx, GetDexFile()->NumMethodIds());
-  const uint32_t slot_idx = method_idx % kDexCacheMethodCacheSize;
-  DCHECK_LT(slot_idx, NumResolvedMethods());
-  return slot_idx;
+  SetResolvedFieldsEntry(field_idx, field);
 }
 
 inline ArtMethod* DexCache::GetResolvedMethod(uint32_t method_idx) {
-  MethodDexCacheType* methods = GetResolvedMethods();
-  if (UNLIKELY(methods == nullptr)) {
-    return nullptr;
-  }
-  auto pair = GetNativePair(methods, MethodSlotIndex(method_idx));
-  return pair.GetObjectForIndex(method_idx);
+  return GetResolvedMethodsEntry(method_idx);
 }
 
 inline void DexCache::SetResolvedMethod(uint32_t method_idx, ArtMethod* method) {
-  DCHECK(method != nullptr);
-  MethodDexCachePair pair(method, method_idx);
-  MethodDexCacheType* methods = GetResolvedMethods();
-  if (UNLIKELY(methods == nullptr)) {
-    methods = AllocArray<MethodDexCacheType, kDexCacheMethodCacheSize>(
-        ResolvedMethodsOffset(), NumResolvedMethodsOffset(), GetDexFile()->NumMethodIds());
-  }
-  SetNativePair(methods, MethodSlotIndex(method_idx), pair);
+  SetResolvedMethodsEntry(method_idx, method);
 }
 
-template <typename T>
-NativeDexCachePair<T> DexCache::GetNativePair(std::atomic<NativeDexCachePair<T>>* pair_array,
-                                              size_t idx) {
-  auto* array = reinterpret_cast<std::atomic<AtomicPair<uintptr_t>>*>(pair_array);
-  AtomicPair<uintptr_t> value = AtomicPairLoadAcquire(&array[idx]);
-  return NativeDexCachePair<T>(reinterpret_cast<T*>(value.first), value.second);
-}
-
-template <typename T>
-void DexCache::SetNativePair(std::atomic<NativeDexCachePair<T>>* pair_array,
-                             size_t idx,
-                             NativeDexCachePair<T> pair) {
-  auto* array = reinterpret_cast<std::atomic<AtomicPair<uintptr_t>>*>(pair_array);
-  AtomicPair<uintptr_t> v(reinterpret_cast<size_t>(pair.object), pair.index);
-  AtomicPairStoreRelease(&array[idx], v);
-}
-
-template <typename T,
-          ReadBarrierOption kReadBarrierOption,
-          typename Visitor>
-inline void VisitDexCachePairs(std::atomic<DexCachePair<T>>* pairs,
+template <ReadBarrierOption kReadBarrierOption,
+          typename Visitor,
+          typename T>
+inline void VisitDexCachePairs(T* array,
                                size_t num_pairs,
                                const Visitor& visitor)
     REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) {
   // Check both the data pointer and count since the array might be initialized
   // concurrently on other thread, and we might observe just one of the values.
-  for (size_t i = 0; pairs != nullptr && i < num_pairs; ++i) {
-    DexCachePair<T> source = pairs[i].load(std::memory_order_relaxed);
+  for (size_t i = 0; array != nullptr && i < num_pairs; ++i) {
+    auto source = array->GetPair(i);
     // NOTE: We need the "template" keyword here to avoid a compilation
     // failure. GcRoot<T> is a template argument-dependent type and we need to
     // tell the compiler to treat "Read" as a template rather than a field or
     // function. Otherwise, on encountering the "<" token, the compiler would
     // treat "Read" as a field.
-    T* const before = source.object.template Read<kReadBarrierOption>();
+    auto const before = source.object.template Read<kReadBarrierOption>();
     visitor.VisitRootIfNonNull(source.object.AddressWithoutBarrier());
     if (source.object.template Read<kReadBarrierOption>() != before) {
-      pairs[i].store(source, std::memory_order_relaxed);
+      array->SetPair(i, source);
     }
   }
 }
 
+template <typename Visitor>
+void DexCache::VisitDexCachePairRoots(Visitor& visitor,
+                                      DexCachePair<Object>* pairs_begin,
+                                      DexCachePair<Object>* pairs_end) {
+  for (; pairs_begin < pairs_end; pairs_begin++) {
+    visitor.VisitRootIfNonNull(pairs_begin->object.AddressWithoutBarrier());
+  }
+}
+
 template <bool kVisitNativeRoots,
           VerifyObjectFlags kVerifyFlags,
           ReadBarrierOption kReadBarrierOption,
@@ -405,20 +299,49 @@
   VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
   // Visit arrays after.
   if (kVisitNativeRoots) {
-    VisitDexCachePairs<String, kReadBarrierOption, Visitor>(
-        GetStrings<kVerifyFlags>(), NumStrings<kVerifyFlags>(), visitor);
+    VisitNativeRoots<kVerifyFlags, kReadBarrierOption>(visitor);
+  }
+}
 
-    VisitDexCachePairs<Class, kReadBarrierOption, Visitor>(
-        GetResolvedTypes<kVerifyFlags>(), NumResolvedTypes<kVerifyFlags>(), visitor);
+template <VerifyObjectFlags kVerifyFlags,
+          ReadBarrierOption kReadBarrierOption,
+          typename Visitor>
+inline void DexCache::VisitNativeRoots(const Visitor& visitor) {
+  VisitDexCachePairs<kReadBarrierOption, Visitor>(
+      GetStrings<kVerifyFlags>(), NumStrings<kVerifyFlags>(), visitor);
 
-    VisitDexCachePairs<MethodType, kReadBarrierOption, Visitor>(
-        GetResolvedMethodTypes<kVerifyFlags>(), NumResolvedMethodTypes<kVerifyFlags>(), visitor);
+  VisitDexCachePairs<kReadBarrierOption, Visitor>(
+      GetResolvedTypes<kVerifyFlags>(), NumResolvedTypes<kVerifyFlags>(), visitor);
 
-    GcRoot<mirror::CallSite>* resolved_call_sites = GetResolvedCallSites<kVerifyFlags>();
-    size_t num_call_sites = NumResolvedCallSites<kVerifyFlags>();
-    for (size_t i = 0; resolved_call_sites != nullptr && i != num_call_sites; ++i) {
-      visitor.VisitRootIfNonNull(resolved_call_sites[i].AddressWithoutBarrier());
-    }
+  VisitDexCachePairs<kReadBarrierOption, Visitor>(
+      GetResolvedMethodTypes<kVerifyFlags>(), NumResolvedMethodTypes<kVerifyFlags>(), visitor);
+
+  GcRootArray<mirror::CallSite>* resolved_call_sites = GetResolvedCallSites<kVerifyFlags>();
+  size_t num_call_sites = NumResolvedCallSites<kVerifyFlags>();
+  for (size_t i = 0; resolved_call_sites != nullptr && i != num_call_sites; ++i) {
+    visitor.VisitRootIfNonNull(resolved_call_sites->GetGcRootAddress(i)->AddressWithoutBarrier());
+  }
+
+  // Dex cache arrays can be reset and cleared during app startup. Assert we do not get
+  // suspended to ensure the arrays are not deallocated.
+  ScopedAssertNoThreadSuspension soants("dex caches");
+  GcRootArray<mirror::Class>* resolved_types = GetResolvedTypesArray<kVerifyFlags>();
+  size_t num_resolved_types = NumResolvedTypesArray<kVerifyFlags>();
+  for (size_t i = 0; resolved_types != nullptr && i != num_resolved_types; ++i) {
+    visitor.VisitRootIfNonNull(resolved_types->GetGcRootAddress(i)->AddressWithoutBarrier());
+  }
+
+  GcRootArray<mirror::String>* resolved_strings = GetStringsArray<kVerifyFlags>();
+  size_t num_resolved_strings = NumStringsArray<kVerifyFlags>();
+  for (size_t i = 0; resolved_strings != nullptr && i != num_resolved_strings; ++i) {
+    visitor.VisitRootIfNonNull(resolved_strings->GetGcRootAddress(i)->AddressWithoutBarrier());
+  }
+
+  GcRootArray<mirror::MethodType>* resolved_method_types =
+      GetResolvedMethodTypesArray<kVerifyFlags>();
+  size_t num_resolved_method_types = NumResolvedMethodTypesArray<kVerifyFlags>();
+  for (size_t i = 0; resolved_method_types != nullptr && i != num_resolved_method_types; ++i) {
+    visitor.VisitRootIfNonNull(resolved_method_types->GetGcRootAddress(i)->AddressWithoutBarrier());
   }
 }
 
diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc
index b7f8ee7..d3f474e 100644
--- a/runtime/mirror/dex_cache.cc
+++ b/runtime/mirror/dex_cache.cc
@@ -20,6 +20,7 @@
 #include "class_linker.h"
 #include "gc/accounting/card_table-inl.h"
 #include "gc/heap.h"
+#include "jit/profile_saver.h"
 #include "linear_alloc.h"
 #include "oat_file.h"
 #include "object-inl.h"
@@ -35,6 +36,10 @@
 namespace art {
 namespace mirror {
 
+// Whether to allocate full dex cache arrays during startup. Currently disabled
+// while debugging b/283632504.
+static constexpr bool kEnableFullArraysAtStartup = false;
+
 void DexCache::Initialize(const DexFile* dex_file, ObjPtr<ClassLoader> class_loader) {
   DCHECK(GetDexFile() == nullptr);
   DCHECK(GetStrings() == nullptr);
@@ -52,48 +57,81 @@
 
 void DexCache::VisitReflectiveTargets(ReflectiveValueVisitor* visitor) {
   bool wrote = false;
-  FieldDexCacheType* fields = GetResolvedFields();
+  auto* fields = GetResolvedFields();
   size_t num_fields = NumResolvedFields();
   // Check both the data pointer and count since the array might be initialized
   // concurrently on other thread, and we might observe just one of the values.
   for (size_t i = 0; fields != nullptr && i < num_fields; i++) {
-    auto pair(GetNativePair(fields, i));
-    if (pair.index == FieldDexCachePair::InvalidIndexForSlot(i)) {
+    auto pair(fields->GetNativePair(i));
+    if (pair.index == NativeDexCachePair<ArtField>::InvalidIndexForSlot(i)) {
       continue;
     }
     ArtField* new_val = visitor->VisitField(
         pair.object, DexCacheSourceInfo(kSourceDexCacheResolvedField, pair.index, this));
     if (UNLIKELY(new_val != pair.object)) {
       if (new_val == nullptr) {
-        pair = FieldDexCachePair(nullptr, FieldDexCachePair::InvalidIndexForSlot(i));
+        pair = NativeDexCachePair<ArtField>(
+            nullptr, NativeDexCachePair<ArtField>::InvalidIndexForSlot(i));
       } else {
         pair.object = new_val;
       }
-      SetNativePair(fields, i, pair);
+      fields->SetNativePair(i, pair);
       wrote = true;
     }
   }
-  MethodDexCacheType* methods = GetResolvedMethods();
+  auto* methods = GetResolvedMethods();
   size_t num_methods = NumResolvedMethods();
   // Check both the data pointer and count since the array might be initialized
   // concurrently on other thread, and we might observe just one of the values.
   for (size_t i = 0; methods != nullptr && i < num_methods; i++) {
-    auto pair(GetNativePair(methods, i));
-    if (pair.index == MethodDexCachePair::InvalidIndexForSlot(i)) {
+    auto pair(methods->GetNativePair(i));
+    if (pair.index == NativeDexCachePair<ArtMethod>::InvalidIndexForSlot(i)) {
       continue;
     }
     ArtMethod* new_val = visitor->VisitMethod(
         pair.object, DexCacheSourceInfo(kSourceDexCacheResolvedMethod, pair.index, this));
     if (UNLIKELY(new_val != pair.object)) {
       if (new_val == nullptr) {
-        pair = MethodDexCachePair(nullptr, MethodDexCachePair::InvalidIndexForSlot(i));
+        pair = NativeDexCachePair<ArtMethod>(
+            nullptr, NativeDexCachePair<ArtMethod>::InvalidIndexForSlot(i));
       } else {
         pair.object = new_val;
       }
-      SetNativePair(methods, i, pair);
+      methods->SetNativePair(i, pair);
       wrote = true;
     }
   }
+
+  auto* fields_array = GetResolvedFieldsArray();
+  num_fields = NumResolvedFieldsArray();
+  for (size_t i = 0; fields_array != nullptr && i < num_fields; i++) {
+    ArtField* old_val = fields_array->Get(i);
+    if (old_val == nullptr) {
+      continue;
+    }
+    ArtField* new_val = visitor->VisitField(
+        old_val, DexCacheSourceInfo(kSourceDexCacheResolvedField, i, this));
+    if (new_val != old_val) {
+      fields_array->Set(i, new_val);
+      wrote = true;
+    }
+  }
+
+  auto* methods_array = GetResolvedMethodsArray();
+  num_methods = NumResolvedMethodsArray();
+  for (size_t i = 0; methods_array != nullptr && i < num_methods; i++) {
+    ArtMethod* old_val = methods_array->Get(i);
+    if (old_val == nullptr) {
+      continue;
+    }
+    ArtMethod* new_val = visitor->VisitMethod(
+        old_val, DexCacheSourceInfo(kSourceDexCacheResolvedMethod, i, this));
+    if (new_val != old_val) {
+      methods_array->Set(i, new_val);
+      wrote = true;
+    }
+  }
+
   if (wrote) {
     WriteBarrier::ForEveryFieldWrite(this);
   }
@@ -106,12 +144,12 @@
   SetResolvedFields(nullptr);
   SetResolvedMethodTypes(nullptr);
   SetResolvedCallSites(nullptr);
-  SetField32<false>(NumStringsOffset(), 0);
-  SetField32<false>(NumResolvedTypesOffset(), 0);
-  SetField32<false>(NumResolvedMethodsOffset(), 0);
-  SetField32<false>(NumResolvedFieldsOffset(), 0);
-  SetField32<false>(NumResolvedMethodTypesOffset(), 0);
-  SetField32<false>(NumResolvedCallSitesOffset(), 0);
+
+  SetStringsArray(nullptr);
+  SetResolvedTypesArray(nullptr);
+  SetResolvedMethodsArray(nullptr);
+  SetResolvedFieldsArray(nullptr);
+  SetResolvedMethodTypesArray(nullptr);
 }
 
 void DexCache::SetLocation(ObjPtr<mirror::String> location) {
@@ -126,5 +164,96 @@
   return GetFieldObject<ClassLoader>(OFFSET_OF_OBJECT_MEMBER(DexCache, class_loader_));
 }
 
+bool DexCache::ShouldAllocateFullArrayAtStartup() {
+  if (!kEnableFullArraysAtStartup) {
+    return false;
+  }
+  Runtime* runtime = Runtime::Current();
+  if (runtime->IsAotCompiler()) {
+    // To save on memory in dex2oat, we don't allocate full arrays by default.
+    return false;
+  }
+
+  if (runtime->IsZygote()) {
+    // Zygote doesn't have a notion of startup.
+    return false;
+  }
+
+  if (runtime->GetStartupCompleted()) {
+    // We only allocate full arrays during app startup.
+    return false;
+  }
+
+  if (GetClassLoader() == nullptr) {
+    // Only allocate full array for app dex files (also note that for
+    // multi-image, the `GetCompilerFilter` call below does not work for
+    // non-primary oat files).
+    return false;
+  }
+
+  const OatDexFile* oat_dex_file = GetDexFile()->GetOatDexFile();
+  if (oat_dex_file != nullptr &&
+      CompilerFilter::IsAotCompilationEnabled(oat_dex_file->GetOatFile()->GetCompilerFilter())) {
+    // We only allocate full arrays for dex files where we do not have
+    // compilation.
+    return false;
+  }
+
+  return true;
+}
+
+void DexCache::UnlinkStartupCaches() {
+  if (GetDexFile() == nullptr) {
+    // Unused dex cache.
+    return;
+  }
+  UnlinkStringsArrayIfStartup();
+  UnlinkResolvedFieldsArrayIfStartup();
+  UnlinkResolvedMethodsArrayIfStartup();
+  UnlinkResolvedTypesArrayIfStartup();
+  UnlinkResolvedMethodTypesArrayIfStartup();
+}
+
+void DexCache::SetResolvedType(dex::TypeIndex type_idx, ObjPtr<Class> resolved) {
+  DCHECK(resolved != nullptr);
+  DCHECK(resolved->IsResolved()) << resolved->GetStatus();
+  // TODO default transaction support.
+  // Use a release store for SetResolvedType. This is done to prevent other threads from seeing a
+  // class but not necessarily seeing the loaded members like the static fields array.
+  // See b/32075261.
+  SetResolvedTypesEntry(type_idx.index_, resolved.Ptr());
+  // TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
+  WriteBarrier::ForEveryFieldWrite(this);
+
+  if (this == resolved->GetDexCache()) {
+    // If we're updating the dex cache of the class, optimistically update the cache for methods and
+    // fields if the caches are full arrays.
+    auto* resolved_methods = GetResolvedMethodsArray();
+    if (resolved_methods != nullptr) {
+      PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+      // Because there could be duplicate method entries, we make sure we only
+      // update the cache with the first one found to be consistent with method
+      // resolution.
+      uint32_t previous_method_index = dex::kDexNoIndex;
+      for (ArtMethod& current_method : resolved->GetDeclaredMethods(pointer_size)) {
+        uint32_t new_index = current_method.GetDexMethodIndex();
+        if (new_index != previous_method_index) {
+          resolved_methods->Set(new_index, &current_method);
+          previous_method_index = new_index;
+        }
+      }
+    }
+    auto* resolved_fields = GetResolvedFieldsArray();
+    if (resolved_fields != nullptr) {
+      for (ArtField& current_field : resolved->GetSFields()) {
+        resolved_fields->Set(current_field.GetDexFieldIndex(), &current_field);
+      }
+      for (ArtField& current_field : resolved->GetIFields()) {
+        resolved_fields->Set(current_field.GetDexFieldIndex(), &current_field);
+      }
+    }
+  }
+}
+
 }  // namespace mirror
 }  // namespace art
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index 6701405..20e3e6c 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -19,10 +19,13 @@
 
 #include "array.h"
 #include "base/array_ref.h"
+#include "base/atomic_pair.h"
 #include "base/bit_utils.h"
 #include "base/locks.h"
+#include "dex/dex_file.h"
 #include "dex/dex_file_types.h"
 #include "gc_root.h"  // Note: must not use -inl here to avoid circular dependency.
+#include "linear_alloc.h"
 #include "object.h"
 #include "object_array.h"
 
@@ -37,7 +40,6 @@
 struct DexCacheOffsets;
 class DexFile;
 union JValue;
-class LinearAlloc;
 class ReflectiveValueVisitor;
 class Thread;
 
@@ -46,6 +48,7 @@
 class CallSite;
 class Class;
 class ClassLoader;
+class DexCache;
 class MethodType;
 class String;
 
@@ -115,20 +118,139 @@
   }
 };
 
-using TypeDexCachePair = DexCachePair<Class>;
-using TypeDexCacheType = std::atomic<TypeDexCachePair>;
+template <typename T, size_t size> class NativeDexCachePairArray {
+ public:
+  NativeDexCachePairArray() {}
 
-using StringDexCachePair = DexCachePair<String>;
-using StringDexCacheType = std::atomic<StringDexCachePair>;
+  T* Get(uint32_t index) REQUIRES_SHARED(Locks::mutator_lock_) {
+    auto pair = GetNativePair(entries_, SlotIndex(index));
+    return pair.GetObjectForIndex(index);
+  }
 
-using FieldDexCachePair = NativeDexCachePair<ArtField>;
-using FieldDexCacheType = std::atomic<FieldDexCachePair>;
+  void Set(uint32_t index, T* value) {
+    NativeDexCachePair<T> pair(value, index);
+    SetNativePair(entries_, SlotIndex(index), pair);
+  }
 
-using MethodDexCachePair = NativeDexCachePair<ArtMethod>;
-using MethodDexCacheType = std::atomic<MethodDexCachePair>;
+  NativeDexCachePair<T> GetNativePair(uint32_t index) REQUIRES_SHARED(Locks::mutator_lock_) {
+    return GetNativePair(entries_, SlotIndex(index));
+  }
 
-using MethodTypeDexCachePair = DexCachePair<MethodType>;
-using MethodTypeDexCacheType = std::atomic<MethodTypeDexCachePair>;
+  void SetNativePair(uint32_t index, NativeDexCachePair<T> value) {
+    SetNativePair(entries_, SlotIndex(index), value);
+  }
+
+ private:
+  NativeDexCachePair<T> GetNativePair(std::atomic<NativeDexCachePair<T>>* pair_array, size_t idx) {
+    auto* array = reinterpret_cast<std::atomic<AtomicPair<uintptr_t>>*>(pair_array);
+    AtomicPair<uintptr_t> value = AtomicPairLoadAcquire(&array[idx]);
+    return NativeDexCachePair<T>(reinterpret_cast<T*>(value.first), value.second);
+  }
+
+  void SetNativePair(std::atomic<NativeDexCachePair<T>>* pair_array,
+                     size_t idx,
+                     NativeDexCachePair<T> pair) {
+    auto* array = reinterpret_cast<std::atomic<AtomicPair<uintptr_t>>*>(pair_array);
+    AtomicPair<uintptr_t> v(reinterpret_cast<size_t>(pair.object), pair.index);
+    AtomicPairStoreRelease(&array[idx], v);
+  }
+
+  uint32_t SlotIndex(uint32_t index) {
+    return index % size;
+  }
+
+  std::atomic<NativeDexCachePair<T>> entries_[0];
+
+  NativeDexCachePairArray(const NativeDexCachePairArray<T, size>&) = delete;
+  NativeDexCachePairArray& operator=(const NativeDexCachePairArray<T, size>&) = delete;
+};
+
+template <typename T, size_t size> class DexCachePairArray {
+ public:
+  DexCachePairArray() {}
+
+  T* Get(uint32_t index) REQUIRES_SHARED(Locks::mutator_lock_) {
+    return GetPair(index).GetObjectForIndex(index);
+  }
+
+  void Set(uint32_t index, T* value) REQUIRES_SHARED(Locks::mutator_lock_) {
+    SetPair(index, DexCachePair<T>(value, index));
+  }
+
+  DexCachePair<T> GetPair(uint32_t index) {
+    return entries_[SlotIndex(index)].load(std::memory_order_relaxed);
+  }
+
+  void SetPair(uint32_t index, DexCachePair<T> value) {
+    entries_[SlotIndex(index)].store(value, std::memory_order_relaxed);
+  }
+
+  void Clear(uint32_t index) {
+    uint32_t slot = SlotIndex(index);
+    // This is racy but should only be called from the transactional interpreter.
+    if (entries_[slot].load(std::memory_order_relaxed).index == index) {
+      DexCachePair<T> cleared(nullptr, DexCachePair<T>::InvalidIndexForSlot(slot));
+      entries_[slot].store(cleared, std::memory_order_relaxed);
+    }
+  }
+
+ private:
+  uint32_t SlotIndex(uint32_t index) {
+    return index % size;
+  }
+
+  std::atomic<DexCachePair<T>> entries_[0];
+
+  DexCachePairArray(const DexCachePairArray<T, size>&) = delete;
+  DexCachePairArray& operator=(const DexCachePairArray<T, size>&) = delete;
+};
+
+template <typename T> class GcRootArray {
+ public:
+  GcRootArray() {}
+
+  T* Get(uint32_t index) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  Atomic<GcRoot<T>>* GetGcRoot(uint32_t index) REQUIRES_SHARED(Locks::mutator_lock_) {
+    return &entries_[index];
+  }
+
+  // Only to be used in locations that don't need the atomic or will later load
+  // and read atomically.
+  GcRoot<T>* GetGcRootAddress(uint32_t index) REQUIRES_SHARED(Locks::mutator_lock_) {
+    static_assert(sizeof(GcRoot<T>) == sizeof(Atomic<GcRoot<T>>));
+    return reinterpret_cast<GcRoot<T>*>(&entries_[index]);
+  }
+
+  void Set(uint32_t index, T* value) REQUIRES_SHARED(Locks::mutator_lock_);
+
+ private:
+  Atomic<GcRoot<T>> entries_[0];
+};
+
+template <typename T> class NativeArray {
+ public:
+  NativeArray() {}
+
+  T* Get(uint32_t index) {
+    return entries_[index].load(std::memory_order_relaxed);
+  }
+
+  T** GetPtrEntryPtrSize(uint32_t index, PointerSize ptr_size) {
+    if (ptr_size == PointerSize::k64) {
+      return reinterpret_cast<T**>(reinterpret_cast<uint64_t*>(entries_) + index);
+    } else {
+      return reinterpret_cast<T**>(reinterpret_cast<uint32_t*>(entries_) + index);
+    }
+  }
+
+  void Set(uint32_t index, T* value) {
+    entries_[index].store(value, std::memory_order_relaxed);
+  }
+
+ private:
+  Atomic<T*> entries_[0];
+};
 
 // C++ mirror of java.lang.DexCache.
 class MANAGED DexCache final : public Object {
@@ -138,6 +260,8 @@
   // Size of java.lang.DexCache.class.
   static uint32_t ClassSize(PointerSize pointer_size);
 
+  // Note: update the image version in image.cc if changing any of these cache sizes.
+
   // Size of type dex cache. Needs to be a power of 2 for entrypoint assumptions to hold.
   static constexpr size_t kDexCacheTypeCacheSize = 1024;
   static_assert(IsPowerOfTwo(kDexCacheTypeCacheSize),
@@ -164,31 +288,18 @@
   static_assert(IsPowerOfTwo(kDexCacheMethodTypeCacheSize),
                 "MethodType dex cache size is not a power of 2.");
 
-  static constexpr size_t StaticTypeSize() {
-    return kDexCacheTypeCacheSize;
-  }
-
-  static constexpr size_t StaticStringSize() {
-    return kDexCacheStringCacheSize;
-  }
-
-  static constexpr size_t StaticArtFieldSize() {
-    return kDexCacheFieldCacheSize;
-  }
-
-  static constexpr size_t StaticMethodSize() {
-    return kDexCacheMethodCacheSize;
-  }
-
-  static constexpr size_t StaticMethodTypeSize() {
-    return kDexCacheMethodTypeCacheSize;
-  }
-
   // Size of an instance of java.lang.DexCache not including referenced values.
   static constexpr uint32_t InstanceSize() {
     return sizeof(DexCache);
   }
 
+  // Visit gc-roots in DexCachePair array in [pairs_begin, pairs_end) range.
+  template <typename Visitor>
+  static void VisitDexCachePairRoots(Visitor& visitor,
+                                     DexCachePair<Object>* pairs_begin,
+                                     DexCachePair<Object>* pairs_end)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   void Initialize(const DexFile* dex_file, ObjPtr<ClassLoader> class_loader)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(Locks::dex_lock_);
@@ -201,66 +312,6 @@
            ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ObjPtr<String> GetLocation() REQUIRES_SHARED(Locks::mutator_lock_);
 
-  static constexpr MemberOffset StringsOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, strings_);
-  }
-
-  static constexpr MemberOffset PreResolvedStringsOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, preresolved_strings_);
-  }
-
-  static constexpr MemberOffset ResolvedTypesOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, resolved_types_);
-  }
-
-  static constexpr MemberOffset ResolvedFieldsOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, resolved_fields_);
-  }
-
-  static constexpr MemberOffset ResolvedMethodsOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, resolved_methods_);
-  }
-
-  static constexpr MemberOffset ResolvedMethodTypesOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, resolved_method_types_);
-  }
-
-  static constexpr MemberOffset ResolvedCallSitesOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, resolved_call_sites_);
-  }
-
-  static constexpr MemberOffset NumStringsOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, num_strings_);
-  }
-
-  static constexpr MemberOffset NumPreResolvedStringsOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, num_preresolved_strings_);
-  }
-
-  static constexpr MemberOffset NumResolvedTypesOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, num_resolved_types_);
-  }
-
-  static constexpr MemberOffset NumResolvedFieldsOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, num_resolved_fields_);
-  }
-
-  static constexpr MemberOffset NumResolvedMethodsOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, num_resolved_methods_);
-  }
-
-  static constexpr MemberOffset NumResolvedMethodTypesOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, num_resolved_method_types_);
-  }
-
-  static constexpr MemberOffset NumResolvedCallSitesOffset() {
-    return OFFSET_OF_OBJECT_MEMBER(DexCache, num_resolved_call_sites_);
-  }
-
-  static constexpr size_t PreResolvedStringsAlignment() {
-    return alignof(GcRoot<mirror::String>);
-  }
-
   String* GetResolvedString(dex::StringIndex string_idx) ALWAYS_INLINE
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -309,106 +360,6 @@
   ObjPtr<CallSite> SetResolvedCallSite(uint32_t call_site_idx, ObjPtr<CallSite> resolved)
       REQUIRES_SHARED(Locks::mutator_lock_) WARN_UNUSED;
 
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  StringDexCacheType* GetStrings() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetFieldPtr64<StringDexCacheType*, kVerifyFlags>(StringsOffset());
-  }
-
-  void SetStrings(StringDexCacheType* strings) ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
-    SetFieldPtr<false>(StringsOffset(), strings);
-  }
-
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  TypeDexCacheType* GetResolvedTypes() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetFieldPtr<TypeDexCacheType*, kVerifyFlags>(ResolvedTypesOffset());
-  }
-
-  void SetResolvedTypes(TypeDexCacheType* resolved_types)
-      ALWAYS_INLINE
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    SetFieldPtr<false>(ResolvedTypesOffset(), resolved_types);
-  }
-
-  MethodDexCacheType* GetResolvedMethods() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetFieldPtr<MethodDexCacheType*>(ResolvedMethodsOffset());
-  }
-
-  void SetResolvedMethods(MethodDexCacheType* resolved_methods)
-      ALWAYS_INLINE
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    SetFieldPtr<false>(ResolvedMethodsOffset(), resolved_methods);
-  }
-
-  FieldDexCacheType* GetResolvedFields() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetFieldPtr<FieldDexCacheType*>(ResolvedFieldsOffset());
-  }
-
-  void SetResolvedFields(FieldDexCacheType* resolved_fields)
-      ALWAYS_INLINE
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    SetFieldPtr<false>(ResolvedFieldsOffset(), resolved_fields);
-  }
-
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  MethodTypeDexCacheType* GetResolvedMethodTypes()
-      ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetFieldPtr64<MethodTypeDexCacheType*, kVerifyFlags>(ResolvedMethodTypesOffset());
-  }
-
-  void SetResolvedMethodTypes(MethodTypeDexCacheType* resolved_method_types)
-      ALWAYS_INLINE
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    SetFieldPtr<false>(ResolvedMethodTypesOffset(), resolved_method_types);
-  }
-
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  GcRoot<CallSite>* GetResolvedCallSites()
-      ALWAYS_INLINE
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetFieldPtr<GcRoot<CallSite>*, kVerifyFlags>(ResolvedCallSitesOffset());
-  }
-
-  void SetResolvedCallSites(GcRoot<CallSite>* resolved_call_sites)
-      ALWAYS_INLINE
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    SetFieldPtr<false>(ResolvedCallSitesOffset(), resolved_call_sites);
-  }
-
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  size_t NumStrings() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetField32<kVerifyFlags>(NumStringsOffset());
-  }
-
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  size_t NumPreResolvedStrings() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetField32<kVerifyFlags>(NumPreResolvedStringsOffset());
-  }
-
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  size_t NumResolvedTypes() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetField32<kVerifyFlags>(NumResolvedTypesOffset());
-  }
-
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  size_t NumResolvedMethods() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetField32<kVerifyFlags>(NumResolvedMethodsOffset());
-  }
-
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  size_t NumResolvedFields() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetField32<kVerifyFlags>(NumResolvedFieldsOffset());
-  }
-
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  size_t NumResolvedMethodTypes() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetField32<kVerifyFlags>(NumResolvedMethodTypesOffset());
-  }
-
-  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  size_t NumResolvedCallSites() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetField32<kVerifyFlags>(NumResolvedCallSitesOffset());
-  }
-
   const DexFile* GetDexFile() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
     return GetFieldPtr<const DexFile*>(OFFSET_OF_OBJECT_MEMBER(DexCache, dex_file_));
   }
@@ -419,35 +370,195 @@
 
   void SetLocation(ObjPtr<String> location) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  template <typename T>
-  static NativeDexCachePair<T> GetNativePair(std::atomic<NativeDexCachePair<T>>* pair_array,
-                                             size_t idx);
-
-  template <typename T>
-  static void SetNativePair(std::atomic<NativeDexCachePair<T>>* pair_array,
-                            size_t idx,
-                            NativeDexCachePair<T> pair);
-
-  static size_t PreResolvedStringsSize(size_t num_strings) {
-    return sizeof(GcRoot<mirror::String>) * num_strings;
-  }
-
-  uint32_t StringSlotIndex(dex::StringIndex string_idx) REQUIRES_SHARED(Locks::mutator_lock_);
-  uint32_t TypeSlotIndex(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_);
-  uint32_t FieldSlotIndex(uint32_t field_idx) REQUIRES_SHARED(Locks::mutator_lock_);
-  uint32_t MethodSlotIndex(uint32_t method_idx) REQUIRES_SHARED(Locks::mutator_lock_);
-  uint32_t MethodTypeSlotIndex(dex::ProtoIndex proto_idx) REQUIRES_SHARED(Locks::mutator_lock_);
-
   void VisitReflectiveTargets(ReflectiveValueVisitor* visitor) REQUIRES(Locks::mutator_lock_);
 
   void SetClassLoader(ObjPtr<ClassLoader> class_loader) REQUIRES_SHARED(Locks::mutator_lock_);
 
   ObjPtr<ClassLoader> GetClassLoader() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template <VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+            ReadBarrierOption kReadBarrierOption = kWithReadBarrier,
+            typename Visitor>
+  void VisitNativeRoots(const Visitor& visitor)
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
+
+  // Sets null to dex cache array fields which were allocated with the startup
+  // allocator.
+  void UnlinkStartupCaches() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Returns whether we should allocate a full array given the number of elements.
+  // Note: update the image version in image.cc if changing this method.
+  static bool ShouldAllocateFullArray(size_t number_of_elements, size_t dex_cache_size) {
+    return number_of_elements <= dex_cache_size;
+  }
+
+
+// NOLINTBEGIN(bugprone-macro-parentheses)
+#define DEFINE_ARRAY(name, array_kind, getter_setter, type, ids, alloc_kind) \
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> \
+  array_kind* Get ##getter_setter() \
+      ALWAYS_INLINE \
+      REQUIRES_SHARED(Locks::mutator_lock_) { \
+    return GetFieldPtr<array_kind*, kVerifyFlags>(getter_setter ##Offset()); \
+  } \
+  void Set ##getter_setter(array_kind* value) \
+      REQUIRES_SHARED(Locks::mutator_lock_) { \
+    SetFieldPtr<false>(getter_setter ##Offset(), value); \
+  } \
+  static constexpr MemberOffset getter_setter ##Offset() { \
+    return OFFSET_OF_OBJECT_MEMBER(DexCache, name); \
+  } \
+  array_kind* Allocate ##getter_setter(bool startup = false) \
+      REQUIRES_SHARED(Locks::mutator_lock_) { \
+    return reinterpret_cast<array_kind*>(AllocArray<type>( \
+        getter_setter ##Offset(), GetDexFile()->ids(), alloc_kind, startup)); \
+  } \
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> \
+  size_t Num ##getter_setter() REQUIRES_SHARED(Locks::mutator_lock_) { \
+    return Get ##getter_setter() == nullptr ? 0u : GetDexFile()->ids(); \
+  } \
+
+#define DEFINE_PAIR_ARRAY(name, pair_kind, getter_setter, type, size, alloc_kind) \
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> \
+  pair_kind ##Array<type, size>* Get ##getter_setter() \
+      ALWAYS_INLINE \
+      REQUIRES_SHARED(Locks::mutator_lock_) { \
+    return GetFieldPtr<pair_kind ##Array<type, size>*, kVerifyFlags>(getter_setter ##Offset()); \
+  } \
+  void Set ##getter_setter(pair_kind ##Array<type, size>* value) \
+      REQUIRES_SHARED(Locks::mutator_lock_) { \
+    SetFieldPtr<false>(getter_setter ##Offset(), value); \
+  } \
+  static constexpr MemberOffset getter_setter ##Offset() { \
+    return OFFSET_OF_OBJECT_MEMBER(DexCache, name); \
+  } \
+  pair_kind ##Array<type, size>* Allocate ##getter_setter() \
+      REQUIRES_SHARED(Locks::mutator_lock_) { \
+    return reinterpret_cast<pair_kind ##Array<type, size>*>( \
+        AllocArray<std::atomic<pair_kind<type>>>( \
+            getter_setter ##Offset(), size, alloc_kind)); \
+  } \
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> \
+  size_t Num ##getter_setter() REQUIRES_SHARED(Locks::mutator_lock_) { \
+    return Get ##getter_setter() == nullptr ? 0u : size; \
+  } \
+
+#define DEFINE_DUAL_CACHE( \
+    name, pair_kind, getter_setter, type, pair_size, alloc_pair_kind, \
+    array_kind, component_type, ids, alloc_array_kind) \
+  DEFINE_PAIR_ARRAY( \
+      name, pair_kind, getter_setter, type, pair_size, alloc_pair_kind) \
+  DEFINE_ARRAY( \
+      name ##array_, array_kind, getter_setter ##Array, component_type, ids, alloc_array_kind) \
+  type* Get ##getter_setter ##Entry(uint32_t index) REQUIRES_SHARED(Locks::mutator_lock_) { \
+    DCHECK_LT(index, GetDexFile()->ids()); \
+    auto* array = Get ##getter_setter ##Array(); \
+    if (array != nullptr) { \
+      return array->Get(index); \
+    } \
+    auto* pairs = Get ##getter_setter(); \
+    if (pairs != nullptr) { \
+      return pairs->Get(index); \
+    } \
+    return nullptr; \
+  } \
+  void Set ##getter_setter ##Entry(uint32_t index, type* resolved) \
+      REQUIRES_SHARED(Locks::mutator_lock_) { \
+    DCHECK_LT(index, GetDexFile()->ids()); \
+    auto* array = Get ##getter_setter ##Array(); \
+    if (array != nullptr) { \
+      array->Set(index, resolved); \
+    } else { \
+      auto* pairs = Get ##getter_setter(); \
+      if (pairs == nullptr) { \
+        bool should_allocate_full_array = ShouldAllocateFullArray(GetDexFile()->ids(), pair_size); \
+        if (ShouldAllocateFullArrayAtStartup() || should_allocate_full_array) { \
+          array = Allocate ##getter_setter ##Array(!should_allocate_full_array); \
+          array->Set(index, resolved); \
+        } else { \
+          pairs = Allocate ##getter_setter(); \
+          pairs->Set(index, resolved); \
+        } \
+      } else { \
+        pairs->Set(index, resolved); \
+      } \
+    } \
+  } \
+  void Unlink ##getter_setter ##ArrayIfStartup() \
+      REQUIRES_SHARED(Locks::mutator_lock_) { \
+    if (!ShouldAllocateFullArray(GetDexFile()->ids(), pair_size)) { \
+      Set ##getter_setter ##Array(nullptr) ; \
+    } \
+  }
+
+  DEFINE_ARRAY(resolved_call_sites_,
+               GcRootArray<CallSite>,
+               ResolvedCallSites,
+               GcRoot<CallSite>,
+               NumCallSiteIds,
+               LinearAllocKind::kGCRootArray)
+
+  DEFINE_DUAL_CACHE(resolved_fields_,
+                    NativeDexCachePair,
+                    ResolvedFields,
+                    ArtField,
+                    kDexCacheFieldCacheSize,
+                    LinearAllocKind::kNoGCRoots,
+                    NativeArray<ArtField>,
+                    ArtField,
+                    NumFieldIds,
+                    LinearAllocKind::kNoGCRoots)
+
+  DEFINE_DUAL_CACHE(resolved_method_types_,
+                    DexCachePair,
+                    ResolvedMethodTypes,
+                    mirror::MethodType,
+                    kDexCacheMethodTypeCacheSize,
+                    LinearAllocKind::kDexCacheArray,
+                    GcRootArray<mirror::MethodType>,
+                    GcRoot<mirror::MethodType>,
+                    NumProtoIds,
+                    LinearAllocKind::kGCRootArray);
+
+  DEFINE_DUAL_CACHE(resolved_methods_,
+                    NativeDexCachePair,
+                    ResolvedMethods,
+                    ArtMethod,
+                    kDexCacheMethodCacheSize,
+                    LinearAllocKind::kNoGCRoots,
+                    NativeArray<ArtMethod>,
+                    ArtMethod,
+                    NumMethodIds,
+                    LinearAllocKind::kNoGCRoots)
+
+  DEFINE_DUAL_CACHE(resolved_types_,
+                    DexCachePair,
+                    ResolvedTypes,
+                    mirror::Class,
+                    kDexCacheTypeCacheSize,
+                    LinearAllocKind::kDexCacheArray,
+                    GcRootArray<mirror::Class>,
+                    GcRoot<mirror::Class>,
+                    NumTypeIds,
+                    LinearAllocKind::kGCRootArray);
+
+  DEFINE_DUAL_CACHE(strings_,
+                    DexCachePair,
+                    Strings,
+                    mirror::String,
+                    kDexCacheStringCacheSize,
+                    LinearAllocKind::kDexCacheArray,
+                    GcRootArray<mirror::String>,
+                    GcRoot<mirror::String>,
+                    NumStringIds,
+                    LinearAllocKind::kGCRootArray);
+
+// NOLINTEND(bugprone-macro-parentheses)
+
  private:
   // Allocate new array in linear alloc and save it in the given fields.
-  template<typename T, size_t kMaxCacheSize>
-  T* AllocArray(MemberOffset obj_offset, MemberOffset num_offset, size_t num)
+  template<typename T>
+  T* AllocArray(MemberOffset obj_offset, size_t num, LinearAllocKind kind, bool startup = false)
      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Visit instance fields of the dex cache as well as its associated arrays.
@@ -458,30 +569,26 @@
   void VisitReferences(ObjPtr<Class> klass, const Visitor& visitor)
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
 
+  // Returns whether we should allocate a full array given the current state of
+  // the runtime and oat files.
+  bool ShouldAllocateFullArrayAtStartup() REQUIRES_SHARED(Locks::mutator_lock_);
+
   HeapReference<ClassLoader> class_loader_;
   HeapReference<String> location_;
 
-  uint64_t dex_file_;                // const DexFile*
-  uint64_t preresolved_strings_;     // GcRoot<mirror::String*> array with num_preresolved_strings
-                                     // elements.
-  uint64_t resolved_call_sites_;     // GcRoot<CallSite>* array with num_resolved_call_sites_
-                                     // elements.
-  uint64_t resolved_fields_;         // std::atomic<FieldDexCachePair>*, array with
-                                     // num_resolved_fields_ elements.
-  uint64_t resolved_method_types_;   // std::atomic<MethodTypeDexCachePair>* array with
-                                     // num_resolved_method_types_ elements.
-  uint64_t resolved_methods_;        // ArtMethod*, array with num_resolved_methods_ elements.
-  uint64_t resolved_types_;          // TypeDexCacheType*, array with num_resolved_types_ elements.
-  uint64_t strings_;                 // std::atomic<StringDexCachePair>*, array with num_strings_
-                                     // elements.
-
-  uint32_t num_preresolved_strings_;    // Number of elements in the preresolved_strings_ array.
-  uint32_t num_resolved_call_sites_;    // Number of elements in the call_sites_ array.
-  uint32_t num_resolved_fields_;        // Number of elements in the resolved_fields_ array.
-  uint32_t num_resolved_method_types_;  // Number of elements in the resolved_method_types_ array.
-  uint32_t num_resolved_methods_;       // Number of elements in the resolved_methods_ array.
-  uint32_t num_resolved_types_;         // Number of elements in the resolved_types_ array.
-  uint32_t num_strings_;                // Number of elements in the strings_ array.
+  uint64_t dex_file_;                     // const DexFile*
+                                          //
+  uint64_t resolved_call_sites_;          // Array of call sites
+  uint64_t resolved_fields_;              // NativeDexCacheArray holding ArtField's
+  uint64_t resolved_fields_array_;        // Array of ArtField's.
+  uint64_t resolved_method_types_;        // DexCacheArray holding mirror::MethodType's
+  uint64_t resolved_method_types_array_;  // Array of mirror::MethodType's
+  uint64_t resolved_methods_;             // NativeDexCacheArray holding ArtMethod's
+  uint64_t resolved_methods_array_;       // Array of ArtMethod's
+  uint64_t resolved_types_;               // DexCacheArray holding mirror::Class's
+  uint64_t resolved_types_array_;         // Array of resolved types.
+  uint64_t strings_;                      // DexCacheArray holding mirror::String's
+  uint64_t strings_array_;                // Array of String's.
 
   friend struct art::DexCacheOffsets;  // for verifying offset information
   friend class linker::ImageWriter;
diff --git a/runtime/mirror/dex_cache_test.cc b/runtime/mirror/dex_cache_test.cc
index a0e8fda..bc521f5 100644
--- a/runtime/mirror/dex_cache_test.cc
+++ b/runtime/mirror/dex_cache_test.cc
@@ -146,15 +146,16 @@
   // The MethodTypes dex file contains a single interface with two abstract
   // methods. It must therefore contain precisely two method IDs.
   ASSERT_EQ(2u, dex_file.NumProtoIds());
-  ASSERT_EQ(dex_file.NumProtoIds(), dex_cache->NumResolvedMethodTypes());
-  MethodTypeDexCacheType* method_types_cache = dex_cache->GetResolvedMethodTypes();
+  ASSERT_EQ(dex_file.NumProtoIds(), dex_cache->NumResolvedMethodTypesArray());
+  ASSERT_EQ(0u, dex_cache->NumResolvedMethodTypes());
+  auto* method_types_cache = dex_cache->GetResolvedMethodTypesArray();
 
   for (size_t i = 0; i < dex_file.NumProtoIds(); ++i) {
-    const MethodTypeDexCachePair pair = method_types_cache[i].load(std::memory_order_relaxed);
-    if (dex::ProtoIndex(pair.index) == method1_id.proto_idx_) {
-      ASSERT_EQ(method1_type.Get(), pair.object.Read());
-    } else if (dex::ProtoIndex(pair.index) == method2_id.proto_idx_) {
-      ASSERT_EQ(method2_type.Get(), pair.object.Read());
+    auto* method_type = method_types_cache->Get(i);
+    if (dex::ProtoIndex(i) == method1_id.proto_idx_) {
+      ASSERT_EQ(method1_type.Get(), method_type);
+    } else if (dex::ProtoIndex(i) == method2_id.proto_idx_) {
+      ASSERT_EQ(method2_type.Get(), method_type);
     } else {
       ASSERT_TRUE(false);
     }
diff --git a/runtime/mirror/method_handles_lookup.cc b/runtime/mirror/method_handles_lookup.cc
index e9c41f9..62c35df 100644
--- a/runtime/mirror/method_handles_lookup.cc
+++ b/runtime/mirror/method_handles_lookup.cc
@@ -20,7 +20,6 @@
 #include "class_root-inl.h"
 #include "dex/modifiers.h"
 #include "handle_scope.h"
-#include "jni/jni_internal.h"
 #include "mirror/method_handle_impl.h"
 #include "obj_ptr-inl.h"
 #include "object-inl.h"
@@ -42,25 +41,17 @@
 }
 
 ObjPtr<MethodHandlesLookup> MethodHandlesLookup::GetDefault(Thread* const self) {
-  ArtMethod* lookup = jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandles_lookup);
-  JValue result;
-  lookup->Invoke(self, nullptr, 0, &result, "L");
-  return ObjPtr<MethodHandlesLookup>::DownCast(result.GetL());
+  ArtMethod* lookup = WellKnownClasses::java_lang_invoke_MethodHandles_lookup;
+  return ObjPtr<MethodHandlesLookup>::DownCast(lookup->InvokeStatic<'L'>(self));
 }
 
 ObjPtr<MethodHandle> MethodHandlesLookup::FindConstructor(Thread* const self,
                                                           Handle<Class> klass,
                                                           Handle<MethodType> method_type) {
-  ArtMethod* findConstructor =
-      jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandles_Lookup_findConstructor);
-  uint32_t args[] = {
-    static_cast<uint32_t>(reinterpret_cast<uintptr_t>(this)),
-    static_cast<uint32_t>(reinterpret_cast<uintptr_t>(klass.Get())),
-    static_cast<uint32_t>(reinterpret_cast<uintptr_t>(method_type.Get()))
-  };
-  JValue result;
-  findConstructor->Invoke(self, args, sizeof(args), &result, "LLL");
-  return ObjPtr<MethodHandle>::DownCast(result.GetL());
+  ArtMethod* find_constructor =
+      WellKnownClasses::java_lang_invoke_MethodHandles_Lookup_findConstructor;
+  return ObjPtr<MethodHandle>::DownCast(
+      find_constructor->InvokeFinal<'L', 'L', 'L'>(self, this, klass.Get(), method_type.Get()));
 }
 
 }  // namespace mirror
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index c679fde..318a811 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -104,7 +104,7 @@
 }
 
 inline uint32_t Object::GetMarkBit() {
-  CHECK(kUseReadBarrier);
+  CHECK(gUseReadBarrier);
   return GetLockWord(false).MarkBitState();
 }
 
@@ -880,7 +880,7 @@
     // inheritance hierarchy and find reference offsets the hard way. In the static case, just
     // consider this class.
     for (ObjPtr<Class> klass = kIsStatic
-            ? AsClass<kVerifyFlags>()
+            ? ObjPtr<Class>::DownCast(this)
             : GetClass<kVerifyFlags, kReadBarrierOption>();
         klass != nullptr;
         klass = kIsStatic ? nullptr : klass->GetSuperClass<kVerifyFlags, kReadBarrierOption>()) {
diff --git a/runtime/mirror/object-readbarrier-inl.h b/runtime/mirror/object-readbarrier-inl.h
index 8b5703e..259b3dd 100644
--- a/runtime/mirror/object-readbarrier-inl.h
+++ b/runtime/mirror/object-readbarrier-inl.h
@@ -107,7 +107,8 @@
   LockWord lw(static_cast<uint32_t>(result));
   uint32_t rb_state = lw.ReadBarrierState();
   return rb_state;
-#elif defined(__i386__) || defined(__x86_64__)
+#elif defined(__i386__) || defined(__x86_64__) || defined(__riscv)
+  // TODO(riscv64): add arch-specific implementation
   LockWord lw = GetLockWord(false);
   // i386/x86_64 don't need fake address dependency. Use a compiler fence to avoid compiler
   // reordering.
diff --git a/runtime/mirror/object-refvisitor-inl.h b/runtime/mirror/object-refvisitor-inl.h
index f98c433..4c72cd5 100644
--- a/runtime/mirror/object-refvisitor-inl.h
+++ b/runtime/mirror/object-refvisitor-inl.h
@@ -90,6 +90,106 @@
   }
 }
 
+// Could be called with from-space address of the object as we access klass and
+// length (in case of arrays/strings) and we don't want to cause cascading faults.
+template <bool kFetchObjSize,
+          bool kVisitNativeRoots,
+          VerifyObjectFlags kVerifyFlags,
+          ReadBarrierOption kReadBarrierOption,
+          typename Visitor>
+inline size_t Object::VisitRefsForCompaction(const Visitor& visitor,
+                                             MemberOffset begin,
+                                             MemberOffset end) {
+  constexpr VerifyObjectFlags kSizeOfFlags = RemoveThisFlags(kVerifyFlags);
+  size_t size;
+  // We want to continue using pre-compact klass to avoid cascading faults.
+  ObjPtr<Class> klass = GetClass<kVerifyFlags, kReadBarrierOption>();
+  DCHECK(klass != nullptr) << "obj=" << this;
+  const uint32_t class_flags = klass->GetClassFlags<kVerifyNone>();
+  if (LIKELY(class_flags == kClassFlagNormal)) {
+    DCHECK((!klass->IsVariableSize<kVerifyFlags>()));
+    VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
+    size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+    DCHECK((!klass->IsClassClass<kVerifyFlags>()));
+    DCHECK(!klass->IsStringClass<kVerifyFlags>());
+    DCHECK(!klass->IsClassLoaderClass<kVerifyFlags>());
+    DCHECK((!klass->IsArrayClass<kVerifyFlags>()));
+  } else {
+    if ((class_flags & kClassFlagNoReferenceFields) == 0) {
+      DCHECK(!klass->IsStringClass<kVerifyFlags>());
+      if (class_flags == kClassFlagClass) {
+        DCHECK((klass->IsClassClass<kVerifyFlags>()));
+        ObjPtr<Class> as_klass = ObjPtr<Class>::DownCast(this);
+        as_klass->VisitReferences<kVisitNativeRoots, kVerifyFlags, kReadBarrierOption>(klass,
+                                                                                       visitor);
+        size = kFetchObjSize ? as_klass->SizeOf<kSizeOfFlags>() : 0;
+      } else if (class_flags == kClassFlagObjectArray) {
+        DCHECK((klass->IsObjectArrayClass<kVerifyFlags, kReadBarrierOption>()));
+        ObjPtr<ObjectArray<Object>> obj_arr = ObjPtr<ObjectArray<Object>>::DownCast(this);
+        obj_arr->VisitReferences(visitor, begin, end);
+        size = kFetchObjSize ?
+                   obj_arr->SizeOf<kSizeOfFlags, kReadBarrierOption, /*kIsObjArray*/ true>() :
+                   0;
+      } else if ((class_flags & kClassFlagReference) != 0) {
+        VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
+        // Visit referent also as this is about updating the reference only.
+        // There is no reference processing happening here.
+        visitor(this, mirror::Reference::ReferentOffset(), /* is_static= */ false);
+        size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+      } else if (class_flags == kClassFlagDexCache) {
+        ObjPtr<DexCache> const dex_cache = ObjPtr<DexCache>::DownCast(this);
+        dex_cache->VisitReferences<kVisitNativeRoots,
+                                   kVerifyFlags,
+                                   kReadBarrierOption>(klass, visitor);
+        size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+      } else {
+        ObjPtr<ClassLoader> const class_loader = ObjPtr<ClassLoader>::DownCast(this);
+        class_loader->VisitReferences<kVisitNativeRoots,
+                                      kVerifyFlags,
+                                      kReadBarrierOption>(klass, visitor);
+        size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+      }
+    } else {
+      DCHECK((!klass->IsClassClass<kVerifyFlags>()));
+      DCHECK((!klass->IsObjectArrayClass<kVerifyFlags, kReadBarrierOption>()));
+      if ((class_flags & kClassFlagString) != 0) {
+        size = kFetchObjSize ? static_cast<String*>(this)->SizeOf<kSizeOfFlags>() : 0;
+      } else if (klass->IsArrayClass<kVerifyFlags>()) {
+        // TODO: We can optimize this by implementing a SizeOf() version which takes
+        // component-size-shift as an argument, thereby avoiding multiple loads of
+        // component_type.
+        size = kFetchObjSize
+               ? static_cast<Array*>(this)->SizeOf<kSizeOfFlags, kReadBarrierOption>()
+               : 0;
+      } else {
+        DCHECK_EQ(class_flags, kClassFlagNoReferenceFields)
+            << "class_flags: " << std::hex << class_flags;
+        // Only possibility left is of a normal klass instance with no references.
+        size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+      }
+
+      if (kIsDebugBuild) {
+        // String still has instance fields for reflection purposes but these don't exist in
+        // actual string instances.
+        if (!klass->IsStringClass<kVerifyFlags>()) {
+          size_t total_reference_instance_fields = 0;
+          ObjPtr<Class> super_class = klass;
+          do {
+            total_reference_instance_fields +=
+                super_class->NumReferenceInstanceFields<kVerifyFlags>();
+            super_class = super_class->GetSuperClass<kVerifyFlags, kReadBarrierOption>();
+          } while (super_class != nullptr);
+          // The only reference field should be the object's class. This field is handled at the
+          // beginning of the function.
+          CHECK_EQ(total_reference_instance_fields, 1u);
+        }
+      }
+    }
+  }
+  visitor(this, ClassOffset(), /* is_static= */ false);
+  return size;
+}
+
 }  // namespace mirror
 }  // namespace art
 
diff --git a/runtime/mirror/object.cc b/runtime/mirror/object.cc
index ede1c66..5016c20 100644
--- a/runtime/mirror/object.cc
+++ b/runtime/mirror/object.cc
@@ -74,48 +74,52 @@
   const ObjPtr<Object> dest_obj_;
 };
 
+void Object::CopyRawObjectData(uint8_t* dst_bytes,
+                               ObjPtr<mirror::Object> src,
+                               size_t num_bytes) {
+  // Copy instance data.  Don't assume memcpy copies by words (b/32012820).
+  const size_t offset = sizeof(Object);
+  uint8_t* src_bytes = reinterpret_cast<uint8_t*>(src.Ptr()) + offset;
+  dst_bytes += offset;
+  DCHECK_ALIGNED(src_bytes, sizeof(uintptr_t));
+  DCHECK_ALIGNED(dst_bytes, sizeof(uintptr_t));
+  // Use word sized copies to begin.
+  while (num_bytes >= sizeof(uintptr_t)) {
+    reinterpret_cast<Atomic<uintptr_t>*>(dst_bytes)->store(
+        reinterpret_cast<Atomic<uintptr_t>*>(src_bytes)->load(std::memory_order_relaxed),
+        std::memory_order_relaxed);
+    src_bytes += sizeof(uintptr_t);
+    dst_bytes += sizeof(uintptr_t);
+    num_bytes -= sizeof(uintptr_t);
+  }
+  // Copy possible 32 bit word.
+  if (sizeof(uintptr_t) != sizeof(uint32_t) && num_bytes >= sizeof(uint32_t)) {
+    reinterpret_cast<Atomic<uint32_t>*>(dst_bytes)->store(
+        reinterpret_cast<Atomic<uint32_t>*>(src_bytes)->load(std::memory_order_relaxed),
+        std::memory_order_relaxed);
+    src_bytes += sizeof(uint32_t);
+    dst_bytes += sizeof(uint32_t);
+    num_bytes -= sizeof(uint32_t);
+  }
+  // Copy remaining bytes, avoid going past the end of num_bytes since there may be a redzone
+  // there.
+  while (num_bytes > 0) {
+    reinterpret_cast<Atomic<uint8_t>*>(dst_bytes)->store(
+        reinterpret_cast<Atomic<uint8_t>*>(src_bytes)->load(std::memory_order_relaxed),
+        std::memory_order_relaxed);
+    src_bytes += sizeof(uint8_t);
+    dst_bytes += sizeof(uint8_t);
+    num_bytes -= sizeof(uint8_t);
+  }
+}
+
 ObjPtr<Object> Object::CopyObject(ObjPtr<mirror::Object> dest,
                                   ObjPtr<mirror::Object> src,
                                   size_t num_bytes) {
-  // Copy instance data.  Don't assume memcpy copies by words (b/32012820).
-  {
-    const size_t offset = sizeof(Object);
-    uint8_t* src_bytes = reinterpret_cast<uint8_t*>(src.Ptr()) + offset;
-    uint8_t* dst_bytes = reinterpret_cast<uint8_t*>(dest.Ptr()) + offset;
-    num_bytes -= offset;
-    DCHECK_ALIGNED(src_bytes, sizeof(uintptr_t));
-    DCHECK_ALIGNED(dst_bytes, sizeof(uintptr_t));
-    // Use word sized copies to begin.
-    while (num_bytes >= sizeof(uintptr_t)) {
-      reinterpret_cast<Atomic<uintptr_t>*>(dst_bytes)->store(
-          reinterpret_cast<Atomic<uintptr_t>*>(src_bytes)->load(std::memory_order_relaxed),
-          std::memory_order_relaxed);
-      src_bytes += sizeof(uintptr_t);
-      dst_bytes += sizeof(uintptr_t);
-      num_bytes -= sizeof(uintptr_t);
-    }
-    // Copy possible 32 bit word.
-    if (sizeof(uintptr_t) != sizeof(uint32_t) && num_bytes >= sizeof(uint32_t)) {
-      reinterpret_cast<Atomic<uint32_t>*>(dst_bytes)->store(
-          reinterpret_cast<Atomic<uint32_t>*>(src_bytes)->load(std::memory_order_relaxed),
-          std::memory_order_relaxed);
-      src_bytes += sizeof(uint32_t);
-      dst_bytes += sizeof(uint32_t);
-      num_bytes -= sizeof(uint32_t);
-    }
-    // Copy remaining bytes, avoid going past the end of num_bytes since there may be a redzone
-    // there.
-    while (num_bytes > 0) {
-      reinterpret_cast<Atomic<uint8_t>*>(dst_bytes)->store(
-          reinterpret_cast<Atomic<uint8_t>*>(src_bytes)->load(std::memory_order_relaxed),
-          std::memory_order_relaxed);
-      src_bytes += sizeof(uint8_t);
-      dst_bytes += sizeof(uint8_t);
-      num_bytes -= sizeof(uint8_t);
-    }
-  }
+  // Copy everything but the header.
+  CopyRawObjectData(reinterpret_cast<uint8_t*>(dest.Ptr()), src, num_bytes - sizeof(Object));
 
-  if (kUseReadBarrier) {
+  if (gUseReadBarrier) {
     // We need a RB here. After copying the whole object above, copy references fields one by one
     // again with a RB to make sure there are no from space refs. TODO: Optimize this later?
     CopyReferenceFieldsWithReadBarrierVisitor visitor(dest);
diff --git a/runtime/mirror/object.h b/runtime/mirror/object.h
index ac72745..95b9f86 100644
--- a/runtime/mirror/object.h
+++ b/runtime/mirror/object.h
@@ -154,7 +154,7 @@
   void SetLockWord(LockWord new_val, bool as_volatile) REQUIRES_SHARED(Locks::mutator_lock_);
   bool CasLockWord(LockWord old_val, LockWord new_val, CASMode mode, std::memory_order memory_order)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  uint32_t GetLockOwnerThreadId();
+  uint32_t GetLockOwnerThreadId() REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Try to enter the monitor, returns non null if we succeeded.
   ObjPtr<mirror::Object> MonitorTryEnter(Thread* self)
@@ -647,6 +647,17 @@
             typename JavaLangRefVisitor = VoidFunctor>
   void VisitReferences(const Visitor& visitor, const JavaLangRefVisitor& ref_visitor)
       NO_THREAD_SAFETY_ANALYSIS;
+  // VisitReferences version for compaction. It is invoked with from-space
+  // object so that portions of the object, like klass and length (for arrays),
+  // can be accessed without causing cascading faults.
+  template <bool kFetchObjSize = true,
+            bool kVisitNativeRoots = false,
+            VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+            ReadBarrierOption kReadBarrierOption = kWithFromSpaceBarrier,
+            typename Visitor>
+  size_t VisitRefsForCompaction(const Visitor& visitor,
+                                MemberOffset begin,
+                                MemberOffset end) NO_THREAD_SAFETY_ANALYSIS;
 
   ArtField* FindFieldByOffset(MemberOffset offset) REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -664,6 +675,13 @@
   std::string PrettyTypeOf()
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // A utility function that does a raw copy of `src`'s data into the buffer `dst_bytes`.
+  // Skips the object header.
+  static void CopyRawObjectData(uint8_t* dst_bytes,
+                                ObjPtr<mirror::Object> src,
+                                size_t num_bytes)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
  protected:
   // Accessors for non-Java type fields
   template<class T, VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags, bool kIsVolatile = false>
@@ -725,7 +743,7 @@
   }
 
   template<VerifyObjectFlags kVerifyFlags>
-  ALWAYS_INLINE void Verify() {
+  ALWAYS_INLINE void Verify() REQUIRES_SHARED(Locks::mutator_lock_) {
     if (kVerifyFlags & kVerifyThis) {
       VerifyObject(this);
     }
@@ -733,21 +751,23 @@
 
   // Not ObjPtr since the values may be unaligned for logic in verification.cc.
   template<VerifyObjectFlags kVerifyFlags, typename Reference>
-  ALWAYS_INLINE static void VerifyRead(Reference value) {
+  ALWAYS_INLINE static void VerifyRead(Reference value) REQUIRES_SHARED(Locks::mutator_lock_) {
     if (kVerifyFlags & kVerifyReads) {
       VerifyObject(value);
     }
   }
 
   template<VerifyObjectFlags kVerifyFlags>
-  ALWAYS_INLINE static void VerifyWrite(ObjPtr<mirror::Object> value) {
+  ALWAYS_INLINE static void VerifyWrite(ObjPtr<mirror::Object> value)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
     if (kVerifyFlags & kVerifyWrites) {
       VerifyObject(value);
     }
   }
 
   template<VerifyObjectFlags kVerifyFlags>
-  ALWAYS_INLINE void VerifyCAS(ObjPtr<mirror::Object> new_value, ObjPtr<mirror::Object> old_value) {
+  ALWAYS_INLINE void VerifyCAS(ObjPtr<mirror::Object> new_value, ObjPtr<mirror::Object> old_value)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
     Verify<kVerifyFlags>();
     VerifyRead<kVerifyFlags>(old_value);
     VerifyWrite<kVerifyFlags>(new_value);
diff --git a/runtime/mirror/object_array-inl.h b/runtime/mirror/object_array-inl.h
index e4fe03b..87f24eb 100644
--- a/runtime/mirror/object_array-inl.h
+++ b/runtime/mirror/object_array-inl.h
@@ -121,7 +121,7 @@
   if (copy_forward) {
     // Forward copy.
     bool baker_non_gray_case = false;
-    if (kUseReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       uintptr_t fake_address_dependency;
       if (!ReadBarrier::IsGray(src.Ptr(), &fake_address_dependency)) {
         baker_non_gray_case = true;
@@ -146,7 +146,7 @@
   } else {
     // Backward copy.
     bool baker_non_gray_case = false;
-    if (kUseReadBarrier && kUseBakerReadBarrier) {
+    if (gUseReadBarrier && kUseBakerReadBarrier) {
       uintptr_t fake_address_dependency;
       if (!ReadBarrier::IsGray(src.Ptr(), &fake_address_dependency)) {
         baker_non_gray_case = true;
@@ -196,7 +196,7 @@
   // We can't use memmove since it does not handle read barriers and may do by per byte copying.
   // See b/32012820.
   bool baker_non_gray_case = false;
-  if (kUseReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     uintptr_t fake_address_dependency;
     if (!ReadBarrier::IsGray(src.Ptr(), &fake_address_dependency)) {
       baker_non_gray_case = true;
@@ -244,7 +244,7 @@
   ObjPtr<T> o = nullptr;
   int i = 0;
   bool baker_non_gray_case = false;
-  if (kUseReadBarrier && kUseBakerReadBarrier) {
+  if (gUseReadBarrier && kUseBakerReadBarrier) {
     uintptr_t fake_address_dependency;
     if (!ReadBarrier::IsGray(src.Ptr(), &fake_address_dependency)) {
       baker_non_gray_case = true;
@@ -327,7 +327,20 @@
 inline void ObjectArray<T>::VisitReferences(const Visitor& visitor) {
   const size_t length = static_cast<size_t>(GetLength());
   for (size_t i = 0; i < length; ++i) {
-    visitor(this, OffsetOfElement(i), false);
+    visitor(this, OffsetOfElement(i), /* is_static= */ false);
+  }
+}
+
+template<class T> template<typename Visitor>
+inline void ObjectArray<T>::VisitReferences(const Visitor& visitor,
+                                            MemberOffset begin,
+                                            MemberOffset end) {
+  const size_t length = static_cast<size_t>(GetLength());
+  begin = std::max(begin, OffsetOfElement(0));
+  end = std::min(end, OffsetOfElement(length));
+  while (begin < end) {
+    visitor(this, begin, /* is_static= */ false, /*is_obj_array*/ true);
+    begin += kHeapReferenceSize;
   }
 }
 
diff --git a/runtime/mirror/object_array.h b/runtime/mirror/object_array.h
index a20c86b..9a53708 100644
--- a/runtime/mirror/object_array.h
+++ b/runtime/mirror/object_array.h
@@ -150,6 +150,10 @@
   // REQUIRES_SHARED(Locks::mutator_lock_).
   template<typename Visitor>
   void VisitReferences(const Visitor& visitor) NO_THREAD_SAFETY_ANALYSIS;
+  template<typename Visitor>
+  void VisitReferences(const Visitor& visitor,
+                       MemberOffset begin,
+                       MemberOffset end) NO_THREAD_SAFETY_ANALYSIS;
 
   friend class Object;  // For VisitReferences
   DISALLOW_IMPLICIT_CONSTRUCTORS(ObjectArray);
diff --git a/runtime/mirror/object_reference.h b/runtime/mirror/object_reference.h
index 386244d..251bcfd 100644
--- a/runtime/mirror/object_reference.h
+++ b/runtime/mirror/object_reference.h
@@ -21,6 +21,7 @@
 #include <string_view>
 
 #include "base/atomic.h"
+#include "base/casts.h"
 #include "base/locks.h"  // For Locks::mutator_lock_.
 #include "heap_poisoning.h"
 #include "obj_ptr.h"
@@ -50,6 +51,7 @@
     vis("Ljava/lang/ClassNotFoundException;")         \
     vis("Ljava/lang/DexCache;")                       \
     vis("Ljava/lang/Object;")                         \
+    vis("Ljava/lang/StackFrameInfo;")                 \
     vis("Ljava/lang/StackTraceElement;")              \
     vis("Ljava/lang/String;")                         \
     vis("Ljava/lang/Throwable;")                      \
@@ -94,14 +96,14 @@
  public:
   // Compress reference to its bit representation.
   static uint32_t Compress(MirrorType* mirror_ptr) {
-    uintptr_t as_bits = reinterpret_cast<uintptr_t>(mirror_ptr);
-    return static_cast<uint32_t>(kPoisonReferences ? -as_bits : as_bits);
+    uint32_t as_bits = reinterpret_cast32<uint32_t>(mirror_ptr);
+    return kPoisonReferences ? -as_bits : as_bits;
   }
 
   // Uncompress an encoded reference from its bit representation.
   static MirrorType* Decompress(uint32_t ref) {
-    uintptr_t as_bits = kPoisonReferences ? -ref : ref;
-    return reinterpret_cast<MirrorType*>(as_bits);
+    uint32_t as_bits = kPoisonReferences ? -ref : ref;
+    return reinterpret_cast32<MirrorType*>(as_bits);
   }
 
   // Convert an ObjPtr to a compressed reference.
@@ -143,10 +145,6 @@
     return reference_ == 0;
   }
 
-  uint32_t AsVRegValue() const {
-    return reference_;
-  }
-
   static ObjectReference<kPoisonReferences, MirrorType> FromMirrorPtr(MirrorType* mirror_ptr)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     return ObjectReference<kPoisonReferences, MirrorType>(mirror_ptr);
@@ -156,6 +154,9 @@
   explicit ObjectReference(MirrorType* mirror_ptr) REQUIRES_SHARED(Locks::mutator_lock_)
       : reference_(Compression::Compress(mirror_ptr)) {
   }
+  ObjectReference() : reference_(0u) {
+    DCHECK(IsNull());
+  }
 
   // The encoded reference to a mirror::Object.
   uint32_t reference_;
@@ -221,17 +222,27 @@
 template<class MirrorType>
 class MANAGED CompressedReference : public mirror::ObjectReference<false, MirrorType> {
  public:
-  CompressedReference<MirrorType>() REQUIRES_SHARED(Locks::mutator_lock_)
-      : mirror::ObjectReference<false, MirrorType>(nullptr) {}
+  CompressedReference<MirrorType>()
+      : mirror::ObjectReference<false, MirrorType>() {}
 
   static CompressedReference<MirrorType> FromMirrorPtr(MirrorType* p)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     return CompressedReference<MirrorType>(p);
   }
 
+  static CompressedReference<MirrorType> FromVRegValue(uint32_t vreg_value) {
+    CompressedReference<MirrorType> result;
+    result.reference_ = vreg_value;
+    return result;
+  }
+
+  uint32_t AsVRegValue() const {
+    return this->reference_;
+  }
+
  private:
   explicit CompressedReference(MirrorType* p) REQUIRES_SHARED(Locks::mutator_lock_)
-      : mirror::ObjectReference<false, MirrorType>(p) {}
+      : ObjectReference<false, MirrorType>(p) {}
 };
 
 }  // namespace mirror
diff --git a/runtime/mirror/object_test.cc b/runtime/mirror/object_test.cc
index e26cf42..6f42c5b 100644
--- a/runtime/mirror/object_test.cc
+++ b/runtime/mirror/object_test.cc
@@ -50,6 +50,10 @@
 
 class ObjectTest : public CommonRuntimeTest {
  protected:
+  ObjectTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
   void AssertString(int32_t expected_utf16_length,
                     const char* utf8_in,
                     const char* utf16_expected_le,
@@ -420,10 +424,12 @@
   ASSERT_TRUE(field_id != nullptr);
   uint32_t field_idx = dex_file->GetIndexForFieldId(*field_id);
 
-  ArtField* field = FindFieldFromCode<StaticObjectRead, true>(field_idx, clinit, Thread::Current(),
-                                                              sizeof(HeapReference<Object>));
+  ArtField* field = FindFieldFromCode<StaticObjectRead>(field_idx,
+                                                        clinit,
+                                                        Thread::Current(),
+                                                        sizeof(HeapReference<Object>));
   ObjPtr<Object> s0 = field->GetObj(klass.Get());
-  EXPECT_TRUE(s0 != nullptr);
+  EXPECT_TRUE(s0 != nullptr) << field->PrettyField();
 
   Handle<CharArray> char_array(hs.NewHandle(CharArray::Alloc(soa.Self(), 0)));
   field->SetObj<false>(field->GetDeclaringClass(), char_array.Get());
@@ -522,7 +528,7 @@
   StackHandleScope<1> hs(soa.Self());
   Handle<String> string(hs.NewHandle(String::AllocFromModifiedUtf8(soa.Self(), "android")));
   EXPECT_EQ(string->GetLength(), 7);
-  EXPECT_EQ(string->GetUtfLength(), 7);
+  EXPECT_EQ(string->GetModifiedUtf8Length(), 7);
 }
 
 TEST_F(ObjectTest, DescriptorCompare) {
diff --git a/runtime/mirror/stack_frame_info.cc b/runtime/mirror/stack_frame_info.cc
new file mode 100644
index 0000000..dd3e8f7
--- /dev/null
+++ b/runtime/mirror/stack_frame_info.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "stack_frame_info.h"
+
+#include "class-alloc-inl.h"
+#include "class.h"
+#include "class_root-inl.h"
+#include "gc/accounting/card_table-inl.h"
+#include "handle_scope-inl.h"
+#include "object-inl.h"
+#include "string.h"
+
+namespace art {
+namespace mirror {
+
+void StackFrameInfo::AssignFields(Handle<Class> declaring_class,
+                                  Handle<MethodType> method_type,
+                                  Handle<String> method_name,
+                                  Handle<String> file_name,
+                                  int32_t line_number,
+                                  int32_t dex_pc) {
+  if (Runtime::Current()->IsActiveTransaction()) {
+    SetFields<true>(declaring_class.Get(), method_type.Get(), method_name.Get(),
+                    file_name.Get(), line_number, dex_pc);
+  } else {
+    SetFields<false>(declaring_class.Get(), method_type.Get(), method_name.Get(),
+                     file_name.Get(), line_number, dex_pc);
+  }
+}
+
+template<bool kTransactionActive>
+void StackFrameInfo::SetFields(ObjPtr<Class> declaring_class,
+                               ObjPtr<MethodType> method_type,
+                               ObjPtr<String> method_name,
+                               ObjPtr<String> file_name,
+                               int32_t line_number,
+                               int32_t bci) {
+  SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, declaring_class_),
+                                     declaring_class);
+  SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, method_type_),
+                                     method_type);
+  SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, method_name_),
+                                     method_name);
+  SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, file_name_),
+                                     file_name);
+  SetField32<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, line_number_),
+                                 line_number);
+  SetField32<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, bci_),
+                                 bci);
+}
+
+}  // namespace mirror
+}  // namespace art
diff --git a/runtime/mirror/stack_frame_info.h b/runtime/mirror/stack_frame_info.h
new file mode 100644
index 0000000..24f8c8f
--- /dev/null
+++ b/runtime/mirror/stack_frame_info.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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_RUNTIME_MIRROR_STACK_FRAME_INFO_H_
+#define ART_RUNTIME_MIRROR_STACK_FRAME_INFO_H_
+
+#include "method_type.h"
+#include "object.h"
+#include "stack_trace_element.h"
+
+namespace art {
+
+template<class T> class Handle;
+struct StackFrameInfoOffsets;
+
+namespace mirror {
+
+// C++ mirror of java.lang.StackFrameInfo
+class MANAGED StackFrameInfo final : public Object {
+ public:
+  MIRROR_CLASS("Ljava/lang/StackFrameInfo;");
+
+  void AssignFields(Handle<Class> declaring_class,
+                    Handle<MethodType> method_type,
+                    Handle<String> method_name,
+                    Handle<String> file_name,
+                    int32_t line_number,
+                    int32_t dex_pc)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+ private:
+  // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
+  HeapReference<Class> declaring_class_;
+  HeapReference<String> file_name_;
+  HeapReference<String> method_name_;
+  HeapReference<Class> method_type_;
+  HeapReference<StackTraceElement> ste_;
+  int32_t bci_;
+  int32_t line_number_;
+  bool retain_class_ref_;
+
+  template<bool kTransactionActive>
+  void SetFields(ObjPtr<Class> declaring_class,
+                 ObjPtr<MethodType> method_type,
+                 ObjPtr<String> method_name,
+                 ObjPtr<String> file_name,
+                 int32_t line_number,
+                 int32_t bci)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  friend struct art::StackFrameInfoOffsets;  // for verifying offset information
+  DISALLOW_IMPLICIT_CONSTRUCTORS(StackFrameInfo);
+};
+
+}  // namespace mirror
+}  // namespace art
+
+#endif  // ART_RUNTIME_MIRROR_STACK_FRAME_INFO_H_
diff --git a/runtime/mirror/string-alloc-inl.h b/runtime/mirror/string-alloc-inl.h
index 533053d..cb2dcb2 100644
--- a/runtime/mirror/string-alloc-inl.h
+++ b/runtime/mirror/string-alloc-inl.h
@@ -18,6 +18,7 @@
 
 #include "string-inl.h"
 
+#include "android-base/endian.h"
 #include "android-base/stringprintf.h"
 
 #include "array.h"
@@ -89,6 +90,41 @@
 };
 
 // Sets string count and value in the allocation code path to ensure it is guarded by a CAS.
+class SetStringCountAndUtf16BytesVisitor {
+ public:
+  SetStringCountAndUtf16BytesVisitor(int32_t count, Handle<ByteArray> src_array, int32_t offset)
+      : count_(count), src_array_(src_array), offset_(offset) {
+  }
+
+  void operator()(ObjPtr<Object> obj, size_t usable_size ATTRIBUTE_UNUSED) const
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    // Avoid AsString as object is not yet in live bitmap or allocation stack.
+    ObjPtr<String> string = ObjPtr<String>::DownCast(obj);
+    string->SetCount(count_);
+    DCHECK_IMPLIES(string->IsCompressed(), kUseStringCompression);
+    uint32_t length = String::GetLengthFromCount(count_);
+    const uint8_t* const src = reinterpret_cast<uint8_t*>(src_array_->GetData()) + offset_;
+    if (UNLIKELY(string->IsCompressed())) {
+      uint8_t* valueCompressed = string->GetValueCompressed();
+      for (uint32_t i = 0; i < length; i++) {
+        valueCompressed[i] = (src[i << 1] & 0xFF);
+      }
+    } else {
+      uint16_t* value = string->GetValue();
+      for (uint32_t i = 0; i < length; i++) {
+        uint32_t index = (i << 1);
+        value[i] = (src[index] & 0xFF) + ((src[index + 1] & 0xFF) << 8);
+      }
+    }
+  }
+
+ private:
+  const int32_t count_;
+  Handle<ByteArray> src_array_;
+  const int32_t offset_;
+};
+
+// Sets string count and value in the allocation code path to ensure it is guarded by a CAS.
 class SetStringCountAndValueVisitorFromCharArray {
  public:
   SetStringCountAndValueVisitorFromCharArray(int32_t count, Handle<CharArray> src_array,
@@ -216,13 +252,39 @@
   const uint8_t* const src = reinterpret_cast<uint8_t*>(array->GetData()) + offset;
   high_byte &= 0xff;  // Extract the relevant bits before determining `compressible`.
   const bool compressible =
-      kUseStringCompression && String::AllASCII<uint8_t>(src, byte_length) && (high_byte == 0);
+      kUseStringCompression &&
+      String::AllASCII<uint8_t>(src, byte_length) &&
+      (high_byte == 0 || byte_length == 0);
   const int32_t length_with_flag = String::GetFlaggedCount(byte_length, compressible);
   SetStringCountAndBytesVisitor visitor(length_with_flag, array, offset, high_byte << 8);
   return Alloc<kIsInstrumented>(self, length_with_flag, allocator_type, visitor);
 }
 
 template <bool kIsInstrumented>
+inline ObjPtr<String> String::AllocFromUtf16ByteArray(Thread* self,
+                                                      int32_t char_count,
+                                                      Handle<ByteArray> array,
+                                                      int32_t offset,
+                                                      gc::AllocatorType allocator_type) {
+  static_assert(__BYTE_ORDER == __LITTLE_ENDIAN,
+      "Please update this function and java-side callers to support big endian.");
+  const uint8_t* const src = reinterpret_cast<uint8_t*>(array->GetData()) + offset;
+  bool compressible = kUseStringCompression;
+  if (compressible) {
+    uint32_t byte_count = (static_cast<uint32_t>(char_count) << 1);
+    for (uint32_t i = 0; i < byte_count; i += 2) {
+      if (!IsASCII((src[i] & 0xff) + ((src[i + 1] & 0xff) << 8))) {
+        compressible = false;
+        break;
+      }
+    }
+  }
+  const int32_t length_with_flag = String::GetFlaggedCount(char_count, compressible);
+  SetStringCountAndUtf16BytesVisitor visitor(length_with_flag, array, offset);
+  return Alloc<kIsInstrumented>(self, length_with_flag, allocator_type, visitor);
+}
+
+template <bool kIsInstrumented>
 inline ObjPtr<String> String::AllocFromCharArray(Thread* self,
                                                  int32_t count,
                                                  Handle<CharArray> array,
diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h
index 5903872..883a45c 100644
--- a/runtime/mirror/string-inl.h
+++ b/runtime/mirror/string-inl.h
@@ -35,11 +35,11 @@
   //   lambda$codePoints$1$CharSequence
   // which were virtual functions in standalone desugar, becomes
   // direct functions with D8 desugaring.
-  uint32_t vtable_entries = Object::kVTableLength + 60;
+  uint32_t vtable_entries = Object::kVTableLength + 67;
 #else
-  uint32_t vtable_entries = Object::kVTableLength + 62;
+  uint32_t vtable_entries = Object::kVTableLength + 69;
 #endif
-  return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 1, 2, pointer_size);
+  return Class::ComputeClassSize(true, vtable_entries, 3, 0, 0, 1, 2, pointer_size);
 }
 
 inline uint16_t String::CharAt(int32_t index) {
@@ -83,11 +83,11 @@
   return result;
 }
 
-inline int32_t String::GetUtfLength() {
+inline int32_t String::GetModifiedUtf8Length() {
   if (IsCompressed()) {
     return GetLength();
   } else {
-    return CountUtf8Bytes(GetValue(), GetLength());
+    return CountModifiedUtf8BytesInUtf16(GetValue(), GetLength());
   }
 }
 
diff --git a/runtime/mirror/string.cc b/runtime/mirror/string.cc
index d059278..839e01a 100644
--- a/runtime/mirror/string.cc
+++ b/runtime/mirror/string.cc
@@ -19,6 +19,7 @@
 #include "arch/memcmp16.h"
 #include "array-alloc-inl.h"
 #include "base/array_ref.h"
+#include "base/casts.h"
 #include "base/stl_util.h"
 #include "class-inl.h"
 #include "dex/descriptors_names.h"
@@ -254,7 +255,7 @@
   return Alloc(self, length_with_flag, allocator_type, visitor);
 }
 
-bool String::Equals(ObjPtr<String> that) {
+bool String::Equals(mirror::String* that) {
   if (this == that) {
     // Quick reference equality test
     return true;
@@ -312,7 +313,7 @@
   if (IsCompressed()) {
     return std::string(reinterpret_cast<const char*>(GetValueCompressed()), GetLength());
   } else {
-    size_t byte_count = GetUtfLength();
+    size_t byte_count = GetModifiedUtf8Length();
     std::string result(byte_count, static_cast<char>(0));
     ConvertUtf16ToModifiedUtf8(&result[0], byte_count, GetValue(), GetLength());
     return result;
@@ -396,6 +397,38 @@
   }
 }
 
+void String::FillBytesLatin1(Handle<ByteArray> array, int32_t index) {
+  int8_t* data = array->GetData() + index;
+  int32_t length = GetLength();
+  if (IsCompressed()) {
+    const uint8_t* value = GetValueCompressed();
+    memcpy(data, value, length * sizeof(uint8_t));
+  } else {
+    // Drop the high byte of the characters.
+    // The caller should check that all dropped high bytes are zeros.
+    const uint16_t* value = GetValue();
+    for (int32_t i = 0; i < length; ++i) {
+      data[i] = static_cast<int8_t>(dchecked_integral_cast<uint8_t>(value[i]));
+    }
+  }
+}
+
+void String::FillBytesUTF16(Handle<ByteArray> array, int32_t index) {
+  int8_t* data = array->GetData() + index;
+  int32_t length = GetLength();
+  if (IsCompressed()) {
+    const uint8_t* value = GetValueCompressed();
+    uint32_t d_index = 0;
+    for (int i = 0; i < length; ++i) {
+      data[d_index++] = static_cast<int8_t>(value[i]);
+      data[d_index++] = 0;
+    }
+  } else {
+    const uint16_t* value = GetValue();
+    memcpy(data, value, length * sizeof(uint16_t));
+  }
+}
+
 bool String::IsValueNull() {
   return (IsCompressed()) ? (GetValueCompressed() == nullptr) : (GetValue() == nullptr);
 }
diff --git a/runtime/mirror/string.h b/runtime/mirror/string.h
index 2eb8e0a..90eb092 100644
--- a/runtime/mirror/string.h
+++ b/runtime/mirror/string.h
@@ -114,7 +114,7 @@
   // Computes and returns the hash code.
   int32_t ComputeHashCode() REQUIRES_SHARED(Locks::mutator_lock_);
 
-  int32_t GetUtfLength() REQUIRES_SHARED(Locks::mutator_lock_);
+  int32_t GetModifiedUtf8Length() REQUIRES_SHARED(Locks::mutator_lock_);
 
   uint16_t CharAt(int32_t index) REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -142,6 +142,14 @@
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_);
 
   template <bool kIsInstrumented = true>
+  ALWAYS_INLINE static ObjPtr<String> AllocFromUtf16ByteArray(Thread* self,
+                                                         int32_t char_count,
+                                                         Handle<ByteArray> array,
+                                                         int32_t offset,
+                                                         gc::AllocatorType allocator_type)
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_);
+
+  template <bool kIsInstrumented = true>
   ALWAYS_INLINE static ObjPtr<String> AllocFromCharArray(Thread* self,
                                                          int32_t count,
                                                          Handle<CharArray> array,
@@ -189,7 +197,14 @@
 
   bool Equals(const char* modified_utf8) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  bool Equals(ObjPtr<String> that) REQUIRES_SHARED(Locks::mutator_lock_);
+  bool Equals(ObjPtr<mirror::String> that) REQUIRES_SHARED(Locks::mutator_lock_) {
+    return Equals(that.Ptr());
+  }
+
+  // A version that takes a mirror::String pointer instead of ObjPtr as it's being
+  // called by the runtime app image code which can encode mirror::String at 64bit
+  // addresses (ObjPtr only works with 32bit pointers).
+  bool Equals(mirror::String* that) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Create a modified UTF-8 encoded std::string from a java/lang/String object.
   std::string ToModifiedUtf8() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -209,6 +224,12 @@
   void GetChars(int32_t start, int32_t end, Handle<CharArray> array, int32_t index)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void FillBytesLatin1(Handle<ByteArray> array, int32_t index)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void FillBytesUTF16(Handle<ByteArray> array, int32_t index)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsCompressed() REQUIRES_SHARED(Locks::mutator_lock_) {
     return kUseStringCompression && IsCompressed(GetCount());
diff --git a/runtime/mirror/throwable.cc b/runtime/mirror/throwable.cc
index b538fa9..a38ba1c 100644
--- a/runtime/mirror/throwable.cc
+++ b/runtime/mirror/throwable.cc
@@ -31,7 +31,7 @@
 #include "object_array.h"
 #include "stack_trace_element-inl.h"
 #include "string.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 namespace mirror {
@@ -70,14 +70,14 @@
 }
 
 bool Throwable::IsCheckedException() {
-  if (InstanceOf(WellKnownClasses::ToClass(WellKnownClasses::java_lang_Error))) {
+  if (IsError()) {
     return false;
   }
-  return !InstanceOf(WellKnownClasses::ToClass(WellKnownClasses::java_lang_RuntimeException));
+  return !InstanceOf(WellKnownClasses::java_lang_RuntimeException.Get());
 }
 
 bool Throwable::IsError() {
-  return InstanceOf(WellKnownClasses::ToClass(WellKnownClasses::java_lang_Error));
+  return InstanceOf(WellKnownClasses::java_lang_Error.Get());
 }
 
 int32_t Throwable::GetStackDepth() {
diff --git a/runtime/mirror/var_handle.cc b/runtime/mirror/var_handle.cc
index d36a2ab..2220f92 100644
--- a/runtime/mirror/var_handle.cc
+++ b/runtime/mirror/var_handle.cc
@@ -205,7 +205,7 @@
 // Method to insert a read barrier for accessors to reference fields.
 inline void ReadBarrierForVarHandleAccess(ObjPtr<Object> obj, MemberOffset field_offset)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  if (kUseReadBarrier) {
+  if (gUseReadBarrier) {
     // We need to ensure that the reference stored in the field is a to-space one before attempting
     // the CompareAndSet/CompareAndExchange/Exchange operation otherwise it will fail incorrectly
     // if obj is in the process of being moved.
@@ -221,11 +221,6 @@
   }
 }
 
-inline MemberOffset GetMemberOffset(jfieldID field_id) REQUIRES_SHARED(Locks::mutator_lock_) {
-  ArtField* const field = jni::DecodeArtField(field_id);
-  return field->GetOffset();
-}
-
 //
 // Helper methods for storing results from atomic operations into
 // JValue instances.
@@ -1934,7 +1929,7 @@
 
   // Check access_mode is compatible with ByteBuffer's read-only property.
   bool is_read_only = byte_buffer->GetFieldBoolean(
-      GetMemberOffset(WellKnownClasses::java_nio_ByteBuffer_isReadOnly));
+      WellKnownClasses::java_nio_ByteBuffer_isReadOnly->GetOffset());
   if (is_read_only && !IsReadOnlyAccessMode(access_mode)) {
     ThrowReadOnlyBufferException();
     return false;
@@ -1942,20 +1937,20 @@
 
   // The native_address is only set for ByteBuffer instances backed by native memory.
   const int64_t native_address =
-      byte_buffer->GetField64(GetMemberOffset(WellKnownClasses::java_nio_ByteBuffer_address));
+      byte_buffer->GetField64(WellKnownClasses::java_nio_Buffer_address->GetOffset());
 
   // Determine offset and limit for accesses.
   int32_t byte_buffer_offset;
   if (native_address == 0L) {
     // Accessing a heap allocated byte buffer.
     byte_buffer_offset = byte_buffer->GetField32(
-        GetMemberOffset(WellKnownClasses::java_nio_ByteBuffer_offset));
+        WellKnownClasses::java_nio_ByteBuffer_offset->GetOffset());
   } else {
     // Accessing direct memory.
     byte_buffer_offset = 0;
   }
-  const int32_t byte_buffer_limit = byte_buffer->GetField32(
-      GetMemberOffset(WellKnownClasses::java_nio_ByteBuffer_limit));
+  const int32_t byte_buffer_limit =
+      byte_buffer->GetField32(WellKnownClasses::java_nio_Buffer_limit->GetOffset());
   const int32_t byte_buffer_length = byte_buffer_offset + byte_buffer_limit;
 
   const Primitive::Type primitive_type = GetVarType()->GetPrimitiveType();
@@ -1967,7 +1962,7 @@
   int8_t* data;
   if (native_address == 0) {
     ObjPtr<ByteArray> heap_byte_array = byte_buffer->GetFieldObject<ByteArray>(
-        GetMemberOffset(WellKnownClasses::java_nio_ByteBuffer_hb));
+        WellKnownClasses::java_nio_ByteBuffer_hb->GetOffset());
     data = heap_byte_array->GetData();
   } else {
     data = reinterpret_cast<int8_t*>(static_cast<uint32_t>(native_address));
diff --git a/runtime/mirror/var_handle_test.cc b/runtime/mirror/var_handle_test.cc
index 9c1a2e7..859e86c 100644
--- a/runtime/mirror/var_handle_test.cc
+++ b/runtime/mirror/var_handle_test.cc
@@ -40,6 +40,10 @@
 // Tests for mirror::VarHandle and it's descendents.
 class VarHandleTest : public CommonRuntimeTest {
  public:
+  VarHandleTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
   static ObjPtr<FieldVarHandle> CreateFieldVarHandle(Thread* const self,
                                                      ArtField* art_field,
                                                      int32_t access_modes_bit_mask)
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 0cad79b..3fed8d4 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -472,7 +472,7 @@
     Locks::thread_list_lock_->ExclusiveLock(self);
     orig_owner = owner_.load(std::memory_order_relaxed);
     if (orig_owner != nullptr) {  // Did the owner_ give the lock up?
-      const uint32_t orig_owner_thread_id = orig_owner->GetThreadId();
+      const uint32_t orig_owner_thread_id = orig_owner->GetTid();
       GetLockOwnerInfo(&owners_method, &owners_dex_pc, orig_owner);
       std::ostringstream oss;
       std::string name;
@@ -1139,7 +1139,7 @@
                                                           lock_word.GCState()));
             // Only this thread pays attention to the count. Thus there is no need for stronger
             // than relaxed memory ordering.
-            if (!kUseReadBarrier) {
+            if (!gUseReadBarrier) {
               h_obj->SetLockWord(thin_locked, /* as_volatile= */ false);
               AtraceMonitorLock(self, h_obj.Get(), /* is_wait= */ false);
               return h_obj.Get();  // Success!
@@ -1239,7 +1239,7 @@
           } else {
             new_lw = LockWord::FromDefault(lock_word.GCState());
           }
-          if (!kUseReadBarrier) {
+          if (!gUseReadBarrier) {
             DCHECK_EQ(new_lw.ReadBarrierState(), 0U);
             // TODO: This really only needs memory_order_release, but we currently have
             // no way to specify that. In fact there seem to be no legitimate uses of SetLockWord
@@ -1409,7 +1409,7 @@
     {
       ObjPtr<mirror::Object> lock_object = thread->GetMonitorEnterObject();
       if (lock_object != nullptr) {
-        if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
+        if (gUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
           // We may call Thread::Dump() in the middle of the CC thread flip and this thread's stack
           // may have not been flipped yet and "pretty_object" may be a from-space (stale) ref, in
           // which case the GetLockOwnerThreadId() call below will crash. So explicitly mark/forward
@@ -1613,13 +1613,13 @@
 }
 
 void MonitorList::DisallowNewMonitors() {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   MutexLock mu(Thread::Current(), monitor_list_lock_);
   allow_new_monitors_ = false;
 }
 
 void MonitorList::AllowNewMonitors() {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   Thread* self = Thread::Current();
   MutexLock mu(self, monitor_list_lock_);
   allow_new_monitors_ = true;
@@ -1637,8 +1637,8 @@
   MutexLock mu(self, monitor_list_lock_);
   // CMS needs this to block for concurrent reference processing because an object allocated during
   // the GC won't be marked and concurrent reference processing would incorrectly clear the JNI weak
-  // ref. But CC (kUseReadBarrier == true) doesn't because of the to-space invariant.
-  while (!kUseReadBarrier && UNLIKELY(!allow_new_monitors_)) {
+  // ref. But CC (gUseReadBarrier == true) doesn't because of the to-space invariant.
+  while (!gUseReadBarrier && UNLIKELY(!allow_new_monitors_)) {
     // Check and run the empty checkpoint before blocking so the empty checkpoint will work in the
     // presence of threads blocking for weak ref access.
     self->CheckEmptyCheckpointFromWeakRefAccess(&monitor_list_lock_);
diff --git a/runtime/monitor.h b/runtime/monitor.h
index dd4c21c..ad7a0b4 100644
--- a/runtime/monitor.h
+++ b/runtime/monitor.h
@@ -134,7 +134,7 @@
   template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ObjPtr<mirror::Object> GetObject() REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void SetObject(ObjPtr<mirror::Object> object);
+  void SetObject(ObjPtr<mirror::Object> object) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Provides no memory ordering guarantees.
   Thread* GetOwner() const {
diff --git a/runtime/monitor_objects_stack_visitor.cc b/runtime/monitor_objects_stack_visitor.cc
index 2e75e37..524c0ec 100644
--- a/runtime/monitor_objects_stack_visitor.cc
+++ b/runtime/monitor_objects_stack_visitor.cc
@@ -90,7 +90,7 @@
 void MonitorObjectsStackVisitor::VisitLockedObject(ObjPtr<mirror::Object> o, void* context) {
   MonitorObjectsStackVisitor* self = reinterpret_cast<MonitorObjectsStackVisitor*>(context);
   if (o != nullptr) {
-    if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
+    if (gUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
       // We may call Thread::Dump() in the middle of the CC thread flip and this thread's stack
       // may have not been flipped yet and "o" may be a from-space (stale) ref, in which case the
       // IdentityHashCode call below will crash. So explicitly mark/forward it here.
diff --git a/runtime/monitor_pool.h b/runtime/monitor_pool.h
index e07aa97..133cde0 100644
--- a/runtime/monitor_pool.h
+++ b/runtime/monitor_pool.h
@@ -189,12 +189,12 @@
   // Size of a monitor, rounded up to a multiple of alignment.
   static constexpr size_t kAlignedMonitorSize = (sizeof(Monitor) + kMonitorAlignment - 1) &
                                                 -kMonitorAlignment;
-  // As close to a page as we can get seems a good start.
-  static constexpr size_t kChunkCapacity = kPageSize / kAlignedMonitorSize;
-  // Chunk size that is referenced in the id. We can collapse this to the actually used storage
-  // in a chunk, i.e., kChunkCapacity * kAlignedMonitorSize, but this will mean proper divisions.
-  static constexpr size_t kChunkSize = kPageSize;
+  // Size of the chunks holding the actual monitors. The bottom bits of the monitor id are the
+  // index into such a chunk. We can collapse this to the actually used storage
+  // in a chunk, i.e., kChunkCapacity * kAlignedMonitorSize, but this would mean proper divisions.
+  static constexpr size_t kChunkSize = 4096;
   static_assert(IsPowerOfTwo(kChunkSize), "kChunkSize must be power of 2");
+  static constexpr size_t kChunkCapacity = kChunkSize / kAlignedMonitorSize;
   // The number of chunks of storage that can be referenced by the initial chunk list.
   // The total number of usable monitor chunks is typically 255 times this number, so it
   // should be large enough that we don't run out. We run out of address bits if it's > 512.
@@ -215,7 +215,7 @@
   // Array of pointers to lists (again arrays) of pointers to chunks containing monitors.
   // Zeroth entry points to a list (array) of kInitialChunkStorage pointers to chunks.
   // Each subsequent list as twice as large as the preceding one.
-  // Monitor Ids are interpreted as follows:
+  // Monitor Ids are effectively interpreted as follows:
   //     Top 3 bits (of 28): index into monitor_chunks_.
   //     Next 16 bits: index into the chunk list, i.e. monitor_chunks_[i].
   //     Last 9 bits: offset within chunk, expressed as multiple of kMonitorAlignment.
diff --git a/runtime/monitor_test.cc b/runtime/monitor_test.cc
index 66008f3..699f2b0 100644
--- a/runtime/monitor_test.cc
+++ b/runtime/monitor_test.cc
@@ -25,6 +25,7 @@
 #include "class_linker-inl.h"
 #include "common_runtime_test.h"
 #include "handle_scope-inl.h"
+#include "jni/java_vm_ext.h"
 #include "mirror/class-inl.h"
 #include "mirror/string-inl.h"  // Strings are easiest to allocate
 #include "object_lock.h"
@@ -35,17 +36,21 @@
 
 class MonitorTest : public CommonRuntimeTest {
  protected:
+  MonitorTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
   void SetUpRuntimeOptions(RuntimeOptions *options) override {
     // Use a smaller heap
     SetUpRuntimeOptionsForFillHeap(options);
 
     options->push_back(std::make_pair("-Xint", nullptr));
   }
+
  public:
   std::unique_ptr<Monitor> monitor_;
-  Handle<mirror::String> object_;
-  Handle<mirror::String> second_object_;
-  Handle<mirror::String> watchdog_object_;
+  jobject object_;
+  jobject watchdog_object_;
   // One exception test is for waiting on another Thread's lock. This is used to race-free &
   // loop-free pass
   Thread* thread_;
@@ -64,59 +69,59 @@
       expected_(expected) {}
 
   void Run(Thread* self) override {
-    {
-      ScopedObjectAccess soa(self);
+    ScopedObjectAccess soa(self);
+    StackHandleScope<1u> hs(self);
+    Handle<mirror::Object> obj = hs.NewHandle(soa.Decode<mirror::Object>(monitor_test_->object_));
 
-      monitor_test_->thread_ = self;        // Pass the Thread.
-      monitor_test_->object_.Get()->MonitorEnter(self);  // Lock the object. This should transition
-      LockWord lock_after = monitor_test_->object_.Get()->GetLockWord(false);  // it to thinLocked.
-      LockWord::LockState new_state = lock_after.GetState();
+    monitor_test_->thread_ = self;        // Pass the Thread.
+    obj->MonitorEnter(self);  // Lock the object. This should transition
+    LockWord lock_after = obj->GetLockWord(false);  // it to thinLocked.
+    LockWord::LockState new_state = lock_after.GetState();
 
-      // Cannot use ASSERT only, as analysis thinks we'll keep holding the mutex.
-      if (LockWord::LockState::kThinLocked != new_state) {
-        monitor_test_->object_.Get()->MonitorExit(self);         // To appease analysis.
-        ASSERT_EQ(LockWord::LockState::kThinLocked, new_state);  // To fail the test.
-        return;
-      }
-
-      // Force a fat lock by running identity hashcode to fill up lock word.
-      monitor_test_->object_.Get()->IdentityHashCode();
-      LockWord lock_after2 = monitor_test_->object_.Get()->GetLockWord(false);
-      LockWord::LockState new_state2 = lock_after2.GetState();
-
-      // Cannot use ASSERT only, as analysis thinks we'll keep holding the mutex.
-      if (LockWord::LockState::kFatLocked != new_state2) {
-        monitor_test_->object_.Get()->MonitorExit(self);         // To appease analysis.
-        ASSERT_EQ(LockWord::LockState::kFatLocked, new_state2);  // To fail the test.
-        return;
-      }
-    }  // Need to drop the mutator lock to use the barrier.
-
-    monitor_test_->barrier_->Wait(self);           // Let the other thread know we're done.
-
-    {
-      ScopedObjectAccess soa(self);
-
-      // Give the other task a chance to do its thing.
-      NanoSleep(initial_sleep_ * 1000 * 1000);
-
-      // Now try to Wait on the Monitor.
-      Monitor::Wait(self, monitor_test_->object_.Get(), millis_, 0, true,
-                    ThreadState::kTimedWaiting);
-
-      // Check the exception status against what we expect.
-      EXPECT_EQ(expected_, self->IsExceptionPending());
-      if (expected_) {
-        self->ClearException();
-      }
+    // Cannot use ASSERT only, as analysis thinks we'll keep holding the mutex.
+    if (LockWord::LockState::kThinLocked != new_state) {
+      obj->MonitorExit(self);         // To appease analysis.
+      ASSERT_EQ(LockWord::LockState::kThinLocked, new_state);  // To fail the test.
+      return;
     }
 
-    monitor_test_->complete_barrier_->Wait(self);  // Wait for test completion.
+    // Force a fat lock by running identity hashcode to fill up lock word.
+    obj->IdentityHashCode();
+    LockWord lock_after2 = obj->GetLockWord(false);
+    LockWord::LockState new_state2 = lock_after2.GetState();
+
+    // Cannot use ASSERT only, as analysis thinks we'll keep holding the mutex.
+    if (LockWord::LockState::kFatLocked != new_state2) {
+      obj->MonitorExit(self);         // To appease analysis.
+      ASSERT_EQ(LockWord::LockState::kFatLocked, new_state2);  // To fail the test.
+      return;
+    }
 
     {
-      ScopedObjectAccess soa(self);
-      monitor_test_->object_.Get()->MonitorExit(self);  // Release the object. Appeases analysis.
+      // Need to drop the mutator lock to use the barrier.
+      ScopedThreadSuspension sts(self, ThreadState::kSuspended);
+      monitor_test_->barrier_->Wait(self);           // Let the other thread know we're done.
     }
+
+    // Give the other task a chance to do its thing.
+    NanoSleep(initial_sleep_ * 1000 * 1000);
+
+    // Now try to Wait on the Monitor.
+    Monitor::Wait(self, obj.Get(), millis_, 0, true, ThreadState::kTimedWaiting);
+
+    // Check the exception status against what we expect.
+    EXPECT_EQ(expected_, self->IsExceptionPending());
+    if (expected_) {
+      self->ClearException();
+    }
+
+    {
+      // Need to drop the mutator lock to use the barrier.
+      ScopedThreadSuspension sts(self, ThreadState::kSuspended);
+      monitor_test_->complete_barrier_->Wait(self);  // Wait for test completion.
+    }
+
+    obj->MonitorExit(self);  // Release the object. Appeases analysis.
   }
 
   void Finalize() override {
@@ -146,8 +151,8 @@
       // Give the other task a chance to do its thing.
       NanoSleep(initial_sleep_ * 1000 * 1000);
 
-      Monitor::Wait(self, monitor_test_->object_.Get(), millis_, 0, true,
-                    ThreadState::kTimedWaiting);
+      ObjPtr<mirror::Object> obj = soa.Decode<mirror::Object>(monitor_test_->object_);
+      Monitor::Wait(self, obj, millis_, 0, true, ThreadState::kTimedWaiting);
 
       // Check the exception status against what we expect.
       EXPECT_EQ(expected_, self->IsExceptionPending());
@@ -191,8 +196,8 @@
       NanoSleep(millis_ * 1000 * 1000);
 
       // Now try to Wait.
-      Monitor::Wait(self, monitor_test_->object_.Get(), 10, 0, true,
-                    ThreadState::kTimedWaiting);
+      ObjPtr<mirror::Object> obj = soa.Decode<mirror::Object>(monitor_test_->object_);
+      Monitor::Wait(self, obj, 10, 0, true, ThreadState::kTimedWaiting);
 
       // No check here, as depending on scheduling we may or may not fail.
       if (self->IsExceptionPending()) {
@@ -219,13 +224,15 @@
 
   void Run(Thread* self) override {
     ScopedObjectAccess soa(self);
+    StackHandleScope<1u> hs(self);
+    Handle<mirror::Object> watchdog_obj =
+        hs.NewHandle(soa.Decode<mirror::Object>(monitor_test_->watchdog_object_));
 
-    monitor_test_->watchdog_object_.Get()->MonitorEnter(self);        // Lock the object.
+    watchdog_obj->MonitorEnter(self);        // Lock the object.
 
-    monitor_test_->watchdog_object_.Get()->Wait(self, 30 * 1000, 0);  // Wait for 30s, or being
-                                                                      // woken up.
+    watchdog_obj->Wait(self, 30 * 1000, 0);  // Wait for 30s, or being woken up.
 
-    monitor_test_->watchdog_object_.Get()->MonitorExit(self);         // Release the lock.
+    watchdog_obj->MonitorExit(self);         // Release the lock.
 
     if (!monitor_test_->completed_) {
       LOG(FATAL) << "Watchdog timeout!";
@@ -246,10 +253,15 @@
   Thread* const self = Thread::Current();
   ScopedObjectAccess soa(self);
   // First create the object we lock. String is easiest.
-  StackHandleScope<3> hs(soa.Self());
-  test->object_ = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "hello, world!"));
-  test->watchdog_object_ = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self,
-                                                                              "hello, world!"));
+  StackHandleScope<2u> hs(soa.Self());
+  Handle<mirror::Object> obj =
+      hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "hello, world!"));
+  test->object_ = soa.Vm()->AddGlobalRef(self, obj.Get());
+  ASSERT_TRUE(test->object_ != nullptr);
+  Handle<mirror::Object> watchdog_obj =
+      hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "hello, world!"));
+  test->watchdog_object_ = soa.Vm()->AddGlobalRef(self, watchdog_obj.Get());
+  ASSERT_TRUE(test->watchdog_object_ != nullptr);
 
   // Create the barrier used to synchronize.
   test->barrier_ = std::make_unique<Barrier>(2);
@@ -283,9 +295,9 @@
   // Wake the watchdog.
   {
     ScopedObjectAccess soa2(self);
-    test->watchdog_object_.Get()->MonitorEnter(self);     // Lock the object.
-    test->watchdog_object_.Get()->NotifyAll(self);        // Wake up waiting parties.
-    test->watchdog_object_.Get()->MonitorExit(self);      // Release the lock.
+    watchdog_obj->MonitorEnter(self);     // Lock the object.
+    watchdog_obj->NotifyAll(self);        // Wake up waiting parties.
+    watchdog_obj->MonitorExit(self);      // Release the lock.
   }
 
   thread_pool.StopWorkers(self);
@@ -325,12 +337,14 @@
 
 class TryLockTask : public Task {
  public:
-  explicit TryLockTask(Handle<mirror::Object> obj) : obj_(obj) {}
+  explicit TryLockTask(jobject obj) : obj_(obj) {}
 
   void Run(Thread* self) override {
     ScopedObjectAccess soa(self);
+    StackHandleScope<1u> hs(self);
+    Handle<mirror::Object> obj = hs.NewHandle(soa.Decode<mirror::Object>(obj_));
     // Lock is held by other thread, try lock should fail.
-    ObjectTryLock<mirror::Object> lock(self, obj_);
+    ObjectTryLock<mirror::Object> lock(self, obj);
     EXPECT_FALSE(lock.Acquired());
   }
 
@@ -339,7 +353,7 @@
   }
 
  private:
-  Handle<mirror::Object> obj_;
+  jobject obj_;
 };
 
 // Test trylock in deadlock scenarios.
@@ -352,6 +366,8 @@
   StackHandleScope<1> hs(self);
   Handle<mirror::Object> obj1(
       hs.NewHandle<mirror::Object>(mirror::String::AllocFromModifiedUtf8(self, "hello, world!")));
+  jobject g_obj1 = soa.Vm()->AddGlobalRef(self, obj1.Get());
+  ASSERT_TRUE(g_obj1 != nullptr);
   {
     ObjectLock<mirror::Object> lock1(self, obj1);
     {
@@ -359,7 +375,7 @@
       EXPECT_TRUE(trylock.Acquired());
     }
     // Test failure case.
-    thread_pool.AddTask(self, new TryLockTask(obj1));
+    thread_pool.AddTask(self, new TryLockTask(g_obj1));
     thread_pool.StartWorkers(self);
     ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     thread_pool.Wait(Thread::Current(), /*do_work=*/false, /*may_hold_locks=*/false);
diff --git a/runtime/mutator_gc_coord.md b/runtime/mutator_gc_coord.md
index b3635c5..aba8421 100644
--- a/runtime/mutator_gc_coord.md
+++ b/runtime/mutator_gc_coord.md
@@ -148,7 +148,7 @@
 were a regular lock. See `base/locks.h` for the hierarchy. In particular, only
 locks at or below level `kPostMutatorTopLockLevel` may be acquired after
 acquiring the mutator lock, e.g. inside the scope of a `ScopedObjectAccess`.
-Similarly only locks at level strictly above `kMutatatorLock`may be held while
+Similarly only locks at level strictly above `kMutatatorLock` may be held while
 acquiring the mutator lock, e.g. either by starting a `ScopedObjectAccess`, or
 ending a `ScopedThreadSuspension`.
 
diff --git a/runtime/native/dalvik_system_BaseDexClassLoader.cc b/runtime/native/dalvik_system_BaseDexClassLoader.cc
index 607395d..a4f702c 100644
--- a/runtime/native/dalvik_system_BaseDexClassLoader.cc
+++ b/runtime/native/dalvik_system_BaseDexClassLoader.cc
@@ -19,43 +19,54 @@
 #include <memory>
 
 #include "class_loader_context.h"
+#include "class_root-inl.h"
+#include "mirror/object_array-alloc-inl.h"
 #include "native_util.h"
 #include "nativehelper/jni_macros.h"
-#include "well_known_classes.h"
+#include "thread-inl.h"
 
 namespace art {
 
-static bool append_string(JNIEnv* env, jobjectArray array, uint32_t& i, const std::string& string) {
-  ScopedLocalRef<jstring> jstring(env, env->NewStringUTF(string.c_str()));
-  if (jstring.get() == nullptr) {
-    DCHECK(env->ExceptionCheck());
+static bool append_string(Thread* self,
+                          Handle<mirror::ObjectArray<mirror::String>> array,
+                          uint32_t& i,
+                          const std::string& string) REQUIRES_SHARED(Locks::mutator_lock_) {
+  ObjPtr<mirror::String> ostring = mirror::String::AllocFromModifiedUtf8(self, string.c_str());
+  if (ostring == nullptr) {
+    DCHECK(self->IsExceptionPending());
     return false;
   }
-  env->SetObjectArrayElement(array, i++, jstring.get());
+  // We're initializing a newly allocated array object, so we do not need to record that under
+  // a transaction. If the transaction is aborted, the whole object shall be unreachable.
+  array->SetWithoutChecks</*kTransactionActive=*/ false, /*kCheckTransaction=*/ false>(i, ostring);
+  ++i;
   return true;
 }
 
 static jobjectArray BaseDexClassLoader_computeClassLoaderContextsNative(JNIEnv* env,
                                                                         jobject class_loader) {
   CHECK(class_loader != nullptr);
-  std::map<std::string, std::string> contextMap =
+  std::map<std::string, std::string> context_map =
       ClassLoaderContext::EncodeClassPathContextsForClassLoader(class_loader);
-  jobjectArray result = env->NewObjectArray(2 * contextMap.size(),
-                                            WellKnownClasses::java_lang_String,
-                                            nullptr);
-  if (result == nullptr) {
-    DCHECK(env->ExceptionCheck());
+  Thread* self = Thread::ForEnv(env);
+  ScopedObjectAccess soa(self);
+  StackHandleScope<1u> hs(self);
+  Handle<mirror::ObjectArray<mirror::String>> array = hs.NewHandle(
+      mirror::ObjectArray<mirror::String>::Alloc(
+          self, GetClassRoot<mirror::ObjectArray<mirror::String>>(), 2 * context_map.size()));
+  if (array == nullptr) {
+    DCHECK(self->IsExceptionPending());
     return nullptr;
   }
   uint32_t i = 0;
-  for (const auto& classpath_to_context : contextMap) {
+  for (const auto& classpath_to_context : context_map) {
     const std::string& classpath = classpath_to_context.first;
     const std::string& context = classpath_to_context.second;
-    if (!append_string(env, result, i, classpath) || !append_string(env, result, i, context)) {
+    if (!append_string(self, array, i, classpath) || !append_string(self, array, i, context)) {
       return nullptr;
     }
   }
-  return result;
+  return soa.AddLocalReference<jobjectArray>(array.Get());
 }
 
 static JNINativeMethod gMethods[] = {
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index d714206..9f0c216 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -28,6 +28,7 @@
 #include "base/logging.h"
 #include "base/os.h"
 #include "base/stl_util.h"
+#include "base/transform_iterator.h"
 #include "base/utils.h"
 #include "base/zip_archive.h"
 #include "class_linker.h"
@@ -53,10 +54,19 @@
 #include "oat_file_manager.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
-#include "well_known_classes.h"
+#include "string_array_utils.h"
+#include "thread-current-inl.h"
+
+#ifdef ART_TARGET_ANDROID
+#include <android/api-level.h>
+#include <sys/system_properties.h>
+#endif  // ART_TARGET_ANDROID
 
 namespace art {
 
+// Should be the same as dalvik.system.DexFile.ENFORCE_READ_ONLY_JAVA_DCL
+static constexpr uint64_t kEnforceReadOnlyJavaDcl = 218865702;
+
 using android::base::StringPrintf;
 
 static bool ConvertJavaArrayToDexFiles(
@@ -303,6 +313,57 @@
   return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
 }
 
+#ifdef ART_TARGET_ANDROID
+static bool isReadOnlyJavaDclEnforced(JNIEnv* env) {
+  static bool is_at_least_u = [] {
+    const int api_level = android_get_device_api_level();
+    if (api_level > __ANDROID_API_T__) {
+      return true;
+    } else if (api_level == __ANDROID_API_T__) {
+      // Check if running U preview
+      char value[92] = {0};
+      if (__system_property_get("ro.build.version.preview_sdk", value) >= 0 && atoi(value) > 0) {
+        return true;
+      }
+    }
+    return false;
+  }();
+  if (is_at_least_u) {
+    // The reason why we are calling the AppCompat framework through JVM
+    // instead of directly using the CompatFramework C++ API is because feature
+    // overrides only apply to the Java API.
+    // CtsLibcoreTestCases is part of mainline modules, which requires the same test
+    // to run on older Android versions; the target SDK of CtsLibcoreTestCases is locked
+    // to the lowest supported API level (at the time of writing, it's API 31).
+    // We would need to be able to manually enable the compat change in CTS tests.
+    ScopedLocalRef<jclass> compat(env, env->FindClass("android/compat/Compatibility"));
+    jmethodID mId = env->GetStaticMethodID(compat.get(), "isChangeEnabled", "(J)Z");
+    return env->CallStaticBooleanMethod(compat.get(), mId, kEnforceReadOnlyJavaDcl) == JNI_TRUE;
+  } else {
+    return false;
+  }
+}
+#else   // ART_TARGET_ANDROID
+constexpr static bool isReadOnlyJavaDclEnforced(JNIEnv*) {
+  (void)kEnforceReadOnlyJavaDcl;
+  return false;
+}
+#endif  // ART_TARGET_ANDROID
+
+static bool isReadOnlyJavaDclChecked() {
+  if (!kIsTargetAndroid) {
+    return false;
+  }
+  const int uid = getuid();
+  // The following UIDs are exempted:
+  // * Root (0): root processes always have write access to files.
+  // * System (1000): /data/app/**.apk are owned by AID_SYSTEM;
+  //   loading installed APKs in system_server is allowed.
+  // * Shell (2000): directly calling dalvikvm/app_process in ADB shell
+  //   to run JARs with CLI is allowed.
+  return uid != 0 && uid != 1000 && uid != 2000;
+}
+
 // TODO(calin): clean up the unused parameters (here and in libcore).
 static jobject DexFile_openDexFileNative(JNIEnv* env,
                                          jclass,
@@ -316,6 +377,17 @@
     return nullptr;
   }
 
+  if (isReadOnlyJavaDclChecked() && access(sourceName.c_str(), W_OK) == 0) {
+    LOG(ERROR) << "Attempt to load writable dex file: " << sourceName.c_str();
+    if (isReadOnlyJavaDclEnforced(env)) {
+      ScopedLocalRef<jclass> se(env, env->FindClass("java/lang/SecurityException"));
+      std::string message(
+          StringPrintf("Writable dex file '%s' is not allowed.", sourceName.c_str()));
+      env->ThrowNew(se.get(), message.c_str());
+      return nullptr;
+    }
+  }
+
   std::vector<std::string> error_msgs;
   const OatFile* oat_file = nullptr;
   std::vector<std::unique_ptr<const DexFile>> dex_files =
@@ -377,6 +449,7 @@
         if (!class_linker->IsDexFileRegistered(soa.Self(), *dex_file)) {
           // Clear the element in the array so that we can call close again.
           long_dex_files->Set(i, 0);
+          class_linker->RemoveDexFromCaches(*dex_file);
           delete dex_file;
         } else {
           all_deleted = false;
@@ -481,23 +554,12 @@
   }
 
   // Now create output array and copy the set into it.
-  jobjectArray result = env->NewObjectArray(descriptors.size(),
-                                            WellKnownClasses::java_lang_String,
-                                            nullptr);
-  if (result != nullptr) {
-    auto it = descriptors.begin();
-    auto it_end = descriptors.end();
-    jsize i = 0;
-    for (; it != it_end; it++, ++i) {
-      std::string descriptor(DescriptorToDot(*it));
-      ScopedLocalRef<jstring> jdescriptor(env, env->NewStringUTF(descriptor.c_str()));
-      if (jdescriptor.get() == nullptr) {
-        return nullptr;
-      }
-      env->SetObjectArrayElement(result, i, jdescriptor.get());
-    }
-  }
-  return result;
+  ScopedObjectAccess soa(Thread::ForEnv(env));
+  auto descriptor_to_dot = [](const char* descriptor) { return DescriptorToDot(descriptor); };
+  return soa.AddLocalReference<jobjectArray>(CreateStringArray(
+      soa.Self(),
+      descriptors.size(),
+      MakeTransformRange(descriptors, descriptor_to_dot)));
 }
 
 static jint GetDexOptNeeded(JNIEnv* env,
@@ -589,6 +651,8 @@
     return nullptr;
   }
 
+  // The API doesn't support passing a class loader context, so skip the class loader context check
+  // and assume that it's OK.
   OatFileAssistant oat_file_assistant(filename.c_str(),
                                       target_instruction_set,
                                       /* context= */ nullptr,
@@ -626,23 +690,11 @@
   OatFileAssistant::GetOptimizationStatus(
       filename.c_str(), target_instruction_set, &compilation_filter, &compilation_reason);
 
-  ScopedLocalRef<jstring> j_compilation_filter(env, env->NewStringUTF(compilation_filter.c_str()));
-  if (j_compilation_filter.get() == nullptr) {
-    return nullptr;
-  }
-  ScopedLocalRef<jstring> j_compilation_reason(env, env->NewStringUTF(compilation_reason.c_str()));
-  if (j_compilation_reason.get() == nullptr) {
-    return nullptr;
-  }
-
-  // Now create output array and copy the set into it.
-  jobjectArray result = env->NewObjectArray(2,
-                                            WellKnownClasses::java_lang_String,
-                                            nullptr);
-  env->SetObjectArrayElement(result, 0, j_compilation_filter.get());
-  env->SetObjectArrayElement(result, 1, j_compilation_reason.get());
-
-  return result;
+  ScopedObjectAccess soa(Thread::ForEnv(env));
+  return soa.AddLocalReference<jobjectArray>(CreateStringArray(soa.Self(), {
+      compilation_filter.c_str(),
+      compilation_reason.c_str()
+  }));
 }
 
 static jint DexFile_getDexOptNeeded(JNIEnv* env,
@@ -733,6 +785,41 @@
   return CompilerFilter::DependsOnProfile(filter) ? JNI_TRUE : JNI_FALSE;
 }
 
+static jboolean DexFile_isVerifiedCompilerFilter(JNIEnv* env,
+                                                 jclass javeDexFileClass ATTRIBUTE_UNUSED,
+                                                 jstring javaCompilerFilter) {
+  ScopedUtfChars compiler_filter(env, javaCompilerFilter);
+  if (env->ExceptionCheck()) {
+    return -1;
+  }
+
+  CompilerFilter::Filter filter;
+  if (!CompilerFilter::ParseCompilerFilter(compiler_filter.c_str(), &filter)) {
+    return JNI_FALSE;
+  }
+  return CompilerFilter::IsVerificationEnabled(filter) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean DexFile_isOptimizedCompilerFilter(JNIEnv* env,
+                                                  jclass javeDexFileClass ATTRIBUTE_UNUSED,
+                                                  jstring javaCompilerFilter) {
+  ScopedUtfChars compiler_filter(env, javaCompilerFilter);
+  if (env->ExceptionCheck()) {
+    return -1;
+  }
+
+  CompilerFilter::Filter filter;
+  if (!CompilerFilter::ParseCompilerFilter(compiler_filter.c_str(), &filter)) {
+    return JNI_FALSE;
+  }
+  return CompilerFilter::IsAotCompilationEnabled(filter) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean DexFile_isReadOnlyJavaDclEnforced(JNIEnv* env,
+                                                  jclass javeDexFileClass ATTRIBUTE_UNUSED) {
+  return (isReadOnlyJavaDclChecked() && isReadOnlyJavaDclEnforced(env)) ? JNI_TRUE : JNI_FALSE;
+}
+
 static jstring DexFile_getNonProfileGuidedCompilerFilter(JNIEnv* env,
                                                          jclass javeDexFileClass ATTRIBUTE_UNUSED,
                                                          jstring javaCompilerFilter) {
@@ -855,31 +942,16 @@
     oat_filename = best_oat_file->GetLocation();
     is_vdex_only = best_oat_file->IsBackedByVdexOnly();
   }
-  ScopedLocalRef<jstring> joatFilename(env, env->NewStringUTF(oat_filename.c_str()));
-  if (joatFilename.get() == nullptr) {
-    return nullptr;
-  }
 
-  if (is_vdex_only) {
-    jobjectArray result = env->NewObjectArray(1,
-                                              WellKnownClasses::java_lang_String,
-                                              nullptr);
-    env->SetObjectArrayElement(result, 0, joatFilename.get());
-    return result;
-  } else {
+  const char* filenames[] = { oat_filename.c_str(), nullptr };
+  ArrayRef<const char* const> used_filenames(filenames, 1u);
+  if (!is_vdex_only) {
     vdex_filename = GetVdexFilename(oat_filename);
-    ScopedLocalRef<jstring> jvdexFilename(env, env->NewStringUTF(vdex_filename.c_str()));
-    if (jvdexFilename.get() == nullptr) {
-      return nullptr;
-    }
-
-    jobjectArray result = env->NewObjectArray(2,
-                                              WellKnownClasses::java_lang_String,
-                                              nullptr);
-    env->SetObjectArrayElement(result, 0, jvdexFilename.get());
-    env->SetObjectArrayElement(result, 1, joatFilename.get());
-    return result;
+    filenames[1] = vdex_filename.c_str();
+    used_filenames = ArrayRef<const char* const>(filenames, 2u);
   }
+  ScopedObjectAccess soa(Thread::ForEnv(env));
+  return soa.AddLocalReference<jobjectArray>(CreateStringArray(soa.Self(), used_filenames));
 }
 
 static jlong DexFile_getStaticSizeOfDexFile(JNIEnv* env, jclass, jobject cookie) {
@@ -904,7 +976,7 @@
   ScopedObjectAccess soa(env);
 
   // Currently only allow this for debuggable apps.
-  if (!runtime->IsJavaDebuggable()) {
+  if (!runtime->IsJavaDebuggableAtInit()) {
     ThrowSecurityException("Can't exempt class, process is not debuggable.");
     return;
   }
@@ -923,55 +995,60 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(DexFile, closeDexFile, "(Ljava/lang/Object;)Z"),
-  NATIVE_METHOD(DexFile,
-                defineClassNative,
-                "(Ljava/lang/String;"
-                "Ljava/lang/ClassLoader;"
-                "Ljava/lang/Object;"
-                "Ldalvik/system/DexFile;"
-                ")Ljava/lang/Class;"),
-  NATIVE_METHOD(DexFile, getClassNameList, "(Ljava/lang/Object;)[Ljava/lang/String;"),
-  NATIVE_METHOD(DexFile, isDexOptNeeded, "(Ljava/lang/String;)Z"),
-  NATIVE_METHOD(DexFile, getDexOptNeeded,
-                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)I"),
-  NATIVE_METHOD(DexFile, openDexFileNative,
-                "(Ljava/lang/String;"
-                "Ljava/lang/String;"
-                "I"
-                "Ljava/lang/ClassLoader;"
-                "[Ldalvik/system/DexPathList$Element;"
-                ")Ljava/lang/Object;"),
-  NATIVE_METHOD(DexFile, openInMemoryDexFilesNative,
-                "([Ljava/nio/ByteBuffer;"
-                "[[B"
-                "[I"
-                "[I"
-                "Ljava/lang/ClassLoader;"
-                "[Ldalvik/system/DexPathList$Element;"
-                ")Ljava/lang/Object;"),
-  NATIVE_METHOD(DexFile, verifyInBackgroundNative,
-                "(Ljava/lang/Object;"
-                "Ljava/lang/ClassLoader;"
-                ")V"),
-  NATIVE_METHOD(DexFile, isValidCompilerFilter, "(Ljava/lang/String;)Z"),
-  NATIVE_METHOD(DexFile, isProfileGuidedCompilerFilter, "(Ljava/lang/String;)Z"),
-  NATIVE_METHOD(DexFile,
-                getNonProfileGuidedCompilerFilter,
-                "(Ljava/lang/String;)Ljava/lang/String;"),
-  NATIVE_METHOD(DexFile,
-                getSafeModeCompilerFilter,
-                "(Ljava/lang/String;)Ljava/lang/String;"),
-  NATIVE_METHOD(DexFile, isBackedByOatFile, "(Ljava/lang/Object;)Z"),
-  NATIVE_METHOD(DexFile, getDexFileStatus,
-                "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
-  NATIVE_METHOD(DexFile, getDexFileOutputPaths,
-                "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),
-  NATIVE_METHOD(DexFile, getStaticSizeOfDexFile, "(Ljava/lang/Object;)J"),
-  NATIVE_METHOD(DexFile, getDexFileOptimizationStatus,
-                "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),
-  NATIVE_METHOD(DexFile, setTrusted, "(Ljava/lang/Object;)V")
-};
+    NATIVE_METHOD(DexFile, closeDexFile, "(Ljava/lang/Object;)Z"),
+    NATIVE_METHOD(DexFile,
+                  defineClassNative,
+                  "(Ljava/lang/String;"
+                  "Ljava/lang/ClassLoader;"
+                  "Ljava/lang/Object;"
+                  "Ldalvik/system/DexFile;"
+                  ")Ljava/lang/Class;"),
+    NATIVE_METHOD(DexFile, getClassNameList, "(Ljava/lang/Object;)[Ljava/lang/String;"),
+    NATIVE_METHOD(DexFile, isDexOptNeeded, "(Ljava/lang/String;)Z"),
+    NATIVE_METHOD(DexFile,
+                  getDexOptNeeded,
+                  "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)I"),
+    NATIVE_METHOD(DexFile,
+                  openDexFileNative,
+                  "(Ljava/lang/String;"
+                  "Ljava/lang/String;"
+                  "I"
+                  "Ljava/lang/ClassLoader;"
+                  "[Ldalvik/system/DexPathList$Element;"
+                  ")Ljava/lang/Object;"),
+    NATIVE_METHOD(DexFile,
+                  openInMemoryDexFilesNative,
+                  "([Ljava/nio/ByteBuffer;"
+                  "[[B"
+                  "[I"
+                  "[I"
+                  "Ljava/lang/ClassLoader;"
+                  "[Ldalvik/system/DexPathList$Element;"
+                  ")Ljava/lang/Object;"),
+    NATIVE_METHOD(DexFile,
+                  verifyInBackgroundNative,
+                  "(Ljava/lang/Object;"
+                  "Ljava/lang/ClassLoader;"
+                  ")V"),
+    NATIVE_METHOD(DexFile, isValidCompilerFilter, "(Ljava/lang/String;)Z"),
+    NATIVE_METHOD(DexFile, isProfileGuidedCompilerFilter, "(Ljava/lang/String;)Z"),
+    NATIVE_METHOD(DexFile, isVerifiedCompilerFilter, "(Ljava/lang/String;)Z"),
+    NATIVE_METHOD(DexFile, isOptimizedCompilerFilter, "(Ljava/lang/String;)Z"),
+    NATIVE_METHOD(DexFile, isReadOnlyJavaDclEnforced, "()Z"),
+    NATIVE_METHOD(
+        DexFile, getNonProfileGuidedCompilerFilter, "(Ljava/lang/String;)Ljava/lang/String;"),
+    NATIVE_METHOD(DexFile, getSafeModeCompilerFilter, "(Ljava/lang/String;)Ljava/lang/String;"),
+    NATIVE_METHOD(DexFile, isBackedByOatFile, "(Ljava/lang/Object;)Z"),
+    NATIVE_METHOD(
+        DexFile, getDexFileStatus, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
+    NATIVE_METHOD(DexFile,
+                  getDexFileOutputPaths,
+                  "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),
+    NATIVE_METHOD(DexFile, getStaticSizeOfDexFile, "(Ljava/lang/Object;)J"),
+    NATIVE_METHOD(DexFile,
+                  getDexFileOptimizationStatus,
+                  "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),
+    NATIVE_METHOD(DexFile, setTrusted, "(Ljava/lang/Object;)V")};
 
 void register_dalvik_system_DexFile(JNIEnv* env) {
   REGISTER_NATIVE_METHODS("dalvik/system/DexFile");
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index 2e09c9f..3653a83 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -27,6 +27,7 @@
 #include "base/histogram-inl.h"
 #include "base/time_utils.h"
 #include "class_linker.h"
+#include "class_root-inl.h"
 #include "common_throws.h"
 #include "debugger.h"
 #include "gc/space/bump_pointer_space.h"
@@ -41,37 +42,26 @@
 #include "mirror/array-alloc-inl.h"
 #include "mirror/array-inl.h"
 #include "mirror/class.h"
-#include "mirror/object_array-inl.h"
+#include "mirror/object_array-alloc-inl.h"
 #include "native_util.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "nativehelper/scoped_utf_chars.h"
 #include "scoped_fast_native_object_access-inl.h"
+#include "string_array_utils.h"
+#include "thread-inl.h"
 #include "trace.h"
-#include "well_known_classes.h"
 
 namespace art {
 
 static jobjectArray VMDebug_getVmFeatureList(JNIEnv* env, jclass) {
-  static const char* features[] = {
-    "method-trace-profiling",
-    "method-trace-profiling-streaming",
-    "method-sample-profiling",
-    "hprof-heap-dump",
-    "hprof-heap-dump-streaming",
-  };
-  jobjectArray result = env->NewObjectArray(arraysize(features),
-                                            WellKnownClasses::java_lang_String,
-                                            nullptr);
-  if (result != nullptr) {
-    for (size_t i = 0; i < arraysize(features); ++i) {
-      ScopedLocalRef<jstring> jfeature(env, env->NewStringUTF(features[i]));
-      if (jfeature.get() == nullptr) {
-        return nullptr;
-      }
-      env->SetObjectArrayElement(result, i, jfeature.get());
-    }
-  }
-  return result;
+  ScopedObjectAccess soa(Thread::ForEnv(env));
+  return soa.AddLocalReference<jobjectArray>(CreateStringArray(soa.Self(), {
+      "method-trace-profiling",
+      "method-trace-profiling-streaming",
+      "method-sample-profiling",
+      "hprof-heap-dump",
+      "hprof-heap-dump-streaming",
+  }));
 }
 
 static void VMDebug_startAllocCounting(JNIEnv*, jclass) {
@@ -178,6 +168,13 @@
   return -1;
 }
 
+static void VMDebug_suspendAllAndSendVmStart(JNIEnv*, jclass)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  // This function will be replaced by the debugger when it's connected. See
+  // external/oj-libjdwp/src/share/vmDebug.c for implementation when debugger is connected.
+  ThrowRuntimeException("ART's suspendAllAndSendVmStart is not implemented");
+}
+
 static void VMDebug_printLoadedClasses(JNIEnv* env, jclass, jint flags) {
   class DumpClassVisitor : public ClassVisitor {
    public:
@@ -373,55 +370,77 @@
   }
 }
 
-static bool SetRuntimeStatValue(JNIEnv* env,
-                                jobjectArray result,
+static bool SetRuntimeStatValue(Thread* self,
+                                Handle<mirror::ObjectArray<mirror::String>> array,
                                 VMDebugRuntimeStatId id,
-                                const std::string& value) {
-  ScopedLocalRef<jstring> jvalue(env, env->NewStringUTF(value.c_str()));
-  if (jvalue.get() == nullptr) {
+                                const std::string& value) REQUIRES_SHARED(Locks::mutator_lock_) {
+  ObjPtr<mirror::String> ovalue = mirror::String::AllocFromModifiedUtf8(self, value.c_str());
+  if (ovalue == nullptr) {
+    DCHECK(self->IsExceptionPending());
     return false;
   }
-  env->SetObjectArrayElement(result, static_cast<jint>(id), jvalue.get());
+  // We're initializing a newly allocated array object, so we do not need to record that under
+  // a transaction. If the transaction is aborted, the whole object shall be unreachable.
+  array->SetWithoutChecks</*kTransactionActive=*/ false, /*kCheckTransaction=*/ false>(
+      static_cast<int32_t>(id), ovalue);
   return true;
 }
 
 static jobjectArray VMDebug_getRuntimeStatsInternal(JNIEnv* env, jclass) {
-  jobjectArray result = env->NewObjectArray(
-      static_cast<jint>(VMDebugRuntimeStatId::kNumRuntimeStats),
-      WellKnownClasses::java_lang_String,
-      nullptr);
-  if (result == nullptr) {
+  Thread* self = Thread::ForEnv(env);
+  ScopedObjectAccess soa(self);
+  StackHandleScope<1u> hs(self);
+  int32_t size = enum_cast<int32_t>(VMDebugRuntimeStatId::kNumRuntimeStats);
+  Handle<mirror::ObjectArray<mirror::String>> array = hs.NewHandle(
+      mirror::ObjectArray<mirror::String>::Alloc(
+          self, GetClassRoot<mirror::ObjectArray<mirror::String>>(), size));
+  if (array == nullptr) {
+    DCHECK(self->IsExceptionPending());
     return nullptr;
   }
   gc::Heap* heap = Runtime::Current()->GetHeap();
-  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcGcCount,
+  if (!SetRuntimeStatValue(self,
+                           array,
+                           VMDebugRuntimeStatId::kArtGcGcCount,
                            std::to_string(heap->GetGcCount()))) {
     return nullptr;
   }
-  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcGcTime,
+  if (!SetRuntimeStatValue(self,
+                           array,
+                           VMDebugRuntimeStatId::kArtGcGcTime,
                            std::to_string(NsToMs(heap->GetGcTime())))) {
     return nullptr;
   }
-  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcBytesAllocated,
+  if (!SetRuntimeStatValue(self,
+                           array,
+                           VMDebugRuntimeStatId::kArtGcBytesAllocated,
                            std::to_string(heap->GetBytesAllocatedEver()))) {
     return nullptr;
   }
-  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcBytesFreed,
+  if (!SetRuntimeStatValue(self,
+                           array,
+                           VMDebugRuntimeStatId::kArtGcBytesFreed,
                            std::to_string(heap->GetBytesFreedEver()))) {
     return nullptr;
   }
-  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcBlockingGcCount,
+  if (!SetRuntimeStatValue(self,
+                           array,
+                           VMDebugRuntimeStatId::kArtGcBlockingGcCount,
                            std::to_string(heap->GetBlockingGcCount()))) {
     return nullptr;
   }
-  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcBlockingGcTime,
+  if (!SetRuntimeStatValue(self,
+                           array,
+                           VMDebugRuntimeStatId::kArtGcBlockingGcTime,
                            std::to_string(NsToMs(heap->GetBlockingGcTime())))) {
     return nullptr;
   }
   {
     std::ostringstream output;
     heap->DumpGcCountRateHistogram(output);
-    if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcGcCountRateHistogram,
+    if (!SetRuntimeStatValue(self,
+                             array,
+                             VMDebugRuntimeStatId::kArtGcGcCountRateHistogram,
                              output.str())) {
       return nullptr;
     }
@@ -429,12 +448,14 @@
   {
     std::ostringstream output;
     heap->DumpBlockingGcCountRateHistogram(output);
-    if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcBlockingGcCountRateHistogram,
+    if (!SetRuntimeStatValue(self,
+                             array,
+                             VMDebugRuntimeStatId::kArtGcBlockingGcCountRateHistogram,
                              output.str())) {
       return nullptr;
     }
   }
-  return result;
+  return soa.AddLocalReference<jobjectArray>(array.Get());
 }
 
 static void VMDebug_nativeAttachAgent(JNIEnv* env, jclass, jstring agent, jobject classloader) {
@@ -466,7 +487,7 @@
   Runtime* runtime = Runtime::Current();
   ScopedObjectAccess soa(env);
 
-  if (!runtime->IsJavaDebuggable()) {
+  if (!runtime->IsJavaDebuggableAtInit()) {
     ThrowSecurityException("Can't exempt class, process is not debuggable.");
     return;
   }
@@ -495,32 +516,33 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(VMDebug, countInstancesOfClass, "(Ljava/lang/Class;Z)J"),
-  NATIVE_METHOD(VMDebug, countInstancesOfClasses, "([Ljava/lang/Class;Z)[J"),
-  NATIVE_METHOD(VMDebug, dumpHprofData, "(Ljava/lang/String;I)V"),
-  NATIVE_METHOD(VMDebug, dumpHprofDataDdms, "()V"),
-  NATIVE_METHOD(VMDebug, dumpReferenceTables, "()V"),
-  NATIVE_METHOD(VMDebug, getAllocCount, "(I)I"),
-  FAST_NATIVE_METHOD(VMDebug, getLoadedClassCount, "()I"),
-  NATIVE_METHOD(VMDebug, getVmFeatureList, "()[Ljava/lang/String;"),
-  FAST_NATIVE_METHOD(VMDebug, isDebuggerConnected, "()Z"),
-  FAST_NATIVE_METHOD(VMDebug, isDebuggingEnabled, "()Z"),
-  NATIVE_METHOD(VMDebug, getMethodTracingMode, "()I"),
-  FAST_NATIVE_METHOD(VMDebug, lastDebuggerActivity, "()J"),
-  FAST_NATIVE_METHOD(VMDebug, printLoadedClasses, "(I)V"),
-  NATIVE_METHOD(VMDebug, resetAllocCount, "(I)V"),
-  NATIVE_METHOD(VMDebug, startAllocCounting, "()V"),
-  NATIVE_METHOD(VMDebug, startMethodTracingDdmsImpl, "(IIZI)V"),
-  NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;IIIZIZ)V"),
-  NATIVE_METHOD(VMDebug, startMethodTracingFilename, "(Ljava/lang/String;IIZI)V"),
-  NATIVE_METHOD(VMDebug, stopAllocCounting, "()V"),
-  NATIVE_METHOD(VMDebug, stopMethodTracing, "()V"),
-  FAST_NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "()J"),
-  NATIVE_METHOD(VMDebug, getRuntimeStatInternal, "(I)Ljava/lang/String;"),
-  NATIVE_METHOD(VMDebug, getRuntimeStatsInternal, "()[Ljava/lang/String;"),
-  NATIVE_METHOD(VMDebug, nativeAttachAgent, "(Ljava/lang/String;Ljava/lang/ClassLoader;)V"),
-  NATIVE_METHOD(VMDebug, allowHiddenApiReflectionFrom, "(Ljava/lang/Class;)V"),
-  NATIVE_METHOD(VMDebug, setAllocTrackerStackDepth, "(I)V"),
+    NATIVE_METHOD(VMDebug, countInstancesOfClass, "(Ljava/lang/Class;Z)J"),
+    NATIVE_METHOD(VMDebug, countInstancesOfClasses, "([Ljava/lang/Class;Z)[J"),
+    NATIVE_METHOD(VMDebug, dumpHprofData, "(Ljava/lang/String;I)V"),
+    NATIVE_METHOD(VMDebug, dumpHprofDataDdms, "()V"),
+    NATIVE_METHOD(VMDebug, dumpReferenceTables, "()V"),
+    NATIVE_METHOD(VMDebug, getAllocCount, "(I)I"),
+    FAST_NATIVE_METHOD(VMDebug, getLoadedClassCount, "()I"),
+    NATIVE_METHOD(VMDebug, getVmFeatureList, "()[Ljava/lang/String;"),
+    FAST_NATIVE_METHOD(VMDebug, isDebuggerConnected, "()Z"),
+    FAST_NATIVE_METHOD(VMDebug, isDebuggingEnabled, "()Z"),
+    NATIVE_METHOD(VMDebug, suspendAllAndSendVmStart, "()V"),
+    NATIVE_METHOD(VMDebug, getMethodTracingMode, "()I"),
+    FAST_NATIVE_METHOD(VMDebug, lastDebuggerActivity, "()J"),
+    FAST_NATIVE_METHOD(VMDebug, printLoadedClasses, "(I)V"),
+    NATIVE_METHOD(VMDebug, resetAllocCount, "(I)V"),
+    NATIVE_METHOD(VMDebug, startAllocCounting, "()V"),
+    NATIVE_METHOD(VMDebug, startMethodTracingDdmsImpl, "(IIZI)V"),
+    NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;IIIZIZ)V"),
+    NATIVE_METHOD(VMDebug, startMethodTracingFilename, "(Ljava/lang/String;IIZI)V"),
+    NATIVE_METHOD(VMDebug, stopAllocCounting, "()V"),
+    NATIVE_METHOD(VMDebug, stopMethodTracing, "()V"),
+    FAST_NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "()J"),
+    NATIVE_METHOD(VMDebug, getRuntimeStatInternal, "(I)Ljava/lang/String;"),
+    NATIVE_METHOD(VMDebug, getRuntimeStatsInternal, "()[Ljava/lang/String;"),
+    NATIVE_METHOD(VMDebug, nativeAttachAgent, "(Ljava/lang/String;Ljava/lang/ClassLoader;)V"),
+    NATIVE_METHOD(VMDebug, allowHiddenApiReflectionFrom, "(Ljava/lang/Class;)V"),
+    NATIVE_METHOD(VMDebug, setAllocTrackerStackDepth, "(I)V"),
 };
 
 void register_dalvik_system_VMDebug(JNIEnv* env) {
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index db5d420..9e2e8b9 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -29,6 +29,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
+#include "android-base/properties.h"
 #include "arch/instruction_set.h"
 #include "art_method-inl.h"
 #include "base/enums.h"
@@ -41,7 +42,7 @@
 #include "dex/dex_file-inl.h"
 #include "dex/dex_file_types.h"
 #include "gc/accounting/card_table-inl.h"
-#include "gc/allocator/dlmalloc.h"
+#include "gc/allocator/art-dlmalloc.h"
 #include "gc/heap.h"
 #include "gc/space/dlmalloc_space.h"
 #include "gc/space/image_space.h"
@@ -60,9 +61,10 @@
 #include "runtime.h"
 #include "scoped_fast_native_object_access-inl.h"
 #include "scoped_thread_state_change-inl.h"
-#include "thread.h"
+#include "startup_completed_task.h"
+#include "string_array_utils.h"
+#include "thread-inl.h"
 #include "thread_list.h"
-#include "well_known_classes.h"
 
 namespace art {
 
@@ -189,27 +191,9 @@
 }
 
 static jobjectArray VMRuntime_properties(JNIEnv* env, jobject) {
-  DCHECK(WellKnownClasses::java_lang_String != nullptr);
-
   const std::vector<std::string>& properties = Runtime::Current()->GetProperties();
-  ScopedLocalRef<jobjectArray> ret(env,
-                                   env->NewObjectArray(static_cast<jsize>(properties.size()),
-                                                       WellKnownClasses::java_lang_String,
-                                                       nullptr /* initial element */));
-  if (ret == nullptr) {
-    DCHECK(env->ExceptionCheck());
-    return nullptr;
-  }
-  for (size_t i = 0; i != properties.size(); ++i) {
-    ScopedLocalRef<jstring> str(env, env->NewStringUTF(properties[i].c_str()));
-    if (str == nullptr) {
-      DCHECK(env->ExceptionCheck());
-      return nullptr;
-    }
-    env->SetObjectArrayElement(ret.get(), static_cast<jsize>(i), str.get());
-    DCHECK(!env->ExceptionCheck());
-  }
-  return ret.release();
+  ScopedObjectAccess soa(Thread::ForEnv(env));
+  return soa.AddLocalReference<jobjectArray>(CreateStringArray(soa.Self(), properties));
 }
 
 // This is for backward compatibility with dalvik which returned the
@@ -253,6 +237,13 @@
   return down_cast<JNIEnvExt*>(env)->GetVm()->IsCheckJniEnabled() ? JNI_TRUE : JNI_FALSE;
 }
 
+static jint VMRuntime_getSdkVersionNative(JNIEnv* env ATTRIBUTE_UNUSED,
+                                          jclass klass ATTRIBUTE_UNUSED,
+                                          jint default_sdk_version) {
+  return android::base::GetIntProperty("ro.build.version.sdk",
+                                       default_sdk_version);
+}
+
 static void VMRuntime_setTargetSdkVersionNative(JNIEnv*, jobject, jint target_sdk_version) {
   // This is the target SDK version of the app we're about to run. It is intended that this a place
   // where workarounds can be enabled.
@@ -333,35 +324,35 @@
 }
 
 static void VMRuntime_notifyStartupCompleted(JNIEnv*, jobject) {
-  Runtime::Current()->NotifyStartupCompleted();
+  Runtime::Current()->GetHeap()->AddHeapTask(new StartupCompletedTask(NanoTime()));
 }
 
 static void VMRuntime_trimHeap(JNIEnv* env, jobject) {
-  Runtime::Current()->GetHeap()->Trim(ThreadForEnv(env));
+  Runtime::Current()->GetHeap()->Trim(Thread::ForEnv(env));
 }
 
 static void VMRuntime_requestHeapTrim(JNIEnv* env, jobject) {
-  Runtime::Current()->GetHeap()->RequestTrim(ThreadForEnv(env));
+  Runtime::Current()->GetHeap()->RequestTrim(Thread::ForEnv(env));
 }
 
 static void VMRuntime_requestConcurrentGC(JNIEnv* env, jobject) {
   gc::Heap *heap = Runtime::Current()->GetHeap();
-  heap->RequestConcurrentGC(ThreadForEnv(env),
+  heap->RequestConcurrentGC(Thread::ForEnv(env),
                             gc::kGcCauseBackground,
                             true,
                             heap->GetCurrentGcNum());
 }
 
 static void VMRuntime_startHeapTaskProcessor(JNIEnv* env, jobject) {
-  Runtime::Current()->GetHeap()->GetTaskProcessor()->Start(ThreadForEnv(env));
+  Runtime::Current()->GetHeap()->GetTaskProcessor()->Start(Thread::ForEnv(env));
 }
 
 static void VMRuntime_stopHeapTaskProcessor(JNIEnv* env, jobject) {
-  Runtime::Current()->GetHeap()->GetTaskProcessor()->Stop(ThreadForEnv(env));
+  Runtime::Current()->GetHeap()->GetTaskProcessor()->Stop(Thread::ForEnv(env));
 }
 
 static void VMRuntime_runHeapTasks(JNIEnv* env, jobject) {
-  Runtime::Current()->GetHeap()->GetTaskProcessor()->RunAllTasks(ThreadForEnv(env));
+  Runtime::Current()->GetHeap()->GetTaskProcessor()->RunAllTasks(Thread::ForEnv(env));
 }
 
 static void VMRuntime_preloadDexCaches(JNIEnv* env ATTRIBUTE_UNUSED, jobject) {
@@ -509,6 +500,41 @@
   return ClassLoaderContext::IsValidEncoding(encoded_class_loader_context.c_str());
 }
 
+static jobject VMRuntime_getBaseApkOptimizationInfo(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
+  AppInfo* app_info = Runtime::Current()->GetAppInfo();
+  DCHECK(app_info != nullptr);
+
+  std::string compiler_filter;
+  std::string compilation_reason;
+  app_info->GetPrimaryApkOptimizationStatus(&compiler_filter, &compilation_reason);
+
+  ScopedLocalRef<jclass> cls(env, env->FindClass("dalvik/system/DexFile$OptimizationInfo"));
+  if (cls == nullptr) {
+    DCHECK(env->ExceptionCheck());
+    return nullptr;
+  }
+
+  jmethodID ctor = env->GetMethodID(cls.get(), "<init>", "(Ljava/lang/String;Ljava/lang/String;)V");
+  if (ctor == nullptr) {
+    DCHECK(env->ExceptionCheck());
+    return nullptr;
+  }
+
+  ScopedLocalRef<jstring> j_compiler_filter(env, env->NewStringUTF(compiler_filter.c_str()));
+  if (j_compiler_filter == nullptr) {
+    DCHECK(env->ExceptionCheck());
+    return nullptr;
+  }
+
+  ScopedLocalRef<jstring> j_compilation_reason(env, env->NewStringUTF(compilation_reason.c_str()));
+  if (j_compilation_reason == nullptr) {
+    DCHECK(env->ExceptionCheck());
+    return nullptr;
+  }
+
+  return env->NewObject(cls.get(), ctor, j_compiler_filter.get(), j_compilation_reason.get());
+}
+
 static JNINativeMethod gMethods[] = {
   FAST_NATIVE_METHOD(VMRuntime, addressOf, "(Ljava/lang/Object;)J"),
   NATIVE_METHOD(VMRuntime, bootClassPath, "()Ljava/lang/String;"),
@@ -524,6 +550,7 @@
   FAST_NATIVE_METHOD(VMRuntime, newNonMovableArray, "(Ljava/lang/Class;I)Ljava/lang/Object;"),
   FAST_NATIVE_METHOD(VMRuntime, newUnpaddedArray, "(Ljava/lang/Class;I)Ljava/lang/Object;"),
   NATIVE_METHOD(VMRuntime, properties, "()[Ljava/lang/String;"),
+  NATIVE_METHOD(VMRuntime, getSdkVersionNative, "(I)I"),
   NATIVE_METHOD(VMRuntime, setTargetSdkVersionNative, "(I)V"),
   NATIVE_METHOD(VMRuntime, setDisabledCompatChangesNative, "([J)V"),
   NATIVE_METHOD(VMRuntime, registerNativeAllocation, "(J)V"),
@@ -557,6 +584,8 @@
   NATIVE_METHOD(VMRuntime, bootCompleted, "()V"),
   NATIVE_METHOD(VMRuntime, resetJitCounters, "()V"),
   NATIVE_METHOD(VMRuntime, isValidClassLoaderContext, "(Ljava/lang/String;)Z"),
+  NATIVE_METHOD(VMRuntime, getBaseApkOptimizationInfo,
+      "()Ldalvik/system/DexFile$OptimizationInfo;"),
 };
 
 void register_dalvik_system_VMRuntime(JNIEnv* env) {
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index eae7c20..3c73cc5 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -40,6 +40,7 @@
 #include "oat_file_manager.h"
 #include "scoped_thread_state_change-inl.h"
 #include "stack.h"
+#include "startup_completed_task.h"
 #include "thread-current-inl.h"
 #include "thread_list.h"
 #include "trace.h"
@@ -167,8 +168,9 @@
     if (!vm->IsCheckJniEnabled()) {
       LOG(INFO) << "Late-enabling -Xcheck:jni";
       vm->SetCheckJniEnabled(true);
-      // There's only one thread running at this point, so only one JNIEnv to fix up.
-      Thread::Current()->GetJniEnv()->SetCheckJniEnabled(true);
+      // This is the only thread that's running at this point and the above call sets
+      // the CheckJNI flag in the corresponding `JniEnvExt`.
+      DCHECK(Thread::Current()->GetJniEnv()->IsCheckJniEnabled());
     } else {
       LOG(INFO) << "Not late-enabling -Xcheck:jni (already on)";
     }
@@ -205,7 +207,7 @@
   if ((runtime_flags & DEBUG_JAVA_DEBUGGABLE) != 0) {
     runtime->AddCompilerOption("--debuggable");
     runtime_flags |= DEBUG_GENERATE_MINI_DEBUG_INFO;
-    runtime->SetJavaDebuggable(true);
+    runtime->SetRuntimeDebugState(Runtime::RuntimeDebugState::kJavaDebuggableAtInit);
     {
       // Deoptimize the boot image as it may be non-debuggable.
       ScopedSuspendAll ssa(__FUNCTION__);
@@ -257,7 +259,7 @@
   runtime->PreZygoteFork();
 
   // Grab thread before fork potentially makes Thread::pthread_key_self_ unusable.
-  return reinterpret_cast<jlong>(ThreadForEnv(env));
+  return reinterpret_cast<jlong>(Thread::ForEnv(env));
 }
 
 static void ZygoteHooks_nativePostZygoteFork(JNIEnv*, jclass) {
@@ -344,6 +346,12 @@
   }
 
   runtime->GetHeap()->PostForkChildAction(thread);
+
+  // Setup an app startup complete task in case the app doesn't notify it
+  // through VMRuntime::notifyStartupCompleted.
+  static constexpr uint64_t kMaxAppStartupTimeNs = MsToNs(5000);  // 5 seconds
+  runtime->GetHeap()->AddHeapTask(new StartupCompletedTask(NanoTime() + kMaxAppStartupTimeNs));
+
   if (runtime->GetJit() != nullptr) {
     if (!is_system_server) {
       // System server already called the JIT cache post fork action in `nativePostForkSystemServer`.
@@ -359,6 +367,8 @@
     Trace::TraceOutputMode output_mode = Trace::GetOutputMode();
     Trace::TraceMode trace_mode = Trace::GetMode();
     size_t buffer_size = Trace::GetBufferSize();
+    int flags = Trace::GetFlags();
+    int interval = Trace::GetIntervalInMillis();
 
     // Just drop it.
     Trace::Abort();
@@ -382,13 +392,14 @@
         proc_name = StringPrintf("%u", static_cast<uint32_t>(pid));
       }
 
-      std::string trace_file = StringPrintf("/data/misc/trace/%s.trace.bin", proc_name.c_str());
+      const char* path = kIsTargetBuild ? "/data/misc/trace" : "/tmp";
+      std::string trace_file = StringPrintf("%s/%s.trace.bin", path, proc_name.c_str());
       Trace::Start(trace_file.c_str(),
                    buffer_size,
-                   0,   // TODO: Expose flags.
+                   flags,
                    output_mode,
                    trace_mode,
-                   0);  // TODO: Expose interval.
+                   interval);
       if (thread->IsExceptionPending()) {
         ScopedObjectAccess soa(env);
         thread->ClearException();
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index da42e61..5e54f85 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -19,7 +19,7 @@
 #include <iostream>
 
 #include "art_field-inl.h"
-#include "art_method-inl.h"
+#include "art_method-alloc-inl.h"
 #include "base/enums.h"
 #include "class_linker-inl.h"
 #include "class_root-inl.h"
@@ -32,6 +32,7 @@
 #include "hidden_api.h"
 #include "jni/jni_internal.h"
 #include "mirror/class-alloc-inl.h"
+#include "mirror/class_ext.h"
 #include "mirror/class-inl.h"
 #include "mirror/class_loader.h"
 #include "mirror/field.h"
@@ -53,7 +54,7 @@
 #include "reflective_handle_scope-inl.h"
 #include "scoped_fast_native_object_access-inl.h"
 #include "scoped_thread_state_change-inl.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -89,14 +90,17 @@
 static jclass Class_classForName(JNIEnv* env, jclass, jstring javaName, jboolean initialize,
                                  jobject javaLoader) {
   ScopedFastNativeObjectAccess soa(env);
-  ScopedUtfChars name(env, javaName);
-  if (name.c_str() == nullptr) {
+  StackHandleScope<3> hs(soa.Self());
+  Handle<mirror::String> mirror_name = hs.NewHandle(soa.Decode<mirror::String>(javaName));
+  if (mirror_name == nullptr) {
+    soa.Self()->ThrowNewWrappedException("Ljava/lang/NullPointerException;", /*msg=*/ nullptr);
     return nullptr;
   }
 
   // We need to validate and convert the name (from x.y.z to x/y/z).  This
   // is especially handy for array types, since we want to avoid
   // auto-generating bogus array classes.
+  std::string name = mirror_name->ToModifiedUtf8();
   if (!IsValidBinaryClassName(name.c_str())) {
     soa.Self()->ThrowNewExceptionF("Ljava/lang/ClassNotFoundException;",
                                    "Invalid name: %s", name.c_str());
@@ -104,29 +108,41 @@
   }
 
   std::string descriptor(DotToDescriptor(name.c_str()));
-  StackHandleScope<2> hs(soa.Self());
   Handle<mirror::ClassLoader> class_loader(
       hs.NewHandle(soa.Decode<mirror::ClassLoader>(javaLoader)));
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   Handle<mirror::Class> c(
       hs.NewHandle(class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader)));
-  if (c == nullptr) {
-    ScopedLocalRef<jthrowable> cause(env, env->ExceptionOccurred());
-    env->ExceptionClear();
-    jthrowable cnfe = reinterpret_cast<jthrowable>(
-        env->NewObject(WellKnownClasses::java_lang_ClassNotFoundException,
-                       WellKnownClasses::java_lang_ClassNotFoundException_init,
-                       javaName,
-                       cause.get()));
+  if (UNLIKELY(c == nullptr)) {
+    StackHandleScope<2> hs2(soa.Self());
+    Handle<mirror::Object> cause = hs2.NewHandle(soa.Self()->GetException());
+    soa.Self()->ClearException();
+    Handle<mirror::Object> cnfe =
+        WellKnownClasses::java_lang_ClassNotFoundException_init->NewObject<'L', 'L'>(
+            hs2, soa.Self(), mirror_name, cause);
     if (cnfe != nullptr) {
       // Make sure allocation didn't fail with an OOME.
-      env->Throw(cnfe);
+      soa.Self()->SetException(ObjPtr<mirror::Throwable>::DownCast(cnfe.Get()));
     }
     return nullptr;
   }
   if (initialize) {
     class_linker->EnsureInitialized(soa.Self(), c, true, true);
   }
+
+  // java.lang.ClassValue was added in Android U, and proguarding tools
+  // used that as justification to remove computeValue method implementation.
+  // Usual pattern was to check that Class.forName("java.lang.ClassValue")
+  // call does not throw and use ClassValue-based implementation or fallback
+  // to other solution if it does throw.
+  // So far ClassValue is the only class with such a problem and hence this
+  // ad-hoc check.
+  // See b/259501764.
+  if (!c->CheckIsVisibleWithTargetSdk(soa.Self())) {
+    DCHECK(soa.Self()->IsExceptionPending());
+    return nullptr;
+  }
+
   return soa.AddLocalReference<jclass>(c.Get());
 }
 
@@ -379,7 +395,7 @@
     }
     // We may have a pending exception if we failed to resolve.
     if (!soa.Self()->IsExceptionPending()) {
-      ThrowNoSuchFieldException(h_klass.Get(), name_str.c_str());
+      ThrowNoSuchFieldException(h_klass.Get(), name_str);
     }
     return nullptr;
   }
@@ -563,7 +579,7 @@
   if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
     // Return an empty array instead of a null pointer.
     ObjPtr<mirror::Class>  annotation_array_class =
-        soa.Decode<mirror::Class>(WellKnownClasses::java_lang_annotation_Annotation__array);
+        WellKnownClasses::ToClass(WellKnownClasses::java_lang_annotation_Annotation__array);
     ObjPtr<mirror::ObjectArray<mirror::Object>> empty_array =
         mirror::ObjectArray<mirror::Object>::Alloc(soa.Self(),
                                                    annotation_array_class,
@@ -716,6 +732,13 @@
   return class_name == nullptr;
 }
 
+static jboolean Class_isRecord0(JNIEnv* env, jobject javaThis) {
+  ScopedFastNativeObjectAccess soa(env);
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  return klass->IsRecordClass();
+}
+
 static jboolean Class_isDeclaredAnnotationPresent(JNIEnv* env, jobject javaThis,
                                                   jclass annotationType) {
   ScopedFastNativeObjectAccess soa(env);
@@ -750,6 +773,92 @@
   return soa.AddLocalReference<jclass>(annotations::GetDeclaringClass(klass));
 }
 
+static jclass Class_getNestHostFromAnnotation(JNIEnv* env, jobject javaThis) {
+  ScopedFastNativeObjectAccess soa(env);
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
+  if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
+    return nullptr;
+  }
+  ObjPtr<mirror::Class> hostClass = annotations::GetNestHost(klass);
+  if (hostClass == nullptr) {
+    return nullptr;
+  }
+  return soa.AddLocalReference<jclass>(hostClass);
+}
+
+static jobjectArray Class_getNestMembersFromAnnotation(JNIEnv* env, jobject javaThis) {
+  ScopedFastNativeObjectAccess soa(env);
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
+  if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
+    return nullptr;
+  }
+  ObjPtr<mirror::ObjectArray<mirror::Class>> classes = annotations::GetNestMembers(klass);
+  if (classes == nullptr) {
+    return nullptr;
+  }
+  return soa.AddLocalReference<jobjectArray>(classes);
+}
+
+static jobjectArray Class_getRecordAnnotationElement(JNIEnv* env,
+                                                     jobject javaThis,
+                                                     jstring element_name,
+                                                     jclass array_class) {
+  ScopedFastNativeObjectAccess soa(env);
+  ScopedUtfChars name(env, element_name);
+  StackHandleScope<2> hs(soa.Self());
+  Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (!(klass->IsRecordClass())) {
+    return nullptr;
+  }
+
+  Handle<mirror::Class> a_class(hs.NewHandle(DecodeClass(soa, array_class)));
+  ObjPtr<mirror::Object> element_array =
+      annotations::getRecordAnnotationElement(klass, a_class, name.c_str());
+  if (element_array == nullptr || !(element_array->IsObjectArray())) {
+    return nullptr;
+  }
+  return soa.AddLocalReference<jobjectArray>(element_array);
+}
+
+static jobjectArray Class_getPermittedSubclassesFromAnnotation(JNIEnv* env, jobject javaThis) {
+  ScopedFastNativeObjectAccess soa(env);
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
+  if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
+    return nullptr;
+  }
+  ObjPtr<mirror::ObjectArray<mirror::Class>> classes = annotations::GetPermittedSubclasses(klass);
+  if (classes == nullptr) {
+    return nullptr;
+  }
+  return soa.AddLocalReference<jobjectArray>(classes);
+}
+
+static jobject Class_ensureExtDataPresent(JNIEnv* env, jobject javaThis) {
+  ScopedFastNativeObjectAccess soa(env);
+  StackHandleScope<2> hs(soa.Self());
+  Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis));
+
+  ObjPtr<mirror::Object> extDataPtr =
+    mirror::Class::EnsureExtDataPresent(klass, Thread::Current());
+
+  return soa.AddLocalReference<jobject>(extDataPtr);
+}
+
 static jobject Class_newInstance(JNIEnv* env, jobject javaThis) {
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<4> hs(soa.Self());
@@ -841,6 +950,7 @@
 static JNINativeMethod gMethods[] = {
   FAST_NATIVE_METHOD(Class, classForName,
                 "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Class, ensureExtDataPresent, "()Ldalvik/system/ClassExt;"),
   FAST_NATIVE_METHOD(Class, getDeclaredAnnotation,
                 "(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
   FAST_NATIVE_METHOD(Class, getDeclaredAnnotations, "()[Ljava/lang/annotation/Annotation;"),
@@ -865,10 +975,15 @@
   FAST_NATIVE_METHOD(Class, getInterfacesInternal, "()[Ljava/lang/Class;"),
   FAST_NATIVE_METHOD(Class, getPrimitiveClass, "(Ljava/lang/String;)Ljava/lang/Class;"),
   FAST_NATIVE_METHOD(Class, getNameNative, "()Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(Class, getNestHostFromAnnotation, "()Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Class, getNestMembersFromAnnotation, "()[Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Class, getPermittedSubclassesFromAnnotation, "()[Ljava/lang/Class;"),
   FAST_NATIVE_METHOD(Class, getPublicDeclaredFields, "()[Ljava/lang/reflect/Field;"),
+  FAST_NATIVE_METHOD(Class, getRecordAnnotationElement, "(Ljava/lang/String;Ljava/lang/Class;)[Ljava/lang/Object;"),
   FAST_NATIVE_METHOD(Class, getSignatureAnnotation, "()[Ljava/lang/String;"),
   FAST_NATIVE_METHOD(Class, isAnonymousClass, "()Z"),
   FAST_NATIVE_METHOD(Class, isDeclaredAnnotationPresent, "(Ljava/lang/Class;)Z"),
+  FAST_NATIVE_METHOD(Class, isRecord0, "()Z"),
   FAST_NATIVE_METHOD(Class, newInstance, "()Ljava/lang/Object;"),
 };
 
diff --git a/runtime/native/java_lang_StackStreamFactory.cc b/runtime/native/java_lang_StackStreamFactory.cc
new file mode 100644
index 0000000..f876c10
--- /dev/null
+++ b/runtime/native/java_lang_StackStreamFactory.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "java_lang_StackStreamFactory.h"
+
+#include "nativehelper/jni_macros.h"
+
+#include "jni/jni_internal.h"
+#include "native_util.h"
+#include "scoped_fast_native_object_access-inl.h"
+#include "thread.h"
+
+namespace art {
+
+static jobject StackStreamFactory_nativeGetStackAnchor(JNIEnv* env, jclass) {
+  ScopedFastNativeObjectAccess soa(env);
+  return soa.Self()->CreateInternalStackTrace(soa);
+}
+
+static jint StackStreamFactory_nativeFetchStackFrameInfo(JNIEnv* env, jclass,
+    jlong mode, jobject anchor, jint startLevel, jint batchSize, jint startBufferIndex,
+    jobjectArray frameBuffer) {
+  if (anchor == nullptr) {
+      return startLevel;
+  }
+  ScopedFastNativeObjectAccess soa(env);
+  return Thread::InternalStackTraceToStackFrameInfoArray(soa, mode, anchor,
+    startLevel, batchSize, startBufferIndex, frameBuffer);
+}
+
+static JNINativeMethod gMethods[] = {
+  FAST_NATIVE_METHOD(StackStreamFactory, nativeGetStackAnchor, "()Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(StackStreamFactory, nativeFetchStackFrameInfo, "(JLjava/lang/Object;III[Ljava/lang/Object;)I"),
+};
+
+void register_java_lang_StackStreamFactory(JNIEnv* env) {
+  REGISTER_NATIVE_METHODS("java/lang/StackStreamFactory");
+}
+
+}  // namespace art
diff --git a/runtime/native/java_lang_StackStreamFactory.h b/runtime/native/java_lang_StackStreamFactory.h
new file mode 100644
index 0000000..2216871
--- /dev/null
+++ b/runtime/native/java_lang_StackStreamFactory.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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_RUNTIME_NATIVE_JAVA_LANG_STACKSTREAMFACTORY_H_
+#define ART_RUNTIME_NATIVE_JAVA_LANG_STACKSTREAMFACTORY_H_
+
+#include <jni.h>
+
+namespace art {
+
+void register_java_lang_StackStreamFactory(JNIEnv* env);
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_NATIVE_JAVA_LANG_STACKSTREAMFACTORY_H_
diff --git a/runtime/native/java_lang_String.cc b/runtime/native/java_lang_String.cc
index 94ca5b5..f70a188 100644
--- a/runtime/native/java_lang_String.cc
+++ b/runtime/native/java_lang_String.cc
@@ -88,6 +88,22 @@
   soa.Decode<mirror::String>(java_this)->GetChars(start, end, char_array, index);
 }
 
+static void String_fillBytesLatin1(JNIEnv* env, jobject java_this,
+                                   jbyteArray buffer, jint byteIndex) {
+  ScopedFastNativeObjectAccess soa(env);
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::ByteArray> byte_array(hs.NewHandle(soa.Decode<mirror::ByteArray>(buffer)));
+  soa.Decode<mirror::String>(java_this)->FillBytesLatin1(byte_array, byteIndex);
+}
+
+static void String_fillBytesUTF16(JNIEnv* env, jobject java_this,
+                                   jbyteArray buffer, jint byteIndex) {
+  ScopedFastNativeObjectAccess soa(env);
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::ByteArray> byte_array(hs.NewHandle(soa.Decode<mirror::ByteArray>(buffer)));
+  soa.Decode<mirror::String>(java_this)->FillBytesUTF16(byte_array, byteIndex);
+}
+
 static jstring String_intern(JNIEnv* env, jobject java_this) {
   ScopedFastNativeObjectAccess soa(env);
   ObjPtr<mirror::String> result = soa.Decode<mirror::String>(java_this)->Intern();
@@ -125,6 +141,8 @@
   FAST_NATIVE_METHOD(String, doReplace, "(CC)Ljava/lang/String;"),
   FAST_NATIVE_METHOD(String, fastSubstring, "(II)Ljava/lang/String;"),
   FAST_NATIVE_METHOD(String, getCharsNoCheck, "(II[CI)V"),
+  FAST_NATIVE_METHOD(String, fillBytesLatin1, "([BI)V"),
+  FAST_NATIVE_METHOD(String, fillBytesUTF16, "([BI)V"),
   FAST_NATIVE_METHOD(String, intern, "()Ljava/lang/String;"),
   FAST_NATIVE_METHOD(String, toCharArray, "()[C"),
 };
diff --git a/runtime/native/java_lang_StringFactory.cc b/runtime/native/java_lang_StringFactory.cc
index 9086ee9..2fbebc0 100644
--- a/runtime/native/java_lang_StringFactory.cc
+++ b/runtime/native/java_lang_StringFactory.cc
@@ -56,6 +56,36 @@
   return soa.AddLocalReference<jstring>(result);
 }
 
+static jstring StringFactory_newStringFromUtf16Bytes(
+    JNIEnv* env, jclass, jbyteArray java_data, jint offset, jint char_count) {
+  ScopedFastNativeObjectAccess soa(env);
+  if (UNLIKELY(java_data == nullptr)) {
+    ThrowNullPointerException("data == null");
+    return nullptr;
+  }
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::ByteArray> byte_array(hs.NewHandle(soa.Decode<mirror::ByteArray>(java_data)));
+  int32_t data_size = byte_array->GetLength();
+  DCHECK_GE(data_size, 0);
+  if (offset < 0 ||
+      offset > data_size ||
+      static_cast<uint32_t>(char_count) > (static_cast<uint32_t>(data_size - offset) >> 1)) {
+    soa.Self()->ThrowNewExceptionF("Ljava/lang/StringIndexOutOfBoundsException;",
+                                   "length=%d; regionStart=%d; bytePairLength=%d",
+                                   data_size,
+                                   offset,
+                                   char_count);
+    return nullptr;
+  }
+  gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
+  ObjPtr<mirror::String> result = mirror::String::AllocFromUtf16ByteArray(soa.Self(),
+                                                                          char_count,
+                                                                          byte_array,
+                                                                          offset,
+                                                                          allocator_type);
+  return soa.AddLocalReference<jstring>(result);
+}
+
 // The char array passed as `java_data` must not be a null reference.
 static jstring StringFactory_newStringFromChars(JNIEnv* env, jclass, jint offset,
                                                 jint char_count, jcharArray java_data) {
@@ -269,6 +299,7 @@
   FAST_NATIVE_METHOD(StringFactory, newStringFromChars, "(II[C)Ljava/lang/String;"),
   FAST_NATIVE_METHOD(StringFactory, newStringFromString, "(Ljava/lang/String;)Ljava/lang/String;"),
   FAST_NATIVE_METHOD(StringFactory, newStringFromUtf8Bytes, "([BII)Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(StringFactory, newStringFromUtf16Bytes, "([BII)Ljava/lang/String;"),
 };
 
 void register_java_lang_StringFactory(JNIEnv* env) {
diff --git a/runtime/native/java_lang_VMClassLoader.cc b/runtime/native/java_lang_VMClassLoader.cc
index 11e02a2..b9c72b8 100644
--- a/runtime/native/java_lang_VMClassLoader.cc
+++ b/runtime/native/java_lang_VMClassLoader.cc
@@ -18,6 +18,8 @@
 
 #include "base/zip_archive.h"
 #include "class_linker.h"
+#include "base/transform_iterator.h"
+#include "base/stl_util.h"
 #include "dex/descriptors_names.h"
 #include "dex/dex_file_loader.h"
 #include "dex/utf.h"
@@ -25,13 +27,16 @@
 #include "jni/jni_internal.h"
 #include "mirror/class_loader.h"
 #include "mirror/object-inl.h"
+#include "mirror/object_array-alloc-inl.h"
 #include "native_util.h"
 #include "nativehelper/jni_macros.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "nativehelper/scoped_utf_chars.h"
 #include "obj_ptr.h"
 #include "scoped_fast_native_object_access-inl.h"
-#include "well_known_classes.h"
+#include "string_array_utils.h"
+#include "thread-inl.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -49,14 +54,13 @@
   }
 
   static ObjPtr<mirror::Class> FindClassInPathClassLoader(ClassLinker* cl,
-                                                          ScopedObjectAccessAlreadyRunnable& soa,
                                                           Thread* self,
                                                           const char* descriptor,
                                                           size_t hash,
                                                           Handle<mirror::ClassLoader> class_loader)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     ObjPtr<mirror::Class> result;
-    if (cl->FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result)) {
+    if (cl->FindClassInBaseDexClassLoader(self, descriptor, hash, class_loader, &result)) {
       DCHECK(!self->IsExceptionPending());
       return result;
     }
@@ -93,12 +97,9 @@
   if (c != nullptr && c->IsErroneous()) {
     cl->ThrowEarlierClassFailure(c);
     Thread* self = soa.Self();
-    ObjPtr<mirror::Class> iae_class =
-        self->DecodeJObject(WellKnownClasses::java_lang_IllegalAccessError)->AsClass();
-    ObjPtr<mirror::Class> ncdfe_class =
-        self->DecodeJObject(WellKnownClasses::java_lang_NoClassDefFoundError)->AsClass();
-    ObjPtr<mirror::Class> exception = self->GetException()->GetClass();
-    if (exception == iae_class || exception == ncdfe_class) {
+    ObjPtr<mirror::Class> exception_class = self->GetException()->GetClass();
+    if (exception_class == WellKnownClasses::java_lang_IllegalAccessError ||
+        exception_class == WellKnownClasses::java_lang_NoClassDefFoundError) {
       self->ThrowNewWrappedException("Ljava/lang/ClassNotFoundException;",
                                      c->PrettyDescriptor().c_str());
     }
@@ -112,7 +113,6 @@
     // Try the common case.
     StackHandleScope<1> hs(soa.Self());
     c = VMClassLoader::FindClassInPathClassLoader(cl,
-                                                  soa,
                                                   soa.Self(),
                                                   descriptor.c_str(),
                                                   descriptor_hash,
@@ -131,28 +131,37 @@
  * Returns an array of entries from the boot classpath that could contain resources.
  */
 static jobjectArray VMClassLoader_getBootClassPathEntries(JNIEnv* env, jclass) {
-  const std::vector<const DexFile*>& path =
-      Runtime::Current()->GetClassLinker()->GetBootClassPath();
-  jobjectArray array =
-      env->NewObjectArray(path.size(), WellKnownClasses::java_lang_String, nullptr);
-  if (array == nullptr) {
-    DCHECK(env->ExceptionCheck());
-    return nullptr;
-  }
-  for (size_t i = 0; i < path.size(); ++i) {
-    const DexFile* dex_file = path[i];
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  const std::vector<const DexFile*>& path = class_linker->GetBootClassPath();
+  auto is_base_dex = [](const DexFile* dex_file) {
+    return !DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str());
+  };
+  size_t jar_count = std::count_if(path.begin(), path.end(), is_base_dex);
 
+  const DexFile* last_dex_file = nullptr;
+  auto dchecked_is_base_dex = [&](const DexFile* dex_file) {
     // For multidex locations, e.g., x.jar!classes2.dex, we want to look into x.jar.
-    const std::string location(DexFileLoader::GetBaseLocation(dex_file->GetLocation()));
-
-    ScopedLocalRef<jstring> javaPath(env, env->NewStringUTF(location.c_str()));
-    if (javaPath.get() == nullptr) {
-      DCHECK(env->ExceptionCheck());
-      return nullptr;
+    // But we do not need to look into the base dex file more than once so we filter
+    // out multidex locations using the fact that they follow the base location.
+    if (kIsDebugBuild) {
+      if (is_base_dex(dex_file)) {
+        CHECK_EQ(DexFileLoader::GetBaseLocation(dex_file->GetLocation().c_str()),
+                 dex_file->GetLocation());
+      } else {
+        CHECK(last_dex_file != nullptr);
+        CHECK_EQ(DexFileLoader::GetBaseLocation(dex_file->GetLocation().c_str()),
+                 DexFileLoader::GetBaseLocation(last_dex_file->GetLocation().c_str()));
+      }
+      last_dex_file = dex_file;
     }
-    env->SetObjectArrayElement(array, i, javaPath.get());
-  }
-  return array;
+    return is_base_dex(dex_file);
+  };
+  auto get_location = [](const DexFile* dex_file) { return dex_file->GetLocation(); };
+  ScopedObjectAccess soa(Thread::ForEnv(env));
+  return soa.AddLocalReference<jobjectArray>(CreateStringArray(
+      soa.Self(),
+      jar_count,
+      MakeTransformRange(Filter(path, dchecked_is_base_dex), get_location)));
 }
 
 static JNINativeMethod gMethods[] = {
diff --git a/runtime/native/java_lang_ref_Reference.cc b/runtime/native/java_lang_ref_Reference.cc
index f23010b..8b5635d 100644
--- a/runtime/native/java_lang_ref_Reference.cc
+++ b/runtime/native/java_lang_ref_Reference.cc
@@ -37,7 +37,7 @@
 }
 
 static jboolean Reference_refersTo0(JNIEnv* env, jobject javaThis, jobject o) {
-  if (kUseReadBarrier && !kUseBakerReadBarrier) {
+  if (gUseReadBarrier && !kUseBakerReadBarrier) {
     // Fall back to naive implementation that may block and needlessly preserve javaThis.
     return env->IsSameObject(Reference_getReferent(env, javaThis), o);
   }
@@ -48,7 +48,7 @@
   if (referent == other) {
       return JNI_TRUE;
   }
-  if (!kUseReadBarrier || referent.IsNull() || other.IsNull()) {
+  if (!gUseReadBarrier || referent.IsNull() || other.IsNull()) {
     return JNI_FALSE;
   }
   // Explicitly handle the case in which referent is a from-space pointer.  Don't use a
diff --git a/runtime/native/java_lang_reflect_Constructor.cc b/runtime/native/java_lang_reflect_Constructor.cc
index 1d362c0..4b2cc43 100644
--- a/runtime/native/java_lang_reflect_Constructor.cc
+++ b/runtime/native/java_lang_reflect_Constructor.cc
@@ -33,7 +33,6 @@
 #include "native_util.h"
 #include "reflection.h"
 #include "scoped_fast_native_object_access-inl.h"
-#include "well_known_classes.h"
 
 namespace art {
 
diff --git a/runtime/native/java_lang_reflect_Executable.cc b/runtime/native/java_lang_reflect_Executable.cc
index fef16b9..87c9f6c 100644
--- a/runtime/native/java_lang_reflect_Executable.cc
+++ b/runtime/native/java_lang_reflect_Executable.cc
@@ -19,7 +19,7 @@
 #include "android-base/stringprintf.h"
 #include "nativehelper/jni_macros.h"
 
-#include "art_method-inl.h"
+#include "art_method-alloc-inl.h"
 #include "class_root-inl.h"
 #include "dex/dex_file_annotations.h"
 #include "handle.h"
@@ -45,7 +45,7 @@
   if (method->GetDeclaringClass()->IsProxyClass()) {
     // Return an empty array instead of a null pointer.
     ObjPtr<mirror::Class> annotation_array_class =
-        soa.Decode<mirror::Class>(WellKnownClasses::java_lang_annotation_Annotation__array);
+        WellKnownClasses::ToClass(WellKnownClasses::java_lang_annotation_Annotation__array);
     ObjPtr<mirror::ObjectArray<mirror::Object>> empty_array =
         mirror::ObjectArray<mirror::Object>::Alloc(soa.Self(), annotation_array_class, 0);
     return soa.AddLocalReference<jobjectArray>(empty_array);
@@ -128,7 +128,7 @@
     // Workaround for dexers (d8/dx) that do not insert annotations
     // for implicit parameters (b/68033708).
     ObjPtr<mirror::Class> annotation_array_class =
-        soa.Decode<mirror::Class>(WellKnownClasses::java_lang_annotation_Annotation__array);
+        WellKnownClasses::ToClass(WellKnownClasses::java_lang_annotation_Annotation__array);
     Handle<mirror::ObjectArray<mirror::Object>> empty_annotations = hs.NewHandle(
         mirror::ObjectArray<mirror::Object>::Alloc(soa.Self(), annotation_array_class, 0));
     if (empty_annotations.IsNull()) {
@@ -157,7 +157,7 @@
 static jobjectArray Executable_getParameters0(JNIEnv* env, jobject javaMethod) {
   ScopedFastNativeObjectAccess soa(env);
   Thread* self = soa.Self();
-  StackHandleScope<8> hs(self);
+  StackHandleScope<6> hs(self);
 
   Handle<mirror::Method> executable = hs.NewHandle(soa.Decode<mirror::Method>(javaMethod));
   ArtMethod* art_method = executable.Get()->GetArtMethod();
@@ -197,57 +197,37 @@
   // Instantiate a Parameter[] to hold the result.
   Handle<mirror::Class> parameter_array_class =
       hs.NewHandle(
-          soa.Decode<mirror::Class>(WellKnownClasses::java_lang_reflect_Parameter__array));
-  Handle<mirror::ObjectArray<mirror::Object>> parameter_array =
-      hs.NewHandle(
-          mirror::ObjectArray<mirror::Object>::Alloc(self,
-                                                     parameter_array_class.Get(),
-                                                     names_count));
+          WellKnownClasses::ToClass(WellKnownClasses::java_lang_reflect_Parameter__array));
+  Handle<mirror::ObjectArray<mirror::Object>> parameter_array = hs.NewHandle(
+      mirror::ObjectArray<mirror::Object>::Alloc(self, parameter_array_class.Get(), names_count));
   if (UNLIKELY(parameter_array == nullptr)) {
     self->AssertPendingException();
     return nullptr;
   }
 
-  Handle<mirror::Class> parameter_class =
-      hs.NewHandle(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_reflect_Parameter));
-  ArtMethod* parameter_init =
-      jni::DecodeArtMethod(WellKnownClasses::java_lang_reflect_Parameter_init);
+  ArtMethod* parameter_init = WellKnownClasses::java_lang_reflect_Parameter_init;
 
   // Mutable handles used in the loop below to ensure cleanup without scaling the number of
   // handles by the number of parameters.
   MutableHandle<mirror::String> name = hs.NewHandle<mirror::String>(nullptr);
-  MutableHandle<mirror::Object> parameter = hs.NewHandle<mirror::Object>(nullptr);
 
   // Populate the Parameter[] to return.
   for (int32_t parameter_index = 0; parameter_index < names_count; parameter_index++) {
     name.Assign(names.Get()->Get(parameter_index));
     int32_t modifiers = access_flags.Get()->Get(parameter_index);
 
-    // Allocate / initialize the Parameter to add to parameter_array.
-    parameter.Assign(parameter_class->AllocObject(self));
+    // Create the Parameter to add to parameter_array.
+    ObjPtr<mirror::Object> parameter = parameter_init->NewObject<'L', 'I', 'L', 'I'>(
+        self, name, modifiers, executable, parameter_index);
     if (UNLIKELY(parameter == nullptr)) {
-      self->AssertPendingOOMException();
+      DCHECK(self->IsExceptionPending());
       return nullptr;
     }
 
-    uint32_t args[5] = { PointerToLowMemUInt32(parameter.Get()),
-                         PointerToLowMemUInt32(name.Get()),
-                         static_cast<uint32_t>(modifiers),
-                         PointerToLowMemUInt32(executable.Get()),
-                         static_cast<uint32_t>(parameter_index)
-    };
-    JValue result;
-    static const char* method_signature = "VLILI";  // return + parameter types
-    parameter_init->Invoke(self, args, sizeof(args), &result, method_signature);
-    if (UNLIKELY(self->IsExceptionPending())) {
-      return nullptr;
-    }
-
-    // Store the Parameter in the Parameter[].
-    parameter_array.Get()->Set(parameter_index, parameter.Get());
-    if (UNLIKELY(self->IsExceptionPending())) {
-      return nullptr;
-    }
+    // We're initializing a newly allocated array object, so we do not need to record that under
+    // a transaction. If the transaction is aborted, the whole object shall be unreachable.
+    parameter_array->SetWithoutChecks</*kTransactionActive=*/ false, /*kCheckTransaction=*/ false>(
+        parameter_index, parameter);
   }
   return soa.AddLocalReference<jobjectArray>(parameter_array.Get());
 }
diff --git a/runtime/native/java_lang_reflect_Field.cc b/runtime/native/java_lang_reflect_Field.cc
index cfc4a29..f2603d4 100644
--- a/runtime/native/java_lang_reflect_Field.cc
+++ b/runtime/native/java_lang_reflect_Field.cc
@@ -340,6 +340,26 @@
   }
 }
 
+ALWAYS_INLINE inline static bool ThrowIAEIfRecordFinalField(ObjPtr<mirror::Field> field)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (!(field->IsFinal())) {
+    return false;
+  }
+  ObjPtr<mirror::Class> declaring_class = field->GetDeclaringClass();
+  DCHECK(declaring_class != nullptr);
+  if (!(declaring_class->IsRecordClass())) {
+    return false;
+  }
+
+  ThrowIllegalAccessException(
+          StringPrintf("Cannot set %s field %s of record class %s",
+              PrettyJavaAccessFlags(field->GetAccessFlags()).c_str(),
+              ArtField::PrettyField(field->GetArtField()).c_str(),
+              declaring_class->PrettyClass().c_str()).c_str());
+
+  return true;
+}
+
 static void Field_set(JNIEnv* env, jobject javaField, jobject javaObj, jobject javaValue) {
   ScopedFastNativeObjectAccess soa(env);
   ObjPtr<mirror::Field> f = soa.Decode<mirror::Field>(javaField);
@@ -349,6 +369,10 @@
     DCHECK(soa.Self()->IsExceptionPending());
     return;
   }
+  if (ThrowIAEIfRecordFinalField(f)) {
+    DCHECK(soa.Self()->IsExceptionPending());
+    return;
+  }
   ObjPtr<mirror::Class> field_type;
   const char* field_type_descriptor = f->GetArtField()->GetTypeDescriptor();
   Primitive::Type field_prim_type = Primitive::GetType(field_type_descriptor[0]);
@@ -389,6 +413,10 @@
   if (!CheckReceiver(soa, javaObj, &f, &o)) {
     return;
   }
+  if (ThrowIAEIfRecordFinalField(f)) {
+    DCHECK(soa.Self()->IsExceptionPending());
+    return;
+  }
   Primitive::Type field_type = f->GetTypeAsPrimitiveType();
   if (UNLIKELY(field_type == Primitive::kPrimNot)) {
     ThrowIllegalArgumentException(
@@ -491,7 +519,7 @@
   if (field->GetDeclaringClass()->IsProxyClass()) {
     // Return an empty array instead of a null pointer.
     ObjPtr<mirror::Class> annotation_array_class =
-        soa.Decode<mirror::Class>(WellKnownClasses::java_lang_annotation_Annotation__array);
+        WellKnownClasses::ToClass(WellKnownClasses::java_lang_annotation_Annotation__array);
     ObjPtr<mirror::ObjectArray<mirror::Object>> empty_array =
         mirror::ObjectArray<mirror::Object>::Alloc(soa.Self(), annotation_array_class, 0);
     return soa.AddLocalReference<jobjectArray>(empty_array);
diff --git a/runtime/native/java_lang_reflect_Method.cc b/runtime/native/java_lang_reflect_Method.cc
index 2c0dd80..5f02ad0 100644
--- a/runtime/native/java_lang_reflect_Method.cc
+++ b/runtime/native/java_lang_reflect_Method.cc
@@ -31,7 +31,6 @@
 #include "native_util.h"
 #include "reflection.h"
 #include "scoped_fast_native_object_access-inl.h"
-#include "well_known_classes.h"
 
 namespace art {
 
@@ -80,6 +79,7 @@
   }
 }
 
+NO_STACK_PROTECTOR
 static jobject Method_invoke(JNIEnv* env, jobject javaMethod, jobject javaReceiver,
                              jobjectArray javaArgs) {
   ScopedFastNativeObjectAccess soa(env);
diff --git a/runtime/native/jdk_internal_misc_Unsafe.cc b/runtime/native/jdk_internal_misc_Unsafe.cc
index 307a2fa..6e2f558 100644
--- a/runtime/native/jdk_internal_misc_Unsafe.cc
+++ b/runtime/native/jdk_internal_misc_Unsafe.cc
@@ -34,7 +34,7 @@
 #include "art_field-inl.h"
 #include "native_util.h"
 #include "scoped_fast_native_object_access-inl.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -92,14 +92,18 @@
   return Unsafe_compareAndSetLong(env, obj, javaObj, offset, expectedValue, newValue);
 }
 
-static jboolean Unsafe_compareAndSetObject(JNIEnv* env, jobject, jobject javaObj, jlong offset,
-                                           jobject javaExpectedValue, jobject javaNewValue) {
+static jboolean Unsafe_compareAndSetReference(JNIEnv* env,
+                                              jobject,
+                                              jobject javaObj,
+                                              jlong offset,
+                                              jobject javaExpectedValue,
+                                              jobject javaNewValue) {
   ScopedFastNativeObjectAccess soa(env);
   ObjPtr<mirror::Object> obj = soa.Decode<mirror::Object>(javaObj);
   ObjPtr<mirror::Object> expectedValue = soa.Decode<mirror::Object>(javaExpectedValue);
   ObjPtr<mirror::Object> newValue = soa.Decode<mirror::Object>(javaNewValue);
   // JNI must use non transactional mode.
-  if (kUseReadBarrier) {
+  if (gUseReadBarrier) {
     // Need to make sure the reference stored in the field is a to-space one before attempting the
     // CAS or the CAS could fail incorrectly.
     // Note that the read barrier load does NOT need to be volatile.
@@ -122,10 +126,10 @@
 
 static jboolean Unsafe_compareAndSwapObject(JNIEnv* env, jobject obj, jobject javaObj, jlong offset,
                                             jobject javaExpectedValue, jobject javaNewValue) {
-  // compareAndSetObject has the same semantics as compareAndSwapObject, except for
+  // compareAndSetReference has the same semantics as compareAndSwapObject, except for
   // being strict (volatile). Since this was implemented in a strict mode it can
   // just call the volatile version unless it gets relaxed.
-  return Unsafe_compareAndSetObject(env, obj, javaObj, offset, javaExpectedValue, javaNewValue);
+  return Unsafe_compareAndSetReference(env, obj, javaObj, offset, javaExpectedValue, javaNewValue);
 }
 
 static jint Unsafe_getInt(JNIEnv* env, jobject, jobject javaObj, jlong offset) {
@@ -201,22 +205,22 @@
   obj->SetField64<false>(MemberOffset(offset), newValue);
 }
 
-static jobject Unsafe_getObjectVolatile(JNIEnv* env, jobject, jobject javaObj, jlong offset) {
+static jobject Unsafe_getReferenceVolatile(JNIEnv* env, jobject, jobject javaObj, jlong offset) {
   ScopedFastNativeObjectAccess soa(env);
   ObjPtr<mirror::Object> obj = soa.Decode<mirror::Object>(javaObj);
   ObjPtr<mirror::Object> value = obj->GetFieldObjectVolatile<mirror::Object>(MemberOffset(offset));
   return soa.AddLocalReference<jobject>(value);
 }
 
-static jobject Unsafe_getObject(JNIEnv* env, jobject, jobject javaObj, jlong offset) {
+static jobject Unsafe_getReference(JNIEnv* env, jobject, jobject javaObj, jlong offset) {
   ScopedFastNativeObjectAccess soa(env);
   ObjPtr<mirror::Object> obj = soa.Decode<mirror::Object>(javaObj);
   ObjPtr<mirror::Object> value = obj->GetFieldObject<mirror::Object>(MemberOffset(offset));
   return soa.AddLocalReference<jobject>(value);
 }
 
-static void Unsafe_putObject(JNIEnv* env, jobject, jobject javaObj, jlong offset,
-                             jobject javaNewValue) {
+static void Unsafe_putReference(
+    JNIEnv* env, jobject, jobject javaObj, jlong offset, jobject javaNewValue) {
   ScopedFastNativeObjectAccess soa(env);
   ObjPtr<mirror::Object> obj = soa.Decode<mirror::Object>(javaObj);
   ObjPtr<mirror::Object> newValue = soa.Decode<mirror::Object>(javaNewValue);
@@ -224,8 +228,8 @@
   obj->SetFieldObject<false>(MemberOffset(offset), newValue);
 }
 
-static void Unsafe_putObjectVolatile(JNIEnv* env, jobject, jobject javaObj, jlong offset,
-                                     jobject javaNewValue) {
+static void Unsafe_putReferenceVolatile(
+    JNIEnv* env, jobject, jobject javaObj, jlong offset, jobject javaNewValue) {
   ScopedFastNativeObjectAccess soa(env);
   ObjPtr<mirror::Object> obj = soa.Decode<mirror::Object>(javaObj);
   ObjPtr<mirror::Object> newValue = soa.Decode<mirror::Object>(javaNewValue);
@@ -474,12 +478,14 @@
 
 static void Unsafe_unpark(JNIEnv* env, jobject, jobject jthread) {
   art::ScopedFastNativeObjectAccess soa(env);
-  if (jthread == nullptr || !env->IsInstanceOf(jthread, WellKnownClasses::java_lang_Thread)) {
+  ObjPtr<mirror::Object> mirror_thread = soa.Decode<mirror::Object>(jthread);
+  if (mirror_thread == nullptr ||
+      !mirror_thread->InstanceOf(WellKnownClasses::java_lang_Thread.Get())) {
     ThrowIllegalArgumentException("Argument to unpark() was not a Thread");
     return;
   }
   art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
-  art::Thread* thread = art::Thread::FromManagedThread(soa, jthread);
+  art::Thread* thread = art::Thread::FromManagedThread(soa, mirror_thread);
   if (thread != nullptr) {
     thread->Unpark();
   } else {
@@ -487,80 +493,82 @@
     // or the thread has already terminated. Setting the field to true will be
     // respected when the thread does start, and is harmless if the thread has
     // already terminated.
-    ArtField* unparked =
-        jni::DecodeArtField(WellKnownClasses::java_lang_Thread_unparkedBeforeStart);
+    ArtField* unparked = WellKnownClasses::java_lang_Thread_unparkedBeforeStart;
     // JNI must use non transactional mode.
-    unparked->SetBoolean<false>(soa.Decode<mirror::Object>(jthread), JNI_TRUE);
+    unparked->SetBoolean<false>(mirror_thread, JNI_TRUE);
   }
 }
 
 static JNINativeMethod gMethods[] = {
-  FAST_NATIVE_METHOD(Unsafe, compareAndSwapInt, "(Ljava/lang/Object;JII)Z"),
-  FAST_NATIVE_METHOD(Unsafe, compareAndSwapLong, "(Ljava/lang/Object;JJJ)Z"),
-  FAST_NATIVE_METHOD(Unsafe, compareAndSwapObject, "(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z"),
-  FAST_NATIVE_METHOD(Unsafe, compareAndSetInt, "(Ljava/lang/Object;JII)Z"),
-  FAST_NATIVE_METHOD(Unsafe, compareAndSetLong, "(Ljava/lang/Object;JJJ)Z"),
-  FAST_NATIVE_METHOD(Unsafe, compareAndSetObject, "(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z"),
-  FAST_NATIVE_METHOD(Unsafe, getIntVolatile, "(Ljava/lang/Object;J)I"),
-  FAST_NATIVE_METHOD(Unsafe, putIntVolatile, "(Ljava/lang/Object;JI)V"),
-  FAST_NATIVE_METHOD(Unsafe, getLongVolatile, "(Ljava/lang/Object;J)J"),
-  FAST_NATIVE_METHOD(Unsafe, putLongVolatile, "(Ljava/lang/Object;JJ)V"),
-  FAST_NATIVE_METHOD(Unsafe, getObjectVolatile, "(Ljava/lang/Object;J)Ljava/lang/Object;"),
-  FAST_NATIVE_METHOD(Unsafe, putObjectVolatile, "(Ljava/lang/Object;JLjava/lang/Object;)V"),
-  FAST_NATIVE_METHOD(Unsafe, getInt, "(Ljava/lang/Object;J)I"),
-  FAST_NATIVE_METHOD(Unsafe, putInt, "(Ljava/lang/Object;JI)V"),
-  FAST_NATIVE_METHOD(Unsafe, putOrderedInt, "(Ljava/lang/Object;JI)V"),
-  FAST_NATIVE_METHOD(Unsafe, getLong, "(Ljava/lang/Object;J)J"),
-  FAST_NATIVE_METHOD(Unsafe, putLong, "(Ljava/lang/Object;JJ)V"),
-  FAST_NATIVE_METHOD(Unsafe, putOrderedLong, "(Ljava/lang/Object;JJ)V"),
-  FAST_NATIVE_METHOD(Unsafe, getObject, "(Ljava/lang/Object;J)Ljava/lang/Object;"),
-  FAST_NATIVE_METHOD(Unsafe, putObject, "(Ljava/lang/Object;JLjava/lang/Object;)V"),
-  FAST_NATIVE_METHOD(Unsafe, putOrderedObject, "(Ljava/lang/Object;JLjava/lang/Object;)V"),
-  FAST_NATIVE_METHOD(Unsafe, getArrayBaseOffsetForComponentType, "(Ljava/lang/Class;)I"),
-  FAST_NATIVE_METHOD(Unsafe, getArrayIndexScaleForComponentType, "(Ljava/lang/Class;)I"),
-  FAST_NATIVE_METHOD(Unsafe, addressSize, "()I"),
-  FAST_NATIVE_METHOD(Unsafe, pageSize, "()I"),
-  FAST_NATIVE_METHOD(Unsafe, allocateMemory, "(J)J"),
-  FAST_NATIVE_METHOD(Unsafe, freeMemory, "(J)V"),
-  FAST_NATIVE_METHOD(Unsafe, setMemory, "(JJB)V"),
-  FAST_NATIVE_METHOD(Unsafe, copyMemory0, "(Ljava/lang/Object;JLjava/lang/Object;JJ)V"),
-  FAST_NATIVE_METHOD(Unsafe, getBoolean, "(Ljava/lang/Object;J)Z"),
+    FAST_NATIVE_METHOD(Unsafe, compareAndSwapInt, "(Ljava/lang/Object;JII)Z"),
+    FAST_NATIVE_METHOD(Unsafe, compareAndSwapLong, "(Ljava/lang/Object;JJJ)Z"),
+    FAST_NATIVE_METHOD(
+        Unsafe, compareAndSwapObject, "(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z"),
+    FAST_NATIVE_METHOD(Unsafe, compareAndSetInt, "(Ljava/lang/Object;JII)Z"),
+    FAST_NATIVE_METHOD(Unsafe, compareAndSetLong, "(Ljava/lang/Object;JJJ)Z"),
+    FAST_NATIVE_METHOD(Unsafe,
+                       compareAndSetReference,
+                       "(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z"),
+    FAST_NATIVE_METHOD(Unsafe, getIntVolatile, "(Ljava/lang/Object;J)I"),
+    FAST_NATIVE_METHOD(Unsafe, putIntVolatile, "(Ljava/lang/Object;JI)V"),
+    FAST_NATIVE_METHOD(Unsafe, getLongVolatile, "(Ljava/lang/Object;J)J"),
+    FAST_NATIVE_METHOD(Unsafe, putLongVolatile, "(Ljava/lang/Object;JJ)V"),
+    FAST_NATIVE_METHOD(Unsafe, getReferenceVolatile, "(Ljava/lang/Object;J)Ljava/lang/Object;"),
+    FAST_NATIVE_METHOD(Unsafe, putReferenceVolatile, "(Ljava/lang/Object;JLjava/lang/Object;)V"),
+    FAST_NATIVE_METHOD(Unsafe, getInt, "(Ljava/lang/Object;J)I"),
+    FAST_NATIVE_METHOD(Unsafe, putInt, "(Ljava/lang/Object;JI)V"),
+    FAST_NATIVE_METHOD(Unsafe, putOrderedInt, "(Ljava/lang/Object;JI)V"),
+    FAST_NATIVE_METHOD(Unsafe, getLong, "(Ljava/lang/Object;J)J"),
+    FAST_NATIVE_METHOD(Unsafe, putLong, "(Ljava/lang/Object;JJ)V"),
+    FAST_NATIVE_METHOD(Unsafe, putOrderedLong, "(Ljava/lang/Object;JJ)V"),
+    FAST_NATIVE_METHOD(Unsafe, getReference, "(Ljava/lang/Object;J)Ljava/lang/Object;"),
+    FAST_NATIVE_METHOD(Unsafe, putReference, "(Ljava/lang/Object;JLjava/lang/Object;)V"),
+    FAST_NATIVE_METHOD(Unsafe, putOrderedObject, "(Ljava/lang/Object;JLjava/lang/Object;)V"),
+    FAST_NATIVE_METHOD(Unsafe, getArrayBaseOffsetForComponentType, "(Ljava/lang/Class;)I"),
+    FAST_NATIVE_METHOD(Unsafe, getArrayIndexScaleForComponentType, "(Ljava/lang/Class;)I"),
+    FAST_NATIVE_METHOD(Unsafe, addressSize, "()I"),
+    FAST_NATIVE_METHOD(Unsafe, pageSize, "()I"),
+    FAST_NATIVE_METHOD(Unsafe, allocateMemory, "(J)J"),
+    FAST_NATIVE_METHOD(Unsafe, freeMemory, "(J)V"),
+    FAST_NATIVE_METHOD(Unsafe, setMemory, "(JJB)V"),
+    FAST_NATIVE_METHOD(Unsafe, copyMemory0, "(Ljava/lang/Object;JLjava/lang/Object;JJ)V"),
+    FAST_NATIVE_METHOD(Unsafe, getBoolean, "(Ljava/lang/Object;J)Z"),
 
-  FAST_NATIVE_METHOD(Unsafe, getByte, "(Ljava/lang/Object;J)B"),
-  FAST_NATIVE_METHOD(Unsafe, getChar, "(Ljava/lang/Object;J)C"),
-  FAST_NATIVE_METHOD(Unsafe, getShort, "(Ljava/lang/Object;J)S"),
-  FAST_NATIVE_METHOD(Unsafe, getFloat, "(Ljava/lang/Object;J)F"),
-  FAST_NATIVE_METHOD(Unsafe, getDouble, "(Ljava/lang/Object;J)D"),
-  FAST_NATIVE_METHOD(Unsafe, putBoolean, "(Ljava/lang/Object;JZ)V"),
-  FAST_NATIVE_METHOD(Unsafe, putByte, "(Ljava/lang/Object;JB)V"),
-  FAST_NATIVE_METHOD(Unsafe, putChar, "(Ljava/lang/Object;JC)V"),
-  FAST_NATIVE_METHOD(Unsafe, putShort, "(Ljava/lang/Object;JS)V"),
-  FAST_NATIVE_METHOD(Unsafe, putFloat, "(Ljava/lang/Object;JF)V"),
-  FAST_NATIVE_METHOD(Unsafe, putDouble, "(Ljava/lang/Object;JD)V"),
-  FAST_NATIVE_METHOD(Unsafe, unpark, "(Ljava/lang/Object;)V"),
-  NATIVE_METHOD(Unsafe, park, "(ZJ)V"),
+    FAST_NATIVE_METHOD(Unsafe, getByte, "(Ljava/lang/Object;J)B"),
+    FAST_NATIVE_METHOD(Unsafe, getChar, "(Ljava/lang/Object;J)C"),
+    FAST_NATIVE_METHOD(Unsafe, getShort, "(Ljava/lang/Object;J)S"),
+    FAST_NATIVE_METHOD(Unsafe, getFloat, "(Ljava/lang/Object;J)F"),
+    FAST_NATIVE_METHOD(Unsafe, getDouble, "(Ljava/lang/Object;J)D"),
+    FAST_NATIVE_METHOD(Unsafe, putBoolean, "(Ljava/lang/Object;JZ)V"),
+    FAST_NATIVE_METHOD(Unsafe, putByte, "(Ljava/lang/Object;JB)V"),
+    FAST_NATIVE_METHOD(Unsafe, putChar, "(Ljava/lang/Object;JC)V"),
+    FAST_NATIVE_METHOD(Unsafe, putShort, "(Ljava/lang/Object;JS)V"),
+    FAST_NATIVE_METHOD(Unsafe, putFloat, "(Ljava/lang/Object;JF)V"),
+    FAST_NATIVE_METHOD(Unsafe, putDouble, "(Ljava/lang/Object;JD)V"),
+    FAST_NATIVE_METHOD(Unsafe, unpark, "(Ljava/lang/Object;)V"),
+    NATIVE_METHOD(Unsafe, park, "(ZJ)V"),
 
-  // Each of the getFoo variants are overloaded with a call that operates
-  // directively on a native pointer.
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getByte, "(J)B", getByteJ),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getChar, "(J)C", getCharJ),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getShort, "(J)S", getShortJ),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getInt, "(J)I", getIntJ),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getLong, "(J)J", getLongJ),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getFloat, "(J)F", getFloatJ),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getDouble, "(J)D", getDoubleJ),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putByte, "(JB)V", putByteJB),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putChar, "(JC)V", putCharJC),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putShort, "(JS)V", putShortJS),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putInt, "(JI)V", putIntJI),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putLong, "(JJ)V", putLongJJ),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putFloat, "(JF)V", putFloatJF),
-  OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putDouble, "(JD)V", putDoubleJD),
+    // Each of the getFoo variants are overloaded with a call that operates
+    // directively on a native pointer.
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getByte, "(J)B", getByteJ),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getChar, "(J)C", getCharJ),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getShort, "(J)S", getShortJ),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getInt, "(J)I", getIntJ),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getLong, "(J)J", getLongJ),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getFloat, "(J)F", getFloatJ),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, getDouble, "(J)D", getDoubleJ),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putByte, "(JB)V", putByteJB),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putChar, "(JC)V", putCharJC),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putShort, "(JS)V", putShortJS),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putInt, "(JI)V", putIntJI),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putLong, "(JJ)V", putLongJJ),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putFloat, "(JF)V", putFloatJF),
+    OVERLOADED_FAST_NATIVE_METHOD(Unsafe, putDouble, "(JD)V", putDoubleJD),
 
-  // CAS
-  FAST_NATIVE_METHOD(Unsafe, loadFence, "()V"),
-  FAST_NATIVE_METHOD(Unsafe, storeFence, "()V"),
-  FAST_NATIVE_METHOD(Unsafe, fullFence, "()V"),
+    // CAS
+    FAST_NATIVE_METHOD(Unsafe, loadFence, "()V"),
+    FAST_NATIVE_METHOD(Unsafe, storeFence, "()V"),
+    FAST_NATIVE_METHOD(Unsafe, fullFence, "()V"),
 };
 
 void register_jdk_internal_misc_Unsafe(JNIEnv* env) {
diff --git a/runtime/native/libcore_util_CharsetUtils.cc b/runtime/native/libcore_util_CharsetUtils.cc
index 92f355e..c53fd6e 100644
--- a/runtime/native/libcore_util_CharsetUtils.cc
+++ b/runtime/native/libcore_util_CharsetUtils.cc
@@ -50,24 +50,6 @@
   }
 }
 
-static void CharsetUtils_isoLatin1BytesToChars(JNIEnv* env, jclass, jbyteArray javaBytes,
-                                               jint offset, jint length, jcharArray javaChars) {
-  ScopedByteArrayRO bytes(env, javaBytes);
-  if (bytes.get() == nullptr) {
-    return;
-  }
-  ScopedCharArrayRW chars(env, javaChars);
-  if (chars.get() == nullptr) {
-    return;
-  }
-
-  const jbyte* src = &bytes[offset];
-  jchar* dst = &chars[0];
-  for (int i = length - 1; i >= 0; --i) {
-    *dst++ = static_cast<jchar>(*src++ & 0xff);
-  }
-}
-
 /**
  * Translates the given characters to US-ASCII or ISO-8859-1 bytes, using the fact that
  * Unicode code points between U+0000 and U+007f inclusive are identical to US-ASCII, while
@@ -157,7 +139,6 @@
 
 static JNINativeMethod gMethods[] = {
   FAST_NATIVE_METHOD(CharsetUtils, asciiBytesToChars, "([BII[C)V"),
-  FAST_NATIVE_METHOD(CharsetUtils, isoLatin1BytesToChars, "([BII[C)V"),
   FAST_NATIVE_METHOD(CharsetUtils, toAsciiBytes, "(Ljava/lang/String;II)[B"),
   FAST_NATIVE_METHOD(CharsetUtils, toIsoLatin1Bytes, "(Ljava/lang/String;II)[B"),
   FAST_NATIVE_METHOD(CharsetUtils, toUtf8Bytes, "(Ljava/lang/String;II)[B"),
diff --git a/runtime/native/string_array_utils.h b/runtime/native/string_array_utils.h
new file mode 100644
index 0000000..41d5093
--- /dev/null
+++ b/runtime/native/string_array_utils.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 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_RUNTIME_NATIVE_STRING_ARRAY_UTILS_H_
+#define ART_RUNTIME_NATIVE_STRING_ARRAY_UTILS_H_
+
+#include "base/locks.h"
+#include "class_root-inl.h"
+#include "handle_scope-inl.h"
+#include "mirror/object_array-alloc-inl.h"
+#include "mirror/string.h"
+
+namespace art {
+
+namespace detail {
+
+inline const char* GetStringCStr(const char* str) { return str; }
+inline const char* GetStringCStr(const std::string& str) { return str.c_str(); }
+
+}  // namespace detail
+
+template <typename Container>
+ObjPtr<mirror::ObjectArray<mirror::String>> CreateStringArray(
+    Thread* self, size_t size, const Container& entries) REQUIRES_SHARED(Locks::mutator_lock_) {
+  StackHandleScope<1u> hs(self);
+  Handle<mirror::ObjectArray<mirror::String>> array = hs.NewHandle(
+      mirror::ObjectArray<mirror::String>::Alloc(
+          self, GetClassRoot<mirror::ObjectArray<mirror::String>>(), size));
+  if (array == nullptr) {
+    DCHECK(self->IsExceptionPending());
+    return nullptr;
+  }
+  // Note: If the container's iterator returns a `std::string` by value, the `auto&&`
+  // binds as a const reference and extends the lifetime of the temporary object.
+  size_t pos = 0u;
+  for (auto&& entry : entries) {
+    ObjPtr<mirror::String> oentry =
+        mirror::String::AllocFromModifiedUtf8(self, detail::GetStringCStr(entry));
+    if (oentry == nullptr) {
+      DCHECK(self->IsExceptionPending());
+      return nullptr;
+    }
+    // We're initializing a newly allocated array object, so we do not need to record that under
+    // a transaction. If the transaction is aborted, the whole object shall be unreachable.
+    DCHECK_LT(pos, size);
+    array->SetWithoutChecks</*kTransactionActive=*/ false, /*kCheckTransaction=*/ false>(
+        pos, oentry);
+    ++pos;
+  }
+  DCHECK_EQ(pos, size);
+  return array.Get();
+}
+
+template <typename Container>
+ObjPtr<mirror::ObjectArray<mirror::String>> CreateStringArray(
+    Thread* self, const Container& entries) REQUIRES_SHARED(Locks::mutator_lock_) {
+  return CreateStringArray(self, entries.size(), entries);
+}
+
+inline ObjPtr<mirror::ObjectArray<mirror::String>> CreateStringArray(
+    Thread* self, std::initializer_list<const char*> entries)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  return CreateStringArray<std::initializer_list<const char*>>(self, entries);
+}
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_NATIVE_STRING_ARRAY_UTILS_H_
diff --git a/runtime/native/sun_misc_Unsafe.cc b/runtime/native/sun_misc_Unsafe.cc
index e9c5af0..8a203ce 100644
--- a/runtime/native/sun_misc_Unsafe.cc
+++ b/runtime/native/sun_misc_Unsafe.cc
@@ -34,7 +34,7 @@
 #include "art_field-inl.h"
 #include "native_util.h"
 #include "scoped_fast_native_object_access-inl.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -69,7 +69,7 @@
   ObjPtr<mirror::Object> expectedValue = soa.Decode<mirror::Object>(javaExpectedValue);
   ObjPtr<mirror::Object> newValue = soa.Decode<mirror::Object>(javaNewValue);
   // JNI must use non transactional mode.
-  if (kUseReadBarrier) {
+  if (gUseReadBarrier) {
     // Need to make sure the reference stored in the field is a to-space one before attempting the
     // CAS or the CAS could fail incorrectly.
     // Note that the read barrier load does NOT need to be volatile.
@@ -521,12 +521,14 @@
 
 static void Unsafe_unpark(JNIEnv* env, jobject, jobject jthread) {
   art::ScopedFastNativeObjectAccess soa(env);
-  if (jthread == nullptr || !env->IsInstanceOf(jthread, WellKnownClasses::java_lang_Thread)) {
+  ObjPtr<mirror::Object> mirror_thread = soa.Decode<mirror::Object>(jthread);
+  if (mirror_thread == nullptr ||
+      !mirror_thread->InstanceOf(WellKnownClasses::java_lang_Thread.Get())) {
     ThrowIllegalArgumentException("Argument to unpark() was not a Thread");
     return;
   }
   art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
-  art::Thread* thread = art::Thread::FromManagedThread(soa, jthread);
+  art::Thread* thread = art::Thread::FromManagedThread(soa, mirror_thread);
   if (thread != nullptr) {
     thread->Unpark();
   } else {
@@ -534,10 +536,9 @@
     // or the thread has already terminated. Setting the field to true will be
     // respected when the thread does start, and is harmless if the thread has
     // already terminated.
-    ArtField* unparked =
-        jni::DecodeArtField(WellKnownClasses::java_lang_Thread_unparkedBeforeStart);
+    ArtField* unparked = WellKnownClasses::java_lang_Thread_unparkedBeforeStart;
     // JNI must use non transactional mode.
-    unparked->SetBoolean<false>(soa.Decode<mirror::Object>(jthread), JNI_TRUE);
+    unparked->SetBoolean<false>(mirror_thread, JNI_TRUE);
   }
 }
 
diff --git a/runtime/native_stack_dump.cc b/runtime/native_stack_dump.cc
index b7c3665..d6a0fae 100644
--- a/runtime/native_stack_dump.cc
+++ b/runtime/native_stack_dump.cc
@@ -18,18 +18,17 @@
 
 #include <memory>
 #include <ostream>
+#include <string_view>
 
 #include <stdio.h>
 
 #include "art_method.h"
 
 // For DumpNativeStack.
-#include <backtrace/Backtrace.h>
-#include <backtrace/BacktraceMap.h>
+#include <unwindstack/AndroidUnwinder.h>
 
 #if defined(__linux__)
 
-#include <memory>
 #include <vector>
 
 #include <linux/unistd.h>
@@ -69,13 +68,16 @@
 static constexpr bool kUseAddr2line = !kIsTargetBuild;
 
 std::string FindAddr2line() {
-#ifdef ART_CLANG_PATH
-  const char* env_value = getenv("ANDROID_BUILD_TOP");
-  if (env_value != nullptr) {
-    return std::string(env_value) + "/" + ART_CLANG_PATH + "/bin/llvm-addr2line";
-  }
+#if !defined(ART_TARGET) && !defined(ART_CLANG_PATH)
+  #error "ART_CLANG_PATH must be defined on host build"
 #endif
-  return std::string("/usr/bin/addr2line");
+#if defined(ART_CLANG_PATH)
+  const char* env_value = getenv("ANDROID_BUILD_TOP");
+  std::string_view top(env_value != nullptr ? env_value : ".");
+  return std::string(top) + "/" + ART_CLANG_PATH + "/bin/llvm-addr2line";
+#else
+  return std::string("llvm-addr2line");
+#endif
 }
 
 ALWAYS_INLINE
@@ -298,50 +300,51 @@
   }
 }
 
-static bool PcIsWithinQuickCode(ArtMethod* method, uintptr_t pc) NO_THREAD_SAFETY_ANALYSIS {
-  const void* entry_point = method->GetEntryPointFromQuickCompiledCode();
-  if (entry_point == nullptr) {
-    return pc == 0;
+// Remove method parameters by finding matching top-level parenthesis and removing them.
+// Since functions can be defined inside functions, this can remove multiple substrings.
+std::string StripParameters(std::string name) {
+  size_t end = name.size();
+  int nesting = 0;
+  for (ssize_t i = name.size() - 1; i > 0; i--) {
+    if (name[i] == ')' && nesting++ == 0) {
+      end = i + 1;
+    }
+    if (name[i] == '(' && --nesting == 0) {
+      name = name.erase(i, end - i);
+    }
   }
-  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  if (class_linker->IsQuickGenericJniStub(entry_point) ||
-      class_linker->IsQuickResolutionStub(entry_point) ||
-      class_linker->IsQuickToInterpreterBridge(entry_point)) {
-    return false;
-  }
-  // The backtrace library might have heuristically subracted instruction
-  // size from the pc, to pretend the pc is at the calling instruction.
-  if (reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) - pc <= 4) {
-    return false;
-  }
-  uintptr_t code = reinterpret_cast<uintptr_t>(EntryPointToCodePointer(entry_point));
-  uintptr_t code_size = reinterpret_cast<const OatQuickMethodHeader*>(code)[-1].GetCodeSize();
-  return code <= pc && pc <= (code + code_size);
+  return name;
 }
 
 void DumpNativeStack(std::ostream& os,
                      pid_t tid,
-                     BacktraceMap* existing_map,
+                     const char* prefix,
+                     ArtMethod* current_method,
+                     void* ucontext_ptr,
+                     bool skip_frames) {
+  unwindstack::AndroidLocalUnwinder unwinder;
+  DumpNativeStack(os, unwinder, tid, prefix, current_method, ucontext_ptr, skip_frames);
+}
+
+void DumpNativeStack(std::ostream& os,
+                     unwindstack::AndroidLocalUnwinder& unwinder,
+                     pid_t tid,
                      const char* prefix,
                      ArtMethod* current_method,
                      void* ucontext_ptr,
                      bool skip_frames) {
   // Historical note: This was disabled when running under Valgrind (b/18119146).
 
-  BacktraceMap* map = existing_map;
-  std::unique_ptr<BacktraceMap> tmp_map;
-  if (map == nullptr) {
-    tmp_map.reset(BacktraceMap::Create(getpid()));
-    map = tmp_map.get();
+  unwindstack::AndroidUnwinderData data(!skip_frames /*show_all_frames*/);
+  bool unwind_ret;
+  if (ucontext_ptr != nullptr) {
+    unwind_ret = unwinder.Unwind(ucontext_ptr, data);
+  } else {
+    unwind_ret = unwinder.Unwind(tid, data);
   }
-  std::unique_ptr<Backtrace> backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, tid, map));
-  backtrace->SetSkipFrames(skip_frames);
-  if (!backtrace->Unwind(0, reinterpret_cast<ucontext*>(ucontext_ptr))) {
-    os << prefix << "(backtrace::Unwind failed for thread " << tid
-       << ": " <<  backtrace->GetErrorString(backtrace->GetError()) << ")" << std::endl;
-    return;
-  } else if (backtrace->NumFrames() == 0) {
-    os << prefix << "(no native stack frames for thread " << tid << ")" << std::endl;
+  if (!unwind_ret) {
+    os << prefix << "(Unwind failed for thread " << tid << ": "
+       <<  data.GetErrorString() << ")" << std::endl;
     return;
   }
 
@@ -356,9 +359,9 @@
   }
 
   std::unique_ptr<Addr2linePipe> addr2line_state;
-
-  for (Backtrace::const_iterator it = backtrace->begin();
-       it != backtrace->end(); ++it) {
+  data.DemangleFunctionNames();
+  bool holds_mutator_lock =  Locks::mutator_lock_->IsSharedHeld(Thread::Current());
+  for (const unwindstack::FrameData& frame : data.frames) {
     // We produce output like this:
     // ]    #00 pc 000075bb8  /system/lib/libc.so (unwind_backtrace_thread+536)
     // In order for parsing tools to continue to function, the stack dump
@@ -367,53 +370,57 @@
     // The parsers require a single space before and after pc, and two spaces
     // after the <RELATIVE_ADDR>. There can be any prefix data before the
     // #XX. <RELATIVE_ADDR> has to be a hex number but with no 0x prefix.
-    os << prefix << StringPrintf("#%02zu pc ", it->num);
+    os << prefix << StringPrintf("#%02zu pc ", frame.num);
     bool try_addr2line = false;
-    if (!BacktraceMap::IsValid(it->map)) {
-      os << StringPrintf(Is64BitInstructionSet(kRuntimeISA) ? "%016" PRIx64 "  ???"
-                                                            : "%08" PRIx64 "  ???",
-                         it->pc);
+    if (frame.map_info == nullptr) {
+      os << StringPrintf("%08" PRIx64 "  ???", frame.pc);
     } else {
-      os << StringPrintf(Is64BitInstructionSet(kRuntimeISA) ? "%016" PRIx64 "  "
-                                                            : "%08" PRIx64 "  ",
-                         it->rel_pc);
-      if (it->map.name.empty()) {
-        os << StringPrintf("<anonymous:%" PRIx64 ">", it->map.start);
+      os << StringPrintf("%08" PRIx64 "  ", frame.rel_pc);
+      const std::shared_ptr<unwindstack::MapInfo>& map_info = frame.map_info;
+      if (map_info->name().empty()) {
+        os << StringPrintf("<anonymous:%" PRIx64 ">", map_info->start());
       } else {
-        os << it->map.name;
+        os << map_info->name().c_str();
       }
-      if (it->map.offset != 0) {
-        os << StringPrintf(" (offset %" PRIx64 ")", it->map.offset);
+      if (map_info->elf_start_offset() != 0) {
+        os << StringPrintf(" (offset %" PRIx64 ")", map_info->elf_start_offset());
       }
       os << " (";
-      if (!it->func_name.empty()) {
-        os << it->func_name;
-        if (it->func_offset != 0) {
-          os << "+" << it->func_offset;
+      if (!frame.function_name.empty()) {
+        // Remove parameters from the printed function name to improve signal/noise in the logs.
+        // Also, ANRs are often trimmed, so printing less means we get more useful data out.
+        // We can still symbolize the function based on the PC and build-id (including inlining).
+        os << StripParameters(frame.function_name.c_str());
+        if (frame.function_offset != 0) {
+          os << "+" << frame.function_offset;
         }
         // Functions found using the gdb jit interface will be in an empty
         // map that cannot be found using addr2line.
-        if (!it->map.name.empty()) {
+        if (!map_info->name().empty()) {
           try_addr2line = true;
         }
-      } else if (current_method != nullptr &&
-          Locks::mutator_lock_->IsSharedHeld(Thread::Current()) &&
-          PcIsWithinQuickCode(current_method, it->pc)) {
-        const void* start_of_code = current_method->GetEntryPointFromQuickCompiledCode();
-        os << current_method->JniLongName() << "+"
-           << (it->pc - reinterpret_cast<uint64_t>(start_of_code));
+      } else if (current_method != nullptr && holds_mutator_lock) {
+        const OatQuickMethodHeader* header = current_method->GetOatQuickMethodHeader(frame.pc);
+        if (header != nullptr) {
+          const void* start_of_code = header->GetCode();
+          os << current_method->JniLongName() << "+"
+             << (frame.pc - reinterpret_cast<uint64_t>(start_of_code));
+        } else {
+          os << "???";
+        }
       } else {
         os << "???";
       }
       os << ")";
-      std::string build_id = map->GetBuildId(it->pc);
+      std::string build_id = map_info->GetPrintableBuildID();
       if (!build_id.empty()) {
         os << " (BuildId: " << build_id << ")";
       }
     }
     os << std::endl;
     if (try_addr2line && use_addr2line) {
-      Addr2line(it->map.name, it->rel_pc, os, prefix, &addr2line_state);
+      // Guaranteed that map_info is not nullptr and name is non-empty.
+      Addr2line(frame.map_info->name(), frame.rel_pc, os, prefix, &addr2line_state);
     }
   }
 
@@ -426,7 +433,15 @@
 
 void DumpNativeStack(std::ostream& os ATTRIBUTE_UNUSED,
                      pid_t tid ATTRIBUTE_UNUSED,
-                     BacktraceMap* existing_map ATTRIBUTE_UNUSED,
+                     const char* prefix ATTRIBUTE_UNUSED,
+                     ArtMethod* current_method ATTRIBUTE_UNUSED,
+                     void* ucontext_ptr ATTRIBUTE_UNUSED,
+                     bool skip_frames ATTRIBUTE_UNUSED) {
+}
+
+void DumpNativeStack(std::ostream& os ATTRIBUTE_UNUSED,
+                     unwindstack::AndroidLocalUnwinder& existing_map ATTRIBUTE_UNUSED,
+                     pid_t tid ATTRIBUTE_UNUSED,
                      const char* prefix ATTRIBUTE_UNUSED,
                      ArtMethod* current_method ATTRIBUTE_UNUSED,
                      void* ucontext_ptr ATTRIBUTE_UNUSED,
diff --git a/runtime/native_stack_dump.h b/runtime/native_stack_dump.h
index 4d4b36b..86a8ce2 100644
--- a/runtime/native_stack_dump.h
+++ b/runtime/native_stack_dump.h
@@ -23,16 +23,30 @@
 
 #include "base/macros.h"
 
-class BacktraceMap;
+namespace unwindstack {
+class AndroidLocalUnwinder;
+}  // namespace unwindstack
 
 namespace art {
 
 class ArtMethod;
 
+// Remove method parameters by finding matching top-level parenthesis and removing them.
+// Since functions can be defined inside functions, this can remove multiple substrings.
+std::string StripParameters(std::string name);
+
 // Dumps the native stack for thread 'tid' to 'os'.
 void DumpNativeStack(std::ostream& os,
                      pid_t tid,
-                     BacktraceMap* map = nullptr,
+                     const char* prefix = "",
+                     ArtMethod* current_method = nullptr,
+                     void* ucontext = nullptr,
+                     bool skip_frames = true)
+    NO_THREAD_SAFETY_ANALYSIS;
+
+void DumpNativeStack(std::ostream& os,
+                     unwindstack::AndroidLocalUnwinder& unwinder,
+                     pid_t tid,
                      const char* prefix = "",
                      ArtMethod* current_method = nullptr,
                      void* ucontext = nullptr,
diff --git a/runtime/native_stack_dump_test.cc b/runtime/native_stack_dump_test.cc
new file mode 100644
index 0000000..4446495
--- /dev/null
+++ b/runtime/native_stack_dump_test.cc
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "native_stack_dump.h"
+
+#include <gtest/gtest.h>
+
+namespace art {
+
+TEST(StripParametersTest, ValidInput) {
+  EXPECT_EQ(StripParameters("foo(int)"), "foo");
+  EXPECT_EQ(StripParameters("foo(int, std::string)"), "foo");
+  EXPECT_EQ(StripParameters("foo(int) const"), "foo const");
+  EXPECT_EQ(StripParameters("foo(int)::bar(int)"), "foo::bar");
+  EXPECT_EQ(StripParameters("foo<int>(int)"), "foo<int>");
+}
+
+TEST(StripParametersTest, InvalidInput) {
+  EXPECT_EQ(StripParameters("foo(int?"), "foo(int?");
+  EXPECT_EQ(StripParameters("foo?int)"), "foo?int)");
+  EXPECT_EQ(StripParameters("(foo(int)"), "(foo");
+  EXPECT_EQ(StripParameters(")foo(int)"), ")foo");
+  EXPECT_EQ(StripParameters("foo(((int)))"), "foo");
+}
+
+}  // namespace art
diff --git a/runtime/non_debuggable_classes.cc b/runtime/non_debuggable_classes.cc
index 412ab0a..a35152f 100644
--- a/runtime/non_debuggable_classes.cc
+++ b/runtime/non_debuggable_classes.cc
@@ -22,6 +22,7 @@
 #include "nativehelper/scoped_local_ref.h"
 #include "obj_ptr-inl.h"
 #include "thread-current-inl.h"
+#include "thread-inl.h"
 
 namespace art {
 
diff --git a/runtime/noop_compiler_callbacks.h b/runtime/noop_compiler_callbacks.h
index f415831..aed0014 100644
--- a/runtime/noop_compiler_callbacks.h
+++ b/runtime/noop_compiler_callbacks.h
@@ -27,6 +27,7 @@
   ~NoopCompilerCallbacks() {}
 
   void AddUncompilableMethod(MethodReference ref ATTRIBUTE_UNUSED) override {}
+  void AddUncompilableClass(ClassReference ref ATTRIBUTE_UNUSED) override {}
   void ClassRejected(ClassReference ref ATTRIBUTE_UNUSED) override {}
 
   verifier::VerifierDeps* GetVerifierDeps() const override { return nullptr; }
diff --git a/runtime/nterp_helpers.cc b/runtime/nterp_helpers.cc
index 12afa3a..36135fd 100644
--- a/runtime/nterp_helpers.cc
+++ b/runtime/nterp_helpers.cc
@@ -112,6 +112,11 @@
       core_spills = arm64::Arm64CalleeSaveFrame::GetCoreSpills(CalleeSaveType::kSaveAllCalleeSaves);
       fp_spills = arm64::Arm64CalleeSaveFrame::GetFpSpills(CalleeSaveType::kSaveAllCalleeSaves);
       break;
+    case InstructionSet::kRiscv64:
+      core_spills =
+          riscv64::Riscv64CalleeSaveFrame::GetCoreSpills(CalleeSaveType::kSaveAllCalleeSaves);
+      fp_spills = riscv64::Riscv64CalleeSaveFrame::GetFpSpills(CalleeSaveType::kSaveAllCalleeSaves);
+      break;
     default:
       InstructionSetAbort(isa);
   }
@@ -120,9 +125,7 @@
       static_cast<size_t>(InstructionSetPointerSize(isa));
 }
 
-static uint16_t GetNumberOfOutRegs(ArtMethod* method, InstructionSet isa)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  CodeItemDataAccessor accessor(method->DexInstructionData());
+static uint16_t GetNumberOfOutRegs(const CodeItemDataAccessor& accessor, InstructionSet isa) {
   uint16_t out_regs = accessor.OutsSize();
   switch (isa) {
     case InstructionSet::kX86: {
@@ -136,14 +139,23 @@
   return out_regs;
 }
 
-size_t NterpGetFrameSize(ArtMethod* method, InstructionSet isa) {
+static uint16_t GetNumberOfOutRegs(ArtMethod* method, InstructionSet isa)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  CodeItemDataAccessor accessor(method->DexInstructionData());
+  return GetNumberOfOutRegs(accessor, isa);
+}
+
+// Note: There may be two pieces of alignment but there is no need to align
+// out args to `kPointerSize` separately before aligning to kStackAlignment.
+// This allows using the size without padding for the maximum frame size check
+// in `CanMethodUseNterp()`.
+static size_t NterpGetFrameSizeWithoutPadding(ArtMethod* method, InstructionSet isa)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
   CodeItemDataAccessor accessor(method->DexInstructionData());
   const uint16_t num_regs = accessor.RegistersSize();
-  const uint16_t out_regs = GetNumberOfOutRegs(method, isa);
+  const uint16_t out_regs = GetNumberOfOutRegs(accessor, isa);
   size_t pointer_size = static_cast<size_t>(InstructionSetPointerSize(isa));
 
-  // Note: There may be two pieces of alignment but there is no need to align
-  // out args to `kPointerSize` separately before aligning to kStackAlignment.
   DCHECK(IsAlignedParam(kStackAlignment, pointer_size));
   DCHECK(IsAlignedParam(NterpGetFrameEntrySize(isa), pointer_size));
   DCHECK(IsAlignedParam(kVRegSize * 2, pointer_size));
@@ -154,7 +166,13 @@
       pointer_size +  // saved dex pc
       (out_regs * kVRegSize) +  // out arguments
       pointer_size;  // method
-  return RoundUp(frame_size, kStackAlignment);
+  return frame_size;
+}
+
+// The frame size nterp will use for the given method.
+static inline size_t NterpGetFrameSize(ArtMethod* method, InstructionSet isa)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  return RoundUp(NterpGetFrameSizeWithoutPadding(method, isa), kStackAlignment);
 }
 
 QuickMethodFrameInfo NterpFrameInfo(ArtMethod** frame) {
@@ -162,7 +180,7 @@
       RuntimeCalleeSaveFrame::GetCoreSpills(CalleeSaveType::kSaveAllCalleeSaves);
   uint32_t fp_spills =
       RuntimeCalleeSaveFrame::GetFpSpills(CalleeSaveType::kSaveAllCalleeSaves);
-  return QuickMethodFrameInfo(NterpGetFrameSize(*frame), core_spills, fp_spills);
+  return QuickMethodFrameInfo(NterpGetFrameSize(*frame, kRuntimeISA), core_spills, fp_spills);
 }
 
 uintptr_t NterpGetRegistersArray(ArtMethod** frame) {
@@ -206,13 +224,20 @@
 }
 
 bool CanMethodUseNterp(ArtMethod* method, InstructionSet isa) {
-  return !method->IsNative() &&
-      method->IsInvokable() &&
-      !method->MustCountLocks() &&
+  uint32_t access_flags = method->GetAccessFlags();
+  if (ArtMethod::IsNative(access_flags) ||
+      !ArtMethod::IsInvokable(access_flags) ||
+      ArtMethod::MustCountLocks(access_flags) ||
       // Proxy methods do not go through the JIT like other methods, so we don't
       // run them with nterp.
-      !method->IsProxyMethod() &&
-      NterpGetFrameSize(method, isa) <= interpreter::kNterpMaxFrame;
+      method->IsProxyMethod()) {
+    return false;
+  }
+  // There is no need to add the alignment padding size for comparison with aligned limit.
+  size_t frame_size_without_padding = NterpGetFrameSizeWithoutPadding(method, isa);
+  DCHECK_EQ(NterpGetFrameSize(method, isa), RoundUp(frame_size_without_padding, kStackAlignment));
+  static_assert(IsAligned<kStackAlignment>(interpreter::kNterpMaxFrame));
+  return frame_size_without_padding <= interpreter::kNterpMaxFrame;
 }
 
 }  // namespace art
diff --git a/runtime/nterp_helpers.h b/runtime/nterp_helpers.h
index 0f9e758..236059b 100644
--- a/runtime/nterp_helpers.h
+++ b/runtime/nterp_helpers.h
@@ -24,12 +24,6 @@
 class ArtMethod;
 
 /**
- * The frame size nterp will use for the given method.
- */
-size_t NterpGetFrameSize(ArtMethod* method, InstructionSet isa = kRuntimeISA)
-    REQUIRES_SHARED(Locks::mutator_lock_);
-
-/**
  * Returns the QuickMethodFrameInfo of the given frame corresponding to the
  * given method.
  */
diff --git a/runtime/oat.cc b/runtime/oat.cc
index 64ed14a..2c7a73f 100644
--- a/runtime/oat.cc
+++ b/runtime/oat.cc
@@ -29,9 +29,6 @@
 
 using android::base::StringPrintf;
 
-constexpr const char OatHeader::kTrueValue[];
-constexpr const char OatHeader::kFalseValue[];
-
 static size_t ComputeOatHeaderSize(const SafeMap<std::string, std::string>* variable_data) {
   size_t estimate = 0U;
   if (variable_data != nullptr) {
@@ -470,4 +467,27 @@
   key_value_store_size_ = data_ptr - reinterpret_cast<char*>(&key_value_store_);
 }
 
+const uint8_t* OatHeader::GetOatAddress(StubType type) const {
+  DCHECK_LE(type, StubType::kLast);
+  switch (type) {
+    // TODO: We could maybe clean this up if we stored them in an array in the oat header.
+    case StubType::kQuickGenericJNITrampoline:
+      return static_cast<const uint8_t*>(GetQuickGenericJniTrampoline());
+    case StubType::kJNIDlsymLookupTrampoline:
+      return static_cast<const uint8_t*>(GetJniDlsymLookupTrampoline());
+    case StubType::kJNIDlsymLookupCriticalTrampoline:
+      return static_cast<const uint8_t*>(GetJniDlsymLookupCriticalTrampoline());
+    case StubType::kQuickIMTConflictTrampoline:
+      return static_cast<const uint8_t*>(GetQuickImtConflictTrampoline());
+    case StubType::kQuickResolutionTrampoline:
+      return static_cast<const uint8_t*>(GetQuickResolutionTrampoline());
+    case StubType::kQuickToInterpreterBridge:
+      return static_cast<const uint8_t*>(GetQuickToInterpreterBridge());
+    case StubType::kNterpTrampoline:
+      return static_cast<const uint8_t*>(GetNterpTrampoline());
+    default:
+      UNREACHABLE();
+  }
+}
+
 }  // namespace art
diff --git a/runtime/oat.h b/runtime/oat.h
index 462d41c..e062baa 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -29,11 +29,23 @@
 enum class InstructionSet;
 class InstructionSetFeatures;
 
+enum class StubType {
+  kJNIDlsymLookupTrampoline,
+  kJNIDlsymLookupCriticalTrampoline,
+  kQuickGenericJNITrampoline,
+  kQuickIMTConflictTrampoline,
+  kQuickResolutionTrampoline,
+  kQuickToInterpreterBridge,
+  kNterpTrampoline,
+  kLast = kNterpTrampoline,
+};
+std::ostream& operator<<(std::ostream& stream, StubType stub_type);
+
 class PACKED(4) OatHeader {
  public:
   static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } };
-  // Last oat version changed reason: Revert^4 "bss support for inlining BCP into non-BCP".
-  static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '2', '5', '\0' } };
+  // Last oat version changed reason: ARM64: Enable implicit suspend checks; compiled code check.
+  static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '3', '0', '\0' } };
 
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
   static constexpr const char* kDebuggableKey = "debuggable";
@@ -111,6 +123,8 @@
   bool IsConcurrentCopying() const;
   bool RequiresImage() const;
 
+  const uint8_t* GetOatAddress(StubType type) const;
+
  private:
   bool KeyHasValue(const char* key, const char* value, size_t value_size) const;
 
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 63778c7..558441a 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -50,6 +50,7 @@
 #include "base/systrace.h"
 #include "base/unix_file/fd_file.h"
 #include "base/utils.h"
+#include "class_loader_context.h"
 #include "dex/art_dex_file_loader.h"
 #include "dex/dex_file.h"
 #include "dex/dex_file_loader.h"
@@ -282,7 +283,7 @@
                            std::string* error_msg) {
   vdex_ = VdexFile::OpenAtAddress(vdex_begin_,
                                   vdex_end_ - vdex_begin_,
-                                  /*mmap_reuse=*/ vdex_begin_ != nullptr,
+                                  /*mmap_reuse=*/vdex_begin_ != nullptr,
                                   vdex_filename,
                                   writable,
                                   low_4gb,
@@ -307,16 +308,15 @@
     if (rc == -1) {
       PLOG(WARNING) << "Failed getting length of vdex file";
     } else {
-      vdex_ = VdexFile::OpenAtAddress(
-          vdex_begin_,
-          vdex_end_ - vdex_begin_,
-          /*mmap_reuse=*/ vdex_begin_ != nullptr,
-          vdex_fd,
-          s.st_size,
-          vdex_filename,
-          writable,
-          low_4gb,
-          error_msg);
+      vdex_ = VdexFile::OpenAtAddress(vdex_begin_,
+                                      vdex_end_ - vdex_begin_,
+                                      /*mmap_reuse=*/vdex_begin_ != nullptr,
+                                      vdex_fd,
+                                      s.st_size,
+                                      vdex_filename,
+                                      writable,
+                                      low_4gb,
+                                      error_msg);
       if (vdex_.get() == nullptr) {
         *error_msg = "Failed opening vdex file.";
         return false;
@@ -445,7 +445,7 @@
           UNLIKELY(oat_file->Size() - index_bss_mapping_offset <
                    IndexBssMapping::ComputeSize(index_bss_mapping->size())))) {
     *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with unaligned or "
-                                  " truncated %s bss mapping, offset %u of %zu, length %zu",
+                                  "truncated %s bss mapping, offset %u of %zu, length %zu",
                               oat_file->GetLocation().c_str(),
                               dex_file_index,
                               dex_file_location.c_str(),
@@ -465,15 +465,25 @@
                                                const VdexFile* vdex_file,
                                                const uint8_t** type_lookup_table_data,
                                                std::string* error_msg) {
-  if (type_lookup_table_start == nullptr ||
-      reinterpret_cast<const uint32_t*>(type_lookup_table_start)[0] == 0) {
+  if (type_lookup_table_start == nullptr) {
     *type_lookup_table_data = nullptr;
     return true;
   }
 
-  *type_lookup_table_data = type_lookup_table_start + sizeof(uint32_t);
-  size_t expected_table_size = TypeLookupTable::RawDataLength(header.class_defs_size_);
+  if (UNLIKELY(!vdex_file->Contains(type_lookup_table_start, sizeof(uint32_t)))) {
+    *error_msg =
+        StringPrintf("In vdex file '%s' found invalid type lookup table start %p of size %zu "
+                         "not in [%p, %p]",
+                     vdex_file->GetName().c_str(),
+                     type_lookup_table_start,
+                     sizeof(uint32_t),
+                     vdex_file->Begin(),
+                     vdex_file->End());
+    return false;
+  }
+
   size_t found_size = reinterpret_cast<const uint32_t*>(type_lookup_table_start)[0];
+  size_t expected_table_size = TypeLookupTable::RawDataLength(header.class_defs_size_);
   if (UNLIKELY(found_size != expected_table_size)) {
     *error_msg =
         StringPrintf("In vdex file '%s' unexpected type lookup table size: found %zu, expected %zu",
@@ -482,20 +492,20 @@
                      expected_table_size);
     return false;
   }
-  if (UNLIKELY(!vdex_file->Contains(*type_lookup_table_data))) {
+
+  if (found_size == 0) {
+    *type_lookup_table_data = nullptr;
+    return true;
+  }
+
+  *type_lookup_table_data = type_lookup_table_start + sizeof(uint32_t);
+  if (UNLIKELY(!vdex_file->Contains(*type_lookup_table_data, found_size))) {
     *error_msg =
-        StringPrintf("In vdex file '%s' found invalid type lookup table pointer %p not in [%p, %p]",
+        StringPrintf("In vdex file '%s' found invalid type lookup table data %p of size %zu "
+                         "not in [%p, %p]",
                      vdex_file->GetName().c_str(),
                      type_lookup_table_data,
-                     vdex_file->Begin(),
-                     vdex_file->End());
-    return false;
-  }
-  if (UNLIKELY(!vdex_file->Contains(*type_lookup_table_data + expected_table_size - 1))) {
-    *error_msg =
-        StringPrintf("In vdex file '%s' found overflowing type lookup table %p not in [%p, %p]",
-                     vdex_file->GetName().c_str(),
-                     type_lookup_table_data + expected_table_size,
+                     found_size,
                      vdex_file->Begin(),
                      vdex_file->End());
     return false;
@@ -514,6 +524,16 @@
   uint32_t i = 0;
   const uint8_t* type_lookup_table_start = nullptr;
   for (const DexFile* dex_file : dex_files) {
+    // Defensively verify external dex file checksum. `OatFileAssistant`
+    // expects this check to happen during oat file setup when the oat file
+    // does not contain dex code.
+    if (dex_file->GetLocationChecksum() != vdex_->GetLocationChecksum(i)) {
+      *error_msg = StringPrintf("Dex checksum does not match for %s, dex has %d, vdex has %d",
+                                dex_file->GetLocation().c_str(),
+                                dex_file->GetLocationChecksum(),
+                                vdex_->GetLocationChecksum(i));
+      return false;
+    }
     std::string dex_location = dex_file->GetLocation();
     std::string canonical_location = DexFileLoader::GetDexCanonicalLocation(dex_location.c_str());
 
@@ -767,29 +787,25 @@
       if (i == external_dex_files_.size()) {
         std::vector<std::unique_ptr<const DexFile>> new_dex_files;
         // No dex files, load it from location.
-        const ArtDexFileLoader dex_file_loader;
         bool loaded = false;
         CHECK(zip_fd == -1 || dex_fds.empty());  // Allow only the supported combinations.
         if (zip_fd != -1) {
-          loaded = dex_file_loader.OpenZip(zip_fd,
-                                           dex_file_location,
-                                           /*verify=*/ false,
-                                           /*verify_checksum=*/ false,
-                                           error_msg,
-                                           &new_dex_files);
+          ArtDexFileLoader dex_file_loader(zip_fd, dex_file_location);
+          loaded = dex_file_loader.Open(/*verify=*/false,
+                                        /*verify_checksum=*/false,
+                                        error_msg,
+                                        &new_dex_files);
         } else if (dex_fd != -1) {
           // Note that we assume dex_fds are backing by jars.
-          loaded = dex_file_loader.OpenZipFromOwnedFd(dex_fd,
-                                                      dex_file_location,
-                                                      /*verify=*/ false,
-                                                      /*verify_checksum=*/ false,
-                                                      error_msg,
-                                                      &new_dex_files);
+          ArtDexFileLoader dex_file_loader(DupCloexec(dex_fd), dex_file_location);
+          loaded = dex_file_loader.Open(/*verify=*/false,
+                                        /*verify_checksum=*/false,
+                                        error_msg,
+                                        &new_dex_files);
         } else {
-          loaded = dex_file_loader.Open(dex_file_name.c_str(),
-                                        dex_file_location,
-                                        /*verify=*/ false,
-                                        /*verify_checksum=*/ false,
+          ArtDexFileLoader dex_file_loader(dex_file_name.c_str(), dex_file_location);
+          loaded = dex_file_loader.Open(/*verify=*/false,
+                                        /*verify_checksum=*/false,
                                         error_msg,
                                         &new_dex_files);
         }
@@ -828,7 +844,9 @@
           external_dex_files_.push_back(std::move(dex_file));
         }
       }
-      // Defensively verify external dex file checksum.
+      // Defensively verify external dex file checksum. `OatFileAssistant`
+      // expects this check to happen during oat file setup when the oat file
+      // does not contain dex code.
       if (dex_file_checksum != external_dex_files_[i]->GetLocationChecksum()) {
         *error_msg = StringPrintf("In oat file '%s', dex file checksum 0x%08x does not match"
                                       " checksum 0x%08x of external dex file '%s'",
@@ -1672,7 +1690,7 @@
   ScopedTrace trace(__PRETTY_FUNCTION__);
   elf_file_.reset(ElfFile::Open(file,
                                 writable,
-                                /*program_header_only=*/ true,
+                                /*program_header_only=*/true,
                                 low_4gb,
                                 error_msg));
   if (elf_file_ == nullptr) {
@@ -1687,15 +1705,16 @@
 class OatFileBackedByVdex final : public OatFileBase {
  public:
   explicit OatFileBackedByVdex(const std::string& filename)
-      : OatFileBase(filename, /*executable=*/ false) {}
+      : OatFileBase(filename, /*executable=*/false) {}
 
   static OatFileBackedByVdex* Open(const std::vector<const DexFile*>& dex_files,
                                    std::unique_ptr<VdexFile>&& vdex_file,
-                                   const std::string& location) {
+                                   const std::string& location,
+                                   ClassLoaderContext* context) {
     std::unique_ptr<OatFileBackedByVdex> oat_file(new OatFileBackedByVdex(location));
     // SetVdex will take ownership of the VdexFile.
     oat_file->SetVdex(vdex_file.release());
-    oat_file->SetupHeader(dex_files.size());
+    oat_file->SetupHeader(dex_files.size(), context);
     // Initialize OatDexFiles.
     std::string error_msg;
     if (!oat_file->Setup(dex_files, &error_msg)) {
@@ -1708,6 +1727,7 @@
   static OatFileBackedByVdex* Open(int zip_fd,
                                    std::unique_ptr<VdexFile>&& unique_vdex_file,
                                    const std::string& dex_location,
+                                   ClassLoaderContext* context,
                                    std::string* error_msg) {
     VdexFile* vdex_file = unique_vdex_file.get();
     std::unique_ptr<OatFileBackedByVdex> oat_file(new OatFileBackedByVdex(vdex_file->GetName()));
@@ -1719,21 +1739,25 @@
       for (const uint8_t* dex_file_start = vdex_file->GetNextDexFileData(nullptr, i);
            dex_file_start != nullptr;
            dex_file_start = vdex_file->GetNextDexFileData(dex_file_start, ++i)) {
-        const DexFile::Header* header = reinterpret_cast<const DexFile::Header*>(dex_file_start);
-        if (UNLIKELY(!vdex_file->Contains(dex_file_start))) {
+        if (UNLIKELY(!vdex_file->Contains(dex_file_start, sizeof(DexFile::Header)))) {
           *error_msg =
-              StringPrintf("In vdex file '%s' found invalid dex file pointer %p not in [%p, %p]",
+              StringPrintf("In vdex file '%s' found invalid dex header %p of size %zu "
+                               "not in [%p, %p]",
                            dex_location.c_str(),
                            dex_file_start,
+                           sizeof(DexFile::Header),
                            vdex_file->Begin(),
                            vdex_file->End());
           return nullptr;
         }
-        if (UNLIKELY(!vdex_file->Contains(dex_file_start + header->file_size_ - 1))) {
+        const DexFile::Header* header = reinterpret_cast<const DexFile::Header*>(dex_file_start);
+        if (UNLIKELY(!vdex_file->Contains(dex_file_start, header->file_size_))) {
           *error_msg =
-              StringPrintf("In vdex file '%s' found overflowing dex file %p not in [%p, %p]",
+              StringPrintf("In vdex file '%s' found invalid dex file pointer %p of size %d "
+                               "not in [%p, %p]",
                            dex_location.c_str(),
-                           dex_file_start + header->file_size_,
+                           dex_file_start,
+                           header->file_size_,
                            vdex_file->Begin(),
                            vdex_file->End());
           return nullptr;
@@ -1772,31 +1796,28 @@
           oat_file->oat_dex_files_.Put(canonical_key, oat_dex_file);
         }
       }
-      oat_file->SetupHeader(oat_file->oat_dex_files_storage_.size());
+      oat_file->SetupHeader(oat_file->oat_dex_files_storage_.size(), context);
     } else {
       // No need for any verification when loading dex files as we already have
       // a vdex file.
-      const ArtDexFileLoader dex_file_loader;
       bool loaded = false;
       if (zip_fd != -1) {
-        loaded = dex_file_loader.OpenZip(zip_fd,
-                                         dex_location,
-                                         /*verify=*/ false,
-                                         /*verify_checksum=*/ false,
-                                         error_msg,
-                                         &oat_file->external_dex_files_);
+        ArtDexFileLoader dex_file_loader(zip_fd, dex_location);
+        loaded = dex_file_loader.Open(/*verify=*/false,
+                                      /*verify_checksum=*/false,
+                                      error_msg,
+                                      &oat_file->external_dex_files_);
       } else {
-        loaded = dex_file_loader.Open(dex_location.c_str(),
-                                      dex_location,
-                                      /*verify=*/ false,
-                                      /*verify_checksum=*/ false,
+        ArtDexFileLoader dex_file_loader(dex_location);
+        loaded = dex_file_loader.Open(/*verify=*/false,
+                                      /*verify_checksum=*/false,
                                       error_msg,
                                       &oat_file->external_dex_files_);
       }
       if (!loaded) {
         return nullptr;
       }
-      oat_file->SetupHeader(oat_file->external_dex_files_.size());
+      oat_file->SetupHeader(oat_file->external_dex_files_.size(), context);
       if (!oat_file->Setup(MakeNonOwningPointerVector(oat_file->external_dex_files_), error_msg)) {
         return nullptr;
       }
@@ -1805,7 +1826,7 @@
     return oat_file.release();
   }
 
-  void SetupHeader(size_t number_of_dex_files) {
+  void SetupHeader(size_t number_of_dex_files, ClassLoaderContext* context) {
     DCHECK(!IsExecutable());
 
     // Create a fake OatHeader with a key store to help debugging.
@@ -1813,9 +1834,13 @@
         InstructionSetFeatures::FromCppDefines();
     SafeMap<std::string, std::string> store;
     store.Put(OatHeader::kCompilerFilter, CompilerFilter::NameOfFilter(CompilerFilter::kVerify));
-    store.Put(OatHeader::kCompilationReasonKey, "vdex");
+    store.Put(OatHeader::kCompilationReasonKey, kReasonVdex);
     store.Put(OatHeader::kConcurrentCopying,
-              kUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);
+              gUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);
+    if (context != nullptr) {
+      store.Put(OatHeader::kClassPathKey, context->EncodeContextForOatFile(""));
+    }
+
     oat_header_.reset(OatHeader::Create(kRuntimeISA,
                                         isa_features.get(),
                                         number_of_dex_files,
@@ -1899,7 +1924,7 @@
                                                                  vdex_filename,
                                                                  oat_filename,
                                                                  oat_location,
-                                                                 /*writable=*/ false,
+                                                                 /*writable=*/false,
                                                                  executable,
                                                                  low_4gb,
                                                                  dex_filenames,
@@ -1907,17 +1932,6 @@
                                                                  reservation,
                                                                  error_msg);
   if (with_dlopen != nullptr) {
-    Runtime* runtime = Runtime::Current();
-    // The runtime might not be available at this point if we're running
-    // dex2oat or oatdump.
-    if (runtime != nullptr) {
-      size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeOdex();
-      Runtime::MadviseFileForRange(madvise_size_limit,
-                                   with_dlopen->Size(),
-                                   with_dlopen->Begin(),
-                                   with_dlopen->End(),
-                                   oat_location);
-    }
     return with_dlopen;
   }
   if (kPrintDlOpenErrorMessage) {
@@ -1940,7 +1954,7 @@
                                                                 vdex_filename,
                                                                 oat_filename,
                                                                 oat_location,
-                                                                /*writable=*/ false,
+                                                                /*writable=*/false,
                                                                 executable,
                                                                 low_4gb,
                                                                 dex_filenames,
@@ -1969,7 +1983,7 @@
                                                                 oat_fd,
                                                                 vdex_location,
                                                                 oat_location,
-                                                                /*writable=*/ false,
+                                                                /*writable=*/false,
                                                                 executable,
                                                                 low_4gb,
                                                                 dex_filenames,
@@ -1981,17 +1995,19 @@
 
 OatFile* OatFile::OpenFromVdex(const std::vector<const DexFile*>& dex_files,
                                std::unique_ptr<VdexFile>&& vdex_file,
-                               const std::string& location) {
+                               const std::string& location,
+                               ClassLoaderContext* context) {
   CheckLocation(location);
-  return OatFileBackedByVdex::Open(dex_files, std::move(vdex_file), location);
+  return OatFileBackedByVdex::Open(dex_files, std::move(vdex_file), location, context);
 }
 
 OatFile* OatFile::OpenFromVdex(int zip_fd,
                                std::unique_ptr<VdexFile>&& vdex_file,
                                const std::string& location,
+                               ClassLoaderContext* context,
                                std::string* error_msg) {
   CheckLocation(location);
-  return OatFileBackedByVdex::Open(zip_fd, std::move(vdex_file), location, error_msg);
+  return OatFileBackedByVdex::Open(zip_fd, std::move(vdex_file), location, context, error_msg);
 }
 
 OatFile::OatFile(const std::string& location, bool is_executable)
@@ -2224,15 +2240,9 @@
   ScopedTrace trace(__PRETTY_FUNCTION__);
   static constexpr bool kVerify = false;
   static constexpr bool kVerifyChecksum = false;
-  const ArtDexFileLoader dex_file_loader;
-  return dex_file_loader.Open(dex_file_pointer_,
-                              FileSize(),
-                              dex_file_location_,
-                              dex_file_location_checksum_,
-                              this,
-                              kVerify,
-                              kVerifyChecksum,
-                              error_msg);
+  ArtDexFileLoader dex_file_loader(dex_file_pointer_, FileSize(), dex_file_location_);
+  return dex_file_loader.Open(
+      dex_file_location_checksum_, this, kVerify, kVerifyChecksum, error_msg);
 }
 
 uint32_t OatDexFile::GetOatClassOffset(uint16_t class_def_index) const {
@@ -2252,7 +2262,7 @@
     return OatFile::OatClass(oat_file_,
                              ClassStatus::kNotReady,
                              /* type= */ OatClassType::kNoneCompiled,
-                             /* bitmap_size= */ 0u,
+                             /* num_methods= */ 0u,
                              /* bitmap_pointer= */ nullptr,
                              /* methods_pointer= */ nullptr);
   }
@@ -2341,34 +2351,6 @@
   return nullptr;
 }
 
-// Madvise the dex file based on the state we are moving to.
-void OatDexFile::MadviseDexFileAtLoad(const DexFile& dex_file) {
-  Runtime* const runtime = Runtime::Current();
-  const bool low_ram = runtime->GetHeap()->IsLowMemoryMode();
-  // TODO(b/196052575): Revisit low-ram madvise behavior in light of vdex/odex/art madvise hints.
-  if (!low_ram) {
-    return;
-  }
-  if (runtime->MAdviseRandomAccess()) {
-    // Default every dex file to MADV_RANDOM when its loaded by default for low ram devices.
-    // Other devices have enough page cache to get performance benefits from loading more pages
-    // into the page cache.
-    DexLayoutSection::MadviseLargestPageAlignedRegion(dex_file.Begin(),
-                                                      dex_file.Begin() + dex_file.Size(),
-                                                      MADV_RANDOM);
-  }
-  const OatDexFile* oat_dex_file = dex_file.GetOatDexFile();
-  if (oat_dex_file != nullptr) {
-    // Should always be there.
-    const DexLayoutSections* const sections = oat_dex_file->GetDexLayoutSections();
-    if (sections != nullptr) {
-      sections->MadviseAtLoad(&dex_file);
-    } else {
-      DCHECK(oat_dex_file->IsBackedByVdexOnly());
-    }
-  }
-}
-
 OatFile::OatClass::OatClass(const OatFile* oat_file,
                             ClassStatus status,
                             OatClassType type,
@@ -2448,7 +2430,8 @@
 }
 
 std::string OatFile::GetClassLoaderContext() const {
-  return GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey);
+  const char* value = GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey);
+  return (value == nullptr) ? "" : value;
 }
 
 const char* OatFile::GetCompilationReason() const {
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index c1b1acb..b7fa867 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -39,6 +39,7 @@
 
 class BitVector;
 class DexFile;
+class ClassLoaderContext;
 class ElfFile;
 class DexLayoutSections;
 template <class MirrorType> class GcRoot;
@@ -59,6 +60,10 @@
 }  // namespace collector
 }  // namespace gc
 
+// A special compilation reason to indicate that only the VDEX file is usable. Keep in sync with
+// `ArtConstants::REASON_VDEX` in artd/binder/com/android/server/art/ArtConstants.aidl.
+static constexpr const char* kReasonVdex = "vdex";
+
 // OatMethodOffsets are currently 5x32-bits=160-bits long, so if we can
 // save even one OatMethodOffsets struct, the more complicated encoding
 // using a bitmap pays for itself since few classes will have 160
@@ -162,13 +167,15 @@
   // the vdex does not have a dex section and accepts a vector of DexFiles separately.
   static OatFile* OpenFromVdex(const std::vector<const DexFile*>& dex_files,
                                std::unique_ptr<VdexFile>&& vdex_file,
-                               const std::string& location);
+                               const std::string& location,
+                               ClassLoaderContext* context);
 
   // Initialize OatFile instance from an already loaded VdexFile. The dex files
   // will be opened through `zip_fd` or `dex_location` if `zip_fd` is -1.
   static OatFile* OpenFromVdex(int zip_fd,
                                std::unique_ptr<VdexFile>&& vdex_file,
                                const std::string& location,
+                               ClassLoaderContext* context,
                                std::string* error_msg);
 
   // Return whether the `OatFile` uses a vdex-only file.
@@ -270,7 +277,7 @@
       return OatClass(/* oat_file= */ nullptr,
                       ClassStatus::kErrorUnresolved,
                       OatClassType::kNoneCompiled,
-                      /* bitmap_size= */ 0,
+                      /* num_methods= */ 0,
                       /* bitmap_pointer= */ nullptr,
                       /* methods_pointer= */ nullptr);
     }
@@ -561,9 +568,6 @@
                                            const char* descriptor,
                                            size_t hash);
 
-  // Madvise the dex file for load-time usage.
-  static void MadviseDexFileAtLoad(const DexFile& dex_file);
-
   const TypeLookupTable& GetTypeLookupTable() const {
     return lookup_table_;
   }
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 914d2dd..c735afa 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -16,15 +16,20 @@
 
 #include "oat_file_assistant.h"
 
-#include <sstream>
-
 #include <sys/stat.h>
-#include "zlib.h"
+
+#include <memory>
+#include <optional>
+#include <sstream>
+#include <vector>
 
 #include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/properties.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
-
+#include "arch/instruction_set.h"
+#include "base/array_ref.h"
 #include "base/compiler_filter.h"
 #include "base/file_utils.h"
 #include "base/logging.h"  // For VLOG.
@@ -40,23 +45,29 @@
 #include "dex/art_dex_file_loader.h"
 #include "dex/dex_file_loader.h"
 #include "exec_utils.h"
+#include "fmt/format.h"
 #include "gc/heap.h"
 #include "gc/space/image_space.h"
 #include "image.h"
 #include "oat.h"
+#include "oat_file_assistant_context.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "vdex_file.h"
+#include "zlib.h"
 
 namespace art {
 
-using android::base::StringPrintf;
+using ::android::base::ConsumePrefix;
+using ::android::base::StringPrintf;
+
+using ::fmt::literals::operator""_format;  // NOLINT
 
 static constexpr const char* kAnonymousDexPrefix = "Anonymous-DexFile@";
 static constexpr const char* kVdexExtension = ".vdex";
 static constexpr const char* kDmExtension = ".dm";
 
-std::ostream& operator << (std::ostream& stream, const OatFileAssistant::OatStatus status) {
+std::ostream& operator<<(std::ostream& stream, const OatFileAssistant::OatStatus status) {
   switch (status) {
     case OatFileAssistant::kOatCannotOpen:
       stream << "kOatCannotOpen";
@@ -82,22 +93,24 @@
                                    const InstructionSet isa,
                                    ClassLoaderContext* context,
                                    bool load_executable,
-                                   bool only_load_trusted_executable)
+                                   bool only_load_trusted_executable,
+                                   OatFileAssistantContext* ofa_context)
     : OatFileAssistant(dex_location,
                        isa,
                        context,
                        load_executable,
                        only_load_trusted_executable,
-                       /*vdex_fd=*/ -1,
-                       /*oat_fd=*/ -1,
-                       /*zip_fd=*/ -1) {}
-
+                       ofa_context,
+                       /*vdex_fd=*/-1,
+                       /*oat_fd=*/-1,
+                       /*zip_fd=*/-1) {}
 
 OatFileAssistant::OatFileAssistant(const char* dex_location,
                                    const InstructionSet isa,
                                    ClassLoaderContext* context,
                                    bool load_executable,
                                    bool only_load_trusted_executable,
+                                   OatFileAssistantContext* ofa_context,
                                    int vdex_fd,
                                    int oat_fd,
                                    int zip_fd)
@@ -105,21 +118,21 @@
       isa_(isa),
       load_executable_(load_executable),
       only_load_trusted_executable_(only_load_trusted_executable),
-      odex_(this, /*is_oat_location=*/ false),
-      oat_(this, /*is_oat_location=*/ true),
-      vdex_for_odex_(this, /*is_oat_location=*/ false),
-      vdex_for_oat_(this, /*is_oat_location=*/ true),
-      dm_for_odex_(this, /*is_oat_location=*/ false),
-      dm_for_oat_(this, /*is_oat_location=*/ true),
+      odex_(this, /*is_oat_location=*/false),
+      oat_(this, /*is_oat_location=*/true),
+      vdex_for_odex_(this, /*is_oat_location=*/false),
+      vdex_for_oat_(this, /*is_oat_location=*/true),
+      dm_for_odex_(this, /*is_oat_location=*/false),
+      dm_for_oat_(this, /*is_oat_location=*/true),
       zip_fd_(zip_fd) {
   CHECK(dex_location != nullptr) << "OatFileAssistant: null dex location";
   CHECK_IMPLIES(load_executable, context != nullptr) << "Loading executable without a context";
 
   if (zip_fd < 0) {
     CHECK_LE(oat_fd, 0) << "zip_fd must be provided with valid oat_fd. zip_fd=" << zip_fd
-      << " oat_fd=" << oat_fd;
+                        << " oat_fd=" << oat_fd;
     CHECK_LE(vdex_fd, 0) << "zip_fd must be provided with valid vdex_fd. zip_fd=" << zip_fd
-      << " vdex_fd=" << vdex_fd;;
+                         << " vdex_fd=" << vdex_fd;
     CHECK(!UseFdToReadFiles());
   } else {
     CHECK(UseFdToReadFiles());
@@ -127,12 +140,33 @@
 
   dex_location_.assign(dex_location);
 
+  Runtime* runtime = Runtime::Current();
+
+  if (load_executable_ && runtime == nullptr) {
+    LOG(WARNING) << "OatFileAssistant: Load executable specified, "
+                 << "but no active runtime is found. Will not attempt to load executable.";
+    load_executable_ = false;
+  }
+
   if (load_executable_ && isa != kRuntimeISA) {
     LOG(WARNING) << "OatFileAssistant: Load executable specified, "
-      << "but isa is not kRuntimeISA. Will not attempt to load executable.";
+                 << "but isa is not kRuntimeISA. Will not attempt to load executable.";
     load_executable_ = false;
   }
 
+  if (ofa_context == nullptr) {
+    CHECK(runtime != nullptr) << "runtime_options is not provided, and no active runtime is found.";
+    ofa_context_ = std::make_unique<OatFileAssistantContext>(runtime);
+  } else {
+    ofa_context_ = ofa_context;
+  }
+
+  if (runtime == nullptr) {
+    // We need `MemMap` for mapping files. We don't have to initialize it when there is a runtime
+    // because the runtime initializes it.
+    MemMap::Init();
+  }
+
   // Get the odex filename.
   std::string error_msg;
   std::string odex_file_name;
@@ -159,15 +193,19 @@
   if (!UseFdToReadFiles()) {
     // Get the oat filename.
     std::string oat_file_name;
-    if (DexLocationToOatFilename(dex_location_, isa_, &oat_file_name, &error_msg)) {
-      oat_.Reset(oat_file_name, /*use_fd=*/ false);
+    if (DexLocationToOatFilename(dex_location_,
+                                 isa_,
+                                 GetRuntimeOptions().deny_art_apex_data_files,
+                                 &oat_file_name,
+                                 &error_msg)) {
+      oat_.Reset(oat_file_name, /*use_fd=*/false);
       std::string vdex_file_name = GetVdexFilename(oat_file_name);
       vdex_for_oat_.Reset(vdex_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd);
       std::string dm_file_name = GetDmFilename(dex_location);
       dm_for_oat_.Reset(dm_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd);
     } else {
-      LOG(WARNING) << "Failed to determine oat file name for dex location "
-                   << dex_location_ << ": " << error_msg;
+      LOG(WARNING) << "Failed to determine oat file name for dex location " << dex_location_ << ": "
+                   << error_msg;
     }
   }
 
@@ -190,20 +228,61 @@
   }
 }
 
-bool OatFileAssistant::UseFdToReadFiles() {
-  return zip_fd_ >= 0;
+std::unique_ptr<OatFileAssistant> OatFileAssistant::Create(
+    const std::string& filename,
+    const std::string& isa_str,
+    const std::optional<std::string>& context_str,
+    bool load_executable,
+    bool only_load_trusted_executable,
+    OatFileAssistantContext* ofa_context,
+    /*out*/ std::unique_ptr<ClassLoaderContext>* context,
+    /*out*/ std::string* error_msg) {
+  InstructionSet isa = GetInstructionSetFromString(isa_str.c_str());
+  if (isa == InstructionSet::kNone) {
+    *error_msg = StringPrintf("Instruction set '%s' is invalid", isa_str.c_str());
+    return nullptr;
+  }
+
+  std::unique_ptr<ClassLoaderContext> tmp_context = nullptr;
+  if (context_str.has_value()) {
+    tmp_context = ClassLoaderContext::Create(context_str.value());
+    if (tmp_context == nullptr) {
+      *error_msg = StringPrintf("Class loader context '%s' is invalid", context_str->c_str());
+      return nullptr;
+    }
+
+    if (!tmp_context->OpenDexFiles(android::base::Dirname(filename),
+                                   /*context_fds=*/{},
+                                   /*only_read_checksums=*/true)) {
+      *error_msg =
+          StringPrintf("Failed to load class loader context files for '%s' with context '%s'",
+                       filename.c_str(),
+                       context_str->c_str());
+      return nullptr;
+    }
+  }
+
+  auto assistant = std::make_unique<OatFileAssistant>(filename.c_str(),
+                                                      isa,
+                                                      tmp_context.get(),
+                                                      load_executable,
+                                                      only_load_trusted_executable,
+                                                      ofa_context);
+
+  *context = std::move(tmp_context);
+  return assistant;
 }
 
+bool OatFileAssistant::UseFdToReadFiles() { return zip_fd_ >= 0; }
+
 bool OatFileAssistant::IsInBootClassPath() {
   // Note: We check the current boot class path, regardless of the ISA
   // specified by the user. This is okay, because the boot class path should
   // be the same for all ISAs.
   // TODO: Can we verify the boot class path is the same for all ISAs?
-  Runtime* runtime = Runtime::Current();
-  ClassLinker* class_linker = runtime->GetClassLinker();
-  const auto& boot_class_path = class_linker->GetBootClassPath();
-  for (size_t i = 0; i < boot_class_path.size(); i++) {
-    if (boot_class_path[i]->GetLocation() == dex_location_) {
+  for (const std::string& boot_class_path_location :
+       GetRuntimeOptions().boot_class_path_locations) {
+    if (boot_class_path_location == dex_location_) {
       VLOG(oat) << "Dex location " << dex_location_ << " is in boot class path";
       return true;
     }
@@ -211,23 +290,71 @@
   return false;
 }
 
-int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target,
+OatFileAssistant::DexOptTrigger OatFileAssistant::GetDexOptTrigger(
+    CompilerFilter::Filter target_compiler_filter, bool profile_changed, bool downgrade) {
+  if (downgrade) {
+    // The caller's intention is to downgrade the compiler filter. We should only re-compile if the
+    // target compiler filter is worse than the current one.
+    return DexOptTrigger{.targetFilterIsWorse = true};
+  }
+
+  // This is the usual case. The caller's intention is to see if a better oat file can be generated.
+  DexOptTrigger dexopt_trigger{
+      .targetFilterIsBetter = true, .primaryBootImageBecomesUsable = true, .needExtraction = true};
+  if (profile_changed && CompilerFilter::DependsOnProfile(target_compiler_filter)) {
+    // Since the profile has been changed, we should re-compile even if the compilation does not
+    // make the compiler filter better.
+    dexopt_trigger.targetFilterIsSame = true;
+  }
+  return dexopt_trigger;
+}
+
+int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
                                       bool profile_changed,
                                       bool downgrade) {
   OatFileInfo& info = GetBestInfo();
-  DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target,
-                                                    profile_changed,
-                                                    downgrade);
+  if (info.CheckDisableCompactDexExperiment()) {  // TODO(b/256664509): Clean this up.
+    return kDex2OatFromScratch;
+  }
+  DexOptNeeded dexopt_needed = info.GetDexOptNeeded(
+      target_compiler_filter, GetDexOptTrigger(target_compiler_filter, profile_changed, downgrade));
+  if (dexopt_needed != kNoDexOptNeeded && (&info == &dm_for_oat_ || &info == &dm_for_odex_)) {
+    // The usable vdex file is in the DM file. This information cannot be encoded in the integer.
+    // Return kDex2OatFromScratch so that neither the vdex in the "oat" location nor the vdex in the
+    // "odex" location will be picked by installd.
+    return kDex2OatFromScratch;
+  }
   if (info.IsOatLocation() || dexopt_needed == kDex2OatFromScratch) {
     return dexopt_needed;
   }
   return -dexopt_needed;
 }
 
-bool OatFileAssistant::IsUpToDate() {
-  return GetBestInfo().Status() == kOatUpToDate;
+bool OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
+                                       DexOptTrigger dexopt_trigger,
+                                       /*out*/ DexOptStatus* dexopt_status) {
+  OatFileInfo& info = GetBestInfo();
+  if (info.CheckDisableCompactDexExperiment()) {  // TODO(b/256664509): Clean this up.
+    dexopt_status->location_ = kLocationNoneOrError;
+    return true;
+  }
+  DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target_compiler_filter, dexopt_trigger);
+  if (info.IsUseable()) {
+    if (&info == &dm_for_oat_ || &info == &dm_for_odex_) {
+      dexopt_status->location_ = kLocationDm;
+    } else if (info.IsOatLocation()) {
+      dexopt_status->location_ = kLocationOat;
+    } else {
+      dexopt_status->location_ = kLocationOdex;
+    }
+  } else {
+    dexopt_status->location_ = kLocationNoneOrError;
+  }
+  return dexopt_needed != kNoDexOptNeeded;
 }
 
+bool OatFileAssistant::IsUpToDate() { return GetBestInfo().Status() == kOatUpToDate; }
+
 std::unique_ptr<OatFile> OatFileAssistant::GetBestOatFile() {
   return GetBestInfo().ReleaseFileForUse();
 }
@@ -280,7 +407,7 @@
 }
 
 std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles(
-    const OatFile &oat_file, const char *dex_location) {
+    const OatFile& oat_file, const char* dex_location) {
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   if (LoadDexFiles(oat_file, dex_location, &dex_files)) {
     return dex_files;
@@ -289,14 +416,13 @@
   }
 }
 
-bool OatFileAssistant::LoadDexFiles(
-    const OatFile &oat_file,
-    const std::string& dex_location,
-    std::vector<std::unique_ptr<const DexFile>>* out_dex_files) {
+bool OatFileAssistant::LoadDexFiles(const OatFile& oat_file,
+                                    const std::string& dex_location,
+                                    std::vector<std::unique_ptr<const DexFile>>* out_dex_files) {
   // Load the main dex file.
   std::string error_msg;
-  const OatDexFile* oat_dex_file = oat_file.GetOatDexFile(
-      dex_location.c_str(), nullptr, &error_msg);
+  const OatDexFile* oat_dex_file =
+      oat_file.GetOatDexFile(dex_location.c_str(), nullptr, &error_msg);
   if (oat_dex_file == nullptr) {
     LOG(WARNING) << error_msg;
     return false;
@@ -328,69 +454,39 @@
   return true;
 }
 
-bool OatFileAssistant::HasDexFiles() {
+std::optional<bool> OatFileAssistant::HasDexFiles(std::string* error_msg) {
   ScopedTrace trace("HasDexFiles");
-  // Ensure GetRequiredDexChecksums has been run so that
-  // has_original_dex_files_ is initialized. We don't care about the result of
-  // GetRequiredDexChecksums.
-  GetRequiredDexChecksums();
-  return has_original_dex_files_;
-}
-
-OatFileAssistant::OatStatus OatFileAssistant::OdexFileStatus() {
-  return odex_.Status();
-}
-
-OatFileAssistant::OatStatus OatFileAssistant::OatFileStatus() {
-  return oat_.Status();
-}
-
-bool OatFileAssistant::DexChecksumUpToDate(const VdexFile& file, std::string* error_msg) {
-  ScopedTrace trace("DexChecksumUpToDate(vdex)");
-  const std::vector<uint32_t>* required_dex_checksums = GetRequiredDexChecksums();
-  if (required_dex_checksums == nullptr) {
-    LOG(WARNING) << "Required dex checksums not found. Assuming dex checksums are up to date.";
-    return true;
+  const std::vector<std::uint32_t>* checksums = GetRequiredDexChecksums(error_msg);
+  if (checksums == nullptr) {
+    return std::nullopt;
   }
-
-  uint32_t number_of_dex_files = file.GetNumberOfDexFiles();
-  if (required_dex_checksums->size() != number_of_dex_files) {
-    *error_msg = StringPrintf("expected %zu dex files but found %u",
-                              required_dex_checksums->size(),
-                              number_of_dex_files);
-    return false;
-  }
-
-  for (uint32_t i = 0; i < number_of_dex_files; i++) {
-    uint32_t expected_checksum = (*required_dex_checksums)[i];
-    uint32_t actual_checksum = file.GetLocationChecksum(i);
-    if (expected_checksum != actual_checksum) {
-      std::string dex = DexFileLoader::GetMultiDexLocation(i, dex_location_.c_str());
-      *error_msg = StringPrintf("Dex checksum does not match for dex: %s."
-                                "Expected: %u, actual: %u",
-                                dex.c_str(),
-                                expected_checksum,
-                                actual_checksum);
-      return false;
-    }
-  }
-
-  return true;
+  return !checksums->empty();
 }
 
+OatFileAssistant::OatStatus OatFileAssistant::OdexFileStatus() { return odex_.Status(); }
+
+OatFileAssistant::OatStatus OatFileAssistant::OatFileStatus() { return oat_.Status(); }
+
 bool OatFileAssistant::DexChecksumUpToDate(const OatFile& file, std::string* error_msg) {
-  ScopedTrace trace("DexChecksumUpToDate(oat)");
-  const std::vector<uint32_t>* required_dex_checksums = GetRequiredDexChecksums();
+  if (!file.ContainsDexCode()) {
+    // We've already checked during oat file creation that the dex files loaded
+    // from external files have the same checksums as the ones in the vdex file.
+    return true;
+  }
+  ScopedTrace trace("DexChecksumUpToDate");
+  const std::vector<uint32_t>* required_dex_checksums = GetRequiredDexChecksums(error_msg);
   if (required_dex_checksums == nullptr) {
+    return false;
+  }
+  if (required_dex_checksums->empty()) {
     LOG(WARNING) << "Required dex checksums not found. Assuming dex checksums are up to date.";
     return true;
   }
 
   uint32_t number_of_dex_files = file.GetOatHeader().GetDexFileCount();
   if (required_dex_checksums->size() != number_of_dex_files) {
-    *error_msg = StringPrintf("expected %zu dex files but found %u",
-                              required_dex_checksums->size(),
-                              number_of_dex_files);
+    *error_msg = StringPrintf(
+        "expected %zu dex files but found %u", required_dex_checksums->size(), number_of_dex_files);
     return false;
   }
 
@@ -405,8 +501,7 @@
     uint32_t actual_checksum = oat_dex_file->GetDexFileLocationChecksum();
     if (expected_checksum != actual_checksum) {
       VLOG(oat) << "Dex checksum does not match for dex: " << dex
-        << ". Expected: " << expected_checksum
-        << ", Actual: " << actual_checksum;
+                << ". Expected: " << expected_checksum << ", Actual: " << actual_checksum;
       return false;
     }
   }
@@ -419,16 +514,13 @@
   // compiled code and are otherwise okay, we should return something like
   // kOatRelocationOutOfDate. If they don't contain compiled code, the read
   // barrier state doesn't matter.
-  const bool is_cc = file.GetOatHeader().IsConcurrentCopying();
-  constexpr bool kRuntimeIsCC = kUseReadBarrier;
-  if (is_cc != kRuntimeIsCC) {
+  if (file.GetOatHeader().IsConcurrentCopying() != gUseReadBarrier) {
     return kOatCannotOpen;
   }
 
   // Verify the dex checksum.
   std::string error_msg;
-  VdexFile* vdex = file.GetVdexFile();
-  if (!DexChecksumUpToDate(*vdex, &error_msg)) {
+  if (!DexChecksumUpToDate(file, &error_msg)) {
     LOG(ERROR) << error_msg;
     return kOatDexOutOfDate;
   }
@@ -443,7 +535,11 @@
       VLOG(oat) << "Oat image checksum does not match image checksum.";
       return kOatBootImageOutOfDate;
     }
-    if (!gc::space::ImageSpace::ValidateApexVersions(file, &error_msg)) {
+    if (!gc::space::ImageSpace::ValidateApexVersions(
+            file.GetOatHeader(),
+            GetOatFileAssistantContext()->GetApexVersions(),
+            file.GetLocation(),
+            &error_msg)) {
       VLOG(oat) << error_msg;
       return kOatBootImageOutOfDate;
     }
@@ -451,14 +547,11 @@
     VLOG(oat) << "Image checksum test skipped for compiler filter " << current_compiler_filter;
   }
 
-  // zip_file_only_contains_uncompressed_dex_ is only set during fetching the dex checksums.
-  DCHECK(required_dex_checksums_attempted_);
+  // The constraint is only enforced if the zip has uncompressed dex code.
   if (only_load_trusted_executable_ &&
-      !LocationIsTrusted(file.GetLocation(), !Runtime::Current()->DenyArtApexDataFiles()) &&
-      file.ContainsDexCode() &&
-      zip_file_only_contains_uncompressed_dex_) {
-    LOG(ERROR) << "Not loading "
-               << dex_location_
+      !LocationIsTrusted(file.GetLocation(), !GetRuntimeOptions().deny_art_apex_data_files) &&
+      file.ContainsDexCode() && ZipFileOnlyContainsUncompressedDex()) {
+    LOG(ERROR) << "Not loading " << dex_location_
                << ": oat file has dex code, but APK has uncompressed dex code";
     return kOatDexOutOfDate;
   }
@@ -474,11 +567,15 @@
                                                 InstructionSet isa,
                                                 /* out */ std::string* dex_location,
                                                 /* out */ std::string* vdex_filename) {
+  // Normally, OatFileAssistant should not assume that there is an active runtime. However, we
+  // reference the runtime here. This is okay because we are in a static function that is unrelated
+  // to other parts of OatFileAssistant.
+  DCHECK(Runtime::Current() != nullptr);
+
   uint32_t checksum = adler32(0L, Z_NULL, 0);
   for (const DexFile::Header* header : headers) {
-    checksum = adler32_combine(checksum,
-                               header->checksum_,
-                               header->file_size_ - DexFile::kNumNonChecksumBytes);
+    checksum = adler32_combine(
+        checksum, header->checksum_, header->file_size_ - DexFile::kNumNonChecksumBytes);
   }
 
   const std::string& data_dir = Runtime::Current()->GetProcessDataDirectory();
@@ -503,7 +600,7 @@
   DCHECK(basename.find('/') == std::string::npos);
   // `basename` must have format: <kAnonymousDexPrefix><checksum><kVdexExtension>
   if (basename.size() < strlen(kAnonymousDexPrefix) + strlen(kVdexExtension) + 1 ||
-      !android::base::StartsWith(basename.c_str(), kAnonymousDexPrefix) ||
+      !android::base::StartsWith(basename, kAnonymousDexPrefix) ||
       !android::base::EndsWith(basename, kVdexExtension)) {
     return false;
   }
@@ -547,7 +644,7 @@
     *error_msg = "Dex location " + location + " has no directory.";
     return false;
   }
-  std::string dir = location.substr(0, pos+1);
+  std::string dir = location.substr(0, pos + 1);
   // Add the oat directory.
   dir += "oat";
 
@@ -555,7 +652,7 @@
   dir += "/" + std::string(GetInstructionSetString(isa));
 
   // Get the base part of the file without the extension.
-  std::string file = location.substr(pos+1);
+  std::string file = location.substr(pos + 1);
   pos = file.rfind('.');
   if (pos == std::string::npos) {
     *error_msg = "Dex location " + location + " has no extension.";
@@ -571,13 +668,23 @@
                                                 InstructionSet isa,
                                                 std::string* oat_filename,
                                                 std::string* error_msg) {
+  DCHECK(Runtime::Current() != nullptr);
+  return DexLocationToOatFilename(
+      location, isa, Runtime::Current()->DenyArtApexDataFiles(), oat_filename, error_msg);
+}
+
+bool OatFileAssistant::DexLocationToOatFilename(const std::string& location,
+                                                InstructionSet isa,
+                                                bool deny_art_apex_data_files,
+                                                std::string* oat_filename,
+                                                std::string* error_msg) {
   CHECK(oat_filename != nullptr);
   CHECK(error_msg != nullptr);
 
   // Check if `location` could have an oat file in the ART APEX data directory. If so, and the
   // file exists, use it.
   const std::string apex_data_file = GetApexDataOdexFilename(location, isa);
-  if (!apex_data_file.empty() && !Runtime::Current()->DenyArtApexDataFiles()) {
+  if (!apex_data_file.empty() && !deny_art_apex_data_files) {
     if (OS::FileExists(apex_data_file.c_str(), /*check_file_type=*/true)) {
       *oat_filename = apex_data_file;
       return true;
@@ -598,11 +705,11 @@
   bool dalvik_cache_exists = false;
   bool is_global_cache = false;
   GetDalvikCache(GetInstructionSetString(isa),
-                  /*create_if_absent=*/ true,
-                  &dalvik_cache,
-                  &have_android_data,
-                  &dalvik_cache_exists,
-                  &is_global_cache);
+                 /*create_if_absent=*/true,
+                 &dalvik_cache,
+                 &have_android_data,
+                 &dalvik_cache_exists,
+                 &is_global_cache);
   if (!dalvik_cache_exists) {
     *error_msg = "Dalvik cache directory does not exist";
     return false;
@@ -614,30 +721,132 @@
   return GetDalvikCacheFilename(location.c_str(), dalvik_cache.c_str(), oat_filename, error_msg);
 }
 
-const std::vector<uint32_t>* OatFileAssistant::GetRequiredDexChecksums() {
+const std::vector<uint32_t>* OatFileAssistant::GetRequiredDexChecksums(std::string* error_msg) {
   if (!required_dex_checksums_attempted_) {
     required_dex_checksums_attempted_ = true;
-    required_dex_checksums_found_ = false;
-    cached_required_dex_checksums_.clear();
-    std::string error_msg;
-    const ArtDexFileLoader dex_file_loader;
+    std::vector<uint32_t> checksums;
     std::vector<std::string> dex_locations_ignored;
-    if (dex_file_loader.GetMultiDexChecksums(dex_location_.c_str(),
-                                             &cached_required_dex_checksums_,
-                                             &dex_locations_ignored,
-                                             &error_msg,
-                                             zip_fd_,
-                                             &zip_file_only_contains_uncompressed_dex_)) {
-      required_dex_checksums_found_ = true;
-      has_original_dex_files_ = true;
-    } else {
-      // The only valid case here is for APKs without dex files.
-      required_dex_checksums_found_ = false;
-      has_original_dex_files_ = false;
-      VLOG(oat) << "Could not get required checksum: " << error_msg;
+    if (ArtDexFileLoader::GetMultiDexChecksums(dex_location_.c_str(),
+                                               &checksums,
+                                               &dex_locations_ignored,
+                                               &cached_required_dex_checksums_error_,
+                                               zip_fd_,
+                                               &zip_file_only_contains_uncompressed_dex_)) {
+      if (checksums.empty()) {
+        // The only valid case here is for APKs without dex files.
+        VLOG(oat) << "No dex file found in " << dex_location_;
+      }
+
+      cached_required_dex_checksums_ = std::move(checksums);
     }
   }
-  return required_dex_checksums_found_ ? &cached_required_dex_checksums_ : nullptr;
+
+  if (cached_required_dex_checksums_.has_value()) {
+    return &cached_required_dex_checksums_.value();
+  } else {
+    *error_msg = cached_required_dex_checksums_error_;
+    DCHECK(!error_msg->empty());
+    return nullptr;
+  }
+}
+
+bool OatFileAssistant::ValidateBootClassPathChecksums(OatFileAssistantContext* ofa_context,
+                                                      InstructionSet isa,
+                                                      std::string_view oat_checksums,
+                                                      std::string_view oat_boot_class_path,
+                                                      /*out*/ std::string* error_msg) {
+  const std::vector<std::string>& bcp_locations =
+      ofa_context->GetRuntimeOptions().boot_class_path_locations;
+
+  if (oat_checksums.empty() || oat_boot_class_path.empty()) {
+    *error_msg = oat_checksums.empty() ? "Empty checksums" : "Empty boot class path";
+    return false;
+  }
+
+  size_t oat_bcp_size = gc::space::ImageSpace::CheckAndCountBCPComponents(
+      oat_boot_class_path, ArrayRef<const std::string>(bcp_locations), error_msg);
+  if (oat_bcp_size == static_cast<size_t>(-1)) {
+    DCHECK(!error_msg->empty());
+    return false;
+  }
+  DCHECK_LE(oat_bcp_size, bcp_locations.size());
+
+  size_t bcp_index = 0;
+  size_t boot_image_index = 0;
+  bool found_d = false;
+
+  while (bcp_index < oat_bcp_size) {
+    static_assert(gc::space::ImageSpace::kImageChecksumPrefix == 'i', "Format prefix check");
+    static_assert(gc::space::ImageSpace::kDexFileChecksumPrefix == 'd', "Format prefix check");
+    if (StartsWith(oat_checksums, "i") && !found_d) {
+      const std::vector<OatFileAssistantContext::BootImageInfo>& boot_image_info_list =
+          ofa_context->GetBootImageInfoList(isa);
+      if (boot_image_index >= boot_image_info_list.size()) {
+        *error_msg = StringPrintf("Missing boot image for %s, remaining checksums: %s",
+                                  bcp_locations[bcp_index].c_str(),
+                                  std::string(oat_checksums).c_str());
+        return false;
+      }
+
+      const OatFileAssistantContext::BootImageInfo& boot_image_info =
+          boot_image_info_list[boot_image_index];
+      if (!ConsumePrefix(&oat_checksums, boot_image_info.checksum)) {
+        *error_msg = StringPrintf("Image checksum mismatch, expected %s to start with %s",
+                                  std::string(oat_checksums).c_str(),
+                                  boot_image_info.checksum.c_str());
+        return false;
+      }
+
+      bcp_index += boot_image_info.component_count;
+      boot_image_index++;
+    } else if (StartsWith(oat_checksums, "d")) {
+      found_d = true;
+      const std::vector<std::string>* bcp_checksums =
+          ofa_context->GetBcpChecksums(bcp_index, error_msg);
+      if (bcp_checksums == nullptr) {
+        return false;
+      }
+      oat_checksums.remove_prefix(1u);
+      for (const std::string& checksum : *bcp_checksums) {
+        if (!ConsumePrefix(&oat_checksums, checksum)) {
+          *error_msg = StringPrintf(
+              "Dex checksum mismatch for bootclasspath file %s, expected %s to start with %s",
+              bcp_locations[bcp_index].c_str(),
+              std::string(oat_checksums).c_str(),
+              checksum.c_str());
+          return false;
+        }
+      }
+
+      bcp_index++;
+    } else {
+      *error_msg = StringPrintf("Unexpected checksums, expected %s to start with %s",
+                                std::string(oat_checksums).c_str(),
+                                found_d ? "'d'" : "'i' or 'd'");
+      return false;
+    }
+
+    if (bcp_index < oat_bcp_size) {
+      if (!ConsumePrefix(&oat_checksums, ":")) {
+        if (oat_checksums.empty()) {
+          *error_msg =
+              StringPrintf("Checksum too short, missing %zu components", oat_bcp_size - bcp_index);
+        } else {
+          *error_msg = StringPrintf("Missing ':' separator at start of %s",
+                                    std::string(oat_checksums).c_str());
+        }
+        return false;
+      }
+    }
+  }
+
+  if (!oat_checksums.empty()) {
+    *error_msg =
+        StringPrintf("Checksum too long, unexpected tail: %s", std::string(oat_checksums).c_str());
+    return false;
+  }
+
+  return true;
 }
 
 bool OatFileAssistant::ValidateBootClassPathChecksums(const OatFile& oat_file) {
@@ -649,45 +858,26 @@
   if (oat_boot_class_path_checksums == nullptr || oat_boot_class_path == nullptr) {
     return false;
   }
-  std::string_view oat_boot_class_path_checksums_view(oat_boot_class_path_checksums);
-  std::string_view oat_boot_class_path_view(oat_boot_class_path);
-  if (oat_boot_class_path_view == cached_boot_class_path_ &&
-      oat_boot_class_path_checksums_view == cached_boot_class_path_checksums_) {
-    return true;
-  }
 
-  Runtime* runtime = Runtime::Current();
   std::string error_msg;
-  bool result = false;
-  // Fast path when the runtime boot classpath cheksums and boot classpath
-  // locations directly match.
-  if (oat_boot_class_path_checksums_view == runtime->GetBootClassPathChecksums() &&
-      isa_ == kRuntimeISA &&
-      oat_boot_class_path_view == android::base::Join(runtime->GetBootClassPathLocations(), ":")) {
-    result = true;
-  } else {
-    result = gc::space::ImageSpace::VerifyBootClassPathChecksums(
-        oat_boot_class_path_checksums_view,
-        oat_boot_class_path_view,
-        ArrayRef<const std::string>(runtime->GetImageLocations()),
-        ArrayRef<const std::string>(runtime->GetBootClassPathLocations()),
-        ArrayRef<const std::string>(runtime->GetBootClassPath()),
-        ArrayRef<const int>(runtime->GetBootClassPathFds()),
-        isa_,
-        &error_msg);
-  }
+  bool result = ValidateBootClassPathChecksums(GetOatFileAssistantContext(),
+                                               isa_,
+                                               oat_boot_class_path_checksums,
+                                               oat_boot_class_path,
+                                               &error_msg);
   if (!result) {
     VLOG(oat) << "Failed to verify checksums of oat file " << oat_file.GetLocation()
-        << " error: " << error_msg;
+              << " error: " << error_msg;
     return false;
   }
 
-  // This checksum has been validated, so save it.
-  cached_boot_class_path_ = oat_boot_class_path_view;
-  cached_boot_class_path_checksums_ = oat_boot_class_path_checksums_view;
   return true;
 }
 
+bool OatFileAssistant::IsPrimaryBootImageUsable() {
+  return !GetOatFileAssistantContext()->GetBootImageInfoList(isa_).empty();
+}
+
 OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() {
   ScopedTrace trace("GetBestInfo");
   // TODO(calin): Document the side effects of class loading when
@@ -702,10 +892,16 @@
 
     // If the odex is not useable, and we have a useable vdex, return the vdex
     // instead.
+    VLOG(oat) << "GetBestInfo checking odex next to the dex file ({})"_format(
+        odex_.DisplayFilename());
     if (!odex_.IsUseable()) {
+      VLOG(oat) << "GetBestInfo checking vdex next to the dex file ({})"_format(
+          vdex_for_odex_.DisplayFilename());
       if (vdex_for_odex_.IsUseable()) {
         return vdex_for_odex_;
-      } else if (dm_for_odex_.IsUseable()) {
+      }
+      VLOG(oat) << "GetBestInfo checking dm ({})"_format(dm_for_odex_.DisplayFilename());
+      if (dm_for_odex_.IsUseable()) {
         return dm_for_odex_;
       }
     }
@@ -715,6 +911,7 @@
   // We cannot write to the odex location. This must be a system app.
 
   // If the oat location is useable take it.
+  VLOG(oat) << "GetBestInfo checking odex in dalvik-cache ({})"_format(oat_.DisplayFilename());
   if (oat_.IsUseable()) {
     return oat_;
   }
@@ -722,20 +919,29 @@
   // The oat file is not useable but the odex file might be up to date.
   // This is an indication that we are dealing with an up to date prebuilt
   // (that doesn't need relocation).
+  VLOG(oat) << "GetBestInfo checking odex next to the dex file ({})"_format(
+      odex_.DisplayFilename());
   if (odex_.IsUseable()) {
     return odex_;
   }
 
   // Look for a useable vdex file.
+  VLOG(oat) << "GetBestInfo checking vdex in dalvik-cache ({})"_format(
+      vdex_for_oat_.DisplayFilename());
   if (vdex_for_oat_.IsUseable()) {
     return vdex_for_oat_;
   }
+  VLOG(oat) << "GetBestInfo checking vdex next to the dex file ({})"_format(
+      vdex_for_odex_.DisplayFilename());
   if (vdex_for_odex_.IsUseable()) {
     return vdex_for_odex_;
   }
+  VLOG(oat) << "GetBestInfo checking dm ({})"_format(dm_for_oat_.DisplayFilename());
   if (dm_for_oat_.IsUseable()) {
     return dm_for_oat_;
   }
+  // TODO(jiakaiz): Is this the same as above?
+  VLOG(oat) << "GetBestInfo checking dm ({})"_format(dm_for_odex_.DisplayFilename());
   if (dm_for_odex_.IsUseable()) {
     return dm_for_odex_;
   }
@@ -746,6 +952,7 @@
   // - the vdex-only file is not useable
   // - and we don't have the original dex file anymore (stripped).
   // Pick the odex if it exists, or the oat if not.
+  VLOG(oat) << "GetBestInfo no usable artifacts";
   return (odex_.Status() == kOatCannotOpen) ? oat_ : odex_;
 }
 
@@ -767,26 +974,29 @@
 
 OatFileAssistant::OatFileInfo::OatFileInfo(OatFileAssistant* oat_file_assistant,
                                            bool is_oat_location)
-  : oat_file_assistant_(oat_file_assistant), is_oat_location_(is_oat_location)
-{}
+    : oat_file_assistant_(oat_file_assistant), is_oat_location_(is_oat_location) {}
 
-bool OatFileAssistant::OatFileInfo::IsOatLocation() {
-  return is_oat_location_;
-}
+bool OatFileAssistant::OatFileInfo::IsOatLocation() { return is_oat_location_; }
 
 const std::string* OatFileAssistant::OatFileInfo::Filename() {
   return filename_provided_ ? &filename_ : nullptr;
 }
 
+const char* OatFileAssistant::OatFileInfo::DisplayFilename() {
+  return filename_provided_ ? filename_.c_str() : "unknown";
+}
+
 bool OatFileAssistant::OatFileInfo::IsUseable() {
   ScopedTrace trace("IsUseable");
   switch (Status()) {
     case kOatCannotOpen:
     case kOatDexOutOfDate:
     case kOatContextOutOfDate:
-    case kOatBootImageOutOfDate: return false;
+    case kOatBootImageOutOfDate:
+      return false;
 
-    case kOatUpToDate: return true;
+    case kOatUpToDate:
+      return true;
   }
   UNREACHABLE();
 }
@@ -800,32 +1010,42 @@
       status_ = kOatCannotOpen;
     } else {
       status_ = oat_file_assistant_->GivenOatFileStatus(*file);
-      VLOG(oat) << file->GetLocation() << " is " << status_
-          << " with filter " << file->GetCompilerFilter();
+      VLOG(oat) << file->GetLocation() << " is " << status_ << " with filter "
+                << file->GetCompilerFilter();
     }
   }
   return status_;
 }
 
 OatFileAssistant::DexOptNeeded OatFileAssistant::OatFileInfo::GetDexOptNeeded(
-    CompilerFilter::Filter target,
-    bool profile_changed,
-    bool downgrade) {
-
+    CompilerFilter::Filter target_compiler_filter, const DexOptTrigger dexopt_trigger) {
   if (IsUseable()) {
-    return CompilerFilterIsOkay(target, profile_changed, downgrade)
-        ? kNoDexOptNeeded
-        : kDex2OatForFilter;
+    return ShouldRecompileForFilter(target_compiler_filter, dexopt_trigger) ? kDex2OatForFilter :
+                                                                              kNoDexOptNeeded;
+  }
+
+  // In this case, the oat file is not usable. If the caller doesn't seek for a better compiler
+  // filter (e.g., the caller wants to downgrade), then we should not recompile.
+  if (!dexopt_trigger.targetFilterIsBetter) {
+    return kNoDexOptNeeded;
   }
 
   if (Status() == kOatBootImageOutOfDate) {
     return kDex2OatForBootImage;
   }
 
-  if (oat_file_assistant_->HasDexFiles()) {
-    return kDex2OatFromScratch;
+  std::string error_msg;
+  std::optional<bool> has_dex_files = oat_file_assistant_->HasDexFiles(&error_msg);
+  if (has_dex_files.has_value()) {
+    if (*has_dex_files) {
+      return kDex2OatFromScratch;
+    } else {
+      // No dex file, so there is nothing we need to do.
+      return kNoDexOptNeeded;
+    }
   } else {
-    // No dex file, there is nothing we need to do.
+    // Unable to open the dex file, so there is nothing we can do.
+    LOG(WARNING) << error_msg;
     return kNoDexOptNeeded;
   }
 }
@@ -840,7 +1060,8 @@
     return nullptr;
   }
 
-  if (LocationIsOnArtApexData(filename_) && Runtime::Current()->DenyArtApexDataFiles()) {
+  if (LocationIsOnArtApexData(filename_) &&
+      oat_file_assistant_->GetRuntimeOptions().deny_art_apex_data_files) {
     LOG(WARNING) << "OatFileAssistant rejected file " << filename_
                  << ": ART apexdata is untrusted.";
     return nullptr;
@@ -862,15 +1083,15 @@
           vdex = VdexFile::Open(vdex_fd_,
                                 s.st_size,
                                 filename_,
-                                /*writable=*/ false,
-                                /*low_4gb=*/ false,
+                                /*writable=*/false,
+                                /*low_4gb=*/false,
                                 &error_msg);
         }
       }
     } else {
       vdex = VdexFile::Open(filename_,
-                            /*writable=*/ false,
-                            /*low_4gb=*/ false,
+                            /*writable=*/false,
+                            /*low_4gb=*/false,
                             &error_msg);
     }
     if (vdex == nullptr) {
@@ -879,6 +1100,7 @@
       file_.reset(OatFile::OpenFromVdex(zip_fd_,
                                         std::move(vdex),
                                         oat_file_assistant_->dex_location_,
+                                        oat_file_assistant_->context_,
                                         &error_msg));
     }
   } else if (android::base::EndsWith(filename_, kDmExtension)) {
@@ -891,68 +1113,72 @@
         file_.reset(OatFile::OpenFromVdex(zip_fd_,
                                           std::move(vdex),
                                           oat_file_assistant_->dex_location_,
+                                          oat_file_assistant_->context_,
                                           &error_msg));
       }
     }
   } else {
     if (executable && oat_file_assistant_->only_load_trusted_executable_) {
-      executable = LocationIsTrusted(filename_, /*trust_art_apex_data_files=*/ true);
+      executable = LocationIsTrusted(filename_, /*trust_art_apex_data_files=*/true);
     }
     VLOG(oat) << "Loading " << filename_ << " with executable: " << executable;
     if (use_fd_) {
       if (oat_fd_ >= 0 && vdex_fd_ >= 0) {
         ArrayRef<const std::string> dex_locations(&oat_file_assistant_->dex_location_,
-                                                  /*size=*/ 1u);
+                                                  /*size=*/1u);
         file_.reset(OatFile::Open(zip_fd_,
                                   vdex_fd_,
                                   oat_fd_,
-                                  filename_.c_str(),
+                                  filename_,
                                   executable,
-                                  /*low_4gb=*/ false,
+                                  /*low_4gb=*/false,
                                   dex_locations,
-                                  /*dex_fds=*/ ArrayRef<const int>(),
-                                  /*reservation=*/ nullptr,
+                                  /*dex_fds=*/ArrayRef<const int>(),
+                                  /*reservation=*/nullptr,
                                   &error_msg));
       }
     } else {
-      file_.reset(OatFile::Open(/*zip_fd=*/ -1,
-                                filename_.c_str(),
-                                filename_.c_str(),
+      file_.reset(OatFile::Open(/*zip_fd=*/-1,
+                                filename_,
+                                filename_,
                                 executable,
-                                /*low_4gb=*/ false,
+                                /*low_4gb=*/false,
                                 oat_file_assistant_->dex_location_,
                                 &error_msg));
     }
   }
   if (file_.get() == nullptr) {
-    VLOG(oat) << "OatFileAssistant test for existing oat file "
-              << filename_
-              << ": " << error_msg;
+    VLOG(oat) << "OatFileAssistant test for existing oat file " << filename_ << ": " << error_msg;
   } else {
     VLOG(oat) << "Successfully loaded " << filename_ << " with executable: " << executable;
   }
   return file_.get();
 }
 
-bool OatFileAssistant::OatFileInfo::CompilerFilterIsOkay(
-    CompilerFilter::Filter target, bool profile_changed, bool downgrade) {
+bool OatFileAssistant::OatFileInfo::ShouldRecompileForFilter(CompilerFilter::Filter target,
+                                                             const DexOptTrigger dexopt_trigger) {
   const OatFile* file = GetFile();
-  if (file == nullptr) {
-    return false;
-  }
+  DCHECK(file != nullptr);
 
   CompilerFilter::Filter current = file->GetCompilerFilter();
-  if (profile_changed && CompilerFilter::DependsOnProfile(current)) {
-    VLOG(oat) << "Compiler filter not okay because Profile changed";
-    return false;
+  if (dexopt_trigger.targetFilterIsBetter && CompilerFilter::IsBetter(target, current)) {
+    VLOG(oat) << "Should recompile: targetFilterIsBetter (current: {}, target: {})"_format(
+        CompilerFilter::NameOfFilter(current), CompilerFilter::NameOfFilter(target));
+    return true;
+  }
+  if (dexopt_trigger.targetFilterIsSame && current == target) {
+    VLOG(oat) << "Should recompile: targetFilterIsSame (current: {}, target: {})"_format(
+        CompilerFilter::NameOfFilter(current), CompilerFilter::NameOfFilter(target));
+    return true;
+  }
+  if (dexopt_trigger.targetFilterIsWorse && CompilerFilter::IsBetter(current, target)) {
+    VLOG(oat) << "Should recompile: targetFilterIsWorse (current: {}, target: {})"_format(
+        CompilerFilter::NameOfFilter(current), CompilerFilter::NameOfFilter(target));
+    return true;
   }
 
-  if (downgrade) {
-    return !CompilerFilter::IsBetter(current, target);
-  }
-
-  if (CompilerFilter::DependsOnImageChecksum(current) &&
-      CompilerFilter::IsAsGoodAs(current, target)) {
+  if (dexopt_trigger.primaryBootImageBecomesUsable &&
+      CompilerFilter::DependsOnImageChecksum(current)) {
     // If the oat file has been compiled without an image, and the runtime is
     // now running with an image loaded from disk, return that we need to
     // re-compile. The recompilation will generate a better oat file, and with an app
@@ -961,16 +1187,29 @@
         file->GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey);
     if (oat_boot_class_path_checksums != nullptr &&
         !StartsWith(oat_boot_class_path_checksums, "i") &&
-        !Runtime::Current()->HasImageWithProfile()) {
+        oat_file_assistant_->IsPrimaryBootImageUsable()) {
       DCHECK(!file->GetOatHeader().RequiresImage());
-      return false;
+      VLOG(oat) << "Should recompile: primaryBootImageBecomesUsable";
+      return true;
     }
   }
 
-  return CompilerFilter::IsAsGoodAs(current, target);
+  if (dexopt_trigger.needExtraction && !file->ContainsDexCode() &&
+      !oat_file_assistant_->ZipFileOnlyContainsUncompressedDex()) {
+    VLOG(oat) << "Should recompile: needExtraction";
+    return true;
+  }
+
+  VLOG(oat) << "Should not recompile";
+  return false;
 }
 
 bool OatFileAssistant::ClassLoaderContextIsOkay(const OatFile& oat_file) const {
+  if (context_ == nullptr) {
+    // The caller requests to skip the check.
+    return true;
+  }
+
   if (oat_file.IsBackedByVdexOnly()) {
     // Only a vdex file, we don't depend on the class loader context.
     return true;
@@ -982,19 +1221,12 @@
     return true;
   }
 
-  if (context_ == nullptr) {
-    // When no class loader context is provided (which happens for deprecated
-    // DexFile APIs), just assume it is OK.
-    return true;
-  }
-
-  ClassLoaderContext::VerificationResult matches = context_->VerifyClassLoaderContextMatch(
-      oat_file.GetClassLoaderContext(),
-      /*verify_names=*/ true,
-      /*verify_checksums=*/ true);
+  ClassLoaderContext::VerificationResult matches =
+      context_->VerifyClassLoaderContextMatch(oat_file.GetClassLoaderContext(),
+                                              /*verify_names=*/true,
+                                              /*verify_checksums=*/true);
   if (matches == ClassLoaderContext::VerificationResult::kMismatch) {
-    VLOG(oat) << "ClassLoaderContext check failed. Context was "
-              << oat_file.GetClassLoaderContext()
+    VLOG(oat) << "ClassLoaderContext check failed. Context was " << oat_file.GetClassLoaderContext()
               << ". The expected context is "
               << context_->EncodeContextForOatFile(android::base::Dirname(dex_location_));
     return false;
@@ -1013,11 +1245,8 @@
   status_attempted_ = false;
 }
 
-void OatFileAssistant::OatFileInfo::Reset(const std::string& filename,
-                                          bool use_fd,
-                                          int zip_fd,
-                                          int vdex_fd,
-                                          int oat_fd) {
+void OatFileAssistant::OatFileInfo::Reset(
+    const std::string& filename, bool use_fd, int zip_fd, int vdex_fd, int oat_fd) {
   filename_provided_ = true;
   filename_ = filename;
   use_fd_ = use_fd;
@@ -1041,48 +1270,77 @@
   return std::unique_ptr<OatFile>();
 }
 
+// Check if we should reject vdex containing cdex code as part of the
+// disable_cdex experiment.
+// TODO(b/256664509): Clean this up.
+bool OatFileAssistant::OatFileInfo::CheckDisableCompactDexExperiment() {
+  std::string ph_disable_compact_dex = android::base::GetProperty(kPhDisableCompactDex, "false");
+  if (ph_disable_compact_dex != "true") {
+    return false;
+  }
+  const OatFile* oat_file = GetFile();
+  if (oat_file == nullptr) {
+    return false;
+  }
+  const VdexFile* vdex_file = oat_file->GetVdexFile();
+  return vdex_file != nullptr && vdex_file->HasDexSection() &&
+         !vdex_file->HasOnlyStandardDexFiles();
+}
+
 // TODO(calin): we could provide a more refined status here
 // (e.g. run from uncompressed apk, run with vdex but not oat etc). It will allow us to
 // track more experiments but adds extra complexity.
-void OatFileAssistant::GetOptimizationStatus(
-    const std::string& filename,
-    InstructionSet isa,
-    std::string* out_compilation_filter,
-    std::string* out_compilation_reason) {
+void OatFileAssistant::GetOptimizationStatus(const std::string& filename,
+                                             InstructionSet isa,
+                                             std::string* out_compilation_filter,
+                                             std::string* out_compilation_reason,
+                                             OatFileAssistantContext* ofa_context) {
   // It may not be possible to load an oat file executable (e.g., selinux restrictions). Load
   // non-executable and check the status manually.
   OatFileAssistant oat_file_assistant(filename.c_str(),
                                       isa,
-                                      /* context= */ nullptr,
-                                      /*load_executable=*/ false);
+                                      /*context=*/nullptr,
+                                      /*load_executable=*/false,
+                                      /*only_load_trusted_executable=*/false,
+                                      ofa_context);
   std::string out_odex_location;  // unused
-  std::string out_odex_status;  // unused
+  std::string out_odex_status;    // unused
   oat_file_assistant.GetOptimizationStatus(
-      &out_odex_location,
-      out_compilation_filter,
-      out_compilation_reason,
-      &out_odex_status);
+      &out_odex_location, out_compilation_filter, out_compilation_reason, &out_odex_status);
 }
 
-void OatFileAssistant::GetOptimizationStatus(
-    std::string* out_odex_location,
-    std::string* out_compilation_filter,
-    std::string* out_compilation_reason,
-    std::string* out_odex_status) {
+void OatFileAssistant::GetOptimizationStatus(std::string* out_odex_location,
+                                             std::string* out_compilation_filter,
+                                             std::string* out_compilation_reason,
+                                             std::string* out_odex_status) {
   OatFileInfo& oat_file_info = GetBestInfo();
   const OatFile* oat_file = GetBestInfo().GetFile();
 
   if (oat_file == nullptr) {
-    *out_odex_location = "error";
-    *out_compilation_filter = "run-from-apk";
-    *out_compilation_reason = "unknown";
-    // This mostly happens when we cannot open the oat file.
-    // Note that it's different than kOatCannotOpen.
-    // TODO: The design of getting the BestInfo is not ideal,
-    // as it's not very clear what's the difference between
-    // a nullptr and kOatcannotOpen. The logic should be revised
-    // and improved.
-    *out_odex_status = "io-error-no-oat";
+    std::string error_msg;
+    std::optional<bool> has_dex_files = HasDexFiles(&error_msg);
+    if (!has_dex_files.has_value()) {
+      *out_odex_location = "error";
+      *out_compilation_filter = "unknown";
+      *out_compilation_reason = "unknown";
+      // This happens when we cannot open the APK/JAR.
+      *out_odex_status = "io-error-no-apk";
+    } else if (!has_dex_files.value()) {
+      *out_odex_location = "none";
+      *out_compilation_filter = "unknown";
+      *out_compilation_reason = "unknown";
+      // This happens when the APK/JAR doesn't contain any DEX file.
+      *out_odex_status = "no-dex-code";
+    } else {
+      *out_odex_location = "error";
+      *out_compilation_filter = "run-from-apk";
+      *out_compilation_reason = "unknown";
+      // This mostly happens when we cannot open the oat file.
+      // Note that it's different than kOatCannotOpen.
+      // TODO: The design of getting the BestInfo is not ideal, as it's not very clear what's the
+      // difference between a nullptr and kOatcannotOpen. The logic should be revised and improved.
+      *out_odex_status = "io-error-no-oat";
+    }
     return;
   }
 
@@ -1090,28 +1348,25 @@
   OatStatus status = oat_file_info.Status();
   const char* reason = oat_file->GetCompilationReason();
   *out_compilation_reason = reason == nullptr ? "unknown" : reason;
+
+  // If the oat file is invalid, the vdex file will be picked, so the status is `kOatUpToDate`. If
+  // the vdex file is also invalid, then either `oat_file` is nullptr, or `status` is
+  // `kOatDexOutOfDate`.
+  DCHECK(status == kOatUpToDate || status == kOatDexOutOfDate);
+
   switch (status) {
     case kOatUpToDate:
       *out_compilation_filter = CompilerFilter::NameOfFilter(oat_file->GetCompilerFilter());
       *out_odex_status = "up-to-date";
       return;
 
-    case kOatCannotOpen:  // This should never happen, but be robust.
-      *out_compilation_filter = "error";
-      *out_compilation_reason = "error";
-      // This mostly happens when we cannot open the vdex file,
-      // or the file is corrupt.
-      *out_odex_status = "io-error-or-corruption";
-      return;
-
+    case kOatCannotOpen:
     case kOatBootImageOutOfDate:
-      *out_compilation_filter = "run-from-apk-fallback";
-      *out_odex_status = "boot-image-more-recent";
-      return;
-
     case kOatContextOutOfDate:
-      *out_compilation_filter = "run-from-apk-fallback";
-      *out_odex_status = "context-mismatch";
+      // These should never happen, but be robust.
+      *out_compilation_filter = "unexpected";
+      *out_compilation_reason = "unexpected";
+      *out_odex_status = "unexpected";
       return;
 
     case kOatDexOutOfDate:
@@ -1123,4 +1378,13 @@
   UNREACHABLE();
 }
 
+bool OatFileAssistant::ZipFileOnlyContainsUncompressedDex() {
+  // zip_file_only_contains_uncompressed_dex_ is only set during fetching the dex checksums.
+  std::string error_msg;
+  if (GetRequiredDexChecksums(&error_msg) == nullptr) {
+    LOG(ERROR) << error_msg;
+  }
+  return zip_file_only_contains_uncompressed_dex_;
+}
+
 }  // namespace art
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index c243cc3..54287eb 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -19,16 +19,20 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <sstream>
 #include <string>
+#include <string_view>
+#include <variant>
 
-#include "base/compiler_filter.h"
 #include "arch/instruction_set.h"
+#include "base/compiler_filter.h"
 #include "base/os.h"
 #include "base/scoped_flock.h"
 #include "base/unix_file/fd_file.h"
 #include "class_loader_context.h"
 #include "oat_file.h"
+#include "oat_file_assistant_context.h"
 
 namespace art {
 
@@ -89,6 +93,48 @@
     kOatUpToDate,
   };
 
+  // A bit field to represent the conditions where dexopt should be performed.
+  struct DexOptTrigger {
+    // Dexopt should be performed if the target compiler filter is better than the current compiler
+    // filter. See `CompilerFilter::IsBetter`.
+    bool targetFilterIsBetter : 1;
+    // Dexopt should be performed if the target compiler filter is the same as the current compiler
+    // filter.
+    bool targetFilterIsSame : 1;
+    // Dexopt should be performed if the target compiler filter is worse than the current compiler
+    // filter. See `CompilerFilter::IsBetter`.
+    bool targetFilterIsWorse : 1;
+    // Dexopt should be performed if the current oat file was compiled without a primary image,
+    // and the runtime is now running with a primary image loaded from disk.
+    bool primaryBootImageBecomesUsable : 1;
+    // Dexopt should be performed if the APK is compressed and the current oat/vdex file doesn't
+    // contain dex code.
+    bool needExtraction : 1;
+  };
+
+  // Represents the location of the current oat file and/or vdex file.
+  enum Location {
+    // Does not exist, or an error occurs.
+    kLocationNoneOrError = 0,
+    // In the global "dalvik-cache" folder.
+    kLocationOat = 1,
+    // In the "oat" folder next to the dex file.
+    kLocationOdex = 2,
+    // In the DM file. This means the only usable file is the vdex file.
+    kLocationDm = 3,
+  };
+
+  // Represents the status of the current oat file and/or vdex file.
+  class DexOptStatus {
+   public:
+    Location GetLocation() { return location_; }
+    bool IsVdexUsable() { return location_ != kLocationNoneOrError; }
+
+   private:
+    Location location_ = kLocationNoneOrError;
+    friend class OatFileAssistant;
+  };
+
   // Constructs an OatFileAssistant object to assist the oat file
   // corresponding to the given dex location with the target instruction set.
   //
@@ -104,17 +150,23 @@
   // device. For example, on an arm device, use arm or arm64. An oat file can
   // be loaded executable only if the ISA matches the current runtime.
   //
+  // context should be the class loader context to check against, or null to skip the check.
+  //
   // load_executable should be true if the caller intends to try and load
   // executable code for this dex location.
   //
   // only_load_trusted_executable should be true if the caller intends to have
   // only oat files from trusted locations loaded executable. See IsTrustedLocation() for
   // details on trusted locations.
+  //
+  // runtime_options should be provided with all the required fields filled if the caller intends to
+  // use OatFileAssistant without a runtime.
   OatFileAssistant(const char* dex_location,
                    const InstructionSet isa,
                    ClassLoaderContext* context,
                    bool load_executable,
-                   bool only_load_trusted_executable = false);
+                   bool only_load_trusted_executable = false,
+                   OatFileAssistantContext* ofa_context = nullptr);
 
   // Similar to this(const char*, const InstructionSet, bool), however, if a valid zip_fd is
   // provided, vdex, oat, and zip files will be read from vdex_fd, oat_fd and zip_fd respectively.
@@ -124,10 +176,25 @@
                    ClassLoaderContext* context,
                    bool load_executable,
                    bool only_load_trusted_executable,
+                   OatFileAssistantContext* ofa_context,
                    int vdex_fd,
                    int oat_fd,
                    int zip_fd);
 
+  // A convenient factory function that accepts ISA, class loader context, and compiler filter in
+  // strings. Returns the created instance and ClassLoaderContext on success, or returns nullptr and
+  // outputs an error message if it fails to parse the input strings.
+  // The returned ClassLoaderContext must live at least as long as the OatFileAssistant.
+  static std::unique_ptr<OatFileAssistant> Create(
+      const std::string& filename,
+      const std::string& isa_str,
+      const std::optional<std::string>& context_str,
+      bool load_executable,
+      bool only_load_trusted_executable,
+      OatFileAssistantContext* ofa_context,
+      /*out*/ std::unique_ptr<ClassLoaderContext>* context,
+      /*out*/ std::string* error_msg);
+
   // Returns true if the dex location refers to an element of the boot class
   // path.
   bool IsInBootClassPath();
@@ -148,10 +215,18 @@
   // Returns a positive status code if the status refers to the oat file in
   // the oat location. Returns a negative status code if the status refers to
   // the oat file in the odex location.
+  //
+  // Deprecated. Use the other overload.
   int GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
                       bool profile_changed = false,
                       bool downgrade = false);
 
+  // Returns true if dexopt needs to be performed with respect to the given target compilation
+  // filter and dexopt trigger. Also returns the status of the current oat file and/or vdex file.
+  bool GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
+                       const DexOptTrigger dexopt_trigger,
+                       /*out*/ DexOptStatus* dexopt_status);
+
   // Returns true if there is up-to-date code for this dex location,
   // irrespective of the compiler filter of the up-to-date code.
   bool IsUpToDate();
@@ -176,7 +251,7 @@
   //   - out_compilation_reason: the optimization reason. The reason might
   //        be "unknown" if the compiler artifacts were not annotated during optimizations.
   //   - out_odex_status: a human readable refined status of the validity of the odex file.
-  //        E.g. up-to-date, boot-image-more-recent, apk-more-recent.
+  //        Possible values are: "up-to-date", "apk-more-recent", and "io-error-no-oat".
   //
   // This method will try to mimic the runtime effect of loading the dex file.
   // For example, if there is no usable oat file, the compiler filter will be set
@@ -189,7 +264,8 @@
   static void GetOptimizationStatus(const std::string& filename,
                                     InstructionSet isa,
                                     std::string* out_compilation_filter,
-                                    std::string* out_compilation_reason);
+                                    std::string* out_compilation_reason,
+                                    OatFileAssistantContext* ofa_context = nullptr);
 
   // Open and returns an image space associated with the oat file.
   static std::unique_ptr<gc::space::ImageSpace> OpenImageSpace(const OatFile* oat_file);
@@ -212,8 +288,8 @@
                            const std::string& dex_location,
                            std::vector<std::unique_ptr<const DexFile>>* out_dex_files);
 
-  // Returns whether this is an apk/zip wit a classes.dex entry.
-  bool HasDexFiles();
+  // Returns whether this is an apk/zip wit a classes.dex entry, or nullopt if an error occurred.
+  std::optional<bool> HasDexFiles(std::string* error_msg);
 
   // If the dex file has been installed with a compiled oat file alongside
   // it, the compiled oat file will have the extension .odex, and is referred
@@ -253,15 +329,28 @@
   // Returns false on error, in which case error_msg describes the error and
   // oat_filename is not changed.
   // Neither oat_filename nor error_msg may be null.
+  //
+  // Calling this function requires an active runtime.
   static bool DexLocationToOatFilename(const std::string& location,
                                        InstructionSet isa,
                                        std::string* oat_filename,
                                        std::string* error_msg);
 
+  // Same as above, but also takes `deny_art_apex_data_files` from input.
+  //
+  // Calling this function does not require an active runtime.
+  static bool DexLocationToOatFilename(const std::string& location,
+                                       InstructionSet isa,
+                                       bool deny_art_apex_data_files,
+                                       std::string* oat_filename,
+                                       std::string* error_msg);
+
   // Computes the dex location and vdex filename. If the data directory of the process
   // is known, creates an absolute path in that directory and tries to infer path
   // of a corresponding vdex file. Otherwise only creates a basename dex_location
   // from the combined checksums. Returns true if all out-arguments have been set.
+  //
+  // Calling this function requires an active runtime.
   static bool AnonymousDexVdexLocation(const std::vector<const DexFile::Header*>& dex_headers,
                                        InstructionSet isa,
                                        /* out */ std::string* dex_location,
@@ -273,6 +362,16 @@
 
   bool ClassLoaderContextIsOkay(const OatFile& oat_file) const;
 
+  // Validates the boot class path checksum of an OatFile.
+  bool ValidateBootClassPathChecksums(const OatFile& oat_file);
+
+  // Validates the given bootclasspath and bootclasspath checksums found in an oat header.
+  static bool ValidateBootClassPathChecksums(OatFileAssistantContext* ofa_context,
+                                             InstructionSet isa,
+                                             std::string_view oat_checksums,
+                                             std::string_view oat_boot_class_path,
+                                             /*out*/ std::string* error_msg);
+
  private:
   class OatFileInfo {
    public:
@@ -287,6 +386,8 @@
 
     const std::string* Filename();
 
+    const char* DisplayFilename();
+
     // Returns true if this oat file can be used for running code. The oat
     // file can be used for running code as long as it is not out of date with
     // respect to the dex code or boot image. An oat file that is out of date
@@ -298,15 +399,10 @@
     // Returns the status of this oat file.
     OatStatus Status();
 
-    // Return the DexOptNeeded value for this oat file with respect to the
-    // given target_compilation_filter.
-    // profile_changed should be true to indicate the profile has recently
-    // changed for this dex location.
-    // downgrade should be true if the purpose of dexopt is to downgrade the
-    // compiler filter.
+    // Return the DexOptNeeded value for this oat file with respect to the given target compilation
+    // filter and dexopt trigger.
     DexOptNeeded GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
-                                 bool profile_changed,
-                                 bool downgrade);
+                                 const DexOptTrigger dexopt_trigger);
 
     // Returns the loaded file.
     // Loads the file if needed. Returns null if the file failed to load.
@@ -338,14 +434,16 @@
     // the OatFileInfo object.
     std::unique_ptr<OatFile> ReleaseFileForUse();
 
+    // Check if we should reject vdex containing cdex code as part of the
+    // disable_cdex experiment.
+    // TODO(b/256664509): Clean this up.
+    bool CheckDisableCompactDexExperiment();
+
    private:
-    // Returns true if the compiler filter used to generate the file is at
-    // least as good as the given target filter. profile_changed should be
-    // true to indicate the profile has recently changed for this dex
-    // location.
-    // downgrade should be true if the purpose of dexopt is to downgrade the
-    // compiler filter.
-    bool CompilerFilterIsOkay(CompilerFilter::Filter target, bool profile_changed, bool downgrade);
+    // Returns true if the oat file is usable but at least one dexopt trigger is matched. This
+    // function should only be called if the oat file is usable.
+    bool ShouldRecompileForFilter(CompilerFilter::Filter target,
+                                  const DexOptTrigger dexopt_trigger);
 
     // Release the loaded oat file.
     // Returns null if the oat file hasn't been loaded.
@@ -387,11 +485,6 @@
   // files are being read.
   bool UseFdToReadFiles();
 
-  // Returns true if the dex checksums in the given vdex file are up to date
-  // with respect to the dex location. If the dex checksums are not up to
-  // date, error_msg is updated with a message describing the problem.
-  bool DexChecksumUpToDate(const VdexFile& file, std::string* error_msg);
-
   // Returns true if the dex checksums in the given oat file are up to date
   // with respect to the dex location. If the dex checksums are not up to
   // date, error_msg is updated with a message describing the problem.
@@ -402,18 +495,45 @@
   OatStatus GivenOatFileStatus(const OatFile& file);
 
   // Gets the dex checksums required for an up-to-date oat file.
-  // Returns cached_required_dex_checksums if the required checksums were
-  // located. Returns null if the required checksums were not found.  The
-  // caller shouldn't clean up or free the returned pointer.  This sets the
-  // has_original_dex_files_ field to true if the checksums were found for the
-  // dex_location_ dex file.
-  const std::vector<uint32_t>* GetRequiredDexChecksums();
+  // Returns cached_required_dex_checksums if the required checksums were located. Returns an empty
+  // list if `dex_location_` refers to a zip and there is no dex file in it. Returns nullptr if an
+  // error occurred. The caller shouldn't clean up or free the returned pointer.
+  const std::vector<uint32_t>* GetRequiredDexChecksums(std::string* error_msg);
 
-  // Validates the boot class path checksum of an OatFile.
-  bool ValidateBootClassPathChecksums(const OatFile& oat_file);
+  // Returns whether there is at least one boot image usable.
+  bool IsPrimaryBootImageUsable();
+
+  // Returns the trigger for the deprecated overload of `GetDexOptNeeded`.
+  //
+  // Deprecated. Do not use in new code.
+  DexOptTrigger GetDexOptTrigger(CompilerFilter::Filter target_compiler_filter,
+                                 bool profile_changed,
+                                 bool downgrade);
+
+  // Returns the pointer to the owned or unowned instance of OatFileAssistantContext.
+  OatFileAssistantContext* GetOatFileAssistantContext() {
+    if (std::holds_alternative<OatFileAssistantContext*>(ofa_context_)) {
+      return std::get<OatFileAssistantContext*>(ofa_context_);
+    } else {
+      return std::get<std::unique_ptr<OatFileAssistantContext>>(ofa_context_).get();
+    }
+  }
+
+  // The runtime options taken from the active runtime or the input.
+  //
+  // All member functions should get runtime options from this variable rather than referencing the
+  // active runtime. This is to allow OatFileAssistant to function without an active runtime.
+  const OatFileAssistantContext::RuntimeOptions& GetRuntimeOptions() {
+    return GetOatFileAssistantContext()->GetRuntimeOptions();
+  }
+
+  // Returns whether the zip file only contains uncompressed dex.
+  bool ZipFileOnlyContainsUncompressedDex();
 
   std::string dex_location_;
 
+  // The class loader context to check against, or null representing that the check should be
+  // skipped.
   ClassLoaderContext* context_;
 
   // Whether or not the parent directory of the dex file is writable.
@@ -428,16 +548,16 @@
 
   // Whether only oat files from trusted locations are loaded executable.
   const bool only_load_trusted_executable_ = false;
-  // Whether the potential zip file only contains uncompressed dex.
-  // Will be set during GetRequiredDexChecksums.
+
+  // Cached value of whether the potential zip file only contains uncompressed dex.
+  // This should be accessed only by the ZipFileOnlyContainsUncompressedDex() method.
   bool zip_file_only_contains_uncompressed_dex_ = true;
 
   // Cached value of the required dex checksums.
   // This should be accessed only by the GetRequiredDexChecksums() method.
-  std::vector<uint32_t> cached_required_dex_checksums_;
+  std::optional<std::vector<uint32_t>> cached_required_dex_checksums_;
+  std::string cached_required_dex_checksums_error_;
   bool required_dex_checksums_attempted_ = false;
-  bool required_dex_checksums_found_;
-  bool has_original_dex_files_;
 
   // The AOT-compiled file of an app when the APK of the app is in /data.
   OatFileInfo odex_;
@@ -459,8 +579,8 @@
   // File descriptor corresponding to apk, dex file, or zip.
   int zip_fd_;
 
-  std::string cached_boot_class_path_;
-  std::string cached_boot_class_path_checksums_;
+  // Owned or unowned instance of OatFileAssistantContext.
+  std::variant<std::unique_ptr<OatFileAssistantContext>, OatFileAssistantContext*> ofa_context_;
 
   friend class OatFileAssistantTest;
 
diff --git a/runtime/oat_file_assistant_context.cc b/runtime/oat_file_assistant_context.cc
new file mode 100644
index 0000000..c3fb73d
--- /dev/null
+++ b/runtime/oat_file_assistant_context.cc
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "oat_file_assistant_context.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "arch/instruction_set.h"
+#include "base/array_ref.h"
+#include "base/logging.h"
+#include "base/mem_map.h"
+#include "class_linker.h"
+#include "dex/art_dex_file_loader.h"
+#include "gc/heap.h"
+#include "gc/space/image_space.h"
+
+namespace art {
+
+using ::android::base::StringPrintf;
+using ::art::gc::space::ImageSpace;
+
+OatFileAssistantContext::OatFileAssistantContext(
+    std::unique_ptr<OatFileAssistantContext::RuntimeOptions> runtime_options)
+    : runtime_options_(std::move(runtime_options)) {
+  DCHECK_EQ(runtime_options_->boot_class_path.size(),
+            runtime_options_->boot_class_path_locations.size());
+  DCHECK_IMPLIES(
+      runtime_options_->boot_class_path_fds != nullptr,
+      runtime_options_->boot_class_path.size() == runtime_options_->boot_class_path_fds->size());
+  // Opening dex files and boot images require MemMap.
+  MemMap::Init();
+}
+
+OatFileAssistantContext::OatFileAssistantContext(Runtime* runtime)
+    : OatFileAssistantContext(std::make_unique<OatFileAssistantContext::RuntimeOptions>(
+          OatFileAssistantContext::RuntimeOptions{
+              .image_locations = runtime->GetImageLocations(),
+              .boot_class_path = runtime->GetBootClassPath(),
+              .boot_class_path_locations = runtime->GetBootClassPathLocations(),
+              .boot_class_path_fds = !runtime->GetBootClassPathFds().empty() ?
+                                         &runtime->GetBootClassPathFds() :
+                                         nullptr,
+              .deny_art_apex_data_files = runtime->DenyArtApexDataFiles(),
+          })) {
+  // Fetch boot image info from the runtime.
+  std::vector<BootImageInfo>& boot_image_info_list = boot_image_info_list_by_isa_[kRuntimeISA];
+  for (const ImageSpace* image_space : runtime->GetHeap()->GetBootImageSpaces()) {
+    // We only need the checksum of the first component for each boot image. They are in image
+    // spaces that have a non-zero component count.
+    if (image_space->GetComponentCount() > 0) {
+      BootImageInfo& boot_image_info = boot_image_info_list.emplace_back();
+      boot_image_info.component_count = image_space->GetComponentCount();
+      ImageSpace::AppendImageChecksum(image_space->GetComponentCount(),
+                                      image_space->GetImageHeader().GetImageChecksum(),
+                                      &boot_image_info.checksum);
+    }
+  }
+
+  // Fetch BCP checksums from the runtime.
+  size_t bcp_index = 0;
+  std::vector<std::string>* current_bcp_checksums = nullptr;
+  for (const DexFile* dex_file : runtime->GetClassLinker()->GetBootClassPath()) {
+    if (!DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str())) {
+      DCHECK_LT(bcp_index, runtime_options_->boot_class_path.size());
+      current_bcp_checksums = &bcp_checksums_by_index_[bcp_index++];
+    }
+    DCHECK_NE(current_bcp_checksums, nullptr);
+    current_bcp_checksums->push_back(StringPrintf("/%08x", dex_file->GetLocationChecksum()));
+  }
+  DCHECK_EQ(bcp_index, runtime_options_->boot_class_path.size());
+
+  // Fetch APEX versions from the runtime.
+  apex_versions_ = runtime->GetApexVersions();
+}
+
+const OatFileAssistantContext::RuntimeOptions& OatFileAssistantContext::GetRuntimeOptions() const {
+  return *runtime_options_;
+}
+
+bool OatFileAssistantContext::FetchAll(std::string* error_msg) {
+  std::vector<InstructionSet> isas = GetSupportedInstructionSets(error_msg);
+  if (isas.empty()) {
+    return false;
+  }
+  for (InstructionSet isa : isas) {
+    GetBootImageInfoList(isa);
+  }
+  for (size_t i = 0; i < runtime_options_->boot_class_path.size(); i++) {
+    if (GetBcpChecksums(i, error_msg) == nullptr) {
+      return false;
+    }
+  }
+  GetApexVersions();
+  return true;
+}
+
+const std::vector<OatFileAssistantContext::BootImageInfo>&
+OatFileAssistantContext::GetBootImageInfoList(InstructionSet isa) {
+  if (auto it = boot_image_info_list_by_isa_.find(isa); it != boot_image_info_list_by_isa_.end()) {
+    return it->second;
+  }
+
+  ImageSpace::BootImageLayout layout(
+      ArrayRef<const std::string>(runtime_options_->image_locations),
+      ArrayRef<const std::string>(runtime_options_->boot_class_path),
+      ArrayRef<const std::string>(runtime_options_->boot_class_path_locations),
+      runtime_options_->boot_class_path_fds != nullptr ?
+          ArrayRef<const int>(*runtime_options_->boot_class_path_fds) :
+          ArrayRef<const int>(),
+      /*boot_class_path_image_fds=*/ArrayRef<const int>(),
+      /*boot_class_path_vdex_fds=*/ArrayRef<const int>(),
+      /*boot_class_path_oat_fds=*/ArrayRef<const int>(),
+      &GetApexVersions());
+
+  std::string error_msg;
+  if (!layout.LoadFromSystem(isa, /*allow_in_memory_compilation=*/false, &error_msg)) {
+    // At this point, `layout` contains nothing.
+    VLOG(oat) << "Some error occurred when loading boot images for oat file validation: "
+              << error_msg;
+    // Create an empty entry so that we don't have to retry when the function is called again.
+    return boot_image_info_list_by_isa_[isa];
+  }
+
+  std::vector<BootImageInfo>& boot_image_info_list = boot_image_info_list_by_isa_[isa];
+  for (const ImageSpace::BootImageLayout::ImageChunk& chunk : layout.GetChunks()) {
+    BootImageInfo& boot_image_info = boot_image_info_list.emplace_back();
+    boot_image_info.component_count = chunk.component_count;
+    ImageSpace::AppendImageChecksum(
+        chunk.component_count, chunk.checksum, &boot_image_info.checksum);
+  }
+  return boot_image_info_list;
+}
+
+const std::vector<std::string>* OatFileAssistantContext::GetBcpChecksums(size_t bcp_index,
+                                                                         std::string* error_msg) {
+  DCHECK_LT(bcp_index, runtime_options_->boot_class_path.size());
+
+  if (auto it = bcp_checksums_by_index_.find(bcp_index); it != bcp_checksums_by_index_.end()) {
+    return &it->second;
+  }
+
+  std::vector<uint32_t> checksums;
+  std::vector<std::string> dex_locations;
+  if (!ArtDexFileLoader::GetMultiDexChecksums(
+          runtime_options_->boot_class_path[bcp_index].c_str(),
+          &checksums,
+          &dex_locations,
+          error_msg,
+          runtime_options_->boot_class_path_fds != nullptr ?
+              (*runtime_options_->boot_class_path_fds)[bcp_index] :
+              -1)) {
+    return nullptr;
+  }
+
+  DCHECK(!checksums.empty());
+  std::vector<std::string>& bcp_checksums = bcp_checksums_by_index_[bcp_index];
+  for (uint32_t checksum : checksums) {
+    bcp_checksums.push_back(StringPrintf("/%08x", checksum));
+  }
+  return &bcp_checksums;
+}
+
+const std::string& OatFileAssistantContext::GetApexVersions() {
+  if (apex_versions_.has_value()) {
+    return apex_versions_.value();
+  }
+
+  apex_versions_ = Runtime::GetApexVersions(
+      ArrayRef<const std::string>(runtime_options_->boot_class_path_locations));
+  return apex_versions_.value();
+}
+
+}  // namespace art
diff --git a/runtime/oat_file_assistant_context.h b/runtime/oat_file_assistant_context.h
new file mode 100644
index 0000000..cc98c59
--- /dev/null
+++ b/runtime/oat_file_assistant_context.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 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_RUNTIME_OAT_FILE_ASSISTANT_CONTEXT_H_
+#define ART_RUNTIME_OAT_FILE_ASSISTANT_CONTEXT_H_
+
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "arch/instruction_set.h"
+#include "runtime.h"
+
+namespace art {
+
+// A helper class for OatFileAssistant that fetches and caches information including boot image
+// checksums, bootclasspath checksums, and APEX versions. The same instance can be reused across
+// OatFileAssistant calls on different dex files for different instruction sets.
+// This class is not thread-safe until `FetchAll` is called.
+class OatFileAssistantContext {
+ public:
+  // Options that a runtime would take.
+  // Note that the struct only keeps references, so the caller must keep the objects alive during
+  // the lifetime of OatFileAssistant.
+  struct RuntimeOptions {
+    // Required. See `-Ximage`.
+    const std::vector<std::string>& image_locations;
+    // Required. See `-Xbootclasspath`.
+    const std::vector<std::string>& boot_class_path;
+    // Required. See `-Xbootclasspath-locations`.
+    const std::vector<std::string>& boot_class_path_locations;
+    // Optional. See `-Xbootclasspathfds`.
+    const std::vector<int>* const boot_class_path_fds = nullptr;
+    // Optional. See `-Xdeny-art-apex-data-files`.
+    const bool deny_art_apex_data_files = false;
+  };
+
+  // Information about a boot image.
+  struct BootImageInfo {
+    // Number of BCP jars covered by the boot image.
+    size_t component_count;
+    // Checksum of the boot image. The format is "i;<component_count>/<checksum_in_8_digit_hex>"
+    std::string checksum;
+  };
+
+  // Constructs OatFileAssistantContext from runtime options. Does not fetch information on
+  // construction. Information will be fetched from disk when needed.
+  explicit OatFileAssistantContext(std::unique_ptr<RuntimeOptions> runtime_options);
+  // Constructs OatFileAssistantContext from a runtime instance. Fetches as much information as
+  // possible from the runtime. The rest information will be fetched from disk when needed.
+  explicit OatFileAssistantContext(Runtime* runtime);
+  // Returns runtime options.
+  const RuntimeOptions& GetRuntimeOptions() const;
+  // Fetches all information that hasn't been fetched from disk and caches it. All operations will
+  // be read-only after a successful call to this function.
+  bool FetchAll(std::string* error_msg);
+  // Returns information about the boot image of the given instruction set.
+  const std::vector<BootImageInfo>& GetBootImageInfoList(InstructionSet isa);
+  // Returns the checksums of the dex files in the BCP jar at the given index, or nullptr on error.
+  // The format of each checksum is "/<checksum_in_8_digit_hex>".
+  const std::vector<std::string>* GetBcpChecksums(size_t bcp_index, std::string* error_msg);
+  // Returns a string that represents the apex versions of boot classpath jars. See
+  // `Runtime::apex_versions_` for the encoding format.
+  const std::string& GetApexVersions();
+
+ private:
+  std::unique_ptr<RuntimeOptions> runtime_options_;
+  std::unordered_map<InstructionSet, std::vector<BootImageInfo>> boot_image_info_list_by_isa_;
+  std::unordered_map<size_t, std::vector<std::string>> bcp_checksums_by_index_;
+  std::optional<std::string> apex_versions_;
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_OAT_FILE_ASSISTANT_CONTEXT_H_
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 07998dd..56d4c70 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -16,97 +16,155 @@
 
 #include "oat_file_assistant.h"
 
+#include <fcntl.h>
+#include <gtest/gtest.h>
 #include <sys/param.h>
 
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <optional>
 #include <string>
+#include <type_traits>
 #include <vector>
-#include <fcntl.h>
 
-#include <gtest/gtest.h>
-
+#include "android-base/scopeguard.h"
 #include "android-base/strings.h"
-
+#include "arch/instruction_set.h"
 #include "art_field-inl.h"
 #include "base/os.h"
 #include "base/utils.h"
-#include "class_linker-inl.h"
+#include "class_linker.h"
 #include "class_loader_context.h"
 #include "common_runtime_test.h"
 #include "dexopt_test.h"
-#include "hidden_api.h"
 #include "oat.h"
 #include "oat_file.h"
+#include "oat_file_assistant_context.h"
 #include "oat_file_manager.h"
-#include "scoped_thread_state_change-inl.h"
-#include "thread-current-inl.h"
+#include "scoped_thread_state_change.h"
+#include "thread.h"
 
 namespace art {
 
-class OatFileAssistantTest : public DexoptTest {
+class OatFileAssistantBaseTest : public DexoptTest {};
+
+class OatFileAssistantTest : public OatFileAssistantBaseTest,
+                             public testing::WithParamInterface<bool> {
  public:
-  void VerifyOptimizationStatus(OatFileAssistant* assistant,
-                                const std::string& file,
-                                const std::string& expected_filter,
+  void SetUp() override {
+    DexoptTest::SetUp();
+    with_runtime_ = GetParam();
+    ofa_context_ = CreateOatFileAssistantContext();
+  }
+
+  // Verifies all variants of `GetOptimizationStatus`.
+  //
+  // `expected_filter` can be either a value of `CompilerFilter::Filter` or a string.
+  // If `check_context` is true, only verifies the variants that checks class loader context.
+  template <typename T>
+  void VerifyOptimizationStatus(const std::string& file,
+                                ClassLoaderContext* context,
+                                const T& expected_filter,
                                 const std::string& expected_reason,
-                                const std::string& expected_odex_status) {
-    // Verify the static methods (called from PM for dexOptNeeded).
-    std::string compilation_filter1;
-    std::string compilation_reason1;
+                                const std::string& expected_odex_status,
+                                bool check_context = false) {
+    std::string expected_filter_name;
+    if constexpr (std::is_same_v<T, CompilerFilter::Filter>) {
+      expected_filter_name = CompilerFilter::NameOfFilter(expected_filter);
+    } else {
+      expected_filter_name = expected_filter;
+    }
 
-    OatFileAssistant::GetOptimizationStatus(
-        file, kRuntimeISA, &compilation_filter1, &compilation_reason1);
+    // Verify the static method (called from PM for dumpsys).
+    // This variant does not check class loader context.
+    if (!check_context) {
+      std::string compilation_filter;
+      std::string compilation_reason;
 
-    ASSERT_EQ(expected_filter, compilation_filter1);
-    ASSERT_EQ(expected_reason, compilation_reason1);
+      OatFileAssistant::GetOptimizationStatus(file,
+                                              kRuntimeISA,
+                                              &compilation_filter,
+                                              &compilation_reason,
+                                              MaybeGetOatFileAssistantContext());
 
-    // Verify the instance methods (called at runtime for systrace).
-    std::string odex_location2;  // ignored
-    std::string compilation_filter2;
-    std::string compilation_reason2;
-    std::string odex_status2;
+      ASSERT_EQ(expected_filter_name, compilation_filter);
+      ASSERT_EQ(expected_reason, compilation_reason);
+    }
+
+    // Verify the instance methods (called at runtime and from artd).
+    OatFileAssistant assistant = CreateOatFileAssistant(file.c_str(), context);
+    VerifyOptimizationStatusWithInstance(
+        &assistant, expected_filter_name, expected_reason, expected_odex_status);
+  }
+
+  void VerifyOptimizationStatusWithInstance(OatFileAssistant* assistant,
+                                            const std::string& expected_filter,
+                                            const std::string& expected_reason,
+                                            const std::string& expected_odex_status) {
+    std::string odex_location;  // ignored
+    std::string compilation_filter;
+    std::string compilation_reason;
+    std::string odex_status;
 
     assistant->GetOptimizationStatus(
-        &odex_location2,
-        &compilation_filter2,
-        &compilation_reason2,
-        &odex_status2);
+        &odex_location, &compilation_filter, &compilation_reason, &odex_status);
 
-    ASSERT_EQ(expected_filter, compilation_filter2);
-    ASSERT_EQ(expected_reason, compilation_reason2);
-    ASSERT_EQ(expected_odex_status, odex_status2);
+    ASSERT_EQ(expected_filter, compilation_filter);
+    ASSERT_EQ(expected_reason, compilation_reason);
+    ASSERT_EQ(expected_odex_status, odex_status);
   }
 
-  void VerifyOptimizationStatus(OatFileAssistant* assistant,
-                                const std::string& file,
-                                CompilerFilter::Filter expected_filter,
-                                const std::string& expected_reason,
-                                const std::string& expected_odex_status) {
-      VerifyOptimizationStatus(
-          assistant,
-          file,
-          CompilerFilter::NameOfFilter(expected_filter),
-          expected_reason,
-          expected_odex_status);
-  }
-
-  void InsertNewBootClasspathEntry() {
-    std::string extra_dex_filename = GetMultiDexSrc1();
-    Runtime* runtime = Runtime::Current();
-    runtime->boot_class_path_.push_back(extra_dex_filename);
-    if (!runtime->boot_class_path_locations_.empty()) {
-      runtime->boot_class_path_locations_.push_back(extra_dex_filename);
+  bool InsertNewBootClasspathEntry(const std::string& src, std::string* error_msg) {
+    std::vector<std::unique_ptr<const DexFile>> dex_files;
+    ArtDexFileLoader dex_file_loader(src);
+    if (!dex_file_loader.Open(/*verify=*/true,
+                              /*verify_checksum=*/false,
+                              error_msg,
+                              &dex_files)) {
+      return false;
     }
+
+    runtime_->AppendToBootClassPath(src, src, dex_files);
+    std::move(dex_files.begin(), dex_files.end(), std::back_inserter(opened_dex_files_));
+
+    return true;
   }
 
-  int GetDexOptNeeded(
-      OatFileAssistant* assistant,
-      CompilerFilter::Filter compiler_filter,
-      bool profile_changed = false,
-      bool downgrade = false) {
-    return assistant->GetDexOptNeeded(
-        compiler_filter,
-        profile_changed,
-        downgrade);
+  // Verifies the current version of `GetDexOptNeeded` (called from artd).
+  void VerifyGetDexOptNeeded(OatFileAssistant* assistant,
+                             CompilerFilter::Filter compiler_filter,
+                             OatFileAssistant::DexOptTrigger dexopt_trigger,
+                             bool expected_dexopt_needed,
+                             bool expected_is_vdex_usable,
+                             OatFileAssistant::Location expected_location) {
+    OatFileAssistant::DexOptStatus status;
+    EXPECT_EQ(
+        assistant->GetDexOptNeeded(compiler_filter, dexopt_trigger, &status),
+        expected_dexopt_needed);
+    EXPECT_EQ(status.IsVdexUsable(), expected_is_vdex_usable);
+    EXPECT_EQ(status.GetLocation(), expected_location);
+  }
+
+  // Verifies all versions of `GetDexOptNeeded` with the default dexopt trigger.
+  void VerifyGetDexOptNeededDefault(OatFileAssistant* assistant,
+                                    CompilerFilter::Filter compiler_filter,
+                                    bool expected_dexopt_needed,
+                                    bool expected_is_vdex_usable,
+                                    OatFileAssistant::Location expected_location,
+                                    int expected_legacy_result) {
+    // Verify the current version (called from artd).
+    VerifyGetDexOptNeeded(assistant,
+                          compiler_filter,
+                          default_trigger_,
+                          expected_dexopt_needed,
+                          expected_is_vdex_usable,
+                          expected_location);
+
+    // Verify the legacy version (called from PM).
+    EXPECT_EQ(
+        assistant->GetDexOptNeeded(compiler_filter, /*profile_changed=*/false, /*downgrade=*/false),
+        expected_legacy_result);
   }
 
   static std::unique_ptr<ClassLoaderContext> InitializeDefaultContext() {
@@ -115,7 +173,65 @@
     return context;
   }
 
+  // Temporarily disables the pointer to the current runtime if `with_runtime_` is false.
+  // Essentially simulates an environment where there is no active runtime.
+  android::base::ScopeGuard<std::function<void()>> ScopedMaybeWithoutRuntime() {
+    if (!with_runtime_) {
+      Runtime::TestOnlySetCurrent(nullptr);
+    }
+    return android::base::make_scope_guard(
+        [this]() { Runtime::TestOnlySetCurrent(runtime_.get()); });
+  }
+
+  std::unique_ptr<OatFileAssistantContext> CreateOatFileAssistantContext() {
+    return std::make_unique<OatFileAssistantContext>(
+        std::make_unique<OatFileAssistantContext::RuntimeOptions>(
+            OatFileAssistantContext::RuntimeOptions{
+                .image_locations = runtime_->GetImageLocations(),
+                .boot_class_path = runtime_->GetBootClassPath(),
+                .boot_class_path_locations = runtime_->GetBootClassPathLocations(),
+                .boot_class_path_fds = !runtime_->GetBootClassPathFds().empty() ?
+                                           &runtime_->GetBootClassPathFds() :
+                                           nullptr,
+                .deny_art_apex_data_files = runtime_->DenyArtApexDataFiles(),
+            }));
+  }
+
+  OatFileAssistantContext* MaybeGetOatFileAssistantContext() {
+    return with_runtime_ ? nullptr : ofa_context_.get();
+  }
+
+  // A helper function to create OatFileAssistant with some default arguments.
+  OatFileAssistant CreateOatFileAssistant(const char* dex_location,
+                                          ClassLoaderContext* context = nullptr,
+                                          bool load_executable = false,
+                                          int vdex_fd = -1,
+                                          int oat_fd = -1,
+                                          int zip_fd = -1) {
+    return OatFileAssistant(dex_location,
+                            kRuntimeISA,
+                            context != nullptr ? context : default_context_.get(),
+                            load_executable,
+                            /*only_load_trusted_executable=*/false,
+                            MaybeGetOatFileAssistantContext(),
+                            vdex_fd,
+                            oat_fd,
+                            zip_fd);
+  }
+
+  void ExpectHasDexFiles(OatFileAssistant* oat_file_assistant, bool expected_value) {
+    std::string error_msg;
+    std::optional<bool> has_dex_files = oat_file_assistant->HasDexFiles(&error_msg);
+    ASSERT_TRUE(has_dex_files.has_value()) << error_msg;
+    EXPECT_EQ(*has_dex_files, expected_value);
+  }
+
   std::unique_ptr<ClassLoaderContext> default_context_ = InitializeDefaultContext();
+  bool with_runtime_;
+  const OatFileAssistant::DexOptTrigger default_trigger_{
+      .targetFilterIsBetter = true, .primaryBootImageBecomesUsable = true, .needExtraction = true};
+  std::unique_ptr<OatFileAssistantContext> ofa_context_;
+  std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
 };
 
 class ScopedNonWritable {
@@ -154,7 +270,7 @@
 // Case: We have a MultiDEX file and up-to-date ODEX file for it with relative
 // encoded dex locations.
 // Expect: The oat file status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, RelativeEncodedDexLocation) {
+TEST_P(OatFileAssistantTest, RelativeEncodedDexLocation) {
   std::string dex_location = GetScratchDir() + "/RelativeEncodedDexLocation.jar";
   std::string odex_location = GetOdexDir() + "/RelativeEncodedDexLocation.odex";
 
@@ -172,21 +288,24 @@
   std::string error_msg;
   ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
 
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
   // Verify we can load both dex files.
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/true);
 
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   ASSERT_TRUE(oat_file.get() != nullptr);
-  EXPECT_TRUE(oat_file->IsExecutable());
+  if (with_runtime_) {
+    EXPECT_TRUE(oat_file->IsExecutable());
+  }
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
   EXPECT_EQ(2u, dex_files.size());
 }
 
-TEST_F(OatFileAssistantTest, MakeUpToDateWithContext) {
+TEST_P(OatFileAssistantTest, MakeUpToDateWithContext) {
   std::string dex_location = GetScratchDir() + "/TestDex.jar";
   std::string odex_location = GetOdexDir() + "/TestDex.odex";
   std::string context_location = GetScratchDir() + "/ContextDex.jar";
@@ -198,8 +317,6 @@
   ASSERT_TRUE(context != nullptr);
   ASSERT_TRUE(context->OpenDexFiles());
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, context.get(), false);
-
   std::string error_msg;
   std::vector<std::string> args;
   args.push_back("--dex-file=" + dex_location);
@@ -207,6 +324,10 @@
   args.push_back("--class-loader-context=" + context_str);
   ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
 
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(), context.get());
+
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   ASSERT_NE(nullptr, oat_file.get());
   ASSERT_NE(nullptr, oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
@@ -214,7 +335,7 @@
             oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
 }
 
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) {
   std::string dex_location = GetScratchDir() + "/TestDex.jar";
   std::string odex_location = GetOdexDir() + "/TestDex.odex";
   std::string context_location = GetScratchDir() + "/ContextDex.jar";
@@ -228,11 +349,6 @@
   std::vector<int> context_fds;
   ASSERT_TRUE(relative_context->OpenDexFiles(GetScratchDir(), context_fds));
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      relative_context.get(),
-                                      false);
-
   std::string error_msg;
   std::vector<std::string> args;
   args.push_back("--dex-file=" + dex_location);
@@ -240,143 +356,173 @@
   args.push_back("--class-loader-context=PCL[" + context_location + "]");
   ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
 
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-            GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kDefaultCompilerFilter));
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant =
+      CreateOatFileAssistant(dex_location.c_str(), relative_context.get());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kDefaultCompilerFilter,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
 }
 
 // Case: We have a DEX file, but no OAT file for it.
 // Expect: The status is kDex2OatNeeded.
-TEST_F(OatFileAssistantTest, DexNoOat) {
+TEST_P(OatFileAssistantTest, DexNoOat) {
   std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
   Copy(GetDexSrc1(), dex_location);
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeedProfile));
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kVerify,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeedProfile,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 
   VerifyOptimizationStatus(
-      &oat_file_assistant,
-      dex_location,
-      "run-from-apk",
-      "unknown",
-      "io-error-no-oat");
+      dex_location, default_context_.get(), "run-from-apk", "unknown", "io-error-no-oat");
 }
 
 // Case: We have no DEX file and no OAT file.
 // Expect: Status is kNoDexOptNeeded. Loading should fail, but not crash.
-TEST_F(OatFileAssistantTest, NoDexNoOat) {
+TEST_P(OatFileAssistantTest, NoDexNoOat) {
   std::string dex_location = GetScratchDir() + "/NoDexNoOat.jar";
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_FALSE(oat_file_assistant.HasDexFiles());
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+  std::string error_msg_ignored;
+  EXPECT_FALSE(oat_file_assistant.HasDexFiles(&error_msg_ignored).has_value());
 
   // Trying to get the best oat file should fail, but not crash.
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   EXPECT_EQ(nullptr, oat_file.get());
+
+  VerifyOptimizationStatusWithInstance(
+      &oat_file_assistant, "unknown", "unknown", "io-error-no-apk");
 }
 
 // Case: We have a DEX file and an ODEX file, but no OAT file.
 // Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, OdexUpToDate) {
+TEST_P(OatFileAssistantTest, OdexUpToDate) {
   std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
   std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
   Copy(GetDexSrc1(), dex_location);
   GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
 
-  // Force the use of oat location by making the dex parent not writable.
-  OatFileAssistant oat_file_assistant(
-      dex_location.c_str(),
-      kRuntimeISA,
-      default_context_.get(),
-      /*load_executable=*/ false);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-            GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-            GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-            GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
-            GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+  // Force the use of oat location by making the dex parent not writable.
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kVerify,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kEverything,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 
   VerifyOptimizationStatus(
-      &oat_file_assistant,
-      dex_location,
-      CompilerFilter::kSpeed,
-      "install",
-      "up-to-date");
+      dex_location, default_context_.get(), CompilerFilter::kSpeed, "install", "up-to-date");
 }
 
 // Case: We have an ODEX file compiled against partial boot image.
 // Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, OdexUpToDatePartialBootImage) {
+TEST_P(OatFileAssistantTest, OdexUpToDatePartialBootImage) {
   std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
   std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
   Copy(GetDexSrc1(), dex_location);
   GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
 
   // Insert an extra dex file to the boot class path.
-  InsertNewBootClasspathEntry();
+  std::string error_msg;
+  ASSERT_TRUE(InsertNewBootClasspathEntry(GetMultiDexSrc1(), &error_msg)) << error_msg;
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
   // Force the use of oat location by making the dex parent not writable.
-  OatFileAssistant oat_file_assistant(
-      dex_location.c_str(),
-      kRuntimeISA,
-      default_context_.get(),
-      /*load_executable=*/ false);
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
 
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-            GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-            GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-            GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
-            GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kVerify,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kEverything,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 
   VerifyOptimizationStatus(
-      &oat_file_assistant,
-      dex_location,
-      CompilerFilter::kSpeed,
-      "install",
-      "up-to-date");
+      dex_location, default_context_.get(), CompilerFilter::kSpeed, "install", "up-to-date");
 }
 
 // Case: We have a DEX file and a PIC ODEX file, but no OAT file. We load the dex
 // file via a symlink.
 // Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, OdexUpToDateSymLink) {
+TEST_P(OatFileAssistantTest, OdexUpToDateSymLink) {
   std::string scratch_dir = GetScratchDir();
   std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
   std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
@@ -389,29 +535,38 @@
   ASSERT_EQ(0, symlink(scratch_dir.c_str(), link.c_str()));
   dex_location = link + "/OdexUpToDate.jar";
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kVerify,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kEverything,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 }
 
 // Case: We have a DEX file and up-to-date OAT file for it.
 // Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, OatUpToDate) {
+TEST_P(OatFileAssistantTest, OatUpToDate) {
   if (IsExecutedAsRoot()) {
     // We cannot simulate non writable locations when executed as root: b/38000545.
     LOG(ERROR) << "Test skipped because it's running as root";
@@ -426,43 +581,48 @@
   ScopedNonWritable scoped_non_writable(dex_location);
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOat,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kVerify,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOat,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kEverything,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOat,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatForFilter);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 
   VerifyOptimizationStatus(
-      &oat_file_assistant,
-      dex_location,
-      CompilerFilter::kSpeed,
-      "unknown",
-      "up-to-date");
+      dex_location, default_context_.get(), CompilerFilter::kSpeed, "unknown", "up-to-date");
 }
 
 // Case: Passing valid file descriptors of updated odex/vdex files along with the dex file.
 // Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithFd) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithFd) {
   std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
   std::string odex_location = GetScratchDir() + "/OatUpToDate.odex";
   std::string vdex_location = GetScratchDir() + "/OatUpToDate.vdex";
 
   Copy(GetDexSrc1(), dex_location);
-  GenerateOatForTest(dex_location.c_str(),
-                     odex_location.c_str(),
+  GenerateOatForTest(dex_location,
+                     odex_location,
                      CompilerFilter::kSpeed,
                      /* with_alternate_image= */ false);
 
@@ -470,123 +630,154 @@
   android::base::unique_fd vdex_fd(open(vdex_location.c_str(), O_RDONLY | O_CLOEXEC));
   android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false,
-                                      false,
-                                      vdex_fd.get(),
-                                      odex_fd.get(),
-                                      zip_fd.get());
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/false,
+                                                               vdex_fd.get(),
+                                                               odex_fd.get(),
+                                                               zip_fd.get());
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kVerify,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kEverything,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 }
 
 // Case: Passing invalid odex fd and valid vdex and zip fds.
 // Expect: The status should be kDex2OatForBootImage.
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexFd) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexFd) {
   std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
   std::string odex_location = GetScratchDir() + "/OatUpToDate.odex";
   std::string vdex_location = GetScratchDir() + "/OatUpToDate.vdex";
 
   Copy(GetDexSrc1(), dex_location);
-  GenerateOatForTest(dex_location.c_str(),
-                     odex_location.c_str(),
+  GenerateOatForTest(dex_location,
+                     odex_location,
                      CompilerFilter::kSpeed,
                      /* with_alternate_image= */ false);
 
   android::base::unique_fd vdex_fd(open(vdex_location.c_str(), O_RDONLY | O_CLOEXEC));
   android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false,
-                                      false,
-                                      vdex_fd.get(),
-                                      /* oat_fd= */ -1,
-                                      zip_fd.get());
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/false,
+                                                               vdex_fd.get(),
+                                                               /*oat_fd=*/-1,
+                                                               zip_fd.get());
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kVerify,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kEverything,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 }
 
 // Case: Passing invalid vdex fd and valid odex and zip fds.
 // Expect: The status should be kDex2OatFromScratch.
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidVdexFd) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithInvalidVdexFd) {
   std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
   std::string odex_location = GetScratchDir() + "/OatUpToDate.odex";
 
   Copy(GetDexSrc1(), dex_location);
-  GenerateOatForTest(dex_location.c_str(),
-                     odex_location.c_str(),
+  GenerateOatForTest(dex_location,
+                     odex_location,
                      CompilerFilter::kSpeed,
                      /* with_alternate_image= */ false);
 
   android::base::unique_fd odex_fd(open(odex_location.c_str(), O_RDONLY | O_CLOEXEC));
   android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false,
-                                      false,
-                                      /* vdex_fd= */ -1,
-                                      odex_fd.get(),
-                                      zip_fd.get());
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/false,
+                                                               /*vdex_fd=*/-1,
+                                                               odex_fd.get(),
+                                                               zip_fd.get());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 }
 
 // Case: Passing invalid vdex and odex fd with valid zip fd.
 // Expect: The status is kDex2oatFromScratch.
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexVdexFd) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexVdexFd) {
   std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
 
   Copy(GetDexSrc1(), dex_location);
 
   android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false,
-                                      false,
-                                      /* vdex_fd= */ -1,
-                                      /* oat_fd= */ -1,
-                                      zip_fd);
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/false,
+                                                               /*vdex_fd=*/-1,
+                                                               /*oat_fd=*/-1,
+                                                               zip_fd);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
 }
 
-// Case: We have a DEX file and up-to-date VDEX file for it, but no
-// ODEX file.
-TEST_F(OatFileAssistantTest, VdexUpToDateNoOdex) {
+// Case: We have a DEX file and up-to-date VDEX file for it, but no ODEX file, and the DEX file is
+// compressed.
+TEST_P(OatFileAssistantTest, VdexUpToDateNoOdex) {
   std::string dex_location = GetScratchDir() + "/VdexUpToDateNoOdex.jar";
   std::string odex_location = GetOdexDir() + "/VdexUpToDateNoOdex.oat";
 
@@ -597,49 +788,54 @@
   GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
   ASSERT_EQ(0, unlink(odex_location.c_str()));
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kVerify,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
 
   // Make sure we don't crash in this case when we dump the status. We don't
   // care what the actual dumped value is.
   oat_file_assistant.GetStatusDump();
 
-  VerifyOptimizationStatus(
-      &oat_file_assistant,
-      dex_location,
-      "verify",
-      "vdex",
-      "up-to-date");
+  VerifyOptimizationStatus(dex_location, default_context_.get(), "verify", "vdex", "up-to-date");
 }
 
 // Case: We have a DEX file and empty VDEX and ODEX files.
-TEST_F(OatFileAssistantTest, EmptyVdexOdex) {
+TEST_P(OatFileAssistantTest, EmptyVdexOdex) {
   std::string dex_location = GetScratchDir() + "/EmptyVdexOdex.jar";
   std::string odex_location = GetOdexDir() + "/EmptyVdexOdex.oat";
   std::string vdex_location = GetOdexDir() + "/EmptyVdexOdex.vdex";
 
   Copy(GetDexSrc1(), dex_location);
-  ScratchFile vdex_file(vdex_location.c_str());
-  ScratchFile odex_file(odex_location.c_str());
+  ScratchFile vdex_file(vdex_location);
+  ScratchFile odex_file(odex_location);
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
 }
 
 // Case: We have a DEX file and up-to-date (OAT) VDEX file for it, but no OAT
 // file.
-TEST_F(OatFileAssistantTest, VdexUpToDateNoOat) {
+TEST_P(OatFileAssistantTest, VdexUpToDateNoOat) {
   if (IsExecutedAsRoot()) {
     // We cannot simulate non writable locations when executed as root: b/38000545.
     LOG(ERROR) << "Test skipped because it's running as root";
@@ -650,7 +846,8 @@
   std::string oat_location;
   std::string error_msg;
   ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
-        dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
+      dex_location, kRuntimeISA, /* deny_art_apex_data_files= */false, &oat_location, &error_msg))
+      << error_msg;
 
   Copy(GetDexSrc1(), dex_location);
   GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
@@ -658,19 +855,23 @@
 
   ScopedNonWritable scoped_non_writable(dex_location);
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
 
-  EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOat,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatForFilter);
 }
 
 // Case: We have a DEX file and speed-profile OAT file for it.
 // Expect: The status is kNoDexOptNeeded if the profile hasn't changed, but
 // kDex2Oat if the profile has changed.
-TEST_F(OatFileAssistantTest, ProfileOatUpToDate) {
+TEST_P(OatFileAssistantTest, ProfileOatUpToDate) {
   if (IsExecutedAsRoot()) {
     // We cannot simulate non writable locations when executed as root: b/38000545.
     LOG(ERROR) << "Test skipped because it's running as root";
@@ -684,29 +885,62 @@
   ScopedNonWritable scoped_non_writable(dex_location);
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeedProfile,
+                        default_trigger_,
+                        /*expected_dexopt_needed=*/false,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationOat);
+  EXPECT_EQ(
+      OatFileAssistant::kNoDexOptNeeded,
+      oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile, /*profile_changed=*/false));
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kVerify,
+                        default_trigger_,
+                        /*expected_dexopt_needed=*/false,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationOat);
   EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeedProfile, false));
+            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify, /*profile_changed=*/false));
+
+  OatFileAssistant::DexOptTrigger profile_changed_trigger = default_trigger_;
+  profile_changed_trigger.targetFilterIsSame = true;
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeedProfile,
+                        profile_changed_trigger,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationOat);
+  EXPECT_EQ(
+      OatFileAssistant::kDex2OatForFilter,
+      oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile, /*profile_changed=*/true));
+
+  // We should not recompile even if `profile_changed` is true because the compiler filter should
+  // not be downgraded.
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kVerify,
+                        profile_changed_trigger,
+                        /*expected_dexopt_needed=*/false,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationOat);
   EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify, false));
-  EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeedProfile, true));
-  EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify, true));
+            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify, /*profile_changed=*/true));
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 }
 
 // Case: We have a MultiDEX file and up-to-date OAT file for it.
 // Expect: The status is kNoDexOptNeeded and we load all dex files.
-TEST_F(OatFileAssistantTest, MultiDexOatUpToDate) {
+TEST_P(OatFileAssistantTest, MultiDexOatUpToDate) {
   if (IsExecutedAsRoot()) {
     // We cannot simulate non writable locations when executed as root: b/38000545.
     LOG(ERROR) << "Test skipped because it's running as root";
@@ -720,18 +954,25 @@
   ScopedNonWritable scoped_non_writable(dex_location);
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/true);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOat,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+  ExpectHasDexFiles(&oat_file_assistant, true);
 
   // Verify we can load both dex files.
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   ASSERT_TRUE(oat_file.get() != nullptr);
-  EXPECT_TRUE(oat_file->IsExecutable());
+  if (with_runtime_) {
+    EXPECT_TRUE(oat_file->IsExecutable());
+  }
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
   EXPECT_EQ(2u, dex_files.size());
@@ -739,7 +980,7 @@
 
 // Case: We have a MultiDEX file where the non-main multdex entry is out of date.
 // Expect: The status is kDex2OatNeeded.
-TEST_F(OatFileAssistantTest, MultiDexNonMainOutOfDate) {
+TEST_P(OatFileAssistantTest, MultiDexNonMainOutOfDate) {
   if (IsExecutedAsRoot()) {
     // We cannot simulate non writable locations when executed as root: b/38000545.
     LOG(ERROR) << "Test skipped because it's running as root";
@@ -759,18 +1000,21 @@
   ScopedNonWritable scoped_non_writable(dex_location);
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
+  ExpectHasDexFiles(&oat_file_assistant, true);
 }
 
 // Case: We have a DEX file and an OAT file out of date with respect to the
 // dex checksum.
-TEST_F(OatFileAssistantTest, OatDexOutOfDate) {
+TEST_P(OatFileAssistantTest, OatDexOutOfDate) {
   if (IsExecutedAsRoot()) {
     // We cannot simulate non writable locations when executed as root: b/38000545.
     LOG(ERROR) << "Test skipped because it's running as root";
@@ -788,31 +1032,28 @@
   ScopedNonWritable scoped_non_writable(dex_location);
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatDexOutOfDate, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 
   VerifyOptimizationStatus(
-      &oat_file_assistant,
-      dex_location,
-      "run-from-apk-fallback",
-      "unknown",
-      "apk-more-recent");
+      dex_location, default_context_.get(), "run-from-apk-fallback", "unknown", "apk-more-recent");
 }
 
 // Case: We have a DEX file and an (ODEX) VDEX file out of date with respect
 // to the dex checksum, but no ODEX file.
-TEST_F(OatFileAssistantTest, VdexDexOutOfDate) {
+TEST_P(OatFileAssistantTest, VdexDexOutOfDate) {
   std::string dex_location = GetScratchDir() + "/VdexDexOutOfDate.jar";
   std::string odex_location = GetOdexDir() + "/VdexDexOutOfDate.oat";
 
@@ -821,18 +1062,21 @@
   ASSERT_EQ(0, unlink(odex_location.c_str()));
   Copy(GetDexSrc2(), dex_location);
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
 }
 
 // Case: We have a MultiDEX (ODEX) VDEX file where the non-main multidex entry
 // is out of date and there is no corresponding ODEX file.
-TEST_F(OatFileAssistantTest, VdexMultiDexNonMainOutOfDate) {
+TEST_P(OatFileAssistantTest, VdexMultiDexNonMainOutOfDate) {
   std::string dex_location = GetScratchDir() + "/VdexMultiDexNonMainOutOfDate.jar";
   std::string odex_location = GetOdexDir() + "/VdexMultiDexNonMainOutOfDate.odex";
 
@@ -841,18 +1085,21 @@
   ASSERT_EQ(0, unlink(odex_location.c_str()));
   Copy(GetMultiDexSrc2(), dex_location);
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
 }
 
 // Case: We have a DEX file and an OAT file out of date with respect to the
 // boot image.
-TEST_F(OatFileAssistantTest, OatImageOutOfDate) {
+TEST_P(OatFileAssistantTest, OatImageOutOfDate) {
   if (IsExecutedAsRoot()) {
     // We cannot simulate non writable locations when executed as root: b/38000545.
     LOG(ERROR) << "Test skipped because it's running as root";
@@ -869,68 +1116,61 @@
   ScopedNonWritable scoped_non_writable(dex_location);
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
-  EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kVerify,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOat,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOat,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatForFilter);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatBootImageOutOfDate, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 
-  VerifyOptimizationStatus(
-      &oat_file_assistant,
-      dex_location,
-      "verify",
-      "vdex",
-      "up-to-date");
+  VerifyOptimizationStatus(dex_location, default_context_.get(), "verify", "vdex", "up-to-date");
 }
 
-// Case: We have a DEX file and a verify-at-runtime OAT file out of date with
-// respect to the boot image.
-// It shouldn't matter that the OAT file is out of date, because it is
-// verify-at-runtime.
-TEST_F(OatFileAssistantTest, OatVerifyAtRuntimeImageOutOfDate) {
-  if (IsExecutedAsRoot()) {
-    // We cannot simulate non writable locations when executed as root: b/38000545.
-    LOG(ERROR) << "Test skipped because it's running as root";
-    return;
-  }
+TEST_P(OatFileAssistantTest, OatContextOutOfDate) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  std::string odex_location = GetOdexDir() + "/TestDex.odex";
 
-  std::string dex_location = GetScratchDir() + "/OatVerifyAtRuntimeImageOutOfDate.jar";
-
+  std::string context_location = GetScratchDir() + "/ContextDex.jar";
   Copy(GetDexSrc1(), dex_location);
-  GenerateOatForTest(dex_location.c_str(),
-                     CompilerFilter::kExtract,
-                     /* with_alternate_image= */ true);
+  Copy(GetDexSrc2(), context_location);
 
-  ScopedNonWritable scoped_non_writable(dex_location);
-  ASSERT_TRUE(scoped_non_writable.IsSuccessful());
+  std::string error_msg;
+  std::vector<std::string> args;
+  args.push_back("--dex-file=" + dex_location);
+  args.push_back("--oat-file=" + odex_location);
+  args.push_back("--class-loader-context=PCL[" + context_location + "]");
+  ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
+  // Update the context by overriding the jar file.
+  Copy(GetMultiDexSrc2(), context_location);
 
-  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
-  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
-  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("PCL[" + context_location + "]");
+  ASSERT_TRUE(context != nullptr);
+  ASSERT_TRUE(context->OpenDexFiles());
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  VerifyOptimizationStatus(
+      dex_location, context.get(), "verify", "vdex", "up-to-date", /*check_context=*/true);
 }
 
 // Case: We have a DEX file and an ODEX file, but no OAT file.
-TEST_F(OatFileAssistantTest, DexOdexNoOat) {
+TEST_P(OatFileAssistantTest, DexOdexNoOat) {
   std::string dex_location = GetScratchDir() + "/DexOdexNoOat.jar";
   std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex";
 
@@ -938,21 +1178,22 @@
   Copy(GetDexSrc1(), dex_location);
   GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
 
-  // Verify the status.
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  // Verify the status.
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 
   // We should still be able to get the non-executable odex file to run from.
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
@@ -961,41 +1202,52 @@
 
 // Case: We have a resource-only DEX file, no ODEX file and no
 // OAT file. Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, ResourceOnlyDex) {
+TEST_P(OatFileAssistantTest, ResourceOnlyDex) {
   std::string dex_location = GetScratchDir() + "/ResourceOnlyDex.jar";
 
   Copy(GetResourceOnlySrc1(), dex_location);
 
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
   // Verify the status.
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
 
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kVerify,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_FALSE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, false);
 
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_FALSE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, false);
+
+  VerifyOptimizationStatusWithInstance(&oat_file_assistant, "unknown", "unknown", "no-dex-code");
 }
 
 // Case: We have a DEX file, an ODEX file and an OAT file.
 // Expect: It shouldn't crash. We should load the odex file executable.
-TEST_F(OatFileAssistantTest, OdexOatOverlap) {
+TEST_P(OatFileAssistantTest, OdexOatOverlap) {
   std::string dex_location = GetScratchDir() + "/OdexOatOverlap.jar";
   std::string odex_location = GetOdexDir() + "/OdexOatOverlap.odex";
 
@@ -1004,59 +1256,39 @@
   GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
   GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
 
-  // Verify things don't go bad.
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-            GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  // Verify things don't go bad.
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/true);
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/true,
+                               /*expected_location=*/OatFileAssistant::kLocationOdex,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+  ExpectHasDexFiles(&oat_file_assistant, true);
 
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   ASSERT_TRUE(oat_file.get() != nullptr);
 
-  EXPECT_TRUE(oat_file->IsExecutable());
+  if (with_runtime_) {
+    EXPECT_TRUE(oat_file->IsExecutable());
+  }
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
   EXPECT_EQ(1u, dex_files.size());
 }
 
-// Case: We have a DEX file and a VerifyAtRuntime ODEX file, but no OAT file.
-// Expect: The status is kNoDexOptNeeded, because VerifyAtRuntime contains no code.
-TEST_F(OatFileAssistantTest, DexVerifyAtRuntimeOdexNoOat) {
-  std::string dex_location = GetScratchDir() + "/DexVerifyAtRuntimeOdexNoOat.jar";
-  std::string odex_location = GetOdexDir() + "/DexVerifyAtRuntimeOdexNoOat.odex";
-
-  // Create the dex and odex files
-  Copy(GetDexSrc1(), dex_location);
-  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kExtract);
-
-  // Verify the status.
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
-
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-
-  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
-  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
-  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_TRUE(oat_file_assistant.HasDexFiles());
-}
-
 // Case: We have a DEX file and up-to-date OAT file for it.
 // Expect: We should load an executable dex file.
-TEST_F(OatFileAssistantTest, LoadOatUpToDate) {
+TEST_P(OatFileAssistantTest, LoadOatUpToDate) {
   if (IsExecutedAsRoot()) {
     // We cannot simulate non writable locations when executed as root: b/38000545.
     LOG(ERROR) << "Test skipped because it's running as root";
@@ -1071,15 +1303,18 @@
   ScopedNonWritable scoped_non_writable(dex_location);
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
 
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
   // Load the oat using an oat file assistant.
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/true);
 
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   ASSERT_TRUE(oat_file.get() != nullptr);
-  EXPECT_TRUE(oat_file->IsExecutable());
+  if (with_runtime_) {
+    EXPECT_TRUE(oat_file->IsExecutable());
+  }
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
   EXPECT_EQ(1u, dex_files.size());
@@ -1087,7 +1322,7 @@
 
 // Case: We have a DEX file and up-to-date quicken OAT file for it.
 // Expect: We should still load the oat file as executable.
-TEST_F(OatFileAssistantTest, LoadExecInterpretOnlyOatUpToDate) {
+TEST_P(OatFileAssistantTest, LoadExecInterpretOnlyOatUpToDate) {
   if (IsExecutedAsRoot()) {
     // We cannot simulate non writable locations when executed as root: b/38000545.
     LOG(ERROR) << "Test skipped because it's running as root";
@@ -1102,15 +1337,18 @@
   ScopedNonWritable scoped_non_writable(dex_location);
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
 
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
   // Load the oat using an oat file assistant.
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/true);
 
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   ASSERT_TRUE(oat_file.get() != nullptr);
-  EXPECT_TRUE(oat_file->IsExecutable());
+  if (with_runtime_) {
+    EXPECT_TRUE(oat_file->IsExecutable());
+  }
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
   EXPECT_EQ(1u, dex_files.size());
@@ -1118,7 +1356,7 @@
 
 // Case: We have a DEX file and up-to-date OAT file for it.
 // Expect: Loading non-executable should load the oat non-executable.
-TEST_F(OatFileAssistantTest, LoadNoExecOatUpToDate) {
+TEST_P(OatFileAssistantTest, LoadNoExecOatUpToDate) {
   if (IsExecutedAsRoot()) {
     // We cannot simulate non writable locations when executed as root: b/38000545.
     LOG(ERROR) << "Test skipped because it's running as root";
@@ -1134,15 +1372,18 @@
 
   GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
 
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
   // Load the oat using an oat file assistant.
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/true);
 
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   ASSERT_TRUE(oat_file.get() != nullptr);
-  EXPECT_FALSE(oat_file->IsExecutable());
+  if (with_runtime_) {
+    EXPECT_TRUE(oat_file->IsExecutable());
+  }
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
   EXPECT_EQ(1u, dex_files.size());
@@ -1186,54 +1427,64 @@
 
 // Case: Non-absolute path to Dex location.
 // Expect: Not sure, but it shouldn't crash.
-TEST_F(OatFileAssistantTest, NonAbsoluteDexLocation) {
+TEST_P(OatFileAssistantTest, NonAbsoluteDexLocation) {
   std::string abs_dex_location = GetScratchDir() + "/NonAbsoluteDexLocation.jar";
   Copy(GetDexSrc1(), abs_dex_location);
 
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
   std::string dex_location = MakePathRelative(abs_dex_location);
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
 }
 
 // Case: Very short, non-existent Dex location.
 // Expect: kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, ShortDexLocation) {
+TEST_P(OatFileAssistantTest, ShortDexLocation) {
   std::string dex_location = "/xx";
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
-  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
-  EXPECT_FALSE(oat_file_assistant.HasDexFiles());
+  std::string error_msg_ignored;
+  EXPECT_FALSE(oat_file_assistant.HasDexFiles(&error_msg_ignored).has_value());
 }
 
 // Case: Non-standard extension for dex file.
 // Expect: The status is kDex2OatNeeded.
-TEST_F(OatFileAssistantTest, LongDexExtension) {
+TEST_P(OatFileAssistantTest, LongDexExtension) {
   std::string dex_location = GetScratchDir() + "/LongDexExtension.jarx";
   Copy(GetDexSrc1(), dex_location);
 
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      false);
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
 
-  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
-      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/true,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
@@ -1243,7 +1494,7 @@
 // A task to generate a dex location. Used by the RaceToGenerate test.
 class RaceGenerateTask : public Task {
  public:
-  RaceGenerateTask(OatFileAssistantTest& test,
+  RaceGenerateTask(OatFileAssistantBaseTest& test,
                    const std::string& dex_location,
                    const std::string& oat_location,
                    Mutex* lock)
@@ -1251,8 +1502,7 @@
         dex_location_(dex_location),
         oat_location_(oat_location),
         lock_(lock),
-        loaded_oat_file_(nullptr)
-  {}
+        loaded_oat_file_(nullptr) {}
 
   void Run(Thread* self ATTRIBUTE_UNUSED) override {
     // Load the dex files, and save a pointer to the loaded oat file, so that
@@ -1288,7 +1538,7 @@
   }
 
  private:
-  OatFileAssistantTest& test_;
+  OatFileAssistantBaseTest& test_;
   std::string dex_location_;
   std::string oat_location_;
   Mutex* lock_;
@@ -1297,7 +1547,7 @@
 
 // Test the case where dex2oat invocations race with multiple processes trying to
 // load the oat file.
-TEST_F(OatFileAssistantTest, RaceToGenerate) {
+TEST_F(OatFileAssistantBaseTest, RaceToGenerate) {
   std::string dex_location = GetScratchDir() + "/RaceToGenerate.jar";
   std::string oat_location = GetOdexDir() + "/RaceToGenerate.oat";
 
@@ -1309,7 +1559,7 @@
   // take a while to generate.
   Copy(GetLibCoreDexFileNames()[0], dex_location);
 
-  const size_t kNumThreads = 32;
+  const size_t kNumThreads = 16;
   Thread* self = Thread::Current();
   ThreadPool thread_pool("Oat file assistant test thread pool", kNumThreads);
   std::vector<std::unique_ptr<RaceGenerateTask>> tasks;
@@ -1336,7 +1586,7 @@
 
 // Case: We have a DEX file and an ODEX file, and no OAT file,
 // Expect: We should load the odex file executable.
-TEST_F(OatFileAssistantTest, LoadDexOdexNoOat) {
+TEST_P(OatFileAssistantTest, LoadDexOdexNoOat) {
   std::string dex_location = GetScratchDir() + "/LoadDexOdexNoOat.jar";
   std::string odex_location = GetOdexDir() + "/LoadDexOdexNoOat.odex";
 
@@ -1344,15 +1594,18 @@
   Copy(GetDexSrc1(), dex_location);
   GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
 
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
   // Load the oat using an executable oat file assistant.
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/true);
 
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   ASSERT_TRUE(oat_file.get() != nullptr);
-  EXPECT_TRUE(oat_file->IsExecutable());
+  if (with_runtime_) {
+    EXPECT_TRUE(oat_file->IsExecutable());
+  }
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
   EXPECT_EQ(1u, dex_files.size());
@@ -1360,7 +1613,7 @@
 
 // Case: We have a MultiDEX file and an ODEX file, and no OAT file.
 // Expect: We should load the odex file executable.
-TEST_F(OatFileAssistantTest, LoadMultiDexOdexNoOat) {
+TEST_P(OatFileAssistantTest, LoadMultiDexOdexNoOat) {
   std::string dex_location = GetScratchDir() + "/LoadMultiDexOdexNoOat.jar";
   std::string odex_location = GetOdexDir() + "/LoadMultiDexOdexNoOat.odex";
 
@@ -1368,15 +1621,18 @@
   Copy(GetMultiDexSrc1(), dex_location);
   GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
 
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
   // Load the oat using an executable oat file assistant.
-  OatFileAssistant oat_file_assistant(dex_location.c_str(),
-                                      kRuntimeISA,
-                                      default_context_.get(),
-                                      true);
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+                                                               /*context=*/nullptr,
+                                                               /*load_executable=*/true);
 
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   ASSERT_TRUE(oat_file.get() != nullptr);
-  EXPECT_TRUE(oat_file->IsExecutable());
+  if (with_runtime_) {
+    EXPECT_TRUE(oat_file->IsExecutable());
+  }
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
   EXPECT_EQ(2u, dex_files.size());
@@ -1402,7 +1658,7 @@
 
 // Verify the dexopt status values from dalvik.system.DexFile
 // match the OatFileAssistant::DexOptStatus values.
-TEST_F(OatFileAssistantTest, DexOptStatusValues) {
+TEST_F(OatFileAssistantBaseTest, DexOptStatusValues) {
   std::pair<OatFileAssistant::DexOptNeeded, const char*> mapping[] = {
     {OatFileAssistant::kNoDexOptNeeded, "NO_DEXOPT_NEEDED"},
     {OatFileAssistant::kDex2OatFromScratch, "DEX2OAT_FROM_SCRATCH"},
@@ -1426,7 +1682,7 @@
   }
 }
 
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) {
   std::string dex_location = GetScratchDir() + "/TestDex.jar";
   std::string odex_location = GetOdexDir() + "/TestDex.odex";
 
@@ -1455,43 +1711,404 @@
     ASSERT_TRUE(updated_context != nullptr);
     std::vector<int> context_fds;
     ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds,  /*only_read_checksums*/ true));
-    OatFileAssistant oat_file_assistant(
-        dex_location.c_str(), kRuntimeISA, updated_context.get(), false);
+
+    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+    OatFileAssistant oat_file_assistant =
+        CreateOatFileAssistant(dex_location.c_str(), updated_context.get());
     // DexOptNeeded should advise compilation for filter when the context changes.
-    EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
-              GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kDefaultCompilerFilter));
+    VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                                 CompilerFilter::kDefaultCompilerFilter,
+                                 /*expected_dexopt_needed=*/true,
+                                 /*expected_is_vdex_usable=*/true,
+                                 /*expected_location=*/OatFileAssistant::kLocationOdex,
+                                 /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
   }
   {
     std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str);
     ASSERT_TRUE(updated_context != nullptr);
     std::vector<int> context_fds;
-    ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds,  /*only_read_checksums*/ true));
-    OatFileAssistant oat_file_assistant(
-        dex_location.c_str(), kRuntimeISA, updated_context.get(), false);
-    // Now check that DexOptNeeded does not advise compilation if we only extracted the file.
-    args.push_back("--compiler-filter=extract");
-    ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
-    EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-              GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  }
-  {
-    std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str);
-    ASSERT_TRUE(updated_context != nullptr);
-    std::vector<int> context_fds;
-    ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds,  /*only_read_checksums*/ true));
-    OatFileAssistant oat_file_assistant(
-        dex_location.c_str(), kRuntimeISA, updated_context.get(), false);
-    // Now check that DexOptNeeded does not advise compilation if we only verify the file.
+    ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds, /*only_read_checksums*/ true));
     args.push_back("--compiler-filter=verify");
     ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
-    EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
-              GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
+
+    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+    OatFileAssistant oat_file_assistant =
+        CreateOatFileAssistant(dex_location.c_str(), updated_context.get());
+    // Now check that DexOptNeeded does not advise compilation if we only verify the file.
+    VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                                 CompilerFilter::kVerify,
+                                 /*expected_dexopt_needed=*/false,
+                                 /*expected_is_vdex_usable=*/true,
+                                 /*expected_location=*/OatFileAssistant::kLocationOdex,
+                                 /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
   }
 }
 
+// Case: We have a DEX file and speed-profile ODEX file for it. The caller's intention is to
+// downgrade the compiler filter.
+// Expect: Dexopt should be performed only if the target compiler filter is worse than the current
+// one.
+TEST_P(OatFileAssistantTest, Downgrade) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  std::string odex_location = GetOdexDir() + "/TestDex.odex";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile);
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+  OatFileAssistant::DexOptTrigger downgrade_trigger{.targetFilterIsWorse = true};
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeed,
+                        downgrade_trigger,
+                        /*expected_dexopt_needed=*/false,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationOdex);
+  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+            oat_file_assistant.GetDexOptNeeded(
+                CompilerFilter::kSpeed, /*profile_changed=*/false, /*downgrade=*/true));
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeedProfile,
+                        downgrade_trigger,
+                        /*expected_dexopt_needed=*/false,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationOdex);
+  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+            oat_file_assistant.GetDexOptNeeded(
+                CompilerFilter::kSpeedProfile, /*profile_changed=*/false, /*downgrade=*/true));
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kVerify,
+                        downgrade_trigger,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationOdex);
+  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
+            oat_file_assistant.GetDexOptNeeded(
+                CompilerFilter::kVerify, /*profile_changed=*/false, /*downgrade=*/true));
+}
+
+// Case: We have a DEX file but we don't have an ODEX file for it. The caller's intention is to
+// downgrade the compiler filter.
+// Expect: Dexopt should never be performed regardless of the target compiler filter.
+TEST_P(OatFileAssistantTest, DowngradeNoOdex) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  Copy(GetDexSrc1(), dex_location);
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+  OatFileAssistant::DexOptTrigger downgrade_trigger{.targetFilterIsWorse = true};
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeed,
+                        downgrade_trigger,
+                        /*expected_dexopt_needed=*/false,
+                        /*expected_is_vdex_usable=*/false,
+                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
+            oat_file_assistant.GetDexOptNeeded(
+                CompilerFilter::kSpeed, /*profile_changed=*/false, /*downgrade=*/true));
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeedProfile,
+                        downgrade_trigger,
+                        /*expected_dexopt_needed=*/false,
+                        /*expected_is_vdex_usable=*/false,
+                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
+            oat_file_assistant.GetDexOptNeeded(
+                CompilerFilter::kSpeedProfile, /*profile_changed=*/false, /*downgrade=*/true));
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kVerify,
+                        downgrade_trigger,
+                        /*expected_dexopt_needed=*/false,
+                        /*expected_is_vdex_usable=*/false,
+                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
+            oat_file_assistant.GetDexOptNeeded(
+                CompilerFilter::kVerify, /*profile_changed=*/false, /*downgrade=*/true));
+}
+
+// Case: We have a DEX file and speed-profile ODEX file for it. The legacy version is called with
+// both `profile_changed` and `downgrade` being true. This won't happen in the real case. Just to be
+// complete.
+// Expect: The behavior should be as `profile_changed` is false and `downgrade` is true.
+TEST_P(OatFileAssistantTest, ProfileChangedDowngrade) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  std::string odex_location = GetOdexDir() + "/TestDex.odex";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile);
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+            oat_file_assistant.GetDexOptNeeded(
+                CompilerFilter::kSpeed, /*profile_changed=*/true, /*downgrade=*/true));
+
+  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+            oat_file_assistant.GetDexOptNeeded(
+                CompilerFilter::kSpeedProfile, /*profile_changed=*/true, /*downgrade=*/true));
+
+  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
+            oat_file_assistant.GetDexOptNeeded(
+                CompilerFilter::kVerify, /*profile_changed=*/true, /*downgrade=*/true));
+}
+
+// Case: We have a DEX file and speed-profile ODEX file for it. The caller's intention is to force
+// the compilation.
+// Expect: Dexopt should be performed regardless of the target compiler filter. The VDEX file is
+// usable.
+//
+// The legacy version does not support this case. Historically, Package Manager does not take the
+// result from OatFileAssistant for forced compilation. It uses an arbitrary non-zero value instead.
+// Therefore, we don't test the legacy version here.
+TEST_P(OatFileAssistantTest, Force) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  std::string odex_location = GetOdexDir() + "/TestDex.odex";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile);
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+  OatFileAssistant::DexOptTrigger force_trigger{.targetFilterIsBetter = true,
+                                                .targetFilterIsSame = true,
+                                                .targetFilterIsWorse = true,
+                                                .primaryBootImageBecomesUsable = true};
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeed,
+                        force_trigger,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationOdex);
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeedProfile,
+                        force_trigger,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationOdex);
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kVerify,
+                        force_trigger,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationOdex);
+}
+
+// Case: We have a DEX file but we don't have an ODEX file for it. The caller's intention is to
+// force the compilation.
+// Expect: Dexopt should be performed regardless of the target compiler filter. No VDEX file is
+// usable.
+//
+// The legacy version does not support this case. Historically, Package Manager does not take the
+// result from OatFileAssistant for forced compilation. It uses an arbitrary non-zero value instead.
+// Therefore, we don't test the legacy version here.
+TEST_P(OatFileAssistantTest, ForceNoOdex) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  Copy(GetDexSrc1(), dex_location);
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+  OatFileAssistant::DexOptTrigger force_trigger{.targetFilterIsBetter = true,
+                                                .targetFilterIsSame = true,
+                                                .targetFilterIsWorse = true,
+                                                .primaryBootImageBecomesUsable = true};
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeed,
+                        force_trigger,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/false,
+                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeedProfile,
+                        force_trigger,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/false,
+                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kVerify,
+                        force_trigger,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/false,
+                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+}
+
+// Case: We have a DEX file and a DM file for it, and the DEX file is uncompressed.
+// Expect: Dexopt should be performed if the compiler filter is better than "verify". The location
+// should be kLocationDm.
+//
+// The legacy version should return kDex2OatFromScratch if the target compiler filter is better than
+// "verify".
+TEST_P(OatFileAssistantTest, DmUpToDateDexUncompressed) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  std::string dm_location = GetScratchDir() + "/TestDex.dm";
+  std::string odex_location = GetOdexDir() + "/TestDex.odex";
+  std::string vdex_location = GetOdexDir() + "/TestDex.vdex";
+  Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location);
+
+  // Generate temporary ODEX and VDEX files in order to create the DM file from.
+  GenerateOdexForTest(
+      dex_location, odex_location, CompilerFilter::kVerify, "install", {"--copy-dex-files=false"});
+
+  CreateDexMetadata(vdex_location, dm_location);
+
+  // Cleanup the temporary files.
+  ASSERT_EQ(0, unlink(odex_location.c_str()));
+  ASSERT_EQ(0, unlink(vdex_location.c_str()));
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeed,
+                        default_trigger_,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationDm);
+  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeedProfile,
+                        default_trigger_,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationDm);
+  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile));
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kVerify,
+                        default_trigger_,
+                        /*expected_dexopt_needed=*/false,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationDm);
+  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
+            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
+}
+
+// Case: We have a DEX file and a DM file for it, and the DEX file is compressed.
+// Expect: Dexopt should be performed regardless of the compiler filter. The location
+// should be kLocationDm.
+TEST_P(OatFileAssistantTest, DmUpToDateDexCompressed) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  std::string dm_location = GetScratchDir() + "/TestDex.dm";
+  std::string odex_location = GetOdexDir() + "/TestDex.odex";
+  std::string vdex_location = GetOdexDir() + "/TestDex.vdex";
+  Copy(GetMultiDexSrc1(), dex_location);
+
+  // Generate temporary ODEX and VDEX files in order to create the DM file from.
+  GenerateOdexForTest(
+      dex_location, odex_location, CompilerFilter::kVerify, "install", {"--copy-dex-files=false"});
+
+  CreateDexMetadata(vdex_location, dm_location);
+
+  // Cleanup the temporary files.
+  ASSERT_EQ(0, unlink(odex_location.c_str()));
+  ASSERT_EQ(0, unlink(vdex_location.c_str()));
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeed,
+                        default_trigger_,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationDm);
+  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kSpeedProfile,
+                        default_trigger_,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationDm);
+  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile));
+
+  VerifyGetDexOptNeeded(&oat_file_assistant,
+                        CompilerFilter::kVerify,
+                        default_trigger_,
+                        /*expected_dexopt_needed=*/true,
+                        /*expected_is_vdex_usable=*/true,
+                        /*expected_location=*/OatFileAssistant::kLocationDm);
+  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
+}
+
+// Case: We have an ODEX file, but the DEX file is gone.
+// Expect: No dexopt is needed, as there's nothing we can do.
+TEST_P(OatFileAssistantTest, OdexNoDex) {
+  std::string dex_location = GetScratchDir() + "/OdexNoDex.jar";
+  std::string odex_location = GetOdexDir() + "/OdexNoDex.oat";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+  ASSERT_EQ(0, unlink(dex_location.c_str()));
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+
+  VerifyOptimizationStatusWithInstance(
+      &oat_file_assistant, "unknown", "unknown", "io-error-no-apk");
+}
+
+// Case: We have a VDEX file, but the DEX file is gone.
+// Expect: No dexopt is needed, as there's nothing we can do.
+TEST_P(OatFileAssistantTest, VdexNoDex) {
+  std::string dex_location = GetScratchDir() + "/VdexNoDex.jar";
+  std::string odex_location = GetOdexDir() + "/VdexNoDex.oat";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+  ASSERT_EQ(0, unlink(odex_location.c_str()));
+  ASSERT_EQ(0, unlink(dex_location.c_str()));
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+  VerifyGetDexOptNeededDefault(&oat_file_assistant,
+                               CompilerFilter::kSpeed,
+                               /*expected_dexopt_needed=*/false,
+                               /*expected_is_vdex_usable=*/false,
+                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+
+  VerifyOptimizationStatusWithInstance(
+      &oat_file_assistant, "unknown", "unknown", "io-error-no-apk");
+}
+
 // Test that GetLocation of a dex file is the same whether the dex
 // filed is backed by an oat file or not.
-TEST_F(OatFileAssistantTest, GetDexLocation) {
+TEST_F(OatFileAssistantBaseTest, GetDexLocation) {
   std::string dex_location = GetScratchDir() + "/TestDex.jar";
   std::string oat_location = GetOdexDir() + "/TestDex.odex";
   std::string art_location = GetOdexDir() + "/TestDex.art";
@@ -1539,7 +2156,7 @@
 
 // Test that a dex file on the platform location gets the right hiddenapi domain,
 // regardless of whether it has a backing oat file.
-TEST_F(OatFileAssistantTest, SystemFrameworkDir) {
+TEST_F(OatFileAssistantBaseTest, SystemFrameworkDir) {
   std::string filebase = "OatFileAssistantTestSystemFrameworkDir";
   std::string dex_location = GetAndroidRoot() + "/framework/" + filebase + ".jar";
   Copy(GetDexSrc1(), dex_location);
@@ -1619,7 +2236,7 @@
 }
 
 // Make sure OAT files that require app images are not loaded as executable.
-TEST_F(OatFileAssistantTest, LoadOatNoArt) {
+TEST_F(OatFileAssistantBaseTest, LoadOatNoArt) {
   std::string dex_location = GetScratchDir() + "/TestDex.jar";
   std::string odex_location = GetOdexDir() + "/TestDex.odex";
   std::string art_location = GetOdexDir() + "/TestDex.art";
@@ -1653,7 +2270,7 @@
   EXPECT_FALSE(oat_file->IsExecutable());
 }
 
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithApexVersions) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithApexVersions) {
   std::string dex_location = GetScratchDir() + "/TestDex.jar";
   std::string odex_location = GetOdexDir() + "/TestDex.odex";
   Copy(GetDexSrc1(), dex_location);
@@ -1667,8 +2284,9 @@
     args.push_back("--apex-versions=" + Runtime::Current()->GetApexVersions());
     ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
 
-    OatFileAssistant oat_file_assistant(
-        dex_location.c_str(), kRuntimeISA, default_context_.get(), false);
+    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+    OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
     EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
   }
 
@@ -1681,8 +2299,9 @@
     args.push_back("--apex-versions=" + Runtime::Current()->GetApexVersions().substr(0, 1));
     ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
 
-    OatFileAssistant oat_file_assistant(
-        dex_location.c_str(), kRuntimeISA, default_context_.get(), false);
+    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+    OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
     EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
   }
 
@@ -1695,12 +2314,161 @@
     args.push_back("--apex-versions=/1/2/3/4");
     ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
 
-    OatFileAssistant oat_file_assistant(
-        dex_location.c_str(), kRuntimeISA, default_context_.get(), false);
+    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+    OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
     EXPECT_EQ(OatFileAssistant::kOatBootImageOutOfDate, oat_file_assistant.OdexFileStatus());
   }
 }
 
+TEST_P(OatFileAssistantTest, Create) {
+  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  std::unique_ptr<ClassLoaderContext> context;
+  std::string error_msg;
+  std::unique_ptr<OatFileAssistant> oat_file_assistant =
+      OatFileAssistant::Create(dex_location,
+                               GetInstructionSetString(kRuntimeISA),
+                               default_context_->EncodeContextForDex2oat(/*base_dir=*/""),
+                               /*load_executable=*/false,
+                               /*only_load_trusted_executable=*/true,
+                               MaybeGetOatFileAssistantContext(),
+                               &context,
+                               &error_msg);
+  ASSERT_NE(oat_file_assistant, nullptr);
+
+  // Verify that the created instance is usable.
+  VerifyOptimizationStatusWithInstance(oat_file_assistant.get(), "speed", "install", "up-to-date");
+}
+
+TEST_P(OatFileAssistantTest, CreateWithNullContext) {
+  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  std::unique_ptr<ClassLoaderContext> context;
+  std::string error_msg;
+  std::unique_ptr<OatFileAssistant> oat_file_assistant =
+      OatFileAssistant::Create(dex_location,
+                               GetInstructionSetString(kRuntimeISA),
+                               /*context_str=*/std::nullopt,
+                               /*load_executable=*/false,
+                               /*only_load_trusted_executable=*/true,
+                               MaybeGetOatFileAssistantContext(),
+                               &context,
+                               &error_msg);
+  ASSERT_NE(oat_file_assistant, nullptr);
+  ASSERT_EQ(context, nullptr);
+
+  // Verify that the created instance is usable.
+  VerifyOptimizationStatusWithInstance(oat_file_assistant.get(), "speed", "install", "up-to-date");
+}
+
+TEST_P(OatFileAssistantTest, ErrorOnInvalidIsaString) {
+  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  std::unique_ptr<ClassLoaderContext> context;
+  std::string error_msg;
+  EXPECT_EQ(OatFileAssistant::Create(dex_location,
+                                     /*isa_str=*/"foo",
+                                     default_context_->EncodeContextForDex2oat(/*base_dir=*/""),
+                                     /*load_executable=*/false,
+                                     /*only_load_trusted_executable=*/true,
+                                     MaybeGetOatFileAssistantContext(),
+                                     &context,
+                                     &error_msg),
+            nullptr);
+  EXPECT_EQ(error_msg, "Instruction set 'foo' is invalid");
+}
+
+TEST_P(OatFileAssistantTest, ErrorOnInvalidContextString) {
+  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  std::unique_ptr<ClassLoaderContext> context;
+  std::string error_msg;
+  EXPECT_EQ(OatFileAssistant::Create(dex_location,
+                                     GetInstructionSetString(kRuntimeISA),
+                                     /*context_str=*/"foo",
+                                     /*load_executable=*/false,
+                                     /*only_load_trusted_executable=*/true,
+                                     MaybeGetOatFileAssistantContext(),
+                                     &context,
+                                     &error_msg),
+            nullptr);
+  EXPECT_EQ(error_msg, "Class loader context 'foo' is invalid");
+}
+
+TEST_P(OatFileAssistantTest, ErrorOnInvalidContextFile) {
+  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+  // Create a broken context file.
+  std::string context_location = GetScratchDir() + "/BrokenContext.jar";
+  std::ofstream output(context_location);
+  output.close();
+
+  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+  std::unique_ptr<ClassLoaderContext> context;
+  std::string error_msg;
+  EXPECT_EQ(OatFileAssistant::Create(dex_location,
+                                     GetInstructionSetString(kRuntimeISA),
+                                     /*context_str=*/"PCL[" + context_location + "]",
+                                     /*load_executable=*/false,
+                                     /*only_load_trusted_executable=*/true,
+                                     MaybeGetOatFileAssistantContext(),
+                                     &context,
+                                     &error_msg),
+            nullptr);
+  EXPECT_EQ(error_msg,
+            "Failed to load class loader context files for '" + dex_location +
+                "' with context 'PCL[" + context_location + "]'");
+}
+
+// Verifies that `OatFileAssistant::ValidateBootClassPathChecksums` accepts the checksum string
+// produced by `gc::space::ImageSpace::GetBootClassPathChecksums`.
+TEST_P(OatFileAssistantTest, ValidateBootClassPathChecksums) {
+  std::string error_msg;
+  auto create_and_verify = [&]() {
+    std::string checksums = gc::space::ImageSpace::GetBootClassPathChecksums(
+        ArrayRef<gc::space::ImageSpace* const>(runtime_->GetHeap()->GetBootImageSpaces()),
+        ArrayRef<const DexFile* const>(runtime_->GetClassLinker()->GetBootClassPath()));
+    std::string bcp_locations = android::base::Join(runtime_->GetBootClassPathLocations(), ':');
+
+    ofa_context_ = CreateOatFileAssistantContext();
+    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+    return OatFileAssistant::ValidateBootClassPathChecksums(
+        ofa_context_.get(), kRuntimeISA, checksums, bcp_locations, &error_msg);
+  };
+
+  ASSERT_TRUE(create_and_verify()) << error_msg;
+
+  for (const std::string& src : {GetDexSrc1(), GetDexSrc2()}) {
+    ASSERT_TRUE(InsertNewBootClasspathEntry(src, &error_msg)) << error_msg;
+    ASSERT_TRUE(create_and_verify()) << error_msg;
+  }
+}
+
 // TODO: More Tests:
 //  * Test class linker falls back to unquickened dex for DexNoOat
 //  * Test class linker falls back to unquickened dex for MultiDexNoOat
@@ -1713,4 +2481,7 @@
 //    - Dex is stripped, don't have odex.
 //    - Oat file corrupted after status check, before reload unexecutable
 //    because it's unrelocated and no dex2oat
+
+INSTANTIATE_TEST_SUITE_P(WithOrWithoutRuntime, OatFileAssistantTest, testing::Values(true, false));
+
 }  // namespace art
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index c3a268d..63e005d 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -50,6 +50,7 @@
 #include "oat_file.h"
 #include "oat_file_assistant.h"
 #include "obj_ptr-inl.h"
+#include "runtime_image.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-current-inl.h"
 #include "thread_list.h"
@@ -65,6 +66,9 @@
 // If true, we attempt to load the application image if it exists.
 static constexpr bool kEnableAppImage = true;
 
+// If true, we attempt to load an app image generated by the runtime.
+static const bool kEnableRuntimeAppImage = true;
+
 const OatFile* OatFileManager::RegisterOatFile(std::unique_ptr<const OatFile> oat_file,
                                                bool in_memory) {
   // Use class_linker vlog to match the log for dex file registration.
@@ -167,7 +171,7 @@
 
 bool OatFileManager::ShouldLoadAppImage(const OatFile* source_oat_file) const {
   Runtime* const runtime = Runtime::Current();
-  return kEnableAppImage && (!runtime->IsJavaDebuggable() || source_oat_file->IsDebuggable());
+  return kEnableAppImage && (!runtime->IsJavaDebuggableAtInit() || source_oat_file->IsDebuggable());
 }
 
 std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
@@ -196,11 +200,11 @@
     LOG(WARNING) << "Opening an oat file without a class loader. "
                  << "Are you using the deprecated DexFile APIs?";
   } else if (context != nullptr) {
-    OatFileAssistant oat_file_assistant(dex_location,
-                                        kRuntimeISA,
-                                        context.get(),
-                                        runtime->GetOatFilesExecutable(),
-                                        only_use_system_oat_files_);
+    auto oat_file_assistant = std::make_unique<OatFileAssistant>(dex_location,
+                                                                 kRuntimeISA,
+                                                                 context.get(),
+                                                                 runtime->GetOatFilesExecutable(),
+                                                                 only_use_system_oat_files_);
 
     // Get the current optimization status for trace debugging.
     // Implementation detail note: GetOptimizationStatus will select the same
@@ -210,17 +214,8 @@
     std::string compilation_filter;
     std::string compilation_reason;
     std::string odex_status;
-    oat_file_assistant.GetOptimizationStatus(
-        &odex_location,
-        &compilation_filter,
-        &compilation_reason,
-        &odex_status);
-
-    Runtime::Current()->GetAppInfo()->RegisterOdexStatus(
-        dex_location,
-        compilation_filter,
-        compilation_reason,
-        odex_status);
+    oat_file_assistant->GetOptimizationStatus(
+        &odex_location, &compilation_filter, &compilation_reason, &odex_status);
 
     ScopedTrace odex_loading(StringPrintf(
         "location=%s status=%s filter=%s reason=%s",
@@ -229,8 +224,18 @@
         compilation_filter.c_str(),
         compilation_reason.c_str()));
 
+    const bool has_registered_app_info = Runtime::Current()->GetAppInfo()->HasRegisteredAppInfo();
+    const AppInfo::CodeType code_type =
+        Runtime::Current()->GetAppInfo()->GetRegisteredCodeType(dex_location);
+    // We only want to madvise primary/split dex artifacts as a startup optimization. However,
+    // as the code_type for those artifacts may not be set until the initial app info registration,
+    // we conservatively madvise everything until the app info registration is complete.
+    const bool should_madvise = !has_registered_app_info ||
+                                code_type == AppInfo::CodeType::kPrimaryApk ||
+                                code_type == AppInfo::CodeType::kSplitApk;
+
     // Proceed with oat file loading.
-    std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());
+    std::unique_ptr<const OatFile> oat_file(oat_file_assistant->GetBestOatFile().release());
     VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()="
               << (oat_file != nullptr ? oat_file->GetLocation() : "")
               << " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")";
@@ -241,64 +246,99 @@
         << " best_oat_file-location=" << oat_file->GetLocation();
 
     if (oat_file != nullptr) {
+      bool compilation_enabled =
+          CompilerFilter::IsAotCompilationEnabled(oat_file->GetCompilerFilter());
       // Load the dex files from the oat file.
       bool added_image_space = false;
-      if (oat_file->IsExecutable()) {
-        ScopedTrace app_image_timing("AppImage:Loading");
+      if (should_madvise) {
+        VLOG(oat) << "Madvising oat file: " << oat_file->GetLocation();
+        size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeOdex();
+        Runtime::MadviseFileForRange(madvise_size_limit,
+                                     oat_file->Size(),
+                                     oat_file->Begin(),
+                                     oat_file->End(),
+                                     oat_file->GetLocation());
+      }
 
-        // We need to throw away the image space if we are debuggable but the oat-file source of the
-        // image is not otherwise we might get classes with inlined methods or other such things.
-        std::unique_ptr<gc::space::ImageSpace> image_space;
-        if (ShouldLoadAppImage(oat_file.get())) {
-          image_space = oat_file_assistant.OpenImageSpace(oat_file.get());
+      ScopedTrace app_image_timing("AppImage:Loading");
+
+      // We need to throw away the image space if we are debuggable but the oat-file source of the
+      // image is not otherwise we might get classes with inlined methods or other such things.
+      std::unique_ptr<gc::space::ImageSpace> image_space;
+      if (ShouldLoadAppImage(oat_file.get())) {
+        if (oat_file->IsExecutable()) {
+          // App images generated by the compiler can only be used if the oat file
+          // is executable.
+          image_space = oat_file_assistant->OpenImageSpace(oat_file.get());
         }
-        if (image_space != nullptr) {
+        if (kEnableRuntimeAppImage && image_space == nullptr && !compilation_enabled) {
+          std::string art_file = RuntimeImage::GetRuntimeImagePath(dex_location);
+          std::string error_msg;
           ScopedObjectAccess soa(self);
-          StackHandleScope<1> hs(self);
-          Handle<mirror::ClassLoader> h_loader(
-              hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
-          // Can not load app image without class loader.
-          if (h_loader != nullptr) {
-            std::string temp_error_msg;
-            // Add image space has a race condition since other threads could be reading from the
-            // spaces array.
+          image_space = gc::space::ImageSpace::CreateFromAppImage(
+              art_file.c_str(), oat_file.get(), &error_msg);
+          if (image_space == nullptr) {
+            (OS::FileExists(art_file.c_str()) ? LOG_STREAM(INFO) : VLOG_STREAM(image))
+                << "Could not load runtime generated app image: " << error_msg;
+          }
+        }
+      }
+      if (image_space != nullptr) {
+        ScopedObjectAccess soa(self);
+        StackHandleScope<1> hs(self);
+        Handle<mirror::ClassLoader> h_loader(
+            hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
+        // Can not load app image without class loader.
+        if (h_loader != nullptr) {
+          std::string temp_error_msg;
+          // Add image space has a race condition since other threads could be reading from the
+          // spaces array.
+          {
+            ScopedThreadSuspension sts(self, ThreadState::kSuspended);
+            gc::ScopedGCCriticalSection gcs(self,
+                                            gc::kGcCauseAddRemoveAppImageSpace,
+                                            gc::kCollectorTypeAddRemoveAppImageSpace);
+            ScopedSuspendAll ssa("Add image space");
+            runtime->GetHeap()->AddSpace(image_space.get());
+          }
+          {
+            ScopedTrace image_space_timing("Adding image space");
+            gc::space::ImageSpace* space_ptr = image_space.get();
+            added_image_space = runtime->GetClassLinker()->AddImageSpaces(
+                ArrayRef<gc::space::ImageSpace*>(&space_ptr, /*size=*/1),
+                h_loader,
+                context.get(),
+                /*out*/ &dex_files,
+                /*out*/ &temp_error_msg);
+          }
+          if (added_image_space) {
+            // Successfully added image space to heap, release the map so that it does not get
+            // freed.
+            image_space.release();  // NOLINT b/117926937
+
+            // Register for tracking.
+            for (const auto& dex_file : dex_files) {
+              dex::tracking::RegisterDexFile(dex_file.get());
+            }
+
+            if (!compilation_enabled) {
+              // Update the filter we are going to report to 'speed-profile'.
+              // Ideally, we would also update the compiler filter of the odex
+              // file, but at this point it's just too late.
+              compilation_filter = CompilerFilter::NameOfFilter(CompilerFilter::kSpeedProfile);
+            }
+          } else {
+            LOG(INFO) << "Failed to add image file: " << temp_error_msg;
+            dex_files.clear();
             {
               ScopedThreadSuspension sts(self, ThreadState::kSuspended);
               gc::ScopedGCCriticalSection gcs(self,
                                               gc::kGcCauseAddRemoveAppImageSpace,
                                               gc::kCollectorTypeAddRemoveAppImageSpace);
-              ScopedSuspendAll ssa("Add image space");
-              runtime->GetHeap()->AddSpace(image_space.get());
+              ScopedSuspendAll ssa("Remove image space");
+              runtime->GetHeap()->RemoveSpace(image_space.get());
             }
-            {
-              ScopedTrace image_space_timing("Adding image space");
-              added_image_space = runtime->GetClassLinker()->AddImageSpace(image_space.get(),
-                                                                           h_loader,
-                                                                           /*out*/&dex_files,
-                                                                           /*out*/&temp_error_msg);
-            }
-            if (added_image_space) {
-              // Successfully added image space to heap, release the map so that it does not get
-              // freed.
-              image_space.release();  // NOLINT b/117926937
-
-              // Register for tracking.
-              for (const auto& dex_file : dex_files) {
-                dex::tracking::RegisterDexFile(dex_file.get());
-              }
-            } else {
-              LOG(INFO) << "Failed to add image file " << temp_error_msg;
-              dex_files.clear();
-              {
-                ScopedThreadSuspension sts(self, ThreadState::kSuspended);
-                gc::ScopedGCCriticalSection gcs(self,
-                                                gc::kGcCauseAddRemoveAppImageSpace,
-                                                gc::kCollectorTypeAddRemoveAppImageSpace);
-                ScopedSuspendAll ssa("Remove image space");
-                runtime->GetHeap()->RemoveSpace(image_space.get());
-              }
-              // Non-fatal, don't update error_msg.
-            }
+            // Non-fatal, don't update error_msg.
           }
         }
       }
@@ -310,12 +350,13 @@
                        << oat_file->GetLocation()
                        << " non-executable as it requires an image which we failed to load";
           // file as non-executable.
-          OatFileAssistant nonexecutable_oat_file_assistant(dex_location,
-                                                            kRuntimeISA,
-                                                            context.get(),
-                                                            /*load_executable=*/false,
-                                                            only_use_system_oat_files_);
-          oat_file.reset(nonexecutable_oat_file_assistant.GetBestOatFile().release());
+          auto nonexecutable_oat_file_assistant =
+              std::make_unique<OatFileAssistant>(dex_location,
+                                                 kRuntimeISA,
+                                                 context.get(),
+                                                 /*load_executable=*/false,
+                                                 only_use_system_oat_files_);
+          oat_file.reset(nonexecutable_oat_file_assistant->GetBestOatFile().release());
 
           // The file could be deleted concurrently (for example background
           // dexopt, or secondary oat file being deleted by the app).
@@ -325,7 +366,7 @@
         }
 
         if (oat_file != nullptr) {
-          dex_files = oat_file_assistant.LoadDexFiles(*oat_file.get(), dex_location);
+          dex_files = oat_file_assistant->LoadDexFiles(*oat_file.get(), dex_location);
 
           // Register for tracking.
           for (const auto& dex_file : dex_files) {
@@ -336,26 +377,25 @@
       if (dex_files.empty()) {
         ScopedTrace failed_to_open_dex_files("FailedToOpenDexFilesFromOat");
         error_msgs->push_back("Failed to open dex files from " + odex_location);
-      } else {
-        // Opened dex files from an oat file, madvise them to their loaded state.
-         for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
-           OatDexFile::MadviseDexFileAtLoad(*dex_file);
-         }
+      } else if (should_madvise) {
+        size_t madvise_size_limit = Runtime::Current()->GetMadviseWillNeedTotalDexSize();
+        for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
+          // Prefetch the dex file based on vdex size limit (name should
+          // have been dex size limit).
+          VLOG(oat) << "Madvising dex file: " << dex_file->GetLocation();
+          Runtime::MadviseFileForRange(madvise_size_limit,
+                                       dex_file->Size(),
+                                       dex_file->Begin(),
+                                       dex_file->Begin() + dex_file->Size(),
+                                       dex_file->GetLocation());
+          if (dex_file->Size() >= madvise_size_limit) {
+            break;
+          }
+          madvise_size_limit -= dex_file->Size();
+        }
       }
 
       if (oat_file != nullptr) {
-        VdexFile* vdex_file = oat_file->GetVdexFile();
-        if (vdex_file != nullptr) {
-          // Opened vdex file from an oat file, madvise it to its loaded state.
-          // TODO(b/196052575): Unify dex and vdex madvise knobs and behavior.
-          const size_t madvise_size_limit = Runtime::Current()->GetMadviseWillNeedSizeVdex();
-          Runtime::MadviseFileForRange(madvise_size_limit,
-                                       vdex_file->Size(),
-                                       vdex_file->Begin(),
-                                       vdex_file->End(),
-                                       vdex_file->GetName());
-        }
-
         VLOG(class_linker) << "Registering " << oat_file->GetLocation();
         *out_oat_file = RegisterOatFile(std::move(oat_file));
       }
@@ -365,7 +405,7 @@
       // If so, report an error with the current stack trace.
       // Most likely the developer didn't intend to do this because it will waste
       // performance and memory.
-      if (oat_file_assistant.GetBestStatus() == OatFileAssistant::kOatContextOutOfDate) {
+      if (oat_file_assistant->GetBestStatus() == OatFileAssistant::kOatContextOutOfDate) {
         std::set<const DexFile*> already_exists_in_classpath =
             context->CheckForDuplicateDexFiles(MakeNonOwningPointerVector(dex_files));
         if (!already_exists_in_classpath.empty()) {
@@ -398,6 +438,12 @@
         }
       }
     }
+
+    Runtime::Current()->GetAppInfo()->RegisterOdexStatus(
+        dex_location,
+        compilation_filter,
+        compilation_reason,
+        odex_status);
   }
 
   // If we arrive here with an empty dex files list, it means we fail to load
@@ -405,10 +451,8 @@
   if (dex_files.empty()) {
     std::string error_msg;
     static constexpr bool kVerifyChecksum = true;
-    const ArtDexFileLoader dex_file_loader;
-    if (!dex_file_loader.Open(dex_location,
-                              dex_location,
-                              Runtime::Current()->IsVerificationEnabled(),
+    ArtDexFileLoader dex_file_loader(dex_location);
+    if (!dex_file_loader.Open(Runtime::Current()->IsVerificationEnabled(),
                               kVerifyChecksum,
                               /*out*/ &error_msg,
                               &dex_files)) {
@@ -511,11 +555,10 @@
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   for (size_t i = 0; i < dex_mem_maps.size(); ++i) {
     static constexpr bool kVerifyChecksum = true;
-    const ArtDexFileLoader dex_file_loader;
+    ArtDexFileLoader dex_file_loader(std::move(dex_mem_maps[i]),
+                                     DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()));
     std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(
-        DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()),
         dex_headers[i]->checksum_,
-        std::move(dex_mem_maps[i]),
         /* verify= */ (vdex_file == nullptr) && Runtime::Current()->IsVerificationEnabled(),
         kVerifyChecksum,
         &error_msg));
@@ -549,7 +592,8 @@
   // Initialize an OatFile instance backed by the loaded vdex.
   std::unique_ptr<OatFile> oat_file(OatFile::OpenFromVdex(MakeNonOwningPointerVector(dex_files),
                                                           std::move(vdex_file),
-                                                          dex_location));
+                                                          dex_location,
+                                                          context.get()));
   if (oat_file != nullptr) {
     VLOG(class_linker) << "Registering " << oat_file->GetLocation();
     *out_oat_file = RegisterOatFile(std::move(oat_file));
@@ -670,7 +714,7 @@
             h_loader)));
 
         if (h_class == nullptr) {
-          CHECK(self->IsExceptionPending());
+          DCHECK(self->IsExceptionPending());
           self->ClearException();
           continue;
         }
@@ -681,15 +725,14 @@
           continue;
         }
 
-        CHECK(h_class->IsResolved()) << h_class->PrettyDescriptor();
+        DCHECK(h_class->IsResolved()) << h_class->PrettyDescriptor();
         class_linker->VerifyClass(self, &verifier_deps, h_class);
-        if (h_class->IsErroneous()) {
-          // ClassLinker::VerifyClass throws, which isn't useful here.
-          CHECK(soa.Self()->IsExceptionPending());
-          soa.Self()->ClearException();
+        if (self->IsExceptionPending()) {
+          // ClassLinker::VerifyClass can throw, but the exception isn't useful here.
+          self->ClearException();
         }
 
-        CHECK(h_class->IsVerified() || h_class->IsErroneous())
+        DCHECK(h_class->IsVerified() || h_class->IsErroneous())
             << h_class->PrettyDescriptor() << ": state=" << h_class->GetStatus();
 
         if (h_class->IsVerified()) {
@@ -817,6 +860,15 @@
   verification_thread_pool_.reset(nullptr);
 }
 
+void OatFileManager::WaitForBackgroundVerificationTasksToFinish() {
+  if (verification_thread_pool_ == nullptr) {
+    return;
+  }
+
+  Thread* const self = Thread::Current();
+  verification_thread_pool_->Wait(self, /* do_work= */ true, /* may_hold_locks= */ false);
+}
+
 void OatFileManager::WaitForBackgroundVerificationTasks() {
   if (verification_thread_pool_ != nullptr) {
     Thread* const self = Thread::Current();
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index b73ac58..e09390b 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -134,6 +134,9 @@
   // If allocated, delete a thread pool of background verification threads.
   void DeleteThreadPool();
 
+  // Wait for any ongoing background verification tasks to finish.
+  void WaitForBackgroundVerificationTasksToFinish();
+
   // Wait for all background verification tasks to finish. This is only used by tests.
   void WaitForBackgroundVerificationTasks();
 
diff --git a/runtime/oat_file_test.cc b/runtime/oat_file_test.cc
index f332357..80265f7 100644
--- a/runtime/oat_file_test.cc
+++ b/runtime/oat_file_test.cc
@@ -18,17 +18,15 @@
 
 #include <string>
 
-#include <gtest/gtest.h>
-
 #include "common_runtime_test.h"
 #include "dexopt_test.h"
+#include "gtest/gtest.h"
 #include "scoped_thread_state_change-inl.h"
 #include "vdex_file.h"
 
 namespace art {
 
-class OatFileTest : public DexoptTest {
-};
+class OatFileTest : public DexoptTest {};
 
 TEST_F(OatFileTest, LoadOat) {
   std::string dex_location = GetScratchDir() + "/LoadOat.jar";
@@ -39,12 +37,13 @@
   std::string oat_location;
   std::string error_msg;
   ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
-        dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                   oat_location.c_str(),
-                                                   oat_location.c_str(),
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+      dex_location, kRuntimeISA, &oat_location, &error_msg))
+      << error_msg;
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                   oat_location,
+                                                   oat_location,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    dex_location,
                                                    &error_msg));
   ASSERT_TRUE(odex_file.get() != nullptr);
@@ -62,15 +61,16 @@
   std::string oat_location;
   std::string error_msg;
   ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
-        dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
+      dex_location, kRuntimeISA, &oat_location, &error_msg))
+      << error_msg;
 
   // Ensure we can load that file. Just a precondition.
   {
-    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
-                                                     oat_location.c_str(),
-                                                     oat_location.c_str(),
-                                                     /*executable=*/ false,
-                                                     /*low_4gb=*/ false,
+    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
+                                                     oat_location,
+                                                     oat_location,
+                                                     /*executable=*/false,
+                                                     /*low_4gb=*/false,
                                                      dex_location,
                                                      &error_msg));
     ASSERT_TRUE(odex_file != nullptr);
@@ -81,11 +81,11 @@
   Copy(GetTestDexFileName("MainUncompressedAligned"), dex_location);
 
   // And try to load again.
-  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
                                                    oat_location,
                                                    oat_location,
-                                                   /*executable=*/ false,
-                                                   /*low_4gb=*/ false,
+                                                   /*executable=*/false,
+                                                   /*low_4gb=*/false,
                                                    dex_location,
                                                    &error_msg));
   EXPECT_TRUE(odex_file == nullptr);
diff --git a/runtime/oat_quick_method_header.cc b/runtime/oat_quick_method_header.cc
index 8fbf02a..f0764da 100644
--- a/runtime/oat_quick_method_header.cc
+++ b/runtime/oat_quick_method_header.cc
@@ -57,33 +57,61 @@
 
 uintptr_t OatQuickMethodHeader::ToNativeQuickPc(ArtMethod* method,
                                                 const uint32_t dex_pc,
-                                                bool is_for_catch_handler,
                                                 bool abort_on_failure) const {
   const void* entry_point = GetEntryPoint();
   DCHECK(!method->IsNative());
+  // For catch handlers use the ArrayRef<const uint32_t> version of ToNativeQuickPc.
+  DCHECK(!IsNterpMethodHeader());
+  DCHECK(IsOptimized());
+  // Search for the dex-to-pc mapping in stack maps.
+  CodeInfo code_info = CodeInfo::DecodeInlineInfoOnly(this);
+
+  StackMap stack_map = code_info.GetStackMapForDexPc(dex_pc);
+  if (stack_map.IsValid()) {
+    return reinterpret_cast<uintptr_t>(entry_point) + stack_map.GetNativePcOffset(kRuntimeISA);
+  }
+  if (abort_on_failure) {
+    ScopedObjectAccess soa(Thread::Current());
+    LOG(FATAL) << "Failed to find native offset for dex pc 0x" << std::hex << dex_pc << " in "
+               << method->PrettyMethod();
+  }
+  return UINTPTR_MAX;
+}
+
+uintptr_t OatQuickMethodHeader::ToNativeQuickPcForCatchHandlers(
+    ArtMethod* method,
+    ArrayRef<const uint32_t> dex_pc_list,
+    /* out */ uint32_t* stack_map_row,
+    bool abort_on_failure) const {
+  const void* entry_point = GetEntryPoint();
+  DCHECK(!method->IsNative());
   if (IsNterpMethodHeader()) {
-    // This should only be called on an nterp frame for getting a catch handler.
-    CHECK(is_for_catch_handler);
     return NterpGetCatchHandler();
   }
   DCHECK(IsOptimized());
   // Search for the dex-to-pc mapping in stack maps.
   CodeInfo code_info = CodeInfo::DecodeInlineInfoOnly(this);
 
-  // All stack maps are stored in the same CodeItem section, safepoint stack
-  // maps first, then catch stack maps. We use `is_for_catch_handler` to select
-  // the order of iteration.
-  StackMap stack_map =
-      LIKELY(is_for_catch_handler) ? code_info.GetCatchStackMapForDexPc(dex_pc)
-                                   : code_info.GetStackMapForDexPc(dex_pc);
+  StackMap stack_map = code_info.GetCatchStackMapForDexPc(dex_pc_list);
+  *stack_map_row = stack_map.Row();
   if (stack_map.IsValid()) {
     return reinterpret_cast<uintptr_t>(entry_point) +
            stack_map.GetNativePcOffset(kRuntimeISA);
   }
   if (abort_on_failure) {
+    std::stringstream ss;
+    bool first = true;
+    ss << "Failed to find native offset for dex pcs (from outermost to innermost) " << std::hex;
+    for (auto dex_pc : dex_pc_list) {
+      if (!first) {
+        ss << ", ";
+      }
+      first = false;
+      ss << "0x" << dex_pc;
+    }
     ScopedObjectAccess soa(Thread::Current());
-    LOG(FATAL) << "Failed to find native offset for dex pc 0x" << std::hex << dex_pc
-               << " in " << method->PrettyMethod();
+    ss << " in " << method->PrettyMethod();
+    LOG(FATAL) << ss.str();
   }
   return UINTPTR_MAX;
 }
diff --git a/runtime/oat_quick_method_header.h b/runtime/oat_quick_method_header.h
index e347588..769d67f 100644
--- a/runtime/oat_quick_method_header.h
+++ b/runtime/oat_quick_method_header.h
@@ -28,6 +28,11 @@
 
 class ArtMethod;
 
+// Size in bytes of the should_deoptimize flag on stack.
+// We just need 4 bytes for our purpose regardless of the architecture. Frame size
+// calculation will automatically do alignment for the final frame size.
+static constexpr size_t kShouldDeoptimizeFlagSize = 4;
+
 // OatQuickMethodHeader precedes the raw code chunk generated by the compiler.
 class PACKED(4) OatQuickMethodHeader {
  public:
@@ -47,8 +52,8 @@
   static OatQuickMethodHeader* FromCodePointer(const void* code_ptr) {
     uintptr_t code = reinterpret_cast<uintptr_t>(code_ptr);
     uintptr_t header = code - OFFSETOF_MEMBER(OatQuickMethodHeader, code_);
-    DCHECK(IsAlignedParam(code, GetInstructionSetAlignment(kRuntimeISA)) ||
-           IsAlignedParam(header, GetInstructionSetAlignment(kRuntimeISA)))
+    DCHECK(IsAlignedParam(code, GetInstructionSetCodeAlignment(kRuntimeISA)) ||
+           IsAlignedParam(header, GetInstructionSetCodeAlignment(kRuntimeISA)))
         << std::hex << code << " " << std::hex << header;
     return reinterpret_cast<OatQuickMethodHeader*>(header);
   }
@@ -58,7 +63,7 @@
   }
 
   static size_t InstructionAlignedSize() {
-    return RoundUp(sizeof(OatQuickMethodHeader), GetInstructionSetAlignment(kRuntimeISA));
+    return RoundUp(sizeof(OatQuickMethodHeader), GetInstructionSetCodeAlignment(kRuntimeISA));
   }
 
   OatQuickMethodHeader(const OatQuickMethodHeader&) = default;
@@ -145,11 +150,28 @@
     return CodeInfo::DecodeFrameInfo(GetOptimizedCodeInfoPtr());
   }
 
+  size_t GetShouldDeoptimizeFlagOffset() const {
+    DCHECK(IsOptimized());
+    QuickMethodFrameInfo frame_info = GetFrameInfo();
+    size_t frame_size = frame_info.FrameSizeInBytes();
+    size_t core_spill_size =
+        POPCOUNT(frame_info.CoreSpillMask()) * GetBytesPerGprSpillLocation(kRuntimeISA);
+    size_t fpu_spill_size =
+        POPCOUNT(frame_info.FpSpillMask()) * GetBytesPerFprSpillLocation(kRuntimeISA);
+    return frame_size - core_spill_size - fpu_spill_size - kShouldDeoptimizeFlagSize;
+  }
+
+  // For non-catch handlers. Only used in test code.
   uintptr_t ToNativeQuickPc(ArtMethod* method,
                             const uint32_t dex_pc,
-                            bool is_for_catch_handler,
                             bool abort_on_failure = true) const;
 
+  // For catch handlers.
+  uintptr_t ToNativeQuickPcForCatchHandlers(ArtMethod* method,
+                                            ArrayRef<const uint32_t> dex_pc_list,
+                                            /* out */ uint32_t* stack_map_row,
+                                            bool abort_on_failure = true) const;
+
   uint32_t ToDexPc(ArtMethod** frame,
                    const uintptr_t pc,
                    bool abort_on_failure = true) const
diff --git a/runtime/obj_ptr.h b/runtime/obj_ptr.h
index a03b67b..7e52a50 100644
--- a/runtime/obj_ptr.h
+++ b/runtime/obj_ptr.h
@@ -48,16 +48,17 @@
                 "must have a least kObjectAlignmentShift bits");
 
  public:
-  OBJPTR_INLINE ObjPtr() REQUIRES_SHARED(Locks::mutator_lock_) : reference_(0u) {}
+  OBJPTR_INLINE ObjPtr() : ObjPtr(nullptr) {}
+
+  OBJPTR_INLINE ObjPtr(std::nullptr_t)
+      : reference_(0u) {
+    DCHECK(IsNull());
+  }
 
   // Note: The following constructors allow implicit conversion. This simplifies code that uses
   //       them, e.g., for parameter passing. However, in general, implicit-conversion constructors
   //       are discouraged and detected by clang-tidy.
 
-  OBJPTR_INLINE ObjPtr(std::nullptr_t)
-      REQUIRES_SHARED(Locks::mutator_lock_)
-      : reference_(0u) {}
-
   template <typename Type,
             typename = typename std::enable_if_t<std::is_base_of_v<MirrorType, Type>>>
   OBJPTR_INLINE ObjPtr(Type* ptr) REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/offsets.h b/runtime/offsets.h
index cc18bf4..7974111 100644
--- a/runtime/offsets.h
+++ b/runtime/offsets.h
@@ -37,12 +37,28 @@
   constexpr size_t SizeValue() const {
     return val_;
   }
+  Offset& operator+=(const size_t rhs) {
+    val_ += rhs;
+    return *this;
+  }
   constexpr bool operator==(Offset o) const {
     return SizeValue() == o.SizeValue();
   }
   constexpr bool operator!=(Offset o) const {
     return !(*this == o);
   }
+  constexpr bool operator<(Offset o) const {
+    return SizeValue() < o.SizeValue();
+  }
+  constexpr bool operator<=(Offset o) const {
+    return !(*this > o);
+  }
+  constexpr bool operator>(Offset o) const {
+    return o < *this;
+  }
+  constexpr bool operator>=(Offset o) const {
+    return !(*this < o);
+  }
 
  protected:
   size_t val_;
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index 4d24482..7df765f 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -82,6 +82,7 @@
   DCHECK_EQ(hiddenapi_policy_valuemap.size(),
             static_cast<size_t>(hiddenapi::EnforcementPolicy::kMax) + 1);
 
+  // clang-format off
   parser_builder->
        SetCategory("standard")
       .Define({"-classpath _", "-cp _"})
@@ -183,6 +184,10 @@
           .IntoKey(M::Image)
       .Define("-Xforcejitzygote")
           .IntoKey(M::ForceJitZygote)
+      .Define("-Xallowinmemorycompilation")
+          .WithHelp("Allows compiling the boot classpath in memory when the given boot image is"
+              "unusable. This option is set by default for Zygote.")
+          .IntoKey(M::AllowInMemoryCompilation)
       .Define("-Xprimaryzygote")
           .IntoKey(M::PrimaryZygote)
       .Define("-Xbootclasspath-locations:_")
@@ -245,6 +250,7 @@
           .WithValueMap({{"false", false}, {"true", true}})
           .IntoKey(M::DumpNativeStackOnSigQuit)
       .Define("-XX:MadviseRandomAccess:_")
+          .WithHelp("Deprecated option")
           .WithType<bool>()
           .WithValueMap({{"false", false}, {"true", true}})
           .IntoKey(M::MadviseRandomAccess)
@@ -351,6 +357,12 @@
           .IntoKey(M::MethodTraceFileSize)
       .Define("-Xmethod-trace-stream")
           .IntoKey(M::MethodTraceStreaming)
+      .Define("-Xmethod-trace-clock:_")
+          .WithType<TraceClockSource>()
+          .WithValueMap({{"threadcpuclock", TraceClockSource::kThreadCpu},
+                         {"wallclock",      TraceClockSource::kWall},
+                         {"dualclock",      TraceClockSource::kDual}})
+          .IntoKey(M::MethodTraceClock)
       .Define("-Xcompiler:_")
           .WithType<std::string>()
           .IntoKey(M::Compiler)
@@ -464,18 +476,44 @@
           .WithType<bool>()
           .WithValueMap({{"false", false}, {"true", true}})
           .IntoKey(M::PerfettoJavaHeapStackProf);
+  // clang-format on
 
-      FlagBase::AddFlagsToCmdlineParser(parser_builder.get());
+  FlagBase::AddFlagsToCmdlineParser(parser_builder.get());
 
-      parser_builder->Ignore({
-          "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa",
-          "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_",
-          "-Xdexopt:_", "-Xnoquithandler", "-Xjnigreflimit:_", "-Xgenregmap", "-Xnogenregmap",
-          "-Xverifyopt:_", "-Xcheckdexsum", "-Xincludeselectedop", "-Xjitop:_",
-          "-Xincludeselectedmethod",
-          "-Xjitblocking", "-Xjitmethod:_", "-Xjitclass:_", "-Xjitoffset:_",
-          "-Xjitosrthreshold:_", "-Xjitconfig:_", "-Xjitcheckcg", "-Xjitverbose", "-Xjitprofile",
-          "-Xjitdisableopt", "-Xjitsuspendpoll", "-XX:mainThreadStackSize=_"})
+  parser_builder
+      ->Ignore({"-ea",
+                "-da",
+                "-enableassertions",
+                "-disableassertions",
+                "--runtime-arg",
+                "-esa",
+                "-dsa",
+                "-enablesystemassertions",
+                "-disablesystemassertions",
+                "-Xrs",
+                "-Xint:_",
+                "-Xdexopt:_",
+                "-Xnoquithandler",
+                "-Xjnigreflimit:_",
+                "-Xgenregmap",
+                "-Xnogenregmap",
+                "-Xverifyopt:_",
+                "-Xcheckdexsum",
+                "-Xincludeselectedop",
+                "-Xjitop:_",
+                "-Xincludeselectedmethod",
+                "-Xjitblocking",
+                "-Xjitmethod:_",
+                "-Xjitclass:_",
+                "-Xjitoffset:_",
+                "-Xjitosrthreshold:_",
+                "-Xjitconfig:_",
+                "-Xjitcheckcg",
+                "-Xjitverbose",
+                "-Xjitprofile",
+                "-Xjitdisableopt",
+                "-Xjitsuspendpoll",
+                "-XX:mainThreadStackSize=_"})
       .IgnoreUnrecognized(ignore_unrecognized)
       .OrderCategories({"standard", "extended", "Dalvik", "ART"});
 
@@ -637,6 +675,7 @@
 
   using M = RuntimeArgumentMap;
   RuntimeArgumentMap args = parser->ReleaseArgumentsMap();
+  bool use_default_bootclasspath = true;
 
   // -help, -showversion, etc.
   if (args.Exists(M::Help)) {
@@ -650,6 +689,7 @@
     Exit(0);
   } else if (args.Exists(M::BootClassPath)) {
     LOG(INFO) << "setting boot class path to " << args.Get(M::BootClassPath)->Join();
+    use_default_bootclasspath = false;
   }
 
   if (args.GetOrDefault(M::Interpret)) {
@@ -732,12 +772,15 @@
       Exit(0);
     }
     // If `boot.art` exists in the ART APEX, it will be used. Otherwise, Everything will be JITed.
-    args.Set(M::Image,
-             ParseStringList<':'>{{"boot.art!/apex/com.android.art/etc/boot-image.prof",
-                                   "/nonx/boot-framework.art!/system/etc/boot-image.prof"}});
+    args.Set(M::Image, ParseStringList<':'>::Split(GetJitZygoteBootImageLocation()));
   }
 
-  if (!args.Exists(M::CompilerCallbacksPtr) && !args.Exists(M::Image)) {
+  if (args.Exists(M::Zygote)) {
+    args.Set(M::AllowInMemoryCompilation, Unit());
+  }
+
+  if (!args.Exists(M::CompilerCallbacksPtr) && !args.Exists(M::Image) &&
+      use_default_bootclasspath) {
     const bool deny_art_apex_data_files = args.Exists(M::DenyArtApexDataFiles);
     std::string image_locations =
         GetDefaultBootImageLocation(GetAndroidRoot(), deny_art_apex_data_files);
diff --git a/runtime/proxy_test.cc b/runtime/proxy_test.cc
index d055186..ac8ec56 100644
--- a/runtime/proxy_test.cc
+++ b/runtime/proxy_test.cc
@@ -30,15 +30,8 @@
 
 class ProxyTest : public CommonRuntimeTest {
  protected:
-  void SetUp() override {
-    CommonRuntimeTest::SetUp();
-    // The creation of a Proxy class uses WellKnownClasses. These are not normally initialized by
-    // CommonRuntimeTest so we need to do that now.
-    WellKnownClasses::Clear();
-    WellKnownClasses::Init(art::Thread::Current()->GetJniEnv());
-    // Since we aren't actually calling any of the native functions we can just immediately call
-    // LateInit after calling Init.
-    WellKnownClasses::LateInit(art::Thread::Current()->GetJniEnv());
+  ProxyTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
   }
 };
 
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index 2a6929a..fd57b4a 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -15,10 +15,14 @@
  */
 
 #include "quick_exception_handler.h"
+
 #include <ios>
+#include <queue>
+#include <sstream>
 
 #include "arch/context.h"
 #include "art_method-inl.h"
+#include "base/array_ref.h"
 #include "base/enums.h"
 #include "base/globals.h"
 #include "base/logging.h"  // For VLOG_IS_ON.
@@ -49,13 +53,10 @@
     : self_(self),
       context_(self->GetLongJumpContext()),
       is_deoptimization_(is_deoptimization),
-      method_tracing_active_(is_deoptimization ||
-                             Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()),
       handler_quick_frame_(nullptr),
       handler_quick_frame_pc_(0),
       handler_method_header_(nullptr),
       handler_quick_arg0_(0),
-      handler_dex_pc_(0),
       clear_exception_(false),
       handler_frame_depth_(kInvalidFrameDepth),
       full_fragment_done_(false) {}
@@ -67,12 +68,15 @@
                          Context* context,
                          Handle<mirror::Throwable>* exception,
                          QuickExceptionHandler* exception_handler,
-                         uint32_t skip_frames)
+                         uint32_t skip_frames,
+                         bool skip_top_unwind_callback)
       REQUIRES_SHARED(Locks::mutator_lock_)
       : StackVisitor(self, context, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
         exception_(exception),
         exception_handler_(exception_handler),
-        skip_frames_(skip_frames) {
+        skip_frames_(skip_frames),
+        skip_unwind_callback_(skip_top_unwind_callback) {
+    DCHECK_IMPLIES(skip_unwind_callback_, skip_frames_ == 0);
   }
 
   bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -95,7 +99,24 @@
       DCHECK(method->IsCalleeSaveMethod());
       return true;
     }
-    return HandleTryItems(method);
+    bool continue_stack_walk = HandleTryItems(method);
+    // Collect methods for which MethodUnwind callback needs to be invoked. MethodUnwind callback
+    // can potentially throw, so we want to call these after we find the catch block.
+    // We stop the stack walk when we find the catch block. If we are ending the stack walk we don't
+    // have to unwind this method so don't record it.
+    if (continue_stack_walk && !skip_unwind_callback_) {
+      // Skip unwind callback is only used when method exit callback has thrown an exception. In
+      // that case, we should have runtime method (artMethodExitHook) on top of stack and the
+      // second should be the method for which method exit was called.
+      DCHECK_IMPLIES(skip_unwind_callback_, GetFrameDepth() == 2);
+      unwound_methods_.push(method);
+    }
+    skip_unwind_callback_ = false;
+    return continue_stack_walk;
+  }
+
+  std::queue<ArtMethod*>& GetUnwoundMethods() {
+    return unwound_methods_;
   }
 
  private:
@@ -112,10 +133,12 @@
       uint32_t found_dex_pc = method->FindCatchBlock(to_find, dex_pc, &clear_exception);
       exception_handler_->SetClearException(clear_exception);
       if (found_dex_pc != dex::kDexNoIndex) {
-        exception_handler_->SetHandlerDexPc(found_dex_pc);
+        exception_handler_->SetHandlerDexPcList(ComputeDexPcList(found_dex_pc));
+        uint32_t stack_map_row = -1;
         exception_handler_->SetHandlerQuickFramePc(
-            GetCurrentOatQuickMethodHeader()->ToNativeQuickPc(
-                method, found_dex_pc, /* is_for_catch_handler= */ true));
+            GetCurrentOatQuickMethodHeader()->ToNativeQuickPcForCatchHandlers(
+                method, exception_handler_->GetHandlerDexPcList(), &stack_map_row));
+        exception_handler_->SetCatchStackMapRow(stack_map_row);
         exception_handler_->SetHandlerQuickFrame(GetCurrentQuickFrame());
         exception_handler_->SetHandlerMethodHeader(GetCurrentOatQuickMethodHeader());
         return false;  // End stack walk.
@@ -139,20 +162,29 @@
   QuickExceptionHandler* const exception_handler_;
   // The number of frames to skip searching for catches in.
   uint32_t skip_frames_;
+  // The list of methods we would skip to reach the catch block. We record these to call
+  // MethodUnwind callbacks.
+  std::queue<ArtMethod*> unwound_methods_;
+  // Specifies if the unwind callback should be ignored for method at the top of the stack.
+  bool skip_unwind_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(CatchBlockStackVisitor);
 };
 
 // Finds the appropriate exception catch after calling all method exit instrumentation functions.
-// Note that this might change the exception being thrown.
-void QuickExceptionHandler::FindCatch(ObjPtr<mirror::Throwable> exception) {
+// Note that this might change the exception being thrown. If is_method_exit_exception is true
+// skip the method unwind call for the method on top of the stack as the exception was thrown by
+// method exit callback.
+void QuickExceptionHandler::FindCatch(ObjPtr<mirror::Throwable> exception,
+                                      bool is_method_exit_exception) {
   DCHECK(!is_deoptimization_);
-  instrumentation::InstrumentationStackPopper popper(self_);
+  instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
   // The number of total frames we have so far popped.
   uint32_t already_popped = 0;
   bool popped_to_top = true;
   StackHandleScope<1> hs(self_);
   MutableHandle<mirror::Throwable> exception_ref(hs.NewHandle(exception));
+  bool skip_top_unwind_callback = is_method_exit_exception;
   // Sending the instrumentation events (done by the InstrumentationStackPopper) can cause new
   // exceptions to be thrown which will override the current exception. Therefore we need to perform
   // the search for a catch in a loop until we have successfully popped all the way to a catch or
@@ -166,11 +198,15 @@
     }
 
     // Walk the stack to find catch handler.
-    CatchBlockStackVisitor visitor(self_, context_,
+    CatchBlockStackVisitor visitor(self_,
+                                   context_,
                                    &exception_ref,
                                    this,
-                                   /*skip_frames=*/already_popped);
+                                   /*skip_frames=*/already_popped,
+                                   skip_top_unwind_callback);
     visitor.WalkStack(true);
+    skip_top_unwind_callback = false;
+
     uint32_t new_pop_count = handler_frame_depth_;
     DCHECK_GE(new_pop_count, already_popped);
     already_popped = new_pop_count;
@@ -181,10 +217,25 @@
       }
       if (GetHandlerMethod() != nullptr) {
         const DexFile* dex_file = GetHandlerMethod()->GetDexFile();
-        int line_number =
-            annotations::GetLineNumFromPC(dex_file, GetHandlerMethod(), handler_dex_pc_);
+        DCHECK(handler_dex_pc_list_.has_value());
+        DCHECK_GE(handler_dex_pc_list_->size(), 1u);
+        int line_number = annotations::GetLineNumFromPC(
+            dex_file, GetHandlerMethod(), handler_dex_pc_list_->front());
+
+        // We may have an inlined method. If so, we can add some extra logging.
+        std::stringstream ss;
+        ArtMethod* maybe_inlined_method = visitor.GetMethod();
+        if (maybe_inlined_method != GetHandlerMethod()) {
+          const DexFile* inlined_dex_file = maybe_inlined_method->GetDexFile();
+          DCHECK_GE(handler_dex_pc_list_->size(), 2u);
+          int inlined_line_number = annotations::GetLineNumFromPC(
+              inlined_dex_file, maybe_inlined_method, handler_dex_pc_list_->back());
+          ss << " which ends up calling inlined method " << maybe_inlined_method->PrettyMethod()
+             << " (line: " << inlined_line_number << ")";
+        }
+
         LOG(INFO) << "Handler: " << GetHandlerMethod()->PrettyMethod() << " (line: "
-                  << line_number << ")";
+                  << line_number << ")" << ss.str();
       }
     }
     // Exception was cleared as part of delivery.
@@ -195,9 +246,11 @@
         handler_method_header_->IsOptimized()) {
       SetCatchEnvironmentForOptimizedHandler(&visitor);
     }
-    popped_to_top =
-        popper.PopFramesTo(reinterpret_cast<uintptr_t>(handler_quick_frame_), exception_ref);
+    popped_to_top = instr->ProcessMethodUnwindCallbacks(self_,
+                                                        visitor.GetUnwoundMethods(),
+                                                        exception_ref);
   } while (!popped_to_top);
+
   if (!clear_exception_) {
     // Put exception back in root set with clear throw location.
     self_->SetException(exception_ref.Get());
@@ -245,15 +298,18 @@
     self_->DumpStack(LOG_STREAM(INFO) << "Setting catch phis: ");
   }
 
-  CodeItemDataAccessor accessor(GetHandlerMethod()->DexInstructionData());
-  const size_t number_of_vregs = accessor.RegistersSize();
   CodeInfo code_info(handler_method_header_);
 
   // Find stack map of the catch block.
-  StackMap catch_stack_map = code_info.GetCatchStackMapForDexPc(GetHandlerDexPc());
+  ArrayRef<const uint32_t> dex_pc_list = GetHandlerDexPcList();
+  DCHECK_GE(dex_pc_list.size(), 1u);
+  StackMap catch_stack_map = code_info.GetStackMapAt(GetCatchStackMapRow());
   DCHECK(catch_stack_map.IsValid());
-  DexRegisterMap catch_vreg_map = code_info.GetDexRegisterMapOf(catch_stack_map);
-  DCHECK_EQ(catch_vreg_map.size(), number_of_vregs);
+  DCHECK_EQ(catch_stack_map.Row(), code_info.GetCatchStackMapForDexPc(dex_pc_list).Row());
+  const uint32_t catch_depth = dex_pc_list.size() - 1;
+  const size_t number_of_registers = stack_visitor->GetNumberOfRegisters(&code_info, catch_depth);
+  DexRegisterMap catch_vreg_map =
+      code_info.GetDexRegisterMapOf(catch_stack_map, /* first= */ 0, number_of_registers);
 
   if (!catch_vreg_map.HasAnyLiveDexRegisters()) {
     return;
@@ -263,26 +319,47 @@
   StackMap throw_stack_map =
       code_info.GetStackMapForNativePcOffset(stack_visitor->GetNativePcOffset());
   DCHECK(throw_stack_map.IsValid());
-  DexRegisterMap throw_vreg_map = code_info.GetDexRegisterMapOf(throw_stack_map);
-  DCHECK_EQ(throw_vreg_map.size(), number_of_vregs);
+  const uint32_t throw_depth = stack_visitor->InlineDepth();
+  DCHECK_EQ(throw_depth, catch_depth);
+  DexRegisterMap throw_vreg_map =
+      code_info.GetDexRegisterMapOf(throw_stack_map, /* first= */ 0, number_of_registers);
+  DCHECK_EQ(throw_vreg_map.size(), catch_vreg_map.size());
 
-  // Copy values between them.
-  for (uint16_t vreg = 0; vreg < number_of_vregs; ++vreg) {
-    DexRegisterLocation::Kind catch_location = catch_vreg_map[vreg].GetKind();
-    if (catch_location == DexRegisterLocation::Kind::kNone) {
+  // First vreg that it is part of the catch's environment.
+  const size_t catch_vreg_start = catch_depth == 0
+    ? 0
+    : stack_visitor->GetNumberOfRegisters(&code_info, catch_depth - 1);
+
+  // We don't need to copy anything in the parent's environment.
+  for (size_t vreg = 0; vreg < catch_vreg_start; ++vreg) {
+    DexRegisterLocation::Kind catch_location_kind = catch_vreg_map[vreg].GetKind();
+    DCHECK(catch_location_kind == DexRegisterLocation::Kind::kNone ||
+           catch_location_kind == DexRegisterLocation::Kind::kConstant ||
+           catch_location_kind == DexRegisterLocation::Kind::kInStack)
+        << "Unexpected catch_location_kind: " << catch_location_kind;
+  }
+
+  // Copy values between the throw and the catch.
+  for (size_t vreg = catch_vreg_start; vreg < catch_vreg_map.size(); ++vreg) {
+    DexRegisterLocation::Kind catch_location_kind = catch_vreg_map[vreg].GetKind();
+    if (catch_location_kind == DexRegisterLocation::Kind::kNone) {
       continue;
     }
-    DCHECK(catch_location == DexRegisterLocation::Kind::kInStack);
 
-    // Get vreg value from its current location.
+    // Consistency checks.
+    DCHECK_EQ(catch_location_kind, DexRegisterLocation::Kind::kInStack);
     uint32_t vreg_value;
     VRegKind vreg_kind = ToVRegKind(throw_vreg_map[vreg].GetKind());
-    bool get_vreg_success =
-        stack_visitor->GetVReg(stack_visitor->GetMethod(),
-                               vreg,
-                               vreg_kind,
-                               &vreg_value,
-                               throw_vreg_map[vreg]);
+    DCHECK_NE(vreg_kind, kReferenceVReg)
+        << "The fast path in GetVReg doesn't expect a kReferenceVReg.";
+
+    // Get vreg value from its current location.
+    bool get_vreg_success = stack_visitor->GetVReg(stack_visitor->GetMethod(),
+                                                   vreg,
+                                                   vreg_kind,
+                                                   &vreg_value,
+                                                   throw_vreg_map[vreg],
+                                                   /* need_full_register_list= */ true);
     CHECK(get_vreg_success) << "VReg " << vreg << " was optimized out ("
                             << "method=" << ArtMethod::PrettyMethod(stack_visitor->GetMethod())
                             << ", dex_pc=" << stack_visitor->GetDexPc() << ", "
@@ -303,8 +380,8 @@
   DeoptimizeStackVisitor(Thread* self,
                          Context* context,
                          QuickExceptionHandler* exception_handler,
-                         bool single_frame)
-      REQUIRES_SHARED(Locks::mutator_lock_)
+                         bool single_frame,
+                         bool skip_method_exit_callbacks) REQUIRES_SHARED(Locks::mutator_lock_)
       : StackVisitor(self, context, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
         exception_handler_(exception_handler),
         prev_shadow_frame_(nullptr),
@@ -313,8 +390,8 @@
         single_frame_done_(false),
         single_frame_deopt_method_(nullptr),
         single_frame_deopt_quick_method_header_(nullptr),
-        callee_method_(nullptr) {
-  }
+        callee_method_(nullptr),
+        skip_method_exit_callbacks_(skip_method_exit_callbacks) {}
 
   ArtMethod* GetSingleFrameDeoptMethod() const {
     return single_frame_deopt_method_;
@@ -361,13 +438,15 @@
       return true;
     } else if (method->IsNative()) {
       // If we return from JNI with a pending exception and want to deoptimize, we need to skip
-      // the native method.
-      // The top method is a runtime method, the native method comes next.
-      CHECK_EQ(GetFrameDepth(), 1U);
+      // the native method. The top method is a runtime method, the native method comes next.
+      // We also deoptimize due to method instrumentation reasons from method entry / exit
+      // callbacks. In these cases native method is at the top of stack.
+      CHECK((GetFrameDepth() == 1U) || (GetFrameDepth() == 0U));
       callee_method_ = method;
       return true;
     } else if (!single_frame_deopt_ &&
-               !Runtime::Current()->IsAsyncDeoptimizeable(GetCurrentQuickFramePc())) {
+               !Runtime::Current()->IsAsyncDeoptimizeable(GetOuterMethod(),
+                                                          GetCurrentQuickFramePc())) {
       // We hit some code that's not deoptimizeable. However, Single-frame deoptimization triggered
       // from compiled code is always allowed since HDeoptimize always saves the full environment.
       LOG(WARNING) << "Got request to deoptimize un-deoptimizable method "
@@ -382,7 +461,7 @@
       CodeItemDataAccessor accessor(method->DexInstructionData());
       const size_t num_regs = accessor.RegistersSize();
       if (new_frame == nullptr) {
-        new_frame = ShadowFrame::CreateDeoptimizedFrame(num_regs, nullptr, method, GetDexPc());
+        new_frame = ShadowFrame::CreateDeoptimizedFrame(num_regs, method, GetDexPc());
         updated_vregs = nullptr;
       } else {
         updated_vregs = GetThread()->GetUpdatedVRegFlags(frame_id);
@@ -393,6 +472,25 @@
       } else {
         HandleOptimizingDeoptimization(method, new_frame, updated_vregs);
       }
+      // Update if method exit event needs to be reported. We should report exit event only if we
+      // have reported an entry event. So tell interpreter if/ an entry event was reported.
+      bool supports_exit_events =
+          Runtime::Current()->GetInstrumentation()->MethodSupportsExitEvents(
+              method, GetCurrentOatQuickMethodHeader());
+      new_frame->SetSkipMethodExitEvents(!supports_exit_events);
+      // If we are deoptimizing after method exit callback we shouldn't call the method exit
+      // callbacks again for the top frame. We may have to deopt after the callback if the callback
+      // either throws or performs other actions that require a deopt.
+      // We only need to skip for the top frame and the rest of the frames should still run the
+      // callbacks. So only do this check for the top frame.
+      if (GetFrameDepth() == 0U && skip_method_exit_callbacks_) {
+        new_frame->SetSkipMethodExitEvents(true);
+        // This exception was raised by method exit callbacks and we shouldn't report it to
+        // listeners for these exceptions.
+        if (GetThread()->IsExceptionPending()) {
+          new_frame->SetSkipNextExceptionEvent(true);
+        }
+      }
       if (updated_vregs != nullptr) {
         // Calling Thread::RemoveDebuggerShadowFrameMapping will also delete the updated_vregs
         // array so this must come after we processed the frame.
@@ -549,6 +647,10 @@
   ArtMethod* single_frame_deopt_method_;
   const OatQuickMethodHeader* single_frame_deopt_quick_method_header_;
   ArtMethod* callee_method_;
+  // This specifies if method exit callbacks should be skipped for the top frame. We may request
+  // a deopt after running method exit callbacks if the callback throws or requests events that
+  // need a deopt.
+  bool skip_method_exit_callbacks_;
 
   DISALLOW_COPY_AND_ASSIGN(DeoptimizeStackVisitor);
 };
@@ -568,13 +670,13 @@
   }
 }
 
-void QuickExceptionHandler::DeoptimizeStack() {
+void QuickExceptionHandler::DeoptimizeStack(bool skip_method_exit_callbacks) {
   DCHECK(is_deoptimization_);
   if (kDebugExceptionDelivery) {
     self_->DumpStack(LOG_STREAM(INFO) << "Deoptimizing: ");
   }
 
-  DeoptimizeStackVisitor visitor(self_, context_, this, false);
+  DeoptimizeStackVisitor visitor(self_, context_, this, false, skip_method_exit_callbacks);
   visitor.WalkStack(true);
   PrepareForLongJumpToInvokeStubOrInterpreterBridge();
 }
@@ -582,7 +684,10 @@
 void QuickExceptionHandler::DeoptimizeSingleFrame(DeoptimizationKind kind) {
   DCHECK(is_deoptimization_);
 
-  DeoptimizeStackVisitor visitor(self_, context_, this, true);
+  // This deopt is requested while still executing the method. We haven't run method exit callbacks
+  // yet, so don't skip them.
+  DeoptimizeStackVisitor visitor(
+      self_, context_, this, true, /* skip_method_exit_callbacks= */ false);
   visitor.WalkStack(true);
 
   // Compiled code made an explicit deoptimization.
@@ -613,21 +718,8 @@
   PrepareForLongJumpToInvokeStubOrInterpreterBridge();
 }
 
-void QuickExceptionHandler::DeoptimizePartialFragmentFixup(uintptr_t return_pc) {
-  // At this point, the instrumentation stack has been updated. We need to install
-  // the real return pc on stack, in case instrumentation stub is stored there,
-  // so that the interpreter bridge code can return to the right place. JITed
-  // frames in Java debuggable runtimes may not have an instrumentation stub, so
-  // update the PC only when required.
-  uintptr_t* pc_addr = reinterpret_cast<uintptr_t*>(handler_quick_frame_);
-  CHECK(pc_addr != nullptr);
-  pc_addr--;
-  if (return_pc != 0 &&
-      (*reinterpret_cast<uintptr_t*>(pc_addr)) ==
-          reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc())) {
-    *reinterpret_cast<uintptr_t*>(pc_addr) = return_pc;
-  }
-
+void QuickExceptionHandler::DeoptimizePartialFragmentFixup() {
+  CHECK(handler_quick_frame_ != nullptr);
   // Architecture-dependent work. This is to get the LR right for x86 and x86-64.
   if (kRuntimeISA == InstructionSet::kX86 || kRuntimeISA == InstructionSet::kX86_64) {
     // On x86, the return address is on the stack, so just reuse it. Otherwise we would have to
@@ -637,17 +729,6 @@
   }
 }
 
-uintptr_t QuickExceptionHandler::UpdateInstrumentationStack() {
-  DCHECK(is_deoptimization_) << "Non-deoptimization handlers should use FindCatch";
-  uintptr_t return_pc = 0;
-  if (method_tracing_active_) {
-    instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
-    return_pc = instrumentation->PopFramesForDeoptimization(
-        self_, reinterpret_cast<uintptr_t>(handler_quick_frame_));
-  }
-  return return_pc;
-}
-
 void QuickExceptionHandler::DoLongJump(bool smash_caller_saves) {
   // Place context back on thread so it will be available when we continue.
   self_->ReleaseLongJumpContext(context_);
@@ -661,9 +742,14 @@
   if (!is_deoptimization_ &&
       handler_method_header_ != nullptr &&
       handler_method_header_->IsNterpMethodHeader()) {
+    // Interpreter procceses one method at a time i.e. not inlining
+    DCHECK(handler_dex_pc_list_.has_value());
+    DCHECK_EQ(handler_dex_pc_list_->size(), 1u) << "We shouldn't have any inlined frames.";
     context_->SetNterpDexPC(reinterpret_cast<uintptr_t>(
-        GetHandlerMethod()->DexInstructions().Insns() + handler_dex_pc_));
+        GetHandlerMethod()->DexInstructions().Insns() + handler_dex_pc_list_->front()));
   }
+  // Clear the dex_pc list so as not to leak memory.
+  handler_dex_pc_list_.reset();
   context_->DoLongJump();
   UNREACHABLE();
 }
diff --git a/runtime/quick_exception_handler.h b/runtime/quick_exception_handler.h
index 4ff981d..39462e5 100644
--- a/runtime/quick_exception_handler.h
+++ b/runtime/quick_exception_handler.h
@@ -18,10 +18,14 @@
 #define ART_RUNTIME_QUICK_EXCEPTION_HANDLER_H_
 
 #include <android-base/logging.h>
+#include <cstdint>
+#include <optional>
 
+#include "base/array_ref.h"
 #include "base/macros.h"
 #include "base/mutex.h"
 #include "deoptimization_kind.h"
+#include "stack_map.h"
 #include "stack_reference.h"
 
 namespace art {
@@ -49,12 +53,16 @@
 
   // Find the catch handler for the given exception and call all required Instrumentation methods.
   // Note this might result in the exception being caught being different from 'exception'.
-  void FindCatch(ObjPtr<mirror::Throwable> exception) REQUIRES_SHARED(Locks::mutator_lock_);
+  void FindCatch(ObjPtr<mirror::Throwable> exception, bool is_method_exit_exception)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Deoptimize the stack to the upcall/some code that's not deoptimizeable. For
   // every compiled frame, we create a "copy" shadow frame that will be executed
   // with the interpreter.
-  void DeoptimizeStack() REQUIRES_SHARED(Locks::mutator_lock_);
+  // skip_method_exit_callbacks specifies if we should skip method exit callbacks for the top frame.
+  // It is set if a deopt is needed after calling method exit callback for ex: if the callback
+  // throws or performs other actions that require a deopt.
+  void DeoptimizeStack(bool skip_method_exit_callbacks) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Deoptimize a single frame. It's directly triggered from compiled code. It
   // has the following properties:
@@ -67,13 +75,7 @@
   //   on whether that single frame covers full or partial fragment.
   void DeoptimizeSingleFrame(DeoptimizationKind kind) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void DeoptimizePartialFragmentFixup(uintptr_t return_pc)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
-  // Update the instrumentation stack by removing all methods that will be unwound
-  // by the exception being thrown.
-  // Return the return pc of the last frame that's unwound.
-  uintptr_t UpdateInstrumentationStack() REQUIRES_SHARED(Locks::mutator_lock_);
+  void DeoptimizePartialFragmentFixup() REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Set up environment before delivering an exception to optimized code.
   void SetCatchEnvironmentForOptimizedHandler(StackVisitor* stack_visitor)
@@ -102,12 +104,21 @@
     return *handler_quick_frame_;
   }
 
-  uint32_t GetHandlerDexPc() const {
-    return handler_dex_pc_;
+  ArrayRef<const uint32_t> GetHandlerDexPcList() const {
+    DCHECK(handler_dex_pc_list_.has_value());
+    return ArrayRef<const uint32_t>(handler_dex_pc_list_.value());
   }
 
-  void SetHandlerDexPc(uint32_t dex_pc) {
-    handler_dex_pc_ = dex_pc;
+  void SetHandlerDexPcList(std::vector<uint32_t>&& handler_dex_pc_list) {
+    handler_dex_pc_list_ = std::move(handler_dex_pc_list);
+  }
+
+  uint32_t GetCatchStackMapRow() const {
+    return catch_stack_map_row_;
+  }
+
+  void SetCatchStackMapRow(uint32_t stack_map_row) {
+    catch_stack_map_row_ = stack_map_row;
   }
 
   bool GetClearException() const {
@@ -140,8 +151,6 @@
   Context* const context_;
   // Should we deoptimize the stack?
   const bool is_deoptimization_;
-  // Is method tracing active?
-  const bool method_tracing_active_;
   // Quick frame with found handler or last frame if no handler found.
   ArtMethod** handler_quick_frame_;
   // PC to branch to for the handler.
@@ -150,8 +159,12 @@
   const OatQuickMethodHeader* handler_method_header_;
   // The value for argument 0.
   uintptr_t handler_quick_arg0_;
-  // The handler's dex PC, zero implies an uncaught exception.
-  uint32_t handler_dex_pc_;
+  // The handler's dex PC list including the inline dex_pcs. The dex_pcs are ordered from outermost
+  // to innermost. An empty list implies an uncaught exception.
+  // Marked as optional so that we can make sure we destroy it before doing a long jump.
+  std::optional<std::vector<uint32_t>> handler_dex_pc_list_;
+  // StackMap row corresponding to the found catch.
+  uint32_t catch_stack_map_row_;
   // Should the exception be cleared as the catch block has no move-exception?
   bool clear_exception_;
   // Frame depth of the catch handler or the upcall.
diff --git a/runtime/read_barrier-inl.h b/runtime/read_barrier-inl.h
index b0434d8..91406c6 100644
--- a/runtime/read_barrier-inl.h
+++ b/runtime/read_barrier-inl.h
@@ -21,6 +21,7 @@
 
 #include "gc/accounting/read_barrier_table.h"
 #include "gc/collector/concurrent_copying-inl.h"
+#include "gc/collector/mark_compact.h"
 #include "gc/heap.h"
 #include "mirror/object-readbarrier-inl.h"
 #include "mirror/object_reference.h"
@@ -34,12 +35,11 @@
 inline MirrorType* ReadBarrier::Barrier(
     mirror::Object* obj, MemberOffset offset, mirror::HeapReference<MirrorType>* ref_addr) {
   constexpr bool with_read_barrier = kReadBarrierOption == kWithReadBarrier;
-  if (kUseReadBarrier && with_read_barrier) {
+  if (gUseReadBarrier && with_read_barrier) {
     if (kCheckDebugDisallowReadBarrierCount) {
       Thread* const self = Thread::Current();
-      if (self != nullptr) {
-        CHECK_EQ(self->GetDebugDisallowReadBarrierCount(), 0u);
-      }
+      CHECK(self != nullptr);
+      CHECK_EQ(self->GetDebugDisallowReadBarrierCount(), 0u);
     }
     if (kUseBakerReadBarrier) {
       // fake_address_dependency (must be zero) is used to create artificial data dependency from
@@ -91,6 +91,12 @@
       LOG(FATAL) << "Unexpected read barrier type";
       UNREACHABLE();
     }
+  } else if (kReadBarrierOption == kWithFromSpaceBarrier) {
+    DCHECK(gUseUserfaultfd);
+    MirrorType* old = ref_addr->template AsMirrorPtr<kIsVolatile>();
+    mirror::Object* ref =
+        Runtime::Current()->GetHeap()->MarkCompactCollector()->GetFromSpaceAddrFromBarrier(old);
+    return reinterpret_cast<MirrorType*>(ref);
   } else {
     // No read barrier.
     return ref_addr->template AsMirrorPtr<kIsVolatile>();
@@ -102,12 +108,11 @@
                                                GcRootSource* gc_root_source) {
   MirrorType* ref = *root;
   const bool with_read_barrier = kReadBarrierOption == kWithReadBarrier;
-  if (kUseReadBarrier && with_read_barrier) {
+  if (gUseReadBarrier && with_read_barrier) {
     if (kCheckDebugDisallowReadBarrierCount) {
       Thread* const self = Thread::Current();
-      if (self != nullptr) {
-        CHECK_EQ(self->GetDebugDisallowReadBarrierCount(), 0u);
-      }
+      CHECK(self != nullptr);
+      CHECK_EQ(self->GetDebugDisallowReadBarrierCount(), 0u);
     }
     if (kUseBakerReadBarrier) {
       // TODO: separate the read barrier code from the collector code more.
@@ -136,6 +141,11 @@
       LOG(FATAL) << "Unexpected read barrier type";
       UNREACHABLE();
     }
+  } else if (kReadBarrierOption == kWithFromSpaceBarrier) {
+    DCHECK(gUseUserfaultfd);
+    mirror::Object* from_ref =
+        Runtime::Current()->GetHeap()->MarkCompactCollector()->GetFromSpaceAddrFromBarrier(ref);
+    return reinterpret_cast<MirrorType*>(from_ref);
   } else {
     return ref;
   }
@@ -147,12 +157,11 @@
                                                GcRootSource* gc_root_source) {
   MirrorType* ref = root->AsMirrorPtr();
   const bool with_read_barrier = kReadBarrierOption == kWithReadBarrier;
-  if (kUseReadBarrier && with_read_barrier) {
+  if (gUseReadBarrier && with_read_barrier) {
     if (kCheckDebugDisallowReadBarrierCount) {
       Thread* const self = Thread::Current();
-      if (self != nullptr) {
-        CHECK_EQ(self->GetDebugDisallowReadBarrierCount(), 0u);
-      }
+      CHECK(self != nullptr);
+      CHECK_EQ(self->GetDebugDisallowReadBarrierCount(), 0u);
     }
     if (kUseBakerReadBarrier) {
       // TODO: separate the read barrier code from the collector code more.
@@ -183,6 +192,11 @@
       LOG(FATAL) << "Unexpected read barrier type";
       UNREACHABLE();
     }
+  } else if (kReadBarrierOption == kWithFromSpaceBarrier) {
+    DCHECK(gUseUserfaultfd);
+    mirror::Object* from_ref =
+        Runtime::Current()->GetHeap()->MarkCompactCollector()->GetFromSpaceAddrFromBarrier(ref);
+    return reinterpret_cast<MirrorType*>(from_ref);
   } else {
     return ref;
   }
@@ -192,7 +206,7 @@
 inline MirrorType* ReadBarrier::IsMarked(MirrorType* ref) {
   // Only read-barrier configurations can have mutators run while
   // the GC is marking.
-  if (!kUseReadBarrier) {
+  if (!gUseReadBarrier) {
     return ref;
   }
   // IsMarked does not handle null, so handle it here.
diff --git a/runtime/read_barrier.h b/runtime/read_barrier.h
index 3b89377..be5a9a0 100644
--- a/runtime/read_barrier.h
+++ b/runtime/read_barrier.h
@@ -94,7 +94,7 @@
   // Without the holder object, and only with the read barrier configuration (no-op otherwise).
   static void MaybeAssertToSpaceInvariant(mirror::Object* ref)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    if (kUseReadBarrier) {
+    if (gUseReadBarrier) {
       AssertToSpaceInvariant(ref);
     }
   }
diff --git a/runtime/read_barrier_config.h b/runtime/read_barrier_config.h
index d505bed..876e3d7 100644
--- a/runtime/read_barrier_config.h
+++ b/runtime/read_barrier_config.h
@@ -41,6 +41,12 @@
 #define USE_READ_BARRIER
 #endif
 
+// Reserve marking register (and its refreshing logic) for all GCs as nterp
+// requires it. In the future if and when nterp is made independent of
+// read-barrier, we can switch back to the current behavior by making this
+// definition conditional on USE_BAKER_READ_BARRIER and setting
+// kReserveMarkingRegister to kUseBakerReadBarrier.
+#define RESERVE_MARKING_REGISTER
 
 // C++-specific configuration part..
 
@@ -56,23 +62,34 @@
 static constexpr bool kUseBakerReadBarrier = false;
 #endif
 
+// Read comment for RESERVE_MARKING_REGISTER above
+static constexpr bool kReserveMarkingRegister = true;
+
 #ifdef USE_TABLE_LOOKUP_READ_BARRIER
 static constexpr bool kUseTableLookupReadBarrier = true;
 #else
 static constexpr bool kUseTableLookupReadBarrier = false;
 #endif
 
-static constexpr bool kUseReadBarrier = kUseBakerReadBarrier || kUseTableLookupReadBarrier;
-
-// Debugging flag that forces the generation of read barriers, but
-// does not trigger the use of the concurrent copying GC.
-//
-// TODO: Remove this flag when the read barriers compiler
-// instrumentation is completed.
-static constexpr bool kForceReadBarrier = false;
-// TODO: Likewise, remove this flag when kForceReadBarrier is removed
-// and replace it with kUseReadBarrier.
-static constexpr bool kEmitCompilerReadBarrier = kForceReadBarrier || kUseReadBarrier;
+// Only if read-barrier isn't forced (see build/art.go) but is selected, that we need
+// to see if we support userfaultfd GC. All the other cases can be constexpr here.
+#ifdef ART_FORCE_USE_READ_BARRIER
+constexpr bool gUseReadBarrier = kUseBakerReadBarrier || kUseTableLookupReadBarrier;
+constexpr bool gUseUserfaultfd = !gUseReadBarrier;
+static_assert(!gUseUserfaultfd);
+#else
+#ifndef ART_USE_READ_BARRIER
+constexpr bool gUseReadBarrier = false;
+#ifdef ART_DEFAULT_GC_TYPE_IS_CMC
+constexpr bool gUseUserfaultfd = true;
+#else
+constexpr bool gUseUserfaultfd = false;
+#endif
+#else
+extern const bool gUseReadBarrier;
+extern const bool gUseUserfaultfd;
+#endif
+#endif
 
 // Disabled for performance reasons.
 static constexpr bool kCheckDebugDisallowReadBarrierCount = kIsDebugBuild;
diff --git a/runtime/read_barrier_option.h b/runtime/read_barrier_option.h
index d918d46..36fc2d2 100644
--- a/runtime/read_barrier_option.h
+++ b/runtime/read_barrier_option.h
@@ -84,6 +84,7 @@
 enum ReadBarrierOption {
   kWithReadBarrier,     // Perform a read barrier.
   kWithoutReadBarrier,  // Don't perform a read barrier.
+  kWithFromSpaceBarrier,  // Get the from-space address for the given to-space address. Used by CMC
 };
 
 }  // namespace art
diff --git a/runtime/reference_table.h b/runtime/reference_table.h
index 2ffd866..b204533 100644
--- a/runtime/reference_table.h
+++ b/runtime/reference_table.h
@@ -28,6 +28,9 @@
 #include "obj_ptr.h"
 
 namespace art {
+namespace jni {
+class LocalReferenceTable;
+}  // namespace jni
 namespace mirror {
 class Object;
 }  // namespace mirror
@@ -61,6 +64,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::alloc_tracker_lock_);
   friend class IndirectReferenceTable;  // For Dump.
+  friend class jni::LocalReferenceTable;  // For Dump.
 
   std::string name_;
   Table entries_;
diff --git a/runtime/reference_table_test.cc b/runtime/reference_table_test.cc
index 7a1e668..420543e 100644
--- a/runtime/reference_table_test.cc
+++ b/runtime/reference_table_test.cc
@@ -22,6 +22,7 @@
 
 #include "art_method-inl.h"
 #include "class_linker.h"
+#include "class_root-inl.h"
 #include "common_runtime_test.h"
 #include "dex/primitive.h"
 #include "handle_scope-inl.h"
@@ -34,13 +35,17 @@
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-current-inl.h"
-#include "well_known_classes.h"
 
 namespace art {
 
 using android::base::StringPrintf;
 
-class ReferenceTableTest : public CommonRuntimeTest {};
+class ReferenceTableTest : public CommonRuntimeTest {
+ protected:
+  ReferenceTableTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+};
 
 static ObjPtr<mirror::Object> CreateWeakReference(ObjPtr<mirror::Object> referent)
     REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -194,18 +199,12 @@
     // avoids having to create the low-level args array ourselves.
     Handle<mirror::Object> h_with_trace;
     {
-      jmethodID substr = soa.Env()->GetMethodID(WellKnownClasses::java_lang_String,
-                                                "substring",
-                                                "(II)Ljava/lang/String;");
+      ArtMethod* substr = GetClassRoot<mirror::String>()->FindClassMethod(
+          "substring", "(II)Ljava/lang/String;", kRuntimePointerSize);
       ASSERT_TRUE(substr != nullptr);
-      jobject jobj = soa.Env()->AddLocalReference<jobject>(h_without_trace.Get());
-      ASSERT_TRUE(jobj != nullptr);
-      jobject result = soa.Env()->CallObjectMethod(jobj,
-                                                   substr,
-                                                   static_cast<jint>(0),
-                                                   static_cast<jint>(4));
-      ASSERT_TRUE(result != nullptr);
-      h_with_trace = hs.NewHandle(soa.Self()->DecodeJObject(result));
+      h_with_trace = hs.NewHandle(
+          substr->InvokeFinal<'L', 'I', 'I'>(soa.Self(), h_without_trace.Get(), 0, 4));
+      ASSERT_TRUE(h_with_trace != nullptr);
     }
 
     Handle<mirror::Object> h_ref;
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index a7290a2..bfe9c8f 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -17,7 +17,7 @@
 #include "reflection-inl.h"
 
 #include "art_field-inl.h"
-#include "art_method-inl.h"
+#include "art_method-alloc-inl.h"
 #include "base/enums.h"
 #include "class_linker.h"
 #include "common_throws.h"
@@ -502,17 +502,17 @@
           << soa.Self()->GetException()->GetClass()->PrettyDescriptor();
     } else {
       // If we get another exception when we are trying to wrap, then just use that instead.
-      ScopedLocalRef<jthrowable> th(soa.Env(), soa.Env()->ExceptionOccurred());
+      StackHandleScope<2u> hs(soa.Self());
+      Handle<mirror::Throwable> cause = hs.NewHandle(soa.Self()->GetException());
       soa.Self()->ClearException();
-      jobject exception_instance =
-          soa.Env()->NewObject(WellKnownClasses::java_lang_reflect_InvocationTargetException,
-                               WellKnownClasses::java_lang_reflect_InvocationTargetException_init,
-                               th.get());
+      Handle<mirror::Object> exception_instance =
+          WellKnownClasses::java_lang_reflect_InvocationTargetException_init->NewObject<'L'>(
+              hs, soa.Self(), cause);
       if (exception_instance == nullptr) {
         soa.Self()->AssertPendingException();
         return false;
       }
-      soa.Env()->Throw(reinterpret_cast<jthrowable>(exception_instance));
+      soa.Self()->SetException(exception_instance->AsThrowable());
     }
     return false;
   }
@@ -523,6 +523,7 @@
 }  // anonymous namespace
 
 template <>
+NO_STACK_PROTECTOR
 JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,
                          jobject obj,
                          ArtMethod* method,
@@ -534,7 +535,7 @@
     ThrowStackOverflowError(soa.Self());
     return JValue();
   }
-  bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor();
+  bool is_string_init = method->IsStringConstructor();
   if (is_string_init) {
     // Replace calls to String.<init> with equivalent StringFactory call.
     method = WellKnownClasses::StringInitToStringFactory(method);
@@ -555,6 +556,7 @@
 }
 
 template <>
+NO_STACK_PROTECTOR
 JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,
                          jobject obj,
                          jmethodID mid,
@@ -575,7 +577,7 @@
     ThrowStackOverflowError(soa.Self());
     return JValue();
   }
-  bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor();
+  bool is_string_init = method->IsStringConstructor();
   if (is_string_init) {
     // Replace calls to String.<init> with equivalent StringFactory call.
     method = WellKnownClasses::StringInitToStringFactory(method);
@@ -618,7 +620,7 @@
   }
   ObjPtr<mirror::Object> receiver = soa.Decode<mirror::Object>(obj);
   ArtMethod* method = FindVirtualMethod(receiver, interface_method);
-  bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor();
+  bool is_string_init = method->IsStringConstructor();
   if (is_string_init) {
     // Replace calls to String.<init> with equivalent StringFactory call.
     method = WellKnownClasses::StringInitToStringFactory(method);
@@ -662,7 +664,7 @@
 
   ObjPtr<mirror::Object> receiver = soa.Decode<mirror::Object>(obj);
   ArtMethod* method = FindVirtualMethod(receiver, interface_method);
-  bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor();
+  bool is_string_init = method->IsStringConstructor();
   if (is_string_init) {
     // Replace calls to String.<init> with equivalent StringFactory call.
     method = WellKnownClasses::StringInitToStringFactory(method);
@@ -838,7 +840,7 @@
     return nullptr;
   }
 
-  jmethodID m = nullptr;
+  ArtMethod* m = nullptr;
   const char* shorty;
   switch (src_class) {
   case Primitive::kPrimBoolean:
@@ -889,11 +891,8 @@
     arg_array.Append(value.GetI());
   }
 
-  jni::DecodeArtMethod(m)->Invoke(soa.Self(),
-                                  arg_array.GetArray(),
-                                  arg_array.GetNumBytes(),
-                                  &result,
-                                  shorty);
+  DCHECK(m->GetDeclaringClass()->IsInitialized());  // By `ClassLinker::RunRootClinits()`.
+  m->Invoke(soa.Self(), arg_array.GetArray(), arg_array.GetNumBytes(), &result, shorty);
   return result.GetL();
 }
 
@@ -1067,8 +1066,8 @@
   IndirectRefKind kind = IndirectReferenceTable::GetIndirectRefKind(ref);
   if (kind == kLocal) {
     self->GetJniEnv()->UpdateLocal(obj, result);
-  } else if (kind == kJniTransitionOrInvalid) {
-    LOG(FATAL) << "Unsupported UpdateReference for kind kJniTransitionOrInvalid";
+  } else if (kind == kJniTransition) {
+    LOG(FATAL) << "Unsupported UpdateReference for kind kJniTransition";
   } else if (kind == kGlobal) {
     self->GetJniEnv()->GetVm()->UpdateGlobal(self, ref, result);
   } else {
diff --git a/runtime/reflection.h b/runtime/reflection.h
index b0e27da..13dc8e1 100644
--- a/runtime/reflection.h
+++ b/runtime/reflection.h
@@ -99,6 +99,7 @@
 
 // num_frames is number of frames we look up for access check.
 template<PointerSize pointer_size>
+NO_STACK_PROTECTOR
 jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa,
                      jobject method,
                      jobject receiver,
diff --git a/runtime/reflection_test.cc b/runtime/reflection_test.cc
index 8a76855..4a24d2a 100644
--- a/runtime/reflection_test.cc
+++ b/runtime/reflection_test.cc
@@ -21,7 +21,7 @@
 
 #include "art_method-inl.h"
 #include "base/enums.h"
-#include "common_compiler_test.h"
+#include "common_runtime_test.h"
 #include "dex/descriptors_names.h"
 #include "jni/java_vm_ext.h"
 #include "jni/jni_internal.h"
@@ -31,11 +31,10 @@
 
 namespace art {
 
-// TODO: Convert to CommonRuntimeTest. Currently CompileDirectMethod is used in one test.
-class ReflectionTest : public CommonCompilerTest {
+class ReflectionTest : public CommonRuntimeTest {
  protected:
   void SetUp() override {
-    CommonCompilerTest::SetUp();
+    CommonRuntimeTest::SetUp();
 
     vm_ = Runtime::Current()->GetJavaVM();
 
@@ -76,7 +75,7 @@
 
   void TearDown() override {
     CleanUpJniEnv();
-    CommonCompilerTest::TearDown();
+    CommonRuntimeTest::TearDown();
   }
 
   jclass GetPrimitiveClass(char descriptor) {
@@ -511,33 +510,6 @@
   jclass sioobe_;
 };
 
-TEST_F(ReflectionTest, StaticMainMethod) {
-  ScopedObjectAccess soa(Thread::Current());
-  jobject jclass_loader = LoadDex("Main");
-  StackHandleScope<1> hs(soa.Self());
-  Handle<mirror::ClassLoader> class_loader(
-      hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader)));
-  CompileDirectMethod(class_loader, "Main", "main", "([Ljava/lang/String;)V");
-
-  ObjPtr<mirror::Class> klass = class_linker_->FindClass(soa.Self(), "LMain;", class_loader);
-  ASSERT_TRUE(klass != nullptr);
-
-  ArtMethod* method = klass->FindClassMethod("main",
-                                             "([Ljava/lang/String;)V",
-                                             kRuntimePointerSize);
-  ASSERT_TRUE(method != nullptr);
-  ASSERT_TRUE(method->IsStatic());
-
-  // Start runtime.
-  bool started = runtime_->Start();
-  CHECK(started);
-  soa.Self()->TransitionFromSuspendedToRunnable();
-
-  jvalue args[1];
-  args[0].l = nullptr;
-  InvokeWithJValues(soa, nullptr, jni::EncodeArtMethod(method), args);
-}
-
 TEST_F(ReflectionTest, StaticNopMethod) {
   InvokeNopMethod(true);
 }
diff --git a/runtime/reflective_handle.h b/runtime/reflective_handle.h
index 014d976..c2dbf53 100644
--- a/runtime/reflective_handle.h
+++ b/runtime/reflective_handle.h
@@ -81,14 +81,12 @@
  public:
   MutableReflectiveHandle() {}
 
-  ALWAYS_INLINE MutableReflectiveHandle(const MutableReflectiveHandle<T>& handle)
-      REQUIRES_SHARED(Locks::mutator_lock_) = default;
+  ALWAYS_INLINE MutableReflectiveHandle(const MutableReflectiveHandle<T>& handle) = default;
 
   ALWAYS_INLINE MutableReflectiveHandle<T>& operator=(const MutableReflectiveHandle<T>& handle)
-      REQUIRES_SHARED(Locks::mutator_lock_) = default;
+      = default;
 
   ALWAYS_INLINE explicit MutableReflectiveHandle(ReflectiveReference<T>* reference)
-      REQUIRES_SHARED(Locks::mutator_lock_)
       : ReflectiveHandle<T>(reference) {}
 
   ALWAYS_INLINE T* Assign(T* reference) REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index e20f883..36d7d7e 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -16,15 +16,15 @@
 
 #include "runtime.h"
 
-// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
-#include <sys/mount.h>
+#include <utility>
+
 #ifdef __linux__
-#include <linux/fs.h>
 #include <sys/prctl.h>
 #endif
 
 #include <fcntl.h>
 #include <signal.h>
+#include <sys/mount.h>
 #include <sys/syscall.h>
 
 #if defined(__APPLE__)
@@ -102,6 +102,7 @@
 #include "jni_id_type.h"
 #include "linear_alloc.h"
 #include "memory_representation.h"
+#include "metrics/statsd.h"
 #include "mirror/array.h"
 #include "mirror/class-alloc-inl.h"
 #include "mirror/class-inl.h"
@@ -125,6 +126,7 @@
 #include "native/dalvik_system_ZygoteHooks.h"
 #include "native/java_lang_Class.h"
 #include "native/java_lang_Object.h"
+#include "native/java_lang_StackStreamFactory.h"
 #include "native/java_lang_String.h"
 #include "native/java_lang_StringFactory.h"
 #include "native/java_lang_System.h"
@@ -152,6 +154,7 @@
 #include "native_bridge_art_interface.h"
 #include "native_stack_dump.h"
 #include "nativehelper/scoped_local_ref.h"
+#include "nterp_helpers.h"
 #include "oat.h"
 #include "oat_file_manager.h"
 #include "oat_quick_method_header.h"
@@ -162,6 +165,7 @@
 #include "reflection.h"
 #include "runtime_callbacks.h"
 #include "runtime_common.h"
+#include "runtime_image.h"
 #include "runtime_intrinsics.h"
 #include "runtime_options.h"
 #include "scoped_thread_state_change-inl.h"
@@ -175,9 +179,10 @@
 #include "transaction.h"
 #include "vdex_file.h"
 #include "verifier/class_verifier.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 #ifdef ART_TARGET_ANDROID
+#include <android/api-level.h>
 #include <android/set_abort_message.h>
 #include "com_android_apex.h"
 namespace apex = com::android::apex;
@@ -200,10 +205,6 @@
 static constexpr double kNormalMinLoadFactor = 0.4;
 static constexpr double kNormalMaxLoadFactor = 0.7;
 
-// Extra added to the default heap growth multiplier. Used to adjust the GC ergonomics for the read
-// barrier config.
-static constexpr double kExtraDefaultHeapGrowthMultiplier = kUseReadBarrier ? 1.0 : 0.0;
-
 Runtime* Runtime::instance_ = nullptr;
 
 struct TraceConfig {
@@ -211,6 +212,7 @@
   Trace::TraceOutputMode trace_output_mode;
   std::string trace_file;
   size_t trace_file_size;
+  TraceClockSource clock_source;
 };
 
 namespace {
@@ -289,14 +291,14 @@
       is_native_debuggable_(false),
       async_exceptions_thrown_(false),
       non_standard_exits_enabled_(false),
-      is_java_debuggable_(false),
+      runtime_debug_state_(RuntimeDebugState::kNonJavaDebuggable),
       monitor_timeout_enable_(false),
       monitor_timeout_ns_(0),
       zygote_max_failed_boots_(0),
       experimental_flags_(ExperimentalFlags::kNone),
       oat_file_manager_(nullptr),
       is_low_memory_mode_(false),
-      madvise_willneed_vdex_filesize_(0),
+      madvise_willneed_total_dex_size_(0),
       madvise_willneed_odex_filesize_(0),
       madvise_willneed_art_filesize_(0),
       safe_mode_(false),
@@ -312,7 +314,8 @@
       verifier_logging_threshold_ms_(100),
       verifier_missing_kthrow_fatal_(false),
       perfetto_hprof_enabled_(false),
-      perfetto_javaheapprof_enabled_(false) {
+      perfetto_javaheapprof_enabled_(false),
+      out_of_memory_error_hook_(nullptr) {
   static_assert(Runtime::kCalleeSaveSize ==
                     static_cast<uint32_t>(CalleeSaveType::kLastCalleeSaveType), "Unexpected size");
   CheckConstants();
@@ -339,10 +342,16 @@
     // In this case we will just try again without allocating a peer so that shutdown can continue.
     // Very few things are actually capable of distinguishing between the peer & peerless states so
     // this should be fine.
+    // Running callbacks is prone to deadlocks in libjdwp tests that need an event handler lock to
+    // process any event. We also need to enter a GCCriticalSection when processing certain events
+    // (for ex: removing the last breakpoint). These two restrictions together make the tear down
+    // of the jdwp tests deadlock prone if we fail to finish Thread::Attach callback.
+    // (TODO:b/251163712) Remove this once we update deopt manager to not use GCCriticalSection.
     bool thread_attached = AttachCurrentThread("Shutdown thread",
                                                /* as_daemon= */ false,
                                                GetSystemThreadGroup(),
-                                               /* create_peer= */ IsStarted());
+                                               /* create_peer= */ IsStarted(),
+                                               /* should_run_callbacks= */ false);
     if (UNLIKELY(!thread_attached)) {
       LOG(WARNING) << "Failed to attach shutdown thread. Trying again without a peer.";
       CHECK(AttachCurrentThread("Shutdown thread (no java peer)",
@@ -406,6 +415,12 @@
   if (oat_file_manager_ != nullptr) {
     oat_file_manager_->WaitForWorkersToBeCreated();
   }
+  // Disable GC before deleting the thread-pool and shutting down runtime as it
+  // restricts attaching new threads.
+  heap_->DisableGCForShutdown();
+  heap_->WaitForWorkersToBeCreated();
+  // Make sure to let the GC complete if it is running.
+  heap_->WaitForGcToComplete(gc::kGcCauseBackground, self);
 
   {
     ScopedTrace trace2("Wait for shutdown cond");
@@ -421,8 +436,8 @@
   if (IsFinishedStarting()) {
     ScopedTrace trace2("Waiting for Daemons");
     self->ClearException();
-    self->GetJniEnv()->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons,
-                                            WellKnownClasses::java_lang_Daemons_stop);
+    ScopedObjectAccess soa(self);
+    WellKnownClasses::java_lang_Daemons_stop->InvokeStatic<'V'>(self);
   }
 
   // Shutdown any trace running.
@@ -435,13 +450,8 @@
     callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kDeath);
   }
 
-  if (attach_shutdown_thread) {
-    DetachCurrentThread();
-    self = nullptr;
-  }
-
-  // Make sure to let the GC complete if it is running.
-  heap_->WaitForGcToComplete(gc::kGcCauseBackground, self);
+  // Delete thread pools before detaching the current thread in case tasks
+  // getting deleted need to have access to Thread::Current.
   heap_->DeleteThreadPool();
   if (oat_file_manager_ != nullptr) {
     oat_file_manager_->DeleteThreadPool();
@@ -449,6 +459,11 @@
   DeleteThreadPool();
   CHECK(thread_pool_ == nullptr);
 
+  if (attach_shutdown_thread) {
+    DetachCurrentThread(/* should_run_callbacks= */ false);
+    self = nullptr;
+  }
+
   // Make sure our internal threads are dead before we start tearing down things they're using.
   GetRuntimeCallbacks()->StopDebugger();
   // Deletion ordering is tricky. Null out everything we've deleted.
@@ -501,8 +516,8 @@
   monitor_pool_ = nullptr;
   delete class_linker_;
   class_linker_ = nullptr;
-  delete small_irt_allocator_;
-  small_irt_allocator_ = nullptr;
+  delete small_lrt_allocator_;
+  small_lrt_allocator_ = nullptr;
   delete heap_;
   heap_ = nullptr;
   delete intern_table_;
@@ -516,7 +531,8 @@
   // Destroy allocators before shutting down the MemMap because they may use it.
   java_vm_.reset();
   linear_alloc_.reset();
-  low_4gb_arena_pool_.reset();
+  delete ReleaseStartupLinearAlloc();
+  linear_alloc_arena_pool_.reset();
   arena_pool_.reset();
   jit_arena_pool_.reset();
   protected_fault_page_.Reset();
@@ -543,7 +559,7 @@
     os << "Runtime aborting...\n";
     if (Runtime::Current() == nullptr) {
       os << "(Runtime does not yet exist!)\n";
-      DumpNativeStack(os, GetTid(), nullptr, "  native: ", nullptr);
+      DumpNativeStack(os, GetTid(), "  native: ", nullptr);
       return;
     }
     Thread* self = Thread::Current();
@@ -555,7 +571,7 @@
 
     if (self == nullptr) {
       os << "(Aborting thread was not attached to runtime!)\n";
-      DumpNativeStack(os, GetTid(), nullptr, "  native: ", nullptr);
+      DumpNativeStack(os, GetTid(), "  native: ", nullptr);
     } else {
       os << "Aborting thread:\n";
       if (Locks::mutator_lock_->IsExclusiveHeld(self) || Locks::mutator_lock_->IsSharedHeld(self)) {
@@ -697,26 +713,24 @@
   // notreached
 }
 
-class FindNativeMethodsVisitor : public ClassVisitor {
+/**
+ * Update entrypoints of methods before the first fork. This
+ * helps sharing pages where ArtMethods are allocated between the zygote and
+ * forked apps.
+ */
+class UpdateMethodsPreFirstForkVisitor : public ClassVisitor {
  public:
-  FindNativeMethodsVisitor(Thread* self, ClassLinker* class_linker)
-      : vm_(down_cast<JNIEnvExt*>(self->GetJniEnv())->GetVm()),
-        self_(self),
-        class_linker_(class_linker) {}
+  explicit UpdateMethodsPreFirstForkVisitor(ClassLinker* class_linker)
+      : class_linker_(class_linker),
+        can_use_nterp_(interpreter::CanRuntimeUseNterp()) {}
 
   bool operator()(ObjPtr<mirror::Class> klass) override REQUIRES_SHARED(Locks::mutator_lock_) {
     bool is_initialized = klass->IsVisiblyInitialized();
     for (ArtMethod& method : klass->GetDeclaredMethods(kRuntimePointerSize)) {
-      if (method.IsNative() && (is_initialized || !NeedsClinitCheckBeforeCall(&method))) {
-        const void* existing = method.GetEntryPointFromJni();
-        if (method.IsCriticalNative()
-                ? class_linker_->IsJniDlsymLookupCriticalStub(existing)
-                : class_linker_->IsJniDlsymLookupStub(existing)) {
-          const void* native_code =
-              vm_->FindCodeForNativeMethod(&method, /*error_msg=*/ nullptr, /*can_suspend=*/ false);
-          if (native_code != nullptr) {
-            class_linker_->RegisterNative(self_, &method, native_code);
-          }
+      if (!is_initialized && method.NeedsClinitCheckBeforeCall() && can_use_nterp_) {
+        const void* existing = method.GetEntryPointFromQuickCompiledCode();
+        if (class_linker_->IsQuickResolutionStub(existing) && CanMethodUseNterp(&method)) {
+          method.SetEntryPointFromQuickCompiledCode(interpreter::GetNterpWithClinitEntryPoint());
         }
       }
     }
@@ -724,11 +738,10 @@
   }
 
  private:
-  JavaVMExt* vm_;
-  Thread* self_;
-  ClassLinker* class_linker_;
+  ClassLinker* const class_linker_;
+  const bool can_use_nterp_;
 
-  DISALLOW_COPY_AND_ASSIGN(FindNativeMethodsVisitor);
+  DISALLOW_COPY_AND_ASSIGN(UpdateMethodsPreFirstForkVisitor);
 };
 
 void Runtime::PreZygoteFork() {
@@ -736,14 +749,16 @@
     GetJit()->PreZygoteFork();
   }
   if (!heap_->HasZygoteSpace()) {
+    Thread* self = Thread::Current();
     // This is the first fork. Update ArtMethods in the boot classpath now to
     // avoid having forked apps dirty the memory.
-    ScopedObjectAccess soa(Thread::Current());
+
     // Ensure we call FixupStaticTrampolines on all methods that are
     // initialized.
-    class_linker_->MakeInitializedClassesVisiblyInitialized(soa.Self(), /*wait=*/ true);
-    // Update native method JNI entrypoints.
-    FindNativeMethodsVisitor visitor(soa.Self(), class_linker_);
+    class_linker_->MakeInitializedClassesVisiblyInitialized(self, /*wait=*/ true);
+
+    ScopedObjectAccess soa(self);
+    UpdateMethodsPreFirstForkVisitor visitor(class_linker_);
     class_linker_->VisitClasses(&visitor);
   }
   heap_->PreZygoteFork();
@@ -779,7 +794,9 @@
   GetMonitorList()->SweepMonitorList(visitor);
   GetJavaVM()->SweepJniWeakGlobals(visitor);
   GetHeap()->SweepAllocationRecords(visitor);
-  if (GetJit() != nullptr) {
+  // Sweep JIT tables only if the GC is moving as in other cases the entries are
+  // not updated.
+  if (GetJit() != nullptr && GetHeap()->IsMovingGc()) {
     // Visit JIT literal tables. Objects in these tables are classes and strings
     // and only classes can be affected by class unloading. The strings always
     // stay alive as they are strongly interned.
@@ -787,7 +804,6 @@
     // from mutators. See b/32167580.
     GetJit()->GetCodeCache()->SweepRootTables(visitor);
   }
-  Thread::SweepInterpreterCaches(visitor);
 
   // All other generic system-weak holders.
   for (gc::AbstractSystemWeakHolder* holder : system_weak_holders_) {
@@ -816,6 +832,18 @@
   return runtime != nullptr && runtime->IsStarted() && !runtime->IsShuttingDownLocked();
 }
 
+void Runtime::AddGeneratedCodeRange(const void* start, size_t size) {
+  if (HandlesSignalsInCompiledCode()) {
+    fault_manager.AddGeneratedCodeRange(start, size);
+  }
+}
+
+void Runtime::RemoveGeneratedCodeRange(const void* start, size_t size) {
+  if (HandlesSignalsInCompiledCode()) {
+    fault_manager.RemoveGeneratedCodeRange(start, size);
+  }
+}
+
 bool Runtime::Create(RuntimeArgumentMap&& runtime_options) {
   // TODO: acquire a static mutex on Runtime to avoid racing.
   if (Runtime::instance_ != nullptr) {
@@ -845,43 +873,34 @@
   }
 
   ScopedObjectAccess soa(Thread::Current());
-  ClassLinker* cl = Runtime::Current()->GetClassLinker();
+  ClassLinker* cl = runtime->GetClassLinker();
   auto pointer_size = cl->GetImagePointerSize();
 
-  StackHandleScope<2> hs(soa.Self());
-  Handle<mirror::Class> class_loader_class(
-      hs.NewHandle(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_ClassLoader)));
-  CHECK(cl->EnsureInitialized(soa.Self(), class_loader_class, true, true));
+  ObjPtr<mirror::Class> class_loader_class = GetClassRoot<mirror::ClassLoader>(cl);
+  DCHECK(class_loader_class->IsInitialized());  // Class roots have been initialized.
 
   ArtMethod* getSystemClassLoader = class_loader_class->FindClassMethod(
       "getSystemClassLoader", "()Ljava/lang/ClassLoader;", pointer_size);
   CHECK(getSystemClassLoader != nullptr);
   CHECK(getSystemClassLoader->IsStatic());
 
-  JValue result = InvokeWithJValues(soa,
-                                    nullptr,
-                                    getSystemClassLoader,
-                                    nullptr);
-  JNIEnv* env = soa.Self()->GetJniEnv();
-  ScopedLocalRef<jobject> system_class_loader(env, soa.AddLocalReference<jobject>(result.GetL()));
-  CHECK(system_class_loader.get() != nullptr);
+  ObjPtr<mirror::Object> system_class_loader = getSystemClassLoader->InvokeStatic<'L'>(soa.Self());
+  CHECK(system_class_loader != nullptr);
 
-  soa.Self()->SetClassLoaderOverride(system_class_loader.get());
+  ScopedAssertNoThreadSuspension sants(__FUNCTION__);
+  jobject g_system_class_loader =
+      runtime->GetJavaVM()->AddGlobalRef(soa.Self(), system_class_loader);
+  soa.Self()->SetClassLoaderOverride(g_system_class_loader);
 
-  Handle<mirror::Class> thread_class(
-      hs.NewHandle(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread)));
-  CHECK(cl->EnsureInitialized(soa.Self(), thread_class, true, true));
-
+  ObjPtr<mirror::Class> thread_class = WellKnownClasses::java_lang_Thread.Get();
   ArtField* contextClassLoader =
       thread_class->FindDeclaredInstanceField("contextClassLoader", "Ljava/lang/ClassLoader;");
   CHECK(contextClassLoader != nullptr);
 
   // We can't run in a transaction yet.
-  contextClassLoader->SetObject<false>(
-      soa.Self()->GetPeer(),
-      soa.Decode<mirror::ClassLoader>(system_class_loader.get()).Ptr());
+  contextClassLoader->SetObject<false>(soa.Self()->GetPeer(), system_class_loader);
 
-  return env->NewGlobalRef(system_class_loader.get());
+  return g_system_class_loader;
 }
 
 std::string Runtime::GetCompilerExecutable() const {
@@ -933,26 +952,15 @@
   // Restore main thread state to kNative as expected by native code.
   Thread* self = Thread::Current();
 
-  self->TransitionFromRunnableToSuspended(ThreadState::kNative);
-
   started_ = true;
 
-  if (!IsImageDex2OatEnabled() || !GetHeap()->HasBootImageSpace()) {
-    ScopedObjectAccess soa(self);
-    StackHandleScope<3> hs(soa.Self());
+  // Before running any clinit, set up the native methods provided by the runtime itself.
+  RegisterRuntimeNativeMethods(self->GetJniEnv());
 
-    ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots = GetClassLinker()->GetClassRoots();
-    auto class_class(hs.NewHandle<mirror::Class>(GetClassRoot<mirror::Class>(class_roots)));
-    auto string_class(hs.NewHandle<mirror::Class>(GetClassRoot<mirror::String>(class_roots)));
-    auto field_class(hs.NewHandle<mirror::Class>(GetClassRoot<mirror::Field>(class_roots)));
+  class_linker_->RunEarlyRootClinits(self);
+  InitializeIntrinsics();
 
-    class_linker_->EnsureInitialized(soa.Self(), class_class, true, true);
-    class_linker_->EnsureInitialized(soa.Self(), string_class, true, true);
-    self->AssertNoPendingException();
-    // Field class is needed for register_java_net_InetAddress in libcore, b/28153851.
-    class_linker_->EnsureInitialized(soa.Self(), field_class, true, true);
-    self->AssertNoPendingException();
-  }
+  self->TransitionFromRunnableToSuspended(ThreadState::kNative);
 
   // InitNativeMethods needs to be after started_ so that the classes
   // it touches will have methods linked to the oat file if necessary.
@@ -961,11 +969,6 @@
     InitNativeMethods();
   }
 
-  // IntializeIntrinsics needs to be called after the WellKnownClasses::Init in InitNativeMethods
-  // because in checking the invocation types of intrinsic methods ArtMethod::GetInvokeType()
-  // needs the SignaturePolymorphic annotation class which is initialized in WellKnownClasses::Init.
-  InitializeIntrinsics();
-
   // InitializeCorePlatformApiPrivateFields() needs to be called after well known class
   // initializtion in InitNativeMethods().
   art::hiddenapi::InitializeCorePlatformApiPrivateFields();
@@ -988,8 +991,23 @@
     if (!jit::Jit::LoadCompilerLibrary(&error_msg)) {
       LOG(WARNING) << "Failed to load JIT compiler with error " << error_msg;
     }
-    CreateJitCodeCache(/*rwx_memory_allowed=*/true);
     CreateJit();
+#ifdef ADDRESS_SANITIZER
+    // (b/238730394): In older implementations of sanitizer + glibc there is a race between
+    // pthread_create and dlopen that could cause a deadlock. pthread_create interceptor in ASAN
+    // uses dl_pthread_iterator with a callback that could request a dl_load_lock via call to
+    // __tls_get_addr [1]. dl_pthread_iterate would already hold dl_load_lock so this could cause a
+    // deadlock. __tls_get_addr needs a dl_load_lock only when there is a dlopen happening in
+    // parallel. As a workaround we wait for the pthread_create (i.e JIT thread pool creation) to
+    // finish before going to the next phase. Creating a system class loader could need a dlopen so
+    // we wait here till threads are initialized.
+    // [1] https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cpp#L408
+    // See this for more context: https://reviews.llvm.org/D98926
+    // TODO(b/238730394): Revisit this workaround once we migrate to musl libc.
+    if (jit_ != nullptr) {
+      jit_->GetThreadPool()->WaitForWorkersToBeCreated();
+    }
+#endif
   }
 
   // Send the start phase event. We have to wait till here as this is when the main thread peer
@@ -1015,24 +1033,14 @@
                             GetInstructionSetString(kRuntimeISA));
   }
 
-  StartDaemonThreads();
-
-  // Make sure the environment is still clean (no lingering local refs from starting daemon
-  // threads).
   {
     ScopedObjectAccess soa(self);
+    StartDaemonThreads();
     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);
+    // Send the initialized phase event. Send it after starting the Daemon threads so that agents
+    // cannot delay the daemon threads from starting forever.
     callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInit);
-  }
-
-  {
-    ScopedObjectAccess soa(self);
     self->GetJniEnv()->AssertLocalsEmpty();
   }
 
@@ -1041,9 +1049,20 @@
 
   if (trace_config_.get() != nullptr && trace_config_->trace_file != "") {
     ScopedThreadStateChange tsc(self, ThreadState::kWaitingForMethodTracingStart);
+    int flags = 0;
+    if (trace_config_->clock_source == TraceClockSource::kDual) {
+      flags = Trace::TraceFlag::kTraceClockSourceWallClock |
+              Trace::TraceFlag::kTraceClockSourceThreadCpu;
+    } else if (trace_config_->clock_source == TraceClockSource::kWall) {
+      flags = Trace::TraceFlag::kTraceClockSourceWallClock;
+    } else if (TraceClockSource::kThreadCpu == trace_config_->clock_source) {
+      flags = Trace::TraceFlag::kTraceClockSourceThreadCpu;
+    } else {
+      LOG(ERROR) << "Unexpected clock source";
+    }
     Trace::Start(trace_config_->trace_file.c_str(),
                  static_cast<int>(trace_config_->trace_file_size),
-                 0,
+                 flags,
                  trace_config_->trace_output_mode,
                  trace_config_->trace_mode,
                  0);
@@ -1128,8 +1147,8 @@
       std::vector<std::string> jars = android::base::Split(system_server_classpath, ":");
       app_info_.RegisterAppInfo("android",
                                 jars,
-                                /*cur_profile_path=*/ "",
-                                /*ref_profile_path=*/ "",
+                                /*profile_output_filename=*/ "",
+                                /*ref_profile_filename=*/ "",
                                 AppInfo::CodeType::kPrimaryApk);
     }
 
@@ -1144,7 +1163,6 @@
   }
 
   // Create the thread pools.
-  heap_->CreateThreadPool();
   // Avoid creating the runtime thread pool for system server since it will not be used and would
   // waste memory.
   if (!is_system_server) {
@@ -1203,12 +1221,13 @@
   }
   if (Runtime::Current()->IsSystemServer()) {
     std::string err;
-    ScopedTrace tr("odrefresh stats logging");
+    ScopedTrace tr("odrefresh and device stats logging");
     ScopedThreadSuspension sts(Thread::Current(), ThreadState::kNative);
     // Report stats if available. This should be moved into ART Services when they are ready.
     if (!odrefresh::UploadStatsIfAvailable(&err)) {
       LOG(WARNING) << "Failed to upload odrefresh metrics: " << err;
     }
+    metrics::ReportDeviceMetrics();
   }
 
   if (LIKELY(automatically_set_jni_ids_indirection_) && CanSetJniIdType()) {
@@ -1244,15 +1263,11 @@
 
   Thread* self = Thread::Current();
 
-  // Must be in the kNative state for calling native methods.
-  CHECK_EQ(self->GetState(), ThreadState::kNative);
+  DCHECK_EQ(self->GetState(), ThreadState::kRunnable);
 
-  JNIEnv* env = self->GetJniEnv();
-  env->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons,
-                            WellKnownClasses::java_lang_Daemons_start);
-  if (env->ExceptionCheck()) {
-    env->ExceptionDescribe();
-    LOG(FATAL) << "Error starting java.lang.Daemons";
+  WellKnownClasses::java_lang_Daemons_start->InvokeStatic<'V'>(self);
+  if (UNLIKELY(self->IsExceptionPending())) {
+    LOG(FATAL) << "Error starting java.lang.Daemons: " << self->GetException()->Dump();
   }
 
   VLOG(startup) << "Runtime::StartDaemonThreads exiting";
@@ -1264,7 +1279,6 @@
                                std::vector<std::unique_ptr<const DexFile>>* dex_files) {
   DCHECK(dex_files != nullptr) << "OpenDexFiles: out-param is nullptr";
   size_t failure_count = 0;
-  const ArtDexFileLoader dex_file_loader;
   for (size_t i = 0; i < dex_filenames.size(); i++) {
     const char* dex_filename = dex_filenames[i].c_str();
     const char* dex_location = dex_locations[i].c_str();
@@ -1276,13 +1290,8 @@
       continue;
     }
     bool verify = Runtime::Current()->IsVerificationEnabled();
-    if (!dex_file_loader.Open(dex_filename,
-                              dex_fd,
-                              dex_location,
-                              verify,
-                              kVerifyChecksum,
-                              &error_msg,
-                              dex_files)) {
+    ArtDexFileLoader dex_file_loader(dex_filename, dex_fd, dex_location);
+    if (!dex_file_loader.Open(verify, kVerifyChecksum, &error_msg, dex_files)) {
       LOG(WARNING) << "Failed to open .dex from file '" << dex_filename << "' / fd " << dex_fd
                    << ": " << error_msg;
       ++failure_count;
@@ -1328,9 +1337,9 @@
   detailMessageField->SetObject</* kTransactionActive= */ false>(exception->Read(), message);
 }
 
-void Runtime::InitializeApexVersions() {
+std::string Runtime::GetApexVersions(ArrayRef<const std::string> boot_class_path_locations) {
   std::vector<std::string_view> bcp_apexes;
-  for (std::string_view jar : Runtime::Current()->GetBootClassPathLocations()) {
+  for (std::string_view jar : boot_class_path_locations) {
     std::string_view apex = ApexNameFromLocation(jar);
     if (!apex.empty()) {
       bcp_apexes.push_back(apex);
@@ -1338,20 +1347,20 @@
   }
   static const char* kApexFileName = "/apex/apex-info-list.xml";
   // Start with empty markers.
-  apex_versions_ = std::string(bcp_apexes.size(), '/');
+  std::string empty_apex_versions(bcp_apexes.size(), '/');
   // When running on host or chroot, we just use empty markers.
   if (!kIsTargetBuild || !OS::FileExists(kApexFileName)) {
-    return;
+    return empty_apex_versions;
   }
 #ifdef ART_TARGET_ANDROID
   if (access(kApexFileName, R_OK) != 0) {
     PLOG(WARNING) << "Failed to read " << kApexFileName;
-    return;
+    return empty_apex_versions;
   }
   auto info_list = apex::readApexInfoList(kApexFileName);
   if (!info_list.has_value()) {
     LOG(WARNING) << "Failed to parse " << kApexFileName;
-    return;
+    return empty_apex_versions;
   }
 
   std::string result;
@@ -1375,10 +1384,17 @@
       android::base::StringAppendF(&result, "/%" PRIu64, version);
     }
   }
-  apex_versions_ = result;
+  return result;
+#else
+  return empty_apex_versions;  // Not an Android build.
 #endif
 }
 
+void Runtime::InitializeApexVersions() {
+  apex_versions_ =
+      GetApexVersions(ArrayRef<const std::string>(Runtime::Current()->GetBootClassPathLocations()));
+}
+
 void Runtime::ReloadAllFlags(const std::string& caller) {
   FlagBase::ReloadAllFlags(caller);
 }
@@ -1490,6 +1506,7 @@
   is_explicit_gc_disabled_ = runtime_options.Exists(Opt::DisableExplicitGC);
   image_dex2oat_enabled_ = runtime_options.GetOrDefault(Opt::ImageDex2Oat);
   dump_native_stack_on_sig_quit_ = runtime_options.GetOrDefault(Opt::DumpNativeStackOnSigQuit);
+  allow_in_memory_compilation_ = runtime_options.Exists(Opt::AllowInMemoryCompilation);
 
   if (is_zygote_ || runtime_options.Exists(Opt::OnlyUseTrustedOatFiles)) {
     oat_file_manager_->SetOnlyUseTrustedOatFiles();
@@ -1505,7 +1522,7 @@
   compiler_options_ = runtime_options.ReleaseOrDefault(Opt::CompilerOptions);
   for (const std::string& option : Runtime::Current()->GetCompilerOptions()) {
     if (option == "--debuggable") {
-      SetJavaDebuggable(true);
+      SetRuntimeDebugState(RuntimeDebugState::kJavaDebuggableAtInit);
       break;
     }
   }
@@ -1552,6 +1569,8 @@
         << (core_platform_api_policy_ == hiddenapi::EnforcementPolicy::kEnabled ? "true" : "false");
   }
 
+  // Dex2Oat's Runtime does not need the signal chain or the fault handler
+  // and it passes the `NoSigChain` option to `Runtime` to indicate this.
   no_sig_chain_ = runtime_options.Exists(Opt::NoSigChain);
   force_native_bridge_ = runtime_options.Exists(Opt::ForceNativeBridge);
 
@@ -1566,8 +1585,7 @@
   zygote_max_failed_boots_ = runtime_options.GetOrDefault(Opt::ZygoteMaxFailedBoots);
   experimental_flags_ = runtime_options.GetOrDefault(Opt::Experimental);
   is_low_memory_mode_ = runtime_options.Exists(Opt::LowMemoryMode);
-  madvise_random_access_ = runtime_options.GetOrDefault(Opt::MadviseRandomAccess);
-  madvise_willneed_vdex_filesize_ = runtime_options.GetOrDefault(Opt::MadviseWillNeedVdexFileSize);
+  madvise_willneed_total_dex_size_ = runtime_options.GetOrDefault(Opt::MadviseWillNeedVdexFileSize);
   madvise_willneed_odex_filesize_ = runtime_options.GetOrDefault(Opt::MadviseWillNeedOdexFileSize);
   madvise_willneed_art_filesize_ = runtime_options.GetOrDefault(Opt::MadviseWillNeedArtFileSize);
 
@@ -1587,9 +1605,11 @@
     // If low memory mode, use 1.0 as the multiplier by default.
     foreground_heap_growth_multiplier = 1.0f;
   } else {
+    // Extra added to the default heap growth multiplier for concurrent GC
+    // compaction algorithms. This is done for historical reasons.
+    // TODO: remove when we revisit heap configurations.
     foreground_heap_growth_multiplier =
-        runtime_options.GetOrDefault(Opt::ForegroundHeapGrowthMultiplier) +
-            kExtraDefaultHeapGrowthMultiplier;
+        runtime_options.GetOrDefault(Opt::ForegroundHeapGrowthMultiplier) + 1.0f;
   }
   XGcOption xgc_option = runtime_options.GetOrDefault(Opt::GcOption);
 
@@ -1599,6 +1619,11 @@
   // Cache the apex versions.
   InitializeApexVersions();
 
+  BackgroundGcOption background_gc =
+      gUseReadBarrier ? BackgroundGcOption(gc::kCollectorTypeCCBackground)
+                      : (gUseUserfaultfd ? BackgroundGcOption(xgc_option.collector_type_)
+                                         : runtime_options.GetOrDefault(Opt::BackgroundGc));
+
   heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize),
                        runtime_options.GetOrDefault(Opt::HeapGrowthLimit),
                        runtime_options.GetOrDefault(Opt::HeapMinFree),
@@ -1617,9 +1642,8 @@
                        image_locations_,
                        instruction_set_,
                        // Override the collector type to CC if the read barrier config.
-                       kUseReadBarrier ? gc::kCollectorTypeCC : xgc_option.collector_type_,
-                       kUseReadBarrier ? BackgroundGcOption(gc::kCollectorTypeCCBackground)
-                                       : runtime_options.GetOrDefault(Opt::BackgroundGc),
+                       gUseReadBarrier ? gc::kCollectorTypeCC : xgc_option.collector_type_,
+                       background_gc,
                        runtime_options.GetOrDefault(Opt::LargeObjectSpace),
                        runtime_options.GetOrDefault(Opt::LargeObjectThreshold),
                        runtime_options.GetOrDefault(Opt::ParallelGCThreads),
@@ -1700,13 +1724,19 @@
     jit_arena_pool_.reset(new MemMapArenaPool(/* low_4gb= */ false, "CompilerMetadata"));
   }
 
-  if (IsAotCompiler() && Is64BitInstructionSet(kRuntimeISA)) {
-    // 4gb, no malloc. Explanation in header.
-    low_4gb_arena_pool_.reset(new MemMapArenaPool(/* low_4gb= */ true));
+  // For 64 bit compilers, it needs to be in low 4GB in the case where we are cross compiling for a
+  // 32 bit target. In this case, we have 32 bit pointers in the dex cache arrays which can't hold
+  // when we have 64 bit ArtMethod pointers.
+  const bool low_4gb = IsAotCompiler() && Is64BitInstructionSet(kRuntimeISA);
+  if (gUseUserfaultfd) {
+    linear_alloc_arena_pool_.reset(new GcVisitedArenaPool(low_4gb, IsZygote()));
+  } else if (low_4gb) {
+    linear_alloc_arena_pool_.reset(new MemMapArenaPool(low_4gb));
   }
   linear_alloc_.reset(CreateLinearAlloc());
+  startup_linear_alloc_.store(CreateLinearAlloc(), std::memory_order_relaxed);
 
-  small_irt_allocator_ = new SmallIrtAllocator();
+  small_lrt_allocator_ = new jni::SmallLrtAllocator();
 
   BlockSignals();
   InitPlatformSignalHandlers();
@@ -1714,9 +1744,7 @@
   // Change the implicit checks flags based on runtime architecture.
   switch (kRuntimeISA) {
     case InstructionSet::kArm64:
-      // TODO: Implicit suspend checks are currently disabled to facilitate search
-      // for unrelated memory use regressions. Bug: 213757852.
-      implicit_suspend_checks_ = false;
+      implicit_suspend_checks_ = true;
       FALLTHROUGH_INTENDED;
     case InstructionSet::kArm:
     case InstructionSet::kThumb2:
@@ -1731,11 +1759,9 @@
       break;
   }
 
+  fault_manager.Init(!no_sig_chain_);
   if (!no_sig_chain_) {
-    // Dex2Oat's Runtime does not need the signal chain or the fault handler.
-    if (implicit_null_checks_ || implicit_so_checks_ || implicit_suspend_checks_) {
-      fault_manager.Init();
-
+    if (HandlesSignalsInCompiledCode()) {
       // These need to be in a specific order.  The null point check handler must be
       // after the suspend check and stack overflow check handlers.
       //
@@ -1756,6 +1782,12 @@
       if (kEnableJavaStackTraceHandler) {
         new JavaStackTraceHandler(&fault_manager);
       }
+
+      if (interpreter::CanRuntimeUseNterp()) {
+        // Nterp code can use signal handling just like the compiled managed code.
+        OatQuickMethodHeader* nterp_header = OatQuickMethodHeader::NterpMethodHeader;
+        fault_manager.AddGeneratedCodeRange(nterp_header->GetCode(), nterp_header->GetCodeSize());
+      }
     }
   }
 
@@ -1777,7 +1809,7 @@
   // ClassLinker needs an attached thread, but we can't fully attach a thread without creating
   // objects. We can't supply a thread group yet; it will be fixed later. Since we are the main
   // thread, we do not get a java peer.
-  Thread* self = Thread::Attach("main", false, nullptr, false);
+  Thread* self = Thread::Attach("main", false, nullptr, false, /* should_run_callbacks= */ true);
   CHECK_EQ(self->GetThreadId(), ThreadList::kMainThreadId);
   CHECK(self != nullptr);
 
@@ -1888,9 +1920,10 @@
     trace_config_->trace_output_mode = runtime_options.Exists(Opt::MethodTraceStreaming) ?
         Trace::TraceOutputMode::kStreaming :
         Trace::TraceOutputMode::kFile;
+    trace_config_->clock_source = runtime_options.GetOrDefault(Opt::MethodTraceClock);
   }
 
-  // TODO: move this to just be an Trace::Start argument
+  // TODO: Remove this in a follow up CL. This isn't used anywhere.
   Trace::SetDefaultClockSource(runtime_options.GetOrDefault(Opt::ProfileClock));
 
   if (GetHeap()->HasBootImageSpace()) {
@@ -2134,13 +2167,6 @@
   // Must be in the kNative state for calling native methods (JNI_OnLoad code).
   CHECK_EQ(self->GetState(), ThreadState::kNative);
 
-  // Set up the native methods provided by the runtime itself.
-  RegisterRuntimeNativeMethods(env);
-
-  // Initialize classes used in JNI. The initialization requires runtime native
-  // methods to be loaded first.
-  WellKnownClasses::Init(env);
-
   // Then set up libjavacore / libopenjdk / libicu_jni ,which are just
   // a regular JNI libraries with a regular JNI_OnLoad. Most JNI libraries can
   // just use System.loadLibrary, but libcore can't because it's the library
@@ -2149,20 +2175,29 @@
   // By setting calling class to java.lang.Object, the caller location for these
   // JNI libs is core-oj.jar in the ART APEX, and hence they are loaded from the
   // com_android_art linker namespace.
+  jclass java_lang_Object;
+  {
+    // Use global JNI reference to keep the local references empty. If we allocated a
+    // local reference here, the `PushLocalFrame(128)` that these internal libraries do
+    // in their `JNI_OnLoad()` would reserve a lot of unnecessary space due to rounding.
+    ScopedObjectAccess soa(self);
+    java_lang_Object = reinterpret_cast<jclass>(
+        GetJavaVM()->AddGlobalRef(self, GetClassRoot<mirror::Object>(GetClassLinker())));
+  }
 
   // libicu_jni has to be initialized before libopenjdk{d} due to runtime dependency from
   // libopenjdk{d} to Icu4cMetadata native methods in libicu_jni. See http://b/143888405
   {
     std::string error_msg;
     if (!java_vm_->LoadNativeLibrary(
-          env, "libicu_jni.so", nullptr, WellKnownClasses::java_lang_Object, &error_msg)) {
+          env, "libicu_jni.so", nullptr, java_lang_Object, &error_msg)) {
       LOG(FATAL) << "LoadNativeLibrary failed for \"libicu_jni.so\": " << error_msg;
     }
   }
   {
     std::string error_msg;
     if (!java_vm_->LoadNativeLibrary(
-          env, "libjavacore.so", nullptr, WellKnownClasses::java_lang_Object, &error_msg)) {
+          env, "libjavacore.so", nullptr, java_lang_Object, &error_msg)) {
       LOG(FATAL) << "LoadNativeLibrary failed for \"libjavacore.so\": " << error_msg;
     }
   }
@@ -2172,10 +2207,11 @@
                                                 : "libopenjdk.so";
     std::string error_msg;
     if (!java_vm_->LoadNativeLibrary(
-          env, kOpenJdkLibrary, nullptr, WellKnownClasses::java_lang_Object, &error_msg)) {
+          env, kOpenJdkLibrary, nullptr, java_lang_Object, &error_msg)) {
       LOG(FATAL) << "LoadNativeLibrary failed for \"" << kOpenJdkLibrary << "\": " << error_msg;
     }
   }
+  env->DeleteGlobalRef(java_lang_Object);
 
   // Initialize well known classes that may invoke runtime native methods.
   WellKnownClasses::LateInit(env);
@@ -2188,17 +2224,28 @@
 }
 
 void Runtime::InitThreadGroups(Thread* self) {
-  JNIEnvExt* env = self->GetJniEnv();
-  ScopedJniEnvLocalRefState env_state(env);
+  ScopedObjectAccess soa(self);
+  ArtField* main_thread_group_field = WellKnownClasses::java_lang_ThreadGroup_mainThreadGroup;
+  ArtField* system_thread_group_field = WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup;
+  // Note: This is running before `ClassLinker::RunRootClinits()`, so we cannot rely on
+  // `ThreadGroup` and `Thread` being initialized.
+  // TODO: Clean up initialization order after all well-known methods are converted to `ArtMethod*`
+  // (and therefore the `WellKnownClasses::Init()` shall not initialize any classes).
+  StackHandleScope<2u> hs(self);
+  Handle<mirror::Class> thread_group_class =
+      hs.NewHandle(main_thread_group_field->GetDeclaringClass());
+  bool initialized = GetClassLinker()->EnsureInitialized(
+      self, thread_group_class, /*can_init_fields=*/ true, /*can_init_parents=*/ true);
+  CHECK(initialized);
+  Handle<mirror::Class> thread_class = hs.NewHandle(WellKnownClasses::java_lang_Thread.Get());
+  initialized = GetClassLinker()->EnsureInitialized(
+      self, thread_class, /*can_init_fields=*/ true, /*can_init_parents=*/ true);
+  CHECK(initialized);
   main_thread_group_ =
-      env->NewGlobalRef(env->GetStaticObjectField(
-          WellKnownClasses::java_lang_ThreadGroup,
-          WellKnownClasses::java_lang_ThreadGroup_mainThreadGroup));
+      soa.Vm()->AddGlobalRef(self, main_thread_group_field->GetObject(thread_group_class.Get()));
   CHECK_IMPLIES(main_thread_group_ == nullptr, IsAotCompiler());
   system_thread_group_ =
-      env->NewGlobalRef(env->GetStaticObjectField(
-          WellKnownClasses::java_lang_ThreadGroup,
-          WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup));
+      soa.Vm()->AddGlobalRef(self, system_thread_group_field->GetObject(thread_group_class.Get()));
   CHECK_IMPLIES(system_thread_group_ == nullptr, IsAotCompiler());
 }
 
@@ -2237,6 +2284,7 @@
   register_java_lang_reflect_Parameter(env);
   register_java_lang_reflect_Proxy(env);
   register_java_lang_ref_Reference(env);
+  register_java_lang_StackStreamFactory(env);
   register_java_lang_String(env);
   register_java_lang_StringFactory(env);
   register_java_lang_System(env);
@@ -2270,6 +2318,9 @@
 }
 
 void Runtime::DumpForSigQuit(std::ostream& os) {
+  // Print backtraces first since they are important do diagnose ANRs,
+  // and ANRs can often be trimmed to limit upload size.
+  thread_list_->DumpForSigQuit(os);
   GetClassLinker()->DumpForSigQuit(os);
   GetInternTable()->DumpForSigQuit(os);
   GetJavaVM()->DumpForSigQuit(os);
@@ -2285,7 +2336,6 @@
   GetMetrics()->DumpForSigQuit(os);
   os << "\n";
 
-  thread_list_->DumpForSigQuit(os);
   BaseMutex::DumpAll(os);
 
   // Inform anyone else who is interested in SigQuit.
@@ -2296,11 +2346,11 @@
 }
 
 void Runtime::DumpLockHolders(std::ostream& os) {
-  uint64_t mutator_lock_owner = Locks::mutator_lock_->GetExclusiveOwnerTid();
+  pid_t mutator_lock_owner = Locks::mutator_lock_->GetExclusiveOwnerTid();
   pid_t thread_list_lock_owner = GetThreadList()->GetLockOwner();
   pid_t classes_lock_owner = GetClassLinker()->GetClassesLockOwner();
   pid_t dex_lock_owner = GetClassLinker()->GetDexLockOwner();
-  if ((thread_list_lock_owner | classes_lock_owner | dex_lock_owner) != 0) {
+  if ((mutator_lock_owner | thread_list_lock_owner | classes_lock_owner | dex_lock_owner) != 0) {
     os << "Mutator lock exclusive owner tid: " << mutator_lock_owner << "\n"
        << "ThreadList lock owner tid: " << thread_list_lock_owner << "\n"
        << "ClassLinker classes lock owner tid: " << classes_lock_owner << "\n"
@@ -2375,9 +2425,13 @@
 }
 
 bool Runtime::AttachCurrentThread(const char* thread_name, bool as_daemon, jobject thread_group,
-                                  bool create_peer) {
+                                  bool create_peer, bool should_run_callbacks) {
   ScopedTrace trace(__FUNCTION__);
-  Thread* self = Thread::Attach(thread_name, as_daemon, thread_group, create_peer);
+  Thread* self = Thread::Attach(thread_name,
+                                as_daemon,
+                                thread_group,
+                                create_peer,
+                                should_run_callbacks);
   // Run ThreadGroup.add to notify the group that this thread is now started.
   if (self != nullptr && create_peer && !IsAotCompiler()) {
     ScopedObjectAccess soa(self);
@@ -2386,7 +2440,7 @@
   return self != nullptr;
 }
 
-void Runtime::DetachCurrentThread() {
+void Runtime::DetachCurrentThread(bool should_run_callbacks) {
   ScopedTrace trace(__FUNCTION__);
   Thread* self = Thread::Current();
   if (self == nullptr) {
@@ -2395,7 +2449,7 @@
   if (self->HasManagedStack()) {
     LOG(FATAL) << *Thread::Current() << " attempting to detach while still running code";
   }
-  thread_list_->Unregister(self);
+  thread_list_->Unregister(self, should_run_callbacks);
 }
 
 mirror::Throwable* Runtime::GetPreAllocatedOutOfMemoryErrorWhenThrowingException() {
@@ -2457,6 +2511,9 @@
   class_linker_->VisitRoots(visitor, flags);
   jni_id_manager_->VisitRoots(visitor);
   heap_->VisitAllocationRecords(visitor);
+  if (jit_ != nullptr) {
+    jit_->GetCodeCache()->VisitRoots(visitor);
+  }
   if ((flags & kVisitRootFlagNewRoots) == 0) {
     // Guaranteed to have no new roots in the constant roots.
     VisitConstantRoots(visitor);
@@ -2506,17 +2563,20 @@
 }
 
 void Runtime::VisitImageRoots(RootVisitor* visitor) {
-  for (auto* space : GetHeap()->GetContinuousSpaces()) {
-    if (space->IsImageSpace()) {
-      auto* image_space = space->AsImageSpace();
-      const auto& image_header = image_space->GetImageHeader();
-      for (int32_t i = 0, size = image_header.GetImageRoots()->GetLength(); i != size; ++i) {
-        mirror::Object* obj =
-            image_header.GetImageRoot(static_cast<ImageHeader::ImageRoot>(i)).Ptr();
-        if (obj != nullptr) {
-          mirror::Object* after_obj = obj;
-          visitor->VisitRoot(&after_obj, RootInfo(kRootStickyClass));
-          CHECK_EQ(after_obj, obj);
+  // We only confirm that image roots are unchanged.
+  if (kIsDebugBuild) {
+    for (auto* space : GetHeap()->GetContinuousSpaces()) {
+      if (space->IsImageSpace()) {
+        auto* image_space = space->AsImageSpace();
+        const auto& image_header = image_space->GetImageHeader();
+        for (int32_t i = 0, size = image_header.GetImageRoots()->GetLength(); i != size; ++i) {
+          mirror::Object* obj =
+              image_header.GetImageRoot(static_cast<ImageHeader::ImageRoot>(i)).Ptr();
+          if (obj != nullptr) {
+            mirror::Object* after_obj = obj;
+            visitor->VisitRoot(&after_obj, RootInfo(kRootStickyClass));
+            CHECK_EQ(after_obj, obj);
+          }
         }
       }
     }
@@ -2585,7 +2645,7 @@
 }
 
 void Runtime::DisallowNewSystemWeaks() {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   monitor_list_->DisallowNewMonitors();
   intern_table_->ChangeWeakRootState(gc::kWeakRootStateNoReadsOrWrites);
   java_vm_->DisallowNewWeakGlobals();
@@ -2601,7 +2661,7 @@
 }
 
 void Runtime::AllowNewSystemWeaks() {
-  CHECK(!kUseReadBarrier);
+  CHECK(!gUseReadBarrier);
   monitor_list_->AllowNewMonitors();
   intern_table_->ChangeWeakRootState(gc::kWeakRootStateNormal);  // TODO: Do this in the sweeping.
   java_vm_->AllowNewWeakGlobals();
@@ -2643,6 +2703,7 @@
       break;
     case InstructionSet::kArm:
     case InstructionSet::kArm64:
+    case InstructionSet::kRiscv64:
     case InstructionSet::kX86:
     case InstructionSet::kX86_64:
       break;
@@ -2697,15 +2758,35 @@
     LOG(WARNING) << "JIT profile information will not be recorded: profile filename is empty.";
     return;
   }
-  if (!OS::FileExists(profile_output_filename.c_str(), /*check_file_type=*/ false)) {
-    LOG(WARNING) << "JIT profile information will not be recorded: profile file does not exist.";
-    return;
-  }
   if (code_paths.empty()) {
     LOG(WARNING) << "JIT profile information will not be recorded: code paths is empty.";
     return;
   }
 
+  // Framework calls this method for all split APKs. Ignore the calls for the ones with no dex code
+  // so that we don't unnecessarily create profiles for them or write bootclasspath profiling info
+  // to those profiles.
+  bool has_code = false;
+  for (const std::string& path : code_paths) {
+    std::string error_msg;
+    std::vector<uint32_t> checksums;
+    std::vector<std::string> dex_locations;
+    if (!ArtDexFileLoader::GetMultiDexChecksums(
+            path.c_str(), &checksums, &dex_locations, &error_msg)) {
+      LOG(WARNING) << error_msg;
+      continue;
+    }
+    if (dex_locations.size() > 0) {
+      has_code = true;
+      break;
+    }
+  }
+  if (!has_code) {
+    VLOG(profiler) << "JIT profile information will not be recorded: no dex code in '" +
+                          android::base::Join(code_paths, ',') + "'.";
+    return;
+  }
+
   jit_->StartProfileSaver(profile_output_filename, code_paths, ref_profile_filename);
 }
 
@@ -2722,7 +2803,13 @@
     // Make initialized classes visibly initialized now. If that happened during the transaction
     // and then the transaction was aborted, we would roll back the status update but not the
     // ClassLinker's bookkeeping structures, so these classes would never be visibly initialized.
-    GetClassLinker()->MakeInitializedClassesVisiblyInitialized(Thread::Current(), /*wait=*/ true);
+    {
+      Thread* self = Thread::Current();
+      StackHandleScope<1> hs(self);
+      HandleWrapper<mirror::Class> h(hs.NewHandleWrapper(&root));
+      ScopedThreadSuspension sts(self, ThreadState::kNative);
+      GetClassLinker()->MakeInitializedClassesVisiblyInitialized(Thread::Current(), /*wait=*/ true);
+    }
     // Pass the runtime `ArenaPool` to the transaction.
     arena_pool = GetArenaPool();
   } else {
@@ -2950,7 +3037,9 @@
   }
 }
 
-void Runtime::CreateJitCodeCache(bool rwx_memory_allowed) {
+void Runtime::CreateJit() {
+  DCHECK(jit_code_cache_ == nullptr);
+  DCHECK(jit_ == nullptr);
   if (kIsDebugBuild && GetInstrumentation()->IsForcedInterpretOnly()) {
     DCHECK(!jit_options_->UseJitCompilation());
   }
@@ -2959,28 +3048,19 @@
     return;
   }
 
+  if (IsSafeMode()) {
+    LOG(INFO) << "Not creating JIT because of SafeMode.";
+    return;
+  }
+
   std::string error_msg;
   bool profiling_only = !jit_options_->UseJitCompilation();
   jit_code_cache_.reset(jit::JitCodeCache::Create(profiling_only,
-                                                  rwx_memory_allowed,
+                                                  /*rwx_memory_allowed=*/ true,
                                                   IsZygote(),
                                                   &error_msg));
   if (jit_code_cache_.get() == nullptr) {
     LOG(WARNING) << "Failed to create JIT Code Cache: " << error_msg;
-  }
-}
-
-void Runtime::CreateJit() {
-  DCHECK(jit_ == nullptr);
-  if (jit_code_cache_.get() == nullptr) {
-    if (!IsSafeMode()) {
-      LOG(WARNING) << "Missing code cache, cannot create JIT.";
-    }
-    return;
-  }
-  if (IsSafeMode()) {
-    LOG(INFO) << "Not creating JIT because of SafeMode.";
-    jit_code_cache_.reset();
     return;
   }
 
@@ -3043,12 +3123,13 @@
   return verify_ == verifier::VerifyMode::kSoftFail;
 }
 
-bool Runtime::IsAsyncDeoptimizeable(uintptr_t code) const {
+bool Runtime::IsAsyncDeoptimizeable(ArtMethod* method, uintptr_t code) const {
   if (OatQuickMethodHeader::NterpMethodHeader != nullptr) {
     if (OatQuickMethodHeader::NterpMethodHeader->Contains(code)) {
       return true;
     }
   }
+
   // We only support async deopt (ie the compiled code is not explicitly asking for
   // deopt, but something else like the debugger) in debuggable JIT code.
   // We could look at the oat file where `code` is being defined,
@@ -3056,17 +3137,58 @@
   // only rely on the JIT for debuggable apps.
   // The JIT-zygote is not debuggable so we need to be sure to exclude code from the non-private
   // region as well.
-  return IsJavaDebuggable() && GetJit() != nullptr &&
-         GetJit()->GetCodeCache()->PrivateRegionContainsPc(reinterpret_cast<const void*>(code));
+  if (GetJit() != nullptr &&
+      GetJit()->GetCodeCache()->PrivateRegionContainsPc(reinterpret_cast<const void*>(code))) {
+    // If the code is JITed code then check if it was compiled as debuggable.
+    const OatQuickMethodHeader* header = method->GetOatQuickMethodHeader(code);
+    return CodeInfo::IsDebuggable(header->GetOptimizedCodeInfoPtr());
+  }
+
+  return false;
 }
 
+
 LinearAlloc* Runtime::CreateLinearAlloc() {
-  // For 64 bit compilers, it needs to be in low 4GB in the case where we are cross compiling for a
-  // 32 bit target. In this case, we have 32 bit pointers in the dex cache arrays which can't hold
-  // when we have 64 bit ArtMethod pointers.
-  return (IsAotCompiler() && Is64BitInstructionSet(kRuntimeISA))
-      ? new LinearAlloc(low_4gb_arena_pool_.get())
-      : new LinearAlloc(arena_pool_.get());
+  ArenaPool* pool = linear_alloc_arena_pool_.get();
+  return pool != nullptr
+      ? new LinearAlloc(pool, gUseUserfaultfd)
+      : new LinearAlloc(arena_pool_.get(), /*track_allocs=*/ false);
+}
+
+class Runtime::SetupLinearAllocForZygoteFork : public AllocatorVisitor {
+ public:
+  explicit SetupLinearAllocForZygoteFork(Thread* self) : self_(self) {}
+
+  bool Visit(LinearAlloc* alloc) override {
+    alloc->SetupForPostZygoteFork(self_);
+    return true;
+  }
+
+ private:
+  Thread* self_;
+};
+
+void Runtime::SetupLinearAllocForPostZygoteFork(Thread* self) {
+  if (gUseUserfaultfd) {
+    // Setup all the linear-allocs out there for post-zygote fork. This will
+    // basically force the arena allocator to ask for a new arena for the next
+    // allocation. All arenas allocated from now on will be in the userfaultfd
+    // visited space.
+    if (GetLinearAlloc() != nullptr) {
+      GetLinearAlloc()->SetupForPostZygoteFork(self);
+    }
+    if (GetStartupLinearAlloc() != nullptr) {
+      GetStartupLinearAlloc()->SetupForPostZygoteFork(self);
+    }
+    {
+      Locks::mutator_lock_->AssertNotHeld(self);
+      ReaderMutexLock mu2(self, *Locks::mutator_lock_);
+      ReaderMutexLock mu3(self, *Locks::classlinker_classes_lock_);
+      SetupLinearAllocForZygoteFork visitor(self);
+      GetClassLinker()->VisitAllocators(&visitor);
+    }
+    static_cast<GcVisitedArenaPool*>(GetLinearAllocArenaPool())->SetupPostZygoteMode();
+  }
 }
 
 double Runtime::GetHashTableMinLoadFactor() const {
@@ -3144,15 +3266,22 @@
     auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
     for (auto& m : klass->GetMethods(pointer_size)) {
       const void* code = m.GetEntryPointFromQuickCompiledCode();
+      if (!m.IsInvokable()) {
+        continue;
+      }
+      // For java debuggable runtimes we also deoptimize native methods. For other cases (boot
+      // image profiling) we don't need to deoptimize native methods. If this changes also
+      // update Instrumentation::CanUseAotCode.
+      bool deoptimize_native_methods = Runtime::Current()->IsJavaDebuggable();
       if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) &&
-          !m.IsNative() &&
+          (!m.IsNative() || deoptimize_native_methods) &&
           !m.IsProxyMethod()) {
         instrumentation_->InitializeMethodsCode(&m, /*aot_code=*/ nullptr);
       }
 
       if (Runtime::Current()->GetJit() != nullptr &&
           Runtime::Current()->GetJit()->GetCodeCache()->IsInZygoteExecSpace(code) &&
-          !m.IsNative()) {
+          (!m.IsNative() || deoptimize_native_methods)) {
         DCHECK(!m.IsProxyMethod());
         instrumentation_->InitializeMethodsCode(&m, /*aot_code=*/ nullptr);
       }
@@ -3171,23 +3300,24 @@
   instrumentation::Instrumentation* const instrumentation_;
 };
 
-void Runtime::SetJavaDebuggable(bool value) {
-  is_java_debuggable_ = value;
-  // Do not call DeoptimizeBootImage just yet, the runtime may still be starting up.
+void Runtime::SetRuntimeDebugState(RuntimeDebugState state) {
+  if (state != RuntimeDebugState::kJavaDebuggableAtInit) {
+    // We never change the state if we started as a debuggable runtime.
+    DCHECK(runtime_debug_state_ != RuntimeDebugState::kJavaDebuggableAtInit);
+  }
+  runtime_debug_state_ = state;
 }
 
 void Runtime::DeoptimizeBootImage() {
   // If we've already started and we are setting this runtime to debuggable,
   // we patch entry points of methods in boot image to interpreter bridge, as
   // boot image code may be AOT compiled as not debuggable.
-  if (!GetInstrumentation()->IsForcedInterpretOnly()) {
-    UpdateEntryPointsClassVisitor visitor(GetInstrumentation());
-    GetClassLinker()->VisitClasses(&visitor);
-    jit::Jit* jit = GetJit();
-    if (jit != nullptr) {
-      // Code previously compiled may not be compiled debuggable.
-      jit->GetCodeCache()->TransitionToDebuggable();
-    }
+  UpdateEntryPointsClassVisitor visitor(GetInstrumentation());
+  GetClassLinker()->VisitClasses(&visitor);
+  jit::Jit* jit = GetJit();
+  if (jit != nullptr) {
+    // Code previously compiled may not be compiled debuggable.
+    jit->GetCodeCache()->TransitionToDebuggable();
   }
 }
 
@@ -3235,69 +3365,23 @@
   startup_completed_.store(false, std::memory_order_seq_cst);
 }
 
-class Runtime::NotifyStartupCompletedTask : public gc::HeapTask {
- public:
-  NotifyStartupCompletedTask() : gc::HeapTask(/*target_run_time=*/ NanoTime()) {}
-
-  void Run(Thread* self) override {
-    VLOG(startup) << "NotifyStartupCompletedTask running";
-    Runtime* const runtime = Runtime::Current();
-    {
-      ScopedTrace trace("Releasing app image spaces metadata");
-      ScopedObjectAccess soa(Thread::Current());
-      // Request empty checkpoints to make sure no threads are accessing the image space metadata
-      // section when we madvise it. Use GC exclusion to prevent deadlocks that may happen if
-      // multiple threads are attempting to run empty checkpoints at the same time.
-      {
-        // Avoid using ScopedGCCriticalSection since that does not allow thread suspension. This is
-        // not allowed to prevent allocations, but it's still safe to suspend temporarily for the
-        // checkpoint.
-        gc::ScopedInterruptibleGCCriticalSection sigcs(self,
-                                                       gc::kGcCauseRunEmptyCheckpoint,
-                                                       gc::kCollectorTypeCriticalSection);
-        runtime->GetThreadList()->RunEmptyCheckpoint();
-      }
-      for (gc::space::ContinuousSpace* space : runtime->GetHeap()->GetContinuousSpaces()) {
-        if (space->IsImageSpace()) {
-          gc::space::ImageSpace* image_space = space->AsImageSpace();
-          if (image_space->GetImageHeader().IsAppImage()) {
-            image_space->ReleaseMetadata();
-          }
-        }
-      }
-    }
-
-    {
-      // Delete the thread pool used for app image loading since startup is assumed to be completed.
-      ScopedTrace trace2("Delete thread pool");
-      runtime->DeleteThreadPool();
-    }
-  }
-};
-
-void Runtime::NotifyStartupCompleted() {
+bool Runtime::NotifyStartupCompleted() {
+  DCHECK(!IsZygote());
   bool expected = false;
   if (!startup_completed_.compare_exchange_strong(expected, true, std::memory_order_seq_cst)) {
     // Right now NotifyStartupCompleted will be called up to twice, once from profiler and up to
     // once externally. For this reason there are no asserts.
-    return;
+    return false;
   }
 
   VLOG(startup) << app_info_;
 
-  VLOG(startup) << "Adding NotifyStartupCompleted task";
-  // Use the heap task processor since we want to be exclusive with the GC and we don't want to
-  // block the caller if the GC is running.
-  if (!GetHeap()->AddHeapTask(new NotifyStartupCompletedTask)) {
-    VLOG(startup) << "Failed to add NotifyStartupCompletedTask";
-  }
-
-  // Notify the profiler saver that startup is now completed.
   ProfileSaver::NotifyStartupCompleted();
 
   if (metrics_reporter_ != nullptr) {
     metrics_reporter_->NotifyStartupCompleted();
   }
+  return true;
 }
 
 void Runtime::NotifyDexFileLoaded() {
@@ -3324,33 +3408,12 @@
   WellKnownClasses::HandleJniIdTypeChange(Thread::Current()->GetJniEnv());
 }
 
-bool Runtime::GetOatFilesExecutable() const {
-  return !IsAotCompiler() && !(IsSystemServer() && jit_options_->GetSaveProfilingInfo());
+bool Runtime::IsSystemServerProfiled() const {
+  return IsSystemServer() && jit_options_->GetSaveProfilingInfo();
 }
 
-void Runtime::ProcessWeakClass(GcRoot<mirror::Class>* root_ptr,
-                               IsMarkedVisitor* visitor,
-                               mirror::Class* update) {
-    // This does not need a read barrier because this is called by GC.
-  mirror::Class* cls = root_ptr->Read<kWithoutReadBarrier>();
-  if (cls != nullptr && cls != GetWeakClassSentinel()) {
-    DCHECK((cls->IsClass<kDefaultVerifyFlags>()));
-    // Look at the classloader of the class to know if it has been unloaded.
-    // This does not need a read barrier because this is called by GC.
-    ObjPtr<mirror::Object> class_loader =
-        cls->GetClassLoader<kDefaultVerifyFlags, kWithoutReadBarrier>();
-    if (class_loader == nullptr || visitor->IsMarked(class_loader.Ptr()) != nullptr) {
-      // The class loader is live, update the entry if the class has moved.
-      mirror::Class* new_cls = down_cast<mirror::Class*>(visitor->IsMarked(cls));
-      // Note that new_object can be null for CMS and newly allocated objects.
-      if (new_cls != nullptr && new_cls != cls) {
-        *root_ptr = GcRoot<mirror::Class>(new_cls);
-      }
-    } else {
-      // The class loader is not live, clear the entry.
-      *root_ptr = GcRoot<mirror::Class>(update);
-    }
-  }
+bool Runtime::GetOatFilesExecutable() const {
+  return !IsAotCompiler() && !IsSystemServerProfiled();
 }
 
 void Runtime::MadviseFileForRange(size_t madvise_size_limit_bytes,
@@ -3358,6 +3421,24 @@
                                   const uint8_t* map_begin,
                                   const uint8_t* map_end,
                                   const std::string& file_name) {
+  map_begin = AlignDown(map_begin, kPageSize);
+  map_size_bytes = RoundUp(map_size_bytes, kPageSize);
+#ifdef ART_TARGET_ANDROID
+  // Short-circuit the madvise optimization for background processes. This
+  // avoids IO and memory contention with foreground processes, particularly
+  // those involving app startup.
+  // Note: We can only safely short-circuit the madvise on T+, as it requires
+  // the framework to always immediately notify ART of process states.
+  static const int kApiLevel = android_get_device_api_level();
+  const bool accurate_process_state_at_startup = kApiLevel >= __ANDROID_API_T__;
+  if (accurate_process_state_at_startup) {
+    const Runtime* runtime = Runtime::Current();
+    if (runtime != nullptr && !runtime->InJankPerceptibleProcessState()) {
+      return;
+    }
+  }
+#endif  // ART_TARGET_ANDROID
+
   // Ideal blockTransferSize for madvising files (128KiB)
   static constexpr size_t kIdealIoTransferSizeBytes = 128*1024;
 
@@ -3374,7 +3455,7 @@
 
     // Clamp endOfFile if its past map_end
     if (target_pos > map_end) {
-        target_pos = map_end;
+      target_pos = map_end;
     }
 
     // Madvise the whole file up to target_pos in chunks of
@@ -3392,13 +3473,17 @@
       int status = madvise(madvise_addr, madvise_length, MADV_WILLNEED);
       // In case of error we stop madvising rest of the file
       if (status < 0) {
-        LOG(ERROR) << "Failed to madvise file:" << file_name << " for size:" << map_size_bytes;
+        LOG(ERROR) << "Failed to madvise file " << file_name
+                   << " for size:" << map_size_bytes
+                   << ": " << strerror(errno);
         break;
       }
     }
   }
 }
 
+// Return whether a boot image has a profile. This means we'll need to pre-JIT
+// methods in that profile for performance.
 bool Runtime::HasImageWithProfile() const {
   for (gc::space::ImageSpace* space : GetHeap()->GetBootImageSpaces()) {
     if (!space->GetProfileFiles().empty()) {
@@ -3408,4 +3493,70 @@
   return false;
 }
 
+void Runtime::AppendToBootClassPath(const std::string& filename, const std::string& location) {
+  DCHECK(!DexFileLoader::IsMultiDexLocation(filename.c_str()));
+  boot_class_path_.push_back(filename);
+  if (!boot_class_path_locations_.empty()) {
+    DCHECK(!DexFileLoader::IsMultiDexLocation(location.c_str()));
+    boot_class_path_locations_.push_back(location);
+  }
+}
+
+void Runtime::AppendToBootClassPath(
+    const std::string& filename,
+    const std::string& location,
+    const std::vector<std::unique_ptr<const art::DexFile>>& dex_files) {
+  AppendToBootClassPath(filename, location);
+  ScopedObjectAccess soa(Thread::Current());
+  for (const std::unique_ptr<const art::DexFile>& dex_file : dex_files) {
+    // The first element must not be at a multi-dex location, while other elements must be.
+    DCHECK_NE(DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str()),
+              dex_file.get() == dex_files.begin()->get());
+    GetClassLinker()->AppendToBootClassPath(Thread::Current(), dex_file.get());
+  }
+}
+
+void Runtime::AppendToBootClassPath(const std::string& filename,
+                                    const std::string& location,
+                                    const std::vector<const art::DexFile*>& dex_files) {
+  AppendToBootClassPath(filename, location);
+  ScopedObjectAccess soa(Thread::Current());
+  for (const art::DexFile* dex_file : dex_files) {
+    // The first element must not be at a multi-dex location, while other elements must be.
+    DCHECK_NE(DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str()),
+              dex_file == *dex_files.begin());
+    GetClassLinker()->AppendToBootClassPath(Thread::Current(), dex_file);
+  }
+}
+
+void Runtime::AppendToBootClassPath(
+    const std::string& filename,
+    const std::string& location,
+    const std::vector<std::pair<const art::DexFile*, ObjPtr<mirror::DexCache>>>&
+        dex_files_and_cache) {
+  AppendToBootClassPath(filename, location);
+  ScopedObjectAccess soa(Thread::Current());
+  for (const auto& [dex_file, dex_cache] : dex_files_and_cache) {
+    // The first element must not be at a multi-dex location, while other elements must be.
+    DCHECK_NE(DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str()),
+              dex_file == dex_files_and_cache.begin()->first);
+    GetClassLinker()->AppendToBootClassPath(dex_file, dex_cache);
+  }
+}
+
+void Runtime::AddExtraBootDexFiles(const std::string& filename,
+                                   const std::string& location,
+                                   std::vector<std::unique_ptr<const art::DexFile>>&& dex_files) {
+  AppendToBootClassPath(filename, location);
+  ScopedObjectAccess soa(Thread::Current());
+  if (kIsDebugBuild) {
+    for (const std::unique_ptr<const art::DexFile>& dex_file : dex_files) {
+      // The first element must not be at a multi-dex location, while other elements must be.
+      DCHECK_NE(DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str()),
+                dex_file.get() == dex_files.begin()->get());
+    }
+  }
+  GetClassLinker()->AddExtraBootDexFiles(Thread::Current(), std::move(dex_files));
+}
+
 }  // namespace art
diff --git a/runtime/runtime.h b/runtime/runtime.h
index e7b71e2..fc8c050 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -68,6 +68,10 @@
 class JitOptions;
 }  // namespace jit
 
+namespace jni {
+class SmallLrtAllocator;
+}  // namespace jni
+
 namespace mirror {
 class Array;
 class ClassLoader;
@@ -107,7 +111,6 @@
 struct RuntimeArgumentMap;
 class RuntimeCallbacks;
 class SignalCatcher;
-class SmallIrtAllocator;
 class StackOverflowHandler;
 class SuspensionHandler;
 class ThreadList;
@@ -133,6 +136,19 @@
   static bool Create(const RuntimeOptions& raw_options, bool ignore_unrecognized)
       SHARED_TRYLOCK_FUNCTION(true, Locks::mutator_lock_);
 
+  enum class RuntimeDebugState {
+    // This doesn't support any debug features / method tracing. This is the expected state usually.
+    kNonJavaDebuggable,
+    // This supports method tracing and a restricted set of debug features (for ex: redefinition
+    // isn't supported). We transition to this state when method tracing has started or when the
+    // debugger was attached and transition back to NonDebuggable once the tracing has stopped /
+    // the debugger agent has detached..
+    kJavaDebuggable,
+    // The runtime was started as a debuggable runtime. This allows us to support the extended set
+    // of debug features (for ex: redefinition). We never transition out of this state.
+    kJavaDebuggableAtInit
+  };
+
   bool EnsurePluginLoaded(const char* plugin_name, std::string* error_msg);
   bool EnsurePerfettoPlugin(std::string* error_msg);
 
@@ -257,6 +273,13 @@
     return instance_;
   }
 
+  // Set the current runtime to be the given instance.
+  // Note that this function is not responsible for cleaning up the old instance or taking the
+  // ownership of the new instance.
+  //
+  // For test use only.
+  static void TestOnlySetCurrent(Runtime* instance) { instance_ = instance; }
+
   // Aborts semi-cleanly. Used in the implementation of LOG(FATAL), which most
   // callers should prefer.
   NO_RETURN static void Abort(const char* msg) REQUIRES(!Locks::abort_lock_);
@@ -271,13 +294,16 @@
   jobject GetSystemClassLoader() const;
 
   // Attaches the calling native thread to the runtime.
-  bool AttachCurrentThread(const char* thread_name, bool as_daemon, jobject thread_group,
-                           bool create_peer);
+  bool AttachCurrentThread(const char* thread_name,
+                           bool as_daemon,
+                           jobject thread_group,
+                           bool create_peer,
+                           bool should_run_callbacks = true);
 
   void CallExitHook(jint status);
 
   // Detaches the current native thread from the runtime.
-  void DetachCurrentThread() REQUIRES(!Locks::mutator_lock_);
+  void DetachCurrentThread(bool should_run_callbacks = true) REQUIRES(!Locks::mutator_lock_);
 
   void DumpDeoptimizations(std::ostream& os);
   void DumpForSigQuit(std::ostream& os);
@@ -295,6 +321,28 @@
     return boot_class_path_locations_.empty() ? boot_class_path_ : boot_class_path_locations_;
   }
 
+  // Dynamically adds an element to boot class path.
+  void AppendToBootClassPath(const std::string& filename,
+                             const std::string& location,
+                             const std::vector<std::unique_ptr<const art::DexFile>>& dex_files);
+
+  // Same as above, but takes raw pointers.
+  void AppendToBootClassPath(const std::string& filename,
+                             const std::string& location,
+                             const std::vector<const art::DexFile*>& dex_files);
+
+  // Same as above, but also takes a dex cache for each dex file.
+  void AppendToBootClassPath(
+      const std::string& filename,
+      const std::string& location,
+      const std::vector<std::pair<const art::DexFile*, ObjPtr<mirror::DexCache>>>&
+          dex_files_and_cache);
+
+  // Dynamically adds an element to boot class path and takes ownership of the dex files.
+  void AddExtraBootDexFiles(const std::string& filename,
+                            const std::string& location,
+                            std::vector<std::unique_ptr<const art::DexFile>>&& dex_files);
+
   const std::vector<int>& GetBootClassPathFds() const {
     return boot_class_path_fds_;
   }
@@ -325,8 +373,8 @@
     return class_linker_;
   }
 
-  SmallIrtAllocator* GetSmallIrtAllocator() const {
-    return small_irt_allocator_;
+  jni::SmallLrtAllocator* GetSmallLrtAllocator() const {
+    return small_lrt_allocator_;
   }
 
   jni::JniIdManager* GetJniIdManager() const {
@@ -430,8 +478,7 @@
 
   // Sweep system weaks, the system weak is deleted if the visitor return null. Otherwise, the
   // system weak is updated to be the visitor's returned value.
-  void SweepSystemWeaks(IsMarkedVisitor* visitor)
-      REQUIRES_SHARED(Locks::mutator_lock_);
+  void SweepSystemWeaks(IsMarkedVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Walk all reflective objects and visit their targets as well as any method/fields held by the
   // runtime threads that are marked as being reflective.
@@ -498,6 +545,10 @@
     return OFFSETOF_MEMBER(Runtime, callee_save_methods_[static_cast<size_t>(type)]);
   }
 
+  static constexpr MemberOffset GetInstrumentationOffset() {
+    return MemberOffset(OFFSETOF_MEMBER(Runtime, instrumentation_));
+  }
+
   InstructionSet GetInstructionSet() const {
     return instruction_set_;
   }
@@ -567,7 +618,8 @@
 
   // Transaction support.
   bool IsActiveTransaction() const;
-  void EnterTransactionMode(bool strict, mirror::Class* root);
+  // EnterTransactionMode may suspend.
+  void EnterTransactionMode(bool strict, mirror::Class* root) REQUIRES_SHARED(Locks::mutator_lock_);
   void ExitTransactionMode();
   void RollbackAllTransactions() REQUIRES_SHARED(Locks::mutator_lock_);
   // Transaction rollback and exit transaction are always done together, it's convenience to
@@ -752,6 +804,9 @@
   // Create the JIT and instrumentation and code cache.
   void CreateJit();
 
+  ArenaPool* GetLinearAllocArenaPool() {
+    return linear_alloc_arena_pool_.get();
+  }
   ArenaPool* GetArenaPool() {
     return arena_pool_.get();
   }
@@ -768,12 +823,21 @@
     return linear_alloc_.get();
   }
 
+  LinearAlloc* GetStartupLinearAlloc() {
+    return startup_linear_alloc_.load(std::memory_order_relaxed);
+  }
+
   jit::JitOptions* GetJITOptions() {
     return jit_options_.get();
   }
 
   bool IsJavaDebuggable() const {
-    return is_java_debuggable_;
+    return runtime_debug_state_ == RuntimeDebugState::kJavaDebuggable ||
+           runtime_debug_state_ == RuntimeDebugState::kJavaDebuggableAtInit;
+  }
+
+  bool IsJavaDebuggableAtInit() const {
+    return runtime_debug_state_ == RuntimeDebugState::kJavaDebuggableAtInit;
   }
 
   void SetProfileableFromShell(bool value) {
@@ -792,7 +856,7 @@
     return is_profileable_;
   }
 
-  void SetJavaDebuggable(bool value);
+  void SetRuntimeDebugState(RuntimeDebugState state);
 
   // Deoptimize the boot image, called for Java debuggable apps.
   void DeoptimizeBootImage() REQUIRES(Locks::mutator_lock_);
@@ -842,14 +906,13 @@
     return reinterpret_cast<mirror::Class*>(0xebadbeef);
   }
 
-  // Helper for the GC to process a weak class in a table.
-  static void ProcessWeakClass(GcRoot<mirror::Class>* root_ptr,
-                               IsMarkedVisitor* visitor,
-                               mirror::Class* update)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   // Create a normal LinearAlloc or low 4gb version if we are 64 bit AOT compiler.
   LinearAlloc* CreateLinearAlloc();
+  // Setup linear-alloc allocators to stop using the current arena so that the
+  // next allocations, which would be after zygote fork, happens in userfaultfd
+  // visited space.
+  void SetupLinearAllocForPostZygoteFork(Thread* self)
+      REQUIRES(!Locks::mutator_lock_, !Locks::classlinker_classes_lock_);
 
   OatFileManager& GetOatFileManager() const {
     DCHECK(oat_file_manager_ != nullptr);
@@ -890,7 +953,8 @@
 
   // Returns if the code can be deoptimized asynchronously. Code may be compiled with some
   // optimization that makes it impossible to deoptimize.
-  bool IsAsyncDeoptimizeable(uintptr_t code) const REQUIRES_SHARED(Locks::mutator_lock_);
+  bool IsAsyncDeoptimizeable(ArtMethod* method, uintptr_t code) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Returns a saved copy of the environment (getenv/setenv values).
   // Used by Fork to protect against overwriting LD_LIBRARY_PATH, etc.
@@ -940,14 +1004,8 @@
     return deny_art_apex_data_files_;
   }
 
-  // Whether or not we use MADV_RANDOM on files that are thought to have random access patterns.
-  // This is beneficial for low RAM devices since it reduces page cache thrashing.
-  bool MAdviseRandomAccess() const {
-    return madvise_random_access_;
-  }
-
-  size_t GetMadviseWillNeedSizeVdex() const {
-    return madvise_willneed_vdex_filesize_;
+  size_t GetMadviseWillNeedTotalDexSize() const {
+    return madvise_willneed_total_dex_size_;
   }
 
   size_t GetMadviseWillNeedSizeOdex() const {
@@ -1004,6 +1062,10 @@
     ThreadPool* const thread_pool_;
   };
 
+  LinearAlloc* ReleaseStartupLinearAlloc() {
+    return startup_linear_alloc_.exchange(nullptr, std::memory_order_relaxed);
+  }
+
   bool LoadAppImageStartupCache() const {
     return load_app_image_startup_cache_;
   }
@@ -1017,8 +1079,8 @@
   void ResetStartupCompleted();
 
   // Notify the runtime that application startup is considered completed. Only has effect for the
-  // first call.
-  void NotifyStartupCompleted();
+  // first call. Returns whether this was the first call.
+  bool NotifyStartupCompleted();
 
   // Notify the runtime that the application finished loading some dex/odex files. This is
   // called everytime we load a set of dex files in a class loader.
@@ -1050,7 +1112,11 @@
   uint64_t GetMonitorTimeoutNs() const {
     return monitor_timeout_ns_;
   }
-  // Return true if we should load oat files as executable or not.
+
+  // Return whether this is system server and it is being profiled.
+  bool IsSystemServerProfiled() const;
+
+  // Return whether we should load oat files as executable or not.
   bool GetOatFilesExecutable() const;
 
   metrics::ArtMetrics* GetMetrics() { return &metrics_; }
@@ -1073,6 +1139,14 @@
   // image rather that an image loaded from disk.
   bool HasImageWithProfile() const;
 
+  bool GetNoSigChain() const {
+    return no_sig_chain_;
+  }
+
+  void AddGeneratedCodeRange(const void* start, size_t size);
+  void RemoveGeneratedCodeRange(const void* start, size_t size)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Trigger a flag reload from system properties or device congfigs.
   //
   // Should only be called from runtime init and zygote post fork as
@@ -1084,11 +1158,33 @@
   // See Flags::ReloadAllFlags as well.
   static void ReloadAllFlags(const std::string& caller);
 
+  // Parses /apex/apex-info-list.xml to build a string containing apex versions of boot classpath
+  // jars, which is encoded into .oat files.
+  static std::string GetApexVersions(ArrayRef<const std::string> boot_class_path_locations);
+
+  bool AllowInMemoryCompilation() const { return allow_in_memory_compilation_; }
+
+  // Used by plugin code to attach a hook for OOME.
+  void SetOutOfMemoryErrorHook(void (*hook)()) {
+    out_of_memory_error_hook_ = hook;
+  }
+
+  void OutOfMemoryErrorHook() {
+    if (out_of_memory_error_hook_ != nullptr) {
+      out_of_memory_error_hook_();
+    }
+  }
+
  private:
   static void InitPlatformSignalHandlers();
 
   Runtime();
 
+  bool HandlesSignalsInCompiledCode() const {
+    return !no_sig_chain_ &&
+           (implicit_null_checks_ || implicit_so_checks_ || implicit_suspend_checks_);
+  }
+
   void BlockSignals();
 
   bool Init(RuntimeArgumentMap&& runtime_options)
@@ -1097,7 +1193,7 @@
   void RegisterRuntimeNativeMethods(JNIEnv* env);
   void InitMetrics();
 
-  void StartDaemonThreads();
+  void StartDaemonThreads() REQUIRES_SHARED(Locks::mutator_lock_);
   void StartSignalCatcher();
 
   void MaybeSaveJitProfilingInfo();
@@ -1124,10 +1220,11 @@
   ThreadPool* AcquireThreadPool() REQUIRES(!Locks::runtime_thread_pool_lock_);
   void ReleaseThreadPool() REQUIRES(!Locks::runtime_thread_pool_lock_);
 
-  // Parses /apex/apex-info-list.xml to initialize a string containing versions
-  // of boot classpath jars and encoded into .oat files.
+  // Caches the apex versions produced by `GetApexVersions`.
   void InitializeApexVersions();
 
+  void AppendToBootClassPath(const std::string& filename, const std::string& location);
+
   // A pointer to the active runtime or null.
   static Runtime* instance_;
 
@@ -1194,14 +1291,21 @@
 
   std::unique_ptr<ArenaPool> jit_arena_pool_;
   std::unique_ptr<ArenaPool> arena_pool_;
-  // Special low 4gb pool for compiler linear alloc. We need ArtFields to be in low 4gb if we are
-  // compiling using a 32 bit image on a 64 bit compiler in case we resolve things in the image
-  // since the field arrays are int arrays in this case.
-  std::unique_ptr<ArenaPool> low_4gb_arena_pool_;
+  // This pool is used for linear alloc if we are using userfaultfd GC, or if
+  // low 4gb pool is required for compiler linear alloc. Otherwise, use
+  // arena_pool_.
+  // We need ArtFields to be in low 4gb if we are compiling using a 32 bit image
+  // on a 64 bit compiler in case we resolve things in the image since the field
+  // arrays are int arrays in this case.
+  std::unique_ptr<ArenaPool> linear_alloc_arena_pool_;
 
   // Shared linear alloc for now.
   std::unique_ptr<LinearAlloc> linear_alloc_;
 
+  // Linear alloc used for allocations during startup. Will be deleted after
+  // startup. Atomic because the pointer can be concurrently updated to null.
+  std::atomic<LinearAlloc*> startup_linear_alloc_;
+
   // The number of spins that are done before thread suspension is used to forcibly inflate.
   size_t max_spins_before_thin_lock_inflation_;
   MonitorList* monitor_list_;
@@ -1215,7 +1319,7 @@
 
   SignalCatcher* signal_catcher_;
 
-  SmallIrtAllocator* small_irt_allocator_;
+  jni::SmallLrtAllocator* small_lrt_allocator_;
 
   std::unique_ptr<jni::JniIdManager> jni_id_manager_;
 
@@ -1332,7 +1436,7 @@
   bool non_standard_exits_enabled_;
 
   // Whether Java code needs to be debuggable.
-  bool is_java_debuggable_;
+  RuntimeDebugState runtime_debug_state_;
 
   bool monitor_timeout_enable_;
   uint64_t monitor_timeout_ns_;
@@ -1365,13 +1469,10 @@
   // Whether or not we are on a low RAM device.
   bool is_low_memory_mode_;
 
-  // Whether or not we use MADV_RANDOM on files that are thought to have random access patterns.
-  // This is beneficial for low RAM devices since it reduces page cache thrashing.
-  bool madvise_random_access_;
-
   // Limiting size (in bytes) for applying MADV_WILLNEED on vdex files
+  // or uncompressed dex files in APKs.
   // A 0 for this will turn off madvising to MADV_WILLNEED
-  size_t madvise_willneed_vdex_filesize_;
+  size_t madvise_willneed_total_dex_size_;
 
   // Limiting size (in bytes) for applying MADV_WILLNEED on odex files
   // A 0 for this will turn off madvising to MADV_WILLNEED
@@ -1437,6 +1538,9 @@
   // True if files in /data/misc/apexdata/com.android.art are considered untrustworthy.
   bool deny_art_apex_data_files_;
 
+  // Whether to allow compiling the boot classpath in memory when the given boot image is unusable.
+  bool allow_in_memory_compilation_ = false;
+
   // Saved environment.
   class EnvSnapshot {
    public:
@@ -1473,6 +1577,9 @@
   bool perfetto_hprof_enabled_;
   bool perfetto_javaheapprof_enabled_;
 
+  // Called on out of memory error
+  void (*out_of_memory_error_hook_)();
+
   metrics::ArtMetrics metrics_;
   std::unique_ptr<metrics::MetricsReporter> metrics_reporter_;
 
@@ -1492,7 +1599,7 @@
   friend class Dex2oatImageTest;
   friend class ScopedThreadPoolUsage;
   friend class OatFileAssistantTest;
-  class NotifyStartupCompletedTask;
+  class SetupLinearAllocForZygoteFork;
 
   DISALLOW_COPY_AND_ASSIGN(Runtime);
 };
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index 753ac28..28c81a2 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -105,9 +105,9 @@
   Remove(cb, &method_inspection_callbacks_);
 }
 
-bool RuntimeCallbacks::IsMethodBeingInspected(ArtMethod* m) {
+bool RuntimeCallbacks::HaveLocalsChanged() {
   for (MethodInspectionCallback* cb : COPY(method_inspection_callbacks_)) {
-    if (cb->IsMethodBeingInspected(m)) {
+    if (cb->HaveLocalsChanged()) {
       return true;
     }
   }
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index b1a7e55..98584a8 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -143,9 +143,8 @@
  public:
   virtual ~MethodInspectionCallback() {}
 
-  // Returns true if the method is being inspected currently and the runtime should not modify it in
-  // potentially dangerous ways (i.e. replace with compiled version, JIT it, etc).
-  virtual bool IsMethodBeingInspected(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+  // Returns true if any locals have changed. If any locals have changed we shouldn't OSR.
+  virtual bool HaveLocalsChanged() REQUIRES_SHARED(Locks::mutator_lock_) = 0;
 };
 
 // Callback to let something request to be notified when reflective objects are being visited and
@@ -225,9 +224,9 @@
   void AddParkCallback(ParkCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
   void RemoveParkCallback(ParkCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Returns true if some MethodInspectionCallback indicates the method is being inspected/depended
-  // on by some code.
-  bool IsMethodBeingInspected(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
+  // Returns true if any locals have changed. This is used to prevent OSRing frames that have
+  // some locals changed.
+  bool HaveLocalsChanged() REQUIRES_SHARED(Locks::mutator_lock_);
 
   void AddMethodInspectionCallback(MethodInspectionCallback* cb)
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index 7f64721..6f0b8a1 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -27,7 +27,7 @@
 
 #include "jni.h"
 
-#include "art_method-inl.h"
+#include "art_method-alloc-inl.h"
 #include "base/mem_map.h"
 #include "base/mutex.h"
 #include "class_linker.h"
@@ -35,7 +35,7 @@
 #include "dex/class_reference.h"
 #include "handle.h"
 #include "handle_scope-inl.h"
-#include "mirror/class-inl.h"
+#include "mirror/class-alloc-inl.h"
 #include "mirror/class_loader.h"
 #include "monitor-inl.h"
 #include "nativehelper/scoped_local_ref.h"
@@ -44,7 +44,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
 #include "thread_list.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 namespace art {
 
@@ -164,40 +164,34 @@
 
   cb_.state = CallbackState::kBase;  // Ignore main thread attach.
 
-  {
-    ScopedObjectAccess soa(self);
-    MakeExecutable(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread));
-  }
+  ScopedObjectAccess soa(self);
+  MakeExecutable(WellKnownClasses::java_lang_Thread.Get());
 
-  JNIEnv* env = self->GetJniEnv();
+  StackHandleScope<3u> hs(self);
+  Handle<mirror::String> thread_name = hs.NewHandle(
+      mirror::String::AllocFromModifiedUtf8(self, "ThreadLifecycleCallback test thread"));
+  ASSERT_TRUE(thread_name != nullptr);
 
-  ScopedLocalRef<jobject> thread_name(env,
-                                      env->NewStringUTF("ThreadLifecycleCallback test thread"));
-  ASSERT_TRUE(thread_name.get() != nullptr);
+  Handle<mirror::Object> thread_group =
+      hs.NewHandle(soa.Decode<mirror::Object>(runtime_->GetMainThreadGroup()));
+  Handle<mirror::Object> thread =
+      WellKnownClasses::java_lang_Thread_init->NewObject<'L', 'L', 'I', 'Z'>(
+          hs, self, thread_group, thread_name, kMinThreadPriority, /*daemon=*/ false);
+  ASSERT_FALSE(self->IsExceptionPending());
+  ASSERT_TRUE(thread != nullptr);
 
-  ScopedLocalRef<jobject> thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread));
-  ASSERT_TRUE(thread.get() != nullptr);
+  ArtMethod* start_method =
+      thread->GetClass()->FindClassMethod("start", "()V", kRuntimePointerSize);
+  ASSERT_TRUE(start_method != nullptr);
 
-  env->CallNonvirtualVoidMethod(thread.get(),
-                                WellKnownClasses::java_lang_Thread,
-                                WellKnownClasses::java_lang_Thread_init,
-                                runtime_->GetMainThreadGroup(),
-                                thread_name.get(),
-                                kMinThreadPriority,
-                                JNI_FALSE);
-  ASSERT_FALSE(env->ExceptionCheck());
+  start_method->InvokeVirtual<'V'>(self, thread.Get());
+  ASSERT_FALSE(self->IsExceptionPending());
 
-  jmethodID start_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "start", "()V");
-  ASSERT_TRUE(start_id != nullptr);
+  ArtMethod* join_method = thread->GetClass()->FindClassMethod("join", "()V", kRuntimePointerSize);
+  ASSERT_TRUE(join_method != nullptr);
 
-  env->CallVoidMethod(thread.get(), start_id);
-  ASSERT_FALSE(env->ExceptionCheck());
-
-  jmethodID join_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "join", "()V");
-  ASSERT_TRUE(join_id != nullptr);
-
-  env->CallVoidMethod(thread.get(), join_id);
-  ASSERT_FALSE(env->ExceptionCheck());
+  join_method->InvokeFinal<'V'>(self, thread.Get());
+  ASSERT_FALSE(self->IsExceptionPending());
 
   EXPECT_EQ(cb_.state, CallbackState::kDied);
 }
@@ -303,6 +297,7 @@
 TEST_F(ClassLoadCallbackRuntimeCallbacksTest, ClassLoadCallback) {
   ScopedObjectAccess soa(Thread::Current());
   jobject jclass_loader = LoadDex("XandY");
+  cb_.data.clear();  // Clear class loading records from `LoadDex()`, if any.
   VariableSizedHandleScope hs(soa.Self());
   Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
       soa.Decode<mirror::ClassLoader>(jclass_loader)));
@@ -514,12 +509,11 @@
     ASSERT_TRUE(started);
     {
       ScopedObjectAccess soa(self);
-      cb_.SetInterestingObject(
-          soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections));
+      cb_.SetInterestingObject(WellKnownClasses::java_util_Collections.Get());
       Monitor::Wait(
           self,
           // Just a random class
-          soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections),
+          WellKnownClasses::java_util_Collections.Get(),
           /*ms=*/0,
           /*ns=*/0,
           /*interruptShouldThrow=*/false,
diff --git a/runtime/runtime_common.h b/runtime/runtime_common.h
index 925594e..ec08907 100644
--- a/runtime/runtime_common.h
+++ b/runtime/runtime_common.h
@@ -42,7 +42,7 @@
   void Dump(std::ostream& os) const {
     // This is a backtrace from a crash, do not skip any frames in case the
     // crash is in the unwinder itself.
-    DumpNativeStack(os, GetTid(), nullptr, "\t", nullptr, raw_context_, false);
+    DumpNativeStack(os, GetTid(), "\t", nullptr, raw_context_, false);
   }
  private:
   // Stores the context of the signal that was unexpected and will terminate the runtime. The
diff --git a/runtime/runtime_image.cc b/runtime/runtime_image.cc
new file mode 100644
index 0000000..f41d4c9
--- /dev/null
+++ b/runtime/runtime_image.cc
@@ -0,0 +1,1914 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "runtime_image.h"
+
+#include <lz4.h>
+#include <sstream>
+#include <unistd.h>
+
+#include "android-base/file.h"
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+
+#include "base/arena_allocator.h"
+#include "base/arena_containers.h"
+#include "base/bit_utils.h"
+#include "base/file_utils.h"
+#include "base/length_prefixed_array.h"
+#include "base/scoped_flock.h"
+#include "base/stl_util.h"
+#include "base/systrace.h"
+#include "base/unix_file/fd_file.h"
+#include "base/utils.h"
+#include "class_loader_context.h"
+#include "class_loader_utils.h"
+#include "class_root-inl.h"
+#include "dex/class_accessor-inl.h"
+#include "gc/space/image_space.h"
+#include "image.h"
+#include "mirror/object-inl.h"
+#include "mirror/object-refvisitor-inl.h"
+#include "mirror/object_array-alloc-inl.h"
+#include "mirror/object_array-inl.h"
+#include "mirror/object_array.h"
+#include "mirror/string-inl.h"
+#include "nterp_helpers.h"
+#include "oat.h"
+#include "profile/profile_compilation_info.h"
+#include "scoped_thread_state_change-inl.h"
+#include "vdex_file.h"
+
+namespace art {
+
+using android::base::StringPrintf;
+
+/**
+ * The native data structures that we store in the image.
+ */
+enum class NativeRelocationKind {
+  kArtFieldArray,
+  kArtMethodArray,
+  kArtMethod,
+  kImTable,
+  // For dex cache arrays which can stay in memory even after startup. Those are
+  // dex cache arrays whose size is below a given threshold, defined by
+  // DexCache::ShouldAllocateFullArray.
+  kFullNativeDexCacheArray,
+  // For dex cache arrays which we will want to release after app startup.
+  kStartupNativeDexCacheArray,
+};
+
+/**
+ * Helper class to generate an app image at runtime.
+ */
+class RuntimeImageHelper {
+ public:
+  explicit RuntimeImageHelper(gc::Heap* heap) :
+    allocator_(Runtime::Current()->GetArenaPool()),
+    objects_(allocator_.Adapter()),
+    art_fields_(allocator_.Adapter()),
+    art_methods_(allocator_.Adapter()),
+    im_tables_(allocator_.Adapter()),
+    metadata_(allocator_.Adapter()),
+    dex_cache_arrays_(allocator_.Adapter()),
+    string_reference_offsets_(allocator_.Adapter()),
+    sections_(ImageHeader::kSectionCount, allocator_.Adapter()),
+    object_offsets_(allocator_.Adapter()),
+    classes_(allocator_.Adapter()),
+    array_classes_(allocator_.Adapter()),
+    dex_caches_(allocator_.Adapter()),
+    class_hashes_(allocator_.Adapter()),
+    native_relocations_(allocator_.Adapter()),
+    boot_image_begin_(heap->GetBootImagesStartAddress()),
+    boot_image_size_(heap->GetBootImagesSize()),
+    image_begin_(boot_image_begin_ + boot_image_size_),
+    // Note: image relocation considers the image header in the bitmap.
+    object_section_size_(sizeof(ImageHeader)),
+    intern_table_(InternStringHash(this), InternStringEquals(this)),
+    class_table_(ClassDescriptorHash(this), ClassDescriptorEquals()) {}
+
+  bool Generate(std::string* error_msg) {
+    if (!WriteObjects(error_msg)) {
+      return false;
+    }
+
+    // Generate the sections information stored in the header.
+    CreateImageSections();
+
+    // Now that all sections have been created and we know their offset and
+    // size, relocate native pointers inside classes and ImTables.
+    RelocateNativePointers();
+
+    // Generate the bitmap section, stored page aligned after the sections data
+    // and of size `object_section_size_` page aligned.
+    size_t sections_end = sections_[ImageHeader::kSectionMetadata].End();
+    image_bitmap_ = gc::accounting::ContinuousSpaceBitmap::Create(
+        "image bitmap",
+        reinterpret_cast<uint8_t*>(image_begin_),
+        RoundUp(object_section_size_, kPageSize));
+    for (uint32_t offset : object_offsets_) {
+      DCHECK(IsAligned<kObjectAlignment>(image_begin_ + sizeof(ImageHeader) + offset));
+      image_bitmap_.Set(
+          reinterpret_cast<mirror::Object*>(image_begin_ + sizeof(ImageHeader) + offset));
+    }
+    const size_t bitmap_bytes = image_bitmap_.Size();
+    auto* bitmap_section = &sections_[ImageHeader::kSectionImageBitmap];
+    *bitmap_section = ImageSection(RoundUp(sections_end, kPageSize),
+                                   RoundUp(bitmap_bytes, kPageSize));
+
+    // Compute boot image checksum and boot image components, to be stored in
+    // the header.
+    gc::Heap* const heap = Runtime::Current()->GetHeap();
+    uint32_t boot_image_components = 0u;
+    uint32_t boot_image_checksums = 0u;
+    const std::vector<gc::space::ImageSpace*>& image_spaces = heap->GetBootImageSpaces();
+    for (size_t i = 0u, size = image_spaces.size(); i != size; ) {
+      const ImageHeader& header = image_spaces[i]->GetImageHeader();
+      boot_image_components += header.GetComponentCount();
+      boot_image_checksums ^= header.GetImageChecksum();
+      DCHECK_LE(header.GetImageSpaceCount(), size - i);
+      i += header.GetImageSpaceCount();
+    }
+
+    header_ = ImageHeader(
+        /* image_reservation_size= */ RoundUp(sections_end, kPageSize),
+        /* component_count= */ 1,
+        image_begin_,
+        sections_end,
+        sections_.data(),
+        /* image_roots= */ image_begin_ + sizeof(ImageHeader),
+        /* oat_checksum= */ 0,
+        /* oat_file_begin= */ 0,
+        /* oat_data_begin= */ 0,
+        /* oat_data_end= */ 0,
+        /* oat_file_end= */ 0,
+        heap->GetBootImagesStartAddress(),
+        heap->GetBootImagesSize(),
+        boot_image_components,
+        boot_image_checksums,
+        static_cast<uint32_t>(kRuntimePointerSize));
+
+    // Data size includes everything except the bitmap and the header.
+    header_.data_size_ = sections_end - sizeof(ImageHeader);
+
+    // Write image methods - needs to happen after creation of the header.
+    WriteImageMethods();
+
+    return true;
+  }
+
+  void FillData(std::vector<uint8_t>& data) {
+    // Note we don't put the header, we only have it reserved in `data` as
+    // Image::WriteData expects the object section to contain the image header.
+    auto compute_dest = [&](const ImageSection& section) {
+      return data.data() + section.Offset();
+    };
+
+    auto objects_section = header_.GetImageSection(ImageHeader::kSectionObjects);
+    memcpy(compute_dest(objects_section) + sizeof(ImageHeader), objects_.data(), objects_.size());
+
+    auto fields_section = header_.GetImageSection(ImageHeader::kSectionArtFields);
+    memcpy(compute_dest(fields_section), art_fields_.data(), fields_section.Size());
+
+    auto methods_section = header_.GetImageSection(ImageHeader::kSectionArtMethods);
+    memcpy(compute_dest(methods_section), art_methods_.data(), methods_section.Size());
+
+    auto im_tables_section = header_.GetImageSection(ImageHeader::kSectionImTables);
+    memcpy(compute_dest(im_tables_section), im_tables_.data(), im_tables_section.Size());
+
+    auto intern_section = header_.GetImageSection(ImageHeader::kSectionInternedStrings);
+    intern_table_.WriteToMemory(compute_dest(intern_section));
+
+    auto class_table_section = header_.GetImageSection(ImageHeader::kSectionClassTable);
+    class_table_.WriteToMemory(compute_dest(class_table_section));
+
+    auto string_offsets_section =
+        header_.GetImageSection(ImageHeader::kSectionStringReferenceOffsets);
+    memcpy(compute_dest(string_offsets_section),
+           string_reference_offsets_.data(),
+           string_offsets_section.Size());
+
+    auto dex_cache_section = header_.GetImageSection(ImageHeader::kSectionDexCacheArrays);
+    memcpy(compute_dest(dex_cache_section), dex_cache_arrays_.data(), dex_cache_section.Size());
+
+    auto metadata_section = header_.GetImageSection(ImageHeader::kSectionMetadata);
+    memcpy(compute_dest(metadata_section), metadata_.data(), metadata_section.Size());
+
+    DCHECK_EQ(metadata_section.Offset() + metadata_section.Size(), data.size());
+  }
+
+
+  ImageHeader* GetHeader() {
+    return &header_;
+  }
+
+  const gc::accounting::ContinuousSpaceBitmap& GetImageBitmap() const {
+    return image_bitmap_;
+  }
+
+  const std::string& GetDexLocation() const {
+    return dex_location_;
+  }
+
+ private:
+  bool IsInBootImage(const void* obj) const {
+    return reinterpret_cast<uintptr_t>(obj) - boot_image_begin_ < boot_image_size_;
+  }
+
+  // Returns the image contents for `cls`. If `cls` is in the boot image, the
+  // method just returns it.
+  mirror::Class* GetClassContent(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (cls == nullptr || IsInBootImage(cls.Ptr())) {
+      return cls.Ptr();
+    }
+    const dex::ClassDef* class_def = cls->GetClassDef();
+    DCHECK(class_def != nullptr) << cls->PrettyClass();
+    auto it = classes_.find(class_def);
+    DCHECK(it != classes_.end()) << cls->PrettyClass();
+    mirror::Class* result = reinterpret_cast<mirror::Class*>(objects_.data() + it->second);
+    DCHECK(result->GetClass()->IsClass());
+    return result;
+  }
+
+  // Returns a pointer that can be stored in `objects_`:
+  // - The pointer itself for boot image objects,
+  // - The offset in the image for all other objects.
+  template <typename T> T* GetOrComputeImageAddress(ObjPtr<T> object)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (object == nullptr || IsInBootImage(object.Ptr())) {
+      DCHECK(object == nullptr || Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(object));
+      return object.Ptr();
+    }
+
+    if (object->IsClassLoader()) {
+      // DexCache and Class point to class loaders. For runtime-generated app
+      // images, we don't encode the class loader. It will be set when the
+      // runtime is loading the image.
+      return nullptr;
+    }
+
+    if (object->GetClass() == GetClassRoot<mirror::ClassExt>()) {
+      // No need to encode `ClassExt`. If needed, it will be reconstructed at
+      // runtime.
+      return nullptr;
+    }
+
+    uint32_t offset = 0u;
+    if (object->IsClass()) {
+      offset = CopyClass(object->AsClass());
+    } else if (object->IsDexCache()) {
+      offset = CopyDexCache(object->AsDexCache());
+    } else {
+      offset = CopyObject(object);
+    }
+    return reinterpret_cast<T*>(image_begin_ + sizeof(ImageHeader) + offset);
+  }
+
+  void CreateImageSections() {
+    sections_[ImageHeader::kSectionObjects] = ImageSection(0u, object_section_size_);
+    sections_[ImageHeader::kSectionArtFields] =
+        ImageSection(sections_[ImageHeader::kSectionObjects].End(), art_fields_.size());
+
+    // Round up to the alignment for ArtMethod.
+    static_assert(IsAligned<sizeof(void*)>(ArtMethod::Size(kRuntimePointerSize)));
+    size_t cur_pos = RoundUp(sections_[ImageHeader::kSectionArtFields].End(), sizeof(void*));
+    sections_[ImageHeader::kSectionArtMethods] = ImageSection(cur_pos, art_methods_.size());
+
+    // Round up to the alignment for ImTables.
+    cur_pos = RoundUp(sections_[ImageHeader::kSectionArtMethods].End(), sizeof(void*));
+    sections_[ImageHeader::kSectionImTables] = ImageSection(cur_pos, im_tables_.size());
+
+    // Round up to the alignment for conflict tables.
+    cur_pos = RoundUp(sections_[ImageHeader::kSectionImTables].End(), sizeof(void*));
+    sections_[ImageHeader::kSectionIMTConflictTables] = ImageSection(cur_pos, 0u);
+
+    sections_[ImageHeader::kSectionRuntimeMethods] =
+        ImageSection(sections_[ImageHeader::kSectionIMTConflictTables].End(), 0u);
+
+    // Round up to the alignment the string table expects. See HashSet::WriteToMemory.
+    cur_pos = RoundUp(sections_[ImageHeader::kSectionRuntimeMethods].End(), sizeof(uint64_t));
+
+    size_t intern_table_bytes = intern_table_.WriteToMemory(nullptr);
+    sections_[ImageHeader::kSectionInternedStrings] = ImageSection(cur_pos, intern_table_bytes);
+
+    // Obtain the new position and round it up to the appropriate alignment.
+    cur_pos = RoundUp(sections_[ImageHeader::kSectionInternedStrings].End(), sizeof(uint64_t));
+
+    size_t class_table_bytes = class_table_.WriteToMemory(nullptr);
+    sections_[ImageHeader::kSectionClassTable] = ImageSection(cur_pos, class_table_bytes);
+
+    // Round up to the alignment of the offsets we are going to store.
+    cur_pos = RoundUp(sections_[ImageHeader::kSectionClassTable].End(), sizeof(uint32_t));
+    sections_[ImageHeader::kSectionStringReferenceOffsets] = ImageSection(
+        cur_pos, string_reference_offsets_.size() * sizeof(string_reference_offsets_[0]));
+
+    // Round up to the alignment dex caches arrays expects.
+    cur_pos =
+        RoundUp(sections_[ImageHeader::kSectionStringReferenceOffsets].End(), sizeof(void*));
+    sections_[ImageHeader::kSectionDexCacheArrays] =
+        ImageSection(cur_pos, dex_cache_arrays_.size());
+
+    // Round up to the alignment expected for the metadata, which holds dex
+    // cache arrays.
+    cur_pos = RoundUp(sections_[ImageHeader::kSectionDexCacheArrays].End(), sizeof(void*));
+    sections_[ImageHeader::kSectionMetadata] = ImageSection(cur_pos, metadata_.size());
+  }
+
+  // Returns the copied mirror Object if in the image, or the object directly if
+  // in the boot image. For the copy, this is really its content, it should not
+  // be returned as an `ObjPtr` (as it's not a GC object), nor stored anywhere.
+  template<typename T> T* FromImageOffsetToRuntimeContent(uint32_t offset) {
+    if (offset == 0u || IsInBootImage(reinterpret_cast<const void*>(offset))) {
+      return reinterpret_cast<T*>(offset);
+    }
+    uint32_t vector_data_offset = FromImageOffsetToVectorOffset(offset);
+    return reinterpret_cast<T*>(objects_.data() + vector_data_offset);
+  }
+
+  uint32_t FromImageOffsetToVectorOffset(uint32_t offset) const {
+    DCHECK(!IsInBootImage(reinterpret_cast<const void*>(offset)));
+    return offset - sizeof(ImageHeader) - image_begin_;
+  }
+
+  class InternStringHash {
+   public:
+    explicit InternStringHash(RuntimeImageHelper* helper) : helper_(helper) {}
+
+    // NO_THREAD_SAFETY_ANALYSIS as these helpers get passed to `HashSet`.
+    size_t operator()(mirror::String* str) const NO_THREAD_SAFETY_ANALYSIS {
+      int32_t hash = str->GetStoredHashCode();
+      DCHECK_EQ(hash, str->ComputeHashCode());
+      // An additional cast to prevent undesired sign extension.
+      return static_cast<uint32_t>(hash);
+    }
+
+    size_t operator()(uint32_t entry) const NO_THREAD_SAFETY_ANALYSIS {
+      return (*this)(helper_->FromImageOffsetToRuntimeContent<mirror::String>(entry));
+    }
+
+   private:
+    RuntimeImageHelper* helper_;
+  };
+
+  class InternStringEquals {
+   public:
+    explicit InternStringEquals(RuntimeImageHelper* helper) : helper_(helper) {}
+
+    // NO_THREAD_SAFETY_ANALYSIS as these helpers get passed to `HashSet`.
+    bool operator()(uint32_t entry, mirror::String* other) const NO_THREAD_SAFETY_ANALYSIS {
+      if (kIsDebugBuild) {
+        Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+      }
+      return other->Equals(helper_->FromImageOffsetToRuntimeContent<mirror::String>(entry));
+    }
+
+    bool operator()(uint32_t entry, uint32_t other) const NO_THREAD_SAFETY_ANALYSIS {
+      return (*this)(entry, helper_->FromImageOffsetToRuntimeContent<mirror::String>(other));
+    }
+
+   private:
+    RuntimeImageHelper* helper_;
+  };
+
+  using InternTableSet =
+        HashSet<uint32_t, DefaultEmptyFn<uint32_t>, InternStringHash, InternStringEquals>;
+
+  class ClassDescriptorHash {
+   public:
+    explicit ClassDescriptorHash(RuntimeImageHelper* helper) : helper_(helper) {}
+
+    uint32_t operator()(const ClassTable::TableSlot& slot) const NO_THREAD_SAFETY_ANALYSIS {
+      uint32_t ptr = slot.NonHashData();
+      if (helper_->IsInBootImage(reinterpret_cast32<const void*>(ptr))) {
+        return reinterpret_cast32<mirror::Class*>(ptr)->DescriptorHash();
+      }
+      return helper_->class_hashes_.Get(helper_->FromImageOffsetToVectorOffset(ptr));
+    }
+
+   private:
+    RuntimeImageHelper* helper_;
+  };
+
+  class ClassDescriptorEquals {
+   public:
+    ClassDescriptorEquals() {}
+
+    bool operator()(const ClassTable::TableSlot& a, const ClassTable::TableSlot& b)
+        const NO_THREAD_SAFETY_ANALYSIS {
+      // No need to fetch the descriptor: we know the classes we are inserting
+      // in the ClassTable are unique.
+      return a.Data() == b.Data();
+    }
+  };
+
+  using ClassTableSet = HashSet<ClassTable::TableSlot,
+                                ClassTable::TableSlotEmptyFn,
+                                ClassDescriptorHash,
+                                ClassDescriptorEquals>;
+
+  // Helper class to collect classes that we will generate in the image.
+  class ClassTableVisitor {
+   public:
+    ClassTableVisitor(Handle<mirror::ClassLoader> loader, VariableSizedHandleScope& handles)
+        : loader_(loader), handles_(handles) {}
+
+    bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+      // Record app classes and boot classpath classes: app classes will be
+      // generated in the image and put in the class table, boot classpath
+      // classes will be put in the class table.
+      ObjPtr<mirror::ClassLoader> class_loader = klass->GetClassLoader();
+      if (class_loader == loader_.Get() || class_loader == nullptr) {
+        handles_.NewHandle(klass);
+      }
+      return true;
+    }
+
+   private:
+    Handle<mirror::ClassLoader> loader_;
+    VariableSizedHandleScope& handles_;
+  };
+
+  // Helper class visitor to filter out classes we cannot emit.
+  class PruneVisitor {
+   public:
+    PruneVisitor(Thread* self,
+                 RuntimeImageHelper* helper,
+                 const ArenaSet<const DexFile*>& dex_files,
+                 ArenaVector<Handle<mirror::Class>>& classes,
+                 ArenaAllocator& allocator)
+        : self_(self),
+          helper_(helper),
+          dex_files_(dex_files),
+          visited_(allocator.Adapter()),
+          classes_to_write_(classes) {}
+
+    bool CanEmitHelper(Handle<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
+      // If the class comes from a dex file which is not part of the primary
+      // APK, don't encode it.
+      if (!ContainsElement(dex_files_, &cls->GetDexFile())) {
+        return false;
+      }
+
+      // Ensure pointers to classes in `cls` can also be emitted.
+      StackHandleScope<1> hs(self_);
+      MutableHandle<mirror::Class> other_class = hs.NewHandle(cls->GetSuperClass());
+      if (!CanEmit(other_class)) {
+        return false;
+      }
+
+      other_class.Assign(cls->GetComponentType());
+      if (!CanEmit(other_class)) {
+        return false;
+      }
+
+      for (size_t i = 0, num_interfaces = cls->NumDirectInterfaces(); i < num_interfaces; ++i) {
+        other_class.Assign(cls->GetDirectInterface(i));
+        if (!CanEmit(other_class)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    bool CanEmit(Handle<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
+      if (cls == nullptr) {
+        return true;
+      }
+      // Only emit classes that are resolved and not erroneous.
+      if (!cls->IsResolved() || cls->IsErroneous()) {
+        return false;
+      }
+
+      // Proxy classes are generated at runtime, so don't emit them.
+      if (cls->IsProxyClass()) {
+        return false;
+      }
+
+      // Classes in the boot image can be trivially encoded directly.
+      if (helper_->IsInBootImage(cls.Get())) {
+        return true;
+      }
+
+      if (cls->IsBootStrapClassLoaded()) {
+        // We cannot encode classes that are part of the boot classpath.
+        return false;
+      }
+
+      DCHECK(!cls->IsPrimitive());
+
+      if (cls->IsArrayClass()) {
+        if (cls->IsBootStrapClassLoaded()) {
+          // For boot classpath arrays, we can only emit them if they are
+          // in the boot image already.
+          return helper_->IsInBootImage(cls.Get());
+        }
+        ObjPtr<mirror::Class> temp = cls.Get();
+        while ((temp = temp->GetComponentType())->IsArrayClass()) {}
+        StackHandleScope<1> hs(self_);
+        Handle<mirror::Class> other_class = hs.NewHandle(temp);
+        return CanEmit(other_class);
+      }
+      const dex::ClassDef* class_def = cls->GetClassDef();
+      DCHECK_NE(class_def, nullptr);
+      auto existing = visited_.find(class_def);
+      if (existing != visited_.end()) {
+        // Already processed;
+        return existing->second == VisitState::kCanEmit;
+      }
+
+      visited_.Put(class_def, VisitState::kVisiting);
+      if (CanEmitHelper(cls)) {
+        visited_.Overwrite(class_def, VisitState::kCanEmit);
+        return true;
+      } else {
+        visited_.Overwrite(class_def, VisitState::kCannotEmit);
+        return false;
+      }
+    }
+
+    void Visit(Handle<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+      MutableHandle<mirror::Class> cls(obj.GetReference());
+      if (CanEmit(cls)) {
+        if (cls->IsBootStrapClassLoaded()) {
+          DCHECK(helper_->IsInBootImage(cls.Get()));
+          // Insert the bootclasspath class in the class table.
+          uint32_t hash = cls->DescriptorHash();
+          helper_->class_table_.InsertWithHash(ClassTable::TableSlot(cls.Get(), hash), hash);
+        } else {
+          classes_to_write_.push_back(cls);
+        }
+      }
+    }
+
+   private:
+    enum class VisitState {
+      kVisiting,
+      kCanEmit,
+      kCannotEmit,
+    };
+
+    Thread* const self_;
+    RuntimeImageHelper* const helper_;
+    const ArenaSet<const DexFile*>& dex_files_;
+    ArenaSafeMap<const dex::ClassDef*, VisitState> visited_;
+    ArenaVector<Handle<mirror::Class>>& classes_to_write_;
+  };
+
+  void EmitClasses(Thread* self, Handle<mirror::ObjectArray<mirror::Object>> dex_cache_array)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    ScopedTrace trace("Emit strings and classes");
+    ArenaSet<const DexFile*> dex_files(allocator_.Adapter());
+    for (int32_t i = 0; i < dex_cache_array->GetLength(); ++i) {
+      dex_files.insert(dex_cache_array->Get(i)->AsDexCache()->GetDexFile());
+    }
+
+    StackHandleScope<1> hs(self);
+    Handle<mirror::ClassLoader> loader = hs.NewHandle(
+        dex_cache_array->Get(0)->AsDexCache()->GetClassLoader());
+    ClassTable* const class_table = loader->GetClassTable();
+    if (class_table == nullptr) {
+      return;
+    }
+
+    VariableSizedHandleScope handles(self);
+    {
+      ClassTableVisitor class_table_visitor(loader, handles);
+      class_table->Visit(class_table_visitor);
+    }
+
+    ArenaVector<Handle<mirror::Class>> classes_to_write(allocator_.Adapter());
+    classes_to_write.reserve(class_table->Size());
+    {
+      PruneVisitor prune_visitor(self, this, dex_files, classes_to_write, allocator_);
+      handles.VisitHandles(prune_visitor);
+    }
+
+    for (Handle<mirror::Class> cls : classes_to_write) {
+      ScopedAssertNoThreadSuspension sants("Writing class");
+      CopyClass(cls.Get());
+    }
+
+    // Relocate the type array entries. We do this now before creating image
+    // sections because we may add new boot image classes into our
+    // `class_table`_.
+    for (auto entry : dex_caches_) {
+      const DexFile& dex_file = *entry.first;
+      mirror::DexCache* cache = reinterpret_cast<mirror::DexCache*>(&objects_[entry.second]);
+      mirror::GcRootArray<mirror::Class>* old_types_array = cache->GetResolvedTypesArray();
+      if (HasNativeRelocation(old_types_array)) {
+        auto reloc_it = native_relocations_.find(old_types_array);
+        DCHECK(reloc_it != native_relocations_.end());
+        ArenaVector<uint8_t>& data =
+            (reloc_it->second.first == NativeRelocationKind::kFullNativeDexCacheArray)
+                ? dex_cache_arrays_ : metadata_;
+        mirror::GcRootArray<mirror::Class>* content_array =
+            reinterpret_cast<mirror::GcRootArray<mirror::Class>*>(
+                data.data() + reloc_it->second.second);
+        for (uint32_t i = 0; i < dex_file.NumTypeIds(); ++i) {
+          ObjPtr<mirror::Class> cls = old_types_array->Get(i);
+          if (cls == nullptr) {
+            content_array->Set(i, nullptr);
+          } else if (IsInBootImage(cls.Ptr())) {
+            if (!cls->IsPrimitive()) {
+              // The dex cache is concurrently updated by the app. If the class
+              // collection logic in `PruneVisitor` did not see this class, insert it now.
+              // Note that application class tables do not contain primitive
+              // classes.
+              uint32_t hash = cls->DescriptorHash();
+              class_table_.InsertWithHash(ClassTable::TableSlot(cls.Ptr(), hash), hash);
+            }
+            content_array->Set(i, cls.Ptr());
+          } else if (cls->IsArrayClass()) {
+            std::string class_name;
+            cls->GetDescriptor(&class_name);
+            auto class_it = array_classes_.find(class_name);
+            if (class_it == array_classes_.end()) {
+              content_array->Set(i, nullptr);
+            } else {
+              mirror::Class* ptr = reinterpret_cast<mirror::Class*>(
+                  image_begin_ + sizeof(ImageHeader) + class_it->second);
+              content_array->Set(i, ptr);
+            }
+          } else {
+            DCHECK(!cls->IsPrimitive());
+            DCHECK(!cls->IsProxyClass());
+            const dex::ClassDef* class_def = cls->GetClassDef();
+            DCHECK_NE(class_def, nullptr);
+            auto class_it = classes_.find(class_def);
+            if (class_it == classes_.end()) {
+              content_array->Set(i, nullptr);
+            } else {
+              mirror::Class* ptr = reinterpret_cast<mirror::Class*>(
+                  image_begin_ + sizeof(ImageHeader) + class_it->second);
+              content_array->Set(i, ptr);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // Helper visitor returning the location of a native pointer in the image.
+  class NativePointerVisitor {
+   public:
+    explicit NativePointerVisitor(RuntimeImageHelper* helper) : helper_(helper) {}
+
+    template <typename T>
+    T* operator()(T* ptr, void** dest_addr ATTRIBUTE_UNUSED) const {
+      return helper_->NativeLocationInImage(ptr, /* must_have_relocation= */ true);
+    }
+
+    template <typename T> T* operator()(T* ptr, bool must_have_relocation = true) const {
+      return helper_->NativeLocationInImage(ptr, must_have_relocation);
+    }
+
+   private:
+    RuntimeImageHelper* helper_;
+  };
+
+  template <typename T> T* NativeLocationInImage(T* ptr, bool must_have_relocation) const {
+    if (ptr == nullptr || IsInBootImage(ptr)) {
+      return ptr;
+    }
+
+    auto it = native_relocations_.find(ptr);
+    if (it == native_relocations_.end()) {
+      DCHECK(!must_have_relocation);
+      return nullptr;
+    }
+    switch (it->second.first) {
+      case NativeRelocationKind::kArtMethod:
+      case NativeRelocationKind::kArtMethodArray: {
+        uint32_t offset = sections_[ImageHeader::kSectionArtMethods].Offset();
+        return reinterpret_cast<T*>(image_begin_ + offset + it->second.second);
+      }
+      case NativeRelocationKind::kArtFieldArray: {
+        uint32_t offset = sections_[ImageHeader::kSectionArtFields].Offset();
+        return reinterpret_cast<T*>(image_begin_ + offset + it->second.second);
+      }
+      case NativeRelocationKind::kImTable: {
+        uint32_t offset = sections_[ImageHeader::kSectionImTables].Offset();
+        return reinterpret_cast<T*>(image_begin_ + offset + it->second.second);
+      }
+      case NativeRelocationKind::kStartupNativeDexCacheArray: {
+        uint32_t offset = sections_[ImageHeader::kSectionMetadata].Offset();
+        return reinterpret_cast<T*>(image_begin_ + offset + it->second.second);
+      }
+      case NativeRelocationKind::kFullNativeDexCacheArray: {
+        uint32_t offset = sections_[ImageHeader::kSectionDexCacheArrays].Offset();
+        return reinterpret_cast<T*>(image_begin_ + offset + it->second.second);
+      }
+    }
+  }
+
+  template <typename Visitor>
+  void RelocateMethodPointerArrays(mirror::Class* klass, const Visitor& visitor)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    // A bit of magic here: we cast contents from our buffer to mirror::Class,
+    // and do pointer comparison between 1) these classes, and 2) boot image objects.
+    // Both kinds do not move.
+
+    // See if we need to fixup the vtable field.
+    mirror::Class* super = FromImageOffsetToRuntimeContent<mirror::Class>(
+        reinterpret_cast32<uint32_t>(
+            klass->GetSuperClass<kVerifyNone, kWithoutReadBarrier>().Ptr()));
+    DCHECK(super != nullptr) << "j.l.Object should never be in an app runtime image";
+    mirror::PointerArray* vtable = FromImageOffsetToRuntimeContent<mirror::PointerArray>(
+        reinterpret_cast32<uint32_t>(klass->GetVTable<kVerifyNone, kWithoutReadBarrier>().Ptr()));
+    mirror::PointerArray* super_vtable = FromImageOffsetToRuntimeContent<mirror::PointerArray>(
+        reinterpret_cast32<uint32_t>(super->GetVTable<kVerifyNone, kWithoutReadBarrier>().Ptr()));
+    if (vtable != nullptr && vtable != super_vtable) {
+      DCHECK(!IsInBootImage(vtable));
+      vtable->Fixup(vtable, kRuntimePointerSize, visitor);
+    }
+
+    // See if we need to fixup entries in the IfTable.
+    mirror::IfTable* iftable = FromImageOffsetToRuntimeContent<mirror::IfTable>(
+        reinterpret_cast32<uint32_t>(
+            klass->GetIfTable<kVerifyNone, kWithoutReadBarrier>().Ptr()));
+    mirror::IfTable* super_iftable = FromImageOffsetToRuntimeContent<mirror::IfTable>(
+        reinterpret_cast32<uint32_t>(
+            super->GetIfTable<kVerifyNone, kWithoutReadBarrier>().Ptr()));
+    int32_t iftable_count = iftable->Count();
+    int32_t super_iftable_count = super_iftable->Count();
+    for (int32_t i = 0; i < iftable_count; ++i) {
+      mirror::PointerArray* methods = FromImageOffsetToRuntimeContent<mirror::PointerArray>(
+          reinterpret_cast32<uint32_t>(
+              iftable->GetMethodArrayOrNull<kVerifyNone, kWithoutReadBarrier>(i).Ptr()));
+      mirror::PointerArray* super_methods = (i < super_iftable_count)
+          ? FromImageOffsetToRuntimeContent<mirror::PointerArray>(
+                reinterpret_cast32<uint32_t>(
+                    super_iftable->GetMethodArrayOrNull<kVerifyNone, kWithoutReadBarrier>(i).Ptr()))
+          : nullptr;
+      if (methods != super_methods) {
+        DCHECK(!IsInBootImage(methods));
+        methods->Fixup(methods, kRuntimePointerSize, visitor);
+      }
+    }
+  }
+
+  template <typename Visitor, typename T>
+  void RelocateNativeDexCacheArray(mirror::NativeArray<T>* old_method_array,
+                                   uint32_t num_ids,
+                                   const Visitor& visitor)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (old_method_array == nullptr) {
+      return;
+    }
+
+    auto it = native_relocations_.find(old_method_array);
+    DCHECK(it != native_relocations_.end());
+    ArenaVector<uint8_t>& data =
+        (it->second.first == NativeRelocationKind::kFullNativeDexCacheArray)
+            ? dex_cache_arrays_ : metadata_;
+
+    mirror::NativeArray<T>* content_array =
+        reinterpret_cast<mirror::NativeArray<T>*>(data.data() + it->second.second);
+    for (uint32_t i = 0; i < num_ids; ++i) {
+      // We may not have relocations for some entries, in which case we'll
+      // just store null.
+      content_array->Set(i, visitor(content_array->Get(i), /* must_have_relocation= */ false));
+    }
+  }
+
+  template <typename Visitor>
+  void RelocateDexCacheArrays(mirror::DexCache* cache,
+                              const DexFile& dex_file,
+                              const Visitor& visitor)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    mirror::NativeArray<ArtMethod>* old_method_array = cache->GetResolvedMethodsArray();
+    cache->SetResolvedMethodsArray(visitor(old_method_array));
+    RelocateNativeDexCacheArray(old_method_array, dex_file.NumMethodIds(), visitor);
+
+    mirror::NativeArray<ArtField>* old_field_array = cache->GetResolvedFieldsArray();
+    cache->SetResolvedFieldsArray(visitor(old_field_array));
+    RelocateNativeDexCacheArray(old_field_array, dex_file.NumFieldIds(), visitor);
+
+    mirror::GcRootArray<mirror::String>* old_strings_array = cache->GetStringsArray();
+    cache->SetStringsArray(visitor(old_strings_array));
+
+    mirror::GcRootArray<mirror::Class>* old_types_array = cache->GetResolvedTypesArray();
+    cache->SetResolvedTypesArray(visitor(old_types_array));
+  }
+
+  void RelocateNativePointers() {
+    ScopedTrace relocate_native_pointers("Relocate native pointers");
+    ScopedObjectAccess soa(Thread::Current());
+    NativePointerVisitor visitor(this);
+    for (auto entry : classes_) {
+      mirror::Class* cls = reinterpret_cast<mirror::Class*>(&objects_[entry.second]);
+      cls->FixupNativePointers(cls, kRuntimePointerSize, visitor);
+      RelocateMethodPointerArrays(cls, visitor);
+    }
+    for (auto it : array_classes_) {
+      mirror::Class* cls = reinterpret_cast<mirror::Class*>(&objects_[it.second]);
+      cls->FixupNativePointers(cls, kRuntimePointerSize, visitor);
+      RelocateMethodPointerArrays(cls, visitor);
+    }
+    for (auto it : native_relocations_) {
+      if (it.second.first == NativeRelocationKind::kImTable) {
+        ImTable* im_table = reinterpret_cast<ImTable*>(im_tables_.data() + it.second.second);
+        RelocateImTable(im_table, visitor);
+      }
+    }
+    for (auto it : dex_caches_) {
+      mirror::DexCache* cache = reinterpret_cast<mirror::DexCache*>(&objects_[it.second]);
+      RelocateDexCacheArrays(cache, *it.first, visitor);
+    }
+  }
+
+  void RelocateImTable(ImTable* im_table, const NativePointerVisitor& visitor) {
+    for (size_t i = 0; i < ImTable::kSize; ++i) {
+      ArtMethod* method = im_table->Get(i, kRuntimePointerSize);
+      ArtMethod* new_method = nullptr;
+      if (method->IsRuntimeMethod() && !IsInBootImage(method)) {
+        // New IMT conflict method: just use the boot image version.
+        // TODO: Consider copying the new IMT conflict method.
+        new_method = Runtime::Current()->GetImtConflictMethod();
+        DCHECK(IsInBootImage(new_method));
+      } else {
+        new_method = visitor(method);
+      }
+      if (method != new_method) {
+        im_table->Set(i, new_method, kRuntimePointerSize);
+      }
+    }
+  }
+
+  void CopyFieldArrays(ObjPtr<mirror::Class> cls, uint32_t class_image_address)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    LengthPrefixedArray<ArtField>* fields[] = {
+        cls->GetSFieldsPtr(), cls->GetIFieldsPtr(),
+    };
+    for (LengthPrefixedArray<ArtField>* cur_fields : fields) {
+      if (cur_fields != nullptr) {
+        // Copy the array.
+        size_t number_of_fields = cur_fields->size();
+        size_t size = LengthPrefixedArray<ArtField>::ComputeSize(number_of_fields);
+        size_t offset = art_fields_.size();
+        art_fields_.resize(offset + size);
+        auto* dest_array =
+            reinterpret_cast<LengthPrefixedArray<ArtField>*>(art_fields_.data() + offset);
+        memcpy(dest_array, cur_fields, size);
+        native_relocations_.Put(cur_fields,
+                                std::make_pair(NativeRelocationKind::kArtFieldArray, offset));
+
+        // Update the class pointer of individual fields.
+        for (size_t i = 0; i != number_of_fields; ++i) {
+          dest_array->At(i).GetDeclaringClassAddressWithoutBarrier()->Assign(
+              reinterpret_cast<mirror::Class*>(class_image_address));
+        }
+      }
+    }
+  }
+
+  void CopyMethodArrays(ObjPtr<mirror::Class> cls,
+                        uint32_t class_image_address,
+                        bool is_class_initialized)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    size_t number_of_methods = cls->NumMethods();
+    if (number_of_methods == 0) {
+      return;
+    }
+
+    size_t size = LengthPrefixedArray<ArtMethod>::ComputeSize(number_of_methods);
+    size_t offset = art_methods_.size();
+    art_methods_.resize(offset + size);
+    auto* dest_array =
+        reinterpret_cast<LengthPrefixedArray<ArtMethod>*>(art_methods_.data() + offset);
+    memcpy(dest_array, cls->GetMethodsPtr(), size);
+    native_relocations_.Put(cls->GetMethodsPtr(),
+                            std::make_pair(NativeRelocationKind::kArtMethodArray, offset));
+
+    for (size_t i = 0; i != number_of_methods; ++i) {
+      ArtMethod* method = &cls->GetMethodsPtr()->At(i);
+      ArtMethod* copy = &dest_array->At(i);
+
+      // Update the class pointer.
+      ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass();
+      if (declaring_class == cls) {
+        copy->GetDeclaringClassAddressWithoutBarrier()->Assign(
+            reinterpret_cast<mirror::Class*>(class_image_address));
+      } else {
+        DCHECK(method->IsCopied());
+        if (!IsInBootImage(declaring_class.Ptr())) {
+          DCHECK(classes_.find(declaring_class->GetClassDef()) != classes_.end());
+          copy->GetDeclaringClassAddressWithoutBarrier()->Assign(
+              reinterpret_cast<mirror::Class*>(
+                  image_begin_ +
+                  sizeof(ImageHeader) +
+                  classes_.Get(declaring_class->GetClassDef())));
+        }
+      }
+
+      // Record the native relocation of the method.
+      uintptr_t copy_offset =
+          reinterpret_cast<uintptr_t>(copy) - reinterpret_cast<uintptr_t>(art_methods_.data());
+      native_relocations_.Put(method,
+                              std::make_pair(NativeRelocationKind::kArtMethod, copy_offset));
+
+      // Ignore the single-implementation info for abstract method.
+      if (method->IsAbstract()) {
+        copy->SetHasSingleImplementation(false);
+        copy->SetSingleImplementation(nullptr, kRuntimePointerSize);
+      }
+
+      // Set the entrypoint and data pointer of the method.
+      StubType stub;
+      if (method->IsNative()) {
+        stub = StubType::kQuickGenericJNITrampoline;
+      } else if (!cls->IsVerified()) {
+        stub = StubType::kQuickToInterpreterBridge;
+      } else if (!is_class_initialized && method->NeedsClinitCheckBeforeCall()) {
+        stub = StubType::kQuickResolutionTrampoline;
+      } else if (interpreter::IsNterpSupported() && CanMethodUseNterp(method)) {
+        stub = StubType::kNterpTrampoline;
+      } else {
+        stub = StubType::kQuickToInterpreterBridge;
+      }
+      const std::vector<gc::space::ImageSpace*>& image_spaces =
+          Runtime::Current()->GetHeap()->GetBootImageSpaces();
+      DCHECK(!image_spaces.empty());
+      const OatFile* oat_file = image_spaces[0]->GetOatFile();
+      DCHECK(oat_file != nullptr);
+      const OatHeader& header = oat_file->GetOatHeader();
+      copy->SetEntryPointFromQuickCompiledCode(header.GetOatAddress(stub));
+
+      if (method->IsNative()) {
+        StubType stub_type = method->IsCriticalNative()
+            ? StubType::kJNIDlsymLookupCriticalTrampoline
+            : StubType::kJNIDlsymLookupTrampoline;
+        copy->SetEntryPointFromJni(header.GetOatAddress(stub_type));
+      } else if (method->IsInvokable()) {
+        DCHECK(method->HasCodeItem()) << method->PrettyMethod();
+        ptrdiff_t code_item_offset = reinterpret_cast<const uint8_t*>(method->GetCodeItem()) -
+                method->GetDexFile()->DataBegin();
+        copy->SetDataPtrSize(
+            reinterpret_cast<const void*>(code_item_offset), kRuntimePointerSize);
+      }
+    }
+  }
+
+  void CopyImTable(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
+    ImTable* table = cls->GetImt(kRuntimePointerSize);
+
+    // If the table is null or shared and/or already emitted, we can skip.
+    if (table == nullptr || IsInBootImage(table) || HasNativeRelocation(table)) {
+      return;
+    }
+    const size_t size = ImTable::SizeInBytes(kRuntimePointerSize);
+    size_t offset = im_tables_.size();
+    im_tables_.resize(offset + size);
+    uint8_t* dest = im_tables_.data() + offset;
+    memcpy(dest, table, size);
+    native_relocations_.Put(table, std::make_pair(NativeRelocationKind::kImTable, offset));
+  }
+
+  bool HasNativeRelocation(void* ptr) const {
+    return native_relocations_.find(ptr) != native_relocations_.end();
+  }
+
+
+  static void LoadClassesFromReferenceProfile(
+      Thread* self,
+      const dchecked_vector<Handle<mirror::DexCache>>& dex_caches)
+          REQUIRES_SHARED(Locks::mutator_lock_) {
+    AppInfo* app_info = Runtime::Current()->GetAppInfo();
+    std::string profile_file = app_info->GetPrimaryApkReferenceProfile();
+
+    if (profile_file.empty()) {
+      return;
+    }
+
+    // Lock the file, it could be concurrently updated by the system. Don't block
+    // as this is app startup sensitive.
+    std::string error;
+    ScopedFlock profile =
+        LockedFile::Open(profile_file.c_str(), O_RDONLY, /*block=*/false, &error);
+
+    if (profile == nullptr) {
+      LOG(DEBUG) << "Couldn't lock the profile file " << profile_file << ": " << error;
+      return;
+    }
+
+    ProfileCompilationInfo profile_info(/* for_boot_image= */ false);
+
+    if (!profile_info.Load(profile->Fd())) {
+      LOG(DEBUG) << "Could not load profile file";
+      return;
+    }
+
+    StackHandleScope<1> hs(self);
+    Handle<mirror::ClassLoader> class_loader =
+        hs.NewHandle<mirror::ClassLoader>(dex_caches[0]->GetClassLoader());
+    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+    ScopedTrace loading_classes("Loading classes from profile");
+    for (auto dex_cache : dex_caches) {
+      const DexFile* dex_file = dex_cache->GetDexFile();
+      const ArenaSet<dex::TypeIndex>* class_types = profile_info.GetClasses(*dex_file);
+      if (class_types == nullptr) {
+        // This means the profile file did not reference the dex file, which is the case
+        // if there's no classes and methods of that dex file in the profile.
+        continue;
+      }
+
+      for (dex::TypeIndex idx : *class_types) {
+        // The index is greater or equal to NumTypeIds if the type is an extra
+        // descriptor, not referenced by the dex file.
+        if (idx.index_ < dex_file->NumTypeIds()) {
+          ObjPtr<mirror::Class> klass = class_linker->ResolveType(idx, dex_cache, class_loader);
+          if (klass == nullptr) {
+            self->ClearException();
+            LOG(DEBUG) << "Failed to preload " << dex_file->PrettyType(idx);
+            continue;
+          }
+        }
+      }
+    }
+  }
+
+  bool WriteObjects(std::string* error_msg) {
+    ScopedTrace write_objects("Writing objects");
+    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+    ScopedObjectAccess soa(Thread::Current());
+    VariableSizedHandleScope handles(soa.Self());
+
+    Handle<mirror::Class> object_array_class = handles.NewHandle(
+        GetClassRoot<mirror::ObjectArray<mirror::Object>>(class_linker));
+
+    Handle<mirror::ObjectArray<mirror::Object>> image_roots = handles.NewHandle(
+        mirror::ObjectArray<mirror::Object>::Alloc(
+            soa.Self(), object_array_class.Get(), ImageHeader::kImageRootsMax));
+
+    if (image_roots == nullptr) {
+      DCHECK(soa.Self()->IsExceptionPending());
+      soa.Self()->ClearException();
+      *error_msg = "Out of memory when trying to generate a runtime app image";
+      return false;
+    }
+
+    // Find the dex files that will be used for generating the app image.
+    dchecked_vector<Handle<mirror::DexCache>> dex_caches;
+    FindDexCaches(soa.Self(), dex_caches, handles);
+
+    if (dex_caches.size() == 0) {
+      *error_msg = "Did not find dex caches to generate an app image";
+      return false;
+    }
+    const OatDexFile* oat_dex_file = dex_caches[0]->GetDexFile()->GetOatDexFile();
+    VdexFile* vdex_file = oat_dex_file->GetOatFile()->GetVdexFile();
+    // The first entry in `dex_caches` contains the location of the primary APK.
+    dex_location_ = oat_dex_file->GetDexFileLocation();
+
+    size_t number_of_dex_files = vdex_file->GetNumberOfDexFiles();
+    if (number_of_dex_files != dex_caches.size()) {
+      // This means some dex files haven't been executed. For simplicity, just
+      // register them and recollect dex caches.
+      Handle<mirror::ClassLoader> loader = handles.NewHandle(dex_caches[0]->GetClassLoader());
+      VisitClassLoaderDexFiles(soa.Self(), loader, [&](const art::DexFile* dex_file)
+          REQUIRES_SHARED(Locks::mutator_lock_) {
+        class_linker->RegisterDexFile(*dex_file, dex_caches[0]->GetClassLoader());
+        return true;  // Continue with other dex files.
+      });
+      dex_caches.clear();
+      FindDexCaches(soa.Self(), dex_caches, handles);
+      if (number_of_dex_files != dex_caches.size()) {
+        *error_msg = "Number of dex caches does not match number of dex files in the primary APK";
+        return false;
+      }
+    }
+
+    // If classes referenced in the reference profile are not loaded, preload
+    // them. This makes sure we generate a good runtime app image, even if this
+    // current app run did not load all startup classes.
+    LoadClassesFromReferenceProfile(soa.Self(), dex_caches);
+
+    // We store the checksums of the dex files used at runtime. These can be
+    // different compared to the vdex checksums due to compact dex.
+    std::vector<uint32_t> checksums(number_of_dex_files);
+    uint32_t checksum_index = 0;
+    for (const OatDexFile* current_oat_dex_file : oat_dex_file->GetOatFile()->GetOatDexFiles()) {
+      const DexFile::Header* header =
+          reinterpret_cast<const DexFile::Header*>(current_oat_dex_file->GetDexFilePointer());
+      checksums[checksum_index++] = header->checksum_;
+    }
+    DCHECK_EQ(checksum_index, number_of_dex_files);
+
+    // Create the fake OatHeader to store the dependencies of the image.
+    SafeMap<std::string, std::string> key_value_store;
+    Runtime* runtime = Runtime::Current();
+    key_value_store.Put(OatHeader::kApexVersionsKey, runtime->GetApexVersions());
+    key_value_store.Put(OatHeader::kBootClassPathKey,
+                        android::base::Join(runtime->GetBootClassPathLocations(), ':'));
+    key_value_store.Put(OatHeader::kBootClassPathChecksumsKey,
+                        runtime->GetBootClassPathChecksums());
+    key_value_store.Put(OatHeader::kClassPathKey,
+                        oat_dex_file->GetOatFile()->GetClassLoaderContext());
+    key_value_store.Put(OatHeader::kConcurrentCopying,
+                        gUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);
+
+    std::unique_ptr<const InstructionSetFeatures> isa_features =
+        InstructionSetFeatures::FromCppDefines();
+    std::unique_ptr<OatHeader> oat_header(
+        OatHeader::Create(kRuntimeISA,
+                          isa_features.get(),
+                          number_of_dex_files,
+                          &key_value_store));
+
+    // Create the byte array containing the oat header and dex checksums.
+    uint32_t checksums_size = checksums.size() * sizeof(uint32_t);
+    Handle<mirror::ByteArray> header_data = handles.NewHandle(
+        mirror::ByteArray::Alloc(soa.Self(), oat_header->GetHeaderSize() + checksums_size));
+
+    if (header_data == nullptr) {
+      DCHECK(soa.Self()->IsExceptionPending());
+      soa.Self()->ClearException();
+      *error_msg = "Out of memory when trying to generate a runtime app image";
+      return false;
+    }
+
+    memcpy(header_data->GetData(), oat_header.get(), oat_header->GetHeaderSize());
+    memcpy(header_data->GetData() + oat_header->GetHeaderSize(), checksums.data(), checksums_size);
+
+    // Create and populate the dex caches aray.
+    Handle<mirror::ObjectArray<mirror::Object>> dex_cache_array = handles.NewHandle(
+        mirror::ObjectArray<mirror::Object>::Alloc(
+            soa.Self(), object_array_class.Get(), dex_caches.size()));
+
+    if (dex_cache_array == nullptr) {
+      DCHECK(soa.Self()->IsExceptionPending());
+      soa.Self()->ClearException();
+      *error_msg = "Out of memory when trying to generate a runtime app image";
+      return false;
+    }
+
+    for (uint32_t i = 0; i < dex_caches.size(); ++i) {
+      dex_cache_array->Set(i, dex_caches[i].Get());
+    }
+
+    image_roots->Set(ImageHeader::kDexCaches, dex_cache_array.Get());
+    image_roots->Set(ImageHeader::kClassRoots, class_linker->GetClassRoots());
+    image_roots->Set(ImageHeader::kAppImageOatHeader, header_data.Get());
+
+    {
+      // Now that we have created all objects needed for the `image_roots`, copy
+      // it into the buffer. Note that this will recursively copy all objects
+      // contained in `image_roots`. That's acceptable as we don't have cycles,
+      // nor a deep graph.
+      ScopedAssertNoThreadSuspension sants("Writing runtime app image");
+      CopyObject(image_roots.Get());
+    }
+
+    // Emit classes defined in the app class loader (which will also indirectly
+    // emit dex caches and their arrays).
+    EmitClasses(soa.Self(), dex_cache_array);
+
+    return true;
+  }
+
+  class FixupVisitor {
+   public:
+    FixupVisitor(RuntimeImageHelper* image, size_t copy_offset)
+        : image_(image), copy_offset_(copy_offset) {}
+
+    // We do not visit native roots. These are handled with other logic.
+    void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED)
+        const {
+      LOG(FATAL) << "UNREACHABLE";
+    }
+    void VisitRoot(mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) const {
+      LOG(FATAL) << "UNREACHABLE";
+    }
+
+    void operator()(ObjPtr<mirror::Object> obj,
+                    MemberOffset offset,
+                    bool is_static) const
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      // We don't copy static fields, they are being handled when we try to
+      // initialize the class.
+      ObjPtr<mirror::Object> ref =
+          is_static ? nullptr : obj->GetFieldObject<mirror::Object>(offset);
+      mirror::Object* address = image_->GetOrComputeImageAddress(ref);
+      mirror::Object* copy =
+          reinterpret_cast<mirror::Object*>(image_->objects_.data() + copy_offset_);
+      copy->GetFieldObjectReferenceAddr<kVerifyNone>(offset)->Assign(address);
+    }
+
+    // java.lang.ref.Reference visitor.
+    void operator()(ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED,
+                    ObjPtr<mirror::Reference> ref) const
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      operator()(ref, mirror::Reference::ReferentOffset(), /* is_static */ false);
+    }
+
+   private:
+    RuntimeImageHelper* image_;
+    size_t copy_offset_;
+  };
+
+  template <typename T>
+  void CopyNativeDexCacheArray(uint32_t num_entries,
+                               uint32_t max_entries,
+                               mirror::NativeArray<T>* array) {
+    if (array == nullptr) {
+      return;
+    }
+
+    bool only_startup = !mirror::DexCache::ShouldAllocateFullArray(num_entries, max_entries);
+    ArenaVector<uint8_t>& data = only_startup ? metadata_ : dex_cache_arrays_;
+    NativeRelocationKind relocation_kind = only_startup
+        ? NativeRelocationKind::kStartupNativeDexCacheArray
+        : NativeRelocationKind::kFullNativeDexCacheArray;
+
+    size_t size = num_entries * sizeof(void*);
+    // We need to reserve space to store `num_entries` because ImageSpace doesn't have
+    // access to the dex files when relocating dex caches.
+    size_t offset = RoundUp(data.size(), sizeof(void*)) + sizeof(uintptr_t);
+    data.resize(RoundUp(data.size(), sizeof(void*)) + sizeof(uintptr_t) + size);
+    reinterpret_cast<uintptr_t*>(data.data() + offset)[-1] = num_entries;
+
+    // Copy each entry individually. We cannot use memcpy, as the entries may be
+    // updated concurrently by other mutator threads.
+    mirror::NativeArray<T>* copy = reinterpret_cast<mirror::NativeArray<T>*>(data.data() + offset);
+    for (uint32_t i = 0; i < num_entries; ++i) {
+      copy->Set(i, array->Get(i));
+    }
+    native_relocations_.Put(array, std::make_pair(relocation_kind, offset));
+  }
+
+  template <typename T>
+  mirror::GcRootArray<T>* CreateGcRootDexCacheArray(uint32_t num_entries,
+                                                    uint32_t max_entries,
+                                                    mirror::GcRootArray<T>* array) {
+    if (array == nullptr) {
+      return nullptr;
+    }
+    bool only_startup = !mirror::DexCache::ShouldAllocateFullArray(num_entries, max_entries);
+    ArenaVector<uint8_t>& data = only_startup ? metadata_ : dex_cache_arrays_;
+    NativeRelocationKind relocation_kind = only_startup
+        ? NativeRelocationKind::kStartupNativeDexCacheArray
+        : NativeRelocationKind::kFullNativeDexCacheArray;
+    size_t size = num_entries * sizeof(GcRoot<T>);
+    // We need to reserve space to store `num_entries` because ImageSpace doesn't have
+    // access to the dex files when relocating dex caches.
+    static_assert(sizeof(GcRoot<T>) == sizeof(uint32_t));
+    size_t offset = data.size() + sizeof(uint32_t);
+    data.resize(data.size() + sizeof(uint32_t) + size);
+    reinterpret_cast<uint32_t*>(data.data() + offset)[-1] = num_entries;
+    native_relocations_.Put(array, std::make_pair(relocation_kind, offset));
+
+    return reinterpret_cast<mirror::GcRootArray<T>*>(data.data() + offset);
+  }
+  static bool EmitDexCacheArrays() {
+    // We need to treat dex cache arrays specially in an image for userfaultfd.
+    // Disable for now. See b/270936884.
+    return !gUseUserfaultfd;
+  }
+
+  uint32_t CopyDexCache(ObjPtr<mirror::DexCache> cache) REQUIRES_SHARED(Locks::mutator_lock_) {
+    auto it = dex_caches_.find(cache->GetDexFile());
+    if (it != dex_caches_.end()) {
+      return it->second;
+    }
+    uint32_t offset = CopyObject(cache);
+    dex_caches_.Put(cache->GetDexFile(), offset);
+    // For dex caches, clear pointers to data that will be set at runtime.
+    mirror::Object* copy = reinterpret_cast<mirror::Object*>(objects_.data() + offset);
+    reinterpret_cast<mirror::DexCache*>(copy)->ResetNativeArrays();
+    reinterpret_cast<mirror::DexCache*>(copy)->SetDexFile(nullptr);
+
+    if (!EmitDexCacheArrays()) {
+      return offset;
+    }
+
+    // Copy the ArtMethod array.
+    mirror::NativeArray<ArtMethod>* resolved_methods = cache->GetResolvedMethodsArray();
+    CopyNativeDexCacheArray(cache->GetDexFile()->NumMethodIds(),
+                            mirror::DexCache::kDexCacheMethodCacheSize,
+                            resolved_methods);
+    // Store the array pointer in the dex cache, which will be relocated at the end.
+    reinterpret_cast<mirror::DexCache*>(copy)->SetResolvedMethodsArray(resolved_methods);
+
+    // Copy the ArtField array.
+    mirror::NativeArray<ArtField>* resolved_fields = cache->GetResolvedFieldsArray();
+    CopyNativeDexCacheArray(cache->GetDexFile()->NumFieldIds(),
+                            mirror::DexCache::kDexCacheFieldCacheSize,
+                            resolved_fields);
+    // Store the array pointer in the dex cache, which will be relocated at the end.
+    reinterpret_cast<mirror::DexCache*>(copy)->SetResolvedFieldsArray(resolved_fields);
+
+    // Copy the type array.
+    mirror::GcRootArray<mirror::Class>* resolved_types = cache->GetResolvedTypesArray();
+    CreateGcRootDexCacheArray(cache->GetDexFile()->NumTypeIds(),
+                              mirror::DexCache::kDexCacheTypeCacheSize,
+                              resolved_types);
+    // Store the array pointer in the dex cache, which will be relocated at the end.
+    reinterpret_cast<mirror::DexCache*>(copy)->SetResolvedTypesArray(resolved_types);
+
+    // Copy the string array.
+    mirror::GcRootArray<mirror::String>* strings = cache->GetStringsArray();
+    // Note: `new_strings` points to temporary data, and is only valid here.
+    mirror::GcRootArray<mirror::String>* new_strings =
+        CreateGcRootDexCacheArray(cache->GetDexFile()->NumStringIds(),
+                                  mirror::DexCache::kDexCacheStringCacheSize,
+                                  strings);
+    // Store the array pointer in the dex cache, which will be relocated at the end.
+    reinterpret_cast<mirror::DexCache*>(copy)->SetStringsArray(strings);
+
+    // The code below copies new objects, so invalidate the address we have for
+    // `copy`.
+    copy = nullptr;
+    if (strings != nullptr) {
+      for (uint32_t i = 0; i < cache->GetDexFile()->NumStringIds(); ++i) {
+        ObjPtr<mirror::String> str = strings->Get(i);
+        if (str == nullptr || IsInBootImage(str.Ptr())) {
+          new_strings->Set(i, str.Ptr());
+        } else {
+          uint32_t hash = static_cast<uint32_t>(str->GetStoredHashCode());
+          DCHECK_EQ(hash, static_cast<uint32_t>(str->ComputeHashCode()))
+              << "Dex cache strings should be interned";
+          auto it2 = intern_table_.FindWithHash(str.Ptr(), hash);
+          if (it2 == intern_table_.end()) {
+            uint32_t string_offset = CopyObject(str);
+            uint32_t address = image_begin_ + string_offset + sizeof(ImageHeader);
+            intern_table_.InsertWithHash(address, hash);
+            new_strings->Set(i, reinterpret_cast<mirror::String*>(address));
+          } else {
+            new_strings->Set(i, reinterpret_cast<mirror::String*>(*it2));
+          }
+          // To not confuse string references from the dex cache object and
+          // string references from the array, we put an offset bigger than the
+          // size of a DexCache object. ClassLinker::VisitInternedStringReferences
+          // knows how to decode this offset.
+          string_reference_offsets_.emplace_back(
+              sizeof(ImageHeader) + offset, sizeof(mirror::DexCache) + i);
+        }
+      }
+    }
+
+    return offset;
+  }
+
+  bool IsInitialized(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (IsInBootImage(cls)) {
+      const OatDexFile* oat_dex_file = cls->GetDexFile().GetOatDexFile();
+      DCHECK(oat_dex_file != nullptr) << "We should always have an .oat file for a boot image";
+      uint16_t class_def_index = cls->GetDexClassDefIndex();
+      ClassStatus oat_file_class_status = oat_dex_file->GetOatClass(class_def_index).GetStatus();
+      return oat_file_class_status == ClassStatus::kVisiblyInitialized;
+    } else {
+      return cls->IsVisiblyInitialized<kVerifyNone>();
+    }
+  }
+  // Try to initialize `copy`. Note that `cls` may not be initialized.
+  // This is called after the image generation logic has visited super classes
+  // and super interfaces, so we can just check those directly.
+  bool TryInitializeClass(mirror::Class* copy, ObjPtr<mirror::Class> cls, uint32_t class_offset)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (!cls->IsVerified()) {
+      return false;
+    }
+    if (cls->IsArrayClass()) {
+      return true;
+    }
+
+    // Check if we have been able to initialize the super class.
+    mirror::Class* super = GetClassContent(cls->GetSuperClass());
+    DCHECK(super != nullptr)
+        << "App image classes should always have a super class: " << cls->PrettyClass();
+    if (!IsInitialized(super)) {
+      return false;
+    }
+
+    // We won't initialize class with class initializers.
+    if (cls->FindClassInitializer(kRuntimePointerSize) != nullptr) {
+      return false;
+    }
+
+    // For non-interface classes, we require all implemented interfaces to be
+    // initialized.
+    if (!cls->IsInterface()) {
+      for (size_t i = 0; i < cls->NumDirectInterfaces(); i++) {
+        mirror::Class* itf = GetClassContent(cls->GetDirectInterface(i));
+        if (!IsInitialized(itf)) {
+          return false;
+        }
+      }
+    }
+
+    // Trivial case: no static fields.
+    if (cls->NumStaticFields() == 0u) {
+      return true;
+    }
+
+    // Go over all static fields and try to initialize them.
+    EncodedStaticFieldValueIterator it(cls->GetDexFile(), *cls->GetClassDef());
+    if (!it.HasNext()) {
+      return true;
+    }
+
+    // Temporary string offsets in case we failed to initialize the class. We
+    // will add the offsets at the end of this method if we are successful.
+    ArenaVector<AppImageReferenceOffsetInfo> string_offsets(allocator_.Adapter());
+    ClassLinker* linker = Runtime::Current()->GetClassLinker();
+    ClassAccessor accessor(cls->GetDexFile(), *cls->GetClassDef());
+    for (const ClassAccessor::Field& field : accessor.GetStaticFields()) {
+      if (!it.HasNext()) {
+        break;
+      }
+      ArtField* art_field = linker->LookupResolvedField(field.GetIndex(),
+                                                        cls->GetDexCache(),
+                                                        cls->GetClassLoader(),
+                                                        /* is_static= */ true);
+      DCHECK_NE(art_field, nullptr);
+      MemberOffset offset(art_field->GetOffset());
+      switch (it.GetValueType()) {
+        case EncodedArrayValueIterator::ValueType::kBoolean:
+          copy->SetFieldBoolean<false>(offset, it.GetJavaValue().z);
+          break;
+        case EncodedArrayValueIterator::ValueType::kByte:
+          copy->SetFieldByte<false>(offset, it.GetJavaValue().b);
+          break;
+        case EncodedArrayValueIterator::ValueType::kShort:
+          copy->SetFieldShort<false>(offset, it.GetJavaValue().s);
+          break;
+        case EncodedArrayValueIterator::ValueType::kChar:
+          copy->SetFieldChar<false>(offset, it.GetJavaValue().c);
+          break;
+        case EncodedArrayValueIterator::ValueType::kInt:
+          copy->SetField32<false>(offset, it.GetJavaValue().i);
+          break;
+        case EncodedArrayValueIterator::ValueType::kLong:
+          copy->SetField64<false>(offset, it.GetJavaValue().j);
+          break;
+        case EncodedArrayValueIterator::ValueType::kFloat:
+          copy->SetField32<false>(offset, it.GetJavaValue().i);
+          break;
+        case EncodedArrayValueIterator::ValueType::kDouble:
+          copy->SetField64<false>(offset, it.GetJavaValue().j);
+          break;
+        case EncodedArrayValueIterator::ValueType::kNull:
+          copy->SetFieldObject<false>(offset, nullptr);
+          break;
+        case EncodedArrayValueIterator::ValueType::kString: {
+          ObjPtr<mirror::String> str =
+              linker->LookupString(dex::StringIndex(it.GetJavaValue().i), cls->GetDexCache());
+          mirror::String* str_copy = nullptr;
+          if (str == nullptr) {
+            // String wasn't created yet.
+            return false;
+          } else if (IsInBootImage(str.Ptr())) {
+            str_copy = str.Ptr();
+          } else {
+            uint32_t hash = static_cast<uint32_t>(str->GetStoredHashCode());
+            DCHECK_EQ(hash, static_cast<uint32_t>(str->ComputeHashCode()))
+                << "Dex cache strings should be interned";
+            auto string_it = intern_table_.FindWithHash(str.Ptr(), hash);
+            if (string_it == intern_table_.end()) {
+              // The string must be interned.
+              uint32_t string_offset = CopyObject(str);
+              // Reload the class copy after having copied the string.
+              copy = reinterpret_cast<mirror::Class*>(objects_.data() + class_offset);
+              uint32_t address = image_begin_ + string_offset + sizeof(ImageHeader);
+              intern_table_.InsertWithHash(address, hash);
+              str_copy = reinterpret_cast<mirror::String*>(address);
+            } else {
+              str_copy = reinterpret_cast<mirror::String*>(*string_it);
+            }
+            string_offsets.emplace_back(sizeof(ImageHeader) + class_offset, offset.Int32Value());
+          }
+          uint8_t* raw_addr = reinterpret_cast<uint8_t*>(copy) + offset.Int32Value();
+          mirror::HeapReference<mirror::Object>* objref_addr =
+              reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_addr);
+          objref_addr->Assign</* kIsVolatile= */ false>(str_copy);
+          break;
+        }
+        case EncodedArrayValueIterator::ValueType::kType: {
+          // Note that it may be that the referenced type hasn't been processed
+          // yet by the image generation logic. In this case we bail out for
+          // simplicity.
+          ObjPtr<mirror::Class> type =
+              linker->LookupResolvedType(dex::TypeIndex(it.GetJavaValue().i), cls);
+          mirror::Class* type_copy = nullptr;
+          if (type == nullptr) {
+            // Class wasn't resolved yet.
+            return false;
+          } else if (IsInBootImage(type.Ptr())) {
+            // Make sure the type is in our class table.
+            uint32_t hash = type->DescriptorHash();
+            class_table_.InsertWithHash(ClassTable::TableSlot(type.Ptr(), hash), hash);
+            type_copy = type.Ptr();
+          } else if (type->IsArrayClass()) {
+            std::string class_name;
+            type->GetDescriptor(&class_name);
+            auto class_it = array_classes_.find(class_name);
+            if (class_it == array_classes_.end()) {
+              return false;
+            }
+            type_copy = reinterpret_cast<mirror::Class*>(
+                image_begin_ + sizeof(ImageHeader) + class_it->second);
+          } else {
+            const dex::ClassDef* class_def = type->GetClassDef();
+            DCHECK_NE(class_def, nullptr);
+            auto class_it = classes_.find(class_def);
+            if (class_it == classes_.end()) {
+              return false;
+            }
+            type_copy = reinterpret_cast<mirror::Class*>(
+                image_begin_ + sizeof(ImageHeader) + class_it->second);
+          }
+          uint8_t* raw_addr = reinterpret_cast<uint8_t*>(copy) + offset.Int32Value();
+          mirror::HeapReference<mirror::Object>* objref_addr =
+              reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_addr);
+          objref_addr->Assign</* kIsVolatile= */ false>(type_copy);
+          break;
+        }
+        default:
+          LOG(FATAL) << "Unreachable";
+      }
+      it.Next();
+    }
+    // We have successfully initialized the class, we can now record the string
+    // offsets.
+    string_reference_offsets_.insert(
+        string_reference_offsets_.end(), string_offsets.begin(), string_offsets.end());
+    return true;
+  }
+
+  uint32_t CopyClass(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK(!cls->IsBootStrapClassLoaded());
+    uint32_t offset = 0u;
+    if (cls->IsArrayClass()) {
+      std::string class_name;
+      cls->GetDescriptor(&class_name);
+      auto it = array_classes_.find(class_name);
+      if (it != array_classes_.end()) {
+        return it->second;
+      }
+      offset = CopyObject(cls);
+      array_classes_.Put(class_name, offset);
+    } else {
+      const dex::ClassDef* class_def = cls->GetClassDef();
+      auto it = classes_.find(class_def);
+      if (it != classes_.end()) {
+        return it->second;
+      }
+      offset = CopyObject(cls);
+      classes_.Put(class_def, offset);
+    }
+
+    uint32_t hash = cls->DescriptorHash();
+    // Save the hash, the `HashSet` implementation requires to find it.
+    class_hashes_.Put(offset, hash);
+    uint32_t class_image_address = image_begin_ + sizeof(ImageHeader) + offset;
+    bool inserted =
+        class_table_.InsertWithHash(ClassTable::TableSlot(class_image_address, hash), hash).second;
+    DCHECK(inserted) << "Class " << cls->PrettyDescriptor()
+                     << " (" << cls.Ptr() << ") already inserted";
+
+    // Clear internal state.
+    mirror::Class* copy = reinterpret_cast<mirror::Class*>(objects_.data() + offset);
+    copy->SetClinitThreadId(static_cast<pid_t>(0u));
+    if (cls->IsArrayClass()) {
+      DCHECK(copy->IsVisiblyInitialized());
+    } else {
+      copy->SetStatusInternal(cls->IsVerified() ? ClassStatus::kVerified : ClassStatus::kResolved);
+    }
+
+    // Clear static field values.
+    auto clear_class = [&] () REQUIRES_SHARED(Locks::mutator_lock_) {
+      MemberOffset static_offset = cls->GetFirstReferenceStaticFieldOffset(kRuntimePointerSize);
+      memset(objects_.data() + offset + static_offset.Uint32Value(),
+             0,
+             cls->GetClassSize() - static_offset.Uint32Value());
+    };
+    clear_class();
+
+    bool is_class_initialized = TryInitializeClass(copy, cls, offset);
+    // Reload the copy, it may have moved after `TryInitializeClass`.
+    copy = reinterpret_cast<mirror::Class*>(objects_.data() + offset);
+    if (is_class_initialized) {
+      copy->SetStatusInternal(ClassStatus::kVisiblyInitialized);
+      if (!cls->IsArrayClass() && !cls->IsFinalizable()) {
+        copy->SetObjectSizeAllocFastPath(RoundUp(cls->GetObjectSize(), kObjectAlignment));
+      }
+      if (cls->IsInterface()) {
+        copy->SetAccessFlags(copy->GetAccessFlags() | kAccRecursivelyInitialized);
+      }
+    } else {
+      // If we fail to initialize, remove initialization related flags and
+      // clear again.
+      copy->SetObjectSizeAllocFastPath(std::numeric_limits<uint32_t>::max());
+      copy->SetAccessFlags(copy->GetAccessFlags() & ~kAccRecursivelyInitialized);
+      clear_class();
+    }
+
+    CopyFieldArrays(cls, class_image_address);
+    CopyMethodArrays(cls, class_image_address, is_class_initialized);
+    if (cls->ShouldHaveImt()) {
+      CopyImTable(cls);
+    }
+
+    return offset;
+  }
+
+  // Copy `obj` in `objects_` and relocate references. Returns the offset
+  // within our buffer.
+  uint32_t CopyObject(ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+    // Copy the object in `objects_`.
+    size_t object_size = obj->SizeOf();
+    size_t offset = objects_.size();
+    DCHECK(IsAligned<kObjectAlignment>(offset));
+    object_offsets_.push_back(offset);
+    objects_.resize(RoundUp(offset + object_size, kObjectAlignment));
+
+    mirror::Object* copy = reinterpret_cast<mirror::Object*>(objects_.data() + offset);
+    mirror::Object::CopyRawObjectData(
+        reinterpret_cast<uint8_t*>(copy), obj, object_size - sizeof(mirror::Object));
+    // Clear any lockword data.
+    copy->SetLockWord(LockWord::Default(), /* as_volatile= */ false);
+    copy->SetClass(obj->GetClass());
+
+    // Fixup reference pointers.
+    FixupVisitor visitor(this, offset);
+    obj->VisitReferences</*kVisitNativeRoots=*/ false>(visitor, visitor);
+
+    if (obj->IsString()) {
+      // Ensure a string always has a hashcode stored. This is checked at
+      // runtime because boot images don't want strings dirtied due to hashcode.
+      reinterpret_cast<mirror::String*>(copy)->GetHashCode();
+    }
+
+    object_section_size_ += RoundUp(object_size, kObjectAlignment);
+    return offset;
+  }
+
+  class CollectDexCacheVisitor : public DexCacheVisitor {
+   public:
+    explicit CollectDexCacheVisitor(VariableSizedHandleScope& handles) : handles_(handles) {}
+
+    void Visit(ObjPtr<mirror::DexCache> dex_cache)
+        REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) override {
+      dex_caches_.push_back(handles_.NewHandle(dex_cache));
+    }
+    const std::vector<Handle<mirror::DexCache>>& GetDexCaches() const {
+      return dex_caches_;
+    }
+   private:
+    VariableSizedHandleScope& handles_;
+    std::vector<Handle<mirror::DexCache>> dex_caches_;
+  };
+
+  // Find dex caches corresponding to the primary APK.
+  void FindDexCaches(Thread* self,
+                     dchecked_vector<Handle<mirror::DexCache>>& dex_caches,
+                     VariableSizedHandleScope& handles)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    ScopedTrace trace("Find dex caches");
+    DCHECK(dex_caches.empty());
+    // Collect all dex caches.
+    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+    CollectDexCacheVisitor visitor(handles);
+    {
+      ReaderMutexLock mu(self, *Locks::dex_lock_);
+      class_linker->VisitDexCaches(&visitor);
+    }
+
+    // Find the primary APK.
+    AppInfo* app_info = Runtime::Current()->GetAppInfo();
+    for (Handle<mirror::DexCache> cache : visitor.GetDexCaches()) {
+      if (app_info->GetRegisteredCodeType(cache->GetDexFile()->GetLocation()) ==
+              AppInfo::CodeType::kPrimaryApk) {
+        dex_caches.push_back(handles.NewHandle(cache.Get()));
+        break;
+      }
+    }
+
+    if (dex_caches.empty()) {
+      return;
+    }
+
+    const OatDexFile* oat_dex_file = dex_caches[0]->GetDexFile()->GetOatDexFile();
+    if (oat_dex_file == nullptr) {
+      // We need a .oat file for loading an app image;
+      dex_caches.clear();
+      return;
+    }
+
+    // Store the dex caches in the order in which their corresponding dex files
+    // are stored in the oat file. When we check for checksums at the point of
+    // loading the image, we rely on this order.
+    for (const OatDexFile* current : oat_dex_file->GetOatFile()->GetOatDexFiles()) {
+      if (current != oat_dex_file) {
+        for (Handle<mirror::DexCache> cache : visitor.GetDexCaches()) {
+          if (cache->GetDexFile()->GetOatDexFile() == current) {
+            dex_caches.push_back(handles.NewHandle(cache.Get()));
+          }
+        }
+      }
+    }
+  }
+
+  static uint64_t PointerToUint64(void* ptr) {
+    return reinterpret_cast64<uint64_t>(ptr);
+  }
+
+  void WriteImageMethods() {
+    ScopedObjectAccess soa(Thread::Current());
+    // We can just use plain runtime pointers.
+    Runtime* runtime = Runtime::Current();
+    header_.image_methods_[ImageHeader::kResolutionMethod] =
+        PointerToUint64(runtime->GetResolutionMethod());
+    header_.image_methods_[ImageHeader::kImtConflictMethod] =
+        PointerToUint64(runtime->GetImtConflictMethod());
+    header_.image_methods_[ImageHeader::kImtUnimplementedMethod] =
+        PointerToUint64(runtime->GetImtUnimplementedMethod());
+    header_.image_methods_[ImageHeader::kSaveAllCalleeSavesMethod] =
+        PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveAllCalleeSaves));
+    header_.image_methods_[ImageHeader::kSaveRefsOnlyMethod] =
+        PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsOnly));
+    header_.image_methods_[ImageHeader::kSaveRefsAndArgsMethod] =
+        PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
+    header_.image_methods_[ImageHeader::kSaveEverythingMethod] =
+        PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverything));
+    header_.image_methods_[ImageHeader::kSaveEverythingMethodForClinit] =
+        PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForClinit));
+    header_.image_methods_[ImageHeader::kSaveEverythingMethodForSuspendCheck] =
+        PointerToUint64(
+            runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForSuspendCheck));
+  }
+
+  // Header for the image, created at the end once we know the size of all
+  // sections.
+  ImageHeader header_;
+
+  // Allocator for the various data structures to allocate while generating the
+  // image.
+  ArenaAllocator allocator_;
+
+  // Contents of the various sections.
+  ArenaVector<uint8_t> objects_;
+  ArenaVector<uint8_t> art_fields_;
+  ArenaVector<uint8_t> art_methods_;
+  ArenaVector<uint8_t> im_tables_;
+  ArenaVector<uint8_t> metadata_;
+  ArenaVector<uint8_t> dex_cache_arrays_;
+
+  ArenaVector<AppImageReferenceOffsetInfo> string_reference_offsets_;
+
+  // Bitmap of live objects in `objects_`. Populated from `object_offsets_`
+  // once we know `object_section_size`.
+  gc::accounting::ContinuousSpaceBitmap image_bitmap_;
+
+  // Sections stored in the header.
+  ArenaVector<ImageSection> sections_;
+
+  // A list of offsets in `objects_` where objects begin.
+  ArenaVector<uint32_t> object_offsets_;
+
+  ArenaSafeMap<const dex::ClassDef*, uint32_t> classes_;
+  ArenaSafeMap<std::string, uint32_t> array_classes_;
+  ArenaSafeMap<const DexFile*, uint32_t> dex_caches_;
+  ArenaSafeMap<uint32_t, uint32_t> class_hashes_;
+
+  ArenaSafeMap<void*, std::pair<NativeRelocationKind, uint32_t>> native_relocations_;
+
+  // Cached values of boot image information.
+  const uint32_t boot_image_begin_;
+  const uint32_t boot_image_size_;
+
+  // Where the image begins: just after the boot image.
+  const uint32_t image_begin_;
+
+  // Size of the `kSectionObjects` section.
+  size_t object_section_size_;
+
+  // The location of the primary APK / dex file.
+  std::string dex_location_;
+
+  // The intern table for strings that we will write to disk.
+  InternTableSet intern_table_;
+
+  // The class table holding classes that we will write to disk.
+  ClassTableSet class_table_;
+
+  friend class ClassDescriptorHash;
+  friend class PruneVisitor;
+  friend class NativePointerVisitor;
+};
+
+static std::string GetOatPath() {
+  const std::string& data_dir = Runtime::Current()->GetProcessDataDirectory();
+  if (data_dir.empty()) {
+    // The data ditectory is empty for tests.
+    return "";
+  }
+  return data_dir + "/cache/oat_primary/";
+}
+
+// Note: this may return a relative path for tests.
+std::string RuntimeImage::GetRuntimeImagePath(const std::string& dex_location) {
+  std::string basename = android::base::Basename(dex_location);
+  std::string filename = ReplaceFileExtension(basename, "art");
+
+  return GetOatPath() + GetInstructionSetString(kRuntimeISA) + "/" + filename;
+}
+
+static bool EnsureDirectoryExists(const std::string& directory, std::string* error_msg) {
+  if (!OS::DirectoryExists(directory.c_str())) {
+    static constexpr mode_t kDirectoryMode = S_IRWXU | S_IRGRP | S_IXGRP| S_IROTH | S_IXOTH;
+    if (mkdir(directory.c_str(), kDirectoryMode) != 0) {
+      *error_msg =
+          StringPrintf("Could not create directory %s: %s", directory.c_str(), strerror(errno));
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RuntimeImage::WriteImageToDisk(std::string* error_msg) {
+  gc::Heap* heap = Runtime::Current()->GetHeap();
+  if (!heap->HasBootImageSpace()) {
+    *error_msg = "Cannot generate an app image without a boot image";
+    return false;
+  }
+  std::string oat_path = GetOatPath();
+  if (!oat_path.empty() && !EnsureDirectoryExists(oat_path, error_msg)) {
+    return false;
+  }
+
+  ScopedTrace generate_image_trace("Generating runtime image");
+  std::unique_ptr<RuntimeImageHelper> image(new RuntimeImageHelper(heap));
+  if (!image->Generate(error_msg)) {
+    return false;
+  }
+
+  ScopedTrace write_image_trace("Writing runtime image to disk");
+
+  const std::string path = GetRuntimeImagePath(image->GetDexLocation());
+  if (!EnsureDirectoryExists(android::base::Dirname(path), error_msg)) {
+    return false;
+  }
+
+  // We first generate the app image in a temporary file, which we will then
+  // move to `path`.
+  const std::string temp_path = ReplaceFileExtension(path, std::to_string(getpid()) + ".tmp");
+  ImageFileGuard image_file;
+  image_file.reset(OS::CreateEmptyFileWriteOnly(temp_path.c_str()));
+
+  if (image_file == nullptr) {
+    *error_msg = "Could not open " + temp_path + " for writing";
+    return false;
+  }
+
+  std::vector<uint8_t> full_data(image->GetHeader()->GetImageSize());
+  image->FillData(full_data);
+
+  // Specify default block size of 512K to enable parallel image decompression.
+  static constexpr size_t kMaxImageBlockSize = 524288;
+  // Use LZ4 as good compromise between CPU time and compression. LZ4HC
+  // empirically takes 10x more time compressing.
+  static constexpr ImageHeader::StorageMode kImageStorageMode = ImageHeader::kStorageModeLZ4;
+  // Note: no need to update the checksum of the runtime app image: we have no
+  // use for it, and computing it takes CPU time.
+  if (!image->GetHeader()->WriteData(
+          image_file,
+          full_data.data(),
+          reinterpret_cast<const uint8_t*>(image->GetImageBitmap().Begin()),
+          kImageStorageMode,
+          kMaxImageBlockSize,
+          /* update_checksum= */ false,
+          error_msg)) {
+    return false;
+  }
+
+  if (!image_file.WriteHeaderAndClose(temp_path, image->GetHeader(), error_msg)) {
+    return false;
+  }
+
+  if (rename(temp_path.c_str(), path.c_str()) != 0) {
+    *error_msg =
+        "Failed to move runtime app image to " + path + ": " + std::string(strerror(errno));
+    // Unlink directly: we cannot use `out` as we have closed it.
+    unlink(temp_path.c_str());
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace art
diff --git a/runtime/runtime_image.h b/runtime/runtime_image.h
new file mode 100644
index 0000000..d494e1c
--- /dev/null
+++ b/runtime/runtime_image.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 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_RUNTIME_RUNTIME_IMAGE_H_
+#define ART_RUNTIME_RUNTIME_IMAGE_H_
+
+#include <string>
+
+namespace art {
+
+class RuntimeImage {
+ public:
+    // Writes an app image for the currently running process.
+  static bool WriteImageToDisk(std::string* error_msg);
+
+  // Gets the path where a runtime-generated app image is stored.
+  static std::string GetRuntimeImagePath(const std::string& dex_location);
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_RUNTIME_IMAGE_H_
diff --git a/runtime/runtime_intrinsics.cc b/runtime/runtime_intrinsics.cc
index 1672c49..fb60cfe 100644
--- a/runtime/runtime_intrinsics.cc
+++ b/runtime/runtime_intrinsics.cc
@@ -90,10 +90,10 @@
   }
 }
 
-bool AreAllIntrinsicsInitialized() {
-  ScopedObjectAccess soa(Thread::Current());
+bool AreAllIntrinsicsInitialized() REQUIRES_SHARED(Locks::mutator_lock_) {
+  Thread* self = Thread::Current();
 #define IS_INTRINSIC_INITIALIZED(Name, InvokeType, _, __, ___, ClassName, MethodName, Signature) \
-  IsIntrinsicInitialized(soa.Self(),                                                             \
+  IsIntrinsicInitialized(self,                                                                   \
                          Intrinsics::k##Name,                                                    \
                          InvokeType,                                                             \
                          ClassName,                                                              \
@@ -107,11 +107,11 @@
 }  // namespace
 
 void InitializeIntrinsics() {
-  ScopedObjectAccess soa(Thread::Current());
+  Thread* self = Thread::Current();
   // Initialization here uses the short-circuit operator || to stop
   // initializing if there's an already initialized intrinsic.
 #define INITIALIZE_INTRINSIC(Name, InvokeType, _, __, ___, ClassName, MethodName, Signature) \
-  InitializeIntrinsic(soa.Self(),                                                            \
+  InitializeIntrinsic(self,                                                                  \
                       Intrinsics::k##Name,                                                   \
                       InvokeType,                                                            \
                       ClassName,                                                             \
diff --git a/runtime/runtime_intrinsics.h b/runtime/runtime_intrinsics.h
index 98dc9bc..bf2cb2b 100644
--- a/runtime/runtime_intrinsics.h
+++ b/runtime/runtime_intrinsics.h
@@ -17,9 +17,11 @@
 #ifndef ART_RUNTIME_RUNTIME_INTRINSICS_H_
 #define ART_RUNTIME_RUNTIME_INTRINSICS_H_
 
+#include "base/locks.h"
+
 namespace art {
 
-void InitializeIntrinsics();
+void InitializeIntrinsics() REQUIRES_SHARED(Locks::mutator_lock_);
 
 }  // namespace art
 
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index 76d1657..b2fcf7d 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -47,6 +47,7 @@
 RUNTIME_OPTIONS_KEY (std::string,         ClassPath)
 RUNTIME_OPTIONS_KEY (ParseStringList<':'>,Image)
 RUNTIME_OPTIONS_KEY (Unit,                ForceJitZygote)
+RUNTIME_OPTIONS_KEY (Unit,                AllowInMemoryCompilation)
 RUNTIME_OPTIONS_KEY (Unit,                CheckJni)
 RUNTIME_OPTIONS_KEY (Unit,                JniOptsForceCopy)
 RUNTIME_OPTIONS_KEY (std::string,         JdwpOptions,                    "suspend=n,server=y")
@@ -80,7 +81,7 @@
 RUNTIME_OPTIONS_KEY (Unit,                IgnoreMaxFootprint)
 RUNTIME_OPTIONS_KEY (bool,                AlwaysLogExplicitGcs,           true)
 RUNTIME_OPTIONS_KEY (Unit,                LowMemoryMode)
-RUNTIME_OPTIONS_KEY (bool,                UseTLAB,                        (kUseTlab || kUseReadBarrier))
+RUNTIME_OPTIONS_KEY (bool,                UseTLAB,                        kUseTlab)
 RUNTIME_OPTIONS_KEY (bool,                EnableHSpaceCompactForOOM,      true)
 RUNTIME_OPTIONS_KEY (bool,                UseJitCompilation,              true)
 RUNTIME_OPTIONS_KEY (bool,                UseProfiledJitCompilation,      false)
@@ -125,6 +126,7 @@
 RUNTIME_OPTIONS_KEY (std::string,         MethodTraceFile,                "/data/misc/trace/method-trace-file.bin")
 RUNTIME_OPTIONS_KEY (unsigned int,        MethodTraceFileSize,            10 * MB)
 RUNTIME_OPTIONS_KEY (Unit,                MethodTraceStreaming)
+RUNTIME_OPTIONS_KEY (TraceClockSource,    MethodTraceClock,               kDefaultTraceClockSource)
 RUNTIME_OPTIONS_KEY (TraceClockSource,    ProfileClock,                   kDefaultTraceClockSource)  // -Xprofile:
 RUNTIME_OPTIONS_KEY (ProfileSaverOptions, ProfileSaverOpts)  // -Xjitsaveprofilinginfo, -Xps-*
 RUNTIME_OPTIONS_KEY (std::string,         Compiler)
diff --git a/runtime/scoped_thread_state_change-inl.h b/runtime/scoped_thread_state_change-inl.h
index d601952..674d791 100644
--- a/runtime/scoped_thread_state_change-inl.h
+++ b/runtime/scoped_thread_state_change-inl.h
@@ -94,7 +94,7 @@
 }
 
 inline ScopedObjectAccessAlreadyRunnable::ScopedObjectAccessAlreadyRunnable(JNIEnv* env)
-    : self_(ThreadForEnv(env)), env_(down_cast<JNIEnvExt*>(env)), vm_(env_->GetVm()) {}
+    : self_(Thread::ForEnv(env)), env_(down_cast<JNIEnvExt*>(env)), vm_(env_->GetVm()) {}
 
 inline ScopedObjectAccessAlreadyRunnable::ScopedObjectAccessAlreadyRunnable(Thread* self)
     : self_(self),
diff --git a/runtime/sdk_checker.cc b/runtime/sdk_checker.cc
index 1dbe39c..9502382 100644
--- a/runtime/sdk_checker.cc
+++ b/runtime/sdk_checker.cc
@@ -25,21 +25,17 @@
 
 SdkChecker::SdkChecker() : enabled_(true) {}
 
-SdkChecker* SdkChecker::Create(
-    const std::string& public_sdk, std::string* error_msg) {
+SdkChecker* SdkChecker::Create(const std::string& public_sdk, std::string* error_msg) {
   std::vector<std::string> dex_file_paths;
   Split(public_sdk, ':', &dex_file_paths);
 
-  ArtDexFileLoader dex_loader;
-
   std::unique_ptr<SdkChecker> sdk_checker(new SdkChecker());
   for (const std::string& path : dex_file_paths) {
-    if (!dex_loader.Open(path.c_str(),
-                         path,
-                         /*verify=*/ true,
-                         /*verify_checksum*/ false,
-                         error_msg,
-                         &sdk_checker->sdk_dex_files_)) {
+    DexFileLoader dex_file_loader(path);
+    if (!dex_file_loader.Open(/*verify=*/true,
+                              /*verify_checksum*/ false,
+                              error_msg,
+                              &sdk_checker->sdk_dex_files_)) {
       return nullptr;
     }
   }
@@ -66,9 +62,7 @@
     dex::TypeIndex return_type_idx;
     std::vector<dex::TypeIndex> param_type_idxs;
     if (!dex_file->CreateTypeList(
-            art_method->GetSignature().ToString().c_str(),
-            &return_type_idx,
-            &param_type_idxs)) {
+            art_method->GetSignature().ToString(), &return_type_idx, &param_type_idxs)) {
       continue;
     }
     const dex::ProtoId* proto_id = dex_file->FindProtoId(return_type_idx, param_type_idxs);
@@ -101,8 +95,8 @@
   for (const std::unique_ptr<const DexFile>& dex_file : sdk_dex_files_) {
     std::string declaring_class;
 
-    const dex::TypeId* declaring_type_id = dex_file->FindTypeId(
-        art_field->GetDeclaringClass()->GetDescriptor(&declaring_class));
+    const dex::TypeId* declaring_type_id =
+        dex_file->FindTypeId(art_field->GetDeclaringClass()->GetDescriptor(&declaring_class));
     if (declaring_type_id == nullptr) {
       continue;
     }
diff --git a/runtime/stack.cc b/runtime/stack.cc
index 50a96d0..d7d5851 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -129,7 +129,7 @@
           GetCurrentQuickFrame(), cur_quick_frame_pc_, abort_on_failure);
     } else if (cur_oat_quick_method_header_->IsOptimized()) {
       StackMap* stack_map = GetCurrentStackMap();
-      DCHECK(stack_map->IsValid());
+      CHECK(stack_map->IsValid()) << "StackMap not found for " << std::hex << cur_quick_frame_pc_;
       return stack_map->GetDexPc();
     } else {
       DCHECK(cur_oat_quick_method_header_->IsNterpMethodHeader());
@@ -140,6 +140,29 @@
   }
 }
 
+std::vector<uint32_t> StackVisitor::ComputeDexPcList(uint32_t handler_dex_pc) const {
+  std::vector<uint32_t> result;
+  if (cur_shadow_frame_ == nullptr && cur_quick_frame_ != nullptr && IsInInlinedFrame()) {
+    const BitTableRange<InlineInfo>& infos = current_inline_frames_;
+    DCHECK_NE(infos.size(), 0u);
+
+    // Outermost dex_pc.
+    result.push_back(GetCurrentStackMap()->GetDexPc());
+
+    // The mid dex_pcs. Note that we skip the last one since we want to change that for
+    // `handler_dex_pc`.
+    for (size_t index = 0; index < infos.size() - 1; ++index) {
+      result.push_back(infos[index].GetDexPc());
+    }
+  }
+
+  // The innermost dex_pc has to be the handler dex_pc. In the case of no inline frames, it will be
+  // just the one dex_pc. In the case of inlining we will be replacing the innermost InlineInfo's
+  // dex_pc with this one.
+  result.push_back(handler_dex_pc);
+  return result;
+}
+
 extern "C" mirror::Object* artQuickGetProxyThisObject(ArtMethod** sp)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -213,7 +236,8 @@
                            uint16_t vreg,
                            VRegKind kind,
                            uint32_t* val,
-                           std::optional<DexRegisterLocation> location) const {
+                           std::optional<DexRegisterLocation> location,
+                           bool need_full_register_list) const {
   if (cur_quick_frame_ != nullptr) {
     DCHECK(context_ != nullptr);  // You can't reliably read registers without a context.
     DCHECK(m == GetMethod());
@@ -235,10 +259,10 @@
         // which does not decode the stack maps.
         result = GetVRegFromOptimizedCode(location.value(), val);
         // Compare to the slower overload.
-        DCHECK_EQ(result, GetVRegFromOptimizedCode(m, vreg, kind, &val2));
+        DCHECK_EQ(result, GetVRegFromOptimizedCode(m, vreg, kind, &val2, need_full_register_list));
         DCHECK_EQ(*val, val2);
       } else {
-        result = GetVRegFromOptimizedCode(m, vreg, kind, val);
+        result = GetVRegFromOptimizedCode(m, vreg, kind, val, need_full_register_list);
       }
     }
     if (kind == kReferenceVReg) {
@@ -261,16 +285,20 @@
   }
 }
 
+size_t StackVisitor::GetNumberOfRegisters(CodeInfo* code_info, int depth) const {
+  return depth == 0
+    ? code_info->GetNumberOfDexRegisters()
+    : current_inline_frames_[depth - 1].GetNumberOfDexRegisters();
+}
+
 bool StackVisitor::GetVRegFromOptimizedCode(ArtMethod* m,
                                             uint16_t vreg,
                                             VRegKind kind,
-                                            uint32_t* val) const {
+                                            uint32_t* val,
+                                            bool need_full_register_list) const {
   DCHECK_EQ(m, GetMethod());
   // Can't be null or how would we compile its instructions?
   DCHECK(m->GetCodeItem() != nullptr) << m->PrettyMethod();
-  CodeItemDataAccessor accessor(m->DexInstructionData());
-  uint16_t number_of_dex_registers = accessor.RegistersSize();
-  DCHECK_LT(vreg, number_of_dex_registers);
   const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();
   CodeInfo code_info(method_header);
 
@@ -278,13 +306,18 @@
   StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset);
   DCHECK(stack_map.IsValid());
 
-  DexRegisterMap dex_register_map = IsInInlinedFrame()
-      ? code_info.GetInlineDexRegisterMapOf(stack_map, current_inline_frames_.back())
-      : code_info.GetDexRegisterMapOf(stack_map);
+  DexRegisterMap dex_register_map = (IsInInlinedFrame() && !need_full_register_list)
+    ? code_info.GetInlineDexRegisterMapOf(stack_map, current_inline_frames_.back())
+    : code_info.GetDexRegisterMapOf(stack_map,
+                                    /* first= */ 0,
+                                    GetNumberOfRegisters(&code_info, InlineDepth()));
+
   if (dex_register_map.empty()) {
     return false;
   }
-  DCHECK_EQ(dex_register_map.size(), number_of_dex_registers);
+
+  const size_t number_of_dex_registers = dex_register_map.size();
+  DCHECK_LT(vreg, number_of_dex_registers);
   DexRegisterLocation::Kind location_kind = dex_register_map[vreg].GetKind();
   switch (location_kind) {
     case DexRegisterLocation::Kind::kInStack: {
@@ -745,15 +778,6 @@
   //     (resolution, instrumentation) trampoline; or
   //   - fake a Generic JNI frame in art_jni_dlsym_lookup_critical_stub.
   DCHECK(method->IsNative());
-  if (kIsDebugBuild && !method->IsCriticalNative()) {
-    ClassLinker* class_linker = runtime->GetClassLinker();
-    const void* entry_point = runtime->GetInstrumentation()->GetCodeForInvoke(method);
-    CHECK(class_linker->IsQuickGenericJniStub(entry_point) ||
-          // The current entrypoint (after filtering out trampolines) may have changed
-          // from GenericJNI to JIT-compiled stub since we have entered this frame.
-          (runtime->GetJit() != nullptr &&
-           runtime->GetJit()->GetCodeCache()->ContainsPc(entry_point))) << method->PrettyMethod();
-  }
   // Generic JNI frame is just like the SaveRefsAndArgs frame.
   // Note that HandleScope, if any, is below the frame.
   return RuntimeCalleeSaveFrame::GetMethodFrameInfo(CalleeSaveType::kSaveRefsAndArgs);
@@ -780,7 +804,6 @@
     DCHECK(thread_ == Thread::Current() || thread_->IsSuspended());
   }
   CHECK_EQ(cur_depth_, 0U);
-  size_t inlined_frames_count = 0;
 
   for (const ManagedStack* current_fragment = thread_->GetManagedStack();
        current_fragment != nullptr; current_fragment = current_fragment->GetLink()) {
@@ -788,6 +811,12 @@
     cur_quick_frame_ = current_fragment->GetTopQuickFrame();
     cur_quick_frame_pc_ = 0;
     DCHECK(cur_oat_quick_method_header_ == nullptr);
+
+    if (kDebugStackWalk) {
+      LOG(INFO) << "Tid=" << thread_-> GetThreadId()
+          << ", ManagedStack fragement: " << current_fragment;
+    }
+
     if (cur_quick_frame_ != nullptr) {  // Handle quick stack frames.
       // Can't be both a shadow and a quick fragment.
       DCHECK(current_fragment->GetTopShadowFrame() == nullptr);
@@ -800,18 +829,26 @@
         // between GenericJNI frame and JIT-compiled JNI stub; the entrypoint may have
         // changed since the frame was entered. The top quick frame tag indicates
         // GenericJNI here, otherwise it's either AOT-compiled or JNI-compiled JNI stub.
-        if (UNLIKELY(current_fragment->GetTopQuickFrameTag())) {
+        if (UNLIKELY(current_fragment->GetTopQuickFrameGenericJniTag())) {
           // The generic JNI does not have any method header.
           cur_oat_quick_method_header_ = nullptr;
+        } else if (UNLIKELY(current_fragment->GetTopQuickFrameJitJniTag())) {
+          // Should be JITed code.
+          Runtime* runtime = Runtime::Current();
+          const void* code = runtime->GetJit()->GetCodeCache()->GetJniStubCode(method);
+          CHECK(code != nullptr) << method->PrettyMethod();
+          cur_oat_quick_method_header_ = OatQuickMethodHeader::FromCodePointer(code);
         } else {
+          // We are sure we are not running GenericJni here. Though the entry point could still be
+          // GenericJnistub. The entry point is usually JITed or AOT code. It could be lso a
+          // resolution stub if the class isn't visibly initialized yet.
           const void* existing_entry_point = method->GetEntryPointFromQuickCompiledCode();
           CHECK(existing_entry_point != nullptr);
           Runtime* runtime = Runtime::Current();
           ClassLinker* class_linker = runtime->GetClassLinker();
           // Check whether we can quickly get the header from the current entrypoint.
           if (!class_linker->IsQuickGenericJniStub(existing_entry_point) &&
-              !class_linker->IsQuickResolutionStub(existing_entry_point) &&
-              existing_entry_point != GetQuickInstrumentationEntryPoint()) {
+              !class_linker->IsQuickResolutionStub(existing_entry_point)) {
             cur_oat_quick_method_header_ =
                 OatQuickMethodHeader::FromEntryPoint(existing_entry_point);
           } else {
@@ -819,7 +856,11 @@
             if (code != nullptr) {
               cur_oat_quick_method_header_ = OatQuickMethodHeader::FromEntryPoint(code);
             } else {
-              // This must be a JITted JNI stub frame.
+              // This must be a JITted JNI stub frame. For non-debuggable runtimes we only generate
+              // JIT stubs if there are no AOT stubs for native methods. Since we checked for AOT
+              // code earlier, we must be running JITed code. For debuggable runtimes we might have
+              // JIT code even when AOT code is present but we tag SP in JITed JNI stubs
+              // in debuggable runtimes. This case is handled earlier.
               CHECK(runtime->GetJit() != nullptr);
               code = runtime->GetJit()->GetCodeCache()->GetJniStubCode(method);
               CHECK(code != nullptr) << method->PrettyMethod();
@@ -834,8 +875,12 @@
           cur_oat_quick_method_header_ = method->GetOatQuickMethodHeader(cur_quick_frame_pc_);
         }
         header_retrieved = false;  // Force header retrieval in next iteration.
-        ValidateFrame();
 
+        if (kDebugStackWalk) {
+          LOG(INFO) << "Early print: Tid=" << thread_-> GetThreadId() << ", method: "
+              << ArtMethod::PrettyMethod(method) << "@" << method;
+        }
+        ValidateFrame();
         if ((walk_kind_ == StackWalkKind::kIncludeInlinedFrames)
             && (cur_oat_quick_method_header_ != nullptr)
             && cur_oat_quick_method_header_->IsOptimized()
@@ -854,7 +899,6 @@
                 return;
               }
               cur_depth_++;
-              inlined_frames_count++;
             }
           }
         }
@@ -871,44 +915,14 @@
         // Compute PC for next stack frame from return PC.
         size_t frame_size = frame_info.FrameSizeInBytes();
         uintptr_t return_pc_addr = GetReturnPcAddr();
-        uintptr_t return_pc = *reinterpret_cast<uintptr_t*>(return_pc_addr);
 
-        if (UNLIKELY(reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) == return_pc)) {
-          // While profiling, the return pc is restored from the side stack, except when walking
-          // the stack for an exception where the side stack will be unwound in VisitFrame.
-          const std::map<uintptr_t, instrumentation::InstrumentationStackFrame>&
-              instrumentation_stack = *thread_->GetInstrumentationStack();
-          auto it = instrumentation_stack.find(return_pc_addr);
-          CHECK(it != instrumentation_stack.end());
-          const instrumentation::InstrumentationStackFrame& instrumentation_frame = it->second;
-          if (GetMethod() ==
-              Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveAllCalleeSaves)) {
-            // Skip runtime save all callee frames which are used to deliver exceptions.
-          } else if (instrumentation_frame.interpreter_entry_) {
-            ArtMethod* callee =
-                Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs);
-            CHECK_EQ(GetMethod(), callee) << "Expected: " << ArtMethod::PrettyMethod(callee)
-                                          << " Found: " << ArtMethod::PrettyMethod(GetMethod());
-          } else if (!instrumentation_frame.method_->IsRuntimeMethod()) {
-            // Trampolines get replaced with their actual method in the stack,
-            // so don't do the check below for runtime methods.
-            // Instrumentation generally doesn't distinguish between a method's obsolete and
-            // non-obsolete version.
-            CHECK_EQ(instrumentation_frame.method_->GetNonObsoleteMethod(),
-                     GetMethod()->GetNonObsoleteMethod())
-                << "Expected: "
-                << ArtMethod::PrettyMethod(instrumentation_frame.method_->GetNonObsoleteMethod())
-                << " Found: " << ArtMethod::PrettyMethod(GetMethod()->GetNonObsoleteMethod());
-          }
-          return_pc = instrumentation_frame.return_pc_;
-        }
-
-        cur_quick_frame_pc_ = return_pc;
+        cur_quick_frame_pc_ = *reinterpret_cast<uintptr_t*>(return_pc_addr);
         uint8_t* next_frame = reinterpret_cast<uint8_t*>(cur_quick_frame_) + frame_size;
         cur_quick_frame_ = reinterpret_cast<ArtMethod**>(next_frame);
 
         if (kDebugStackWalk) {
-          LOG(INFO) << ArtMethod::PrettyMethod(method) << "@" << method << " size=" << frame_size
+          LOG(INFO) << "Tid=" << thread_-> GetThreadId() << ", method: "
+              << ArtMethod::PrettyMethod(method) << "@" << method << " size=" << frame_size
               << std::boolalpha
               << " optimized=" << (cur_oat_quick_method_header_ != nullptr &&
                                    cur_oat_quick_method_header_->IsOptimized())
@@ -928,6 +942,12 @@
       cur_oat_quick_method_header_ = nullptr;
     } else if (cur_shadow_frame_ != nullptr) {
       do {
+        if (kDebugStackWalk) {
+          ArtMethod* method = cur_shadow_frame_->GetMethod();
+          LOG(INFO) << "Tid=" << thread_-> GetThreadId() << ", method: "
+              << ArtMethod::PrettyMethod(method) << "@" << method
+              << ", ShadowFrame";
+        }
         ValidateFrame();
         bool should_continue = VisitFrame();
         if (UNLIKELY(!should_continue)) {
diff --git a/runtime/stack.h b/runtime/stack.h
index 1b00b54..a4bcf17 100644
--- a/runtime/stack.h
+++ b/runtime/stack.h
@@ -58,11 +58,6 @@
 };
 std::ostream& operator<<(std::ostream& os, VRegKind rhs);
 
-// Size in bytes of the should_deoptimize flag on stack.
-// We just need 4 bytes for our purpose regardless of the architecture. Frame size
-// calculation will automatically do alignment for the final frame size.
-static constexpr size_t kShouldDeoptimizeFlagSize = 4;
-
 /*
  * Our current stack layout.
  * The Dalvik registers come first, followed by the
@@ -197,6 +192,12 @@
 
   uint32_t GetDexPc(bool abort_on_failure = true) const REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Returns a vector of the inlined dex pcs, in order from outermost to innermost but it replaces
+  // the innermost one with `handler_dex_pc`. In essence, (outermost dex pc, mid dex pc #1, ..., mid
+  // dex pc #n-1, `handler_dex_pc`).
+  std::vector<uint32_t> ComputeDexPcList(uint32_t handler_dex_pc) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   ObjPtr<mirror::Object> GetThisObject() const REQUIRES_SHARED(Locks::mutator_lock_);
 
   size_t GetNativePcOffset() const REQUIRES_SHARED(Locks::mutator_lock_);
@@ -230,9 +231,8 @@
                uint16_t vreg,
                VRegKind kind,
                uint32_t* val,
-               std::optional<DexRegisterLocation> location =
-                   std::optional<DexRegisterLocation>()) const
-      REQUIRES_SHARED(Locks::mutator_lock_);
+               std::optional<DexRegisterLocation> location = std::optional<DexRegisterLocation>(),
+               bool need_full_register_list = false) const REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool GetVRegPair(ArtMethod* m, uint16_t vreg, VRegKind kind_lo, VRegKind kind_hi,
                    uint64_t* val) const
@@ -268,10 +268,16 @@
     return !current_inline_frames_.empty();
   }
 
+  size_t InlineDepth() const { return current_inline_frames_.size(); }
+
   InlineInfo GetCurrentInlinedFrame() const {
     return current_inline_frames_.back();
   }
 
+  const BitTableRange<InlineInfo>& GetCurrentInlinedFrames() const {
+    return current_inline_frames_;
+  }
+
   uintptr_t GetCurrentQuickFramePc() const {
     return cur_quick_frame_pc_;
   }
@@ -302,10 +308,26 @@
     *should_deoptimize_addr = *should_deoptimize_addr | static_cast<uint8_t>(value);
   };
 
+  void UnsetShouldDeoptimizeFlag(DeoptimizeFlagValue value) REQUIRES_SHARED(Locks::mutator_lock_) {
+    uint8_t* should_deoptimize_addr = GetShouldDeoptimizeFlagAddr();
+    *should_deoptimize_addr = *should_deoptimize_addr & ~static_cast<uint8_t>(value);
+  };
+
   uint8_t GetShouldDeoptimizeFlag() const REQUIRES_SHARED(Locks::mutator_lock_) {
     return *GetShouldDeoptimizeFlagAddr();
   }
 
+  bool ShouldForceDeoptForRedefinition() const REQUIRES_SHARED(Locks::mutator_lock_) {
+    uint8_t should_deopt_flag = GetShouldDeoptimizeFlag();
+    return (should_deopt_flag &
+            static_cast<uint8_t>(DeoptimizeFlagValue::kForceDeoptForRedefinition)) != 0;
+  }
+
+  // Return the number of dex register in the map from the outermost frame to the number of inlined
+  // frames indicated by `depth`. If `depth` is 0, grab just the registers from the outermost level.
+  // If it is greater than 0, grab as many inline frames as `depth` indicates.
+  size_t GetNumberOfRegisters(CodeInfo* code_info, int depth) const;
+
  private:
   // Private constructor known in the case that num_frames_ has already been computed.
   StackVisitor(Thread* thread,
@@ -334,7 +356,8 @@
   bool GetVRegFromOptimizedCode(ArtMethod* m,
                                 uint16_t vreg,
                                 VRegKind kind,
-                                uint32_t* val) const
+                                uint32_t* val,
+                                bool need_full_register_list = false) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool GetVRegPairFromDebuggerShadowFrame(uint16_t vreg,
diff --git a/runtime/stack_map.h b/runtime/stack_map.h
index 7a13dbd..514d30e 100644
--- a/runtime/stack_map.h
+++ b/runtime/stack_map.h
@@ -20,9 +20,12 @@
 #include <limits>
 
 #include "arch/instruction_set.h"
+#include "base/array_ref.h"
 #include "base/bit_memory_region.h"
 #include "base/bit_table.h"
 #include "base/bit_utils.h"
+#include "base/globals.h"
+#include "base/logging.h"
 #include "base/memory_region.h"
 #include "dex/dex_file_types.h"
 #include "dex_register_location.h"
@@ -360,15 +363,12 @@
     return GetMethodInfoOf(inline_info).GetMethodIndex();
   }
 
+  // Returns the dex registers for `stack_map`, ignoring any inlined dex registers.
   ALWAYS_INLINE DexRegisterMap GetDexRegisterMapOf(StackMap stack_map) const {
-    if (stack_map.HasDexRegisterMap()) {
-      DexRegisterMap map(number_of_dex_registers_, DexRegisterLocation::Invalid());
-      DecodeDexRegisterMap(stack_map.Row(), /* first_dex_register= */ 0, &map);
-      return map;
-    }
-    return DexRegisterMap(0, DexRegisterLocation::None());
+    return GetDexRegisterMapOf(stack_map, /* first= */ 0, number_of_dex_registers_);
   }
 
+  // Returns the dex register map of `inline_info`, and just those registers.
   ALWAYS_INLINE DexRegisterMap GetInlineDexRegisterMapOf(StackMap stack_map,
                                                          InlineInfo inline_info) const {
     if (stack_map.HasDexRegisterMap()) {
@@ -381,6 +381,17 @@
           ? number_of_dex_registers_
           : inline_infos_.GetRow(inline_info.Row() - 1).GetNumberOfDexRegisters();
       uint32_t last = inline_info.GetNumberOfDexRegisters();
+      return GetDexRegisterMapOf(stack_map, first, last);
+    }
+    return DexRegisterMap(0, DexRegisterLocation::None());
+  }
+
+  // Returns the dex register map of `stack_map` in the range the range [first, last).
+  ALWAYS_INLINE DexRegisterMap GetDexRegisterMapOf(StackMap stack_map,
+                                                   uint32_t first,
+                                                   uint32_t last) const {
+    if (stack_map.HasDexRegisterMap()) {
+      DCHECK_LE(first, last);
       DexRegisterMap map(last - first, DexRegisterLocation::Invalid());
       DecodeDexRegisterMap(stack_map.Row(), first, &map);
       return map;
@@ -409,12 +420,39 @@
     return stack_maps_.GetInvalidRow();
   }
 
-  // Searches the stack map list backwards because catch stack maps are stored at the end.
-  StackMap GetCatchStackMapForDexPc(uint32_t dex_pc) const {
+  StackMap GetCatchStackMapForDexPc(ArrayRef<const uint32_t> dex_pcs) const {
+    // Searches the stack map list backwards because catch stack maps are stored at the end.
     for (size_t i = GetNumberOfStackMaps(); i > 0; --i) {
       StackMap stack_map = GetStackMapAt(i - 1);
-      if (stack_map.GetDexPc() == dex_pc && stack_map.GetKind() == StackMap::Kind::Catch) {
-        return stack_map;
+      if (UNLIKELY(stack_map.GetKind() != StackMap::Kind::Catch)) {
+        // Early break since we should have catch stack maps only at the end.
+        if (kIsDebugBuild) {
+          for (size_t j = i - 1; j > 0; --j) {
+            DCHECK(GetStackMapAt(j - 1).GetKind() != StackMap::Kind::Catch);
+          }
+        }
+        break;
+      }
+
+      // Both the handler dex_pc and all of the inline dex_pcs have to match i.e. we want dex_pcs to
+      // be [stack_map_dex_pc, inline_dex_pc_1, ..., inline_dex_pc_n].
+      if (stack_map.GetDexPc() != dex_pcs.front()) {
+        continue;
+      }
+
+      const BitTableRange<InlineInfo>& inline_infos = GetInlineInfosOf(stack_map);
+      if (inline_infos.size() == dex_pcs.size() - 1) {
+        bool matching_dex_pcs = true;
+        for (size_t inline_info_index = 0; inline_info_index < inline_infos.size();
+             ++inline_info_index) {
+          if (inline_infos[inline_info_index].GetDexPc() != dex_pcs[inline_info_index + 1]) {
+            matching_dex_pcs = false;
+            break;
+          }
+        }
+        if (matching_dex_pcs) {
+          return stack_map;
+        }
       }
     }
     return stack_maps_.GetInvalidRow();
@@ -449,6 +487,14 @@
     return (*code_info_data & kIsBaseline) != 0;
   }
 
+  ALWAYS_INLINE static bool IsDebuggable(const uint8_t* code_info_data) {
+    return (*code_info_data & kIsDebuggable) != 0;
+  }
+
+  uint32_t GetNumberOfDexRegisters() {
+    return number_of_dex_registers_;
+  }
+
  private:
   // Scan backward to determine dex register locations at given stack map.
   void DecodeDexRegisterMap(uint32_t stack_map_index,
@@ -495,11 +541,18 @@
   enum Flags {
     kHasInlineInfo = 1 << 0,
     kIsBaseline = 1 << 1,
+    kIsDebuggable = 1 << 2,
   };
 
   // The CodeInfo starts with sequence of variable-length bit-encoded integers.
+  // (Please see kVarintMax for more details about encoding).
   static constexpr size_t kNumHeaders = 7;
-  uint32_t flags_ = 0;      // Note that the space is limited to three bits.
+  // Note that the space for flags is limited to three bits. We use a custom encoding where we
+  // encode the value inline if it is less than kVarintMax. We want to access flags without
+  // decoding the entire CodeInfo header so the value of flags cannot be more than kVarintMax.
+  // See IsDebuggable / IsBaseline / HasInlineInfo on how we access flags_ without decoding the
+  // header.
+  uint32_t flags_ = 0;
   uint32_t code_size_ = 0;  // The size of native PC range in bytes.
   uint32_t packed_frame_size_ = 0;  // Frame size in kStackAlignment units.
   uint32_t core_spill_mask_ = 0;
diff --git a/runtime/startup_completed_task.cc b/runtime/startup_completed_task.cc
new file mode 100644
index 0000000..9709965
--- /dev/null
+++ b/runtime/startup_completed_task.cc
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "startup_completed_task.h"
+
+#include "base/systrace.h"
+#include "class_linker.h"
+#include "gc/heap.h"
+#include "gc/scoped_gc_critical_section.h"
+#include "gc/space/image_space.h"
+#include "gc/space/space-inl.h"
+#include "handle_scope-inl.h"
+#include "linear_alloc-inl.h"
+#include "mirror/dex_cache.h"
+#include "mirror/object-inl.h"
+#include "obj_ptr.h"
+#include "runtime_image.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread.h"
+#include "thread_list.h"
+
+namespace art {
+
+class UnlinkStartupDexCacheVisitor : public DexCacheVisitor {
+ public:
+  UnlinkStartupDexCacheVisitor() {}
+
+  void Visit(ObjPtr<mirror::DexCache> dex_cache)
+      REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) override {
+    dex_cache->UnlinkStartupCaches();
+  }
+};
+
+void StartupCompletedTask::Run(Thread* self) {
+  Runtime* const runtime = Runtime::Current();
+  if (runtime->NotifyStartupCompleted()) {
+    // Maybe generate a runtime app image. If the runtime is debuggable, boot
+    // classpath classes can be dynamically changed, so don't bother generating an
+    // image.
+    if (!runtime->IsJavaDebuggable()) {
+      std::string compiler_filter;
+      std::string compilation_reason;
+      runtime->GetAppInfo()->GetPrimaryApkOptimizationStatus(&compiler_filter, &compilation_reason);
+      CompilerFilter::Filter filter;
+      if (CompilerFilter::ParseCompilerFilter(compiler_filter.c_str(), &filter) &&
+          !CompilerFilter::IsAotCompilationEnabled(filter)) {
+        std::string error_msg;
+        if (!RuntimeImage::WriteImageToDisk(&error_msg)) {
+          LOG(DEBUG) << "Could not write temporary image to disk " << error_msg;
+        }
+      }
+    }
+
+    ScopedObjectAccess soa(self);
+    DeleteStartupDexCaches(self, /* called_by_gc= */ false);
+  }
+
+  // Delete the thread pool used for app image loading since startup is assumed to be completed.
+  ScopedTrace trace2("Delete thread pool");
+  Runtime::Current()->DeleteThreadPool();
+}
+
+void StartupCompletedTask::DeleteStartupDexCaches(Thread* self, bool called_by_gc) {
+  VLOG(startup) << "StartupCompletedTask running";
+  Runtime* const runtime = Runtime::Current();
+
+  ScopedTrace trace("Releasing dex caches and app image spaces metadata");
+
+  static struct EmptyClosure : Closure {
+    void Run([[maybe_unused]] Thread* thread) override {}
+  } closure;
+
+  // Fetch the startup linear alloc so no other thread tries to allocate there.
+  std::unique_ptr<LinearAlloc> startup_linear_alloc(runtime->ReleaseStartupLinearAlloc());
+  // No thread could be allocating arrays or accessing dex caches when this
+  // thread has mutator-lock held exclusively.
+  bool run_checkpoints = !Locks::mutator_lock_->IsExclusiveHeld(self);
+
+  // Request a checkpoint to make sure all threads see we have started up and
+  // won't allocate in the startup linear alloc. Without this checkpoint what
+  // could happen is (T0 == self):
+  // 1) T1 fetches startup alloc, allocates an array there.
+  // 2) T0 goes over the dex caches, clear dex cache arrays in the startup alloc.
+  // 3) T1 sets the dex cache array from startup alloc in a dex cache.
+  // 4) T0 releases startup alloc.
+  //
+  // With this checkpoint, 3) cannot happen as T0 waits for T1 to reach the
+  // checkpoint.
+  if (run_checkpoints) {
+    runtime->GetThreadList()->RunCheckpoint(&closure);
+  }
+
+  {
+    UnlinkStartupDexCacheVisitor visitor;
+    ReaderMutexLock mu(self, *Locks::dex_lock_);
+    runtime->GetClassLinker()->VisitDexCaches(&visitor);
+  }
+
+
+  // Request a checkpoint to make sure no threads are:
+  // - accessing the image space metadata section when we madvise it
+  // - accessing dex caches when we free them
+  if (run_checkpoints) {
+    runtime->GetThreadList()->RunCheckpoint(&closure);
+  }
+
+  // If this isn't the GC calling `DeleteStartupDexCaches` and a GC may be
+  // running, wait for it to be complete. We don't want it to see these dex
+  // caches.
+  if (!called_by_gc) {
+    runtime->GetHeap()->WaitForGcToComplete(gc::kGcCauseDeletingDexCacheArrays, self);
+  }
+
+  // At this point, we know no other thread can see the arrays, nor the GC. So
+  // we can safely release them.
+  for (gc::space::ContinuousSpace* space : runtime->GetHeap()->GetContinuousSpaces()) {
+    if (space->IsImageSpace()) {
+      gc::space::ImageSpace* image_space = space->AsImageSpace();
+      if (image_space->GetImageHeader().IsAppImage()) {
+        image_space->ReleaseMetadata();
+      }
+    }
+  }
+
+  if (startup_linear_alloc != nullptr) {
+    ScopedTrace trace2("Delete startup linear alloc");
+    ArenaPool* arena_pool = startup_linear_alloc->GetArenaPool();
+    startup_linear_alloc.reset();
+    arena_pool->TrimMaps();
+  }
+}
+
+}  // namespace art
diff --git a/runtime/startup_completed_task.h b/runtime/startup_completed_task.h
new file mode 100644
index 0000000..8077561
--- /dev/null
+++ b/runtime/startup_completed_task.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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_RUNTIME_STARTUP_COMPLETED_TASK_H_
+#define ART_RUNTIME_STARTUP_COMPLETED_TASK_H_
+
+#include "gc/task_processor.h"
+
+namespace art {
+
+class Thread;
+
+class StartupCompletedTask : public gc::HeapTask {
+ public:
+  explicit StartupCompletedTask(uint64_t target_run_time) : gc::HeapTask(target_run_time) {}
+
+  void Run(Thread* self) override;
+  static void DeleteStartupDexCaches(Thread* self, bool called_by_gc)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_STARTUP_COMPLETED_TASK_H_
diff --git a/runtime/string_builder_append.cc b/runtime/string_builder_append.cc
index 85b70eb..0083b91 100644
--- a/runtime/string_builder_append.cc
+++ b/runtime/string_builder_append.cc
@@ -20,9 +20,11 @@
 #include "base/logging.h"
 #include "common_throws.h"
 #include "gc/heap.h"
+#include "mirror/array-inl.h"
 #include "mirror/string-alloc-inl.h"
 #include "obj_ptr-inl.h"
 #include "runtime.h"
+#include "well_known_classes.h"
 
 namespace art {
 
@@ -60,6 +62,11 @@
     return new_string->GetLength() - (data - new_string->GetValue());
   }
 
+  template <typename CharType>
+  CharType* AppendFpArg(ObjPtr<mirror::String> new_string,
+                        CharType* data,
+                        size_t fp_arg_index) const REQUIRES_SHARED(Locks::mutator_lock_);
+
   template <typename CharType, size_t size>
   static CharType* AppendLiteral(ObjPtr<mirror::String> new_string,
                                  CharType* data,
@@ -75,6 +82,8 @@
                                CharType* data,
                                int64_t value) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  int32_t ConvertFpArgs() REQUIRES_SHARED(Locks::mutator_lock_);
+
   template <typename CharType>
   void StoreData(ObjPtr<mirror::String> new_string, CharType* data) const
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -93,6 +102,15 @@
   // References are moved to the handle scope during CalculateLengthWithFlag().
   StackHandleScope<kMaxArgs> hs_;
 
+  // We convert float/double values using jdk.internal.math.FloatingDecimal which uses
+  // a thread-local converter under the hood. As we may have more than one
+  // float/double argument, we need to copy the data out of the converter.
+  // Maximum number of characters is 26. See BinaryToASCIIBuffer.buffer in FloatingDecimal.java .
+  // (This is more than enough for the `ExceptionalBinaryToASCIIBuffer` cases.)
+  static constexpr size_t kBinaryToASCIIBufferSize = 26;
+  uint8_t converted_fp_args_[kMaxArgs][kBinaryToASCIIBufferSize];
+  int32_t converted_fp_arg_lengths_[kMaxArgs];
+
   // The length and flag to store when the AppendBuilder is used as a pre-fence visitor.
   int32_t length_with_flag_ = 0u;
 };
@@ -142,6 +160,18 @@
   return log10_value_estimate + adjustment;
 }
 
+template <typename CharType>
+inline CharType* StringBuilderAppend::Builder::AppendFpArg(ObjPtr<mirror::String> new_string,
+                                                           CharType* data,
+                                                           size_t fp_arg_index) const {
+  DCHECK_LE(fp_arg_index, std::size(converted_fp_args_));
+  const uint8_t* src = converted_fp_args_[fp_arg_index];
+  size_t length = converted_fp_arg_lengths_[fp_arg_index];
+  DCHECK_LE(length, kBinaryToASCIIBufferSize);
+  DCHECK_LE(length, RemainingSpace(new_string, data));
+  return std::copy_n(src, length, data);
+}
+
 template <typename CharType, size_t size>
 inline CharType* StringBuilderAppend::Builder::AppendLiteral(ObjPtr<mirror::String> new_string,
                                                              CharType* data,
@@ -204,10 +234,111 @@
   return data + length;
 }
 
+int32_t StringBuilderAppend::Builder::ConvertFpArgs() {
+  int32_t fp_args_length = 0u;
+  const uint32_t* current_arg = args_;
+  size_t fp_arg_index = 0u;
+  for (uint32_t f = format_; f != 0u; f >>= kBitsPerArg) {
+    DCHECK_LE(f & kArgMask, static_cast<uint32_t>(Argument::kLast));
+    bool fp_arg = false;
+    ObjPtr<mirror::Object> converter;
+    switch (static_cast<Argument>(f & kArgMask)) {
+      case Argument::kString:
+      case Argument::kBoolean:
+      case Argument::kChar:
+      case Argument::kInt:
+        break;
+      case Argument::kLong: {
+        current_arg = AlignUp(current_arg, sizeof(int64_t));
+        ++current_arg;  // Skip the low word, let the common code skip the high word.
+        break;
+      }
+      case Argument::kFloat: {
+        fp_arg = true;
+        float arg = bit_cast<float>(*current_arg);
+        converter = WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F
+            ->InvokeStatic<'L', 'F'>(hs_.Self(), arg);
+        break;
+      }
+      case Argument::kDouble: {
+        fp_arg = true;
+        current_arg = AlignUp(current_arg, sizeof(int64_t));
+        double arg = bit_cast<double>(
+            static_cast<uint64_t>(current_arg[0]) + (static_cast<uint64_t>(current_arg[1]) << 32));
+        converter = WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D
+            ->InvokeStatic<'L', 'D'>(hs_.Self(), arg);
+        ++current_arg;  // Skip the low word, let the common code skip the high word.
+        break;
+      }
+      case Argument::kStringBuilder:
+      case Argument::kCharArray:
+      case Argument::kObject:
+        LOG(FATAL) << "Unimplemented arg format: 0x" << std::hex
+            << (f & kArgMask) << " full format: 0x" << std::hex << format_;
+        UNREACHABLE();
+      default:
+        LOG(FATAL) << "Unexpected arg format: 0x" << std::hex
+            << (f & kArgMask) << " full format: 0x" << std::hex << format_;
+        UNREACHABLE();
+    }
+    if (fp_arg) {
+      // If we see an exception (presumably OOME or SOE), keep it as is, even
+      // though it may be confusing to see the stack trace for FP argument
+      // conversion continue at the StringBuilder.toString() invoke location.
+      DCHECK_EQ(converter == nullptr, hs_.Self()->IsExceptionPending());
+      if (UNLIKELY(converter == nullptr)) {
+        return -1;
+      }
+      ArtField* btab_buffer_field =
+          WellKnownClasses::jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer;
+      int32_t length;
+      if (converter->GetClass() == btab_buffer_field->GetDeclaringClass()) {
+        // Call `converter.getChars(converter.buffer)`.
+        StackHandleScope<1u> hs2(hs_.Self());
+        Handle<mirror::CharArray> buffer =
+            hs2.NewHandle(btab_buffer_field->GetObj<mirror::CharArray>(converter));
+        DCHECK(buffer != nullptr);
+        length = WellKnownClasses::jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars
+            ->InvokeInstance<'I', 'L'>(hs_.Self(), converter, buffer.Get());
+        if (UNLIKELY(hs_.Self()->IsExceptionPending())) {
+          return -1;
+        }
+        // The converted string is now at the front of the buffer.
+        DCHECK_GT(length, 0);
+        DCHECK_LE(length, buffer->GetLength());
+        DCHECK_LE(static_cast<size_t>(length), std::size(converted_fp_args_[0]));
+        DCHECK(mirror::String::AllASCII(buffer->GetData(), length));
+        std::copy_n(buffer->GetData(), length, converted_fp_args_[fp_arg_index]);
+      } else {
+        ArtField* ebtab_image_field = WellKnownClasses::
+            jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image;
+        DCHECK(converter->GetClass() == ebtab_image_field->GetDeclaringClass());
+        ObjPtr<mirror::String> converted = ebtab_image_field->GetObj<mirror::String>(converter);
+        DCHECK(converted != nullptr);
+        length = converted->GetLength();
+        if (mirror::kUseStringCompression) {
+          DCHECK(converted->IsCompressed());
+          memcpy(converted_fp_args_[fp_arg_index], converted->GetValueCompressed(), length);
+        } else {
+          DCHECK(mirror::String::AllASCII(converted->GetValue(), length));
+          std::copy_n(converted->GetValue(), length, converted_fp_args_[fp_arg_index]);
+        }
+      }
+      converted_fp_arg_lengths_[fp_arg_index] = length;
+      fp_args_length += length;
+      ++fp_arg_index;
+    }
+    ++current_arg;
+    DCHECK_LE(fp_arg_index, kMaxArgs);
+  }
+  return fp_args_length;
+}
+
 inline int32_t StringBuilderAppend::Builder::CalculateLengthWithFlag() {
   static_assert(static_cast<size_t>(Argument::kEnd) == 0u, "kEnd must be 0.");
   bool compressible = mirror::kUseStringCompression;
   uint64_t length = 0u;
+  bool has_fp_args = false;
   const uint32_t* current_arg = args_;
   for (uint32_t f = format_; f != 0u; f >>= kBitsPerArg) {
     DCHECK_LE(f & kArgMask, static_cast<uint32_t>(Argument::kLast));
@@ -243,12 +374,19 @@
         ++current_arg;  // Skip the low word, let the common code skip the high word.
         break;
       }
+      case Argument::kDouble:
+        current_arg = AlignUp(current_arg, sizeof(int64_t));
+        ++current_arg;  // Skip the low word, let the common code skip the high word.
+        FALLTHROUGH_INTENDED;
+      case Argument::kFloat:
+        // Conversion shall be performed in a separate pass because it calls back to
+        // managed code and we need to convert reference arguments to `Handle<>`s first.
+        has_fp_args = true;
+        break;
 
       case Argument::kStringBuilder:
       case Argument::kCharArray:
       case Argument::kObject:
-      case Argument::kFloat:
-      case Argument::kDouble:
         LOG(FATAL) << "Unimplemented arg format: 0x" << std::hex
             << (f & kArgMask) << " full format: 0x" << std::hex << format_;
         UNREACHABLE();
@@ -261,6 +399,16 @@
     DCHECK_LE(hs_.NumberOfReferences(), kMaxArgs);
   }
 
+  if (UNLIKELY(has_fp_args)) {
+    // Call Java helpers to convert FP args.
+    int32_t fp_args_length = ConvertFpArgs();
+    if (fp_args_length == -1) {
+      return -1;
+    }
+    DCHECK_GT(fp_args_length, 0);
+    length += fp_args_length;
+  }
+
   if (length > std::numeric_limits<int32_t>::max()) {
     // We cannot allocate memory for the entire result.
     hs_.Self()->ThrowNewException("Ljava/lang/OutOfMemoryError;",
@@ -276,6 +424,7 @@
 inline void StringBuilderAppend::Builder::StoreData(ObjPtr<mirror::String> new_string,
                                                     CharType* data) const {
   size_t handle_index = 0u;
+  size_t fp_arg_index = 0u;
   const uint32_t* current_arg = args_;
   for (uint32_t f = format_; f != 0u; f >>= kBitsPerArg) {
     DCHECK_LE(f & kArgMask, static_cast<uint32_t>(Argument::kLast));
@@ -315,11 +464,18 @@
         ++current_arg;  // Skip the low word, let the common code skip the high word.
         break;
       }
+      case Argument::kDouble:
+        current_arg = AlignUp(current_arg, sizeof(int64_t));
+        ++current_arg;  // Skip the low word, let the common code skip the high word.
+        FALLTHROUGH_INTENDED;
+      case Argument::kFloat: {
+        data = AppendFpArg(new_string, data, fp_arg_index);
+        ++fp_arg_index;
+        break;
+      }
 
       case Argument::kStringBuilder:
       case Argument::kCharArray:
-      case Argument::kFloat:
-      case Argument::kDouble:
         LOG(FATAL) << "Unimplemented arg format: 0x" << std::hex
             << (f & kArgMask) << " full format: 0x" << std::hex << format_;
         UNREACHABLE();
@@ -330,6 +486,7 @@
     }
     ++current_arg;
     DCHECK_LE(handle_index, hs_.NumberOfReferences());
+    DCHECK_LE(fp_arg_index, std::size(converted_fp_args_));
   }
   DCHECK_EQ(RemainingSpace(new_string, data), 0u) << std::hex << format_;
 }
diff --git a/runtime/subtype_check.h b/runtime/subtype_check.h
index ca1feb5..90b9b57 100644
--- a/runtime/subtype_check.h
+++ b/runtime/subtype_check.h
@@ -382,12 +382,12 @@
     if (UNLIKELY(!klass->HasSuperClass())) {
       // Object root always goes directly from Uninitialized -> Assigned.
 
-      const SubtypeCheckInfo root_sci = GetSubtypeCheckInfo(klass);
+      SubtypeCheckInfo root_sci = GetSubtypeCheckInfo(klass);
       if (root_sci.GetState() != SubtypeCheckInfo::kUninitialized) {
         return root_sci;  // No change needed.
       }
 
-      const SubtypeCheckInfo new_root_sci = root_sci.CreateRoot();
+      SubtypeCheckInfo new_root_sci = root_sci.CreateRoot();
       SetSubtypeCheckInfo(klass, new_root_sci);
 
       // The object root is always in the Uninitialized|Assigned state.
@@ -571,9 +571,7 @@
     DCHECK_EQ(depth, klass->Depth());
     SubtypeCheckBitsAndStatus current_bits_and_status = ReadField(klass);
 
-    const SubtypeCheckInfo current =
-        SubtypeCheckInfo::Create(current_bits_and_status.subtype_check_info_, depth);
-    return current;
+    return SubtypeCheckInfo::Create(current_bits_and_status.subtype_check_info_, depth);
   }
 
   static void SetSubtypeCheckInfo(ClassPtr klass, const SubtypeCheckInfo& new_sci)
diff --git a/runtime/subtype_check_info.h b/runtime/subtype_check_info.h
index d734557..eef68d7 100644
--- a/runtime/subtype_check_info.h
+++ b/runtime/subtype_check_info.h
@@ -153,7 +153,7 @@
   // Create from the depth and the bitstring+of state.
   // This is done for convenience to avoid passing in "depth" everywhere,
   // since our current state is almost always a function of depth.
-  static SubtypeCheckInfo Create(SubtypeCheckBits compressed_value, size_t depth) {
+  static SubtypeCheckInfo Create(const SubtypeCheckBits& compressed_value, size_t depth) {
     SubtypeCheckInfo io;
     io.depth_ = depth;
     io.bitstring_and_of_ = compressed_value;
@@ -168,9 +168,8 @@
   //
   // Normally, return kSubtypeOf or kNotSubtypeOf.
   Result IsSubtypeOf(const SubtypeCheckInfo& target) {
-    if (target.GetState() != SubtypeCheckInfo::kAssigned) {
-      return Result::kUnknownSubtypeOf;
-    } else if (GetState() == SubtypeCheckInfo::kUninitialized) {
+    if (target.GetState() != SubtypeCheckInfo::kAssigned ||
+        GetState() == SubtypeCheckInfo::kUninitialized) {
       return Result::kUnknownSubtypeOf;
     }
 
diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h
index 324cd37..e4b3f64 100644
--- a/runtime/thread-inl.h
+++ b/runtime/thread-inl.h
@@ -24,9 +24,10 @@
 #include "base/casts.h"
 #include "base/mutex-inl.h"
 #include "base/time_utils.h"
+#include "indirect_reference_table.h"
 #include "jni/jni_env_ext.h"
 #include "managed_stack-inl.h"
-#include "obj_ptr.h"
+#include "obj_ptr-inl.h"
 #include "suspend_reason.h"
 #include "thread-current-inl.h"
 #include "thread_pool.h"
@@ -34,11 +35,37 @@
 namespace art {
 
 // Quickly access the current thread from a JNIEnv.
-static inline Thread* ThreadForEnv(JNIEnv* env) {
+inline Thread* Thread::ForEnv(JNIEnv* env) {
   JNIEnvExt* full_env(down_cast<JNIEnvExt*>(env));
   return full_env->GetSelf();
 }
 
+inline ObjPtr<mirror::Object> Thread::DecodeJObject(jobject obj) const {
+  if (obj == nullptr) {
+    return nullptr;
+  }
+  IndirectRef ref = reinterpret_cast<IndirectRef>(obj);
+  if (LIKELY(IndirectReferenceTable::IsJniTransitionOrLocalReference(ref))) {
+    // For JNI transitions, the `jclass` for a static method points to the
+    // `CompressedReference<>` in the `ArtMethod::declaring_class_` and other `jobject`
+    // arguments point to spilled stack references but a `StackReference<>` is just
+    // a subclass of `CompressedReference<>`. Local references also point to
+    // a `CompressedReference<>` encapsulated in a `GcRoot<>`.
+    if (kIsDebugBuild && IndirectReferenceTable::GetIndirectRefKind(ref) == kJniTransition) {
+      CHECK(IsJniTransitionReference(obj));
+    }
+    auto* cref = IndirectReferenceTable::ClearIndirectRefKind<
+        mirror::CompressedReference<mirror::Object>*>(ref);
+    ObjPtr<mirror::Object> result = cref->AsMirrorPtr();
+    if (kIsDebugBuild && IndirectReferenceTable::GetIndirectRefKind(ref) != kJniTransition) {
+      CHECK_EQ(result, tlsPtr_.jni_env->locals_.Get(ref));
+    }
+    return result;
+  } else {
+    return DecodeGlobalJObject(obj);
+  }
+}
+
 inline void Thread::AllowThreadSuspension() {
   CheckSuspend();
   // Invalidate the current thread's object pointers (ObjPtr) to catch possible moving GC bugs due
@@ -373,7 +400,7 @@
 }
 
 inline bool Thread::GetWeakRefAccessEnabled() const {
-  CHECK(kUseReadBarrier);
+  DCHECK(gUseReadBarrier);
   DCHECK(this == Thread::Current());
   WeakRefAccessState s = tls32_.weak_ref_access_enabled.load(std::memory_order_relaxed);
   if (LIKELY(s == WeakRefAccessState::kVisiblyEnabled)) {
@@ -428,7 +455,8 @@
                                        int delta,
                                        AtomicInteger* suspend_barrier,
                                        SuspendReason reason) {
-  if (delta > 0 && ((kUseReadBarrier && this != self) || suspend_barrier != nullptr)) {
+  if (delta > 0 &&
+      (((gUseUserfaultfd || gUseReadBarrier) && this != self) || suspend_barrier != nullptr)) {
     // When delta > 0 (requesting a suspend), ModifySuspendCountInternal() may fail either if
     // active_suspend_barriers is full or we are in the middle of a thread flip. Retry in a loop.
     while (true) {
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 78ba26d..6b1934c 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -23,12 +23,6 @@
 #include <sys/resource.h>
 #include <sys/time.h>
 
-#if __has_feature(hwaddress_sanitizer)
-#include <sanitizer/hwasan_interface.h>
-#else
-#define __hwasan_tag_pointer(p, t) (p)
-#endif
-
 #include <algorithm>
 #include <atomic>
 #include <bitset>
@@ -41,6 +35,8 @@
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 
+#include "unwindstack/AndroidUnwinder.h"
+
 #include "arch/context-inl.h"
 #include "arch/context.h"
 #include "art_field-inl.h"
@@ -75,6 +71,7 @@
 #include "handle_scope-inl.h"
 #include "indirect_reference_table-inl.h"
 #include "instrumentation.h"
+#include "intern_table.h"
 #include "interpreter/interpreter.h"
 #include "interpreter/shadow_frame-inl.h"
 #include "java_frame_root_info.h"
@@ -84,6 +81,7 @@
 #include "mirror/class_loader.h"
 #include "mirror/object_array-alloc-inl.h"
 #include "mirror/object_array-inl.h"
+#include "mirror/stack_frame_info.h"
 #include "mirror/stack_trace_element.h"
 #include "monitor.h"
 #include "monitor_objects_stack_visitor.h"
@@ -110,9 +108,10 @@
 #include "stack_map.h"
 #include "thread-inl.h"
 #include "thread_list.h"
+#include "trace.h"
 #include "verifier/method_verifier.h"
 #include "verify_object.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
 
 #if ART_USE_FUTEXES
 #include "linux/futex.h"
@@ -125,12 +124,15 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic error "-Wconversion"
 
+extern "C" __attribute__((weak)) void* __hwasan_tag_pointer(const volatile void* p,
+                                                            unsigned char tag);
+
 namespace art {
 
 using android::base::StringAppendV;
 using android::base::StringPrintf;
 
-extern "C" NO_RETURN void artDeoptimize(Thread* self);
+extern "C" NO_RETURN void artDeoptimize(Thread* self, bool skip_method_exit_callbacks);
 
 bool Thread::is_started_ = false;
 pthread_key_t Thread::pthread_key_self_;
@@ -166,7 +168,7 @@
 void UpdateReadBarrierEntrypoints(QuickEntryPoints* qpoints, bool is_active);
 
 void Thread::SetIsGcMarkingAndUpdateEntrypoints(bool is_marking) {
-  CHECK(kUseReadBarrier);
+  CHECK(gUseReadBarrier);
   tls32_.is_gc_marking = is_marking;
   UpdateReadBarrierEntrypoints(&tlsPtr_.quick_entrypoints, /* is_active= */ is_marking);
 }
@@ -210,7 +212,9 @@
   JValue GetReturnValue() const { return ret_val_; }
   bool IsReference() const { return is_reference_; }
   bool GetFromCode() const { return from_code_; }
-  ObjPtr<mirror::Throwable> GetPendingException() const { return pending_exception_; }
+  ObjPtr<mirror::Throwable> GetPendingException() const REQUIRES_SHARED(Locks::mutator_lock_) {
+    return pending_exception_;
+  }
   DeoptimizationContextRecord* GetLink() const { return link_; }
   mirror::Object** GetReturnValueAsGCRoot() {
     DCHECK(is_reference_);
@@ -272,6 +276,7 @@
                                        ObjPtr<mirror::Throwable> exception,
                                        bool from_code,
                                        DeoptimizationMethodType method_type) {
+  DCHECK(exception != Thread::GetDeoptimizationException());
   DeoptimizationContextRecord* record = new DeoptimizationContextRecord(
       return_value,
       is_reference,
@@ -433,15 +438,18 @@
   tlsPtr_.stacked_shadow_frame_record = record;
 }
 
-ShadowFrame* Thread::PopStackedShadowFrame(StackedShadowFrameType type, bool must_be_present) {
+ShadowFrame* Thread::MaybePopDeoptimizedStackedShadowFrame() {
   StackedShadowFrameRecord* record = tlsPtr_.stacked_shadow_frame_record;
-  if (must_be_present) {
-    DCHECK(record != nullptr);
-  } else {
-    if (record == nullptr || record->GetType() != type) {
-      return nullptr;
-    }
+  if (record == nullptr ||
+      record->GetType() != StackedShadowFrameType::kDeoptimizationShadowFrame) {
+    return nullptr;
   }
+  return PopStackedShadowFrame();
+}
+
+ShadowFrame* Thread::PopStackedShadowFrame() {
+  StackedShadowFrameRecord* record = tlsPtr_.stacked_shadow_frame_record;
+  DCHECK_NE(record, nullptr);
   tlsPtr_.stacked_shadow_frame_record = record->GetLink();
   ShadowFrame* shadow_frame = record->GetShadowFrame();
   delete record;
@@ -531,7 +539,7 @@
     return shadow_frame;
   }
   VLOG(deopt) << "Create pre-deopted ShadowFrame for " << ArtMethod::PrettyMethod(method);
-  shadow_frame = ShadowFrame::CreateDeoptimizedFrame(num_vregs, nullptr, method, dex_pc);
+  shadow_frame = ShadowFrame::CreateDeoptimizedFrame(num_vregs, method, dex_pc);
   FrameIdToShadowFrame* record = FrameIdToShadowFrame::Create(frame_id,
                                                               shadow_frame,
                                                               tlsPtr_.frame_id_to_shadow_frame,
@@ -601,6 +609,10 @@
   env->DeleteGlobalRef(old_jpeer);
 }
 
+void* Thread::CreateCallbackWithUffdGc(void* arg) {
+  return Thread::CreateCallback(arg);
+}
+
 void* Thread::CreateCallback(void* arg) {
   Thread* self = reinterpret_cast<Thread*>(arg);
   Runtime* runtime = Runtime::Current();
@@ -634,7 +646,7 @@
     self->DeleteJPeer(self->GetJniEnv());
     self->SetThreadName(self->GetThreadName()->ToModifiedUtf8().c_str());
 
-    ArtField* priorityField = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority);
+    ArtField* priorityField = WellKnownClasses::java_lang_Thread_priority;
     self->SetNativePriority(priorityField->GetInt(self->tlsPtr_.opeer));
 
     runtime->GetRuntimeCallbacks()->ThreadStart(self);
@@ -642,8 +654,7 @@
     // Unpark ourselves if the java peer was unparked before it started (see
     // b/28845097#comment49 for more information)
 
-    ArtField* unparkedField = jni::DecodeArtField(
-        WellKnownClasses::java_lang_Thread_unparkedBeforeStart);
+    ArtField* unparkedField = WellKnownClasses::java_lang_Thread_unparkedBeforeStart;
     bool should_unpark = false;
     {
       // Hold the lock here, so that if another thread calls unpark before the thread starts
@@ -657,19 +668,17 @@
     }
     // Invoke the 'run' method of our java.lang.Thread.
     ObjPtr<mirror::Object> receiver = self->tlsPtr_.opeer;
-    jmethodID mid = WellKnownClasses::java_lang_Thread_run;
-    ScopedLocalRef<jobject> ref(soa.Env(), soa.AddLocalReference<jobject>(receiver));
-    InvokeVirtualOrInterfaceWithJValues(soa, ref.get(), mid, nullptr);
+    WellKnownClasses::java_lang_Thread_run->InvokeVirtual<'V'>(self, receiver);
   }
   // Detach and delete self.
-  Runtime::Current()->GetThreadList()->Unregister(self);
+  Runtime::Current()->GetThreadList()->Unregister(self, /* should_run_callbacks= */ true);
 
   return nullptr;
 }
 
 Thread* Thread::FromManagedThread(const ScopedObjectAccessAlreadyRunnable& soa,
                                   ObjPtr<mirror::Object> thread_peer) {
-  ArtField* f = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_nativePeer);
+  ArtField* f = WellKnownClasses::java_lang_Thread_nativePeer;
   Thread* result = reinterpret_cast64<Thread*>(f->GetLong(thread_peer));
   // Check that if we have a result it is either suspended or we hold the thread_list_lock_
   // to stop it from going away.
@@ -786,7 +795,7 @@
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wframe-larger-than="
     NO_INLINE
-    static void Touch(uintptr_t target) {
+    __attribute__((no_sanitize("memtag"))) static void Touch(uintptr_t target) {
       volatile size_t zero = 0;
       // Use a large local volatile array to ensure a large frame size. Do not use anything close
       // to a full page for ASAN. It would be nice to ensure the frame size is at most a page, but
@@ -803,7 +812,8 @@
       volatile char space[kPageSize - (kAsanMultiplier * 256)] __attribute__((uninitialized));
       char sink ATTRIBUTE_UNUSED = space[zero];  // NOLINT
       // Remove tag from the pointer. Nop in non-hwasan builds.
-      uintptr_t addr = reinterpret_cast<uintptr_t>(__hwasan_tag_pointer(space, 0));
+      uintptr_t addr = reinterpret_cast<uintptr_t>(
+          __hwasan_tag_pointer != nullptr ? __hwasan_tag_pointer(space, 0) : space);
       if (addr >= target + kPageSize) {
         Touch(target);
       }
@@ -827,6 +837,22 @@
   madvise(pregion, unwanted_size, MADV_DONTNEED);
 }
 
+template <bool kSupportTransaction>
+static void SetNativePeer(ObjPtr<mirror::Object> java_peer, Thread* thread)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ArtField* field = WellKnownClasses::java_lang_Thread_nativePeer;
+  if (kSupportTransaction && Runtime::Current()->IsActiveTransaction()) {
+    field->SetLong</*kTransactionActive=*/ true>(java_peer, reinterpret_cast<jlong>(thread));
+  } else {
+    field->SetLong</*kTransactionActive=*/ false>(java_peer, reinterpret_cast<jlong>(thread));
+  }
+}
+
+static void SetNativePeer(JNIEnv* env, jobject java_peer, Thread* thread) {
+  ScopedObjectAccess soa(env);
+  SetNativePeer</*kSupportTransaction=*/ false>(soa.Decode<mirror::Object>(java_peer), thread);
+}
+
 void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
   CHECK(java_peer != nullptr);
   Thread* self = static_cast<JNIEnvExt*>(env)->GetSelf();
@@ -834,7 +860,7 @@
   if (VLOG_IS_ON(threads)) {
     ScopedObjectAccess soa(env);
 
-    ArtField* f = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_name);
+    ArtField* f = WellKnownClasses::java_lang_Thread_name;
     ObjPtr<mirror::String> java_name =
         f->GetObject(soa.Decode<mirror::Object>(java_peer))->AsString();
     std::string thread_name;
@@ -873,8 +899,7 @@
 
   // Thread.start is synchronized, so we know that nativePeer is 0, and know that we're not racing
   // to assign it.
-  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
-                    reinterpret_cast<jlong>(child_thread));
+  SetNativePeer(env, java_peer, child_thread);
 
   // Try to allocate a JNIEnvExt for the thread. We do this here as we might be out of memory and
   // do not have a good way to report this on the child's side.
@@ -893,7 +918,8 @@
     CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size);
     pthread_create_result = pthread_create(&new_pthread,
                                            &attr,
-                                           Thread::CreateCallback,
+                                           gUseUserfaultfd ? Thread::CreateCallbackWithUffdGc
+                                                           : Thread::CreateCallback,
                                            child_thread);
     CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");
 
@@ -918,7 +944,7 @@
   delete child_thread;
   child_thread = nullptr;
   // TODO: remove from thread group?
-  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
+  SetNativePeer(env, java_peer, nullptr);
   {
     std::string msg(child_jni_env_ext.get() == nullptr ?
         StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :
@@ -982,7 +1008,10 @@
 }
 
 template <typename PeerAction>
-Thread* Thread::Attach(const char* thread_name, bool as_daemon, PeerAction peer_action) {
+Thread* Thread::Attach(const char* thread_name,
+                       bool as_daemon,
+                       PeerAction peer_action,
+                       bool should_run_callbacks) {
   Runtime* runtime = Runtime::Current();
   ScopedTrace trace("Thread::Attach");
   if (runtime == nullptr) {
@@ -1017,7 +1046,7 @@
 
   // Run the action that is acting on the peer.
   if (!peer_action(self)) {
-    runtime->GetThreadList()->Unregister(self);
+    runtime->GetThreadList()->Unregister(self, should_run_callbacks);
     // Unregister deletes self, no need to do this here.
     return nullptr;
   }
@@ -1032,7 +1061,7 @@
     self->Dump(LOG_STREAM(INFO));
   }
 
-  {
+  if (should_run_callbacks) {
     ScopedObjectAccess soa(self);
     runtime->GetRuntimeCallbacks()->ThreadStart(self);
   }
@@ -1043,7 +1072,8 @@
 Thread* Thread::Attach(const char* thread_name,
                        bool as_daemon,
                        jobject thread_group,
-                       bool create_peer) {
+                       bool create_peer,
+                       bool should_run_callbacks) {
   auto create_peer_action = [&](Thread* self) {
     // If we're the main thread, ClassLinker won't be created until after we're attached,
     // so that thread needs a two-stage attach. Regular threads don't need this hack.
@@ -1076,67 +1106,58 @@
     }
     return true;
   };
-  return Attach(thread_name, as_daemon, create_peer_action);
+  return Attach(thread_name, as_daemon, create_peer_action, should_run_callbacks);
 }
 
 Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_peer) {
   auto set_peer_action = [&](Thread* self) {
     // Install the given peer.
-    {
-      DCHECK(self == Thread::Current());
-      ScopedObjectAccess soa(self);
-      self->tlsPtr_.opeer = soa.Decode<mirror::Object>(thread_peer).Ptr();
-    }
-    self->GetJniEnv()->SetLongField(thread_peer,
-                                    WellKnownClasses::java_lang_Thread_nativePeer,
-                                    reinterpret_cast64<jlong>(self));
+    DCHECK(self == Thread::Current());
+    ScopedObjectAccess soa(self);
+    ObjPtr<mirror::Object> peer = soa.Decode<mirror::Object>(thread_peer);
+    self->tlsPtr_.opeer = peer.Ptr();
+    SetNativePeer</*kSupportTransaction=*/ false>(peer, self);
     return true;
   };
-  return Attach(thread_name, as_daemon, set_peer_action);
+  return Attach(thread_name, as_daemon, set_peer_action, /* should_run_callbacks= */ true);
 }
 
 void Thread::CreatePeer(const char* name, bool as_daemon, jobject thread_group) {
   Runtime* runtime = Runtime::Current();
   CHECK(runtime->IsStarted());
-  JNIEnv* env = tlsPtr_.jni_env;
+  Thread* self = this;
+  DCHECK_EQ(self, Thread::Current());
 
-  if (thread_group == nullptr) {
-    thread_group = runtime->GetMainThreadGroup();
-  }
-  ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF(name));
+  ScopedObjectAccess soa(self);
+  StackHandleScope<4u> hs(self);
+  DCHECK(WellKnownClasses::java_lang_ThreadGroup->IsInitialized());
+  Handle<mirror::Object> thr_group = hs.NewHandle(soa.Decode<mirror::Object>(
+      thread_group != nullptr ? thread_group : runtime->GetMainThreadGroup()));
+  Handle<mirror::String> thread_name = hs.NewHandle(
+      name != nullptr ? mirror::String::AllocFromModifiedUtf8(self, name) : nullptr);
   // Add missing null check in case of OOM b/18297817
-  if (name != nullptr && thread_name.get() == nullptr) {
-    CHECK(IsExceptionPending());
+  if (name != nullptr && UNLIKELY(thread_name == nullptr)) {
+    CHECK(self->IsExceptionPending());
     return;
   }
   jint thread_priority = GetNativePriority();
-  jboolean thread_is_daemon = as_daemon;
 
-  ScopedLocalRef<jobject> peer(env, env->AllocObject(WellKnownClasses::java_lang_Thread));
-  if (peer.get() == nullptr) {
+  DCHECK(WellKnownClasses::java_lang_Thread->IsInitialized());
+  Handle<mirror::Object> peer =
+      hs.NewHandle(WellKnownClasses::java_lang_Thread->AllocObject(self));
+  if (UNLIKELY(peer == nullptr)) {
     CHECK(IsExceptionPending());
     return;
   }
-  {
-    ScopedObjectAccess soa(this);
-    tlsPtr_.opeer = soa.Decode<mirror::Object>(peer.get()).Ptr();
-  }
-  env->CallNonvirtualVoidMethod(peer.get(),
-                                WellKnownClasses::java_lang_Thread,
-                                WellKnownClasses::java_lang_Thread_init,
-                                thread_group, thread_name.get(), thread_priority, thread_is_daemon);
-  if (IsExceptionPending()) {
+  tlsPtr_.opeer = peer.Get();
+  WellKnownClasses::java_lang_Thread_init->InvokeInstance<'V', 'L', 'L', 'I', 'Z'>(
+      self, peer.Get(), thr_group.Get(), thread_name.Get(), thread_priority, as_daemon);
+  if (self->IsExceptionPending()) {
     return;
   }
 
-  Thread* self = this;
-  DCHECK_EQ(self, Thread::Current());
-  env->SetLongField(peer.get(),
-                    WellKnownClasses::java_lang_Thread_nativePeer,
-                    reinterpret_cast64<jlong>(self));
+  SetNativePeer</*kSupportTransaction=*/ false>(peer.Get(), self);
 
-  ScopedObjectAccess soa(self);
-  StackHandleScope<1> hs(self);
   MutableHandle<mirror::String> peer_thread_name(hs.NewHandle(GetThreadName()));
   if (peer_thread_name == nullptr) {
     // The Thread constructor should have set the Thread.name to a
@@ -1144,18 +1165,16 @@
     // available (in the compiler, in tests), we manually assign the
     // fields the constructor should have set.
     if (runtime->IsActiveTransaction()) {
-      InitPeer<true>(soa,
-                     tlsPtr_.opeer,
-                     thread_is_daemon,
-                     thread_group,
-                     thread_name.get(),
+      InitPeer<true>(tlsPtr_.opeer,
+                     as_daemon,
+                     thr_group.Get(),
+                     thread_name.Get(),
                      thread_priority);
     } else {
-      InitPeer<false>(soa,
-                      tlsPtr_.opeer,
-                      thread_is_daemon,
-                      thread_group,
-                      thread_name.get(),
+      InitPeer<false>(tlsPtr_.opeer,
+                      as_daemon,
+                      thr_group.Get(),
+                      thread_name.Get(),
                       thread_priority);
     }
     peer_thread_name.Assign(GetThreadName());
@@ -1166,27 +1185,32 @@
   }
 }
 
-jobject Thread::CreateCompileTimePeer(JNIEnv* env,
-                                      const char* name,
-                                      bool as_daemon,
-                                      jobject thread_group) {
+ObjPtr<mirror::Object> Thread::CreateCompileTimePeer(const char* name,
+                                                     bool as_daemon,
+                                                     jobject thread_group) {
   Runtime* runtime = Runtime::Current();
   CHECK(!runtime->IsStarted());
+  Thread* self = this;
+  DCHECK_EQ(self, Thread::Current());
 
-  if (thread_group == nullptr) {
-    thread_group = runtime->GetMainThreadGroup();
-  }
-  ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF(name));
+  ScopedObjectAccessUnchecked soa(self);
+  StackHandleScope<3u> hs(self);
+  DCHECK(WellKnownClasses::java_lang_ThreadGroup->IsInitialized());
+  Handle<mirror::Object> thr_group = hs.NewHandle(soa.Decode<mirror::Object>(
+      thread_group != nullptr ? thread_group : runtime->GetMainThreadGroup()));
+  Handle<mirror::String> thread_name = hs.NewHandle(
+      name != nullptr ? mirror::String::AllocFromModifiedUtf8(self, name) : nullptr);
   // Add missing null check in case of OOM b/18297817
-  if (name != nullptr && thread_name.get() == nullptr) {
-    CHECK(Thread::Current()->IsExceptionPending());
+  if (name != nullptr && UNLIKELY(thread_name == nullptr)) {
+    CHECK(self->IsExceptionPending());
     return nullptr;
   }
   jint thread_priority = kNormThreadPriority;  // Always normalize to NORM priority.
-  jboolean thread_is_daemon = as_daemon;
 
-  ScopedLocalRef<jobject> peer(env, env->AllocObject(WellKnownClasses::java_lang_Thread));
-  if (peer.get() == nullptr) {
+  DCHECK(WellKnownClasses::java_lang_Thread->IsInitialized());
+  Handle<mirror::Object> peer = hs.NewHandle(
+      WellKnownClasses::java_lang_Thread->AllocObject(self));
+  if (peer == nullptr) {
     CHECK(Thread::Current()->IsExceptionPending());
     return nullptr;
   }
@@ -1197,41 +1221,34 @@
   // non-null value. However, because we can run without code
   // available (in the compiler, in tests), we manually assign the
   // fields the constructor should have set.
-  ScopedObjectAccessUnchecked soa(Thread::Current());
   if (runtime->IsActiveTransaction()) {
-    InitPeer<true>(soa,
-                   soa.Decode<mirror::Object>(peer.get()),
-                   thread_is_daemon,
-                   thread_group,
-                   thread_name.get(),
+    InitPeer<true>(peer.Get(),
+                   as_daemon,
+                   thr_group.Get(),
+                   thread_name.Get(),
                    thread_priority);
   } else {
-    InitPeer<false>(soa,
-                    soa.Decode<mirror::Object>(peer.get()),
-                    thread_is_daemon,
-                    thread_group,
-                    thread_name.get(),
+    InitPeer<false>(peer.Get(),
+                    as_daemon,
+                    thr_group.Get(),
+                    thread_name.Get(),
                     thread_priority);
   }
 
-  return peer.release();
+  return peer.Get();
 }
 
 template<bool kTransactionActive>
-void Thread::InitPeer(ScopedObjectAccessAlreadyRunnable& soa,
-                      ObjPtr<mirror::Object> peer,
-                      jboolean thread_is_daemon,
-                      jobject thread_group,
-                      jobject thread_name,
+void Thread::InitPeer(ObjPtr<mirror::Object> peer,
+                      bool as_daemon,
+                      ObjPtr<mirror::Object> thread_group,
+                      ObjPtr<mirror::String> thread_name,
                       jint thread_priority) {
-  jni::DecodeArtField(WellKnownClasses::java_lang_Thread_daemon)->
-      SetBoolean<kTransactionActive>(peer, thread_is_daemon);
-  jni::DecodeArtField(WellKnownClasses::java_lang_Thread_group)->
-      SetObject<kTransactionActive>(peer, soa.Decode<mirror::Object>(thread_group));
-  jni::DecodeArtField(WellKnownClasses::java_lang_Thread_name)->
-      SetObject<kTransactionActive>(peer, soa.Decode<mirror::Object>(thread_name));
-  jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority)->
-      SetInt<kTransactionActive>(peer, thread_priority);
+  WellKnownClasses::java_lang_Thread_daemon->SetBoolean<kTransactionActive>(peer,
+      static_cast<uint8_t>(as_daemon ? 1u : 0u));
+  WellKnownClasses::java_lang_Thread_group->SetObject<kTransactionActive>(peer, thread_group);
+  WellKnownClasses::java_lang_Thread_name->SetObject<kTransactionActive>(peer, thread_name);
+  WellKnownClasses::java_lang_Thread_priority->SetInt<kTransactionActive>(peer, thread_priority);
 }
 
 void Thread::SetCachedThreadName(const char* name) {
@@ -1390,18 +1407,26 @@
   tls32_.num_name_readers.fetch_sub(1 /* at least memory_order_release */);
 }
 
-void Thread::Dump(std::ostream& os, bool dump_native_stack, BacktraceMap* backtrace_map,
-                  bool force_dump_stack) const {
+Thread::DumpOrder Thread::Dump(std::ostream& os,
+                               bool dump_native_stack,
+                               bool force_dump_stack) const {
   DumpState(os);
-  DumpStack(os, dump_native_stack, backtrace_map, force_dump_stack);
+  return DumpStack(os, dump_native_stack, force_dump_stack);
+}
+
+Thread::DumpOrder Thread::Dump(std::ostream& os,
+                               unwindstack::AndroidLocalUnwinder& unwinder,
+                               bool dump_native_stack,
+                               bool force_dump_stack) const {
+  DumpState(os);
+  return DumpStack(os, unwinder, dump_native_stack, force_dump_stack);
 }
 
 ObjPtr<mirror::String> Thread::GetThreadName() const {
-  ArtField* f = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_name);
   if (tlsPtr_.opeer == nullptr) {
     return nullptr;
   }
-  ObjPtr<mirror::Object> name = f->GetObject(tlsPtr_.opeer);
+  ObjPtr<mirror::Object> name = WellKnownClasses::java_lang_Thread_name->GetObject(tlsPtr_.opeer);
   return name == nullptr ? nullptr : name->AsString();
 }
 
@@ -1473,7 +1498,7 @@
     return false;
   }
 
-  if (kUseReadBarrier && delta > 0 && this != self && tlsPtr_.flip_function != nullptr) {
+  if (delta > 0 && this != self && tlsPtr_.flip_function != nullptr) {
     // Force retry of a suspend request if it's in the middle of a thread flip to avoid a
     // deadlock. b/31683379.
     return false;
@@ -1778,6 +1803,17 @@
           sched_yield();
         }
       }
+      // Ensure that the flip function for this thread, if pending, is finished *before*
+      // the checkpoint function is run. Otherwise, we may end up with both `to' and 'from'
+      // space references on the stack, confusing the GC's thread-flip logic. The caller is
+      // runnable so can't have a pending flip function.
+      DCHECK_EQ(self->GetState(), ThreadState::kRunnable);
+      DCHECK(
+          !self->GetStateAndFlags(std::memory_order_relaxed).IsAnyOfFlagsSet(FlipFunctionFlags()));
+      EnsureFlipFunctionStarted(self);
+      while (GetStateAndFlags(std::memory_order_acquire).IsAnyOfFlagsSet(FlipFunctionFlags())) {
+        sched_yield();
+      }
 
       function->Run(this);
     }
@@ -1939,21 +1975,18 @@
   // cause ScopedObjectAccessUnchecked to deadlock.
   if (gAborting == 0 && self != nullptr && thread != nullptr && thread->tlsPtr_.opeer != nullptr) {
     ScopedObjectAccessUnchecked soa(self);
-    priority = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority)
-        ->GetInt(thread->tlsPtr_.opeer);
-    is_daemon = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_daemon)
-        ->GetBoolean(thread->tlsPtr_.opeer);
+    priority = WellKnownClasses::java_lang_Thread_priority->GetInt(thread->tlsPtr_.opeer);
+    is_daemon = WellKnownClasses::java_lang_Thread_daemon->GetBoolean(thread->tlsPtr_.opeer);
 
     ObjPtr<mirror::Object> thread_group =
-        jni::DecodeArtField(WellKnownClasses::java_lang_Thread_group)
-            ->GetObject(thread->tlsPtr_.opeer);
+        WellKnownClasses::java_lang_Thread_group->GetObject(thread->tlsPtr_.opeer);
 
     if (thread_group != nullptr) {
-      ArtField* group_name_field =
-          jni::DecodeArtField(WellKnownClasses::java_lang_ThreadGroup_name);
-      ObjPtr<mirror::String> group_name_string =
-          group_name_field->GetObject(thread_group)->AsString();
-      group_name = (group_name_string != nullptr) ? group_name_string->ToModifiedUtf8() : "<null>";
+      ObjPtr<mirror::Object> group_name_object =
+          WellKnownClasses::java_lang_ThreadGroup_name->GetObject(thread_group);
+      group_name = (group_name_object != nullptr)
+          ? group_name_object->AsString()->ToModifiedUtf8()
+          : "<null>";
     }
   } else if (thread != nullptr) {
     priority = thread->GetNativePriority();
@@ -1980,6 +2013,9 @@
     if (thread->IsStillStarting()) {
       os << " (still starting up)";
     }
+    if (thread->tls32_.disable_thread_flip_count != 0) {
+      os << " DisableFlipCount = " << thread->tls32_.disable_thread_flip_count;
+    }
     os << "\n";
   } else {
     os << '"' << ::art::GetThreadName(tid) << '"'
@@ -2192,11 +2228,13 @@
         UNREACHABLE();
     }
     PrintObject(obj, msg, owner_tid);
+    num_blocked++;
   }
   void VisitLockedObject(ObjPtr<mirror::Object> obj)
       override
       REQUIRES_SHARED(Locks::mutator_lock_) {
     PrintObject(obj, "  - locked ", ThreadList::kInvalidThreadId);
+    num_locked++;
   }
 
   void PrintObject(ObjPtr<mirror::Object> obj,
@@ -2230,6 +2268,8 @@
   ArtMethod* last_method;
   int last_line_number;
   size_t repetition_count;
+  size_t num_blocked = 0;
+  size_t num_locked = 0;
 };
 
 static bool ShouldShowNativeStack(const Thread* thread)
@@ -2261,7 +2301,9 @@
   return current_method != nullptr && current_method->IsNative();
 }
 
-void Thread::DumpJavaStack(std::ostream& os, bool check_suspended, bool dump_locks) const {
+Thread::DumpOrder Thread::DumpJavaStack(std::ostream& os,
+                                        bool check_suspended,
+                                        bool dump_locks) const {
   // Dumping the Java stack involves the verifier for locks. The verifier operates under the
   // assumption that there is no exception pending on entry. Thus, stash any pending exception.
   // Thread::Current() instead of this in case a thread is dumping the stack of another suspended
@@ -2272,12 +2314,28 @@
   StackDumpVisitor dumper(os, const_cast<Thread*>(this), context.get(),
                           !tls32_.throwing_OutOfMemoryError, check_suspended, dump_locks);
   dumper.WalkStack();
+  if (IsJitSensitiveThread()) {
+    return DumpOrder::kMain;
+  } else if (dumper.num_blocked > 0) {
+    return DumpOrder::kBlocked;
+  } else if (dumper.num_locked > 0) {
+    return DumpOrder::kLocked;
+  } else {
+    return DumpOrder::kDefault;
+  }
 }
 
-void Thread::DumpStack(std::ostream& os,
-                       bool dump_native_stack,
-                       BacktraceMap* backtrace_map,
-                       bool force_dump_stack) const {
+Thread::DumpOrder Thread::DumpStack(std::ostream& os,
+                                    bool dump_native_stack,
+                                    bool force_dump_stack) const {
+  unwindstack::AndroidLocalUnwinder unwinder;
+  return DumpStack(os, unwinder, dump_native_stack, force_dump_stack);
+}
+
+Thread::DumpOrder Thread::DumpStack(std::ostream& os,
+                                    unwindstack::AndroidLocalUnwinder& unwinder,
+                                    bool dump_native_stack,
+                                    bool force_dump_stack) const {
   // TODO: we call this code when dying but may not have suspended the thread ourself. The
   //       IsSuspended check is therefore racy with the use for dumping (normally we inhibit
   //       the race with the thread_suspend_count_lock_).
@@ -2288,6 +2346,7 @@
     // thread's stack in debug builds where we'll hit the not suspended check in the stack walk.
     safe_to_dump = (safe_to_dump || dump_for_abort);
   }
+  DumpOrder dump_order = DumpOrder::kDefault;
   if (safe_to_dump || force_dump_stack) {
     // If we're currently in native code, dump that stack before dumping the managed stack.
     if (dump_native_stack && (dump_for_abort || force_dump_stack || ShouldShowNativeStack(this))) {
@@ -2295,14 +2354,15 @@
           GetCurrentMethod(nullptr,
                            /*check_suspended=*/ !force_dump_stack,
                            /*abort_on_error=*/ !(dump_for_abort || force_dump_stack));
-      DumpNativeStack(os, GetTid(), backtrace_map, "  native: ", method);
+      DumpNativeStack(os, unwinder, GetTid(), "  native: ", method);
     }
-    DumpJavaStack(os,
-                  /*check_suspended=*/ !force_dump_stack,
-                  /*dump_locks=*/ !force_dump_stack);
+    dump_order = DumpJavaStack(os,
+                               /*check_suspended=*/ !force_dump_stack,
+                               /*dump_locks=*/ !force_dump_stack);
   } else {
     os << "Not able to dump stack of thread that isn't suspended";
   }
+  return dump_order;
 }
 
 void Thread::ThreadExitCallback(void* arg) {
@@ -2380,25 +2440,17 @@
 }
 
 void Thread::NotifyThreadGroup(ScopedObjectAccessAlreadyRunnable& soa, jobject thread_group) {
-  ScopedLocalRef<jobject> thread_jobject(
-      soa.Env(), soa.Env()->AddLocalReference<jobject>(Thread::Current()->GetPeer()));
-  ScopedLocalRef<jobject> thread_group_jobject_scoped(
-      soa.Env(), nullptr);
-  jobject thread_group_jobject = thread_group;
+  ObjPtr<mirror::Object> thread_object = soa.Self()->GetPeer();
+  ObjPtr<mirror::Object> thread_group_object = soa.Decode<mirror::Object>(thread_group);
   if (thread_group == nullptr || kIsDebugBuild) {
     // There is always a group set. Retrieve it.
-    thread_group_jobject_scoped.reset(
-        soa.Env()->GetObjectField(thread_jobject.get(),
-                                  WellKnownClasses::java_lang_Thread_group));
-    thread_group_jobject = thread_group_jobject_scoped.get();
+    thread_group_object = WellKnownClasses::java_lang_Thread_group->GetObject(thread_object);
     if (kIsDebugBuild && thread_group != nullptr) {
-      CHECK(soa.Env()->IsSameObject(thread_group, thread_group_jobject));
+      CHECK(thread_group_object == soa.Decode<mirror::Object>(thread_group));
     }
   }
-  soa.Env()->CallNonvirtualVoidMethod(thread_group_jobject,
-                                      WellKnownClasses::java_lang_ThreadGroup,
-                                      WellKnownClasses::java_lang_ThreadGroup_add,
-                                      thread_jobject.get());
+  WellKnownClasses::java_lang_ThreadGroup_add->InvokeVirtual<'V', 'L'>(
+      soa.Self(), thread_group_object, thread_object);
 }
 
 Thread::Thread(bool daemon)
@@ -2409,8 +2461,6 @@
   wait_cond_ = new ConditionVariable("a thread wait condition variable", *wait_mutex_);
   tlsPtr_.mutator_lock = Locks::mutator_lock_;
   DCHECK(tlsPtr_.mutator_lock != nullptr);
-  tlsPtr_.instrumentation_stack =
-      new std::map<uintptr_t, instrumentation::InstrumentationStackFrame>;
   tlsPtr_.name.store(kThreadNameDuringStartup, std::memory_order_relaxed);
 
   static_assert((sizeof(Thread) % 4) == 0U,
@@ -2458,8 +2508,7 @@
 void Thread::AssertPendingOOMException() const {
   AssertPendingException();
   auto* e = GetException();
-  CHECK_EQ(e->GetClass(), DecodeJObject(WellKnownClasses::java_lang_OutOfMemoryError)->AsClass())
-      << e->Dump();
+  CHECK_EQ(e->GetClass(), WellKnownClasses::java_lang_OutOfMemoryError.Get()) << e->Dump();
 }
 
 void Thread::AssertNoPendingException() const {
@@ -2497,7 +2546,7 @@
   Thread* const self_;
 };
 
-void Thread::Destroy() {
+void Thread::Destroy(bool should_run_callbacks) {
   Thread* self = this;
   DCHECK_EQ(self, Thread::Current());
 
@@ -2522,27 +2571,25 @@
 
   if (tlsPtr_.opeer != nullptr) {
     ScopedObjectAccess soa(self);
+    if (UNLIKELY(self->GetMethodTraceBuffer() != nullptr)) {
+      Trace::FlushThreadBuffer(self);
+      self->ResetMethodTraceBuffer();
+    }
     // We may need to call user-supplied managed code, do this before final clean-up.
-    HandleUncaughtExceptions(soa);
-    RemoveFromThreadGroup(soa);
+    HandleUncaughtExceptions();
+    RemoveFromThreadGroup();
     Runtime* runtime = Runtime::Current();
-    if (runtime != nullptr) {
+    if (runtime != nullptr && should_run_callbacks) {
       runtime->GetRuntimeCallbacks()->ThreadDeath(self);
     }
 
     // this.nativePeer = 0;
-    if (Runtime::Current()->IsActiveTransaction()) {
-      jni::DecodeArtField(WellKnownClasses::java_lang_Thread_nativePeer)
-          ->SetLong<true>(tlsPtr_.opeer, 0);
-    } else {
-      jni::DecodeArtField(WellKnownClasses::java_lang_Thread_nativePeer)
-          ->SetLong<false>(tlsPtr_.opeer, 0);
-    }
+    SetNativePeer</*kSupportTransaction=*/ true>(tlsPtr_.opeer, nullptr);
 
     // Thread.join() is implemented as an Object.wait() on the Thread.lock object. Signal anyone
     // who is waiting.
     ObjPtr<mirror::Object> lock =
-        jni::DecodeArtField(WellKnownClasses::java_lang_Thread_lock)->GetObject(tlsPtr_.opeer);
+        WellKnownClasses::java_lang_Thread_lock->GetObject(tlsPtr_.opeer);
     // (This conditional is only needed for tests, where Thread.lock won't have been set.)
     if (lock != nullptr) {
       StackHandleScope<1> hs(self);
@@ -2559,7 +2606,7 @@
   }
   // Mark-stack revocation must be performed at the very end. No
   // checkpoint/flip-function or read-barrier should be called after this.
-  if (kUseReadBarrier) {
+  if (gUseReadBarrier) {
     Runtime::Current()->GetHeap()->ConcurrentCopyingCollector()->RevokeThreadLocalMarkStack(this);
   }
 }
@@ -2600,47 +2647,47 @@
     CleanupCpu();
   }
 
-  delete tlsPtr_.instrumentation_stack;
   SetCachedThreadName(nullptr);  // Deallocate name.
   delete tlsPtr_.deps_or_stack_trace_sample.stack_trace_sample;
 
+  if (tlsPtr_.method_trace_buffer != nullptr) {
+    delete[] tlsPtr_.method_trace_buffer;
+  }
+
   Runtime::Current()->GetHeap()->AssertThreadLocalBuffersAreRevoked(this);
 
   TearDownAlternateSignalStack();
 }
 
-void Thread::HandleUncaughtExceptions(ScopedObjectAccessAlreadyRunnable& soa) {
-  if (!IsExceptionPending()) {
+void Thread::HandleUncaughtExceptions() {
+  Thread* self = this;
+  DCHECK_EQ(self, Thread::Current());
+  if (!self->IsExceptionPending()) {
     return;
   }
-  ScopedLocalRef<jobject> peer(tlsPtr_.jni_env, soa.AddLocalReference<jobject>(tlsPtr_.opeer));
-  ScopedThreadStateChange tsc(this, ThreadState::kNative);
 
   // Get and clear the exception.
-  ScopedLocalRef<jthrowable> exception(tlsPtr_.jni_env, tlsPtr_.jni_env->ExceptionOccurred());
-  tlsPtr_.jni_env->ExceptionClear();
+  ObjPtr<mirror::Object> exception = self->GetException();
+  self->ClearException();
 
   // Call the Thread instance's dispatchUncaughtException(Throwable)
-  tlsPtr_.jni_env->CallVoidMethod(peer.get(),
-      WellKnownClasses::java_lang_Thread_dispatchUncaughtException,
-      exception.get());
+  WellKnownClasses::java_lang_Thread_dispatchUncaughtException->InvokeFinal<'V', 'L'>(
+      self, tlsPtr_.opeer, exception);
 
   // If the dispatchUncaughtException threw, clear that exception too.
-  tlsPtr_.jni_env->ExceptionClear();
+  self->ClearException();
 }
 
-void Thread::RemoveFromThreadGroup(ScopedObjectAccessAlreadyRunnable& soa) {
-  // this.group.removeThread(this);
+void Thread::RemoveFromThreadGroup() {
+  Thread* self = this;
+  DCHECK_EQ(self, Thread::Current());
+  // this.group.threadTerminated(this);
   // group can be null if we're in the compiler or a test.
-  ObjPtr<mirror::Object> ogroup = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_group)
-      ->GetObject(tlsPtr_.opeer);
-  if (ogroup != nullptr) {
-    ScopedLocalRef<jobject> group(soa.Env(), soa.AddLocalReference<jobject>(ogroup));
-    ScopedLocalRef<jobject> peer(soa.Env(), soa.AddLocalReference<jobject>(tlsPtr_.opeer));
-    ScopedThreadStateChange tsc(soa.Self(), ThreadState::kNative);
-    tlsPtr_.jni_env->CallVoidMethod(group.get(),
-                                    WellKnownClasses::java_lang_ThreadGroup_removeThread,
-                                    peer.get());
+  ObjPtr<mirror::Object> group =
+      WellKnownClasses::java_lang_Thread_group->GetObject(tlsPtr_.opeer);
+  if (group != nullptr) {
+    WellKnownClasses::java_lang_ThreadGroup_threadTerminated->InvokeVirtual<'V', 'L'>(
+        self, group, tlsPtr_.opeer);
   }
 }
 
@@ -2731,27 +2778,15 @@
   }
 }
 
-ObjPtr<mirror::Object> Thread::DecodeJObject(jobject obj) const {
-  if (obj == nullptr) {
-    return nullptr;
-  }
+ObjPtr<mirror::Object> Thread::DecodeGlobalJObject(jobject obj) const {
+  DCHECK(obj != nullptr);
   IndirectRef ref = reinterpret_cast<IndirectRef>(obj);
   IndirectRefKind kind = IndirectReferenceTable::GetIndirectRefKind(ref);
+  DCHECK_NE(kind, kJniTransition);
+  DCHECK_NE(kind, kLocal);
   ObjPtr<mirror::Object> result;
   bool expect_null = false;
-  // The "kinds" below are sorted by the frequency we expect to encounter them.
-  if (kind == kLocal) {
-    IndirectReferenceTable& locals = tlsPtr_.jni_env->locals_;
-    // Local references do not need a read barrier.
-    result = locals.Get<kWithoutReadBarrier>(ref);
-  } else if (kind == kJniTransitionOrInvalid) {
-    // The `jclass` for a static method points to the CompressedReference<> in the
-    // `ArtMethod::declaring_class_`. Other `jobject` arguments point to spilled stack
-    // references but a StackReference<> is just a subclass of CompressedReference<>.
-    DCHECK(IsJniTransitionReference(obj));
-    result = reinterpret_cast<mirror::CompressedReference<mirror::Object>*>(obj)->AsMirrorPtr();
-    VerifyObject(result);
-  } else if (kind == kGlobal) {
+  if (kind == kGlobal) {
     result = tlsPtr_.jni_env->vm_->DecodeGlobal(ref);
   } else {
     DCHECK_EQ(kind, kWeakGlobal);
@@ -2947,9 +2982,19 @@
     methods_and_pcs->SetElementPtrSize</*kTransactionActive=*/ false, /*kCheckTransaction=*/ false>(
         static_cast<uint32_t>(methods_and_pcs->GetLength()) / 2 + count_, dex_pc, pointer_size_);
     // Save the declaring class of the method to ensure that the declaring classes of the methods
-    // do not get unloaded while the stack trace is live.
+    // do not get unloaded while the stack trace is live. However, this does not work for copied
+    // methods because the declaring class of a copied method points to an interface class which
+    // may be in a different class loader. Instead, retrieve the class loader associated with the
+    // allocator that holds the copied method. This is much cheaper than finding the actual class.
+    ObjPtr<mirror::Object> keep_alive;
+    if (UNLIKELY(method->IsCopied())) {
+      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+      keep_alive = class_linker->GetHoldingClassLoaderOfCopiedMethod(self_, method);
+    } else {
+      keep_alive = method->GetDeclaringClass();
+    }
     trace_->Set</*kTransactionActive=*/ false, /*kCheckTransaction=*/ false>(
-        static_cast<int32_t>(count_) + 1, method->GetDeclaringClass());
+        static_cast<int32_t>(count_) + 1, keep_alive);
     ++count_;
   }
 
@@ -2967,11 +3012,14 @@
   uint32_t skip_depth_;
   // Current position down stack trace.
   uint32_t count_ = 0;
-  // An object array where the first element is a pointer array that contains the ArtMethod
-  // pointers on the stack and dex PCs. The rest of the elements are the declaring class of
-  // the ArtMethod pointers. trace_[i+1] contains the declaring class of the ArtMethod of the
-  // i'th frame. We're initializing a newly allocated trace, so we do not need to record that
-  // under a transaction. If the transaction is aborted, the whole trace shall be unreachable.
+  // An object array where the first element is a pointer array that contains the `ArtMethod`
+  // pointers on the stack and dex PCs. The rest of the elements are referencing objects
+  // that shall keep the methods alive, namely the declaring class of the `ArtMethod` for
+  // declared methods and the class loader for copied methods (because it's faster to find
+  // the class loader than the actual class that holds the copied method). The `trace_[i+1]`
+  // contains the declaring class or class loader of the `ArtMethod` of the i'th frame.
+  // We're initializing a newly allocated trace, so we do not need to record that under
+  // a transaction. If the transaction is aborted, the whole trace shall be unreachable.
   mirror::ObjectArray<mirror::Object>* trace_ = nullptr;
   // For cross compilation.
   const PointerSize pointer_size_;
@@ -3138,6 +3186,148 @@
   return result;
 }
 
+[[nodiscard]] static ObjPtr<mirror::StackFrameInfo> InitStackFrameInfo(
+    const ScopedObjectAccessAlreadyRunnable& soa,
+    ClassLinker* class_linker,
+    Handle<mirror::StackFrameInfo> stackFrameInfo,
+    ArtMethod* method,
+    uint32_t dex_pc) REQUIRES_SHARED(Locks::mutator_lock_) {
+  StackHandleScope<4> hs(soa.Self());
+  int32_t line_number;
+  auto source_name_object(hs.NewHandle<mirror::String>(nullptr));
+  if (method->IsProxyMethod()) {
+    line_number = -1;
+    // source_name_object intentionally left null for proxy methods
+  } else {
+    line_number = method->GetLineNumFromDexPC(dex_pc);
+    if (line_number == -1) {
+      // Make the line_number field of StackFrameInfo hold the dex pc.
+      // source_name_object is intentionally left null if we failed to map the dex pc to
+      // a line number (most probably because there is no debug info). See b/30183883.
+      line_number = static_cast<int32_t>(dex_pc);
+    } else {
+      const char* source_file = method->GetDeclaringClassSourceFile();
+      if (source_file != nullptr) {
+        source_name_object.Assign(mirror::String::AllocFromModifiedUtf8(soa.Self(), source_file));
+        if (source_name_object == nullptr) {
+          soa.Self()->AssertPendingOOMException();
+          return nullptr;
+        }
+      }
+    }
+  }
+
+  Handle<mirror::Class> declaring_class_object(
+      hs.NewHandle<mirror::Class>(method->GetDeclaringClass()));
+
+  ArtMethod* interface_method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
+  const char* method_name = interface_method->GetName();
+  CHECK(method_name != nullptr);
+  Handle<mirror::String> method_name_object(
+      hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), method_name)));
+  if (method_name_object == nullptr) {
+    soa.Self()->AssertPendingOOMException();
+    return nullptr;
+  }
+
+  dex::ProtoIndex proto_idx =
+      method->GetDexFile()->GetIndexForProtoId(interface_method->GetPrototype());
+  Handle<mirror::MethodType> method_type_object(hs.NewHandle<mirror::MethodType>(
+      class_linker->ResolveMethodType(soa.Self(), proto_idx, interface_method)));
+  if (method_type_object == nullptr) {
+    soa.Self()->AssertPendingOOMException();
+    return nullptr;
+  }
+
+  stackFrameInfo->AssignFields(declaring_class_object,
+                               method_type_object,
+                               method_name_object,
+                               source_name_object,
+                               line_number,
+                               static_cast<int32_t>(dex_pc));
+  return stackFrameInfo.Get();
+}
+
+constexpr jlong FILL_CLASS_REFS_ONLY = 0x2;  // StackStreamFactory.FILL_CLASS_REFS_ONLY
+
+jint Thread::InternalStackTraceToStackFrameInfoArray(
+    const ScopedObjectAccessAlreadyRunnable& soa,
+    jlong mode,  // See java.lang.StackStreamFactory for the mode flags
+    jobject internal,
+    jint startLevel,
+    jint batchSize,
+    jint startBufferIndex,
+    jobjectArray output_array) {
+  // Decode the internal stack trace into the depth, method trace and PC trace.
+  // Subtract one for the methods and PC trace.
+  int32_t depth = soa.Decode<mirror::Array>(internal)->GetLength() - 1;
+  DCHECK_GE(depth, 0);
+
+  StackHandleScope<6> hs(soa.Self());
+  Handle<mirror::ObjectArray<mirror::Object>> framesOrClasses =
+      hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>>(output_array));
+
+  jint endBufferIndex = startBufferIndex;
+
+  if (startLevel < 0 || startLevel >= depth) {
+    return endBufferIndex;
+  }
+
+  int32_t bufferSize = framesOrClasses->GetLength();
+  if (startBufferIndex < 0 || startBufferIndex >= bufferSize) {
+    return endBufferIndex;
+  }
+
+  // The FILL_CLASS_REFS_ONLY flag is defined in AbstractStackWalker.fetchStackFrames() javadoc.
+  bool isClassArray = (mode & FILL_CLASS_REFS_ONLY) != 0;
+
+  Handle<mirror::ObjectArray<mirror::Object>> decoded_traces =
+      hs.NewHandle(soa.Decode<mirror::Object>(internal)->AsObjectArray<mirror::Object>());
+  // Methods and dex PC trace is element 0.
+  DCHECK(decoded_traces->Get(0)->IsIntArray() || decoded_traces->Get(0)->IsLongArray());
+  Handle<mirror::PointerArray> method_trace =
+      hs.NewHandle(ObjPtr<mirror::PointerArray>::DownCast(decoded_traces->Get(0)));
+
+  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+  Handle<mirror::Class> sfi_class =
+      hs.NewHandle(class_linker->FindSystemClass(soa.Self(), "Ljava/lang/StackFrameInfo;"));
+  DCHECK(sfi_class != nullptr);
+
+  MutableHandle<mirror::StackFrameInfo> frame = hs.NewHandle<mirror::StackFrameInfo>(nullptr);
+  MutableHandle<mirror::Class> clazz = hs.NewHandle<mirror::Class>(nullptr);
+  for (uint32_t i = static_cast<uint32_t>(startLevel); i < static_cast<uint32_t>(depth); ++i) {
+    if (endBufferIndex >= startBufferIndex + batchSize || endBufferIndex >= bufferSize) {
+      break;
+    }
+
+    ArtMethod* method = method_trace->GetElementPtrSize<ArtMethod*>(i, kRuntimePointerSize);
+    if (isClassArray) {
+      clazz.Assign(method->GetDeclaringClass());
+      framesOrClasses->Set(endBufferIndex, clazz.Get());
+    } else {
+      // Prepare parameters for fields in StackFrameInfo
+      uint32_t dex_pc = method_trace->GetElementPtrSize<uint32_t>(
+          i + static_cast<uint32_t>(method_trace->GetLength()) / 2, kRuntimePointerSize);
+
+      ObjPtr<mirror::Object> frameObject = framesOrClasses->Get(endBufferIndex);
+      // If libcore didn't allocate the object, we just stop here, but it's unlikely.
+      if (frameObject == nullptr || !frameObject->InstanceOf(sfi_class.Get())) {
+        break;
+      }
+      frame.Assign(ObjPtr<mirror::StackFrameInfo>::DownCast(frameObject));
+      frame.Assign(InitStackFrameInfo(soa, class_linker, frame, method, dex_pc));
+      // Break if InitStackFrameInfo fails to allocate objects or assign the fields.
+      if (frame == nullptr) {
+        break;
+      }
+    }
+
+    ++endBufferIndex;
+  }
+
+  return endBufferIndex;
+}
+
 jobjectArray Thread::CreateAnnotatedStackTrace(const ScopedObjectAccessAlreadyRunnable& soa) const {
   // This code allocates. Do not allow it to operate with a pending exception.
   if (IsExceptionPending()) {
@@ -3574,6 +3764,7 @@
   QUICK_ENTRY_POINT_INFO(pAputObject)
   QUICK_ENTRY_POINT_INFO(pJniMethodStart)
   QUICK_ENTRY_POINT_INFO(pJniMethodEnd)
+  QUICK_ENTRY_POINT_INFO(pJniMethodEntryHook)
   QUICK_ENTRY_POINT_INFO(pJniDecodeReferenceResult)
   QUICK_ENTRY_POINT_INFO(pJniLockObject)
   QUICK_ENTRY_POINT_INFO(pJniUnlockObject)
@@ -3639,6 +3830,7 @@
   QUICK_ENTRY_POINT_INFO(pA64Store)
   QUICK_ENTRY_POINT_INFO(pNewEmptyString)
   QUICK_ENTRY_POINT_INFO(pNewStringFromBytes_B)
+  QUICK_ENTRY_POINT_INFO(pNewStringFromBytes_BB)
   QUICK_ENTRY_POINT_INFO(pNewStringFromBytes_BI)
   QUICK_ENTRY_POINT_INFO(pNewStringFromBytes_BII)
   QUICK_ENTRY_POINT_INFO(pNewStringFromBytes_BIII)
@@ -3653,6 +3845,7 @@
   QUICK_ENTRY_POINT_INFO(pNewStringFromString)
   QUICK_ENTRY_POINT_INFO(pNewStringFromStringBuffer)
   QUICK_ENTRY_POINT_INFO(pNewStringFromStringBuilder)
+  QUICK_ENTRY_POINT_INFO(pNewStringFromUtf16Bytes_BII)
   QUICK_ENTRY_POINT_INFO(pJniReadBarrier)
   QUICK_ENTRY_POINT_INFO(pReadBarrierMarkReg00)
   QUICK_ENTRY_POINT_INFO(pReadBarrierMarkReg01)
@@ -3691,12 +3884,15 @@
   os << offset;
 }
 
-void Thread::QuickDeliverException() {
+void Thread::QuickDeliverException(bool skip_method_exit_callbacks) {
   // Get exception from thread.
   ObjPtr<mirror::Throwable> exception = GetException();
   CHECK(exception != nullptr);
   if (exception == GetDeoptimizationException()) {
-    artDeoptimize(this);
+    // This wasn't a real exception, so just clear it here. If there was an actual exception it
+    // will be recorded in the DeoptimizationContext and it will be restored later.
+    ClearException();
+    artDeoptimize(this, skip_method_exit_callbacks);
     UNREACHABLE();
   }
 
@@ -3717,63 +3913,37 @@
   // Note: we do this *after* reporting the exception to instrumentation in case it now requires
   // deoptimization. It may happen if a debugger is attached and requests new events (single-step,
   // breakpoint, ...) when the exception is reported.
-  //
-  // Note we need to check for both force_frame_pop and force_retry_instruction. The first is
-  // expected to happen fairly regularly but the second can only happen if we are using
-  // instrumentation trampolines (for example with DDMS tracing). That forces us to do deopt later
-  // and see every frame being popped. We don't need to handle it any differently.
-  ShadowFrame* cf;
-  bool force_deopt = false;
-  if (Runtime::Current()->AreNonStandardExitsEnabled() || kIsDebugBuild) {
+  // Frame pop can be requested on a method unwind callback which requires a deopt. We could
+  // potentially check after each unwind callback to see if a frame pop was requested and deopt if
+  // needed. Since this is a debug only feature and this path is only taken when an exception is
+  // thrown, it is not performance critical and we keep it simple by just deopting if method exit
+  // listeners are installed and frame pop feature is supported.
+  bool needs_deopt =
+      instrumentation->HasMethodExitListeners() && Runtime::Current()->AreNonStandardExitsEnabled();
+  if (Dbg::IsForcedInterpreterNeededForException(this) || IsForceInterpreter() || needs_deopt) {
     NthCallerVisitor visitor(this, 0, false);
     visitor.WalkStack();
-    cf = visitor.GetCurrentShadowFrame();
-    if (cf == nullptr) {
-      cf = FindDebuggerShadowFrame(visitor.GetFrameId());
-    }
-    bool force_frame_pop = cf != nullptr && cf->GetForcePopFrame();
-    bool force_retry_instr = cf != nullptr && cf->GetForceRetryInstruction();
-    if (kIsDebugBuild && force_frame_pop) {
-      DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
-      NthCallerVisitor penultimate_visitor(this, 1, false);
-      penultimate_visitor.WalkStack();
-      ShadowFrame* penultimate_frame = penultimate_visitor.GetCurrentShadowFrame();
-      if (penultimate_frame == nullptr) {
-        penultimate_frame = FindDebuggerShadowFrame(penultimate_visitor.GetFrameId());
+    if (visitor.GetCurrentQuickFrame() != nullptr) {
+      if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.GetOuterMethod(), visitor.caller_pc)) {
+        // method_type shouldn't matter due to exception handling.
+        const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault;
+        // Save the exception into the deoptimization context so it can be restored
+        // before entering the interpreter.
+        PushDeoptimizationContext(
+            JValue(),
+            /* is_reference= */ false,
+            exception,
+            /* from_code= */ false,
+            method_type);
+        artDeoptimize(this, skip_method_exit_callbacks);
+        UNREACHABLE();
+      } else {
+        LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
+                     << visitor.caller->PrettyMethod();
       }
-    }
-    if (force_retry_instr) {
-      DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
-    }
-    force_deopt = force_frame_pop || force_retry_instr;
-  }
-  if (Dbg::IsForcedInterpreterNeededForException(this) || force_deopt || IsForceInterpreter()) {
-    NthCallerVisitor visitor(this, 0, false);
-    visitor.WalkStack();
-    if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.caller_pc)) {
-      // method_type shouldn't matter due to exception handling.
-      const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault;
-      // Save the exception into the deoptimization context so it can be restored
-      // before entering the interpreter.
-      if (force_deopt) {
-        VLOG(deopt) << "Deopting " << cf->GetMethod()->PrettyMethod() << " for frame-pop";
-        DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
-        // Get rid of the exception since we are doing a framepop instead.
-        LOG(WARNING) << "Suppressing pending exception for retry-instruction/frame-pop: "
-                     << exception->Dump();
-        ClearException();
-      }
-      PushDeoptimizationContext(
-          JValue(),
-          /* is_reference= */ false,
-          (force_deopt ? nullptr : exception),
-          /* from_code= */ false,
-          method_type);
-      artDeoptimize(this);
-      UNREACHABLE();
-    } else if (visitor.caller != nullptr) {
-      LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
-                   << visitor.caller->PrettyMethod();
+    } else {
+      // This is either top of call stack, or shadow frame.
+      DCHECK(visitor.caller == nullptr || visitor.IsShadowFrame());
     }
   }
 
@@ -3781,7 +3951,7 @@
   // resolution.
   ClearException();
   QuickExceptionHandler exception_handler(this, false);
-  exception_handler.FindCatch(exception);
+  exception_handler.FindCatch(exception, skip_method_exit_callbacks);
   if (exception_handler.GetClearException()) {
     // Exception was cleared as part of delivery.
     DCHECK(!IsExceptionPending());
@@ -3848,10 +4018,11 @@
  public:
   ReferenceMapVisitor(Thread* thread, Context* context, RootVisitor& visitor)
       REQUIRES_SHARED(Locks::mutator_lock_)
-        // We are visiting the references in compiled frames, so we do not need
-        // to know the inlined frames.
+      // We are visiting the references in compiled frames, so we do not need
+      // to know the inlined frames.
       : StackVisitor(thread, context, StackVisitor::StackWalkKind::kSkipInlinedFrames),
-        visitor_(visitor) {}
+        visitor_(visitor),
+        visit_declaring_class_(!Runtime::Current()->GetHeap()->IsPerformingUffdCompaction()) {}
 
   bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
     if (false) {
@@ -3896,6 +4067,9 @@
   void VisitDeclaringClass(ArtMethod* method)
       REQUIRES_SHARED(Locks::mutator_lock_)
       NO_THREAD_SAFETY_ANALYSIS {
+    if (!visit_declaring_class_) {
+      return;
+    }
     ObjPtr<mirror::Class> klass = method->GetDeclaringClassUnchecked<kWithoutReadBarrier>();
     // klass can be null for runtime methods.
     if (klass != nullptr) {
@@ -3978,7 +4152,7 @@
         // (PC shall be known thanks to the runtime frame for throwing SIOOBE).
         // Note that JIT does not emit that intrinic implementation.
         const void* pc = reinterpret_cast<const void*>(GetCurrentQuickFramePc());
-        if (pc != 0u && Runtime::Current()->GetHeap()->IsInBootImageOatFile(pc)) {
+        if (pc != nullptr && Runtime::Current()->GetHeap()->IsInBootImageOatFile(pc)) {
           return;
         }
       }
@@ -4189,6 +4363,7 @@
 
   // Visitor for when we visit a root.
   RootVisitor& visitor_;
+  bool visit_declaring_class_;
 };
 
 class RootCallbackVisitor {
@@ -4274,9 +4449,6 @@
   RootCallbackVisitor visitor_to_callback(visitor, thread_id);
   ReferenceMapVisitor<RootCallbackVisitor, kPrecise> mapper(this, &context, visitor_to_callback);
   mapper.template WalkStack<StackVisitor::CountTransitions::kNo>(false);
-  for (auto& entry : *GetInstrumentationStack()) {
-    visitor->VisitRootIfNonNull(&entry.second.this_object_, RootInfo(kRootVMInternal, thread_id));
-  }
 }
 #pragma GCC diagnostic pop
 
@@ -4293,27 +4465,46 @@
     case Opcode::INSTANCE_OF:
     case Opcode::NEW_ARRAY:
     case Opcode::CONST_CLASS: {
-      mirror::Class* cls = reinterpret_cast<mirror::Class*>(*value);
-      if (cls == nullptr || cls == Runtime::GetWeakClassSentinel()) {
-        // Entry got deleted in a previous sweep.
+      mirror::Class* klass = reinterpret_cast<mirror::Class*>(*value);
+      if (klass == nullptr || klass == Runtime::GetWeakClassSentinel()) {
         return;
       }
-      Runtime::ProcessWeakClass(
-          reinterpret_cast<GcRoot<mirror::Class>*>(value),
-          visitor,
-          Runtime::GetWeakClassSentinel());
+      mirror::Class* new_klass = down_cast<mirror::Class*>(visitor->IsMarked(klass));
+      if (new_klass == nullptr) {
+        *value = reinterpret_cast<size_t>(Runtime::GetWeakClassSentinel());
+      } else if (new_klass != klass) {
+        *value = reinterpret_cast<size_t>(new_klass);
+      }
       return;
     }
     case Opcode::CONST_STRING:
     case Opcode::CONST_STRING_JUMBO: {
       mirror::Object* object = reinterpret_cast<mirror::Object*>(*value);
+      if (object == nullptr) {
+        return;
+      }
       mirror::Object* new_object = visitor->IsMarked(object);
       // We know the string is marked because it's a strongly-interned string that
       // is always alive (see b/117621117 for trying to make those strings weak).
-      // The IsMarked implementation of the CMS collector returns
-      // null for newly allocated objects, but we know those haven't moved. Therefore,
-      // only update the entry if we get a different non-null string.
-      if (new_object != nullptr && new_object != object) {
+      if (kIsDebugBuild && new_object == nullptr) {
+        // (b/275005060) Currently the problem is reported only on CC GC.
+        // Therefore we log it with more information. But since the failure rate
+        // is quite high, sampling it.
+        if (gUseReadBarrier) {
+          Runtime* runtime = Runtime::Current();
+          gc::collector::ConcurrentCopying* cc = runtime->GetHeap()->ConcurrentCopyingCollector();
+          CHECK_NE(cc, nullptr);
+          LOG(FATAL) << cc->DumpReferenceInfo(object, "string")
+                     << " string interned: " << std::boolalpha
+                     << runtime->GetInternTable()->LookupStrong(Thread::Current(),
+                                                                down_cast<mirror::String*>(object))
+                     << std::noboolalpha;
+        } else {
+          // Other GCs
+          LOG(FATAL) << __FUNCTION__
+                     << ": IsMarked returned null for a strongly interned string: " << object;
+        }
+      } else if (new_object != object) {
         *value = reinterpret_cast<size_t>(new_object);
       }
       return;
@@ -4327,16 +4518,12 @@
       // New opcode is using the cache. We need to explicitly handle it in this method.
       DCHECK(false) << "Unhandled opcode " << inst->Opcode();
   }
-};
+}
 
-void Thread::SweepInterpreterCaches(IsMarkedVisitor* visitor) {
-  MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
-  Runtime::Current()->GetThreadList()->ForEach([visitor](Thread* thread) {
-    Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
-    for (InterpreterCache::Entry& entry : thread->GetInterpreterCache()->GetArray()) {
-      SweepCacheEntry(visitor, reinterpret_cast<const Instruction*>(entry.first), &entry.second);
-    }
-  });
+void Thread::SweepInterpreterCache(IsMarkedVisitor* visitor) {
+  for (InterpreterCache::Entry& entry : GetInterpreterCache()->GetArray()) {
+    SweepCacheEntry(visitor, reinterpret_cast<const Instruction*>(entry.first), &entry.second);
+  }
 }
 
 // FIXME: clang-r433403 reports the below function exceeds frame size limit.
@@ -4425,6 +4612,15 @@
   return has_tlab;
 }
 
+void Thread::AdjustTlab(size_t slide_bytes) {
+  if (HasTlab()) {
+    tlsPtr_.thread_local_start -= slide_bytes;
+    tlsPtr_.thread_local_pos -= slide_bytes;
+    tlsPtr_.thread_local_end -= slide_bytes;
+    tlsPtr_.thread_local_limit -= slide_bytes;
+  }
+}
+
 std::ostream& operator<<(std::ostream& os, const Thread& thread) {
   thread.ShortDump(os);
   return os;
@@ -4435,9 +4631,11 @@
   VLOG(threads) << "Protecting stack at " << pregion;
   if (mprotect(pregion, kStackOverflowProtectedSize, PROT_NONE) == -1) {
     if (fatal_on_error) {
-      LOG(FATAL) << "Unable to create protected region in stack for implicit overflow check. "
+      // b/249586057, LOG(FATAL) times out
+      LOG(ERROR) << "Unable to create protected region in stack for implicit overflow check. "
           "Reason: "
           << strerror(errno) << " size:  " << kStackOverflowProtectedSize;
+      exit(1);
     }
     return false;
   }
@@ -4471,25 +4669,29 @@
 void Thread::DeoptimizeWithDeoptimizationException(JValue* result) {
   DCHECK_EQ(GetException(), Thread::GetDeoptimizationException());
   ClearException();
-  ShadowFrame* shadow_frame =
-      PopStackedShadowFrame(StackedShadowFrameType::kDeoptimizationShadowFrame);
   ObjPtr<mirror::Throwable> pending_exception;
   bool from_code = false;
   DeoptimizationMethodType method_type;
   PopDeoptimizationContext(result, &pending_exception, &from_code, &method_type);
   SetTopOfStack(nullptr);
-  SetTopOfShadowStack(shadow_frame);
 
   // Restore the exception that was pending before deoptimization then interpret the
   // deoptimized frames.
   if (pending_exception != nullptr) {
     SetException(pending_exception);
   }
-  interpreter::EnterInterpreterFromDeoptimize(this,
-                                              shadow_frame,
-                                              result,
-                                              from_code,
-                                              method_type);
+
+  ShadowFrame* shadow_frame = MaybePopDeoptimizedStackedShadowFrame();
+  // We may not have a shadow frame if we deoptimized at the return of the
+  // quick_to_interpreter_bridge which got directly called by art_quick_invoke_stub.
+  if (shadow_frame != nullptr) {
+    SetTopOfShadowStack(shadow_frame);
+    interpreter::EnterInterpreterFromDeoptimize(this,
+                                                shadow_frame,
+                                                result,
+                                                from_code,
+                                                method_type);
+  }
 }
 
 void Thread::SetAsyncException(ObjPtr<mirror::Throwable> new_exception) {
@@ -4534,7 +4736,7 @@
 mirror::Object* Thread::GetPeerFromOtherThread() const {
   DCHECK(tlsPtr_.jpeer == nullptr);
   mirror::Object* peer = tlsPtr_.opeer;
-  if (kUseReadBarrier && Current()->GetIsGcMarking()) {
+  if (gUseReadBarrier && Current()->GetIsGcMarking()) {
     // We may call Thread::Dump() in the middle of the CC thread flip and this thread's stack
     // may have not been flipped yet and peer may be a from-space (stale) ref. So explicitly
     // mark/forward it here.
@@ -4587,8 +4789,7 @@
   if (GetPeer() == nullptr) {
     return false;
   }
-  return jni::DecodeArtField(
-      WellKnownClasses::java_lang_Thread_systemDaemon)->GetBoolean(GetPeer());
+  return WellKnownClasses::java_lang_Thread_systemDaemon->GetBoolean(GetPeer());
 }
 
 std::string Thread::StateAndFlagsAsHexString() const {
@@ -4606,7 +4807,9 @@
   CHECK(self_->IsExceptionPending()) << *self_;
   ObjPtr<mirror::Throwable> old_suppressed(excp_.Get());
   excp_.Assign(self_->GetException());
-  LOG(WARNING) << message << "Suppressing old exception: " << old_suppressed->Dump();
+  if (old_suppressed != nullptr) {
+    LOG(WARNING) << message << "Suppressing old exception: " << old_suppressed->Dump();
+  }
   self_->ClearException();
 }
 
diff --git a/runtime/thread.h b/runtime/thread.h
index dd8b061..5350330 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -38,6 +38,7 @@
 #include "handle.h"
 #include "handle_scope.h"
 #include "interpreter/interpreter_cache.h"
+#include "interpreter/shadow_frame.h"
 #include "javaheapprof/javaheapsampler.h"
 #include "jvalue.h"
 #include "managed_stack.h"
@@ -48,7 +49,9 @@
 #include "runtime_stats.h"
 #include "thread_state.h"
 
-class BacktraceMap;
+namespace unwindstack {
+class AndroidLocalUnwinder;
+}  // namespace unwindstack
 
 namespace art {
 
@@ -188,7 +191,7 @@
 // This should match RosAlloc::kNumThreadLocalSizeBrackets.
 static constexpr size_t kNumRosAllocThreadLocalSizeBracketsInThread = 16;
 
-static constexpr size_t kSharedMethodHotnessThreshold = 0xffff;
+static constexpr size_t kSharedMethodHotnessThreshold = 0x1fff;
 
 // Thread's stack layout for implicit stack overflow checks:
 //
@@ -229,8 +232,11 @@
 
   // Attaches the calling native thread to the runtime, returning the new native peer.
   // Used to implement JNI AttachCurrentThread and AttachCurrentThreadAsDaemon calls.
-  static Thread* Attach(const char* thread_name, bool as_daemon, jobject thread_group,
-                        bool create_peer);
+  static Thread* Attach(const char* thread_name,
+                        bool as_daemon,
+                        jobject thread_group,
+                        bool create_peer,
+                        bool should_run_callbacks);
   // Attaches the calling native thread to the runtime, returning the new native peer.
   static Thread* Attach(const char* thread_name, bool as_daemon, jobject thread_peer);
 
@@ -242,6 +248,9 @@
   // TODO: mark as PURE so the compiler may coalesce and remove?
   static Thread* Current();
 
+  // Get the thread from the JNI environment.
+  static Thread* ForEnv(JNIEnv* env);
+
   // On a runnable thread, check for pending thread suspension request and handle if pending.
   void AllowThreadSuspension() REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -267,16 +276,28 @@
   // Dumps a one-line summary of thread state (used for operator<<).
   void ShortDump(std::ostream& os) const;
 
+  // Order of threads for ANRs (ANRs can be trimmed, so we print important ones first).
+  enum class DumpOrder : uint8_t {
+    kMain,     // Always print the main thread first (there might not be one).
+    kBlocked,  // Then print all threads that are blocked due to waiting on lock.
+    kLocked,   // Then print all threads that are holding some lock already.
+    kDefault,  // Print all other threads which might not be interesting for ANR.
+  };
+
   // Dumps the detailed thread state and the thread stack (used for SIGQUIT).
-  void Dump(std::ostream& os,
-            bool dump_native_stack = true,
-            BacktraceMap* backtrace_map = nullptr,
-            bool force_dump_stack = false) const
+  DumpOrder Dump(std::ostream& os,
+                 bool dump_native_stack = true,
+                 bool force_dump_stack = false) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  DumpOrder Dump(std::ostream& os,
+                 unwindstack::AndroidLocalUnwinder& unwinder,
+                 bool dump_native_stack = true,
+                 bool force_dump_stack = false) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void DumpJavaStack(std::ostream& os,
-                     bool check_suspended = true,
-                     bool dump_locks = true) const
+  DumpOrder DumpJavaStack(std::ostream& os,
+                          bool check_suspended = true,
+                          bool dump_locks = true) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Dumps the SIGQUIT per-thread header. 'thread' can be null for a non-attached thread, in which
@@ -373,14 +394,23 @@
   void WaitForFlipFunction(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
 
   gc::accounting::AtomicStack<mirror::Object>* GetThreadLocalMarkStack() {
-    CHECK(kUseReadBarrier);
+    CHECK(gUseReadBarrier);
     return tlsPtr_.thread_local_mark_stack;
   }
   void SetThreadLocalMarkStack(gc::accounting::AtomicStack<mirror::Object>* stack) {
-    CHECK(kUseReadBarrier);
+    CHECK(gUseReadBarrier);
     tlsPtr_.thread_local_mark_stack = stack;
   }
 
+  uint8_t* GetThreadLocalGcBuffer() {
+    DCHECK(gUseUserfaultfd);
+    return tlsPtr_.thread_local_gc_buffer;
+  }
+  void SetThreadLocalGcBuffer(uint8_t* buf) {
+    DCHECK(gUseUserfaultfd);
+    tlsPtr_.thread_local_gc_buffer = buf;
+  }
+
   // Called when thread detected that the thread_suspend_count_ was non-zero. Gives up share of
   // mutator_lock_ and waits until it is resumed and thread_suspend_count_ is zero.
   void FullSuspendCheck(bool implicit = false)
@@ -546,8 +576,12 @@
   // that needs to be dealt with, false otherwise.
   bool ObserveAsyncException() REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Find catch block and perform long jump to appropriate exception handle
-  NO_RETURN void QuickDeliverException() REQUIRES_SHARED(Locks::mutator_lock_);
+  // Find catch block and perform long jump to appropriate exception handle. When
+  // is_method_exit_exception is true, the exception was thrown by the method exit callback and we
+  // should not send method unwind for the method on top of the stack since method exit callback was
+  // already called.
+  NO_RETURN void QuickDeliverException(bool is_method_exit_exception = false)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   Context* GetLongJumpContext();
   void ReleaseLongJumpContext(Context* context) {
@@ -573,8 +607,8 @@
     tlsPtr_.managed_stack.SetTopQuickFrame(top_method);
   }
 
-  void SetTopOfStackTagged(ArtMethod** top_method) {
-    tlsPtr_.managed_stack.SetTopQuickFrameTagged(top_method);
+  void SetTopOfStackGenericJniTagged(ArtMethod** top_method) {
+    tlsPtr_.managed_stack.SetTopQuickFrameGenericJniTagged(top_method);
   }
 
   void SetTopOfShadowStack(ShadowFrame* top) {
@@ -708,6 +742,16 @@
       jobjectArray output_array = nullptr, int* stack_depth = nullptr)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  static jint InternalStackTraceToStackFrameInfoArray(
+      const ScopedObjectAccessAlreadyRunnable& soa,
+      jlong mode,  // See java.lang.StackStreamFactory for the mode flags
+      jobject internal,
+      jint startLevel,
+      jint batchSize,
+      jint startIndex,
+      jobjectArray output_array)  // java.lang.StackFrameInfo[]
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   jobjectArray CreateAnnotatedStackTrace(const ScopedObjectAccessAlreadyRunnable& soa) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -715,6 +759,9 @@
     return tlsPtr_.frame_id_to_shadow_frame != nullptr;
   }
 
+  // This is done by GC using a checkpoint (or in a stop-the-world pause).
+  void SweepInterpreterCache(IsMarkedVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
   void VisitRoots(RootVisitor* visitor, VisitRootFlags flags)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -739,6 +786,13 @@
   }
 
   template<PointerSize pointer_size>
+  static constexpr ThreadOffset<pointer_size> TidOffset() {
+    return ThreadOffset<pointer_size>(
+        OFFSETOF_MEMBER(Thread, tls32_) +
+        OFFSETOF_MEMBER(tls_32bit_sized_values, tid));
+  }
+
+  template<PointerSize pointer_size>
   static constexpr ThreadOffset<pointer_size> InterruptedOffset() {
     return ThreadOffset<pointer_size>(
         OFFSETOF_MEMBER(Thread, tls32_) +
@@ -766,6 +820,13 @@
         OFFSETOF_MEMBER(tls_32bit_sized_values, is_gc_marking));
   }
 
+  template <PointerSize pointer_size>
+  static constexpr ThreadOffset<pointer_size> DeoptCheckRequiredOffset() {
+    return ThreadOffset<pointer_size>(
+        OFFSETOF_MEMBER(Thread, tls32_) +
+        OFFSETOF_MEMBER(tls_32bit_sized_values, is_deopt_check_required));
+  }
+
   static constexpr size_t IsGcMarkingSize() {
     return sizeof(tls32_.is_gc_marking);
   }
@@ -955,6 +1016,10 @@
   // Is the given obj in one of this thread's JNI transition frames?
   bool IsJniTransitionReference(jobject obj) const REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Convert a global (or weak global) jobject into a Object*
+  ObjPtr<mirror::Object> DecodeGlobalJObject(jobject obj) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   void HandleScopeVisitRoots(RootVisitor* visitor, uint32_t thread_id)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -1011,33 +1076,34 @@
   }
 
   bool GetIsGcMarking() const {
-    CHECK(kUseReadBarrier);
+    DCHECK(gUseReadBarrier);
     return tls32_.is_gc_marking;
   }
 
   void SetIsGcMarkingAndUpdateEntrypoints(bool is_marking);
 
+  bool IsDeoptCheckRequired() const { return tls32_.is_deopt_check_required; }
+
+  void SetDeoptCheckRequired(bool flag) { tls32_.is_deopt_check_required = flag; }
+
   bool GetWeakRefAccessEnabled() const;  // Only safe for current thread.
 
   void SetWeakRefAccessEnabled(bool enabled) {
-    CHECK(kUseReadBarrier);
+    DCHECK(gUseReadBarrier);
     WeakRefAccessState new_state = enabled ?
         WeakRefAccessState::kEnabled : WeakRefAccessState::kDisabled;
     tls32_.weak_ref_access_enabled.store(new_state, std::memory_order_release);
   }
 
   uint32_t GetDisableThreadFlipCount() const {
-    CHECK(kUseReadBarrier);
     return tls32_.disable_thread_flip_count;
   }
 
   void IncrementDisableThreadFlipCount() {
-    CHECK(kUseReadBarrier);
     ++tls32_.disable_thread_flip_count;
   }
 
   void DecrementDisableThreadFlipCount() {
-    CHECK(kUseReadBarrier);
     DCHECK_GT(tls32_.disable_thread_flip_count, 0U);
     --tls32_.disable_thread_flip_count;
   }
@@ -1091,7 +1157,8 @@
   void AssertHasDeoptimizationContext()
       REQUIRES_SHARED(Locks::mutator_lock_);
   void PushStackedShadowFrame(ShadowFrame* sf, StackedShadowFrameType type);
-  ShadowFrame* PopStackedShadowFrame(StackedShadowFrameType type, bool must_be_present = true);
+  ShadowFrame* PopStackedShadowFrame();
+  ShadowFrame* MaybePopDeoptimizedStackedShadowFrame();
 
   // For debugger, find the shadow frame that corresponds to a frame id.
   // Or return null if there is none.
@@ -1112,22 +1179,6 @@
   void RemoveDebuggerShadowFrameMapping(size_t frame_id)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // While getting this map requires shared the mutator lock, manipulating it
-  // should actually follow these rules:
-  // (1) The owner of this map (the thread) can change it with its mutator lock.
-  // (2) Other threads can read this map when the owner is suspended and they
-  //     hold the mutator lock.
-  // (3) Other threads can change this map when owning the mutator lock exclusively.
-  //
-  // The reason why (3) needs the mutator lock exclusively (and not just having
-  // the owner suspended) is that we don't want other threads to concurrently read the map.
-  //
-  // TODO: Add a class abstraction to express these rules.
-  std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* GetInstrumentationStack()
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    return tlsPtr_.instrumentation_stack;
-  }
-
   std::vector<ArtMethod*>* GetStackTraceSample() const {
     DCHECK(!IsAotCompiler());
     return tlsPtr_.deps_or_stack_trace_sample.stack_trace_sample;
@@ -1152,6 +1203,22 @@
     tlsPtr_.deps_or_stack_trace_sample.verifier_deps = verifier_deps;
   }
 
+  uintptr_t* GetMethodTraceBuffer() { return tlsPtr_.method_trace_buffer; }
+
+  size_t* GetMethodTraceIndexPtr() { return &tlsPtr_.method_trace_buffer_index; }
+
+  uintptr_t* SetMethodTraceBuffer(uintptr_t* buffer) {
+    return tlsPtr_.method_trace_buffer = buffer;
+  }
+
+  void ResetMethodTraceBuffer() {
+    if (tlsPtr_.method_trace_buffer != nullptr) {
+      delete[] tlsPtr_.method_trace_buffer;
+    }
+    tlsPtr_.method_trace_buffer = nullptr;
+    tlsPtr_.method_trace_buffer_index = 0;
+  }
+
   uint64_t GetTraceClockBase() const {
     return tls64_.trace_clock_base;
   }
@@ -1206,6 +1273,10 @@
     DCHECK_LE(tlsPtr_.thread_local_end, tlsPtr_.thread_local_limit);
   }
 
+  // Called from Concurrent mark-compact GC to slide the TLAB pointers backwards
+  // to adjust to post-compact addresses.
+  void AdjustTlab(size_t slide_bytes);
+
   // Doesn't check that there is room.
   mirror::Object* AllocTlab(size_t bytes);
   void SetTlab(uint8_t* start, uint8_t* end, uint8_t* limit);
@@ -1298,11 +1369,12 @@
 
   bool IncrementMakeVisiblyInitializedCounter() {
     tls32_.make_visibly_initialized_counter += 1u;
-    return tls32_.make_visibly_initialized_counter == kMakeVisiblyInitializedCounterTriggerCount;
-  }
-
-  void ClearMakeVisiblyInitializedCounter() {
-    tls32_.make_visibly_initialized_counter = 0u;
+    DCHECK_LE(tls32_.make_visibly_initialized_counter, kMakeVisiblyInitializedCounterTriggerCount);
+    if (tls32_.make_visibly_initialized_counter == kMakeVisiblyInitializedCounterTriggerCount) {
+      tls32_.make_visibly_initialized_counter = 0u;
+      return true;
+    }
+    return false;
   }
 
   void PushVerifier(verifier::MethodVerifier* verifier);
@@ -1347,10 +1419,9 @@
   // Set to the read barrier marking entrypoints to be non-null.
   void SetReadBarrierEntrypoints();
 
-  static jobject CreateCompileTimePeer(JNIEnv* env,
-                                       const char* name,
-                                       bool as_daemon,
-                                       jobject thread_group)
+  ObjPtr<mirror::Object> CreateCompileTimePeer(const char* name,
+                                               bool as_daemon,
+                                               jobject thread_group)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   ALWAYS_INLINE InterpreterCache* GetInterpreterCache() {
@@ -1413,7 +1484,7 @@
  private:
   explicit Thread(bool daemon);
   ~Thread() REQUIRES(!Locks::mutator_lock_, !Locks::thread_suspend_count_lock_);
-  void Destroy();
+  void Destroy(bool should_run_callbacks);
 
   // Deletes and clears the tlsPtr_.jpeer field. Done in a way so that both it and opeer cannot be
   // observed to be set at the same time by instrumentation.
@@ -1424,16 +1495,16 @@
   template <typename PeerAction>
   static Thread* Attach(const char* thread_name,
                         bool as_daemon,
-                        PeerAction p);
+                        PeerAction p,
+                        bool should_run_callbacks);
 
   void CreatePeer(const char* name, bool as_daemon, jobject thread_group);
 
   template<bool kTransactionActive>
-  static void InitPeer(ScopedObjectAccessAlreadyRunnable& soa,
-                       ObjPtr<mirror::Object> peer,
-                       jboolean thread_is_daemon,
-                       jobject thread_group,
-                       jobject thread_name,
+  static void InitPeer(ObjPtr<mirror::Object> peer,
+                       bool as_daemon,
+                       ObjPtr<mirror::Object> thread_group,
+                       ObjPtr<mirror::String> thread_name,
                        jint thread_priority)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -1479,10 +1550,14 @@
   void VerifyStackImpl() REQUIRES_SHARED(Locks::mutator_lock_);
 
   void DumpState(std::ostream& os) const REQUIRES_SHARED(Locks::mutator_lock_);
-  void DumpStack(std::ostream& os,
-                 bool dump_native_stack = true,
-                 BacktraceMap* backtrace_map = nullptr,
-                 bool force_dump_stack = false) const
+  DumpOrder DumpStack(std::ostream& os,
+                      bool dump_native_stack = true,
+                      bool force_dump_stack = false) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  DumpOrder DumpStack(std::ostream& os,
+                      unwindstack::AndroidLocalUnwinder& unwinder,
+                      bool dump_native_stack = true,
+                      bool force_dump_stack = false) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Out-of-line conveniences for debugging in gdb.
@@ -1490,12 +1565,13 @@
   // Like Thread::Dump(std::cerr).
   void DumpFromGdb() const REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // A wrapper around CreateCallback used when userfaultfd GC is used to
+  // identify the GC by stacktrace.
+  static NO_INLINE void* CreateCallbackWithUffdGc(void* arg);
   static void* CreateCallback(void* arg);
 
-  void HandleUncaughtExceptions(ScopedObjectAccessAlreadyRunnable& soa)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-  void RemoveFromThreadGroup(ScopedObjectAccessAlreadyRunnable& soa)
-      REQUIRES_SHARED(Locks::mutator_lock_);
+  void HandleUncaughtExceptions() REQUIRES_SHARED(Locks::mutator_lock_);
+  void RemoveFromThreadGroup() REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Initialize a thread.
   //
@@ -1563,9 +1639,6 @@
   template <bool kPrecise>
   void VisitRoots(RootVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  static void SweepInterpreterCaches(IsMarkedVisitor* visitor)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   static bool IsAotCompiler();
 
   void ReleaseLongJumpContextInternal();
@@ -1712,6 +1785,7 @@
           thread_exit_check_count(0),
           is_transitioning_to_runnable(false),
           is_gc_marking(false),
+          is_deopt_check_required(false),
           weak_ref_access_enabled(WeakRefAccessState::kVisiblyEnabled),
           disable_thread_flip_count(0),
           user_code_suspend_count(0),
@@ -1766,6 +1840,12 @@
     // GC roots.
     bool32_t is_gc_marking;
 
+    // True if we need to check for deoptimization when returning from the runtime functions. This
+    // is required only when a class is redefined to prevent executing code that has field offsets
+    // embedded. For non-debuggable apps redefinition is not allowed and this flag should always be
+    // set to false.
+    bool32_t is_deopt_check_required;
+
     // Thread "interrupted" status; stays raised until queried or thrown.
     Atomic<bool32_t> interrupted;
 
@@ -1853,19 +1933,18 @@
                                top_handle_scope(nullptr),
                                class_loader_override(nullptr),
                                long_jump_context(nullptr),
-                               instrumentation_stack(nullptr),
                                stacked_shadow_frame_record(nullptr),
                                deoptimization_context_stack(nullptr),
                                frame_id_to_shadow_frame(nullptr),
                                name(nullptr),
                                pthread_self(0),
                                last_no_thread_suspension_cause(nullptr),
-                               checkpoint_function(nullptr),
                                thread_local_start(nullptr),
                                thread_local_pos(nullptr),
                                thread_local_end(nullptr),
                                thread_local_limit(nullptr),
                                thread_local_objects(0),
+                               checkpoint_function(nullptr),
                                thread_local_alloc_stack_top(nullptr),
                                thread_local_alloc_stack_end(nullptr),
                                mutator_lock(nullptr),
@@ -1873,7 +1952,9 @@
                                method_verifier(nullptr),
                                thread_local_mark_stack(nullptr),
                                async_exception(nullptr),
-                               top_reflective_handle_scope(nullptr) {
+                               top_reflective_handle_scope(nullptr),
+                               method_trace_buffer(nullptr),
+                               method_trace_buffer_index(0) {
       std::fill(held_mutexes, held_mutexes + kLockLevelCount, nullptr);
     }
 
@@ -1946,14 +2027,6 @@
     // Thread local, lazily allocated, long jump context. Used to deliver exceptions.
     Context* long_jump_context;
 
-    // Additional stack used by method instrumentation to store method and return pc values.
-    // Stored as a pointer since std::map is not PACKED.
-    // !DO NOT CHANGE! to std::unordered_map: the users of this map require an
-    // ordered iteration on the keys (which are stack addresses).
-    // Also see Thread::GetInstrumentationStack for the requirements on
-    // manipulating and reading this map.
-    std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* instrumentation_stack;
-
     // For gc purpose, a shadow frame record stack that keeps track of:
     // 1) shadow frames under construction.
     // 2) deoptimization shadow frames.
@@ -1979,10 +2052,6 @@
     // If no_thread_suspension_ is > 0, what is causing that assertion.
     const char* last_no_thread_suspension_cause;
 
-    // Pending checkpoint function or null if non-pending. If this checkpoint is set and someone\
-    // requests another checkpoint, it goes to the checkpoint overflow list.
-    Closure* checkpoint_function GUARDED_BY(Locks::thread_suspend_count_lock_);
-
     // Pending barriers that require passing or NULL if non-pending. Installation guarding by
     // Locks::thread_suspend_count_lock_.
     // They work effectively as art::Barrier, but implemented directly using AtomicInteger and futex
@@ -2003,6 +2072,10 @@
 
     size_t thread_local_objects;
 
+    // Pending checkpoint function or null if non-pending. If this checkpoint is set and someone\
+    // requests another checkpoint, it goes to the checkpoint overflow list.
+    Closure* checkpoint_function GUARDED_BY(Locks::thread_suspend_count_lock_);
+
     // Entrypoint function pointers.
     // TODO: move this to more of a global offset table model to avoid per-thread duplication.
     JniEntryPoints jni_entrypoints;
@@ -2028,14 +2101,24 @@
     // Current method verifier, used for root marking.
     verifier::MethodVerifier* method_verifier;
 
-    // Thread-local mark stack for the concurrent copying collector.
-    gc::accounting::AtomicStack<mirror::Object>* thread_local_mark_stack;
+    union {
+      // Thread-local mark stack for the concurrent copying collector.
+      gc::accounting::AtomicStack<mirror::Object>* thread_local_mark_stack;
+      // Thread-local page-sized buffer for userfaultfd GC.
+      uint8_t* thread_local_gc_buffer;
+    };
 
     // The pending async-exception or null.
     mirror::Throwable* async_exception;
 
     // Top of the linked-list for reflective-handle scopes or null if none.
     BaseReflectiveHandleScope* top_reflective_handle_scope;
+
+    // Pointer to a thread-local buffer for method tracing.
+    uintptr_t* method_trace_buffer;
+
+    // The index of the next free entry in method_trace_buffer.
+    size_t method_trace_buffer_index;
   } tlsPtr_;
 
   // Small thread-local cache to be used from the interpreter.
@@ -2068,8 +2151,10 @@
   SafeMap<std::string, std::unique_ptr<TLSData>, std::less<>> custom_tls_
       GUARDED_BY(Locks::custom_tls_lock_);
 
-#ifndef __BIONIC__
-  __attribute__((tls_model("initial-exec")))
+#if !defined(__BIONIC__)
+#if !defined(ANDROID_HOST_MUSL)
+    __attribute__((tls_model("initial-exec")))
+#endif
   static thread_local Thread* self_tls_;
 #endif
 
@@ -2152,17 +2237,18 @@
 
 class ScopedStackedShadowFramePusher {
  public:
-  ScopedStackedShadowFramePusher(Thread* self, ShadowFrame* sf, StackedShadowFrameType type)
-    : self_(self), type_(type) {
-    self_->PushStackedShadowFrame(sf, type);
+  ScopedStackedShadowFramePusher(Thread* self, ShadowFrame* sf) : self_(self), sf_(sf) {
+    DCHECK_EQ(sf->GetLink(), nullptr);
+    self_->PushStackedShadowFrame(sf, StackedShadowFrameType::kShadowFrameUnderConstruction);
   }
   ~ScopedStackedShadowFramePusher() {
-    self_->PopStackedShadowFrame(type_);
+    ShadowFrame* sf = self_->PopStackedShadowFrame();
+    DCHECK_EQ(sf, sf_);
   }
 
  private:
   Thread* const self_;
-  const StackedShadowFrameType type_;
+  ShadowFrame* const sf_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedStackedShadowFramePusher);
 };
@@ -2186,16 +2272,10 @@
   explicit ScopedTransitioningToRunnable(Thread* self)
       : self_(self) {
     DCHECK_EQ(self, Thread::Current());
-    if (kUseReadBarrier) {
-      self_->SetIsTransitioningToRunnable(true);
-    }
+    self_->SetIsTransitioningToRunnable(true);
   }
 
-  ~ScopedTransitioningToRunnable() {
-    if (kUseReadBarrier) {
-      self_->SetIsTransitioningToRunnable(false);
-    }
-  }
+  ~ScopedTransitioningToRunnable() { self_->SetIsTransitioningToRunnable(false); }
 
  private:
   Thread* const self_;
diff --git a/runtime/thread_android.cc b/runtime/thread_android.cc
index e8d01cc..0f41e2f 100644
--- a/runtime/thread_android.cc
+++ b/runtime/thread_android.cc
@@ -42,8 +42,10 @@
       IsAligned<kPageSize>(old_ss.ss_sp) &&
       IsAligned<kPageSize>(old_ss.ss_size)) {
     CHECK_EQ(old_ss.ss_flags & SS_ONSTACK, 0);
-    result = madvise(old_ss.ss_sp, old_ss.ss_size, MADV_DONTNEED);
-    CHECK_EQ(result, 0);
+    // Note: We're testing and benchmarking ART on devices with old kernels
+    // which may not support `MADV_FREE`, so we do not check the result.
+    // It should succeed on devices with Android 12+.
+    madvise(old_ss.ss_sp, old_ss.ss_size, MADV_FREE);
   }
 }
 
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index 6482e72..d98415f 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -20,14 +20,17 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <map>
 #include <sstream>
+#include <tuple>
 #include <vector>
 
 #include "android-base/stringprintf.h"
-#include "backtrace/BacktraceMap.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "nativehelper/scoped_utf_chars.h"
+#include "unwindstack/AndroidUnwinder.h"
 
+#include "art_field-inl.h"
 #include "base/aborting.h"
 #include "base/histogram-inl.h"
 #include "base/mutex-inl.h"
@@ -42,8 +45,10 @@
 #include "gc_root.h"
 #include "jni/jni_internal.h"
 #include "lock_word.h"
+#include "mirror/string.h"
 #include "monitor.h"
 #include "native_stack_dump.h"
+#include "obj_ptr-inl.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
 #include "trace.h"
@@ -101,12 +106,11 @@
     Runtime::Current()->DetachCurrentThread();
   }
   WaitForOtherNonDaemonThreadsToExit();
-  // Disable GC and wait for GC to complete in case there are still daemon threads doing
-  // allocations.
+  // The only caller of this function, ~Runtime, has already disabled GC and
+  // ensured that the last GC is finished.
   gc::Heap* const heap = Runtime::Current()->GetHeap();
-  heap->DisableGCForShutdown();
-  // In case a GC is in progress, wait for it to finish.
-  heap->WaitForGcToComplete(gc::kGcCauseBackground, Thread::Current());
+  CHECK(heap->IsGCDisabledForShutdown());
+
   // TODO: there's an unaddressed race here where a thread may attach during shutdown, see
   //       Thread::Init.
   SuspendAllDaemonThreadsForShutdown();
@@ -124,10 +128,10 @@
 
 void ThreadList::DumpNativeStacks(std::ostream& os) {
   MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
-  std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid()));
+  unwindstack::AndroidLocalUnwinder unwinder;
   for (const auto& thread : list_) {
     os << "DUMPING THREAD " << thread->GetTid() << "\n";
-    DumpNativeStack(os, thread->GetTid(), map.get(), "\t");
+    DumpNativeStack(os, unwinder, thread->GetTid(), "\t");
     os << "\n";
   }
 }
@@ -153,7 +157,7 @@
   // refactor DumpState to avoid skipping analysis.
   Thread::DumpState(os, nullptr, tid);
   if (dump_native_stack) {
-    DumpNativeStack(os, tid, nullptr, "  native: ");
+    DumpNativeStack(os, tid, "  native: ");
   }
   os << std::endl;
 }
@@ -190,16 +194,14 @@
 // A closure used by Thread::Dump.
 class DumpCheckpoint final : public Closure {
  public:
-  DumpCheckpoint(std::ostream* os, bool dump_native_stack)
-      : os_(os),
+  DumpCheckpoint(bool dump_native_stack)
+      : lock_("Dump checkpoint lock", kGenericBottomLock),
+        os_(),
         // Avoid verifying count in case a thread doesn't end up passing through the barrier.
         // This avoids a SIGABRT that would otherwise happen in the destructor.
         barrier_(0, /*verify_count_on_shutdown=*/false),
-        backtrace_map_(dump_native_stack ? BacktraceMap::Create(getpid()) : nullptr),
+        unwinder_(std::vector<std::string>{}, std::vector<std::string> {"oat", "odex"}),
         dump_native_stack_(dump_native_stack) {
-    if (backtrace_map_ != nullptr) {
-      backtrace_map_->SetSuffixesToIgnore(std::vector<std::string> { "oat", "odex" });
-    }
   }
 
   void Run(Thread* thread) override {
@@ -208,18 +210,28 @@
     Thread* self = Thread::Current();
     CHECK(self != nullptr);
     std::ostringstream local_os;
+    Thread::DumpOrder dump_order;
     {
       ScopedObjectAccess soa(self);
-      thread->Dump(local_os, dump_native_stack_, backtrace_map_.get());
+      dump_order = thread->Dump(local_os, unwinder_, dump_native_stack_);
     }
     {
-      // Use the logging lock to ensure serialization when writing to the common ostream.
-      MutexLock mu(self, *Locks::logging_lock_);
-      *os_ << local_os.str() << std::endl;
+      MutexLock mu(self, lock_);
+      // Sort, so that the most interesting threads for ANR are printed first (ANRs can be trimmed).
+      std::pair<Thread::DumpOrder, uint32_t> sort_key(dump_order, thread->GetThreadId());
+      os_.emplace(sort_key, std::move(local_os));
     }
     barrier_.Pass(self);
   }
 
+  // Called at the end to print all the dumps in sequential prioritized order.
+  void Dump(Thread* self, std::ostream& os) {
+    MutexLock mu(self, lock_);
+    for (const auto& it : os_) {
+      os << it.second.str() << std::endl;
+    }
+  }
+
   void WaitForThreadsToRunThroughCheckpoint(size_t threads_running_checkpoint) {
     Thread* self = Thread::Current();
     ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
@@ -232,12 +244,14 @@
   }
 
  private:
-  // The common stream that will accumulate all the dumps.
-  std::ostream* const os_;
+  // Storage for the per-thread dumps (guarded by lock since they are generated in parallel).
+  // Map is used to obtain sorted order. The key is unique, but use multimap just in case.
+  Mutex lock_;
+  std::multimap<std::pair<Thread::DumpOrder, uint32_t>, std::ostringstream> os_ GUARDED_BY(lock_);
   // The barrier to be passed through and for the requestor to wait upon.
   Barrier barrier_;
   // A backtrace map, so that all threads use a shared info and don't reacquire/parse separately.
-  std::unique_ptr<BacktraceMap> backtrace_map_;
+  unwindstack::AndroidLocalUnwinder unwinder_;
   // Whether we should dump the native stack.
   const bool dump_native_stack_;
 };
@@ -249,7 +263,7 @@
     os << "DALVIK THREADS (" << list_.size() << "):\n";
   }
   if (self != nullptr) {
-    DumpCheckpoint checkpoint(&os, dump_native_stack);
+    DumpCheckpoint checkpoint(dump_native_stack);
     size_t threads_running_checkpoint;
     {
       // Use SOA to prevent deadlocks if multiple threads are calling Dump() at the same time.
@@ -259,6 +273,7 @@
     if (threads_running_checkpoint != 0) {
       checkpoint.WaitForThreadsToRunThroughCheckpoint(threads_running_checkpoint);
     }
+    checkpoint.Dump(self, os);
   } else {
     DumpUnattachedThreads(os, dump_native_stack);
   }
@@ -486,7 +501,6 @@
               // Assume it's stuck and safe to dump its stack.
               thread->Dump(LOG_STREAM(FATAL_WITHOUT_ABORT),
                            /*dump_native_stack=*/ true,
-                           /*backtrace_map=*/ nullptr,
                            /*force_dump_stack=*/ true);
             }
           }
@@ -515,11 +529,13 @@
   Locks::thread_list_lock_->AssertNotHeld(self);
   Locks::thread_suspend_count_lock_->AssertNotHeld(self);
   CHECK_NE(self->GetState(), ThreadState::kRunnable);
+  size_t runnable_thread_count = 0;
+  std::vector<Thread*> other_threads;
 
   collector->GetHeap()->ThreadFlipBegin(self);  // Sync with JNI critical calls.
 
-  // ThreadFlipBegin happens before we suspend all the threads, so it does not count towards the
-  // pause.
+  // ThreadFlipBegin happens before we suspend all the threads, so it does not
+  // count towards the pause.
   const uint64_t suspend_start_time = NanoTime();
   SuspendAllInternal(self, self, nullptr);
   if (pause_listener != nullptr) {
@@ -530,15 +546,20 @@
   Locks::mutator_lock_->ExclusiveLock(self);
   suspend_all_historam_.AdjustAndAddValue(NanoTime() - suspend_start_time);
   flip_callback->Run(self);
-  Locks::mutator_lock_->ExclusiveUnlock(self);
-  collector->RegisterPause(NanoTime() - suspend_start_time);
-  if (pause_listener != nullptr) {
-    pause_listener->EndPause();
-  }
+  // Releasing mutator-lock *before* setting up flip function in the threads
+  // leaves a gap for another thread trying to suspend all threads. That thread
+  // gets to run with mutator-lock, thereby accessing the heap, without running
+  // its flip function. It's not a problem with CC as the gc-thread hasn't
+  // started marking yet and the from-space is accessible. By delaying releasing
+  // mutator-lock until after the flip function are running on all threads we
+  // fix that without increasing pause time, except for any thread that might be
+  // trying to suspend all. Even though the change works irrespective of the GC,
+  // it has been limited to userfaultfd GC to keep the change behind the flag.
+  //
+  // TODO: It's a temporary change as aosp/2377951 is going to clean-up at a
+  // broad scale, including not allowing concurrent suspend-all.
 
   // Resume runnable threads.
-  size_t runnable_thread_count = 0;
-  std::vector<Thread*> other_threads;
   {
     TimingLogger::ScopedTiming split2("ResumeRunnableThreads", collector->GetTimings());
     MutexLock mu(self, *Locks::thread_list_lock_);
@@ -569,12 +590,15 @@
     Thread::resume_cond_->Broadcast(self);
   }
 
+  collector->RegisterPause(NanoTime() - suspend_start_time);
+  if (pause_listener != nullptr) {
+    pause_listener->EndPause();
+  }
   collector->GetHeap()->ThreadFlipEnd(self);
 
   // Try to run the closure on the other threads.
   {
     TimingLogger::ScopedTiming split3("FlipOtherThreads", collector->GetTimings());
-    ReaderMutexLock mu(self, *Locks::mutator_lock_);
     for (Thread* thread : other_threads) {
       thread->EnsureFlipFunctionStarted(self);
       DCHECK(!thread->ReadFlag(ThreadFlag::kPendingFlipFunction));
@@ -584,6 +608,8 @@
     DCHECK(!self->ReadFlag(ThreadFlag::kPendingFlipFunction));
   }
 
+  Locks::mutator_lock_->ExclusiveUnlock(self);
+
   // Resume other threads.
   {
     TimingLogger::ScopedTiming split4("ResumeOtherThreads", collector->GetTimings());
@@ -851,20 +877,16 @@
   return true;
 }
 
-static void ThreadSuspendByPeerWarning(Thread* self,
+static void ThreadSuspendByPeerWarning(ScopedObjectAccess& soa,
                                        LogSeverity severity,
                                        const char* message,
-                                       jobject peer) {
-  JNIEnvExt* env = self->GetJniEnv();
-  ScopedLocalRef<jstring>
-      scoped_name_string(env, static_cast<jstring>(env->GetObjectField(
-          peer, WellKnownClasses::java_lang_Thread_name)));
-  ScopedUtfChars scoped_name_chars(env, scoped_name_string.get());
-  if (scoped_name_chars.c_str() == nullptr) {
-      LOG(severity) << message << ": " << peer;
-      env->ExceptionClear();
+                                       jobject peer) REQUIRES_SHARED(Locks::mutator_lock_) {
+  ObjPtr<mirror::Object> name =
+      WellKnownClasses::java_lang_Thread_name->GetObject(soa.Decode<mirror::Object>(peer));
+  if (name == nullptr) {
+    LOG(severity) << message << ": " << peer;
   } else {
-      LOG(severity) << message << ": " << peer << ":" << scoped_name_chars.c_str();
+    LOG(severity) << message << ": " << peer << ":" << name->AsString()->ToModifiedUtf8();
   }
 }
 
@@ -902,7 +924,7 @@
                                                               reason);
           DCHECK(updated);
         }
-        ThreadSuspendByPeerWarning(self,
+        ThreadSuspendByPeerWarning(soa,
                                    ::android::base::WARNING,
                                     "No such thread for suspend",
                                     peer);
@@ -955,7 +977,7 @@
         const uint64_t total_delay = NanoTime() - start_time;
         if (total_delay >= thread_suspend_timeout_ns_) {
           if (suspended_thread == nullptr) {
-            ThreadSuspendByPeerWarning(self,
+            ThreadSuspendByPeerWarning(soa,
                                        ::android::base::FATAL,
                                        "Failed to issue suspend request",
                                        peer);
@@ -967,7 +989,7 @@
             // Explicitly release thread_suspend_count_lock_; we haven't held it for long, so
             // seeing threads blocked on it is not informative.
             Locks::thread_suspend_count_lock_->Unlock(self);
-            ThreadSuspendByPeerWarning(self,
+            ThreadSuspendByPeerWarning(soa,
                                        ::android::base::FATAL,
                                        "Thread suspension timed out",
                                        peer);
@@ -1275,7 +1297,7 @@
   }
   CHECK(!Contains(self));
   list_.push_back(self);
-  if (kUseReadBarrier) {
+  if (gUseReadBarrier) {
     gc::collector::ConcurrentCopying* const cc =
         Runtime::Current()->GetHeap()->ConcurrentCopyingCollector();
     // Initialize according to the state of the CC collector.
@@ -1287,10 +1309,14 @@
   }
 }
 
-void ThreadList::Unregister(Thread* self) {
+void ThreadList::Unregister(Thread* self, bool should_run_callbacks) {
   DCHECK_EQ(self, Thread::Current());
   CHECK_NE(self->GetState(), ThreadState::kRunnable);
   Locks::mutator_lock_->AssertNotHeld(self);
+  if (self->tls32_.disable_thread_flip_count != 0) {
+    LOG(FATAL) << "Incomplete PrimitiveArrayCritical section at exit: " << *self << "count = "
+               << self->tls32_.disable_thread_flip_count;
+  }
 
   VLOG(threads) << "ThreadList::Unregister() " << *self;
 
@@ -1304,7 +1330,7 @@
   // causes the threads to join. It is important to do this after incrementing unregistering_count_
   // since we want the runtime to wait for the daemon threads to exit before deleting the thread
   // list.
-  self->Destroy();
+  self->Destroy(should_run_callbacks);
 
   // If tracing, remember thread id and name before thread exits.
   Trace::StoreExitingThreadInfo(self);
@@ -1320,7 +1346,7 @@
         std::string thread_name;
         self->GetThreadName(thread_name);
         std::ostringstream os;
-        DumpNativeStack(os, GetTid(), nullptr, "  native: ", nullptr);
+        DumpNativeStack(os, GetTid(), "  native: ", nullptr);
         LOG(ERROR) << "Request to unregister unattached thread " << thread_name << "\n" << os.str();
         break;
       } else {
@@ -1414,6 +1440,13 @@
   }
 }
 
+void ThreadList::SweepInterpreterCaches(IsMarkedVisitor* visitor) const {
+  MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+  for (const auto& thread : list_) {
+    thread->SweepInterpreterCache(visitor);
+  }
+}
+
 uint32_t ThreadList::AllocThreadId(Thread* self) {
   MutexLock mu(self, *Locks::allocated_thread_ids_lock_);
   for (size_t i = 0; i < allocated_ids_.size(); ++i) {
diff --git a/runtime/thread_list.h b/runtime/thread_list.h
index 29b0c52..51fac4a 100644
--- a/runtime/thread_list.h
+++ b/runtime/thread_list.h
@@ -127,7 +127,7 @@
       REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_);
 
   // Flip thread roots from from-space refs to to-space refs. Used by
-  // the concurrent copying collector.
+  // the concurrent moving collectors.
   size_t FlipThreadRoots(Closure* thread_flip_visitor,
                          Closure* flip_callback,
                          gc::collector::GarbageCollector* collector,
@@ -153,7 +153,7 @@
       REQUIRES(!Locks::mutator_lock_,
                !Locks::thread_list_lock_,
                !Locks::thread_suspend_count_lock_);
-  void Unregister(Thread* self)
+  void Unregister(Thread* self, bool should_run_callbacks)
       REQUIRES(!Locks::mutator_lock_,
                !Locks::thread_list_lock_,
                !Locks::thread_suspend_count_lock_);
@@ -167,6 +167,9 @@
 
   void VisitReflectiveTargets(ReflectiveValueVisitor* visitor) const REQUIRES(Locks::mutator_lock_);
 
+  void SweepInterpreterCaches(IsMarkedVisitor* visitor) const
+      REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_);
+
   // Return a copy of the thread list.
   std::list<Thread*> GetList() REQUIRES(Locks::thread_list_lock_) {
     return list_;
diff --git a/runtime/thread_pool.cc b/runtime/thread_pool.cc
index 57d7f61..92ac845 100644
--- a/runtime/thread_pool.cc
+++ b/runtime/thread_pool.cc
@@ -119,6 +119,12 @@
 void* ThreadPoolWorker::Callback(void* arg) {
   ThreadPoolWorker* worker = reinterpret_cast<ThreadPoolWorker*>(arg);
   Runtime* runtime = Runtime::Current();
+  // Don't run callbacks for ThreadPoolWorkers. These are created for JITThreadPool and
+  // HeapThreadPool and are purely internal threads of the runtime and we don't need to run
+  // callbacks for the thread attach / detach listeners.
+  // (b/251163712) Calling callbacks for heap thread pool workers causes deadlocks in some libjdwp
+  // tests. Deadlocks happen when a GC thread is attached while libjdwp holds the event handler
+  // lock for an event that triggers an entrypoint update from deopt manager.
   CHECK(runtime->AttachCurrentThread(
       worker->name_.c_str(),
       true,
@@ -129,13 +135,14 @@
       // rely on being able to (for example) wait for all threads to finish some task. If debuggers
       // are suspending these threads that might not be possible.
       worker->thread_pool_->create_peers_ ? runtime->GetSystemThreadGroup() : nullptr,
-      worker->thread_pool_->create_peers_));
+      worker->thread_pool_->create_peers_,
+      /* should_run_callbacks= */ false));
   worker->thread_ = Thread::Current();
   // Mark thread pool workers as runtime-threads.
   worker->thread_->SetIsRuntimeThread(true);
   // Do work until its time to shut down.
   worker->Run();
-  runtime->DetachCurrentThread();
+  runtime->DetachCurrentThread(/* should_run_callbacks= */ false);
   return nullptr;
 }
 
@@ -246,6 +253,11 @@
   started_ = false;
 }
 
+bool ThreadPool::HasStarted(Thread* self) {
+  MutexLock mu(self, task_queue_lock_);
+  return started_;
+}
+
 Task* ThreadPool::GetTask(Thread* self) {
   MutexLock mu(self, task_queue_lock_);
   while (!IsShuttingDown()) {
diff --git a/runtime/thread_pool.h b/runtime/thread_pool.h
index b9e5a97..5c75733 100644
--- a/runtime/thread_pool.h
+++ b/runtime/thread_pool.h
@@ -123,6 +123,9 @@
   // Do not allow workers to grab any new tasks.
   void StopWorkers(Thread* self) REQUIRES(!task_queue_lock_);
 
+  // Returns if the thread pool has started.
+  bool HasStarted(Thread* self) REQUIRES(!task_queue_lock_);
+
   // Add a new task, the first available started worker will process it. Does not delete the task
   // after running it, it is the caller's responsibility.
   void AddTask(Thread* self, Task* task) REQUIRES(!task_queue_lock_);
diff --git a/runtime/trace.cc b/runtime/trace.cc
index ec61726..429a5ae 100644
--- a/runtime/trace.cc
+++ b/runtime/trace.cc
@@ -82,33 +82,147 @@
   return static_cast<TraceAction>(tmid & kTraceMethodActionMask);
 }
 
+namespace {
+// Scaling factor to convert timestamp counter into wall clock time reported in micro seconds.
+// This is initialized at the start of tracing using the timestamp counter update frequency.
+// See InitializeTimestampCounters for more details.
+double tsc_to_microsec_scaling_factor = -1.0;
+
+uint64_t GetTimestamp() {
+  uint64_t t = 0;
+#if defined(__arm__)
+  // See Architecture Reference Manual ARMv7-A and ARMv7-R edition section B4.1.34
+  // Q and R specify that they should be written to lower and upper halves of 64-bit value.
+  // See: https://llvm.org/docs/LangRef.html#asm-template-argument-modifiers
+  asm volatile("mrrc p15, 1, %Q0, %R0, c14" : "=r"(t));
+#elif defined(__aarch64__)
+  // See Arm Architecture Registers  Armv8 section System Registers
+  asm volatile("mrs %0, cntvct_el0" : "=r"(t));
+#elif defined(__i386__) || defined(__x86_64__)
+  // rdtsc returns two 32-bit values in rax and rdx even on 64-bit architectures.
+  unsigned int lo, hi;
+  asm volatile("rdtsc" : "=a"(lo), "=d"(hi));
+  t = (static_cast<uint64_t>(hi) << 32) | lo;
+#else
+  t = MicroTime();
+#endif
+  return t;
+}
+
+#if defined(__i386__) || defined(__x86_64__)
+// Here we compute the scaling factor by sleeping for a millisecond. Alternatively, we could
+// generate raw timestamp counter and also time using clock_gettime at the start and the end of the
+// trace. We can compute the frequency of timestamp counter upadtes in the post processing step
+// using these two samples. However, that would require a change in Android Studio which is the main
+// consumer of these profiles. For now, just compute the frequency of tsc updates here.
+double computeScalingFactor() {
+  uint64_t start = MicroTime();
+  uint64_t start_tsc = GetTimestamp();
+  // Sleep for one millisecond.
+  usleep(1000);
+  uint64_t diff_tsc = GetTimestamp() - start_tsc;
+  uint64_t diff_time = MicroTime() - start;
+  double scaling_factor = static_cast<double>(diff_time) / diff_tsc;
+  DCHECK(scaling_factor > 0.0) << scaling_factor;
+  return scaling_factor;
+}
+
+double GetScalingFactorForX86() {
+  uint32_t eax, ebx, ecx;
+  asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx) : "a"(0x0), "c"(0));
+  if (eax < 0x15) {
+    // There is no 15H - Timestamp counter and core crystal clock information
+    // leaf. Just compute the frequency.
+    return computeScalingFactor();
+  }
+
+  // From Intel architecture-instruction-set-extensions-programming-reference:
+  // EBX[31:0]/EAX[31:0] indicates the ratio of the TSC frequency and the
+  // core crystal clock frequency.
+  // If EBX[31:0] is 0, the TSC and "core crystal clock" ratio is not enumerated.
+  // If ECX is 0, the nominal core crystal clock frequency is not enumerated.
+  // "TSC frequency" = "core crystal clock frequency" * EBX/EAX.
+  // The core crystal clock may differ from the reference clock, bus clock, or core clock
+  // frequencies.
+  // EAX Bits 31 - 00: An unsigned integer which is the denominator of the
+  //                   TSC/"core crystal clock" ratio.
+  // EBX Bits 31 - 00: An unsigned integer which is the numerator of the
+  //                   TSC/"core crystal clock" ratio.
+  // ECX Bits 31 - 00: An unsigned integer which is the nominal frequency of the core
+  //                   crystal clock in Hz.
+  // EDX Bits 31 - 00: Reserved = 0.
+  asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx) : "a"(0x15), "c"(0));
+  if (ebx == 0 || ecx == 0) {
+    return computeScalingFactor();
+  }
+  double coreCrystalFreq = ecx;
+  // frequency = coreCrystalFreq * (ebx / eax)
+  // scaling_factor = seconds_to_microseconds / frequency
+  //                = seconds_to_microseconds * eax / (coreCrystalFreq * ebx)
+  double seconds_to_microseconds = 1000 * 1000;
+  double scaling_factor = (seconds_to_microseconds * eax) / (coreCrystalFreq * ebx);
+  return scaling_factor;
+}
+#endif
+
+void InitializeTimestampCounters() {
+  // It is sufficient to initialize this once for the entire execution. Just return if it is
+  // already initialized.
+  if (tsc_to_microsec_scaling_factor > 0.0) {
+    return;
+  }
+
+#if defined(__arm__)
+  double seconds_to_microseconds = 1000 * 1000;
+  uint64_t freq = 0;
+  // See Architecture Reference Manual ARMv7-A and ARMv7-R edition section B4.1.21
+  asm volatile("mrc p15, 0, %0, c14, c0, 0" : "=r"(freq));
+  tsc_to_microsec_scaling_factor = seconds_to_microseconds / static_cast<double>(freq);
+#elif defined(__aarch64__)
+  double seconds_to_microseconds = 1000 * 1000;
+  uint64_t freq = 0;
+  // See Arm Architecture Registers  Armv8 section System Registers
+  asm volatile("mrs %0,  cntfrq_el0" : "=r"(freq));
+  tsc_to_microsec_scaling_factor = seconds_to_microseconds / static_cast<double>(freq);
+#elif defined(__i386__) || defined(__x86_64__)
+  tsc_to_microsec_scaling_factor = GetScalingFactorForX86();
+#else
+  tsc_to_microsec_scaling_factor = 1.0;
+#endif
+}
+
+ALWAYS_INLINE uint64_t GetMicroTime(uint64_t counter) {
+  DCHECK(tsc_to_microsec_scaling_factor > 0.0) << tsc_to_microsec_scaling_factor;
+  return tsc_to_microsec_scaling_factor * counter;
+}
+
+}  // namespace
+
 ArtMethod* Trace::DecodeTraceMethod(uint32_t tmid) {
-  MutexLock mu(Thread::Current(), *unique_methods_lock_);
-  return unique_methods_[tmid >> TraceActionBits];
+  uint32_t method_index = tmid >> TraceActionBits;
+  // This is used only for logging which is usually needed only for debugging ART. So it's not
+  // performance critical.
+  for (auto const& entry : art_method_id_map_) {
+    if (method_index == entry.second) {
+      return entry.first;
+    }
+  }
+  return nullptr;
 }
 
 uint32_t Trace::EncodeTraceMethod(ArtMethod* method) {
-  MutexLock mu(Thread::Current(), *unique_methods_lock_);
-  uint32_t idx;
+  uint32_t idx = 0;
   auto it = art_method_id_map_.find(method);
   if (it != art_method_id_map_.end()) {
     idx = it->second;
   } else {
-    unique_methods_.push_back(method);
-    idx = unique_methods_.size() - 1;
+    idx = current_method_index_;
     art_method_id_map_.emplace(method, idx);
+    current_method_index_++;
   }
-  DCHECK_LT(idx, unique_methods_.size());
-  DCHECK_EQ(unique_methods_[idx], method);
   return idx;
 }
 
-uint32_t Trace::EncodeTraceMethodAndAction(ArtMethod* method, TraceAction action) {
-  uint32_t tmid = (EncodeTraceMethod(method) << TraceActionBits) | action;
-  DCHECK_EQ(method, DecodeTraceMethod(tmid));
-  return tmid;
-}
-
 std::vector<ArtMethod*>* Trace::AllocStackTrace() {
   return (temp_stack_trace_.get() != nullptr)  ? temp_stack_trace_.release() :
       new std::vector<ArtMethod*>();
@@ -154,7 +268,7 @@
     Thread::Current()->GetCpuMicroTime();
   }
   if (UseWallClock()) {
-    MicroTime();
+    GetTimestamp();
   }
 }
 
@@ -237,14 +351,13 @@
   thread->SetStackTraceSample(stack_trace);
   // Read timer clocks to use for all events in this trace.
   uint32_t thread_clock_diff = 0;
-  uint32_t wall_clock_diff = 0;
-  ReadClocks(thread, &thread_clock_diff, &wall_clock_diff);
+  uint64_t timestamp_counter = 0;
+  ReadClocks(thread, &thread_clock_diff, &timestamp_counter);
   if (old_stack_trace == nullptr) {
     // If there's no previous stack trace sample for this thread, log an entry event for all
     // methods in the trace.
     for (auto rit = stack_trace->rbegin(); rit != stack_trace->rend(); ++rit) {
-      LogMethodTraceEvent(thread, *rit, instrumentation::Instrumentation::kMethodEntered,
-                          thread_clock_diff, wall_clock_diff);
+      LogMethodTraceEvent(thread, *rit, kTraceMethodEnter, thread_clock_diff, timestamp_counter);
     }
   } else {
     // If there's a previous stack trace for this thread, diff the traces and emit entry and exit
@@ -258,13 +371,11 @@
     }
     // Iterate top-down over the old trace until the point where they differ, emitting exit events.
     for (auto old_it = old_stack_trace->begin(); old_it != old_rit.base(); ++old_it) {
-      LogMethodTraceEvent(thread, *old_it, instrumentation::Instrumentation::kMethodExited,
-                          thread_clock_diff, wall_clock_diff);
+      LogMethodTraceEvent(thread, *old_it, kTraceMethodExit, thread_clock_diff, timestamp_counter);
     }
     // Iterate bottom-up over the new trace from the point where they differ, emitting entry events.
     for (; rit != stack_trace->rend(); ++rit) {
-      LogMethodTraceEvent(thread, *rit, instrumentation::Instrumentation::kMethodEntered,
-                          thread_clock_diff, wall_clock_diff);
+      LogMethodTraceEvent(thread, *rit, kTraceMethodEnter, thread_clock_diff, timestamp_counter);
     }
     FreeStackTrace(old_stack_trace);
   }
@@ -285,7 +396,7 @@
     {
       MutexLock mu(self, *Locks::trace_lock_);
       the_trace = the_trace_;
-      if (the_trace == nullptr) {
+      if (the_trace_->stop_tracing_) {
         break;
       }
     }
@@ -388,22 +499,21 @@
     return;
   }
 
+  // Initialize the frequency of timestamp counter updates here. This is needed
+  // to get wallclock time from timestamp counter values.
+  InitializeTimestampCounters();
+
   Runtime* runtime = Runtime::Current();
 
   // Enable count of allocs if specified in the flags.
   bool enable_stats = false;
 
-  if (runtime->GetJit() != nullptr) {
-    // TODO b/110263880 It would be better if we didn't need to do this.
-    // Since we need to hold the method entrypoint across a suspend to ensure instrumentation
-    // hooks are called correctly we have to disable jit-gc to ensure that the entrypoint doesn't
-    // go away. Furthermore we need to leave this off permanently since one could get the same
-    // effect by causing this to be toggled on and off.
-    runtime->GetJit()->GetCodeCache()->SetGarbageCollectCode(false);
-  }
-
   // Create Trace object.
   {
+    // Suspend JIT here since we are switching runtime to debuggable. Debuggable runtimes cannot use
+    // JITed code from before so we need to invalidated all JITed code here. Enter suspend JIT scope
+    // to prevent any races with ongoing JIT compilations.
+    jit::ScopedJitSuspend suspend_jit;
     // Required since EnableMethodTracing calls ConfigureStubs which visits class linker classes.
     gc::ScopedGCCriticalSection gcs(self,
                                     gc::kGcCauseInstrumentation,
@@ -421,6 +531,17 @@
                                             "Sampling profiler thread");
         the_trace_->interval_us_ = interval_us;
       } else {
+        if (!runtime->IsJavaDebuggable()) {
+          art::jit::Jit* jit = runtime->GetJit();
+          if (jit != nullptr) {
+            jit->GetCodeCache()->InvalidateAllCompiledCode();
+            jit->GetCodeCache()->TransitionToDebuggable();
+            jit->GetJitCompiler()->SetDebuggableCompilerOption(true);
+          }
+          runtime->SetRuntimeDebugState(art::Runtime::RuntimeDebugState::kJavaDebuggable);
+          runtime->GetInstrumentation()->UpdateEntrypointsForDebuggable();
+          runtime->DeoptimizeBootImage();
+        }
         runtime->GetInstrumentation()->AddListener(
             the_trace_,
             instrumentation::Instrumentation::kMethodEntered |
@@ -431,8 +552,9 @@
         // we know that inlining and other problematic optimizations are disabled. We might just
         // want to use the trampolines anyway since it is faster. It makes the story with disabling
         // jit-gc more complex though.
-        runtime->GetInstrumentation()->EnableMethodTracing(
-            kTracerInstrumentationKey, /*needs_interpreter=*/!runtime->IsJavaDebuggable());
+        runtime->GetInstrumentation()->EnableMethodTracing(kTracerInstrumentationKey,
+                                                           the_trace_,
+                                                           /*needs_interpreter=*/false);
       }
     }
   }
@@ -443,78 +565,129 @@
   }
 }
 
+void Trace::UpdateThreadsList(Thread* thread) {
+  // TODO(mythria): Clean this up and update threads_list_ when recording the trace event similar
+  // to what we do for streaming case.
+  std::string name;
+  thread->GetThreadName(name);
+  // In tests, we destroy VM after already detaching the current thread. When a thread is
+  // detached we record the information about the threads_list_. We re-attach the current
+  // thread again as a "Shutdown thread" in the process of shutting down. So don't record
+  // information about shutdown threads.
+  if (name.compare("Shutdown thread") == 0) {
+    return;
+  }
+
+  // There can be races when unregistering a thread and stopping the trace and it is possible to
+  // update the list twice. For example, This information is updated here when stopping tracing and
+  // also when a thread is detaching. In thread detach, we first update this information and then
+  // remove the thread from the list of active threads. If the tracing was stopped in between these
+  // events, we can see two updates for the same thread. Since we need a trace_lock_ it isn't easy
+  // to prevent this race (for ex: update this information when holding thread_list_lock_). It is
+  // harmless to do two updates so just use overwrite here.
+  threads_list_.Overwrite(thread->GetTid(), name);
+}
+
 void Trace::StopTracing(bool finish_tracing, bool flush_file) {
-  bool stop_alloc_counting = false;
   Runtime* const runtime = Runtime::Current();
-  Trace* the_trace = nullptr;
   Thread* const self = Thread::Current();
   pthread_t sampling_pthread = 0U;
   {
     MutexLock mu(self, *Locks::trace_lock_);
     if (the_trace_ == nullptr) {
       LOG(ERROR) << "Trace stop requested, but no trace currently running";
-    } else {
-      the_trace = the_trace_;
-      the_trace_ = nullptr;
-      sampling_pthread = sampling_pthread_;
+      return;
     }
+    // Tell sampling_pthread_ to stop tracing.
+    the_trace_->stop_tracing_ = true;
+    sampling_pthread = sampling_pthread_;
   }
+
   // Make sure that we join before we delete the trace since we don't want to have
   // the sampling thread access a stale pointer. This finishes since the sampling thread exits when
   // the_trace_ is null.
   if (sampling_pthread != 0U) {
     CHECK_PTHREAD_CALL(pthread_join, (sampling_pthread, nullptr), "sampling thread shutdown");
+  }
+
+  // Make a copy of the_trace_, so it can be flushed later. We want to reset
+  // the_trace_ to nullptr in suspend all scope to prevent any races
+  Trace* the_trace = the_trace_;
+  bool stop_alloc_counting = (the_trace->flags_ & Trace::kTraceCountAllocs) != 0;
+  // Stop the trace sources adding more entries to the trace buffer and synchronise stores.
+  {
+    gc::ScopedGCCriticalSection gcs(
+        self, gc::kGcCauseInstrumentation, gc::kCollectorTypeInstrumentation);
+    jit::ScopedJitSuspend suspend_jit;
+    ScopedSuspendAll ssa(__FUNCTION__);
+
+    if (the_trace->trace_mode_ == TraceMode::kSampling) {
+      MutexLock mu(self, *Locks::thread_list_lock_);
+      runtime->GetThreadList()->ForEach(ClearThreadStackTraceAndClockBase, nullptr);
+    } else {
+      runtime->GetInstrumentation()->RemoveListener(
+          the_trace,
+          instrumentation::Instrumentation::kMethodEntered |
+              instrumentation::Instrumentation::kMethodExited |
+              instrumentation::Instrumentation::kMethodUnwind);
+      runtime->GetInstrumentation()->DisableMethodTracing(kTracerInstrumentationKey);
+      runtime->GetInstrumentation()->MaybeSwitchRuntimeDebugState(self);
+    }
+
+    // Flush thread specific buffer from all threads before resetting the_trace_ to nullptr.
+    // We also flush the buffer when destroying a thread which expects the_trace_ to be valid so
+    // make sure that the per-thread buffer is reset before resetting the_trace_.
+    {
+      MutexLock tl_lock(Thread::Current(), *Locks::thread_list_lock_);
+      for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) {
+        if (thread->GetMethodTraceBuffer() != nullptr) {
+          the_trace_->FlushStreamingBuffer(thread);
+          thread->ResetMethodTraceBuffer();
+        }
+        // Record threads here before resetting the_trace_ to prevent any races between
+        // unregistering the thread and resetting the_trace_.
+        the_trace->UpdateThreadsList(thread);
+      }
+    }
+
+    // Reset the_trace_ by taking a trace_lock
+    MutexLock mu(self, *Locks::trace_lock_);
+    the_trace_ = nullptr;
     sampling_pthread_ = 0U;
   }
 
-  if (the_trace != nullptr) {
-    stop_alloc_counting = (the_trace->flags_ & Trace::kTraceCountAllocs) != 0;
-    // Stop the trace sources adding more entries to the trace buffer and synchronise stores.
-    {
-      gc::ScopedGCCriticalSection gcs(self,
-                                      gc::kGcCauseInstrumentation,
-                                      gc::kCollectorTypeInstrumentation);
-      ScopedSuspendAll ssa(__FUNCTION__);
-
-      if (the_trace->trace_mode_ == TraceMode::kSampling) {
-        MutexLock mu(self, *Locks::thread_list_lock_);
-        runtime->GetThreadList()->ForEach(ClearThreadStackTraceAndClockBase, nullptr);
-      } else {
-        runtime->GetInstrumentation()->RemoveListener(
-            the_trace,
-            instrumentation::Instrumentation::kMethodEntered |
-                instrumentation::Instrumentation::kMethodExited |
-                instrumentation::Instrumentation::kMethodUnwind);
-        runtime->GetInstrumentation()->DisableMethodTracing(kTracerInstrumentationKey);
-      }
-    }
-    // At this point, code may read buf_ as it's writers are shutdown
-    // and the ScopedSuspendAll above has ensured all stores to buf_
-    // are now visible.
-    if (finish_tracing) {
-      the_trace->FinishTracing();
-    }
-    if (the_trace->trace_file_.get() != nullptr) {
-      // Do not try to erase, so flush and close explicitly.
-      if (flush_file) {
-        if (the_trace->trace_file_->Flush() != 0) {
-          PLOG(WARNING) << "Could not flush trace file.";
-        }
-      } else {
-        the_trace->trace_file_->MarkUnchecked();  // Do not trigger guard.
-      }
-      if (the_trace->trace_file_->Close() != 0) {
-        PLOG(ERROR) << "Could not close trace file.";
-      }
-    }
-    delete the_trace;
+  // At this point, code may read buf_ as its writers are shutdown
+  // and the ScopedSuspendAll above has ensured all stores to buf_
+  // are now visible.
+  if (finish_tracing) {
+    the_trace->FinishTracing();
   }
+  if (the_trace->trace_file_.get() != nullptr) {
+    // Do not try to erase, so flush and close explicitly.
+    if (flush_file) {
+      if (the_trace->trace_file_->Flush() != 0) {
+        PLOG(WARNING) << "Could not flush trace file.";
+      }
+    } else {
+      the_trace->trace_file_->MarkUnchecked();  // Do not trigger guard.
+    }
+    if (the_trace->trace_file_->Close() != 0) {
+      PLOG(ERROR) << "Could not close trace file.";
+    }
+  }
+  delete the_trace;
+
   if (stop_alloc_counting) {
     // Can be racy since SetStatsEnabled is not guarded by any locks.
     runtime->SetStatsEnabled(false);
   }
 }
 
+void Trace::FlushThreadBuffer(Thread* self) {
+  MutexLock mu(self, *Locks::trace_lock_);
+  the_trace_->FlushStreamingBuffer(self);
+}
+
 void Trace::Abort() {
   // Do not write anything anymore.
   StopTracing(false, false);
@@ -548,6 +721,28 @@
 }
 
 static constexpr size_t kMinBufSize = 18U;  // Trace header is up to 18B.
+// Size of per-thread buffer size. The value is chosen arbitrarily. This value
+// should be greater than kMinBufSize.
+static constexpr size_t kPerThreadBufSize = 512 * 1024;
+static_assert(kPerThreadBufSize > kMinBufSize);
+
+namespace {
+
+TraceClockSource GetClockSourceFromFlags(int flags) {
+  bool need_wall = flags & Trace::TraceFlag::kTraceClockSourceWallClock;
+  bool need_thread_cpu = flags & Trace::TraceFlag::kTraceClockSourceThreadCpu;
+  if (need_wall && need_thread_cpu) {
+    return TraceClockSource::kDual;
+  } else if (need_wall) {
+    return TraceClockSource::kWall;
+  } else if (need_thread_cpu) {
+    return TraceClockSource::kThreadCpu;
+  } else {
+    return kDefaultTraceClockSource;
+  }
+}
+
+}  // namespace
 
 Trace::Trace(File* trace_file,
              size_t buffer_size,
@@ -556,12 +751,17 @@
              TraceMode trace_mode)
     : trace_file_(trace_file),
       buf_(new uint8_t[std::max(kMinBufSize, buffer_size)]()),
-      flags_(flags), trace_output_mode_(output_mode), trace_mode_(trace_mode),
-      clock_source_(default_clock_source_),
+      flags_(flags),
+      trace_output_mode_(output_mode),
+      trace_mode_(trace_mode),
+      clock_source_(GetClockSourceFromFlags(flags)),
       buffer_size_(std::max(kMinBufSize, buffer_size)),
-      start_time_(MicroTime()), clock_overhead_ns_(GetClockOverheadNanoSeconds()),
-      overflow_(false), interval_us_(0), streaming_lock_(nullptr),
-      unique_methods_lock_(new Mutex("unique methods lock", kTracingUniqueMethodsLock)) {
+      start_time_(GetMicroTime(GetTimestamp())),
+      clock_overhead_ns_(GetClockOverheadNanoSeconds()),
+      overflow_(false),
+      interval_us_(0),
+      stop_tracing_(false),
+      tracing_lock_("tracing lock", LockLevel::kTracingStreamingLock) {
   CHECK_IMPLIES(trace_file == nullptr, output_mode == TraceOutputMode::kDDMS);
 
   uint16_t trace_version = GetTraceVersion(clock_source_);
@@ -583,16 +783,15 @@
   cur_offset_.store(kTraceHeaderLength, std::memory_order_relaxed);
 
   if (output_mode == TraceOutputMode::kStreaming) {
-    streaming_lock_ = new Mutex("tracing lock", LockLevel::kTracingStreamingLock);
-    seen_threads_.reset(new ThreadIDBitSet());
+    // Flush the header information to the file. We use a per thread buffer, so
+    // it is easier to just write the header information directly to file.
+    if (!trace_file_->WriteFully(buf_.get(), kTraceHeaderLength)) {
+      PLOG(WARNING) << "Failed streaming a tracing event.";
+    }
+    cur_offset_.store(0, std::memory_order_relaxed);
   }
 }
 
-Trace::~Trace() {
-  delete streaming_lock_;
-  delete unique_methods_lock_;
-}
-
 static uint64_t ReadBytes(uint8_t* buf, size_t bytes) {
   uint64_t ret = 0;
   for (size_t i = 0; i < bytes; ++i) {
@@ -605,6 +804,7 @@
   uint8_t* ptr = buf + kTraceHeaderLength;
   uint8_t* end = buf + buf_size;
 
+  MutexLock mu(Thread::Current(), tracing_lock_);
   while (ptr < end) {
     uint32_t tmid = ReadBytes(ptr + 2, sizeof(tmid));
     ArtMethod* method = DecodeTraceMethod(tmid);
@@ -616,18 +816,12 @@
 
 void Trace::FinishTracing() {
   size_t final_offset = 0;
-  std::set<ArtMethod*> visited_methods;
-  if (trace_output_mode_ == TraceOutputMode::kStreaming) {
-    // Clean up.
-    MutexLock mu(Thread::Current(), *streaming_lock_);
-    STLDeleteValues(&seen_methods_);
-  } else {
+  if (trace_output_mode_ != TraceOutputMode::kStreaming) {
     final_offset = cur_offset_.load(std::memory_order_relaxed);
-    GetVisitedMethods(final_offset, &visited_methods);
   }
 
   // Compute elapsed time.
-  uint64_t elapsed = MicroTime() - start_time_;
+  uint64_t elapsed = GetMicroTime(GetTimestamp()) - start_time_;
 
   std::ostringstream os;
 
@@ -659,25 +853,25 @@
   os << StringPrintf("%cthreads\n", kTraceTokenChar);
   DumpThreadList(os);
   os << StringPrintf("%cmethods\n", kTraceTokenChar);
-  DumpMethodList(os, visited_methods);
+  DumpMethodList(os);
   os << StringPrintf("%cend\n", kTraceTokenChar);
   std::string header(os.str());
 
   if (trace_output_mode_ == TraceOutputMode::kStreaming) {
-    // Protect access to buf_ and satisfy sanitizer for calls to WriteBuf / FlushBuf.
-    MutexLock mu(Thread::Current(), *streaming_lock_);
+    // It is expected that this method is called when all other threads are suspended, so there
+    // cannot be any writes to trace_file_ after finish tracing.
     // Write a special token to mark the end of trace records and the start of
     // trace summary.
     uint8_t buf[7];
     Append2LE(buf, 0);
     buf[2] = kOpTraceSummary;
     Append4LE(buf + 3, static_cast<uint32_t>(header.length()));
-    WriteToBuf(buf, sizeof(buf));
     // Write the trace summary. The summary is identical to the file header when
     // the output mode is not streaming (except for methods).
-    WriteToBuf(reinterpret_cast<const uint8_t*>(header.c_str()), header.length());
-    // Flush the buffer, which may include some trace records before the summary.
-    FlushBuf();
+    if (!trace_file_->WriteFully(buf, sizeof(buf)) ||
+        !trace_file_->WriteFully(header.c_str(), header.length())) {
+      PLOG(WARNING) << "Failed streaming a tracing event.";
+    }
   } else {
     if (trace_file_.get() == nullptr) {
       std::vector<uint8_t> data;
@@ -736,10 +930,9 @@
 
 void Trace::MethodEntered(Thread* thread, ArtMethod* method) {
   uint32_t thread_clock_diff = 0;
-  uint32_t wall_clock_diff = 0;
-  ReadClocks(thread, &thread_clock_diff, &wall_clock_diff);
-  LogMethodTraceEvent(thread, method, instrumentation::Instrumentation::kMethodEntered,
-                      thread_clock_diff, wall_clock_diff);
+  uint64_t timestamp_counter = 0;
+  ReadClocks(thread, &thread_clock_diff, &timestamp_counter);
+  LogMethodTraceEvent(thread, method, kTraceMethodEnter, thread_clock_diff, timestamp_counter);
 }
 
 void Trace::MethodExited(Thread* thread,
@@ -747,24 +940,18 @@
                          instrumentation::OptionalFrame frame ATTRIBUTE_UNUSED,
                          JValue& return_value ATTRIBUTE_UNUSED) {
   uint32_t thread_clock_diff = 0;
-  uint32_t wall_clock_diff = 0;
-  ReadClocks(thread, &thread_clock_diff, &wall_clock_diff);
-  LogMethodTraceEvent(thread,
-                      method,
-                      instrumentation::Instrumentation::kMethodExited,
-                      thread_clock_diff,
-                      wall_clock_diff);
+  uint64_t timestamp_counter = 0;
+  ReadClocks(thread, &thread_clock_diff, &timestamp_counter);
+  LogMethodTraceEvent(thread, method, kTraceMethodExit, thread_clock_diff, timestamp_counter);
 }
 
 void Trace::MethodUnwind(Thread* thread,
-                         Handle<mirror::Object> this_object ATTRIBUTE_UNUSED,
                          ArtMethod* method,
                          uint32_t dex_pc ATTRIBUTE_UNUSED) {
   uint32_t thread_clock_diff = 0;
-  uint32_t wall_clock_diff = 0;
-  ReadClocks(thread, &thread_clock_diff, &wall_clock_diff);
-  LogMethodTraceEvent(thread, method, instrumentation::Instrumentation::kMethodUnwind,
-                      thread_clock_diff, wall_clock_diff);
+  uint64_t timestamp_counter = 0;
+  ReadClocks(thread, &thread_clock_diff, &timestamp_counter);
+  LogMethodTraceEvent(thread, method, kTraceUnroll, thread_clock_diff, timestamp_counter);
 }
 
 void Trace::ExceptionThrown(Thread* thread ATTRIBUTE_UNUSED,
@@ -790,7 +977,7 @@
   LOG(ERROR) << "Unexpected WatchedFramePop event in tracing";
 }
 
-void Trace::ReadClocks(Thread* thread, uint32_t* thread_clock_diff, uint32_t* wall_clock_diff) {
+void Trace::ReadClocks(Thread* thread, uint32_t* thread_clock_diff, uint64_t* timestamp_counter) {
   if (UseThreadCpuClock()) {
     uint64_t clock_base = thread->GetTraceClockBase();
     if (UNLIKELY(clock_base == 0)) {
@@ -802,90 +989,186 @@
     }
   }
   if (UseWallClock()) {
-    *wall_clock_diff = MicroTime() - start_time_;
+    *timestamp_counter = GetTimestamp();
   }
 }
 
-bool Trace::RegisterMethod(ArtMethod* method) {
-  const DexFile* dex_file = method->GetDexFile();
-  if (seen_methods_.find(dex_file) == seen_methods_.end()) {
-    seen_methods_.insert(std::make_pair(dex_file, new DexIndexBitSet()));
-  }
-  DexIndexBitSet* bit_set = seen_methods_.find(dex_file)->second;
-  if (!(*bit_set)[method->GetDexMethodIndex()]) {
-    bit_set->set(method->GetDexMethodIndex());
-    return true;
-  }
-  return false;
-}
-
-bool Trace::RegisterThread(Thread* thread) {
-  pid_t tid = thread->GetTid();
-  CHECK_LT(0U, static_cast<uint32_t>(tid));
-  CHECK_LT(static_cast<uint32_t>(tid), kMaxThreadIdNumber);
-
-  if (!(*seen_threads_)[tid]) {
-    seen_threads_->set(tid);
-    return true;
-  }
-  return false;
-}
-
-std::string Trace::GetMethodLine(ArtMethod* method) {
+std::string Trace::GetMethodLine(ArtMethod* method, uint32_t method_index) {
   method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
-  return StringPrintf("%#x\t%s\t%s\t%s\t%s\n", (EncodeTraceMethod(method) << TraceActionBits),
-      PrettyDescriptor(method->GetDeclaringClassDescriptor()).c_str(), method->GetName(),
-      method->GetSignature().ToString().c_str(), method->GetDeclaringClassSourceFile());
+  return StringPrintf("%#x\t%s\t%s\t%s\t%s\n",
+                      (method_index << TraceActionBits),
+                      PrettyDescriptor(method->GetDeclaringClassDescriptor()).c_str(),
+                      method->GetName(),
+                      method->GetSignature().ToString().c_str(),
+                      method->GetDeclaringClassSourceFile());
 }
 
-void Trace::WriteToBuf(const uint8_t* src, size_t src_size) {
-  // Updates to cur_offset_ are done under the streaming_lock_ here as in streaming mode.
-  int32_t old_offset = cur_offset_.load(std::memory_order_relaxed);
-  int32_t new_offset = old_offset + static_cast<int32_t>(src_size);
-  if (dchecked_integral_cast<size_t>(new_offset) > buffer_size_) {
-    // Flush buffer.
-    if (!trace_file_->WriteFully(buf_.get(), old_offset)) {
-      PLOG(WARNING) << "Failed streaming a tracing event.";
-    }
+void Trace::RecordStreamingMethodEvent(Thread* thread,
+                                       ArtMethod* method,
+                                       TraceAction action,
+                                       uint32_t thread_clock_diff,
+                                       uint64_t timestamp_counter) {
+  uintptr_t* method_trace_buffer = thread->GetMethodTraceBuffer();
+  size_t* current_offset = thread->GetMethodTraceIndexPtr();
+  // Initialize the buffer lazily. It's just simpler to keep the creation at one place.
+  if (method_trace_buffer == nullptr) {
+    method_trace_buffer = new uintptr_t[std::max(kMinBufSize, kPerThreadBufSize)]();
+    thread->SetMethodTraceBuffer(method_trace_buffer);
+    *current_offset = 0;
 
-    // Check whether the data is too large for the buffer, then write immediately.
-    if (src_size >= buffer_size_) {
-      if (!trace_file_->WriteFully(src, src_size)) {
+    // This is the first event from this thread, so first record information about the thread.
+    std::string thread_name;
+    thread->GetThreadName(thread_name);
+    static constexpr size_t kThreadNameHeaderSize = 7;
+    uint8_t header[kThreadNameHeaderSize];
+    Append2LE(header, 0);
+    header[2] = kOpNewThread;
+    // We use only 16 bits to encode thread id. On Android, we don't expect to use more than
+    // 16-bits for a Tid. For 32-bit platforms it is always ensured we use less than 16 bits.
+    // See  __check_max_thread_id in bionic for more details. Even on 64-bit the max threads
+    // is currently less than 65536.
+    // TODO(mythria): On host, we know thread ids can be greater than 16 bits. Consider adding
+    // a map similar to method ids.
+    DCHECK(!kIsTargetBuild || thread->GetTid() < (1 << 16));
+    Append2LE(header + 3, static_cast<uint16_t>(thread->GetTid()));
+    Append2LE(header + 5, static_cast<uint16_t>(thread_name.length()));
+
+    {
+      MutexLock mu(Thread::Current(), tracing_lock_);
+      if (!trace_file_->WriteFully(header, kThreadNameHeaderSize) ||
+          !trace_file_->WriteFully(reinterpret_cast<const uint8_t*>(thread_name.c_str()),
+                                   thread_name.length())) {
         PLOG(WARNING) << "Failed streaming a tracing event.";
       }
-      cur_offset_.store(0, std::memory_order_relaxed);  // Buffer is empty now.
-      return;
+    }
+  }
+
+  size_t required_entries = (clock_source_ == TraceClockSource::kDual) ? 4 : 3;
+  if (*current_offset + required_entries >= kPerThreadBufSize) {
+    // We don't have space for further entries. Flush the contents of the buffer and reuse the
+    // buffer to store contents. Reset the index to the start of the buffer.
+    FlushStreamingBuffer(thread);
+    *current_offset = 0;
+  }
+
+  // Record entry in per-thread trace buffer.
+  int current_index = *current_offset;
+  method_trace_buffer[current_index++] = reinterpret_cast<uintptr_t>(method);
+  // TODO(mythria): We only need two bits to record the action. Consider merging
+  // it with the method entry to save space.
+  method_trace_buffer[current_index++] = action;
+  if (UseThreadCpuClock()) {
+    method_trace_buffer[current_index++] = thread_clock_diff;
+  }
+  if (UseWallClock()) {
+    if (art::kRuntimePointerSize == PointerSize::k32) {
+      // On 32-bit architectures store timestamp counter as two 32-bit values.
+      method_trace_buffer[current_index++] = timestamp_counter >> 32;
+      method_trace_buffer[current_index++] = static_cast<uint32_t>(timestamp_counter);
+    } else {
+      method_trace_buffer[current_index++] = timestamp_counter;
+    }
+  }
+  *current_offset = current_index;
+}
+
+void Trace::WriteToBuf(uint8_t* header,
+                       size_t header_size,
+                       const std::string& data,
+                       size_t* current_index,
+                       uint8_t* buffer,
+                       size_t buffer_size) {
+  EnsureSpace(buffer, current_index, buffer_size, header_size);
+  memcpy(buffer + *current_index, header, header_size);
+  *current_index += header_size;
+
+  EnsureSpace(buffer, current_index, buffer_size, data.length());
+  if (data.length() < buffer_size) {
+    memcpy(buffer + *current_index, reinterpret_cast<const uint8_t*>(data.c_str()), data.length());
+    *current_index += data.length();
+  } else {
+    // The data is larger than buffer, so write directly to the file. EnsureSpace should have
+    // flushed any data in the buffer.
+    DCHECK_EQ(*current_index, 0U);
+    if (!trace_file_->WriteFully(reinterpret_cast<const uint8_t*>(data.c_str()), data.length())) {
+      PLOG(WARNING) << "Failed streaming a tracing event.";
+    }
+  }
+}
+
+void Trace::FlushStreamingBuffer(Thread* thread) {
+  // Take a tracing_lock_ to serialize writes across threads. We also need to allocate a unique
+  // method id for each method. We do that by maintaining a map from id to method for each newly
+  // seen method. tracing_lock_ is required to serialize these.
+  MutexLock mu(Thread::Current(), tracing_lock_);
+  uintptr_t* method_trace_buffer = thread->GetMethodTraceBuffer();
+  // Create a temporary buffer to encode the trace events from the specified thread.
+  size_t buffer_size = kPerThreadBufSize;
+  size_t current_index = 0;
+  std::unique_ptr<uint8_t[]> buffer(new uint8_t[std::max(kMinBufSize, buffer_size)]);
+
+  size_t num_entries = *(thread->GetMethodTraceIndexPtr());
+  for (size_t entry_index = 0; entry_index < num_entries;) {
+    ArtMethod* method = reinterpret_cast<ArtMethod*>(method_trace_buffer[entry_index++]);
+    TraceAction action = DecodeTraceAction(method_trace_buffer[entry_index++]);
+    uint32_t thread_time = 0;
+    uint32_t wall_time = 0;
+    if (UseThreadCpuClock()) {
+      thread_time = method_trace_buffer[entry_index++];
+    }
+    if (UseWallClock()) {
+      uint64_t timestamp = method_trace_buffer[entry_index++];
+      if (art::kRuntimePointerSize == PointerSize::k32) {
+        // On 32-bit architectures timestamp is stored as two 32-bit values.
+        timestamp = (timestamp << 32 | method_trace_buffer[entry_index++]);
+      }
+      wall_time = GetMicroTime(timestamp) - start_time_;
     }
 
-    old_offset = 0;
-    new_offset = static_cast<int32_t>(src_size);
+    auto it = art_method_id_map_.find(method);
+    uint32_t method_index = 0;
+    // If we haven't seen this method before record information about the method.
+    if (it == art_method_id_map_.end()) {
+      art_method_id_map_.emplace(method, current_method_index_);
+      method_index = current_method_index_;
+      current_method_index_++;
+      // Write a special block with the name.
+      std::string method_line(GetMethodLine(method, method_index));
+      static constexpr size_t kMethodNameHeaderSize = 5;
+      uint8_t method_header[kMethodNameHeaderSize];
+      DCHECK_LT(kMethodNameHeaderSize, kPerThreadBufSize);
+      Append2LE(method_header, 0);
+      method_header[2] = kOpNewMethod;
+      Append2LE(method_header + 3, static_cast<uint16_t>(method_line.length()));
+      WriteToBuf(method_header,
+                 kMethodNameHeaderSize,
+                 method_line,
+                 &current_index,
+                 buffer.get(),
+                 buffer_size);
+    } else {
+      method_index = it->second;
+    }
+
+    const size_t record_size = GetRecordSize(clock_source_);
+    DCHECK_LT(record_size, kPerThreadBufSize);
+    EnsureSpace(buffer.get(), &current_index, buffer_size, record_size);
+    EncodeEventEntry(
+        buffer.get() + current_index, thread, method_index, action, thread_time, wall_time);
+    current_index += record_size;
   }
-  cur_offset_.store(new_offset, std::memory_order_relaxed);
-  // Fill in data.
-  memcpy(buf_.get() + old_offset, src, src_size);
+
+  // Flush the contents of buffer to file.
+  if (!trace_file_->WriteFully(buffer.get(), current_index)) {
+    PLOG(WARNING) << "Failed streaming a tracing event.";
+  }
 }
 
-void Trace::FlushBuf() {
-  // Updates to cur_offset_ are done under the streaming_lock_ here as in streaming mode.
-  int32_t offset = cur_offset_.load(std::memory_order_relaxed);
-  if (!trace_file_->WriteFully(buf_.get(), offset)) {
-    PLOG(WARNING) << "Failed flush the remaining data in streaming.";
-  }
-  cur_offset_.store(0, std::memory_order_relaxed);
-}
-
-void Trace::LogMethodTraceEvent(Thread* thread, ArtMethod* method,
-                                instrumentation::Instrumentation::InstrumentationEvent event,
-                                uint32_t thread_clock_diff, uint32_t wall_clock_diff) {
-  // This method is called in both tracing modes (method and
-  // sampling). In sampling mode, this method is only called by the
-  // sampling thread. In method tracing mode, it can be called
-  // concurrently.
-
-  // Ensure we always use the non-obsolete version of the method so that entry/exit events have the
-  // same pointer value.
-  method = method->GetNonObsoleteMethod();
-
+void Trace::RecordMethodEvent(Thread* thread,
+                              ArtMethod* method,
+                              TraceAction action,
+                              uint32_t thread_clock_diff,
+                              uint64_t timestamp_counter) {
   // Advance cur_offset_ atomically.
   int32_t new_offset;
   int32_t old_offset = 0;
@@ -893,37 +1176,18 @@
   // In the non-streaming case, we do a busy loop here trying to get
   // an offset to write our record and advance cur_offset_ for the
   // next use.
-  if (trace_output_mode_ != TraceOutputMode::kStreaming) {
-    // Although multiple threads can call this method concurrently,
-    // the compare_exchange_weak here is still atomic (by definition).
-    // A succeeding update is visible to other cores when they pass
-    // through this point.
-    old_offset = cur_offset_.load(std::memory_order_relaxed);  // Speculative read
-    do {
-      new_offset = old_offset + GetRecordSize(clock_source_);
-      if (static_cast<size_t>(new_offset) > buffer_size_) {
-        overflow_ = true;
-        return;
-      }
-    } while (!cur_offset_.compare_exchange_weak(old_offset, new_offset, std::memory_order_relaxed));
-  }
-
-  TraceAction action = kTraceMethodEnter;
-  switch (event) {
-    case instrumentation::Instrumentation::kMethodEntered:
-      action = kTraceMethodEnter;
-      break;
-    case instrumentation::Instrumentation::kMethodExited:
-      action = kTraceMethodExit;
-      break;
-    case instrumentation::Instrumentation::kMethodUnwind:
-      action = kTraceUnroll;
-      break;
-    default:
-      UNIMPLEMENTED(FATAL) << "Unexpected event: " << event;
-  }
-
-  uint32_t method_value = EncodeTraceMethodAndAction(method, action);
+  // Although multiple threads can call this method concurrently,
+  // the compare_exchange_weak here is still atomic (by definition).
+  // A succeeding update is visible to other cores when they pass
+  // through this point.
+  old_offset = cur_offset_.load(std::memory_order_relaxed);  // Speculative read
+  do {
+    new_offset = old_offset + GetRecordSize(clock_source_);
+    if (static_cast<size_t>(new_offset) > buffer_size_) {
+      overflow_ = true;
+      return;
+    }
+  } while (!cur_offset_.compare_exchange_weak(old_offset, new_offset, std::memory_order_relaxed));
 
   // Write data into the tracing buffer (if not streaming) or into a
   // small buffer on the stack (if streaming) which we'll put into the
@@ -934,14 +1198,41 @@
   // of FinishTracing() acquire locks and (implicitly) synchronise
   // the buffer memory.
   uint8_t* ptr;
-  static constexpr size_t kPacketSize = 14U;  // The maximum size of data in a packet.
-  uint8_t stack_buf[kPacketSize];             // Space to store a packet when in streaming mode.
-  if (trace_output_mode_ == TraceOutputMode::kStreaming) {
-    ptr = stack_buf;
-  } else {
-    ptr = buf_.get() + old_offset;
-  }
+  ptr = buf_.get() + old_offset;
+  uint32_t wall_clock_diff = GetMicroTime(timestamp_counter) - start_time_;
+  MutexLock mu(Thread::Current(), tracing_lock_);
+  EncodeEventEntry(
+      ptr, thread, EncodeTraceMethod(method), action, thread_clock_diff, wall_clock_diff);
+}
 
+void Trace::LogMethodTraceEvent(Thread* thread,
+                                ArtMethod* method,
+                                TraceAction action,
+                                uint32_t thread_clock_diff,
+                                uint64_t timestamp_counter) {
+  // This method is called in both tracing modes (method and sampling). In sampling mode, this
+  // method is only called by the sampling thread. In method tracing mode, it can be called
+  // concurrently.
+
+  // Ensure we always use the non-obsolete version of the method so that entry/exit events have the
+  // same pointer value.
+  method = method->GetNonObsoleteMethod();
+
+  if (trace_output_mode_ == TraceOutputMode::kStreaming) {
+    RecordStreamingMethodEvent(thread, method, action, thread_clock_diff, timestamp_counter);
+  } else {
+    RecordMethodEvent(thread, method, action, thread_clock_diff, timestamp_counter);
+  }
+}
+
+void Trace::EncodeEventEntry(uint8_t* ptr,
+                             Thread* thread,
+                             uint32_t method_index,
+                             TraceAction action,
+                             uint32_t thread_clock_diff,
+                             uint32_t wall_clock_diff) {
+  static constexpr size_t kPacketSize = 14U;  // The maximum size of data in a packet.
+  uint32_t method_value = (method_index << TraceActionBits) | action;
   Append2LE(ptr, thread->GetTid());
   Append4LE(ptr + 2, method_value);
   ptr += 6;
@@ -954,79 +1245,46 @@
     Append4LE(ptr, wall_clock_diff);
   }
   static_assert(kPacketSize == 2 + 4 + 4 + 4, "Packet size incorrect.");
-
-  if (trace_output_mode_ == TraceOutputMode::kStreaming) {
-    MutexLock mu(Thread::Current(), *streaming_lock_);  // To serialize writing.
-    if (RegisterMethod(method)) {
-      // Write a special block with the name.
-      std::string method_line(GetMethodLine(method));
-      uint8_t buf2[5];
-      Append2LE(buf2, 0);
-      buf2[2] = kOpNewMethod;
-      Append2LE(buf2 + 3, static_cast<uint16_t>(method_line.length()));
-      WriteToBuf(buf2, sizeof(buf2));
-      WriteToBuf(reinterpret_cast<const uint8_t*>(method_line.c_str()), method_line.length());
-    }
-    if (RegisterThread(thread)) {
-      // It might be better to postpone this. Threads might not have received names...
-      std::string thread_name;
-      thread->GetThreadName(thread_name);
-      uint8_t buf2[7];
-      Append2LE(buf2, 0);
-      buf2[2] = kOpNewThread;
-      Append2LE(buf2 + 3, static_cast<uint16_t>(thread->GetTid()));
-      Append2LE(buf2 + 5, static_cast<uint16_t>(thread_name.length()));
-      WriteToBuf(buf2, sizeof(buf2));
-      WriteToBuf(reinterpret_cast<const uint8_t*>(thread_name.c_str()), thread_name.length());
-    }
-    WriteToBuf(stack_buf, sizeof(stack_buf));
-  }
 }
 
-void Trace::GetVisitedMethods(size_t buf_size,
-                              std::set<ArtMethod*>* visited_methods) {
-  uint8_t* ptr = buf_.get() + kTraceHeaderLength;
-  uint8_t* end = buf_.get() + buf_size;
-
-  while (ptr < end) {
-    uint32_t tmid = ReadBytes(ptr + 2, sizeof(tmid));
-    ArtMethod* method = DecodeTraceMethod(tmid);
-    visited_methods->insert(method);
-    ptr += GetRecordSize(clock_source_);
+void Trace::EnsureSpace(uint8_t* buffer,
+                        size_t* current_index,
+                        size_t buffer_size,
+                        size_t required_size) {
+  if (*current_index + required_size < buffer_size) {
+    return;
   }
+
+  if (!trace_file_->WriteFully(buffer, *current_index)) {
+    PLOG(WARNING) << "Failed streaming a tracing event.";
+  }
+  *current_index = 0;
 }
 
-void Trace::DumpMethodList(std::ostream& os, const std::set<ArtMethod*>& visited_methods) {
-  for (const auto& method : visited_methods) {
-    os << GetMethodLine(method);
+void Trace::DumpMethodList(std::ostream& os) {
+  MutexLock mu(Thread::Current(), tracing_lock_);
+  for (auto const& entry : art_method_id_map_) {
+    os << GetMethodLine(entry.first, entry.second);
   }
 }
 
-static void DumpThread(Thread* t, void* arg) {
-  std::ostream& os = *reinterpret_cast<std::ostream*>(arg);
-  std::string name;
-  t->GetThreadName(name);
-  os << t->GetTid() << "\t" << name << "\n";
-}
-
 void Trace::DumpThreadList(std::ostream& os) {
-  Thread* self = Thread::Current();
-  for (const auto& it : exited_threads_) {
-    os << it.first << "\t" << it.second << "\n";
+  for (const auto& it : threads_list_) {
+    // We use only 16 bits to encode thread id. On Android, we don't expect to use more than
+    // 16-bits for a Tid. For 32-bit platforms it is always ensured we use less than 16 bits.
+    // See  __check_max_thread_id in bionic for more details. Even on 64-bit the max threads
+    // is currently less than 65536.
+    // TODO(mythria): On host, we know thread ids can be greater than 16 bits. Consider adding
+    // a map similar to method ids.
+    DCHECK(!kIsTargetBuild || it.first < (1 << 16));
+    os << static_cast<uint16_t>(it.first) << "\t" << it.second << "\n";
   }
-  Locks::thread_list_lock_->AssertNotHeld(self);
-  MutexLock mu(self, *Locks::thread_list_lock_);
-  Runtime::Current()->GetThreadList()->ForEach(DumpThread, &os);
 }
 
 void Trace::StoreExitingThreadInfo(Thread* thread) {
   MutexLock mu(thread, *Locks::trace_lock_);
   if (the_trace_ != nullptr) {
-    std::string name;
-    thread->GetThreadName(name);
-    // The same thread/tid may be used multiple times. As SafeMap::Put does not allow to override
-    // a previous mapping, use SafeMap::Overwrite.
-    the_trace_->exited_threads_.Overwrite(thread->GetTid(), name);
+    the_trace_->UpdateThreadsList(thread);
   }
 }
 
@@ -1042,9 +1300,21 @@
   return the_trace_->trace_mode_;
 }
 
+int Trace::GetFlags() {
+  MutexLock mu(Thread::Current(), *Locks::trace_lock_);
+  CHECK(the_trace_ != nullptr) << "Trace flags requested, but no trace currently running";
+  return the_trace_->flags_;
+}
+
+int Trace::GetIntervalInMillis() {
+  MutexLock mu(Thread::Current(), *Locks::trace_lock_);
+  CHECK(the_trace_ != nullptr) << "Trace interval requested, but no trace currently running";
+  return the_trace_->interval_us_;
+}
+
 size_t Trace::GetBufferSize() {
   MutexLock mu(Thread::Current(), *Locks::trace_lock_);
-  CHECK(the_trace_ != nullptr) << "Trace mode requested, but no trace currently running";
+  CHECK(the_trace_ != nullptr) << "Trace buffer size requested, but no trace currently running";
   return the_trace_->buffer_size_;
 }
 
diff --git a/runtime/trace.h b/runtime/trace.h
index c6f36e4..6df21c6 100644
--- a/runtime/trace.h
+++ b/runtime/trace.h
@@ -29,6 +29,7 @@
 #include "base/atomic.h"
 #include "base/locks.h"
 #include "base/macros.h"
+#include "base/mutex.h"
 #include "base/os.h"
 #include "base/safe_map.h"
 #include "instrumentation.h"
@@ -43,15 +44,11 @@
 class ArtField;
 class ArtMethod;
 class DexFile;
-class LOCKABLE Mutex;
 class ShadowFrame;
 class Thread;
 
 using DexIndexBitSet = std::bitset<65536>;
 
-constexpr size_t kMaxThreadIdNumber = kIsTargetBuild ? 65536U : 1048576U;
-using ThreadIDBitSet = std::bitset<kMaxThreadIdNumber>;
-
 enum TracingMode {
   kTracingInactive,
   kMethodTracingActive,  // Trace activity synchronous with method progress.
@@ -107,7 +104,9 @@
 class Trace final : public instrumentation::InstrumentationListener {
  public:
   enum TraceFlag {
-    kTraceCountAllocs = 1,
+    kTraceCountAllocs = 0x001,
+    kTraceClockSourceWallClock = 0x010,
+    kTraceClockSourceThreadCpu = 0x100,
   };
 
   enum class TraceOutputMode {
@@ -121,8 +120,6 @@
     kSampling
   };
 
-  ~Trace();
-
   static void SetDefaultClockSource(TraceClockSource clock_source);
 
   static void Start(const char* trace_filename,
@@ -166,58 +163,61 @@
       REQUIRES(!Locks::mutator_lock_, !Locks::thread_list_lock_, !Locks::trace_lock_);
   static TracingMode GetMethodTracingMode() REQUIRES(!Locks::trace_lock_);
 
+  // Flush the per-thread buffer. This is called when the thread is about to detach.
+  static void FlushThreadBuffer(Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::trace_lock_) NO_THREAD_SAFETY_ANALYSIS;
+
   bool UseWallClock();
   bool UseThreadCpuClock();
   void MeasureClockOverhead();
   uint32_t GetClockOverheadNanoSeconds();
 
   void CompareAndUpdateStackTrace(Thread* thread, std::vector<ArtMethod*>* stack_trace)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_, !streaming_lock_);
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_);
 
   // InstrumentationListener implementation.
   void MethodEntered(Thread* thread, ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_)
-      REQUIRES(!unique_methods_lock_, !streaming_lock_) override;
+      REQUIRES(!tracing_lock_) override;
   void MethodExited(Thread* thread,
                     ArtMethod* method,
                     instrumentation::OptionalFrame frame,
                     JValue& return_value)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_, !streaming_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_)
       override;
   void MethodUnwind(Thread* thread,
-                    Handle<mirror::Object> this_object,
                     ArtMethod* method,
                     uint32_t dex_pc)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_, !streaming_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_)
       override;
   void DexPcMoved(Thread* thread,
                   Handle<mirror::Object> this_object,
                   ArtMethod* method,
                   uint32_t new_dex_pc)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_, !streaming_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_)
       override;
   void FieldRead(Thread* thread,
                  Handle<mirror::Object> this_object,
                  ArtMethod* method,
                  uint32_t dex_pc,
                  ArtField* field)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_) override;
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_) override;
   void FieldWritten(Thread* thread,
                     Handle<mirror::Object> this_object,
                     ArtMethod* method,
                     uint32_t dex_pc,
                     ArtField* field,
                     const JValue& field_value)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_) override;
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_) override;
   void ExceptionThrown(Thread* thread,
                        Handle<mirror::Throwable> exception_object)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_) override;
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_) override;
   void ExceptionHandled(Thread* thread, Handle<mirror::Throwable> exception_object)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_) override;
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_) override;
   void Branch(Thread* thread,
               ArtMethod* method,
               uint32_t dex_pc,
               int32_t dex_pc_offset)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_) override;
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_) override;
   void WatchedFramePop(Thread* thread, const ShadowFrame& frame)
       REQUIRES_SHARED(Locks::mutator_lock_) override;
   // Reuse an old stack trace if it exists, otherwise allocate a new one.
@@ -230,6 +230,8 @@
   static TraceOutputMode GetOutputMode() REQUIRES(!Locks::trace_lock_);
   static TraceMode GetMode() REQUIRES(!Locks::trace_lock_);
   static size_t GetBufferSize() REQUIRES(!Locks::trace_lock_);
+  static int GetFlags() REQUIRES(!Locks::trace_lock_);
+  static int GetIntervalInMillis() REQUIRES(!Locks::trace_lock_);
 
   // Used by class linker to prevent class unloading.
   static bool IsTracingEnabled() REQUIRES(!Locks::trace_lock_);
@@ -252,46 +254,81 @@
       // how to annotate this.
       NO_THREAD_SAFETY_ANALYSIS;
   void FinishTracing()
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_, !streaming_lock_);
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_);
 
-  void ReadClocks(Thread* thread, uint32_t* thread_clock_diff, uint32_t* wall_clock_diff);
+  void ReadClocks(Thread* thread, uint32_t* thread_clock_diff, uint64_t* timestamp_counter);
 
-  void LogMethodTraceEvent(Thread* thread, ArtMethod* method,
-                           instrumentation::Instrumentation::InstrumentationEvent event,
-                           uint32_t thread_clock_diff, uint32_t wall_clock_diff)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_, !streaming_lock_);
+  void LogMethodTraceEvent(Thread* thread,
+                           ArtMethod* method,
+                           TraceAction action,
+                           uint32_t thread_clock_diff,
+                           uint64_t timestamp_counter) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!tracing_lock_);
 
   // Methods to output traced methods and threads.
-  void GetVisitedMethods(size_t end_offset, std::set<ArtMethod*>* visited_methods)
-      REQUIRES(!unique_methods_lock_);
-  void DumpMethodList(std::ostream& os, const std::set<ArtMethod*>& visited_methods)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_);
+  void DumpMethodList(std::ostream& os)
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_);
   void DumpThreadList(std::ostream& os) REQUIRES(!Locks::thread_list_lock_);
 
-  // Methods to register seen entitites in streaming mode. The methods return true if the entity
-  // is newly discovered.
-  bool RegisterMethod(ArtMethod* method)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(streaming_lock_);
-  bool RegisterThread(Thread* thread)
-      REQUIRES(streaming_lock_);
+  void RecordMethodEvent(Thread* thread,
+                         ArtMethod* method,
+                         TraceAction action,
+                         uint32_t thread_clock_diff,
+                         uint64_t timestamp) REQUIRES(!tracing_lock_);
 
-  // Copy a temporary buffer to the main buffer. Used for streaming. Exposed here for lock
-  // annotation.
-  void WriteToBuf(const uint8_t* src, size_t src_size)
-      REQUIRES(streaming_lock_);
-  // Flush the main buffer to file. Used for streaming. Exposed here for lock annotation.
-  void FlushBuf()
-      REQUIRES(streaming_lock_);
+  // Encodes event in non-streaming mode. This assumes that there is enough space reserved to
+  // encode the entry.
+  void EncodeEventEntry(uint8_t* ptr,
+                        Thread* thread,
+                        uint32_t method_index,
+                        TraceAction action,
+                        uint32_t thread_clock_diff,
+                        uint32_t wall_clock_diff) REQUIRES(tracing_lock_);
 
-  uint32_t EncodeTraceMethod(ArtMethod* method) REQUIRES(!unique_methods_lock_);
-  uint32_t EncodeTraceMethodAndAction(ArtMethod* method, TraceAction action)
-      REQUIRES(!unique_methods_lock_);
-  ArtMethod* DecodeTraceMethod(uint32_t tmid) REQUIRES(!unique_methods_lock_);
-  std::string GetMethodLine(ArtMethod* method) REQUIRES(!unique_methods_lock_)
+  // These methods are used to encode events in streaming mode.
+
+  // This records the method event in the per-thread buffer if there is sufficient space for the
+  // entire record. If the buffer is full then it just flushes the buffer and then records the
+  // entry.
+  void RecordStreamingMethodEvent(Thread* thread,
+                                  ArtMethod* method,
+                                  TraceAction action,
+                                  uint32_t thread_clock_diff,
+                                  uint64_t timestamp) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!tracing_lock_);
+  // This encodes all the events in the per-thread trace buffer and writes it to the trace file.
+  // This acquires streaming lock to prevent any other threads writing concurrently. It is required
+  // to serialize these since each method is encoded with a unique id which is assigned when the
+  // method is seen for the first time in the recoreded events. So we need to serialize these
+  // flushes across threads.
+  void FlushStreamingBuffer(Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!tracing_lock_);
+  // Ensures there is sufficient space in the buffer to record the requested_size. If there is not
+  // enough sufficient space the current contents of the buffer are written to the file and
+  // current_index is reset to 0. This doesn't check if buffer_size is big enough to hold the
+  // requested size.
+  void EnsureSpace(uint8_t* buffer,
+                   size_t* current_index,
+                   size_t buffer_size,
+                   size_t required_size);
+  // Writes header followed by data to the buffer at the current_index. This also updates the
+  // current_index to point to the next entry.
+  void WriteToBuf(uint8_t* header,
+                  size_t header_size,
+                  const std::string& data,
+                  size_t* current_index,
+                  uint8_t* buffer,
+                  size_t buffer_size);
+
+  uint32_t EncodeTraceMethod(ArtMethod* method) REQUIRES(tracing_lock_);
+  ArtMethod* DecodeTraceMethod(uint32_t tmid) REQUIRES(tracing_lock_);
+  std::string GetMethodLine(ArtMethod* method, uint32_t method_id) REQUIRES(tracing_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   void DumpBuf(uint8_t* buf, size_t buf_size, TraceClockSource clock_source)
-      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_);
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!tracing_lock_);
+
+  void UpdateThreadsList(Thread* thread);
 
   // Singleton instance of the Trace or null when no method tracing is active.
   static Trace* volatile the_trace_ GUARDED_BY(Locks::trace_lock_);
@@ -309,7 +346,7 @@
   std::unique_ptr<File> trace_file_;
 
   // Buffer to store trace data. In streaming mode, this is protected
-  // by the streaming_lock_. In non-streaming mode, reserved regions
+  // by the tracing_lock_. In non-streaming mode, reserved regions
   // are atomically allocated (using cur_offset_) for log entries to
   // be written.
   std::unique_ptr<uint8_t[]> buf_;
@@ -338,7 +375,7 @@
   // to concurrently reserve space in the buffer. The newly written
   // buffer contents are not read without some other form of thread
   // synchronization, such as suspending all potential writers or
-  // acquiring *streaming_lock_. Reading cur_offset_ is thus never
+  // acquiring *tracing_lock_. Reading cur_offset_ is thus never
   // used to ensure visibility of any other objects, and all accesses
   // are memory_order_relaxed.
   //
@@ -360,22 +397,22 @@
   // Did we overflow the buffer recording traces?
   bool overflow_;
 
-  // Map of thread ids and names that have already exited.
-  SafeMap<pid_t, std::string> exited_threads_;
+  // Map of thread ids and names. We record the information when the threads are
+  // exiting and when the tracing has finished.
+  SafeMap<pid_t, std::string> threads_list_;
 
   // Sampling profiler sampling interval.
   int interval_us_;
 
-  // Streaming mode data.
-  Mutex* streaming_lock_;
-  std::map<const DexFile*, DexIndexBitSet*> seen_methods_ GUARDED_BY(streaming_lock_);
-  std::unique_ptr<ThreadIDBitSet> seen_threads_ GUARDED_BY(streaming_lock_);
+  // A flag to indicate to the sampling thread whether to stop tracing
+  bool stop_tracing_;
 
-  // Bijective map from ArtMethod* to index.
-  // Map from ArtMethod* to index in unique_methods_;
-  Mutex* unique_methods_lock_ ACQUIRED_AFTER(streaming_lock_);
-  std::unordered_map<ArtMethod*, uint32_t> art_method_id_map_ GUARDED_BY(unique_methods_lock_);
-  std::vector<ArtMethod*> unique_methods_ GUARDED_BY(unique_methods_lock_);
+  // Streaming mode data.
+  Mutex tracing_lock_;
+
+  // Map from ArtMethod* to index.
+  std::unordered_map<ArtMethod*, uint32_t> art_method_id_map_ GUARDED_BY(tracing_lock_);
+  uint32_t current_method_index_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(Trace);
 };
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 006aa56..08452bd 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -410,7 +410,6 @@
 
   for (auto& it : array_logs_) {
     mirror::Array* old_root = it.first;
-    CHECK(!old_root->IsObjectArray());
     mirror::Array* new_root = old_root;
     visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&new_root), RootInfo(kRootUnknown));
     if (new_root != old_root) {
diff --git a/runtime/transaction.h b/runtime/transaction.h
index d147038..6fa8e58 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -245,15 +245,17 @@
         REQUIRES(Locks::intern_table_lock_);
     void VisitRoots(RootVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_);
 
-    InternStringLog() = default;
+    // Only the move constructor is supported.
+    InternStringLog() = delete;
+    InternStringLog(const InternStringLog& log) = delete;
+    InternStringLog& operator=(const InternStringLog& log) = delete;
     InternStringLog(InternStringLog&& log) = default;
+    InternStringLog& operator=(InternStringLog&& log) = delete;
 
    private:
     mutable GcRoot<mirror::String> str_;
     const StringKind string_kind_;
     const StringOp string_op_;
-
-    DISALLOW_COPY_AND_ASSIGN(InternStringLog);
   };
 
   class ResolveStringLog : public ValueObject {
diff --git a/runtime/var_handles.cc b/runtime/var_handles.cc
index 73b4dda..0c7e3bd 100644
--- a/runtime/var_handles.cc
+++ b/runtime/var_handles.cc
@@ -41,7 +41,7 @@
   const size_t num_vregs = accessor_type->NumberOfVRegs();
   const int num_params = accessor_type->GetPTypes()->GetLength();
   ShadowFrameAllocaUniquePtr accessor_frame =
-      CREATE_SHADOW_FRAME(num_vregs, nullptr, shadow_frame.GetMethod(), shadow_frame.GetDexPC());
+      CREATE_SHADOW_FRAME(num_vregs, shadow_frame.GetMethod(), shadow_frame.GetDexPC());
   ShadowFrameGetter getter(shadow_frame, operands);
   static const uint32_t kFirstDestinationReg = 0;
   ShadowFrameSetter setter(accessor_frame.get(), kFirstDestinationReg);
diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc
index fb3f809..9bcde00 100644
--- a/runtime/vdex_file.cc
+++ b/runtime/vdex_file.cc
@@ -49,10 +49,6 @@
 
 using android::base::StringPrintf;
 
-constexpr uint8_t VdexFile::VdexFileHeader::kVdexInvalidMagic[4];
-constexpr uint8_t VdexFile::VdexFileHeader::kVdexMagic[4];
-constexpr uint8_t VdexFile::VdexFileHeader::kVdexVersion[4];
-
 bool VdexFile::VdexFileHeader::IsMagicValid() const {
   return (memcmp(magic_, kVdexMagic, sizeof(kVdexMagic)) == 0);
 }
@@ -219,7 +215,6 @@
 
 bool VdexFile::OpenAllDexFiles(std::vector<std::unique_ptr<const DexFile>>* dex_files,
                                std::string* error_msg) const {
-  const ArtDexFileLoader dex_file_loader;
   size_t i = 0;
   for (const uint8_t* dex_file_start = GetNextDexFileData(nullptr, i);
        dex_file_start != nullptr;
@@ -228,17 +223,12 @@
     // TODO: Supply the location information for a vdex file.
     static constexpr char kVdexLocation[] = "";
     std::string location = DexFileLoader::GetMultiDexLocation(i, kVdexLocation);
-    std::unique_ptr<const DexFile> dex(dex_file_loader.OpenWithDataSection(
-        dex_file_start,
-        size,
-        /*data_base=*/ nullptr,
-        /*data_size=*/ 0u,
-        location,
-        GetLocationChecksum(i),
-        /*oat_dex_file=*/ nullptr,
-        /*verify=*/ false,
-        /*verify_checksum=*/ false,
-        error_msg));
+    ArtDexFileLoader dex_file_loader(dex_file_start, size, location);
+    std::unique_ptr<const DexFile> dex(dex_file_loader.Open(GetLocationChecksum(i),
+                                                            /*oat_dex_file=*/nullptr,
+                                                            /*verify=*/false,
+                                                            /*verify_checksum=*/false,
+                                                            error_msg));
     if (dex == nullptr) {
       return false;
     }
@@ -394,6 +384,12 @@
   return true;
 }
 
+bool VdexFile::HasOnlyStandardDexFiles() const {
+  // All are the same so it's enough to check the first one.
+  const uint8_t* dex_file_start = GetNextDexFileData(nullptr, 0);
+  return dex_file_start == nullptr || StandardDexFile::IsMagicValid(dex_file_start);
+}
+
 static ObjPtr<mirror::Class> FindClassAndClearException(ClassLinker* class_linker,
                                                         Thread* self,
                                                         const char* name,
diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h
index 3ccbfa5..fe65c07 100644
--- a/runtime/vdex_file.h
+++ b/runtime/vdex_file.h
@@ -246,8 +246,8 @@
   const uint8_t* Begin() const { return mmap_.Begin(); }
   const uint8_t* End() const { return mmap_.End(); }
   size_t Size() const { return mmap_.Size(); }
-  bool Contains(const uint8_t* pointer) const {
-    return pointer >= Begin() && pointer < End();
+  bool Contains(const uint8_t* pointer, size_t size) const {
+    return Begin() <= pointer && size <= Size() && pointer <= End() - size;
   }
 
   const VdexFileHeader& GetVdexFileHeader() const {
@@ -296,6 +296,10 @@
   // order must match too.
   bool MatchesDexFileChecksums(const std::vector<const DexFile::Header*>& dex_headers) const;
 
+  // Returns true if all dex files are standard dex rather than compact dex.
+  // Also returns true if there are no dex files at all.
+  bool HasOnlyStandardDexFiles() const;
+
   ClassStatus ComputeClassStatus(Thread* self, Handle<mirror::Class> cls) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/vdex_file_test.cc b/runtime/vdex_file_test.cc
index 565487e..1ac10eb 100644
--- a/runtime/vdex_file_test.cc
+++ b/runtime/vdex_file_test.cc
@@ -20,12 +20,11 @@
 
 #include <gtest/gtest.h>
 
-#include "common_runtime_test.h"
+#include "base/common_art_test.h"
 
 namespace art {
 
-class VdexFileTest : public CommonRuntimeTest {
-};
+class VdexFileTest : public CommonArtTest {};
 
 TEST_F(VdexFileTest, OpenEmptyVdex) {
   // Verify we fail to open an empty vdex file.
diff --git a/runtime/verifier/class_verifier.cc b/runtime/verifier/class_verifier.cc
index 8c541f8..8946bb2 100644
--- a/runtime/verifier/class_verifier.cc
+++ b/runtime/verifier/class_verifier.cc
@@ -176,6 +176,9 @@
 
   GetMetrics()->ClassVerificationCount()->AddOne();
 
+  GetMetrics()->ClassVerificationTotalTimeDelta()->Add(elapsed_time_microseconds);
+  GetMetrics()->ClassVerificationCountDelta()->AddOne();
+
   if (failure_data.kind == verifier::FailureKind::kHardFailure && callbacks != nullptr) {
     ClassReference ref(dex_file, dex_file->GetIndexForClassDef(class_def));
     callbacks->ClassRejected(ref);
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index ec0ac73..9cdbce9 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -1054,6 +1054,10 @@
   // We can't assume the instruction is well formed, handle the case where calculating the size
   // goes past the end of the code item.
   SafeDexInstructionIterator it(code_item_accessor_.begin(), code_item_accessor_.end());
+  if (it == code_item_accessor_.end()) {
+    Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "code item has no opcode";
+    return false;
+  }
   for ( ; !it.IsErrorState() && it < code_item_accessor_.end(); ++it) {
     // In case the instruction goes past the end of the code item, make sure to not process it.
     SafeDexInstructionIterator next = it;
@@ -4314,10 +4318,7 @@
       }
       for (size_t ui = 0; ui < arg_count; ui++) {
         uint32_t get_reg = is_range ? inst->VRegC_3rc() + ui : arg[ui];
-        if (!work_line_->VerifyRegisterType(this, get_reg, expected_type)) {
-          work_line_->SetResultRegisterType(this, reg_types_.Conflict());
-          return;
-        }
+        work_line_->VerifyRegisterType(this, get_reg, expected_type);
       }
       // filled-array result goes into "result" register
       const RegType& precise_type = reg_types_.FromUninitialized(res_type);
@@ -4899,7 +4900,7 @@
 
 template <bool kVerifierDebug>
 bool MethodVerifier<kVerifierDebug>::PotentiallyMarkRuntimeThrow() {
-  if (IsAotMode() || IsSdkVersionSetAndAtLeast(api_level_, SdkVersion::kT)) {
+  if (IsAotMode() || IsSdkVersionSetAndAtLeast(api_level_, SdkVersion::kS_V2)) {
     return false;
   }
   // Compatibility mode: we treat the following code unreachable and the verifier
diff --git a/runtime/verifier/method_verifier_test.cc b/runtime/verifier/method_verifier_test.cc
index ec07047..0f0fd17 100644
--- a/runtime/verifier/method_verifier_test.cc
+++ b/runtime/verifier/method_verifier_test.cc
@@ -37,6 +37,10 @@
 
 class MethodVerifierTest : public CommonRuntimeTest {
  protected:
+  MethodVerifierTest() {
+    use_boot_image_ = true;  // Make the Runtime creation cheaper.
+  }
+
   void VerifyClass(const std::string& descriptor)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     ASSERT_FALSE(descriptor.empty());
diff --git a/runtime/verifier/reg_type_test.cc b/runtime/verifier/reg_type_test.cc
index a157464..a36cf71 100644
--- a/runtime/verifier/reg_type_test.cc
+++ b/runtime/verifier/reg_type_test.cc
@@ -32,7 +32,7 @@
 namespace verifier {
 
 class RegTypeTest : public CommonRuntimeTest {
- public:
+ protected:
   RegTypeTest() {
     use_boot_image_ = true;  // Make the Runtime creation cheaper.
   }
@@ -360,7 +360,7 @@
   EXPECT_TRUE(double_reg_type.HasClass());
 }
 
-class RegTypeReferenceTest : public CommonRuntimeTest {};
+class RegTypeReferenceTest : public RegTypeTest {};
 
 TEST_F(RegTypeReferenceTest, JavalangObjectImprecise) {
   // Tests matching precisions. A reference type that was created precise doesn't
diff --git a/runtime/verifier/register_line-inl.h b/runtime/verifier/register_line-inl.h
index 6b53687..7b5a496 100644
--- a/runtime/verifier/register_line-inl.h
+++ b/runtime/verifier/register_line-inl.h
@@ -20,7 +20,6 @@
 #include "register_line.h"
 
 #include "base/logging.h"  // For VLOG.
-#include "debug_print.h"
 #include "method_verifier.h"
 #include "reg_type_cache-inl.h"
 
@@ -139,14 +138,6 @@
     }
     verifier->Fail(fail_type) << "register v" << vsrc << " has type "
                                << src_type << " but expected " << check_type;
-    if (check_type.IsNonZeroReferenceTypes() &&
-        !check_type.IsUnresolvedTypes() &&
-        check_type.HasClass() &&
-        src_type.IsNonZeroReferenceTypes() &&
-        !src_type.IsUnresolvedTypes() &&
-        src_type.HasClass()) {
-      DumpB77342775DebugData(check_type.GetClass(), src_type.GetClass());
-    }
     return false;
   }
   if (check_type.IsLowHalf()) {
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index db5fa0f..e9e488b 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -52,11 +52,8 @@
 // Size of `other` must be equal to size of `to_update`.
 static inline void BitVectorOr(std::vector<bool>& to_update, const std::vector<bool>& other) {
   DCHECK_EQ(to_update.size(), other.size());
-  std::transform(other.begin(),
-                 other.end(),
-                 to_update.begin(),
-                 to_update.begin(),
-                 std::logical_or<bool>());
+  std::transform(
+      other.begin(), other.end(), to_update.begin(), to_update.begin(), std::logical_or<bool>());
 }
 
 void VerifierDeps::MergeWith(std::unique_ptr<VerifierDeps> other,
@@ -175,8 +172,8 @@
   }
 }
 
-std::string VerifierDeps::GetStringFromId(const DexFile& dex_file, dex::StringIndex string_id)
-    const {
+std::string VerifierDeps::GetStringFromId(const DexFile& dex_file,
+                                          dex::StringIndex string_id) const {
   uint32_t num_ids_in_dex = dex_file.NumStringIds();
   if (string_id.index_ < num_ids_in_dex) {
     return std::string(dex_file.StringDataByIdx(string_id));
@@ -224,10 +221,7 @@
     // Only perform the optimization if both types are resolved which guarantees
     // that they linked successfully, as required at the top of this method.
     if (destination_component->IsResolved() && source_component->IsResolved()) {
-      AddAssignability(dex_file,
-                       class_def,
-                       destination_component,
-                       source_component);
+      AddAssignability(dex_file, class_def, destination_component, source_component);
       return;
     }
   }
@@ -337,25 +331,29 @@
 
 namespace {
 
-template<typename T> inline uint32_t Encode(T in);
+template <typename T>
+inline uint32_t Encode(T in);
 
-template<> inline uint32_t Encode<dex::StringIndex>(dex::StringIndex in) {
+template <>
+inline uint32_t Encode<dex::StringIndex>(dex::StringIndex in) {
   return in.index_;
 }
 
-template<typename T> inline T Decode(uint32_t in);
+template <typename T>
+inline T Decode(uint32_t in);
 
-template<> inline dex::StringIndex Decode<dex::StringIndex>(uint32_t in) {
+template <>
+inline dex::StringIndex Decode<dex::StringIndex>(uint32_t in) {
   return dex::StringIndex(in);
 }
 
-template<typename T1, typename T2>
+template <typename T1, typename T2>
 static inline void EncodeTuple(std::vector<uint8_t>* out, const std::tuple<T1, T2>& t) {
   EncodeUnsignedLeb128(out, Encode(std::get<0>(t)));
   EncodeUnsignedLeb128(out, Encode(std::get<1>(t)));
 }
 
-template<typename T1, typename T2>
+template <typename T1, typename T2>
 static inline bool DecodeTuple(const uint8_t** in, const uint8_t* end, std::tuple<T1, T2>* t) {
   uint32_t v1, v2;
   if (UNLIKELY(!DecodeUnsignedLeb128Checked(in, end, &v1)) ||
@@ -366,14 +364,14 @@
   return true;
 }
 
-template<typename T1, typename T2, typename T3>
+template <typename T1, typename T2, typename T3>
 static inline void EncodeTuple(std::vector<uint8_t>* out, const std::tuple<T1, T2, T3>& t) {
   EncodeUnsignedLeb128(out, Encode(std::get<0>(t)));
   EncodeUnsignedLeb128(out, Encode(std::get<1>(t)));
   EncodeUnsignedLeb128(out, Encode(std::get<2>(t)));
 }
 
-template<typename T1, typename T2, typename T3>
+template <typename T1, typename T2, typename T3>
 static inline bool DecodeTuple(const uint8_t** in, const uint8_t* end, std::tuple<T1, T2, T3>* t) {
   uint32_t v1, v2, v3;
   if (UNLIKELY(!DecodeUnsignedLeb128Checked(in, end, &v1)) ||
@@ -393,7 +391,7 @@
   (reinterpret_cast<uint32_t*>(out->data() + uint8_offset))[uint32_offset] = value;
 }
 
-template<typename T>
+template <typename T>
 static void EncodeSetVector(std::vector<uint8_t>* out,
                             const std::vector<std::set<T>>& vector,
                             const std::vector<bool>& verified_classes) {
@@ -417,7 +415,7 @@
   SetUint32InUint8Array(out, offsets_index, class_def_index, out->size());
 }
 
-template<bool kFillSet, typename T>
+template <bool kFillSet, typename T>
 static bool DecodeSetVector(const uint8_t** cursor,
                             const uint8_t* start,
                             const uint8_t* end,
@@ -468,7 +466,7 @@
   uint32_t offsets_index = out->size();
   // Make room for offsets for each string, +1 for putting the number of
   // strings.
-  out->resize(out->size() + (strings.size() + 1 ) * sizeof(uint32_t));
+  out->resize(out->size() + (strings.size() + 1) * sizeof(uint32_t));
   (reinterpret_cast<uint32_t*>(out->data() + offsets_index))[0] = strings.size();
   uint32_t string_index = 1;
   for (const std::string& str : strings) {
@@ -483,7 +481,7 @@
   }
 }
 
-template<bool kFillVector>
+template <bool kFillVector>
 static inline bool DecodeStringVector(const uint8_t** cursor,
                                       const uint8_t* start,
                                       const uint8_t* end,
@@ -498,8 +496,8 @@
   for (uint32_t i = 0; i < num_strings; ++i) {
     uint32_t string_offset = reinterpret_cast<const uint32_t*>(offsets)[i + 1];
     const char* string_start = reinterpret_cast<const char*>(start + string_offset);
-    const char* string_end = reinterpret_cast<const char*>(
-        memchr(string_start, 0, end - start - string_offset));
+    const char* string_end =
+        reinterpret_cast<const char*>(memchr(string_start, 0, end - start - string_offset));
     if (UNLIKELY(string_end == nullptr)) {
       return false;
     }
@@ -537,16 +535,14 @@
                                      const uint8_t* data_start,
                                      const uint8_t* data_end,
                                      size_t num_class_defs) {
-  return
-      DecodeSetVector</*kFillSet=*/ !kOnlyVerifiedClasses>(
-          cursor,
-          data_start,
-          data_end,
-          &deps.assignable_types_,
-          &deps.verified_classes_,
-          num_class_defs) &&
-      DecodeStringVector</*kFillVector=*/ !kOnlyVerifiedClasses>(
-          cursor, data_start, data_end, &deps.strings_);
+  return DecodeSetVector</*kFillSet=*/!kOnlyVerifiedClasses>(cursor,
+                                                             data_start,
+                                                             data_end,
+                                                             &deps.assignable_types_,
+                                                             &deps.verified_classes_,
+                                                             num_class_defs) &&
+         DecodeStringVector</*kFillVector=*/!kOnlyVerifiedClasses>(
+             cursor, data_start, data_end, &deps.strings_);
 }
 
 bool VerifierDeps::ParseStoredData(const std::vector<const DexFile*>& dex_files,
@@ -566,11 +562,8 @@
     // Fetch the offset of this dex file's verifier data.
     cursor = data_start + reinterpret_cast<const uint32_t*>(data_start)[dex_file_index++];
     size_t num_class_defs = dex_file->NumClassDefs();
-    if (UNLIKELY(!DecodeDexFileDeps</*kOnlyVerifiedClasses=*/ false>(*deps,
-                                                                     &cursor,
-                                                                     data_start,
-                                                                     data_end,
-                                                                     num_class_defs))) {
+    if (UNLIKELY(!DecodeDexFileDeps</*kOnlyVerifiedClasses=*/false>(
+            *deps, &cursor, data_start, data_end, num_class_defs))) {
       LOG(ERROR) << "Failed to parse dex file dependencies for " << dex_file->GetLocation();
       return false;
     }
@@ -582,7 +575,7 @@
 bool VerifierDeps::ParseVerifiedClasses(
     const std::vector<const DexFile*>& dex_files,
     ArrayRef<const uint8_t> data,
-    /*out*/std::vector<std::vector<bool>>* verified_classes_per_dex) {
+    /*out*/ std::vector<std::vector<bool>>* verified_classes_per_dex) {
   DCHECK(!data.empty());
   DCHECK(!dex_files.empty());
   DCHECK(verified_classes_per_dex->empty());
@@ -594,16 +587,13 @@
   const uint8_t* cursor = data_start;
   uint32_t dex_file_index = 0;
   for (const DexFile* dex_file : dex_files) {
-    DexFileDeps deps(/*num_class_defs=*/ 0u);  // Do not initialize vectors.
+    DexFileDeps deps(/*num_class_defs=*/0u);  // Do not initialize vectors.
     // Fetch the offset of this dex file's verifier data.
     cursor = data_start + reinterpret_cast<const uint32_t*>(data_start)[dex_file_index++];
     size_t num_class_defs = dex_file->NumClassDefs();
     deps.verified_classes_.resize(num_class_defs);
-    if (UNLIKELY(!DecodeDexFileDeps</*kOnlyVerifiedClasses=*/ true>(deps,
-                                                                    &cursor,
-                                                                    data_start,
-                                                                    data_end,
-                                                                    num_class_defs))) {
+    if (UNLIKELY(!DecodeDexFileDeps</*kOnlyVerifiedClasses=*/true>(
+            deps, &cursor, data_start, data_end, num_class_defs))) {
       LOG(ERROR) << "Failed to parse dex file dependencies for " << dex_file->GetLocation();
       return false;
     }
@@ -640,8 +630,7 @@
 }
 
 bool VerifierDeps::DexFileDeps::Equals(const VerifierDeps::DexFileDeps& rhs) const {
-  return (strings_ == rhs.strings_) &&
-         (assignable_types_ == rhs.assignable_types_) &&
+  return (strings_ == rhs.strings_) && (assignable_types_ == rhs.assignable_types_) &&
          (verified_classes_ == rhs.verified_classes_);
 }
 
@@ -653,18 +642,12 @@
   for (const auto& dep : dex_deps_) {
     dex_deps.emplace_back(dep.first, dep.second.get());
   }
-  std::sort(
-      dex_deps.begin(),
-      dex_deps.end(),
-      [](const DepsEntry& lhs, const DepsEntry& rhs) {
-        return lhs.first->GetLocation() < rhs.first->GetLocation();
-      });
+  std::sort(dex_deps.begin(), dex_deps.end(), [](const DepsEntry& lhs, const DepsEntry& rhs) {
+    return lhs.first->GetLocation() < rhs.first->GetLocation();
+  });
   for (const auto& dep : dex_deps) {
     const DexFile& dex_file = *dep.first;
-    vios->Stream()
-        << "Dependencies of "
-        << dex_file.GetLocation()
-        << ":\n";
+    vios->Stream() << "Dependencies of " << dex_file.GetLocation() << ":\n";
 
     ScopedIndentation indent(vios);
 
@@ -673,24 +656,18 @@
     }
 
     for (size_t idx = 0; idx < dep.second->assignable_types_.size(); idx++) {
-      vios->Stream()
-          << "Dependencies of "
-          << dex_file.GetClassDescriptor(dex_file.GetClassDef(idx))
-          << ":\n";
+      vios->Stream() << "Dependencies of " << dex_file.GetClassDescriptor(dex_file.GetClassDef(idx))
+                     << ":\n";
       for (const TypeAssignability& entry : dep.second->assignable_types_[idx]) {
-        vios->Stream()
-          << GetStringFromId(dex_file, entry.GetSource())
-          << " must be assignable to "
-          << GetStringFromId(dex_file, entry.GetDestination())
-          << "\n";
+        vios->Stream() << GetStringFromId(dex_file, entry.GetSource()) << " must be assignable to "
+                       << GetStringFromId(dex_file, entry.GetDestination()) << "\n";
       }
     }
 
     for (size_t idx = 0; idx < dep.second->verified_classes_.size(); idx++) {
       if (!dep.second->verified_classes_[idx]) {
-        vios->Stream()
-            << dex_file.GetClassDescriptor(dex_file.GetClassDef(idx))
-            << " will be verified at runtime\n";
+        vios->Stream() << dex_file.GetClassDescriptor(dex_file.GetClassDef(idx))
+                       << " will be verified at runtime\n";
       }
     }
   }
@@ -738,10 +715,9 @@
     for (const auto& entry : vec) {
       const std::string& destination_desc = GetStringFromId(dex_file, entry.GetDestination());
       destination.Assign(
-          FindClassAndClearException(class_linker, self, destination_desc.c_str(), class_loader));
+          FindClassAndClearException(class_linker, self, destination_desc, class_loader));
       const std::string& source_desc = GetStringFromId(dex_file, entry.GetSource());
-      source.Assign(
-          FindClassAndClearException(class_linker, self, source_desc.c_str(), class_loader));
+      source.Assign(FindClassAndClearException(class_linker, self, source_desc, class_loader));
 
       if (destination == nullptr || source == nullptr) {
         // We currently don't use assignability information for unresolved
@@ -760,16 +736,23 @@
   return true;
 }
 
+void VerifierDeps::ClearData(const std::vector<const DexFile*>& dex_files) {
+  for (const DexFile* dex_file : dex_files) {
+    auto it = dex_deps_.find(dex_file);
+    if (it == dex_deps_.end()) {
+      continue;
+    }
+    std::unique_ptr<DexFileDeps> deps(new DexFileDeps(dex_file->NumClassDefs()));
+    it->second.swap(deps);
+  }
+}
+
 bool VerifierDeps::VerifyDexFile(Handle<mirror::ClassLoader> class_loader,
                                  const DexFile& dex_file,
                                  const DexFileDeps& deps,
                                  Thread* self,
                                  /* out */ std::string* error_msg) const {
-  return VerifyAssignability(class_loader,
-                             dex_file,
-                             deps.assignable_types_,
-                             self,
-                             error_msg);
+  return VerifyAssignability(class_loader, dex_file, deps.assignable_types_, self, error_msg);
 }
 
 }  // namespace verifier
diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h
index 8e3b8a7..46b3554 100644
--- a/runtime/verifier/verifier_deps.h
+++ b/runtime/verifier/verifier_deps.h
@@ -137,6 +137,9 @@
     return GetDexFileDeps(dex_file) != nullptr;
   }
 
+  // Resets the data related to the given dex files.
+  void ClearData(const std::vector<const DexFile*>& dex_files);
+
   // Parses raw VerifierDeps data to extract bitvectors of which class def indices
   // were verified or not. The given `dex_files` must match the order and count of
   // dex files used to create the VerifierDeps.
diff --git a/runtime/well_known_classes-inl.h b/runtime/well_known_classes-inl.h
new file mode 100644
index 0000000..23fe145
--- /dev/null
+++ b/runtime/well_known_classes-inl.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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_RUNTIME_WELL_KNOWN_CLASSES_INL_H_
+#define ART_RUNTIME_WELL_KNOWN_CLASSES_INL_H_
+
+#include "well_known_classes.h"
+
+#include "art_field-inl.h"
+#include "art_method-inl.h"
+
+namespace art {
+namespace detail {
+
+template <typename MemberType, MemberType** kMember>
+template <ReadBarrierOption kReadBarrierOption>
+ObjPtr<mirror::Class> ClassFromMember<MemberType, kMember>::Get() {
+  return (*kMember)->template GetDeclaringClass<kReadBarrierOption>();
+}
+
+template <typename MemberType, MemberType** kMember>
+mirror::Class* ClassFromMember<MemberType, kMember>::operator->() const {
+  return Get().Ptr();
+}
+
+template <typename MemberType, MemberType** kMember>
+inline bool operator==(const ClassFromMember<MemberType, kMember> lhs, ObjPtr<mirror::Class> rhs) {
+  return lhs.Get() == rhs;
+}
+
+template <typename MemberType, MemberType** kMember>
+inline bool operator==(ObjPtr<mirror::Class> lhs, const ClassFromMember<MemberType, kMember> rhs) {
+  return rhs == lhs;
+}
+
+template <typename MemberType, MemberType** kMember>
+bool operator!=(const ClassFromMember<MemberType, kMember> lhs, ObjPtr<mirror::Class> rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename MemberType, MemberType** kMember>
+bool operator!=(ObjPtr<mirror::Class> lhs, const ClassFromMember<MemberType, kMember> rhs) {
+  return !(rhs == lhs);
+}
+
+}  // namespace detail
+}  // namespace art
+
+#endif  // ART_RUNTIME_WELL_KNOWN_CLASSES_INL_H_
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index cf9e321..780fa83 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -23,11 +23,15 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 
+#include "art_method-inl.h"
+#include "base/casts.h"
 #include "base/enums.h"
 #include "class_linker.h"
+#include "class_root-inl.h"
 #include "entrypoints/quick/quick_entrypoints_enum.h"
-#include "entrypoints/runtime_asm_entrypoints.h"
+#include "handle_scope-inl.h"
 #include "hidden_api.h"
+#include "jni/java_vm_ext.h"
 #include "jni/jni_internal.h"
 #include "jni_id_type.h"
 #include "mirror/class.h"
@@ -44,139 +48,127 @@
 jclass WellKnownClasses::dalvik_annotation_optimization_CriticalNative;
 jclass WellKnownClasses::dalvik_annotation_optimization_FastNative;
 jclass WellKnownClasses::dalvik_annotation_optimization_NeverCompile;
-jclass WellKnownClasses::dalvik_system_BaseDexClassLoader;
-jclass WellKnownClasses::dalvik_system_DelegateLastClassLoader;
-jclass WellKnownClasses::dalvik_system_DexClassLoader;
-jclass WellKnownClasses::dalvik_system_DexFile;
-jclass WellKnownClasses::dalvik_system_DexPathList;
-jclass WellKnownClasses::dalvik_system_DexPathList__Element;
-jclass WellKnownClasses::dalvik_system_EmulatedStackFrame;
-jclass WellKnownClasses::dalvik_system_InMemoryDexClassLoader;
-jclass WellKnownClasses::dalvik_system_PathClassLoader;
-jclass WellKnownClasses::dalvik_system_VMRuntime;
+jclass WellKnownClasses::dalvik_annotation_optimization_NeverInline;
 jclass WellKnownClasses::java_lang_annotation_Annotation__array;
-jclass WellKnownClasses::java_lang_BootClassLoader;
-jclass WellKnownClasses::java_lang_ClassLoader;
-jclass WellKnownClasses::java_lang_ClassNotFoundException;
-jclass WellKnownClasses::java_lang_Daemons;
-jclass WellKnownClasses::java_lang_Error;
-jclass WellKnownClasses::java_lang_IllegalAccessError;
-jclass WellKnownClasses::java_lang_NoClassDefFoundError;
-jclass WellKnownClasses::java_lang_Object;
-jclass WellKnownClasses::java_lang_OutOfMemoryError;
-jclass WellKnownClasses::java_lang_reflect_InvocationTargetException;
-jclass WellKnownClasses::java_lang_reflect_Parameter;
+jclass WellKnownClasses::java_lang_ClassValue;
+jclass WellKnownClasses::java_lang_Record;
 jclass WellKnownClasses::java_lang_reflect_Parameter__array;
-jclass WellKnownClasses::java_lang_reflect_Proxy;
-jclass WellKnownClasses::java_lang_RuntimeException;
-jclass WellKnownClasses::java_lang_StackOverflowError;
-jclass WellKnownClasses::java_lang_String;
 jclass WellKnownClasses::java_lang_StringFactory;
 jclass WellKnownClasses::java_lang_System;
-jclass WellKnownClasses::java_lang_Thread;
-jclass WellKnownClasses::java_lang_ThreadGroup;
-jclass WellKnownClasses::java_lang_Throwable;
 jclass WellKnownClasses::java_lang_Void;
-jclass WellKnownClasses::java_nio_Buffer;
-jclass WellKnownClasses::java_nio_ByteBuffer;
-jclass WellKnownClasses::java_nio_DirectByteBuffer;
-jclass WellKnownClasses::java_util_Collections;
-jclass WellKnownClasses::java_util_function_Consumer;
-jclass WellKnownClasses::libcore_reflect_AnnotationFactory;
-jclass WellKnownClasses::libcore_reflect_AnnotationMember;
-jclass WellKnownClasses::libcore_util_EmptyArray;
-jclass WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk;
-jclass WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer;
+jclass WellKnownClasses::libcore_reflect_AnnotationMember__array;
 
-jmethodID WellKnownClasses::dalvik_system_BaseDexClassLoader_getLdLibraryPath;
-jmethodID WellKnownClasses::dalvik_system_VMRuntime_runFinalization;
-jmethodID WellKnownClasses::dalvik_system_VMRuntime_hiddenApiUsed;
-jmethodID WellKnownClasses::java_lang_Boolean_valueOf;
-jmethodID WellKnownClasses::java_lang_Byte_valueOf;
-jmethodID WellKnownClasses::java_lang_Character_valueOf;
-jmethodID WellKnownClasses::java_lang_ClassLoader_loadClass;
-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_doubleToRawLongBits;
-jmethodID WellKnownClasses::java_lang_Double_valueOf;
-jmethodID WellKnownClasses::java_lang_Float_floatToRawIntBits;
-jmethodID WellKnownClasses::java_lang_Float_valueOf;
-jmethodID WellKnownClasses::java_lang_Integer_valueOf;
-jmethodID WellKnownClasses::java_lang_invoke_MethodHandle_asType;
-jmethodID WellKnownClasses::java_lang_invoke_MethodHandle_invokeExact;
-jmethodID WellKnownClasses::java_lang_invoke_MethodHandles_lookup;
-jmethodID WellKnownClasses::java_lang_invoke_MethodHandles_Lookup_findConstructor;
-jmethodID WellKnownClasses::java_lang_Long_valueOf;
-jmethodID WellKnownClasses::java_lang_ref_FinalizerReference_add;
-jmethodID WellKnownClasses::java_lang_ref_ReferenceQueue_add;
-jmethodID WellKnownClasses::java_lang_reflect_InvocationTargetException_init;
-jmethodID WellKnownClasses::java_lang_reflect_Parameter_init;
-jmethodID WellKnownClasses::java_lang_reflect_Proxy_init;
-jmethodID WellKnownClasses::java_lang_reflect_Proxy_invoke;
-jmethodID WellKnownClasses::java_lang_Runtime_nativeLoad;
-jmethodID WellKnownClasses::java_lang_Short_valueOf;
-jmethodID WellKnownClasses::java_lang_String_charAt;
-jmethodID WellKnownClasses::java_lang_Thread_dispatchUncaughtException;
-jmethodID WellKnownClasses::java_lang_Thread_init;
-jmethodID WellKnownClasses::java_lang_Thread_run;
-jmethodID WellKnownClasses::java_lang_ThreadGroup_add;
-jmethodID WellKnownClasses::java_lang_ThreadGroup_removeThread;
-jmethodID WellKnownClasses::java_nio_Buffer_isDirect;
-jmethodID WellKnownClasses::java_nio_DirectByteBuffer_init;
-jmethodID WellKnownClasses::java_util_function_Consumer_accept;
-jmethodID WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation;
-jmethodID WellKnownClasses::libcore_reflect_AnnotationMember_init;
-jmethodID WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
-jmethodID WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_dispatch;
+ArtMethod* WellKnownClasses::dalvik_system_BaseDexClassLoader_getLdLibraryPath;
+ArtMethod* WellKnownClasses::dalvik_system_DelegateLastClassLoader_init;
+ArtMethod* WellKnownClasses::dalvik_system_DexClassLoader_init;
+ArtMethod* WellKnownClasses::dalvik_system_InMemoryDexClassLoader_init;
+ArtMethod* WellKnownClasses::dalvik_system_PathClassLoader_init;
+ArtMethod* WellKnownClasses::dalvik_system_VMRuntime_hiddenApiUsed;
+ArtMethod* WellKnownClasses::java_lang_Boolean_valueOf;
+ArtMethod* WellKnownClasses::java_lang_BootClassLoader_init;
+ArtMethod* WellKnownClasses::java_lang_Byte_valueOf;
+ArtMethod* WellKnownClasses::java_lang_Character_valueOf;
+ArtMethod* WellKnownClasses::java_lang_ClassLoader_loadClass;
+ArtMethod* WellKnownClasses::java_lang_ClassNotFoundException_init;
+ArtMethod* WellKnownClasses::java_lang_Daemons_start;
+ArtMethod* WellKnownClasses::java_lang_Daemons_stop;
+ArtMethod* WellKnownClasses::java_lang_Daemons_waitForDaemonStart;
+ArtMethod* WellKnownClasses::java_lang_Double_doubleToRawLongBits;
+ArtMethod* WellKnownClasses::java_lang_Double_valueOf;
+ArtMethod* WellKnownClasses::java_lang_Error_init;
+ArtMethod* WellKnownClasses::java_lang_Float_floatToRawIntBits;
+ArtMethod* WellKnownClasses::java_lang_Float_valueOf;
+ArtMethod* WellKnownClasses::java_lang_IllegalAccessError_init;
+ArtMethod* WellKnownClasses::java_lang_Integer_valueOf;
+ArtMethod* WellKnownClasses::java_lang_Long_valueOf;
+ArtMethod* WellKnownClasses::java_lang_NoClassDefFoundError_init;
+ArtMethod* WellKnownClasses::java_lang_OutOfMemoryError_init;
+ArtMethod* WellKnownClasses::java_lang_Runtime_nativeLoad;
+ArtMethod* WellKnownClasses::java_lang_RuntimeException_init;
+ArtMethod* WellKnownClasses::java_lang_Short_valueOf;
+ArtMethod* WellKnownClasses::java_lang_StackOverflowError_init;
+ArtMethod* WellKnownClasses::java_lang_String_charAt;
+ArtMethod* WellKnownClasses::java_lang_Thread_dispatchUncaughtException;
+ArtMethod* WellKnownClasses::java_lang_Thread_init;
+ArtMethod* WellKnownClasses::java_lang_Thread_run;
+ArtMethod* WellKnownClasses::java_lang_ThreadGroup_add;
+ArtMethod* WellKnownClasses::java_lang_ThreadGroup_threadTerminated;
+ArtMethod* WellKnownClasses::java_lang_invoke_MethodHandle_asType;
+ArtMethod* WellKnownClasses::java_lang_invoke_MethodHandle_invokeExact;
+ArtMethod* WellKnownClasses::java_lang_invoke_MethodHandles_lookup;
+ArtMethod* WellKnownClasses::java_lang_invoke_MethodHandles_Lookup_findConstructor;
+ArtMethod* WellKnownClasses::java_lang_ref_FinalizerReference_add;
+ArtMethod* WellKnownClasses::java_lang_ref_ReferenceQueue_add;
+ArtMethod* WellKnownClasses::java_lang_reflect_InvocationTargetException_init;
+ArtMethod* WellKnownClasses::java_lang_reflect_Parameter_init;
+ArtMethod* WellKnownClasses::java_lang_reflect_Proxy_init;
+ArtMethod* WellKnownClasses::java_lang_reflect_Proxy_invoke;
+ArtMethod* WellKnownClasses::java_nio_Buffer_isDirect;
+ArtMethod* WellKnownClasses::java_nio_DirectByteBuffer_init;
+ArtMethod* WellKnownClasses::java_util_function_Consumer_accept;
+ArtMethod* WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D;
+ArtMethod* WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F;
+ArtMethod* WellKnownClasses::jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars;
+ArtMethod* WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation;
+ArtMethod* WellKnownClasses::libcore_reflect_AnnotationMember_init;
+ArtMethod* WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
+ArtMethod* WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_dispatch;
 
-jfieldID WellKnownClasses::dalvik_system_DexFile_cookie;
-jfieldID WellKnownClasses::dalvik_system_DexFile_fileName;
-jfieldID WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList;
-jfieldID WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
-jfieldID WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter;
-jfieldID WellKnownClasses::dalvik_system_DexPathList_dexElements;
-jfieldID WellKnownClasses::dalvik_system_DexPathList__Element_dexFile;
-jfieldID WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer;
-jfieldID WellKnownClasses::java_io_FileDescriptor_descriptor;
-jfieldID WellKnownClasses::java_lang_ClassLoader_parent;
-jfieldID WellKnownClasses::java_lang_Thread_parkBlocker;
-jfieldID WellKnownClasses::java_lang_Thread_daemon;
-jfieldID WellKnownClasses::java_lang_Thread_group;
-jfieldID WellKnownClasses::java_lang_Thread_lock;
-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;
-jfieldID WellKnownClasses::java_lang_ThreadGroup_mainThreadGroup;
-jfieldID WellKnownClasses::java_lang_ThreadGroup_name;
-jfieldID WellKnownClasses::java_lang_ThreadGroup_parent;
-jfieldID WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup;
-jfieldID WellKnownClasses::java_lang_Throwable_cause;
-jfieldID WellKnownClasses::java_lang_Throwable_detailMessage;
-jfieldID WellKnownClasses::java_lang_Throwable_stackTrace;
-jfieldID WellKnownClasses::java_lang_Throwable_stackState;
-jfieldID WellKnownClasses::java_lang_Throwable_suppressedExceptions;
-jfieldID WellKnownClasses::java_nio_Buffer_address;
-jfieldID WellKnownClasses::java_nio_Buffer_capacity;
-jfieldID WellKnownClasses::java_nio_Buffer_elementSizeShift;
-jfieldID WellKnownClasses::java_nio_Buffer_limit;
-jfieldID WellKnownClasses::java_nio_Buffer_position;
-jfieldID WellKnownClasses::java_nio_ByteBuffer_address;
-jfieldID WellKnownClasses::java_nio_ByteBuffer_hb;
-jfieldID WellKnownClasses::java_nio_ByteBuffer_isReadOnly;
-jfieldID WellKnownClasses::java_nio_ByteBuffer_limit;
-jfieldID WellKnownClasses::java_nio_ByteBuffer_offset;
-jfieldID WellKnownClasses::java_util_Collections_EMPTY_LIST;
-jfieldID WellKnownClasses::libcore_util_EmptyArray_STACK_TRACE_ELEMENT;
-jfieldID WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_data;
-jfieldID WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_length;
-jfieldID WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_offset;
-jfieldID WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_type;
+ArtField* WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList;
+ArtField* WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
+ArtField* WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter;
+ArtField* WellKnownClasses::dalvik_system_DexFile_cookie;
+ArtField* WellKnownClasses::dalvik_system_DexFile_fileName;
+ArtField* WellKnownClasses::dalvik_system_DexPathList_dexElements;
+ArtField* WellKnownClasses::dalvik_system_DexPathList__Element_dexFile;
+ArtField* WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer;
+ArtField* WellKnownClasses::java_io_FileDescriptor_descriptor;
+ArtField* WellKnownClasses::java_lang_ClassLoader_parent;
+ArtField* WellKnownClasses::java_lang_Thread_parkBlocker;
+ArtField* WellKnownClasses::java_lang_Thread_daemon;
+ArtField* WellKnownClasses::java_lang_Thread_group;
+ArtField* WellKnownClasses::java_lang_Thread_lock;
+ArtField* WellKnownClasses::java_lang_Thread_name;
+ArtField* WellKnownClasses::java_lang_Thread_priority;
+ArtField* WellKnownClasses::java_lang_Thread_nativePeer;
+ArtField* WellKnownClasses::java_lang_Thread_systemDaemon;
+ArtField* WellKnownClasses::java_lang_Thread_unparkedBeforeStart;
+ArtField* WellKnownClasses::java_lang_ThreadGroup_groups;
+ArtField* WellKnownClasses::java_lang_ThreadGroup_ngroups;
+ArtField* WellKnownClasses::java_lang_ThreadGroup_mainThreadGroup;
+ArtField* WellKnownClasses::java_lang_ThreadGroup_name;
+ArtField* WellKnownClasses::java_lang_ThreadGroup_parent;
+ArtField* WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup;
+ArtField* WellKnownClasses::java_lang_Throwable_cause;
+ArtField* WellKnownClasses::java_lang_Throwable_detailMessage;
+ArtField* WellKnownClasses::java_lang_Throwable_stackTrace;
+ArtField* WellKnownClasses::java_lang_Throwable_stackState;
+ArtField* WellKnownClasses::java_lang_Throwable_suppressedExceptions;
+ArtField* WellKnownClasses::java_nio_Buffer_address;
+ArtField* WellKnownClasses::java_nio_Buffer_capacity;
+ArtField* WellKnownClasses::java_nio_Buffer_elementSizeShift;
+ArtField* WellKnownClasses::java_nio_Buffer_limit;
+ArtField* WellKnownClasses::java_nio_Buffer_position;
+ArtField* WellKnownClasses::java_nio_ByteBuffer_hb;
+ArtField* WellKnownClasses::java_nio_ByteBuffer_isReadOnly;
+ArtField* WellKnownClasses::java_nio_ByteBuffer_offset;
+ArtField* WellKnownClasses::java_util_Collections_EMPTY_LIST;
+ArtField* WellKnownClasses::jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer;
+ArtField* WellKnownClasses::jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image;
+ArtField* WellKnownClasses::libcore_util_EmptyArray_STACK_TRACE_ELEMENT;
+ArtField* WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_data;
+ArtField* WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_length;
+ArtField* WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_offset;
+ArtField* WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_type;
+
+static ObjPtr<mirror::Class> FindSystemClass(ClassLinker* class_linker,
+                                             Thread* self,
+                                             const char* descriptor)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ObjPtr<mirror::Class> klass = class_linker->FindSystemClass(self, descriptor);
+  CHECK(klass != nullptr) << "Couldn't find system class: " << descriptor;
+  return klass;
+}
 
 static jclass CacheClass(JNIEnv* env, const char* jni_class_name) {
   ScopedLocalRef<jclass> c(env, env->FindClass(jni_class_name));
@@ -186,73 +178,57 @@
   return reinterpret_cast<jclass>(env->NewGlobalRef(c.get()));
 }
 
-static jfieldID CacheField(JNIEnv* env, jclass c, bool is_static,
-                           const char* name, const char* signature) {
-  jfieldID fid;
-  {
-    ScopedObjectAccess soa(env);
-    if (Runtime::Current()->GetJniIdType() != JniIdType::kSwapablePointer) {
-      fid = jni::EncodeArtField</*kEnableIndexIds*/ true>(
-          FindFieldJNI(soa, c, name, signature, is_static));
-    } else {
-      fid = jni::EncodeArtField</*kEnableIndexIds*/ false>(
-          FindFieldJNI(soa, c, name, signature, is_static));
-    }
-  }
-  if (fid == nullptr) {
-    ScopedObjectAccess soa(env);
-    if (soa.Self()->IsExceptionPending()) {
-      LOG(FATAL_WITHOUT_ABORT) << soa.Self()->GetException()->Dump();
-    }
+static ArtField* CacheField(ObjPtr<mirror::Class> klass,
+                            bool is_static,
+                            const char* name,
+                            const char* signature) REQUIRES_SHARED(Locks::mutator_lock_) {
+  ArtField* field = is_static
+      ? klass->FindDeclaredStaticField(name, signature)
+      : klass->FindDeclaredInstanceField(name, signature);
+  if (UNLIKELY(field == nullptr)) {
     std::ostringstream os;
-    WellKnownClasses::ToClass(c)->DumpClass(os, mirror::Class::kDumpClassFullDetail);
-    LOG(FATAL) << "Couldn't find field \"" << name << "\" with signature \"" << signature << "\": "
-               << os.str();
+    klass->DumpClass(os, mirror::Class::kDumpClassFullDetail);
+    LOG(FATAL) << "Couldn't find " << (is_static ? "static" : "instance") << " field \""
+               << name << "\" with signature \"" << signature << "\": " << os.str();
+    UNREACHABLE();
   }
-  return fid;
+  return field;
 }
 
-static jmethodID CacheMethod(JNIEnv* env, jclass c, bool is_static,
-                             const char* name, const char* signature) {
-  jmethodID mid;
-  {
-    ScopedObjectAccess soa(env);
-    if (Runtime::Current()->GetJniIdType() != JniIdType::kSwapablePointer) {
-      mid = jni::EncodeArtMethod</*kEnableIndexIds*/ true>(
-          FindMethodJNI(soa, c, name, signature, is_static));
-    } else {
-      mid = jni::EncodeArtMethod</*kEnableIndexIds*/ false>(
-          FindMethodJNI(soa, c, name, signature, is_static));
-    }
-  }
-  if (mid == nullptr) {
-    ScopedObjectAccess soa(env);
-    if (soa.Self()->IsExceptionPending()) {
-      LOG(FATAL_WITHOUT_ABORT) << soa.Self()->GetException()->Dump();
-    }
+static ArtMethod* CacheMethod(ObjPtr<mirror::Class> klass,
+                              bool is_static,
+                              const char* name,
+                              const char* signature,
+                              PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_) {
+  ArtMethod* method = klass->IsInterface()
+      ? klass->FindInterfaceMethod(name, signature, pointer_size)
+      : klass->FindClassMethod(name, signature, pointer_size);
+  if (UNLIKELY(method == nullptr) || UNLIKELY(is_static != method->IsStatic())) {
     std::ostringstream os;
-    WellKnownClasses::ToClass(c)->DumpClass(os, mirror::Class::kDumpClassFullDetail);
-    LOG(FATAL) << "Couldn't find method \"" << name << "\" with signature \"" << signature << "\": "
-               << os.str();
+    klass->DumpClass(os, mirror::Class::kDumpClassFullDetail);
+    LOG(FATAL) << "Couldn't find " << (is_static ? "static" : "instance") << " method \""
+               << name << "\" with signature \"" << signature << "\": " << os.str();
+    UNREACHABLE();
   }
-  return mid;
+  DCHECK(method->GetDeclaringClass() == klass);
+  return method;
 }
 
-static jmethodID CacheMethod(JNIEnv* env, const char* klass, bool is_static,
-                      const char* name, const char* signature) {
-  ScopedLocalRef<jclass> java_class(env, env->FindClass(klass));
-  return CacheMethod(env, java_class.get(), is_static, name, signature);
-}
-
-static jmethodID CachePrimitiveBoxingMethod(JNIEnv* env, char prim_name, const char* boxed_name) {
-  ScopedLocalRef<jclass> boxed_class(env, env->FindClass(boxed_name));
-  return CacheMethod(env, boxed_class.get(), true, "valueOf",
-                     android::base::StringPrintf("(%c)L%s;", prim_name, boxed_name).c_str());
+static ArtMethod* CachePrimitiveBoxingMethod(ClassLinker* class_linker,
+                                             Thread* self,
+                                             char prim_name,
+                                             const char* boxed_name)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ObjPtr<mirror::Class> boxed_class = FindSystemClass(class_linker, self, boxed_name);
+  PointerSize pointer_size = class_linker->GetImagePointerSize();
+  std::string signature = android::base::StringPrintf("(%c)%s", prim_name, boxed_name);
+  return CacheMethod(boxed_class, /*is_static=*/ true, "valueOf", signature.c_str(), pointer_size);
 }
 
 #define STRING_INIT_LIST(V) \
   V(java_lang_String_init, "()V", newEmptyString, "newEmptyString", "()Ljava/lang/String;", NewEmptyString) \
   V(java_lang_String_init_B, "([B)V", newStringFromBytes_B, "newStringFromBytes", "([B)Ljava/lang/String;", NewStringFromBytes_B) \
+  V(java_lang_String_init_BB, "([BB)V", newStringFromBytes_BB, "newStringFromBytes", "([BB)Ljava/lang/String;", NewStringFromBytes_BB) \
   V(java_lang_String_init_BI, "([BI)V", newStringFromBytes_BI, "newStringFromBytes", "([BI)Ljava/lang/String;", NewStringFromBytes_BI) \
   V(java_lang_String_init_BII, "([BII)V", newStringFromBytes_BII, "newStringFromBytes", "([BII)Ljava/lang/String;", NewStringFromBytes_BII) \
   V(java_lang_String_init_BIII, "([BIII)V", newStringFromBytes_BIII, "newStringFromBytes", "([BIII)Ljava/lang/String;", NewStringFromBytes_BIII) \
@@ -345,50 +321,17 @@
   dalvik_annotation_optimization_FastNative = CacheClass(env, "dalvik/annotation/optimization/FastNative");
   dalvik_annotation_optimization_NeverCompile =
       CacheClass(env, "dalvik/annotation/optimization/NeverCompile");
-  dalvik_system_BaseDexClassLoader = CacheClass(env, "dalvik/system/BaseDexClassLoader");
-  dalvik_system_DelegateLastClassLoader = CacheClass(env, "dalvik/system/DelegateLastClassLoader");
-  dalvik_system_DexClassLoader = CacheClass(env, "dalvik/system/DexClassLoader");
-  dalvik_system_DexFile = CacheClass(env, "dalvik/system/DexFile");
-  dalvik_system_DexPathList = CacheClass(env, "dalvik/system/DexPathList");
-  dalvik_system_DexPathList__Element = CacheClass(env, "dalvik/system/DexPathList$Element");
-  dalvik_system_EmulatedStackFrame = CacheClass(env, "dalvik/system/EmulatedStackFrame");
-  dalvik_system_InMemoryDexClassLoader = CacheClass(env, "dalvik/system/InMemoryDexClassLoader");
-  dalvik_system_PathClassLoader = CacheClass(env, "dalvik/system/PathClassLoader");
-  dalvik_system_VMRuntime = CacheClass(env, "dalvik/system/VMRuntime");
+  dalvik_annotation_optimization_NeverInline =
+      CacheClass(env, "dalvik/annotation/optimization/NeverInline");
 
   java_lang_annotation_Annotation__array = CacheClass(env, "[Ljava/lang/annotation/Annotation;");
-  java_lang_BootClassLoader = CacheClass(env, "java/lang/BootClassLoader");
-  java_lang_ClassLoader = CacheClass(env, "java/lang/ClassLoader");
-  java_lang_ClassNotFoundException = CacheClass(env, "java/lang/ClassNotFoundException");
-  java_lang_Daemons = CacheClass(env, "java/lang/Daemons");
-  java_lang_Object = CacheClass(env, "java/lang/Object");
-  java_lang_OutOfMemoryError = CacheClass(env, "java/lang/OutOfMemoryError");
-  java_lang_Error = CacheClass(env, "java/lang/Error");
-  java_lang_IllegalAccessError = CacheClass(env, "java/lang/IllegalAccessError");
-  java_lang_NoClassDefFoundError = CacheClass(env, "java/lang/NoClassDefFoundError");
-  java_lang_reflect_InvocationTargetException = CacheClass(env, "java/lang/reflect/InvocationTargetException");
-  java_lang_reflect_Parameter = CacheClass(env, "java/lang/reflect/Parameter");
+  java_lang_ClassValue = CacheClass(env, "java/lang/ClassValue");
+  java_lang_Record = CacheClass(env, "java/lang/Record");
   java_lang_reflect_Parameter__array = CacheClass(env, "[Ljava/lang/reflect/Parameter;");
-  java_lang_reflect_Proxy = CacheClass(env, "java/lang/reflect/Proxy");
-  java_lang_RuntimeException = CacheClass(env, "java/lang/RuntimeException");
-  java_lang_StackOverflowError = CacheClass(env, "java/lang/StackOverflowError");
-  java_lang_String = CacheClass(env, "java/lang/String");
   java_lang_StringFactory = CacheClass(env, "java/lang/StringFactory");
   java_lang_System = CacheClass(env, "java/lang/System");
-  java_lang_Thread = CacheClass(env, "java/lang/Thread");
-  java_lang_ThreadGroup = CacheClass(env, "java/lang/ThreadGroup");
-  java_lang_Throwable = CacheClass(env, "java/lang/Throwable");
   java_lang_Void = CacheClass(env, "java/lang/Void");
-  java_nio_Buffer = CacheClass(env, "java/nio/Buffer");
-  java_nio_ByteBuffer = CacheClass(env, "java/nio/ByteBuffer");
-  java_nio_DirectByteBuffer = CacheClass(env, "java/nio/DirectByteBuffer");
-  java_util_Collections = CacheClass(env, "java/util/Collections");
-  java_util_function_Consumer = CacheClass(env, "java/util/function/Consumer");
-  libcore_reflect_AnnotationFactory = CacheClass(env, "libcore/reflect/AnnotationFactory");
-  libcore_reflect_AnnotationMember = CacheClass(env, "libcore/reflect/AnnotationMember");
-  libcore_util_EmptyArray = CacheClass(env, "libcore/util/EmptyArray");
-  org_apache_harmony_dalvik_ddmc_Chunk = CacheClass(env, "org/apache/harmony/dalvik/ddmc/Chunk");
-  org_apache_harmony_dalvik_ddmc_DdmServer = CacheClass(env, "org/apache/harmony/dalvik/ddmc/DdmServer");
+  libcore_reflect_AnnotationMember__array = CacheClass(env, "[Llibcore/reflect/AnnotationMember;");
 
   InitFieldsAndMethodsOnly(env);
 }
@@ -397,149 +340,453 @@
   hiddenapi::ScopedHiddenApiEnforcementPolicySetting hiddenapi_exemption(
       hiddenapi::EnforcementPolicy::kDisabled);
 
-  dalvik_system_BaseDexClassLoader_getLdLibraryPath = CacheMethod(env, dalvik_system_BaseDexClassLoader, false, "getLdLibraryPath", "()Ljava/lang/String;");
-  dalvik_system_VMRuntime_runFinalization = CacheMethod(env, dalvik_system_VMRuntime, true, "runFinalization", "(J)V");
-  dalvik_system_VMRuntime_hiddenApiUsed = CacheMethod(env, dalvik_system_VMRuntime, true, "hiddenApiUsed", "(ILjava/lang/String;Ljava/lang/String;IZ)V");
+  Thread* self = Thread::ForEnv(env);
+  ScopedObjectAccess soa(self);
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
 
-  java_lang_ClassNotFoundException_init = CacheMethod(env, java_lang_ClassNotFoundException, false, "<init>", "(Ljava/lang/String;Ljava/lang/Throwable;)V");
-  java_lang_ClassLoader_loadClass = CacheMethod(env, java_lang_ClassLoader, false, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+  java_lang_Boolean_valueOf =
+      CachePrimitiveBoxingMethod(class_linker, self, 'Z', "Ljava/lang/Boolean;");
+  java_lang_Byte_valueOf =
+      CachePrimitiveBoxingMethod(class_linker, self, 'B', "Ljava/lang/Byte;");
+  java_lang_Character_valueOf =
+      CachePrimitiveBoxingMethod(class_linker, self, 'C', "Ljava/lang/Character;");
+  java_lang_Double_valueOf =
+      CachePrimitiveBoxingMethod(class_linker, self, 'D', "Ljava/lang/Double;");
+  java_lang_Float_valueOf =
+      CachePrimitiveBoxingMethod(class_linker, self, 'F', "Ljava/lang/Float;");
+  java_lang_Integer_valueOf =
+      CachePrimitiveBoxingMethod(class_linker, self, 'I', "Ljava/lang/Integer;");
+  java_lang_Long_valueOf =
+      CachePrimitiveBoxingMethod(class_linker, self, 'J', "Ljava/lang/Long;");
+  java_lang_Short_valueOf =
+      CachePrimitiveBoxingMethod(class_linker, self, 'S', "Ljava/lang/Short;");
 
-  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_MethodHandle_asType = CacheMethod(env, "java/lang/invoke/MethodHandle", false, "asType", "(Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;");
-  java_lang_invoke_MethodHandle_invokeExact = CacheMethod(env, "java/lang/invoke/MethodHandle", false, "invokeExact", "([Ljava/lang/Object;)Ljava/lang/Object;");
-  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;");
+  StackHandleScope<42u> hs(self);
+  Handle<mirror::Class> d_s_bdcl =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/BaseDexClassLoader;"));
+  Handle<mirror::Class> d_s_dlcl =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/DelegateLastClassLoader;"));
+  Handle<mirror::Class> d_s_dcl =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/DexClassLoader;"));
+  Handle<mirror::Class> d_s_df =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/DexFile;"));
+  Handle<mirror::Class> d_s_dpl =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/DexPathList;"));
+  Handle<mirror::Class> d_s_dpl_e =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/DexPathList$Element;"));
+  Handle<mirror::Class> d_s_imdcl =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/InMemoryDexClassLoader;"));
+  Handle<mirror::Class> d_s_pcl =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/PathClassLoader;"));
+  Handle<mirror::Class> d_s_vmr =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/VMRuntime;"));
+  Handle<mirror::Class> j_i_fd =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/io/FileDescriptor;"));
+  Handle<mirror::Class> j_l_bcl =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/BootClassLoader;"));
+  Handle<mirror::Class> j_l_cl =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/ClassLoader;"));
+  Handle<mirror::Class> j_l_cnfe =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/ClassNotFoundException;"));
+  Handle<mirror::Class> j_l_Daemons =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/Daemons;"));
+  Handle<mirror::Class> j_l_Error =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/Error;"));
+  Handle<mirror::Class> j_l_IllegalAccessError =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/IllegalAccessError;"));
+  Handle<mirror::Class> j_l_NoClassDefFoundError =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/NoClassDefFoundError;"));
+  Handle<mirror::Class> j_l_OutOfMemoryError =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/OutOfMemoryError;"));
+  Handle<mirror::Class> j_l_RuntimeException =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/RuntimeException;"));
+  Handle<mirror::Class> j_l_StackOverflowError =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/StackOverflowError;"));
+  Handle<mirror::Class> j_l_Thread =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/Thread;"));
+  Handle<mirror::Class> j_l_tg =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/ThreadGroup;"));
+  Handle<mirror::Class> j_l_i_MethodHandle =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/invoke/MethodHandle;"));
+  Handle<mirror::Class> j_l_i_MethodHandles =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/invoke/MethodHandles;"));
+  Handle<mirror::Class> j_l_i_MethodHandles_Lookup =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/invoke/MethodHandles$Lookup;"));
+  Handle<mirror::Class> j_l_r_fr =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/ref/FinalizerReference;"));
+  Handle<mirror::Class> j_l_r_rq =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/ref/ReferenceQueue;"));
+  Handle<mirror::Class> j_l_rl_ite = hs.NewHandle(
+      FindSystemClass(class_linker, self, "Ljava/lang/reflect/InvocationTargetException;"));
+  Handle<mirror::Class> j_l_rl_Parameter =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/reflect/Parameter;"));
+  Handle<mirror::Class> j_n_b =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/nio/Buffer;"));
+  Handle<mirror::Class> j_n_bb =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/nio/ByteBuffer;"));
+  Handle<mirror::Class> j_n_dbb =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/nio/DirectByteBuffer;"));
+  Handle<mirror::Class> j_u_c =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/util/Collections;"));
+  Handle<mirror::Class> j_u_f_c =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/util/function/Consumer;"));
+  Handle<mirror::Class> j_i_m_fd =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljdk/internal/math/FloatingDecimal;"));
+  Handle<mirror::Class> j_i_m_fd_btab = hs.NewHandle(FindSystemClass(
+      class_linker, self, "Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;"));
+  Handle<mirror::Class> j_i_m_fd_ebtab = hs.NewHandle(FindSystemClass(
+      class_linker, self, "Ljdk/internal/math/FloatingDecimal$ExceptionalBinaryToASCIIBuffer;"));
+  Handle<mirror::Class> l_r_af =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Llibcore/reflect/AnnotationFactory;"));
+  Handle<mirror::Class> l_r_am =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Llibcore/reflect/AnnotationMember;"));
+  Handle<mirror::Class> l_u_ea =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Llibcore/util/EmptyArray;"));
+  Handle<mirror::Class> o_a_h_d_c =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Lorg/apache/harmony/dalvik/ddmc/Chunk;"));
+  Handle<mirror::Class> o_a_h_d_d_ds =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Lorg/apache/harmony/dalvik/ddmc/DdmServer;"));
 
-  java_lang_ref_FinalizerReference_add = CacheMethod(env, "java/lang/ref/FinalizerReference", true, "add", "(Ljava/lang/Object;)V");
-  java_lang_ref_ReferenceQueue_add = CacheMethod(env, "java/lang/ref/ReferenceQueue", true, "add", "(Ljava/lang/ref/Reference;)V");
+  ScopedAssertNoThreadSuspension sants(__FUNCTION__);
+  PointerSize pointer_size = class_linker->GetImagePointerSize();
 
-  java_lang_reflect_InvocationTargetException_init = CacheMethod(env, java_lang_reflect_InvocationTargetException, false, "<init>", "(Ljava/lang/Throwable;)V");
-  java_lang_reflect_Parameter_init = CacheMethod(env, java_lang_reflect_Parameter, false, "<init>", "(Ljava/lang/String;ILjava/lang/reflect/Executable;I)V");
-  java_lang_String_charAt = CacheMethod(env, java_lang_String, false, "charAt", "(I)C");
-  java_lang_Thread_dispatchUncaughtException = CacheMethod(env, java_lang_Thread, false, "dispatchUncaughtException", "(Ljava/lang/Throwable;)V");
-  java_lang_Thread_init = CacheMethod(env, java_lang_Thread, false, "<init>", "(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V");
-  java_lang_Thread_run = CacheMethod(env, java_lang_Thread, false, "run", "()V");
-  java_lang_ThreadGroup_add = CacheMethod(env, java_lang_ThreadGroup, false, "add", "(Ljava/lang/Thread;)V");
-  java_lang_ThreadGroup_removeThread = CacheMethod(env, java_lang_ThreadGroup, false, "threadTerminated", "(Ljava/lang/Thread;)V");
-  java_nio_Buffer_isDirect = CacheMethod(env, java_nio_Buffer, false, "isDirect", "()Z");
-  java_nio_DirectByteBuffer_init = CacheMethod(env, java_nio_DirectByteBuffer, false, "<init>", "(JI)V");
-  java_util_function_Consumer_accept = CacheMethod(env, java_util_function_Consumer, false, "accept", "(Ljava/lang/Object;)V");
-  libcore_reflect_AnnotationFactory_createAnnotation = CacheMethod(env, libcore_reflect_AnnotationFactory, true, "createAnnotation", "(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)Ljava/lang/annotation/Annotation;");
-  libcore_reflect_AnnotationMember_init = CacheMethod(env, libcore_reflect_AnnotationMember, false, "<init>", "(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/reflect/Method;)V");
-  org_apache_harmony_dalvik_ddmc_DdmServer_broadcast = CacheMethod(env, org_apache_harmony_dalvik_ddmc_DdmServer, true, "broadcast", "(I)V");
-  org_apache_harmony_dalvik_ddmc_DdmServer_dispatch = CacheMethod(env, org_apache_harmony_dalvik_ddmc_DdmServer, true, "dispatch", "(I[BII)Lorg/apache/harmony/dalvik/ddmc/Chunk;");
+  dalvik_system_BaseDexClassLoader_getLdLibraryPath = CacheMethod(
+      d_s_bdcl.Get(),
+      /*is_static=*/ false,
+      "getLdLibraryPath",
+      "()Ljava/lang/String;",
+      pointer_size);
+  dalvik_system_DelegateLastClassLoader_init = CacheMethod(
+      d_s_dlcl.Get(),
+      /*is_static=*/ false,
+      "<init>",
+      "(Ljava/lang/String;Ljava/lang/ClassLoader;)V",
+      pointer_size);
+  dalvik_system_DexClassLoader_init = CacheMethod(
+      d_s_dcl.Get(),
+      /*is_static=*/ false,
+      "<init>",
+      "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V",
+      pointer_size);
+  dalvik_system_InMemoryDexClassLoader_init = CacheMethod(
+      d_s_imdcl.Get(),
+      /*is_static=*/ false,
+      "<init>",
+      "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V",
+      pointer_size);
+  dalvik_system_PathClassLoader_init = CacheMethod(
+      d_s_pcl.Get(),
+      /*is_static=*/ false,
+      "<init>",
+      "(Ljava/lang/String;Ljava/lang/ClassLoader;)V",
+      pointer_size);
 
-  dalvik_system_BaseDexClassLoader_pathList = CacheField(env, dalvik_system_BaseDexClassLoader, false, "pathList", "Ldalvik/system/DexPathList;");
-  dalvik_system_BaseDexClassLoader_sharedLibraryLoaders = CacheField(env, dalvik_system_BaseDexClassLoader, false, "sharedLibraryLoaders", "[Ljava/lang/ClassLoader;");
-  dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter = CacheField(env, dalvik_system_BaseDexClassLoader, false, "sharedLibraryLoadersAfter", "[Ljava/lang/ClassLoader;");
-  dalvik_system_DexFile_cookie = CacheField(env, dalvik_system_DexFile, false, "mCookie", "Ljava/lang/Object;");
-  dalvik_system_DexFile_fileName = CacheField(env, dalvik_system_DexFile, false, "mFileName", "Ljava/lang/String;");
-  dalvik_system_DexPathList_dexElements = CacheField(env, dalvik_system_DexPathList, false, "dexElements", "[Ldalvik/system/DexPathList$Element;");
-  dalvik_system_DexPathList__Element_dexFile = CacheField(env, dalvik_system_DexPathList__Element, false, "dexFile", "Ldalvik/system/DexFile;");
-  dalvik_system_VMRuntime_nonSdkApiUsageConsumer = CacheField(env, dalvik_system_VMRuntime, true, "nonSdkApiUsageConsumer", "Ljava/util/function/Consumer;");
+  dalvik_system_VMRuntime_hiddenApiUsed = CacheMethod(
+      d_s_vmr.Get(),
+      /*is_static=*/ true,
+      "hiddenApiUsed",
+      "(ILjava/lang/String;Ljava/lang/String;IZ)V",
+      pointer_size);
 
-  ScopedLocalRef<jclass> java_io_FileDescriptor(env, env->FindClass("java/io/FileDescriptor"));
-  java_io_FileDescriptor_descriptor = CacheField(env, java_io_FileDescriptor.get(), false, "descriptor", "I");
+  java_lang_BootClassLoader_init =
+      CacheMethod(j_l_bcl.Get(), /*is_static=*/ false, "<init>", "()V", pointer_size);
+  java_lang_ClassLoader_loadClass = CacheMethod(
+      j_l_cl.Get(),
+      /*is_static=*/ false,
+      "loadClass",
+      "(Ljava/lang/String;)Ljava/lang/Class;",
+      pointer_size);
 
-  java_lang_ClassLoader_parent =
-      CacheField(env, java_lang_ClassLoader, false, "parent", "Ljava/lang/ClassLoader;");
-  java_lang_Thread_parkBlocker =
-      CacheField(env, java_lang_Thread, false, "parkBlocker", "Ljava/lang/Object;");
-  java_lang_Thread_daemon = CacheField(env, java_lang_Thread, false, "daemon", "Z");
-  java_lang_Thread_group =
-      CacheField(env, java_lang_Thread, false, "group", "Ljava/lang/ThreadGroup;");
-  java_lang_Thread_lock = CacheField(env, java_lang_Thread, false, "lock", "Ljava/lang/Object;");
-  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");
-  java_lang_ThreadGroup_mainThreadGroup =
-      CacheField(env, java_lang_ThreadGroup, true, "mainThreadGroup", "Ljava/lang/ThreadGroup;");
-  java_lang_ThreadGroup_name =
-      CacheField(env, java_lang_ThreadGroup, false, "name", "Ljava/lang/String;");
-  java_lang_ThreadGroup_parent =
-      CacheField(env, java_lang_ThreadGroup, false, "parent", "Ljava/lang/ThreadGroup;");
-  java_lang_ThreadGroup_systemThreadGroup =
-      CacheField(env, java_lang_ThreadGroup, true, "systemThreadGroup", "Ljava/lang/ThreadGroup;");
-  java_lang_Throwable_cause =
-      CacheField(env, java_lang_Throwable, false, "cause", "Ljava/lang/Throwable;");
-  java_lang_Throwable_detailMessage =
-      CacheField(env, java_lang_Throwable, false, "detailMessage", "Ljava/lang/String;");
-  java_lang_Throwable_stackTrace =
-      CacheField(env, java_lang_Throwable, false, "stackTrace", "[Ljava/lang/StackTraceElement;");
-  java_lang_Throwable_stackState =
-      CacheField(env, java_lang_Throwable, false, "backtrace", "Ljava/lang/Object;");
-  java_lang_Throwable_suppressedExceptions =
-      CacheField(env, java_lang_Throwable, false, "suppressedExceptions", "Ljava/util/List;");
+  java_lang_ClassNotFoundException_init = CacheMethod(
+      j_l_cnfe.Get(),
+      /*is_static=*/ false,
+      "<init>",
+      "(Ljava/lang/String;Ljava/lang/Throwable;)V",
+      pointer_size);
 
-  java_nio_Buffer_address = CacheField(env, java_nio_Buffer, false, "address", "J");
-  java_nio_Buffer_capacity = CacheField(env, java_nio_Buffer, false, "capacity", "I");
-  java_nio_Buffer_elementSizeShift =
-      CacheField(env, java_nio_Buffer, false, "_elementSizeShift", "I");
-  java_nio_Buffer_limit = CacheField(env, java_nio_Buffer, false, "limit", "I");
-  java_nio_Buffer_position = CacheField(env, java_nio_Buffer, false, "position", "I");
-
-  java_nio_ByteBuffer_address = CacheField(env, java_nio_ByteBuffer, false, "address", "J");
-  java_nio_ByteBuffer_hb = CacheField(env, java_nio_ByteBuffer, false, "hb", "[B");
-  java_nio_ByteBuffer_isReadOnly = CacheField(env, java_nio_ByteBuffer, false, "isReadOnly", "Z");
-  java_nio_ByteBuffer_limit = CacheField(env, java_nio_ByteBuffer, false, "limit", "I");
-  java_nio_ByteBuffer_offset = CacheField(env, java_nio_ByteBuffer, false, "offset", "I");
-  java_util_Collections_EMPTY_LIST =
-      CacheField(env, java_util_Collections, true, "EMPTY_LIST", "Ljava/util/List;");
-  libcore_util_EmptyArray_STACK_TRACE_ELEMENT = CacheField(
-      env, libcore_util_EmptyArray, true, "STACK_TRACE_ELEMENT", "[Ljava/lang/StackTraceElement;");
-  org_apache_harmony_dalvik_ddmc_Chunk_data =
-      CacheField(env, org_apache_harmony_dalvik_ddmc_Chunk, false, "data", "[B");
-  org_apache_harmony_dalvik_ddmc_Chunk_length =
-      CacheField(env, org_apache_harmony_dalvik_ddmc_Chunk, false, "length", "I");
-  org_apache_harmony_dalvik_ddmc_Chunk_offset =
-      CacheField(env, org_apache_harmony_dalvik_ddmc_Chunk, false, "offset", "I");
-  org_apache_harmony_dalvik_ddmc_Chunk_type =
-      CacheField(env, org_apache_harmony_dalvik_ddmc_Chunk, false, "type", "I");
-
-  java_lang_Boolean_valueOf = CachePrimitiveBoxingMethod(env, 'Z', "java/lang/Boolean");
-  java_lang_Byte_valueOf = CachePrimitiveBoxingMethod(env, 'B', "java/lang/Byte");
-  java_lang_Character_valueOf = CachePrimitiveBoxingMethod(env, 'C', "java/lang/Character");
-  java_lang_Double_valueOf = CachePrimitiveBoxingMethod(env, 'D', "java/lang/Double");
-  java_lang_Float_valueOf = CachePrimitiveBoxingMethod(env, 'F', "java/lang/Float");
-  java_lang_Integer_valueOf = CachePrimitiveBoxingMethod(env, 'I', "java/lang/Integer");
-  java_lang_Long_valueOf = CachePrimitiveBoxingMethod(env, 'J', "java/lang/Long");
-  java_lang_Short_valueOf = CachePrimitiveBoxingMethod(env, 'S', "java/lang/Short");
-
+  ObjPtr<mirror::Class> j_l_Double = java_lang_Double_valueOf->GetDeclaringClass();
   java_lang_Double_doubleToRawLongBits =
-      CacheMethod(env, "java/lang/Double", /*is_static=*/ true, "doubleToRawLongBits", "(D)J");
+      CacheMethod(j_l_Double, /*is_static=*/ true, "doubleToRawLongBits", "(D)J", pointer_size);
+  ObjPtr<mirror::Class> j_l_Float = java_lang_Float_valueOf->GetDeclaringClass();
   java_lang_Float_floatToRawIntBits =
-      CacheMethod(env, "java/lang/Float", /*is_static=*/ true, "floatToRawIntBits", "(F)I");
+      CacheMethod(j_l_Float, /*is_static=*/ true, "floatToRawIntBits", "(F)I", pointer_size);
+
+  java_lang_Daemons_start = CacheMethod(
+      j_l_Daemons.Get(), /*is_static=*/ true, "start", "()V", pointer_size);
+  java_lang_Daemons_stop = CacheMethod(
+      j_l_Daemons.Get(), /*is_static=*/ true, "stop", "()V", pointer_size);
+  java_lang_Daemons_waitForDaemonStart = CacheMethod(
+      j_l_Daemons.Get(), /*is_static=*/ true, "waitForDaemonStart", "()V", pointer_size);
+
+  java_lang_Error_init = CacheMethod(
+      j_l_Error.Get(), /*is_static=*/ false, "<init>", "()V", pointer_size);
+  java_lang_IllegalAccessError_init = CacheMethod(
+      j_l_IllegalAccessError.Get(), /*is_static=*/ false, "<init>", "()V", pointer_size);
+  java_lang_NoClassDefFoundError_init = CacheMethod(
+      j_l_NoClassDefFoundError.Get(), /*is_static=*/ false, "<init>", "()V", pointer_size);
+  java_lang_OutOfMemoryError_init = CacheMethod(
+      j_l_OutOfMemoryError.Get(), /*is_static=*/ false, "<init>", "()V", pointer_size);
+  java_lang_RuntimeException_init = CacheMethod(
+      j_l_RuntimeException.Get(), /*is_static=*/ false, "<init>", "()V", pointer_size);
+  java_lang_StackOverflowError_init = CacheMethod(
+      j_l_StackOverflowError.Get(), /*is_static=*/ false, "<init>", "()V", pointer_size);
+
+  ObjPtr<mirror::Class> j_l_String = GetClassRoot<mirror::String>(class_linker);
+  java_lang_String_charAt = CacheMethod(
+      j_l_String, /*is_static=*/ false, "charAt", "(I)C", pointer_size);
+
+  java_lang_Thread_dispatchUncaughtException = CacheMethod(
+      j_l_Thread.Get(),
+      /*is_static=*/ false,
+      "dispatchUncaughtException",
+      "(Ljava/lang/Throwable;)V",
+      pointer_size);
+  java_lang_Thread_init = CacheMethod(
+      j_l_Thread.Get(),
+      /*is_static=*/ false,
+      "<init>",
+      "(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V",
+      pointer_size);
+  java_lang_Thread_run = CacheMethod(
+      j_l_Thread.Get(), /*is_static=*/ false, "run", "()V", pointer_size);
+  java_lang_ThreadGroup_add = CacheMethod(
+      j_l_tg.Get(), /*is_static=*/ false, "add", "(Ljava/lang/Thread;)V", pointer_size);
+  java_lang_ThreadGroup_threadTerminated = CacheMethod(
+      j_l_tg.Get(),
+      /*is_static=*/ false,
+      "threadTerminated",
+      "(Ljava/lang/Thread;)V",
+      pointer_size);
+
+  java_lang_invoke_MethodHandle_asType = CacheMethod(
+      j_l_i_MethodHandle.Get(),
+      /*is_static=*/ false,
+      "asType",
+      "(Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;",
+      pointer_size);
+  java_lang_invoke_MethodHandle_invokeExact = CacheMethod(
+      j_l_i_MethodHandle.Get(),
+      /*is_static=*/ false,
+      "invokeExact",
+      "([Ljava/lang/Object;)Ljava/lang/Object;",
+      pointer_size);
+  java_lang_invoke_MethodHandles_lookup = CacheMethod(
+      j_l_i_MethodHandles.Get(),
+      /*is_static=*/ true,
+      "lookup",
+      "()Ljava/lang/invoke/MethodHandles$Lookup;",
+      pointer_size);
+  java_lang_invoke_MethodHandles_Lookup_findConstructor = CacheMethod(
+      j_l_i_MethodHandles_Lookup.Get(),
+      /*is_static=*/ false,
+      "findConstructor",
+      "(Ljava/lang/Class;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;",
+      pointer_size);
+
+  java_lang_ref_FinalizerReference_add = CacheMethod(
+      j_l_r_fr.Get(), /*is_static=*/ true, "add", "(Ljava/lang/Object;)V", pointer_size);
+  java_lang_ref_ReferenceQueue_add = CacheMethod(
+      j_l_r_rq.Get(), /*is_static=*/ true, "add", "(Ljava/lang/ref/Reference;)V", pointer_size);
+
+  java_lang_reflect_InvocationTargetException_init = CacheMethod(
+      j_l_rl_ite.Get(), /*is_static=*/ false, "<init>", "(Ljava/lang/Throwable;)V", pointer_size);
+  java_lang_reflect_Parameter_init = CacheMethod(
+      j_l_rl_Parameter.Get(),
+      /*is_static=*/ false,
+      "<init>",
+      "(Ljava/lang/String;ILjava/lang/reflect/Executable;I)V",
+      pointer_size);
+
+  ObjPtr<mirror::Class> j_l_rl_Proxy = GetClassRoot<mirror::Proxy>(class_linker);
+  java_lang_reflect_Proxy_init = CacheMethod(
+      j_l_rl_Proxy,
+      /*is_static=*/ false,
+      "<init>",
+      "(Ljava/lang/reflect/InvocationHandler;)V",
+      pointer_size);
+  java_lang_reflect_Proxy_invoke = CacheMethod(
+      j_l_rl_Proxy,
+      /*is_static=*/ true,
+      "invoke",
+      "(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;",
+      pointer_size);
+
+  java_nio_Buffer_isDirect =
+      CacheMethod(j_n_b.Get(), /*is_static=*/ false, "isDirect", "()Z", pointer_size);
+  java_nio_DirectByteBuffer_init =
+      CacheMethod(j_n_dbb.Get(), /*is_static=*/ false, "<init>", "(JI)V", pointer_size);
+
+  java_util_function_Consumer_accept = CacheMethod(
+      j_u_f_c.Get(), /*is_static=*/ false, "accept", "(Ljava/lang/Object;)V", pointer_size);
+
+  jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D = CacheMethod(
+      j_i_m_fd.Get(),
+      /*is_static=*/ true,
+      "getBinaryToASCIIConverter",
+      "(D)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;",
+      pointer_size);
+  jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F = CacheMethod(
+      j_i_m_fd.Get(),
+      /*is_static=*/ true,
+      "getBinaryToASCIIConverter",
+      "(F)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;",
+      pointer_size);
+  jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars =
+      CacheMethod(j_i_m_fd_btab.Get(), /*is_static=*/ false, "getChars", "([C)I", pointer_size);
+
+  libcore_reflect_AnnotationFactory_createAnnotation = CacheMethod(
+      l_r_af.Get(),
+      /*is_static=*/ true,
+      "createAnnotation",
+      "(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)Ljava/lang/annotation/Annotation;",
+      pointer_size);
+  libcore_reflect_AnnotationMember_init = CacheMethod(
+      l_r_am.Get(),
+      /*is_static=*/ false,
+      "<init>",
+      "(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/reflect/Method;)V",
+      pointer_size);
+
+  org_apache_harmony_dalvik_ddmc_DdmServer_broadcast =
+      CacheMethod(o_a_h_d_d_ds.Get(), /*is_static=*/ true, "broadcast", "(I)V", pointer_size);
+  org_apache_harmony_dalvik_ddmc_DdmServer_dispatch = CacheMethod(
+      o_a_h_d_d_ds.Get(),
+      /*is_static=*/ true,
+      "dispatch",
+      "(I[BII)Lorg/apache/harmony/dalvik/ddmc/Chunk;",
+      pointer_size);
+
+  dalvik_system_BaseDexClassLoader_pathList = CacheField(
+      d_s_bdcl.Get(), /*is_static=*/ false, "pathList", "Ldalvik/system/DexPathList;");
+  dalvik_system_BaseDexClassLoader_sharedLibraryLoaders = CacheField(
+      d_s_bdcl.Get(), /*is_static=*/ false, "sharedLibraryLoaders", "[Ljava/lang/ClassLoader;");
+  dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter = CacheField(
+      d_s_bdcl.Get(),
+      /*is_static=*/ false,
+      "sharedLibraryLoadersAfter",
+      "[Ljava/lang/ClassLoader;");
+  dalvik_system_DexFile_cookie = CacheField(
+      d_s_df.Get(), /*is_static=*/ false, "mCookie", "Ljava/lang/Object;");
+  dalvik_system_DexFile_fileName = CacheField(
+      d_s_df.Get(), /*is_static=*/ false, "mFileName", "Ljava/lang/String;");
+  dalvik_system_DexPathList_dexElements = CacheField(
+      d_s_dpl.Get(), /*is_static=*/ false, "dexElements", "[Ldalvik/system/DexPathList$Element;");
+  dalvik_system_DexPathList__Element_dexFile = CacheField(
+      d_s_dpl_e.Get(), /*is_static=*/ false, "dexFile", "Ldalvik/system/DexFile;");
+
+  dalvik_system_VMRuntime_nonSdkApiUsageConsumer = CacheField(
+      d_s_vmr.Get(),
+      /*is_static=*/ true,
+      "nonSdkApiUsageConsumer",
+      "Ljava/util/function/Consumer;");
+
+  java_io_FileDescriptor_descriptor = CacheField(
+      j_i_fd.Get(), /*is_static=*/ false, "descriptor", "I");
+
+  java_lang_ClassLoader_parent = CacheField(
+      j_l_cl.Get(), /*is_static=*/ false, "parent", "Ljava/lang/ClassLoader;");
+
+  java_lang_Thread_parkBlocker =
+      CacheField(j_l_Thread.Get(), /*is_static=*/ false, "parkBlocker", "Ljava/lang/Object;");
+  java_lang_Thread_daemon = CacheField(j_l_Thread.Get(), /*is_static=*/ false, "daemon", "Z");
+  java_lang_Thread_group =
+      CacheField(j_l_Thread.Get(), /*is_static=*/ false, "group", "Ljava/lang/ThreadGroup;");
+  java_lang_Thread_lock =
+      CacheField(j_l_Thread.Get(), /*is_static=*/ false, "lock", "Ljava/lang/Object;");
+  java_lang_Thread_name =
+      CacheField(j_l_Thread.Get(), /*is_static=*/ false, "name", "Ljava/lang/String;");
+  java_lang_Thread_priority = CacheField(j_l_Thread.Get(), /*is_static=*/ false, "priority", "I");
+  java_lang_Thread_nativePeer =
+      CacheField(j_l_Thread.Get(), /*is_static=*/ false, "nativePeer", "J");
+  java_lang_Thread_systemDaemon =
+      CacheField(j_l_Thread.Get(), /*is_static=*/ false, "systemDaemon", "Z");
+  java_lang_Thread_unparkedBeforeStart =
+      CacheField(j_l_Thread.Get(), /*is_static=*/ false, "unparkedBeforeStart", "Z");
+
+  java_lang_ThreadGroup_groups =
+      CacheField(j_l_tg.Get(), /*is_static=*/ false, "groups", "[Ljava/lang/ThreadGroup;");
+  java_lang_ThreadGroup_ngroups = CacheField(j_l_tg.Get(), /*is_static=*/ false, "ngroups", "I");
+  java_lang_ThreadGroup_mainThreadGroup =
+      CacheField(j_l_tg.Get(), /*is_static=*/ true, "mainThreadGroup", "Ljava/lang/ThreadGroup;");
+  java_lang_ThreadGroup_name =
+      CacheField(j_l_tg.Get(), /*is_static=*/ false, "name", "Ljava/lang/String;");
+  java_lang_ThreadGroup_parent =
+      CacheField(j_l_tg.Get(), /*is_static=*/ false, "parent", "Ljava/lang/ThreadGroup;");
+  java_lang_ThreadGroup_systemThreadGroup =
+      CacheField(j_l_tg.Get(), /*is_static=*/ true, "systemThreadGroup", "Ljava/lang/ThreadGroup;");
+
+  ObjPtr<mirror::Class> j_l_Throwable = GetClassRoot<mirror::Throwable>(class_linker);
+  java_lang_Throwable_cause = CacheField(
+      j_l_Throwable, /*is_static=*/ false, "cause", "Ljava/lang/Throwable;");
+  java_lang_Throwable_detailMessage = CacheField(
+      j_l_Throwable, /*is_static=*/ false, "detailMessage", "Ljava/lang/String;");
+  java_lang_Throwable_stackTrace = CacheField(
+      j_l_Throwable, /*is_static=*/ false, "stackTrace", "[Ljava/lang/StackTraceElement;");
+  java_lang_Throwable_stackState = CacheField(
+      j_l_Throwable, /*is_static=*/ false, "backtrace", "Ljava/lang/Object;");
+  java_lang_Throwable_suppressedExceptions = CacheField(
+      j_l_Throwable, /*is_static=*/ false, "suppressedExceptions", "Ljava/util/List;");
+
+  java_nio_Buffer_address = CacheField(j_n_b.Get(), /*is_static=*/ false, "address", "J");
+  java_nio_Buffer_capacity = CacheField(j_n_b.Get(), /*is_static=*/ false, "capacity", "I");
+  java_nio_Buffer_elementSizeShift =
+      CacheField(j_n_b.Get(), /*is_static=*/ false, "_elementSizeShift", "I");
+  java_nio_Buffer_limit = CacheField(j_n_b.Get(), /*is_static=*/ false, "limit", "I");
+  java_nio_Buffer_position = CacheField(j_n_b.Get(), /*is_static=*/ false, "position", "I");
+
+  java_nio_ByteBuffer_hb = CacheField(j_n_bb.Get(), /*is_static=*/ false, "hb", "[B");
+  java_nio_ByteBuffer_isReadOnly =
+      CacheField(j_n_bb.Get(), /*is_static=*/ false, "isReadOnly", "Z");
+  java_nio_ByteBuffer_offset = CacheField(j_n_bb.Get(), /*is_static=*/ false, "offset", "I");
+
+  java_util_Collections_EMPTY_LIST =
+      CacheField(j_u_c.Get(), /*is_static=*/ true, "EMPTY_LIST", "Ljava/util/List;");
+
+  jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer =
+      CacheField(j_i_m_fd_btab.Get(), /*is_static=*/ false, "buffer", "[C");
+  jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image = CacheField(
+      j_i_m_fd_ebtab.Get(), /*is_static=*/ false, "image", "Ljava/lang/String;");
+
+  libcore_util_EmptyArray_STACK_TRACE_ELEMENT = CacheField(
+      l_u_ea.Get(), /*is_static=*/ true, "STACK_TRACE_ELEMENT", "[Ljava/lang/StackTraceElement;");
+
+  org_apache_harmony_dalvik_ddmc_Chunk_data =
+      CacheField(o_a_h_d_c.Get(), /*is_static=*/ false, "data", "[B");
+  org_apache_harmony_dalvik_ddmc_Chunk_length =
+      CacheField(o_a_h_d_c.Get(), /*is_static=*/ false, "length", "I");
+  org_apache_harmony_dalvik_ddmc_Chunk_offset =
+      CacheField(o_a_h_d_c.Get(), /*is_static=*/ false, "offset", "I");
+  org_apache_harmony_dalvik_ddmc_Chunk_type =
+      CacheField(o_a_h_d_c.Get(), /*is_static=*/ false, "type", "I");
 }
 
 void WellKnownClasses::LateInit(JNIEnv* env) {
-  // CacheField and CacheMethod will initialize their classes. Classes below
-  // have clinit sections that call JNI methods. Late init is required
-  // to make sure these JNI methods are available.
-  ScopedLocalRef<jclass> java_lang_Runtime(env, env->FindClass("java/lang/Runtime"));
-  java_lang_Runtime_nativeLoad =
-      CacheMethod(env, java_lang_Runtime.get(), true, "nativeLoad",
-                  "(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/Class;)"
-                      "Ljava/lang/String;");
-  java_lang_reflect_Proxy_init =
-    CacheMethod(env, java_lang_reflect_Proxy, false, "<init>",
-                "(Ljava/lang/reflect/InvocationHandler;)V");
-  // This invariant is important since otherwise we will have the entire proxy invoke system
-  // confused.
-  DCHECK_NE(
-      jni::DecodeArtMethod(java_lang_reflect_Proxy_init)->GetEntryPointFromQuickCompiledCode(),
-      GetQuickInstrumentationEntryPoint());
-  java_lang_reflect_Proxy_invoke =
-    CacheMethod(env, java_lang_reflect_Proxy, true, "invoke",
-                "(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;"
-                    "[Ljava/lang/Object;)Ljava/lang/Object;");
+  // Initialize the `Runtime` class that was previously initialized
+  // by `CacheMethod()` calling `FindMethodJNI()`.
+  // TODO: Move this initialization to `ClassLinker`.
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  Thread* self = Thread::ForEnv(env);
+  ScopedObjectAccess soa(self);
+  StackHandleScope<1u> hs(self);
+  Handle<mirror::Class> j_l_Runtime =
+      hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/lang/Runtime;"));
+  bool success = class_linker->EnsureInitialized(
+      self, j_l_Runtime, /*can_init_fields=*/ true, /*can_init_parents=*/ true);
+  CHECK(success) << "Failed to initialize " << j_l_Runtime->PrettyDescriptor();
+
+  // The function `GetClassLoader()` in `jni_internal.cc` is checking if the caller
+  // is `java_lang_Runtime_nativeLoad` and, if so, returns the class loader override.
+  // However, this function is used several times between `WellKnownClasses::Init()`
+  // and setting up the override by the `Runtime` and requires that we take the other
+  // path, rather than returning the uninitialized override. Therefore we cannot
+  // initialize this well-known method early and require the `LateInit()`.
+  // TODO: Clean up the initialization steps.
+  java_lang_Runtime_nativeLoad = CacheMethod(
+      j_l_Runtime.Get(),
+      /*is_static=*/ true,
+      "nativeLoad",
+      "(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/Class;)Ljava/lang/String;",
+      class_linker->GetImagePointerSize());
 }
 
 void WellKnownClasses::HandleJniIdTypeChange(JNIEnv* env) {
@@ -551,85 +798,67 @@
   dalvik_annotation_optimization_CriticalNative = nullptr;
   dalvik_annotation_optimization_FastNative = nullptr;
   dalvik_annotation_optimization_NeverCompile = nullptr;
-  dalvik_system_BaseDexClassLoader = nullptr;
-  dalvik_system_DelegateLastClassLoader = nullptr;
-  dalvik_system_DexClassLoader = nullptr;
-  dalvik_system_DexFile = nullptr;
-  dalvik_system_DexPathList = nullptr;
-  dalvik_system_DexPathList__Element = nullptr;
-  dalvik_system_EmulatedStackFrame = nullptr;
-  dalvik_system_PathClassLoader = nullptr;
-  dalvik_system_VMRuntime = nullptr;
+  dalvik_annotation_optimization_NeverInline = nullptr;
   java_lang_annotation_Annotation__array = nullptr;
-  java_lang_BootClassLoader = nullptr;
-  java_lang_ClassLoader = nullptr;
-  java_lang_ClassNotFoundException = nullptr;
-  java_lang_Daemons = nullptr;
-  java_lang_Error = nullptr;
-  java_lang_IllegalAccessError = nullptr;
-  java_lang_NoClassDefFoundError = nullptr;
-  java_lang_Object = nullptr;
-  java_lang_OutOfMemoryError = nullptr;
-  java_lang_reflect_InvocationTargetException = nullptr;
-  java_lang_reflect_Parameter = nullptr;
+  java_lang_ClassValue = nullptr;
+  java_lang_Record = nullptr;
   java_lang_reflect_Parameter__array = nullptr;
-  java_lang_reflect_Proxy = nullptr;
-  java_lang_RuntimeException = nullptr;
-  java_lang_StackOverflowError = nullptr;
-  java_lang_String = nullptr;
   java_lang_StringFactory = nullptr;
   java_lang_System = nullptr;
-  java_lang_Thread = nullptr;
-  java_lang_ThreadGroup = nullptr;
-  java_lang_Throwable = nullptr;
   java_lang_Void = nullptr;
-  java_util_Collections = nullptr;
-  java_nio_Buffer = nullptr;
-  java_nio_ByteBuffer = nullptr;
-  java_nio_DirectByteBuffer = nullptr;
-  libcore_reflect_AnnotationFactory = nullptr;
-  libcore_reflect_AnnotationMember = nullptr;
-  libcore_util_EmptyArray = nullptr;
-  org_apache_harmony_dalvik_ddmc_Chunk = nullptr;
-  org_apache_harmony_dalvik_ddmc_DdmServer = nullptr;
+  libcore_reflect_AnnotationMember__array = nullptr;
 
   dalvik_system_BaseDexClassLoader_getLdLibraryPath = nullptr;
-  dalvik_system_VMRuntime_runFinalization = nullptr;
+  WellKnownClasses::dalvik_system_DelegateLastClassLoader_init = nullptr;
+  WellKnownClasses::dalvik_system_DexClassLoader_init = nullptr;
+  WellKnownClasses::dalvik_system_InMemoryDexClassLoader_init = nullptr;
+  WellKnownClasses::dalvik_system_PathClassLoader_init = nullptr;
   dalvik_system_VMRuntime_hiddenApiUsed = nullptr;
   java_io_FileDescriptor_descriptor = nullptr;
   java_lang_Boolean_valueOf = nullptr;
   java_lang_Byte_valueOf = nullptr;
   java_lang_Character_valueOf = nullptr;
+  java_lang_BootClassLoader_init = nullptr;
   java_lang_ClassLoader_loadClass = nullptr;
   java_lang_ClassNotFoundException_init = nullptr;
   java_lang_Daemons_start = nullptr;
   java_lang_Daemons_stop = nullptr;
+  java_lang_Daemons_waitForDaemonStart = nullptr;
   java_lang_Double_doubleToRawLongBits = nullptr;
   java_lang_Double_valueOf = nullptr;
+  java_lang_Error_init = nullptr;
   java_lang_Float_floatToRawIntBits = nullptr;
   java_lang_Float_valueOf = nullptr;
+  java_lang_IllegalAccessError_init = nullptr;
   java_lang_Integer_valueOf = nullptr;
+  java_lang_Long_valueOf = nullptr;
+  java_lang_NoClassDefFoundError_init = nullptr;
+  java_lang_OutOfMemoryError_init = nullptr;
+  java_lang_Runtime_nativeLoad = nullptr;
+  java_lang_RuntimeException_init = nullptr;
+  java_lang_Short_valueOf = nullptr;
+  java_lang_StackOverflowError_init = nullptr;
+  java_lang_String_charAt = nullptr;
+  java_lang_Thread_dispatchUncaughtException = nullptr;
+  java_lang_Thread_init = nullptr;
+  java_lang_Thread_run = nullptr;
+  java_lang_ThreadGroup_add = nullptr;
+  java_lang_ThreadGroup_threadTerminated = nullptr;
   java_lang_invoke_MethodHandle_asType = nullptr;
   java_lang_invoke_MethodHandle_invokeExact = nullptr;
   java_lang_invoke_MethodHandles_lookup = nullptr;
   java_lang_invoke_MethodHandles_Lookup_findConstructor = nullptr;
-  java_lang_Long_valueOf = nullptr;
   java_lang_ref_FinalizerReference_add = nullptr;
   java_lang_ref_ReferenceQueue_add = nullptr;
   java_lang_reflect_InvocationTargetException_init = nullptr;
   java_lang_reflect_Parameter_init = nullptr;
   java_lang_reflect_Proxy_init = nullptr;
   java_lang_reflect_Proxy_invoke = nullptr;
-  java_lang_Runtime_nativeLoad = nullptr;
-  java_lang_Short_valueOf = nullptr;
-  java_lang_String_charAt = nullptr;
-  java_lang_Thread_dispatchUncaughtException = nullptr;
-  java_lang_Thread_init = nullptr;
-  java_lang_Thread_run = nullptr;
-  java_lang_ThreadGroup_add = nullptr;
-  java_lang_ThreadGroup_removeThread = nullptr;
   java_nio_Buffer_isDirect = nullptr;
   java_nio_DirectByteBuffer_init = nullptr;
+  jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D = nullptr;
+  jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F = nullptr;
+  jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars = nullptr;
   libcore_reflect_AnnotationFactory_createAnnotation = nullptr;
   libcore_reflect_AnnotationMember_init = nullptr;
   org_apache_harmony_dalvik_ddmc_DdmServer_broadcast = nullptr;
@@ -664,12 +893,12 @@
   java_nio_Buffer_elementSizeShift = nullptr;
   java_nio_Buffer_limit = nullptr;
   java_nio_Buffer_position = nullptr;
-  java_nio_ByteBuffer_address = nullptr;
   java_nio_ByteBuffer_hb = nullptr;
   java_nio_ByteBuffer_isReadOnly = nullptr;
-  java_nio_ByteBuffer_limit = nullptr;
   java_nio_ByteBuffer_offset = nullptr;
   java_util_Collections_EMPTY_LIST = nullptr;
+  jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer = nullptr;
+  jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image = nullptr;
   libcore_util_EmptyArray_STACK_TRACE_ELEMENT = nullptr;
   org_apache_harmony_dalvik_ddmc_Chunk_data = nullptr;
   org_apache_harmony_dalvik_ddmc_Chunk_length = nullptr;
@@ -678,7 +907,8 @@
 }
 
 ObjPtr<mirror::Class> WellKnownClasses::ToClass(jclass global_jclass) {
-  auto ret = ObjPtr<mirror::Class>::DownCast(Thread::Current()->DecodeJObject(global_jclass));
+  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
+  auto ret = ObjPtr<mirror::Class>::DownCast(vm->DecodeGlobal(global_jclass));
   DCHECK(!ret.IsNull());
   return ret;
 }
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index c334f5d..cc6347c 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -20,15 +20,45 @@
 #include "base/locks.h"
 #include "jni.h"
 #include "obj_ptr.h"
+#include "read_barrier_option.h"
 
 namespace art {
 
+class ArtField;
 class ArtMethod;
 
 namespace mirror {
 class Class;
 }  // namespace mirror
 
+namespace detail {
+
+template <typename MemberType, MemberType** kMember>
+struct ClassFromMember {
+  template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
+  static ObjPtr<mirror::Class> Get() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  mirror::Class* operator->() const REQUIRES_SHARED(Locks::mutator_lock_);
+};
+
+template <typename MemberType, MemberType** kMember>
+bool operator==(const ClassFromMember<MemberType, kMember> lhs, ObjPtr<mirror::Class> rhs)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+template <typename MemberType, MemberType** kMember>
+bool operator==(ObjPtr<mirror::Class> lhs, const ClassFromMember<MemberType, kMember> rhs)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+template <typename MemberType, MemberType** kMember>
+bool operator!=(const ClassFromMember<MemberType, kMember> lhs, ObjPtr<mirror::Class> rhs)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+template <typename MemberType, MemberType** kMember>
+bool operator!=(ObjPtr<mirror::Class> lhs, const ClassFromMember<MemberType, kMember> rhs)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+}  // namespace detail
+
 // Various classes used in JNI. We cache them so we don't have to keep looking them up.
 
 struct WellKnownClasses {
@@ -53,144 +83,165 @@
  private:
   static void InitFieldsAndMethodsOnly(JNIEnv* env);
 
+  template <ArtMethod** kMethod>
+  using ClassFromMethod = detail::ClassFromMember<ArtMethod, kMethod>;
+
+  template <ArtField** kField>
+  using ClassFromField = detail::ClassFromMember<ArtField, kField>;
+
  public:
   static jclass dalvik_annotation_optimization_CriticalNative;
   static jclass dalvik_annotation_optimization_FastNative;
   static jclass dalvik_annotation_optimization_NeverCompile;
-  static jclass dalvik_system_BaseDexClassLoader;
-  static jclass dalvik_system_DelegateLastClassLoader;
-  static jclass dalvik_system_DexClassLoader;
-  static jclass dalvik_system_DexFile;
-  static jclass dalvik_system_DexPathList;
-  static jclass dalvik_system_DexPathList__Element;
-  static jclass dalvik_system_EmulatedStackFrame;
-  static jclass dalvik_system_InMemoryDexClassLoader;
-  static jclass dalvik_system_PathClassLoader;
-  static jclass dalvik_system_VMRuntime;
+  static jclass dalvik_annotation_optimization_NeverInline;
   static jclass java_lang_annotation_Annotation__array;
-  static jclass java_lang_BootClassLoader;
-  static jclass java_lang_ClassLoader;
-  static jclass java_lang_ClassNotFoundException;
-  static jclass java_lang_Daemons;
-  static jclass java_lang_Error;
-  static jclass java_lang_IllegalAccessError;
-  static jclass java_lang_NoClassDefFoundError;
-  static jclass java_lang_Object;
-  static jclass java_lang_OutOfMemoryError;
-  static jclass java_lang_reflect_InvocationTargetException;
-  static jclass java_lang_reflect_Parameter;
+  static jclass java_lang_ClassValue;
+  static jclass java_lang_Record;
   static jclass java_lang_reflect_Parameter__array;
-  static jclass java_lang_reflect_Proxy;
-  static jclass java_lang_RuntimeException;
-  static jclass java_lang_StackOverflowError;
-  static jclass java_lang_String;
   static jclass java_lang_StringFactory;
   static jclass java_lang_System;
-  static jclass java_lang_Thread;
-  static jclass java_lang_ThreadGroup;
-  static jclass java_lang_Throwable;
   static jclass java_lang_Void;
-  static jclass java_nio_Buffer;
-  static jclass java_nio_ByteBuffer;
-  static jclass java_nio_DirectByteBuffer;
-  static jclass java_util_Collections;
-  static jclass java_util_function_Consumer;
-  static jclass libcore_reflect_AnnotationFactory;
-  static jclass libcore_reflect_AnnotationMember;
-  static jclass libcore_util_EmptyArray;
-  static jclass org_apache_harmony_dalvik_ddmc_Chunk;
-  static jclass org_apache_harmony_dalvik_ddmc_DdmServer;
+  static jclass libcore_reflect_AnnotationMember__array;
 
-  static jmethodID dalvik_system_BaseDexClassLoader_getLdLibraryPath;
-  static jmethodID dalvik_system_VMRuntime_runFinalization;
-  static jmethodID dalvik_system_VMRuntime_hiddenApiUsed;
-  static jmethodID java_lang_Boolean_valueOf;
-  static jmethodID java_lang_Byte_valueOf;
-  static jmethodID java_lang_Character_valueOf;
-  static jmethodID java_lang_ClassLoader_loadClass;
-  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_doubleToRawLongBits;
-  static jmethodID java_lang_Double_valueOf;
-  static jmethodID java_lang_Float_floatToRawIntBits;
-  static jmethodID java_lang_Float_valueOf;
-  static jmethodID java_lang_Integer_valueOf;
-  static jmethodID java_lang_invoke_MethodHandle_asType;
-  static jmethodID java_lang_invoke_MethodHandle_invokeExact;
-  static jmethodID java_lang_invoke_MethodHandles_lookup;
-  static jmethodID java_lang_invoke_MethodHandles_Lookup_findConstructor;
-  static jmethodID java_lang_Long_valueOf;
-  static jmethodID java_lang_ref_FinalizerReference_add;
-  static jmethodID java_lang_ref_ReferenceQueue_add;
-  static jmethodID java_lang_reflect_InvocationTargetException_init;
-  static jmethodID java_lang_reflect_Parameter_init;
-  static jmethodID java_lang_reflect_Proxy_init;
-  static jmethodID java_lang_reflect_Proxy_invoke;
-  static jmethodID java_lang_Runtime_nativeLoad;
-  static jmethodID java_lang_Short_valueOf;
-  static jmethodID java_lang_String_charAt;
-  static jmethodID java_lang_Thread_dispatchUncaughtException;
-  static jmethodID java_lang_Thread_init;
-  static jmethodID java_lang_Thread_run;
-  static jmethodID java_lang_ThreadGroup_add;
-  static jmethodID java_lang_ThreadGroup_removeThread;
-  static jmethodID java_nio_Buffer_isDirect;
-  static jmethodID java_nio_DirectByteBuffer_init;
-  static jmethodID java_util_function_Consumer_accept;
-  static jmethodID libcore_reflect_AnnotationFactory_createAnnotation;
-  static jmethodID libcore_reflect_AnnotationMember_init;
-  static jmethodID org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
-  static jmethodID org_apache_harmony_dalvik_ddmc_DdmServer_dispatch;
+  static ArtMethod* dalvik_system_BaseDexClassLoader_getLdLibraryPath;
+  static ArtMethod* dalvik_system_DelegateLastClassLoader_init;  // Only for the declaring class.
+  static ArtMethod* dalvik_system_DexClassLoader_init;  // Only for the declaring class.
+  static ArtMethod* dalvik_system_InMemoryDexClassLoader_init;  // Only for the declaring class.
+  static ArtMethod* dalvik_system_PathClassLoader_init;  // Only for the declaring class.
+  static ArtMethod* dalvik_system_VMRuntime_hiddenApiUsed;
+  static ArtMethod* java_lang_Boolean_valueOf;
+  static ArtMethod* java_lang_BootClassLoader_init;  // Only for the declaring class.
+  static ArtMethod* java_lang_Byte_valueOf;
+  static ArtMethod* java_lang_Character_valueOf;
+  static ArtMethod* java_lang_ClassLoader_loadClass;
+  static ArtMethod* java_lang_ClassNotFoundException_init;
+  static ArtMethod* java_lang_Daemons_start;
+  static ArtMethod* java_lang_Daemons_stop;
+  static ArtMethod* java_lang_Daemons_waitForDaemonStart;
+  static ArtMethod* java_lang_Double_doubleToRawLongBits;
+  static ArtMethod* java_lang_Double_valueOf;
+  static ArtMethod* java_lang_Error_init;  // Only for the declaring class.
+  static ArtMethod* java_lang_Float_floatToRawIntBits;
+  static ArtMethod* java_lang_Float_valueOf;
+  static ArtMethod* java_lang_IllegalAccessError_init;  // Only for the declaring class.
+  static ArtMethod* java_lang_Integer_valueOf;
+  static ArtMethod* java_lang_Long_valueOf;
+  static ArtMethod* java_lang_NoClassDefFoundError_init;  // Only for the declaring class.
+  static ArtMethod* java_lang_OutOfMemoryError_init;  // Only for the declaring class.
+  static ArtMethod* java_lang_Runtime_nativeLoad;
+  static ArtMethod* java_lang_RuntimeException_init;  // Only for the declaring class.
+  static ArtMethod* java_lang_Short_valueOf;
+  static ArtMethod* java_lang_StackOverflowError_init;  // Only for the declaring class.
+  static ArtMethod* java_lang_String_charAt;
+  static ArtMethod* java_lang_Thread_dispatchUncaughtException;
+  static ArtMethod* java_lang_Thread_init;
+  static ArtMethod* java_lang_Thread_run;
+  static ArtMethod* java_lang_ThreadGroup_add;
+  static ArtMethod* java_lang_ThreadGroup_threadTerminated;
+  static ArtMethod* java_lang_invoke_MethodHandle_asType;
+  static ArtMethod* java_lang_invoke_MethodHandle_invokeExact;
+  static ArtMethod* java_lang_invoke_MethodHandles_lookup;
+  static ArtMethod* java_lang_invoke_MethodHandles_Lookup_findConstructor;
+  static ArtMethod* java_lang_ref_FinalizerReference_add;
+  static ArtMethod* java_lang_ref_ReferenceQueue_add;
+  static ArtMethod* java_lang_reflect_InvocationTargetException_init;
+  static ArtMethod* java_lang_reflect_Parameter_init;
+  static ArtMethod* java_lang_reflect_Proxy_init;
+  static ArtMethod* java_lang_reflect_Proxy_invoke;
+  static ArtMethod* java_nio_Buffer_isDirect;
+  static ArtMethod* java_nio_DirectByteBuffer_init;
+  static ArtMethod* java_util_function_Consumer_accept;
+  static ArtMethod* jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D;
+  static ArtMethod* jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F;
+  static ArtMethod* jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars;
+  static ArtMethod* libcore_reflect_AnnotationFactory_createAnnotation;
+  static ArtMethod* libcore_reflect_AnnotationMember_init;
+  static ArtMethod* org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
+  static ArtMethod* org_apache_harmony_dalvik_ddmc_DdmServer_dispatch;
 
-  static jfieldID dalvik_system_BaseDexClassLoader_pathList;
-  static jfieldID dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
-  static jfieldID dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter;
-  static jfieldID dalvik_system_DexFile_cookie;
-  static jfieldID dalvik_system_DexFile_fileName;
-  static jfieldID dalvik_system_DexPathList_dexElements;
-  static jfieldID dalvik_system_DexPathList__Element_dexFile;
-  static jfieldID dalvik_system_VMRuntime_nonSdkApiUsageConsumer;
-  static jfieldID java_io_FileDescriptor_descriptor;
-  static jfieldID java_lang_ClassLoader_parent;
-  static jfieldID java_lang_Thread_parkBlocker;
-  static jfieldID java_lang_Thread_daemon;
-  static jfieldID java_lang_Thread_group;
-  static jfieldID java_lang_Thread_lock;
-  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;
-  static jfieldID java_lang_ThreadGroup_mainThreadGroup;
-  static jfieldID java_lang_ThreadGroup_name;
-  static jfieldID java_lang_ThreadGroup_parent;
-  static jfieldID java_lang_ThreadGroup_systemThreadGroup;
-  static jfieldID java_lang_Throwable_cause;
-  static jfieldID java_lang_Throwable_detailMessage;
-  static jfieldID java_lang_Throwable_stackTrace;
-  static jfieldID java_lang_Throwable_stackState;
-  static jfieldID java_lang_Throwable_suppressedExceptions;
-  static jfieldID java_nio_Buffer_address;
-  static jfieldID java_nio_Buffer_capacity;
-  static jfieldID java_nio_Buffer_elementSizeShift;
-  static jfieldID java_nio_Buffer_limit;
-  static jfieldID java_nio_Buffer_position;
-  static jfieldID java_nio_ByteBuffer_address;
-  static jfieldID java_nio_ByteBuffer_hb;
-  static jfieldID java_nio_ByteBuffer_isReadOnly;
-  static jfieldID java_nio_ByteBuffer_limit;
-  static jfieldID java_nio_ByteBuffer_offset;
+  static ArtField* dalvik_system_BaseDexClassLoader_pathList;
+  static ArtField* dalvik_system_BaseDexClassLoader_sharedLibraryLoaders;
+  static ArtField* dalvik_system_BaseDexClassLoader_sharedLibraryLoadersAfter;
+  static ArtField* dalvik_system_DexFile_cookie;
+  static ArtField* dalvik_system_DexFile_fileName;
+  static ArtField* dalvik_system_DexPathList_dexElements;
+  static ArtField* dalvik_system_DexPathList__Element_dexFile;
+  static ArtField* dalvik_system_VMRuntime_nonSdkApiUsageConsumer;
+  static ArtField* java_io_FileDescriptor_descriptor;
+  static ArtField* java_lang_ClassLoader_parent;
+  static ArtField* java_lang_Thread_parkBlocker;
+  static ArtField* java_lang_Thread_daemon;
+  static ArtField* java_lang_Thread_group;
+  static ArtField* java_lang_Thread_lock;
+  static ArtField* java_lang_Thread_name;
+  static ArtField* java_lang_Thread_priority;
+  static ArtField* java_lang_Thread_nativePeer;
+  static ArtField* java_lang_Thread_systemDaemon;
+  static ArtField* java_lang_Thread_unparkedBeforeStart;
+  static ArtField* java_lang_ThreadGroup_groups;
+  static ArtField* java_lang_ThreadGroup_ngroups;
+  static ArtField* java_lang_ThreadGroup_mainThreadGroup;
+  static ArtField* java_lang_ThreadGroup_name;
+  static ArtField* java_lang_ThreadGroup_parent;
+  static ArtField* java_lang_ThreadGroup_systemThreadGroup;
+  static ArtField* java_lang_Throwable_cause;
+  static ArtField* java_lang_Throwable_detailMessage;
+  static ArtField* java_lang_Throwable_stackTrace;
+  static ArtField* java_lang_Throwable_stackState;
+  static ArtField* java_lang_Throwable_suppressedExceptions;
+  static ArtField* java_nio_Buffer_address;
+  static ArtField* java_nio_Buffer_capacity;
+  static ArtField* java_nio_Buffer_elementSizeShift;
+  static ArtField* java_nio_Buffer_limit;
+  static ArtField* java_nio_Buffer_position;
+  static ArtField* java_nio_ByteBuffer_hb;
+  static ArtField* java_nio_ByteBuffer_isReadOnly;
+  static ArtField* java_nio_ByteBuffer_offset;
+  static ArtField* java_util_Collections_EMPTY_LIST;
+  static ArtField* jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer;
+  static ArtField* jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image;
+  static ArtField* libcore_util_EmptyArray_STACK_TRACE_ELEMENT;
+  static ArtField* org_apache_harmony_dalvik_ddmc_Chunk_data;
+  static ArtField* org_apache_harmony_dalvik_ddmc_Chunk_length;
+  static ArtField* org_apache_harmony_dalvik_ddmc_Chunk_offset;
+  static ArtField* org_apache_harmony_dalvik_ddmc_Chunk_type;
 
-  static jfieldID java_util_Collections_EMPTY_LIST;
-  static jfieldID libcore_util_EmptyArray_STACK_TRACE_ELEMENT;
-  static jfieldID org_apache_harmony_dalvik_ddmc_Chunk_data;
-  static jfieldID org_apache_harmony_dalvik_ddmc_Chunk_length;
-  static jfieldID org_apache_harmony_dalvik_ddmc_Chunk_offset;
-  static jfieldID org_apache_harmony_dalvik_ddmc_Chunk_type;
+  static constexpr ClassFromField<&dalvik_system_BaseDexClassLoader_pathList>
+      dalvik_system_BaseDexClassLoader;
+  static constexpr ClassFromMethod<&dalvik_system_DelegateLastClassLoader_init>
+      dalvik_system_DelegateLastClassLoader;
+  static constexpr ClassFromMethod<&dalvik_system_DexClassLoader_init>
+      dalvik_system_DexClassLoader;
+  static constexpr ClassFromField<&dalvik_system_DexFile_cookie> dalvik_system_DexFile;
+  static constexpr ClassFromField<&dalvik_system_DexPathList_dexElements> dalvik_system_DexPathList;
+  static constexpr ClassFromField<&dalvik_system_DexPathList__Element_dexFile>
+      dalvik_system_DexPathList__Element;
+  static constexpr ClassFromMethod<&dalvik_system_InMemoryDexClassLoader_init>
+      dalvik_system_InMemoryDexClassLoader;
+  static constexpr ClassFromMethod<&dalvik_system_PathClassLoader_init>
+      dalvik_system_PathClassLoader;
+  static constexpr ClassFromMethod<&java_lang_BootClassLoader_init> java_lang_BootClassLoader;
+  static constexpr ClassFromField<&java_lang_ClassLoader_parent> java_lang_ClassLoader;
+  static constexpr ClassFromMethod<&java_lang_Daemons_start> java_lang_Daemons;
+  static constexpr ClassFromMethod<&java_lang_Error_init> java_lang_Error;
+  static constexpr ClassFromMethod<&java_lang_IllegalAccessError_init>
+      java_lang_IllegalAccessError;
+  static constexpr ClassFromMethod<&java_lang_NoClassDefFoundError_init>
+      java_lang_NoClassDefFoundError;
+  static constexpr ClassFromMethod<&java_lang_OutOfMemoryError_init> java_lang_OutOfMemoryError;
+  static constexpr ClassFromMethod<&java_lang_RuntimeException_init> java_lang_RuntimeException;
+  static constexpr ClassFromMethod<&java_lang_StackOverflowError_init>
+      java_lang_StackOverflowError;
+  static constexpr ClassFromField<&java_lang_Thread_daemon> java_lang_Thread;
+  static constexpr ClassFromField<&java_lang_ThreadGroup_groups> java_lang_ThreadGroup;
+  static constexpr ClassFromMethod<&java_lang_reflect_InvocationTargetException_init>
+      java_lang_reflect_InvocationTargetException;
+  static constexpr ClassFromMethod<&java_lang_reflect_Parameter_init>
+      java_lang_reflect_Parameter;
+  static constexpr ClassFromField<&java_nio_Buffer_address> java_nio_Buffer;
+  static constexpr ClassFromField<&java_util_Collections_EMPTY_LIST> java_util_Collections;
+  static constexpr ClassFromField<&libcore_util_EmptyArray_STACK_TRACE_ELEMENT>
+      libcore_util_EmptyArray;
 };
 
 }  // namespace art
diff --git a/sigchainlib/Android.bp b/sigchainlib/Android.bp
index ee874dc..68dec35 100644
--- a/sigchainlib/Android.bp
+++ b/sigchainlib/Android.bp
@@ -62,6 +62,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
     stubs: {
         symbol_file: "libsigchain.map.txt",
diff --git a/sigchainlib/libsigchain.map.txt b/sigchainlib/libsigchain.map.txt
index 02c20c7..377662a 100644
--- a/sigchainlib/libsigchain.map.txt
+++ b/sigchainlib/libsigchain.map.txt
@@ -19,7 +19,7 @@
     # Export no symbols - the only external entry points are libc overrides.
     # Since this section cannot be empty for APEX stubs generation we provide a
     # phony entry.
-    LibsigchainNonexistentFunction;
+    LibsigchainNonexistentFunction; # apex
   local:
     *;
 };
diff --git a/sigchainlib/sigchain.cc b/sigchainlib/sigchain.cc
index 5bad856..a7f73f7 100644
--- a/sigchainlib/sigchain.cc
+++ b/sigchainlib/sigchain.cc
@@ -27,6 +27,7 @@
 #endif
 
 #include <algorithm>
+#include <atomic>
 #include <initializer_list>
 #include <mutex>
 #include <type_traits>
@@ -151,38 +152,92 @@
   });
 }
 
-static pthread_key_t GetHandlingSignalKey() {
-  static pthread_key_t key;
+template <typename T>
+static constexpr bool IsPowerOfTwo(T x) {
+  static_assert(std::is_integral_v<T>, "T must be integral");
+  static_assert(std::is_unsigned_v<T>, "T must be unsigned");
+  return (x & (x - 1)) == 0;
+}
+
+template <typename T>
+static constexpr T RoundUp(T x, T n) {
+  return (x + n - 1) & -n;
+}
+// Use a bitmap to indicate which signal is being handled so that other
+// non-blocked signals are allowed to be handled, if raised.
+static constexpr size_t kSignalSetLength = _NSIG - 1;
+static constexpr size_t kNumSignalsPerKey = std::numeric_limits<uintptr_t>::digits;
+static_assert(IsPowerOfTwo(kNumSignalsPerKey));
+static constexpr size_t kHandlingSignalKeyCount =
+    RoundUp(kSignalSetLength, kNumSignalsPerKey) / kNumSignalsPerKey;
+
+// We rely on bionic's implementation of pthread_(get/set)specific being
+// async-signal safe.
+static pthread_key_t GetHandlingSignalKey(size_t idx) {
+  static pthread_key_t key[kHandlingSignalKeyCount];
   static std::once_flag once;
   std::call_once(once, []() {
-    int rc = pthread_key_create(&key, nullptr);
-    if (rc != 0) {
-      fatal("failed to create sigchain pthread key: %s", strerror(rc));
+    for (size_t i = 0; i < kHandlingSignalKeyCount; i++) {
+      int rc = pthread_key_create(&key[i], nullptr);
+      if (rc != 0) {
+        fatal("failed to create sigchain pthread key: %s", strerror(rc));
+      }
     }
   });
-  return key;
+  return key[idx];
 }
 
 static bool GetHandlingSignal() {
-  void* result = pthread_getspecific(GetHandlingSignalKey());
-  return reinterpret_cast<uintptr_t>(result);
+  for (size_t i = 0; i < kHandlingSignalKeyCount; i++) {
+    void* result = pthread_getspecific(GetHandlingSignalKey(i));
+    if (reinterpret_cast<uintptr_t>(result) != 0) {
+      return true;
+    }
+  }
+  return false;
 }
 
-static void SetHandlingSignal(bool value) {
-  pthread_setspecific(GetHandlingSignalKey(),
-                      reinterpret_cast<void*>(static_cast<uintptr_t>(value)));
+static bool GetHandlingSignal(int signo) {
+  size_t bit_idx = signo - 1;
+  size_t key_idx = bit_idx / kNumSignalsPerKey;
+  uintptr_t bit_mask = static_cast<uintptr_t>(1) << (bit_idx % kNumSignalsPerKey);
+  uintptr_t result =
+      reinterpret_cast<uintptr_t>(pthread_getspecific(GetHandlingSignalKey(key_idx)));
+  return result & bit_mask;
+}
+
+static bool SetHandlingSignal(int signo, bool value) {
+  // Use signal-fence to ensure that compiler doesn't reorder generated code
+  // across signal handlers.
+  size_t bit_idx = signo - 1;
+  size_t key_idx = bit_idx / kNumSignalsPerKey;
+  uintptr_t bit_mask = static_cast<uintptr_t>(1) << (bit_idx % kNumSignalsPerKey);
+  pthread_key_t key = GetHandlingSignalKey(key_idx);
+  std::atomic_signal_fence(std::memory_order_seq_cst);
+  uintptr_t bitmap = reinterpret_cast<uintptr_t>(pthread_getspecific(key));
+  bool ret = bitmap & bit_mask;
+  if (value) {
+    bitmap |= bit_mask;
+  } else {
+    bitmap &= ~bit_mask;
+  }
+  pthread_setspecific(key, reinterpret_cast<void*>(bitmap));
+  std::atomic_signal_fence(std::memory_order_seq_cst);
+  return ret;
 }
 
 class ScopedHandlingSignal {
  public:
-  ScopedHandlingSignal() : original_value_(GetHandlingSignal()) {
-  }
+  ScopedHandlingSignal(int signo, bool set)
+      : signo_(signo),
+        original_value_(set ? SetHandlingSignal(signo, true) : GetHandlingSignal(signo)) {}
 
   ~ScopedHandlingSignal() {
-    SetHandlingSignal(original_value_);
+    SetHandlingSignal(signo_, original_value_);
   }
 
  private:
+  int signo_;
   bool original_value_;
 };
 
@@ -243,9 +298,11 @@
 #if !defined(__BIONIC__)
 #define SA_RESTORER 0x04000000
 #endif
-    kernel_supported_flags_ = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_SIGINFO |
-                              SA_ONSTACK | SA_RESTART | SA_NODEFER |
-                              SA_RESETHAND | SA_RESTORER;
+    kernel_supported_flags_ = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_SIGINFO | SA_ONSTACK | SA_RESTART |
+                              SA_NODEFER | SA_RESETHAND;
+#if defined(SA_RESTORER)
+    kernel_supported_flags_ |= SA_RESTORER;
+#endif
 
     // Determine whether the kernel supports SA_EXPOSE_TAGBITS. For newer
     // kernels we use the flag support detection protocol described above. In
@@ -336,14 +393,20 @@
 
 // _NSIG is 1 greater than the highest valued signal, but signals start from 1.
 // Leave an empty element at index 0 for convenience.
-static SignalChain chains[_NSIG + 1];
+static SignalChain chains[_NSIG];
 
 static bool is_signal_hook_debuggable = false;
 
+// Weak linkage, as the ART APEX might be deployed on devices where this symbol doesn't exist (i.e.
+// all OS's before Android U). This symbol comes from libdl.
+__attribute__((weak)) extern "C" bool android_handle_signal(int signal_number,
+                                                            siginfo_t* info,
+                                                            void* context);
+
 void SignalChain::Handler(int signo, siginfo_t* siginfo, void* ucontext_raw) {
   // Try the special handlers first.
   // If one of them crashes, we'll reenter this handler and pass that crash onto the user handler.
-  if (!GetHandlingSignal()) {
+  if (!GetHandlingSignal(signo)) {
     for (const auto& handler : chains[signo].special_handlers_) {
       if (handler.sc_sigaction == nullptr) {
         break;
@@ -356,10 +419,7 @@
       sigset_t previous_mask;
       linked_sigprocmask(SIG_SETMASK, &handler.sc_mask, &previous_mask);
 
-      ScopedHandlingSignal restorer;
-      if (!handler_noreturn) {
-        SetHandlingSignal(true);
-      }
+      ScopedHandlingSignal restorer(signo, !handler_noreturn);
 
       if (handler.sc_sigaction(signo, siginfo, ucontext_raw)) {
         return;
@@ -369,6 +429,25 @@
     }
   }
 
+  // In Android 14, there's a special feature called "recoverable" GWP-ASan. GWP-ASan is a tool that
+  // finds heap-buffer-overflow and heap-use-after-free on native heap allocations (e.g. malloc()
+  // inside of JNI, not the ART heap). The way it catches buffer overflow (roughly) is by rounding
+  // up the malloc() so that it's page-sized, and mapping an inaccessible page on the left- and
+  // right-hand side. It catches use-after-free by mprotecting the allocation page to be PROT_NONE
+  // on free(). The new "recoverable" mode is designed to allow debuggerd to print a crash report,
+  // but for the app or process in question to not crash (i.e. recover) and continue even after the
+  // bug is detected. Sigchain thus must allow debuggerd to handle the signal first, and if
+  // debuggerd has promised that it can recover, and it's done the steps to allow recovery (as
+  // identified by android_handle_signal returning true), then we should return from this handler
+  // and let the app continue.
+  //
+  // For all non-GWP-ASan-recoverable crashes, or crashes where recovery is not possible,
+  // android_handle_signal returns false, and we will continue to the rest of the sigchain handler
+  // logic.
+  if (android_handle_signal != nullptr && android_handle_signal(signo, siginfo, ucontext_raw)) {
+    return;
+  }
+
   // Forward to the user's signal handler.
   int handler_flags = chains[signo].action_.sa_flags;
   ucontext_t* ucontext = static_cast<ucontext_t*>(ucontext_raw);
@@ -407,7 +486,15 @@
     if (handler == SIG_IGN) {
       return;
     } else if (handler == SIG_DFL) {
-      fatal("exiting due to SIG_DFL handler for signal %d, ucontext %p", signo, ucontext);
+      // We'll only get here if debuggerd is disabled. In that case, whatever next tries to handle
+      // the crash will have no way to know our ucontext, and thus no way to dump the original crash
+      // stack (since we're on an alternate stack.) Let's remove our handler and return. Then the
+      // pre-crash state is restored, the crash happens again, and the next handler gets a chance.
+      log("reverting to SIG_DFL handler for signal %d, ucontext %p", signo, ucontext);
+      struct sigaction dfl = {};
+      dfl.sa_handler = SIG_DFL;
+      linked_sigaction(signo, &dfl, nullptr);
+      return;
     } else {
       handler(signo);
     }
diff --git a/sigchainlib/sigchain_test.cc b/sigchainlib/sigchain_test.cc
index d879f5a..5e9c7fe 100644
--- a/sigchainlib/sigchain_test.cc
+++ b/sigchainlib/sigchain_test.cc
@@ -37,6 +37,12 @@
 
 #include "sigchain.h"
 
+#if defined(__clang__) && __has_feature(hwaddress_sanitizer)
+#define DISABLE_HWASAN __attribute__((no_sanitize("hwaddress")))
+#else
+#define DISABLE_HWASAN
+#endif
+
 #if !defined(__BIONIC__)
 using sigset64_t = sigset_t;
 
@@ -75,13 +81,17 @@
   void RaiseHandled() {
       sigval value;
       value.sival_ptr = &value;
-      pthread_sigqueue(pthread_self(), SIGSEGV, value);
+      // pthread_sigqueue would guarantee the signal is delivered to this
+      // thread, but it is a nonstandard extension and does not exist in
+      // musl.  Gtest is single threaded, and these tests don't create any
+      // threads, so sigqueue can be used and will deliver to this thread.
+      sigqueue(getpid(), SIGSEGV, value);
   }
 
   void RaiseUnhandled() {
       sigval value;
       value.sival_ptr = nullptr;
-      pthread_sigqueue(pthread_self(), SIGSEGV, value);
+      sigqueue(getpid(), SIGSEGV, value);
   }
 };
 
@@ -245,9 +255,10 @@
   called = 0;
 }
 
-TEST_F(SigchainTest, fault_address_tag) {
-#define SA_EXPOSE_TAGBITS 0x00000800
 #if defined(__aarch64__)
+// The test intentionally dereferences (tagged) null to trigger SIGSEGV.
+// We need to disable HWASAN since it would catch the dereference first.
+DISABLE_HWASAN void fault_address_tag_impl() {
   struct sigaction action = {};
   action.sa_flags = SA_SIGINFO;
   action.sa_sigaction = [](int, siginfo_t* siginfo, void*) {
@@ -269,6 +280,13 @@
     EXPECT_EXIT({ volatile int load __attribute__((unused)) = *tagged_null; },
                 testing::ExitedWithCode(0x2b), "");
   }
+}
+#endif
+
+TEST_F(SigchainTest, fault_address_tag) {
+#define SA_EXPOSE_TAGBITS 0x00000800
+#if defined(__aarch64__)
+  fault_address_tag_impl();
 #else
   GTEST_SKIP() << "arm64 only";
 #endif
diff --git a/test/000-nop/build b/test/000-nop/build
deleted file mode 100644
index 5233a2d..0000000
--- a/test/000-nop/build
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-# Nothing to do here.
diff --git a/test/000-nop/build.py b/test/000-nop/build.py
new file mode 100644
index 0000000..54f21cc
--- /dev/null
+++ b/test/000-nop/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  pass  # Nothing to do here.
diff --git a/test/000-nop/run b/test/000-nop/run
deleted file mode 100644
index 210296b..0000000
--- a/test/000-nop/run
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-echo "Blort."
diff --git a/test/000-nop/run.py b/test/000-nop/run.py
new file mode 100644
index 0000000..9239ae8
--- /dev/null
+++ b/test/000-nop/run.py
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+
+def run(ctx, args):
+  ctx.echo("Blort.")
diff --git a/test/003-omnibus-opcodes/build b/test/003-omnibus-opcodes/build
deleted file mode 100644
index a14ddc9..0000000
--- a/test/003-omnibus-opcodes/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2008 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/003-omnibus-opcodes/javac_post.sh b/test/003-omnibus-opcodes/javac_post.sh
new file mode 100755
index 0000000..6065423
--- /dev/null
+++ b/test/003-omnibus-opcodes/javac_post.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e # Stop on error - the caller script may not have this set.
+
+rm -f classes/UnresClass.class
diff --git a/test/003-omnibus-opcodes/javac_wrapper.sh b/test/003-omnibus-opcodes/javac_wrapper.sh
deleted file mode 100755
index 62e94f8..0000000
--- a/test/003-omnibus-opcodes/javac_wrapper.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e # Stop on error - the caller script may not have this set.
-
-$JAVAC "$@"
-rm -f classes/UnresClass.class
diff --git a/test/003-omnibus-opcodes/src/Goto.java b/test/003-omnibus-opcodes/src/Goto.java
index d56ceae..35b4e66 100644
--- a/test/003-omnibus-opcodes/src/Goto.java
+++ b/test/003-omnibus-opcodes/src/Goto.java
@@ -44,6 +44,7 @@
         if (which) {
             i += filler(i);
         } else {
+            // clang-format off
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
@@ -54,6 +55,7 @@
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
+            // clang-format on
         }
 
         return i;
@@ -67,6 +69,7 @@
         if (which) {
             i += filler(i);
         } else {
+            // clang-format off
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
@@ -2392,6 +2395,7 @@
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
             i -= filler(i);  i -= filler(i);  i -= filler(i);  i -= filler(i);
+            // clang-format on
         }
 
         return i;
diff --git a/test/004-JniTest/build b/test/004-JniTest/build
deleted file mode 100755
index 460f2db..0000000
--- a/test/004-JniTest/build
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-#
-# Perform a mostly normal build.
-# Since this test imports 'dalvik.annotation.optimization.FastNative' (and CriticalNative),
-# we put them to src-aotex/ to allow the annotations to be used at javac-compile time,
-# but remove compiled *-aotex* artifacts afterwards.
-#
-# This enables the test to compile with vanilla RI javac and work on either ART or RI.
-# TODO: The test is currently disabled for RI anyway, presumably because @CriticalNative
-# has a different ABI and cannot be tested on RI.
-#
-
-# Stop on failure.
-set -e
-
-# Use release mode to check optimizations do not break JNI.
-export D8_FLAGS=--release
-./default-build "$@"
-
-# Remove the *-aotex build artifacts (but keep src-aotex) with dalvik.* annotations.
-rm -rf classes-aotex classes-aotex.jar $TEST_NAME-aotex.jar
diff --git a/test/004-JniTest/build.py b/test/004-JniTest/build.py
new file mode 100644
index 0000000..b701d5a
--- /dev/null
+++ b/test/004-JniTest/build.py
@@ -0,0 +1,38 @@
+#
+# Copyright (C) 2022 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.
+
+import shutil, os
+
+#
+# Perform a mostly normal build.
+# Since this test imports 'dalvik.annotation.optimization.FastNative' (and CriticalNative),
+# we put them to src-aotex/ to allow the annotations to be used at javac-compile time,
+# but remove compiled *-aotex* artifacts afterwards.
+#
+# This enables the test to compile with vanilla RI javac and work on either ART or RI.
+# TODO: The test is currently disabled for RI anyway, presumably because @CriticalNative
+# has a different ABI and cannot be tested on RI.
+#
+
+
+# Use release mode to check optimizations do not break JNI.
+def build(ctx):
+  ctx.default_build(d8_flags=["--release"])
+
+  # Remove the *-aotex build artifacts (but keep src-aotex) with dalvik.* annotations.
+  shutil.rmtree(ctx.test_dir / "classes-aotex")
+  if not ctx.jvm:
+    os.remove(ctx.test_dir / "classes-aotex.jar")
+    os.remove(ctx.test_dir / "004-JniTest-aotex.jar")
diff --git a/test/004-ReferenceMap/build b/test/004-ReferenceMap/build
deleted file mode 100644
index 6203c97..0000000
--- a/test/004-ReferenceMap/build
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 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.
-
-# Stop if something fails.
-set -e
-
-# Do not invoke D8 for this test.
-export D8=':'
-
-######################################################################
-
-${SOONG_ZIP} --jar -o classes.jar -f classes.dex
-./default-build "$@"
diff --git a/test/004-ReferenceMap/build.py b/test/004-ReferenceMap/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/004-ReferenceMap/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/004-ReferenceMap/generate-sources b/test/004-ReferenceMap/generate-sources
new file mode 100755
index 0000000..9edc200
--- /dev/null
+++ b/test/004-ReferenceMap/generate-sources
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright (C) 2015 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.
+
+# Stop if something fails.
+set -e
+
+# Do not invoke D8 for this test.
+export D8=':'
+
+######################################################################
+
+${SOONG_ZIP} --jar -o classes.jar -f classes.dex
diff --git a/test/004-ReferenceMap/javac_wrapper.sh b/test/004-ReferenceMap/javac_post.sh
similarity index 100%
rename from test/004-ReferenceMap/javac_wrapper.sh
rename to test/004-ReferenceMap/javac_post.sh
diff --git a/test/004-ReferenceMap/stack_walk_refmap_jni.cc b/test/004-ReferenceMap/stack_walk_refmap_jni.cc
index 0659c0b..c84bd88 100644
--- a/test/004-ReferenceMap/stack_walk_refmap_jni.cc
+++ b/test/004-ReferenceMap/stack_walk_refmap_jni.cc
@@ -25,9 +25,8 @@
   int t_size = sizeof(t) / sizeof(*t);                                                        \
   const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();               \
   uintptr_t native_quick_pc = method_header->ToNativeQuickPc(GetMethod(),                     \
-                                                 dex_pc,                                      \
-                                                 /* is_catch_handler */ false,                \
-                                                 abort_if_not_found);                         \
+                                                             dex_pc,                          \
+                                                             abort_if_not_found);             \
   if (native_quick_pc != UINTPTR_MAX) {                                                       \
     CheckReferences(t,                                                                        \
                     t_size,                                                                   \
diff --git a/test/004-SignalTest/signaltest.cc b/test/004-SignalTest/signaltest.cc
index 6f97b2a..daa31d3 100644
--- a/test/004-SignalTest/signaltest.cc
+++ b/test/004-SignalTest/signaltest.cc
@@ -77,18 +77,18 @@
   sigprocmask(SIG_UNBLOCK, &mask, nullptr);
 
 #if defined(__arm__)
-  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
-  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
-  sc->arm_pc += 2;          // Skip instruction causing segv.
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  mc->arm_pc += 2;          // Skip instruction causing segv.
 #elif defined(__aarch64__)
-  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
-  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
-  sc->pc += 4;          // Skip instruction causing segv.
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+  mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+  mc->pc += 4;          // Skip instruction causing segv.
 #elif defined(__i386__)
-  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
   uc->CTX_EIP += 3;
 #elif defined(__x86_64__)
-  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
+  ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
   uc->CTX_EIP += 2;
 #else
   UNUSED(context);
diff --git a/test/004-StackWalk/build b/test/004-StackWalk/build
deleted file mode 100644
index 6203c97..0000000
--- a/test/004-StackWalk/build
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 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.
-
-# Stop if something fails.
-set -e
-
-# Do not invoke D8 for this test.
-export D8=':'
-
-######################################################################
-
-${SOONG_ZIP} --jar -o classes.jar -f classes.dex
-./default-build "$@"
diff --git a/test/004-StackWalk/build.py b/test/004-StackWalk/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/004-StackWalk/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/004-StackWalk/generate-sources b/test/004-StackWalk/generate-sources
new file mode 100755
index 0000000..9edc200
--- /dev/null
+++ b/test/004-StackWalk/generate-sources
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright (C) 2015 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.
+
+# Stop if something fails.
+set -e
+
+# Do not invoke D8 for this test.
+export D8=':'
+
+######################################################################
+
+${SOONG_ZIP} --jar -o classes.jar -f classes.dex
diff --git a/test/004-StackWalk/javac_wrapper.sh b/test/004-StackWalk/javac_post.sh
similarity index 100%
rename from test/004-StackWalk/javac_wrapper.sh
rename to test/004-StackWalk/javac_post.sh
diff --git a/test/004-ThreadStress/check b/test/004-ThreadStress/check
deleted file mode 100755
index f389e6b..0000000
--- a/test/004-ThreadStress/check
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Do not compare numbers, so replace numbers with 'N'.
-# Remove all messages relating to failing to allocate a java-peer for the
-# shutdown thread. This can occasionally happen with this test but it is not
-# something we really need to worry about here.
-sed '-es/[0-9][0-9]*/N/g' "$2" \
-    | diff --strip-trailing-cr -q "$1" - >/dev/null \
-  && grep -v "Exception creating thread peer:" "$4" \
-    | diff --strip-trailing-cr -q "$3" - >/dev/null
diff --git a/test/004-ThreadStress/run b/test/004-ThreadStress/run
deleted file mode 100755
index 8004036..0000000
--- a/test/004-ThreadStress/run
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Enable lock contention logging.
-${RUN} --runtime-option -Xlockprofthreshold:10 "${@}"
-return_status1=$?
-
-# Run locks-only mode with stack-dump lock profiling. Reduce the number of total operations from
-# the default 1000 to 100.
-${RUN} --runtime-option -Xlockprofthreshold:10 --runtime-option -Xstackdumplockprofthreshold:20 \
-  "${@}" Main --locks-only -o 100
-return_status2=$?
-
-# Make sure we don't silently ignore an early failure.
-(exit $return_status1) && (exit $return_status2)
diff --git a/test/004-ThreadStress/run.py b/test/004-ThreadStress/run.py
new file mode 100644
index 0000000..7cc4d79
--- /dev/null
+++ b/test/004-ThreadStress/run.py
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# 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.
+
+import sys
+
+
+def run(ctx, args):
+  # Enable lock contention logging.
+  ctx.default_run(args, runtime_option=["-Xlockprofthreshold:10"])
+
+  # Run locks-only mode with stack-dump lock profiling. Reduce the number of total operations from
+  # the default 1000 to 100.
+  ctx.default_run(
+      args,
+      test_args=["--locks-only -o 100"],
+      runtime_option=[
+          "-Xlockprofthreshold:10", "-Xstackdumplockprofthreshold:20"
+      ])
+
+  # Do not compare numbers, so replace numbers with 'N'.
+  ctx.run(fr"sed -i 's/[0-9][0-9]*/N/g' '{args.stdout_file}'")
+
+  # Remove all messages relating to failing to allocate a java-peer for the
+  # shutdown thread. This can occasionally happen with this test but it is not
+  # something we really need to worry about here.
+  ctx.run(fr"sed -i '/Exception creating thread peer:/d' '{args.stderr_file}'")
diff --git a/test/004-UnsafeTest/build.py b/test/004-UnsafeTest/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/004-UnsafeTest/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/004-UnsafeTest/test-metadata.json b/test/004-UnsafeTest/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/004-UnsafeTest/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/005-annotations/build b/test/005-annotations/build
deleted file mode 100644
index bdc1950..0000000
--- a/test/005-annotations/build
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2012 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.
-
-# Stop if something fails.
-set -e
-
-# Build intermediate object to preserve class-retention annotations.
-export D8_FLAGS=--intermediate
-./default-build "$@"
diff --git a/test/005-annotations/build.py b/test/005-annotations/build.py
new file mode 100644
index 0000000..a3aefce
--- /dev/null
+++ b/test/005-annotations/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+# Build intermediate object to preserve class-retention annotations.
+def build(ctx):
+  ctx.default_build(d8_flags=['--intermediate'])
diff --git a/test/005-annotations/javac_post.sh b/test/005-annotations/javac_post.sh
new file mode 100755
index 0000000..abec8b0
--- /dev/null
+++ b/test/005-annotations/javac_post.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e # Stop on error - the caller script may not have this set.
+
+# Classes available at compile time, but not at runtime.
+rm -f classes/android/test/anno/MissingAnnotation.class
+rm -f 'classes/android/test/anno/ClassWithInnerAnnotationClass$MissingInnerAnnotationClass.class'
+
+# overwrite RenamedEnum in classes
+if [ -f classes2/android/test/anno/RenamedEnumClass.java ] ; then
+  mv classes2/android/test/anno/RenamedEnumClass.java classes/android/test/anno/RenamedEnumClass.java
+fi
diff --git a/test/005-annotations/javac_wrapper.sh b/test/005-annotations/javac_wrapper.sh
deleted file mode 100755
index 69e6161..0000000
--- a/test/005-annotations/javac_wrapper.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e # Stop on error - the caller script may not have this set.
-
-$JAVAC "$@"
-
-# Classes available at compile time, but not at runtime.
-rm -f classes/android/test/anno/MissingAnnotation.class
-rm -f 'classes/android/test/anno/ClassWithInnerAnnotationClass$MissingInnerAnnotationClass.class'
-
-# overwrite RenamedEnum in classes
-if [ -f classes2/android/test/anno/RenamedEnumClass.java ] ; then
-  mv classes2/android/test/anno/RenamedEnumClass.java classes/android/test/anno/RenamedEnumClass.java
-fi
diff --git a/test/011-array-copy/expected-stdout.txt b/test/011-array-copy/expected-stdout.txt
index 724786e..9c53fb7 100644
--- a/test/011-array-copy/expected-stdout.txt
+++ b/test/011-array-copy/expected-stdout.txt
@@ -13,3 +13,28 @@
 copy: 0,3,5: [0, 1, 2, 0, 1, 2, 3, 4]
 copy: 3,0,5: [3, 4, 5, 6, 7, 5, 6, 7]
 copy: 0,5,1: [0, 1, 2, 3, 4, 0, 6, 7]
+arraycopy(char) const case 2 passed
+arraycopy(char) const case 3 passed
+arraycopy(char) const case 5 passed
+arraycopy(char) const case 7 passed
+arraycopy(char) const case 8 passed
+arraycopy(char) const case 9 passed
+arraycopy(char) const case 11 passed
+arraycopy(char) 0 passed
+arraycopy(char) 1 passed
+arraycopy(char) 3 passed
+arraycopy(char) 4 passed
+arraycopy(char) 5 passed
+arraycopy(char) 7 passed
+arraycopy(char) 15 passed
+arraycopy(char) 16 passed
+arraycopy(char) 17 passed
+arraycopy(char) 31 passed
+arraycopy(char) 32 passed
+arraycopy(char) 33 passed
+arraycopy(char) 63 passed
+arraycopy(char) 64 passed
+arraycopy(char) 65 passed
+arraycopy(char) 255 passed
+arraycopy(char) 513 passed
+arraycopy(char) 1025 passed
diff --git a/test/011-array-copy/src/Main.java b/test/011-array-copy/src/Main.java
index d9b61e7..d4d1f67 100644
--- a/test/011-array-copy/src/Main.java
+++ b/test/011-array-copy/src/Main.java
@@ -24,6 +24,7 @@
         testObjectCopy();
         testOverlappingMoves();
         testFloatAndDouble();
+        testArrayCopyChar();
     }
 
     public static void testObjectCopy() {
@@ -165,4 +166,190 @@
         System.arraycopy(new float[len], 0, new float[len], 0, len);
         System.arraycopy(new double[len], 0, new double[len], 0, len);
     }
+
+    static final char SRC_INIT_CHAR = '1';
+    static final char DST_CHAR = '0';
+
+    /* Return a char array of the specified length.
+     * If do_increment is true, populate the array with (numerically) ascending
+     * characters starting from initChar (note: char wraps-around on overflow).
+     * If do_increment is false, populate all array elements with initChar.
+     */
+    public static char[] createCharArray(int length, char initChar, boolean do_increment) {
+        char[] charArr = new char[length];
+        char nextChar = initChar;
+
+        for (int i = 0; i < length; ++i) {
+            charArr[i] = nextChar;
+            if (do_increment) {
+                nextChar++;
+            }
+        }
+        return charArr;
+    }
+
+    public static boolean verifyCorrectness(char[] src, char[] dst, int copiedPrefixLength) {
+        for (int i = 0; i < dst.length; ++i) {
+            if (i < copiedPrefixLength) {
+                // Check that we copied source array.
+                if (dst[i] != src[i]) {
+                    return false;
+                }
+            } else {
+                // Check that we didn't write more chars than necessary.
+                if (dst[i] != DST_CHAR) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public static void testArrayCopyCharConstCase2() {
+        final int copy_length = 2;
+        char[] src = createCharArray(2 * copy_length, SRC_INIT_CHAR, true);
+        char[] dst = createCharArray(4 * copy_length, DST_CHAR, false);
+
+        System.arraycopy(src, 0, dst, 0, copy_length);
+
+        boolean passed = verifyCorrectness(src, dst, copy_length);
+        if (!passed) {
+            System.out.println("arraycopy(char) const case 2 failed");
+        } else {
+            System.out.println("arraycopy(char) const case 2 passed");
+        }
+    }
+
+    public static void testArrayCopyCharConstCase3() {
+        final int copy_length = 3;
+        char[] src = createCharArray(2 * copy_length, SRC_INIT_CHAR, true);
+        char[] dst = createCharArray(4 * copy_length, DST_CHAR, false);
+
+        System.arraycopy(src, 0, dst, 0, copy_length);
+
+        boolean passed = verifyCorrectness(src, dst, copy_length);
+        if (!passed) {
+            System.out.println("arraycopy(char) const case 3 failed");
+        } else {
+            System.out.println("arraycopy(char) const case 3 passed");
+        }
+    }
+
+    public static void testArrayCopyCharConstCase5() {
+        final int copy_length = 5;
+        char[] src = createCharArray(2 * copy_length, SRC_INIT_CHAR, true);
+        char[] dst = createCharArray(4 * copy_length, DST_CHAR, false);
+
+        System.arraycopy(src, 0, dst, 0, copy_length);
+
+        boolean passed = verifyCorrectness(src, dst, copy_length);
+        if (!passed) {
+            System.out.println("arraycopy(char) const case 5 failed");
+        } else {
+            System.out.println("arraycopy(char) const case 5 passed");
+        }
+    }
+
+    public static void testArrayCopyCharConstCase7() {
+        final int copy_length = 7;
+        char[] src = createCharArray(2 * copy_length, SRC_INIT_CHAR, true);
+        char[] dst = createCharArray(4 * copy_length, DST_CHAR, false);
+
+        System.arraycopy(src, 0, dst, 0, copy_length);
+
+        boolean passed = verifyCorrectness(src, dst, copy_length);
+        if (!passed) {
+            System.out.println("arraycopy(char) const case 7 failed");
+        } else {
+            System.out.println("arraycopy(char) const case 7 passed");
+        }
+    }
+
+    public static void testArrayCopyCharConstCase8() {
+        final int copy_length = 8;
+        char[] src = createCharArray(2 * copy_length, SRC_INIT_CHAR, true);
+        char[] dst = createCharArray(4 * copy_length, DST_CHAR, false);
+
+        System.arraycopy(src, 0, dst, 0, copy_length);
+
+        boolean passed = verifyCorrectness(src, dst, copy_length);
+        if (!passed) {
+            System.out.println("arraycopy(char) const case 8 failed");
+        } else {
+            System.out.println("arraycopy(char) const case 8 passed");
+        }
+    }
+
+    public static void testArrayCopyCharConstCase9() {
+        final int copy_length = 9;
+        char[] src = createCharArray(2 * copy_length, SRC_INIT_CHAR, true);
+        char[] dst = createCharArray(4 * copy_length, DST_CHAR, false);
+
+        System.arraycopy(src, 0, dst, 0, copy_length);
+
+        boolean passed = verifyCorrectness(src, dst, copy_length);
+        if (!passed) {
+            System.out.println("arraycopy(char) const case 9 failed");
+        } else {
+            System.out.println("arraycopy(char) const case 9 passed");
+        }
+    }
+
+    public static void testArrayCopyCharConstCase11() {
+        final int copy_length = 11;
+        char[] src = createCharArray(2 * copy_length, SRC_INIT_CHAR, true);
+        char[] dst = createCharArray(4 * copy_length, DST_CHAR, false);
+
+        System.arraycopy(src, 0, dst, 0, copy_length);
+
+        boolean passed = verifyCorrectness(src, dst, copy_length);
+        if (!passed) {
+            System.out.println("arraycopy(char) const case 11 failed");
+        } else {
+            System.out.println("arraycopy(char) const case 11 passed");
+        }
+    }
+
+    public static void testArrayCopyCharCase(int copy_length) {
+        char[] src = createCharArray(2 * copy_length, SRC_INIT_CHAR, true);
+        char[] dst = createCharArray(4 * copy_length, DST_CHAR, false);
+
+        System.arraycopy(src, 0, dst, 0, copy_length);
+
+        boolean passed = verifyCorrectness(src, dst, copy_length);
+        if (!passed) {
+            System.out.println("arraycopy(char) " + copy_length + " failed");
+        } else {
+            System.out.println("arraycopy(char) " + copy_length + " passed");
+        }
+    }
+
+    public static void testArrayCopyChar() {
+        testArrayCopyCharConstCase2();
+        testArrayCopyCharConstCase3();
+        testArrayCopyCharConstCase5();
+        testArrayCopyCharConstCase7();
+        testArrayCopyCharConstCase8();
+        testArrayCopyCharConstCase9();
+        testArrayCopyCharConstCase11();
+        testArrayCopyCharCase(0);
+        testArrayCopyCharCase(1);
+        testArrayCopyCharCase(3);
+        testArrayCopyCharCase(4);
+        testArrayCopyCharCase(5);
+        testArrayCopyCharCase(7);
+        testArrayCopyCharCase(15);
+        testArrayCopyCharCase(16);
+        testArrayCopyCharCase(17);
+        testArrayCopyCharCase(31);
+        testArrayCopyCharCase(32);
+        testArrayCopyCharCase(33);
+        testArrayCopyCharCase(63);
+        testArrayCopyCharCase(64);
+        testArrayCopyCharCase(65);
+        testArrayCopyCharCase(255);
+        testArrayCopyCharCase(513);
+        testArrayCopyCharCase(1025);
+    }
+
 }
diff --git a/test/018-stack-overflow/Android.bp b/test/018-stack-overflow/Android.bp
index 504bdaa..ef817a8 100644
--- a/test/018-stack-overflow/Android.bp
+++ b/test/018-stack-overflow/Android.bp
@@ -15,12 +15,13 @@
 java_test {
     name: "art-run-test-018-stack-overflow",
     defaults: ["art-run-test-defaults"],
-    test_config_template: ":art-run-test-target-template",
+    test_config_template: ":art-run-test-target-cts-template",
     srcs: ["src/**/*.java"],
     data: [
         ":art-run-test-018-stack-overflow-expected-stdout",
         ":art-run-test-018-stack-overflow-expected-stderr",
     ],
+    test_suites: ["cts"],
 }
 
 // Test's expected standard output.
diff --git a/test/018-stack-overflow/test-metadata.json b/test/018-stack-overflow/test-metadata.json
new file mode 100644
index 0000000..975ada7
--- /dev/null
+++ b/test/018-stack-overflow/test-metadata.json
@@ -0,0 +1,3 @@
+{
+  "test_suites": ["cts"]
+}
diff --git a/test/021-string2/src/Main.java b/test/021-string2/src/Main.java
index 39595f3..5da10ca 100644
--- a/test/021-string2/src/Main.java
+++ b/test/021-string2/src/Main.java
@@ -119,6 +119,7 @@
         testEqualsConstString();
         testConstStringEquals();
         testStringConcat();
+        testEmptyWithHighByte();
 
         // Regression tests for String.setCharAt() breaking string compression invariants.
         Locale en_US = new Locale("en", "US");
@@ -760,6 +761,11 @@
         Assert.assertEquals("abc\u0440xyzw\u0440", "abc\u0440".concat("xyzw\u0440"));
     }
 
+    public static void testEmptyWithHighByte() {
+        String empty = new String(new byte[0], 1);
+        Assert.assertEquals("", empty);
+    }
+
     public static boolean $noinline$equalsConstString0(String s) {
         return s.equals("");
     }
diff --git a/test/023-many-interfaces/build b/test/023-many-interfaces/build
deleted file mode 100644
index 6d1c8e3..0000000
--- a/test/023-many-interfaces/build
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2008 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.
-
-# Stop if something fails.
-set -e
-
-# Write out a bunch of interface source files.
-./iface-gen
-
-./default-build "$@"
diff --git a/test/023-many-interfaces/build.py b/test/023-many-interfaces/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/023-many-interfaces/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/023-many-interfaces/generate-sources b/test/023-many-interfaces/generate-sources
new file mode 100755
index 0000000..f9ba1b8
--- /dev/null
+++ b/test/023-many-interfaces/generate-sources
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright (C) 2008 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.
+
+# Stop if something fails.
+set -e
+
+# Write out a bunch of interface source files.
+./iface-gen
diff --git a/test/024-illegal-access/Android.bp b/test/024-illegal-access/Android.bp
new file mode 100644
index 0000000..7ed1a67
--- /dev/null
+++ b/test/024-illegal-access/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `024-illegal-access`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-024-illegal-access-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-024-illegal-access",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-024-illegal-access-src"
+    ],
+    data: [
+        ":art-run-test-024-illegal-access-expected-stdout",
+        ":art-run-test-024-illegal-access-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-024-illegal-access-expected-stdout",
+    out: ["art-run-test-024-illegal-access-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-024-illegal-access-expected-stderr",
+    out: ["art-run-test-024-illegal-access-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/030-bad-finalizer/build.py b/test/030-bad-finalizer/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/030-bad-finalizer/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/030-bad-finalizer/expected-stdout.txt b/test/030-bad-finalizer/expected-stdout.txt
index a0ae9ab..63e2895 100644
--- a/test/030-bad-finalizer/expected-stdout.txt
+++ b/test/030-bad-finalizer/expected-stdout.txt
@@ -2,4 +2,3 @@
 Finalizer started and sleeping briefly...
 Finalizer done snoozing.
 Finalizer sleeping forever now.
-exit status: 2
diff --git a/test/030-bad-finalizer/run b/test/030-bad-finalizer/run
deleted file mode 100755
index 54747ee..0000000
--- a/test/030-bad-finalizer/run
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# The test logs error messages which is expected, discard them.
-export ANDROID_LOG_TAGS='*:f'
-
-# Squash the exit status and put it in expected
-./default-run --external-log-tags "${@}"
-echo "exit status:" $?
diff --git a/test/030-bad-finalizer/run.py b/test/030-bad-finalizer/run.py
new file mode 100644
index 0000000..74fcbb9
--- /dev/null
+++ b/test/030-bad-finalizer/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, android_log_tags="*:f", expected_exit_code=2)
diff --git a/test/030-bad-finalizer/src/Main.java b/test/030-bad-finalizer/src/Main.java
index f33b3aa..bdc2b41 100644
--- a/test/030-bad-finalizer/src/Main.java
+++ b/test/030-bad-finalizer/src/Main.java
@@ -21,8 +21,10 @@
 /**
  * Test a class with a bad finalizer.
  *
- * This test is inherently flaky. It assumes that the system will schedule the finalizer daemon
- * and finalizer watchdog daemon enough to reach the timeout and throwing the fatal exception.
+ * This test is inherently very slightly flaky. It assumes that the system will schedule the
+ * finalizer daemon and finalizer watchdog daemon soon and often enough to reach the timeout and
+ * throw the fatal exception before we time out. Since we build in a 100 second buffer, failures
+ * should be very rare.
  */
 public class Main {
     public static void main(String[] args) throws Exception {
@@ -76,6 +78,7 @@
         try {
             Thread.sleep(ms);
         } catch (InterruptedException ie) {
+            System.out.println("Unexpected interrupt");
         }
     }
 
diff --git a/test/030-bad-finalizer/test-metadata.json b/test/030-bad-finalizer/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/030-bad-finalizer/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/032-concrete-sub/Android.bp b/test/032-concrete-sub/Android.bp
new file mode 100644
index 0000000..f4c43f8
--- /dev/null
+++ b/test/032-concrete-sub/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `032-concrete-sub`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-032-concrete-sub-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-032-concrete-sub",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-032-concrete-sub-src"
+    ],
+    data: [
+        ":art-run-test-032-concrete-sub-expected-stdout",
+        ":art-run-test-032-concrete-sub-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-032-concrete-sub-expected-stdout",
+    out: ["art-run-test-032-concrete-sub-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-032-concrete-sub-expected-stderr",
+    out: ["art-run-test-032-concrete-sub-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/034-call-null/expected-stdout.txt b/test/034-call-null/expected-stdout.txt
index a9bf5a0..e69de29 100644
--- a/test/034-call-null/expected-stdout.txt
+++ b/test/034-call-null/expected-stdout.txt
@@ -1 +0,0 @@
-exit status: 1
diff --git a/test/034-call-null/run b/test/034-call-null/run
deleted file mode 100755
index 7a0d0d0..0000000
--- a/test/034-call-null/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Squash the exit status and put it in expected
-./default-run "$@"
-echo "exit status:" $?
diff --git a/test/034-call-null/run.py b/test/034-call-null/run.py
new file mode 100644
index 0000000..80a8a33
--- /dev/null
+++ b/test/034-call-null/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, expected_exit_code=1)
diff --git a/test/038-inner-null/expected-stdout.txt b/test/038-inner-null/expected-stdout.txt
index 3d87b3f..24836c2 100644
--- a/test/038-inner-null/expected-stdout.txt
+++ b/test/038-inner-null/expected-stdout.txt
@@ -1,2 +1 @@
 new Special()
-exit status: 1
diff --git a/test/038-inner-null/run b/test/038-inner-null/run
deleted file mode 100755
index 7a0d0d0..0000000
--- a/test/038-inner-null/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Squash the exit status and put it in expected
-./default-run "$@"
-echo "exit status:" $?
diff --git a/test/038-inner-null/run.py b/test/038-inner-null/run.py
new file mode 100644
index 0000000..80a8a33
--- /dev/null
+++ b/test/038-inner-null/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, expected_exit_code=1)
diff --git a/test/042-new-instance/Android.bp b/test/042-new-instance/Android.bp
new file mode 100644
index 0000000..fd777be
--- /dev/null
+++ b/test/042-new-instance/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `042-new-instance`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-042-new-instance-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-042-new-instance",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-042-new-instance-src"
+    ],
+    data: [
+        ":art-run-test-042-new-instance-expected-stdout",
+        ":art-run-test-042-new-instance-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-042-new-instance-expected-stdout",
+    out: ["art-run-test-042-new-instance-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-042-new-instance-expected-stderr",
+    out: ["art-run-test-042-new-instance-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/044-proxy/run b/test/044-proxy/run
deleted file mode 100644
index 4a322f3..0000000
--- a/test/044-proxy/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Use a smaller heap so it's easier to fill up.
-exec ${RUN} $@ --runtime-option -Xmx4m
diff --git a/test/044-proxy/run.py b/test/044-proxy/run.py
new file mode 100644
index 0000000..1784fc5
--- /dev/null
+++ b/test/044-proxy/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Use a smaller heap so it's easier to fill up.
+  ctx.default_run(args, runtime_option=["-Xmx4m"])
diff --git a/test/051-thread/thread_test.cc b/test/051-thread/thread_test.cc
index 079ad40..33841eb 100644
--- a/test/051-thread/thread_test.cc
+++ b/test/051-thread/thread_test.cc
@@ -22,7 +22,7 @@
 
 extern "C" JNIEXPORT jint JNICALL Java_Main_getNativePriority(JNIEnv* env,
                                                               jclass clazz ATTRIBUTE_UNUSED) {
-  return ThreadForEnv(env)->GetNativePriority();
+  return Thread::ForEnv(env)->GetNativePriority();
 }
 
 extern "C" JNIEXPORT jboolean JNICALL Java_Main_supportsThreadPriorities(
diff --git a/test/054-uncaught/expected-stdout.txt b/test/054-uncaught/expected-stdout.txt
index 878260a..7d7f03c 100644
--- a/test/054-uncaught/expected-stdout.txt
+++ b/test/054-uncaught/expected-stdout.txt
@@ -18,4 +18,3 @@
 java.lang.NullPointerException: Hi diddly-ho, neighborino.
 	at Main.catchTheUncaught(Main.java:63)
 	at Main.main(Main.java:26)
-exit status: 1
diff --git a/test/054-uncaught/run b/test/054-uncaught/run
deleted file mode 100755
index 7a0d0d0..0000000
--- a/test/054-uncaught/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Squash the exit status and put it in expected
-./default-run "$@"
-echo "exit status:" $?
diff --git a/test/054-uncaught/run.py b/test/054-uncaught/run.py
new file mode 100644
index 0000000..80a8a33
--- /dev/null
+++ b/test/054-uncaught/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, expected_exit_code=1)
diff --git a/test/055-enum-performance/run b/test/055-enum-performance/run
deleted file mode 100755
index e27a622..0000000
--- a/test/055-enum-performance/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2012 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.
-
-# As this is a performance test we always use the non-debug build.
-exec ${RUN} "${@/#libartd.so/libart.so}"
diff --git a/test/055-enum-performance/run.py b/test/055-enum-performance/run.py
new file mode 100644
index 0000000..dbb09fd
--- /dev/null
+++ b/test/055-enum-performance/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2012 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.
+
+
+def run(ctx, args):
+  # As this is a performance test we always use the non-debug build.
+  ctx.default_run(args, lib="libart.so")
diff --git a/test/056-const-string-jumbo/build b/test/056-const-string-jumbo/build
deleted file mode 100644
index c1d711b..0000000
--- a/test/056-const-string-jumbo/build
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2008 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.
-
-# Stop if something fails.
-set -e
-
-# Write out files with 32768 total static string declarations, so that
-# the reference to "zorch" in the real test file will be guaranteed to
-# need a jumbo string reference (it sorts last after all the others).
-# Note: Each string reference is stored in a separate static variable,
-# and that variable's name is also represented in the strings, which
-# is why we can just have 32768 and not 65536 declarations.
-
-awk '
-BEGIN {
-    writeFile("Zorch1", 0, 16383);
-    writeFile("Zorch2", 16384, 32767);
-}
-function writeFile(name, start, end) {
-    fileName = "src/" name ".java";
-    printf("public class %s {\n", name) > fileName;
-    for (i = start; i <= end; i++) {
-        printf("    static public final String s%d = \"%d\";\n",
-            i, i) > fileName;
-    }
-    printf("}\n") > fileName;
-}'
-
-./default-build "$@"
diff --git a/test/056-const-string-jumbo/build.py b/test/056-const-string-jumbo/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/056-const-string-jumbo/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/056-const-string-jumbo/generate-sources b/test/056-const-string-jumbo/generate-sources
new file mode 100755
index 0000000..5c7ade8
--- /dev/null
+++ b/test/056-const-string-jumbo/generate-sources
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Copyright (C) 2008 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.
+
+# Stop if something fails.
+set -e
+
+# Write out files with 32768 total static string declarations, so that
+# the reference to "zorch" in the real test file will be guaranteed to
+# need a jumbo string reference (it sorts last after all the others).
+# Note: Each string reference is stored in a separate static variable,
+# and that variable's name is also represented in the strings, which
+# is why we can just have 32768 and not 65536 declarations.
+
+awk '
+BEGIN {
+    writeFile("Zorch1", 0, 16383);
+    writeFile("Zorch2", 16384, 32767);
+}
+function writeFile(name, start, end) {
+    fileName = "src/" name ".java";
+    printf("public class %s {\n", name) > fileName;
+    for (i = start; i <= end; i++) {
+        printf("    static public final String s%d = \"%d\";\n",
+            i, i) > fileName;
+    }
+    printf("}\n") > fileName;
+}'
diff --git a/test/059-finalizer-throw/run b/test/059-finalizer-throw/run
deleted file mode 100644
index dda4159..0000000
--- a/test/059-finalizer-throw/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-# The test logs error messages which is expected, discard them.
-export ANDROID_LOG_TAGS='*:f'
-exec ${RUN} --external-log-tags "${@}"
diff --git a/test/059-finalizer-throw/run.py b/test/059-finalizer-throw/run.py
new file mode 100644
index 0000000..930dc04
--- /dev/null
+++ b/test/059-finalizer-throw/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2020 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, android_log_tags="*:f")
diff --git a/test/064-field-access/run b/test/064-field-access/run
deleted file mode 100644
index 4a322f3..0000000
--- a/test/064-field-access/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Use a smaller heap so it's easier to fill up.
-exec ${RUN} $@ --runtime-option -Xmx4m
diff --git a/test/064-field-access/run.py b/test/064-field-access/run.py
new file mode 100644
index 0000000..1784fc5
--- /dev/null
+++ b/test/064-field-access/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Use a smaller heap so it's easier to fill up.
+  ctx.default_run(args, runtime_option=["-Xmx4m"])
diff --git a/test/065-mismatched-implements/build b/test/065-mismatched-implements/build
deleted file mode 100755
index 41823b5..0000000
--- a/test/065-mismatched-implements/build
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-# Make us exit on a failure.
-
-set -e
-
-# Don't use desugar because the build fails when it encounters ICCE.
-#
-# Exception in thread "main" java.lang.IllegalArgumentException
-#  at com.google.common.base.Preconditions.checkArgument(Preconditions.java:108)
-#  at com.google.devtools.build.android.desugar.DefaultMethodClassFixer$DefaultMethodFinder.visit(DefaultMethodClassFixer.java:295)
-export USE_DESUGAR=false
-
-./default-build "$@"
diff --git a/test/065-mismatched-implements/build.py b/test/065-mismatched-implements/build.py
new file mode 100644
index 0000000..bc346b0
--- /dev/null
+++ b/test/065-mismatched-implements/build.py
@@ -0,0 +1,23 @@
+#
+# Copyright (C) 2022 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.
+
+
+# Don't use desugar because the build fails when it encounters ICCE.
+#
+# Exception in thread "main" java.lang.IllegalArgumentException
+#  at com.google.common.base.Preconditions.checkArgument(Preconditions.java:108)
+#  at com.google.devtools.build.android.desugar.DefaultMethodClassFixer$DefaultMethodFinder.visit(DefaultMethodClassFixer.java:295)
+def build(ctx):
+  ctx.default_build(use_desugar=False)
diff --git a/test/066-mismatched-super/build b/test/066-mismatched-super/build
deleted file mode 100644
index 50aceb4..0000000
--- a/test/066-mismatched-super/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-USE_DESUGAR=false ./default-build "$@"
diff --git a/test/066-mismatched-super/build.py b/test/066-mismatched-super/build.py
new file mode 100644
index 0000000..59b119d
--- /dev/null
+++ b/test/066-mismatched-super/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(use_desugar=False)
diff --git a/test/066-mismatched-super/src/Main.java b/test/066-mismatched-super/src/Main.java
index 6ae1198..bb042b7 100644
--- a/test/066-mismatched-super/src/Main.java
+++ b/test/066-mismatched-super/src/Main.java
@@ -28,7 +28,8 @@
         try {
             ExtendsFinal ef = new ExtendsFinal();
             System.out.println("Succeeded unexpectedly");
-        } catch (VerifyError ve) {
+        } catch (VerifyError | IncompatibleClassChangeError ve) {
+            // b/242985231 - from JDK 17 the JVM throws IncompatibleClassChangeError
             System.out.println("Got expected VerifyError");
         }
     }
diff --git a/test/069-field-type/Android.bp b/test/069-field-type/Android.bp
new file mode 100644
index 0000000..c407a92
--- /dev/null
+++ b/test/069-field-type/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `069-field-type`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-069-field-type-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-069-field-type",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-069-field-type-src"
+    ],
+    data: [
+        ":art-run-test-069-field-type-expected-stdout",
+        ":art-run-test-069-field-type-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-069-field-type-expected-stdout",
+    out: ["art-run-test-069-field-type-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-069-field-type-expected-stderr",
+    out: ["art-run-test-069-field-type-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/071-dexfile-get-static-size/build b/test/071-dexfile-get-static-size/build
deleted file mode 100755
index 4ce24cf..0000000
--- a/test/071-dexfile-get-static-size/build
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-build "$@"
-
-# Bundle with the test the following resources:
-# 1. test1.dex
-# 2. test2.dex
-# 3. test-jar.jar, containing test1.dex as classes.dex
-# 4. multi-jar.jar, containing test1.dex as classes.dex and test2.dex as classes2.dex
-mkdir test-jar
-cp res/test1.dex test-jar/classes.dex
-cp res/test2.dex test-jar/classes2.dex
-${SOONG_ZIP} -j -o res/test-jar.jar -f test-jar/classes.dex
-${SOONG_ZIP} -j -o res/multi-jar.jar -f test-jar/classes.dex -f test-jar/classes2.dex
diff --git a/test/071-dexfile-get-static-size/build.py b/test/071-dexfile-get-static-size/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/071-dexfile-get-static-size/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/071-dexfile-get-static-size/generate-sources b/test/071-dexfile-get-static-size/generate-sources
new file mode 100755
index 0000000..55afc03
--- /dev/null
+++ b/test/071-dexfile-get-static-size/generate-sources
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Bundle with the test the following resources:
+# 1. test1.dex
+# 2. test2.dex
+# 3. test-jar.jar, containing test1.dex as classes.dex
+# 4. multi-jar.jar, containing test1.dex as classes.dex and test2.dex as classes2.dex
+mkdir test-jar
+cp res/test1.dex test-jar/classes.dex
+cp res/test2.dex test-jar/classes2.dex
+${SOONG_ZIP} -j -o res/test-jar.jar -f test-jar/classes.dex
+${SOONG_ZIP} -j -o res/multi-jar.jar -f test-jar/classes.dex -f test-jar/classes2.dex
diff --git a/test/071-dexfile-map-clean/build b/test/071-dexfile-map-clean/build
deleted file mode 100755
index a171fc3..0000000
--- a/test/071-dexfile-map-clean/build
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Any JAR files used by this test shall have their classes.dex be stored, NOT compressed.
-# This is needed for our test correctness which validates classes.dex are mapped file-backed.
-#
-# In addition, align to at least 4 bytes since that's the dex alignment requirement.
-./default-build "$@" --zip-compression-method store --zip-align 4
diff --git a/test/071-dexfile-map-clean/build.py b/test/071-dexfile-map-clean/build.py
new file mode 100644
index 0000000..ab9c4c3
--- /dev/null
+++ b/test/071-dexfile-map-clean/build.py
@@ -0,0 +1,22 @@
+#
+# Copyright (C) 2022 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.
+
+
+# Any JAR files used by this test shall have their classes.dex be stored, NOT compressed.
+# This is needed for our test correctness which validates classes.dex are mapped file-backed.
+#
+# In addition, align to at least 4 bytes since that's the dex alignment requirement.
+def build(ctx):
+  ctx.default_build(zip_compression_method="store", zip_align_bytes="4")
diff --git a/test/071-dexfile-map-clean/run b/test/071-dexfile-map-clean/run
deleted file mode 100755
index afa2ff7..0000000
--- a/test/071-dexfile-map-clean/run
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Make sure we call 'sync'
-# before executing dalvikvm because otherwise
-# it's highly likely the pushed JAR files haven't
-# been committed to permanent storage yet,
-# and when we mmap them the kernel will think
-# the memory is dirty (despite being file-backed).
-# (Note: this was reproducible 100% of the time on
-# a target angler device).
-./default-run "$@" --sync
diff --git a/test/071-dexfile-map-clean/run.py b/test/071-dexfile-map-clean/run.py
new file mode 100644
index 0000000..eda60fa
--- /dev/null
+++ b/test/071-dexfile-map-clean/run.py
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Make sure we call 'sync'
+  # before executing dalvikvm because otherwise
+  # it's highly likely the pushed JAR files haven't
+  # been committed to permanent storage yet,
+  # and when we mmap them the kernel will think
+  # the memory is dirty (despite being file-backed).
+  # (Note: this was reproducible 100% of the time on
+  # a target angler device).
+  ctx.default_run(args, sync=True)
diff --git a/test/073-mismatched-field/Android.bp b/test/073-mismatched-field/Android.bp
new file mode 100644
index 0000000..16ca7da
--- /dev/null
+++ b/test/073-mismatched-field/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `073-mismatched-field`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-073-mismatched-field-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-073-mismatched-field",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-073-mismatched-field-src"
+    ],
+    data: [
+        ":art-run-test-073-mismatched-field-expected-stdout",
+        ":art-run-test-073-mismatched-field-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-073-mismatched-field-expected-stdout",
+    out: ["art-run-test-073-mismatched-field-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-073-mismatched-field-expected-stderr",
+    out: ["art-run-test-073-mismatched-field-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/075-verification-error/Android.bp b/test/075-verification-error/Android.bp
new file mode 100644
index 0000000..ab87fc8
--- /dev/null
+++ b/test/075-verification-error/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `075-verification-error`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-075-verification-error-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-075-verification-error",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-075-verification-error-src"
+    ],
+    data: [
+        ":art-run-test-075-verification-error-expected-stdout",
+        ":art-run-test-075-verification-error-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-075-verification-error-expected-stdout",
+    out: ["art-run-test-075-verification-error-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-075-verification-error-expected-stderr",
+    out: ["art-run-test-075-verification-error-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/077-method-override/Android.bp b/test/077-method-override/Android.bp
new file mode 100644
index 0000000..f33a9ac03
--- /dev/null
+++ b/test/077-method-override/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `077-method-override`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-077-method-override-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-077-method-override",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-077-method-override-src"
+    ],
+    data: [
+        ":art-run-test-077-method-override-expected-stdout",
+        ":art-run-test-077-method-override-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-077-method-override-expected-stdout",
+    out: ["art-run-test-077-method-override-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-077-method-override-expected-stderr",
+    out: ["art-run-test-077-method-override-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/080-oom-throw/run b/test/080-oom-throw/run
deleted file mode 100644
index 08db73b..0000000
--- a/test/080-oom-throw/run
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Ensure the minimum log severity is at least 'WARNING' to display the
-# stack trace shown before exception
-#
-#   "java.lang.OutOfMemoryError: OutOfMemoryError thrown while trying
-#   to throw OutOfMemoryError; no stack trace available"
-#
-# is set, to try to understand a recurring crash in this test (b/77567088).
-case "$ANDROID_LOG_TAGS" in
-  # Lower the minimum log severity to WARNING if it was initialy set
-  # to a higher level ('ERROR', 'FATAL' or 'SILENT' -- see
-  # https://developer.android.com/studio/command-line/logcat#filteringOutput).
-  (\*:[efs]) export ANDROID_LOG_TAGS='*:w';;
-esac
-
-exec ${RUN} $@ --runtime-option -Xmx16m
diff --git a/test/080-oom-throw/run.py b/test/080-oom-throw/run.py
new file mode 100644
index 0000000..fb10464
--- /dev/null
+++ b/test/080-oom-throw/run.py
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Ensure the minimum log severity is at least 'WARNING' to display the
+  # stack trace shown before exception
+  #
+  #   "java.lang.OutOfMemoryError: OutOfMemoryError thrown while trying
+  #   to throw OutOfMemoryError; no stack trace available"
+  #
+  # is set, to try to understand a recurring crash in this test (b/77567088).
+  if ctx.env.ANDROID_LOG_TAGS in ["*:e", "*:f", "*:s"]:
+    # Lower the minimum log severity to WARNING if it was initialy set
+    # to a higher level ('ERROR', 'FATAL' or 'SILENT' -- see
+    # https://developer.android.com/studio/command-line/logcat#filteringOutput).
+    ctx.env.ANDROID_LOG_TAGS = "*:w"
+
+  ctx.default_run(args, runtime_option=["-Xmx16m"])
diff --git a/test/080-oom-throw/src/Main.java b/test/080-oom-throw/src/Main.java
index cd36eff..c9fd361 100644
--- a/test/080-oom-throw/src/Main.java
+++ b/test/080-oom-throw/src/Main.java
@@ -55,12 +55,20 @@
 
     private static int exhaustJavaHeap(Object[] data, int index, int size) {
         Runtime.getRuntime().gc();
-        while (index != data.length && size != 0) {
+        while (index != data.length) {
             try {
                 data[index] = new byte[size];
                 ++index;
             } catch (OutOfMemoryError oome) {
-                size /= 2;
+                // Rapidly shrink the object size to fill any remaining space.
+                // Use few different sizes, since detecting out-of-memory is slow.
+                if (size >= 32) {
+                    size /= 32;
+                } else if (size > 1) {
+                    size = 1;
+                } else {
+                    break;
+                }
             }
         }
         return index;
diff --git a/test/083-compiler-regressions/src/Main.java b/test/083-compiler-regressions/src/Main.java
index 9de3f5a..53641b4 100644
--- a/test/083-compiler-regressions/src/Main.java
+++ b/test/083-compiler-regressions/src/Main.java
@@ -9756,6 +9756,7 @@
     private static int ifLez(int src, int thn, int els) { return (src <= 0) ? thn : els; }
 
     public static void testIfCcz() {
+        // clang-format off
         int[] results = new int[] {
             ifEqzThen0Else1(-1), 1,
             ifEqzThen0Else1(0), 0,
@@ -9826,6 +9827,7 @@
             ifGtzThen8Else9(0), 9,
             ifGtzThen8Else9(1), 8
         };
+        // clang-format on
 
         boolean success = true;
         StringBuilder fails = new StringBuilder();
diff --git a/test/088-monitor-verification/src/Main.java b/test/088-monitor-verification/src/Main.java
index 3924fa6..3ed1939 100644
--- a/test/088-monitor-verification/src/Main.java
+++ b/test/088-monitor-verification/src/Main.java
@@ -119,6 +119,7 @@
      * Confirms that we can have 32 nested monitors on one method.
      */
     void notExcessiveNesting() {
+        // clang-format off
         assertIsManaged();
         synchronized (this) {   // 1
         synchronized (this) {   // 2
@@ -153,6 +154,7 @@
         synchronized (this) {   // 31
         synchronized (this) {   // 32
         }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
+        // clang-format on
     }
 
     /**
@@ -160,6 +162,7 @@
      * method.
      */
     void notNested() {
+        // clang-format off
         assertIsManaged();
         synchronized (this) {}  // 1
         synchronized (this) {}  // 2
@@ -195,6 +198,7 @@
         synchronized (this) {}  // 32
         synchronized (this) {}  // 33
         synchronized (this) {}  // 34
+        // clang-format on
     }
 
     /* does nothing but ensure that the compiler doesn't discard an object */
diff --git a/test/089-many-methods/build b/test/089-many-methods/build
deleted file mode 100644
index 5225e3f..0000000
--- a/test/089-many-methods/build
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2008 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.
-
-# Stop if something fails.
-set -e
-
-# Write out files with 65500 total static fields, instance fields, and methods
-# to exceed the dex format's limits.
-mkdir src
-awk '
-BEGIN {
-    writeFileField("FillerStatic", "static public int staticInt");
-    writeFileField("FillerField", "public int fieldInt");
-    writeFileMethod("FillerMethod");
-}
-function writeFileField(name, type) {
-    fileName = "src/" name ".java";
-    printf("public class %s {\n", name) > fileName;
-    for (i = 1; i <= 65500; i++) {
-        printf("    %s%d;\n", type, i) > fileName;
-    }
-    printf("}\n") > fileName;
-}
-function writeFileMethod(name) {
-    fileName = "src/" name ".java";
-    printf("public class %s {\n", name) > fileName;
-    for (i = 1; i <= 65500; i++) {
-      printf("    public void meth%d() { }\n", i) > fileName;
-    }
-    printf("}\n") > fileName;
-}'
-
-# Force DEX generation so test also passes with --jvm.
-export NEED_DEX=true
-
-# Specify old API level as d8 automagically produces a multidex file
-# when the API level is above 20. Failing the build here is deliberate.
-./default-build --api-level 20 "$@" > /dev/null 2> stderr.txt || true
-
-# Check that a build failure happened (the test is not expected to run).
-EXPECTED_ERROR="Cannot fit requested classes in a single dex"
-grep -q "$EXPECTED_ERROR" stderr.txt
-rm stderr.txt  # Check passed. Remove output due to non-deterministic paths.
diff --git a/test/089-many-methods/info.txt b/test/089-many-methods/info.txt
deleted file mode 100644
index 4f73bd6..0000000
--- a/test/089-many-methods/info.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Test that we print a reasonable message when the application exceeds more
-than 65536 methods.
diff --git a/test/089-many-methods/run b/test/089-many-methods/run
deleted file mode 100644
index 867dedc..0000000
--- a/test/089-many-methods/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2021 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.
-
-# Do nothing - the build intentionally failed.
diff --git a/test/091-override-package-private-method/build b/test/091-override-package-private-method/build
deleted file mode 100755
index 6b298f7..0000000
--- a/test/091-override-package-private-method/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/091-override-package-private-method/javac_post.sh b/test/091-override-package-private-method/javac_post.sh
new file mode 100755
index 0000000..d2dd7e6
--- /dev/null
+++ b/test/091-override-package-private-method/javac_post.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e # Stop on error - the caller script may not have this set.
+
+mkdir -p classes-ex
+mv classes/OverridePackagePrivateMethodSuper.class classes-ex
diff --git a/test/091-override-package-private-method/javac_wrapper.sh b/test/091-override-package-private-method/javac_wrapper.sh
deleted file mode 100755
index 0e2b2e1..0000000
--- a/test/091-override-package-private-method/javac_wrapper.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e # Stop on error - the caller script may not have this set.
-$JAVAC "$@"
-mkdir -p classes-ex
-mv classes/OverridePackagePrivateMethodSuper.class classes-ex
diff --git a/test/091-override-package-private-method/run b/test/091-override-package-private-method/run
deleted file mode 100755
index d8c3c79..0000000
--- a/test/091-override-package-private-method/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Use secondary switch to add secondary dex file to class path.
-exec ${RUN} "${@}" --secondary
diff --git a/test/091-override-package-private-method/run.py b/test/091-override-package-private-method/run.py
new file mode 100644
index 0000000..2746221
--- /dev/null
+++ b/test/091-override-package-private-method/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+
+def run(ctx, args):
+  # Use secondary switch to add secondary dex file to class path.
+  ctx.default_run(args, secondary=True)
diff --git a/test/099-vmdebug/check b/test/099-vmdebug/check
deleted file mode 100755
index 3d92188..0000000
--- a/test/099-vmdebug/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Strip the process pids and line numbers from exact error messages.
-sed -e '/^.*dalvikvm\(\|32\|64\) E.*\] /d' "$4" > "$4.tmp"
-
-diff --strip-trailing-cr -q "$1" "$2" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4.tmp" >/dev/null
diff --git a/test/099-vmdebug/run.py b/test/099-vmdebug/run.py
new file mode 100644
index 0000000..4492f36
--- /dev/null
+++ b/test/099-vmdebug/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # Strip the process pids and line numbers from exact error messages.
+  ctx.run(fr"sed -i '/^.*dalvikvm\(\|32\|64\) E.*\] /d' '{args.stderr_file}'")
diff --git a/test/099-vmdebug/test-metadata.json b/test/099-vmdebug/test-metadata.json
new file mode 100644
index 0000000..2d1dd45
--- /dev/null
+++ b/test/099-vmdebug/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "run-param": {
+    "default-run": true
+  }
+}
diff --git a/test/100-reflect2/expected-stdout.txt b/test/100-reflect2/expected-stdout.txt
index a59f230..ca85392 100644
--- a/test/100-reflect2/expected-stdout.txt
+++ b/test/100-reflect2/expected-stdout.txt
@@ -31,9 +31,9 @@
 30 (class java.lang.Integer)
 62 (class java.lang.Long)
 14 (class java.lang.Short)
-[java.lang.String(int,int,char[]), public java.lang.String(), public java.lang.String(byte[]), public java.lang.String(byte[],int), public java.lang.String(byte[],int,int), public java.lang.String(byte[],int,int,int), public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException, public java.lang.String(byte[],int,int,java.nio.charset.Charset), public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException, public java.lang.String(byte[],java.nio.charset.Charset), public java.lang.String(char[]), public java.lang.String(char[],int,int), public java.lang.String(int[],int,int), public java.lang.String(java.lang.String), public java.lang.String(java.lang.StringBuffer), public java.lang.String(java.lang.StringBuilder)]
-[private final int java.lang.String.count, private int java.lang.String.hash, private static final java.io.ObjectStreamField[] java.lang.String.serialPersistentFields, private static final long java.lang.String.serialVersionUID, public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER]
-[native void java.lang.String.getCharsNoCheck(int,int,char[],int), private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder), private int java.lang.String.indexOfNonWhitespace(), private int java.lang.String.indexOfSupplementary(int,int), private int java.lang.String.lastIndexOfSupplementary(int,int), private native java.lang.String java.lang.String.doRepeat(int), private native java.lang.String java.lang.String.doReplace(char,char), private native java.lang.String java.lang.String.fastSubstring(int,int), private static int java.lang.String.indexOf(java.lang.String,java.lang.String,int), private static int java.lang.String.lastIndexOf(java.lang.String,java.lang.String,int), public boolean java.lang.String.contains(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.StringBuffer), public boolean java.lang.String.endsWith(java.lang.String), public boolean java.lang.String.equals(java.lang.Object), public boolean java.lang.String.equalsIgnoreCase(java.lang.String), public boolean java.lang.String.isBlank(), public boolean java.lang.String.isEmpty(), public boolean java.lang.String.matches(java.lang.String), public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int), public boolean java.lang.String.regionMatches(int,java.lang.String,int,int), public boolean java.lang.String.startsWith(java.lang.String), public boolean java.lang.String.startsWith(java.lang.String,int), public byte[] java.lang.String.getBytes(), public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException, public byte[] java.lang.String.getBytes(java.nio.charset.Charset), public int java.lang.String.codePointAt(int), public int java.lang.String.codePointBefore(int), public int java.lang.String.codePointCount(int,int), public int java.lang.String.compareTo(java.lang.Object), public int java.lang.String.compareToIgnoreCase(java.lang.String), public int java.lang.String.hashCode(), public int java.lang.String.indexOf(int), public int java.lang.String.indexOf(int,int), public int java.lang.String.indexOf(java.lang.String), public int java.lang.String.indexOf(java.lang.String,int), public int java.lang.String.lastIndexOf(int), public int java.lang.String.lastIndexOf(int,int), public int java.lang.String.lastIndexOf(java.lang.String), public int java.lang.String.lastIndexOf(java.lang.String,int), public int java.lang.String.length(), public int java.lang.String.offsetByCodePoints(int,int), public java.lang.CharSequence java.lang.String.subSequence(int,int), public java.lang.String java.lang.String.repeat(int), public java.lang.String java.lang.String.replace(char,char), public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence), public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String), public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String), public java.lang.String java.lang.String.strip(), public java.lang.String java.lang.String.stripLeading(), public java.lang.String java.lang.String.stripTrailing(), public java.lang.String java.lang.String.substring(int), public java.lang.String java.lang.String.substring(int,int), public java.lang.String java.lang.String.toLowerCase(), public java.lang.String java.lang.String.toLowerCase(java.util.Locale), public java.lang.String java.lang.String.toString(), public java.lang.String java.lang.String.toUpperCase(), public java.lang.String java.lang.String.toUpperCase(java.util.Locale), public java.lang.String java.lang.String.trim(), public java.lang.String[] java.lang.String.split(java.lang.String), public java.lang.String[] java.lang.String.split(java.lang.String,int), public java.util.stream.IntStream java.lang.String.chars(), public java.util.stream.IntStream java.lang.String.codePoints(), public java.util.stream.Stream java.lang.String.lines(), public native char java.lang.String.charAt(int), public native char[] java.lang.String.toCharArray(), public native int java.lang.String.compareTo(java.lang.String), public native java.lang.String java.lang.String.concat(java.lang.String), public native java.lang.String java.lang.String.intern(), public static java.lang.String java.lang.String.copyValueOf(char[]), public static java.lang.String java.lang.String.copyValueOf(char[],int,int), public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[]), public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.Iterable), public static java.lang.String java.lang.String.valueOf(boolean), public static java.lang.String java.lang.String.valueOf(char), public static java.lang.String java.lang.String.valueOf(char[]), public static java.lang.String java.lang.String.valueOf(char[],int,int), public static java.lang.String java.lang.String.valueOf(double), public static java.lang.String java.lang.String.valueOf(float), public static java.lang.String java.lang.String.valueOf(int), public static java.lang.String java.lang.String.valueOf(java.lang.Object), public static java.lang.String java.lang.String.valueOf(long), public void java.lang.String.getBytes(int,int,byte[],int), public void java.lang.String.getChars(int,int,char[],int), static int java.lang.String.indexOf(char[],int,int,char[],int,int,int), static int java.lang.String.indexOf(char[],int,int,java.lang.String,int), static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int), static int java.lang.String.lastIndexOf(char[],int,int,java.lang.String,int), static void java.lang.String.checkBoundsBeginEnd(int,int,int), static void java.lang.String.checkBoundsOffCount(int,int,int), static void java.lang.String.checkIndex(int,int), void java.lang.String.getChars(char[],int)]
+[java.lang.String(byte[],byte), java.lang.String(int,int,char[]), public java.lang.String(), public java.lang.String(byte[]), public java.lang.String(byte[],int), public java.lang.String(byte[],int,int), public java.lang.String(byte[],int,int,int), public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException, public java.lang.String(byte[],int,int,java.nio.charset.Charset), public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException, public java.lang.String(byte[],java.nio.charset.Charset), public java.lang.String(char[]), public java.lang.String(char[],int,int), public java.lang.String(int[],int,int), public java.lang.String(java.lang.String), public java.lang.String(java.lang.StringBuffer), public java.lang.String(java.lang.StringBuilder)]
+[private final int java.lang.String.count, private int java.lang.String.hash, private static final java.io.ObjectStreamField[] java.lang.String.serialPersistentFields, private static final long java.lang.String.serialVersionUID, public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER, static final boolean java.lang.String.COMPACT_STRINGS, static final byte java.lang.String.LATIN1, static final byte java.lang.String.UTF16]
+[byte java.lang.String.coder(), native void java.lang.String.getCharsNoCheck(int,int,char[],int), private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder), private int java.lang.String.indexOfNonWhitespace(), private int java.lang.String.indexOfSupplementary(int,int), private int java.lang.String.lastIndexOfNonWhitespace(), private int java.lang.String.lastIndexOfSupplementary(int,int), private native java.lang.String java.lang.String.doRepeat(int), private native java.lang.String java.lang.String.doReplace(char,char), private native java.lang.String java.lang.String.fastSubstring(int,int), private native void java.lang.String.fillBytesLatin1(byte[],int), private native void java.lang.String.fillBytesUTF16(byte[],int), private static int java.lang.String.indexOf(java.lang.String,java.lang.String,int), private static int java.lang.String.lastIndexOf(java.lang.String,java.lang.String,int), private static int java.lang.String.outdent(java.util.List), public boolean java.lang.String.contains(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.StringBuffer), public boolean java.lang.String.endsWith(java.lang.String), public boolean java.lang.String.equals(java.lang.Object), public boolean java.lang.String.equalsIgnoreCase(java.lang.String), public boolean java.lang.String.isBlank(), public boolean java.lang.String.isEmpty(), public boolean java.lang.String.matches(java.lang.String), public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int), public boolean java.lang.String.regionMatches(int,java.lang.String,int,int), public boolean java.lang.String.startsWith(java.lang.String), public boolean java.lang.String.startsWith(java.lang.String,int), public byte[] java.lang.String.getBytes(), public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException, public byte[] java.lang.String.getBytes(java.nio.charset.Charset), public int java.lang.String.codePointAt(int), public int java.lang.String.codePointBefore(int), public int java.lang.String.codePointCount(int,int), public int java.lang.String.compareTo(java.lang.Object), public int java.lang.String.compareToIgnoreCase(java.lang.String), public int java.lang.String.hashCode(), public int java.lang.String.indexOf(int), public int java.lang.String.indexOf(int,int), public int java.lang.String.indexOf(java.lang.String), public int java.lang.String.indexOf(java.lang.String,int), public int java.lang.String.lastIndexOf(int), public int java.lang.String.lastIndexOf(int,int), public int java.lang.String.lastIndexOf(java.lang.String), public int java.lang.String.lastIndexOf(java.lang.String,int), public int java.lang.String.length(), public int java.lang.String.offsetByCodePoints(int,int), public java.lang.CharSequence java.lang.String.subSequence(int,int), public java.lang.Object java.lang.String.transform(java.util.function.Function), public java.lang.String java.lang.String.formatted(java.lang.Object[]), public java.lang.String java.lang.String.indent(int), public java.lang.String java.lang.String.repeat(int), public java.lang.String java.lang.String.replace(char,char), public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence), public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String), public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String), public java.lang.String java.lang.String.strip(), public java.lang.String java.lang.String.stripIndent(), public java.lang.String java.lang.String.stripLeading(), public java.lang.String java.lang.String.stripTrailing(), public java.lang.String java.lang.String.substring(int), public java.lang.String java.lang.String.substring(int,int), public java.lang.String java.lang.String.toLowerCase(), public java.lang.String java.lang.String.toLowerCase(java.util.Locale), public java.lang.String java.lang.String.toString(), public java.lang.String java.lang.String.toUpperCase(), public java.lang.String java.lang.String.toUpperCase(java.util.Locale), public java.lang.String java.lang.String.translateEscapes(), public java.lang.String java.lang.String.trim(), public java.lang.String[] java.lang.String.split(java.lang.String), public java.lang.String[] java.lang.String.split(java.lang.String,int), public java.util.stream.IntStream java.lang.String.chars(), public java.util.stream.IntStream java.lang.String.codePoints(), public java.util.stream.Stream java.lang.String.lines(), public native char java.lang.String.charAt(int), public native char[] java.lang.String.toCharArray(), public native int java.lang.String.compareTo(java.lang.String), public native java.lang.String java.lang.String.concat(java.lang.String), public native java.lang.String java.lang.String.intern(), public static java.lang.String java.lang.String.copyValueOf(char[]), public static java.lang.String java.lang.String.copyValueOf(char[],int,int), public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[]), public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.Iterable), public static java.lang.String java.lang.String.valueOf(boolean), public static java.lang.String java.lang.String.valueOf(char), public static java.lang.String java.lang.String.valueOf(char[]), public static java.lang.String java.lang.String.valueOf(char[],int,int), public static java.lang.String java.lang.String.valueOf(double), public static java.lang.String java.lang.String.valueOf(float), public static java.lang.String java.lang.String.valueOf(int), public static java.lang.String java.lang.String.valueOf(java.lang.Object), public static java.lang.String java.lang.String.valueOf(long), public void java.lang.String.getBytes(int,int,byte[],int), public void java.lang.String.getChars(int,int,char[],int), static int java.lang.String.indexOf(byte[],byte,int,java.lang.String,int), static int java.lang.String.lastIndexOf(byte[],byte,int,java.lang.String,int), static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int), static java.lang.String java.lang.String.lambda$indent$0(java.lang.String,java.lang.String), static java.lang.String java.lang.String.lambda$indent$1(java.lang.String), static java.lang.String java.lang.String.lambda$indent$2(int,java.lang.String), static java.lang.String java.lang.String.lambda$stripIndent$3(int,java.lang.String), static void java.lang.String.checkBoundsBeginEnd(int,int,int), static void java.lang.String.checkBoundsOffCount(int,int,int), static void java.lang.String.checkIndex(int,int), static void java.lang.String.checkOffset(int,int), void java.lang.String.getBytes(byte[],int,byte), void java.lang.String.getChars(char[],int)]
 []
 [interface java.io.Serializable, interface java.lang.Comparable, interface java.lang.CharSequence]
 0
diff --git a/test/1000-non-moving-space-stress/src-art/Main.java b/test/1000-non-moving-space-stress/src-art/Main.java
index 18bfdd3..3cd7224 100644
--- a/test/1000-non-moving-space-stress/src-art/Main.java
+++ b/test/1000-non-moving-space-stress/src-art/Main.java
@@ -15,8 +15,11 @@
  */
 
 import dalvik.system.VMRuntime;
+import java.lang.ref.Reference;  // For reachabilityFence.
+import java.util.ArrayList;
 
 public class Main {
+  private static final boolean SHOULD_PRINT = false;  // True causes failure.
 
   public static void main(String[] args) throws Exception {
     VMRuntime runtime = VMRuntime.getRuntime();
@@ -35,15 +38,40 @@
         Object[] moving_array = new Object[S];
       }
     } catch (OutOfMemoryError e) {
-      // Stop here.
+      System.out.println("Unexpected OOME");
     }
-    System.out.println("passed");
+    Runtime.getRuntime().gc();
+    int numAllocs = 0;
+    ArrayList<Object> chunks = new ArrayList<>();
+    try {
+      final int MAX_PLAUSIBLE_ALLOCS = 1024 * 1024;
+      for (numAllocs = 0; numAllocs < MAX_PLAUSIBLE_ALLOCS; ++numAllocs) {
+        chunks.add(runtime.newNonMovableArray(Object.class, 252));  // About 1KB
+      }
+      // If we get here, we've allocated about 1GB of nonmovable memory, which
+      // should be impossible.
+    } catch (OutOfMemoryError e) {
+      chunks.remove(0);  // Give us a little space back.
+      if (((Object[]) (chunks.get(42)))[17] != null) {
+        System.out.println("Bad entry in chunks array");
+      } else {
+        chunks.clear();  // Recover remaining space.
+        if (SHOULD_PRINT) {
+          System.out.println("Successfully allocated " + numAllocs + " non-movable KBs");
+        }
+        System.out.println("passed");
+      }
+      Reference.reachabilityFence(chunks);
+      return;
+    }
+    Reference.reachabilityFence(chunks);
+    System.out.println("Failed to exhaust non-movable space");
   }
 
   // When using the Concurrent Copying (CC) collector (default collector),
   // this method allocates an object in the non-moving space and an object
   // in the region space, make the former reference the later, and returns
-  // nothing (so that none of these objects are reachable when upon return).
+  // nothing (so that none of these objects are reachable upon return).
   static void $noinline$Alloc(VMRuntime runtime) {
     Object[] non_moving_array = (Object[]) runtime.newNonMovableArray(Object.class, 1);
     // Small object, unlikely to trigger garbage collection.
diff --git a/test/1001-app-image-regions/build b/test/1001-app-image-regions/build
deleted file mode 100755
index 16c3a5b..0000000
--- a/test/1001-app-image-regions/build
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# make us exit on a failure
-set -e
-
-count=10000
-echo "LMain;" >> profile
-for i in $(seq 1 "$count"); do
-  echo "LOther\$Inner${i};" >> "profile"
-done
-
-# Generate the other class.
-other_file="src/Other.java"
-echo "class Other {" >> "${other_file}"
-for i in $(seq 1 "$count"); do
-  echo "  static class Inner${i} { void test(){} }" >> "${other_file}"
-done
-echo "}" >> "${other_file}"
-
-./default-build "$@"
diff --git a/test/1001-app-image-regions/build.py b/test/1001-app-image-regions/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/1001-app-image-regions/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/1001-app-image-regions/generate-sources b/test/1001-app-image-regions/generate-sources
new file mode 100755
index 0000000..0fbe23f
--- /dev/null
+++ b/test/1001-app-image-regions/generate-sources
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+# make us exit on a failure
+set -e
+
+count=10000
+echo "LMain;" >> profile
+for i in $(seq 1 "$count"); do
+  echo "LOther\$Inner${i};" >> "profile"
+done
+
+# Generate the other class.
+other_file="src/Other.java"
+echo "class Other {" >> "${other_file}"
+for i in $(seq 1 "$count"); do
+  echo "  static class Inner${i} { void test(){} }" >> "${other_file}"
+done
+echo "}" >> "${other_file}"
diff --git a/test/1001-app-image-regions/run b/test/1001-app-image-regions/run
deleted file mode 100644
index 128aa2e..0000000
--- a/test/1001-app-image-regions/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 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.
-
-exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile
diff --git a/test/1001-app-image-regions/run.py b/test/1001-app-image-regions/run.py
new file mode 100644
index 0000000..d529700
--- /dev/null
+++ b/test/1001-app-image-regions/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args, profile=True, Xcompiler_option=["--compiler-filter=speed-profile"])
diff --git a/test/1002-notify-startup/check b/test/1002-notify-startup/check
deleted file mode 100644
index eadb559..0000000
--- a/test/1002-notify-startup/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Oat file manager will complain about duplicate dex files. Ignore.
-sed -e '/.*oat_file_manager.*/d' "$4" > "$4.tmp"
-
-diff --strip-trailing-cr -q "$1" "$2" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4.tmp" >/dev/null
diff --git a/test/1002-notify-startup/expected-stdout.txt b/test/1002-notify-startup/expected-stdout.txt
index a86b9aa..6a5618e 100644
--- a/test/1002-notify-startup/expected-stdout.txt
+++ b/test/1002-notify-startup/expected-stdout.txt
@@ -1,3 +1 @@
 JNI_OnLoad called
-Startup completed: false
-Startup completed: true
diff --git a/test/1002-notify-startup/run.py b/test/1002-notify-startup/run.py
new file mode 100644
index 0000000..6e15f3a
--- /dev/null
+++ b/test/1002-notify-startup/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # Oat file manager will complain about duplicate dex files. Ignore.
+  ctx.run(fr"sed -i '/.*oat_file_manager.*/d' '{args.stderr_file}'")
diff --git a/test/1002-notify-startup/src-art/Main.java b/test/1002-notify-startup/src-art/Main.java
index 8951af8..68033fa 100644
--- a/test/1002-notify-startup/src-art/Main.java
+++ b/test/1002-notify-startup/src-art/Main.java
@@ -26,9 +26,16 @@
     static final String LIBRARY_SEARCH_PATH = System.getProperty("java.library.path");
     static AtomicBoolean completed = new AtomicBoolean(false);
 
+    public static void assertFalse(boolean value) {
+        if (value) {
+            throw new Error("Expected false false");
+        }
+    }
+
     public static void main(String[] args) {
         System.loadLibrary(args[0]);
-        System.out.println("Startup completed: " + hasStartupCompleted());
+        assertFalse(hasStartupCompleted());
+
         Thread workerThread = new WorkerThread();
         workerThread.start();
         do {
@@ -41,7 +48,9 @@
         } catch (Throwable e) {
             System.err.println(e);
         }
-        System.out.println("Startup completed: " + hasStartupCompleted());
+        while (!hasStartupCompleted()) {
+            Thread.yield();
+        }
     }
 
     private static class WorkerThread extends Thread {
@@ -49,7 +58,7 @@
 
         private WeakReference<Class<?>> $noinline$loadClassInLoader() throws Exception {
             ClassLoader loader = new PathClassLoader(
-                    DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
+                    DEX_FILE, LIBRARY_SEARCH_PATH, Object.class.getClassLoader());
             Class ret = loader.loadClass("Main");
             return new WeakReference(ret);
         }
diff --git a/test/1003-metadata-section-strings/build b/test/1003-metadata-section-strings/build
deleted file mode 100755
index cd2cacd..0000000
--- a/test/1003-metadata-section-strings/build
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# make us exit on a failure
-set -e
-
-count=5000
-
-# Generate the other class.
-other_file="src-art/Other.java"
-cat >${other_file} <<EOF
-class Other {
-static String getString(int i) {
-switch(i) {
-EOF
-
-for i in $(seq 1 "$count"); do
-  echo "  case ${i}: return \"string$i\";" >> "${other_file}"
-done
-
-cat >>${other_file} <<EOF
-}
-return "not found";
-}
-}
-EOF
-
-./default-build "$@"
diff --git a/test/1003-metadata-section-strings/build.py b/test/1003-metadata-section-strings/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/1003-metadata-section-strings/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/1003-metadata-section-strings/generate-sources b/test/1003-metadata-section-strings/generate-sources
new file mode 100755
index 0000000..c3e73f5
--- /dev/null
+++ b/test/1003-metadata-section-strings/generate-sources
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+# make us exit on a failure
+set -e
+
+count=5000
+
+# Generate the other class.
+other_file="src-art/Other.java"
+cat >${other_file} <<EOF
+class Other {
+static String getString(int i) {
+switch(i) {
+EOF
+
+for i in $(seq 1 "$count"); do
+  echo "  case ${i}: return \"string$i\";" >> "${other_file}"
+done
+
+cat >>${other_file} <<EOF
+}
+return "not found";
+}
+}
+EOF
diff --git a/test/1003-metadata-section-strings/run b/test/1003-metadata-section-strings/run
deleted file mode 100644
index 9762aba..0000000
--- a/test/1003-metadata-section-strings/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 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.
-
-exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile -Xcompiler-option --resolve-startup-const-strings=true
diff --git a/test/1003-metadata-section-strings/run.py b/test/1003-metadata-section-strings/run.py
new file mode 100644
index 0000000..7fc97aa
--- /dev/null
+++ b/test/1003-metadata-section-strings/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args,
+      profile=True,
+      Xcompiler_option=[
+          "--compiler-filter=speed-profile",
+          "--resolve-startup-const-strings=true"
+      ])
diff --git a/test/1004-checker-volatile-ref-load/run b/test/1004-checker-volatile-ref-load/run
deleted file mode 100644
index bdaa71b..0000000
--- a/test/1004-checker-volatile-ref-load/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#! /bin/bash
-#
-# Copyright (C) 2019 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.
-
-# Limit the managed heap to 16 MiB to force more garbage collections.
-exec ${RUN} $@ --runtime-option -Xmx16m
diff --git a/test/1004-checker-volatile-ref-load/run.py b/test/1004-checker-volatile-ref-load/run.py
new file mode 100644
index 0000000..97dc42a
--- /dev/null
+++ b/test/1004-checker-volatile-ref-load/run.py
@@ -0,0 +1,20 @@
+#! /bin/bash
+#
+# Copyright (C) 2019 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.
+
+
+def run(ctx, args):
+  # Limit the managed heap to 16 MiB to force more garbage collections.
+  ctx.default_run(args, runtime_option=["-Xmx16m"])
diff --git a/test/1004-checker-volatile-ref-load/src/Main.java b/test/1004-checker-volatile-ref-load/src/Main.java
index 9542f36..6455e3e 100644
--- a/test/1004-checker-volatile-ref-load/src/Main.java
+++ b/test/1004-checker-volatile-ref-load/src/Main.java
@@ -36,16 +36,23 @@
   /// CHECK:       <<Foo:l\d+>> InstanceFieldGet [{{l\d+}}] field_name:Main.foo field_type:Reference loop:<<Loop:B\d+>>
   /// CHECK:       NullCheck [<<Foo>>] dex_pc:<<PC:\d+>> loop:<<Loop>>
   /// CHECK-NEXT:  InstanceFieldGet [<<Foo>>] dex_pc:<<PC>> field_name:Foo.bar field_type:Reference loop:<<Loop>>
-  /// CHECK-NEXT:      add w<<BaseRegNum:\d+>>, {{w\d+}}, #0x8 (8)
-  /// CHECK-NEXT:      adr lr, #+0x{{c|10}}
+  /* The following following Checker assertions are only valid when the compiler is emitting Baker
+     read barriers, i.e. when ART is using the Concurrent Copying (CC) garbage collector.
+
+     TODO(b/283392413, b/283780888): Re-enable the following Checker assertions (by replacing the
+     double forward slash comments with triple forward slash ones) when b/283780888 is resolved.
+
+  // CHECK-NEXT:      add w<<BaseRegNum:\d+>>, {{w\d+}}, #0x8 (8)
+  // CHECK-NEXT:      adr lr, #+0x{{c|10}}
   // The following instruction (generated by
   // `art::arm64::CodeGeneratorARM64::EmitBakerReadBarrierCbnz`) checks the
   // Marking Register (X20) and goes into the Baker read barrier thunk if MR is
   // not null. The null offset (#+0x0) in the CBNZ instruction is a placeholder
   // for the offset to the Baker read barrier thunk (which is not yet set when
   // the CFG output is emitted).
-  /// CHECK-NEXT:      cbnz x20, #+0x0
-  /// CHECK-NEXT:      ldar {{w\d+}}, [x<<BaseRegNum>>]
+  // CHECK-NEXT:      cbnz x20, #+0x0
+  // CHECK-NEXT:      ldar {{w\d+}}, [x<<BaseRegNum>>]
+  */
 
   public void test() {
     // Continually check that reading field `foo.bar` throws a
diff --git a/test/105-invoke/src/Main.java b/test/105-invoke/src/Main.java
index d5f3b62..d4e4c4e 100644
--- a/test/105-invoke/src/Main.java
+++ b/test/105-invoke/src/Main.java
@@ -71,6 +71,7 @@
     static int invoke(int a) {
         Main foo = new Main();
 
+        // clang-format off
         return foo.virI_I(a) +
                foo.virI_II(a, 1) +
                foo.virI_III(a, 1, 2) +
@@ -84,6 +85,7 @@
                statI_IIIII(a, 1, 2, 3, 4) +
                statI_IIIIII(a, 1, 2, 3, 4, 5) +
                foo.interfaceMethod(a);
+        // clang-format on
     }
 
     public static void main(String[] args) {
diff --git a/test/107-int-math2/src/Main.java b/test/107-int-math2/src/Main.java
index ec5678d..727d47c 100644
--- a/test/107-int-math2/src/Main.java
+++ b/test/107-int-math2/src/Main.java
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+// clang-format off
+
 class Main extends IntMathBase {
 
     public static boolean mBoolean1, mBoolean2;
diff --git a/test/111-unresolvable-exception/build b/test/111-unresolvable-exception/build
deleted file mode 100644
index f378df1..0000000
--- a/test/111-unresolvable-exception/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/111-unresolvable-exception/javac_post.sh b/test/111-unresolvable-exception/javac_post.sh
new file mode 100755
index 0000000..355f792
--- /dev/null
+++ b/test/111-unresolvable-exception/javac_post.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e # Stop on error - the caller script may not have this set.
+
+# Remove class available at compile time but not at run time.
+rm classes/TestException.class
diff --git a/test/111-unresolvable-exception/javac_wrapper.sh b/test/111-unresolvable-exception/javac_wrapper.sh
deleted file mode 100755
index 9e4d2a2..0000000
--- a/test/111-unresolvable-exception/javac_wrapper.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e # Stop on error - the caller script may not have this set.
-
-$JAVAC "$@"
-
-# Remove class available at compile time but not at run time.
-rm classes/TestException.class
diff --git a/test/115-native-bridge/check b/test/115-native-bridge/check
deleted file mode 100755
index e01de91..0000000
--- a/test/115-native-bridge/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# ASAN prints a warning here.
-
-diff --strip-trailing-cr -q "$1" "$2" >/dev/null \
-  && sed -e '/WARNING: ASan is ignoring requested __asan_handle_no_return/,+2d' "$4" \
-       | diff --strip-trailing-cr -q "$3" - >/dev/null
diff --git a/test/115-native-bridge/nativebridge.cc b/test/115-native-bridge/nativebridge.cc
index 3209449..a5541ce 100644
--- a/test/115-native-bridge/nativebridge.cc
+++ b/test/115-native-bridge/nativebridge.cc
@@ -557,18 +557,18 @@
                                      void* context) {
   if (sig == SIGSEGV) {
 #if defined(__arm__)
-    struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
-    struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
-    sc->arm_pc += 2;          // Skip instruction causing segv & sigill.
+    ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+    mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+    mc->arm_pc += 2;          // Skip instruction causing segv & sigill.
 #elif defined(__aarch64__)
-    struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
-    struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
-    sc->pc += 4;          // Skip instruction causing segv & sigill.
+    ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+    mcontext_t* mc = reinterpret_cast<mcontext_t*>(&uc->uc_mcontext);
+    mc->pc += 4;          // Skip instruction causing segv & sigill.
 #elif defined(__i386__)
-    struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
+    ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
     uc->CTX_EIP += 3;
 #elif defined(__x86_64__)
-    struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
+    ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
     uc->CTX_EIP += 2;
 #else
     UNUSED(context);
diff --git a/test/115-native-bridge/run b/test/115-native-bridge/run
deleted file mode 100644
index 8ce44a9..0000000
--- a/test/115-native-bridge/run
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2012 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.
-
-ARGS=${@}
-
-BRIDGE_SO=libnativebridgetestd.so
-if echo ${ARGS} | grep -q " -O"; then
-  BRIDGE_SO=libnativebridgetest.so
-fi
-
-# Use libnativebridgetest as a native bridge, start NativeBridgeMain (Main is JniTest main file).
-LIBPATH=$(echo ${ARGS} | sed -r 's/.*Djava.library.path=([^ ]*) .*/\1/')
-# Trim all but the last entry in LIBPATH, which will be nativetest[64]
-LIBPATH=${LIBPATH##*:}
-ln -sf ${LIBPATH}/$BRIDGE_SO .
-touch libarttest.so
-touch libarttestd.so
-touch libinvalid.so
-ln -sf ${LIBPATH}/libarttest.so libarttest2.so
-ln -sf ${LIBPATH}/libarttestd.so libarttestd2.so
-
-# pwd likely has /, so it's a pain to put that into a sed rule.
-LEFT=$(echo ${ARGS} | sed -r 's/-Djava.library.path.*//')
-RIGHT=$(echo ${ARGS} | sed -r 's/.*Djava.library.path[^ ]* //')
-MODARGS="${LEFT} -Djava.library.path=`pwd` ${RIGHT}"
-exec ${RUN} --runtime-option -Xforce-nb-testing --runtime-option -XX:NativeBridge=$BRIDGE_SO ${MODARGS} NativeBridgeMain
diff --git a/test/115-native-bridge/run.py b/test/115-native-bridge/run.py
new file mode 100644
index 0000000..619bde0
--- /dev/null
+++ b/test/115-native-bridge/run.py
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Copyright (C) 2012 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.
+
+import re
+
+
+def run(ctx, args):
+  bridge_so = "libnativebridgetest.so" if args.O else "libnativebridgetestd.so"
+  test_dir = ctx.env.DEX_LOCATION
+
+  # Use libnativebridgetest as a native bridge, start NativeBridgeMain (Main is JniTest main file).
+  for i, opt in enumerate(args.runtime_option):
+    if opt.startswith("-Djava.library.path="):
+      libpath = opt.split(":")[-1]  # last entry in libpath is nativetest[64]
+      args.runtime_option[i] = "-Djava.library.path=" + test_dir
+
+  assert libpath
+  ctx.run(f"ln -sf {libpath}/{bridge_so} {test_dir}/.")
+  ctx.run(
+      f"touch {test_dir}/libarttest.so {test_dir}/libarttestd.so {test_dir}/libinvalid.so"
+  )
+  ctx.run(f"ln -sf {libpath}/libarttest.so {test_dir}/libarttest2.so")
+  ctx.run(f"ln -sf {libpath}/libarttestd.so {test_dir}/libarttestd2.so")
+
+  ctx.default_run(
+      args,
+      runtime_option=["-Xforce-nb-testing", f"-XX:NativeBridge={bridge_so}"],
+      main="NativeBridgeMain")
+
+  # ASAN prints a warning here.
+  ctx.run(
+      fr"sed -i '/WARNING: ASan is ignoring requested __asan_handle_no_return/,+2d' '{args.stderr_file}'"
+  )
diff --git a/test/116-nodex2oat/run b/test/116-nodex2oat/run
deleted file mode 100755
index 9063685..0000000
--- a/test/116-nodex2oat/run
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-flags="${@}"
-
-# This test is supposed to test without oat files, so doesn't work for prebuild. Make sure that
-# flag isn't set, or complain.
-# Note: prebuild is the default.
-if [[ "${flags}" == *--prebuild* || "${flags}" != *--no-prebuild* ]] ; then
-  echo "Test 116-nodex2oat cannot run in prebuild mode."
-  exit 1
-fi
-
-${RUN} ${flags}
diff --git a/test/116-nodex2oat/run.py b/test/116-nodex2oat/run.py
new file mode 100644
index 0000000..5b38316
--- /dev/null
+++ b/test/116-nodex2oat/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+
+def run(ctx, args):
+  # This test is supposed to test without oat files, so doesn't work for prebuild.
+  assert not args.prebuild
+
+  ctx.default_run(args)
diff --git a/test/116-nodex2oat/src/Main.java b/test/116-nodex2oat/src/Main.java
index 5491c49..0e675c8 100644
--- a/test/116-nodex2oat/src/Main.java
+++ b/test/116-nodex2oat/src/Main.java
@@ -15,10 +15,10 @@
  */
 
 public class Main {
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
-    System.out.println("Has oat is " + hasOatFile() + ".");
-  }
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
+        System.out.println("Has oat is " + hasOatFile() + ".");
+    }
 
-  private native static boolean hasOatFile();
+    private native static boolean hasOatFile();
 }
diff --git a/test/118-noimage-dex2oat/check b/test/118-noimage-dex2oat/check
deleted file mode 100755
index 90ffdf8..0000000
--- a/test/118-noimage-dex2oat/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Strip the process pids and line numbers from exact error messages.
-sed -e '/^dalvikvm.* E.*\] /d' "$4" > "$4.tmp"
-
-diff --strip-trailing-cr -q "$1" "$2" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4.tmp" >/dev/null
diff --git a/test/118-noimage-dex2oat/run b/test/118-noimage-dex2oat/run
deleted file mode 100644
index 1b6251d..0000000
--- a/test/118-noimage-dex2oat/run
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-flags="$@"
-
-# This test is supposed to test without oat files, so doesn't work for prebuild. Make sure that
-# flag isn't set, or complain.
-# Note: prebuild is the default.
-if [[ "${flags}" == *--prebuild* || "${flags}" != *--no-prebuild* ]] ; then
-  echo "Test 118-noimage-dex2oat cannot run in prebuild mode."
-  exit 1
-fi
-
-# Force relocation otherwise we will just use the already created core.oat/art pair.
-# Note: relocate is the default.
-if [[ "${flags}" == *--no-relocate* ]] ; then
-  echo "Test 118-noimage-dex2oat is not intended to run in no-relocate mode."
-  exit 1
-fi
-
-
-# Make sure we can run without an oat file.
-echo "Run -Xnoimage-dex2oat"
-${RUN} ${flags} --runtime-option -Xnoimage-dex2oat
-return_status1=$?
-
-# Make sure we can run with the oat file.
-echo "Run -Ximage-dex2oat"
-${RUN} ${flags} --runtime-option -Ximage-dex2oat
-return_status2=$?
-
-# Make sure we can run with the default settings.
-echo "Run default"
-${RUN} ${flags}
-return_status3=$?
-
-# Make sure we don't silently ignore an early failure.
-(exit $return_status1) && (exit $return_status2) && (exit $return_status3)
diff --git a/test/118-noimage-dex2oat/run.py b/test/118-noimage-dex2oat/run.py
new file mode 100644
index 0000000..88efcca
--- /dev/null
+++ b/test/118-noimage-dex2oat/run.py
@@ -0,0 +1,38 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+
+def run(ctx, args):
+  # This test is supposed to test without oat files, so doesn't work for prebuild.
+  assert not args.prebuild
+
+  # Force relocation otherwise we will just use the already created core.oat/art pair.
+  assert args.relocate
+
+  # Make sure we can run without an oat file.
+  ctx.echo("Run -Xnoimage-dex2oat")
+  ctx.default_run(args, runtime_option=["-Xnoimage-dex2oat"])
+
+  # Make sure we can run with the oat file.
+  ctx.echo("Run -Ximage-dex2oat")
+  ctx.default_run(args, runtime_option=["-Ximage-dex2oat"])
+
+  # Make sure we can run with the default settings.
+  ctx.echo("Run default")
+  ctx.default_run(args)
+
+  # Strip the process pids and line numbers from exact error messages.
+  ctx.run(fr"sed -i '/^dalvikvm.* E.*\] /d' '{args.stderr_file}'")
diff --git a/test/118-noimage-dex2oat/src/Main.java b/test/118-noimage-dex2oat/src/Main.java
index cc19107..f83cadc 100644
--- a/test/118-noimage-dex2oat/src/Main.java
+++ b/test/118-noimage-dex2oat/src/Main.java
@@ -18,68 +18,68 @@
 import java.lang.reflect.Method;
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[0]);
-    boolean hasImage = hasImage();
-    String instructionSet = VMRuntime.getCurrentInstructionSet();
-    boolean isBootClassPathOnDisk = VMRuntime.isBootClassPathOnDisk(instructionSet);
-    System.out.println(
-        "Has image is " + hasImage + ", is image dex2oat enabled is "
-        + isImageDex2OatEnabled() + ", is BOOTCLASSPATH on disk is "
-        + isBootClassPathOnDisk + ".");
+    public static void main(String[] args) throws Exception {
+        System.loadLibrary(args[0]);
+        boolean hasImage = hasImage();
+        String instructionSet = VMRuntime.getCurrentInstructionSet();
+        boolean isBootClassPathOnDisk = VMRuntime.isBootClassPathOnDisk(instructionSet);
+        System.out.println(
+                "Has image is " + hasImage + ", is image dex2oat enabled is "
+                + isImageDex2OatEnabled() + ", is BOOTCLASSPATH on disk is "
+                + isBootClassPathOnDisk + ".");
 
-    if (hasImage && !isImageDex2OatEnabled()) {
-      throw new Error("Image with dex2oat disabled runs with an oat file");
-    } else if (!hasImage && isImageDex2OatEnabled()) {
-      throw new Error("Image with dex2oat enabled runs without an oat file");
-    }
-    if (hasImage && !isBootClassPathOnDisk) {
-      throw new Error("Image with dex2oat disabled runs with an image file");
-    } else if (!hasImage && isBootClassPathOnDisk) {
-      throw new Error("Image with dex2oat enabled runs without an image file");
+        if (hasImage && !isImageDex2OatEnabled()) {
+            throw new Error("Image with dex2oat disabled runs with an oat file");
+        } else if (!hasImage && isImageDex2OatEnabled()) {
+            throw new Error("Image with dex2oat enabled runs without an oat file");
+        }
+        if (hasImage && !isBootClassPathOnDisk) {
+            throw new Error("Image with dex2oat disabled runs with an image file");
+        } else if (!hasImage && isBootClassPathOnDisk) {
+            throw new Error("Image with dex2oat enabled runs without an image file");
+        }
+
+        testB18485243();
     }
 
-    testB18485243();
-  }
+    private native static boolean hasImage();
 
-  private native static boolean hasImage();
+    private native static boolean isImageDex2OatEnabled();
 
-  private native static boolean isImageDex2OatEnabled();
+    private static class VMRuntime {
+        private static final Method getCurrentInstructionSetMethod;
+        private static final Method isBootClassPathOnDiskMethod;
+        static {
+            try {
+                Class<?> c = Class.forName("dalvik.system.VMRuntime");
+                getCurrentInstructionSetMethod = c.getDeclaredMethod("getCurrentInstructionSet");
+                isBootClassPathOnDiskMethod =
+                        c.getDeclaredMethod("isBootClassPathOnDisk", String.class);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
 
-  private static class VMRuntime {
-    private static final Method getCurrentInstructionSetMethod;
-    private static final Method isBootClassPathOnDiskMethod;
-    static {
-        try {
-            Class<?> c = Class.forName("dalvik.system.VMRuntime");
-            getCurrentInstructionSetMethod = c.getDeclaredMethod("getCurrentInstructionSet");
-            isBootClassPathOnDiskMethod = c.getDeclaredMethod("isBootClassPathOnDisk",
-                                                              String.class);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
+        public static String getCurrentInstructionSet() throws Exception {
+            return (String) getCurrentInstructionSetMethod.invoke(null);
+        }
+        public static boolean isBootClassPathOnDisk(String instructionSet) throws Exception {
+            return (boolean) isBootClassPathOnDiskMethod.invoke(null, instructionSet);
         }
     }
 
-    public static String getCurrentInstructionSet() throws Exception {
-      return (String) getCurrentInstructionSetMethod.invoke(null);
+    private static void testB18485243() throws Exception {
+        Class<?> k = Class.forName("B18485243");
+        Object o = k.newInstance();
+        Method m = k.getDeclaredMethod("run");
+        try {
+            m.invoke(o);
+        } catch (InvocationTargetException e) {
+            Throwable actual = e.getTargetException();
+            if (!(actual instanceof IncompatibleClassChangeError)) {
+                throw new AssertionError("Expected IncompatibleClassChangeError", actual);
+            }
+        }
+        System.out.println("testB18485243 PASS");
     }
-    public static boolean isBootClassPathOnDisk(String instructionSet) throws Exception {
-      return (boolean) isBootClassPathOnDiskMethod.invoke(null, instructionSet);
-    }
-  }
-
-  private static void testB18485243() throws Exception {
-    Class<?> k = Class.forName("B18485243");
-    Object o = k.newInstance();
-    Method m = k.getDeclaredMethod("run");
-    try {
-      m.invoke(o);
-    } catch (InvocationTargetException e) {
-      Throwable actual = e.getTargetException();
-      if (!(actual instanceof IncompatibleClassChangeError)) {
-        throw new AssertionError("Expected IncompatibleClassChangeError", actual);
-      }
-    }
-    System.out.println("testB18485243 PASS");
-  }
 }
diff --git a/test/122-npe/build.py b/test/122-npe/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/122-npe/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/122-npe/test-metadata.json b/test/122-npe/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/122-npe/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/124-missing-classes/build b/test/124-missing-classes/build
deleted file mode 100644
index a40cbc9..0000000
--- a/test/124-missing-classes/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2012 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/124-missing-classes/javac_post.sh b/test/124-missing-classes/javac_post.sh
new file mode 100755
index 0000000..39e26bb
--- /dev/null
+++ b/test/124-missing-classes/javac_post.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e # Stop on error - the caller script may not have this set.
+
+# Some classes are available at compile time but not at run time.
+rm 'classes/MissingClass.class' 'classes/Main$MissingInnerClass.class'
diff --git a/test/124-missing-classes/javac_wrapper.sh b/test/124-missing-classes/javac_wrapper.sh
deleted file mode 100755
index c6c234d..0000000
--- a/test/124-missing-classes/javac_wrapper.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e # Stop on error - the caller script may not have this set.
-
-# Some classes are available at compile time...
-$JAVAC "$@"
-
-# ...but not at run time.
-rm 'classes/MissingClass.class' 'classes/Main$MissingInnerClass.class'
diff --git a/test/126-miranda-multidex/build b/test/126-miranda-multidex/build
deleted file mode 100644
index c827e55..0000000
--- a/test/126-miranda-multidex/build
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Stop if something fails.
-set -e
-
-# Signal to default-build that this is a multidex test.
-mkdir src-multidex
-./default-build "$@"
diff --git a/test/126-miranda-multidex/build.py b/test/126-miranda-multidex/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/126-miranda-multidex/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/126-miranda-multidex/generate-sources b/test/126-miranda-multidex/generate-sources
new file mode 100755
index 0000000..f42ba10
--- /dev/null
+++ b/test/126-miranda-multidex/generate-sources
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+# Stop if something fails.
+set -e
+
+# Signal to default-build that this is a multidex test.
+mkdir src-multidex
+touch src-multidex/Empty.java
diff --git a/test/126-miranda-multidex/javac_post.sh b/test/126-miranda-multidex/javac_post.sh
new file mode 100755
index 0000000..79a9cef
--- /dev/null
+++ b/test/126-miranda-multidex/javac_post.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e # Stop on error - the caller script may not have this set.
+
+if [[ "$1" == "classes2" ]]; then
+  mv classes/MirandaInterface.class classes2
+fi
diff --git a/test/126-miranda-multidex/javac_wrapper.sh b/test/126-miranda-multidex/javac_wrapper.sh
deleted file mode 100755
index 71cafa1..0000000
--- a/test/126-miranda-multidex/javac_wrapper.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e # Stop on error - the caller script may not have this set.
-
-if [[ "$*" != *"classes2"* ]]; then
-  # First invocation: compile src/ files.
-  $JAVAC "$@"
-else
-  # Second invocation: move MirandaInterface.class for placement in
-  # a secondary dex file. There are no other source files for the
-  # secondary DEX so no compilation required.
-  mv classes/MirandaInterface.class classes2
-fi
-exit $?
diff --git a/test/126-miranda-multidex/run b/test/126-miranda-multidex/run
deleted file mode 100755
index abd63cb..0000000
--- a/test/126-miranda-multidex/run
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-${RUN} $@
-return_status1=$?
-
-# The problem was first exposed in a no-verify setting, as that changes the resolution path
-# taken. Make sure we also test in that environment.
-${RUN} --no-verify ${@}
-return_status2=$?
-
-# Make sure we don't silently ignore an early failure.
-(exit $return_status1) && (exit $return_status2)
diff --git a/test/126-miranda-multidex/run.py b/test/126-miranda-multidex/run.py
new file mode 100644
index 0000000..1896315
--- /dev/null
+++ b/test/126-miranda-multidex/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+import sys
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # The problem was first exposed in a no-verify setting, as that changes the resolution path
+  # taken. Make sure we also test in that environment.
+  ctx.default_run(args, no_verify=True)
diff --git a/test/127-checker-secondarydex/build b/test/127-checker-secondarydex/build
deleted file mode 100755
index f378df1..0000000
--- a/test/127-checker-secondarydex/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/127-checker-secondarydex/javac_post.sh b/test/127-checker-secondarydex/javac_post.sh
new file mode 100755
index 0000000..e9d5d18
--- /dev/null
+++ b/test/127-checker-secondarydex/javac_post.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e # Stop on error - the caller script may not have this set.
+
+mkdir classes-ex
+mv classes/Super.class classes-ex
diff --git a/test/127-checker-secondarydex/javac_wrapper.sh b/test/127-checker-secondarydex/javac_wrapper.sh
deleted file mode 100755
index 7fe6a55..0000000
--- a/test/127-checker-secondarydex/javac_wrapper.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e # Stop on error - the caller script may not have this set.
-
-$JAVAC "$@"
-
-mkdir classes-ex
-mv classes/Super.class classes-ex
diff --git a/test/127-checker-secondarydex/run b/test/127-checker-secondarydex/run
deleted file mode 100755
index d8c3c79..0000000
--- a/test/127-checker-secondarydex/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Use secondary switch to add secondary dex file to class path.
-exec ${RUN} "${@}" --secondary
diff --git a/test/127-checker-secondarydex/run.py b/test/127-checker-secondarydex/run.py
new file mode 100644
index 0000000..2746221
--- /dev/null
+++ b/test/127-checker-secondarydex/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+
+def run(ctx, args):
+  # Use secondary switch to add secondary dex file to class path.
+  ctx.default_run(args, secondary=True)
diff --git a/test/130-hprof/run b/test/130-hprof/run
deleted file mode 100644
index 73a984e..0000000
--- a/test/130-hprof/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-# Currently app images aren't unloaded when dex files are unloaded.
-exec ${RUN} $@ --no-secondary-app-image
diff --git a/test/130-hprof/run.py b/test/130-hprof/run.py
new file mode 100644
index 0000000..c3fda2f
--- /dev/null
+++ b/test/130-hprof/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2020 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.
+
+
+def run(ctx, args):
+  # Currently app images aren't unloaded when dex files are unloaded.
+  ctx.default_run(args, secondary_app_image=False)
diff --git a/test/133-static-invoke-super/run b/test/133-static-invoke-super/run
deleted file mode 100755
index e27a622..0000000
--- a/test/133-static-invoke-super/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2012 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.
-
-# As this is a performance test we always use the non-debug build.
-exec ${RUN} "${@/#libartd.so/libart.so}"
diff --git a/test/133-static-invoke-super/run.py b/test/133-static-invoke-super/run.py
new file mode 100644
index 0000000..dbb09fd
--- /dev/null
+++ b/test/133-static-invoke-super/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2012 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.
+
+
+def run(ctx, args):
+  # As this is a performance test we always use the non-debug build.
+  ctx.default_run(args, lib="libart.so")
diff --git a/test/1336-short-finalizer-timeout/build.py b/test/1336-short-finalizer-timeout/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/1336-short-finalizer-timeout/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/1336-short-finalizer-timeout/expected-stdout.txt b/test/1336-short-finalizer-timeout/expected-stdout.txt
index 496e0e6..802b89e 100644
--- a/test/1336-short-finalizer-timeout/expected-stdout.txt
+++ b/test/1336-short-finalizer-timeout/expected-stdout.txt
@@ -3,4 +3,3 @@
 Finalizer started and snoozing...
 Finalizer done snoozing.
 Finalizer sleeping forever now.
-exit status: 2
diff --git a/test/1336-short-finalizer-timeout/run b/test/1336-short-finalizer-timeout/run
deleted file mode 100755
index 4a07034..0000000
--- a/test/1336-short-finalizer-timeout/run
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# The test logs error messages which is expected, discard them.
-export ANDROID_LOG_TAGS='*:f'
-
-# Squash the exit status and put it in expected
-./default-run --external-log-tags "$@" --runtime-option -XX:FinalizerTimeoutMs=500
-echo "exit status:" $?
diff --git a/test/1336-short-finalizer-timeout/run.py b/test/1336-short-finalizer-timeout/run.py
new file mode 100644
index 0000000..3c74a56
--- /dev/null
+++ b/test/1336-short-finalizer-timeout/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args,
+      android_log_tags="*:f",
+      runtime_option=["-XX:FinalizerTimeoutMs=500"],
+      expected_exit_code=2)
diff --git a/test/1336-short-finalizer-timeout/src/Main.java b/test/1336-short-finalizer-timeout/src/Main.java
index 1a28a64..3f024b1 100644
--- a/test/1336-short-finalizer-timeout/src/Main.java
+++ b/test/1336-short-finalizer-timeout/src/Main.java
@@ -22,7 +22,8 @@
  * Test a class with a bad finalizer in an environment with a short finalizer timeout.
  *
  * This test is inherently flaky. It assumes that the system will schedule the finalizer daemon
- * and finalizer watchdog daemon enough to reach the timeout and throwing the fatal exception.
+ * and finalizer watchdog daemon soon and often enough to reach the timeout and throw the fatal
+ * exception before we time out here.
  * Largely cloned from 030-bad-finalizer.
  */
 public class Main {
@@ -49,7 +50,7 @@
         snooze(9800);
 
         // We should not get here, since it should only take 5.5 seconds for the timed out
-        // fiinalizer to kill the process.
+        // finalizer to kill the process.
         System.out.println("UNREACHABLE");
         System.exit(0);
     }
diff --git a/test/1336-short-finalizer-timeout/test-metadata.json b/test/1336-short-finalizer-timeout/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/1336-short-finalizer-timeout/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/1337-gc-coverage/check b/test/1337-gc-coverage/check
deleted file mode 100755
index 67fcafb..0000000
--- a/test/1337-gc-coverage/check
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Check that the string "error" isn't present
-grep -vq error "$2"  \
-  && diff --strip-trailing-cr -q "$3" "$4" >/dev/null
diff --git a/test/1337-gc-coverage/run.py b/test/1337-gc-coverage/run.py
new file mode 100644
index 0000000..9216c06
--- /dev/null
+++ b/test/1337-gc-coverage/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # Check that the string "error" isn't present (delete all non-error lines)
+  ctx.run(fr"sed -i '/error/!d' '{args.stdout_file}'")
diff --git a/test/1338-gc-no-los/run b/test/1338-gc-no-los/run
deleted file mode 100755
index f3c43fb..0000000
--- a/test/1338-gc-no-los/run
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-./default-run "$@" --runtime-option -XX:LargeObjectSpace=disabled
diff --git a/test/1338-gc-no-los/run.py b/test/1338-gc-no-los/run.py
new file mode 100644
index 0000000..1c64d11
--- /dev/null
+++ b/test/1338-gc-no-los/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, runtime_option=["-XX:LargeObjectSpace=disabled"])
diff --git a/test/1339-dead-reference-safe/build.py b/test/1339-dead-reference-safe/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/1339-dead-reference-safe/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/1339-dead-reference-safe/check b/test/1339-dead-reference-safe/check
deleted file mode 100644
index e7f5f96..0000000
--- a/test/1339-dead-reference-safe/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# DeadReferenceSafe result differs for interpreted mode. A real failure
-# will produce an extra line anyway.
-
-diff --ignore-matching-lines="DeadReferenceSafe count:" -q $1 $2 \
-  && diff --strip-trailing-cr -q "$3" "$4" >/dev/null
diff --git a/test/1339-dead-reference-safe/run.py b/test/1339-dead-reference-safe/run.py
new file mode 100644
index 0000000..6b81ed4
--- /dev/null
+++ b/test/1339-dead-reference-safe/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # DeadReferenceSafe result differs for interpreted mode. A real failure
+  # will produce an extra line anyway.
+  ctx.run(
+      fr"sed -i -E 's/^(.*DeadReferenceSafe count: )[0-9]+(.*)$/\1N\2/' '{args.stdout_file}'"
+  )
diff --git a/test/1339-dead-reference-safe/test-metadata.json b/test/1339-dead-reference-safe/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/1339-dead-reference-safe/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/137-cfi/cfi.cc b/test/137-cfi/cfi.cc
index 4e756d2..cd5b4cf 100644
--- a/test/137-cfi/cfi.cc
+++ b/test/137-cfi/cfi.cc
@@ -28,7 +28,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
-#include <backtrace/Backtrace.h>
+#include <unwindstack/AndroidUnwinder.h>
 
 #include "base/file_utils.h"
 #include "base/logging.h"
@@ -106,47 +106,55 @@
 
 // Helper to look for a sequence in the stack trace.
 #if __linux__
-static bool CheckStack(Backtrace* bt, const std::vector<std::string>& seq) {
+static bool CheckStack(unwindstack::AndroidUnwinder& unwinder,
+                       unwindstack::AndroidUnwinderData& data,
+                       const std::vector<std::string>& seq) {
   size_t cur_search_index = 0;  // The currently active index in seq.
   CHECK_GT(seq.size(), 0U);
 
-  bool any_empty_name = false;
-  for (size_t i = 0; i < bt->NumFrames(); i++) {
-    const backtrace_frame_data_t* frame = bt->GetFrame(i);
-    if (BacktraceMap::IsValid(frame->map)) {
-      if (cur_search_index < seq.size()) {
-        LOG(INFO) << "Got " << frame->func_name << ", looking for " << seq[cur_search_index];
-        if (frame->func_name.find(seq[cur_search_index]) != std::string::npos) {
-          cur_search_index++;
-        }
+  bool ok = true;
+  for (const unwindstack::FrameData& frame : data.frames) {
+    if (frame.map_info == nullptr) {
+      printf("Error: No map_info for frame #%02zu\n", frame.num);
+      ok = false;
+      continue;
+    }
+    const std::string& function_name = frame.function_name;
+    if (cur_search_index < seq.size()) {
+      LOG(INFO) << "Got " << function_name << ", looking for " << seq[cur_search_index];
+      if (function_name.find(seq[cur_search_index]) != std::string::npos) {
+        cur_search_index++;
       }
     }
-    any_empty_name |= frame->func_name.empty();
-    if (frame->func_name == "main") {
+    if (function_name == "main") {
       break;
     }
+#if !defined(__ANDROID__) && defined(__BIONIC__ )  // host-bionic
+    // TODO(b/182810709): Unwinding is broken on host-bionic so we expect some empty frames.
+#else
+    const std::string& lib_name = frame.map_info->name();
+    if (!kIsTargetBuild && lib_name.find("libc.so") != std::string::npos) {
+      // TODO(b/254626913): Unwinding can fail for libc on host.
+    } else if (function_name.empty()) {
+      printf("Error: No function name for frame #%02zu\n", frame.num);
+      ok = false;
+    }
+#endif
   }
 
   if (cur_search_index < seq.size()) {
-    printf("Cannot find %s in backtrace:\n", seq[cur_search_index].c_str());
-  } else if (any_empty_name) {
-#if defined(__BIONIC__ ) && !defined(__ANDROID__)
-    // TODO(b/182810709): Unwinding is broken on host-bionic so we expect some empty frames.
-    return true;
-#else
-    printf("Missing frames in backtrace:\n");
-#endif
-  } else {
-    return true;
+    printf("Error: Cannot find %s\n", seq[cur_search_index].c_str());
+    ok = false;
   }
 
-  for (Backtrace::const_iterator it = bt->begin(); it != bt->end(); ++it) {
-    if (BacktraceMap::IsValid(it->map)) {
-      printf("  %s\n", Backtrace::FormatFrameData(&*it).c_str());
+  if (!ok) {
+    printf("Backtrace:\n");
+    for (const unwindstack::FrameData& frame : data.frames) {
+      printf("  %s\n", unwinder.FormatFrame(frame).c_str());
     }
   }
 
-  return false;
+  return ok;
 }
 
 static void MoreErrorInfo(pid_t pid, bool sig_quit_on_fail) {
@@ -166,13 +174,11 @@
 #if __linux__
   MutexLock mu(Thread::Current(), *GetNativeDebugInfoLock());  // Avoid races with the JIT thread.
 
-  std::unique_ptr<Backtrace> bt(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, GetTid()));
-  if (!bt->Unwind(0, nullptr)) {
+  unwindstack::AndroidLocalUnwinder unwinder;
+  unwindstack::AndroidUnwinderData data;
+  if (!unwinder.Unwind(data)) {
     printf("Cannot unwind in process.\n");
     return JNI_FALSE;
-  } else if (bt->NumFrames() == 0) {
-    printf("No frames for unwind in process.\n");
-    return JNI_FALSE;
   }
 
   // We cannot really parse an exact stack, as the optimizing compiler may inline some functions.
@@ -186,7 +192,7 @@
       "Main.main"                        // The Java entry method.
   };
 
-  bool result = CheckStack(bt.get(), seq);
+  bool result = CheckStack(unwinder, data, seq);
   if (!kCauseSegfault) {
     return result ? JNI_TRUE : JNI_FALSE;
   } else {
@@ -260,17 +266,13 @@
     return JNI_FALSE;
   }
 
-  std::unique_ptr<Backtrace> bt(Backtrace::Create(pid, BACKTRACE_CURRENT_THREAD));
   bool result = true;
-  if (!bt->Unwind(0, nullptr)) {
+  unwindstack::AndroidRemoteUnwinder unwinder(pid);
+  unwindstack::AndroidUnwinderData data;
+  if (!unwinder.Unwind(data)) {
     printf("Cannot unwind other process.\n");
     result = false;
-  } else if (bt->NumFrames() == 0) {
-    printf("No frames for unwind of other process.\n");
-    result = false;
-  }
-
-  if (result) {
+  } else {
     // See comment in unwindInProcess for non-exact stack matching.
     // "mini-debug-info" does not include parameters to save space.
     std::vector<std::string> seq = {
@@ -280,7 +282,7 @@
         "Main.main"                         // The Java entry method.
     };
 
-    result = CheckStack(bt.get(), seq);
+    result = CheckStack(unwinder, data, seq);
   }
 
   constexpr bool kSigQuitOnFail = true;
diff --git a/test/137-cfi/run b/test/137-cfi/run
deleted file mode 100755
index f09765f..0000000
--- a/test/137-cfi/run
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2008 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.
-
-# Test with full DWARF debugging information.
-# Check full signatures of methods.
-${RUN} "$@" -Xcompiler-option --generate-debug-info \
-  --args --test-local --args --test-remote
-return_status1=$?
-
-# The option jitthreshold:0 ensures that if we run the test in JIT mode,
-# there will be JITed frames on the callstack (it synchronously JITs on first use).
-${RUN} "$@" -Xcompiler-option --generate-debug-info \
-  --runtime-option -Xjitthreshold:0 \
-  --args --test-local --args --test-remote
-return_status2=$?
-
-# Test with minimal compressed debugging information.
-# Check only method names (parameters are omitted to save space).
-# Check only remote unwinding since decompression is disabled in local unwinds (b/27391690).
-${RUN} "$@" -Xcompiler-option --generate-mini-debug-info \
-  --runtime-option -Xjitthreshold:0 \
-  --args --test-remote
-return_status3=$?
-
-# Make sure we don't silently ignore an early failure.
-(exit $return_status1) && (exit $return_status2) && (exit $return_status3)
diff --git a/test/137-cfi/run.py b/test/137-cfi/run.py
new file mode 100644
index 0000000..987d147
--- /dev/null
+++ b/test/137-cfi/run.py
@@ -0,0 +1,43 @@
+#!/bin/bash
+#
+# Copyright (C) 2008 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.
+
+import sys
+
+
+def run(ctx, args):
+  # Test with full DWARF debugging information.
+  # Check full signatures of methods.
+  ctx.default_run(
+      args,
+      Xcompiler_option=["--generate-debug-info"],
+      test_args=["--test-local", "--test-remote"])
+
+  # The option jitthreshold:0 ensures that if we run the test in JIT mode,
+  # there will be JITed frames on the callstack (it synchronously JITs on first use).
+  ctx.default_run(
+      args,
+      Xcompiler_option=["--generate-debug-info"],
+      runtime_option=["-Xjitthreshold:0"],
+      test_args=["--test-local", "--test-remote"])
+
+  # Test with minimal compressed debugging information.
+  # Check only method names (parameters are omitted to save space).
+  # Check only remote unwinding since decompression is disabled in local unwinds (b/27391690).
+  ctx.default_run(
+      args,
+      Xcompiler_option=["--generate-mini-debug-info"],
+      runtime_option=["-Xjitthreshold:0"],
+      test_args=["--test-remote"])
diff --git a/test/138-duplicate-classes-check2/build b/test/138-duplicate-classes-check2/build
deleted file mode 100755
index 6b298f7..0000000
--- a/test/138-duplicate-classes-check2/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/138-duplicate-classes-check2/javac_post.sh b/test/138-duplicate-classes-check2/javac_post.sh
new file mode 100755
index 0000000..ce8b82f
--- /dev/null
+++ b/test/138-duplicate-classes-check2/javac_post.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e # Stop on error - the caller script may not have this set.
+
+# Remove one A.class from classes-ex
+rm -f classes-ex/A.class
diff --git a/test/138-duplicate-classes-check2/javac_wrapper.sh b/test/138-duplicate-classes-check2/javac_wrapper.sh
deleted file mode 100755
index 68c352f..0000000
--- a/test/138-duplicate-classes-check2/javac_wrapper.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e # Stop on error - the caller script may not have this set.
-
-$JAVAC "$@"
-
-# Remove one A.class from classes-ex
-rm -f classes-ex/A.class
diff --git a/test/139-register-natives/check b/test/139-register-natives/check
deleted file mode 100755
index f9b35f6..0000000
--- a/test/139-register-natives/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Strip any JNI registration error messages
-sed -e '/jni_entrypoints/d' -e '/jni_internal.cc/d' "$4" > "$4.tmp"
-
-diff --strip-trailing-cr -q "$1" "$2" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4.tmp" >/dev/null
diff --git a/test/139-register-natives/run.py b/test/139-register-natives/run.py
new file mode 100644
index 0000000..a4b0436
--- /dev/null
+++ b/test/139-register-natives/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # Strip any JNI registration error messages
+  ctx.run(
+      fr"sed -i -E '/(jni_entrypoints|jni_internal.cc)/d' '{args.stderr_file}'")
diff --git a/test/141-class-unload/profile b/test/141-class-unload/profile
new file mode 100644
index 0000000..709f6ea
--- /dev/null
+++ b/test/141-class-unload/profile
@@ -0,0 +1,3 @@
+LMain;
+LIface;
+LImpl2;
diff --git a/test/141-class-unload/run b/test/141-class-unload/run
deleted file mode 100644
index 73a984e..0000000
--- a/test/141-class-unload/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-# Currently app images aren't unloaded when dex files are unloaded.
-exec ${RUN} $@ --no-secondary-app-image
diff --git a/test/141-class-unload/run.py b/test/141-class-unload/run.py
new file mode 100644
index 0000000..691d209
--- /dev/null
+++ b/test/141-class-unload/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2020 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.
+
+
+def run(ctx, args):
+  # Currently app images aren't unloaded when dex files are unloaded.
+  ctx.default_run(args, profile=True, secondary_app_image=False)
diff --git a/test/141-class-unload/src-ex/ConflictImpl.java b/test/141-class-unload/src-ex/ConflictImpl.java
new file mode 100644
index 0000000..8144c7e
--- /dev/null
+++ b/test/141-class-unload/src-ex/ConflictImpl.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class ConflictImpl extends ConflictSuper {
+}
diff --git a/test/141-class-unload/src-ex/Impl.java b/test/141-class-unload/src-ex/Impl.java
new file mode 100644
index 0000000..978b3de
--- /dev/null
+++ b/test/141-class-unload/src-ex/Impl.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Impl implements Iface {
+}
diff --git a/test/141-class-unload/src/ConflictIface.java b/test/141-class-unload/src/ConflictIface.java
new file mode 100644
index 0000000..033349f
--- /dev/null
+++ b/test/141-class-unload/src/ConflictIface.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public interface ConflictIface {
+  public default Class<?> method1() { return ConflictIface.class; }
+  public default Class<?> method2() { return ConflictIface.class; }
+  public default Class<?> method3() { return ConflictIface.class; }
+  public default Class<?> method4() { return ConflictIface.class; }
+  public default Class<?> method5() { return ConflictIface.class; }
+  public default Class<?> method6() { return ConflictIface.class; }
+  public default Class<?> method7() { return ConflictIface.class; }
+  public default Class<?> method8() { return ConflictIface.class; }
+  public default Class<?> method9() { return ConflictIface.class; }
+  public default Class<?> method10() { return ConflictIface.class; }
+  public default Class<?> method11() { return ConflictIface.class; }
+  public default Class<?> method12() { return ConflictIface.class; }
+  public default Class<?> method13() { return ConflictIface.class; }
+  public default Class<?> method14() { return ConflictIface.class; }
+  public default Class<?> method15() { return ConflictIface.class; }
+  public default Class<?> method16() { return ConflictIface.class; }
+  public default Class<?> method17() { return ConflictIface.class; }
+  public default Class<?> method18() { return ConflictIface.class; }
+  public default Class<?> method19() { return ConflictIface.class; }
+  public default Class<?> method20() { return ConflictIface.class; }
+  public default Class<?> method21() { return ConflictIface.class; }
+  public default Class<?> method22() { return ConflictIface.class; }
+  public default Class<?> method23() { return ConflictIface.class; }
+  public default Class<?> method24() { return ConflictIface.class; }
+  public default Class<?> method25() { return ConflictIface.class; }
+  public default Class<?> method26() { return ConflictIface.class; }
+  public default Class<?> method27() { return ConflictIface.class; }
+  public default Class<?> method28() { return ConflictIface.class; }
+  public default Class<?> method29() { return ConflictIface.class; }
+  public default Class<?> method30() { return ConflictIface.class; }
+  public default Class<?> method31() { return ConflictIface.class; }
+  public default Class<?> method32() { return ConflictIface.class; }
+  public default Class<?> method33() { return ConflictIface.class; }
+  public default Class<?> method34() { return ConflictIface.class; }
+  public default Class<?> method35() { return ConflictIface.class; }
+  public default Class<?> method36() { return ConflictIface.class; }
+  public default Class<?> method37() { return ConflictIface.class; }
+  public default Class<?> method38() { return ConflictIface.class; }
+  public default Class<?> method39() { return ConflictIface.class; }
+  public default Class<?> method40() { return ConflictIface.class; }
+  public default Class<?> method41() { return ConflictIface.class; }
+  public default Class<?> method42() { return ConflictIface.class; }
+  public default Class<?> method43() { return ConflictIface.class; }
+  public default Class<?> method44() { return ConflictIface.class; }
+  public default Class<?> method45() { return ConflictIface.class; }
+  public default Class<?> method46() { return ConflictIface.class; }
+  public default Class<?> method47() { return ConflictIface.class; }
+  public default Class<?> method48() { return ConflictIface.class; }
+  public default Class<?> method49() { return ConflictIface.class; }
+  public default Class<?> method50() { return ConflictIface.class; }
+  public default Class<?> method51() { return ConflictIface.class; }
+  public default Class<?> method52() { return ConflictIface.class; }
+  public default Class<?> method53() { return ConflictIface.class; }
+  public default Class<?> method54() { return ConflictIface.class; }
+  public default Class<?> method55() { return ConflictIface.class; }
+  public default Class<?> method56() { return ConflictIface.class; }
+  public default Class<?> method57() { return ConflictIface.class; }
+  public default Class<?> method58() { return ConflictIface.class; }
+  public default Class<?> method59() { return ConflictIface.class; }
+  public default Class<?> method60() { return ConflictIface.class; }
+  public default Class<?> method61() { return ConflictIface.class; }
+  public default Class<?> method62() { return ConflictIface.class; }
+  public default Class<?> method63() { return ConflictIface.class; }
+  public default Class<?> method64() { return ConflictIface.class; }
+  public default Class<?> method65() { return ConflictIface.class; }
+  public default Class<?> method66() { return ConflictIface.class; }
+  public default Class<?> method67() { return ConflictIface.class; }
+  public default Class<?> method68() { return ConflictIface.class; }
+  public default Class<?> method69() { return ConflictIface.class; }
+  public default Class<?> method70() { return ConflictIface.class; }
+  public default Class<?> method71() { return ConflictIface.class; }
+  public default Class<?> method72() { return ConflictIface.class; }
+  public default Class<?> method73() { return ConflictIface.class; }
+  public default Class<?> method74() { return ConflictIface.class; }
+  public default Class<?> method75() { return ConflictIface.class; }
+  public default Class<?> method76() { return ConflictIface.class; }
+  public default Class<?> method77() { return ConflictIface.class; }
+  public default Class<?> method78() { return ConflictIface.class; }
+  public default Class<?> method79() { return ConflictIface.class; }
+}
diff --git a/test/141-class-unload/src/ConflictSuper.java b/test/141-class-unload/src/ConflictSuper.java
new file mode 100644
index 0000000..5ca587b
--- /dev/null
+++ b/test/141-class-unload/src/ConflictSuper.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class ConflictSuper implements ConflictIface {
+}
diff --git a/test/141-class-unload/src/Iface.java b/test/141-class-unload/src/Iface.java
new file mode 100644
index 0000000..37b513f
--- /dev/null
+++ b/test/141-class-unload/src/Iface.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public interface Iface {
+    default void invokeRun(Runnable r) {
+        r.run();
+    }
+}
diff --git a/test/141-class-unload/src/Impl2.java b/test/141-class-unload/src/Impl2.java
new file mode 100644
index 0000000..8fd77e5
--- /dev/null
+++ b/test/141-class-unload/src/Impl2.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Impl2 implements Iface {
+}
diff --git a/test/141-class-unload/src/Main.java b/test/141-class-unload/src/Main.java
index 3cfe006..7b7ffa2 100644
--- a/test/141-class-unload/src/Main.java
+++ b/test/141-class-unload/src/Main.java
@@ -20,6 +20,8 @@
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.function.Consumer;
 
 public class Main {
     static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/141-class-unload-ex.jar";
@@ -49,6 +51,16 @@
             testOatFilesUnloaded(getPid());
             // Test that objects keep class loader live for sticky GC.
             testStickyUnload(constructor);
+            // Test that copied methods recorded in a stack trace prevents unloading.
+            testCopiedMethodInStackTrace(constructor);
+            // Test that code preventing unloading holder classes of copied methods recorded in
+            // a stack trace does not crash when processing a copied method in the boot class path.
+            testCopiedBcpMethodInStackTrace();
+            // Test that code preventing unloading holder classes of copied methods recorded in
+            // a stack trace does not crash when processing a copied method in an app image.
+            testCopiedAppImageMethodInStackTrace();
+            // Test that the runtime uses the right allocator when creating conflict methods.
+            testConflictMethod(constructor);
         } catch (Exception e) {
             e.printStackTrace(System.out);
         }
@@ -103,13 +115,12 @@
         System.out.println(klass2.get());
     }
 
-    private static void testUnloadLoader(Constructor<?> constructor)
-        throws Exception {
-      WeakReference<ClassLoader> loader = setUpUnloadLoader(constructor, true);
-      // No strong references to class loader, should get unloaded.
-      doUnloading();
-      // If the weak reference is cleared, then it was unloaded.
-      System.out.println(loader.get());
+    private static void testUnloadLoader(Constructor<?> constructor) throws Exception {
+        WeakReference<ClassLoader> loader = setUpUnloadLoader(constructor, true);
+        // No strong references to class loader, should get unloaded.
+        doUnloading();
+        // If the weak reference is cleared, then it was unloaded.
+        System.out.println(loader.get());
     }
 
     private static void testStackTrace(Constructor<?> constructor) throws Exception {
@@ -138,33 +149,34 @@
     }
 
     static class Pair {
-      public Pair(Object o, ClassLoader l) {
-        object = o;
-        classLoader = new WeakReference<ClassLoader>(l);
-      }
+        public Pair(Object o, ClassLoader l) {
+            object = o;
+            classLoader = new WeakReference<ClassLoader>(l);
+        }
 
-      public Object object;
-      public WeakReference<ClassLoader> classLoader;
+        public Object object;
+        public WeakReference<ClassLoader> classLoader;
     }
 
-    private static Pair testNoUnloadInstanceHelper(Constructor<?> constructor) throws Exception {
+    // Make the method not inline-able to prevent the compiler optimizing away the allocation.
+    private static Pair $noinline$testNoUnloadInstanceHelper(Constructor<?> constructor)
+            throws Exception {
         ClassLoader loader = (ClassLoader) constructor.newInstance(
-            DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
+                DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
         Object o = testNoUnloadHelper(loader);
         return new Pair(o, loader);
     }
 
     private static void testNoUnloadInstance(Constructor<?> constructor) throws Exception {
-        Pair p = testNoUnloadInstanceHelper(constructor);
+        Pair p = $noinline$testNoUnloadInstanceHelper(constructor);
         doUnloading();
-        // If the class loader was unloded too early due to races, just pass the test.
         boolean isNull = p.classLoader.get() == null;
         System.out.println("loader null " + isNull);
     }
 
     private static Class<?> setUpUnloadClass(Constructor<?> constructor) throws Exception {
         ClassLoader loader = (ClassLoader) constructor.newInstance(
-            DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
+                DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
         Class<?> intHolder = loader.loadClass("IntHolder");
         Method getValue = intHolder.getDeclaredMethod("getValue");
         Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE);
@@ -201,6 +213,179 @@
         System.out.println("Too small " + (s.length() < 1000));
     }
 
+    private static void assertStackTraceContains(Throwable t, String className, String methodName) {
+        boolean found = false;
+        for (StackTraceElement e : t.getStackTrace()) {
+            if (className.equals(e.getClassName()) && methodName.equals(e.getMethodName())) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            throw new Error("Did not find " + className + "." + methodName);
+        }
+    }
+
+    private static void $noinline$callAllMethods(ConflictIface iface) {
+        // Call all methods in the interface to make sure we hit conflicts in the IMT.
+        iface.method1();
+        iface.method2();
+        iface.method3();
+        iface.method4();
+        iface.method5();
+        iface.method6();
+        iface.method7();
+        iface.method8();
+        iface.method9();
+        iface.method10();
+        iface.method11();
+        iface.method12();
+        iface.method13();
+        iface.method14();
+        iface.method15();
+        iface.method16();
+        iface.method17();
+        iface.method18();
+        iface.method19();
+        iface.method20();
+        iface.method21();
+        iface.method22();
+        iface.method23();
+        iface.method24();
+        iface.method25();
+        iface.method26();
+        iface.method27();
+        iface.method28();
+        iface.method29();
+        iface.method30();
+        iface.method31();
+        iface.method32();
+        iface.method33();
+        iface.method34();
+        iface.method35();
+        iface.method36();
+        iface.method37();
+        iface.method38();
+        iface.method39();
+        iface.method40();
+        iface.method41();
+        iface.method42();
+        iface.method43();
+        iface.method44();
+        iface.method45();
+        iface.method46();
+        iface.method47();
+        iface.method48();
+        iface.method49();
+        iface.method50();
+        iface.method51();
+        iface.method52();
+        iface.method53();
+        iface.method54();
+        iface.method55();
+        iface.method56();
+        iface.method57();
+        iface.method58();
+        iface.method59();
+        iface.method60();
+        iface.method61();
+        iface.method62();
+        iface.method63();
+        iface.method64();
+        iface.method65();
+        iface.method66();
+        iface.method67();
+        iface.method68();
+        iface.method69();
+        iface.method70();
+        iface.method71();
+        iface.method72();
+        iface.method73();
+        iface.method74();
+        iface.method75();
+        iface.method76();
+        iface.method77();
+        iface.method78();
+        iface.method79();
+    }
+
+    private static void $noinline$invokeConflictMethod(Constructor<?> constructor)
+            throws Exception {
+        ClassLoader loader = (ClassLoader) constructor.newInstance(
+                DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
+        Class<?> impl = loader.loadClass("ConflictImpl");
+        ConflictIface iface = (ConflictIface) impl.newInstance();
+        $noinline$callAllMethods(iface);
+    }
+
+    private static void testConflictMethod(Constructor<?> constructor) throws Exception {
+        // Load and unload a few class loaders to force re-use of the native memory where we
+        // used to allocate the conflict table.
+        for (int i = 0; i < 2; i++) {
+            $noinline$invokeConflictMethod(constructor);
+            doUnloading();
+        }
+        Class<?> impl = Class.forName("ConflictSuper");
+        ConflictIface iface = (ConflictIface) impl.newInstance();
+        $noinline$callAllMethods(iface);
+    }
+
+    private static void testCopiedMethodInStackTrace(Constructor<?> constructor) throws Exception {
+        Throwable t = $noinline$createStackTraceWithCopiedMethod(constructor);
+        doUnloading();
+        assertStackTraceContains(t, "Iface", "invokeRun");
+    }
+
+    private static Throwable $noinline$createStackTraceWithCopiedMethod(Constructor<?> constructor)
+            throws Exception {
+      ClassLoader loader = (ClassLoader) constructor.newInstance(
+              DEX_FILE, LIBRARY_SEARCH_PATH, Main.class.getClassLoader());
+      Iface impl = (Iface) loader.loadClass("Impl").newInstance();
+      Runnable throwingRunnable = new Runnable() {
+          public void run() {
+              throw new Error();
+          }
+      };
+      try {
+          impl.invokeRun(throwingRunnable);
+          System.out.println("UNREACHABLE");
+          return null;
+      } catch (Error expected) {
+          return expected;
+      }
+    }
+
+    private static void testCopiedBcpMethodInStackTrace() {
+        Consumer<Object> consumer = new Consumer<Object>() {
+            public void accept(Object o) {
+                throw new Error();
+            }
+        };
+        Error err = null;
+        try {
+            Arrays.asList(new Object[] { new Object() }).iterator().forEachRemaining(consumer);
+        } catch (Error expected) {
+            err = expected;
+        }
+        assertStackTraceContains(err, "Main", "testCopiedBcpMethodInStackTrace");
+    }
+
+    private static void testCopiedAppImageMethodInStackTrace() throws Exception {
+        Iface limpl = (Iface) Class.forName("Impl2").newInstance();
+        Runnable throwingRunnable = new Runnable() {
+            public void run() {
+                throw new Error();
+            }
+        };
+        Error err = null;
+        try {
+            limpl.invokeRun(throwingRunnable);
+        } catch (Error expected) {
+            err = expected;
+        }
+        assertStackTraceContains(err, "Main", "testCopiedAppImageMethodInStackTrace");
+    }
+
     private static WeakReference<Class> setUpUnloadClassWeak(Constructor<?> constructor)
             throws Exception {
         return new WeakReference<Class>(setUpUnloadClass(constructor));
@@ -241,7 +426,7 @@
     }
 
     private static int getPid() throws Exception {
-      return Integer.parseInt(new File("/proc/self").getCanonicalFile().getName());
+        return Integer.parseInt(new File("/proc/self").getCanonicalFile().getName());
     }
 
     public static native void stopJit();
diff --git a/test/143-string-value/check b/test/143-string-value/check
deleted file mode 100755
index 0691e88..0000000
--- a/test/143-string-value/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Strip error log messages.
-sed -e '/^.*dalvikvm\(\|32\|64\) E.*\] /d' "$4" > "$4.tmp"
-
-diff --strip-trailing-cr -q "$1" "$2" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4.tmp" >/dev/null
diff --git a/test/143-string-value/run.py b/test/143-string-value/run.py
new file mode 100644
index 0000000..5f32b06
--- /dev/null
+++ b/test/143-string-value/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # Strip error log messages.
+  ctx.run(fr"sed -i '/^.*dalvikvm\(\|32\|64\) E.*\] /d' '{args.stderr_file}'")
diff --git a/test/143-string-value/test-metadata.json b/test/143-string-value/test-metadata.json
new file mode 100644
index 0000000..2d1dd45
--- /dev/null
+++ b/test/143-string-value/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "run-param": {
+    "default-run": true
+  }
+}
diff --git a/test/144-static-field-sigquit/Android.bp b/test/144-static-field-sigquit/Android.bp
index 0c21c07..1febd32 100644
--- a/test/144-static-field-sigquit/Android.bp
+++ b/test/144-static-field-sigquit/Android.bp
@@ -15,7 +15,7 @@
 java_test {
     name: "art-run-test-144-static-field-sigquit",
     defaults: ["art-run-test-defaults"],
-    test_config_template: ":art-run-test-target-template",
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
     srcs: ["src/**/*.java"],
     data: [
         ":art-run-test-144-static-field-sigquit-expected-stdout",
diff --git a/test/144-static-field-sigquit/run.py b/test/144-static-field-sigquit/run.py
new file mode 100644
index 0000000..c4268ce
--- /dev/null
+++ b/test/144-static-field-sigquit/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, android_log_tags="*:w")
diff --git a/test/146-bad-interface/check b/test/146-bad-interface/check
deleted file mode 100644
index eadb559..0000000
--- a/test/146-bad-interface/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Oat file manager will complain about duplicate dex files. Ignore.
-sed -e '/.*oat_file_manager.*/d' "$4" > "$4.tmp"
-
-diff --strip-trailing-cr -q "$1" "$2" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4.tmp" >/dev/null
diff --git a/test/146-bad-interface/run b/test/146-bad-interface/run
deleted file mode 100755
index 2b4bbb0..0000000
--- a/test/146-bad-interface/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 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.
-
-# Use the `--secondary-class-loader-context` switch to compile the secondary dex
-# file with the right class loader context. Do not use `--secondary` as we're
-# loading the *-ex.jar file in a separate class loader.
-exec ${RUN} "${@}" \
-    --secondary-class-loader-context "PCL[$DEX_LOCATION/$TEST_NAME.jar]"
diff --git a/test/146-bad-interface/run.py b/test/146-bad-interface/run.py
new file mode 100644
index 0000000..d47bde2
--- /dev/null
+++ b/test/146-bad-interface/run.py
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Copyright (C) 2015 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.
+
+
+def run(ctx, args):
+  # Use the `--secondary-class-loader-context` switch to compile the secondary dex
+  # file with the right class loader context. Do not use `--secondary` as we're
+  # loading the *-ex.jar file in a separate class loader.
+  pcl = f"PCL[{ctx.env.DEX_LOCATION}/{ctx.env.TEST_NAME}.jar]"
+  ctx.default_run(args, secondary_class_loader_context=pcl)
+
+  # Oat file manager will complain about duplicate dex files. Ignore.
+  ctx.run(fr"sed -i '/.*oat_file_manager.*/d' '{args.stderr_file}'")
diff --git a/test/148-multithread-gc-annotations/check b/test/148-multithread-gc-annotations/check
deleted file mode 100755
index 67fcafb..0000000
--- a/test/148-multithread-gc-annotations/check
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Check that the string "error" isn't present
-grep -vq error "$2"  \
-  && diff --strip-trailing-cr -q "$3" "$4" >/dev/null
diff --git a/test/148-multithread-gc-annotations/run.py b/test/148-multithread-gc-annotations/run.py
new file mode 100644
index 0000000..9216c06
--- /dev/null
+++ b/test/148-multithread-gc-annotations/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # Check that the string "error" isn't present (delete all non-error lines)
+  ctx.run(fr"sed -i '/error/!d' '{args.stdout_file}'")
diff --git a/test/149-suspend-all-stress/check b/test/149-suspend-all-stress/check
deleted file mode 100755
index 0386d58..0000000
--- a/test/149-suspend-all-stress/check
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Only compare the last line.
-tail -n 1 "$2" | diff --strip-trailing-cr -q "$1" - >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4" >/dev/null
-
diff --git a/test/149-suspend-all-stress/run.py b/test/149-suspend-all-stress/run.py
new file mode 100644
index 0000000..f6f670e
--- /dev/null
+++ b/test/149-suspend-all-stress/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # Only compare the last line.
+  ctx.run(fr"tail -n 1 '{args.stdout_file}' > temp-stdout.txt")
+  ctx.run(fr"mv temp-stdout.txt '{args.stdout_file}'")
diff --git a/test/151-OpenFileLimit/Android.bp b/test/151-OpenFileLimit/Android.bp
index 946be83..6b2a8c9 100644
--- a/test/151-OpenFileLimit/Android.bp
+++ b/test/151-OpenFileLimit/Android.bp
@@ -15,7 +15,7 @@
 java_test {
     name: "art-run-test-151-OpenFileLimit",
     defaults: ["art-run-test-defaults"],
-    test_config_template: ":art-run-test-target-template",
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
     srcs: ["src/**/*.java"],
     data: [
         ":art-run-test-151-OpenFileLimit-expected-stdout",
diff --git a/test/151-OpenFileLimit/expected-stdout.txt b/test/151-OpenFileLimit/expected-stdout.txt
index 6bc45ef..18903be 100644
--- a/test/151-OpenFileLimit/expected-stdout.txt
+++ b/test/151-OpenFileLimit/expected-stdout.txt
@@ -1,3 +1,4 @@
+JNI_OnLoad called
 Message includes "Too many open files"
 thread run.
 done.
diff --git a/test/151-OpenFileLimit/run b/test/151-OpenFileLimit/run
deleted file mode 100755
index 6faeb0d..0000000
--- a/test/151-OpenFileLimit/run
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-# Filter out expected error messages, which happen on device.
-export ANDROID_LOG_TAGS='*:f'
-
-flags="$@"
-
-# Reduce the file descriptor limit so the test will reach the limit sooner.
-ulimit -n 512
-${RUN} --external-log-tags ${flags}
diff --git a/test/151-OpenFileLimit/run.py b/test/151-OpenFileLimit/run.py
new file mode 100644
index 0000000..65d34ed
--- /dev/null
+++ b/test/151-OpenFileLimit/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+
+import resource
+
+
+def run(ctx, args):
+  ctx.default_run(args, android_log_tags="*:f")
diff --git a/test/151-OpenFileLimit/src/Main.java b/test/151-OpenFileLimit/src/Main.java
index 9b16090..561c17a 100644
--- a/test/151-OpenFileLimit/src/Main.java
+++ b/test/151-OpenFileLimit/src/Main.java
@@ -24,6 +24,9 @@
     private static final String TEMP_FILE_NAME_SUFFIX = ".txt";
 
     public static void main(String[] args) throws IOException {
+        System.loadLibrary(args[0]);
+
+        setRlimitNoFile(512);
 
         // Exhaust the number of open file descriptors.
         List<File> files = new ArrayList<File>();
@@ -79,4 +82,6 @@
             }
         }
     }
+
+    public static native void setRlimitNoFile(int value);
 }
diff --git a/test/157-void-class/run b/test/157-void-class/run
deleted file mode 100755
index 8c6159f..0000000
--- a/test/157-void-class/run
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Let the test build its own core image with --no-image and use verify,
-# so that the compiler does not try to initialize classes. This leaves the
-# java.lang.Void compile-time verified but uninitialized.
-./default-run "$@" --no-image \
-    --runtime-option -Ximage-compiler-option \
-    --runtime-option --compiler-filter=verify
diff --git a/test/157-void-class/run.py b/test/157-void-class/run.py
new file mode 100644
index 0000000..c0cc3e9
--- /dev/null
+++ b/test/157-void-class/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Let the test build its own core image with --no-image and use verify,
+  # so that the compiler does not try to initialize classes. This leaves the
+  # java.lang.Void compile-time verified but uninitialized.
+  ctx.default_run(
+      args,
+      image=False,
+      runtime_option=["-Ximage-compiler-option", "--compiler-filter=verify"])
diff --git a/test/158-app-image-class-table/run b/test/158-app-image-class-table/run
deleted file mode 100644
index 146e180..0000000
--- a/test/158-app-image-class-table/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile
diff --git a/test/158-app-image-class-table/run.py b/test/158-app-image-class-table/run.py
new file mode 100644
index 0000000..7678899
--- /dev/null
+++ b/test/158-app-image-class-table/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args, profile=True, Xcompiler_option=["--compiler-filter=speed-profile"])
diff --git a/test/159-app-image-fields/run b/test/159-app-image-fields/run
deleted file mode 100644
index 74facb0..0000000
--- a/test/159-app-image-fields/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Use a profile to put specific classes in the app image.
-# Also run the compiler with -j1 to ensure specific class verification order.
-# And limit the managed heap to 16 MiB to speed up GCs.
-exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile \
-    -Xcompiler-option -j1 --runtime-option -Xmx16m
diff --git a/test/159-app-image-fields/run.py b/test/159-app-image-fields/run.py
new file mode 100644
index 0000000..ae99f54
--- /dev/null
+++ b/test/159-app-image-fields/run.py
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Use a profile to put specific classes in the app image.
+  # Also run the compiler with -j1 to ensure specific class verification order.
+  # And limit the managed heap to 16 MiB to speed up GCs.
+  ctx.default_run(
+      args,
+      profile=True,
+      Xcompiler_option=["--compiler-filter=speed-profile", "-j1"],
+      runtime_option=["-Xmx16m"])
diff --git a/test/159-app-image-fields/src/Main.java b/test/159-app-image-fields/src/Main.java
index e7db1e1..d238f77 100644
--- a/test/159-app-image-fields/src/Main.java
+++ b/test/159-app-image-fields/src/Main.java
@@ -115,6 +115,7 @@
 // and the verification of Main is last and fills the DexCache with Derived.value.
 //
 class Fields {
+    // clang-format off
     public static int clobberDexCache() {
         return 0
                 + testField0000
diff --git a/test/160-read-barrier-stress/build b/test/160-read-barrier-stress/build
deleted file mode 100755
index 90b6b95..0000000
--- a/test/160-read-barrier-stress/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2020 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.
-
-./default-build "$@" --experimental var-handles
diff --git a/test/160-read-barrier-stress/build.py b/test/160-read-barrier-stress/build.py
new file mode 100644
index 0000000..b65e088
--- /dev/null
+++ b/test/160-read-barrier-stress/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build(api_level="var-handles")
diff --git a/test/160-read-barrier-stress/run b/test/160-read-barrier-stress/run
deleted file mode 100644
index ab82229..0000000
--- a/test/160-read-barrier-stress/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Limit the Java heap to 16MiB to force more GCs.
-exec ${RUN} $@ --runtime-option -Xmx16m
diff --git a/test/160-read-barrier-stress/run.py b/test/160-read-barrier-stress/run.py
new file mode 100644
index 0000000..fb006ea
--- /dev/null
+++ b/test/160-read-barrier-stress/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Limit the Java heap to 16MiB to force more GCs.
+  ctx.default_run(args, runtime_option=["-Xmx16m"])
diff --git a/test/160-read-barrier-stress/test-metadata.json b/test/160-read-barrier-stress/test-metadata.json
new file mode 100644
index 0000000..b7e262c
--- /dev/null
+++ b/test/160-read-barrier-stress/test-metadata.json
@@ -0,0 +1,6 @@
+{
+  "build-param": {
+    "experimental": "var-handles",
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/161-final-abstract-class/build.py b/test/161-final-abstract-class/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/161-final-abstract-class/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/161-final-abstract-class/build b/test/161-final-abstract-class/generate-sources
old mode 100644
new mode 100755
similarity index 100%
rename from test/161-final-abstract-class/build
rename to test/161-final-abstract-class/generate-sources
diff --git a/test/163-app-image-methods/run b/test/163-app-image-methods/run
deleted file mode 100644
index 74facb0..0000000
--- a/test/163-app-image-methods/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Use a profile to put specific classes in the app image.
-# Also run the compiler with -j1 to ensure specific class verification order.
-# And limit the managed heap to 16 MiB to speed up GCs.
-exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile \
-    -Xcompiler-option -j1 --runtime-option -Xmx16m
diff --git a/test/163-app-image-methods/run.py b/test/163-app-image-methods/run.py
new file mode 100644
index 0000000..ae99f54
--- /dev/null
+++ b/test/163-app-image-methods/run.py
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Use a profile to put specific classes in the app image.
+  # Also run the compiler with -j1 to ensure specific class verification order.
+  # And limit the managed heap to 16 MiB to speed up GCs.
+  ctx.default_run(
+      args,
+      profile=True,
+      Xcompiler_option=["--compiler-filter=speed-profile", "-j1"],
+      runtime_option=["-Xmx16m"])
diff --git a/test/164-resolution-trampoline-dex-cache/run b/test/164-resolution-trampoline-dex-cache/run
deleted file mode 100644
index 33a6f01..0000000
--- a/test/164-resolution-trampoline-dex-cache/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Make sure we compile the required method using speed-profile compiler filter.
-# Enable JIT through runtime option to avoid the compiler filter set by --jit.
-exec ${RUN} "${@}" \
-    -Xcompiler-option --compiler-filter=speed-profile --profile \
-    --runtime-option -Xusejit:true
diff --git a/test/164-resolution-trampoline-dex-cache/run.py b/test/164-resolution-trampoline-dex-cache/run.py
new file mode 100644
index 0000000..6e7b777
--- /dev/null
+++ b/test/164-resolution-trampoline-dex-cache/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Make sure we compile the required method using speed-profile compiler filter.
+  # Enable JIT through runtime option to avoid the compiler filter set by --jit.
+  ctx.default_run(
+      args,
+      Xcompiler_option=["--compiler-filter=speed-profile"],
+      profile=True,
+      runtime_option=["-Xusejit:true"])
diff --git a/test/165-lock-owner-proxy/run b/test/165-lock-owner-proxy/run
deleted file mode 100644
index 9365411..0000000
--- a/test/165-lock-owner-proxy/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Use a smaller heap so it's easier to potentially fill up.
-exec ${RUN} $@ --runtime-option -Xmx2m
diff --git a/test/165-lock-owner-proxy/run.py b/test/165-lock-owner-proxy/run.py
new file mode 100644
index 0000000..266f747
--- /dev/null
+++ b/test/165-lock-owner-proxy/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Use a smaller heap so it's easier to potentially fill up.
+  ctx.default_run(args, runtime_option=["-Xmx2m"])
diff --git a/test/165-lock-owner-proxy/src/Main.java b/test/165-lock-owner-proxy/src/Main.java
index fff8e58..99a6d42 100644
--- a/test/165-lock-owner-proxy/src/Main.java
+++ b/test/165-lock-owner-proxy/src/Main.java
@@ -18,6 +18,11 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 
+// Note that this is run with a tiny heap.
+// See b/260228356 for some discussion. It's unclear if and how this reliably forces an
+// OOME. For now, we're keeping this around because it appears to have detected some bugs
+// in the past. It may need revisiting.
+
 public class Main {
     static final int numberOfThreads = 5;
     static final int totalOperations = 10000;
@@ -27,9 +32,16 @@
     static volatile boolean finish = false;
 
     public static void main(String[] args) throws Exception {
+        // Wait for system daemons to start, so that we minimize the chances of
+        // a system daemon still starting up if/when we run out of memory.
+        try {
+            Thread.sleep(100);
+        } catch (InterruptedException e) {
+            System.out.println("Unexpected interrupt:" + e);
+        }
+
         inf = (SimpleInterface)Proxy.newProxyInstance(SimpleInterface.class.getClassLoader(),
             new Class[] { SimpleInterface.class }, new EmptyInvocationHandler());
-
         Thread garbageThread = new Thread(new GarbageRunner());
         garbageThread.start();
 
@@ -111,6 +123,7 @@
                 try {
                     Thread.sleep(10);
                 } catch (Exception e) {
+                    System.out.println("Unexpected exception in sleep():" + e);
                 }
             }
         }
diff --git a/test/166-bad-interface-super/build b/test/166-bad-interface-super/build
deleted file mode 100644
index bba6184..0000000
--- a/test/166-bad-interface-super/build
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Use the jasmin sources for JVM, otherwise the smali sources.
-extra_arg="--no-jasmin"
-
-for arg in "$@"; do
-  if [[ "$arg" == "--jvm" ]]; then
-    extra_arg="--no-smali"
-    break
-  fi
-done
-
-./default-build "$@" "$extra_arg"
diff --git a/test/166-bad-interface-super/build.py b/test/166-bad-interface-super/build.py
new file mode 100644
index 0000000..b2ff2d7
--- /dev/null
+++ b/test/166-bad-interface-super/build.py
@@ -0,0 +1,24 @@
+#
+# Copyright (C) 2022 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.
+
+import os
+
+
+def build(ctx):
+  # Use the jasmin sources for JVM, otherwise the smali sources.
+  if ctx.jvm:
+    ctx.default_build(use_smali=False)
+  else:
+    ctx.default_build(use_jasmin=False)
diff --git a/test/167-visit-locks/run b/test/167-visit-locks/run
deleted file mode 100644
index 9365411..0000000
--- a/test/167-visit-locks/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Use a smaller heap so it's easier to potentially fill up.
-exec ${RUN} $@ --runtime-option -Xmx2m
diff --git a/test/167-visit-locks/run.py b/test/167-visit-locks/run.py
new file mode 100644
index 0000000..266f747
--- /dev/null
+++ b/test/167-visit-locks/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Use a smaller heap so it's easier to potentially fill up.
+  ctx.default_run(args, runtime_option=["-Xmx2m"])
diff --git a/test/168-vmstack-annotated/run b/test/168-vmstack-annotated/run
deleted file mode 100644
index 9365411..0000000
--- a/test/168-vmstack-annotated/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Use a smaller heap so it's easier to potentially fill up.
-exec ${RUN} $@ --runtime-option -Xmx2m
diff --git a/test/168-vmstack-annotated/run.py b/test/168-vmstack-annotated/run.py
new file mode 100644
index 0000000..266f747
--- /dev/null
+++ b/test/168-vmstack-annotated/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Use a smaller heap so it's easier to potentially fill up.
+  ctx.default_run(args, runtime_option=["-Xmx2m"])
diff --git a/test/168-vmstack-annotated/src/Main.java b/test/168-vmstack-annotated/src/Main.java
index 8234f94..5056c42 100644
--- a/test/168-vmstack-annotated/src/Main.java
+++ b/test/168-vmstack-annotated/src/Main.java
@@ -22,6 +22,7 @@
 import java.util.Map;
 import java.util.concurrent.BrokenBarrierException;
 import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ForkJoinPool;
 
 public class Main {
 
@@ -89,6 +90,9 @@
     }
 
     public static void main(String[] args) throws Exception {
+        // Eagerly initialize ForkJoinPool as we've seen the class initialization code interfere
+        // with the logic of the test when waiting for threads to be non-runnable.
+        Class.forName(ForkJoinPool.class.getName(), true, ForkJoinPool.class.getClassLoader());
         try {
             testCluster1();
         } catch (Exception e) {
diff --git a/test/172-app-image-twice/check b/test/172-app-image-twice/check
deleted file mode 100755
index 228bcb5..0000000
--- a/test/172-app-image-twice/check
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Remove all lines not containing "passed".
-grep "^passed" "$2" | diff --strip-trailing-cr -q "$1" - >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4" >/dev/null
diff --git a/test/172-app-image-twice/run b/test/172-app-image-twice/run
deleted file mode 100644
index 2987b4b..0000000
--- a/test/172-app-image-twice/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Build an app image with TestClass (specified by profile).
-
-${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile
diff --git a/test/172-app-image-twice/run.py b/test/172-app-image-twice/run.py
new file mode 100644
index 0000000..6b53067
--- /dev/null
+++ b/test/172-app-image-twice/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+
+
+def run(ctx, args):
+  # Build an app image with TestClass (specified by profile).
+
+  ctx.default_run(
+      args, profile=True, Xcompiler_option=["--compiler-filter=speed-profile"])
+
+  # Remove all lines not containing "passed".
+  ctx.run(fr"sed -i '/^passed/!d' '{args.stdout_file}'")
diff --git a/test/173-missing-field-type/smali/BadField.smali b/test/173-missing-field-type/smali/BadField.smali
index 7593983..1734034 100644
--- a/test/173-missing-field-type/smali/BadField.smali
+++ b/test/173-missing-field-type/smali/BadField.smali
@@ -41,6 +41,18 @@
     return-void
 .end method
 
+.method public static storeStatic(Ljava/lang/Object;)V
+    .registers 1
+    sput-object p0, LBadField;->widget:LWidget;
+    return-void
+.end method
+
+.method public static storeInstance(LBadField;Ljava/lang/Object;)V
+    .registers 2
+    iput-object p1, p0, LBadField;->iwidget:LWidget;
+    return-void
+.end method
+
 .method public static storeInstanceObject()V
     .registers 2
     new-instance v1, LBadField;
diff --git a/test/173-missing-field-type/src-art/Main.java b/test/173-missing-field-type/src-art/Main.java
index 35dbbea..8c5a741 100644
--- a/test/173-missing-field-type/src-art/Main.java
+++ b/test/173-missing-field-type/src-art/Main.java
@@ -15,6 +15,7 @@
  */
 
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 
 public class Main {
     public static void main(String[] args) throws Throwable {
@@ -24,10 +25,16 @@
         // Storing null is OK.
         c.getMethod("storeStaticNull").invoke(null);
         c.getMethod("storeInstanceNull").invoke(null);
+        c.getMethod("storeStatic", Object.class).invoke(null, new Object[]{ null });
+        c.getMethod("storeInstance", c, Object.class).invoke(
+            null, new Object[]{ c.newInstance(), null });
 
         // Storing anything else should throw an exception.
-        testStoreObject(c, "storeStaticObject");
-        testStoreObject(c, "storeInstanceObject");
+        testStoreObject(c.getMethod("storeStaticObject"));
+        testStoreObject(c.getMethod("storeInstanceObject"));
+        testStoreObject(c.getMethod("storeStatic", Object.class), new Object());
+        testStoreObject(
+            c.getMethod("storeInstance", c, Object.class), c.newInstance(), new Object());
 
         // Loading is OK.
         c = Class.forName("BadFieldGet");
@@ -38,9 +45,9 @@
       c.getMethod(methodName).invoke(null);
     }
 
-    public static void testStoreObject(Class<?> c, String methodName) throws Throwable {
+    public static void testStoreObject(Method method, Object... arguments) throws Throwable {
         try {
-          c.getMethod(methodName).invoke(null);
+          method.invoke(null, arguments);
           throw new Error("Expected NoClassDefFoundError");
         } catch (InvocationTargetException expected) {
           Throwable e = expected.getCause();
diff --git a/test/176-app-image-string/run b/test/176-app-image-string/run
deleted file mode 100644
index 52d2b5f..0000000
--- a/test/176-app-image-string/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 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.
-
-exec ${RUN} $@ --profile
diff --git a/test/176-app-image-string/run.py b/test/176-app-image-string/run.py
new file mode 100644
index 0000000..0f3a0eac
--- /dev/null
+++ b/test/176-app-image-string/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, profile=True)
diff --git a/test/178-app-image-native-method/build.py b/test/178-app-image-native-method/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/178-app-image-native-method/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/178-app-image-native-method/check b/test/178-app-image-native-method/check
deleted file mode 100755
index 45d3654..0000000
--- a/test/178-app-image-native-method/check
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Filter out error messages for missing native methods.
-diff --strip-trailing-cr -q "$1" "$2" >/dev/null \
-  && grep -v 'No implementation found for ' "$4" | diff -q "$3" - >/dev/null
diff --git a/test/178-app-image-native-method/run b/test/178-app-image-native-method/run
deleted file mode 100644
index 7cd0d57..0000000
--- a/test/178-app-image-native-method/run
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 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.
-
-# Use a profile to put specific classes in the app image. Increase the large
-# method threshold to compile Main.$noinline$opt$testCriticalSignatures().
-${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile \
-    -Xcompiler-option --large-method-max=2000
-return_status1=$?
-
-# Also run with the verify filter to avoid compiling JNI stubs.
-${RUN} ${@} --profile -Xcompiler-option --compiler-filter=verify
-return_status2=$?
-
-(exit ${return_status1}) && (exit ${return_status2})
diff --git a/test/178-app-image-native-method/run.py b/test/178-app-image-native-method/run.py
new file mode 100644
index 0000000..5f8c507
--- /dev/null
+++ b/test/178-app-image-native-method/run.py
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 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.
+
+
+def run(ctx, args):
+  # Use a profile to put specific classes in the app image. Increase the large
+  # method threshold to compile Main.$noinline$opt$testCriticalSignatures().
+  ctx.default_run(
+      args,
+      profile=True,
+      Xcompiler_option=[
+          "--compiler-filter=speed-profile", "--large-method-max=2000"
+      ])
+
+  # Also run with the verify filter to avoid compiling JNI stubs.
+  ctx.default_run(
+      args, profile=True, Xcompiler_option=["--compiler-filter=verify"])
+
+  # Filter out error messages for missing native methods.
+  ctx.run(fr"sed -i '/No implementation found for/d' '{args.stderr_file}'")
diff --git a/test/178-app-image-native-method/src/Main.java b/test/178-app-image-native-method/src/Main.java
index 294ad47..877efa4 100644
--- a/test/178-app-image-native-method/src/Main.java
+++ b/test/178-app-image-native-method/src/Main.java
@@ -17,6 +17,8 @@
 import dalvik.annotation.optimization.FastNative;
 import dalvik.annotation.optimization.CriticalNative;
 
+// clang-format off
+
 public class Main {
 
   public static void main(String[] args) throws Exception {
diff --git a/test/178-app-image-native-method/test-metadata.json b/test/178-app-image-native-method/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/178-app-image-native-method/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/180-native-default-method/build b/test/180-native-default-method/build
deleted file mode 100644
index b6b604f..0000000
--- a/test/180-native-default-method/build
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2020 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@"
-
-if [[ $@ != *"--jvm"* ]]; then
-  # Change the generated dex file to have a v35 magic number if it is version 38
-  if test -f classes.dex && head -c 7 classes.dex | grep -q 038; then
-    # place ascii value '035' into the classes.dex file starting at byte 4.
-    printf '035' | dd status=none conv=notrunc of=classes.dex bs=1 seek=4 count=3
-    rm -f $TEST_NAME.jar
-    ${SOONG_ZIP} -o $TEST_NAME.jar -f classes.dex
-  else
-    echo Unexpected dex verison
-    exit 1
-  fi
-fi
diff --git a/test/180-native-default-method/build.py b/test/180-native-default-method/build.py
new file mode 100644
index 0000000..62aac82
--- /dev/null
+++ b/test/180-native-default-method/build.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2022 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.
+
+def build(ctx):
+  ctx.default_build()
+  if ctx.jvm:
+    return
+  # Change the generated dex file to have a v35 magic number if it is version 38
+  with open(ctx.test_dir / "classes.dex", "rb+") as f:
+    assert f.read(8) == b"dex\n038\x00"
+    f.seek(0)
+    f.write(b"dex\n035\x00")
+  (ctx.test_dir / "180-native-default-method.jar").unlink()
+  ctx.soong_zip([
+      "-o", ctx.test_dir / "180-native-default-method.jar", "-j",
+      "-f", ctx.test_dir / "classes.dex"
+  ])
diff --git a/test/181-default-methods/build b/test/181-default-methods/build
deleted file mode 100644
index 9cd5738..0000000
--- a/test/181-default-methods/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2022 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental default-methods
diff --git a/test/181-default-methods/build.py b/test/181-default-methods/build.py
new file mode 100644
index 0000000..3e0ecd5
--- /dev/null
+++ b/test/181-default-methods/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="default-methods")
diff --git a/test/182-method-linking/Android.bp b/test/182-method-linking/Android.bp
new file mode 100644
index 0000000..cee24a9
--- /dev/null
+++ b/test/182-method-linking/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `182-method-linking`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-182-method-linking-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-182-method-linking",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-182-method-linking-src"
+    ],
+    data: [
+        ":art-run-test-182-method-linking-expected-stdout",
+        ":art-run-test-182-method-linking-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-182-method-linking-expected-stdout",
+    out: ["art-run-test-182-method-linking-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-182-method-linking-expected-stderr",
+    out: ["art-run-test-182-method-linking-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/182-method-linking/src/Main.java b/test/182-method-linking/src/Main.java
index 3902956..638ff65 100644
--- a/test/182-method-linking/src/Main.java
+++ b/test/182-method-linking/src/Main.java
@@ -33,12 +33,6 @@
 
 public class Main {
     public static void main(String args[]) {
-        try {
-            Class.forName("dalvik.system.PathClassLoader");
-        } catch (ClassNotFoundException e) {
-            usingRI = true;
-        }
-
         // A single method signature can result in multiple vtable entries
         // when package-private methods from different packages are involved.
         // All classes here define the method `void foo()` but classes
@@ -120,7 +114,6 @@
             CXI1 cxi1 = new CXI1();
             I1.callI1Foo(cxi1);
         } catch (IllegalAccessError expected) {
-            printOnDalvik("Calling pkg1.I1.foo on pkg1.CXI1");
             System.out.println("Caught IllegalAccessError");
         }
 
@@ -128,7 +121,6 @@
             CXI2 cxi2 = new CXI2();
             I2.callI2Foo(cxi2);
         } catch (IllegalAccessError expected) {
-            printOnDalvik("Calling pkg2.I2.foo on pkg1.CXI2");
             System.out.println("Caught IllegalAccessError");
         }
 
@@ -136,7 +128,6 @@
             DXI1 dxi1 = new DXI1();
             I1.callI1Foo(dxi1);
         } catch (IllegalAccessError expected) {
-            printOnDalvik("Calling pkg1.I1.foo on pkg2.DXI1");
             System.out.println("Caught IllegalAccessError");
         }
 
@@ -144,17 +135,7 @@
             DXI2 dxi2 = new DXI2();
             I2.callI2Foo(dxi2);
         } catch (IllegalAccessError expected) {
-            printOnDalvik("Calling pkg2.I2.foo on pkg2.DXI2");
             System.out.println("Caught IllegalAccessError");
         }
     }
-
-    private static void printOnDalvik(String line) {
-        if (!usingRI) {
-            // FIXME: Delay IAE until calling the method. Bug: 211854716
-            System.out.println(line);
-        }
-    }
-
-    private static boolean usingRI = false;
 }
diff --git a/test/1900-track-alloc/run b/test/1900-track-alloc/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1900-track-alloc/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1900-track-alloc/run.py b/test/1900-track-alloc/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1900-track-alloc/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1901-get-bytecodes/run b/test/1901-get-bytecodes/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1901-get-bytecodes/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1901-get-bytecodes/run.py b/test/1901-get-bytecodes/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1901-get-bytecodes/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1902-suspend/run b/test/1902-suspend/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1902-suspend/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1902-suspend/run.py b/test/1902-suspend/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1902-suspend/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1903-suspend-self/run b/test/1903-suspend-self/run
deleted file mode 100755
index 97f077b..0000000
--- a/test/1903-suspend-self/run
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
-
-return_status1=$?
-
-#Also do one run with --no-image to have one test coverage with no-image.
-./default-run "$@" --jvmti --no-image
-
-return_status2=$?
-
-# Make sure we don't silently ignore an early failure.
-(exit $return_status1) && (exit $return_status2)
diff --git a/test/1903-suspend-self/run.py b/test/1903-suspend-self/run.py
new file mode 100644
index 0000000..6f8d830
--- /dev/null
+++ b/test/1903-suspend-self/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
+
+  #Also do one run with --no-image to have one test coverage with no-image.
+  ctx.default_run(args, jvmti=True, image=False)
diff --git a/test/1904-double-suspend/run b/test/1904-double-suspend/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1904-double-suspend/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1904-double-suspend/run.py b/test/1904-double-suspend/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1904-double-suspend/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1905-suspend-native/run b/test/1905-suspend-native/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1905-suspend-native/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1905-suspend-native/run.py b/test/1905-suspend-native/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1905-suspend-native/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1906-suspend-list-me-first/run b/test/1906-suspend-list-me-first/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1906-suspend-list-me-first/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1906-suspend-list-me-first/run.py b/test/1906-suspend-list-me-first/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1906-suspend-list-me-first/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1907-suspend-list-self-twice/run b/test/1907-suspend-list-self-twice/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1907-suspend-list-self-twice/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1907-suspend-list-self-twice/run.py b/test/1907-suspend-list-self-twice/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1907-suspend-list-self-twice/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1908-suspend-native-resume-self/run b/test/1908-suspend-native-resume-self/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1908-suspend-native-resume-self/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1908-suspend-native-resume-self/run.py b/test/1908-suspend-native-resume-self/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1908-suspend-native-resume-self/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1909-per-agent-tls/run b/test/1909-per-agent-tls/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1909-per-agent-tls/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1909-per-agent-tls/run.py b/test/1909-per-agent-tls/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1909-per-agent-tls/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1910-transform-with-default/run b/test/1910-transform-with-default/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1910-transform-with-default/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1910-transform-with-default/run.py b/test/1910-transform-with-default/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1910-transform-with-default/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1911-get-local-var-table/run b/test/1911-get-local-var-table/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1911-get-local-var-table/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1911-get-local-var-table/run.py b/test/1911-get-local-var-table/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1911-get-local-var-table/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1912-get-set-local-primitive/run b/test/1912-get-set-local-primitive/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1912-get-set-local-primitive/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1912-get-set-local-primitive/run.py b/test/1912-get-set-local-primitive/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1912-get-set-local-primitive/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1913-get-set-local-objects/run b/test/1913-get-set-local-objects/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1913-get-set-local-objects/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1913-get-set-local-objects/run.py b/test/1913-get-set-local-objects/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1913-get-set-local-objects/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1914-get-local-instance/run b/test/1914-get-local-instance/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1914-get-local-instance/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1914-get-local-instance/run.py b/test/1914-get-local-instance/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1914-get-local-instance/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1915-get-set-local-current-thread/run b/test/1915-get-set-local-current-thread/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1915-get-set-local-current-thread/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1915-get-set-local-current-thread/run.py b/test/1915-get-set-local-current-thread/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1915-get-set-local-current-thread/src/Main.java b/test/1915-get-set-local-current-thread/src/Main.java
index 47e6767..5ca6f09 100644
--- a/test/1915-get-set-local-current-thread/src/Main.java
+++ b/test/1915-get-set-local-current-thread/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test1915.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test1915.run();
+    }
 }
diff --git a/test/1915-get-set-local-current-thread/src/art/Test1915.java b/test/1915-get-set-local-current-thread/src/art/Test1915.java
index a99a487..8398afa 100644
--- a/test/1915-get-set-local-current-thread/src/art/Test1915.java
+++ b/test/1915-get-set-local-current-thread/src/art/Test1915.java
@@ -31,75 +31,75 @@
 import java.util.function.Consumer;
 
 public class Test1915 {
-  public static final int SET_VALUE = 1337;
-  public static final String TARGET_VAR = "TARGET";
+    public static final int SET_VALUE = 1337;
+    public static final String TARGET_VAR = "TARGET";
 
-  public static void reportValue(Object val) {
-    System.out.println("\tValue is '" + val + "'");
-  }
-  public static interface ThrowRunnable {
-    public void run() throws Exception;
-  }
-
-  public static void IntMethod(ThrowRunnable safepoint) throws Exception {
-    int TARGET = 42;
-    safepoint.run();
-    reportValue(TARGET);
-  }
-
-  public static void run() throws Exception {
-    Locals.EnableLocalVariableAccess();
-    final Method target = Test1915.class.getDeclaredMethod("IntMethod", ThrowRunnable.class);
-    // Get Variable.
-    System.out.println("GetLocalInt on current thread!");
-    IntMethod(() -> {
-      StackTrace.StackFrameData frame = FindStackFrame(target);
-      int depth = FindExpectedFrameDepth(frame);
-      int slot = FindSlot(frame);
-      int value = Locals.GetLocalVariableInt(Thread.currentThread(), depth, slot);
-      System.out.println("From GetLocalInt(), value is " + value);
-    });
-    // Set Variable.
-    System.out.println("SetLocalInt on current thread!");
-    IntMethod(() -> {
-      StackTrace.StackFrameData frame = FindStackFrame(target);
-      int depth = FindExpectedFrameDepth(frame);
-      int slot = FindSlot(frame);
-      Locals.SetLocalVariableInt(Thread.currentThread(), depth, slot, SET_VALUE);
-    });
-  }
-
-  public static int FindSlot(StackTrace.StackFrameData frame) throws Exception {
-    long loc = frame.current_location;
-    for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) {
-      if (var.start_location <= loc &&
-          var.length + var.start_location > loc &&
-          var.name.equals(TARGET_VAR)) {
-        return var.slot;
-      }
+    public static void reportValue(Object val) {
+        System.out.println("\tValue is '" + val + "'");
     }
-    throw new Error(
-        "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc);
-  }
-
-  public static int FindExpectedFrameDepth(StackTrace.StackFrameData frame) throws Exception {
-    // Adjust the 'frame' depth since it is modified by:
-    // +1 for Get/SetLocalVariableInt in future.
-    // -1 for FindStackFrame
-    // -1 for GetStackTrace
-    // -1 for GetStackTraceNative
-    // ------------------------------
-    // -2
-    return frame.depth - 2;
-  }
-
-  private static StackTrace.StackFrameData FindStackFrame(Method target) {
-    for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(Thread.currentThread())) {
-      if (frame.method.equals(target)) {
-        return frame;
-      }
+    public static interface ThrowRunnable {
+        public void run() throws Exception;
     }
-    throw new Error("Unable to find stack frame in method " + target);
-  }
+
+    public static void IntMethod(ThrowRunnable safepoint) throws Exception {
+        int TARGET = 42;
+        safepoint.run();
+        reportValue(TARGET);
+    }
+
+    public static void run() throws Exception {
+        Locals.EnableLocalVariableAccess();
+        final Method target = Test1915.class.getDeclaredMethod("IntMethod", ThrowRunnable.class);
+        // Get Variable.
+        System.out.println("GetLocalInt on current thread!");
+        IntMethod(() -> {
+            StackTrace.StackFrameData frame = FindStackFrame(target);
+            int depth = FindExpectedFrameDepth(frame);
+            int slot = FindSlot(frame);
+            int value = Locals.GetLocalVariableInt(Thread.currentThread(), depth, slot);
+            System.out.println("From GetLocalInt(), value is " + value);
+        });
+        // Set Variable.
+        System.out.println("SetLocalInt on current thread!");
+        IntMethod(() -> {
+            StackTrace.StackFrameData frame = FindStackFrame(target);
+            int depth = FindExpectedFrameDepth(frame);
+            int slot = FindSlot(frame);
+            Locals.SetLocalVariableInt(Thread.currentThread(), depth, slot, SET_VALUE);
+        });
+    }
+
+    public static int FindSlot(StackTrace.StackFrameData frame) throws Exception {
+        long loc = frame.current_location;
+        for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) {
+            if (var.start_location <= loc &&
+                    var.length + var.start_location > loc &&
+                    var.name.equals(TARGET_VAR)) {
+                return var.slot;
+            }
+        }
+        throw new Error(
+                "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc);
+    }
+
+    public static int FindExpectedFrameDepth(StackTrace.StackFrameData frame) throws Exception {
+        // Adjust the 'frame' depth since it is modified by:
+        // +1 for Get/SetLocalVariableInt in future.
+        // -1 for FindStackFrame
+        // -1 for GetStackTrace
+        // -1 for GetStackTraceNative
+        // ------------------------------
+        // -2
+        return frame.depth - 2;
+    }
+
+    private static StackTrace.StackFrameData FindStackFrame(Method target) {
+        for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(Thread.currentThread())) {
+            if (frame.method.equals(target)) {
+                return frame;
+            }
+        }
+        throw new Error("Unable to find stack frame in method " + target);
+    }
 }
 
diff --git a/test/1916-get-set-current-frame/run b/test/1916-get-set-current-frame/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1916-get-set-current-frame/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1916-get-set-current-frame/run.py b/test/1916-get-set-current-frame/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1916-get-set-current-frame/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1916-get-set-current-frame/src/Main.java b/test/1916-get-set-current-frame/src/Main.java
index 7d0cd21..2ecf0ca 100644
--- a/test/1916-get-set-current-frame/src/Main.java
+++ b/test/1916-get-set-current-frame/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test1916.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test1916.run();
+    }
 }
diff --git a/test/1916-get-set-current-frame/src/art/Test1916.java b/test/1916-get-set-current-frame/src/art/Test1916.java
index 3e5bce2..6fe4ba2 100644
--- a/test/1916-get-set-current-frame/src/art/Test1916.java
+++ b/test/1916-get-set-current-frame/src/art/Test1916.java
@@ -31,118 +31,118 @@
 import java.util.function.Consumer;
 
 public class Test1916 {
-  public static final int SET_VALUE = 1337;
-  public static final String TARGET_VAR = "TARGET";
+    public static final int SET_VALUE = 1337;
+    public static final String TARGET_VAR = "TARGET";
 
-  public static void reportValue(Object val) {
-    System.out.println("\tValue is '" + val + "'");
-  }
+    public static void reportValue(Object val) {
+        System.out.println("\tValue is '" + val + "'");
+    }
 
-  public static class IntRunner implements Runnable {
-    private volatile boolean continueBusyLoop;
-    private volatile boolean inBusyLoop;
-    public IntRunner() {
-      this.continueBusyLoop = true;
-      this.inBusyLoop = false;
+    public static class IntRunner implements Runnable {
+        private volatile boolean continueBusyLoop;
+        private volatile boolean inBusyLoop;
+        public IntRunner() {
+            this.continueBusyLoop = true;
+            this.inBusyLoop = false;
+        }
+        public void run() {
+            int TARGET = 42;
+            // We will suspend the thread during this loop.
+            while (continueBusyLoop) {
+                inBusyLoop = true;
+            }
+            reportValue(TARGET);
+        }
+        public void waitForBusyLoopStart() { while (!inBusyLoop) {} }
+        public void finish() { continueBusyLoop = false; }
     }
-    public void run() {
-      int TARGET = 42;
-      // We will suspend the thread during this loop.
-      while (continueBusyLoop) {
-        inBusyLoop = true;
-      }
-      reportValue(TARGET);
-    }
-    public void waitForBusyLoopStart() { while (!inBusyLoop) {} }
-    public void finish() { continueBusyLoop = false; }
-  }
 
-  public static void run() throws Exception {
-    Locals.EnableLocalVariableAccess();
-    runGet();
-    runSet();
-  }
+    public static void run() throws Exception {
+        Locals.EnableLocalVariableAccess();
+        runGet();
+        runSet();
+    }
 
-  public static void runGet() throws Exception {
-    Method target = IntRunner.class.getDeclaredMethod("run");
-    // Get Int
-    IntRunner int_runner = new IntRunner();
-    Thread target_get = new Thread(int_runner, "GetLocalInt - Target");
-    target_get.start();
-    int_runner.waitForBusyLoopStart();
-    try {
-      Suspension.suspend(target_get);
-    } catch (Exception e) {
-      System.out.println("FAIL: got " + e);
-      e.printStackTrace();
-      int_runner.finish();
-      target_get.join();
-      return;
+    public static void runGet() throws Exception {
+        Method target = IntRunner.class.getDeclaredMethod("run");
+        // Get Int
+        IntRunner int_runner = new IntRunner();
+        Thread target_get = new Thread(int_runner, "GetLocalInt - Target");
+        target_get.start();
+        int_runner.waitForBusyLoopStart();
+        try {
+            Suspension.suspend(target_get);
+        } catch (Exception e) {
+            System.out.println("FAIL: got " + e);
+            e.printStackTrace();
+            int_runner.finish();
+            target_get.join();
+            return;
+        }
+        try {
+            StackTrace.StackFrameData frame = FindStackFrame(target_get, target);
+            int depth = frame.depth;
+            if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); }
+            int slot = FindSlot(frame);
+            int value = Locals.GetLocalVariableInt(target_get, depth, slot);
+            System.out.println("From GetLocalInt(), value is " + value);
+        } finally {
+            Suspension.resume(target_get);
+            int_runner.finish();
+            target_get.join();
+        }
     }
-    try {
-      StackTrace.StackFrameData frame = FindStackFrame(target_get, target);
-      int depth = frame.depth;
-      if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); }
-      int slot = FindSlot(frame);
-      int value = Locals.GetLocalVariableInt(target_get, depth, slot);
-      System.out.println("From GetLocalInt(), value is " + value);
-    } finally {
-      Suspension.resume(target_get);
-      int_runner.finish();
-      target_get.join();
-    }
-  }
 
-  public static void runSet() throws Exception {
-    Method target = IntRunner.class.getDeclaredMethod("run");
-    // Set Int
-    IntRunner int_runner = new IntRunner();
-    Thread target_set = new Thread(int_runner, "SetLocalInt - Target");
-    target_set.start();
-    int_runner.waitForBusyLoopStart();
-    try {
-      Suspension.suspend(target_set);
-    } catch (Exception e) {
-      System.out.println("FAIL: got " + e);
-      e.printStackTrace();
-      int_runner.finish();
-      target_set.join();
-      return;
+    public static void runSet() throws Exception {
+        Method target = IntRunner.class.getDeclaredMethod("run");
+        // Set Int
+        IntRunner int_runner = new IntRunner();
+        Thread target_set = new Thread(int_runner, "SetLocalInt - Target");
+        target_set.start();
+        int_runner.waitForBusyLoopStart();
+        try {
+            Suspension.suspend(target_set);
+        } catch (Exception e) {
+            System.out.println("FAIL: got " + e);
+            e.printStackTrace();
+            int_runner.finish();
+            target_set.join();
+            return;
+        }
+        try {
+            StackTrace.StackFrameData frame = FindStackFrame(target_set, target);
+            int depth = frame.depth;
+            if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); }
+            int slot = FindSlot(frame);
+            System.out.println("Setting TARGET to " + SET_VALUE);
+            Locals.SetLocalVariableInt(target_set, depth, slot, SET_VALUE);
+        } finally {
+            Suspension.resume(target_set);
+            int_runner.finish();
+            target_set.join();
+        }
     }
-    try {
-      StackTrace.StackFrameData frame = FindStackFrame(target_set, target);
-      int depth = frame.depth;
-      if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); }
-      int slot = FindSlot(frame);
-      System.out.println("Setting TARGET to " + SET_VALUE);
-      Locals.SetLocalVariableInt(target_set, depth, slot, SET_VALUE);
-    } finally {
-      Suspension.resume(target_set);
-      int_runner.finish();
-      target_set.join();
-    }
-  }
 
-  public static int FindSlot(StackTrace.StackFrameData frame) throws Exception {
-    long loc = frame.current_location;
-    for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) {
-      if (var.start_location <= loc &&
-          var.length + var.start_location > loc &&
-          var.name.equals(TARGET_VAR)) {
-        return var.slot;
-      }
+    public static int FindSlot(StackTrace.StackFrameData frame) throws Exception {
+        long loc = frame.current_location;
+        for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) {
+            if (var.start_location <= loc &&
+                    var.length + var.start_location > loc &&
+                    var.name.equals(TARGET_VAR)) {
+                return var.slot;
+            }
+        }
+        throw new Error(
+                "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc);
     }
-    throw new Error(
-        "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc);
-  }
 
-  private static StackTrace.StackFrameData FindStackFrame(Thread thr, Method target) {
-    for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
-      if (frame.method.equals(target)) {
-        return frame;
-      }
+    private static StackTrace.StackFrameData FindStackFrame(Thread thr, Method target) {
+        for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
+            if (frame.method.equals(target)) {
+                return frame;
+            }
+        }
+        throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
     }
-    throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
-  }
 }
 
diff --git a/test/1917-get-stack-frame/run b/test/1917-get-stack-frame/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1917-get-stack-frame/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1917-get-stack-frame/run.py b/test/1917-get-stack-frame/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1917-get-stack-frame/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1917-get-stack-frame/src/Main.java b/test/1917-get-stack-frame/src/Main.java
index c055a5c..c6f0853 100644
--- a/test/1917-get-stack-frame/src/Main.java
+++ b/test/1917-get-stack-frame/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test1917.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test1917.run();
+    }
 }
diff --git a/test/1917-get-stack-frame/src/art/Test1917.java b/test/1917-get-stack-frame/src/art/Test1917.java
index 75af43b..47b06cf 100644
--- a/test/1917-get-stack-frame/src/art/Test1917.java
+++ b/test/1917-get-stack-frame/src/art/Test1917.java
@@ -34,124 +34,124 @@
 import java.util.function.Consumer;
 
 public class Test1917 {
-  public final static boolean TEST_PRINT_ALL = false;
+    public final static boolean TEST_PRINT_ALL = false;
 
-  public static class ThreadPauser implements Runnable {
-    public Semaphore sem_wakeup_main = new Semaphore(0);
-    public Semaphore sem_wait = new Semaphore(0);
+    public static class ThreadPauser implements Runnable {
+        public Semaphore sem_wakeup_main = new Semaphore(0);
+        public Semaphore sem_wait = new Semaphore(0);
 
-    public void run() {
-      try {
-        sem_wakeup_main.release();
-        sem_wait.acquire();
-      } catch (Exception e) {
-        throw new Error("Error with semaphores!", e);
-      }
-    }
-
-    public void waitForOtherThreadToPause() throws Exception {
-      sem_wakeup_main.acquire();
-      while (!sem_wait.hasQueuedThreads()) {}
-    }
-
-    public void wakeupOtherThread() throws Exception {
-      sem_wait.release();
-    }
-  }
-
-  public static class StackTraceGenerator implements Runnable {
-    private final Thread thr;
-    private final Consumer<StackTrace.StackFrameData> con;
-    public StackTraceGenerator(Thread thr, Consumer<StackTrace.StackFrameData> con) {
-      this.thr = thr;
-      this.con = con;
-    }
-
-    public StackTraceGenerator(Consumer<StackTrace.StackFrameData> con) {
-      this(null, con);
-    }
-
-    public Thread getThread() {
-      if (thr == null) {
-        return Thread.currentThread();
-      } else {
-        return thr;
-      }
-    }
-    public void run() {
-      for (StackTrace.StackFrameData s : StackTrace.GetStackTrace(getThread())) {
-        con.accept(s);
-      }
-    }
-  }
-
-  public static class RecurCount implements Runnable {
-    private final int cnt;
-    private final Runnable then;
-    public RecurCount(int cnt, Runnable then) {
-      this.cnt = cnt;
-      this.then = then;
-    }
-
-    public void run() {
-      doRecur(0);
-    }
-
-    public void doRecur(int n) {
-      if (n < cnt) {
-        doRecur(n + 1);
-      } else {
-        then.run();
-      }
-    }
-  }
-
-  public static Consumer<StackTrace.StackFrameData> makePrintStackFramesConsumer()
-      throws Exception {
-    final Method end_method = Test1917.class.getDeclaredMethod("run");
-    return new Consumer<StackTrace.StackFrameData>() {
-      public void accept(StackTrace.StackFrameData data) {
-        if (TEST_PRINT_ALL) {
-          System.out.println(data);
-        } else {
-          Package p = data.method.getDeclaringClass().getPackage();
-          // Filter out anything to do with the testing harness.
-          if (p != null && p.equals(Test1917.class.getPackage())) {
-            System.out.printf("'%s' line: %d\n",
-                data.method,
-                Breakpoint.locationToLine(data.method, data.current_location));
-          } else if (data.method.getDeclaringClass().equals(Semaphore.class)) {
-            System.out.printf("'%s' line: <NOT-DETERMINISTIC>\n", data.method);
-          }
+        public void run() {
+            try {
+                sem_wakeup_main.release();
+                sem_wait.acquire();
+            } catch (Exception e) {
+                throw new Error("Error with semaphores!", e);
+            }
         }
-      }
-    };
-  }
 
-  public static void run() throws Exception {
-    System.out.println("Recurring 5 times");
-    new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer())).run();
+        public void waitForOtherThreadToPause() throws Exception {
+            sem_wakeup_main.acquire();
+            while (!sem_wait.hasQueuedThreads()) {}
+        }
 
-    System.out.println("Recurring 5 times on another thread");
-    Thread thr = new Thread(
-        Thread.currentThread().getThreadGroup(),
-        new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer())),
-        "Recurring Thread 1",
-        10*1000000 /* 10 mb*/);
-    thr.start();
-    thr.join();
+        public void wakeupOtherThread() throws Exception {
+            sem_wait.release();
+        }
+    }
 
-    System.out.println("Recurring 5 times on another thread. Stack trace from main thread!");
-    ThreadPauser pause = new ThreadPauser();
-    Thread thr2 = new Thread(
-        Thread.currentThread().getThreadGroup(),
-        new RecurCount(5, pause),
-        "Recurring Thread 2",
-        10*1000000 /* 10 mb*/);
-    thr2.start();
-    pause.waitForOtherThreadToPause();
-    new StackTraceGenerator(thr2, makePrintStackFramesConsumer()).run();
-    pause.wakeupOtherThread();
-    thr2.join();
-  }
+    public static class StackTraceGenerator implements Runnable {
+        private final Thread thr;
+        private final Consumer<StackTrace.StackFrameData> con;
+        public StackTraceGenerator(Thread thr, Consumer<StackTrace.StackFrameData> con) {
+            this.thr = thr;
+            this.con = con;
+        }
+
+        public StackTraceGenerator(Consumer<StackTrace.StackFrameData> con) {
+            this(null, con);
+        }
+
+        public Thread getThread() {
+            if (thr == null) {
+                return Thread.currentThread();
+            } else {
+                return thr;
+            }
+        }
+        public void run() {
+            for (StackTrace.StackFrameData s : StackTrace.GetStackTrace(getThread())) {
+                con.accept(s);
+            }
+        }
+    }
+
+    public static class RecurCount implements Runnable {
+        private final int cnt;
+        private final Runnable then;
+        public RecurCount(int cnt, Runnable then) {
+            this.cnt = cnt;
+            this.then = then;
+        }
+
+        public void run() {
+            doRecur(0);
+        }
+
+        public void doRecur(int n) {
+            if (n < cnt) {
+                doRecur(n + 1);
+            } else {
+                then.run();
+            }
+        }
+    }
+
+    public static Consumer<StackTrace.StackFrameData> makePrintStackFramesConsumer()
+            throws Exception {
+        final Method end_method = Test1917.class.getDeclaredMethod("run");
+        return new Consumer<StackTrace.StackFrameData>() {
+            public void accept(StackTrace.StackFrameData data) {
+                if (TEST_PRINT_ALL) {
+                    System.out.println(data);
+                } else {
+                    Package p = data.method.getDeclaringClass().getPackage();
+                    // Filter out anything to do with the testing harness.
+                    if (p != null && p.equals(Test1917.class.getPackage())) {
+                        System.out.printf("'%s' line: %d\n",
+                                data.method,
+                                Breakpoint.locationToLine(data.method, data.current_location));
+                    } else if (data.method.getDeclaringClass().equals(Semaphore.class)) {
+                        System.out.printf("'%s' line: <NOT-DETERMINISTIC>\n", data.method);
+                    }
+                }
+            }
+        };
+    }
+
+    public static void run() throws Exception {
+        System.out.println("Recurring 5 times");
+        new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer())).run();
+
+        System.out.println("Recurring 5 times on another thread");
+        Thread thr = new Thread(
+                Thread.currentThread().getThreadGroup(),
+                new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer())),
+                "Recurring Thread 1",
+                10*1000000 /* 10 mb*/);
+        thr.start();
+        thr.join();
+
+        System.out.println("Recurring 5 times on another thread. Stack trace from main thread!");
+        ThreadPauser pause = new ThreadPauser();
+        Thread thr2 = new Thread(
+                Thread.currentThread().getThreadGroup(),
+                new RecurCount(5, pause),
+                "Recurring Thread 2",
+                10*1000000 /* 10 mb*/);
+        thr2.start();
+        pause.waitForOtherThreadToPause();
+        new StackTraceGenerator(thr2, makePrintStackFramesConsumer()).run();
+        pause.wakeupOtherThread();
+        thr2.join();
+    }
 }
diff --git a/test/1919-vminit-thread-start-timing/run b/test/1919-vminit-thread-start-timing/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1919-vminit-thread-start-timing/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1919-vminit-thread-start-timing/run.py b/test/1919-vminit-thread-start-timing/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1919-vminit-thread-start-timing/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1919-vminit-thread-start-timing/src/Main.java b/test/1919-vminit-thread-start-timing/src/Main.java
index 41baf24..32a45c5 100644
--- a/test/1919-vminit-thread-start-timing/src/Main.java
+++ b/test/1919-vminit-thread-start-timing/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test1919.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test1919.run();
+    }
 }
diff --git a/test/1919-vminit-thread-start-timing/src/art/Test1919.java b/test/1919-vminit-thread-start-timing/src/art/Test1919.java
index 26ff49f..f146ae6 100644
--- a/test/1919-vminit-thread-start-timing/src/art/Test1919.java
+++ b/test/1919-vminit-thread-start-timing/src/art/Test1919.java
@@ -17,49 +17,49 @@
 package art;
 
 public class Test1919 {
-  public static final boolean PRINT_ALL_THREADS = false;
+    public static final boolean PRINT_ALL_THREADS = false;
 
-  public static void run() throws Exception {
-    Thread testing_thread = getTestingThread();
-    // TODO(b/124284724): For unknown reasons the testing thread will sometimes SEGV after the test
-    // has otherwise completed successfully. This has only been observed on the release version of
-    // art (libart.so) and I haven't had any luck reproing it. I assume it has something to do with
-    // racing between the DetachCurrentThread and shutdown but I'm not sure. Since the runtime
-    // normally never shuts down anyway for now I'll just ensure everything gets cleaned up early to
-    // prevent the problem from showing up.
-    testing_thread.join();
-    for (Event e : getEvents()) {
-      if (e.thr != null) {
-        if (PRINT_ALL_THREADS ||
-            e.thr.equals(Thread.currentThread()) ||
-            e.thr.equals(testing_thread)) {
-          System.out.println(e.name + ": " + e.thr.getName());
+    public static void run() throws Exception {
+        Thread testing_thread = getTestingThread();
+        // TODO(b/124284724): For unknown reasons the testing thread will sometimes SEGV after the
+        // test has otherwise completed successfully. This has only been observed on the release
+        // version of art (libart.so) and I haven't had any luck reproing it. I assume it has
+        // something to do with racing between the DetachCurrentThread and shutdown but I'm not
+        // sure. Since the runtime normally never shuts down anyway for now I'll just ensure
+        // everything gets cleaned up early to prevent the problem from showing up.
+        testing_thread.join();
+        for (Event e : getEvents()) {
+            if (e.thr != null) {
+                if (PRINT_ALL_THREADS ||
+                        e.thr.equals(Thread.currentThread()) ||
+                        e.thr.equals(testing_thread)) {
+                    System.out.println(e.name + ": " + e.thr.getName());
+                }
+            }
         }
-      }
     }
-  }
 
-  static class Event {
-    public final String name;
-    public final Thread thr;
-    public Event(String name, Thread thr) {
-      this.name = name;
-      this.thr = thr;
+    static class Event {
+        public final String name;
+        public final Thread thr;
+        public Event(String name, Thread thr) {
+            this.name = name;
+            this.thr = thr;
+        }
     }
-  }
 
-  public static Event[] getEvents() {
-    String[] ns = getEventNames();
-    Thread[] ts = getEventThreads();
-    Event[] es = new Event[Math.min(ns.length, ts.length)];
-    for (int i = 0; i < es.length; i++) {
-      es[i] = new Event(ns[i], ts[i]);
+    public static Event[] getEvents() {
+        String[] ns = getEventNames();
+        Thread[] ts = getEventThreads();
+        Event[] es = new Event[Math.min(ns.length, ts.length)];
+        for (int i = 0; i < es.length; i++) {
+            es[i] = new Event(ns[i], ts[i]);
+        }
+        return es;
     }
-    return es;
-  }
 
-  public static native String[] getEventNames();
-  public static native Thread[] getEventThreads();
+    public static native String[] getEventNames();
+    public static native Thread[] getEventThreads();
 
-  public static native Thread getTestingThread();
+    public static native Thread getTestingThread();
 }
diff --git a/test/1920-suspend-native-monitor/run b/test/1920-suspend-native-monitor/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1920-suspend-native-monitor/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1920-suspend-native-monitor/run.py b/test/1920-suspend-native-monitor/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1920-suspend-native-monitor/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1921-suspend-native-recursive-monitor/run b/test/1921-suspend-native-recursive-monitor/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1921-suspend-native-recursive-monitor/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1921-suspend-native-recursive-monitor/run.py b/test/1921-suspend-native-recursive-monitor/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1921-suspend-native-recursive-monitor/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1922-owned-monitors-info/run b/test/1922-owned-monitors-info/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1922-owned-monitors-info/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1922-owned-monitors-info/run.py b/test/1922-owned-monitors-info/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1922-owned-monitors-info/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1923-frame-pop/run b/test/1923-frame-pop/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1923-frame-pop/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1923-frame-pop/run.py b/test/1923-frame-pop/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1923-frame-pop/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1924-frame-pop-toggle/run b/test/1924-frame-pop-toggle/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1924-frame-pop-toggle/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1924-frame-pop-toggle/run.py b/test/1924-frame-pop-toggle/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1924-frame-pop-toggle/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1925-self-frame-pop/run b/test/1925-self-frame-pop/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1925-self-frame-pop/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1925-self-frame-pop/run.py b/test/1925-self-frame-pop/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1925-self-frame-pop/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1926-missed-frame-pop/run b/test/1926-missed-frame-pop/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1926-missed-frame-pop/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1926-missed-frame-pop/run.py b/test/1926-missed-frame-pop/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1926-missed-frame-pop/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1927-exception-event/run b/test/1927-exception-event/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1927-exception-event/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1927-exception-event/run.py b/test/1927-exception-event/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1927-exception-event/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1928-exception-event-exception/run b/test/1928-exception-event-exception/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1928-exception-event-exception/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1928-exception-event-exception/run.py b/test/1928-exception-event-exception/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1928-exception-event-exception/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1929-exception-catch-exception/run b/test/1929-exception-catch-exception/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1929-exception-catch-exception/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1929-exception-catch-exception/run.py b/test/1929-exception-catch-exception/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1929-exception-catch-exception/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1930-monitor-info/run b/test/1930-monitor-info/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1930-monitor-info/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1930-monitor-info/run.py b/test/1930-monitor-info/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1930-monitor-info/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1931-monitor-events/check b/test/1931-monitor-events/check
deleted file mode 100644
index 3f799cc..0000000
--- a/test/1931-monitor-events/check
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Art sends events for park/unpark, and the RI doesn't. Remove it from the expected output.
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  patch -p0 expected-stdout.txt < jvm-expected.patch >/dev/null
-fi
-
-./default-check "$@"
diff --git a/test/1931-monitor-events/expected-stdout.jvm.txt b/test/1931-monitor-events/expected-stdout.jvm.txt
new file mode 100644
index 0000000..e26d459
--- /dev/null
+++ b/test/1931-monitor-events/expected-stdout.jvm.txt
@@ -0,0 +1,30 @@
+Testing contended locking.
+Locker thread 1 for NamedLock[Lock testLock] contended-LOCKING NamedLock[Lock testLock]
+Locker thread 1 for NamedLock[Lock testLock] LOCKED NamedLock[Lock testLock]
+Testing park.
+Testing monitor wait.
+Locker thread 2 for NamedLock[Lock testWait] start-monitor-wait NamedLock[Lock testWait] timeout: 0
+Locker thread 2 for NamedLock[Lock testWait] monitor-waited NamedLock[Lock testWait] timed_out: false
+Testing monitor timed wait.
+Locker thread 4 for NamedLock[Lock testTimedWait] start-monitor-wait NamedLock[Lock testTimedWait] timeout: 3600000
+Locker thread 4 for NamedLock[Lock testTimedWait] monitor-waited NamedLock[Lock testTimedWait] timed_out: false
+Testing monitor timed with timeout.
+Waiting for 10 seconds.
+Locker thread 6 for NamedLock[Lock testTimedWaitTimeout] start-monitor-wait NamedLock[Lock testTimedWaitTimeout] timeout: 10000
+Locker thread 6 for NamedLock[Lock testTimedWaitTimeout] monitor-waited NamedLock[Lock testTimedWaitTimeout] timed_out: true
+Wait finished with timeout.
+Waiting on an unlocked monitor.
+Unlocked wait thread: start-monitor-wait NamedLock[Lock testUnlockedWait] timeout: 0
+Caught exception: java.lang.reflect.InvocationTargetException
+	Caused by: class java.lang.IllegalMonitorStateException
+Waiting with an illegal argument (negative timeout)
+Locker thread 7 for NamedLock[Lock testIllegalWait] start-monitor-wait NamedLock[Lock testIllegalWait] timeout: -100
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: Got an error while performing action TIMED_WAIT
+	Caused by: class java.lang.IllegalArgumentException
+Interrupt a monitor being waited on.
+Locker thread 8 for NamedLock[Lock testInteruptWait] start-monitor-wait NamedLock[Lock testInteruptWait] timeout: 0
+Locker thread 8 for NamedLock[Lock testInteruptWait] monitor-waited NamedLock[Lock testInteruptWait] timed_out: false
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: Got an error while performing action WAIT
+	Caused by: class java.lang.InterruptedException
diff --git a/test/1931-monitor-events/jvm-expected.patch b/test/1931-monitor-events/jvm-expected.patch
deleted file mode 100644
index 7595b14..0000000
--- a/test/1931-monitor-events/jvm-expected.patch
+++ /dev/null
@@ -1,3 +0,0 @@
-5,6d4
-< ParkThread start-monitor-wait NamedLock[Parking blocker object] timeout: 1
-< ParkThread monitor-waited NamedLock[Parking blocker object] timed_out: true
diff --git a/test/1931-monitor-events/run b/test/1931-monitor-events/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1931-monitor-events/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1931-monitor-events/run.py b/test/1931-monitor-events/run.py
new file mode 100644
index 0000000..6c5d4ad
--- /dev/null
+++ b/test/1931-monitor-events/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
+
+  # Art sends events for park/unpark, and the RI doesn't.
+  if args.jvm:
+    ctx.expected_stdout = ctx.expected_stdout.with_suffix(".jvm.txt")
diff --git a/test/1932-monitor-events-misc/check b/test/1932-monitor-events-misc/check
deleted file mode 100644
index 0fe4429..0000000
--- a/test/1932-monitor-events-misc/check
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# The RI sends an extra event that art doesn't. Add it to the expected output.
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  patch -p0 expected-stdout.txt < jvm-expected.patch >/dev/null
-fi
-
-./default-check "$@"
diff --git a/test/1932-monitor-events-misc/expected-stdout.jvm.txt b/test/1932-monitor-events-misc/expected-stdout.jvm.txt
new file mode 100644
index 0000000..b2b051e
--- /dev/null
+++ b/test/1932-monitor-events-misc/expected-stdout.jvm.txt
@@ -0,0 +1,105 @@
+Testing contended locking where lock is released before callback ends.
+Locker thread 1 for NamedLock[Lock testLockUncontend] contended-LOCKING NamedLock[Lock testLockUncontend]
+Releasing NamedLock[Lock testLockUncontend] during monitorEnter event.
+Locker thread 1 for NamedLock[Lock testLockUncontend] LOCKED NamedLock[Lock testLockUncontend]
+Testing throwing exceptions in monitor_enter
+Locker thread 3 for NamedLock[Lock testLockThrowEnter] contended-LOCKING NamedLock[Lock testLockThrowEnter]
+Throwing exception in MonitorEnter
+Locker thread 3 for NamedLock[Lock testLockThrowEnter] LOCKED NamedLock[Lock testLockThrowEnter]
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorEnter of NamedLock[Lock testLockThrowEnter]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowEnter], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exceptions in monitor_entered
+Locker thread 5 for NamedLock[Lock testLockThrowEntered] contended-LOCKING NamedLock[Lock testLockThrowEntered]
+Locker thread 5 for NamedLock[Lock testLockThrowEntered] LOCKED NamedLock[Lock testLockThrowEntered]
+Throwing exception in MonitorEntered
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[Lock testLockThrowEntered]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowEntered], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exceptions in both monitorEnter & MonitorEntered
+Locker thread 7 for NamedLock[Lock testLockThrowBoth] contended-LOCKING NamedLock[Lock testLockThrowBoth]
+Throwing exception in MonitorEnter
+Locker thread 7 for NamedLock[Lock testLockThrowBoth] LOCKED NamedLock[Lock testLockThrowBoth]
+Throwing exception in MonitorEntered
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[Lock testLockThrowBoth]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowBoth], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWait event
+Locker thread 8 for NamedLock[Lock testThrowWait] start-monitor-wait NamedLock[Lock testThrowWait] timeout: 0
+Throwing exception in MonitorWait
+Locker thread 8 for NamedLock[Lock testThrowWait] monitor-waited NamedLock[Lock testThrowWait] timed_out: false
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during MonitorWait of NamedLock[Lock testThrowWait]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWait event with illegal aruments
+Locker thread 9 for NamedLock[Lock testThrowIllegalWait] start-monitor-wait NamedLock[Lock testThrowIllegalWait] timeout: -100000
+Throwing exception in MonitorWait timeout = -100000
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorWait of NamedLock[Lock testThrowIllegalWait]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowIllegalWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event
+Locker thread 10 for NamedLock[Lock testThrowWaited] start-monitor-wait NamedLock[Lock testThrowWaited] timeout: 0
+Locker thread 10 for NamedLock[Lock testThrowWaited] monitor-waited NamedLock[Lock testThrowWaited] timed_out: false
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaited]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaited], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event caused by timeout
+Locker thread 12 for NamedLock[Lock testThrowWaitedTimeout] start-monitor-wait NamedLock[Lock testThrowWaitedTimeout] timeout: 5000
+Locker thread 12 for NamedLock[Lock testThrowWaitedTimeout] monitor-waited NamedLock[Lock testThrowWaitedTimeout] timed_out: true
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaitedTimeout]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaitedTimeout], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event caused by interrupt
+Locker thread 13 for NamedLock[Lock testThrowWaitedInterrupt] start-monitor-wait NamedLock[Lock testThrowWaitedInterrupt] timeout: 0
+Locker thread 13 for NamedLock[Lock testThrowWaitedInterrupt] monitor-waited NamedLock[Lock testThrowWaitedInterrupt] timed_out: false
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaitedInterrupt]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaitedInterrupt], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing ObjectMonitorInfo inside of events
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] contended-LOCKING NamedLock[Lock testMonitorInfoInEvents]
+Monitor usage in MonitorEnter: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 14 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] LOCKED NamedLock[Lock testMonitorInfoInEvents]
+Monitor usage in MonitorEntered: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] start-monitor-wait NamedLock[Lock testMonitorInfoInEvents] timeout: 0
+Monitor usage in MonitorWait: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] monitor-waited NamedLock[Lock testMonitorInfoInEvents] timed_out: false
+Monitor usage in MonitorWaited: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing that the monitor can be stolen during the MonitorWaited event.
+Locker thread 17 for NamedLock[test testWaitEnterInterleaving] start-monitor-wait NamedLock[test testWaitEnterInterleaving] timeout: 0
+Locker thread 17 for NamedLock[test testWaitEnterInterleaving] monitor-waited NamedLock[test testWaitEnterInterleaving] timed_out: false
+locking controller3 in controller2 MonitorWaited event
+Controller3 now holds the lock the monitor wait will try to re-acquire
+Testing that we can lock and release the monitor in the MonitorWait event
+Locker thread 20 for NamedLock[test testWaitMonitorEnter] start-monitor-wait NamedLock[test testWaitMonitorEnter] timeout: 0
+In wait monitor usage: MonitorUsage{ monitor: NamedLock[test testWaitMonitorEnter], owner: Locker thread 20 for NamedLock[test testWaitMonitorEnter], entryCount: 1, waiters: [], notify_waiters: [] }
+In wait monitor usage sync: MonitorUsage{ monitor: NamedLock[test testWaitMonitorEnter], owner: Locker thread 20 for NamedLock[test testWaitMonitorEnter], entryCount: 2, waiters: [], notify_waiters: [] }
+Locker thread 20 for NamedLock[test testWaitMonitorEnter] monitor-waited NamedLock[test testWaitMonitorEnter] timed_out: false
+Testing that we can lock and release the monitor in the MonitorWaited event
+Locker thread 22 for NamedLock[test testWaitedMonitorEnter] start-monitor-wait NamedLock[test testWaitedMonitorEnter] timeout: 0
+Locker thread 22 for NamedLock[test testWaitedMonitorEnter] monitor-waited NamedLock[test testWaitedMonitorEnter] timed_out: false
+In waited monitor usage: MonitorUsage{ monitor: NamedLock[test testWaitedMonitorEnter], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+In waited monitor usage sync: MonitorUsage{ monitor: NamedLock[test testWaitedMonitorEnter], owner: Locker thread 22 for NamedLock[test testWaitedMonitorEnter], entryCount: 1, waiters: [], notify_waiters: [] }
+Testing we can perform recursive lock in MonitorEntered
+Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock] contended-LOCKING NamedLock[test testRecursiveMontiorEnteredLock]
+Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock] LOCKED NamedLock[test testRecursiveMontiorEnteredLock]
+In MonitorEntered usage: MonitorUsage{ monitor: NamedLock[test testRecursiveMontiorEnteredLock], owner: Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock], entryCount: 1, waiters: [], notify_waiters: [] }
+In MonitorEntered sync: MonitorUsage{ monitor: NamedLock[test testRecursiveMontiorEnteredLock], owner: Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock], entryCount: 2, waiters: [], notify_waiters: [] }
+Testing the lock state if MonitorEnter throws in a native method
+NativeLockStateThrowEnter thread contended-LOCKING NamedLock[test testNativeLockStateThrowEnter]
+Unlocking controller1 in MonitorEnter
+Throwing exception in MonitorEnter
+NativeLockStateThrowEnter thread LOCKED NamedLock[test testNativeLockStateThrowEnter]
+MonitorEnter returned: -1
+Lock state is: MonitorUsage{ monitor: NamedLock[test testNativeLockStateThrowEnter], owner: NativeLockStateThrowEnter thread, entryCount: 1, waiters: [], notify_waiters: [] }
+Caught exception: art.Monitors$TestException: throwing exception during monitorEnter of NamedLock[test testNativeLockStateThrowEnter]
+Testing the lock state if MonitorEntered throws in a native method
+NativeLockStateThrowEntered thread contended-LOCKING NamedLock[test testNativeLockStateThrowEntered]
+Unlocking controller1 in MonitorEnter
+NativeLockStateThrowEntered thread LOCKED NamedLock[test testNativeLockStateThrowEntered]
+Throwing exception in MonitorEntered
+MonitorEnter returned: -1
+Lock state is: MonitorUsage{ monitor: NamedLock[test testNativeLockStateThrowEntered], owner: NativeLockStateThrowEntered thread, entryCount: 1, waiters: [], notify_waiters: [] }
+Caught exception: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[test testNativeLockStateThrowEntered]
diff --git a/test/1932-monitor-events-misc/jvm-expected.patch b/test/1932-monitor-events-misc/jvm-expected.patch
deleted file mode 100644
index f6b2285..0000000
--- a/test/1932-monitor-events-misc/jvm-expected.patch
+++ /dev/null
@@ -1,2 +0,0 @@
-29a30
-> Locker thread 8 for NamedLock[Lock testThrowWait] monitor-waited NamedLock[Lock testThrowWait] timed_out: false
diff --git a/test/1932-monitor-events-misc/run b/test/1932-monitor-events-misc/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1932-monitor-events-misc/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1932-monitor-events-misc/run.py b/test/1932-monitor-events-misc/run.py
new file mode 100644
index 0000000..43e66fd
--- /dev/null
+++ b/test/1932-monitor-events-misc/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
+
+  # The RI sends an extra event that art doesn't.
+  if args.jvm:
+    ctx.expected_stdout = ctx.expected_stdout.with_suffix(".jvm.txt")
diff --git a/test/1933-monitor-current-contended/run b/test/1933-monitor-current-contended/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1933-monitor-current-contended/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1933-monitor-current-contended/run.py b/test/1933-monitor-current-contended/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1933-monitor-current-contended/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1934-jvmti-signal-thread/run b/test/1934-jvmti-signal-thread/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1934-jvmti-signal-thread/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1934-jvmti-signal-thread/run.py b/test/1934-jvmti-signal-thread/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1935-get-set-current-frame-jit/run b/test/1935-get-set-current-frame-jit/run
deleted file mode 100755
index e569d08..0000000
--- a/test/1935-get-set-current-frame-jit/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ensure the test is not subject to code collection
-./default-run "$@" --jvmti --runtime-option -Xjitinitialsize:32M
diff --git a/test/1935-get-set-current-frame-jit/run.py b/test/1935-get-set-current-frame-jit/run.py
new file mode 100644
index 0000000..fb36023
--- /dev/null
+++ b/test/1935-get-set-current-frame-jit/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ensure the test is not subject to code collection
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xjitinitialsize:32M"])
diff --git a/test/1936-thread-end-events/check b/test/1936-thread-end-events/check
deleted file mode 100644
index 0fe4429..0000000
--- a/test/1936-thread-end-events/check
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# The RI sends an extra event that art doesn't. Add it to the expected output.
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  patch -p0 expected-stdout.txt < jvm-expected.patch >/dev/null
-fi
-
-./default-check "$@"
diff --git a/test/1936-thread-end-events/expected-stdout.jvm.txt b/test/1936-thread-end-events/expected-stdout.jvm.txt
new file mode 100644
index 0000000..a8e79a4
--- /dev/null
+++ b/test/1936-thread-end-events/expected-stdout.jvm.txt
@@ -0,0 +1,49 @@
+Entered public static void art.Test1936.foo()
+Thread: test-thread
+  | alive: true
+  | interrupted: false
+  | daemon: false
+  | group: java.lang.ThreadGroup[name=main,maxpri=10]
+
+Entered private void java.lang.Thread.exit()
+Thread: test-thread
+  | alive: true
+  | interrupted: false
+  | daemon: false
+  | group: java.lang.ThreadGroup[name=main,maxpri=10]
+
+Entered void java.lang.ThreadGroup.threadTerminated(java.lang.Thread)
+Thread: test-thread
+  | alive: true
+  | interrupted: false
+  | daemon: false
+  | group: java.lang.ThreadGroup[name=main,maxpri=10]
+
+Entered private void java.lang.ThreadGroup.remove(java.lang.Thread)
+Thread: test-thread
+  | alive: true
+  | interrupted: false
+  | daemon: false
+  | group: java.lang.ThreadGroup[name=main,maxpri=10]
+
+Entered public static native void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int)
+Thread: test-thread
+  | alive: true
+  | interrupted: false
+  | daemon: false
+  | group: java.lang.ThreadGroup[name=main,maxpri=10]
+
+Entered public static void art.Test1936.NotifyThreadEnd(java.lang.Thread)
+Thread: test-thread
+  | alive: true
+  | interrupted: false
+  | daemon: false
+  | group: null
+
+Entered public static void art.Test1936.foo()
+Thread: test-thread
+  | alive: true
+  | interrupted: false
+  | daemon: false
+  | group: null
+
diff --git a/test/1936-thread-end-events/jvm-expected.patch b/test/1936-thread-end-events/jvm-expected.patch
deleted file mode 100644
index ddb30a3..0000000
--- a/test/1936-thread-end-events/jvm-expected.patch
+++ /dev/null
@@ -1,16 +0,0 @@
-7a8,14
-> Entered private void java.lang.Thread.exit()
-> Thread: test-thread
->   | alive: true
->   | interrupted: false
->   | daemon: false
->   | group: java.lang.ThreadGroup[name=main,maxpri=10]
-> 
-34c41
-<   | group: java.lang.ThreadGroup[name=main,maxpri=10]
----
->   | group: null
-41c48
-<   | group: java.lang.ThreadGroup[name=main,maxpri=10]
----
->   | group: null
diff --git a/test/1936-thread-end-events/run b/test/1936-thread-end-events/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1936-thread-end-events/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1936-thread-end-events/run.py b/test/1936-thread-end-events/run.py
new file mode 100644
index 0000000..3e1a8e9
--- /dev/null
+++ b/test/1936-thread-end-events/run.py
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
+
+  # The RI sends an extra event that art doesn't.
+  if args.jvm:
+    ctx.expected_stdout = ctx.expected_stdout.with_suffix(".jvm.txt")
diff --git a/test/1937-transform-soft-fail/run b/test/1937-transform-soft-fail/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1937-transform-soft-fail/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1937-transform-soft-fail/run.py b/test/1937-transform-soft-fail/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1937-transform-soft-fail/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1938-transform-abstract-single-impl/run b/test/1938-transform-abstract-single-impl/run
deleted file mode 100755
index adb1a1c..0000000
--- a/test/1938-transform-abstract-single-impl/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --no-app-image
diff --git a/test/1938-transform-abstract-single-impl/run.py b/test/1938-transform-abstract-single-impl/run.py
new file mode 100644
index 0000000..23b8dc3
--- /dev/null
+++ b/test/1938-transform-abstract-single-impl/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, app_image=False)
diff --git a/test/1939-proxy-frames/run b/test/1939-proxy-frames/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1939-proxy-frames/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1939-proxy-frames/run.py b/test/1939-proxy-frames/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1939-proxy-frames/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1940-ddms-ext/ddm_ext.cc b/test/1940-ddms-ext/ddm_ext.cc
index 110ad64..6461bdd 100644
--- a/test/1940-ddms-ext/ddm_ext.cc
+++ b/test/1940-ddms-ext/ddm_ext.cc
@@ -21,8 +21,8 @@
 
 // Test infrastructure
 #include "jvmti_helper.h"
-#include "nativehelper/scoped_local_ref.h"
-#include "nativehelper/scoped_primitive_array.h"
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
 #include "test_env.h"
 
 namespace art {
diff --git a/test/1940-ddms-ext/run b/test/1940-ddms-ext/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1940-ddms-ext/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1940-ddms-ext/run.py b/test/1940-ddms-ext/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1940-ddms-ext/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1940-ddms-ext/src-art/art/Test1940.java b/test/1940-ddms-ext/src-art/art/Test1940.java
index 605e409..7adcd4a 100644
--- a/test/1940-ddms-ext/src-art/art/Test1940.java
+++ b/test/1940-ddms-ext/src-art/art/Test1940.java
@@ -16,6 +16,7 @@
 
 package art;
 
+import java.lang.reflect.Field;
 import org.apache.harmony.dalvik.ddmc.*;
 import dalvik.system.VMDebug;
 
@@ -50,18 +51,21 @@
     }
   }
 
-  private static boolean chunkEq(Chunk a, Chunk b) {
-    return a.type == b.type &&
-           a.offset == b.offset &&
-           a.length == b.length &&
-           Arrays.equals(a.data, b.data);
+  private static boolean chunkEq(Chunk c1, Chunk c2) {
+    ChunkWrapper a = new ChunkWrapper(c1);
+    ChunkWrapper b = new ChunkWrapper(c2);
+    return a.type() == b.type() &&
+           a.offset() == b.offset() &&
+           a.length() == b.length() &&
+           Arrays.equals(a.data(), b.data());
   }
 
   private static String printChunk(Chunk k) {
-    byte[] out = new byte[k.length];
-    System.arraycopy(k.data, k.offset, out, 0, k.length);
+    ChunkWrapper c = new ChunkWrapper(k);
+    byte[] out = new byte[c.length()];
+    System.arraycopy(c.data(), c.offset(), out, 0, c.length());
     return String.format("Chunk(Type: 0x%X, Len: %d, data: %s)",
-        k.type, k.length, Arrays.toString(out));
+        c.type(), c.length(), Arrays.toString(out));
   }
 
   private static final class MyDdmHandler extends ChunkHandler {
@@ -73,7 +77,8 @@
         // For this test we will simply calculate the checksum
         ByteBuffer b = ByteBuffer.wrap(new byte[8]);
         Adler32 a = new Adler32();
-        a.update(req.data, req.offset, req.length);
+        ChunkWrapper reqWrapper = new ChunkWrapper(req);
+        a.update(reqWrapper.data(), reqWrapper.offset(), reqWrapper.length());
         b.order(ByteOrder.BIG_ENDIAN);
         long val = a.getValue();
         b.putLong(val);
@@ -92,6 +97,49 @@
     }
   }
 
+
+  /**
+   * Wrapper for accessing the hidden fields in {@link Chunk} in CTS.
+   */
+  private static class ChunkWrapper {
+    private Chunk c;
+
+    ChunkWrapper(Chunk c) {
+      this.c = c;
+    }
+
+    int type() {
+      return c.type;
+    }
+
+    int length() {
+      try {
+        Field f = Chunk.class.getField("length");
+        return (int) f.get(c);
+      } catch (NoSuchFieldException | IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    byte[] data() {
+      try {
+        Field f = Chunk.class.getField("data");
+        return (byte[]) f.get(c);
+      } catch (NoSuchFieldException | IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    int offset() {
+      try {
+        Field f = Chunk.class.getField("offset");
+        return (int) f.get(c);
+      } catch (NoSuchFieldException | IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
   public static final ChunkHandler SINGLE_HANDLER = new MyDdmHandler();
 
   public static DdmHandler CURRENT_HANDLER;
diff --git a/test/1941-dispose-stress/run b/test/1941-dispose-stress/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1941-dispose-stress/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1941-dispose-stress/run.py b/test/1941-dispose-stress/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1941-dispose-stress/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1942-suspend-raw-monitor-exit/run b/test/1942-suspend-raw-monitor-exit/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1942-suspend-raw-monitor-exit/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1942-suspend-raw-monitor-exit/run.py b/test/1942-suspend-raw-monitor-exit/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1942-suspend-raw-monitor-exit/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1943-suspend-raw-monitor-wait/run b/test/1943-suspend-raw-monitor-wait/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1943-suspend-raw-monitor-wait/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1943-suspend-raw-monitor-wait/run.py b/test/1943-suspend-raw-monitor-wait/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/1943-suspend-raw-monitor-wait/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1946-list-descriptors/run b/test/1946-list-descriptors/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1946-list-descriptors/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1946-list-descriptors/run.py b/test/1946-list-descriptors/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1946-list-descriptors/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1947-breakpoint-redefine-deopt/run b/test/1947-breakpoint-redefine-deopt/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1947-breakpoint-redefine-deopt/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1947-breakpoint-redefine-deopt/run.py b/test/1947-breakpoint-redefine-deopt/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1947-breakpoint-redefine-deopt/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1948-obsolete-const-method-handle/build b/test/1948-obsolete-const-method-handle/build
deleted file mode 100644
index d0e7a8c..0000000
--- a/test/1948-obsolete-const-method-handle/build
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Make us exit on a failure
-set -e
-
-mkdir classes
-./util-src/build-classes $PWD/classes
-
-./default-build --api-level 28 "$@"
diff --git a/test/1948-obsolete-const-method-handle/build.py b/test/1948-obsolete-const-method-handle/build.py
new file mode 100644
index 0000000..4eb0ccb
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build(api_level=28)
diff --git a/test/1948-obsolete-const-method-handle/generate-sources b/test/1948-obsolete-const-method-handle/generate-sources
new file mode 100755
index 0000000..9cdc749
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/generate-sources
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2018 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.
+
+# Make us exit on a failure
+set -e
+
+mkdir classes
+./util-src/build-classes $PWD/classes
diff --git a/test/1948-obsolete-const-method-handle/run b/test/1948-obsolete-const-method-handle/run
deleted file mode 100755
index 9eacb4c..0000000
--- a/test/1948-obsolete-const-method-handle/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Squash the exit status and put it in expected
-./default-run --jvmti "$@"
diff --git a/test/1948-obsolete-const-method-handle/run.py b/test/1948-obsolete-const-method-handle/run.py
new file mode 100644
index 0000000..afb1374
--- /dev/null
+++ b/test/1948-obsolete-const-method-handle/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2018 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.
+
+
+def run(ctx, args):
+  # Squash the exit status and put it in expected
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/TestGenerator.java b/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/TestGenerator.java
index 3d194b4..f20012f 100644
--- a/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/TestGenerator.java
+++ b/test/1948-obsolete-const-method-handle/util-src/src/art/constmethodhandle/TestGenerator.java
@@ -71,7 +71,7 @@
     ClassReader cr = new ClassReader(initClass);
     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
     cr.accept(
-        new ClassVisitor(Opcodes.ASM7, cw) {
+        new ClassVisitor(Opcodes.ASM9, cw) {
           @Override
           public void visitEnd() {
             generateStringAccessorMethod(
@@ -143,7 +143,7 @@
     ClassReader cr = new ClassReader(inputClass);
     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
     cr.accept(
-        new ClassVisitor(Opcodes.ASM7, cw) {
+        new ClassVisitor(Opcodes.ASM9, cw) {
           @Override
           public void visitEnd() {
             generateRunTest(cw, toCall);
diff --git a/test/1949-short-dex-file/run b/test/1949-short-dex-file/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1949-short-dex-file/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1949-short-dex-file/run.py b/test/1949-short-dex-file/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1949-short-dex-file/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1950-unprepared-transform/check b/test/1950-unprepared-transform/check
deleted file mode 100755
index 0fe4429..0000000
--- a/test/1950-unprepared-transform/check
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# The RI sends an extra event that art doesn't. Add it to the expected output.
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  patch -p0 expected-stdout.txt < jvm-expected.patch >/dev/null
-fi
-
-./default-check "$@"
diff --git a/test/1950-unprepared-transform/expected-stdout.jvm.txt b/test/1950-unprepared-transform/expected-stdout.jvm.txt
new file mode 100644
index 0000000..cc470e1
--- /dev/null
+++ b/test/1950-unprepared-transform/expected-stdout.jvm.txt
@@ -0,0 +1,7 @@
+Redefine in ClassLoad on current thread.
+retransformClasses on an unprepared class succeeded
+Object out is: Transformed object!
+Redefine during ClassLoad on another thread.
+retransformClasses on an unprepared class succeeded
+Object out is: Transformed object!
+Redefinition thread finished.
diff --git a/test/1950-unprepared-transform/jvm-expected.patch b/test/1950-unprepared-transform/jvm-expected.patch
deleted file mode 100644
index fd0131e..0000000
--- a/test/1950-unprepared-transform/jvm-expected.patch
+++ /dev/null
@@ -1,6 +0,0 @@
-2,3c2,3
-< Trying to redefine: class Transform. Caught error class java.lang.Exception: Failed to retransform class <LTransform;> due to JVMTI_ERROR_INTERNAL
-< Object out is: NON Transformed Object
----
-> retransformClasses on an unprepared class succeeded
-> Object out is: Transformed object!
diff --git a/test/1950-unprepared-transform/run b/test/1950-unprepared-transform/run
deleted file mode 100755
index adb1a1c..0000000
--- a/test/1950-unprepared-transform/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --no-app-image
diff --git a/test/1950-unprepared-transform/run.py b/test/1950-unprepared-transform/run.py
new file mode 100644
index 0000000..995bbbd
--- /dev/null
+++ b/test/1950-unprepared-transform/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, app_image=False)
+
+  # The RI sends an extra event that art doesn't.
+  if args.jvm:
+    ctx.expected_stdout = ctx.expected_stdout.with_suffix(".jvm.txt")
diff --git a/test/1951-monitor-enter-no-suspend/run b/test/1951-monitor-enter-no-suspend/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1951-monitor-enter-no-suspend/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1951-monitor-enter-no-suspend/run.py b/test/1951-monitor-enter-no-suspend/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1951-monitor-enter-no-suspend/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1953-pop-frame/check b/test/1953-pop-frame/check
deleted file mode 100755
index 24bbf07..0000000
--- a/test/1953-pop-frame/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# The RI has restrictions and bugs around some PopFrame behavior that ART lacks.
-# See b/116003018. Some configurations cannot handle the class load events in
-# quite the right way so they are disabled there too.
-./default-check "$@" || \
-  (patch -p0 expected-stdout.txt < class-loading-expected.patch >/dev/null && ./default-check "$@")
diff --git a/test/1953-pop-frame/class-loading-expected.patch b/test/1953-pop-frame/class-loading-expected.patch
deleted file mode 100644
index 1a5eda7..0000000
--- a/test/1953-pop-frame/class-loading-expected.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-36a37,50
-> Test stopped during notifyFramePop without exception on pop of calledFunction
-> Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
-> result is StandardTestObject { cnt: 2 } base-call count: 1
-> Test stopped during notifyFramePop without exception on pop of doNothing
-> Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
-> result is StandardTestObject { cnt: 1 } base-call count: 1
-> Test stopped during notifyFramePop with exception on pop of calledFunction
-> Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
-> art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
-> result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
-> Test stopped during notifyFramePop with exception on pop of doThrow
-> Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
-> art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
-> result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1
-60a75,94
-> Test stopped during a ClassLoad event.
-> Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
-> Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
-> 	art.Test1953.popFrame(Native Method)
-> 	art.Test1953.runTestOn(Test1953.java)
-> 	art.Test1953.runTestOn(Test1953.java)
-> 	art.Test1953.runTests(Test1953.java)
-> 	<Additional frames hidden>
-> TC0.foo == 1
-> result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
-> Test stopped during a ClassPrepare event.
-> Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
-> Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
-> 	art.Test1953.popFrame(Native Method)
-> 	art.Test1953.runTestOn(Test1953.java)
-> 	art.Test1953.runTestOn(Test1953.java)
-> 	art.Test1953.runTests(Test1953.java)
-> 	<Additional frames hidden>
-> TC1.foo == 2
-> result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
diff --git a/test/1953-pop-frame/expected-stdout.no-jvm.txt b/test/1953-pop-frame/expected-stdout.no-jvm.txt
new file mode 100644
index 0000000..e75ea64
--- /dev/null
+++ b/test/1953-pop-frame/expected-stdout.no-jvm.txt
@@ -0,0 +1,121 @@
+Test stopped using breakpoint
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with declared synchronized function
+Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with synchronized block
+Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedTestObject { cnt: 2 } base-call count: 1
+Test stopped on single step
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped on field access
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped on field modification
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped during Method Exit of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Enter of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during Method Enter of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit due to exception thrown in same function
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1
+Test stopped during Method Exit due to exception thrown in subroutine
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1
+Test stopped during notifyFramePop without exception on pop of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during notifyFramePop without exception on pop of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during notifyFramePop with exception on pop of calledFunction
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during notifyFramePop with exception on pop of doThrow
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine)
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in calling function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in parent of calling function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught!
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function.
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during a ClassLoad event.
+Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+TC0.foo == 1
+result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
+Test stopped during a ClassPrepare event.
+Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+TC1.foo == 2
+result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
+Test stopped during random Suspend.
+Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0
+result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1
+Test redefining frame being popped.
+Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0
+result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1
+Test stopped during a native method fails
+Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCalledObject { cnt: 1 } base-call count: 1
+Test stopped in a method called by native fails
+Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCallerObject { cnt: 1 } base-call count: 1
+Test stopped with monitor in enclosing frame.
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
diff --git a/test/1953-pop-frame/run b/test/1953-pop-frame/run
deleted file mode 100755
index d16d4e6..0000000
--- a/test/1953-pop-frame/run
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# On RI we need to turn class-load tests off since those events are buggy around
-# pop-frame (see b/116003018).
-ARGS=""
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  ARGS="--args DISABLE_CLASS_LOAD_TESTS"
-fi
-
-./default-run "$@" --jvmti $ARGS
diff --git a/test/1953-pop-frame/run.py b/test/1953-pop-frame/run.py
new file mode 100644
index 0000000..6438a1b
--- /dev/null
+++ b/test/1953-pop-frame/run.py
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # On RI we need to turn class-load tests off since those events are buggy around
+  # pop-frame (see b/116003018).
+  test_args = ["DISABLE_CLASS_LOAD_TESTS"] if args.jvm else []
+
+  ctx.default_run(args, jvmti=True, test_args=test_args)
+
+  # The RI has restrictions and bugs around some PopFrame behavior that ART lacks.
+  # See b/116003018. Some configurations cannot handle the class load events in
+  # quite the right way so they are disabled there too.
+  if not (args.jvm or not args.prebuild or
+          (args.jvmti_redefine_stress and args.host)):
+    ctx.expected_stdout = ctx.expected_stdout.with_suffix(".no-jvm.txt")
diff --git a/test/1954-pop-frame-jit/check b/test/1954-pop-frame-jit/check
deleted file mode 100755
index e25838a..0000000
--- a/test/1954-pop-frame-jit/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# The RI has restrictions and bugs around some PopFrame behavior that ART lacks.
-# See b/116003018. Some configurations cannot handle the class load events in
-# quite the right way so they are disabled there too.
-./default-check "$@" || \
-  (patch -p0 expected-stdout.txt < jvm-expected.patch >/dev/null && ./default-check "$@")
diff --git a/test/1954-pop-frame-jit/expected-stdout.jvm.txt b/test/1954-pop-frame-jit/expected-stdout.jvm.txt
new file mode 100644
index 0000000..dafc6b4
--- /dev/null
+++ b/test/1954-pop-frame-jit/expected-stdout.jvm.txt
@@ -0,0 +1,87 @@
+Test stopped using breakpoint
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with declared synchronized function
+Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with synchronized block
+Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedTestObject { cnt: 2 } base-call count: 1
+Test stopped on single step
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped on field access
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped on field modification
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped during Method Exit of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Enter of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during Method Enter of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit due to exception thrown in same function
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1
+Test stopped during Method Exit due to exception thrown in subroutine
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine)
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in calling function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in parent of calling function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught!
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function.
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during random Suspend.
+Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0
+result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1
+Test redefining frame being popped.
+Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0
+result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1
+Test stopped during a native method fails
+Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCalledObject { cnt: 1 } base-call count: 1
+Test stopped in a method called by native fails
+Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCallerObject { cnt: 1 } base-call count: 1
+Test stopped with monitor in enclosing frame.
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
diff --git a/test/1954-pop-frame-jit/jvm-expected.patch b/test/1954-pop-frame-jit/jvm-expected.patch
deleted file mode 100644
index 6539d56..0000000
--- a/test/1954-pop-frame-jit/jvm-expected.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-37,50d36
-< Test stopped during notifyFramePop without exception on pop of calledFunction
-< Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
-< result is StandardTestObject { cnt: 2 } base-call count: 1
-< Test stopped during notifyFramePop without exception on pop of doNothing
-< Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
-< result is StandardTestObject { cnt: 1 } base-call count: 1
-< Test stopped during notifyFramePop with exception on pop of calledFunction
-< Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
-< art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
-< result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
-< Test stopped during notifyFramePop with exception on pop of doThrow
-< Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
-< art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
-< result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1
-75,94d60
-< Test stopped during a ClassLoad event.
-< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
-< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
-< 	art.Test1953.popFrame(Native Method)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTests(Test1953.java)
-< 	<Additional frames hidden>
-< TC0.foo == 1
-< result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
-< Test stopped during a ClassPrepare event.
-< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
-< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
-< 	art.Test1953.popFrame(Native Method)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTests(Test1953.java)
-< 	<Additional frames hidden>
-< TC1.foo == 2
-< result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
diff --git a/test/1954-pop-frame-jit/run b/test/1954-pop-frame-jit/run
deleted file mode 100755
index d16d4e6..0000000
--- a/test/1954-pop-frame-jit/run
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# On RI we need to turn class-load tests off since those events are buggy around
-# pop-frame (see b/116003018).
-ARGS=""
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  ARGS="--args DISABLE_CLASS_LOAD_TESTS"
-fi
-
-./default-run "$@" --jvmti $ARGS
diff --git a/test/1954-pop-frame-jit/run.py b/test/1954-pop-frame-jit/run.py
new file mode 100644
index 0000000..43ece36
--- /dev/null
+++ b/test/1954-pop-frame-jit/run.py
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # On RI we need to turn class-load tests off since those events are buggy around
+  # pop-frame (see b/116003018).
+  test_args = ["DISABLE_CLASS_LOAD_TESTS"] if args.jvm else []
+
+  ctx.default_run(args, jvmti=True, test_args=test_args)
+
+  # The RI has restrictions and bugs around some PopFrame behavior that ART lacks.
+  # See b/116003018. Some configurations cannot handle the class load events in
+  # quite the right way so they are disabled there too.
+  if (args.jvm or not args.prebuild or
+      (args.jvmti_redefine_stress and args.host)):
+    ctx.expected_stdout = ctx.expected_stdout.with_suffix(".jvm.txt")
diff --git a/test/1955-pop-frame-jit-called/check b/test/1955-pop-frame-jit-called/check
deleted file mode 100755
index e25838a..0000000
--- a/test/1955-pop-frame-jit-called/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# The RI has restrictions and bugs around some PopFrame behavior that ART lacks.
-# See b/116003018. Some configurations cannot handle the class load events in
-# quite the right way so they are disabled there too.
-./default-check "$@" || \
-  (patch -p0 expected-stdout.txt < jvm-expected.patch >/dev/null && ./default-check "$@")
diff --git a/test/1955-pop-frame-jit-called/expected-stdout.jvm.txt b/test/1955-pop-frame-jit-called/expected-stdout.jvm.txt
new file mode 100644
index 0000000..dafc6b4
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/expected-stdout.jvm.txt
@@ -0,0 +1,87 @@
+Test stopped using breakpoint
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with declared synchronized function
+Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with synchronized block
+Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedTestObject { cnt: 2 } base-call count: 1
+Test stopped on single step
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped on field access
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped on field modification
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped during Method Exit of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Enter of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during Method Enter of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit due to exception thrown in same function
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1
+Test stopped during Method Exit due to exception thrown in subroutine
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine)
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in calling function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in parent of calling function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught!
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function.
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during random Suspend.
+Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0
+result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1
+Test redefining frame being popped.
+Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0
+result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1
+Test stopped during a native method fails
+Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCalledObject { cnt: 1 } base-call count: 1
+Test stopped in a method called by native fails
+Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCallerObject { cnt: 1 } base-call count: 1
+Test stopped with monitor in enclosing frame.
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
diff --git a/test/1955-pop-frame-jit-called/jvm-expected.patch b/test/1955-pop-frame-jit-called/jvm-expected.patch
deleted file mode 100644
index 6539d56..0000000
--- a/test/1955-pop-frame-jit-called/jvm-expected.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-37,50d36
-< Test stopped during notifyFramePop without exception on pop of calledFunction
-< Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
-< result is StandardTestObject { cnt: 2 } base-call count: 1
-< Test stopped during notifyFramePop without exception on pop of doNothing
-< Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
-< result is StandardTestObject { cnt: 1 } base-call count: 1
-< Test stopped during notifyFramePop with exception on pop of calledFunction
-< Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
-< art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
-< result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
-< Test stopped during notifyFramePop with exception on pop of doThrow
-< Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
-< art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
-< result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1
-75,94d60
-< Test stopped during a ClassLoad event.
-< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
-< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
-< 	art.Test1953.popFrame(Native Method)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTests(Test1953.java)
-< 	<Additional frames hidden>
-< TC0.foo == 1
-< result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
-< Test stopped during a ClassPrepare event.
-< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
-< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
-< 	art.Test1953.popFrame(Native Method)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTests(Test1953.java)
-< 	<Additional frames hidden>
-< TC1.foo == 2
-< result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
diff --git a/test/1955-pop-frame-jit-called/run b/test/1955-pop-frame-jit-called/run
deleted file mode 100755
index 2984461..0000000
--- a/test/1955-pop-frame-jit-called/run
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# On RI we need to turn class-load tests off since those events are buggy around
-# pop-frame (see b/116003018).
-ARGS=""
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  ARGS="--args DISABLE_CLASS_LOAD_TESTS"
-fi
-
-# The jitthreshold prevents the jit from compiling anything except those which
-# we explicitly request.
-./default-run "$@" --android-runtime-option -Xjitthreshold:1000 --jvmti $ARGS
diff --git a/test/1955-pop-frame-jit-called/run.py b/test/1955-pop-frame-jit-called/run.py
new file mode 100644
index 0000000..44fe18e
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/run.py
@@ -0,0 +1,36 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # On RI we need to turn class-load tests off since those events are buggy around
+  # pop-frame (see b/116003018).
+  test_args = ["DISABLE_CLASS_LOAD_TESTS"] if args.jvm else []
+
+  # The jitthreshold prevents the jit from compiling anything except those which
+  # we explicitly request.
+  ctx.default_run(
+      args,
+      android_runtime_option=["-Xjitthreshold:1000"],
+      jvmti=True,
+      test_args=test_args)
+
+  # The RI has restrictions and bugs around some PopFrame behavior that ART lacks.
+  # See b/116003018. Some configurations cannot handle the class load events in
+  # quite the right way so they are disabled there too.
+  if (args.jvm or not args.prebuild or
+      (args.jvmti_redefine_stress and args.host)):
+    ctx.expected_stdout = ctx.expected_stdout.with_suffix(".jvm.txt")
diff --git a/test/1956-pop-frame-jit-calling/check b/test/1956-pop-frame-jit-calling/check
deleted file mode 100755
index e25838a..0000000
--- a/test/1956-pop-frame-jit-calling/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# The RI has restrictions and bugs around some PopFrame behavior that ART lacks.
-# See b/116003018. Some configurations cannot handle the class load events in
-# quite the right way so they are disabled there too.
-./default-check "$@" || \
-  (patch -p0 expected-stdout.txt < jvm-expected.patch >/dev/null && ./default-check "$@")
diff --git a/test/1956-pop-frame-jit-calling/expected-stdout.jvm.txt b/test/1956-pop-frame-jit-calling/expected-stdout.jvm.txt
new file mode 100644
index 0000000..dafc6b4
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/expected-stdout.jvm.txt
@@ -0,0 +1,87 @@
+Test stopped using breakpoint
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with declared synchronized function
+Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with synchronized block
+Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedTestObject { cnt: 2 } base-call count: 1
+Test stopped on single step
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped on field access
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped on field modification
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped during Method Exit of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Enter of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during Method Enter of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit due to exception thrown in same function
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1
+Test stopped during Method Exit due to exception thrown in subroutine
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine)
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in calling function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in parent of calling function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught!
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function.
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during random Suspend.
+Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0
+result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1
+Test redefining frame being popped.
+Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0
+result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1
+Test stopped during a native method fails
+Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCalledObject { cnt: 1 } base-call count: 1
+Test stopped in a method called by native fails
+Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCallerObject { cnt: 1 } base-call count: 1
+Test stopped with monitor in enclosing frame.
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
diff --git a/test/1956-pop-frame-jit-calling/jvm-expected.patch b/test/1956-pop-frame-jit-calling/jvm-expected.patch
deleted file mode 100644
index 6539d56..0000000
--- a/test/1956-pop-frame-jit-calling/jvm-expected.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-37,50d36
-< Test stopped during notifyFramePop without exception on pop of calledFunction
-< Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
-< result is StandardTestObject { cnt: 2 } base-call count: 1
-< Test stopped during notifyFramePop without exception on pop of doNothing
-< Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
-< result is StandardTestObject { cnt: 1 } base-call count: 1
-< Test stopped during notifyFramePop with exception on pop of calledFunction
-< Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
-< art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
-< result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
-< Test stopped during notifyFramePop with exception on pop of doThrow
-< Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
-< art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
-< result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1
-75,94d60
-< Test stopped during a ClassLoad event.
-< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
-< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
-< 	art.Test1953.popFrame(Native Method)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTests(Test1953.java)
-< 	<Additional frames hidden>
-< TC0.foo == 1
-< result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
-< Test stopped during a ClassPrepare event.
-< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
-< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
-< 	art.Test1953.popFrame(Native Method)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTestOn(Test1953.java)
-< 	art.Test1953.runTests(Test1953.java)
-< 	<Additional frames hidden>
-< TC1.foo == 2
-< result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
diff --git a/test/1956-pop-frame-jit-calling/run b/test/1956-pop-frame-jit-calling/run
deleted file mode 100755
index 2984461..0000000
--- a/test/1956-pop-frame-jit-calling/run
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# On RI we need to turn class-load tests off since those events are buggy around
-# pop-frame (see b/116003018).
-ARGS=""
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  ARGS="--args DISABLE_CLASS_LOAD_TESTS"
-fi
-
-# The jitthreshold prevents the jit from compiling anything except those which
-# we explicitly request.
-./default-run "$@" --android-runtime-option -Xjitthreshold:1000 --jvmti $ARGS
diff --git a/test/1956-pop-frame-jit-calling/run.py b/test/1956-pop-frame-jit-calling/run.py
new file mode 100644
index 0000000..44fe18e
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/run.py
@@ -0,0 +1,36 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # On RI we need to turn class-load tests off since those events are buggy around
+  # pop-frame (see b/116003018).
+  test_args = ["DISABLE_CLASS_LOAD_TESTS"] if args.jvm else []
+
+  # The jitthreshold prevents the jit from compiling anything except those which
+  # we explicitly request.
+  ctx.default_run(
+      args,
+      android_runtime_option=["-Xjitthreshold:1000"],
+      jvmti=True,
+      test_args=test_args)
+
+  # The RI has restrictions and bugs around some PopFrame behavior that ART lacks.
+  # See b/116003018. Some configurations cannot handle the class load events in
+  # quite the right way so they are disabled there too.
+  if (args.jvm or not args.prebuild or
+      (args.jvmti_redefine_stress and args.host)):
+    ctx.expected_stdout = ctx.expected_stdout.with_suffix(".jvm.txt")
diff --git a/test/1957-error-ext/run b/test/1957-error-ext/run
deleted file mode 100755
index 8be0ed4..0000000
--- a/test/1957-error-ext/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-
-./default-run "$@" --jvmti
diff --git a/test/1957-error-ext/run.py b/test/1957-error-ext/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1957-error-ext/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1958-transform-try-jit/run b/test/1958-transform-try-jit/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1958-transform-try-jit/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1958-transform-try-jit/run.py b/test/1958-transform-try-jit/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1958-transform-try-jit/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1959-redefine-object-instrument/run b/test/1959-redefine-object-instrument/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1959-redefine-object-instrument/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1959-redefine-object-instrument/run.py b/test/1959-redefine-object-instrument/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1959-redefine-object-instrument/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1960-obsolete-jit-multithread-native/run b/test/1960-obsolete-jit-multithread-native/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1960-obsolete-jit-multithread-native/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1960-obsolete-jit-multithread-native/run.py b/test/1960-obsolete-jit-multithread-native/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1960-obsolete-jit-multithread-native/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1961-obsolete-jit-multithread/run b/test/1961-obsolete-jit-multithread/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1961-obsolete-jit-multithread/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1961-obsolete-jit-multithread/run.py b/test/1961-obsolete-jit-multithread/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1961-obsolete-jit-multithread/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1962-multi-thread-events/run b/test/1962-multi-thread-events/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1962-multi-thread-events/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1962-multi-thread-events/run.py b/test/1962-multi-thread-events/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1962-multi-thread-events/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1963-add-to-dex-classloader-in-memory/check b/test/1963-add-to-dex-classloader-in-memory/check
deleted file mode 100755
index d536891..0000000
--- a/test/1963-add-to-dex-classloader-in-memory/check
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Some of our test devices are so old that they don't have memfd_create and are setup in such a way
-# that tmpfile() doesn't work. In these cases this test cannot complete successfully.
-
-if grep -q  -- '---NO memfd_create---' $@; then
-  echo "The test device doesn't have memfd_create. Cannot verify test!" >&2
-  exit 0
-fi
-
-
-./default-check "$@"
diff --git a/test/1963-add-to-dex-classloader-in-memory/run b/test/1963-add-to-dex-classloader-in-memory/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1963-add-to-dex-classloader-in-memory/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1963-add-to-dex-classloader-in-memory/run.py b/test/1963-add-to-dex-classloader-in-memory/run.py
new file mode 100644
index 0000000..ad7c1ae
--- /dev/null
+++ b/test/1963-add-to-dex-classloader-in-memory/run.py
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
+
+  # Some of our test devices are so old that they don't have memfd_create and are setup in such a way
+  # that tmpfile() doesn't work. In these cases this test cannot complete successfully.
+  # If we see this in stdout, make the expected stdout identical.
+  ctx.run(
+      fr"grep -q -- '---NO memfd_create---' '{args.stdout_file}' &&"
+      fr" echo '---NO memfd_create---' > expected-stdout.txt",
+      check=False)
diff --git a/test/1964-add-to-dex-classloader-file/run b/test/1964-add-to-dex-classloader-file/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1964-add-to-dex-classloader-file/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1964-add-to-dex-classloader-file/run.py b/test/1964-add-to-dex-classloader-file/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1964-add-to-dex-classloader-file/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1965-get-set-local-primitive-no-tables/build b/test/1965-get-set-local-primitive-no-tables/build
deleted file mode 100644
index 6631df9..0000000
--- a/test/1965-get-set-local-primitive-no-tables/build
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# make us exit on a failure
-set -e
-
-if [[ $@ != *"--jvm"* ]]; then
-  mv jasmin jasmin-unused
-else
-  mv smali smali-unused
-fi
-./default-build "$@" 
diff --git a/test/1965-get-set-local-primitive-no-tables/build.py b/test/1965-get-set-local-primitive-no-tables/build.py
new file mode 100644
index 0000000..3a908c0
--- /dev/null
+++ b/test/1965-get-set-local-primitive-no-tables/build.py
@@ -0,0 +1,21 @@
+#
+# Copyright (C) 2022 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.
+
+import os
+
+
+def build(ctx):
+  ctx.bash("./generate-sources --" + ctx.mode)
+  ctx.default_build()
diff --git a/test/1965-get-set-local-primitive-no-tables/generate-sources b/test/1965-get-set-local-primitive-no-tables/generate-sources
new file mode 100755
index 0000000..c2f8653
--- /dev/null
+++ b/test/1965-get-set-local-primitive-no-tables/generate-sources
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+# make us exit on a failure
+set -e
+
+if [[ $@ != *"--jvm"* ]]; then
+  mv jasmin jasmin-unused
+else
+  mv smali smali-unused
+fi
diff --git a/test/1965-get-set-local-primitive-no-tables/run b/test/1965-get-set-local-primitive-no-tables/run
deleted file mode 100755
index 9b741ee..0000000
--- a/test/1965-get-set-local-primitive-no-tables/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# If we compile the .oat files non-debuggable we could end up with dex2dex running over the files
-# which will cause some instructions to be removed from smali/TestCases1966.smali. This test relies
-# on the instructions being exactly as written so pass --debuggable to 'dex2oat' only to prevent
-# this from happening.
-./default-run "$@" --jvmti --compiler-only-option --debuggable
diff --git a/test/1965-get-set-local-primitive-no-tables/run.py b/test/1965-get-set-local-primitive-no-tables/run.py
new file mode 100644
index 0000000..77a45e9
--- /dev/null
+++ b/test/1965-get-set-local-primitive-no-tables/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+
+def run(ctx, args):
+  # If we compile the .oat files non-debuggable we could end up with dex2dex running over the files
+  # which will cause some instructions to be removed from smali/TestCases1966.smali. This test relies
+  # on the instructions being exactly as written so pass --debuggable to 'dex2oat' only to prevent
+  # this from happening.
+  ctx.default_run(args, jvmti=True, compiler_only_option=["--debuggable"])
diff --git a/test/1966-get-set-local-objects-no-table/build b/test/1966-get-set-local-objects-no-table/build
deleted file mode 100644
index 6631df9..0000000
--- a/test/1966-get-set-local-objects-no-table/build
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# make us exit on a failure
-set -e
-
-if [[ $@ != *"--jvm"* ]]; then
-  mv jasmin jasmin-unused
-else
-  mv smali smali-unused
-fi
-./default-build "$@" 
diff --git a/test/1966-get-set-local-objects-no-table/build.py b/test/1966-get-set-local-objects-no-table/build.py
new file mode 100644
index 0000000..3a908c0
--- /dev/null
+++ b/test/1966-get-set-local-objects-no-table/build.py
@@ -0,0 +1,21 @@
+#
+# Copyright (C) 2022 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.
+
+import os
+
+
+def build(ctx):
+  ctx.bash("./generate-sources --" + ctx.mode)
+  ctx.default_build()
diff --git a/test/1966-get-set-local-objects-no-table/generate-sources b/test/1966-get-set-local-objects-no-table/generate-sources
new file mode 100755
index 0000000..c2f8653
--- /dev/null
+++ b/test/1966-get-set-local-objects-no-table/generate-sources
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+# make us exit on a failure
+set -e
+
+if [[ $@ != *"--jvm"* ]]; then
+  mv jasmin jasmin-unused
+else
+  mv smali smali-unused
+fi
diff --git a/test/1966-get-set-local-objects-no-table/run b/test/1966-get-set-local-objects-no-table/run
deleted file mode 100755
index 9b741ee..0000000
--- a/test/1966-get-set-local-objects-no-table/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# If we compile the .oat files non-debuggable we could end up with dex2dex running over the files
-# which will cause some instructions to be removed from smali/TestCases1966.smali. This test relies
-# on the instructions being exactly as written so pass --debuggable to 'dex2oat' only to prevent
-# this from happening.
-./default-run "$@" --jvmti --compiler-only-option --debuggable
diff --git a/test/1966-get-set-local-objects-no-table/run.py b/test/1966-get-set-local-objects-no-table/run.py
new file mode 100644
index 0000000..77a45e9
--- /dev/null
+++ b/test/1966-get-set-local-objects-no-table/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+
+def run(ctx, args):
+  # If we compile the .oat files non-debuggable we could end up with dex2dex running over the files
+  # which will cause some instructions to be removed from smali/TestCases1966.smali. This test relies
+  # on the instructions being exactly as written so pass --debuggable to 'dex2oat' only to prevent
+  # this from happening.
+  ctx.default_run(args, jvmti=True, compiler_only_option=["--debuggable"])
diff --git a/test/1967-get-set-local-bad-slot/run b/test/1967-get-set-local-bad-slot/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/1967-get-set-local-bad-slot/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/1967-get-set-local-bad-slot/run.py b/test/1967-get-set-local-bad-slot/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/1967-get-set-local-bad-slot/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1968-force-early-return/run b/test/1968-force-early-return/run
deleted file mode 100755
index d16d4e6..0000000
--- a/test/1968-force-early-return/run
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# On RI we need to turn class-load tests off since those events are buggy around
-# pop-frame (see b/116003018).
-ARGS=""
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  ARGS="--args DISABLE_CLASS_LOAD_TESTS"
-fi
-
-./default-run "$@" --jvmti $ARGS
diff --git a/test/1968-force-early-return/run.py b/test/1968-force-early-return/run.py
new file mode 100644
index 0000000..fa08256
--- /dev/null
+++ b/test/1968-force-early-return/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # On RI we need to turn class-load tests off since those events are buggy around
+  # pop-frame (see b/116003018).
+  test_args = ["DISABLE_CLASS_LOAD_TESTS"] if args.jvm else []
+
+  ctx.default_run(args, jvmti=True, test_args=test_args)
diff --git a/test/1969-force-early-return-void/check b/test/1969-force-early-return-void/check
deleted file mode 100755
index 24bbf07..0000000
--- a/test/1969-force-early-return-void/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# The RI has restrictions and bugs around some PopFrame behavior that ART lacks.
-# See b/116003018. Some configurations cannot handle the class load events in
-# quite the right way so they are disabled there too.
-./default-check "$@" || \
-  (patch -p0 expected-stdout.txt < class-loading-expected.patch >/dev/null && ./default-check "$@")
diff --git a/test/1969-force-early-return-void/class-loading-expected.patch b/test/1969-force-early-return-void/class-loading-expected.patch
deleted file mode 100644
index 5e13595..0000000
--- a/test/1969-force-early-return-void/class-loading-expected.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-178a179,212
-> Test stopped during class-load.
-> NORMAL RUN: Single call with no interference on (ID: 46) ClassLoadObject { cnt: 0, curClass: 0}
-> TC0.foo == 100
-> NORMAL RUN: result for (ID: 46) ClassLoadObject { cnt: 1, curClass: 1} on Test1969 target thread - 46
-> Single call with force-early-return on (ID: 47) ClassLoadObject { cnt: 0, curClass: 1}
-> Will force return of Test1969 target thread - 47
-> Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
-> 	art.NonStandardExit.forceEarlyReturnVoid(Native Method)
-> 	art.Test1969$TestSuspender.performForceReturn(Test1969.java)
-> 	art.Test1969.runTestOn(Test1969.java)
-> 	art.Test1969.runTestOn(Test1969.java)
-> 	art.Test1969.runTestOn(Test1969.java)
-> 	art.Test1969.runTests(Test1969.java)
-> 	<Additional frames hidden>
-> 
-> TC1.foo == 201
-> result for (ID: 47) ClassLoadObject { cnt: 1, curClass: 2} on Test1969 target thread - 47
-> Test stopped during class-load.
-> NORMAL RUN: Single call with no interference on (ID: 48) ClassLoadObject { cnt: 0, curClass: 2}
-> TC2.foo == 302
-> NORMAL RUN: result for (ID: 48) ClassLoadObject { cnt: 1, curClass: 3} on Test1969 target thread - 48
-> Single call with force-early-return on (ID: 49) ClassLoadObject { cnt: 0, curClass: 3}
-> Will force return of Test1969 target thread - 49
-> Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
-> 	art.NonStandardExit.forceEarlyReturnVoid(Native Method)
-> 	art.Test1969$TestSuspender.performForceReturn(Test1969.java)
-> 	art.Test1969.runTestOn(Test1969.java)
-> 	art.Test1969.runTestOn(Test1969.java)
-> 	art.Test1969.runTestOn(Test1969.java)
-> 	art.Test1969.runTests(Test1969.java)
-> 	<Additional frames hidden>
-> 
-> TC3.foo == 403
-> result for (ID: 49) ClassLoadObject { cnt: 1, curClass: 4} on Test1969 target thread - 49
diff --git a/test/1969-force-early-return-void/expected-stdout.no-jvm.txt b/test/1969-force-early-return-void/expected-stdout.no-jvm.txt
new file mode 100644
index 0000000..1bb3252
--- /dev/null
+++ b/test/1969-force-early-return-void/expected-stdout.no-jvm.txt
@@ -0,0 +1,212 @@
+Test stopped using breakpoint
+NORMAL RUN: Single call with no interference on (ID: 0) StandardTestObject { cnt: 0 }
+NORMAL RUN: result for (ID: 0) StandardTestObject { cnt: 2 } on Test1969 target thread - 0
+Single call with force-early-return on (ID: 1) StandardTestObject { cnt: 0 }
+Will force return of Test1969 target thread - 1
+result for (ID: 1) StandardTestObject { cnt: 1 } on Test1969 target thread - 1
+Test stopped using breakpoint with declared synchronized function
+NORMAL RUN: Single call with no interference on (ID: 2) SynchronizedFunctionTestObject { cnt: 0 }
+NORMAL RUN: result for (ID: 2) SynchronizedFunctionTestObject { cnt: 2 } on Test1969 target thread - 2
+Single call with force-early-return on (ID: 3) SynchronizedFunctionTestObject { cnt: 0 }
+Will force return of Test1969 target thread - 3
+result for (ID: 3) SynchronizedFunctionTestObject { cnt: 1 } on Test1969 target thread - 3
+Test stopped using breakpoint with synchronized block
+NORMAL RUN: Single call with no interference on (ID: 4) SynchronizedTestObject { cnt: 0 }
+NORMAL RUN: result for (ID: 4) SynchronizedTestObject { cnt: 2 } on Test1969 target thread - 4
+Single call with force-early-return on (ID: 5) SynchronizedTestObject { cnt: 0 }
+Will force return of Test1969 target thread - 5
+result for (ID: 5) SynchronizedTestObject { cnt: 1 } on Test1969 target thread - 5
+Test stopped on single step
+NORMAL RUN: Single call with no interference on (ID: 6) StandardTestObject { cnt: 0 }
+NORMAL RUN: result for (ID: 6) StandardTestObject { cnt: 2 } on Test1969 target thread - 6
+Single call with force-early-return on (ID: 7) StandardTestObject { cnt: 0 }
+Will force return of Test1969 target thread - 7
+result for (ID: 7) StandardTestObject { cnt: 1 } on Test1969 target thread - 7
+Test stopped on field access
+NORMAL RUN: Single call with no interference on (ID: 8) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 0 }
+NORMAL RUN: result for (ID: 8) FieldBasedTestObject { TARGET_FIELD: 10, cnt: 2 } on Test1969 target thread - 8
+Single call with force-early-return on (ID: 9) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 0 }
+Will force return of Test1969 target thread - 9
+result for (ID: 9) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 1 } on Test1969 target thread - 9
+Test stopped on field modification
+NORMAL RUN: Single call with no interference on (ID: 10) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 0 }
+NORMAL RUN: result for (ID: 10) FieldBasedTestObject { TARGET_FIELD: 10, cnt: 2 } on Test1969 target thread - 10
+Single call with force-early-return on (ID: 11) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 0 }
+Will force return of Test1969 target thread - 11
+result for (ID: 11) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 1 } on Test1969 target thread - 11
+Test stopped during Method Exit of calledFunction
+NORMAL RUN: Single call with no interference on (ID: 12) StandardTestObject { cnt: 0 }
+NORMAL RUN: result for (ID: 12) StandardTestObject { cnt: 2 } on Test1969 target thread - 12
+Single call with force-early-return on (ID: 13) StandardTestObject { cnt: 0 }
+Will force return of Test1969 target thread - 13
+result for (ID: 13) StandardTestObject { cnt: 2 } on Test1969 target thread - 13
+Test stopped during Method Enter of calledFunction
+NORMAL RUN: Single call with no interference on (ID: 14) StandardTestObject { cnt: 0 }
+NORMAL RUN: result for (ID: 14) StandardTestObject { cnt: 2 } on Test1969 target thread - 14
+Single call with force-early-return on (ID: 15) StandardTestObject { cnt: 0 }
+Will force return of Test1969 target thread - 15
+result for (ID: 15) StandardTestObject { cnt: 0 } on Test1969 target thread - 15
+Test stopped during Method Exit due to exception thrown in same function
+NORMAL RUN: Single call with no interference on (ID: 16) ExceptionOnceObject { cnt: 0, throwInSub: false }
+Uncaught exception in thread Thread[Test1969 target thread - 16,5,main] - art.Test1969$ExceptionOnceObject$TestError: null
+	art.Test1969$ExceptionOnceObject.calledFunction(Test1969.java)
+	art.Test1969$AbstractTestObject.run(Test1969.java)
+	art.Test1969$2.run(Test1969.java)
+	java.lang.Thread.run(Thread.java)
+
+NORMAL RUN: result for (ID: 16) ExceptionOnceObject { cnt: 1, throwInSub: false } on Test1969 target thread - 16
+Single call with force-early-return on (ID: 17) ExceptionOnceObject { cnt: 0, throwInSub: false }
+Will force return of Test1969 target thread - 17
+result for (ID: 17) ExceptionOnceObject { cnt: 1, throwInSub: false } on Test1969 target thread - 17
+Test stopped during Method Exit due to exception thrown in subroutine
+NORMAL RUN: Single call with no interference on (ID: 18) ExceptionOnceObject { cnt: 0, throwInSub: true }
+Uncaught exception in thread Thread[Test1969 target thread - 18,5,main] - art.Test1969$ExceptionOnceObject$TestError: null
+	art.Test1969$ExceptionOnceObject.doThrow(Test1969.java)
+	art.Test1969$ExceptionOnceObject.calledFunction(Test1969.java)
+	art.Test1969$AbstractTestObject.run(Test1969.java)
+	art.Test1969$2.run(Test1969.java)
+	java.lang.Thread.run(Thread.java)
+
+NORMAL RUN: result for (ID: 18) ExceptionOnceObject { cnt: 1, throwInSub: true } on Test1969 target thread - 18
+Single call with force-early-return on (ID: 19) ExceptionOnceObject { cnt: 0, throwInSub: true }
+Will force return of Test1969 target thread - 19
+result for (ID: 19) ExceptionOnceObject { cnt: 1, throwInSub: true } on Test1969 target thread - 19
+Test stopped during notifyFramePop with exception on pop of calledFunction
+NORMAL RUN: Single call with no interference on (ID: 20) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 }
+art.Test1969$ExceptionThrowTestObject$TestError thrown and caught!
+NORMAL RUN: result for (ID: 20) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 20
+Single call with force-early-return on (ID: 21) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 }
+Will force return of Test1969 target thread - 21
+result for (ID: 21) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 21
+Test stopped during notifyFramePop with exception on pop of doThrow
+NORMAL RUN: Single call with no interference on (ID: 22) ExceptionCatchTestObject { cnt: 0 }
+art.Test1969$ExceptionCatchTestObject$TestError caught in called function.
+NORMAL RUN: result for (ID: 22) ExceptionCatchTestObject { cnt: 2 } on Test1969 target thread - 22
+Single call with force-early-return on (ID: 23) ExceptionCatchTestObject { cnt: 0 }
+Will force return of Test1969 target thread - 23
+Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_TYPE_MISMATCH
+	art.NonStandardExit.forceEarlyReturnVoid(Native Method)
+	art.Test1969$TestSuspender.performForceReturn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTests(Test1969.java)
+	<Additional frames hidden>
+
+art.Test1969$ExceptionCatchTestObject$TestError caught in called function.
+result for (ID: 23) ExceptionCatchTestObject { cnt: 2 } on Test1969 target thread - 23
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function)
+NORMAL RUN: Single call with no interference on (ID: 24) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 }
+art.Test1969$ExceptionThrowTestObject$TestError caught in same function.
+NORMAL RUN: result for (ID: 24) ExceptionThrowTestObject { cnt: 111, baseCnt: 2 } on Test1969 target thread - 24
+Single call with force-early-return on (ID: 25) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 }
+Will force return of Test1969 target thread - 25
+result for (ID: 25) ExceptionThrowTestObject { cnt: 11, baseCnt: 2 } on Test1969 target thread - 25
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine)
+NORMAL RUN: Single call with no interference on (ID: 26) ExceptionCatchTestObject { cnt: 0 }
+art.Test1969$ExceptionCatchTestObject$TestError caught in called function.
+NORMAL RUN: result for (ID: 26) ExceptionCatchTestObject { cnt: 2 } on Test1969 target thread - 26
+Single call with force-early-return on (ID: 27) ExceptionCatchTestObject { cnt: 0 }
+Will force return of Test1969 target thread - 27
+result for (ID: 27) ExceptionCatchTestObject { cnt: 1 } on Test1969 target thread - 27
+Test stopped during Exception event of calledFunction (catch in calling function)
+NORMAL RUN: Single call with no interference on (ID: 28) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 }
+art.Test1969$ExceptionThrowTestObject$TestError thrown and caught!
+NORMAL RUN: result for (ID: 28) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 28
+Single call with force-early-return on (ID: 29) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 }
+Will force return of Test1969 target thread - 29
+result for (ID: 29) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 29
+Test stopped during Exception event of calledFunction (catch in called function)
+NORMAL RUN: Single call with no interference on (ID: 30) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 }
+art.Test1969$ExceptionThrowTestObject$TestError caught in same function.
+NORMAL RUN: result for (ID: 30) ExceptionThrowTestObject { cnt: 111, baseCnt: 2 } on Test1969 target thread - 30
+Single call with force-early-return on (ID: 31) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 }
+Will force return of Test1969 target thread - 31
+result for (ID: 31) ExceptionThrowTestObject { cnt: 11, baseCnt: 2 } on Test1969 target thread - 31
+Test stopped during Exception event of calledFunction (catch in parent of calling function)
+NORMAL RUN: Single call with no interference on (ID: 32) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 }
+art.Test1969$ExceptionThrowFarTestObject$TestError thrown and caught!
+NORMAL RUN: result for (ID: 32) ExceptionThrowFarTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 32
+Single call with force-early-return on (ID: 33) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 }
+Will force return of Test1969 target thread - 33
+result for (ID: 33) ExceptionThrowFarTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 33
+Test stopped during Exception event of calledFunction (catch in called function)
+NORMAL RUN: Single call with no interference on (ID: 34) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 }
+art.Test1969$ExceptionThrowFarTestObject$TestError caught in same function.
+NORMAL RUN: result for (ID: 34) ExceptionThrowFarTestObject { cnt: 111, baseCnt: 2 } on Test1969 target thread - 34
+Single call with force-early-return on (ID: 35) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 }
+Will force return of Test1969 target thread - 35
+result for (ID: 35) ExceptionThrowFarTestObject { cnt: 101, baseCnt: 2 } on Test1969 target thread - 35
+Test stopped during random Suspend.
+NORMAL RUN: Single call with no interference on (ID: 36) SuspendSuddenlyObject { cnt: 0, spun: false }
+NORMAL RUN: result for (ID: 36) SuspendSuddenlyObject { cnt: 2, spun: true } on Test1969 target thread - 36
+Single call with force-early-return on (ID: 37) SuspendSuddenlyObject { cnt: 0, spun: false }
+Will force return of Test1969 target thread - 37
+result for (ID: 37) SuspendSuddenlyObject { cnt: 1, spun: true } on Test1969 target thread - 37
+Test stopped during a native method fails
+NORMAL RUN: Single call with no interference on (ID: 38) NativeCalledObject { cnt: 0 }
+NORMAL RUN: result for (ID: 38) NativeCalledObject { cnt: 2 } on Test1969 target thread - 38
+Single call with force-early-return on (ID: 39) NativeCalledObject { cnt: 0 }
+Will force return of Test1969 target thread - 39
+Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.NonStandardExit.forceEarlyReturnVoid(Native Method)
+	art.Test1969$TestSuspender.performForceReturn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTests(Test1969.java)
+	<Additional frames hidden>
+
+result for (ID: 39) NativeCalledObject { cnt: 2 } on Test1969 target thread - 39
+Test stopped in a method called by native succeeds
+NORMAL RUN: Single call with no interference on (ID: 40) NativeCallerObject { cnt: 0 }
+NORMAL RUN: result for (ID: 40) NativeCallerObject { cnt: 2 } on Test1969 target thread - 40
+Single call with force-early-return on (ID: 41) NativeCallerObject { cnt: 0 }
+Will force return of Test1969 target thread - 41
+result for (ID: 41) NativeCallerObject { cnt: 2 } on Test1969 target thread - 41
+Test stopped in a static method
+NORMAL RUN: Single call with no interference on (ID: 42) StaticMethodObject { cnt: 0 }
+NORMAL RUN: result for (ID: 42) StaticMethodObject { cnt: 2 } on Test1969 target thread - 42
+Single call with force-early-return on (ID: 43) StaticMethodObject { cnt: 0 }
+Will force return of Test1969 target thread - 43
+result for (ID: 43) StaticMethodObject { cnt: 1 } on Test1969 target thread - 43
+Test stopped in a Object <init> method
+NORMAL RUN: Single call with no interference on (ID: 44) ObjectInitTestObject { cnt: 0 }
+NORMAL RUN: result for (ID: 44) ObjectInitTestObject { cnt: 2 } on Test1969 target thread - 44
+Single call with force-early-return on (ID: 45) ObjectInitTestObject { cnt: 0 }
+Will force return of Test1969 target thread - 45
+result for (ID: 45) ObjectInitTestObject { cnt: 1 } on Test1969 target thread - 45
+Test stopped during class-load.
+NORMAL RUN: Single call with no interference on (ID: 46) ClassLoadObject { cnt: 0, curClass: 0}
+TC0.foo == 100
+NORMAL RUN: result for (ID: 46) ClassLoadObject { cnt: 1, curClass: 1} on Test1969 target thread - 46
+Single call with force-early-return on (ID: 47) ClassLoadObject { cnt: 0, curClass: 1}
+Will force return of Test1969 target thread - 47
+Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.NonStandardExit.forceEarlyReturnVoid(Native Method)
+	art.Test1969$TestSuspender.performForceReturn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTests(Test1969.java)
+	<Additional frames hidden>
+
+TC1.foo == 201
+result for (ID: 47) ClassLoadObject { cnt: 1, curClass: 2} on Test1969 target thread - 47
+Test stopped during class-load.
+NORMAL RUN: Single call with no interference on (ID: 48) ClassLoadObject { cnt: 0, curClass: 2}
+TC2.foo == 302
+NORMAL RUN: result for (ID: 48) ClassLoadObject { cnt: 1, curClass: 3} on Test1969 target thread - 48
+Single call with force-early-return on (ID: 49) ClassLoadObject { cnt: 0, curClass: 3}
+Will force return of Test1969 target thread - 49
+Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.NonStandardExit.forceEarlyReturnVoid(Native Method)
+	art.Test1969$TestSuspender.performForceReturn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTestOn(Test1969.java)
+	art.Test1969.runTests(Test1969.java)
+	<Additional frames hidden>
+
+TC3.foo == 403
+result for (ID: 49) ClassLoadObject { cnt: 1, curClass: 4} on Test1969 target thread - 49
diff --git a/test/1969-force-early-return-void/run b/test/1969-force-early-return-void/run
deleted file mode 100755
index e92b873..0000000
--- a/test/1969-force-early-return-void/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1969-force-early-return-void/run.py b/test/1969-force-early-return-void/run.py
new file mode 100644
index 0000000..d80f46f
--- /dev/null
+++ b/test/1969-force-early-return-void/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
+
+  # The RI has restrictions and bugs around some PopFrame behavior that ART lacks.
+  # See b/116003018. Some configurations cannot handle the class load events in
+  # quite the right way so they are disabled there too.
+  if not (not args.prebuild or (args.jvmti_redefine_stress and args.host)):
+    ctx.expected_stdout = ctx.expected_stdout.with_suffix(".no-jvm.txt")
diff --git a/test/1970-force-early-return-long/run b/test/1970-force-early-return-long/run
deleted file mode 100755
index d16d4e6..0000000
--- a/test/1970-force-early-return-long/run
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# On RI we need to turn class-load tests off since those events are buggy around
-# pop-frame (see b/116003018).
-ARGS=""
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  ARGS="--args DISABLE_CLASS_LOAD_TESTS"
-fi
-
-./default-run "$@" --jvmti $ARGS
diff --git a/test/1970-force-early-return-long/run.py b/test/1970-force-early-return-long/run.py
new file mode 100644
index 0000000..fa08256
--- /dev/null
+++ b/test/1970-force-early-return-long/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # On RI we need to turn class-load tests off since those events are buggy around
+  # pop-frame (see b/116003018).
+  test_args = ["DISABLE_CLASS_LOAD_TESTS"] if args.jvm else []
+
+  ctx.default_run(args, jvmti=True, test_args=test_args)
diff --git a/test/1971-multi-force-early-return/run b/test/1971-multi-force-early-return/run
deleted file mode 100755
index d16d4e6..0000000
--- a/test/1971-multi-force-early-return/run
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# On RI we need to turn class-load tests off since those events are buggy around
-# pop-frame (see b/116003018).
-ARGS=""
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  ARGS="--args DISABLE_CLASS_LOAD_TESTS"
-fi
-
-./default-run "$@" --jvmti $ARGS
diff --git a/test/1971-multi-force-early-return/run.py b/test/1971-multi-force-early-return/run.py
new file mode 100644
index 0000000..fa08256
--- /dev/null
+++ b/test/1971-multi-force-early-return/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # On RI we need to turn class-load tests off since those events are buggy around
+  # pop-frame (see b/116003018).
+  test_args = ["DISABLE_CLASS_LOAD_TESTS"] if args.jvm else []
+
+  ctx.default_run(args, jvmti=True, test_args=test_args)
diff --git a/test/1972-jni-id-swap-indices/run b/test/1972-jni-id-swap-indices/run
deleted file mode 100755
index 999b92a..0000000
--- a/test/1972-jni-id-swap-indices/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-args=$(echo "$@" | sed  's/--runtime-option -Xopaque-jni-ids\:true//g')
-
-./default-run $args --android-runtime-option -Xopaque-jni-ids:swapable --android-runtime-option -Xauto-promote-opaque-jni-ids:false
\ No newline at end of file
diff --git a/test/1972-jni-id-swap-indices/run.py b/test/1972-jni-id-swap-indices/run.py
new file mode 100644
index 0000000..81ec061
--- /dev/null
+++ b/test/1972-jni-id-swap-indices/run.py
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  for i, opt in enumerate(args.runtime_option):
+    if opt == '-Xopaque-jni-ids:true':
+      args.runtime_option.pop(i)
+      break
+
+  ctx.default_run(
+      args,
+      android_runtime_option=[
+          '-Xopaque-jni-ids:swapable', '-Xauto-promote-opaque-jni-ids:false'
+      ])
diff --git a/test/1973-jni-id-swap-pointer/run b/test/1973-jni-id-swap-pointer/run
deleted file mode 100755
index 999b92a..0000000
--- a/test/1973-jni-id-swap-pointer/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-args=$(echo "$@" | sed  's/--runtime-option -Xopaque-jni-ids\:true//g')
-
-./default-run $args --android-runtime-option -Xopaque-jni-ids:swapable --android-runtime-option -Xauto-promote-opaque-jni-ids:false
\ No newline at end of file
diff --git a/test/1973-jni-id-swap-pointer/run.py b/test/1973-jni-id-swap-pointer/run.py
new file mode 100644
index 0000000..b7ed57a
--- /dev/null
+++ b/test/1973-jni-id-swap-pointer/run.py
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  for i, opt in enumerate(args.runtime_option):
+    if opt == '-Xopaque-jni-ids:true':
+      args.runtime_option.pop(i)
+
+  ctx.default_run(
+      args,
+      android_runtime_option=[
+          '-Xopaque-jni-ids:swapable', '-Xauto-promote-opaque-jni-ids:false'
+      ])
diff --git a/test/1974-resize-array/run b/test/1974-resize-array/run
deleted file mode 100755
index 96646c8..0000000
--- a/test/1974-resize-array/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-
-./default-run "$@" --jvmti
diff --git a/test/1974-resize-array/run.py b/test/1974-resize-array/run.py
new file mode 100644
index 0000000..4f5f922
--- /dev/null
+++ b/test/1974-resize-array/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1975-hello-structural-transformation/run b/test/1975-hello-structural-transformation/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1975-hello-structural-transformation/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1975-hello-structural-transformation/run.py b/test/1975-hello-structural-transformation/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1975-hello-structural-transformation/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1976-hello-structural-static-methods/run b/test/1976-hello-structural-static-methods/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1976-hello-structural-static-methods/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1976-hello-structural-static-methods/run.py b/test/1976-hello-structural-static-methods/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1976-hello-structural-static-methods/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1977-hello-structural-obsolescence/run b/test/1977-hello-structural-obsolescence/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1977-hello-structural-obsolescence/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1977-hello-structural-obsolescence/run.py b/test/1977-hello-structural-obsolescence/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1977-hello-structural-obsolescence/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1978-regular-obsolete-then-structural-obsolescence/run b/test/1978-regular-obsolete-then-structural-obsolescence/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1978-regular-obsolete-then-structural-obsolescence/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1978-regular-obsolete-then-structural-obsolescence/run.py b/test/1978-regular-obsolete-then-structural-obsolescence/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1978-regular-obsolete-then-structural-obsolescence/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1979-threaded-structural-transformation/run b/test/1979-threaded-structural-transformation/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1979-threaded-structural-transformation/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1979-threaded-structural-transformation/run.py b/test/1979-threaded-structural-transformation/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1979-threaded-structural-transformation/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1980-obsolete-object-cleared/expected-stdout.txt b/test/1980-obsolete-object-cleared/expected-stdout.txt
index 9d18b2c..8562dcb 100644
--- a/test/1980-obsolete-object-cleared/expected-stdout.txt
+++ b/test/1980-obsolete-object-cleared/expected-stdout.txt
@@ -17,6 +17,10 @@
 Using obsolete class object!
 
 
+Calling public java.lang.Class java.lang.Class.arrayType() with params: []
+public java.lang.Class java.lang.Class.arrayType() on (obsolete)class Main$Transform with [] = class [LMain$Transform;
+Calling public java.lang.invoke.TypeDescriptor$OfField java.lang.Class.arrayType() with params: []
+public java.lang.invoke.TypeDescriptor$OfField java.lang.Class.arrayType() on (obsolete)class Main$Transform with [] = class [LMain$Transform;
 Calling public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
 public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isAssignableFrom(java.lang.Class)' on a null object reference
 public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) on (obsolete)class Main$Transform with [class java.lang.Object] = (obsolete)class Main$Transform
@@ -29,6 +33,12 @@
 public java.lang.Object java.lang.Class.cast(java.lang.Object) with [foo] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.String to Main$Transform
 public java.lang.Object java.lang.Class.cast(java.lang.Object) with [NOT_USED_STRING] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.String to Main$Transform
 public java.lang.Object java.lang.Class.cast(java.lang.Object) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.Class to Main$Transform
+Calling public java.lang.Class java.lang.Class.componentType() with params: []
+public java.lang.Class java.lang.Class.componentType() on (obsolete)class Main$Transform with [] = null
+Calling public java.lang.invoke.TypeDescriptor$OfField java.lang.Class.componentType() with params: []
+public java.lang.invoke.TypeDescriptor$OfField java.lang.Class.componentType() on (obsolete)class Main$Transform with [] = null
+Calling public java.lang.String java.lang.Class.descriptorString() with params: []
+public java.lang.String java.lang.Class.descriptorString() on (obsolete)class Main$Transform with [] = LMain$Transform;
 Calling public boolean java.lang.Class.desiredAssertionStatus() with params: []
 public boolean java.lang.Class.desiredAssertionStatus() on (obsolete)class Main$Transform with [] = false
 Calling public int java.lang.Class.getAccessFlags() with params: []
@@ -72,6 +82,13 @@
 public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
 Calling public native java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotations() with params: []
 public native java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotations() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [class java.lang.Object] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [long] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
 Calling public native java.lang.Class[] java.lang.Class.getDeclaredClasses() with params: []
 public native java.lang.Class[] java.lang.Class.getDeclaredClasses() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
 Calling public java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with params: [[new java.lang.Object[0], new java.lang.Class[0], null]]
@@ -154,12 +171,39 @@
 public int java.lang.Class.getModifiers() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
 Calling public java.lang.String java.lang.Class.getName() with params: []
 public java.lang.String java.lang.Class.getName() on (obsolete)class Main$Transform with [] = Main$Transform
+Calling public java.lang.Class java.lang.Class.getNestHost() with params: []
+public java.lang.Class java.lang.Class.getNestHost() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.Class[] java.lang.Class.getNestMembers() with params: []
+public java.lang.Class[] java.lang.Class.getNestMembers() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
 Calling public java.lang.Package java.lang.Class.getPackage() with params: []
 public java.lang.Package java.lang.Class.getPackage() on (obsolete)class Main$Transform with [] = null
 Calling public java.lang.String java.lang.Class.getPackageName() with params: []
 public java.lang.String java.lang.Class.getPackageName() on (obsolete)class Main$Transform with [] = 
+Calling public java.lang.Class[] java.lang.Class.getPermittedSubclasses() with params: []
+public java.lang.Class[] java.lang.Class.getPermittedSubclasses() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
 Calling public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() with params: []
 public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() on (obsolete)class Main$Transform with [] = null
+Calling public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) with params: [[NOT_USED_STRING, foo, SECRET_ARRAY], [null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [NOT_USED_STRING, null] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [NOT_USED_STRING, class java.lang.Object] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [NOT_USED_STRING, (obsolete)class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [NOT_USED_STRING, class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [NOT_USED_STRING, long] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [NOT_USED_STRING, class java.lang.Class] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [foo, null] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [foo, class java.lang.Object] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [foo, (obsolete)class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [foo, class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [foo, long] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [foo, class java.lang.Class] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [SECRET_ARRAY, null] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [SECRET_ARRAY, class java.lang.Object] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [SECRET_ARRAY, (obsolete)class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [SECRET_ARRAY, class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [SECRET_ARRAY, long] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on (obsolete)class Main$Transform with [SECRET_ARRAY, class java.lang.Class] = null
+Calling public java.lang.reflect.RecordComponent[] java.lang.Class.getRecordComponents() with params: []
+public java.lang.reflect.RecordComponent[] java.lang.Class.getRecordComponents() on (obsolete)class Main$Transform with [] = null
 Calling public java.net.URL java.lang.Class.getResource(java.lang.String) with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
 public java.net.URL java.lang.Class.getResource(java.lang.String) on (obsolete)class Main$Transform with [NOT_USED_STRING] = null
 public java.net.URL java.lang.Class.getResource(java.lang.String) on (obsolete)class Main$Transform with [foo] = null
@@ -213,10 +257,21 @@
 public boolean java.lang.Class.isLocalClass() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
 Calling public boolean java.lang.Class.isMemberClass() with params: []
 public boolean java.lang.Class.isMemberClass() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public boolean java.lang.Class.isNestmateOf(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isPrimitive()' on a null object reference
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) with [class java.lang.Object] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) on (obsolete)class Main$Transform with [(obsolete)class Main$Transform] = true
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) on (obsolete)class Main$Transform with [long] = false
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
 Calling public boolean java.lang.Class.isPrimitive() with params: []
 public boolean java.lang.Class.isPrimitive() on (obsolete)class Main$Transform with [] = false
 Calling public boolean java.lang.Class.isProxy() with params: []
 public boolean java.lang.Class.isProxy() on (obsolete)class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isRecord() with params: []
+public boolean java.lang.Class.isRecord() on (obsolete)class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isSealed() with params: []
+public boolean java.lang.Class.isSealed() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
 Calling public boolean java.lang.Class.isSynthetic() with params: []
 public boolean java.lang.Class.isSynthetic() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
 Calling public native java.lang.Object java.lang.Class.newInstance() throws java.lang.InstantiationException,java.lang.IllegalAccessException with params: []
@@ -230,6 +285,10 @@
 Using non-obsolete class object!
 
 
+Calling public java.lang.Class java.lang.Class.arrayType() with params: []
+public java.lang.Class java.lang.Class.arrayType() on class Main$Transform with [] = class [LMain$Transform;
+Calling public java.lang.invoke.TypeDescriptor$OfField java.lang.Class.arrayType() with params: []
+public java.lang.invoke.TypeDescriptor$OfField java.lang.Class.arrayType() on class Main$Transform with [] = class [LMain$Transform;
 Calling public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
 public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isAssignableFrom(java.lang.Class)' on a null object reference
 public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) on class Main$Transform with [class java.lang.Object] = class Main$Transform
@@ -242,6 +301,12 @@
 public java.lang.Object java.lang.Class.cast(java.lang.Object) with [foo] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.String to Main$Transform
 public java.lang.Object java.lang.Class.cast(java.lang.Object) with [NOT_USED_STRING] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.String to Main$Transform
 public java.lang.Object java.lang.Class.cast(java.lang.Object) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.Class to Main$Transform
+Calling public java.lang.Class java.lang.Class.componentType() with params: []
+public java.lang.Class java.lang.Class.componentType() on class Main$Transform with [] = null
+Calling public java.lang.invoke.TypeDescriptor$OfField java.lang.Class.componentType() with params: []
+public java.lang.invoke.TypeDescriptor$OfField java.lang.Class.componentType() on class Main$Transform with [] = null
+Calling public java.lang.String java.lang.Class.descriptorString() with params: []
+public java.lang.String java.lang.Class.descriptorString() on class Main$Transform with [] = LMain$Transform;
 Calling public boolean java.lang.Class.desiredAssertionStatus() with params: []
 public boolean java.lang.Class.desiredAssertionStatus() on class Main$Transform with [] = false
 Calling public int java.lang.Class.getAccessFlags() with params: []
@@ -285,6 +350,13 @@
 public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) on class Main$Transform with [class java.lang.Class] = null
 Calling public native java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotations() with params: []
 public native java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotations() on class Main$Transform with [] = []
+Calling public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [class java.lang.Object] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: java.lang.Object[] cannot be cast to java.lang.annotation.Annotation[]
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Main$Transform[] cannot be cast to java.lang.annotation.Annotation[]
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [long] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: long[] cannot be cast to java.lang.annotation.Annotation[]
+public java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: java.lang.Class[] cannot be cast to java.lang.annotation.Annotation[]
 Calling public native java.lang.Class[] java.lang.Class.getDeclaredClasses() with params: []
 public native java.lang.Class[] java.lang.Class.getDeclaredClasses() on class Main$Transform with [] = []
 Calling public java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with params: [[new java.lang.Object[0], new java.lang.Class[0], null]]
@@ -367,12 +439,39 @@
 public int java.lang.Class.getModifiers() on class Main$Transform with [] = 9
 Calling public java.lang.String java.lang.Class.getName() with params: []
 public java.lang.String java.lang.Class.getName() on class Main$Transform with [] = Main$Transform
+Calling public java.lang.Class java.lang.Class.getNestHost() with params: []
+public java.lang.Class java.lang.Class.getNestHost() on class Main$Transform with [] = class Main$Transform
+Calling public java.lang.Class[] java.lang.Class.getNestMembers() with params: []
+public java.lang.Class[] java.lang.Class.getNestMembers() on class Main$Transform with [] = [class Main$Transform]
 Calling public java.lang.Package java.lang.Class.getPackage() with params: []
 public java.lang.Package java.lang.Class.getPackage() on class Main$Transform with [] = null
 Calling public java.lang.String java.lang.Class.getPackageName() with params: []
 public java.lang.String java.lang.Class.getPackageName() on class Main$Transform with [] = 
+Calling public java.lang.Class[] java.lang.Class.getPermittedSubclasses() with params: []
+public java.lang.Class[] java.lang.Class.getPermittedSubclasses() on class Main$Transform with [] = null
 Calling public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() with params: []
 public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() on class Main$Transform with [] = null
+Calling public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) with params: [[NOT_USED_STRING, foo, SECRET_ARRAY], [null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [NOT_USED_STRING, null] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [NOT_USED_STRING, class java.lang.Object] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [NOT_USED_STRING, (obsolete)class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [NOT_USED_STRING, class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [NOT_USED_STRING, long] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [NOT_USED_STRING, class java.lang.Class] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [foo, null] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [foo, class java.lang.Object] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [foo, (obsolete)class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [foo, class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [foo, long] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [foo, class java.lang.Class] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [SECRET_ARRAY, null] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [SECRET_ARRAY, class java.lang.Object] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [SECRET_ARRAY, (obsolete)class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [SECRET_ARRAY, class Main$Transform] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [SECRET_ARRAY, long] = null
+public native java.lang.Object[] java.lang.Class.getRecordAnnotationElement(java.lang.String,java.lang.Class) on class Main$Transform with [SECRET_ARRAY, class java.lang.Class] = null
+Calling public java.lang.reflect.RecordComponent[] java.lang.Class.getRecordComponents() with params: []
+public java.lang.reflect.RecordComponent[] java.lang.Class.getRecordComponents() on class Main$Transform with [] = null
 Calling public java.net.URL java.lang.Class.getResource(java.lang.String) with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
 public java.net.URL java.lang.Class.getResource(java.lang.String) on class Main$Transform with [NOT_USED_STRING] = null
 public java.net.URL java.lang.Class.getResource(java.lang.String) on class Main$Transform with [foo] = null
@@ -426,10 +525,21 @@
 public boolean java.lang.Class.isLocalClass() on class Main$Transform with [] = false
 Calling public boolean java.lang.Class.isMemberClass() with params: []
 public boolean java.lang.Class.isMemberClass() on class Main$Transform with [] = true
+Calling public boolean java.lang.Class.isNestmateOf(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isPrimitive()' on a null object reference
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) on class Main$Transform with [class java.lang.Object] = false
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) on class Main$Transform with [class Main$Transform] = true
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) on class Main$Transform with [long] = false
+public boolean java.lang.Class.isNestmateOf(java.lang.Class) on class Main$Transform with [class java.lang.Class] = false
 Calling public boolean java.lang.Class.isPrimitive() with params: []
 public boolean java.lang.Class.isPrimitive() on class Main$Transform with [] = false
 Calling public boolean java.lang.Class.isProxy() with params: []
 public boolean java.lang.Class.isProxy() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isRecord() with params: []
+public boolean java.lang.Class.isRecord() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isSealed() with params: []
+public boolean java.lang.Class.isSealed() on class Main$Transform with [] = false
 Calling public boolean java.lang.Class.isSynthetic() with params: []
 public boolean java.lang.Class.isSynthetic() on class Main$Transform with [] = false
 Calling public native java.lang.Object java.lang.Class.newInstance() throws java.lang.InstantiationException,java.lang.IllegalAccessException with params: []
diff --git a/test/1980-obsolete-object-cleared/run b/test/1980-obsolete-object-cleared/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1980-obsolete-object-cleared/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1980-obsolete-object-cleared/run.py b/test/1980-obsolete-object-cleared/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1980-obsolete-object-cleared/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1981-structural-redef-private-method-handles/build b/test/1981-structural-redef-private-method-handles/build
deleted file mode 100755
index c80d7ad..0000000
--- a/test/1981-structural-redef-private-method-handles/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# Make us exit on a failure
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/1981-structural-redef-private-method-handles/build.py b/test/1981-structural-redef-private-method-handles/build.py
new file mode 100644
index 0000000..7ccbe96
--- /dev/null
+++ b/test/1981-structural-redef-private-method-handles/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="var-handles")
diff --git a/test/1981-structural-redef-private-method-handles/run b/test/1981-structural-redef-private-method-handles/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1981-structural-redef-private-method-handles/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1981-structural-redef-private-method-handles/run.py b/test/1981-structural-redef-private-method-handles/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1981-structural-redef-private-method-handles/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1981-structural-redef-private-method-handles/test-metadata.json b/test/1981-structural-redef-private-method-handles/test-metadata.json
new file mode 100644
index 0000000..ed29691
--- /dev/null
+++ b/test/1981-structural-redef-private-method-handles/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "experimental": "var-handles"
+  }
+}
diff --git a/test/1982-no-virtuals-structural-redefinition/run b/test/1982-no-virtuals-structural-redefinition/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1982-no-virtuals-structural-redefinition/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1982-no-virtuals-structural-redefinition/run.py b/test/1982-no-virtuals-structural-redefinition/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1982-no-virtuals-structural-redefinition/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1983-structural-redefinition-failures/build b/test/1983-structural-redefinition-failures/build
deleted file mode 100755
index c80d7ad..0000000
--- a/test/1983-structural-redefinition-failures/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# Make us exit on a failure
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/1983-structural-redefinition-failures/build.py b/test/1983-structural-redefinition-failures/build.py
new file mode 100644
index 0000000..7ccbe96
--- /dev/null
+++ b/test/1983-structural-redefinition-failures/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="var-handles")
diff --git a/test/1983-structural-redefinition-failures/run b/test/1983-structural-redefinition-failures/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1983-structural-redefinition-failures/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1983-structural-redefinition-failures/run.py b/test/1983-structural-redefinition-failures/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1983-structural-redefinition-failures/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1983-structural-redefinition-failures/test-metadata.json b/test/1983-structural-redefinition-failures/test-metadata.json
new file mode 100644
index 0000000..ed29691
--- /dev/null
+++ b/test/1983-structural-redefinition-failures/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "experimental": "var-handles"
+  }
+}
diff --git a/test/1984-structural-redefine-field-trace/run b/test/1984-structural-redefine-field-trace/run
deleted file mode 100755
index a36de16..0000000
--- a/test/1984-structural-redefine-field-trace/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti --android-runtime-option -Xopaque-jni-ids:true
diff --git a/test/1984-structural-redefine-field-trace/run.py b/test/1984-structural-redefine-field-trace/run.py
new file mode 100644
index 0000000..69d8985
--- /dev/null
+++ b/test/1984-structural-redefine-field-trace/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(
+      args, jvmti=True, android_runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1985-structural-redefine-stack-scope/run b/test/1985-structural-redefine-stack-scope/run
deleted file mode 100755
index a36de16..0000000
--- a/test/1985-structural-redefine-stack-scope/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti --android-runtime-option -Xopaque-jni-ids:true
diff --git a/test/1985-structural-redefine-stack-scope/run.py b/test/1985-structural-redefine-stack-scope/run.py
new file mode 100644
index 0000000..69d8985
--- /dev/null
+++ b/test/1985-structural-redefine-stack-scope/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(
+      args, jvmti=True, android_runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1986-structural-redefine-multi-thread-stack-scope/run b/test/1986-structural-redefine-multi-thread-stack-scope/run
deleted file mode 100755
index a36de16..0000000
--- a/test/1986-structural-redefine-multi-thread-stack-scope/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti --android-runtime-option -Xopaque-jni-ids:true
diff --git a/test/1986-structural-redefine-multi-thread-stack-scope/run.py b/test/1986-structural-redefine-multi-thread-stack-scope/run.py
new file mode 100644
index 0000000..69d8985
--- /dev/null
+++ b/test/1986-structural-redefine-multi-thread-stack-scope/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(
+      args, jvmti=True, android_runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1987-structural-redefine-recursive-stack-scope/run b/test/1987-structural-redefine-recursive-stack-scope/run
deleted file mode 100755
index a36de16..0000000
--- a/test/1987-structural-redefine-recursive-stack-scope/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti --android-runtime-option -Xopaque-jni-ids:true
diff --git a/test/1987-structural-redefine-recursive-stack-scope/run.py b/test/1987-structural-redefine-recursive-stack-scope/run.py
new file mode 100644
index 0000000..69d8985
--- /dev/null
+++ b/test/1987-structural-redefine-recursive-stack-scope/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(
+      args, jvmti=True, android_runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1988-multi-structural-redefine/run b/test/1988-multi-structural-redefine/run
deleted file mode 100755
index a36de16..0000000
--- a/test/1988-multi-structural-redefine/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti --android-runtime-option -Xopaque-jni-ids:true
diff --git a/test/1988-multi-structural-redefine/run.py b/test/1988-multi-structural-redefine/run.py
new file mode 100644
index 0000000..69d8985
--- /dev/null
+++ b/test/1988-multi-structural-redefine/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(
+      args, jvmti=True, android_runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1989-transform-bad-monitor/run b/test/1989-transform-bad-monitor/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1989-transform-bad-monitor/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1989-transform-bad-monitor/run.py b/test/1989-transform-bad-monitor/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1989-transform-bad-monitor/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1990-structural-bad-verify/run b/test/1990-structural-bad-verify/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1990-structural-bad-verify/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1990-structural-bad-verify/run.py b/test/1990-structural-bad-verify/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1990-structural-bad-verify/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1991-hello-structural-retransform/run b/test/1991-hello-structural-retransform/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1991-hello-structural-retransform/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1991-hello-structural-retransform/run.py b/test/1991-hello-structural-retransform/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1991-hello-structural-retransform/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1992-retransform-no-such-field/run b/test/1992-retransform-no-such-field/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/1992-retransform-no-such-field/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/1992-retransform-no-such-field/run.py b/test/1992-retransform-no-such-field/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/1992-retransform-no-such-field/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/1993-fallback-non-structural/run b/test/1993-fallback-non-structural/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1993-fallback-non-structural/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1993-fallback-non-structural/run.py b/test/1993-fallback-non-structural/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1993-fallback-non-structural/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1994-final-virtual-structural/run b/test/1994-final-virtual-structural/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1994-final-virtual-structural/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1994-final-virtual-structural/run.py b/test/1994-final-virtual-structural/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1994-final-virtual-structural/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1995-final-virtual-structural-multithread/run b/test/1995-final-virtual-structural-multithread/run
deleted file mode 100755
index e912529..0000000
--- a/test/1995-final-virtual-structural-multithread/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# TODO(b/144168550) This test uses access patterns that can be replaced by
-# iget-object-quick during dex2dex compilation. This breaks the test since the
-# -quick opcode encodes the exact byte offset of fields. Since this test changes
-# the offset this causes problems.
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1995-final-virtual-structural-multithread/run.py b/test/1995-final-virtual-structural-multithread/run.py
new file mode 100644
index 0000000..3fd3061
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  # TODO(b/144168550) This test uses access patterns that can be replaced by
+  # iget-object-quick during dex2dex compilation. This breaks the test since the
+  # -quick opcode encodes the exact byte offset of fields. Since this test changes
+  # the offset this causes problems.
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1995-final-virtual-structural-multithread/src/Main.java b/test/1995-final-virtual-structural-multithread/src/Main.java
index f19358d..f3bf4f6 100644
--- a/test/1995-final-virtual-structural-multithread/src/Main.java
+++ b/test/1995-final-virtual-structural-multithread/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test1995.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test1995.run();
+    }
 }
diff --git a/test/1995-final-virtual-structural-multithread/src/art/Test1995.java b/test/1995-final-virtual-structural-multithread/src/art/Test1995.java
index 7073494..a960c74 100644
--- a/test/1995-final-virtual-structural-multithread/src/art/Test1995.java
+++ b/test/1995-final-virtual-structural-multithread/src/art/Test1995.java
@@ -21,150 +21,153 @@
 import java.util.Base64;
 import java.util.concurrent.CountDownLatch;
 public class Test1995 {
-  private static final int NUM_THREADS = 20;
-  // Don't perform more than this many repeats per thread to prevent OOMEs
-  private static final int TASK_COUNT_LIMIT = 1000;
+    private static final int NUM_THREADS = 20;
+    // Don't perform more than this many repeats per thread to prevent OOMEs
+    private static final int TASK_COUNT_LIMIT = 1000;
 
-  public static final class Transform {
-    public String greetingEnglish;
-    public Transform() {
-      this.greetingEnglish = "Hello";
-    }
-    public String sayHi() {
-      return greetingEnglish + " from " + Thread.currentThread().getName();
-    }
-  }
-
-  /**
-   * base64 encoded class/dex file for
-   * public static final class Transform {
-   *   public String greetingEnglish;
-   *   public String greetingFrench;
-   *   public String greetingDanish;
-   *   public String greetingJapanese;
-   *
-   *   public Transform() {
-   *     this.greetingEnglish = "Hello World";
-   *     this.greetingFrench = "Bonjour le Monde";
-   *     this.greetingDanish = "Hej Verden";
-   *     this.greetingJapanese = "こんにちは世界";
-   *   }
-   *   public String sayHi() {
-   *     return sayHiEnglish() + ", " + sayHiFrench() + ", " + sayHiDanish() + ", " + sayHiJapanese() + " from " + Thread.currentThread().getName();
-   *   }
-   *   public String sayHiEnglish() {
-   *     return greetingEnglish;
-   *   }
-   *   public String sayHiDanish() {
-   *     return greetingDanish;
-   *   }
-   *   public String sayHiJapanese() {
-   *     return greetingJapanese;
-   *   }
-   *   public String sayHiFrench() {
-   *     return greetingFrench;
-   *   }
-   * }
-   */
-  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
-"ZGV4CjAzNQCsHrUqkb8cYgT2oYN7HlVbeOxJT/kONRvgBgAAcAAAAHhWNBIAAAAAAAAAABwGAAAl" +
-"AAAAcAAAAAkAAAAEAQAABAAAACgBAAAEAAAAWAEAAAwAAAB4AQAAAQAAANgBAADoBAAA+AEAAEoD" +
-"AABSAwAAVgMAAF4DAABwAwAAfAMAAIkDAACMAwAAkAMAAKoDAAC6AwAA3gMAAP4DAAASBAAAJgQA" +
-"AEEEAABVBAAAZAQAAG8EAAByBAAAfwQAAIcEAACWBAAAnwQAAK8EAADABAAA0AQAAOIEAADoBAAA" +
-"7wQAAPwEAAAKBQAAFwUAACYFAAAwBQAANwUAAK8FAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAO" +
-"AAAADwAAABIAAAAGAAAABQAAAAAAAAAHAAAABgAAAEQDAAAGAAAABwAAAAAAAAASAAAACAAAAAAA" +
-"AAAAAAUAFwAAAAAABQAYAAAAAAAFABkAAAAAAAUAGgAAAAAAAwACAAAAAAAAABwAAAAAAAAAHQAA" +
-"AAAAAAAeAAAAAAAAAB8AAAAAAAAAIAAAAAQAAwACAAAABgADAAIAAAAGAAEAFAAAAAYAAAAhAAAA" +
-"BwACABUAAAAHAAAAFgAAAAAAAAARAAAABAAAAAAAAAAQAAAADAYAANUFAAAAAAAABwABAAIAAAAt" +
-"AwAAQQAAAG4QAwAGAAwAbhAEAAYADAFuEAIABgAMAm4QBQAGAAwDcQAKAAAADARuEAsABAAMBCIF" +
-"BgBwEAcABQBuIAgABQAaAAEAbiAIAAUAbiAIABUAbiAIAAUAbiAIACUAbiAIAAUAbiAIADUAGgAA" +
-"AG4gCAAFAG4gCABFAG4QCQAFAAwAEQAAAAIAAQAAAAAAMQMAAAMAAABUEAAAEQAAAAIAAQAAAAAA" +
-"NQMAAAMAAABUEAEAEQAAAAIAAQAAAAAAOQMAAAMAAABUEAIAEQAAAAIAAQAAAAAAPQMAAAMAAABU" +
-"EAMAEQAAAAIAAQABAAAAJAMAABQAAABwEAYAAQAaAAUAWxABABoAAwBbEAIAGgAEAFsQAAAaACQA" +
-"WxADAA4ACQAOPEtLS0sAEAAOABYADgATAA4AHAAOABkADgAAAAABAAAABQAGIGZyb20gAAIsIAAG" +
-"PGluaXQ+ABBCb25qb3VyIGxlIE1vbmRlAApIZWogVmVyZGVuAAtIZWxsbyBXb3JsZAABTAACTEwA" +
-"GExhcnQvVGVzdDE5OTUkVHJhbnNmb3JtOwAOTGFydC9UZXN0MTk5NTsAIkxkYWx2aWsvYW5ub3Rh" +
-"dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph" +
-"dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp" +
-"bGRlcjsAEkxqYXZhL2xhbmcvVGhyZWFkOwANVGVzdDE5OTUuamF2YQAJVHJhbnNmb3JtAAFWAAth" +
-"Y2Nlc3NGbGFncwAGYXBwZW5kAA1jdXJyZW50VGhyZWFkAAdnZXROYW1lAA5ncmVldGluZ0Rhbmlz" +
-"aAAPZ3JlZXRpbmdFbmdsaXNoAA5ncmVldGluZ0ZyZW5jaAAQZ3JlZXRpbmdKYXBhbmVzZQAEbmFt" +
-"ZQAFc2F5SGkAC3NheUhpRGFuaXNoAAxzYXlIaUVuZ2xpc2gAC3NheUhpRnJlbmNoAA1zYXlIaUph" +
-"cGFuZXNlAAh0b1N0cmluZwAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIs" +
-"Im1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2ZDg4" +
-"YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AB+OBk+OCk+OBq+OBoeOBr+S4lueVjAACAgEi" +
-"GAECAwITBBkbFxEABAEFAAEBAQEBAQEAgYAE7AUBAfgDAQGMBQEBpAUBAbwFAQHUBQAAAAAAAgAA" +
-"AMYFAADMBQAAAAYAAAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAJQAAAHAAAAACAAAA" +
-"CQAAAAQBAAADAAAABAAAACgBAAAEAAAABAAAAFgBAAAFAAAADAAAAHgBAAAGAAAAAQAAANgBAAAB" +
-"IAAABgAAAPgBAAADIAAABgAAACQDAAABEAAAAQAAAEQDAAACIAAAJQAAAEoDAAAEIAAAAgAAAMYF" +
-"AAAAIAAAAQAAANUFAAADEAAAAgAAAPwFAAAGIAAAAQAAAAwGAAAAEAAAAQAAABwGAAA=");
-
-
-  public static void run() throws Exception {
-    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
-    doTest();
-  }
-
-  public static final class MyThread extends Thread {
-    public MyThread(CountDownLatch delay, int id) {
-      super("Thread: " + id);
-      this.thr_id = id;
-      this.results = new ArrayList<>(TASK_COUNT_LIMIT);
-      this.finish = false;
-      this.delay = delay;
-    }
-
-    public void run() {
-      delay.countDown();
-      while (!finish && results.size() < TASK_COUNT_LIMIT) {
-        Transform t = new Transform();
-        results.add(t.sayHi());
-      }
-    }
-
-    public void finish() throws Exception {
-      finish = true;
-      this.join();
-    }
-
-    public void Check() throws Exception {
-      for (String s : results) {
-        if (!s.equals("Hello from " + getName()) &&
-            !s.equals("Hello, null, null, null from " + getName()) &&
-            !s.equals("Hello World, Bonjour le Monde, Hej Verden, こんにちは世界 from " + getName())) {
-          System.out.println("FAIL " + thr_id + ": Unexpected result: " + s);
+    public static final class Transform {
+        public String greetingEnglish;
+        public Transform() {
+            this.greetingEnglish = "Hello";
         }
-      }
+        public String sayHi() {
+            return greetingEnglish + " from " + Thread.currentThread().getName();
+        }
     }
 
-    public ArrayList<String> results;
-    public volatile boolean finish;
-    public int thr_id;
-    public CountDownLatch delay;
-  }
+    /**
+     * base64 encoded class/dex file for
+     * public static final class Transform {
+     *     public String greetingEnglish;
+     *     public String greetingFrench;
+     *     public String greetingDanish;
+     *     public String greetingJapanese;
+     *
+     *     public Transform() {
+     *         this.greetingEnglish = "Hello World";
+     *         this.greetingFrench = "Bonjour le Monde";
+     *         this.greetingDanish = "Hej Verden";
+     *         this.greetingJapanese = "こんにちは世界";
+     *     }
+     *     public String sayHi() {
+     *         return sayHiEnglish() + ", " + sayHiFrench() + ", " + sayHiDanish() + ", " +
+     *                 sayHiJapanese() + " from " + Thread.currentThread().getName();
+     *     }
+     *     public String sayHiEnglish() {
+     *         return greetingEnglish;
+     *     }
+     *     public String sayHiDanish() {
+     *         return greetingDanish;
+     *     }
+     *     public String sayHiJapanese() {
+     *         return greetingJapanese;
+     *     }
+     *     public String sayHiFrench() {
+     *         return greetingFrench;
+     *     }
+     * }
+     */
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQCsHrUqkb8cYgT2oYN7HlVbeOxJT/kONRvgBgAAcAAAAHhWNBIAAAAAAAAAABwGAAAl" +
+            "AAAAcAAAAAkAAAAEAQAABAAAACgBAAAEAAAAWAEAAAwAAAB4AQAAAQAAANgBAADoBAAA+AEAAEoD" +
+            "AABSAwAAVgMAAF4DAABwAwAAfAMAAIkDAACMAwAAkAMAAKoDAAC6AwAA3gMAAP4DAAASBAAAJgQA" +
+            "AEEEAABVBAAAZAQAAG8EAAByBAAAfwQAAIcEAACWBAAAnwQAAK8EAADABAAA0AQAAOIEAADoBAAA" +
+            "7wQAAPwEAAAKBQAAFwUAACYFAAAwBQAANwUAAK8FAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAO" +
+            "AAAADwAAABIAAAAGAAAABQAAAAAAAAAHAAAABgAAAEQDAAAGAAAABwAAAAAAAAASAAAACAAAAAAA" +
+            "AAAAAAUAFwAAAAAABQAYAAAAAAAFABkAAAAAAAUAGgAAAAAAAwACAAAAAAAAABwAAAAAAAAAHQAA" +
+            "AAAAAAAeAAAAAAAAAB8AAAAAAAAAIAAAAAQAAwACAAAABgADAAIAAAAGAAEAFAAAAAYAAAAhAAAA" +
+            "BwACABUAAAAHAAAAFgAAAAAAAAARAAAABAAAAAAAAAAQAAAADAYAANUFAAAAAAAABwABAAIAAAAt" +
+            "AwAAQQAAAG4QAwAGAAwAbhAEAAYADAFuEAIABgAMAm4QBQAGAAwDcQAKAAAADARuEAsABAAMBCIF" +
+            "BgBwEAcABQBuIAgABQAaAAEAbiAIAAUAbiAIABUAbiAIAAUAbiAIACUAbiAIAAUAbiAIADUAGgAA" +
+            "AG4gCAAFAG4gCABFAG4QCQAFAAwAEQAAAAIAAQAAAAAAMQMAAAMAAABUEAAAEQAAAAIAAQAAAAAA" +
+            "NQMAAAMAAABUEAEAEQAAAAIAAQAAAAAAOQMAAAMAAABUEAIAEQAAAAIAAQAAAAAAPQMAAAMAAABU" +
+            "EAMAEQAAAAIAAQABAAAAJAMAABQAAABwEAYAAQAaAAUAWxABABoAAwBbEAIAGgAEAFsQAAAaACQA" +
+            "WxADAA4ACQAOPEtLS0sAEAAOABYADgATAA4AHAAOABkADgAAAAABAAAABQAGIGZyb20gAAIsIAAG" +
+            "PGluaXQ+ABBCb25qb3VyIGxlIE1vbmRlAApIZWogVmVyZGVuAAtIZWxsbyBXb3JsZAABTAACTEwA" +
+            "GExhcnQvVGVzdDE5OTUkVHJhbnNmb3JtOwAOTGFydC9UZXN0MTk5NTsAIkxkYWx2aWsvYW5ub3Rh" +
+            "dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph" +
+            "dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp" +
+            "bGRlcjsAEkxqYXZhL2xhbmcvVGhyZWFkOwANVGVzdDE5OTUuamF2YQAJVHJhbnNmb3JtAAFWAAth" +
+            "Y2Nlc3NGbGFncwAGYXBwZW5kAA1jdXJyZW50VGhyZWFkAAdnZXROYW1lAA5ncmVldGluZ0Rhbmlz" +
+            "aAAPZ3JlZXRpbmdFbmdsaXNoAA5ncmVldGluZ0ZyZW5jaAAQZ3JlZXRpbmdKYXBhbmVzZQAEbmFt" +
+            "ZQAFc2F5SGkAC3NheUhpRGFuaXNoAAxzYXlIaUVuZ2xpc2gAC3NheUhpRnJlbmNoAA1zYXlIaUph" +
+            "cGFuZXNlAAh0b1N0cmluZwAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIs" +
+            "Im1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2ZDg4" +
+            "YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AB+OBk+OCk+OBq+OBoeOBr+S4lueVjAACAgEi" +
+            "GAECAwITBBkbFxEABAEFAAEBAQEBAQEAgYAE7AUBAfgDAQGMBQEBpAUBAbwFAQHUBQAAAAAAAgAA" +
+            "AMYFAADMBQAAAAYAAAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAJQAAAHAAAAACAAAA" +
+            "CQAAAAQBAAADAAAABAAAACgBAAAEAAAABAAAAFgBAAAFAAAADAAAAHgBAAAGAAAAAQAAANgBAAAB" +
+            "IAAABgAAAPgBAAADIAAABgAAACQDAAABEAAAAQAAAEQDAAACIAAAJQAAAEoDAAAEIAAAAgAAAMYF" +
+            "AAAAIAAAAQAAANUFAAADEAAAAgAAAPwFAAAGIAAAAQAAAAwGAAAAEAAAAQAAABwGAAA=");
 
-  public static MyThread[] startThreads(int num_threads) throws Exception {
-    CountDownLatch cdl = new CountDownLatch(num_threads);
-    MyThread[] res = new MyThread[num_threads];
-    for (int i = 0; i < num_threads; i++) {
-      res[i] = new MyThread(cdl, i);
-      res[i].start();
-    }
-    cdl.await();
-    return res;
-  }
-  public static void finishThreads(MyThread[] thrs) throws Exception {
-    for (MyThread t : thrs) {
-      t.finish();
-    }
-    for (MyThread t : thrs) {
-      t.Check();
-    }
-  }
 
-  public static void doTest() throws Exception {
-    MyThread[] threads = startThreads(NUM_THREADS);
-    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
-    finishThreads(threads);
-  }
+    public static void run() throws Exception {
+        Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+        doTest();
+    }
+
+    public static final class MyThread extends Thread {
+        public MyThread(CountDownLatch delay, int id) {
+            super("Thread: " + id);
+            this.thr_id = id;
+            this.results = new ArrayList<>(TASK_COUNT_LIMIT);
+            this.finish = false;
+            this.delay = delay;
+        }
+
+        public void run() {
+            delay.countDown();
+            while (!finish && results.size() < TASK_COUNT_LIMIT) {
+                Transform t = new Transform();
+                results.add(t.sayHi());
+            }
+        }
+
+        public void finish() throws Exception {
+            finish = true;
+            this.join();
+        }
+
+        public void Check() throws Exception {
+            for (String s : results) {
+                if (!s.equals("Hello from " + getName()) &&
+                        !s.equals("Hello, null, null, null from " + getName()) &&
+                        !s.equals(
+                                "Hello World, Bonjour le Monde, Hej Verden, こんにちは世界 from " +
+                                getName())) {
+                    System.out.println("FAIL " + thr_id + ": Unexpected result: " + s);
+                }
+            }
+        }
+
+        public ArrayList<String> results;
+        public volatile boolean finish;
+        public int thr_id;
+        public CountDownLatch delay;
+    }
+
+    public static MyThread[] startThreads(int num_threads) throws Exception {
+        CountDownLatch cdl = new CountDownLatch(num_threads);
+        MyThread[] res = new MyThread[num_threads];
+        for (int i = 0; i < num_threads; i++) {
+            res[i] = new MyThread(cdl, i);
+            res[i].start();
+        }
+        cdl.await();
+        return res;
+    }
+    public static void finishThreads(MyThread[] thrs) throws Exception {
+        for (MyThread t : thrs) {
+            t.finish();
+        }
+        for (MyThread t : thrs) {
+            t.Check();
+        }
+    }
+
+    public static void doTest() throws Exception {
+        MyThread[] threads = startThreads(NUM_THREADS);
+        Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+        finishThreads(threads);
+    }
 }
diff --git a/test/1996-final-override-virtual-structural/run b/test/1996-final-override-virtual-structural/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1996-final-override-virtual-structural/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1996-final-override-virtual-structural/run.py b/test/1996-final-override-virtual-structural/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1996-final-override-virtual-structural/src/Main.java b/test/1996-final-override-virtual-structural/src/Main.java
index ade69cf..ff10798 100644
--- a/test/1996-final-override-virtual-structural/src/Main.java
+++ b/test/1996-final-override-virtual-structural/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test1996.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test1996.run();
+    }
 }
diff --git a/test/1996-final-override-virtual-structural/src/art/Test1996.java b/test/1996-final-override-virtual-structural/src/art/Test1996.java
index c2b1125..2649476 100644
--- a/test/1996-final-override-virtual-structural/src/art/Test1996.java
+++ b/test/1996-final-override-virtual-structural/src/art/Test1996.java
@@ -19,76 +19,76 @@
 import java.util.Base64;
 public class Test1996 {
 
-  public static class SuperTransform {
-    public String hiValue = "Hi";
-    public String sayHi() {
-      return this.hiValue;
+    public static class SuperTransform {
+        public String hiValue = "Hi";
+        public String sayHi() {
+            return this.hiValue;
+        }
     }
-  }
-  public static final class Transform extends SuperTransform {
-    public void PostTransform() { }
-    public String sayHiTwice(Runnable run) {
-      run.run();
-      return "super: " + super.sayHi() + " this: " + sayHi();
+    public static final class Transform extends SuperTransform {
+        public void PostTransform() { }
+        public String sayHiTwice(Runnable run) {
+            run.run();
+            return "super: " + super.sayHi() + " this: " + sayHi();
+        }
     }
-  }
 
-  /**
-   * base64 encoded class/dex file for
-   * public static final class Transform extends SuperTransform {
-   *   public String myGreeting;
-   *   public void PostTransform() {
-   *     myGreeting = "SALUTATIONS";
-   *   }
-   *   public String sayHiTwice(Runnable run) {
-   *     run.run();
-   *     return "super: " + super.sayHi() + " and then this: " + sayHi();
-   *   }
-   *   public String sayHi() {
-   *     return myGreeting;
-   *   }
-   * }
-   */
-  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
-"ZGV4CjAzNQAO4Dwurw97RcUtfH7np7S5RR8gsJYOfmeABQAAcAAAAHhWNBIAAAAAAAAAALwEAAAc" +
-"AAAAcAAAAAkAAADgAAAABAAAAAQBAAABAAAANAEAAAoAAAA8AQAAAQAAAIwBAADUAwAArAEAAHYC" +
-"AACIAgAAkAIAAJMCAACXAgAAtgIAANACAADgAgAABAMAACQDAAA6AwAATgMAAGkDAAB4AwAAhQMA" +
-"AJQDAACfAwAAogMAAK8DAAC3AwAAwwMAAMkDAADOAwAA1QMAAOEDAADqAwAA9AMAAPsDAAAEAAAA" +
-"BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAABAAAAACAAAABgAAAAAAAAADAAAABgAAAGgCAAAD" +
-"AAAABwAAAHACAAAQAAAACAAAAAAAAAABAAYAEwAAAAAAAwABAAAAAAAAABYAAAABAAMAAQAAAAEA" +
-"AwAMAAAAAQAAABYAAAABAAEAFwAAAAUAAwAVAAAABwADAAEAAAAHAAIAEgAAAAcAAAAZAAAAAQAA" +
-"ABEAAAAAAAAAAAAAAA4AAACsBAAAggQAAAAAAAACAAEAAAAAAFsCAAADAAAAVBAAABEAAAAFAAIA" +
-"AgAAAF8CAAAlAAAAchAGAAQAbxABAAMADARuEAQAAwAMACIBBwBwEAcAAQAaAhgAbiAIACEAbiAI" +
-"AEEAGgQAAG4gCABBAG4gCAABAG4QCQABAAwEEQQAAAEAAQABAAAAUgIAAAQAAABwEAAAAAAOAAIA" +
-"AQAAAAAAVgIAAAUAAAAaAA0AWxAAAA4ACgAOAA0ADksAFAAOABABAA48AAAAAAEAAAAFAAAAAQAA" +
-"AAYAECBhbmQgdGhlbiB0aGlzOiAABjxpbml0PgABTAACTEwAHUxhcnQvVGVzdDE5OTYkU3VwZXJU" +
-"cmFuc2Zvcm07ABhMYXJ0L1Rlc3QxOTk2JFRyYW5zZm9ybTsADkxhcnQvVGVzdDE5OTY7ACJMZGFs" +
-"dmlrL2Fubm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJD" +
-"bGFzczsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xh" +
-"bmcvU3RyaW5nQnVpbGRlcjsADVBvc3RUcmFuc2Zvcm0AC1NBTFVUQVRJT05TAA1UZXN0MTk5Ni5q" +
-"YXZhAAlUcmFuc2Zvcm0AAVYAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQACm15R3JlZXRpbmcABG5hbWUA" +
-"A3J1bgAFc2F5SGkACnNheUhpVHdpY2UAB3N1cGVyOiAACHRvU3RyaW5nAAV2YWx1ZQB2fn5EOHsi" +
-"Y29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiI2MGRhNGQ2N2Iz" +
-"ODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwidmVyc2lvbiI6IjEuNy4xMi1kZXYifQAC" +
-"AwEaGAICBAIRBBkUFw8AAQEDAAECgYAEoAQDAbgEAQGsAwEBxAMAAAAAAAACAAAAcwQAAHkEAACg" +
-"BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAcAAAAcAAAAAIAAAAJAAAA4AAAAAMA" +
-"AAAEAAAABAEAAAQAAAABAAAANAEAAAUAAAAKAAAAPAEAAAYAAAABAAAAjAEAAAEgAAAEAAAArAEA" +
-"AAMgAAAEAAAAUgIAAAEQAAACAAAAaAIAAAIgAAAcAAAAdgIAAAQgAAACAAAAcwQAAAAgAAABAAAA" +
-"ggQAAAMQAAACAAAAnAQAAAYgAAABAAAArAQAAAAQAAABAAAAvAQAAA==");
+    /**
+     * base64 encoded class/dex file for
+     * public static final class Transform extends SuperTransform {
+     *   public String myGreeting;
+     *   public void PostTransform() {
+     *     myGreeting = "SALUTATIONS";
+     *   }
+     *   public String sayHiTwice(Runnable run) {
+     *     run.run();
+     *     return "super: " + super.sayHi() + " and then this: " + sayHi();
+     *   }
+     *   public String sayHi() {
+     *     return myGreeting;
+     *   }
+     * }
+     */
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQAO4Dwurw97RcUtfH7np7S5RR8gsJYOfmeABQAAcAAAAHhWNBIAAAAAAAAAALwEAAAc" +
+            "AAAAcAAAAAkAAADgAAAABAAAAAQBAAABAAAANAEAAAoAAAA8AQAAAQAAAIwBAADUAwAArAEAAHYC" +
+            "AACIAgAAkAIAAJMCAACXAgAAtgIAANACAADgAgAABAMAACQDAAA6AwAATgMAAGkDAAB4AwAAhQMA" +
+            "AJQDAACfAwAAogMAAK8DAAC3AwAAwwMAAMkDAADOAwAA1QMAAOEDAADqAwAA9AMAAPsDAAAEAAAA" +
+            "BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAABAAAAACAAAABgAAAAAAAAADAAAABgAAAGgCAAAD" +
+            "AAAABwAAAHACAAAQAAAACAAAAAAAAAABAAYAEwAAAAAAAwABAAAAAAAAABYAAAABAAMAAQAAAAEA" +
+            "AwAMAAAAAQAAABYAAAABAAEAFwAAAAUAAwAVAAAABwADAAEAAAAHAAIAEgAAAAcAAAAZAAAAAQAA" +
+            "ABEAAAAAAAAAAAAAAA4AAACsBAAAggQAAAAAAAACAAEAAAAAAFsCAAADAAAAVBAAABEAAAAFAAIA" +
+            "AgAAAF8CAAAlAAAAchAGAAQAbxABAAMADARuEAQAAwAMACIBBwBwEAcAAQAaAhgAbiAIACEAbiAI" +
+            "AEEAGgQAAG4gCABBAG4gCAABAG4QCQABAAwEEQQAAAEAAQABAAAAUgIAAAQAAABwEAAAAAAOAAIA" +
+            "AQAAAAAAVgIAAAUAAAAaAA0AWxAAAA4ACgAOAA0ADksAFAAOABABAA48AAAAAAEAAAAFAAAAAQAA" +
+            "AAYAECBhbmQgdGhlbiB0aGlzOiAABjxpbml0PgABTAACTEwAHUxhcnQvVGVzdDE5OTYkU3VwZXJU" +
+            "cmFuc2Zvcm07ABhMYXJ0L1Rlc3QxOTk2JFRyYW5zZm9ybTsADkxhcnQvVGVzdDE5OTY7ACJMZGFs" +
+            "dmlrL2Fubm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJD" +
+            "bGFzczsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xh" +
+            "bmcvU3RyaW5nQnVpbGRlcjsADVBvc3RUcmFuc2Zvcm0AC1NBTFVUQVRJT05TAA1UZXN0MTk5Ni5q" +
+            "YXZhAAlUcmFuc2Zvcm0AAVYAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQACm15R3JlZXRpbmcABG5hbWUA" +
+            "A3J1bgAFc2F5SGkACnNheUhpVHdpY2UAB3N1cGVyOiAACHRvU3RyaW5nAAV2YWx1ZQB2fn5EOHsi" +
+            "Y29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiI2MGRhNGQ2N2Iz" +
+            "ODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwidmVyc2lvbiI6IjEuNy4xMi1kZXYifQAC" +
+            "AwEaGAICBAIRBBkUFw8AAQEDAAECgYAEoAQDAbgEAQGsAwEBxAMAAAAAAAACAAAAcwQAAHkEAACg" +
+            "BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAcAAAAcAAAAAIAAAAJAAAA4AAAAAMA" +
+            "AAAEAAAABAEAAAQAAAABAAAANAEAAAUAAAAKAAAAPAEAAAYAAAABAAAAjAEAAAEgAAAEAAAArAEA" +
+            "AAMgAAAEAAAAUgIAAAEQAAACAAAAaAIAAAIgAAAcAAAAdgIAAAQgAAACAAAAcwQAAAAgAAABAAAA" +
+            "ggQAAAMQAAACAAAAnAQAAAYgAAABAAAArAQAAAAQAAABAAAAvAQAAA==");
 
-  public static void run() {
-    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
-    doTest(new Transform());
-  }
+    public static void run() {
+        Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+        doTest(new Transform());
+    }
 
-  public static void doTest(final Transform t) {
-    System.out.println(t.sayHiTwice(() -> { System.out.println("Not doing anything"); }));
-    System.out.println(t.sayHiTwice(
-      () -> {
-        System.out.println("Redefining calling class");
-        Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
-        t.PostTransform();
-      }));
-    System.out.println(t.sayHiTwice(() -> { System.out.println("Not doing anything"); }));
-  }
+    public static void doTest(final Transform t) {
+        System.out.println(t.sayHiTwice(() -> { System.out.println("Not doing anything"); }));
+        System.out.println(t.sayHiTwice(
+            () -> {
+                System.out.println("Redefining calling class");
+                Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+                t.PostTransform();
+            }));
+        System.out.println(t.sayHiTwice(() -> { System.out.println("Not doing anything"); }));
+    }
 }
diff --git a/test/1997-structural-shadow-method/run b/test/1997-structural-shadow-method/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1997-structural-shadow-method/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1997-structural-shadow-method/run.py b/test/1997-structural-shadow-method/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1997-structural-shadow-method/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1997-structural-shadow-method/src/Main.java b/test/1997-structural-shadow-method/src/Main.java
index 3c9bc85..f6552ee 100644
--- a/test/1997-structural-shadow-method/src/Main.java
+++ b/test/1997-structural-shadow-method/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test1997.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test1997.run();
+    }
 }
diff --git a/test/1997-structural-shadow-method/src/art/Test1997.java b/test/1997-structural-shadow-method/src/art/Test1997.java
index 7309a31..5186518 100644
--- a/test/1997-structural-shadow-method/src/art/Test1997.java
+++ b/test/1997-structural-shadow-method/src/art/Test1997.java
@@ -20,65 +20,63 @@
 
 public class Test1997 {
 
-  public static class SuperTransform {
-    // We will be shadowing this function.
-    public static void sayHi() {
-      System.out.println("Hello!");
+    public static class SuperTransform {
+        // We will be shadowing this function.
+        public static void sayHi() {
+            System.out.println("Hello!");
+        }
     }
-  }
 
-  // The class we will be transforming.
-  public static class Transform extends SuperTransform {
-    public static void sayHiTwice() {
-      Transform.sayHi();
-      Transform.sayHi();
+    // The class we will be transforming.
+    public static class Transform extends SuperTransform {
+        public static void sayHiTwice() {
+            Transform.sayHi();
+            Transform.sayHi();
+        }
     }
-  }
 
-  // public static class Transform extends SuperTransform {
-  //   public static void sayHiTwice() {
-  //     Transform.sayHi();
-  //     Transform.sayHi();
-  //   }
-  //   public static void sayHi() {
-  //     System.out.println("Hello World!");
-  //   }
-  // }
-  private static final byte[] DEX_BYTES =
-      Base64.getDecoder()
-          .decode(
-              "ZGV4CjAzNQA9wdy7Lgbrv+sD+wixborREr0maZCK5yqABAAAcAAAAHhWNBIAAAAAAAAAALwDAAAW"
-                  + "AAAAcAAAAAkAAADIAAAAAgAAAOwAAAABAAAABAEAAAUAAAAMAQAAAQAAADQBAAAsAwAAVAEAAMIB"
-                  + "AADKAQAA2AEAAPcBAAARAgAAIQIAAEUCAABlAgAAfAIAAJACAACkAgAAswIAAL4CAADBAgAAxQIA"
-                  + "ANICAADYAgAA3QIAAOYCAADtAgAA+QIAAAADAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAA"
-                  + "CQAAAAwAAAAMAAAACAAAAAAAAAANAAAACAAAALwBAAAHAAUAEAAAAAAAAAAAAAAAAQAAAAAAAAAB"
-                  + "AAAAEgAAAAEAAAATAAAABQABABEAAAABAAAAAQAAAAAAAAAAAAAACgAAAKwDAACHAwAAAAAAAAEA"
-                  + "AQABAAAAqgEAAAQAAABwEAAAAAAOAAIAAAACAAAArgEAAAgAAABiAAAAGgEBAG4gBAAQAA4AAAAA"
-                  + "AAAAAACzAQAABwAAAHEAAgAAAHEAAgAAAA4ADwAOABUADngAEQAOPDwAAAAAAQAAAAYABjxpbml0"
-                  + "PgAMSGVsbG8gV29ybGQhAB1MYXJ0L1Rlc3QxOTk3JFN1cGVyVHJhbnNmb3JtOwAYTGFydC9UZXN0"
-                  + "MTk5NyRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QxOTk3OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv"
-                  + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu"
-                  + "dFN0cmVhbTsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AA1UZXN0MTk5"
-                  + "Ny5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFncwAEbmFtZQADb3V0AAdwcmludGxu"
-                  + "AAVzYXlIaQAKc2F5SGlUd2ljZQAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1"
-                  + "ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2"
-                  + "ZDg4YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AAgMBFBgCAgQCDgQJDxcLAAADAAGBgATU"
-                  + "AgEJ7AIBCYwDAAAAAAAAAAIAAAB4AwAAfgMAAKADAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAA"
-                  + "AAAAAQAAABYAAABwAAAAAgAAAAkAAADIAAAAAwAAAAIAAADsAAAABAAAAAEAAAAEAQAABQAAAAUA"
-                  + "AAAMAQAABgAAAAEAAAA0AQAAASAAAAMAAABUAQAAAyAAAAMAAACqAQAAARAAAAEAAAC8AQAAAiAA"
-                  + "ABYAAADCAQAABCAAAAIAAAB4AwAAACAAAAEAAACHAwAAAxAAAAIAAACcAwAABiAAAAEAAACsAwAA"
-                  + "ABAAAAEAAAC8AwAA");
+    // public static class Transform extends SuperTransform {
+    //     public static void sayHiTwice() {
+    //         Transform.sayHi();
+    //         Transform.sayHi();
+    //     }
+    //     public static void sayHi() {
+    //         System.out.println("Hello World!");
+    //     }
+    // }
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQA9wdy7Lgbrv+sD+wixborREr0maZCK5yqABAAAcAAAAHhWNBIAAAAAAAAAALwDAAAW"
+            + "AAAAcAAAAAkAAADIAAAAAgAAAOwAAAABAAAABAEAAAUAAAAMAQAAAQAAADQBAAAsAwAAVAEAAMIB"
+            + "AADKAQAA2AEAAPcBAAARAgAAIQIAAEUCAABlAgAAfAIAAJACAACkAgAAswIAAL4CAADBAgAAxQIA"
+            + "ANICAADYAgAA3QIAAOYCAADtAgAA+QIAAAADAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAA"
+            + "CQAAAAwAAAAMAAAACAAAAAAAAAANAAAACAAAALwBAAAHAAUAEAAAAAAAAAAAAAAAAQAAAAAAAAAB"
+            + "AAAAEgAAAAEAAAATAAAABQABABEAAAABAAAAAQAAAAAAAAAAAAAACgAAAKwDAACHAwAAAAAAAAEA"
+            + "AQABAAAAqgEAAAQAAABwEAAAAAAOAAIAAAACAAAArgEAAAgAAABiAAAAGgEBAG4gBAAQAA4AAAAA"
+            + "AAAAAACzAQAABwAAAHEAAgAAAHEAAgAAAA4ADwAOABUADngAEQAOPDwAAAAAAQAAAAYABjxpbml0"
+            + "PgAMSGVsbG8gV29ybGQhAB1MYXJ0L1Rlc3QxOTk3JFN1cGVyVHJhbnNmb3JtOwAYTGFydC9UZXN0"
+            + "MTk5NyRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QxOTk3OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv"
+            + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu"
+            + "dFN0cmVhbTsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AA1UZXN0MTk5"
+            + "Ny5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFncwAEbmFtZQADb3V0AAdwcmludGxu"
+            + "AAVzYXlIaQAKc2F5SGlUd2ljZQAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1"
+            + "ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2"
+            + "ZDg4YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AAgMBFBgCAgQCDgQJDxcLAAADAAGBgATU"
+            + "AgEJ7AIBCYwDAAAAAAAAAAIAAAB4AwAAfgMAAKADAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAA"
+            + "AAAAAQAAABYAAABwAAAAAgAAAAkAAADIAAAAAwAAAAIAAADsAAAABAAAAAEAAAAEAQAABQAAAAUA"
+            + "AAAMAQAABgAAAAEAAAA0AQAAASAAAAMAAABUAQAAAyAAAAMAAACqAQAAARAAAAEAAAC8AQAAAiAA"
+            + "ABYAAADCAQAABCAAAAIAAAB4AwAAACAAAAEAAACHAwAAAxAAAAIAAACcAwAABiAAAAEAAACsAwAA"
+            + "ABAAAAEAAAC8AwAA");
 
-  public static void run() throws Exception {
-    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
-    doTest();
-  }
+    public static void run() throws Exception {
+        Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+        doTest();
+    }
 
-  public static void doTest() throws Exception {
-    Transform.sayHiTwice();
-    Transform.sayHi();
-    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
-    Transform.sayHiTwice();
-    Transform.sayHi();
-  }
+    public static void doTest() throws Exception {
+        Transform.sayHiTwice();
+        Transform.sayHi();
+        Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+        Transform.sayHiTwice();
+        Transform.sayHi();
+    }
 }
diff --git a/test/1998-structural-shadow-field/run b/test/1998-structural-shadow-field/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1998-structural-shadow-field/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1998-structural-shadow-field/run.py b/test/1998-structural-shadow-field/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1998-structural-shadow-field/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1998-structural-shadow-field/src/Main.java b/test/1998-structural-shadow-field/src/Main.java
index f6aeca5..e35d1b1 100644
--- a/test/1998-structural-shadow-field/src/Main.java
+++ b/test/1998-structural-shadow-field/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test1998.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test1998.run();
+    }
 }
diff --git a/test/1998-structural-shadow-field/src/art/Test1998.java b/test/1998-structural-shadow-field/src/art/Test1998.java
index 3fda936..3aaa9f3 100644
--- a/test/1998-structural-shadow-field/src/art/Test1998.java
+++ b/test/1998-structural-shadow-field/src/art/Test1998.java
@@ -20,46 +20,44 @@
 
 public class Test1998 {
 
-  public static class SuperTransform {
-    public static String greeting = "Hello";
-  }
+    public static class SuperTransform {
+        public static String greeting = "Hello";
+    }
 
-  // The class we will be transforming.
-  public static class Transform extends SuperTransform { }
+    // The class we will be transforming.
+    public static class Transform extends SuperTransform { }
 
-  // public static class Transform extends SuperTransform {
-  //   public static String greeting;
-  // }
-  private static final byte[] DEX_BYTES =
-      Base64.getDecoder()
-          .decode(
-"ZGV4CjAzNQCYmnoWz4BqygrZQM4zf/mJ/25+dM86MHKAAwAAcAAAAHhWNBIAAAAAAAAAAMgCAAAP" +
-"AAAAcAAAAAcAAACsAAAAAQAAAMgAAAABAAAA1AAAAAIAAADcAAAAAQAAAOwAAAB0AgAADAEAACgB" +
-"AAAwAQAATwEAAGkBAAB5AQAAnQEAAL0BAADRAQAA4AEAAOsBAADuAQAA+wEAAAUCAAALAgAAEgIA" +
-"AAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAkAAAAJAAAABgAAAAAAAAABAAUACwAAAAAAAAAAAAAA" +
-"AQAAAAAAAAABAAAAAQAAAAAAAAAAAAAABwAAALgCAACZAgAAAAAAAAEAAQABAAAAJAEAAAQAAABw" +
-"EAAAAAAOAAUADgAGPGluaXQ+AB1MYXJ0L1Rlc3QxOTk4JFN1cGVyVHJhbnNmb3JtOwAYTGFydC9U" +
-"ZXN0MTk5OCRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QxOTk4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0Vu" +
-"Y2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5n" +
-"L1N0cmluZzsADVRlc3QxOTk4LmphdmEACVRyYW5zZm9ybQABVgALYWNjZXNzRmxhZ3MACGdyZWV0" +
-"aW5nAARuYW1lAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFw" +
-"aSI6MSwic2hhLTEiOiI2MGRhNGQ2N2IzODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwi" +
-"dmVyc2lvbiI6IjEuNy4xMi1kZXYifQACAwENGAICBAIKBAkMFwgBAAEAAAkBgYAEjAIAAAAAAAAA" +
-"AgAAAIoCAACQAgAArAIAAAAAAAAAAAAAAAAAAA8AAAAAAAAAAQAAAAAAAAABAAAADwAAAHAAAAAC" +
-"AAAABwAAAKwAAAADAAAAAQAAAMgAAAAEAAAAAQAAANQAAAAFAAAAAgAAANwAAAAGAAAAAQAAAOwA" +
-"AAABIAAAAQAAAAwBAAADIAAAAQAAACQBAAACIAAADwAAACgBAAAEIAAAAgAAAIoCAAAAIAAAAQAA" +
-"AJkCAAADEAAAAgAAAKgCAAAGIAAAAQAAALgCAAAAEAAAAQAAAMgCAAA=");
+    // public static class Transform extends SuperTransform {
+    //   public static String greeting;
+    // }
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQCYmnoWz4BqygrZQM4zf/mJ/25+dM86MHKAAwAAcAAAAHhWNBIAAAAAAAAAAMgCAAAP" +
+            "AAAAcAAAAAcAAACsAAAAAQAAAMgAAAABAAAA1AAAAAIAAADcAAAAAQAAAOwAAAB0AgAADAEAACgB" +
+            "AAAwAQAATwEAAGkBAAB5AQAAnQEAAL0BAADRAQAA4AEAAOsBAADuAQAA+wEAAAUCAAALAgAAEgIA" +
+            "AAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAkAAAAJAAAABgAAAAAAAAABAAUACwAAAAAAAAAAAAAA" +
+            "AQAAAAAAAAABAAAAAQAAAAAAAAAAAAAABwAAALgCAACZAgAAAAAAAAEAAQABAAAAJAEAAAQAAABw" +
+            "EAAAAAAOAAUADgAGPGluaXQ+AB1MYXJ0L1Rlc3QxOTk4JFN1cGVyVHJhbnNmb3JtOwAYTGFydC9U" +
+            "ZXN0MTk5OCRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QxOTk4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0Vu" +
+            "Y2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5n" +
+            "L1N0cmluZzsADVRlc3QxOTk4LmphdmEACVRyYW5zZm9ybQABVgALYWNjZXNzRmxhZ3MACGdyZWV0" +
+            "aW5nAARuYW1lAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFw" +
+            "aSI6MSwic2hhLTEiOiI2MGRhNGQ2N2IzODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwi" +
+            "dmVyc2lvbiI6IjEuNy4xMi1kZXYifQACAwENGAICBAIKBAkMFwgBAAEAAAkBgYAEjAIAAAAAAAAA" +
+            "AgAAAIoCAACQAgAArAIAAAAAAAAAAAAAAAAAAA8AAAAAAAAAAQAAAAAAAAABAAAADwAAAHAAAAAC" +
+            "AAAABwAAAKwAAAADAAAAAQAAAMgAAAAEAAAAAQAAANQAAAAFAAAAAgAAANwAAAAGAAAAAQAAAOwA" +
+            "AAABIAAAAQAAAAwBAAADIAAAAQAAACQBAAACIAAADwAAACgBAAAEIAAAAgAAAIoCAAAAIAAAAQAA" +
+            "AJkCAAADEAAAAgAAAKgCAAAGIAAAAQAAALgCAAAAEAAAAQAAAMgCAAA=");
 
-  public static void run() throws Exception {
-    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
-    doTest();
-  }
+    public static void run() throws Exception {
+        Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+        doTest();
+    }
 
-  public static void doTest() throws Exception {
-    System.out.println(Transform.greeting);
-    System.out.println(SuperTransform.greeting);
-    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
-    System.out.println(Transform.greeting);
-    System.out.println(SuperTransform.greeting);
-  }
+    public static void doTest() throws Exception {
+        System.out.println(Transform.greeting);
+        System.out.println(SuperTransform.greeting);
+        Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+        System.out.println(Transform.greeting);
+        System.out.println(SuperTransform.greeting);
+    }
 }
diff --git a/test/1999-virtual-structural/run b/test/1999-virtual-structural/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/1999-virtual-structural/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1999-virtual-structural/run.py b/test/1999-virtual-structural/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/1999-virtual-structural/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/1999-virtual-structural/src/Main.java b/test/1999-virtual-structural/src/Main.java
index 86a492b..f4a1bd6 100644
--- a/test/1999-virtual-structural/src/Main.java
+++ b/test/1999-virtual-structural/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test1999.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test1999.run();
+    }
 }
diff --git a/test/1999-virtual-structural/src/art/Test1999.java b/test/1999-virtual-structural/src/art/Test1999.java
index f6811a9..6943442 100644
--- a/test/1999-virtual-structural/src/art/Test1999.java
+++ b/test/1999-virtual-structural/src/art/Test1999.java
@@ -19,67 +19,67 @@
 import java.util.Base64;
 public class Test1999 {
 
-  public static class Transform {
-    public String getGreeting() {
-      return "Hi";
+    public static class Transform {
+        public String getGreeting() {
+            return "Hi";
+        }
     }
-  }
 
-  public static class SubTransform extends Transform {
-    private int count = 0;
-    public void sayHi() {
-      System.out.println(getGreeting() + "(SubTransform called " + (++count) + " times)");
+    public static class SubTransform extends Transform {
+        private int count = 0;
+        public void sayHi() {
+            System.out.println(getGreeting() + "(SubTransform called " + (++count) + " times)");
+        }
     }
-  }
 
-  /**
-   * base64 encoded class/dex file for
-   * public static class Transform {
-   *   private int count;
-   *   public String getGreeting() {
-   *     return "Hello (Transform called " + incrCount() + " times)";
-   *   }
-   *   protected int incrCount() {
-   *     return ++count;
-   *   }
-   * }
-   */
-  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
-"ZGV4CjAzNQAwwbMpPdPdWkU+6UJnvqa7v4VBdcuq2vkMBQAAcAAAAHhWNBIAAAAAAAAAAEgEAAAa" +
-"AAAAcAAAAAkAAADYAAAABQAAAPwAAAABAAAAOAEAAAgAAABAAQAAAQAAAIABAABsAwAAoAEAADoC" +
-"AABDAgAASwIAAGUCAABoAgAAawIAAG8CAABzAgAAjQIAAJ0CAADBAgAA4QIAAPUCAAAJAwAAJAMA" +
-"ADMDAAA+AwAAQQMAAE4DAABWAwAAXQMAAGoDAAB1AwAAewMAAIUDAACMAwAAAwAAAAcAAAAIAAAA" +
-"CQAAAAoAAAALAAAADAAAAA0AAAAQAAAAAwAAAAAAAAAAAAAABAAAAAYAAAAAAAAABQAAAAcAAAAs" +
-"AgAABgAAAAcAAAA0AgAAEAAAAAgAAAAAAAAAAQAAABMAAAABAAQAAQAAAAEAAQAUAAAAAQAAABUA" +
-"AAAFAAQAAQAAAAcABAABAAAABwACABIAAAAHAAMAEgAAAAcAAQAXAAAAAQAAAAEAAAAFAAAAAAAA" +
-"AA4AAAA4BAAAEwQAAAAAAAACAAEAAAAAACgCAAAHAAAAUhAAANgAAAFZEAAADwAAAAQAAQACAAAA" +
-"JAIAABsAAABuEAIAAwAKACIBBwBwEAQAAQAaAgIAbiAGACEAbiAFAAEAGgAAAG4gBgABAG4QBwAB" +
-"AAwAEQAAAAEAAQABAAAAIAIAAAQAAABwEAMAAAAOAAMADgAGAA4ACQAOAAEAAAAAAAAAAQAAAAYA" +
-"ByB0aW1lcykABjxpbml0PgAYSGVsbG8gKFRyYW5zZm9ybSBjYWxsZWQgAAFJAAFMAAJMSQACTEwA" +
-"GExhcnQvVGVzdDE5OTkkVHJhbnNmb3JtOwAOTGFydC9UZXN0MTk5OTsAIkxkYWx2aWsvYW5ub3Rh" +
-"dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph" +
-"dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp" +
-"bGRlcjsADVRlc3QxOTk5LmphdmEACVRyYW5zZm9ybQABVgALYWNjZXNzRmxhZ3MABmFwcGVuZAAF" +
-"Y291bnQAC2dldEdyZWV0aW5nAAlpbmNyQ291bnQABG5hbWUACHRvU3RyaW5nAAV2YWx1ZQB2fn5E" +
-"OHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiI2MGRhNGQ2" +
-"N2IzODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwidmVyc2lvbiI6IjEuNy4xMi1kZXYi" +
-"fQACAwEYGAICBAIRBAkWFw8AAQECAAIAgYAEiAQBAcADAQSgAwAAAAAAAgAAAAQEAAAKBAAALAQA" +
-"AAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAGgAAAHAAAAACAAAACQAAANgAAAADAAAA" +
-"BQAAAPwAAAAEAAAAAQAAADgBAAAFAAAACAAAAEABAAAGAAAAAQAAAIABAAABIAAAAwAAAKABAAAD" +
-"IAAAAwAAACACAAABEAAAAgAAACwCAAACIAAAGgAAADoCAAAEIAAAAgAAAAQEAAAAIAAAAQAAABME" +
-"AAADEAAAAgAAACgEAAAGIAAAAQAAADgEAAAAEAAAAQAAAEgEAAA=");
+    /**
+     * base64 encoded class/dex file for
+     * public static class Transform {
+     *   private int count;
+     *   public String getGreeting() {
+     *     return "Hello (Transform called " + incrCount() + " times)";
+     *   }
+     *   protected int incrCount() {
+     *     return ++count;
+     *   }
+     * }
+     */
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQAwwbMpPdPdWkU+6UJnvqa7v4VBdcuq2vkMBQAAcAAAAHhWNBIAAAAAAAAAAEgEAAAa" +
+            "AAAAcAAAAAkAAADYAAAABQAAAPwAAAABAAAAOAEAAAgAAABAAQAAAQAAAIABAABsAwAAoAEAADoC" +
+            "AABDAgAASwIAAGUCAABoAgAAawIAAG8CAABzAgAAjQIAAJ0CAADBAgAA4QIAAPUCAAAJAwAAJAMA" +
+            "ADMDAAA+AwAAQQMAAE4DAABWAwAAXQMAAGoDAAB1AwAAewMAAIUDAACMAwAAAwAAAAcAAAAIAAAA" +
+            "CQAAAAoAAAALAAAADAAAAA0AAAAQAAAAAwAAAAAAAAAAAAAABAAAAAYAAAAAAAAABQAAAAcAAAAs" +
+            "AgAABgAAAAcAAAA0AgAAEAAAAAgAAAAAAAAAAQAAABMAAAABAAQAAQAAAAEAAQAUAAAAAQAAABUA" +
+            "AAAFAAQAAQAAAAcABAABAAAABwACABIAAAAHAAMAEgAAAAcAAQAXAAAAAQAAAAEAAAAFAAAAAAAA" +
+            "AA4AAAA4BAAAEwQAAAAAAAACAAEAAAAAACgCAAAHAAAAUhAAANgAAAFZEAAADwAAAAQAAQACAAAA" +
+            "JAIAABsAAABuEAIAAwAKACIBBwBwEAQAAQAaAgIAbiAGACEAbiAFAAEAGgAAAG4gBgABAG4QBwAB" +
+            "AAwAEQAAAAEAAQABAAAAIAIAAAQAAABwEAMAAAAOAAMADgAGAA4ACQAOAAEAAAAAAAAAAQAAAAYA" +
+            "ByB0aW1lcykABjxpbml0PgAYSGVsbG8gKFRyYW5zZm9ybSBjYWxsZWQgAAFJAAFMAAJMSQACTEwA" +
+            "GExhcnQvVGVzdDE5OTkkVHJhbnNmb3JtOwAOTGFydC9UZXN0MTk5OTsAIkxkYWx2aWsvYW5ub3Rh" +
+            "dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph" +
+            "dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp" +
+            "bGRlcjsADVRlc3QxOTk5LmphdmEACVRyYW5zZm9ybQABVgALYWNjZXNzRmxhZ3MABmFwcGVuZAAF" +
+            "Y291bnQAC2dldEdyZWV0aW5nAAlpbmNyQ291bnQABG5hbWUACHRvU3RyaW5nAAV2YWx1ZQB2fn5E" +
+            "OHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiI2MGRhNGQ2" +
+            "N2IzODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwidmVyc2lvbiI6IjEuNy4xMi1kZXYi" +
+            "fQACAwEYGAICBAIRBAkWFw8AAQECAAIAgYAEiAQBAcADAQSgAwAAAAAAAgAAAAQEAAAKBAAALAQA" +
+            "AAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAGgAAAHAAAAACAAAACQAAANgAAAADAAAA" +
+            "BQAAAPwAAAAEAAAAAQAAADgBAAAFAAAACAAAAEABAAAGAAAAAQAAAIABAAABIAAAAwAAAKABAAAD" +
+            "IAAAAwAAACACAAABEAAAAgAAACwCAAACIAAAGgAAADoCAAAEIAAAAgAAAAQEAAAAIAAAAQAAABME" +
+            "AAADEAAAAgAAACgEAAAGIAAAAQAAADgEAAAAEAAAAQAAAEgEAAA=");
 
 
-  public static void run() {
-    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
-    doTest(new SubTransform());
-  }
+    public static void run() {
+        Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+        doTest(new SubTransform());
+    }
 
-  public static void doTest(SubTransform t) {
-    t.sayHi();
-    t.sayHi();
-    t.sayHi();
-    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
-    t.sayHi();
-  }
+    public static void doTest(SubTransform t) {
+        t.sayHi();
+        t.sayHi();
+        t.sayHi();
+        Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+        t.sayHi();
+    }
 }
diff --git a/test/2000-virtual-list-structural/build b/test/2000-virtual-list-structural/build
deleted file mode 100755
index f8496bec..0000000
--- a/test/2000-virtual-list-structural/build
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# Stop on failure.
-set -e
-
-# Deref the symlink.
-mv src-ex/java/util/AbstractCollection.java src-ex/java/util/AbstractCollection.bak
-cp src-ex/java/util/AbstractCollection.bak src-ex/java/util/AbstractCollection.java
-
-# Patch the copied version.
-patch src-ex/java/util/AbstractCollection.java AbstractCollection.patch
-
-USE_DESUGAR=false ./default-build "$@"
-
-# restore the symlink
-rm src-ex/java/util/AbstractCollection.java
-mv src-ex/java/util/AbstractCollection.bak src-ex/java/util/AbstractCollection.java
diff --git a/test/2000-virtual-list-structural/build.py b/test/2000-virtual-list-structural/build.py
new file mode 100644
index 0000000..4791995
--- /dev/null
+++ b/test/2000-virtual-list-structural/build.py
@@ -0,0 +1,24 @@
+#
+# Copyright (C) 2022 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.
+
+import os
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build(use_desugar=False)
+
+  os.rename(ctx.test_dir / "src-ex/java/util/AbstractCollection.bak",
+            ctx.test_dir / "src-ex/java/util/AbstractCollection.java")
diff --git a/test/2000-virtual-list-structural/generate-sources b/test/2000-virtual-list-structural/generate-sources
new file mode 100755
index 0000000..5857b40
--- /dev/null
+++ b/test/2000-virtual-list-structural/generate-sources
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+# Stop on failure.
+set -e
+
+# Deref the symlink.
+mv src-ex/java/util/AbstractCollection.java src-ex/java/util/AbstractCollection.bak
+cp src-ex/java/util/AbstractCollection.bak src-ex/java/util/AbstractCollection.java
+
+# Patch the copied version.
+patch -s src-ex/java/util/AbstractCollection.java AbstractCollection.patch
diff --git a/test/2000-virtual-list-structural/run b/test/2000-virtual-list-structural/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/2000-virtual-list-structural/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2000-virtual-list-structural/run.py b/test/2000-virtual-list-structural/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/2000-virtual-list-structural/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2001-virtual-structural-multithread/run b/test/2001-virtual-structural-multithread/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/2001-virtual-structural-multithread/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2001-virtual-structural-multithread/run.py b/test/2001-virtual-structural-multithread/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2001-virtual-structural-multithread/src-art/art/Test2001.java b/test/2001-virtual-structural-multithread/src-art/art/Test2001.java
index 40972db..87661d0 100644
--- a/test/2001-virtual-structural-multithread/src-art/art/Test2001.java
+++ b/test/2001-virtual-structural-multithread/src-art/art/Test2001.java
@@ -17,6 +17,7 @@
 package art;
 
 import dalvik.system.InMemoryDexClassLoader;
+import dalvik.system.VMRuntime;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Base64;
@@ -26,7 +27,7 @@
 public class Test2001 {
   private static final int NUM_THREADS = 20;
   // Don't perform more than this many repeats per thread to prevent OOMEs
-  private static final int TASK_COUNT_LIMIT = 1000;
+  private static int TASK_COUNT_LIMIT = 1000;
 
   public static class Transform {
     public String greetingEnglish;
@@ -226,6 +227,13 @@
   }
 
   public static void doTest() throws Exception {
+    if (!VMRuntime.getRuntime().is64Bit()) {
+      // Reduce the task count limit on 32-bit VMs. Since we create a class loader for each task,
+      // we create too many linear alloc spaces. On 32-bit systems this might cause an OOM because
+      // we run out of virtual address space.
+      TASK_COUNT_LIMIT = 100;
+    }
+
     MyThread[] threads = startThreads(NUM_THREADS);
     Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
     finishThreads(threads);
diff --git a/test/2002-virtual-structural-initializing/run b/test/2002-virtual-structural-initializing/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/2002-virtual-structural-initializing/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2002-virtual-structural-initializing/run.py b/test/2002-virtual-structural-initializing/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2003-double-virtual-structural/run b/test/2003-double-virtual-structural/run
deleted file mode 100755
index b59f97c..0000000
--- a/test/2003-double-virtual-structural/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2003-double-virtual-structural/run.py b/test/2003-double-virtual-structural/run.py
new file mode 100644
index 0000000..882774b
--- /dev/null
+++ b/test/2003-double-virtual-structural/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2004-double-virtual-structural-abstract/run b/test/2004-double-virtual-structural-abstract/run
deleted file mode 100755
index b59f97c..0000000
--- a/test/2004-double-virtual-structural-abstract/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2004-double-virtual-structural-abstract/run.py b/test/2004-double-virtual-structural-abstract/run.py
new file mode 100644
index 0000000..882774b
--- /dev/null
+++ b/test/2004-double-virtual-structural-abstract/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2005-pause-all-redefine-multithreaded/build.py b/test/2005-pause-all-redefine-multithreaded/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/2005-pause-all-redefine-multithreaded/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/2005-pause-all-redefine-multithreaded/pause-all.cc b/test/2005-pause-all-redefine-multithreaded/pause-all.cc
index 9928411..37d6c4d 100644
--- a/test/2005-pause-all-redefine-multithreaded/pause-all.cc
+++ b/test/2005-pause-all-redefine-multithreaded/pause-all.cc
@@ -41,10 +41,12 @@
                                                     jobjectArray new_fields,
                                                     jstring default_val) {
   std::vector<jthread> threads;
+  threads.reserve(env->GetArrayLength(threads_arr));
   for (jint i = 0; i < env->GetArrayLength(threads_arr); i++) {
     threads.push_back(env->GetObjectArrayElement(threads_arr, i));
   }
   std::vector<jfieldID> fields;
+  fields.reserve(env->GetArrayLength(new_fields));
   for (jint i = 0; i < env->GetArrayLength(new_fields); i++) {
     fields.push_back(env->FromReflectedField(env->GetObjectArrayElement(new_fields, i)));
   }
diff --git a/test/2005-pause-all-redefine-multithreaded/run b/test/2005-pause-all-redefine-multithreaded/run
deleted file mode 100755
index b59f97c..0000000
--- a/test/2005-pause-all-redefine-multithreaded/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2005-pause-all-redefine-multithreaded/run.py b/test/2005-pause-all-redefine-multithreaded/run.py
new file mode 100644
index 0000000..882774b
--- /dev/null
+++ b/test/2005-pause-all-redefine-multithreaded/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2005-pause-all-redefine-multithreaded/test-metadata.json b/test/2005-pause-all-redefine-multithreaded/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/2005-pause-all-redefine-multithreaded/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/2006-virtual-structural-finalizing/run b/test/2006-virtual-structural-finalizing/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/2006-virtual-structural-finalizing/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2006-virtual-structural-finalizing/run.py b/test/2006-virtual-structural-finalizing/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/2006-virtual-structural-finalizing/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2007-virtual-structural-finalizable/run b/test/2007-virtual-structural-finalizable/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/2007-virtual-structural-finalizable/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2007-virtual-structural-finalizable/run.py b/test/2007-virtual-structural-finalizable/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/2007-virtual-structural-finalizable/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2008-redefine-then-old-reflect-field/run b/test/2008-redefine-then-old-reflect-field/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/2008-redefine-then-old-reflect-field/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/2008-redefine-then-old-reflect-field/run.py b/test/2008-redefine-then-old-reflect-field/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/2008-redefine-then-old-reflect-field/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/2009-structural-local-ref/run b/test/2009-structural-local-ref/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/2009-structural-local-ref/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2009-structural-local-ref/run.py b/test/2009-structural-local-ref/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/2009-structural-local-ref/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc b/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc
index 5eaaa05..78bb772 100644
--- a/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc
+++ b/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc
@@ -89,9 +89,8 @@
   ScopedSuspendAll ssa(__FUNCTION__);
   Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(other,
                                                                   /* deopt_all_frames= */ false);
-  MutexLock mu(Thread::Current(), *Locks::thread_suspend_count_lock_);
-  bool updated = other->ModifySuspendCount(Thread::Current(), -1, nullptr, SuspendReason::kInternal);
-  CHECK(updated);
+  bool resumed = art::Runtime::Current()->GetThreadList()->Resume(other, SuspendReason::kInternal);
+  CHECK(resumed);
   instrumented = true;
   return;
 }
diff --git a/test/2012-structural-redefinition-failures-jni-id/run b/test/2012-structural-redefinition-failures-jni-id/run
deleted file mode 100755
index 03e41a5..0000000
--- a/test/2012-structural-redefinition-failures-jni-id/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2012-structural-redefinition-failures-jni-id/run.py b/test/2012-structural-redefinition-failures-jni-id/run.py
new file mode 100644
index 0000000..9ef412d
--- /dev/null
+++ b/test/2012-structural-redefinition-failures-jni-id/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2031-zygote-compiled-frame-deopt/native-wait.cc b/test/2031-zygote-compiled-frame-deopt/native-wait.cc
index bd1d224..fb45345 100644
--- a/test/2031-zygote-compiled-frame-deopt/native-wait.cc
+++ b/test/2031-zygote-compiled-frame-deopt/native-wait.cc
@@ -42,7 +42,7 @@
   }
   runtime->SetAsZygoteChild(/*is_system_server=*/false, /*is_zygote=*/false);
   runtime->AddCompilerOption("--debuggable");
-  runtime->SetJavaDebuggable(true);
+  runtime->SetRuntimeDebugState(Runtime::RuntimeDebugState::kJavaDebuggableAtInit);
   {
     // Deoptimize the boot image as it may be non-debuggable.
     ScopedSuspendAll ssa(__FUNCTION__);
diff --git a/test/2031-zygote-compiled-frame-deopt/run b/test/2031-zygote-compiled-frame-deopt/run
deleted file mode 100755
index 900099f..0000000
--- a/test/2031-zygote-compiled-frame-deopt/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2020 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.
-
-# The -Xopaque-jni-ids makes sure we can do structural redefinition. The --add-libdir-argument tells
-# default-run to pass the directory where the jvmti-agent is so we can load it later. The others
-# set the process to zygote mode and setup the jit cache size. We use a larger than normal jit-size
-# to avoid having to deal with jit-gc, a complication that's not relevant to this test.
-./default-run "$@" --runtime-option -Xopaque-jni-ids:true --add-libdir-argument --runtime-option -Xzygote --runtime-option -Xjitinitialsize:64M
diff --git a/test/2031-zygote-compiled-frame-deopt/run.py b/test/2031-zygote-compiled-frame-deopt/run.py
new file mode 100644
index 0000000..1dedf3b
--- /dev/null
+++ b/test/2031-zygote-compiled-frame-deopt/run.py
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# Copyright 2020 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.
+
+
+def run(ctx, args):
+  # The -Xopaque-jni-ids makes sure we can do structural redefinition. The --add-libdir-argument tells
+  # default-run to pass the directory where the jvmti-agent is so we can load it later. The others
+  # set the process to zygote mode and setup the jit cache size. We use a larger than normal jit-size
+  # to avoid having to deal with jit-gc, a complication that's not relevant to this test.
+  ctx.default_run(
+      args,
+      runtime_option=[
+          "-Xopaque-jni-ids:true", "-Xzygote", "-Xjitinitialsize:64M"
+      ],
+      add_libdir_argument=True)
diff --git a/test/2033-shutdown-mechanics/check b/test/2033-shutdown-mechanics/check
deleted file mode 100755
index 9523159..0000000
--- a/test/2033-shutdown-mechanics/check
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# The daemon thread seems to occasionally get stopped before finishing.
-# Check that the actual output is a line-by-line prefix of expected.
-head -n $(wc -l < $2) $1 | diff --strip-trailing-cr -q - "$2" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4" >/dev/null
diff --git a/test/2033-shutdown-mechanics/run.py b/test/2033-shutdown-mechanics/run.py
new file mode 100644
index 0000000..3dfc965
--- /dev/null
+++ b/test/2033-shutdown-mechanics/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # The daemon thread seems to occasionally get stopped before finishing.
+  # Check that the actual output is a line-by-line prefix of expected.
+  ctx.run(
+      fr"head -n $(wc -l < '{args.stdout_file}') expected-stdout.txt > expected-stdout.txt.tmp &&"
+      fr"mv expected-stdout.txt.tmp expected-stdout.txt")
diff --git a/test/2034-spaces-in-SimpleName/build b/test/2034-spaces-in-SimpleName/build
deleted file mode 100755
index 8261ed2..0000000
--- a/test/2034-spaces-in-SimpleName/build
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# Stop on failure and be verbose.
-set -e -x
-
-export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.2.jar"
-
-# generate Java bytecode with ASM
-cd src_gen
-${JAVA:-java} -cp "$ASM_JAR:." SpacesInSimpleName.java
-mkdir ../classes && mv Main.class ../classes/Main.class
-cd ..
-
-# Use API level 10000 for spaces in SimpleName
-USE_DESUGAR=false ./default-build "$@" --api-level 10000
diff --git a/test/2034-spaces-in-SimpleName/build.py b/test/2034-spaces-in-SimpleName/build.py
new file mode 100644
index 0000000..c392e24
--- /dev/null
+++ b/test/2034-spaces-in-SimpleName/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+# Use API level 10000 for spaces in SimpleName
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build(use_desugar=False, api_level=10000)
diff --git a/test/2034-spaces-in-SimpleName/generate-sources b/test/2034-spaces-in-SimpleName/generate-sources
new file mode 100755
index 0000000..2e921a7
--- /dev/null
+++ b/test/2034-spaces-in-SimpleName/generate-sources
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+# Stop on failure
+set -e
+
+export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.2.jar"
+
+# generate Java bytecode with ASM
+cd src_gen
+${JAVA:-java} -cp "$ASM_JAR:." SpacesInSimpleName.java
+mkdir ../classes && mv Main.class ../classes/Main.class
+cd ..
diff --git a/test/2035-structural-native-method/run b/test/2035-structural-native-method/run
deleted file mode 100755
index ff387ff..0000000
--- a/test/2035-structural-native-method/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2020 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2035-structural-native-method/run.py b/test/2035-structural-native-method/run.py
new file mode 100644
index 0000000..3774a6c
--- /dev/null
+++ b/test/2035-structural-native-method/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2020 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2036-structural-subclass-shadow/run b/test/2036-structural-subclass-shadow/run
deleted file mode 100755
index ff387ff..0000000
--- a/test/2036-structural-subclass-shadow/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2020 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.
-
-./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2036-structural-subclass-shadow/run.py b/test/2036-structural-subclass-shadow/run.py
new file mode 100644
index 0000000..3774a6c
--- /dev/null
+++ b/test/2036-structural-subclass-shadow/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2020 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, runtime_option=["-Xopaque-jni-ids:true"])
diff --git a/test/2038-hiddenapi-jvmti-ext/build b/test/2038-hiddenapi-jvmti-ext/build
deleted file mode 100644
index f4b029f..0000000
--- a/test/2038-hiddenapi-jvmti-ext/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-USE_HIDDENAPI=true ./default-build "$@"
diff --git a/test/2038-hiddenapi-jvmti-ext/build.py b/test/2038-hiddenapi-jvmti-ext/build.py
new file mode 100644
index 0000000..942bb00
--- /dev/null
+++ b/test/2038-hiddenapi-jvmti-ext/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(use_hiddenapi=True)
diff --git a/test/2038-hiddenapi-jvmti-ext/run b/test/2038-hiddenapi-jvmti-ext/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/2038-hiddenapi-jvmti-ext/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/2038-hiddenapi-jvmti-ext/run.py b/test/2038-hiddenapi-jvmti-ext/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/2038-hiddenapi-jvmti-ext/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/2039-load-transform-larger/run b/test/2039-load-transform-larger/run
deleted file mode 100755
index 144c17d..0000000
--- a/test/2039-load-transform-larger/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2021 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.
-
-./default-run "$@" --jvmti --no-app-image
diff --git a/test/2039-load-transform-larger/run.py b/test/2039-load-transform-larger/run.py
new file mode 100644
index 0000000..157a24b
--- /dev/null
+++ b/test/2039-load-transform-larger/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2021 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, app_image=False)
diff --git a/test/2040-huge-native-alloc/build.py b/test/2040-huge-native-alloc/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/2040-huge-native-alloc/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/2040-huge-native-alloc/src/Main.java b/test/2040-huge-native-alloc/src/Main.java
index 59e1266..3c8ae23 100644
--- a/test/2040-huge-native-alloc/src/Main.java
+++ b/test/2040-huge-native-alloc/src/Main.java
@@ -24,7 +24,7 @@
   int allocated = 0;
   int deallocated = 0;
   static Object lock = new Object();
-  final static int MAX_TRIES = 4;
+  final static int MAX_TRIES = 10;
   WeakReference<BufferHolder>[] references = new WeakReference[HOW_MANY_HUGE];
 
   class BufferHolder {
@@ -61,6 +61,14 @@
       if (new Main().tryToRun(i == MAX_TRIES)) {
         break;
       }
+      if (i == MAX_TRIES / 2) {
+        // Maybe some transient CPU load is causing issues here?
+        try {
+          Thread.sleep(3000);
+        } catch (InterruptedException ignored) {
+          System.out.println("Unexpected interrupt");
+        }
+      }
       // Clean up and try again.
       Runtime.getRuntime().gc();
       System.runFinalization();
@@ -84,7 +92,10 @@
 
     if (startingGcNum != getGcNum()) {
       // Happens rarely, fail and retry.
-      return false;
+      if (!lastChance) {
+        return false;
+      }
+      System.out.println("Triggered early GC");
     }
     // One of the notifications should block for GC to catch up.
     long actualTime = timeNotifications();
diff --git a/test/2040-huge-native-alloc/test-metadata.json b/test/2040-huge-native-alloc/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/2040-huge-native-alloc/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/2041-bad-cleaner/build.py b/test/2041-bad-cleaner/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/2041-bad-cleaner/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/2041-bad-cleaner/expected-stdout.txt b/test/2041-bad-cleaner/expected-stdout.txt
index 848a352..db36097 100644
--- a/test/2041-bad-cleaner/expected-stdout.txt
+++ b/test/2041-bad-cleaner/expected-stdout.txt
@@ -2,4 +2,3 @@
 Cleaner started and sleeping briefly...
 Cleaner done snoozing.
 Cleaner sleeping forever now.
-exit status: 2
diff --git a/test/2041-bad-cleaner/run b/test/2041-bad-cleaner/run
deleted file mode 100755
index 54747ee..0000000
--- a/test/2041-bad-cleaner/run
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# The test logs error messages which is expected, discard them.
-export ANDROID_LOG_TAGS='*:f'
-
-# Squash the exit status and put it in expected
-./default-run --external-log-tags "${@}"
-echo "exit status:" $?
diff --git a/test/2041-bad-cleaner/run.py b/test/2041-bad-cleaner/run.py
new file mode 100644
index 0000000..74fcbb9
--- /dev/null
+++ b/test/2041-bad-cleaner/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, android_log_tags="*:f", expected_exit_code=2)
diff --git a/test/2041-bad-cleaner/test-metadata.json b/test/2041-bad-cleaner/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/2041-bad-cleaner/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/2042-checker-dce-always-throw/src/Main.java b/test/2042-checker-dce-always-throw/src/Main.java
index 99738a7..4df4e63 100644
--- a/test/2042-checker-dce-always-throw/src/Main.java
+++ b/test/2042-checker-dce-always-throw/src/Main.java
@@ -20,12 +20,31 @@
     assertEquals(0, $noinline$testSimplifyThrow(1));
 
     // Basic test for non-trivial blocks (i.e. not just an invoke and a Goto)
+    assertEquals(0, $noinline$testSimplifyThrowAndPrint(1));
     assertEquals(0, $noinline$testSimplifyTwoThrows(1));
+    assertEquals(0, $noinline$testSimplifyWithArgument(1));
 
     // Try catch tests
     assertEquals(0, $noinline$testDoNotSimplifyInTry(1));
     assertEquals(0, $noinline$testSimplifyInCatch(1));
-    assertEquals(0, $noinline$testDoNotSimplifyInCatchInOuterTry(1));
+    assertEquals(0, $noinline$testDoNotSimplifyInCatchInOuterTry(1, 1));
+
+    // Test that we update the phis correctly after simplifying an always throwing method, and
+    // recomputing dominance.
+    assertEquals(0, $noinline$testUpdatePhisCorrectly(1));
+    assertEquals(0, $noinline$testDeleteAllUsesBeforeDeletingInstruction(1));
+
+    // SimplifyAlwaysThrows for blocks without a goto at the end
+    assertEquals(0, $noinline$testEndsWithIf(1, 1));
+    assertEquals(0, $noinline$testEndsWithReturn(1));
+    // Since we cannot use `assertEquals`, not throwing would be the success.
+    $noinline$testEndsWithReturnVoid(1);
+    assertEquals(0, $noinline$testEndsWithSwitch(1, 1));
+    assertEquals(0, $noinline$testEndsWithThrow(1));
+    assertEquals(0, $noinline$testEndsWithTryBoundary(1));
+
+    // SimplifyAlwaysThrows for invokes in catch blocks
+    assertEquals(0, $noinline$testInsideCatch(1));
   }
 
   private static void alwaysThrows() throws Error {
@@ -51,6 +70,28 @@
     return 0;
   }
 
+  /// CHECK-START: int Main.$noinline$testSimplifyThrowAndPrint(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
+  /// CHECK-EVAL:  "<<ExitBlock>>" != "<<TargetBlock>>"
+
+  /// CHECK-START: int Main.$noinline$testSimplifyThrowAndPrint(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testSimplifyThrowAndPrint(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testSimplifyThrowAndPrint(int num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+    }
+    return 0;
+  }
+
   /// CHECK-START: int Main.$noinline$testSimplifyTwoThrows(int) dead_code_elimination$after_inlining (before)
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock>> method_name:Main.alwaysThrows always_throws:true
@@ -60,10 +101,14 @@
 
   /// CHECK-START: int Main.$noinline$testSimplifyTwoThrows(int) dead_code_elimination$after_inlining (after)
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
-  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock>> method_name:Main.alwaysThrows always_throws:true
   /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
   /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
 
+  // Check that the second `alwaysThrows` gets removed.
+  /// CHECK-START: int Main.$noinline$testSimplifyTwoThrows(int) dead_code_elimination$after_inlining (after)
+  /// CHECK:       InvokeStaticOrDirect method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-NOT:   InvokeStaticOrDirect method_name:Main.alwaysThrows always_throws:true
+
   // Tests that we simplify the always throwing branch directly to the exit, even with blocks that
   // are not just the throwing instruction and a Goto.
   private static int $noinline$testSimplifyTwoThrows(int num) {
@@ -74,6 +119,35 @@
     return 0;
   }
 
+  private static int throwIfZero(int num) {
+    if (num == 0) {
+      throw new Error("num is 0!");
+    }
+    return num / num;
+  }
+
+  /// CHECK-START: int Main.$noinline$testSimplifyWithArgument(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.throwIfZero always_throws:true
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
+  /// CHECK-EVAL:  "<<ExitBlock>>" != "<<TargetBlock>>"
+
+  /// CHECK-START: int Main.$noinline$testSimplifyWithArgument(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.throwIfZero always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testSimplifyWithArgument(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testSimplifyWithArgument(int num) {
+    if (num == 0) {
+      throwIfZero(0);
+      System.out.println("I am unrechable!");
+    }
+    return 0;
+  }
+
   /// CHECK-START: int Main.$noinline$testSimplifyThrowWithTryCatch(int) dead_code_elimination$after_inlining (before)
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
   /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
@@ -140,7 +214,7 @@
   /// CHECK:       TryBoundary kind:entry
 
   // Consistency check to that we do not simplify it by the last DCE pass either
-  /// CHECK-START: int Main.$noinline$testDoNotSimplifyInTry(int) dead_code_elimination$final (after)
+  /// CHECK-START: int Main.$noinline$testDoNotSimplifyInTry(int) dead_code_elimination$before_codegen (after)
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
   /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
   /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
@@ -190,25 +264,25 @@
     }
   }
 
-  /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int, int) dead_code_elimination$after_inlining (before)
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
   /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
   /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
   /// CHECK-EVAL:  "<<ExitBlock>>" != "<<TargetBlock>>"
 
-  /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int, int) dead_code_elimination$after_inlining (after)
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
   /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
   /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
   /// CHECK-EVAL:  "<<ExitBlock>>" != "<<TargetBlock>>"
 
   // Consistency check to make sure we have the try catches in the graph at this stage.
-  /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int, int) dead_code_elimination$after_inlining (before)
   /// CHECK-DAG:   TryBoundary kind:entry
   /// CHECK-DAG:   TryBoundary kind:entry
 
   // Consistency check to that we do not simplify it by the last DCE pass either
-  /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$final (after)
+  /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int, int) dead_code_elimination$before_codegen (after)
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
   /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
   /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
@@ -217,13 +291,15 @@
   // Similar to testSimplifyInCatch, but now the throw is in an outer try and we shouldn't simplify
   // it. Like in testDoNotSimplifyInTry, we need the help of the inliner to have an invoke followed
   // by a Goto.
-  private static int $noinline$testDoNotSimplifyInCatchInOuterTry(int num) {
+  private static int $noinline$testDoNotSimplifyInCatchInOuterTry(int num, int other_num) {
     try {
       try {
         throw new Error();
       } catch (Error e) {
         if (num == 0) {
-          $inline$testDoNotSimplifyInner(num);
+          // We use `other_num` here because otherwise we propagate the knowledge that `num` equals
+          // zero.
+          $inline$testDoNotSimplifyInner(other_num);
         }
         return 0;
       }
@@ -232,6 +308,254 @@
     }
   }
 
+  // Check that when we perform SimplifyAlwaysThrows, that the phi for `phi_value` exists, and that
+  // we correctly update it after running DCE.
+
+  /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   <<Const0:i\d+>> IntConstant 0
+  /// CHECK-DAG:   <<Const5:i\d+>> IntConstant 5
+  /// CHECK-DAG:   <<ReturnValue:i\d+>> Phi [<<Const0>>,<<Const5>>]
+  /// CHECK-DAG:   Return [<<ReturnValue>>]
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
+  /// CHECK-EVAL:  "<<ExitBlock>>" != "<<TargetBlock>>"
+
+  /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   <<Const0:i\d+>> IntConstant 0
+  /// CHECK-DAG:   Return [<<Const0>>]
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   Phi
+  private static int $noinline$testUpdatePhisCorrectly(int num) {
+    int phi_value = 0;
+    if (num == 0) {
+      alwaysThrows();
+      phi_value = 5;
+    }
+    return phi_value;
+  }
+
+  // Test to check that we delete all uses before the instruction.
+  private static int $noinline$foo(int num) {
+    return num;
+  }
+
+  /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   <<Const0:i\d+>> IntConstant 0
+  /// CHECK-DAG:   <<Invoke:i\d+>> InvokeStaticOrDirect method_name:Main.$noinline$foo
+  /// CHECK-DAG:   <<ReturnValue:i\d+>> Phi [<<Const0>>,<<Invoke>>]
+  /// CHECK-DAG:   Return [<<ReturnValue>>]
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   TryBoundary block:<<InvokeBlock>>
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   <<Const0:i\d+>> IntConstant 0
+  /// CHECK-DAG:   Return [<<Const0>>]
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   Phi
+  private static int $noinline$testDeleteAllUsesBeforeDeletingInstruction(int num) {
+    int phi_value = 0;
+    if (num == 0) {
+      alwaysThrows();
+      try {
+        phi_value = $noinline$foo(2);
+      } catch (Error e) {
+        throw new Error("We shouldn't hit this");
+      }
+    }
+    return phi_value;
+  }
+
+  /// CHECK-START: int Main.$noinline$testEndsWithIf(int, int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   If block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithIf(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithIf(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testEndsWithIf(int num, int other_num) {
+    if (num == 0) {
+      alwaysThrows();
+      if (other_num == 0) {
+        System.out.println("I am unrechable!");
+      }
+    }
+    return 0;
+  }
+
+  /// CHECK-START: int Main.$noinline$testEndsWithReturn(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Return block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithReturn(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithReturn(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testEndsWithReturn(int num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+      return 1;
+    }
+    return 0;
+  }
+
+  /// CHECK-START: void Main.$noinline$testEndsWithReturnVoid(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   ReturnVoid block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: void Main.$noinline$testEndsWithReturnVoid(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: void Main.$noinline$testEndsWithReturnVoid(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static void $noinline$testEndsWithReturnVoid(int num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+      return;
+    }
+    return;
+  }
+
+  /// CHECK-START: int Main.$noinline$testEndsWithSwitch(int, int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   PackedSwitch block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithSwitch(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithSwitch(int, int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testEndsWithSwitch(int num, int other_num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+      int result = 10;
+      switch (other_num) {
+        case 1:
+          result = 100;
+          break;
+        case 2:
+          result = 300;
+          break;
+        case 3:
+          result = 500;
+          break;
+        case 4:
+          result = 700;
+          break;
+      }
+      return result;
+    }
+    return 0;
+  }
+
+  /// CHECK-START: int Main.$noinline$testEndsWithThrow(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Throw block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithThrow(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithThrow(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testEndsWithThrow(int num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+      throw new Error("Other error");
+    }
+    return 0;
+  }
+
+  /// CHECK-START: int Main.$noinline$testEndsWithTryBoundary(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   TryBoundary block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithTryBoundary(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testEndsWithTryBoundary(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testEndsWithTryBoundary(int num) {
+    if (num == 0) {
+      alwaysThrows();
+      System.out.println("I am unrechable!");
+      try {
+        alwaysThrows();
+      } catch (Error e) {
+        return 1;
+      }
+    }
+    return 0;
+  }
+
+  // Empty method to forbid the try from disappearing
+  private static void $noinline$emptyMethod() {}
+
+  /// CHECK-START: int Main.$noinline$testInsideCatch(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Return block:<<InvokeBlock>>
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+
+  /// CHECK-START: int Main.$noinline$testInsideCatch(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testInsideCatch(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testInsideCatch(int num) {
+    if (num == 0) {
+      try {
+        $noinline$emptyMethod();
+      } catch (Error e) {
+        alwaysThrows();
+        System.out.println("I am unrechable!");
+        return 1;
+      }
+    }
+    return 0;
+  }
+
   static void assertEquals(int expected, int actual) {
     if (expected != actual) {
       throw new AssertionError("Expected " + expected + " got " + actual);
diff --git a/test/2042-reference-processing/Android.bp b/test/2042-reference-processing/Android.bp
new file mode 100644
index 0000000..a73b8d0
--- /dev/null
+++ b/test/2042-reference-processing/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2042-reference-processing`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2042-reference-processing",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2042-reference-processing-expected-stdout",
+        ":art-run-test-2042-reference-processing-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2042-reference-processing-expected-stdout",
+    out: ["art-run-test-2042-reference-processing-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2042-reference-processing-expected-stderr",
+    out: ["art-run-test-2042-reference-processing-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2042-reference-processing/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2042-reference-processing/expected-stderr.txt
diff --git a/test/2042-reference-processing/expected-stdout.txt b/test/2042-reference-processing/expected-stdout.txt
new file mode 100644
index 0000000..8b3e832
--- /dev/null
+++ b/test/2042-reference-processing/expected-stdout.txt
@@ -0,0 +1,2 @@
+Starting
+Finished
diff --git a/test/2042-reference-processing/info.txt b/test/2042-reference-processing/info.txt
new file mode 100644
index 0000000..e2d7a99
--- /dev/null
+++ b/test/2042-reference-processing/info.txt
@@ -0,0 +1,6 @@
+A test for reference processing correctness.
+
+The emphasis here is on fundamental properties. In particular, references to
+unreachable referents should be enqueued, and this should ensure that uncleared
+References don't point to objects for which References were enqueued. We also
+check various other ordering properties for java.lang.ref.References.
diff --git a/test/2042-reference-processing/src/Main.java b/test/2042-reference-processing/src/Main.java
new file mode 100644
index 0000000..ed67052
--- /dev/null
+++ b/test/2042-reference-processing/src/Main.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.math.BigInteger;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+/**
+ * Test that objects get finalized and their references cleared in the right order.
+ *
+ * We maintain a list of nominally MAX_LIVE_OBJS numbered finalizable objects.
+ * We then alternately drop the last 50, and add 50 more. When we see an object finalized
+ * or its reference cleared, we make sure that the preceding objects in its group of 50
+ * have also had their references cleared. We also perform a number of other more
+ * straightforward checks, such as ensuring that all references are eventually cleared,
+ * and all objects are finalized.
+ */
+public class Main {
+    // TODO(b/216481630) Enable CHECK_PHANTOM_REFS. This currently occasionally reports a few
+    // PhantomReferences as not enqueued. If this report is correct, this needs to be tracked
+    // down and fixed.
+    static final boolean CHECK_PHANTOM_REFS = false;
+
+    static final int MAX_LIVE_OBJS = 150;
+    static final int DROP_OBJS = 50;  // Number of linked objects dropped in each batch.
+    static final int MIN_LIVE_OBJS = MAX_LIVE_OBJS - DROP_OBJS;
+    static final int TOTAL_OBJS = 200_000;  // Allocate this many finalizable objects in total.
+    static final boolean REPORT_DROPS = false;
+    static volatile boolean pleaseStop;
+
+    AtomicInteger totalFinalized = new AtomicInteger(0);
+    Object phantomRefsLock = new Object();
+    int maxDropped = 0;
+    int liveObjects = 0;
+
+    // Number of next finalizable object to be allocated.
+    int nextAllocated = 0;
+
+    // List of finalizable objects in descending order. We add to the front and drop
+    // from the rear.
+    FinalizableObject listHead;
+
+    // A possibly incomplete list of FinalizableObject indices that were finalized, but
+    // have yet to be checked for consistency with reference processing.
+    ArrayBlockingQueue<Integer> finalized = new ArrayBlockingQueue<>(20_000);
+
+    // Maps from object number to Reference; Cleared references are deleted when queues are
+    // processed.
+    TreeMap<Integer, MyWeakReference> weakRefs = new TreeMap<>();
+    HashMap<Integer, MyPhantomReference> phantomRefs = new HashMap<>();
+
+    class FinalizableObject {
+        int n;
+        FinalizableObject next;
+        FinalizableObject(int num, FinalizableObject nextObj) {
+            n = num;
+            next = nextObj;
+        }
+        protected void finalize() {
+            if (!inPhantomRefs(n)) {
+                System.out.println("PhantomRef enqueued before finalizer ran");
+            }
+            totalFinalized.incrementAndGet();
+            if (!finalized.offer(n) && REPORT_DROPS) {
+                System.out.println("Dropped finalization of " + n);
+            }
+        }
+    }
+    ReferenceQueue<FinalizableObject> refQueue = new ReferenceQueue<>();
+    class MyWeakReference extends WeakReference<FinalizableObject> {
+        int n;
+        MyWeakReference(FinalizableObject obj) {
+            super(obj, refQueue);
+            n = obj.n;
+        }
+    };
+    class MyPhantomReference extends PhantomReference<FinalizableObject> {
+        int n;
+        MyPhantomReference(FinalizableObject obj) {
+            super(obj, refQueue);
+            n = obj.n;
+        }
+    }
+    boolean inPhantomRefs(int n) {
+        synchronized(phantomRefsLock) {
+            MyPhantomReference ref = phantomRefs.get(n);
+            if (ref == null) {
+                return false;
+            }
+            if (ref.n != n) {
+                System.out.println("phantomRef retrieval failed");
+            }
+            return true;
+        }
+    }
+
+    void CheckOKToClearWeak(int num) {
+        if (num > maxDropped) {
+            System.out.println("WeakRef to live object " + num + " was cleared/enqueued.");
+        }
+        int batchEnd = (num / DROP_OBJS + 1) * DROP_OBJS;
+        for (MyWeakReference wr : weakRefs.subMap(num + 1, batchEnd).values()) {
+            if (wr.n <= num || wr.n / DROP_OBJS != num / DROP_OBJS) {
+                throw new AssertionError("MyWeakReference logic error!");
+            }
+            // wr referent was dropped in same batch and precedes it in list.
+            if (wr.get() != null) {
+                // This violates the WeakReference spec, and can result in strong references
+                // to objects that have been cleaned.
+                System.out.println("WeakReference to " + wr.n
+                    + " was erroneously cleared after " + num);
+            }
+        }
+    }
+
+    void CheckOKToClearPhantom(int num) {
+        if (num > maxDropped) {
+            System.out.println("PhantomRef to live object " + num + " was enqueued.");
+        }
+        MyWeakReference wr = weakRefs.get(num);
+        if (wr != null && wr.get() != null) {
+            System.out.println("PhantomRef cleared before WeakRef for " + num);
+        }
+    }
+
+    void emptyAndCheckQueues() {
+        // Check recently finalized objects for consistency with cleared references.
+        while (true) {
+            Integer num = finalized.poll();
+            if (num == null) {
+                break;
+            }
+            MyWeakReference wr = weakRefs.get(num);
+            if (wr != null) {
+                if (wr.n != num) {
+                    System.out.println("Finalization logic error!");
+                }
+                if (wr.get() != null) {
+                    System.out.println("Finalizing object with uncleared reference");
+                }
+            }
+            CheckOKToClearWeak(num);
+        }
+        // Check recently enqueued references for consistency.
+        while (true) {
+            Reference<FinalizableObject> ref = (Reference<FinalizableObject>) refQueue.poll();
+            if (ref == null) {
+                break;
+            }
+            if (ref instanceof MyWeakReference) {
+                MyWeakReference wr = (MyWeakReference) ref;
+                if (wr.get() != null) {
+                    System.out.println("WeakRef " + wr.n + " enqueued but not cleared");
+                }
+                CheckOKToClearWeak(wr.n);
+                if (weakRefs.remove(Integer.valueOf(wr.n)) != ref) {
+                    System.out.println("Missing WeakReference: " + wr.n);
+                }
+            } else if (ref instanceof MyPhantomReference) {
+                MyPhantomReference pr = (MyPhantomReference) ref;
+                CheckOKToClearPhantom(pr.n);
+                if (phantomRefs.remove(Integer.valueOf(pr.n)) != ref) {
+                    System.out.println("Missing PhantomReference: " + pr.n);
+                }
+            } else {
+                System.out.println("Found unrecognized reference in queue");
+            }
+        }
+    }
+
+
+    /**
+     * Add n objects to the head of the list. These will be assigned the next n consecutive
+     * numbers after the current head of the list.
+     */
+    void addObjects(int n) {
+        for (int i = 0; i < n; ++i) {
+            int me = nextAllocated++;
+            listHead = new FinalizableObject(me, listHead);
+            weakRefs.put(me, new MyWeakReference(listHead));
+            synchronized(phantomRefsLock) {
+                phantomRefs.put(me, new MyPhantomReference(listHead));
+            }
+        }
+        liveObjects += n;
+    }
+
+    /**
+     * Drop n finalizable objects from the tail of the list. These are the lowest-numbered objects
+     * in the list.
+     */
+    void dropObjects(int n) {
+        FinalizableObject list = listHead;
+        FinalizableObject last = null;
+        if (n > liveObjects) {
+            System.out.println("Removing too many elements");
+        }
+        if (liveObjects == n) {
+            maxDropped = list.n;
+            listHead = null;
+        } else {
+            final int skip = liveObjects - n;
+            for (int i = 0; i < skip; ++i) {
+                last = list;
+                list = list.next;
+            }
+            int expected = nextAllocated - skip - 1;
+            if (list.n != expected) {
+                System.out.println("dropObjects found " + list.n + " but expected " + expected);
+            }
+            maxDropped = expected;
+            last.next = null;
+        }
+        liveObjects -= n;
+    }
+
+    void testLoop() {
+        System.out.println("Starting");
+        addObjects(MIN_LIVE_OBJS);
+        final int ITERS = (TOTAL_OBJS - MIN_LIVE_OBJS) / DROP_OBJS;
+        for (int i = 0; i < ITERS; ++i) {
+            addObjects(DROP_OBJS);
+            if (liveObjects != MAX_LIVE_OBJS) {
+                System.out.println("Unexpected live object count");
+            }
+            dropObjects(DROP_OBJS);
+            emptyAndCheckQueues();
+        }
+        dropObjects(MIN_LIVE_OBJS);
+        if (liveObjects != 0 || listHead != null) {
+            System.out.println("Unexpected live objecs at end");
+        }
+        if (maxDropped != TOTAL_OBJS - 1) {
+            System.out.println("Unexpected dropped object count: " + maxDropped);
+        }
+        for (int i = 0; i < 2; ++i) {
+            Runtime.getRuntime().gc();
+            System.runFinalization();
+            emptyAndCheckQueues();
+        }
+        if (!weakRefs.isEmpty()) {
+            System.out.println("Weak Reference map nonempty size = " + weakRefs.size());
+        }
+        if (CHECK_PHANTOM_REFS && !phantomRefs.isEmpty()) {
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                System.out.println("Unexpected interrupt");
+            }
+            if (!phantomRefs.isEmpty()) {
+                System.out.println("Phantom Reference map nonempty size = " + phantomRefs.size());
+                System.out.print("First elements:");
+                int i = 0;
+                for (MyPhantomReference pr : phantomRefs.values()) {
+                    System.out.print(" " + pr.n);
+                    if (++i > 10) {
+                        break;
+                    }
+                }
+                System.out.println("");
+            }
+        }
+        if (totalFinalized.get() != TOTAL_OBJS) {
+            System.out.println("Finalized only " + totalFinalized + " objects");
+        }
+    }
+
+    static Runnable causeGCs = new Runnable() {
+        public void run() {
+            // Allocate a lot.
+            BigInteger counter = BigInteger.ZERO;
+            while (!pleaseStop) {
+                counter = counter.add(BigInteger.TEN);
+            }
+            // Look at counter to reduce chance of optimizing out the allocation.
+            if (counter.longValue() % 10 != 0) {
+                 System.out.println("Bad causeGCs counter value: " + counter);
+            }
+        }
+    };
+
+    public static void main(String[] args) throws Exception {
+        Main theTest = new Main();
+        Thread gcThread = new Thread(causeGCs);
+        gcThread.setDaemon(true);  // Terminate if main thread dies.
+        gcThread.start();
+        theTest.testLoop();
+        pleaseStop = true;
+        gcThread.join();
+        System.out.println("Finished");
+    }
+}
diff --git a/test/2043-reference-pauses/Android.bp b/test/2043-reference-pauses/Android.bp
new file mode 100644
index 0000000..a84aea2
--- /dev/null
+++ b/test/2043-reference-pauses/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2043-reference-pauses`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2043-reference-pauses",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2043-reference-pauses-expected-stdout",
+        ":art-run-test-2043-reference-pauses-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2043-reference-pauses-expected-stdout",
+    out: ["art-run-test-2043-reference-pauses-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2043-reference-pauses-expected-stderr",
+    out: ["art-run-test-2043-reference-pauses-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2043-reference-pauses/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2043-reference-pauses/expected-stderr.txt
diff --git a/test/2043-reference-pauses/expected-stdout.txt b/test/2043-reference-pauses/expected-stdout.txt
new file mode 100644
index 0000000..8b3e832
--- /dev/null
+++ b/test/2043-reference-pauses/expected-stdout.txt
@@ -0,0 +1,2 @@
+Starting
+Finished
diff --git a/test/2043-reference-pauses/info.txt b/test/2043-reference-pauses/info.txt
new file mode 100644
index 0000000..f76fa32
--- /dev/null
+++ b/test/2043-reference-pauses/info.txt
@@ -0,0 +1,5 @@
+Tests WeakReference processing and retention of objects needed by finalizers.
+
+Can be used as Reference.get() blocking benchmark by setting PRINT_TIMES to
+true. This will print maximum observed latencies for Reference.get() when
+significant memory is only reachable from SoftReferences and Finalizers.
diff --git a/test/2043-reference-pauses/src/Main.java b/test/2043-reference-pauses/src/Main.java
new file mode 100644
index 0000000..e390155
--- /dev/null
+++ b/test/2043-reference-pauses/src/Main.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.ref.SoftReference;
+import java.math.BigInteger;
+import java.util.ArrayList;
+
+/**
+ * Basic test of WeakReferences with large amounts of memory that's only reachable through
+ * finalizers. Also makes sure that finalizer-reachable data is not collected.
+ * Can easily be modified to time Reference.get() blocking.
+ */
+public class Main {
+    static final boolean PRINT_TIMES = false;  // true will cause benchmark failure.
+    // Data structures repeatedly allocated in background to trigger GC.
+    // Size of finalizer reachable trees.
+    static final int TREE_HEIGHT = 15;  // Trees contain 2^TREE_HEIGHT -1 allocated objects.
+    // Number of finalizable tree-owning objects that exist at one point.
+    static final int N_RESURRECTING_OBJECTS = 10;
+    // Number of short-lived, not finalizer-reachable, objects allocated between trees.
+    static final int N_PLAIN_OBJECTS = 20_000;
+    // Number of SoftReferences to CBTs we allocate.
+    static final int N_SOFTREFS = 10;
+
+    static final boolean BACKGROUND_GC_THREAD = true;
+    static final int NBATCHES = 10;
+    static final int NREFS = PRINT_TIMES ? 1_000_000 : 300_000;  // Multiple of NBATCHES.
+    static final int REFS_PER_BATCH = NREFS / NBATCHES;
+
+    static volatile boolean pleaseStop = false;
+
+    // Large array of WeakReferences filled and accessed by tests below.
+    ArrayList<WeakReference<Integer>> weakRefs = new ArrayList<>(NREFS);
+
+    /**
+     * Complete binary tree data structure. make(n) takes O(2^n) space.
+     */
+    static class CBT {
+        CBT left;
+        CBT right;
+        CBT(CBT l, CBT r) {
+            left = l;
+            right = r;
+        }
+        static CBT make(int n) {
+            if (n == 0) {
+                return null;
+            }
+            return new CBT(make(n - 1), make(n - 1));
+        }
+        /**
+         * Check that path described by bit-vector path has the correct length.
+         */
+        void check(int n, int path) {
+            CBT current = this;
+            for (int i = 0; i < n; i++, path = path >>> 1) {
+                // Unexpectedly short paths result in NPE.
+                if ((path & 1) == 0) {
+                    current = current.left;
+                } else {
+                    current = current.right;
+                }
+            }
+            if (current != null) {
+                System.out.println("Complete binary tree path too long");
+            }
+        }
+    }
+
+
+    /**
+     * A finalizable object that refers to O(2^TREE_HEIGHT) otherwise unreachable memory.
+     * When finalized, it creates a new identical object, making sure that one always stays
+     * around.
+     */
+    static class ResurrectingObject {
+        CBT stuff;
+        ResurrectingObject() {
+            stuff = CBT.make(TREE_HEIGHT);
+        }
+        static ResurrectingObject a[] = new ResurrectingObject[2];
+        static int i = 0;
+        static synchronized void allocOne() {
+            a[(++i) % 2] = new ResurrectingObject();
+            // Check the previous one to make it hard to optimize anything out.
+            if (i > 1) {
+                a[(i + 1) % 2].stuff.check(TREE_HEIGHT, i /* weirdly interpreted as path */);
+            }
+        }
+        protected void finalize() {
+            stuff.check(TREE_HEIGHT, 42 /* Some path descriptor */);
+            // Allocate a new one to replace this one.
+            allocOne();
+        }
+    }
+
+    void fillWeakRefs() {
+        for (int i = 0; i < NREFS; ++i) {
+             weakRefs.add(null);
+        }
+    }
+
+    /*
+     * Return maximum observed time in nanos to dereference a WeakReference to an unreachable
+     * object. weakRefs is presumed to be pre-filled to have the correct size.
+     */
+    long timeUnreachableInner() {
+        long maxNanos = 0;
+        // Fill weakRefs with WeakReferences to unreachable integers, a batch at a time.
+        // Then time and test .get() calls on carefully sampled array entries, some of which
+        // will have been cleared.
+        for (int i = 0; i < NBATCHES; ++i) {
+            for (int j = 0; j < REFS_PER_BATCH; ++j) {
+                weakRefs.set(i * REFS_PER_BATCH + j,
+                        new WeakReference(new Integer(i * REFS_PER_BATCH + j)));
+            }
+            try {
+                Thread.sleep(50);
+            } catch (InterruptedException e) {
+                System.out.println("Unexpected exception");
+            }
+            // Iterate over the filled-in section of weakRefs, but look only at a subset of the
+            // elements, making sure the subsets for different top-level iterations are disjoint.
+            // Otherwise the get() calls here will extend the lifetimes of the referents, and we
+            // may never see any cleared WeakReferences.
+            for (int j = (i + 1) * REFS_PER_BATCH - i - 1; j >= 0; j -= NBATCHES) {
+                WeakReference<Integer> wr = weakRefs.get(j);
+                if (wr != null) {
+                    long startNanos = System.nanoTime();
+                    Integer referent = wr.get();
+                    long totalNanos = System.nanoTime() - startNanos;
+                    if (referent == null) {
+                        // Optimization to reduce max space use and scanning time.
+                        weakRefs.set(j, null);
+                    }
+                    maxNanos = Math.max(maxNanos, totalNanos);
+                    if (referent != null && referent.intValue() != j) {
+                        System.out.println("Unexpected referent; expected " + j + " got "
+                                + referent.intValue());
+                    }
+                }
+            }
+        }
+        return maxNanos;
+    }
+
+    /*
+     * Wrapper for the above that also checks that references were reclaimed.
+     * We do this separately to make sure any stack references from the core of the
+     * test are gone. Empirically, we otherwise sometimes see the zeroth WeakReference
+     * not reclaimed.
+     */
+    long timeUnreachable() {
+        long maxNanos = timeUnreachableInner();
+        Runtime.getRuntime().gc();
+        System.runFinalization();  // Presumed to wait for reference clearing.
+        for (int i = 0; i < NREFS; ++i) {
+            if (weakRefs.get(i) != null && weakRefs.get(i).get() != null) {
+                System.out.println("WeakReference to " + i + " wasn't cleared");
+            }
+        }
+        return maxNanos;
+    }
+
+    /**
+     * Return maximum observed time in nanos to dereference a WeakReference to a reachable
+     * object. Overwrites weakRefs, which is presumed to have NREFS entries already.
+    */
+    long timeReachable() {
+        long maxNanos = 0;
+        // Similar to the above, but we use WeakReferences to otherwise reachable objects,
+        // which should thus not get cleared.
+        Integer[] strongRefs = new Integer[NREFS];
+        for (int i = 0; i < NBATCHES; ++i) {
+            for (int j = i * REFS_PER_BATCH; j < (i + 1) * REFS_PER_BATCH; ++j) {
+                Integer newObj = new Integer(j);
+                strongRefs[j] = newObj;
+                weakRefs.set(j, new WeakReference(newObj));
+            }
+            for (int j = (i + 1) * REFS_PER_BATCH - 1; j >= 0; --j) {
+                WeakReference<Integer> wr = weakRefs.get(j);
+                long startNanos = System.nanoTime();
+                Integer referent = wr.get();
+                long totalNanos = System.nanoTime() - startNanos;
+                maxNanos = Math.max(maxNanos, totalNanos);
+                if (referent == null) {
+                    System.out.println("Unexpectedly cleared referent at " + j);
+                } else if (referent.intValue() != j) {
+                    System.out.println("Unexpected reachable referent; expected " + j + " got "
+                            + referent.intValue());
+                }
+            }
+        }
+        Reference.reachabilityFence(strongRefs);
+        return maxNanos;
+    }
+
+    void runTest() {
+        System.out.println("Starting");
+        fillWeakRefs();
+        long unreachableNanos = timeUnreachable();
+        if (PRINT_TIMES) {
+            System.out.println("Finished timeUnrechable()");
+        }
+        long reachableNanos = timeReachable();
+        String unreachableMillis =
+                String. format("%,.3f", ((double) unreachableNanos) / 1_000_000);
+        String reachableMillis =
+                String. format("%,.3f", ((double) reachableNanos) / 1_000_000);
+        if (PRINT_TIMES) {
+            System.out.println(
+                    "Max time for WeakReference.get (unreachable): " + unreachableMillis);
+            System.out.println(
+                    "Max time for WeakReference.get (reachable): " + reachableMillis);
+        }
+        // Only report extremely egregious pauses to avoid spurious failures.
+        if (unreachableNanos > 10_000_000_000L) {
+            System.out.println("WeakReference.get (unreachable) time unreasonably long");
+        }
+        if (reachableNanos > 10_000_000_000L) {
+            System.out.println("WeakReference.get (reachable) time unreasonably long");
+        }
+    }
+
+    /**
+     * Allocate and GC a lot, while keeping significant amounts of finalizer and
+     * SoftReference-reachable memory around.
+     */
+    static Runnable allocFinalizable = new Runnable() {
+        public void run() {
+            // Allocate and drop some finalizable objects that take a long time
+            // to mark. Designed to be hard to optimize away. Each of these objects will
+            // build a new one in its finalizer before really going away.
+            ArrayList<SoftReference<CBT>> softRefs = new ArrayList<>(N_SOFTREFS);
+            for (int i = 0; i < N_SOFTREFS; ++i) {
+                // These should not normally get reclaimed, since we shouldn't run out of
+                // memory. They do increase tracing time.
+                softRefs.add(new SoftReference(CBT.make(TREE_HEIGHT)));
+            }
+            for (int i = 0; i < N_RESURRECTING_OBJECTS; ++i) {
+                ResurrectingObject.allocOne();
+            }
+            BigInteger counter = BigInteger.ZERO;
+            for (int i = 1; !pleaseStop; ++i) {
+                // Allocate a lot of short-lived objects, using BigIntegers to minimize the chance
+                // of the allocation getting optimized out. This makes things slightly more
+                // realistic, since not all objects will be finalizer reachable.
+                for (int j = 0; j < N_PLAIN_OBJECTS / 2; ++j) {
+                    counter = counter.add(BigInteger.TEN);
+                }
+                // Look at counter to reduce chance of optimizing out the allocation.
+                if (counter.longValue() % 10 != 0) {
+                    System.out.println("Bad allocFinalizable counter value: " + counter);
+                }
+                // Explicitly collect here, mostly to prevent heap growth. Otherwise we get
+                // ahead of the GC and eventually block on it.
+                Runtime.getRuntime().gc();
+                if (PRINT_TIMES && i % 100 == 0) {
+                    System.out.println("Collected " + i + " times");
+                }
+            }
+            // To be safe, access softRefs.
+            final CBT sample = softRefs.get(N_SOFTREFS / 2).get();
+            if (sample != null) {
+              sample.check(TREE_HEIGHT, 47 /* some path descriptor */);
+            }
+        }
+    };
+
+    public static void main(String[] args) throws Exception {
+        Main theTest = new Main();
+        Thread allocThread = null;
+        if (BACKGROUND_GC_THREAD) {
+            allocThread = new Thread(allocFinalizable);
+            allocThread.setDaemon(true);  // Terminate if main thread dies.
+            allocThread.start();
+        }
+        theTest.runTest();
+        if (BACKGROUND_GC_THREAD) {
+            pleaseStop = true;
+            allocThread.join();
+        }
+        System.out.println("Finished");
+    }
+}
diff --git a/test/2044-get-stack-traces/Android.bp b/test/2044-get-stack-traces/Android.bp
new file mode 100644
index 0000000..79aea7c
--- /dev/null
+++ b/test/2044-get-stack-traces/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2044-get-stack-traces`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2044-get-stack-traces",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2044-get-stack-traces-expected-stdout",
+        ":art-run-test-2044-get-stack-traces-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2044-get-stack-traces-expected-stdout",
+    out: ["art-run-test-2044-get-stack-traces-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2044-get-stack-traces-expected-stderr",
+    out: ["art-run-test-2044-get-stack-traces-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2044-get-stack-traces/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2044-get-stack-traces/expected-stderr.txt
diff --git a/test/2044-get-stack-traces/expected-stdout.txt b/test/2044-get-stack-traces/expected-stdout.txt
new file mode 100644
index 0000000..d396083
--- /dev/null
+++ b/test/2044-get-stack-traces/expected-stdout.txt
@@ -0,0 +1,8 @@
+Starting
+Starting helper
+Starting helper
+Starting helper
+Starting helper
+Starting helper
+Finished worker stack traces
+Finished
diff --git a/test/2044-get-stack-traces/info.txt b/test/2044-get-stack-traces/info.txt
new file mode 100644
index 0000000..f9cac7a
--- /dev/null
+++ b/test/2044-get-stack-traces/info.txt
@@ -0,0 +1,4 @@
+Tests multiple simultaneous calls to Thread.getStackTrace()
+
+This is a stress test for code under suspicion in the context of b/234542166
+and others.
diff --git a/test/2044-get-stack-traces/src/Main.java b/test/2044-get-stack-traces/src/Main.java
new file mode 100644
index 0000000..7840389
--- /dev/null
+++ b/test/2044-get-stack-traces/src/Main.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.ref.SoftReference;
+import java.math.BigInteger;
+import java.util.ArrayList;
+
+/**
+ * We construct a main thread and worker threads, each retrieving stack traces
+ * from the other. Since there are multiple workers, we may get a large number
+ * of simultaneous stack trace attempts.
+ */
+public class Main {
+    static final int NUM_THREADS = 5;
+    static Thread mainThread;
+    static volatile boolean pleaseStop = false;
+
+    private static void getTrace(Thread t) {
+      StackTraceElement trace[] = t.getStackTrace();
+      if (!pleaseStop && (trace.length < 1 || trace.length > 20)) {
+        // If called from traceGetter, we were started by the main thread, and it was still
+        // running after the trace, so the main thread should have at least one frame on
+        // the stack. If called by main(), we waited for all the traceGetters to start,
+        // and didn't yet allow them to stop, so the same should be true.
+        System.out.println("Stack trace for " + t.getName() + " has size " + trace.length);
+        for (StackTraceElement e : trace) {
+          System.out.println(e.toString());
+        }
+      }
+    }
+
+    /**
+     * Repeatedly get and minimally check stack trace of main thread.
+     */
+    static Runnable traceGetter = new Runnable() {
+        public void run() {
+          System.out.println("Starting helper");
+          while (!pleaseStop) {
+            getTrace(mainThread);
+          }
+        }
+    };
+
+    public static void main(String[] args) throws Exception {
+        System.out.println("Starting");
+        Thread[] t = new Thread[NUM_THREADS];
+        mainThread = Thread.currentThread();
+        for (int i = 0; i < NUM_THREADS; ++i) {
+          t[i] = new Thread(traceGetter);
+          t[i].start();
+        }
+        try {
+          Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            System.out.println("Unexpectedly interrupted");
+        }
+        for (int i = 0; i < NUM_THREADS; ++i) {
+          getTrace(t[i]);
+        }
+        System.out.println("Finished worker stack traces");
+        long now = System.currentTimeMillis();
+        while (System.currentTimeMillis() - now < 2000) {
+          try {
+            Thread.sleep(1);
+          } catch (InterruptedException e) {
+            System.out.println("Unexpectedly interrupted");
+          }
+        }
+        pleaseStop = true;
+        System.out.println("Finished");
+    }
+}
diff --git a/test/2045-uffd-kernelfault/Android.bp b/test/2045-uffd-kernelfault/Android.bp
new file mode 100644
index 0000000..79aedab
--- /dev/null
+++ b/test/2045-uffd-kernelfault/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2045-uffd-kernelfault`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2045-uffd-kernelfault",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2045-uffd-kernelfault-expected-stdout",
+        ":art-run-test-2045-uffd-kernelfault-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2045-uffd-kernelfault-expected-stdout",
+    out: ["art-run-test-2045-uffd-kernelfault-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2045-uffd-kernelfault-expected-stderr",
+    out: ["art-run-test-2045-uffd-kernelfault-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2045-uffd-kernelfault/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2045-uffd-kernelfault/expected-stderr.txt
diff --git a/test/2045-uffd-kernelfault/expected-stdout.txt b/test/2045-uffd-kernelfault/expected-stdout.txt
new file mode 100644
index 0000000..a965a70
--- /dev/null
+++ b/test/2045-uffd-kernelfault/expected-stdout.txt
@@ -0,0 +1 @@
+Done
diff --git a/test/2045-uffd-kernelfault/info.txt b/test/2045-uffd-kernelfault/info.txt
new file mode 100644
index 0000000..c0967d5
--- /dev/null
+++ b/test/2045-uffd-kernelfault/info.txt
@@ -0,0 +1,2 @@
+Test that fault-handler doesn't cause userfaultfd kernel-faults, which are not
+allowed in unpriviledged processes.
diff --git a/test/2045-uffd-kernelfault/run.py b/test/2045-uffd-kernelfault/run.py
new file mode 100644
index 0000000..5b262bb
--- /dev/null
+++ b/test/2045-uffd-kernelfault/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+
+def run(ctx, args):
+  # Limit the Java heap to 20MiB to force more GCs.
+  ctx.default_run(args, runtime_option=["-Xmx20m"])
diff --git a/test/2045-uffd-kernelfault/src/Main.java b/test/2045-uffd-kernelfault/src/Main.java
new file mode 100644
index 0000000..c5fac30
--- /dev/null
+++ b/test/2045-uffd-kernelfault/src/Main.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+    // TODO: Reduce it once the userfaultfd GC is tested long enough.
+    static final long DURATION_IN_MILLIS = 10_000;
+
+    static public Object obj = null;
+    static public Object[] array = new Object[4096];
+
+    public static void main(String args[]) {
+      final long start_time = System.currentTimeMillis();
+      long end_time = start_time;
+      int idx = 0;
+      while (end_time - start_time < DURATION_IN_MILLIS) {
+        try {
+          // Trigger a null-pointer exception
+          System.out.println(obj.toString());
+        } catch (NullPointerException npe) {
+          // Small enough to be not allocated in large-object space and hence keep the compaction
+          // phase longer, while keeping marking phase shorter (as there aren't any references to
+          // chase).
+          array[idx++] = new byte[3000];
+          idx %= array.length;
+        }
+        end_time = System.currentTimeMillis();
+      }
+      System.out.println("Done");
+    }
+}
diff --git a/test/2046-checker-comparison/Android.bp b/test/2046-checker-comparison/Android.bp
new file mode 100644
index 0000000..edd7761
--- /dev/null
+++ b/test/2046-checker-comparison/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2046-checker-comparison`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2046-checker-comparison",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2046-checker-comparison-expected-stdout",
+        ":art-run-test-2046-checker-comparison-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2046-checker-comparison-expected-stdout",
+    out: ["art-run-test-2046-checker-comparison-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2046-checker-comparison-expected-stderr",
+    out: ["art-run-test-2046-checker-comparison-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2046-checker-comparison/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2046-checker-comparison/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2046-checker-comparison/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2046-checker-comparison/expected-stdout.txt
diff --git a/test/2046-checker-comparison/info.txt b/test/2046-checker-comparison/info.txt
new file mode 100644
index 0000000..ead1764
--- /dev/null
+++ b/test/2046-checker-comparison/info.txt
@@ -0,0 +1 @@
+Tests that we optimize comparisons where lhs and rhs are the same.
diff --git a/test/2046-checker-comparison/src/Main.java b/test/2046-checker-comparison/src/Main.java
new file mode 100644
index 0000000..9caa727
--- /dev/null
+++ b/test/2046-checker-comparison/src/Main.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Main {
+    public static void main(String[] args) {
+        assertEquals(1, $noinline$testEqualBool(true));
+        assertEquals(0, $noinline$testNotEqualBool(true));
+        // Other comparisons e.g. `<` don't exists for boolean values.
+
+        assertEquals(1, $noinline$testEqualInt(0));
+        assertEquals(0, $noinline$testNotEqualInt(0));
+        assertEquals(0, $noinline$testGreaterThanInt(0));
+        assertEquals(1, $noinline$testGreaterThanOrEqualInt(0));
+        assertEquals(0, $noinline$testLessThanInt(0));
+        assertEquals(1, $noinline$testLessThanOrEqualInt(0));
+
+        assertEquals(1, $noinline$testEqualLong(0L));
+        assertEquals(0, $noinline$testNotEqualLong(0L));
+        assertEquals(0, $noinline$testGreaterThanLong(0L));
+        assertEquals(1, $noinline$testGreaterThanOrEqualLong(0L));
+        assertEquals(0, $noinline$testLessThanLong(0L));
+        assertEquals(1, $noinline$testLessThanOrEqualLong(0L));
+
+        // We cannot perform the optimization on unknown float/doubles since equality for NaN
+        // returns the opposite as for normal numbers.
+        assertEquals(1, $noinline$testEqualFloat(0f));
+        assertEquals(0, $noinline$testEqualFloat(Float.NaN));
+        assertEquals(1, $noinline$testEqualFloat(Float.NEGATIVE_INFINITY));
+        assertEquals(1, $noinline$testEqualFloat(Float.POSITIVE_INFINITY));
+        assertEquals(0, $noinline$testNotEqualFloat(0f));
+        assertEquals(1, $noinline$testNotEqualFloat(Float.NaN));
+        assertEquals(0, $noinline$testNotEqualFloat(Float.NEGATIVE_INFINITY));
+        assertEquals(0, $noinline$testNotEqualFloat(Float.POSITIVE_INFINITY));
+        assertEquals(0, $noinline$testGreaterThanFloat(0f));
+        assertEquals(0, $noinline$testGreaterThanFloat(Float.NaN));
+        assertEquals(0, $noinline$testGreaterThanFloat(Float.NEGATIVE_INFINITY));
+        assertEquals(0, $noinline$testGreaterThanFloat(Float.POSITIVE_INFINITY));
+        assertEquals(1, $noinline$testGreaterThanOrEqualFloat(0f));
+        assertEquals(0, $noinline$testGreaterThanOrEqualFloat(Float.NaN));
+        assertEquals(1, $noinline$testGreaterThanOrEqualFloat(Float.NEGATIVE_INFINITY));
+        assertEquals(1, $noinline$testGreaterThanOrEqualFloat(Float.POSITIVE_INFINITY));
+        assertEquals(0, $noinline$testLessThanFloat(0f));
+        assertEquals(0, $noinline$testLessThanFloat(Float.NaN));
+        assertEquals(0, $noinline$testLessThanFloat(Float.NEGATIVE_INFINITY));
+        assertEquals(0, $noinline$testLessThanFloat(Float.POSITIVE_INFINITY));
+        assertEquals(1, $noinline$testLessThanOrEqualFloat(0f));
+        assertEquals(0, $noinline$testLessThanOrEqualFloat(Float.NaN));
+        assertEquals(1, $noinline$testLessThanOrEqualFloat(Float.NEGATIVE_INFINITY));
+        assertEquals(1, $noinline$testLessThanOrEqualFloat(Float.POSITIVE_INFINITY));
+
+        assertEquals(1, $noinline$testEqualDouble(0d));
+        assertEquals(0, $noinline$testEqualDouble(Double.NaN));
+        assertEquals(1, $noinline$testEqualDouble(Double.NEGATIVE_INFINITY));
+        assertEquals(1, $noinline$testEqualDouble(Double.POSITIVE_INFINITY));
+        assertEquals(0, $noinline$testNotEqualDouble(0d));
+        assertEquals(1, $noinline$testNotEqualDouble(Double.NaN));
+        assertEquals(0, $noinline$testNotEqualDouble(Double.NEGATIVE_INFINITY));
+        assertEquals(0, $noinline$testNotEqualDouble(Double.POSITIVE_INFINITY));
+        assertEquals(0, $noinline$testGreaterThanDouble(0d));
+        assertEquals(0, $noinline$testGreaterThanDouble(Double.NaN));
+        assertEquals(0, $noinline$testGreaterThanDouble(Double.NEGATIVE_INFINITY));
+        assertEquals(0, $noinline$testGreaterThanDouble(Double.POSITIVE_INFINITY));
+        assertEquals(1, $noinline$testGreaterThanOrEqualDouble(0d));
+        assertEquals(0, $noinline$testGreaterThanOrEqualDouble(Double.NaN));
+        assertEquals(1, $noinline$testGreaterThanOrEqualDouble(Double.NEGATIVE_INFINITY));
+        assertEquals(1, $noinline$testGreaterThanOrEqualDouble(Double.POSITIVE_INFINITY));
+        assertEquals(0, $noinline$testLessThanDouble(0d));
+        assertEquals(0, $noinline$testLessThanDouble(Double.NaN));
+        assertEquals(0, $noinline$testLessThanDouble(Double.NEGATIVE_INFINITY));
+        assertEquals(0, $noinline$testLessThanDouble(Double.POSITIVE_INFINITY));
+        assertEquals(1, $noinline$testLessThanOrEqualDouble(0d));
+        assertEquals(0, $noinline$testLessThanOrEqualDouble(Double.NaN));
+        assertEquals(1, $noinline$testLessThanOrEqualDouble(Double.NEGATIVE_INFINITY));
+        assertEquals(1, $noinline$testLessThanOrEqualDouble(Double.POSITIVE_INFINITY));
+
+        assertEquals(1, $noinline$testEqualObject(null));
+        assertEquals(1, $noinline$testEqualObject(new Object()));
+        assertEquals(0, $noinline$testNotEqualObject(null));
+        assertEquals(0, $noinline$testNotEqualObject(new Object()));
+        // Other comparisons e.g. `<` don't exists for references.
+    }
+
+    /// CHECK-START: int Main.$noinline$testEqualBool(boolean) register (after)
+    /// CHECK: <<Const1:i\d+>> IntConstant 1
+    /// CHECK:                 Return [<<Const1>>]
+    private static int $noinline$testEqualBool(boolean a) {
+        if (a == $inline$returnValueBool(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testNotEqualBool(boolean) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testNotEqualBool(boolean a) {
+        if (a != $inline$returnValueBool(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    private static boolean $inline$returnValueBool(boolean a) {
+        return a;
+    }
+
+    /// CHECK-START: int Main.$noinline$testEqualInt(int) register (after)
+    /// CHECK: <<Const1:i\d+>> IntConstant 1
+    /// CHECK:                 Return [<<Const1>>]
+    private static int $noinline$testEqualInt(int a) {
+        if (a == $inline$returnValueInt(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testNotEqualInt(int) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testNotEqualInt(int a) {
+        if (a != $inline$returnValueInt(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testGreaterThanInt(int) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testGreaterThanInt(int a) {
+        if (a > $inline$returnValueInt(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testGreaterThanOrEqualInt(int) register (after)
+    /// CHECK: <<Const1:i\d+>> IntConstant 1
+    /// CHECK:                 Return [<<Const1>>]
+    private static int $noinline$testGreaterThanOrEqualInt(int a) {
+        if (a >= $inline$returnValueInt(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testLessThanInt(int) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testLessThanInt(int a) {
+        if (a < $inline$returnValueInt(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testLessThanOrEqualInt(int) register (after)
+    /// CHECK: <<Const1:i\d+>> IntConstant 1
+    /// CHECK:                 Return [<<Const1>>]
+    private static int $noinline$testLessThanOrEqualInt(int a) {
+        if (a <= $inline$returnValueInt(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    private static int $inline$returnValueInt(int a) {
+        return a;
+    }
+
+    /// CHECK-START: int Main.$noinline$testEqualLong(long) register (after)
+    /// CHECK: <<Const1:i\d+>> IntConstant 1
+    /// CHECK:                 Return [<<Const1>>]
+    private static int $noinline$testEqualLong(long a) {
+        if (a == $inline$returnValueLong(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testNotEqualLong(long) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testNotEqualLong(long a) {
+        if (a != $inline$returnValueLong(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testGreaterThanLong(long) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testGreaterThanLong(long a) {
+        if (a > $inline$returnValueLong(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testGreaterThanOrEqualLong(long) register (after)
+    /// CHECK: <<Const1:i\d+>> IntConstant 1
+    /// CHECK:                 Return [<<Const1>>]
+    private static int $noinline$testGreaterThanOrEqualLong(long a) {
+        if (a >= $inline$returnValueLong(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testLessThanLong(long) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testLessThanLong(long a) {
+        if (a < $inline$returnValueLong(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testLessThanOrEqualLong(long) register (after)
+    /// CHECK: <<Const1:i\d+>> IntConstant 1
+    /// CHECK:                 Return [<<Const1>>]
+    private static int $noinline$testLessThanOrEqualLong(long a) {
+        if (a <= $inline$returnValueLong(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    private static long $inline$returnValueLong(long a) {
+        return a;
+    }
+
+    /// CHECK-START: int Main.$noinline$testEqualFloat(float) register (after)
+    /// CHECK: <<NotEqual:z\d+>> NotEqual
+    /// CHECK: <<BNot:z\d+>>     BooleanNot [<<NotEqual>>]
+    /// CHECK:                   Return [<<BNot>>]
+    private static int $noinline$testEqualFloat(float a) {
+        if (a == $inline$returnValueFloat(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testNotEqualFloat(float) register (after)
+    /// CHECK: <<Equal:z\d+>>    Equal
+    /// CHECK: <<BNot:z\d+>>     BooleanNot [<<Equal>>]
+    /// CHECK:                   Return [<<BNot>>]
+    private static int $noinline$testNotEqualFloat(float a) {
+        if (a != $inline$returnValueFloat(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testGreaterThanFloat(float) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testGreaterThanFloat(float a) {
+        if (a > $inline$returnValueFloat(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testGreaterThanOrEqualFloat(float) register (after)
+    /// CHECK: <<LessThan:z\d+>> LessThan
+    /// CHECK: <<BNot:z\d+>>     BooleanNot [<<LessThan>>]
+    /// CHECK:                   Return [<<BNot>>]
+    private static int $noinline$testGreaterThanOrEqualFloat(float a) {
+        if (a >= $inline$returnValueFloat(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testLessThanFloat(float) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testLessThanFloat(float a) {
+        if (a < $inline$returnValueFloat(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testLessThanOrEqualFloat(float) register (after)
+    /// CHECK: <<GreaterThan:z\d+>> GreaterThan
+    /// CHECK: <<BNot:z\d+>>        BooleanNot [<<GreaterThan>>]
+    /// CHECK:                      Return [<<BNot>>]
+    private static int $noinline$testLessThanOrEqualFloat(float a) {
+        if (a <= $inline$returnValueFloat(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    private static float $inline$returnValueFloat(float a) {
+        return a;
+    }
+
+    /// CHECK-START: int Main.$noinline$testEqualDouble(double) register (after)
+    /// CHECK: <<NotEqual:z\d+>> NotEqual
+    /// CHECK: <<BNot:z\d+>>     BooleanNot [<<NotEqual>>]
+    /// CHECK:                   Return [<<BNot>>]
+    private static int $noinline$testEqualDouble(double a) {
+        if (a == $inline$returnValueDouble(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testNotEqualDouble(double) register (after)
+    /// CHECK: <<Equal:z\d+>>    Equal
+    /// CHECK: <<BNot:z\d+>>     BooleanNot [<<Equal>>]
+    /// CHECK:                   Return [<<BNot>>]
+    private static int $noinline$testNotEqualDouble(double a) {
+        if (a != $inline$returnValueDouble(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testGreaterThanDouble(double) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testGreaterThanDouble(double a) {
+        if (a > $inline$returnValueDouble(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testGreaterThanOrEqualDouble(double) register (after)
+    /// CHECK: <<LessThan:z\d+>> LessThan
+    /// CHECK: <<BNot:z\d+>>     BooleanNot [<<LessThan>>]
+    /// CHECK:                   Return [<<BNot>>]
+    private static int $noinline$testGreaterThanOrEqualDouble(double a) {
+        if (a >= $inline$returnValueDouble(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testLessThanDouble(double) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testLessThanDouble(double a) {
+        if (a < $inline$returnValueDouble(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testLessThanOrEqualDouble(double) register (after)
+    /// CHECK: <<GreaterThan:z\d+>> GreaterThan
+    /// CHECK: <<BNot:z\d+>>        BooleanNot [<<GreaterThan>>]
+    /// CHECK:                      Return [<<BNot>>]
+    private static int $noinline$testLessThanOrEqualDouble(double a) {
+        if (a <= $inline$returnValueDouble(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    private static double $inline$returnValueDouble(double a) {
+        return a;
+    }
+
+    /// CHECK-START: int Main.$noinline$testEqualObject(java.lang.Object) register (after)
+    /// CHECK: <<Const1:i\d+>> IntConstant 1
+    /// CHECK:                 Return [<<Const1>>]
+    private static int $noinline$testEqualObject(Object a) {
+        if (a == $inline$returnValueObject(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testNotEqualObject(java.lang.Object) register (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+    private static int $noinline$testNotEqualObject(Object a) {
+        if (a != $inline$returnValueObject(a)) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    private static Object $inline$returnValueObject(Object a) {
+        return a;
+    }
+
+    static void assertEquals(int expected, int actual) {
+        if (expected != actual) {
+            throw new AssertionError("Expected " + expected + " got " + actual);
+        }
+    }
+}
diff --git a/test/2047-checker-const-string-length/Android.bp b/test/2047-checker-const-string-length/Android.bp
new file mode 100644
index 0000000..41cbfd3
--- /dev/null
+++ b/test/2047-checker-const-string-length/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2047-checker-const-string-length`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2047-checker-const-string-length",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2047-checker-const-string-length-expected-stdout",
+        ":art-run-test-2047-checker-const-string-length-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2047-checker-const-string-length-expected-stdout",
+    out: ["art-run-test-2047-checker-const-string-length-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2047-checker-const-string-length-expected-stderr",
+    out: ["art-run-test-2047-checker-const-string-length-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2047-checker-const-string-length/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2047-checker-const-string-length/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2047-checker-const-string-length/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2047-checker-const-string-length/expected-stdout.txt
diff --git a/test/2047-checker-const-string-length/info.txt b/test/2047-checker-const-string-length/info.txt
new file mode 100644
index 0000000..61e987f
--- /dev/null
+++ b/test/2047-checker-const-string-length/info.txt
@@ -0,0 +1 @@
+Tests that we optimize String's length()/isEmpty() for constant strings.
diff --git a/test/2047-checker-const-string-length/src/Main.java b/test/2047-checker-const-string-length/src/Main.java
new file mode 100644
index 0000000..0130943
--- /dev/null
+++ b/test/2047-checker-const-string-length/src/Main.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+// Note that the empty string is present in the BootImage but the other one is a BSS string. We are
+// testing both AOT LoadString kinds.
+
+public class Main {
+    public static void main(String[] args) {
+        $noinline$testLength();
+        $noinline$testIsEmpty();
+    }
+
+    private static void $noinline$testLength() {
+        assertEquals(0, $noinline$testLengthEmptyString());
+        assertEquals(0, $noinline$testLengthEmptyStringWithInline());
+        assertEquals(32, $noinline$testLengthBssString());
+        assertEquals(32, $noinline$testLengthBssStringWithInline());
+    }
+
+    /// CHECK-START: int Main.$noinline$testLengthEmptyString() constant_folding (before)
+    /// CHECK: LoadString load_kind:BootImageRelRo
+    /// CHECK: <<Length:i\d+>> ArrayLength
+    /// CHECK:                 Return [<<Length>>]
+
+    /// CHECK-START: int Main.$noinline$testLengthEmptyString() constant_folding (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+
+    /// CHECK-START: int Main.$noinline$testLengthEmptyString() dead_code_elimination$initial (after)
+    /// CHECK-NOT: LoadString
+
+    /// CHECK-START: int Main.$noinline$testLengthEmptyString() dead_code_elimination$initial (after)
+    /// CHECK-NOT: ArrayLength
+    private static int $noinline$testLengthEmptyString() {
+        String str = "";
+        return str.length();
+    }
+
+    /// CHECK-START: int Main.$noinline$testLengthEmptyStringWithInline() constant_folding$after_inlining (before)
+    /// CHECK: LoadString load_kind:BootImageRelRo
+    /// CHECK: <<Length:i\d+>> ArrayLength
+    /// CHECK:                 Return [<<Length>>]
+
+    /// CHECK-START: int Main.$noinline$testLengthEmptyStringWithInline() constant_folding$after_inlining (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+
+    /// CHECK-START: int Main.$noinline$testLengthEmptyStringWithInline() dead_code_elimination$after_inlining (after)
+    /// CHECK-NOT: LoadString
+
+    /// CHECK-START: int Main.$noinline$testLengthEmptyStringWithInline() dead_code_elimination$after_inlining (after)
+    /// CHECK-NOT: ArrayLength
+    private static int $noinline$testLengthEmptyStringWithInline() {
+        String str = "";
+        return $inline$returnLength(str);
+    }
+
+    /// CHECK-START: int Main.$noinline$testLengthBssString() constant_folding (before)
+    /// CHECK: LoadString load_kind:BssEntry
+    /// CHECK: <<Length:i\d+>> ArrayLength
+    /// CHECK:                 Return [<<Length>>]
+
+    /// CHECK-START: int Main.$noinline$testLengthBssString() constant_folding (after)
+    /// CHECK: <<Const32:i\d+>> IntConstant 32
+    /// CHECK:                  Return [<<Const32>>]
+
+    // We don't remove LoadString load_kind:BssEntry even if they have no uses, since IsRemovable()
+    // returns false for them.
+    /// CHECK-START: int Main.$noinline$testLengthBssString() dead_code_elimination$initial (after)
+    /// CHECK: LoadString load_kind:BssEntry
+
+    /// CHECK-START: int Main.$noinline$testLengthBssString() dead_code_elimination$initial (after)
+    /// CHECK-NOT: ArrayLength
+    private static int $noinline$testLengthBssString() {
+        String str = "2047-checker-const-string-length";
+        return str.length();
+    }
+
+    /// CHECK-START: int Main.$noinline$testLengthBssStringWithInline() constant_folding$after_inlining (before)
+    /// CHECK: LoadString load_kind:BssEntry
+    /// CHECK: <<Length:i\d+>> ArrayLength
+    /// CHECK:                 Return [<<Length>>]
+
+    /// CHECK-START: int Main.$noinline$testLengthBssStringWithInline() constant_folding$after_inlining (after)
+    /// CHECK: <<Const32:i\d+>> IntConstant 32
+    /// CHECK:                  Return [<<Const32>>]
+
+    // We don't remove LoadString load_kind:BssEntry even if they have no uses, since IsRemovable()
+    // returns false for them.
+    /// CHECK-START: int Main.$noinline$testLengthBssStringWithInline() dead_code_elimination$after_inlining (after)
+    /// CHECK: LoadString load_kind:BssEntry
+
+    /// CHECK-START: int Main.$noinline$testLengthBssStringWithInline() dead_code_elimination$after_inlining (after)
+    /// CHECK-NOT: ArrayLength
+    private static int $noinline$testLengthBssStringWithInline() {
+        String str = "2047-checker-const-string-length";
+        return $inline$returnLength(str);
+    }
+
+    private static int $inline$returnLength(String str) {
+        return str.length();
+    }
+
+    private static void $noinline$testIsEmpty() {
+        assertEquals(true, $noinline$testIsEmptyEmptyString());
+        assertEquals(true, $noinline$testIsEmptyEmptyStringWithInline());
+        assertEquals(false, $noinline$testIsEmptyBssString());
+        assertEquals(false, $noinline$testIsEmptyBssStringWithInline());
+    }
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyEmptyString() constant_folding (before)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK: LoadString load_kind:BootImageRelRo
+    /// CHECK: <<Length:i\d+>> ArrayLength
+    /// CHECK: <<Eq:z\d+>>     Equal [<<Length>>,<<Const0>>]
+    /// CHECK:                 Return [<<Eq>>]
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyEmptyString() constant_folding (after)
+    /// CHECK: <<Const1:i\d+>> IntConstant 1
+    /// CHECK:                 Return [<<Const1>>]
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyEmptyString() dead_code_elimination$initial (after)
+    /// CHECK-NOT: LoadString
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyEmptyString() dead_code_elimination$initial (after)
+    /// CHECK-NOT: ArrayLength
+    private static boolean $noinline$testIsEmptyEmptyString() {
+        String str = "";
+        return str.isEmpty();
+    }
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyEmptyStringWithInline() constant_folding$after_inlining (before)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK: LoadString load_kind:BootImageRelRo
+    /// CHECK: <<Length:i\d+>> ArrayLength
+    /// CHECK: <<Eq:z\d+>>     Equal [<<Length>>,<<Const0>>]
+    /// CHECK:                 Return [<<Eq>>]
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyEmptyStringWithInline() constant_folding$after_inlining (after)
+    /// CHECK: <<Const1:i\d+>> IntConstant 1
+    /// CHECK:                 Return [<<Const1>>]
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyEmptyStringWithInline() dead_code_elimination$after_inlining (after)
+    /// CHECK-NOT: LoadString
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyEmptyStringWithInline() dead_code_elimination$after_inlining (after)
+    /// CHECK-NOT: ArrayLength
+    private static boolean $noinline$testIsEmptyEmptyStringWithInline() {
+        String str = "";
+        return $inline$returnIsEmpty(str);
+    }
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyBssString() constant_folding (before)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK: LoadString load_kind:BssEntry
+    /// CHECK: <<Length:i\d+>> ArrayLength
+    /// CHECK: <<Eq:z\d+>>     Equal [<<Length>>,<<Const0>>]
+    /// CHECK:                 Return [<<Eq>>]
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyBssString() constant_folding (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+
+    // We don't remove LoadString load_kind:BssEntry even if they have no uses, since IsRemovable()
+    // returns false for them.
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyBssString() dead_code_elimination$initial (after)
+    /// CHECK: LoadString load_kind:BssEntry
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyBssString() dead_code_elimination$initial (after)
+    /// CHECK-NOT: ArrayLength
+    private static boolean $noinline$testIsEmptyBssString() {
+        String str = "2047-checker-const-string-length";
+        return str.isEmpty();
+    }
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyBssStringWithInline() constant_folding$after_inlining (before)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK: LoadString load_kind:BssEntry
+    /// CHECK: <<Length:i\d+>> ArrayLength
+    /// CHECK: <<Eq:z\d+>>     Equal [<<Length>>,<<Const0>>]
+    /// CHECK:                 Return [<<Eq>>]
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyBssStringWithInline() constant_folding$after_inlining (after)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK:                 Return [<<Const0>>]
+
+    // We don't remove LoadString load_kind:BssEntry even if they have no uses, since IsRemovable()
+    // returns false for them.
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyBssStringWithInline() dead_code_elimination$after_inlining (after)
+    /// CHECK: LoadString load_kind:BssEntry
+
+    /// CHECK-START: boolean Main.$noinline$testIsEmptyBssStringWithInline() dead_code_elimination$after_inlining (after)
+    /// CHECK-NOT: ArrayLength
+    private static boolean $noinline$testIsEmptyBssStringWithInline() {
+        String str = "2047-checker-const-string-length";
+        return $inline$returnIsEmpty(str);
+    }
+
+    private static boolean $inline$returnIsEmpty(String str) {
+        return str.isEmpty();
+    }
+
+    static void assertEquals(int expected, int actual) {
+        if (expected != actual) {
+            throw new AssertionError("Expected " + expected + " got " + actual);
+        }
+    }
+
+    static void assertEquals(boolean expected, boolean actual) {
+        if (expected != actual) {
+            throw new AssertionError("Expected " + expected + " got " + actual);
+        }
+    }
+}
diff --git a/test/2230-profile-save-hotness/run b/test/2230-profile-save-hotness/run
deleted file mode 100644
index d0c49b6..0000000
--- a/test/2230-profile-save-hotness/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-${RUN} \
-  -Xcompiler-option --count-hotness-in-compiled-code \
-  -Xcompiler-option --compiler-filter=speed \
-  --runtime-option -Xps-profile-aot-code \
-  --runtime-option -Xjitsaveprofilinginfo \
-  --runtime-option -Xusejit:true "${@}"
diff --git a/test/2230-profile-save-hotness/run.py b/test/2230-profile-save-hotness/run.py
new file mode 100644
index 0000000..526b841
--- /dev/null
+++ b/test/2230-profile-save-hotness/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+def run(ctx, args):
+  ctx.default_run(
+      args,
+      Xcompiler_option=[
+          "--count-hotness-in-compiled-code", "--compiler-filter=speed"
+      ],
+      runtime_option=[
+          "-Xps-profile-aot-code", "-Xjitsaveprofilinginfo", "-Xusejit:true"
+      ],
+  )
diff --git a/test/2232-write-metrics-to-log/Android.bp b/test/2232-write-metrics-to-log/Android.bp
index a64567e..6a1e68c 100644
--- a/test/2232-write-metrics-to-log/Android.bp
+++ b/test/2232-write-metrics-to-log/Android.bp
@@ -15,7 +15,7 @@
 java_test {
     name: "art-run-test-2232-write-metrics-to-log",
     defaults: ["art-run-test-defaults"],
-    test_config_template: ":art-run-test-target-template",
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
     srcs: ["src/**/*.java"],
     data: [
         ":art-run-test-2232-write-metrics-to-log-expected-stdout",
diff --git a/test/2232-write-metrics-to-log/check b/test/2232-write-metrics-to-log/check
deleted file mode 100755
index d12e8b1..0000000
--- a/test/2232-write-metrics-to-log/check
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Check that one of the metrics appears in stderr.
-grep 'ClassVerificationTotalTime' "$4" >/dev/null
-MSG_FOUND=$?
-
-if [[ $MSG_FOUND -ne 0 ]] ; then
-  # Print out the log and return with error.
-  cat "$4"
-  exit 1
-fi
-
-# Success.
-exit 0
diff --git a/test/2232-write-metrics-to-log/expected-stderr.txt b/test/2232-write-metrics-to-log/expected-stderr.txt
index e69de29..5bc27fe 100644
--- a/test/2232-write-metrics-to-log/expected-stderr.txt
+++ b/test/2232-write-metrics-to-log/expected-stderr.txt
@@ -0,0 +1 @@
+ClassVerificationTotalTimeDelta
diff --git a/test/2232-write-metrics-to-log/run b/test/2232-write-metrics-to-log/run
deleted file mode 100755
index d34ec6c..0000000
--- a/test/2232-write-metrics-to-log/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-export ANDROID_LOG_TAGS="*:i"
-exec ${RUN} $@ --external-log-tags --runtime-option -Xmetrics-write-to-logcat:true --runtime-option -Xmetrics-reporting-mods:100
diff --git a/test/2232-write-metrics-to-log/run.py b/test/2232-write-metrics-to-log/run.py
new file mode 100644
index 0000000..89fe040
--- /dev/null
+++ b/test/2232-write-metrics-to-log/run.py
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# Copyright (C) 2020 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args,
+      android_log_tags="*:i",
+      diff_min_log_tag="i",
+      runtime_option=[
+          "-Xmetrics-write-to-logcat:true", "-Xmetrics-reporting-mods:100"
+      ])
+
+  # Check that one of the metrics appears in stderr.
+  ctx.run(
+      fr"sed -i -n 's/.*\(ClassVerificationTotalTimeDelta\).*/\1/p' '{args.stderr_file}'"
+  )
diff --git a/test/2233-checker-remove-loop-suspend-check/Android.bp b/test/2233-checker-remove-loop-suspend-check/Android.bp
new file mode 100644
index 0000000..f581ee3
--- /dev/null
+++ b/test/2233-checker-remove-loop-suspend-check/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2233-checker-remove-loop-suspend-check`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2233-checker-remove-loop-suspend-check",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2233-checker-remove-loop-suspend-check-expected-stdout",
+        ":art-run-test-2233-checker-remove-loop-suspend-check-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2233-checker-remove-loop-suspend-check-expected-stdout",
+    out: ["art-run-test-2233-checker-remove-loop-suspend-check-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2233-checker-remove-loop-suspend-check-expected-stderr",
+    out: ["art-run-test-2233-checker-remove-loop-suspend-check-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2233-checker-remove-loop-suspend-check/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2233-checker-remove-loop-suspend-check/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2233-checker-remove-loop-suspend-check/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2233-checker-remove-loop-suspend-check/expected-stdout.txt
diff --git a/test/2233-checker-remove-loop-suspend-check/info.txt b/test/2233-checker-remove-loop-suspend-check/info.txt
new file mode 100644
index 0000000..fe8d8eb
--- /dev/null
+++ b/test/2233-checker-remove-loop-suspend-check/info.txt
@@ -0,0 +1 @@
+Test to check the removal of SuspendCheck for finite simple plain loops.
diff --git a/test/2233-checker-remove-loop-suspend-check/src/Main.java b/test/2233-checker-remove-loop-suspend-check/src/Main.java
new file mode 100644
index 0000000..c56bd66
--- /dev/null
+++ b/test/2233-checker-remove-loop-suspend-check/src/Main.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+
+  static final int ITERATIONS = 16;
+
+  // Test 1: This test checks whether the SuspendCheck is removed from the
+  // header.
+
+  /// CHECK-START-ARM64: void Main.$noinline$testRemoveSuspendCheck(int[]) disassembly (after)
+  /// CHECK:        SuspendCheck         loop:<<LoopId:B\d+>>
+  /// CHECK-NEXT:   dex_pc:{{.*}}
+  /// CHECK:        Goto                 loop:<<LoopId>>
+  /// CHECK-NEXT:   b
+
+  public static void $noinline$testRemoveSuspendCheck(int[] a) {
+    for (int i = 0; i < ITERATIONS; i++) {
+      a[i++] = i;
+    }
+  }
+
+  // Test 2: This test checks that the SuspendCheck is not removed from the
+  // header because it contains a call to another function.
+
+  /// CHECK-START-ARM64: void Main.testRemoveSuspendCheckWithCall(int[]) disassembly (after)
+  /// CHECK:        SuspendCheck         loop:<<LoopId:B\d+>>
+  /// CHECK:        Goto                 loop:<<LoopId>>
+  /// CHECK-NEXT:   ldr
+
+  public static void testRemoveSuspendCheckWithCall(int[] a) {
+    for (int i = 0; i < ITERATIONS; i++) {
+      a[i++] = i;
+      $noinline$testRemoveSuspendCheck(a);
+    }
+  }
+
+  // Test 3:  This test checks that the SuspendCheck is not removed from the
+  // header because INSTR_COUNT * TRIP_COUNT exceeds the defined heuristic.
+
+  /// CHECK-START-ARM64: void Main.testRemoveSuspendCheckAboveHeuristic(int[]) disassembly (after)
+  /// CHECK:        SuspendCheck         loop:<<LoopId:B\d+>>
+  /// CHECK:        Goto                 loop:<<LoopId>>
+  /// CHECK-NEXT:   ldr
+
+  public static void testRemoveSuspendCheckAboveHeuristic(int[] a) {
+    for (int i = 0; i < ITERATIONS * 6; i++) {
+      a[i++] = i;
+    }
+  }
+
+  // Test 4:  This test checks that the SuspendCheck is not removed from the
+  // header because the trip count is not known at compile time.
+
+  /// CHECK-START-ARM64: void Main.testRemoveSuspendCheckUnknownCount(int[], int) disassembly (after)
+  /// CHECK:        SuspendCheck         loop:<<LoopId:B\d+>>
+  /// CHECK:        Goto                 loop:<<LoopId>>
+  /// CHECK-NEXT:   ldr
+
+  public static void testRemoveSuspendCheckUnknownCount(int[] a, int n) {
+    for (int i = 0; i < n; i++) {
+      a[i++] = i;
+    }
+  }
+
+  public static void main(String[] args) {
+    int[] a = new int[100];
+    $noinline$testRemoveSuspendCheck(a);
+    testRemoveSuspendCheckWithCall(a);
+    testRemoveSuspendCheckAboveHeuristic(a);
+    testRemoveSuspendCheckUnknownCount(a, 4);
+  }
+}
diff --git a/test/2235-JdkUnsafeTest/build.py b/test/2235-JdkUnsafeTest/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/2235-JdkUnsafeTest/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/2235-JdkUnsafeTest/test-metadata.json b/test/2235-JdkUnsafeTest/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/2235-JdkUnsafeTest/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/2237-checker-inline-multidex/expected-stdout.txt b/test/2237-checker-inline-multidex/expected-stdout.txt
index 571349a..c2c127f 100644
--- a/test/2237-checker-inline-multidex/expected-stdout.txt
+++ b/test/2237-checker-inline-multidex/expected-stdout.txt
@@ -2,3 +2,4 @@
 def
 ghi
 class Multi$Multi2
+122
diff --git a/test/2237-checker-inline-multidex/info.txt b/test/2237-checker-inline-multidex/info.txt
index fc6dc60..2e639ba 100644
--- a/test/2237-checker-inline-multidex/info.txt
+++ b/test/2237-checker-inline-multidex/info.txt
@@ -1 +1 @@
-Checks that we inline across dex files, even when we need an environment.
+Checks that we inline across dex files.
diff --git a/test/2237-checker-inline-multidex/src-multidex/Multi.java b/test/2237-checker-inline-multidex/src-multidex/Multi.java
index cd234c3..c830c59 100644
--- a/test/2237-checker-inline-multidex/src-multidex/Multi.java
+++ b/test/2237-checker-inline-multidex/src-multidex/Multi.java
@@ -38,4 +38,12 @@
   }
 
   private class Multi2 {}
+
+  public static int $inline$TryCatch(String str) {
+    try {
+      return Integer.parseInt(str);
+    } catch (NumberFormatException ex) {
+      return -1;
+    }
+  }
 }
diff --git a/test/2237-checker-inline-multidex/src/Main.java b/test/2237-checker-inline-multidex/src/Main.java
index 7ab2e7f..7a2ca82 100644
--- a/test/2237-checker-inline-multidex/src/Main.java
+++ b/test/2237-checker-inline-multidex/src/Main.java
@@ -16,57 +16,73 @@
 
 public class Main {
   public static void main(String[] args) {
-    System.out.println(testNeedsEnvironment());
-    System.out.println(testNeedsBssEntryString());
-    System.out.println(testNeedsBssEntryInvoke());
-    System.out.println(testClass());
+    // Test that the cross-dex inlining is working for HInstructions that need an environment.
+    System.out.println($noinline$testNeedsEnvironment());
+
+    // Test that the cross-dex inlining is working for HInstructions that need a bss entry.
+    System.out.println($noinline$testNeedsBssEntryString());
+    System.out.println($noinline$testNeedsBssEntryInvoke());
+    System.out.println($noinline$testClass());
+
+    // Test that we are able to inline try catches across dex files.
+    System.out.println($noinline$testTryCatch());
   }
 
-  /// CHECK-START: java.lang.String Main.testNeedsEnvironment() inliner (before)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsEnvironment() inliner (before)
   /// CHECK:       InvokeStaticOrDirect method_name:Multi.$inline$NeedsEnvironmentMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsEnvironment() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsEnvironment() inliner (after)
   /// CHECK-NOT:   InvokeStaticOrDirect method_name:Multi.$inline$NeedsEnvironmentMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsEnvironment() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsEnvironment() inliner (after)
   /// CHECK:       StringBuilderAppend
-  public static String testNeedsEnvironment() {
+  private static String $noinline$testNeedsEnvironment() {
     return Multi.$inline$NeedsEnvironmentMultiDex("abc");
   }
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryString() inliner (before)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryString() inliner (before)
   /// CHECK:       InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryStringMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryString() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryString() inliner (after)
   /// CHECK-NOT:   InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryStringMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryString() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryString() inliner (after)
   /// CHECK:       LoadString load_kind:BssEntry
-  public static String testNeedsBssEntryString() {
+  private static String $noinline$testNeedsBssEntryString() {
     return Multi.$inline$NeedsBssEntryStringMultiDex();
   }
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryInvoke() inliner (before)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryInvoke() inliner (before)
   /// CHECK:       InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryInvokeMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryInvoke() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryInvoke() inliner (after)
   /// CHECK-NOT:   InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryInvokeMultiDex
 
-  /// CHECK-START: java.lang.String Main.testNeedsBssEntryInvoke() inliner (after)
+  /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryInvoke() inliner (after)
   /// CHECK:       InvokeStaticOrDirect method_name:Multi.$noinline$InnerInvokeMultiDex method_load_kind:BssEntry
-  public static String testNeedsBssEntryInvoke() {
+  private static String $noinline$testNeedsBssEntryInvoke() {
     return Multi.$inline$NeedsBssEntryInvokeMultiDex();
   }
 
-  /// CHECK-START: java.lang.Class Main.testClass() inliner (before)
+  /// CHECK-START: java.lang.Class Main.$noinline$testClass() inliner (before)
   /// CHECK:       InvokeStaticOrDirect method_name:Multi.NeedsBssEntryClassMultiDex
 
-  /// CHECK-START: java.lang.Class Main.testClass() inliner (after)
+  /// CHECK-START: java.lang.Class Main.$noinline$testClass() inliner (after)
   /// CHECK-NOT:   InvokeStaticOrDirect method_name:Multi.NeedsBssEntryClassMultiDex
 
-  /// CHECK-START: java.lang.Class Main.testClass() inliner (after)
+  /// CHECK-START: java.lang.Class Main.$noinline$testClass() inliner (after)
   /// CHECK:       LoadClass load_kind:BssEntry class_name:Multi$Multi2
-  public static Class<?> testClass() {
+  private static Class<?> $noinline$testClass() {
     return Multi.NeedsBssEntryClassMultiDex();
   }
+
+
+  /// CHECK-START: int Main.$noinline$testTryCatch() inliner (before)
+  /// CHECK-NOT:   TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testTryCatch() inliner (after)
+  /// CHECK:       TryBoundary
+  private static int $noinline$testTryCatch() {
+    return Multi.$inline$TryCatch("123") + Multi.$inline$TryCatch("abc");
+  }
 }
diff --git a/test/2238-checker-polymorphic-recursive-inlining/run b/test/2238-checker-polymorphic-recursive-inlining/run
deleted file mode 100644
index a4e2692..0000000
--- a/test/2238-checker-polymorphic-recursive-inlining/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-# Use a profile to put specific classes in the app image to trigger polymorphic inlining.
-exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile
diff --git a/test/2238-checker-polymorphic-recursive-inlining/run.py b/test/2238-checker-polymorphic-recursive-inlining/run.py
new file mode 100644
index 0000000..b85f926
--- /dev/null
+++ b/test/2238-checker-polymorphic-recursive-inlining/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+
+def run(ctx, args):
+  # Use a profile to put specific classes in the app image to trigger polymorphic inlining.
+  ctx.default_run(
+      args, profile=True, Xcompiler_option=["--compiler-filter=speed-profile"])
diff --git a/test/2239-varhandle-perf/build b/test/2239-varhandle-perf/build
deleted file mode 100755
index 115a0fb..0000000
--- a/test/2239-varhandle-perf/build
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2022 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.
-
-# Make us exit on a failure
-set -e
-
-# Set variables for source directories. Using src-art so we use
-# VarHandles in the bootclasspath and can compile with the Java 8
-# compiler.
-MANUAL_SRC=src
-GENERATED_SRC=src2
-
-# Build the Java files
-mkdir -p src2
-
-# Generate tests and Main that covers both the generated tests and manual tests
-python3 ./util-src/generate_java.py "${GENERATED_SRC}"
-
-./default-build "$@" --experimental var-handles
diff --git a/test/2239-varhandle-perf/build.py b/test/2239-varhandle-perf/build.py
new file mode 100644
index 0000000..3466dfe
--- /dev/null
+++ b/test/2239-varhandle-perf/build.py
@@ -0,0 +1,22 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  if ctx.jvm:
+    return  # The test does not build on JVM
+
+  ctx.default_build(api_level="var-handles")
diff --git a/test/2239-varhandle-perf/check b/test/2239-varhandle-perf/check
deleted file mode 100644
index 8ea102d..0000000
--- a/test/2239-varhandle-perf/check
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2020 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.
-
-# Dump the output of the benchmarks that run and report success. The
-# benchmarks ran successfully if we get as far as this script.
-
-cat "$2"
-exit 0
diff --git a/test/2239-varhandle-perf/generate-sources b/test/2239-varhandle-perf/generate-sources
new file mode 100755
index 0000000..310ad06
--- /dev/null
+++ b/test/2239-varhandle-perf/generate-sources
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+# Make us exit on a failure
+set -e
+
+# Set variables for source directories. Using src-art so we use
+# VarHandles in the bootclasspath and can compile with the Java 8
+# compiler.
+MANUAL_SRC=src
+GENERATED_SRC=src2
+
+# Build the Java files
+mkdir -p src2
+
+# Generate tests and Main that covers both the generated tests and manual tests
+python3 ./util-src/generate_java.py "${GENERATED_SRC}"
diff --git a/test/2239-varhandle-perf/run.py b/test/2239-varhandle-perf/run.py
new file mode 100644
index 0000000..4e8755d
--- /dev/null
+++ b/test/2239-varhandle-perf/run.py
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # Dump the output of the benchmarks that run and report success. The
+  # benchmarks ran successfully if we get as far as this script.
+  ctx.run(fr"cat '{args.stdout_file}'")
+
+  # Delete all output to make the diff unconditionally pass.
+  ctx.run(fr"> '{args.stdout_file}'")
diff --git a/test/2240-tracing-non-invokable-method/Android.bp b/test/2240-tracing-non-invokable-method/Android.bp
new file mode 100644
index 0000000..b3a2c79
--- /dev/null
+++ b/test/2240-tracing-non-invokable-method/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2240-tracing-non-invokable-method`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-2240-tracing-non-invokable-method-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2240-tracing-non-invokable-method",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-2240-tracing-non-invokable-method-src"
+    ],
+    data: [
+        ":art-run-test-2240-tracing-non-invokable-method-expected-stdout",
+        ":art-run-test-2240-tracing-non-invokable-method-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2240-tracing-non-invokable-method-expected-stdout",
+    out: ["art-run-test-2240-tracing-non-invokable-method-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2240-tracing-non-invokable-method-expected-stderr",
+    out: ["art-run-test-2240-tracing-non-invokable-method-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2240-tracing-non-invokable-method/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2240-tracing-non-invokable-method/expected-stderr.txt
diff --git a/test/2240-tracing-non-invokable-method/expected-stdout.txt b/test/2240-tracing-non-invokable-method/expected-stdout.txt
new file mode 100644
index 0000000..6a5618e
--- /dev/null
+++ b/test/2240-tracing-non-invokable-method/expected-stdout.txt
@@ -0,0 +1 @@
+JNI_OnLoad called
diff --git a/test/2240-tracing-non-invokable-method/info.txt b/test/2240-tracing-non-invokable-method/info.txt
new file mode 100644
index 0000000..27877aa
--- /dev/null
+++ b/test/2240-tracing-non-invokable-method/info.txt
@@ -0,0 +1,2 @@
+Tests that tracing handles non-invokable methods correctly without updating the
+entrypoints for non-invokable methods.
diff --git a/test/2240-tracing-non-invokable-method/run.py b/test/2240-tracing-non-invokable-method/run.py
new file mode 100644
index 0000000..ac17e68
--- /dev/null
+++ b/test/2240-tracing-non-invokable-method/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args, runtime_option=["-Xmethod-trace", "-Xmethod-trace-file:/dev/null"])
diff --git a/test/2240-tracing-non-invokable-method/src/Main.java b/test/2240-tracing-non-invokable-method/src/Main.java
new file mode 100644
index 0000000..e5a4e43
--- /dev/null
+++ b/test/2240-tracing-non-invokable-method/src/Main.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+interface Itf {
+  public default void m() throws Exception {
+    throw new Exception("Don't inline me");
+  }
+  public default void mConflict() throws Exception {
+    throw new Exception("Don't inline me");
+  }
+}
+
+// This is redefined in src2 with a mConflict method.
+interface Itf2 {
+}
+
+public class Main implements Itf, Itf2 {
+
+  public static void main(String[] args) {
+    System.loadLibrary(args[0]);
+
+    try {
+      itf.mConflict();
+      throw new Error("Expected IncompatibleClassChangeError");
+    } catch (Exception e) {
+      throw new Error("Unexpected exception");
+    } catch (IncompatibleClassChangeError e) {
+      // Expected.
+    }
+  }
+
+  static Itf itf = new Main();
+}
diff --git a/test/2240-tracing-non-invokable-method/src2/Itf2.java b/test/2240-tracing-non-invokable-method/src2/Itf2.java
new file mode 100644
index 0000000..e962411
--- /dev/null
+++ b/test/2240-tracing-non-invokable-method/src2/Itf2.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+interface Itf2 {
+  public default void mConflict(){
+  }
+}
diff --git a/test/2241-checker-inline-try-catch/Android.bp b/test/2241-checker-inline-try-catch/Android.bp
new file mode 100644
index 0000000..15708d6
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2241-checker-inline-try-catch`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2241-checker-inline-try-catch",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2241-checker-inline-try-catch-expected-stdout",
+        ":art-run-test-2241-checker-inline-try-catch-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2241-checker-inline-try-catch-expected-stdout",
+    out: ["art-run-test-2241-checker-inline-try-catch-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2241-checker-inline-try-catch-expected-stderr",
+    out: ["art-run-test-2241-checker-inline-try-catch-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2241-checker-inline-try-catch/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2241-checker-inline-try-catch/expected-stderr.txt
diff --git a/test/2241-checker-inline-try-catch/expected-stdout.txt b/test/2241-checker-inline-try-catch/expected-stdout.txt
new file mode 100644
index 0000000..6d127d1
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/expected-stdout.txt
@@ -0,0 +1,2 @@
+Finally, a worthy opponent!
+Our battle it will be legendary!
diff --git a/test/2241-checker-inline-try-catch/info.txt b/test/2241-checker-inline-try-catch/info.txt
new file mode 100644
index 0000000..3b86006
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/info.txt
@@ -0,0 +1 @@
+Tests that we inline try catches, as long as we don't inline inside of other try catches.
diff --git a/test/2241-checker-inline-try-catch/src/Main.java b/test/2241-checker-inline-try-catch/src/Main.java
new file mode 100644
index 0000000..a80fbd7
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/src/Main.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+  public static void main(String[] args) {
+    $noinline$testSingleTryCatch();
+    $noinline$testSingleTryCatchTwice();
+    $noinline$testSingleTryCatchDifferentInputs();
+    $noinline$testDifferentTryCatches();
+    $noinline$testTryCatchFinally();
+    $noinline$testTryCatchFinallyDifferentInputs();
+    $noinline$testRecursiveTryCatch();
+    $noinline$testDoNotInlineInsideTryInlineInsideCatch();
+    $noinline$testInlineInsideNestedCatches();
+    $noinline$testBeforeAfterTryCatch();
+    $noinline$testDifferentTypes();
+    $noinline$testRawThrow();
+    $noinline$testRawThrowTwice();
+    $noinline$testThrowCaughtInOuterMethod();
+  }
+
+  public static void $noinline$assertEquals(int expected, int result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  // Basic try catch inline.
+  private static void $noinline$testSingleTryCatch() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+  }
+
+  // Two instances of the same method with a try catch.
+  private static void $noinline$testSingleTryCatchTwice() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+  }
+
+  // Triggering both normal and the exceptional flow.
+  private static void $noinline$testSingleTryCatchDifferentInputs() {
+    $noinline$assertEquals(1, $inline$OOBTryCatch(null));
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+    int[] filled_numbers = {42};
+    $noinline$assertEquals(42, $inline$OOBTryCatch(filled_numbers));
+  }
+
+
+  // Two different try catches, with the same catch's dex_pc.
+  private static void $noinline$testDifferentTryCatches() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+    $noinline$assertEquals(2, $inline$OtherOOBTryCatch(numbers));
+  }
+
+  // Basic try/catch/finally.
+  private static void $noinline$testTryCatchFinally() {
+    int[] numbers = {};
+    $noinline$assertEquals(3, $inline$OOBTryCatchFinally(numbers));
+  }
+
+  // Triggering both normal and the exceptional flow.
+  private static void $noinline$testTryCatchFinallyDifferentInputs() {
+    $noinline$assertEquals(3, $inline$OOBTryCatchFinally(null));
+    int[] numbers = {};
+    $noinline$assertEquals(3, $inline$OOBTryCatchFinally(numbers));
+    int[] filled_numbers = {42};
+    $noinline$assertEquals(42, $inline$OOBTryCatchFinally(filled_numbers));
+  }
+
+  // Test that we can inline even when the try catch is several levels deep.
+  private static void $noinline$testRecursiveTryCatch() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatchLevel4(numbers));
+  }
+
+  // Tests that we don't inline inside outer tries, but we do inline inside of catches.
+  /// CHECK-START: void Main.$noinline$testDoNotInlineInsideTryInlineInsideCatch() inliner (before)
+  /// CHECK:       InvokeStaticOrDirect method_name:Main.DoNotInlineOOBTryCatch
+  /// CHECK:       InvokeStaticOrDirect method_name:Main.$inline$OOBTryCatch
+
+  /// CHECK-START: void Main.$noinline$testDoNotInlineInsideTryInlineInsideCatch() inliner (after)
+  /// CHECK:       InvokeStaticOrDirect method_name:Main.DoNotInlineOOBTryCatch
+  private static void $noinline$testDoNotInlineInsideTryInlineInsideCatch() {
+    int val = 0;
+    try {
+      int[] numbers = {};
+      val = DoNotInlineOOBTryCatch(numbers);
+    } catch (Exception ex) {
+      unreachable();
+      // This is unreachable but we will still compile it so it works for checking that it inlines.
+      int[] numbers = {};
+      $inline$OOBTryCatch(numbers);
+    }
+    $noinline$assertEquals(1, val);
+  }
+
+  private static void $noinline$emptyMethod() {}
+
+  private static void $inline$testInlineInsideNestedCatches_inner() {
+    try {
+      $noinline$emptyMethod();
+    } catch (Exception ex) {
+      int[] numbers = {};
+      $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+    }
+  }
+
+  private static void $noinline$testInlineInsideNestedCatches() {
+    try {
+      $noinline$emptyMethod();
+    } catch (Exception ex) {
+      $inline$testInlineInsideNestedCatches_inner();
+    }
+  }
+
+  // Tests that outer tries or catches don't affect as long as we are not inlining the inner
+  // try/catch inside of them.
+  private static void $noinline$testBeforeAfterTryCatch() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+
+    // Unrelated try catch does not block inlining outside of it. We fill it in to make sure it is
+    // still there by the time the inliner runs.
+    int val = 0;
+    try {
+      int[] other_array = {};
+      val = other_array[0];
+    } catch (Exception ex) {
+      $noinline$assertEquals(0, val);
+      val = 1;
+    }
+    $noinline$assertEquals(1, val);
+
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+  }
+
+  // Tests different try catch types in the same outer method.
+  private static void $noinline$testDifferentTypes() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+    $noinline$assertEquals(2, $inline$OtherOOBTryCatch(numbers));
+    $noinline$assertEquals(123, $inline$ParseIntTryCatch("123"));
+    $noinline$assertEquals(-1, $inline$ParseIntTryCatch("abc"));
+  }
+
+  // Tests a raw throw (rather than an instruction that happens to throw).
+  private static void $noinline$testRawThrow() {
+    $noinline$assertEquals(1, $inline$rawThrowCaught());
+  }
+
+  // Tests a raw throw twice.
+  private static void $noinline$testRawThrowTwice() {
+    $noinline$assertEquals(1, $inline$rawThrowCaught());
+    $noinline$assertEquals(1, $inline$rawThrowCaught());
+  }
+
+  // Tests that the outer method can successfully catch the throw in the inner method.
+  private static void $noinline$testThrowCaughtInOuterMethod() {
+    int[] numbers = {};
+    $noinline$assertEquals(1, $inline$testThrowCaughtInOuterMethod_simpleTryCatch(numbers));
+    $noinline$assertEquals(1, $inline$testThrowCaughtInOuterMethod_simpleTryCatch_inliningInner(numbers));
+    $noinline$assertEquals(1, $inline$testThrowCaughtInOuterMethod_withFinally(numbers));
+  }
+
+  // Building blocks for the test functions.
+  private static int $inline$OOBTryCatch(int[] array) {
+    try {
+      return array[0];
+    } catch (Exception e) {
+      return 1;
+    }
+  }
+
+  private static int $inline$OtherOOBTryCatch(int[] array) {
+    try {
+      return array[0];
+    } catch (Exception e) {
+      return 2;
+    }
+  }
+
+  private static int $inline$OOBTryCatchFinally(int[] array) {
+    int val = 0;
+    try {
+      val = 1;
+      return array[0];
+    } catch (Exception e) {
+      val = 2;
+    } finally {
+      val = 3;
+    }
+    return val;
+  }
+
+  // If we make the depthness a parameter, we wouldn't be able to mark as $inline$ and we would
+  // need extra CHECKer statements.
+  private static int $inline$OOBTryCatchLevel4(int[] array) {
+    return $inline$OOBTryCatchLevel3(array);
+  }
+
+  private static int $inline$OOBTryCatchLevel3(int[] array) {
+    return $inline$OOBTryCatchLevel2(array);
+  }
+
+  private static int $inline$OOBTryCatchLevel2(int[] array) {
+    return $inline$OOBTryCatchLevel1(array);
+  }
+
+  private static int $inline$OOBTryCatchLevel1(int[] array) {
+    return $inline$OOBTryCatch(array);
+  }
+
+  private static int DoNotInlineOOBTryCatch(int[] array) {
+    try {
+      return array[0];
+    } catch (Exception e) {
+      return 1;
+    }
+  }
+
+  private static void unreachable() {
+    throw new Error("Unreachable");
+  }
+
+  private static int $inline$ParseIntTryCatch(String str) {
+    try {
+      return Integer.parseInt(str);
+    } catch (NumberFormatException ex) {
+      return -1;
+    }
+  }
+
+  private static int $inline$rawThrowCaught() {
+    try {
+      throw new Error();
+    } catch (Error e) {
+      return 1;
+    }
+  }
+
+  private static int $inline$testThrowCaughtInOuterMethod_simpleTryCatch(int[] array) {
+    int val = 0;
+    try {
+      $noinline$throwingMethod(array);
+    } catch (Exception ex) {
+      val = 1;
+    }
+    return val;
+  }
+
+  private static int $noinline$throwingMethod(int[] array) {
+    return array[0];
+  }
+
+  private static int $inline$testThrowCaughtInOuterMethod_simpleTryCatch_inliningInner(int[] array) {
+    int val = 0;
+    try {
+      $inline$throwingMethod(array);
+    } catch (Exception ex) {
+      val = 1;
+    }
+    return val;
+  }
+
+  private static int $inline$throwingMethod(int[] array) {
+    return array[0];
+  }
+
+  private static int $inline$testThrowCaughtInOuterMethod_withFinally(int[] array) {
+    int val = 0;
+    try {
+      $noinline$throwingMethodWithFinally(array);
+    } catch (Exception ex) {
+      System.out.println("Our battle it will be legendary!");
+      val = 1;
+    }
+    return val;
+  }
+
+  private static int $noinline$throwingMethodWithFinally(int[] array) {
+    try {
+      return array[0];
+    } finally {
+      System.out.println("Finally, a worthy opponent!");
+    }
+  }
+}
diff --git a/test/2242-checker-lse-acquire-release-operations/Android.bp b/test/2242-checker-lse-acquire-release-operations/Android.bp
new file mode 100644
index 0000000..bb493ce
--- /dev/null
+++ b/test/2242-checker-lse-acquire-release-operations/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2242-checker-lse-acquire-release-operations`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2242-checker-lse-acquire-release-operations",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2242-checker-lse-acquire-release-operations-expected-stdout",
+        ":art-run-test-2242-checker-lse-acquire-release-operations-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2242-checker-lse-acquire-release-operations-expected-stdout",
+    out: ["art-run-test-2242-checker-lse-acquire-release-operations-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2242-checker-lse-acquire-release-operations-expected-stderr",
+    out: ["art-run-test-2242-checker-lse-acquire-release-operations-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2242-checker-lse-acquire-release-operations/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2242-checker-lse-acquire-release-operations/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2242-checker-lse-acquire-release-operations/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2242-checker-lse-acquire-release-operations/expected-stdout.txt
diff --git a/test/2242-checker-lse-acquire-release-operations/info.txt b/test/2242-checker-lse-acquire-release-operations/info.txt
new file mode 100644
index 0000000..efdc63f
--- /dev/null
+++ b/test/2242-checker-lse-acquire-release-operations/info.txt
@@ -0,0 +1,3 @@
+Tests that we perform LSE with graphs with acquire loads
+(i.e. monitor enter and volatile load) and release stores
+(i.e. monitor exit and voaltile stores)
diff --git a/test/2242-checker-lse-acquire-release-operations/src/Main.java b/test/2242-checker-lse-acquire-release-operations/src/Main.java
new file mode 100644
index 0000000..433a4cd
--- /dev/null
+++ b/test/2242-checker-lse-acquire-release-operations/src/Main.java
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2022 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 TestClass {
+  TestClass() {}
+  int i;
+  int j;
+  volatile int vi;
+}
+
+public class Main {
+  public static void main(String[] args) {
+    // Volatile accesses.
+    assertEquals($noinline$testVolatileAccessesMustBeKept(new TestClass()), 3);
+
+    // Volatile loads - Different fields shouldn't alias.
+    assertEquals($noinline$testVolatileLoadDifferentFields(new TestClass(), new TestClass()), 3);
+    assertEquals(
+            $noinline$testVolatileLoadDifferentFieldsBlocking(new TestClass(), new TestClass()),
+            3);
+
+    // Volatile loads - Redundant store.
+    assertEquals($noinline$testVolatileLoadRedundantStore(new TestClass()), 2);
+    assertEquals($noinline$testVolatileLoadRedundantStoreBlocking(new TestClass()), 2);
+    assertEquals($noinline$testVolatileLoadRedundantStoreBlockingOnlyLoad(new TestClass()), 2);
+
+    // Volatile loads - Set and merge values.
+    assertEquals($noinline$testVolatileLoadSetAndMergeValues(new TestClass(), true), 1);
+    assertEquals($noinline$testVolatileLoadSetAndMergeValues(new TestClass(), false), 2);
+    assertEquals($noinline$testVolatileLoadSetAndMergeValuesBlocking(new TestClass(), true), 1);
+    assertEquals($noinline$testVolatileLoadSetAndMergeValuesBlocking(new TestClass(), false), 2);
+
+    // Volatile stores - Different fields shouldn't alias.
+    assertEquals($noinline$testVolatileStoreDifferentFields(new TestClass(), new TestClass()), 3);
+    assertEquals(
+            $noinline$testVolatileStoreDifferentFieldsBlocking(new TestClass(), new TestClass()),
+            3);
+
+    // Volatile stores - Redundant store.
+    assertEquals($noinline$testVolatileStoreRedundantStore(new TestClass()), 2);
+    assertEquals($noinline$testVolatileStoreRedundantStoreBlocking(new TestClass()), 2);
+    assertEquals($noinline$testVolatileStoreRedundantStoreBlockingOnlyLoad(new TestClass()), 2);
+
+    // Volatile stores - Set and merge values.
+    assertEquals($noinline$testVolatileStoreSetAndMergeValues(new TestClass(), true), 1);
+    assertEquals($noinline$testVolatileStoreSetAndMergeValues(new TestClass(), false), 2);
+    assertEquals($noinline$testVolatileStoreSetAndMergeValuesNotBlocking(new TestClass(), true), 1);
+    assertEquals($noinline$testVolatileStoreSetAndMergeValuesNotBlocking(new TestClass(), false), 2);
+
+    // Monitor Operations - Different fields shouldn't alias.
+    assertEquals(
+            $noinline$testMonitorOperationDifferentFields(new TestClass(), new TestClass()), 3);
+    assertEquals($noinline$testMonitorOperationDifferentFieldsBlocking(
+                          new TestClass(), new TestClass()),
+            3);
+
+    // Monitor Operations - Redundant store.
+    assertEquals($noinline$testMonitorOperationRedundantStore(new TestClass()), 2);
+    assertEquals($noinline$testMonitorOperationRedundantStoreBlocking(new TestClass()), 2);
+    assertEquals(
+            $noinline$testMonitorOperationRedundantStoreBlockingOnlyLoad(new TestClass()), 2);
+    assertEquals($noinline$testMonitorOperationRedundantStoreBlockingExit(new TestClass()), 2);
+
+    // Monitor Operations - Set and merge values.
+    assertEquals($noinline$testMonitorOperationSetAndMergeValues(new TestClass(), true), 1);
+    assertEquals($noinline$testMonitorOperationSetAndMergeValues(new TestClass(), false), 2);
+    assertEquals(
+            $noinline$testMonitorOperationSetAndMergeValuesBlocking(new TestClass(), true), 1);
+    assertEquals(
+            $noinline$testMonitorOperationSetAndMergeValuesBlocking(new TestClass(), false), 2);
+  }
+
+  public static void assertEquals(int expected, int result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileAccessesMustBeKept(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testVolatileAccessesMustBeKept(TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  static int $noinline$testVolatileAccessesMustBeKept(TestClass obj1) {
+    int result;
+    obj1.vi = 3;
+    // Redundant load that has to be kept.
+    result = obj1.vi;
+    result = obj1.vi;
+    // Redundant store that has to be kept.
+    obj1.vi = 3;
+    result = obj1.vi;
+    return result;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadDifferentFields(TestClass, TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldGet field_name:TestClass.vi
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet field_name:TestClass.i
+  /// CHECK: InstanceFieldGet field_name:TestClass.j
+  /// CHECK: InstanceFieldGet field_name:TestClass.vi
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadDifferentFields(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldGet field_name:TestClass.vi
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet field_name:TestClass.vi
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadDifferentFields(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet field_name:TestClass.i
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadDifferentFields(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet field_name:TestClass.j
+
+  // Unrelated volatile loads shouldn't block LSE.
+  static int $noinline$testVolatileLoadDifferentFields(TestClass obj1, TestClass obj2) {
+    int unused = obj1.vi;
+    obj1.i = 1;
+    obj2.j = 2;
+    int result = obj1.i + obj2.j;
+    unused = obj1.vi;
+    return result;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadDifferentFieldsBlocking(TestClass, TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadDifferentFieldsBlocking(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+
+  // A volatile load blocks load elimination.
+  static int $noinline$testVolatileLoadDifferentFieldsBlocking(TestClass obj1, TestClass obj2) {
+    obj1.i = 1;
+    obj2.j = 2;
+    int unused = obj1.vi;
+    return obj1.i + obj2.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadRedundantStore(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldGet field_name:TestClass.vi
+  /// CHECK: InstanceFieldSet field_name:TestClass.j
+  /// CHECK: InstanceFieldSet field_name:TestClass.j
+  /// CHECK: InstanceFieldGet field_name:TestClass.j
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadRedundantStore(TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldGet field_name:TestClass.vi
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadRedundantStore(TestClass) load_store_elimination (after)
+  /// CHECK:     InstanceFieldSet field_name:TestClass.j
+  /// CHECK-NOT: InstanceFieldSet field_name:TestClass.j
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadRedundantStore(TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet field_name:TestClass.j
+  static int $noinline$testVolatileLoadRedundantStore(TestClass obj) {
+    int unused = obj.vi;
+    obj.j = 1;
+    obj.j = 2;
+    return obj.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadRedundantStoreBlocking(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadRedundantStoreBlocking(TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet field_name:TestClass.vi
+  /// CHECK: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadRedundantStoreBlocking(TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet field_name:TestClass.j
+  static int $noinline$testVolatileLoadRedundantStoreBlocking(TestClass obj) {
+    // This store must be kept due to the volatile load.
+    obj.j = 1;
+    int unused = obj.vi;
+    obj.j = 2;
+    return obj.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (after)
+  /// CHECK:     InstanceFieldSet
+  /// CHECK-NOT: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+  static int $noinline$testVolatileLoadRedundantStoreBlockingOnlyLoad(TestClass obj) {
+    // This store can be safely removed.
+    obj.j = 1;
+    obj.j = 2;
+    int unused = obj.vi;
+    // This load remains due to the volatile load in the middle.
+    return obj.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadSetAndMergeValues(TestClass, boolean) load_store_elimination (before)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldGet field_name:TestClass.i
+  /// CHECK-DAG: InstanceFieldGet field_name:TestClass.vi
+  /// CHECK-DAG: InstanceFieldGet field_name:TestClass.vi
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldGet field_name:TestClass.vi
+  /// CHECK-DAG: InstanceFieldGet field_name:TestClass.vi
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK: Phi
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet field_name:TestClass.i
+
+  static int $noinline$testVolatileLoadSetAndMergeValues(TestClass obj, boolean b) {
+    if (b) {
+      int unused = obj.vi;
+      obj.i = 1;
+    } else {
+      int unused = obj.vi;
+      obj.i = 2;
+    }
+    return obj.i;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadSetAndMergeValuesBlocking(TestClass, boolean) load_store_elimination (before)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldGet field_name:TestClass.i
+  /// CHECK-DAG: InstanceFieldGet field_name:TestClass.vi
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadSetAndMergeValuesBlocking(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldGet field_name:TestClass.vi
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadSetAndMergeValuesBlocking(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-NOT: Phi
+
+  /// CHECK-START: int Main.$noinline$testVolatileLoadSetAndMergeValuesBlocking(TestClass, boolean) load_store_elimination (after)
+  /// CHECK: InstanceFieldGet
+
+  static int $noinline$testVolatileLoadSetAndMergeValuesBlocking(TestClass obj, boolean b) {
+    if (b) {
+      obj.i = 1;
+    } else {
+      obj.i = 2;
+    }
+    int unused = obj.vi;
+    return obj.i;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreDifferentFields(TestClass, TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet field_name:TestClass.vi
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet field_name:TestClass.i
+  /// CHECK: InstanceFieldGet field_name:TestClass.j
+  /// CHECK: InstanceFieldSet field_name:TestClass.vi
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreDifferentFields(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet field_name:TestClass.vi
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet field_name:TestClass.vi
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreDifferentFields(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet field_name:TestClass.i
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreDifferentFields(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet field_name:TestClass.j
+
+  // Unrelated volatile stores shouldn't block LSE.
+  static int $noinline$testVolatileStoreDifferentFields(TestClass obj1, TestClass obj2) {
+    obj1.vi = 123;
+    obj1.i = 1;
+    obj2.j = 2;
+    int result = obj1.i + obj2.j;
+    obj1.vi = 123;
+    return result;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreDifferentFieldsBlocking(TestClass, TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreDifferentFieldsBlocking(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreDifferentFieldsBlocking(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+
+  // A volatile store doesn't block load elimination, as it doesn't clobber existing values.
+  static int $noinline$testVolatileStoreDifferentFieldsBlocking(TestClass obj1, TestClass obj2) {
+    obj1.i = 1;
+    obj2.j = 2;
+    obj1.vi = 123;
+    return obj1.i + obj2.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreRedundantStore(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet field_name:TestClass.vi
+  /// CHECK: InstanceFieldSet field_name:TestClass.j
+  /// CHECK: InstanceFieldSet field_name:TestClass.j
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreRedundantStore(TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet field_name:TestClass.vi
+  /// CHECK:     InstanceFieldSet field_name:TestClass.j
+  /// CHECK-NOT: InstanceFieldSet field_name:TestClass.j
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreRedundantStore(TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+  static int $noinline$testVolatileStoreRedundantStore(TestClass obj) {
+    obj.vi = 123;
+    obj.j = 1;
+    obj.j = 2;
+    return obj.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreRedundantStoreBlocking(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreRedundantStoreBlocking(TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreRedundantStoreBlocking(TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+  static int $noinline$testVolatileStoreRedundantStoreBlocking(TestClass obj) {
+    // This store must be kept due to the volatile store.
+    obj.j = 1;
+    obj.vi = 123;
+    obj.j = 2;
+    return obj.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet field_name:TestClass.j
+  /// CHECK: InstanceFieldSet field_name:TestClass.j
+  /// CHECK: InstanceFieldSet field_name:TestClass.vi
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (after)
+  /// CHECK:     InstanceFieldSet field_name:TestClass.j
+  /// CHECK-NOT: InstanceFieldSet field_name:TestClass.j
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet field_name:TestClass.vi
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+  static int $noinline$testVolatileStoreRedundantStoreBlockingOnlyLoad(TestClass obj) {
+    // This store can be safely removed.
+    obj.j = 1;
+    obj.j = 2;
+    obj.vi = 123;
+    // This load can also be safely eliminated as the volatile store doesn't clobber values.
+    return obj.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreSetAndMergeValues(TestClass, boolean) load_store_elimination (before)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK: Phi
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+  static int $noinline$testVolatileStoreSetAndMergeValues(TestClass obj, boolean b) {
+    if (b) {
+      obj.vi = 123;
+      obj.i = 1;
+    } else {
+      obj.vi = 123;
+      obj.i = 2;
+    }
+    return obj.i;
+  }
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreSetAndMergeValuesNotBlocking(TestClass, boolean) load_store_elimination (before)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreSetAndMergeValuesNotBlocking(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK: Phi
+
+  /// CHECK-START: int Main.$noinline$testVolatileStoreSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+  static int $noinline$testVolatileStoreSetAndMergeValuesNotBlocking(TestClass obj, boolean b) {
+    if (b) {
+      obj.i = 1;
+    } else {
+      obj.i = 2;
+    }
+    // This volatile store doesn't block the load elimination
+    obj.vi = 123;
+    return obj.i;
+  }
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationDifferentFields(TestClass, TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationDifferentFields(TestClass, TestClass) load_store_elimination (before)
+  /// CHECK: MonitorOperation kind:enter
+  /// CHECK: MonitorOperation kind:exit
+  /// CHECK: MonitorOperation kind:enter
+  /// CHECK: MonitorOperation kind:exit
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationDifferentFields(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationDifferentFields(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+
+  // Unrelated monitor operations shouldn't block LSE.
+  static int $noinline$testMonitorOperationDifferentFields(TestClass obj1, TestClass obj2) {
+    Main m = new Main();
+    synchronized (m) {}
+
+    obj1.i = 1;
+    obj2.j = 2;
+    int result = obj1.i + obj2.j;
+
+    synchronized (m) {}
+
+    return result;
+  }
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationDifferentFieldsBlocking(TestClass, TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationDifferentFieldsBlocking(TestClass, TestClass) load_store_elimination (before)
+  /// CHECK: MonitorOperation kind:enter
+  /// CHECK: MonitorOperation kind:exit
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationDifferentFieldsBlocking(TestClass, TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+  /// CHECK: InstanceFieldGet
+
+  // A synchronized operation blocks loads.
+  static int $noinline$testMonitorOperationDifferentFieldsBlocking(TestClass obj1, TestClass obj2) {
+    Main m = new Main();
+
+    obj1.i = 1;
+    obj2.j = 2;
+    synchronized (m) {
+      return obj1.i + obj2.j;
+    }
+  }
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStore(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStore(TestClass) load_store_elimination (before)
+  /// CHECK: MonitorOperation kind:enter
+  /// CHECK: MonitorOperation kind:exit
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStore(TestClass) load_store_elimination (after)
+  /// CHECK:     InstanceFieldSet
+  /// CHECK-NOT: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStore(TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+
+  static int $noinline$testMonitorOperationRedundantStore(TestClass obj) {
+    Main m = new Main();
+    synchronized (m) {
+      obj.j = 1;
+      obj.j = 2;
+    }
+
+    return obj.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlocking(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlocking(TestClass) load_store_elimination (before)
+  /// CHECK: MonitorOperation kind:enter
+  /// CHECK: MonitorOperation kind:exit
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlocking(TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlocking(TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+
+  static int $noinline$testMonitorOperationRedundantStoreBlocking(TestClass obj) {
+    Main m = new Main();
+
+    // This store must be kept due to the monitor operation.
+    obj.j = 1;
+    synchronized (m) {}
+    obj.j = 2;
+
+    return obj.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (before)
+  /// CHECK: MonitorOperation kind:enter
+  /// CHECK: MonitorOperation kind:exit
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (after)
+  /// CHECK:     InstanceFieldSet
+  /// CHECK-NOT: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlockingOnlyLoad(TestClass) load_store_elimination (after)
+  /// CHECK: InstanceFieldGet
+
+  static int $noinline$testMonitorOperationRedundantStoreBlockingOnlyLoad(TestClass obj) {
+    Main m = new Main();
+
+    // This store can be safely removed.
+    obj.j = 1;
+    obj.j = 2;
+    synchronized (m) {}
+
+    // This load remains due to the monitor operation.
+    return obj.j;
+  }
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlockingExit(TestClass) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldSet
+  /// CHECK: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlockingExit(TestClass) load_store_elimination (before)
+  /// CHECK: MonitorOperation kind:enter
+  /// CHECK: MonitorOperation kind:exit
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlockingExit(TestClass) load_store_elimination (after)
+  /// CHECK:     InstanceFieldSet
+  /// CHECK:     InstanceFieldSet
+  /// CHECK-NOT: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationRedundantStoreBlockingExit(TestClass) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+
+  static int $noinline$testMonitorOperationRedundantStoreBlockingExit(TestClass obj) {
+    Main m = new Main();
+
+    synchronized (m) {
+      // This store can be removed.
+      obj.j = 0;
+      // This store must be kept due to the monitor exit operation.
+      obj.j = 1;
+    }
+    obj.j = 2;
+
+    return obj.j;
+  }
+
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationSetAndMergeValues(TestClass, boolean) load_store_elimination (before)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationSetAndMergeValues(TestClass, boolean) load_store_elimination (before)
+  /// CHECK-DAG: MonitorOperation kind:enter
+  /// CHECK-DAG: MonitorOperation kind:exit
+  /// CHECK-DAG: MonitorOperation kind:enter
+  /// CHECK-DAG: MonitorOperation kind:exit
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK: Phi
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationSetAndMergeValues(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet
+
+  static int $noinline$testMonitorOperationSetAndMergeValues(TestClass obj, boolean b) {
+    Main m = new Main();
+
+    if (b) {
+      synchronized (m) {}
+      obj.i = 1;
+    } else {
+      synchronized (m) {}
+      obj.i = 2;
+    }
+    return obj.i;
+  }
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationSetAndMergeValuesBlocking(TestClass, boolean) load_store_elimination (before)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldGet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationSetAndMergeValuesBlocking(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationSetAndMergeValuesBlocking(TestClass, boolean) load_store_elimination (before)
+  /// CHECK-DAG: MonitorOperation kind:enter
+  /// CHECK-DAG: MonitorOperation kind:exit
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationSetAndMergeValuesBlocking(TestClass, boolean) load_store_elimination (after)
+  /// CHECK-NOT: Phi
+
+  /// CHECK-START: int Main.$noinline$testMonitorOperationSetAndMergeValuesBlocking(TestClass, boolean) load_store_elimination (after)
+  /// CHECK: InstanceFieldGet
+
+  static int $noinline$testMonitorOperationSetAndMergeValuesBlocking(TestClass obj, boolean b) {
+    Main m = new Main();
+
+    if (b) {
+      obj.i = 1;
+    } else {
+      obj.i = 2;
+    }
+    synchronized (m) {}
+    return obj.i;
+  }
+}
diff --git a/test/2243-checker-not-inline-into-throw/Android.bp b/test/2243-checker-not-inline-into-throw/Android.bp
new file mode 100644
index 0000000..78c5c66
--- /dev/null
+++ b/test/2243-checker-not-inline-into-throw/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2243-checker-not-inline-into-throw`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2243-checker-not-inline-into-throw",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2243-checker-not-inline-into-throw-expected-stdout",
+        ":art-run-test-2243-checker-not-inline-into-throw-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2243-checker-not-inline-into-throw-expected-stdout",
+    out: ["art-run-test-2243-checker-not-inline-into-throw-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2243-checker-not-inline-into-throw-expected-stderr",
+    out: ["art-run-test-2243-checker-not-inline-into-throw-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2243-checker-not-inline-into-throw/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2243-checker-not-inline-into-throw/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2243-checker-not-inline-into-throw/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2243-checker-not-inline-into-throw/expected-stdout.txt
diff --git a/test/2243-checker-not-inline-into-throw/info.txt b/test/2243-checker-not-inline-into-throw/info.txt
new file mode 100644
index 0000000..a2ded81
--- /dev/null
+++ b/test/2243-checker-not-inline-into-throw/info.txt
@@ -0,0 +1 @@
+Tests that we don't inline methods if their basic blocks end with a throw.
diff --git a/test/2243-checker-not-inline-into-throw/src/Main.java b/test/2243-checker-not-inline-into-throw/src/Main.java
new file mode 100644
index 0000000..6f1280c
--- /dev/null
+++ b/test/2243-checker-not-inline-into-throw/src/Main.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    try {
+      $noinline$testEndsWithThrow();
+      throw new Exception("Unreachable");
+    } catch (Error expected) {
+    }
+
+    try {
+      $noinline$testEndsWithThrowButNotDirectly();
+      throw new Exception("Unreachable");
+    } catch (Error expected) {
+    }
+  }
+
+  // Empty methods are easy to inline anywhere.
+  private static void easyToInline() {}
+  private static void $inline$easyToInline() {}
+
+  /// CHECK-START: int Main.$noinline$testEndsWithThrow() inliner (before)
+  /// CHECK: InvokeStaticOrDirect method_name:Main.easyToInline
+
+  /// CHECK-START: int Main.$noinline$testEndsWithThrow() inliner (after)
+  /// CHECK: InvokeStaticOrDirect method_name:Main.easyToInline
+  static int $noinline$testEndsWithThrow() {
+    easyToInline();
+    throw new Error("");
+  }
+
+  // Currently we only stop inlining if the method's basic block ends with a throw. We do not stop
+  // inlining for methods that eventually always end with a throw.
+  static int $noinline$testEndsWithThrowButNotDirectly() {
+    $inline$easyToInline();
+    if (justABoolean) {
+      $inline$easyToInline();
+    } else {
+      $inline$easyToInline();
+    }
+    throw new Error("");
+  }
+
+  static boolean justABoolean;
+}
diff --git a/test/2243-single-step-default/Android.bp b/test/2243-single-step-default/Android.bp
new file mode 100644
index 0000000..d7d1b2f
--- /dev/null
+++ b/test/2243-single-step-default/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2243-single-step-default`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2243-single-step-default",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2243-single-step-default-expected-stdout",
+        ":art-run-test-2243-single-step-default-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2243-single-step-default-expected-stdout",
+    out: ["art-run-test-2243-single-step-default-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2243-single-step-default-expected-stderr",
+    out: ["art-run-test-2243-single-step-default-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2243-single-step-default/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2243-single-step-default/expected-stderr.txt
diff --git a/test/2243-single-step-default/expected-stdout.txt b/test/2243-single-step-default/expected-stdout.txt
new file mode 100644
index 0000000..3a1c1bd
--- /dev/null
+++ b/test/2243-single-step-default/expected-stdout.txt
@@ -0,0 +1 @@
+art.Test2243$DefaultImpl::doSomething
diff --git a/test/2243-single-step-default/info.txt b/test/2243-single-step-default/info.txt
new file mode 100644
index 0000000..ada764b
--- /dev/null
+++ b/test/2243-single-step-default/info.txt
@@ -0,0 +1 @@
+Tests that the jmethodIDs are always for canonicalized methods.
diff --git a/test/2243-single-step-default/run.py b/test/2243-single-step-default/run.py
new file mode 100755
index 0000000..832c074
--- /dev/null
+++ b/test/2243-single-step-default/run.py
@@ -0,0 +1,18 @@
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/2243-single-step-default/single_step_helper.cc b/test/2243-single-step-default/single_step_helper.cc
new file mode 100644
index 0000000..432e982
--- /dev/null
+++ b/test/2243-single-step-default/single_step_helper.cc
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "android-base/macros.h"
+#include "jni.h"
+#include "jvmti.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test2243SingleStepDefault {
+
+jmethodID interface_default_method;
+
+static void singleStepCB(jvmtiEnv* jvmti,
+                         JNIEnv* env,
+                         jthread thr,
+                         jmethodID method,
+                         jlocation location ATTRIBUTE_UNUSED) {
+  // We haven't reached the default method yet. Continue single stepping
+  if (method != interface_default_method) {
+    return;
+  }
+
+  // Disable single stepping
+  jvmtiError err = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_SINGLE_STEP, thr);
+  if (JvmtiErrorToException(env, jvmti_env, err)) {
+    return;
+  }
+
+  // Inspect the frame.
+  jint frame_count;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameCount(thr, &frame_count))) {
+    return;
+  }
+  CHECK_GT(frame_count, 0);
+
+  // Check that the method id from the stack frame is same as the one returned
+  // by single step callback
+  jmethodID m = nullptr;
+  jlong loc = -1;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameLocation(thr, 0, &m, &loc))) {
+    return;
+  }
+  CHECK_EQ(m, method) << "Method id from stack walk doesn't match id from single step callback";
+
+  // Check that the method id is also present in the declaring class
+  jclass klass = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetMethodDeclaringClass(m, &klass))) {
+    return;
+  }
+  jint count = 0;
+  jmethodID* methods = nullptr;
+  jvmtiError result = jvmti_env->GetClassMethods(klass, &count, &methods);
+  if (JvmtiErrorToException(env, jvmti_env, result)) {
+    return;
+  }
+
+  bool found_method_id = false;
+  for (int i = 0; i < count; i++) {
+    if (methods[i] == method) {
+      found_method_id = true;
+      break;
+    }
+  }
+  CHECK(found_method_id) << "Couldn't find the method id in the declaring class";
+
+  // Check it isn't copied method.
+  jint access_flags = 0;
+  if (JvmtiErrorToException(env, jvmti, jvmti->GetMethodModifiers(m, &access_flags))) {
+    return;
+  }
+  static constexpr uint32_t kAccCopied = 0x01000000;
+  static constexpr uint32_t kAccIntrinsic = 0x80000000;
+  bool is_copied = ((access_flags & (kAccIntrinsic | kAccCopied)) == kAccCopied);
+  CHECK(!is_copied) << "Got copied methodID. Missed canonicalizing?\n";
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test2243_setSingleStepCallback(JNIEnv* env) {
+  jvmtiEventCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+  callbacks.SingleStep = singleStepCB;
+
+  jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+  JvmtiErrorToException(env, jvmti_env, ret);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test2243_enableSingleStep(JNIEnv* env,
+                                                                     jclass ATTRIBUTE_UNUSED,
+                                                                     jthread thr) {
+  jvmtiError err = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_SINGLE_STEP, thr);
+  JvmtiErrorToException(env, jvmti_env, err);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test2243_setSingleStepUntil(JNIEnv* env,
+                                                                       jclass cl ATTRIBUTE_UNUSED,
+                                                                       jobject method) {
+  interface_default_method = env->FromReflectedMethod(method);
+}
+
+}  // namespace Test2243SingleStepDefault
+}  // namespace art
diff --git a/test/2243-single-step-default/src/Main.java b/test/2243-single-step-default/src/Main.java
new file mode 100644
index 0000000..222fe49
--- /dev/null
+++ b/test/2243-single-step-default/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+    public static void main(String[] args) throws Exception {
+        art.Test2243.run();
+    }
+}
diff --git a/test/2243-single-step-default/src/art/InterfaceWithDefaultMethods.java b/test/2243-single-step-default/src/art/InterfaceWithDefaultMethods.java
new file mode 100644
index 0000000..8c6fc91
--- /dev/null
+++ b/test/2243-single-step-default/src/art/InterfaceWithDefaultMethods.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package art;
+
+interface InterfaceWithDefaultMethods {
+    default void doSomething() {
+        String name = getClass().getName();
+        System.out.println(name + "::doSomething");
+    }
+}
diff --git a/test/2243-single-step-default/src/art/Test2243.java b/test/2243-single-step-default/src/art/Test2243.java
new file mode 100644
index 0000000..68bdcd1
--- /dev/null
+++ b/test/2243-single-step-default/src/art/Test2243.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+public class Test2243 {
+    static Method default_method;
+    static class DefaultImpl implements InterfaceWithDefaultMethods {}
+
+    public static void testDefaultMethod(InterfaceWithDefaultMethods i) {
+        enableSingleStep(Thread.currentThread());
+        i.doSomething();
+    }
+
+    public static void run() throws Exception {
+        setSingleStepCallback();
+        setSingleStepUntil(InterfaceWithDefaultMethods.class.getDeclaredMethod("doSomething"));
+        testDefaultMethod(new DefaultImpl());
+    }
+
+    public static native void setSingleStepCallback();
+    public static native void setSingleStepUntil(Method m);
+    public static native void enableSingleStep(Thread thr);
+}
diff --git a/test/2244-checker-remove-try-boundary/Android.bp b/test/2244-checker-remove-try-boundary/Android.bp
new file mode 100644
index 0000000..9e03692
--- /dev/null
+++ b/test/2244-checker-remove-try-boundary/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2244-checker-remove-try-boundary`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2244-checker-remove-try-boundary",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2244-checker-remove-try-boundary-expected-stdout",
+        ":art-run-test-2244-checker-remove-try-boundary-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2244-checker-remove-try-boundary-expected-stdout",
+    out: ["art-run-test-2244-checker-remove-try-boundary-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2244-checker-remove-try-boundary-expected-stderr",
+    out: ["art-run-test-2244-checker-remove-try-boundary-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2244-checker-remove-try-boundary/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2244-checker-remove-try-boundary/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2244-checker-remove-try-boundary/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2244-checker-remove-try-boundary/expected-stdout.txt
diff --git a/test/2244-checker-remove-try-boundary/info.txt b/test/2244-checker-remove-try-boundary/info.txt
new file mode 100644
index 0000000..4247e14
--- /dev/null
+++ b/test/2244-checker-remove-try-boundary/info.txt
@@ -0,0 +1,2 @@
+Tests that we remove TryBoundary instructions if doesn't contain instructions that can throw.
+Sometimes we can remove the catch blocks too.
diff --git a/test/2244-checker-remove-try-boundary/src/Main.java b/test/2244-checker-remove-try-boundary/src/Main.java
new file mode 100644
index 0000000..65c40c5
--- /dev/null
+++ b/test/2244-checker-remove-try-boundary/src/Main.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    assertEquals(2, $noinline$testDivideOverTen(20));
+    assertEquals(-2, $noinline$testDivideOverTen(-20));
+    assertEquals(0, $noinline$testSimpleDivisionInLoop(0));
+    assertEquals(1, $noinline$testSimpleDivisionInLoop(81));
+    assertEquals(10, $noinline$testOptimizeSeparateBranches(60, true));
+    assertEquals(10, $noinline$testOptimizeSeparateBranches(80, false));
+    assertEquals(1, $noinline$testDoNotOptimizeOneBranchThrows(81, false));
+    assertEquals(-1000, $noinline$testDoNotOptimizeOneBranchThrows(81, true));
+    assertEquals(1, $noinline$testOptimizeAfterOneBranchDisappears(81, false));
+    assertEquals(10, $noinline$testRemoveTryBoundaryNested(60));
+    assertEquals(-2000, $noinline$testRemoveTryBoundaryNestedButNotCatch(60, true));
+    assertEquals(30, $noinline$testRemoveTryBoundaryNestedButNotCatch(60, false));
+    assertEquals(30, $noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(60, false));
+  }
+
+  public static void assertEquals(int expected, int result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  // Check that this version cannot remove the TryBoundary instructions since we may throw.
+
+  /// CHECK-START: int Main.$inline$division(int, int) register (after)
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$inline$division(int, int) register (after)
+  /// CHECK:     flags "catch_block"
+  private static int $inline$division(int a, int b) {
+    try {
+      return a / b;
+    } catch (Error unexpected) {
+      return -1000;
+    }
+  }
+
+  // Check that we can remove the TryBoundary afer inlining since we know we can't throw.
+
+  /// CHECK-START: int Main.$noinline$testDivideOverTen(int) inliner (after)
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testDivideOverTen(int) inliner (after)
+  /// CHECK-NOT: flags "catch_block"
+  private static int $noinline$testDivideOverTen(int a) {
+    return $inline$division(a, 10);
+  }
+
+  /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (before)
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (before)
+  /// CHECK:     flags "catch_block"
+
+  /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (after)
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (after)
+  /// CHECK-NOT: flags "catch_block"
+  private static int $noinline$testSimpleDivisionInLoop(int a) {
+    try {
+      for (int i = 0; i < 4; i++) {
+        a /= 3;
+      }
+    } catch (Error unexpected) {
+      return -1000;
+    }
+    return a;
+  }
+
+  // Even though the `TryBoundary`s are split, we can remove them as nothing in the try can throw.
+
+  /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (before)
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (before)
+  /// CHECK:     flags "catch_block"
+  /// CHECK-NOT: flags "catch_block"
+
+  /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (after)
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (after)
+  /// CHECK-NOT: flags "catch_block"
+  private static int $noinline$testOptimizeSeparateBranches(int a, boolean val) {
+    try {
+      if (val) {
+        // TryBoundary kind:entry
+        a /= 3;
+      } else {
+        // TryBoundary kind:entry
+        a /= 4;
+      }
+      a /= 2;
+      // TryBoundary kind:exit
+    } catch (Error unexpected) {
+      return -1000;
+    }
+    return a;
+  }
+
+  // Even though the `a /= 3;` can't throw, we don't eliminate any `TryBoundary` instructions. This
+  // is because we have the `throw new Error();` in the try as well. We could potentially support
+  // removing some `TryBoundary` instructions and not all in the try, but this would complicate the
+  // code and wouldn't bring code size reductions since we would be unable to remove the catch
+  // block.
+
+  /// CHECK-START: int Main.$noinline$testDoNotOptimizeOneBranchThrows(int, boolean) register (after)
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testDoNotOptimizeOneBranchThrows(int, boolean) register (after)
+  /// CHECK:     flags "catch_block"
+  public static int $noinline$testDoNotOptimizeOneBranchThrows(int a, boolean val) {
+    try {
+      for (int i = 0; i < 4; i++) {
+        // TryBoundary kind:entry
+        a /= 3;
+        // TryBoundary kind:exit
+      }
+
+      if (val) {
+        // TryBoundary kind:entry
+        throw new Error();
+        // TryBoundary kind:exit
+      }
+    } catch (Error e) {
+      return -1000;
+    }
+    return a;
+  }
+
+  // The throw gets eliminated by `SimplifyIfs` in DCE, so we can detect that nothing can throw in
+  // the graph and eliminate the `TryBoundary` instructions. It does so in `after_gvn` since it
+  // requires the VisitIf optimization which happens later in the graph.
+
+  /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (before)
+  /// CHECK:     Throw
+
+  /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (before)
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (before)
+  /// CHECK:     flags "catch_block"
+  /// CHECK-NOT: flags "catch_block"
+
+  /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (after)
+  /// CHECK-NOT: Throw
+
+  /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (after)
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (after)
+  /// CHECK-NOT: flags "catch_block"
+  public static int $noinline$testOptimizeAfterOneBranchDisappears(int a, boolean val) {
+    try {
+      for (int i = 0; i < 4; i++) {
+        // TryBoundary kind:entry
+        a /= 3;
+        // TryBoundary kind:exit
+      }
+
+      if (val && !val) {
+        // TryBoundary kind:entry
+        throw new Error();
+        // TryBoundary kind:exit
+      }
+    } catch (Error e) {
+      return -1000;
+    }
+    return a;
+  }
+
+  /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (before)
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (before)
+  /// CHECK:     flags "catch_block"
+  /// CHECK:     flags "catch_block"
+  /// CHECK-NOT: flags "catch_block"
+
+  /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (after)
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (after)
+  /// CHECK-NOT: flags "catch_block"
+  public static int $noinline$testRemoveTryBoundaryNested(int a) {
+    try {
+      // TryBoundary kind:entry
+      a /= 2;
+      // TryBoundary kind:exit
+      try {
+        // TryBoundary kind:entry
+        a /= 3;
+        // TryBoundary kind:exit
+      } catch (Error e) {
+        return -2000;
+      }
+    } catch (Exception e) {
+      return -1000;
+    }
+    return a;
+  }
+
+  // We can remove the `TryBoundary` instructions surrounding `a /= 2;` but since the inner try can
+  // throw, we must keep both the inner and outer catches as they are catch handlers of the inner
+  // try.
+
+  /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (before)
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (before)
+  /// CHECK:     flags "catch_block"
+  /// CHECK:     flags "catch_block"
+  /// CHECK-NOT: flags "catch_block"
+
+  /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (after)
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (after)
+  /// CHECK:     flags "catch_block"
+  /// CHECK:     flags "catch_block"
+  /// CHECK-NOT: flags "catch_block"
+  public static int $noinline$testRemoveTryBoundaryNestedButNotCatch(int a, boolean val) {
+    try {
+      // TryBoundary kind:entry
+      a /= 2;
+      // TryBoundary kind:exit
+      try {
+        if (val) {
+          // TryBoundary kind:entry
+          throw new Error();
+          // TryBoundary kind:exit
+        }
+        // TryBoundary kind:exit
+      } catch (Error e) {
+        return -2000;
+      }
+    } catch (Exception e) {
+      return -1000;
+    }
+    return a;
+  }
+
+  // We eliminate the return -1000 catch block which is outside of the loop in
+  // dead_code_elimination$initial. We can do so since we eliminated the TryBoundary of `a /= 2;`.
+
+  /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before)
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before)
+  /// CHECK:     flags "catch_block"
+  /// CHECK:     flags "catch_block"
+  /// CHECK:     flags "catch_block"
+  /// CHECK-NOT: flags "catch_block"
+
+  /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before)
+  /// CHECK:     IntConstant -1000
+
+  /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after)
+  /// CHECK:     TryBoundary
+  /// CHECK:     TryBoundary
+  /// CHECK-NOT: TryBoundary
+
+  /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after)
+  /// CHECK:     flags "catch_block"
+  /// CHECK:     flags "catch_block"
+  /// CHECK-NOT: flags "catch_block"
+
+  /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after)
+  /// CHECK-NOT: IntConstant -1000
+
+  // When removing that block, we are removing a block outside of a loop but we still need to update
+  // the loop information in the graph since we removed TryBoundary instructions inside of a loop
+  // and now `a /= 2;` is not considered part of a loop (Cannot throw so it will not `continue` and
+  // will always return).
+
+  /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before)
+  /// CHECK:     Div loop:B2
+
+  /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after)
+  /// CHECK-NOT:  Div loop:B2
+
+  /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after)
+  /// CHECK:      Div
+  /// CHECK-NOT:  Div
+  public static int $noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(
+          int a, boolean val) {
+    try {
+      for (int i = 0; i < 4; ++i) {
+        try {
+          try {
+            if (val) {
+              // TryBoundary kind:entry
+              throw new Error();
+              // TryBoundary kind:exit
+            }
+            // TryBoundary kind:exit
+          } catch (Exception e) {
+              continue;
+          }
+          // TryBoundary kind:entry
+          a /= 2;
+          // TryBoundary kind:exit
+          return a;
+        } catch (Error e) {
+          continue;
+        }
+      }
+    } catch (Exception e) {
+      return -1000;
+    }
+    return a;
+  }
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2245-checker-smali-instance-of-comparison/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2245-checker-smali-instance-of-comparison/expected-stderr.txt
diff --git a/test/2245-checker-smali-instance-of-comparison/expected-stdout.txt b/test/2245-checker-smali-instance-of-comparison/expected-stdout.txt
new file mode 100644
index 0000000..6a5618e
--- /dev/null
+++ b/test/2245-checker-smali-instance-of-comparison/expected-stdout.txt
@@ -0,0 +1 @@
+JNI_OnLoad called
diff --git a/test/2245-checker-smali-instance-of-comparison/info.txt b/test/2245-checker-smali-instance-of-comparison/info.txt
new file mode 100644
index 0000000..5c6df8e
--- /dev/null
+++ b/test/2245-checker-smali-instance-of-comparison/info.txt
@@ -0,0 +1 @@
+Smali test comparing instance-of (which returns 0 or 1) with a constant 2.
diff --git a/test/2245-checker-smali-instance-of-comparison/smali/b_252804549.smali b/test/2245-checker-smali-instance-of-comparison/smali/b_252804549.smali
new file mode 100644
index 0000000..38d4d0c
--- /dev/null
+++ b/test/2245-checker-smali-instance-of-comparison/smali/b_252804549.smali
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2022 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 LB252804549;
+
+.super Ljava/lang/Object;
+
+## CHECK-START: int B252804549.compareInstanceOfWithTwo(java.lang.Object) builder (after)
+## CHECK-DAG: <<Const2:i\d+>>     IntConstant 2
+## CHECK-DAG: <<InstanceOf:z\d+>> InstanceOf
+## CHECK-DAG: <<Eq:z\d+>>         Equal [<<InstanceOf>>,<<Const2>>]
+## CHECK-DAG:                     If [<<Eq>>]
+.method public static compareInstanceOfWithTwo(Ljava/lang/Object;)I
+   .registers 2
+   instance-of v0, p0, Ljava/lang/String;
+   const/4 v1, 0x2
+   # Compare instance-of with 2 (i.e. neither 0 nor 1)
+   if-eq v0, v1, :Lequal
+   const/4 v1, 0x3
+   return v1
+:Lequal
+   return v1
+.end method
diff --git a/test/2245-checker-smali-instance-of-comparison/src/Main.java b/test/2245-checker-smali-instance-of-comparison/src/Main.java
new file mode 100644
index 0000000..cd9003a
--- /dev/null
+++ b/test/2245-checker-smali-instance-of-comparison/src/Main.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import java.lang.reflect.Method;
+
+/**
+ * Smali exercise, copied from 800-smali and modified for this test case.
+ */
+public class Main {
+    public static void main(String[] args) throws Exception {
+        System.loadLibrary(args[0]);
+        Object retValue = Class.forName("B252804549")
+                                  .getDeclaredMethod("compareInstanceOfWithTwo", Object.class)
+                                  .invoke(null, new Object[] {new Object()});
+        if (retValue == null || !retValue.equals(3)) {
+            throw new Exception("Expected 3, but got " + retValue);
+        }
+    }
+}
diff --git a/test/2246-trace-stream/Android.bp b/test/2246-trace-stream/Android.bp
new file mode 100644
index 0000000..bef9deb
--- /dev/null
+++ b/test/2246-trace-stream/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2246-trace-stream`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2246-trace-stream",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2246-trace-stream-expected-stdout",
+        ":art-run-test-2246-trace-stream-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2246-trace-stream-expected-stdout",
+    out: ["art-run-test-2246-trace-stream-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2246-trace-stream-expected-stderr",
+    out: ["art-run-test-2246-trace-stream-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2246-trace-stream/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2246-trace-stream/expected-stderr.txt
diff --git a/test/2246-trace-stream/expected-stdout.txt b/test/2246-trace-stream/expected-stdout.txt
new file mode 100644
index 0000000..d6ad93c
--- /dev/null
+++ b/test/2246-trace-stream/expected-stdout.txt
@@ -0,0 +1,156 @@
+***** streaming test *******
+.>> TestThread2246 java.lang.Thread run ()V Thread.java
+..>> TestThread2246 Main$$ExternalSyntheticLambda0 run ()V D8$$SyntheticClass
+...>> TestThread2246 Main lambda$testTracing$0 ()V Main.java
+....>> TestThread2246 Main <init> ()V Main.java
+.....>> TestThread2246 java.lang.Object <init> ()V Object.java
+.....<< TestThread2246 java.lang.Object <init> ()V Object.java
+....<< TestThread2246 Main <init> ()V Main.java
+....>> TestThread2246 Main $noinline$doSomeWork ()V Main.java
+.....>> TestThread2246 Main callOuterFunction ()V Main.java
+......>> TestThread2246 Main callLeafFunction ()V Main.java
+......<< TestThread2246 Main callLeafFunction ()V Main.java
+.....<< TestThread2246 Main callOuterFunction ()V Main.java
+.....>> TestThread2246 Main callLeafFunction ()V Main.java
+.....<< TestThread2246 Main callLeafFunction ()V Main.java
+....<< TestThread2246 Main $noinline$doSomeWork ()V Main.java
+...<< TestThread2246 Main lambda$testTracing$0 ()V Main.java
+..<< TestThread2246 Main$$ExternalSyntheticLambda0 run ()V D8$$SyntheticClass
+.<< TestThread2246 java.lang.Thread run ()V Thread.java
+.>> main Main main ([Ljava/lang/String;)V Main.java
+..>> main Main testTracing (ZLBaseTraceParser;I)V Main.java
+...>> main Main$VMDebug startMethodTracing (Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V Main.java
+....>> main java.lang.reflect.Method invoke (Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; Method.java
+.....>> main dalvik.system.VMDebug startMethodTracing (Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V VMDebug.java
+......>> main dalvik.system.VMDebug startMethodTracingFd (Ljava/lang/String;IIIZIZ)V VMDebug.java
+......<< main dalvik.system.VMDebug startMethodTracingFd (Ljava/lang/String;IIIZIZ)V VMDebug.java
+.....<< main dalvik.system.VMDebug startMethodTracing (Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V VMDebug.java
+....<< main java.lang.reflect.Method invoke (Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; Method.java
+...<< main Main$VMDebug startMethodTracing (Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V Main.java
+...>> main java.lang.Thread start ()V Thread.java
+....>> main java.lang.ThreadGroup add (Ljava/lang/Thread;)V ThreadGroup.java
+....<< main java.lang.ThreadGroup add (Ljava/lang/Thread;)V ThreadGroup.java
+....>> main java.lang.Thread nativeCreate (Ljava/lang/Thread;JZ)V Thread.java
+....<< main java.lang.Thread nativeCreate (Ljava/lang/Thread;JZ)V Thread.java
+...<< main java.lang.Thread start ()V Thread.java
+...>> main java.lang.Thread join ()V Thread.java
+....>> main java.lang.Thread join (J)V Thread.java
+.....>> main java.lang.System currentTimeMillis ()J System.java
+.....<< main java.lang.System currentTimeMillis ()J System.java
+.....>> main java.lang.Thread isAlive ()Z Thread.java
+.....<< main java.lang.Thread isAlive ()Z Thread.java
+.....>> main java.lang.Object wait (J)V Object.java
+......>> main java.lang.Object wait (JI)V Object.java
+......<< main java.lang.Object wait (JI)V Object.java
+.....<< main java.lang.Object wait (J)V Object.java
+.....>> main java.lang.Thread isAlive ()Z Thread.java
+.....<< main java.lang.Thread isAlive ()Z Thread.java
+....<< main java.lang.Thread join (J)V Thread.java
+...<< main java.lang.Thread join ()V Thread.java
+...>> main Main $noinline$doSomeWork ()V Main.java
+....>> main Main callOuterFunction ()V Main.java
+.....>> main Main callLeafFunction ()V Main.java
+.....<< main Main callLeafFunction ()V Main.java
+....<< main Main callOuterFunction ()V Main.java
+....>> main Main callLeafFunction ()V Main.java
+....<< main Main callLeafFunction ()V Main.java
+...<< main Main $noinline$doSomeWork ()V Main.java
+...>> main Main doSomeWorkThrow ()V Main.java
+....>> main Main callThrowFunction ()V Main.java
+.....>> main java.lang.Exception <init> (Ljava/lang/String;)V Exception.java
+......>> main java.lang.Throwable <init> (Ljava/lang/String;)V Throwable.java
+.......>> main java.lang.Object <init> ()V Object.java
+.......<< main java.lang.Object <init> ()V Object.java
+.......>> main java.util.Collections emptyList ()Ljava/util/List; Collections.java
+.......<< main java.util.Collections emptyList ()Ljava/util/List; Collections.java
+.......>> main java.lang.Throwable fillInStackTrace ()Ljava/lang/Throwable; Throwable.java
+........>> main java.lang.Throwable nativeFillInStackTrace ()Ljava/lang/Object; Throwable.java
+........<< main java.lang.Throwable nativeFillInStackTrace ()Ljava/lang/Object; Throwable.java
+.......<< main java.lang.Throwable fillInStackTrace ()Ljava/lang/Throwable; Throwable.java
+......<< main java.lang.Throwable <init> (Ljava/lang/String;)V Throwable.java
+.....<< main java.lang.Exception <init> (Ljava/lang/String;)V Exception.java
+....<<E main Main callThrowFunction ()V Main.java
+...<< main Main doSomeWorkThrow ()V Main.java
+...>> main Main$VMDebug $noinline$stopMethodTracing ()V Main.java
+***** non streaming test *******
+.>> TestThread2246 java.lang.Thread	run	()V	Thread.java
+..>> TestThread2246 Main$$ExternalSyntheticLambda0	run	()V	D8$$SyntheticClass
+...>> TestThread2246 Main	lambda$testTracing$0	()V	Main.java
+....>> TestThread2246 Main	<init>	()V	Main.java
+.....>> TestThread2246 java.lang.Object	<init>	()V	Object.java
+.....<< TestThread2246 java.lang.Object	<init>	()V	Object.java
+....<< TestThread2246 Main	<init>	()V	Main.java
+....>> TestThread2246 Main	$noinline$doSomeWork	()V	Main.java
+.....>> TestThread2246 Main	callOuterFunction	()V	Main.java
+......>> TestThread2246 Main	callLeafFunction	()V	Main.java
+......<< TestThread2246 Main	callLeafFunction	()V	Main.java
+.....<< TestThread2246 Main	callOuterFunction	()V	Main.java
+.....>> TestThread2246 Main	callLeafFunction	()V	Main.java
+.....<< TestThread2246 Main	callLeafFunction	()V	Main.java
+....<< TestThread2246 Main	$noinline$doSomeWork	()V	Main.java
+...<< TestThread2246 Main	lambda$testTracing$0	()V	Main.java
+..<< TestThread2246 Main$$ExternalSyntheticLambda0	run	()V	D8$$SyntheticClass
+.<< TestThread2246 java.lang.Thread	run	()V	Thread.java
+.>> TestThread2246 java.lang.ThreadGroup	threadTerminated	(Ljava/lang/Thread;)V	ThreadGroup.java
+..>> TestThread2246 java.lang.ThreadGroup	remove	(Ljava/lang/Thread;)V	ThreadGroup.java
+...>> TestThread2246 java.lang.System	arraycopy	(Ljava/lang/Object;ILjava/lang/Object;II)V	System.java
+...<< TestThread2246 java.lang.System	arraycopy	(Ljava/lang/Object;ILjava/lang/Object;II)V	System.java
+..<< TestThread2246 java.lang.ThreadGroup	remove	(Ljava/lang/Thread;)V	ThreadGroup.java
+.<< TestThread2246 java.lang.ThreadGroup	threadTerminated	(Ljava/lang/Thread;)V	ThreadGroup.java
+.>> main Main	main	([Ljava/lang/String;)V	Main.java
+..>> main Main	testTracing	(ZLBaseTraceParser;I)V	Main.java
+...>> main Main$VMDebug	startMethodTracing	(Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V	Main.java
+....>> main java.lang.reflect.Method	invoke	(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;	Method.java
+.....>> main dalvik.system.VMDebug	startMethodTracing	(Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V	VMDebug.java
+......>> main dalvik.system.VMDebug	startMethodTracingFd	(Ljava/lang/String;IIIZIZ)V	VMDebug.java
+......<< main dalvik.system.VMDebug	startMethodTracingFd	(Ljava/lang/String;IIIZIZ)V	VMDebug.java
+.....<< main dalvik.system.VMDebug	startMethodTracing	(Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V	VMDebug.java
+....<< main java.lang.reflect.Method	invoke	(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;	Method.java
+...<< main Main$VMDebug	startMethodTracing	(Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V	Main.java
+...>> main java.lang.Thread	start	()V	Thread.java
+....>> main java.lang.ThreadGroup	add	(Ljava/lang/Thread;)V	ThreadGroup.java
+....<< main java.lang.ThreadGroup	add	(Ljava/lang/Thread;)V	ThreadGroup.java
+....>> main java.lang.Thread	nativeCreate	(Ljava/lang/Thread;JZ)V	Thread.java
+....<< main java.lang.Thread	nativeCreate	(Ljava/lang/Thread;JZ)V	Thread.java
+...<< main java.lang.Thread	start	()V	Thread.java
+...>> main java.lang.Thread	join	()V	Thread.java
+....>> main java.lang.Thread	join	(J)V	Thread.java
+.....>> main java.lang.System	currentTimeMillis	()J	System.java
+.....<< main java.lang.System	currentTimeMillis	()J	System.java
+.....>> main java.lang.Thread	isAlive	()Z	Thread.java
+.....<< main java.lang.Thread	isAlive	()Z	Thread.java
+.....>> main java.lang.Object	wait	(J)V	Object.java
+......>> main java.lang.Object	wait	(JI)V	Object.java
+......<< main java.lang.Object	wait	(JI)V	Object.java
+.....<< main java.lang.Object	wait	(J)V	Object.java
+.....>> main java.lang.Thread	isAlive	()Z	Thread.java
+.....<< main java.lang.Thread	isAlive	()Z	Thread.java
+....<< main java.lang.Thread	join	(J)V	Thread.java
+...<< main java.lang.Thread	join	()V	Thread.java
+...>> main Main	$noinline$doSomeWork	()V	Main.java
+....>> main Main	callOuterFunction	()V	Main.java
+.....>> main Main	callLeafFunction	()V	Main.java
+.....<< main Main	callLeafFunction	()V	Main.java
+....<< main Main	callOuterFunction	()V	Main.java
+....>> main Main	callLeafFunction	()V	Main.java
+....<< main Main	callLeafFunction	()V	Main.java
+...<< main Main	$noinline$doSomeWork	()V	Main.java
+...>> main Main	doSomeWorkThrow	()V	Main.java
+....>> main Main	callThrowFunction	()V	Main.java
+.....>> main java.lang.Exception	<init>	(Ljava/lang/String;)V	Exception.java
+......>> main java.lang.Throwable	<init>	(Ljava/lang/String;)V	Throwable.java
+.......>> main java.lang.Object	<init>	()V	Object.java
+.......<< main java.lang.Object	<init>	()V	Object.java
+.......>> main java.util.Collections	emptyList	()Ljava/util/List;	Collections.java
+.......<< main java.util.Collections	emptyList	()Ljava/util/List;	Collections.java
+.......>> main java.lang.Throwable	fillInStackTrace	()Ljava/lang/Throwable;	Throwable.java
+........>> main java.lang.Throwable	nativeFillInStackTrace	()Ljava/lang/Object;	Throwable.java
+........<< main java.lang.Throwable	nativeFillInStackTrace	()Ljava/lang/Object;	Throwable.java
+.......<< main java.lang.Throwable	fillInStackTrace	()Ljava/lang/Throwable;	Throwable.java
+......<< main java.lang.Throwable	<init>	(Ljava/lang/String;)V	Throwable.java
+.....<< main java.lang.Exception	<init>	(Ljava/lang/String;)V	Exception.java
+....<<E main Main	callThrowFunction	()V	Main.java
+...<< main Main	doSomeWorkThrow	()V	Main.java
+...>> main Main$VMDebug	$noinline$stopMethodTracing	()V	Main.java
+....>> main java.lang.reflect.Method	invoke	(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;	Method.java
+.....>> main dalvik.system.VMDebug	stopMethodTracing	()V	VMDebug.java
diff --git a/test/2246-trace-stream/info.txt b/test/2246-trace-stream/info.txt
new file mode 100644
index 0000000..fa93a97
--- /dev/null
+++ b/test/2246-trace-stream/info.txt
@@ -0,0 +1,2 @@
+Tests streaming method tracing. It verifies the format of the generated file is
+as expected.
diff --git a/test/2246-trace-stream/run.py b/test/2246-trace-stream/run.py
new file mode 100644
index 0000000..955f39a
--- /dev/null
+++ b/test/2246-trace-stream/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # The expected output non debuggable isn't consistent in all configurations.
+  # Investigate why the output is different and update the test to work for non
+  # debuggable runtimes too.
+  ctx.default_run(args,  Xcompiler_option=["--debuggable"])
diff --git a/test/2246-trace-stream/src/BaseTraceParser.java b/test/2246-trace-stream/src/BaseTraceParser.java
new file mode 100644
index 0000000..c4b14aa
--- /dev/null
+++ b/test/2246-trace-stream/src/BaseTraceParser.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+
+abstract class BaseTraceParser {
+    public static final int MAGIC_NUMBER = 0x574f4c53;
+    public static final int DUAL_CLOCK_VERSION = 3;
+    public static final int STREAMING_DUAL_CLOCK_VERSION = 0xF3;
+    public static final String START_SECTION_ID = "*";
+    public static final String METHODS_SECTION_ID = "*methods";
+    public static final String THREADS_SECTION_ID = "*threads";
+    public static final String END_SECTION_ID = "*end";
+
+    public void InitializeParser(File file) throws IOException {
+        dataStream = new DataInputStream(new FileInputStream(file));
+        methodIdMap = new HashMap<Integer, String>();
+        threadIdMap = new HashMap<Integer, String>();
+        nestingLevelMap = new HashMap<Integer, Integer>();
+        threadEventsMap = new HashMap<String, String>();
+    }
+
+    public void closeFile() throws IOException {
+        dataStream.close();
+    }
+
+    public String readString(int numBytes) throws IOException {
+        byte[] buffer = new byte[numBytes];
+        dataStream.readFully(buffer);
+        return new String(buffer, StandardCharsets.UTF_8);
+    }
+
+    public String readLine() throws IOException {
+        StringBuilder sb = new StringBuilder();
+        char lineSeparator = '\n';
+        char c = (char)dataStream.readUnsignedByte();
+        while ( c != lineSeparator) {
+            sb.append(c);
+            c = (char)dataStream.readUnsignedByte();
+        }
+        return sb.toString();
+    }
+
+    public int readNumber(int numBytes) throws IOException {
+        int number = 0;
+        for (int i = 0; i < numBytes; i++) {
+            number += dataStream.readUnsignedByte() << (i * 8);
+        }
+        return number;
+    }
+
+    public void validateTraceHeader(int expectedVersion) throws Exception {
+        // Read 4-byte magicNumber.
+        int magicNumber = readNumber(4);
+        if (magicNumber != MAGIC_NUMBER) {
+            throw new Exception("Magic number doesn't match. Expected "
+                    + Integer.toHexString(MAGIC_NUMBER) + " Got "
+                    + Integer.toHexString(magicNumber));
+        }
+        // Read 2-byte version.
+        int version = readNumber(2);
+        if (version != expectedVersion) {
+            throw new Exception(
+                    "Unexpected version. Expected " + expectedVersion + " Got " + version);
+        }
+        traceFormatVersion = version & 0xF;
+        // Read 2-byte headerLength length.
+        int headerLength = readNumber(2);
+        // Read 8-byte starting time - Ignore timestamps since they are not deterministic.
+        dataStream.skipBytes(8);
+        // 4 byte magicNumber + 2 byte version + 2 byte offset + 8 byte timestamp.
+        int numBytesRead = 16;
+        if (version >= DUAL_CLOCK_VERSION) {
+            // Read 2-byte record size.
+            // TODO(mythria): Check why this is needed. We can derive recordSize from version. Not
+            // sure why this is needed.
+            recordSize = readNumber(2);
+            numBytesRead += 2;
+        }
+        // Skip any padding.
+        if (headerLength > numBytesRead) {
+            dataStream.skipBytes(headerLength - numBytesRead);
+        }
+    }
+
+    public int GetEntryHeader() throws IOException {
+        // Read 2-byte thread-id. On host thread-ids can be greater than 16-bit.
+        int threadId = readNumber(2);
+        if (threadId != 0) {
+            return threadId;
+        }
+        // Read 1-byte header type
+        return readNumber(1);
+    }
+
+    public void ProcessMethodInfoEntry() throws IOException {
+        // Read 2-byte method info size
+        int headerLength = readNumber(2);
+        // Read header size data.
+        String methodInfo = readString(headerLength);
+        String[] tokens = methodInfo.split("\t", 2);
+        // Get methodId and record methodId -> methodName map.
+        int methodId = Integer.decode(tokens[0]);
+        String methodLine = tokens[1].replace('\t', ' ');
+        methodLine = methodLine.substring(0, methodLine.length() - 1);
+        methodIdMap.put(methodId, methodLine);
+    }
+
+    public void ProcessThreadInfoEntry() throws IOException {
+        // Read 2-byte thread id
+        int threadId = readNumber(2);
+        // Read 2-byte thread info size
+        int headerLength = readNumber(2);
+        // Read header size data.
+        String threadInfo = readString(headerLength);
+        threadIdMap.put(threadId, threadInfo);
+    }
+
+    public boolean ShouldIgnoreThread(int threadId) throws Exception {
+        if (threadIdMap.get(threadId).contains("Daemon")) {
+            return true;
+        }
+        return false;
+    }
+
+    public String eventTypeToString(int eventType, int threadId) {
+        if (!nestingLevelMap.containsKey(threadId)) {
+            nestingLevelMap.put(threadId, 0);
+        }
+
+        int nestingLevel = nestingLevelMap.get(threadId);
+        String str = "";
+        for (int i = 0; i < nestingLevel; i++) {
+            str += ".";
+        }
+        switch (eventType) {
+            case 0:
+                nestingLevel++;
+                str += ".>>";
+                break;
+            case 1:
+                nestingLevel--;
+                str += "<<";
+                break;
+            case 2:
+                nestingLevel--;
+                str += "<<E";
+                break;
+            default:
+                str += "??";
+        }
+        nestingLevelMap.put(threadId, nestingLevel);
+        return str;
+    }
+
+    public String ProcessEventEntry(int threadId) throws IOException {
+        // Read 4-byte method value
+        int methodAndEvent = readNumber(4);
+        int methodId = methodAndEvent & ~0x3;
+        int eventType = methodAndEvent & 0x3;
+
+        String str = eventTypeToString(eventType, threadId) + " " + threadIdMap.get(threadId)
+                + " " + methodIdMap.get(methodId);
+        // Depending on the version skip either one or two timestamps.
+        // TODO(mythria): Probably add a check that time stamps are always greater than initial
+        // timestamp.
+        int numBytesTimestamp = (traceFormatVersion == 2) ? 4 : 8;
+        dataStream.skipBytes(numBytesTimestamp);
+        return str;
+    }
+
+    public void UpdateThreadEvents(int threadId, String entry) {
+        String threadName = threadIdMap.get(threadId);
+        if (!threadEventsMap.containsKey(threadName)) {
+            threadEventsMap.put(threadName, entry);
+            return;
+        }
+        threadEventsMap.put(threadName, threadEventsMap.get(threadName) + "\n" + entry);
+    }
+
+    public abstract void CheckTraceFileFormat(File traceFile, int expectedVersion)
+            throws Exception;
+
+    DataInputStream dataStream;
+    HashMap<Integer, String> methodIdMap;
+    HashMap<Integer, String> threadIdMap;
+    HashMap<Integer, Integer> nestingLevelMap;
+    HashMap<String, String> threadEventsMap;
+    int recordSize = 0;
+    int traceFormatVersion = 0;
+}
diff --git a/test/2246-trace-stream/src/Main.java b/test/2246-trace-stream/src/Main.java
new file mode 100644
index 0000000..daada7d
--- /dev/null
+++ b/test/2246-trace-stream/src/Main.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+public class Main {
+    private static final String TEMP_FILE_NAME_PREFIX = "test";
+    private static final String TEMP_FILE_NAME_SUFFIX = ".trace";
+    private static File file;
+
+    public static void main(String[] args) throws Exception {
+        String name = System.getProperty("java.vm.name");
+        if (!"Dalvik".equals(name)) {
+            System.out.println("This test is not supported on " + name);
+            return;
+        }
+        System.out.println("***** streaming test *******");
+        StreamTraceParser stream_parser = new StreamTraceParser();
+        testTracing(
+                /* streaming=*/true, stream_parser, BaseTraceParser.STREAMING_DUAL_CLOCK_VERSION);
+
+        System.out.println("***** non streaming test *******");
+        NonStreamTraceParser non_stream_parser = new NonStreamTraceParser();
+        testTracing(/* streaming=*/false, non_stream_parser, BaseTraceParser.DUAL_CLOCK_VERSION);
+    }
+
+    public static void testTracing(boolean streaming, BaseTraceParser parser, int expected_version)
+            throws Exception {
+        file = createTempFile();
+        FileOutputStream out_file = new FileOutputStream(file);
+        Main m = new Main();
+        Thread t = new Thread(() -> {
+            Main m1 = new Main();
+            m1.$noinline$doSomeWork();
+        }, "TestThread2246");
+        try {
+            if (VMDebug.getMethodTracingMode() != 0) {
+                VMDebug.$noinline$stopMethodTracing();
+            }
+
+            VMDebug.startMethodTracing(file.getPath(), out_file.getFD(), 0, 0, false, 0, streaming);
+            t.start();
+            t.join();
+            m.$noinline$doSomeWork();
+            m.doSomeWorkThrow();
+            VMDebug.$noinline$stopMethodTracing();
+            out_file.close();
+            parser.CheckTraceFileFormat(file, expected_version);
+        } finally {
+            if (out_file != null) {
+                out_file.close();
+            }
+        }
+    }
+
+    private static File createTempFile() throws Exception {
+        try {
+            return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
+        } catch (IOException e) {
+            System.setProperty("java.io.tmpdir", "/data/local/tmp");
+            try {
+                return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
+            } catch (IOException e2) {
+                System.setProperty("java.io.tmpdir", "/sdcard");
+                return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
+            }
+        }
+    }
+
+    public void callOuterFunction() {
+        callLeafFunction();
+    }
+
+    public void callLeafFunction() {}
+
+    public void $noinline$doSomeWork() {
+        callOuterFunction();
+        callLeafFunction();
+    }
+
+    public void callThrowFunction() throws Exception {
+        throw new Exception("test");
+    }
+
+    public void doSomeWorkThrow() {
+        try {
+            callThrowFunction();
+        } catch (Exception e) {
+        }
+    }
+
+    private static class VMDebug {
+        private static final Method startMethodTracingMethod;
+        private static final Method stopMethodTracingMethod;
+        private static final Method getMethodTracingModeMethod;
+        static {
+            try {
+                Class<?> c = Class.forName("dalvik.system.VMDebug");
+                startMethodTracingMethod = c.getDeclaredMethod("startMethodTracing", String.class,
+                        FileDescriptor.class, Integer.TYPE, Integer.TYPE, Boolean.TYPE,
+                        Integer.TYPE, Boolean.TYPE);
+                stopMethodTracingMethod = c.getDeclaredMethod("stopMethodTracing");
+                getMethodTracingModeMethod = c.getDeclaredMethod("getMethodTracingMode");
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public static void startMethodTracing(String filename, FileDescriptor fd, int bufferSize,
+                int flags, boolean samplingEnabled, int intervalUs, boolean streaming)
+                throws Exception {
+            startMethodTracingMethod.invoke(
+                    null, filename, fd, bufferSize, flags, samplingEnabled, intervalUs, streaming);
+        }
+        public static void $noinline$stopMethodTracing() throws Exception {
+            stopMethodTracingMethod.invoke(null);
+        }
+        public static int getMethodTracingMode() throws Exception {
+            return (int) getMethodTracingModeMethod.invoke(null);
+        }
+    }
+}
diff --git a/test/2246-trace-stream/src/NonStreamTraceParser.java b/test/2246-trace-stream/src/NonStreamTraceParser.java
new file mode 100644
index 0000000..7763c9c
--- /dev/null
+++ b/test/2246-trace-stream/src/NonStreamTraceParser.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+
+public class NonStreamTraceParser extends BaseTraceParser {
+
+    public void CheckTraceFileFormat(File file, int expectedVersion) throws Exception {
+        InitializeParser(file);
+
+        // On non-streaming formats, the file starts with information about options and threads and
+        // method information.
+        // Read version string and version.
+        String line = readLine();
+        if (!line.equals("*version")) {
+            throw new Exception("Trace doesn't start with version. Starts with: " + line);
+        }
+        int version = Integer.decode(readLine());
+        if (version != expectedVersion) {
+            throw new Exception("Unexpected version: " + version);
+        }
+
+        // Record numEntries and ignore next few options that provides some metadata.
+        line = readLine();
+        int numEntries = 0;
+        while (!line.startsWith(START_SECTION_ID)) {
+            if (line.startsWith("num-method-calls")) {
+                String[] tokens = line.split("=");
+                numEntries = Integer.decode(tokens[1]);
+            }
+            line = readLine();
+        }
+
+        // This should be threads.
+        if (!line.equals(THREADS_SECTION_ID)) {
+            throw new Exception("Missing information about threads " + line);
+        }
+
+        line = readLine();
+        while (!line.startsWith(START_SECTION_ID)) {
+            String[] threadInfo = line.split("\t", 2);
+            threadIdMap.put(Integer.decode(threadInfo[0]), threadInfo[1]);
+            line = readLine();
+        }
+
+        // Parse methods
+        if (!line.equals(METHODS_SECTION_ID)) {
+            throw new Exception("Missing information about methods " + line);
+        }
+
+        line = readLine();
+        while (!line.startsWith(START_SECTION_ID)) {
+            String[] methodInfo = line.split("\t", 2);
+            methodIdMap.put(Integer.decode(methodInfo[0]), methodInfo[1]);
+            line = readLine();
+        }
+
+        // This should be end
+        if (!line.equals(END_SECTION_ID)) {
+            throw new Exception("Missing end after methods " + line);
+        }
+
+        // Validate the actual data.
+        validateTraceHeader(expectedVersion);
+        boolean hasEntries = true;
+        boolean seenStopTracingMethod = false;
+        for (int i = 0; i < numEntries; i++) {
+            int threadId = GetEntryHeader();
+            String eventString = ProcessEventEntry(threadId);
+            // Ignore daemons (ex: heap task daemon, reference queue daemon) because they may not
+            // be deterministic.
+            if (ShouldIgnoreThread(threadId)) {
+                continue;
+            }
+            // Ignore events after method tracing was stopped. The code that is executed
+            // later could be non-deterministic.
+            if (!seenStopTracingMethod) {
+                UpdateThreadEvents(threadId, eventString);
+            }
+            if (eventString.contains("Main$VMDebug $noinline$stopMethodTracing")) {
+                seenStopTracingMethod = true;
+            }
+        }
+        closeFile();
+
+        // Printout the events.
+        for (String str : threadEventsMap.values()) {
+            System.out.println(str);
+        }
+    }
+}
diff --git a/test/2246-trace-stream/src/StreamTraceParser.java b/test/2246-trace-stream/src/StreamTraceParser.java
new file mode 100644
index 0000000..c723d3c
--- /dev/null
+++ b/test/2246-trace-stream/src/StreamTraceParser.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+
+public class StreamTraceParser extends BaseTraceParser {
+
+    public void CheckTraceFileFormat(File file, int expectedVersion) throws Exception {
+        InitializeParser(file);
+
+        validateTraceHeader(expectedVersion);
+        boolean hasEntries = true;
+        boolean seenStopTracingMethod = false;
+        while (hasEntries) {
+            int headerType = GetEntryHeader();
+            switch (headerType) {
+                case 1:
+                    ProcessMethodInfoEntry();
+                    break;
+                case 2:
+                    ProcessThreadInfoEntry();
+                    break;
+                case 3:
+                    // TODO(mythria): Add test to also check format of trace summary.
+                    hasEntries = false;
+                    break;
+                default:
+                    int threadId = headerType;
+                    String eventString = ProcessEventEntry(threadId);
+                    if (ShouldIgnoreThread(threadId)) {
+                        break;
+                    }
+                    // Ignore events after method tracing was stopped. The code that is executed
+                    // later could be non-deterministic.
+                    if (!seenStopTracingMethod) {
+                        UpdateThreadEvents(threadId, eventString);
+                    }
+                    if (eventString.contains("Main$VMDebug $noinline$stopMethodTracing")) {
+                        seenStopTracingMethod = true;
+                    }
+            }
+        }
+        closeFile();
+
+        // Printout the events.
+        for (String str : threadEventsMap.values()) {
+            System.out.println(str);
+        }
+    }
+}
diff --git a/test/2247-checker-write-barrier-elimination/Android.bp b/test/2247-checker-write-barrier-elimination/Android.bp
new file mode 100644
index 0000000..c9744e9
--- /dev/null
+++ b/test/2247-checker-write-barrier-elimination/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2247-checker-write-barrier-elimination`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2247-checker-write-barrier-elimination",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2247-checker-write-barrier-elimination-expected-stdout",
+        ":art-run-test-2247-checker-write-barrier-elimination-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2247-checker-write-barrier-elimination-expected-stdout",
+    out: ["art-run-test-2247-checker-write-barrier-elimination-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2247-checker-write-barrier-elimination-expected-stderr",
+    out: ["art-run-test-2247-checker-write-barrier-elimination-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2247-checker-write-barrier-elimination/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2247-checker-write-barrier-elimination/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2247-checker-write-barrier-elimination/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2247-checker-write-barrier-elimination/expected-stdout.txt
diff --git a/test/2247-checker-write-barrier-elimination/info.txt b/test/2247-checker-write-barrier-elimination/info.txt
new file mode 100644
index 0000000..2515317
--- /dev/null
+++ b/test/2247-checker-write-barrier-elimination/info.txt
@@ -0,0 +1 @@
+Tests that we eliminate unneeded write barriers.
diff --git a/test/2247-checker-write-barrier-elimination/src/Main.java b/test/2247-checker-write-barrier-elimination/src/Main.java
new file mode 100644
index 0000000..76fb05a
--- /dev/null
+++ b/test/2247-checker-write-barrier-elimination/src/Main.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2022 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 MultipleObject {
+    Object inner;
+    Object inner2;
+
+    static Object inner_static;
+}
+
+public class Main {
+    public static void main(String[] args) throws Error {
+        // Several sets, same receiver.
+        $noinline$testInstanceFieldSets(new Main(), new Object(), new Object(), new Object());
+        $noinline$testStaticFieldSets(new Object(), new Object(), new Object());
+        // Object ArraySets can throw since they need a type check so we cannot perform the
+        // optimization.
+        $noinline$testArraySets(new Object[3], new Object(), new Object(), new Object());
+        // If we are swapping elements in the array, no need for a type check.
+        $noinline$testSwapArray(new Object[3]);
+        // If the array and the values have the same RTI, no need for a type check.
+        $noinline$testArraySetsSameRTI();
+
+        // We cannot rely on `null` sets to perform the optimization.
+        $noinline$testNullInstanceFieldSets(new Main(), new Object());
+        $noinline$testNullStaticFieldSets(new Object());
+        $noinline$testNullArraySets(new Object[3], new Object());
+
+        // Several sets, multiple receivers. (set obj1, obj2, obj1 and see that the card of obj1
+        // gets eliminated)
+        $noinline$testInstanceFieldSetsMultipleReceivers(
+                new Main(), new Object(), new Object(), new Object());
+        $noinline$testStaticFieldSetsMultipleReceivers(new Object(), new Object(), new Object());
+        $noinline$testArraySetsMultipleReceiversSameRTI();
+
+        // The write barrier elimination optimization is blocked by invokes, suspend checks, and
+        // instructions that can throw.
+        $noinline$testInstanceFieldSetsBlocked(
+                new Main(), new Object(), new Object(), new Object());
+        $noinline$testStaticFieldSetsBlocked(new Object(), new Object(), new Object());
+        $noinline$testArraySetsSameRTIBlocked();
+    }
+
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSets(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: InstanceFieldSet field_name:Main.inner field_type:Reference write_barrier_kind:EmitNoNullCheck
+    /// CHECK: InstanceFieldSet field_name:Main.inner2 field_type:Reference write_barrier_kind:DontEmit
+    /// CHECK: InstanceFieldSet field_name:Main.inner3 field_type:Reference write_barrier_kind:DontEmit
+
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSets(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static Main $noinline$testInstanceFieldSets(Main m, Object o, Object o2, Object o3) {
+        m.inner = o;
+        m.inner2 = o2;
+        m.inner3 = o3;
+        return m;
+    }
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSets(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: StaticFieldSet field_name:Main.inner_static field_type:Reference write_barrier_kind:EmitNoNullCheck
+    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:DontEmit
+    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:DontEmit
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSets(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static void $noinline$testStaticFieldSets(Object o, Object o2, Object o3) {
+        inner_static = o;
+        inner_static2 = o2;
+        inner_static3 = o3;
+    }
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySets(java.lang.Object[], java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySets(java.lang.Object[], java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static java.lang.Object[] $noinline$testArraySets(
+            Object[] arr, Object o, Object o2, Object o3) {
+        arr[0] = o;
+        arr[1] = o2;
+        arr[2] = o3;
+        return arr;
+    }
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testSwapArray(java.lang.Object[]) disassembly (after)
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testSwapArray(java.lang.Object[]) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static java.lang.Object[] $noinline$testSwapArray(Object[] arr) {
+        arr[0] = arr[1];
+        arr[1] = arr[2];
+        arr[2] = arr[0];
+        return arr;
+    }
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTI() disassembly (after)
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTI() disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static java.lang.Object[] $noinline$testArraySetsSameRTI() {
+        Object[] arr = new Object[3];
+        arr[0] = inner_static;
+        arr[1] = inner_static2;
+        arr[2] = inner_static3;
+        return arr;
+    }
+
+    /// CHECK-START: Main Main.$noinline$testNullInstanceFieldSets(Main, java.lang.Object) disassembly (after)
+    /// CHECK: InstanceFieldSet field_name:Main.inner field_type:Reference write_barrier_kind:DontEmit
+    /// CHECK: InstanceFieldSet field_name:Main.inner2 field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: InstanceFieldSet field_name:Main.inner3 field_type:Reference write_barrier_kind:DontEmit
+
+    /// CHECK-START: Main Main.$noinline$testNullInstanceFieldSets(Main, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static Main $noinline$testNullInstanceFieldSets(Main m, Object o) {
+        m.inner = null;
+        m.inner2 = o;
+        m.inner3 = null;
+        return m;
+    }
+
+    /// CHECK-START: void Main.$noinline$testNullStaticFieldSets(java.lang.Object) disassembly (after)
+    /// CHECK: StaticFieldSet field_name:Main.inner_static field_type:Reference write_barrier_kind:DontEmit
+    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:DontEmit
+
+    /// CHECK-START: void Main.$noinline$testNullStaticFieldSets(java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static void $noinline$testNullStaticFieldSets(Object o) {
+        inner_static = null;
+        inner_static2 = o;
+        inner_static3 = null;
+    }
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testNullArraySets(java.lang.Object[], java.lang.Object) disassembly (after)
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testNullArraySets(java.lang.Object[], java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static Object[] $noinline$testNullArraySets(Object[] arr, Object o) {
+        arr[0] = null;
+        arr[1] = o;
+        arr[2] = null;
+        return arr;
+    }
+
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsMultipleReceivers(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    // There are two extra card_tables for the initialization of the MultipleObject.
+    /// CHECK: InstanceFieldSet field_name:MultipleObject.inner field_type:Reference write_barrier_kind:EmitNoNullCheck
+    /// CHECK: InstanceFieldSet field_name:MultipleObject.inner field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: InstanceFieldSet field_name:MultipleObject.inner2 field_type:Reference write_barrier_kind:DontEmit
+
+    // Each one of the two NewInstance instructions have their own `card_table` reference.
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsMultipleReceivers(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static Main $noinline$testInstanceFieldSetsMultipleReceivers(
+            Main m, Object o, Object o2, Object o3) throws Error {
+        m.mo = new MultipleObject();
+        m.mo2 = new MultipleObject();
+
+        m.mo.inner = o;
+        // This card table for `m.mo2` can't me removed. Note that in `m.mo2 = new
+        // MultipleObject();` above the receiver is `m`, not `m.mo2.
+        m.mo2.inner = o2;
+        // This card table for `m.mo` can me removed.
+        m.mo.inner2 = o3;
+        return m;
+    }
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSetsMultipleReceivers(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: StaticFieldSet field_name:MultipleObject.inner_static field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:EmitNoNullCheck
+    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:DontEmit
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSetsMultipleReceivers(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static void $noinline$testStaticFieldSetsMultipleReceivers(
+            Object o, Object o2, Object o3) {
+        MultipleObject.inner_static = o;
+        inner_static2 = o2;
+        inner_static3 = o3;
+    }
+
+    /// CHECK-START: java.lang.Object[][] Main.$noinline$testArraySetsMultipleReceiversSameRTI() disassembly (after)
+    // Initializing the values
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+    // Setting the `array_of_arrays`.
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+
+    /// CHECK-START: java.lang.Object[][] Main.$noinline$testArraySetsMultipleReceiversSameRTI() disassembly (after)
+    // Two array sets can't eliminate the write barrier
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    // One write barrier for the array of arrays' sets
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static java.lang.Object[][] $noinline$testArraySetsMultipleReceiversSameRTI() {
+        Object[] arr = new Object[3];
+        Object[] other_arr = new Object[3];
+
+        arr[0] = inner_static;
+        other_arr[1] = inner_static2;
+        arr[2] = inner_static3;
+
+        // Return them so that LSE doesn't delete them
+        Object[][] array_of_arrays = {arr, other_arr};
+        return array_of_arrays;
+    }
+
+    private static void $noinline$emptyMethod() {}
+
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsBlocked(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: InstanceFieldSet field_name:Main.inner field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: InvokeStaticOrDirect method_name:Main.$noinline$emptyMethod
+    /// CHECK: InstanceFieldSet field_name:Main.inner2 field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: MonitorOperation kind:enter
+    /// CHECK: InstanceFieldSet field_name:Main.inner3 field_type:Reference write_barrier_kind:EmitWithNullCheck
+
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsBlocked(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static Main $noinline$testInstanceFieldSetsBlocked(
+            Main m, Object o, Object o2, Object o3) {
+        m.inner = o;
+        $noinline$emptyMethod();
+        m.inner2 = o2;
+        synchronized (m) {
+            m.inner3 = o3;
+        }
+        return m;
+    }
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSetsBlocked(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: StaticFieldSet field_name:Main.inner_static field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: InvokeStaticOrDirect method_name:Main.$noinline$emptyMethod
+    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: MonitorOperation kind:enter
+    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:EmitWithNullCheck
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSetsBlocked(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static void $noinline$testStaticFieldSetsBlocked(Object o, Object o2, Object o3) {
+        inner_static = o;
+        $noinline$emptyMethod();
+        inner_static2 = o2;
+        Main m = new Main();
+        synchronized (m) {
+            inner_static3 = o3;
+        }
+    }
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTIBlocked() disassembly (after)
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: InvokeStaticOrDirect method_name:Main.$noinline$emptyMethod
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: MonitorOperation kind:enter
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTIBlocked() disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static java.lang.Object[] $noinline$testArraySetsSameRTIBlocked() {
+        Object[] arr = new Object[3];
+        arr[0] = inner_static;
+        $noinline$emptyMethod();
+        arr[1] = inner_static2;
+        Main m = new Main();
+        synchronized (m) {
+            arr[2] = inner_static3;
+        }
+        return arr;
+    }
+
+    Object inner;
+    Object inner2;
+    Object inner3;
+
+    MultipleObject mo;
+    MultipleObject mo2;
+
+    static Object inner_static;
+    static Object inner_static2;
+    static Object inner_static3;
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2248-checker-smali-remove-try-until-the-end/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2248-checker-smali-remove-try-until-the-end/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2248-checker-smali-remove-try-until-the-end/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2248-checker-smali-remove-try-until-the-end/expected-stdout.txt
diff --git a/test/2248-checker-smali-remove-try-until-the-end/info.txt b/test/2248-checker-smali-remove-try-until-the-end/info.txt
new file mode 100644
index 0000000..0d7ded0
--- /dev/null
+++ b/test/2248-checker-smali-remove-try-until-the-end/info.txt
@@ -0,0 +1,2 @@
+Smali test checking that we correctly set the domination graph when having a
+try until the end of the method, and the catch doesn't flow to the exit.
diff --git a/test/2248-checker-smali-remove-try-until-the-end/smali/b_260387991.smali b/test/2248-checker-smali-remove-try-until-the-end/smali/b_260387991.smali
new file mode 100644
index 0000000..26d4de7
--- /dev/null
+++ b/test/2248-checker-smali-remove-try-until-the-end/smali/b_260387991.smali
@@ -0,0 +1,45 @@
+#
+# Copyright (C) 2022 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 LB260387991;
+
+.super Ljava/lang/Object;
+
+# When eliminating the unnecessary try and its catch block we turn the
+# TryBoundary instructions into Goto instructions. If one of these
+# instructions is pointing to the exit block, we use its single
+# predecessor instead. If this TryBoundary-turned-into-Goto instruction
+# was the only one pointing to the Exit, we also have to update the dominators.
+
+## CHECK-START: void B260387991.testInfiniteCatch() dead_code_elimination$initial (before)
+## CHECK: TryBoundary
+## CHECK: TryBoundary
+
+## CHECK-START: void B260387991.testInfiniteCatch() dead_code_elimination$initial (after)
+## CHECK-NOT: TryBoundary
+.method public static testInfiniteCatch()V
+   .registers 4
+    const/4 v0, 0x2
+    const/4 v1, 0x4
+    :try_start
+    div-int v0, v1, v0
+    return-void
+    :try_end
+    .catchall {:try_start .. :try_end} :catch_all
+
+    # Infinite catch block which does not lead to the exit block.
+    :catch_all
+    nop
+    goto :catch_all
+.end method
diff --git a/test/2248-checker-smali-remove-try-until-the-end/src/Main.java b/test/2248-checker-smali-remove-try-until-the-end/src/Main.java
new file mode 100644
index 0000000..1ad1a26
--- /dev/null
+++ b/test/2248-checker-smali-remove-try-until-the-end/src/Main.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+    public static void main(String[] args) {}
+}
diff --git a/test/2249-checker-return-try-boundary-exit-in-loop/Android.bp b/test/2249-checker-return-try-boundary-exit-in-loop/Android.bp
new file mode 100644
index 0000000..3bc249e
--- /dev/null
+++ b/test/2249-checker-return-try-boundary-exit-in-loop/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2249-checker-return-try-boundary-exit-in-loop`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2249-checker-return-try-boundary-exit-in-loop",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2249-checker-return-try-boundary-exit-in-loop-expected-stdout",
+        ":art-run-test-2249-checker-return-try-boundary-exit-in-loop-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2249-checker-return-try-boundary-exit-in-loop-expected-stdout",
+    out: ["art-run-test-2249-checker-return-try-boundary-exit-in-loop-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2249-checker-return-try-boundary-exit-in-loop-expected-stderr",
+    out: ["art-run-test-2249-checker-return-try-boundary-exit-in-loop-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2249-checker-return-try-boundary-exit-in-loop/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2249-checker-return-try-boundary-exit-in-loop/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2249-checker-return-try-boundary-exit-in-loop/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2249-checker-return-try-boundary-exit-in-loop/expected-stdout.txt
diff --git a/test/2249-checker-return-try-boundary-exit-in-loop/info.txt b/test/2249-checker-return-try-boundary-exit-in-loop/info.txt
new file mode 100644
index 0000000..db79b12
--- /dev/null
+++ b/test/2249-checker-return-try-boundary-exit-in-loop/info.txt
@@ -0,0 +1,3 @@
+Tests that there is a case in which we have a
+  Return->TryBoundary kind:exit->Exit
+chain inside of a loop, and that said TryBoundary has loop information.
diff --git a/test/2249-checker-return-try-boundary-exit-in-loop/src/Main.java b/test/2249-checker-return-try-boundary-exit-in-loop/src/Main.java
new file mode 100644
index 0000000..070cd9f
--- /dev/null
+++ b/test/2249-checker-return-try-boundary-exit-in-loop/src/Main.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+    public static void main(String[] args) throws Exception {
+        assertNotNull($noinline$testReturnTryBoundaryExitInLoop(new Object()));
+    }
+
+    public static void assertNotNull(Object o) {
+        if (o == null) {
+            throw new Error("Expected not null!");
+        }
+    }
+
+    // Simple method to have a call inside of the synchronized block.
+    private static Object $noinline$call() {
+        return new Object();
+    }
+
+    // Consistency check: Three try boundary kind:exit. One for the explicit try catch, and two for
+    // the synchronized block (normal, and exceptional path).
+
+    /// CHECK-START: java.lang.Object Main.$inline$inner(java.lang.Object) builder (after)
+    /// CHECK:     TryBoundary kind:exit
+    /// CHECK:     TryBoundary kind:exit
+    /// CHECK:     TryBoundary kind:exit
+    /// CHECK-NOT: TryBoundary kind:exit
+
+    /// CHECK-START: java.lang.Object Main.$inline$inner(java.lang.Object) builder (after)
+    /// CHECK: Return loop:B2
+
+    /// CHECK-START: java.lang.Object Main.$inline$inner(java.lang.Object) builder (after)
+    /// CHECK: TryBoundary kind:exit loop:B2
+    /// CHECK: TryBoundary kind:exit loop:B2
+    /// CHECK: TryBoundary kind:exit loop:B2
+    private static Object $inline$inner(Object o) {
+        for (int i = 0; i < 4; i++) {
+            try {
+                synchronized (o) {
+                    return $noinline$call();
+                }
+            } catch (Error e) {
+                continue;
+            }
+        }
+        return null;
+    }
+
+    // Simple outer to inline `inner`.
+    private static Object $noinline$testReturnTryBoundaryExitInLoop(Object o) {
+        return $inline$inner(o);
+    }
+}
diff --git a/test/2250-inline-throw-into-try/Android.bp b/test/2250-inline-throw-into-try/Android.bp
new file mode 100644
index 0000000..6a0a7d6
--- /dev/null
+++ b/test/2250-inline-throw-into-try/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2250-inline-throw-into-try`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2250-inline-throw-into-try",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2250-inline-throw-into-try-expected-stdout",
+        ":art-run-test-2250-inline-throw-into-try-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2250-inline-throw-into-try-expected-stdout",
+    out: ["art-run-test-2250-inline-throw-into-try-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2250-inline-throw-into-try-expected-stderr",
+    out: ["art-run-test-2250-inline-throw-into-try-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2250-inline-throw-into-try/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2250-inline-throw-into-try/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2250-inline-throw-into-try/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2250-inline-throw-into-try/expected-stdout.txt
diff --git a/test/2250-inline-throw-into-try/info.txt b/test/2250-inline-throw-into-try/info.txt
new file mode 100644
index 0000000..4d4cab5
--- /dev/null
+++ b/test/2250-inline-throw-into-try/info.txt
@@ -0,0 +1 @@
+Tests that we inline methods that end with a throw, inside of try blocks.
diff --git a/test/2250-inline-throw-into-try/src/Main.java b/test/2250-inline-throw-into-try/src/Main.java
new file mode 100644
index 0000000..d562279
--- /dev/null
+++ b/test/2250-inline-throw-into-try/src/Main.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+    public static void main(String[] args) throws Exception {
+        // Inline methods that sometimes throw.
+        $noinline$assertEquals(-1000, $noinline$testThrowsWithZero(0));
+        $noinline$assertEquals(1, $noinline$testThrowsWithZero(1));
+
+        // Tests that we can correctly inline even when the throw is not caught.
+        try {
+            $noinline$testThrowNotCaught(0);
+            unreachable();
+        } catch (Error expected) {
+        }
+    }
+
+    public static void $noinline$assertEquals(int expected, int result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+
+    private static int $noinline$testThrowsWithZero(int value) {
+        try {
+            return $inline$throwsWithZeroOrReturns(value);
+        } catch (Error e) {
+            return -1000;
+        }
+    }
+
+    private static int $inline$throwsWithZeroOrReturns(int value) {
+        if (value == 0) {
+            throw new Error("Zero!");
+        } else {
+            return value;
+        }
+    }
+
+    private static int $noinline$testThrowNotCaught(int value) {
+        try {
+            return $inline$throwsWithZeroOrReturns(value);
+        } catch (Exception e) {
+            return -1000;
+        }
+    }
+
+    private static void unreachable() throws Exception{
+        throw new Exception("Unreachable");
+    }
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2251-checker-irreducible-loop-do-not-inline/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2251-checker-irreducible-loop-do-not-inline/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2251-checker-irreducible-loop-do-not-inline/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2251-checker-irreducible-loop-do-not-inline/expected-stdout.txt
diff --git a/test/2251-checker-irreducible-loop-do-not-inline/info.txt b/test/2251-checker-irreducible-loop-do-not-inline/info.txt
new file mode 100644
index 0000000..6feb5a5
--- /dev/null
+++ b/test/2251-checker-irreducible-loop-do-not-inline/info.txt
@@ -0,0 +1,3 @@
+Tests that we don't inline a callee with
+  Return -> TryBoundary ->Exit
+chain if the caller has irreducible loops.
diff --git a/test/2251-checker-irreducible-loop-do-not-inline/smali/IrreducibleLoop.smali b/test/2251-checker-irreducible-loop-do-not-inline/smali/IrreducibleLoop.smali
new file mode 100644
index 0000000..700f73c
--- /dev/null
+++ b/test/2251-checker-irreducible-loop-do-not-inline/smali/IrreducibleLoop.smali
@@ -0,0 +1,58 @@
+# Copyright (C) 2022 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 LIrreducibleLoop;
+
+.super Ljava/lang/Object;
+
+# Back-edges in the ascii-art graphs are represented with dash '-'.
+#
+# Check that testDoNotInlineInner has a simple irreducible loop
+#
+#        entry
+#       /    \
+#      /      \
+# loop_entry   \
+#    /    \-    \
+# try_start\-    \
+#           other_loop_entry
+#
+# Consistency check: we didn't optimize away the irreducible loop
+## CHECK-START: java.lang.Object IrreducibleLoop.testDoNotInlineInner(java.lang.Object) register (after)
+## CHECK: irreducible:true
+#
+# We shouldn't inline `inner`.
+## CHECK-START: java.lang.Object IrreducibleLoop.testDoNotInlineInner(java.lang.Object) inliner (before)
+## CHECK: InvokeStaticOrDirect method_name:Main.inner
+#
+## CHECK-START: java.lang.Object IrreducibleLoop.testDoNotInlineInner(java.lang.Object) inliner (after)
+## CHECK: InvokeStaticOrDirect method_name:Main.inner
+.method public static testDoNotInlineInner(Ljava/lang/Object;)Ljava/lang/Object;
+  .registers 3
+  const/16 v0, 42
+  const/16 v1, 21
+  # Irreducible loop
+  if-eq v1, v0, :other_loop_entry
+  :loop_entry
+  if-ne v1, v0, :continue
+  add-int v0, v0, v0
+  :other_loop_entry
+  add-int v0, v0, v0
+  goto :loop_entry
+
+  :continue
+  invoke-static {p0}, LMain;->inner(Ljava/lang/Object;)Ljava/lang/Object;
+  move-result-object v0
+  return-object v0
+.end method
diff --git a/test/2251-checker-irreducible-loop-do-not-inline/src/Main.java b/test/2251-checker-irreducible-loop-do-not-inline/src/Main.java
new file mode 100644
index 0000000..94815db
--- /dev/null
+++ b/test/2251-checker-irreducible-loop-do-not-inline/src/Main.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import java.lang.reflect.Method;
+
+public class Main {
+    public static void main(String[] args) throws Exception {
+        Object[] arguments = {new Object()};
+        Object result = Class.forName("IrreducibleLoop")
+                                .getMethod("testDoNotInlineInner", Object.class)
+                                .invoke(null, arguments);
+        if (result == null) {
+            throw new Exception("Expected non-null result");
+        }
+    }
+
+    // Simple method to have a call inside of the synchronized block.
+    private static Object $noinline$call() {
+        return new Object();
+    }
+
+    // `inner` has a Return -> TryBoundary -> Exit chain, which means that when we inline it we
+    // would need to recompute the loop information.
+
+    // Consistency check: Three try boundary kind:exit. One for the explicit try catch, and two for
+    // the synchronized block (normal, and exceptional path).
+
+    /// CHECK-START: java.lang.Object Main.inner(java.lang.Object) builder (after)
+    /// CHECK:     TryBoundary kind:exit
+    /// CHECK:     TryBoundary kind:exit
+    /// CHECK:     TryBoundary kind:exit
+    /// CHECK-NOT: TryBoundary kind:exit
+
+    /// CHECK-START: java.lang.Object Main.inner(java.lang.Object) builder (after)
+    /// CHECK: Return loop:B2
+
+    /// CHECK-START: java.lang.Object Main.inner(java.lang.Object) builder (after)
+    /// CHECK: TryBoundary kind:exit loop:B2
+    /// CHECK: TryBoundary kind:exit loop:B2
+    /// CHECK: TryBoundary kind:exit loop:B2
+    public static Object inner(Object o) {
+        for (int i = 0; i < 4; i++) {
+            try {
+                synchronized (o) {
+                    return $noinline$call();
+                }
+            } catch (Error e) {
+                continue;
+            }
+        }
+        return null;
+    }
+}
diff --git a/test/2252-rem-optimization-dividend-divisor/Android.bp b/test/2252-rem-optimization-dividend-divisor/Android.bp
new file mode 100644
index 0000000..75dad8a
--- /dev/null
+++ b/test/2252-rem-optimization-dividend-divisor/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2252-rem-optimization-dividend-divisor`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2252-rem-optimization-dividend-divisor",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2252-rem-optimization-dividend-divisor-expected-stdout",
+        ":art-run-test-2252-rem-optimization-dividend-divisor-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2252-rem-optimization-dividend-divisor-expected-stdout",
+    out: ["art-run-test-2252-rem-optimization-dividend-divisor-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2252-rem-optimization-dividend-divisor-expected-stderr",
+    out: ["art-run-test-2252-rem-optimization-dividend-divisor-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2252-rem-optimization-dividend-divisor/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2252-rem-optimization-dividend-divisor/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2252-rem-optimization-dividend-divisor/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2252-rem-optimization-dividend-divisor/expected-stdout.txt
diff --git a/test/2252-rem-optimization-dividend-divisor/info.txt b/test/2252-rem-optimization-dividend-divisor/info.txt
new file mode 100644
index 0000000..57e8b2e
--- /dev/null
+++ b/test/2252-rem-optimization-dividend-divisor/info.txt
@@ -0,0 +1,2 @@
+Test checking that FindDivWithInputsInBasicBlock works correctly
+if the dividend equals the divisor.
diff --git a/test/2252-rem-optimization-dividend-divisor/src/Main.java b/test/2252-rem-optimization-dividend-divisor/src/Main.java
new file mode 100644
index 0000000..1e1a674
--- /dev/null
+++ b/test/2252-rem-optimization-dividend-divisor/src/Main.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Main {
+    public static void main(String[] args) {
+        $noinline$assertEquals(0, $noinline$testRemCaller());
+    }
+
+    public static void $noinline$assertEquals(int expected, int result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+
+    private static int $noinline$testRemCaller() {
+        return $inline$remMethod(50);
+    }
+
+    private static int $inline$remMethod(int param) {
+        // We were replacing this Rem with the div below when both the dividend and the
+        // divisor were the same. We shouldn't do that since we didn't find a Div(50, 50).
+        int result = param % 50;
+        return result / 50;
+    }
+}
diff --git a/test/2253-checker-devirtualize-always-throws/Android.bp b/test/2253-checker-devirtualize-always-throws/Android.bp
new file mode 100644
index 0000000..c818281
--- /dev/null
+++ b/test/2253-checker-devirtualize-always-throws/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2253-checker-devirtualize-always-throws`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2253-checker-devirtualize-always-throws",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2253-checker-devirtualize-always-throws-expected-stdout",
+        ":art-run-test-2253-checker-devirtualize-always-throws-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2253-checker-devirtualize-always-throws-expected-stdout",
+    out: ["art-run-test-2253-checker-devirtualize-always-throws-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2253-checker-devirtualize-always-throws-expected-stderr",
+    out: ["art-run-test-2253-checker-devirtualize-always-throws-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2253-checker-devirtualize-always-throws/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2253-checker-devirtualize-always-throws/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2253-checker-devirtualize-always-throws/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2253-checker-devirtualize-always-throws/expected-stdout.txt
diff --git a/test/2253-checker-devirtualize-always-throws/info.txt b/test/2253-checker-devirtualize-always-throws/info.txt
new file mode 100644
index 0000000..ea76478
--- /dev/null
+++ b/test/2253-checker-devirtualize-always-throws/info.txt
@@ -0,0 +1,3 @@
+Tests that if we devirtualize a method that throws given the
+parameters, then we should also set the devirtualization as always
+throws.
diff --git a/test/2253-checker-devirtualize-always-throws/src/Main.java b/test/2253-checker-devirtualize-always-throws/src/Main.java
new file mode 100644
index 0000000..b6d2424
--- /dev/null
+++ b/test/2253-checker-devirtualize-always-throws/src/Main.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public final class Main {
+    public static void main(String[] args) {
+        try {
+            $noinline$testAlwaysThrowsDevirtualization();
+            System.out.println("Expected to throw error.");
+        } catch (Error expected) {
+        }
+    }
+
+    public void throwsIfParamIsZero(int param) {
+        if (param == 0) {
+            throw new Error("");
+        }
+    }
+
+    /// CHECK-START: void Main.$noinline$testAlwaysThrowsDevirtualization() inliner (before)
+    /// CHECK:     InvokeVirtual method_name:Main.throwsIfParamIsZero always_throws:false
+    /// CHECK:     ReturnVoid
+
+    /// CHECK-START: void Main.$noinline$testAlwaysThrowsDevirtualization() inliner (after)
+    /// CHECK-NOT: InvokeVirtual
+
+    /// CHECK-START: void Main.$noinline$testAlwaysThrowsDevirtualization() inliner (after)
+    /// CHECK:     InvokeStaticOrDirect method_name:Main.throwsIfParamIsZero always_throws:true
+
+    public static void $noinline$testAlwaysThrowsDevirtualization() {
+        Main m = new Main();
+        m.throwsIfParamIsZero(0);
+    }
+}
diff --git a/test/2254-checker-not-var-analyzed-pathological/Android.bp b/test/2254-checker-not-var-analyzed-pathological/Android.bp
new file mode 100644
index 0000000..deb5c93
--- /dev/null
+++ b/test/2254-checker-not-var-analyzed-pathological/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2254-checker-not-var-analyzed-pathological`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2254-checker-not-var-analyzed-pathological",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2254-checker-not-var-analyzed-pathological-expected-stdout",
+        ":art-run-test-2254-checker-not-var-analyzed-pathological-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2254-checker-not-var-analyzed-pathological-expected-stdout",
+    out: ["art-run-test-2254-checker-not-var-analyzed-pathological-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2254-checker-not-var-analyzed-pathological-expected-stderr",
+    out: ["art-run-test-2254-checker-not-var-analyzed-pathological-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2254-checker-not-var-analyzed-pathological/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2254-checker-not-var-analyzed-pathological/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2254-checker-not-var-analyzed-pathological/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2254-checker-not-var-analyzed-pathological/expected-stdout.txt
diff --git a/test/2254-checker-not-var-analyzed-pathological/info.txt b/test/2254-checker-not-var-analyzed-pathological/info.txt
new file mode 100644
index 0000000..616c1e7
--- /dev/null
+++ b/test/2254-checker-not-var-analyzed-pathological/info.txt
@@ -0,0 +1,2 @@
+Make sure that a pathological case in induction var analysis
+doesn't hang up the compiler.
diff --git a/test/2254-checker-not-var-analyzed-pathological/src/Main.java b/test/2254-checker-not-var-analyzed-pathological/src/Main.java
new file mode 100644
index 0000000..484f866
--- /dev/null
+++ b/test/2254-checker-not-var-analyzed-pathological/src/Main.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Main {
+    public static void main(String[] args) {
+        $noinline$assertEquals(0, $noinline$pathologicalCase());
+    }
+
+    public static void $noinline$assertEquals(int expected, int result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+
+    // Empty $noinline$ method so that it doesn't get removed.
+    private static void $noinline$emptyMethod(int val) {}
+
+    // A pathological case which has > 15 loop header phis in a row.
+    /// CHECK-START: int Main.$noinline$pathologicalCase() induction_var_analysis (before)
+    /// CHECK: <<Const0:i\d+>> IntConstant 0
+    /// CHECK: <<Phi1:i\d+>>  Phi [<<Const0>>,<<Add1:i\d+>>]
+    /// CHECK: <<Phi2:i\d+>>  Phi [<<Phi1>>,<<Add2:i\d+>>]
+    /// CHECK: <<Phi3:i\d+>>  Phi [<<Phi2>>,<<Add3:i\d+>>]
+    /// CHECK: <<Phi4:i\d+>>  Phi [<<Phi3>>,<<Add4:i\d+>>]
+    /// CHECK: <<Phi5:i\d+>>  Phi [<<Phi4>>,<<Add5:i\d+>>]
+    /// CHECK: <<Phi6:i\d+>>  Phi [<<Phi5>>,<<Add6:i\d+>>]
+    /// CHECK: <<Phi7:i\d+>>  Phi [<<Phi6>>,<<Add7:i\d+>>]
+    /// CHECK: <<Phi8:i\d+>>  Phi [<<Phi7>>,<<Add8:i\d+>>]
+    /// CHECK: <<Phi9:i\d+>>  Phi [<<Phi8>>,<<Add9:i\d+>>]
+    /// CHECK: <<Phi10:i\d+>> Phi [<<Phi9>>,<<Add10:i\d+>>]
+    /// CHECK: <<Phi11:i\d+>> Phi [<<Phi10>>,<<Add11:i\d+>>]
+    /// CHECK: <<Phi12:i\d+>> Phi [<<Phi11>>,<<Add12:i\d+>>]
+    /// CHECK: <<Phi13:i\d+>> Phi [<<Phi12>>,<<Add13:i\d+>>]
+    /// CHECK: <<Phi14:i\d+>> Phi [<<Phi13>>,<<Add14:i\d+>>]
+    /// CHECK: <<Phi15:i\d+>> Phi [<<Phi14>>,<<Add15:i\d+>>]
+    /// CHECK: <<Phi16:i\d+>> Phi [<<Phi15>>,<<Add16:i\d+>>]
+    private static int $noinline$pathologicalCase() {
+        int value = 0;
+        for (; value < 3; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 5; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 7; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 9; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 11; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 13; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 15; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 17; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 19; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 21; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 23; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 25; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 27; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 29; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 31; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        for (; value < 33; value++) {
+            $noinline$emptyMethod(value);
+        }
+
+        return 0;
+    }
+}
diff --git a/test/2254-class-value-before-and-after-u/Android.bp b/test/2254-class-value-before-and-after-u/Android.bp
new file mode 100644
index 0000000..25c25fb
--- /dev/null
+++ b/test/2254-class-value-before-and-after-u/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2254-class-value-before-and-after-u`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2254-class-value-before-and-after-u",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src-art/**/*.java"],
+    data: [
+        ":art-run-test-2254-class-value-before-and-after-u-expected-stdout",
+        ":art-run-test-2254-class-value-before-and-after-u-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2254-class-value-before-and-after-u-expected-stdout",
+    out: ["art-run-test-2254-class-value-before-and-after-u-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2254-class-value-before-and-after-u-expected-stderr",
+    out: ["art-run-test-2254-class-value-before-and-after-u-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2254-class-value-before-and-after-u/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2254-class-value-before-and-after-u/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2254-class-value-before-and-after-u/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2254-class-value-before-and-after-u/expected-stdout.txt
diff --git a/test/2254-class-value-before-and-after-u/info.txt b/test/2254-class-value-before-and-after-u/info.txt
new file mode 100644
index 0000000..3a8d31f
--- /dev/null
+++ b/test/2254-class-value-before-and-after-u/info.txt
@@ -0,0 +1,2 @@
+java.lang.ClassValue should be visible to U or newer Android, but on older versions
+Class.forName("java.lang.ClassValue") should throw CNFE. See b/259501764.
\ No newline at end of file
diff --git a/test/2254-class-value-before-and-after-u/src-art/Main.java b/test/2254-class-value-before-and-after-u/src-art/Main.java
new file mode 100644
index 0000000..53704d7
--- /dev/null
+++ b/test/2254-class-value-before-and-after-u/src-art/Main.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+import dalvik.system.VMRuntime;
+
+public class Main {
+    public static void main(String[] args) {
+        VMRuntime.getRuntime().setTargetSdkVersion(0);
+        try {
+            Class classValueClass = Class.forName("java.lang.ClassValue");
+        } catch (ClassNotFoundException ignored) {
+            throw new Error(
+                    "java.lang.ClassValue should be available when targetSdkLevel is not set");
+        }
+
+        VMRuntime.getRuntime().setTargetSdkVersion(34);
+
+        try {
+            Class classValueClass = Class.forName("java.lang.ClassValue");
+        } catch (ClassNotFoundException ignored) {
+            throw new Error("java.lang.ClassValue should be available on targetSdkLevel 34");
+        }
+
+        VMRuntime.getRuntime().setTargetSdkVersion(33);
+        try {
+            Class classValueClass = Class.forName("java.lang.ClassValue");
+            throw new Error("Was able to find " + classValueClass + " on targetSdkLevel 33");
+        } catch (ClassNotFoundException expected) {
+            if (!expected.getMessage().contains("java.lang.ClassValue")) {
+                throw new Error("Thrown exception should contain class name, but was: " + expected);
+            }
+        }
+    }
+}
diff --git a/test/2255-checker-branch-redirection/Android.bp b/test/2255-checker-branch-redirection/Android.bp
new file mode 100644
index 0000000..b7ac541
--- /dev/null
+++ b/test/2255-checker-branch-redirection/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2255-checker-branch-redirection`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2255-checker-branch-redirection",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2255-checker-branch-redirection-expected-stdout",
+        ":art-run-test-2255-checker-branch-redirection-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2255-checker-branch-redirection-expected-stdout",
+    out: ["art-run-test-2255-checker-branch-redirection-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2255-checker-branch-redirection-expected-stderr",
+    out: ["art-run-test-2255-checker-branch-redirection-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2255-checker-branch-redirection/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2255-checker-branch-redirection/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2255-checker-branch-redirection/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2255-checker-branch-redirection/expected-stdout.txt
diff --git a/test/2255-checker-branch-redirection/info.txt b/test/2255-checker-branch-redirection/info.txt
new file mode 100644
index 0000000..10b71ef
--- /dev/null
+++ b/test/2255-checker-branch-redirection/info.txt
@@ -0,0 +1,2 @@
+Tests that we can redirect branches if the block and its dominator
+have the same condition, or the exact opposite condition.
diff --git a/test/2255-checker-branch-redirection/src/Main.java b/test/2255-checker-branch-redirection/src/Main.java
new file mode 100644
index 0000000..bfc6381
--- /dev/null
+++ b/test/2255-checker-branch-redirection/src/Main.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Main {
+    public static void main(String[] args) throws Exception {
+        assertEquals(40, $noinline$testEliminateIf(20, 40));
+        assertEquals(30, $noinline$testEliminateIf(20, 10));
+        assertEquals(40, $noinline$testEliminateIfTwiceInARow(20, 40));
+        assertEquals(30, $noinline$testEliminateIfTwiceInARow(20, 10));
+        assertEquals(40, $noinline$testEliminateIfThreePredecessors(20, 40));
+        assertEquals(30, $noinline$testEliminateIfThreePredecessors(20, 10));
+        assertEquals(40, $noinline$testEliminateIfOppositeCondition(20, 40));
+        assertEquals(30, $noinline$testEliminateIfOppositeCondition(20, 10));
+        assertEquals(40, $noinline$testEliminateIfParameter(20, 40, 20 < 40));
+        assertEquals(30, $noinline$testEliminateIfParameter(20, 10, 20 < 10));
+        assertEquals(40, $noinline$testEliminateIfParameterReverseCondition(20, 40, 20 < 40));
+        assertEquals(30, $noinline$testEliminateIfParameterReverseCondition(20, 10, 20 < 10));
+        assertEquals(40, $noinline$testEliminateIfParameterOppositeCondition(20, 40, 20 < 40));
+        assertEquals(30, $noinline$testEliminateIfParameterOppositeCondition(20, 10, 20 < 10));
+        assertEquals(40, $noinline$testEliminateIfParameterOppositeCondition_2(20, 40, 20 < 40));
+        assertEquals(30, $noinline$testEliminateIfParameterOppositeCondition_2(20, 10, 20 < 10));
+    }
+
+    private static int $noinline$emptyMethod(int a) {
+        return a;
+    }
+
+    /// CHECK-START: int Main.$noinline$testEliminateIf(int, int) dead_code_elimination$after_gvn (before)
+    /// CHECK:     If
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIf(int, int) dead_code_elimination$after_gvn (after)
+    /// CHECK:     If
+    /// CHECK-NOT: If
+    private static int $noinline$testEliminateIf(int a, int b) {
+        int result = 0;
+        if (a < b) {
+            $noinline$emptyMethod(a + b);
+        } else {
+            $noinline$emptyMethod(a - b);
+        }
+        if (a < b) {
+            result += $noinline$emptyMethod(a * 2);
+        } else {
+            result += $noinline$emptyMethod(b * 3);
+        }
+        return result;
+    }
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfTwiceInARow(int, int) dead_code_elimination$after_gvn (before)
+    /// CHECK:     If
+    /// CHECK:     If
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfTwiceInARow(int, int) dead_code_elimination$after_gvn (after)
+    /// CHECK:     If
+    /// CHECK-NOT: If
+    private static int $noinline$testEliminateIfTwiceInARow(int a, int b) {
+        int result = 0;
+        if (a < b) {
+            $noinline$emptyMethod(a + b);
+        } else {
+            $noinline$emptyMethod(a - b);
+        }
+        if (a < b) {
+            $noinline$emptyMethod(a * 2);
+        } else {
+            $noinline$emptyMethod(b * 3);
+        }
+        if (a < b) {
+            result += $noinline$emptyMethod(40);
+        } else {
+            result += $noinline$emptyMethod(30);
+        }
+        return result;
+    }
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfThreePredecessors(int, int) dead_code_elimination$after_gvn (before)
+    /// CHECK:     If
+    /// CHECK:     If
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfThreePredecessors(int, int) dead_code_elimination$after_gvn (after)
+    /// CHECK:     If
+    /// CHECK:     If
+    /// CHECK-NOT: If
+    private static int $noinline$testEliminateIfThreePredecessors(int a, int b) {
+        int result = 0;
+        if (a < b) {
+            $noinline$emptyMethod(a + b);
+        } else {
+            if (b < 5) {
+                $noinline$emptyMethod(a - b);
+            } else {
+                $noinline$emptyMethod(a * b);
+            }
+        }
+        if (a < b) {
+            result += $noinline$emptyMethod(a * 2);
+        } else {
+            result += $noinline$emptyMethod(b * 3);
+        }
+        return result;
+    }
+
+    // Note that we can perform this optimization in dead_code_elimination$initial since we don't
+    // rely on gvn to de-duplicate the values.
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfOppositeCondition(int, int) dead_code_elimination$initial (before)
+    /// CHECK:     If
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfOppositeCondition(int, int) dead_code_elimination$initial (after)
+    /// CHECK:     If
+    /// CHECK-NOT: If
+    private static int $noinline$testEliminateIfOppositeCondition(int a, int b) {
+        int result = 0;
+        if (a < b) {
+            $noinline$emptyMethod(a + b);
+        } else {
+            $noinline$emptyMethod(a - b);
+        }
+        if (a >= b) {
+            result += $noinline$emptyMethod(b * 3);
+        } else {
+            result += $noinline$emptyMethod(a * 2);
+        }
+        return result;
+    }
+
+    // In this scenario, we have a BooleanNot before the If instructions so we have to wait until
+    // the following pass to perform the optimization. The BooleanNot is dead at this time (even
+    // when starting DCE), but RemoveDeadInstructions runs after SimplifyIfs so the optimization
+    // doesn't trigger.
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameter(int, int, boolean) dead_code_elimination$initial (before)
+    /// CHECK:     BooleanNot
+    /// CHECK:     If
+    /// CHECK:     BooleanNot
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameter(int, int, boolean) dead_code_elimination$initial (after)
+    /// CHECK:     If
+    /// CHECK:     Phi
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameter(int, int, boolean) dead_code_elimination$initial (after)
+    /// CHECK-NOT: BooleanNot
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameter(int, int, boolean) dead_code_elimination$after_gvn (before)
+    /// CHECK:     If
+    /// CHECK:     Phi
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameter(int, int, boolean) dead_code_elimination$after_gvn (after)
+    /// CHECK:     If
+    /// CHECK-NOT: If
+    private static int $noinline$testEliminateIfParameter(int a, int b, boolean condition) {
+        int result = 0;
+        if (condition) {
+            $noinline$emptyMethod(a + b);
+        } else {
+            $noinline$emptyMethod(a - b);
+        }
+        if (condition) {
+            result += $noinline$emptyMethod(a * 2);
+        } else {
+            result += $noinline$emptyMethod(b * 3);
+        }
+        return result;
+    }
+
+    // Same in the following two cases: we do it in dead_code_elimination$initial since GVN is not
+    // needed.
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameterReverseCondition(int, int, boolean) dead_code_elimination$initial (before)
+    /// CHECK:     If
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameterReverseCondition(int, int, boolean) dead_code_elimination$initial (after)
+    /// CHECK:     If
+    /// CHECK-NOT: If
+    private static int $noinline$testEliminateIfParameterReverseCondition(
+            int a, int b, boolean condition) {
+        int result = 0;
+        if (!condition) {
+            $noinline$emptyMethod(a + b);
+        } else {
+            $noinline$emptyMethod(a - b);
+        }
+        if (!condition) {
+            result += $noinline$emptyMethod(b * 3);
+        } else {
+            result += $noinline$emptyMethod(a * 2);
+        }
+        return result;
+    }
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameterOppositeCondition(int, int, boolean) dead_code_elimination$initial (before)
+    /// CHECK:     If
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameterOppositeCondition(int, int, boolean) dead_code_elimination$initial (after)
+    /// CHECK:     If
+    /// CHECK-NOT: If
+    private static int $noinline$testEliminateIfParameterOppositeCondition(
+            int a, int b, boolean condition) {
+        int result = 0;
+        if (condition) {
+            $noinline$emptyMethod(a + b);
+        } else {
+            $noinline$emptyMethod(a - b);
+        }
+        if (!condition) {
+            result += $noinline$emptyMethod(b * 3);
+        } else {
+            result += $noinline$emptyMethod(a * 2);
+        }
+        return result;
+    }
+
+    // In this scenario, we have a BooleanNot before the If instructions so we have to wait until
+    // the following pass to perform the optimization. The BooleanNot is dead at this time (even
+    // when starting DCE), but RemoveDeadInstructions runs after SimplifyIfs so the optimization
+    // doesn't trigger.
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameterOppositeCondition_2(int, int, boolean) dead_code_elimination$initial (before)
+    /// CHECK:     If
+    /// CHECK:     BooleanNot
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameterOppositeCondition_2(int, int, boolean) dead_code_elimination$initial (after)
+    /// CHECK:     If
+    /// CHECK:     Phi
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameterOppositeCondition_2(int, int, boolean) dead_code_elimination$initial (after)
+    /// CHECK-NOT: BooleanNot
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameterOppositeCondition_2(int, int, boolean) dead_code_elimination$after_gvn (before)
+    /// CHECK:     If
+    /// CHECK:     Phi
+    /// CHECK:     If
+
+    /// CHECK-START: int Main.$noinline$testEliminateIfParameterOppositeCondition_2(int, int, boolean) dead_code_elimination$after_gvn (after)
+    /// CHECK:     If
+    /// CHECK-NOT: If
+    private static int $noinline$testEliminateIfParameterOppositeCondition_2(
+            int a, int b, boolean condition) {
+        int result = 0;
+        if (!condition) {
+            $noinline$emptyMethod(a + b);
+        } else {
+            $noinline$emptyMethod(a - b);
+        }
+        if (condition) {
+            result += $noinline$emptyMethod(a * 2);
+        } else {
+            result += $noinline$emptyMethod(b * 3);
+        }
+        return result;
+    }
+
+    public static void assertEquals(int expected, int result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+}
diff --git a/test/2256-checker-vector-replacement/Android.bp b/test/2256-checker-vector-replacement/Android.bp
new file mode 100644
index 0000000..b475493
--- /dev/null
+++ b/test/2256-checker-vector-replacement/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2256-checker-vector-replacement`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2256-checker-vector-replacement",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2256-checker-vector-replacement-expected-stdout",
+        ":art-run-test-2256-checker-vector-replacement-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2256-checker-vector-replacement-expected-stdout",
+    out: ["art-run-test-2256-checker-vector-replacement-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2256-checker-vector-replacement-expected-stderr",
+    out: ["art-run-test-2256-checker-vector-replacement-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2256-checker-vector-replacement/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2256-checker-vector-replacement/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2256-checker-vector-replacement/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2256-checker-vector-replacement/expected-stdout.txt
diff --git a/test/2256-checker-vector-replacement/info.txt b/test/2256-checker-vector-replacement/info.txt
new file mode 100644
index 0000000..374d7eb
--- /dev/null
+++ b/test/2256-checker-vector-replacement/info.txt
@@ -0,0 +1,2 @@
+Regression test to check we can perform LSE even when vector and
+non-vector operations reference the same heap location.
diff --git a/test/2256-checker-vector-replacement/src/Main.java b/test/2256-checker-vector-replacement/src/Main.java
new file mode 100644
index 0000000..fca80fb
--- /dev/null
+++ b/test/2256-checker-vector-replacement/src/Main.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Main {
+    public static void main(String[] args) {
+        $noinline$testVectorAndNonVector();
+    }
+
+    // Before loop optimization we only had an array get. After it, we optimized to also have
+    // VecLoad operations. This happens consistently only for Arm64. Arm32 vectorizes consistently
+    // but it also removes the ArrayGet. X86/X86_64 doesn't vectorize consistently (other
+    // vectorization tests also ignore x86/x86_64).
+
+    /// CHECK-START-ARM64: void Main.$noinline$testVectorAndNonVector() loop_optimization (before)
+    /// CHECK:     ArrayGet
+
+    /// CHECK-START-ARM64: void Main.$noinline$testVectorAndNonVector() loop_optimization (after)
+    /// CHECK:     ArrayGet
+
+    /// CHECK-START-ARM64: void Main.$noinline$testVectorAndNonVector() loop_optimization (before)
+    /// CHECK-NOT: VecLoad
+
+    /// CHECK-START-ARM64: void Main.$noinline$testVectorAndNonVector() loop_optimization (after)
+    /// CHECK:     VecLoad
+
+    // In LoadStoreElimination both ArrayGet and VecLoad have the same heap location. We will try to
+    // replace the ArrayGet with the constant 0. The crash happens when we want to do the same with
+    // the vector operation, changing the vector operation to a scalar.
+
+    /// CHECK-START-ARM64: void Main.$noinline$testVectorAndNonVector() load_store_elimination (before)
+    /// CHECK-DAG:     VecLoad outer_loop:<<VecBlock:B\d+>>
+    /// CHECK-DAG:     ArrayGet outer_loop:<<ScalarBlock:B\d+>>
+    /// CHECK-EVAL:    "<<VecBlock>>" == "<<ScalarBlock>>"
+
+    private static void $noinline$testVectorAndNonVector() {
+        int[] result = new int[2];
+        int[] source = new int[12];
+
+        // This will get vectorized.
+        for (int i = 0; i < result.length; ++i) {
+            int value = 0;
+            // Always true but needed to repro a crash since we need Phis.
+            if (i + 10 < source.length) {
+                for (int j = 0; j < 10; j++) {
+                    value += Math.abs(source[i + j]);
+                }
+            }
+            result[i] = value;
+        }
+    }
+}
diff --git a/test/2257-checker-constant-folding-before-codegen/Android.bp b/test/2257-checker-constant-folding-before-codegen/Android.bp
new file mode 100644
index 0000000..3326c5f
--- /dev/null
+++ b/test/2257-checker-constant-folding-before-codegen/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2257-checker-constant-folding-before-codegen`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2257-checker-constant-folding-before-codegen",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2257-checker-constant-folding-before-codegen-expected-stdout",
+        ":art-run-test-2257-checker-constant-folding-before-codegen-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2257-checker-constant-folding-before-codegen-expected-stdout",
+    out: ["art-run-test-2257-checker-constant-folding-before-codegen-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2257-checker-constant-folding-before-codegen-expected-stderr",
+    out: ["art-run-test-2257-checker-constant-folding-before-codegen-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2257-checker-constant-folding-before-codegen/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2257-checker-constant-folding-before-codegen/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2257-checker-constant-folding-before-codegen/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2257-checker-constant-folding-before-codegen/expected-stdout.txt
diff --git a/test/2257-checker-constant-folding-before-codegen/info.txt b/test/2257-checker-constant-folding-before-codegen/info.txt
new file mode 100644
index 0000000..f6eb041
--- /dev/null
+++ b/test/2257-checker-constant-folding-before-codegen/info.txt
@@ -0,0 +1 @@
+Test the benefits of running constant folding after LSE.
diff --git a/test/2257-checker-constant-folding-before-codegen/src/Main.java b/test/2257-checker-constant-folding-before-codegen/src/Main.java
new file mode 100644
index 0000000..492ad9e
--- /dev/null
+++ b/test/2257-checker-constant-folding-before-codegen/src/Main.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Main {
+    public static void main(String[] args) {
+        assertEquals(0, $noinline$testRemoveAbsAndReturnConstant());
+    }
+
+    // After LSE we know that some values are 0, making the Abs operation redundant.
+
+    /// CHECK-START: int Main.$noinline$testRemoveAbsAndReturnConstant() constant_folding$before_codegen (before)
+    /// CHECK:     Abs
+
+    /// CHECK-START: int Main.$noinline$testRemoveAbsAndReturnConstant() constant_folding$before_codegen (after)
+    /// CHECK-NOT:  Abs
+
+    // This enables DCE to know the return value at compile time.
+
+    /// CHECK-START: int Main.$noinline$testRemoveAbsAndReturnConstant() dead_code_elimination$before_codegen (before)
+    /// CHECK:     <<ReturnPhi:i\d+>> Phi [<<Val1:i\d+>>,<<Val2:i\d+>>]
+    /// CHECK:     Return [<<ReturnPhi>>]
+
+    /// CHECK-START: int Main.$noinline$testRemoveAbsAndReturnConstant() dead_code_elimination$before_codegen (after)
+    /// CHECK:     <<Const0:i\d+>> IntConstant 0
+    /// CHECK:     Return [<<Const0>>]
+
+    private static int $noinline$testRemoveAbsAndReturnConstant() {
+        final int ARRAY_SIZE = 10;
+        int[] result = new int[ARRAY_SIZE];
+        int[] source = new int[ARRAY_SIZE];
+
+        int value = 0;
+        for (int i = 0; i < ARRAY_SIZE; ++i) {
+            value += Math.abs(source[i]);
+            result[i] = value;
+        }
+        return value;
+    }
+
+    public static void assertEquals(int expected, int result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+}
diff --git a/test/2258-checker-valid-rti/Android.bp b/test/2258-checker-valid-rti/Android.bp
new file mode 100644
index 0000000..701835d
--- /dev/null
+++ b/test/2258-checker-valid-rti/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2258-checker-valid-rti`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2258-checker-valid-rti",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2258-checker-valid-rti-expected-stdout",
+        ":art-run-test-2258-checker-valid-rti-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2258-checker-valid-rti-expected-stdout",
+    out: ["art-run-test-2258-checker-valid-rti-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2258-checker-valid-rti-expected-stderr",
+    out: ["art-run-test-2258-checker-valid-rti-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2258-checker-valid-rti/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2258-checker-valid-rti/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2258-checker-valid-rti/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2258-checker-valid-rti/expected-stdout.txt
diff --git a/test/2258-checker-valid-rti/info.txt b/test/2258-checker-valid-rti/info.txt
new file mode 100644
index 0000000..fd0b254
--- /dev/null
+++ b/test/2258-checker-valid-rti/info.txt
@@ -0,0 +1,2 @@
+Small example where we don't have enough information to set an RTI
+for several instructions.
diff --git a/test/2258-checker-valid-rti/src/Main.java b/test/2258-checker-valid-rti/src/Main.java
new file mode 100644
index 0000000..d7ae1c2
--- /dev/null
+++ b/test/2258-checker-valid-rti/src/Main.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Main {
+    public static void main(String[] args) {}
+
+    /// CHECK-START: java.lang.Object[] Main.testNoRTILoopNoIterations() builder (after)
+    /// CHECK-DAG:     <<Null:l\d+>> NullConstant
+    /// CHECK-DAG:     <<Phi:l\d+>> Phi [<<Null>>,<<PhiBoundType:l\d+>>] klass:invalid
+    /// CHECK-DAG:     <<Check:l\d+>> NullCheck [<<Phi>>] klass:invalid
+    /// CHECK-DAG:     <<ArrayGet:l\d+>> ArrayGet [<<Check>>,<<BoundsCheck:i\d+>>] klass:invalid
+    /// CHECK-DAG:     <<BoundType:l\d+>> BoundType [<<ArrayGet>>] klass:invalid
+    // Due to the circular uses, this is how we check that the BoundType is a use of the Phi.
+    /// CHECK-EVAL:    "<<PhiBoundType>>" == "<<BoundType>>"
+    private static Object[] testNoRTILoopNoIterations() {
+        Object[] h = null;
+        for (int j = 0; j < 0; j++) {
+            h = (Object[]) h[0];
+        }
+        return h;
+    }
+
+    private static void $inline$doNothing(Object[] obj) {}
+
+    // This test also has no iterations, but we won't know until LSE. We inline the doNothing
+    // method, try to apply the invalid RTI and crash.
+
+    /// CHECK-START: java.lang.Object[] Main.testNoRTILoopNoIterationsWithInlining() builder (after)
+    /// CHECK-DAG:     <<Null:l\d+>> NullConstant
+    /// CHECK-DAG:     <<Phi:l\d+>> Phi [<<Null>>,<<PhiBoundType:l\d+>>] klass:invalid
+    /// CHECK-DAG:     InvokeStaticOrDirect [<<Phi>>] method_name:Main.$inline$doNothing
+    /// CHECK-DAG:     <<Check:l\d+>> NullCheck [<<Phi>>] klass:invalid
+    /// CHECK-DAG:     <<ArrayGet:l\d+>> ArrayGet [<<Check>>,<<BoundsCheck:i\d+>>] klass:invalid
+    /// CHECK-DAG:     <<BoundType:l\d+>> BoundType [<<ArrayGet>>] klass:invalid
+    // Due to the circular uses, this is how we check that the BoundType is a use of the Phi.
+    /// CHECK-EVAL:    "<<PhiBoundType>>" == "<<BoundType>>"
+    public static Object[] testNoRTILoopNoIterationsWithInlining() {
+        Object[] h = null;
+        int iterations = 0;
+        int other_iterations = iterations;
+        for (int j = 0; j < other_iterations; j++) {
+            $inline$doNothing(h);
+            h = (Object[]) h[0];
+        }
+        return h;
+    }
+
+}
diff --git a/test/2259-checker-code-sinking-infinite-try-catch/Android.bp b/test/2259-checker-code-sinking-infinite-try-catch/Android.bp
new file mode 100644
index 0000000..ab53ee3
--- /dev/null
+++ b/test/2259-checker-code-sinking-infinite-try-catch/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2259-checker-code-sinking-infinite-try-catch`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2259-checker-code-sinking-infinite-try-catch",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2259-checker-code-sinking-infinite-try-catch-expected-stdout",
+        ":art-run-test-2259-checker-code-sinking-infinite-try-catch-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2259-checker-code-sinking-infinite-try-catch-expected-stdout",
+    out: ["art-run-test-2259-checker-code-sinking-infinite-try-catch-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2259-checker-code-sinking-infinite-try-catch-expected-stderr",
+    out: ["art-run-test-2259-checker-code-sinking-infinite-try-catch-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2259-checker-code-sinking-infinite-try-catch/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2259-checker-code-sinking-infinite-try-catch/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2259-checker-code-sinking-infinite-try-catch/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2259-checker-code-sinking-infinite-try-catch/expected-stdout.txt
diff --git a/test/2259-checker-code-sinking-infinite-try-catch/info.txt b/test/2259-checker-code-sinking-infinite-try-catch/info.txt
new file mode 100644
index 0000000..6a77db3
--- /dev/null
+++ b/test/2259-checker-code-sinking-infinite-try-catch/info.txt
@@ -0,0 +1 @@
+Regression test to check we don't break domination while code sinking.
diff --git a/test/2259-checker-code-sinking-infinite-try-catch/src/Main.java b/test/2259-checker-code-sinking-infinite-try-catch/src/Main.java
new file mode 100644
index 0000000..a1a39b9
--- /dev/null
+++ b/test/2259-checker-code-sinking-infinite-try-catch/src/Main.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Main {
+    public static void main(String[] args) throws Exception {
+        try {
+            $noinline$testInfiniteLoopUnlessItThrows();
+            throw new Exception("Unreachable");
+        } catch (Error expected) {
+        }
+    }
+
+    /// CHECK-START: int Main.$noinline$testInfiniteLoopUnlessItThrows() code_sinking (before)
+    /// CHECK-NOT: Add loop:none
+
+    /// CHECK-START: int Main.$noinline$testInfiniteLoopUnlessItThrows() code_sinking (before)
+    /// CHECK-DAG:   <<Const0:i\d+>> IntConstant 0
+    /// CHECK-DAG:   <<Add:i\d+>> Add loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG:   Phi [<<Const0>>,<<Add>>] loop:<<Loop>> outer_loop:none
+
+    /// CHECK-START: int Main.$noinline$testInfiniteLoopUnlessItThrows() code_sinking (after)
+    /// CHECK-NOT: Add loop:none
+
+    /// CHECK-START: int Main.$noinline$testInfiniteLoopUnlessItThrows() code_sinking (after)
+    /// CHECK-DAG:   <<Const0:i\d+>> IntConstant 0
+    /// CHECK-DAG:   <<Add:i\d+>> Add loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG:   Phi [<<Const0>>,<<Add>>] loop:<<Loop>> outer_loop:none
+    private static int $noinline$testInfiniteLoopUnlessItThrows() {
+        int a = 0;
+        while (true) {
+            try {
+                $noinline$throwOrReturn(a);
+                throw new Exception();
+            } catch (Exception e) {
+                a++;
+            }
+        }
+    }
+
+    // Throws Error if `input` is 10. Otherwise it returns `input`.
+    private static int $noinline$throwOrReturn(int input) throws Error {
+        if (input == 10) {
+            throw new Error();
+        }
+        return input;
+    }
+}
diff --git a/test/2260-checker-inline-unimplemented-intrinsics/Android.bp b/test/2260-checker-inline-unimplemented-intrinsics/Android.bp
new file mode 100644
index 0000000..7fca8e3
--- /dev/null
+++ b/test/2260-checker-inline-unimplemented-intrinsics/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2260-checker-inline-unimplemented-intrinsics`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2260-checker-inline-unimplemented-intrinsics",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2260-checker-inline-unimplemented-intrinsics-expected-stdout",
+        ":art-run-test-2260-checker-inline-unimplemented-intrinsics-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2260-checker-inline-unimplemented-intrinsics-expected-stdout",
+    out: ["art-run-test-2260-checker-inline-unimplemented-intrinsics-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2260-checker-inline-unimplemented-intrinsics-expected-stderr",
+    out: ["art-run-test-2260-checker-inline-unimplemented-intrinsics-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2260-checker-inline-unimplemented-intrinsics/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2260-checker-inline-unimplemented-intrinsics/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2260-checker-inline-unimplemented-intrinsics/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2260-checker-inline-unimplemented-intrinsics/expected-stdout.txt
diff --git a/test/2260-checker-inline-unimplemented-intrinsics/info.txt b/test/2260-checker-inline-unimplemented-intrinsics/info.txt
new file mode 100644
index 0000000..4752416
--- /dev/null
+++ b/test/2260-checker-inline-unimplemented-intrinsics/info.txt
@@ -0,0 +1 @@
+Tests that we inline unimplemented intrinsics.
diff --git a/test/2260-checker-inline-unimplemented-intrinsics/src/Main.java b/test/2260-checker-inline-unimplemented-intrinsics/src/Main.java
new file mode 100644
index 0000000..3f125ca
--- /dev/null
+++ b/test/2260-checker-inline-unimplemented-intrinsics/src/Main.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+import java.lang.reflect.Field;
+
+import sun.misc.Unsafe;
+
+public class Main {
+    private static final Unsafe unsafe = getUnsafe();
+    public int i = 0;
+    public long l = 0;
+
+    public static void main(String[] args) {
+        $noinline$testGetAndAdd();
+    }
+
+    private static void $noinline$testGetAndAdd() {
+        final Main m = new Main();
+        final long intOffset, longOffset;
+        try {
+            Field intField = Main.class.getDeclaredField("i");
+            Field longField = Main.class.getDeclaredField("l");
+
+            intOffset = unsafe.objectFieldOffset(intField);
+            longOffset = unsafe.objectFieldOffset(longField);
+
+        } catch (NoSuchFieldException e) {
+            throw new Error("No offset: " + e);
+        }
+
+        $noinline$add(m, intOffset, 11);
+        assertEquals(11, m.i);
+        $noinline$add(m, intOffset, 13);
+        assertEquals(24, m.i);
+
+        $noinline$add(m, longOffset, 11L);
+        assertEquals(11L, m.l);
+        $noinline$add(m, longOffset, 13L);
+        assertEquals(24L, m.l);
+    }
+
+    // UnsafeGetAndAddInt/Long are part of core-oj and we will not inline on host.
+
+    /// CHECK-START-{ARM,ARM64}: int Main.$noinline$add(java.lang.Object, long, int) inliner (before)
+    /// CHECK:     InvokeVirtual intrinsic:UnsafeGetAndAddInt
+
+    /// CHECK-START-{ARM,ARM64}: int Main.$noinline$add(java.lang.Object, long, int) inliner (after)
+    /// CHECK-NOT: InvokeVirtual intrinsic:UnsafeGetAndAddInt
+    private static int $noinline$add(Object o, long offset, int delta) {
+        return unsafe.getAndAddInt(o, offset, delta);
+    }
+
+    /// CHECK-START-{ARM,ARM64}: long Main.$noinline$add(java.lang.Object, long, long) inliner (before)
+    /// CHECK:     InvokeVirtual intrinsic:UnsafeGetAndAddLong
+
+    /// CHECK-START-{ARM,ARM64}: long Main.$noinline$add(java.lang.Object, long, long) inliner (after)
+    /// CHECK-NOT: InvokeVirtual intrinsic:UnsafeGetAndAddLong
+    private static long $noinline$add(Object o, long offset, long delta) {
+        return unsafe.getAndAddLong(o, offset, delta);
+    }
+
+    private static Unsafe getUnsafe() {
+        try {
+            Class<?> unsafeClass = Unsafe.class;
+            Field f = unsafeClass.getDeclaredField("theUnsafe");
+            f.setAccessible(true);
+            return (Unsafe) f.get(null);
+        } catch (Exception e) {
+            throw new Error("Cannot get Unsafe instance");
+        }
+    }
+
+    private static void assertEquals(int expected, int result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+
+    private static void assertEquals(long expected, long result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+}
diff --git a/test/2261-badcleaner-in-systemcleaner/Android.bp b/test/2261-badcleaner-in-systemcleaner/Android.bp
new file mode 100644
index 0000000..4643c77
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2261-badcleaner-in-systemcleaner`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2261-badcleaner-in-systemcleaner",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src-art/**/*.java"],
+    data: [
+        ":art-run-test-2261-badcleaner-in-systemcleaner-expected-stdout",
+        ":art-run-test-2261-badcleaner-in-systemcleaner-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2261-badcleaner-in-systemcleaner-expected-stdout",
+    out: ["art-run-test-2261-badcleaner-in-systemcleaner-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2261-badcleaner-in-systemcleaner-expected-stderr",
+    out: ["art-run-test-2261-badcleaner-in-systemcleaner-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2261-badcleaner-in-systemcleaner/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2261-badcleaner-in-systemcleaner/expected-stderr.txt
diff --git a/test/2261-badcleaner-in-systemcleaner/expected-stdout.txt b/test/2261-badcleaner-in-systemcleaner/expected-stdout.txt
new file mode 100644
index 0000000..db36097
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/expected-stdout.txt
@@ -0,0 +1,4 @@
+About to null reference.
+Cleaner started and sleeping briefly...
+Cleaner done snoozing.
+Cleaner sleeping forever now.
diff --git a/test/2261-badcleaner-in-systemcleaner/info.txt b/test/2261-badcleaner-in-systemcleaner/info.txt
new file mode 100644
index 0000000..9916ff1
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/info.txt
@@ -0,0 +1,3 @@
+Cleanup actions registered at android.system.SystemCleaner are run within FinalizerDaemon thread
+and the same time out rules are applied. Essentially this is SystemCleaner version of
+030-bad-finalizer.
diff --git a/test/2261-badcleaner-in-systemcleaner/run.py b/test/2261-badcleaner-in-systemcleaner/run.py
new file mode 100644
index 0000000..3151272
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2023 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, android_log_tags="*:f", expected_exit_code=2)
diff --git a/test/2261-badcleaner-in-systemcleaner/src-art/Main.java b/test/2261-badcleaner-in-systemcleaner/src-art/Main.java
new file mode 100644
index 0000000..a14d0d5
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/src-art/Main.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+import android.system.SystemCleaner;
+import dalvik.system.VMRuntime;
+import java.util.concurrent.CountDownLatch;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+/**
+ * Test SystemCleaner with a bad cleaning action.
+ *
+ * This test is inherently very slightly flaky. It assumes that the system will schedule the
+ * finalizer daemon and finalizer watchdog daemon soon and often enough to reach the timeout and
+ * throw the fatal exception before we time out. Since we build in a 100 second buffer, failures
+ * should be very rare.
+ */
+public class Main {
+    public static void main(String[] args) throws Exception {
+        CountDownLatch cleanerWait = new CountDownLatch(1);
+
+        registerBadCleaner(cleanerWait);
+
+        // Should have at least two iterations to trigger finalization, but just to make sure run
+        // some more.
+        for (int i = 0; i < 5; i++) {
+            Runtime.getRuntime().gc();
+        }
+
+        // Now wait for the finalizer to start running. Give it a minute.
+        cleanerWait.await(1, MINUTES);
+
+        // Now fall asleep with a timeout. The timeout is large enough that we expect the
+        // finalizer daemon to have killed the process before the deadline elapses.
+        // The timeout is also large enough to cover the extra 5 seconds we wait
+        // to dump threads, plus potentially substantial gcstress overhead.
+        // Note: the timeout is here (instead of an infinite sleep) to protect the test
+        //       environment (e.g., in case this is run without a timeout wrapper).
+        final long timeout = 100 * 1000 + VMRuntime.getRuntime().getFinalizerTimeoutMs();
+        long remainingWait = timeout;
+        final long waitStart = System.currentTimeMillis();
+        while (remainingWait > 0) {
+            synchronized (args) {  // Just use an already existing object for simplicity...
+                try {
+                    args.wait(remainingWait);
+                } catch (Exception e) {
+                    System.out.println("UNEXPECTED EXCEPTION");
+                }
+            }
+            remainingWait = timeout - (System.currentTimeMillis() - waitStart);
+        }
+
+        // We should not get here.
+        System.out.println("UNREACHABLE");
+        System.exit(0);
+    }
+
+    private static void registerBadCleaner(CountDownLatch cleanerWait) {
+        Object obj = new Object();
+        SystemCleaner.cleaner().register(obj, () -> neverEndingCleanup(cleanerWait));
+
+        System.out.println("About to null reference.");
+        obj = null;  // Not that this would make a difference, could be eliminated earlier.
+    }
+
+    public static void snooze(int ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException ie) {
+            System.out.println("Unexpected interrupt");
+        }
+    }
+
+    private static void neverEndingCleanup(CountDownLatch cleanerWait) {
+        cleanerWait.countDown();
+
+        System.out.println("Cleaner started and sleeping briefly...");
+
+        long start, end;
+        start = System.nanoTime();
+        snooze(2000);
+        end = System.nanoTime();
+        System.out.println("Cleaner done snoozing.");
+
+        System.out.println("Cleaner sleeping forever now.");
+        while (true) {
+            snooze(10000);
+        }
+    }
+}
diff --git a/test/2261-badcleaner-in-systemcleaner/test-metadata.json b/test/2261-badcleaner-in-systemcleaner/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/2261-badcleaner-in-systemcleaner/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2262-checker-return-sinking/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2262-checker-return-sinking/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/2262-checker-return-sinking/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/2262-checker-return-sinking/expected-stdout.txt
diff --git a/test/2262-checker-return-sinking/info.txt b/test/2262-checker-return-sinking/info.txt
new file mode 100644
index 0000000..6cc9ae9
--- /dev/null
+++ b/test/2262-checker-return-sinking/info.txt
@@ -0,0 +1 @@
+Tests that we sink Return/ReturnVoid instructions and coalesce them.
diff --git a/test/2262-checker-return-sinking/src/Main.java b/test/2262-checker-return-sinking/src/Main.java
new file mode 100644
index 0000000..4698964
--- /dev/null
+++ b/test/2262-checker-return-sinking/src/Main.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Main {
+    public static void main(String[] args) {
+        // Switch case.
+        assertEquals("First case", $noinline$testSinkReturnSwitch(1));
+        assertEquals("Second case", $noinline$testSinkReturnSwitch(2));
+        assertEquals("default", $noinline$testSinkReturnSwitch(3));
+        $noinline$testSinkReturnVoidSwitch(1);
+
+        // If/else if/else.
+        assertEquals("First case", $noinline$testSinkReturnIfElse(1));
+        assertEquals("Second case", $noinline$testSinkReturnIfElse(2));
+        assertEquals("default", $noinline$testSinkReturnIfElse(3));
+        $noinline$testSinkReturnVoidIfElse(1);
+
+        // Non-trivial if cases.
+        assertEquals("First case", $noinline$testSinkReturnSeparatedReturns(1));
+        assertEquals("Second case", $noinline$testSinkReturnSeparatedReturns(2));
+        assertEquals("default", $noinline$testSinkReturnSeparatedReturns(3));
+        $noinline$testSinkReturnVoidSeparatedReturns(1);
+    }
+
+    /// CHECK-START: java.lang.String Main.$noinline$testSinkReturnSwitch(int) code_sinking (before)
+    /// CHECK:     Return
+    /// CHECK:     Return
+    /// CHECK:     Return
+    /// CHECK-NOT: Return
+
+    /// CHECK-START: java.lang.String Main.$noinline$testSinkReturnSwitch(int) code_sinking (after)
+    /// CHECK:     Return
+    /// CHECK-NOT: Return
+    private static String $noinline$testSinkReturnSwitch(int switch_id) {
+        switch (switch_id) {
+            case 1:
+                return "First case";
+            case 2:
+                return "Second case";
+            default:
+                return "default";
+        }
+    }
+
+    /// CHECK-START: void Main.$noinline$testSinkReturnVoidSwitch(int) code_sinking (before)
+    /// CHECK:     ReturnVoid
+    /// CHECK:     ReturnVoid
+    /// CHECK:     ReturnVoid
+    /// CHECK-NOT: ReturnVoid
+
+    /// CHECK-START: void Main.$noinline$testSinkReturnVoidSwitch(int) code_sinking (after)
+    /// CHECK:     ReturnVoid
+    /// CHECK-NOT: ReturnVoid
+    private static void $noinline$testSinkReturnVoidSwitch(int switch_id) {
+        switch (switch_id) {
+            case 1:
+                $noinline$emptyMethod();
+                return;
+            case 2:
+                $noinline$emptyMethod2();
+                return;
+            default:
+                $noinline$emptyMethod3();
+                return;
+        }
+    }
+
+    /// CHECK-START: java.lang.String Main.$noinline$testSinkReturnIfElse(int) code_sinking (before)
+    /// CHECK:     Return
+    /// CHECK:     Return
+    /// CHECK:     Return
+    /// CHECK-NOT: Return
+
+    /// CHECK-START: java.lang.String Main.$noinline$testSinkReturnIfElse(int) code_sinking (after)
+    /// CHECK:     Return
+    /// CHECK-NOT: Return
+    private static String $noinline$testSinkReturnIfElse(int id) {
+        if (id == 1) {
+            return "First case";
+        } else if (id == 2) {
+            return "Second case";
+        } else {
+            return "default";
+        }
+    }
+
+    /// CHECK-START: void Main.$noinline$testSinkReturnVoidIfElse(int) code_sinking (before)
+    /// CHECK:     ReturnVoid
+    /// CHECK:     ReturnVoid
+    /// CHECK:     ReturnVoid
+    /// CHECK-NOT: ReturnVoid
+
+    /// CHECK-START: void Main.$noinline$testSinkReturnVoidIfElse(int) code_sinking (after)
+    /// CHECK:     ReturnVoid
+    /// CHECK-NOT: ReturnVoid
+    private static void $noinline$testSinkReturnVoidIfElse(int id) {
+        if (id == 1) {
+            $noinline$emptyMethod();
+            return;
+        } else if (id == 2) {
+            $noinline$emptyMethod2();
+            return;
+        } else {
+            $noinline$emptyMethod3();
+            return;
+        }
+    }
+
+    /// CHECK-START: java.lang.String Main.$noinline$testSinkReturnSeparatedReturns(int) code_sinking (before)
+    /// CHECK:     Return
+    /// CHECK:     Return
+    /// CHECK:     Return
+    /// CHECK-NOT: Return
+
+    /// CHECK-START: java.lang.String Main.$noinline$testSinkReturnSeparatedReturns(int) code_sinking (after)
+    /// CHECK:     Return
+    /// CHECK-NOT: Return
+    private static String $noinline$testSinkReturnSeparatedReturns(int id) {
+        if (id == 1) {
+            return "First case";
+        }
+        $noinline$emptyMethod();
+
+        if (id == 2) {
+            return "Second case";
+        }
+
+        $noinline$emptyMethod2();
+        return "default";
+    }
+
+    /// CHECK-START: void Main.$noinline$testSinkReturnVoidSeparatedReturns(int) code_sinking (before)
+    /// CHECK:     ReturnVoid
+    /// CHECK:     ReturnVoid
+    /// CHECK:     ReturnVoid
+    /// CHECK-NOT: ReturnVoid
+
+    /// CHECK-START: void Main.$noinline$testSinkReturnVoidSeparatedReturns(int) code_sinking (after)
+    /// CHECK:     ReturnVoid
+    /// CHECK-NOT: ReturnVoid
+    private static void $noinline$testSinkReturnVoidSeparatedReturns(int id) {
+        if (id == 1) {
+            return;
+        }
+        $noinline$emptyMethod();
+
+        if (id == 2) {
+            return;
+        }
+
+        $noinline$emptyMethod2();
+        return;
+    }
+
+    private static void $noinline$emptyMethod() {}
+    private static void $noinline$emptyMethod2() {}
+    private static void $noinline$emptyMethod3() {}
+
+    private static void assertEquals(String expected, String result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+}
diff --git a/test/2262-default-conflict-methods/Android.bp b/test/2262-default-conflict-methods/Android.bp
new file mode 100644
index 0000000..8fb2f39
--- /dev/null
+++ b/test/2262-default-conflict-methods/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2262-default-conflict-methods`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-2262-default-conflict-methods-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2262-default-conflict-methods",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-2262-default-conflict-methods-src"
+    ],
+    data: [
+        ":art-run-test-2262-default-conflict-methods-expected-stdout",
+        ":art-run-test-2262-default-conflict-methods-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2262-default-conflict-methods-expected-stdout",
+    out: ["art-run-test-2262-default-conflict-methods-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2262-default-conflict-methods-expected-stderr",
+    out: ["art-run-test-2262-default-conflict-methods-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2262-default-conflict-methods/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/2262-default-conflict-methods/expected-stderr.txt
diff --git a/test/2262-default-conflict-methods/expected-stdout.txt b/test/2262-default-conflict-methods/expected-stdout.txt
new file mode 100644
index 0000000..818f313
--- /dev/null
+++ b/test/2262-default-conflict-methods/expected-stdout.txt
@@ -0,0 +1,10 @@
+JNI_OnLoad called
+Create Main instance
+Calling functions on concrete Main
+Calling non-conflicting function on Main
+Test from Interface
+Unexpected normal exit from GetMethodId
+Test from Interface
+Calling conflicting function on Main
+Expected ICCE on main
+Expected ICCE on main
diff --git a/test/2262-default-conflict-methods/info.txt b/test/2262-default-conflict-methods/info.txt
new file mode 100644
index 0000000..6e1a6a1
--- /dev/null
+++ b/test/2262-default-conflict-methods/info.txt
@@ -0,0 +1 @@
+Tests handling of method ids for default method conflicts.
diff --git a/test/2262-default-conflict-methods/src/Iface.java b/test/2262-default-conflict-methods/src/Iface.java
new file mode 100644
index 0000000..7ced383
--- /dev/null
+++ b/test/2262-default-conflict-methods/src/Iface.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+public interface Iface {
+    public default void test() {
+        System.out.println("Test from Interface");
+    }
+
+    public default void test_throws() {
+        System.out.println("Test throws from Interface");
+    }
+}
diff --git a/test/2262-default-conflict-methods/src/Iface2.java b/test/2262-default-conflict-methods/src/Iface2.java
new file mode 100644
index 0000000..a7e9216
--- /dev/null
+++ b/test/2262-default-conflict-methods/src/Iface2.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+public interface Iface2 extends Iface {}
diff --git a/test/2262-default-conflict-methods/src/Main.java b/test/2262-default-conflict-methods/src/Main.java
new file mode 100644
index 0000000..f916a48
--- /dev/null
+++ b/test/2262-default-conflict-methods/src/Main.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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 Main implements Iface, Iface2 {
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
+        System.out.println("Create Main instance");
+        Main m = new Main();
+        System.out.println("Calling functions on concrete Main");
+        callMain(m);
+    }
+
+    public static void callMain(Main m) {
+        System.out.println("Calling non-conflicting function on Main");
+        m.test();
+        long main_id = GetMethodId(false, Main.class, "test", "()V");
+        long iface_id = GetMethodId(false, Iface.class, "test", "()V");
+        try {
+            long iface2_id = GetMethodId(false, Main.class, "test", "()V");
+            System.out.println("Unexpected normal exit from GetMethodId");
+        } catch (NoSuchMethodError e) {
+            System.out.println("Expected NoSuchMethodError thrown on Iface2");
+        }
+        if (main_id != iface_id) {
+            throw new Error("Default methods have different method ids");
+        }
+        CallNonvirtual(m, Main.class, main_id);
+
+        System.out.println("Calling conflicting function on Main");
+        try {
+            m.test_throws();
+        } catch (IncompatibleClassChangeError e) {
+            System.out.println("Expected ICCE on main");
+        }
+
+        long main_throws_id = GetMethodId(false, Main.class, "test_throws", "()V");
+        long iface_throws_id = GetMethodId(false, Iface.class, "test_throws", "()V");
+        long iface2_throws_id = GetMethodId(false, Iface2.class, "test_throws", "()V");
+        if (main_throws_id == iface_throws_id || main_throws_id == iface2_throws_id) {
+            System.out.println(
+                    "Unexpected: method id of default conflicting matches one of the interface methods");
+        }
+
+        try {
+            CallNonvirtual(m, Main.class, main_throws_id);
+        } catch (IncompatibleClassChangeError e) {
+            System.out.println("Expected ICCE on main");
+        }
+        return;
+    }
+
+    private static native long GetMethodId(boolean is_static, Class k, String name, String sig);
+    private static native long CallNonvirtual(Object obj, Class k, long methodid);
+}
diff --git a/test/2262-default-conflict-methods/src2/Iface2.java b/test/2262-default-conflict-methods/src2/Iface2.java
new file mode 100644
index 0000000..1c2f22f
--- /dev/null
+++ b/test/2262-default-conflict-methods/src2/Iface2.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+public interface Iface2 {
+    public default void test_throws() {
+        System.out.println("TestThrows from Iface2");
+    }
+}
diff --git a/test/2262-miranda-methods/Android.bp b/test/2262-miranda-methods/Android.bp
new file mode 100644
index 0000000..0c00467
--- /dev/null
+++ b/test/2262-miranda-methods/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2262-miranda-methods`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-2262-miranda-methods-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2262-miranda-methods",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-2262-miranda-methods-src"
+    ],
+    data: [
+        ":art-run-test-2262-miranda-methods-expected-stdout",
+        ":art-run-test-2262-miranda-methods-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2262-miranda-methods-expected-stdout",
+    out: ["art-run-test-2262-miranda-methods-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2262-miranda-methods-expected-stderr",
+    out: ["art-run-test-2262-miranda-methods-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/2262-miranda-methods/expected-stderr.txt
similarity index 100%
rename from test/089-many-methods/expected-stderr.txt
rename to test/2262-miranda-methods/expected-stderr.txt
diff --git a/test/2262-miranda-methods/expected-stdout.txt b/test/2262-miranda-methods/expected-stdout.txt
new file mode 100644
index 0000000..4976738
--- /dev/null
+++ b/test/2262-miranda-methods/expected-stdout.txt
@@ -0,0 +1,12 @@
+JNI_OnLoad called
+Create Main instance
+Test method with concrete implementation
+Impl test correct - new
+Test method with concrete implementation via JNI call
+Abstract interface and Main have different method ids
+Impl test correct - new
+Test method with no concrete implementation
+Expected AME Thrown on Main
+Expected NoSuchMethodError on Main
+Abstract interface and Main have same method ids
+Expected AME Thrown on Main via JNI call
diff --git a/test/2262-miranda-methods/info.txt b/test/2262-miranda-methods/info.txt
new file mode 100644
index 0000000..6e1a6a1
--- /dev/null
+++ b/test/2262-miranda-methods/info.txt
@@ -0,0 +1 @@
+Tests handling of method ids for default method conflicts.
diff --git a/test/2262-miranda-methods/jni_invoke.cc b/test/2262-miranda-methods/jni_invoke.cc
new file mode 100644
index 0000000..da55f8b
--- /dev/null
+++ b/test/2262-miranda-methods/jni_invoke.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <jni.h>
+
+#include "jni/java_vm_ext.h"
+#include "runtime.h"
+
+namespace art {
+
+extern "C" JNIEXPORT void JNICALL
+Java_Main_CallNonvirtual(JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jobject o, jclass c, jmethodID m) {
+  env->CallNonvirtualVoidMethod(o, c, m);
+}
+
+}  // namespace art
diff --git a/test/2262-miranda-methods/src/Iface.java b/test/2262-miranda-methods/src/Iface.java
new file mode 100644
index 0000000..cbc2626
--- /dev/null
+++ b/test/2262-miranda-methods/src/Iface.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+public interface Iface {
+    public abstract void test_throws();
+    public abstract void test_correct();
+}
diff --git a/test/2262-miranda-methods/src/Impl.java b/test/2262-miranda-methods/src/Impl.java
new file mode 100644
index 0000000..b0a241a
--- /dev/null
+++ b/test/2262-miranda-methods/src/Impl.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Impl implements Iface {
+    public void test_correct() {
+        System.out.println("Impl test correct");
+    }
+
+    public void test_throws() {
+        System.out.println("Impl test throws");
+    }
+}
diff --git a/test/2262-miranda-methods/src/Main.java b/test/2262-miranda-methods/src/Main.java
new file mode 100644
index 0000000..9a890e6
--- /dev/null
+++ b/test/2262-miranda-methods/src/Main.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 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 Main extends Impl implements Iface {
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
+        System.out.println("Create Main instance");
+        Main m = new Main();
+        callMain(m);
+    }
+
+    public static void callMain(Main m) {
+        System.out.println("Test method with concrete implementation");
+        m.test_correct();
+        System.out.println("Test method with concrete implementation via JNI call");
+        long iface_id = GetMethodId(false, Iface.class, "test_correct", "()V");
+        long main_id = GetMethodId(false, Main.class, "test_correct", "()V");
+        long impl_id = GetMethodId(false, Impl.class, "test_correct", "()V");
+        if (iface_id != main_id) {
+            System.out.println("Abstract interface and Main have different method ids");
+        } else {
+            System.out.println("Unexpected: Abstract interface and Main have same method ids");
+        }
+        CallNonvirtual(m, Main.class, main_id);
+
+        System.out.println("Test method with no concrete implementation");
+        try {
+            m.test_throws();
+        } catch (AbstractMethodError e) {
+            System.out.println("Expected AME Thrown on Main");
+        }
+        long iface_throws_id = GetMethodId(false, Iface.class, "test_throws", "()V");
+        long main_throws_id = GetMethodId(false, Main.class, "test_throws", "()V");
+        try {
+            long id = GetMethodId(false, Impl.class, "test_throws", "()V");
+        } catch (NoSuchMethodError e) {
+            System.out.println("Expected NoSuchMethodError on Main");
+        }
+        if (iface_throws_id == main_throws_id) {
+            System.out.println("Abstract interface and Main have same method ids");
+        } else {
+            System.out.println("Unexpected: Abstract interface and Main have different method ids");
+        }
+
+        try {
+            CallNonvirtual(m, Main.class, main_throws_id);
+        } catch (AbstractMethodError e) {
+            System.out.println("Expected AME Thrown on Main via JNI call");
+        }
+        return;
+    }
+
+    private static native long GetMethodId(boolean is_static, Class k, String name, String sig);
+    private static native long CallNonvirtual(Object obj, Class k, long methodid);
+}
diff --git a/test/2262-miranda-methods/src2/Impl.java b/test/2262-miranda-methods/src2/Impl.java
new file mode 100644
index 0000000..d7e8261
--- /dev/null
+++ b/test/2262-miranda-methods/src2/Impl.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+public class Impl {
+    public void test_correct() {
+        System.out.println("Impl test correct - new");
+    }
+}
diff --git a/test/2263-method-trace-jit/Android.bp b/test/2263-method-trace-jit/Android.bp
new file mode 100644
index 0000000..798d6da
--- /dev/null
+++ b/test/2263-method-trace-jit/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2263-method-trace-jit`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-2263-method-trace-jit",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-2263-method-trace-jit-expected-stdout",
+        ":art-run-test-2263-method-trace-jit-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-2263-method-trace-jit-expected-stdout",
+    out: ["art-run-test-2263-method-trace-jit-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-2263-method-trace-jit-expected-stderr",
+    out: ["art-run-test-2263-method-trace-jit-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2263-method-trace-jit/expected-stderr.txt b/test/2263-method-trace-jit/expected-stderr.txt
new file mode 120000
index 0000000..0abcd1b
--- /dev/null
+++ b/test/2263-method-trace-jit/expected-stderr.txt
@@ -0,0 +1 @@
+../988-method-trace/expected-stderr.txt
\ No newline at end of file
diff --git a/test/2263-method-trace-jit/expected-stdout.txt b/test/2263-method-trace-jit/expected-stdout.txt
new file mode 120000
index 0000000..e5311a0
--- /dev/null
+++ b/test/2263-method-trace-jit/expected-stdout.txt
@@ -0,0 +1 @@
+../988-method-trace/expected-stdout.txt
\ No newline at end of file
diff --git a/test/2263-method-trace-jit/info.txt b/test/2263-method-trace-jit/info.txt
new file mode 100644
index 0000000..f0a200d
--- /dev/null
+++ b/test/2263-method-trace-jit/info.txt
@@ -0,0 +1,15 @@
+Tests method tracing in JVMTI
+
+This test is sensitive to the internal implementations of:
+ * java.lang.Error
+ * java.lang.Integer
+ * java.lang.Math
+ * java.lang.String
+ * java.lang.System
+ * java.util.ArrayList
+ * java.util.Arrays
+ * java.util.StringBuilder
+ * all super-classes and super-interfaces of the above types.
+
+Changes to the internal implementation of these classes might (or might not)
+change the output of this test.
diff --git a/test/2263-method-trace-jit/run.py b/test/2263-method-trace-jit/run.py
new file mode 100644
index 0000000..c02159d
--- /dev/null
+++ b/test/2263-method-trace-jit/run.py
@@ -0,0 +1,19 @@
+#
+# Copyright 2023 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/2263-method-trace-jit/src/Main.java b/test/2263-method-trace-jit/src/Main.java
new file mode 100644
index 0000000..470f896
--- /dev/null
+++ b/test/2263-method-trace-jit/src/Main.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+public class Main {
+    public static void main(String[] args) throws Exception {
+        // Test calls to intrinsics from JITed code.
+        ensureJitCompiled(art.Test988Intrinsics.class, "test");
+        ensureJitCompiled(art.Test988.class, "doFibTest");
+        art.Test988.run();
+    }
+
+    public static native void ensureJitCompiled(Class<?> cls, String methodName);
+}
diff --git a/test/2263-method-trace-jit/src/art/Test988.java b/test/2263-method-trace-jit/src/art/Test988.java
new file mode 120000
index 0000000..24dcdeb
--- /dev/null
+++ b/test/2263-method-trace-jit/src/art/Test988.java
@@ -0,0 +1 @@
+../../../988-method-trace/src/art/Test988.java
\ No newline at end of file
diff --git a/test/2263-method-trace-jit/src/art/Test988Intrinsics.java b/test/2263-method-trace-jit/src/art/Test988Intrinsics.java
new file mode 120000
index 0000000..7fb019c
--- /dev/null
+++ b/test/2263-method-trace-jit/src/art/Test988Intrinsics.java
@@ -0,0 +1 @@
+../../../988-method-trace/src/art/Test988Intrinsics.java
\ No newline at end of file
diff --git a/test/2263-method-trace-jit/src/art/Trace.java b/test/2263-method-trace-jit/src/art/Trace.java
new file mode 120000
index 0000000..5d9b44b
--- /dev/null
+++ b/test/2263-method-trace-jit/src/art/Trace.java
@@ -0,0 +1 @@
+../../../jvmti-common/Trace.java
\ No newline at end of file
diff --git a/test/2263-method-trace-jit/trace_fib.cc b/test/2263-method-trace-jit/trace_fib.cc
new file mode 120000
index 0000000..d6167d4
--- /dev/null
+++ b/test/2263-method-trace-jit/trace_fib.cc
@@ -0,0 +1 @@
+../988-method-trace/trace_fib.cc
\ No newline at end of file
diff --git a/test/303-verification-stress/build b/test/303-verification-stress/build
deleted file mode 100644
index 6e4a1d6..0000000
--- a/test/303-verification-stress/build
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2013 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.
-
-# Stop if something fails.
-set -e
-
-# Write out a bunch of source files.
-./classes-gen
-
-./default-build "$@"
diff --git a/test/303-verification-stress/build.py b/test/303-verification-stress/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/303-verification-stress/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/303-verification-stress/generate-sources b/test/303-verification-stress/generate-sources
new file mode 100755
index 0000000..2a2cdb6
--- /dev/null
+++ b/test/303-verification-stress/generate-sources
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright (C) 2013 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.
+
+# Stop if something fails.
+set -e
+
+# Write out a bunch of source files.
+./classes-gen
diff --git a/test/304-method-tracing/run b/test/304-method-tracing/run
deleted file mode 100755
index 7bd1895..0000000
--- a/test/304-method-tracing/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Runs the test with method tracing enabled.
-exec ${RUN} "$@" --runtime-option -Xmethod-trace --runtime-option -Xmethod-trace-file:${DEX_LOCATION}/trace.bin
diff --git a/test/304-method-tracing/run.py b/test/304-method-tracing/run.py
new file mode 100644
index 0000000..2fb72d3
--- /dev/null
+++ b/test/304-method-tracing/run.py
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+
+def run(ctx, args):
+  # Runs the test with method tracing enabled.
+  ctx.default_run(
+      args,
+      runtime_option=[
+          "-Xmethod-trace", "-Xmethod-trace-file:${DEX_LOCATION}/trace.bin"
+      ])
diff --git a/test/370-dex-v37/build b/test/370-dex-v37/build
deleted file mode 100755
index f472428..0000000
--- a/test/370-dex-v37/build
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@"
-
-if [[ $@ != *"--jvm"* ]]; then
-  # Change the generated dex file to have a v36 magic number if it is version 35
-  if test -f classes.dex && head -c 7 classes.dex | grep -q 035; then
-    # place ascii value '037' into the classes.dex file starting at byte 4.
-    printf '037' | dd status=none conv=notrunc of=classes.dex bs=1 seek=4 count=3
-    rm -f $TEST_NAME.jar
-    zip $TEST_NAME.jar classes.dex
-  fi
-fi
diff --git a/test/370-dex-v37/build.py b/test/370-dex-v37/build.py
new file mode 100644
index 0000000..096ee82
--- /dev/null
+++ b/test/370-dex-v37/build.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2022 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.
+
+def build(ctx):
+  ctx.default_build()
+  if ctx.jvm:
+    return
+  # Change the generated dex file to have a v37 magic number if it is version 35
+  with open(ctx.test_dir / "classes.dex", "rb+") as f:
+    if f.read(8) == b"dex\n035\x00":
+      f.seek(0)
+      f.write(b"dex\n037\x00")
+  (ctx.test_dir / "370-dex-v37.jar").unlink()
+  ctx.soong_zip([
+      "-o", ctx.test_dir / "370-dex-v37.jar", "-j", "-f",
+      ctx.test_dir / "classes.dex"
+  ])
diff --git a/test/411-checker-instruct-simplifier-hrem/src/Main.java b/test/411-checker-instruct-simplifier-hrem/src/Main.java
index e6853dd..d790eda 100644
--- a/test/411-checker-instruct-simplifier-hrem/src/Main.java
+++ b/test/411-checker-instruct-simplifier-hrem/src/Main.java
@@ -381,11 +381,11 @@
   /// CHECK:           Div loop:B{{\d+}}
   /// CHECK-NEXT:      Rem loop:B{{\d+}}
   //
-  /// CHECK-START: int Main.$noinline$IntRemBy18InLoop(int) instruction_simplifier$after_bce (before)
+  /// CHECK-START: int Main.$noinline$IntRemBy18InLoop(int) instruction_simplifier$after_loop_opt (before)
   /// CHECK:           Div loop:B{{\d+}}
   /// CHECK-NEXT:      Rem loop:B{{\d+}}
   //
-  /// CHECK-START: int Main.$noinline$IntRemBy18InLoop(int) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int Main.$noinline$IntRemBy18InLoop(int) instruction_simplifier$after_loop_opt (after)
   /// CHECK-NOT:       Rem
   /// CHECK:           Div loop:B{{\d+}}
   /// CHECK-NEXT:      Mul loop:B{{\d+}}
@@ -799,11 +799,11 @@
   /// CHECK:           Div loop:B{{\d+}}
   /// CHECK-NEXT:      Rem loop:B{{\d+}}
   //
-  /// CHECK-START: long Main.$noinline$LongRemBy18InLoop(long) instruction_simplifier$after_bce (before)
+  /// CHECK-START: long Main.$noinline$LongRemBy18InLoop(long) instruction_simplifier$after_loop_opt (before)
   /// CHECK:           Div loop:B{{\d+}}
   /// CHECK-NEXT:      Rem loop:B{{\d+}}
   //
-  /// CHECK-START: long Main.$noinline$LongRemBy18InLoop(long) instruction_simplifier$after_bce (after)
+  /// CHECK-START: long Main.$noinline$LongRemBy18InLoop(long) instruction_simplifier$after_loop_opt (after)
   /// CHECK-NOT:       Rem
   /// CHECK:           Div loop:B{{\d+}}
   /// CHECK-NEXT:      Mul loop:B{{\d+}}
diff --git a/test/416-optimizing-arith-not/src/Main.java b/test/416-optimizing-arith-not/src/Main.java
index 44c7d3c..6bded7b 100644
--- a/test/416-optimizing-arith-not/src/Main.java
+++ b/test/416-optimizing-arith-not/src/Main.java
@@ -18,62 +18,62 @@
 
 public class Main {
 
-  public static void expectEquals(int expected, int result) {
-    if (expected != result) {
-      throw new Error("Expected: " + expected + ", found: " + result);
+    public static void expectEquals(int expected, int result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
     }
-  }
 
-  public static void expectEquals(long expected, long result) {
-    if (expected != result) {
-      throw new Error("Expected: " + expected + ", found: " + result);
+    public static void expectEquals(long expected, long result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
     }
-  }
 
-  public static void main(String[] args) throws Exception {
-    notInt();
-    notLong();
-  }
+    public static void main(String[] args) throws Exception {
+        notInt();
+        notLong();
+    }
 
-  private static void notInt() throws Exception {
-    expectEquals(1, smaliNotInt(-2));
-    expectEquals(0, smaliNotInt(-1));
-    expectEquals(-1, smaliNotInt(0));
-    expectEquals(-2, smaliNotInt(1));
-    expectEquals(2147483647, smaliNotInt(-2147483648));  // -(2^31)
-    expectEquals(2147483646, smaliNotInt(-2147483647));  // -(2^31 - 1)
-    expectEquals(-2147483647, smaliNotInt(2147483646));  // 2^31 - 2
-    expectEquals(-2147483648, smaliNotInt(2147483647));  // 2^31 - 1
-  }
+    private static void notInt() throws Exception {
+        expectEquals(1, smaliNotInt(-2));
+        expectEquals(0, smaliNotInt(-1));
+        expectEquals(-1, smaliNotInt(0));
+        expectEquals(-2, smaliNotInt(1));
+        expectEquals(2147483647, smaliNotInt(-2147483648));  // -(2^31)
+        expectEquals(2147483646, smaliNotInt(-2147483647));  // -(2^31 - 1)
+        expectEquals(-2147483647, smaliNotInt(2147483646));  // 2^31 - 2
+        expectEquals(-2147483648, smaliNotInt(2147483647));  // 2^31 - 1
+    }
 
-  private static void notLong() throws Exception {
-    expectEquals(1L, smaliNotLong(-2L));
-    expectEquals(0L, smaliNotLong(-1L));
-    expectEquals(-1L, smaliNotLong(0L));
-    expectEquals(-2L, smaliNotLong(1L));
-    expectEquals(2147483647L, smaliNotLong(-2147483648L));  // -(2^31)
-    expectEquals(2147483646L, smaliNotLong(-2147483647L));  // -(2^31 - 1)
-    expectEquals(-2147483647L, smaliNotLong(2147483646L));  // 2^31 - 2
-    expectEquals(-2147483648L, smaliNotLong(2147483647L));  // 2^31 - 1
-    expectEquals(9223372036854775807L, smaliNotLong(-9223372036854775808L));  // -(2^63)
-    expectEquals(9223372036854775806L, smaliNotLong(-9223372036854775807L));  // -(2^63 - 1)
-    expectEquals(-9223372036854775807L, smaliNotLong(9223372036854775806L));  // 2^63 - 2
-    expectEquals(-9223372036854775808L, smaliNotLong(9223372036854775807L));  // 2^63 - 1
-  }
+    private static void notLong() throws Exception {
+        expectEquals(1L, smaliNotLong(-2L));
+        expectEquals(0L, smaliNotLong(-1L));
+        expectEquals(-1L, smaliNotLong(0L));
+        expectEquals(-2L, smaliNotLong(1L));
+        expectEquals(2147483647L, smaliNotLong(-2147483648L));  // -(2^31)
+        expectEquals(2147483646L, smaliNotLong(-2147483647L));  // -(2^31 - 1)
+        expectEquals(-2147483647L, smaliNotLong(2147483646L));  // 2^31 - 2
+        expectEquals(-2147483648L, smaliNotLong(2147483647L));  // 2^31 - 1
+        expectEquals(9223372036854775807L, smaliNotLong(-9223372036854775808L));  // -(2^63)
+        expectEquals(9223372036854775806L, smaliNotLong(-9223372036854775807L));  // -(2^63 - 1)
+        expectEquals(-9223372036854775807L, smaliNotLong(9223372036854775806L));  // 2^63 - 2
+        expectEquals(-9223372036854775808L, smaliNotLong(9223372036854775807L));  // 2^63 - 1
+    }
 
-  // Wrappers around methods located in file not.smali.
+    // Wrappers around methods located in file not.smali.
 
-  private static int smaliNotInt(int a) throws Exception {
-    Class<?> c = Class.forName("TestNot");
-    Method m = c.getMethod("$opt$NotInt", int.class);
-    int result = (Integer)m.invoke(null, a);
-    return result;
-  }
+    private static int smaliNotInt(int a) throws Exception {
+        Class<?> c = Class.forName("TestNot");
+        Method m = c.getMethod("$opt$NotInt", int.class);
+        int result = (Integer)m.invoke(null, a);
+        return result;
+    }
 
-  private static long smaliNotLong(long a) throws Exception {
-    Class<?> c = Class.forName("TestNot");
-    Method m = c.getMethod("$opt$NotLong", long.class);
-    long result = (Long)m.invoke(null, a);
-    return result;
-  }
+    private static long smaliNotLong(long a) throws Exception {
+        Class<?> c = Class.forName("TestNot");
+        Method m = c.getMethod("$opt$NotLong", long.class);
+        long result = (Long)m.invoke(null, a);
+        return result;
+    }
 }
diff --git a/test/418-const-string/src/Main.java b/test/418-const-string/src/Main.java
index 7c1ffec..3b7d8e2 100644
--- a/test/418-const-string/src/Main.java
+++ b/test/418-const-string/src/Main.java
@@ -15,14 +15,14 @@
  */
 
 public class Main {
-  public static void main(String[] args) {
-    // First call: may go in slow path.
-    System.out.println($opt$ReturnHelloWorld());
-    // Second call: no slow path.
-    System.out.println($opt$ReturnHelloWorld());
-  }
+    public static void main(String[] args) {
+        // First call: may go in slow path.
+        System.out.println($opt$ReturnHelloWorld());
+        // Second call: no slow path.
+        System.out.println($opt$ReturnHelloWorld());
+    }
 
-  public static String $opt$ReturnHelloWorld() {
-    return "Hello World";
-  }
+    public static String $opt$ReturnHelloWorld() {
+        return "Hello World";
+    }
 }
diff --git a/test/419-long-parameter/src/Main.java b/test/419-long-parameter/src/Main.java
index 808b7f6..f83b3f7 100644
--- a/test/419-long-parameter/src/Main.java
+++ b/test/419-long-parameter/src/Main.java
@@ -15,20 +15,20 @@
  */
 
 public class Main {
-  public static void main(String[] args) {
-    if ($opt$TestCallee(1.0, 2.0, 1L, 2L) != 1L) {
-      throw new Error("Unexpected result");
+    public static void main(String[] args) {
+        if ($opt$TestCallee(1.0, 2.0, 1L, 2L) != 1L) {
+            throw new Error("Unexpected result");
+        }
+        if ($opt$TestCaller() != 1L) {
+            throw new Error("Unexpected result");
+        }
     }
-    if ($opt$TestCaller() != 1L) {
-      throw new Error("Unexpected result");
+
+    public static long $opt$TestCallee(double a, double b, long c, long d) {
+        return d - c;
     }
-  }
 
-  public static long $opt$TestCallee(double a, double b, long c, long d) {
-    return d - c;
-  }
-
-  public static long $opt$TestCaller() {
-    return $opt$TestCallee(1.0, 2.0, 1L, 2L);
-  }
+    public static long $opt$TestCaller() {
+        return $opt$TestCallee(1.0, 2.0, 1L, 2L);
+    }
 }
diff --git a/test/442-checker-constant-folding/src/Main.java b/test/442-checker-constant-folding/src/Main.java
index 1bdf7b5..c924fca 100644
--- a/test/442-checker-constant-folding/src/Main.java
+++ b/test/442-checker-constant-folding/src/Main.java
@@ -1577,6 +1577,300 @@
     return (double) imm;
   }
 
+  /// CHECK-START: int Main.$inline$SpecialCaseForZeroInt(int) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Shl
+  /// CHECK-DAG:     Add
+
+  /// CHECK-START: int Main.$inline$SpecialCaseForZeroInt(int) constant_folding$after_gvn (before)
+  /// CHECK-NOT:     IntConstant 6
+
+  /// CHECK-START: int Main.$inline$SpecialCaseForZeroInt(int) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Add
+
+  /// CHECK-START: int Main.$inline$SpecialCaseForZeroInt(int) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Shl
+
+  /// CHECK-START: int Main.$inline$SpecialCaseForZeroInt(int) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     <<Const:i\d+>>    IntConstant 6
+  /// CHECK-DAG:                       Return [<<Const>>]
+  private static int $inline$SpecialCaseForZeroInt(int value) {
+    if (value == 0) {
+      return (value + 2) * 3;
+    }
+    return value;
+  }
+
+  /// CHECK-START: long Main.$inline$SpecialCaseForZeroLong(long) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Shl
+  /// CHECK-DAG:     Add
+
+  /// CHECK-START: long Main.$inline$SpecialCaseForZeroLong(long) constant_folding$after_gvn (before)
+  /// CHECK-NOT:     LongConstant 6
+
+  /// CHECK-START: long Main.$inline$SpecialCaseForZeroLong(long) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Add
+
+  /// CHECK-START: long Main.$inline$SpecialCaseForZeroLong(long) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Shl
+
+  /// CHECK-START: long Main.$inline$SpecialCaseForZeroLong(long) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     <<Const:j\d+>>    LongConstant 6
+  /// CHECK-DAG:                       Return [<<Const>>]
+  private static long $inline$SpecialCaseForZeroLong(long value) {
+    if (value == 0L) {
+      return (value + 2) * 3;
+    }
+    return value;
+  }
+
+  /// CHECK-START: float Main.$noinline$SpecialCaseForZeroFloat(float) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Mul
+
+  /// CHECK-START: float Main.$noinline$SpecialCaseForZeroFloat(float) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     FloatConstant 6
+
+  /// CHECK-START: float Main.$noinline$SpecialCaseForZeroFloat(float) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Mul
+  private static float $noinline$SpecialCaseForZeroFloat(float value) {
+    if (value == 0F) {
+      return (value + 2F) * 3F;
+    }
+    return value;
+  }
+
+  /// CHECK-START: double Main.$noinline$SpecialCaseForZeroDouble(double) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Mul
+
+  /// CHECK-START: double Main.$noinline$SpecialCaseForZeroDouble(double) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     DoubleConstant 6
+
+  /// CHECK-START: double Main.$noinline$SpecialCaseForZeroDouble(double) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Mul
+  private static double $noinline$SpecialCaseForZeroDouble(double value) {
+    if (value == 0D) {
+      return (value + 2D) * 3D;
+    }
+    return value;
+  }
+
+  // Note that we have Add instead of sub since internally we do `Add(value, -1)`.
+  /// CHECK-START: int Main.$noinline$NotEqualsPropagationInt(int) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Div
+
+  /// CHECK-START: int Main.$noinline$NotEqualsPropagationInt(int) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Add
+
+  /// CHECK-START: int Main.$noinline$NotEqualsPropagationInt(int) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Div
+
+  /// CHECK-START: int Main.$noinline$NotEqualsPropagationInt(int) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     <<Const:i\d+>>    IntConstant 1
+  /// CHECK-DAG:                       Return [<<Const>>]
+  private static int $noinline$NotEqualsPropagationInt(int value) {
+    if (value != 3) {
+      return value;
+    } else {
+      return (value - 1) / 2;
+    }
+  }
+
+  /// CHECK-START: long Main.$noinline$NotEqualsPropagationLong(long) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Div
+
+  /// CHECK-START: long Main.$noinline$NotEqualsPropagationLong(long) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Sub
+
+  /// CHECK-START: long Main.$noinline$NotEqualsPropagationLong(long) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Div
+
+  /// CHECK-START: long Main.$noinline$NotEqualsPropagationLong(long) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     <<Const:j\d+>>    LongConstant 1
+  /// CHECK-DAG:                       Return [<<Const>>]
+  private static long $noinline$NotEqualsPropagationLong(long value) {
+    if (value != 3L) {
+      return value;
+    } else {
+      return (value - 1L) / 2L;
+    }
+  }
+
+  /// CHECK-START: float Main.$noinline$NotEqualsPropagationFloat(float) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Mul
+
+  /// CHECK-START: float Main.$noinline$NotEqualsPropagationFloat(float) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Mul
+  private static float $noinline$NotEqualsPropagationFloat(float value) {
+    if (value != 3F) {
+      return value;
+    } else {
+      return (value - 1F) / 2F;
+    }
+  }
+
+  /// CHECK-START: double Main.$noinline$NotEqualsPropagationDouble(double) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Mul
+
+  /// CHECK-START: double Main.$noinline$NotEqualsPropagationDouble(double) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Mul
+  private static double $noinline$NotEqualsPropagationDouble(double value) {
+    if (value != 3D) {
+      return value;
+    } else {
+      return (value - 1D) / 2D;
+    }
+  }
+
+  /// CHECK-START: int Main.$noinline$InlineCalleeWithSpecialCaseForZeroInt(int) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Shl
+  /// CHECK-DAG:     Add
+
+  /// CHECK-START: int Main.$noinline$InlineCalleeWithSpecialCaseForZeroInt(int) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Add
+
+  /// CHECK-START: int Main.$noinline$InlineCalleeWithSpecialCaseForZeroInt(int) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Shl
+
+  /// CHECK-START: int Main.$noinline$InlineCalleeWithSpecialCaseForZeroInt(int) dead_code_elimination$after_gvn (after)
+  /// CHECK-DAG:     <<Param:i\d+>>    ParameterValue
+  /// CHECK-DAG:     <<Const6:i\d+>>   IntConstant 6
+  /// CHECK-DAG:                       Return [<<Param>>]
+  /// CHECK-DAG:                       Return [<<Const6>>]
+  private static int $noinline$InlineCalleeWithSpecialCaseForZeroInt(int value) {
+    if (value == 0) {
+      return $inline$SpecialCaseForZeroInt(value);
+    }
+    return value;
+  }
+
+  /// CHECK-START: long Main.$noinline$InlineCalleeWithSpecialCaseForZeroLong(long) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Shl
+  /// CHECK-DAG:     Add
+
+  /// CHECK-START: long Main.$noinline$InlineCalleeWithSpecialCaseForZeroLong(long) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Add
+
+  /// CHECK-START: long Main.$noinline$InlineCalleeWithSpecialCaseForZeroLong(long) constant_folding$after_gvn (after)
+  /// CHECK-NOT:     Shl
+
+  /// CHECK-START: long Main.$noinline$InlineCalleeWithSpecialCaseForZeroLong(long) dead_code_elimination$after_gvn (after)
+  /// CHECK-DAG:     <<Param:j\d+>>    ParameterValue
+  /// CHECK-DAG:     <<Const6:j\d+>>   LongConstant 6
+  /// CHECK-DAG:                       Return [<<Param>>]
+  /// CHECK-DAG:                       Return [<<Const6>>]
+  private static long $noinline$InlineCalleeWithSpecialCaseForZeroLong(long value) {
+    if (value == 0L) {
+      return $inline$SpecialCaseForZeroLong(value);
+    }
+    return value;
+  }
+
+  // Check that don't propagate the value == 3 on `if not true` branch, as the `if true` branch also
+  // flows into the same block.
+  /// CHECK-START: int Main.$noinline$NotEqualsImplicitElseInt(int) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Div
+
+  /// CHECK-START: int Main.$noinline$NotEqualsImplicitElseInt(int) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     Add
+  /// CHECK-DAG:     Div
+  private static int $noinline$NotEqualsImplicitElseInt(int value) {
+    if (value != 3) {
+      value++;
+    }
+    return (value - 1) / 2;
+  }
+
+  /// CHECK-START: long Main.$noinline$NotEqualsImplicitElseLong(long) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Div
+
+  /// CHECK-START: long Main.$noinline$NotEqualsImplicitElseLong(long) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Div
+  private static long $noinline$NotEqualsImplicitElseLong(long value) {
+    if (value != 3L) {
+      value += 1L;
+    }
+    return (value - 1L) / 2L;
+  }
+
+  /// CHECK-START: float Main.$noinline$NotEqualsImplicitElseFloat(float) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Mul
+
+  /// CHECK-START: float Main.$noinline$NotEqualsImplicitElseFloat(float) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Mul
+  private static float $noinline$NotEqualsImplicitElseFloat(float value) {
+    if (value != 3F) {
+      value += 1F;
+    }
+    return (value - 1F) / 2F;
+  }
+
+  /// CHECK-START: double Main.$noinline$NotEqualsImplicitElseDouble(double) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Mul
+
+  /// CHECK-START: double Main.$noinline$NotEqualsImplicitElseDouble(double) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     Sub
+  /// CHECK-DAG:     Mul
+  private static double $noinline$NotEqualsImplicitElseDouble(double value) {
+    if (value != 3D) {
+      value += 1D;
+    }
+    return (value - 1D) / 2D;
+  }
+
+  // By propagating the boolean we can eliminate some equality comparisons as we already know their
+  // result. In turn, we also enable DeadCodeElimination to eliminate more code.
+  /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean, int) constant_folding$after_gvn (before)
+  /// CHECK-DAG:     <<Param:z\d+>>    ParameterValue
+  /// CHECK-DAG:                       Select [{{i\d+}},{{i\d+}},<<Param>>]
+  /// CHECK-DAG:                       Select [{{i\d+}},{{i\d+}},<<Param>>]
+
+  /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean, int) constant_folding$after_gvn (after)
+  /// CHECK-DAG:     <<Const0:i\d+>>   IntConstant 0
+  /// CHECK-DAG:     <<Const1:i\d+>>   IntConstant 1
+  /// CHECK-DAG:                       Select [{{i\d+}},{{i\d+}},<<Const0>>]
+  /// CHECK-DAG:                       Select [{{i\d+}},{{i\d+}},<<Const1>>]
+
+  /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean, int) dead_code_elimination$after_gvn (before)
+  /// CHECK-DAG:     IntConstant 1
+  /// CHECK-DAG:     IntConstant 2
+  /// CHECK-DAG:     IntConstant 3
+  /// CHECK-DAG:     IntConstant 4
+
+  /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean, int) dead_code_elimination$after_gvn (after)
+  /// CHECK-DAG:     IntConstant 1
+  /// CHECK-DAG:     IntConstant 4
+
+  /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean, int) dead_code_elimination$after_gvn (after)
+  /// CHECK-NOT:     IntConstant 2
+
+  /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean, int) dead_code_elimination$after_gvn (after)
+  /// CHECK-NOT:     IntConstant 3
+  private static int $noinline$PropagatingParameterValue(boolean value, int initial_value) {
+    if (value) {
+      return value ? initial_value + 1 : initial_value + 2;
+    } else {
+      return value ? initial_value + 3 : initial_value + 4;
+    }
+  }
 
   public static void main(String[] args) throws Exception {
     assertIntEquals(-42, IntNegation());
@@ -1708,6 +2002,51 @@
     assertDoubleEquals(33, ReturnDouble33());
     assertDoubleEquals(34, ReturnDouble34());
     assertDoubleEquals(99.25, ReturnDouble99P25());
+
+    // Tests for propagating known values due to if clauses.
+
+    // Propagating within the same method. These are marked $inline$ since we used them in
+    // `InlineCalleeWithSpecialCaseForZeroInt`.
+    assertIntEquals(6, $inline$SpecialCaseForZeroInt(0));
+    assertIntEquals(3, $inline$SpecialCaseForZeroInt(3));
+    assertLongEquals(6L, $inline$SpecialCaseForZeroLong(0L));
+    assertLongEquals(3L, $inline$SpecialCaseForZeroLong(3L));
+    // Floats and doubles we do not optimize (here and below). These methods are here to guarantee
+    // that.
+    assertFloatEquals(6F, $noinline$SpecialCaseForZeroFloat(0F));
+    assertFloatEquals(3F, $noinline$SpecialCaseForZeroFloat(3F));
+    assertDoubleEquals(6D, $noinline$SpecialCaseForZeroDouble(0D));
+    assertDoubleEquals(3D, $noinline$SpecialCaseForZeroDouble(3D));
+
+    // Propagating within the same method, with not equals
+    assertIntEquals(0, $noinline$NotEqualsPropagationInt(0));
+    assertIntEquals(1, $noinline$NotEqualsPropagationInt(3));
+    assertLongEquals(0L, $noinline$NotEqualsPropagationLong(0L));
+    assertLongEquals(1L, $noinline$NotEqualsPropagationLong(3L));
+    assertFloatEquals(0F, $noinline$NotEqualsPropagationFloat(0F));
+    assertFloatEquals(1F, $noinline$NotEqualsPropagationFloat(3F));
+    assertDoubleEquals(0D, $noinline$NotEqualsPropagationDouble(0D));
+    assertDoubleEquals(1D, $noinline$NotEqualsPropagationDouble(3D));
+
+    // Propagating so that the inliner can use it.
+    assertIntEquals(6, $noinline$InlineCalleeWithSpecialCaseForZeroInt(0));
+    assertIntEquals(3, $noinline$InlineCalleeWithSpecialCaseForZeroInt(3));
+    assertLongEquals(6L, $noinline$InlineCalleeWithSpecialCaseForZeroLong(0L));
+    assertLongEquals(3L, $noinline$InlineCalleeWithSpecialCaseForZeroLong(3L));
+
+    // Propagating within the same method, with not equals
+    assertIntEquals(0, $noinline$NotEqualsImplicitElseInt(0));
+    assertIntEquals(1, $noinline$NotEqualsImplicitElseInt(3));
+    assertLongEquals(0L, $noinline$NotEqualsImplicitElseLong(0L));
+    assertLongEquals(1L, $noinline$NotEqualsImplicitElseLong(3L));
+    assertFloatEquals(0F, $noinline$NotEqualsImplicitElseFloat(0F));
+    assertFloatEquals(1F, $noinline$NotEqualsImplicitElseFloat(3F));
+    assertDoubleEquals(0D, $noinline$NotEqualsImplicitElseDouble(0D));
+    assertDoubleEquals(1D, $noinline$NotEqualsImplicitElseDouble(3D));
+
+    // Propagating parameters.
+    assertIntEquals(1, $noinline$PropagatingParameterValue(true, 0));
+    assertIntEquals(4, $noinline$PropagatingParameterValue(false, 0));
   }
 
   Main() throws ClassNotFoundException {
diff --git a/test/449-checker-bce/src/Main.java b/test/449-checker-bce/src/Main.java
index 1144366..3e41410 100644
--- a/test/449-checker-bce/src/Main.java
+++ b/test/449-checker-bce/src/Main.java
@@ -1125,7 +1125,7 @@
   /// CHECK-DAG: <<Len:i\d+>> ArrayLength [<<Nul>>]         loop:none
   /// CHECK-DAG:              Equal [<<Len>>,<<Val>>]       loop:none
   /// CHECK-DAG: <<Idx:i\d+>> Phi                           loop:<<Loop:B\d+>>
-  /// CHECK-DAG:              BoundsCheck [<<Idx>>,<<Len>>] loop:<<Loop>>
+  /// CHECK-DAG:              BoundsCheck [<<Idx>>,<<Val>>] loop:<<Loop>>
   //
   /// CHECK-START: void Main.lengthAlias4(int[]) BCE (after)
   /// CHECK-NOT:              BoundsCheck
@@ -1576,7 +1576,7 @@
   /// CHECK-NOT: BoundsCheck
   /// CHECK: ArrayGet
 
-  /// CHECK-START: void Main.foo9(int[], boolean) instruction_simplifier$after_bce (after)
+  /// CHECK-START: void Main.foo9(int[], boolean) instruction_simplifier$before_codegen (after)
   //  Simplification removes the redundant check
   /// CHECK: Deoptimize
   /// CHECK: Deoptimize
diff --git a/test/457-regs/run b/test/457-regs/run
deleted file mode 100644
index 2591855..0000000
--- a/test/457-regs/run
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-${RUN} "$@"
-return_status1=$?
-
-# Force baseline JIT compilation as the test explicitly requests JIT
-# compilation, which by default is 'optimizing'.
-${RUN} "$@" -Xcompiler-option --baseline
-return_status2=$?
-
-# Make sure we don't silently ignore an early failure.
-(exit $return_status1) && (exit $return_status2)
diff --git a/test/457-regs/run.py b/test/457-regs/run.py
new file mode 100644
index 0000000..7c18068
--- /dev/null
+++ b/test/457-regs/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright (C) 2020 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # Force baseline JIT compilation as the test explicitly requests JIT
+  # compilation, which by default is 'optimizing'.
+  ctx.default_run(args, Xcompiler_option=["--baseline"])
diff --git a/test/458-checker-instruct-simplification/smali/SmaliTests.smali b/test/458-checker-instruct-simplification/smali/SmaliTests.smali
index d987398..1dd66d6 100644
--- a/test/458-checker-instruct-simplification/smali/SmaliTests.smali
+++ b/test/458-checker-instruct-simplification/smali/SmaliTests.smali
@@ -415,7 +415,7 @@
 ## CHECK-DAG:     <<NotArg:z\d+>>    BooleanNot [<<Arg>>]
 ## CHECK-DAG:                        Return [<<Arg>>]
 
-## CHECK-START: boolean SmaliTests.$noinline$NotNotBool(boolean) dead_code_elimination$final (after)
+## CHECK-START: boolean SmaliTests.$noinline$NotNotBool(boolean) dead_code_elimination$before_codegen (after)
 ## CHECK-DAG:     <<Arg:z\d+>>       ParameterValue
 ## CHECK-DAG:                        Return [<<Arg>>]
 
diff --git a/test/458-checker-instruct-simplification/src/Main.java b/test/458-checker-instruct-simplification/src/Main.java
index 22b6dfe..8ab059d 100644
--- a/test/458-checker-instruct-simplification/src/Main.java
+++ b/test/458-checker-instruct-simplification/src/Main.java
@@ -2751,6 +2751,49 @@
     return (byte) ((value & mask) >> 8);
   }
 
+  /// CHECK-START: int Main.$noinline$deadAddAfterUnrollingAndSimplification(int[]) dead_code_elimination$before_codegen (before)
+  /// CHECK-DAG: <<Param:l\d+>>     ParameterValue                             loop:none
+  /// CHECK-DAG: <<Const0:i\d+>>    IntConstant 0                              loop:none
+  /// CHECK-DAG: <<Const1:i\d+>>    IntConstant 1                              loop:none
+  /// CHECK-DAG: <<Const2:i\d+>>    IntConstant 2                              loop:none
+  /// CHECK-DAG: <<IndexPhi:i\d+>>  Phi [<<Const0>>,{{i\d+}}]                  loop:<<Loop:B\d+>> outer_loop:none
+  //            Induction variable:
+  /// CHECK-DAG:                    Add [<<IndexPhi>>,<<Const2>>]              loop:<<Loop>>      outer_loop:none
+  //            Array Element Addition:
+  /// CHECK-DAG: <<Store1:i\d+>>    Add [{{i\d+}},<<Const1>>]                  loop:<<Loop>>      outer_loop:none
+  /// CHECK-DAG: <<Store2:i\d+>>    Add [{{i\d+}},<<Const2>>]                  loop:<<Loop>>      outer_loop:none
+  /// CHECK-DAG:                    ArraySet [<<Param>>,<<Const0>>,<<Store2>>] loop:<<Loop>>      outer_loop:none
+
+  /// CHECK-START: int Main.$noinline$deadAddAfterUnrollingAndSimplification(int[]) dead_code_elimination$before_codegen (before)
+  /// CHECK:                        Add
+  /// CHECK:                        Add
+  /// CHECK:                        Add
+  /// CHECK:                        Add
+  /// CHECK-NOT:                    Add
+
+  /// CHECK-START: int Main.$noinline$deadAddAfterUnrollingAndSimplification(int[]) dead_code_elimination$before_codegen (after)
+  /// CHECK-DAG: <<Param:l\d+>>     ParameterValue                             loop:none
+  /// CHECK-DAG: <<Const0:i\d+>>    IntConstant 0                              loop:none
+  /// CHECK-DAG: <<Const2:i\d+>>    IntConstant 2                              loop:none
+  /// CHECK-DAG: <<IndexPhi:i\d+>>  Phi [<<Const0>>,{{i\d+}}]                  loop:<<Loop:B\d+>> outer_loop:none
+  //            Induction variable:
+  /// CHECK-DAG:                    Add [<<IndexPhi>>,<<Const2>>]              loop:<<Loop>>      outer_loop:none
+  //            Array Element Addition:
+  /// CHECK-DAG: <<Store:i\d+>>     Add [{{i\d+}},<<Const2>>]                  loop:<<Loop>>      outer_loop:none
+  /// CHECK-DAG:                    ArraySet [<<Param>>,<<Const0>>,<<Store>>] loop:<<Loop>>      outer_loop:none
+
+  /// CHECK-START: int Main.$noinline$deadAddAfterUnrollingAndSimplification(int[]) dead_code_elimination$before_codegen (after)
+  /// CHECK:                        Add
+  /// CHECK:                        Add
+  /// CHECK-NOT:                    Add
+  public static int $noinline$deadAddAfterUnrollingAndSimplification(int[] array) {
+    for (int i = 0; i < 50; ++i) {
+        // Array access prevents transformation to closed-form expression
+        array[0]++;
+    }
+    return array[0];
+  }
+
   public static void main(String[] args) throws Exception {
     Class smaliTests2 = Class.forName("SmaliTests2");
     Method $noinline$XorAllOnes = smaliTests2.getMethod("$noinline$XorAllOnes", int.class);
@@ -3058,6 +3101,8 @@
     assertIntEquals(-1, $noinline$redundantAndIntToByteShortAndConstant(0x7fffff45));
     assertIntEquals(-1, $noinline$redundantAndIntToByteShortAndConstant(0xffffff45));
     assertIntEquals(111, $noinline$redundantAndRegressionNotConstant(-1, 0x6f45));
+
+    assertIntEquals(50, $noinline$deadAddAfterUnrollingAndSimplification(new int[] { 0 }));
   }
 
   private static boolean $inline$true() { return true; }
diff --git a/test/463-checker-boolean-simplifier/smali/Main2.smali b/test/463-checker-boolean-simplifier/smali/Main2.smali
index 5fc553e..e8ebb23 100644
--- a/test/463-checker-boolean-simplifier/smali/Main2.smali
+++ b/test/463-checker-boolean-simplifier/smali/Main2.smali
@@ -211,6 +211,9 @@
     goto :goto_4
 .end method
 
+# This test currently checks that we don't perform select generation due to
+# having multiple phis.
+
 ## CHECK-START: int Main2.MultiplePhis() select_generator (before)
 ## CHECK-DAG:     <<Const0:i\d+>>   IntConstant 0
 ## CHECK-DAG:     <<Const1:i\d+>>   IntConstant 1
@@ -228,11 +231,11 @@
 ## CHECK-DAG:     <<Const1:i\d+>>   IntConstant 1
 ## CHECK-DAG:     <<Const13:i\d+>>  IntConstant 13
 ## CHECK-DAG:     <<Const42:i\d+>>  IntConstant 42
-## CHECK-DAG:     <<PhiX:i\d+>>     Phi [<<Const0>>,<<Select:i\d+>>]
-## CHECK-DAG:     <<PhiY:i\d+>>     Phi [<<Const1>>,<<Add:i\d+>>]
+## CHECK-DAG:     <<PhiX:i\d+>>     Phi [<<Const0>>,<<Const13>>,<<Const42>>]
+## CHECK-DAG:     <<PhiY:i\d+>>     Phi [<<Const1>>,<<Add:i\d+>>,<<Add>>]
 ## CHECK-DAG:     <<Add>>           Add [<<PhiY>>,<<Const1>>]
 ## CHECK-DAG:     <<Cond:z\d+>>     LessThanOrEqual [<<Add>>,<<Const1>>]
-## CHECK-DAG:     <<Select>>        Select [<<Const13>>,<<Const42>>,<<Cond>>]
+## CHECK-DAG:                       If [<<Cond>>]
 ## CHECK-DAG:                       Return [<<PhiX>>]
 
 # The original java source of this method:
diff --git a/test/465-checker-clinit-gvn/src/Main.java b/test/465-checker-clinit-gvn/src/Main.java
index 9c77acc..213437c 100644
--- a/test/465-checker-clinit-gvn/src/Main.java
+++ b/test/465-checker-clinit-gvn/src/Main.java
@@ -54,11 +54,11 @@
 
   public static int accessTwoStaticsCallInBetween() {
     int b = OtherClass.b;
-    foo();
+    $noinline$foo();
     return b - OtherClass.a;
   }
 
-  public static void foo() {
+  public static void $noinline$foo() {
     try {
       Thread.sleep(0);
     } catch (Exception e) {
diff --git a/test/495-checker-checkcast-tests/src/Main.java b/test/495-checker-checkcast-tests/src/Main.java
index 6011c7c..2c0126a 100644
--- a/test/495-checker-checkcast-tests/src/Main.java
+++ b/test/495-checker-checkcast-tests/src/Main.java
@@ -15,207 +15,207 @@
  */
 
 public class Main {
-  public static boolean $inline$classTypeTest(Object o) {
-    return ((SubMain)o) == o;
-  }
+    public static boolean $inline$classTypeTest(Object o) {
+        return ((SubMain) o) == o;
+    }
 
-  public static boolean $inline$interfaceTypeTest(Object o) {
-    return ((Itf)o) == o;
-  }
+    public static boolean $inline$interfaceTypeTest(Object o) {
+        return ((Itf) o) == o;
+    }
 
-  public static SubMain subMain;
-  public static Main mainField;
-  public static Unrelated unrelatedField;
-  public static FinalUnrelated finalUnrelatedField;
+    public static SubMain subMain;
+    public static Main mainField;
+    public static Unrelated unrelatedField;
+    public static FinalUnrelated finalUnrelatedField;
 
-  /// CHECK-START: boolean Main.classTypeTestNull() register (after)
-  /// CHECK-NOT: CheckCast
-  public static boolean classTypeTestNull() {
-    return $inline$classTypeTest(null);
-  }
+    /// CHECK-START: boolean Main.classTypeTestNull() register (after)
+    /// CHECK-NOT: CheckCast
+    public static boolean classTypeTestNull() {
+        return $inline$classTypeTest(null);
+    }
 
-  /// CHECK-START: boolean Main.classTypeTestExactMain() register (after)
-  /// CHECK: CheckCast
-  public static boolean classTypeTestExactMain() {
-    return $inline$classTypeTest(new Main());
-  }
+    /// CHECK-START: boolean Main.classTypeTestExactMain() register (after)
+    /// CHECK: CheckCast
+    public static boolean classTypeTestExactMain() {
+        return $inline$classTypeTest(new Main());
+    }
 
-  /// CHECK-START: boolean Main.classTypeTestExactSubMain() register (after)
-  /// CHECK-NOT: CheckCast
-  public static boolean classTypeTestExactSubMain() {
-    return $inline$classTypeTest(new SubMain());
-  }
+    /// CHECK-START: boolean Main.classTypeTestExactSubMain() register (after)
+    /// CHECK-NOT: CheckCast
+    public static boolean classTypeTestExactSubMain() {
+        return $inline$classTypeTest(new SubMain());
+    }
 
-  /// CHECK-START: boolean Main.classTypeTestSubMainOrNull() register (after)
-  /// CHECK-NOT: CheckCast
-  public static boolean classTypeTestSubMainOrNull() {
-    return $inline$classTypeTest(subMain);
-  }
+    /// CHECK-START: boolean Main.classTypeTestSubMainOrNull() register (after)
+    /// CHECK-NOT: CheckCast
+    public static boolean classTypeTestSubMainOrNull() {
+        return $inline$classTypeTest(subMain);
+    }
 
-  /// CHECK-START: boolean Main.classTypeTestMainOrNull() register (after)
-  /// CHECK: CheckCast
-  public static boolean classTypeTestMainOrNull() {
-    return $inline$classTypeTest(mainField);
-  }
+    /// CHECK-START: boolean Main.classTypeTestMainOrNull() register (after)
+    /// CHECK: CheckCast
+    public static boolean classTypeTestMainOrNull() {
+        return $inline$classTypeTest(mainField);
+    }
 
-  /// CHECK-START: boolean Main.classTypeTestUnrelated() register (after)
-  /// CHECK: CheckCast
-  public static boolean classTypeTestUnrelated() {
-    return $inline$classTypeTest(unrelatedField);
-  }
+    /// CHECK-START: boolean Main.classTypeTestUnrelated() register (after)
+    /// CHECK: CheckCast
+    public static boolean classTypeTestUnrelated() {
+        return $inline$classTypeTest(unrelatedField);
+    }
 
-  /// CHECK-START: boolean Main.classTypeTestFinalUnrelated() register (after)
-  /// CHECK: CheckCast
-  public static boolean classTypeTestFinalUnrelated() {
-    return $inline$classTypeTest(finalUnrelatedField);
-  }
+    /// CHECK-START: boolean Main.classTypeTestFinalUnrelated() register (after)
+    /// CHECK: CheckCast
+    public static boolean classTypeTestFinalUnrelated() {
+        return $inline$classTypeTest(finalUnrelatedField);
+    }
 
-  /// CHECK-START: boolean Main.interfaceTypeTestNull() register (after)
-  /// CHECK-NOT: CheckCast
-  public static boolean interfaceTypeTestNull() {
-    return $inline$interfaceTypeTest(null);
-  }
+    /// CHECK-START: boolean Main.interfaceTypeTestNull() register (after)
+    /// CHECK-NOT: CheckCast
+    public static boolean interfaceTypeTestNull() {
+        return $inline$interfaceTypeTest(null);
+    }
 
-  /// CHECK-START: boolean Main.interfaceTypeTestExactMain() register (after)
-  /// CHECK: CheckCast
-  public static boolean interfaceTypeTestExactMain() {
-    return $inline$interfaceTypeTest(new Main());
-  }
+    /// CHECK-START: boolean Main.interfaceTypeTestExactMain() register (after)
+    /// CHECK: CheckCast
+    public static boolean interfaceTypeTestExactMain() {
+        return $inline$interfaceTypeTest(new Main());
+    }
 
-  /// CHECK-START: boolean Main.interfaceTypeTestExactSubMain() register (after)
-  /// CHECK-NOT: CheckCast
-  public static boolean interfaceTypeTestExactSubMain() {
-    return $inline$interfaceTypeTest(new SubMain());
-  }
+    /// CHECK-START: boolean Main.interfaceTypeTestExactSubMain() register (after)
+    /// CHECK-NOT: CheckCast
+    public static boolean interfaceTypeTestExactSubMain() {
+        return $inline$interfaceTypeTest(new SubMain());
+    }
 
-  /// CHECK-START: boolean Main.interfaceTypeTestSubMainOrNull() register (after)
-  /// CHECK-NOT: CheckCast
-  public static boolean interfaceTypeTestSubMainOrNull() {
-    return $inline$interfaceTypeTest(subMain);
-  }
+    /// CHECK-START: boolean Main.interfaceTypeTestSubMainOrNull() register (after)
+    /// CHECK-NOT: CheckCast
+    public static boolean interfaceTypeTestSubMainOrNull() {
+        return $inline$interfaceTypeTest(subMain);
+    }
 
-  /// CHECK-START: boolean Main.interfaceTypeTestMainOrNull() register (after)
-  /// CHECK: CheckCast
-  public static boolean interfaceTypeTestMainOrNull() {
-    return $inline$interfaceTypeTest(mainField);
-  }
+    /// CHECK-START: boolean Main.interfaceTypeTestMainOrNull() register (after)
+    /// CHECK: CheckCast
+    public static boolean interfaceTypeTestMainOrNull() {
+        return $inline$interfaceTypeTest(mainField);
+    }
 
-  /// CHECK-START: boolean Main.interfaceTypeTestUnrelated() register (after)
-  /// CHECK: CheckCast
-  public static boolean interfaceTypeTestUnrelated() {
-    return $inline$interfaceTypeTest(unrelatedField);
-  }
+    /// CHECK-START: boolean Main.interfaceTypeTestUnrelated() register (after)
+    /// CHECK: CheckCast
+    public static boolean interfaceTypeTestUnrelated() {
+        return $inline$interfaceTypeTest(unrelatedField);
+    }
 
-  /// CHECK-START: boolean Main.interfaceTypeTestFinalUnrelated() register (after)
-  /// CHECK: CheckCast
-  public static boolean interfaceTypeTestFinalUnrelated() {
-    return $inline$interfaceTypeTest(finalUnrelatedField);
-  }
+    /// CHECK-START: boolean Main.interfaceTypeTestFinalUnrelated() register (after)
+    /// CHECK: CheckCast
+    public static boolean interfaceTypeTestFinalUnrelated() {
+        return $inline$interfaceTypeTest(finalUnrelatedField);
+    }
 
-  /// CHECK-START: java.lang.String Main.knownTestWithLoadedClass() register (after)
-  /// CHECK-NOT: CheckCast
-  public static String knownTestWithLoadedClass() {
-    return (String)$inline$getString();
-  }
+    /// CHECK-START: java.lang.String Main.knownTestWithLoadedClass() register (after)
+    /// CHECK-NOT: CheckCast
+    public static String knownTestWithLoadedClass() {
+        return (String)$inline$getString();
+    }
 
-  /// CHECK-START: Itf Main.knownTestWithUnloadedClass() register (after)
-  /// CHECK: CheckCast
-  public static Itf knownTestWithUnloadedClass() {
-    return (Itf)$inline$getString();
-  }
+    /// CHECK-START: Itf Main.knownTestWithUnloadedClass() register (after)
+    /// CHECK: CheckCast
+    public static Itf knownTestWithUnloadedClass() {
+        return (Itf)$inline$getString();
+    }
 
-  public static Object $inline$getString() {
-    return new String();
-  }
+    public static Object $inline$getString() {
+        return new String();
+    }
 
-  public static Object $inline$getMain() {
-    return new Main();
-  }
+    public static Object $inline$getMain() {
+        return new Main();
+    }
 
-  /// CHECK-START: void Main.nonNullBoundType() register (after)
-  /// CHECK-NOT: NullCheck
-  public static void nonNullBoundType() {
-    Main main = (Main)$inline$getMain();
-    main.getClass();
-  }
+    /// CHECK-START: void Main.nonNullBoundType() register (after)
+    /// CHECK-NOT: NullCheck
+    public static void nonNullBoundType() {
+        Main main = (Main)$inline$getMain();
+        main.getClass();
+    }
 
-  public static void main(String[] args) {
-    classTypeTestNull();
-    try {
-      classTypeTestExactMain();
-      throw new Error("ClassCastException expected");
-    } catch (ClassCastException e) {}
-    classTypeTestExactSubMain();
+    public static void main(String[] args) {
+        classTypeTestNull();
+        try {
+            classTypeTestExactMain();
+            throw new Error("ClassCastException expected");
+        } catch (ClassCastException e) {}
+        classTypeTestExactSubMain();
 
-    subMain = null;
-    classTypeTestSubMainOrNull();
-    subMain = new SubMain();
-    classTypeTestSubMainOrNull();
+        subMain = null;
+        classTypeTestSubMainOrNull();
+        subMain = new SubMain();
+        classTypeTestSubMainOrNull();
 
-    mainField = null;
-    classTypeTestMainOrNull();
-    mainField = new Main();
-    try {
-      classTypeTestMainOrNull();
-      throw new Error("ClassCastException expected");
-    } catch (ClassCastException e) {}
-    mainField = new SubMain();
-    classTypeTestMainOrNull();
+        mainField = null;
+        classTypeTestMainOrNull();
+        mainField = new Main();
+        try {
+            classTypeTestMainOrNull();
+            throw new Error("ClassCastException expected");
+        } catch (ClassCastException e) {}
+        mainField = new SubMain();
+        classTypeTestMainOrNull();
 
-    unrelatedField = null;
-    classTypeTestUnrelated();
-    unrelatedField = new Unrelated();
-    try {
-      classTypeTestUnrelated();
-      throw new Error("ClassCastException expected");
-    } catch (ClassCastException e) {}
+        unrelatedField = null;
+        classTypeTestUnrelated();
+        unrelatedField = new Unrelated();
+        try {
+            classTypeTestUnrelated();
+            throw new Error("ClassCastException expected");
+        } catch (ClassCastException e) {}
 
-    finalUnrelatedField = null;
-    classTypeTestFinalUnrelated();
-    finalUnrelatedField = new FinalUnrelated();
-    try {
-      classTypeTestFinalUnrelated();
-      throw new Error("ClassCastException expected");
-    } catch (ClassCastException e) {}
+        finalUnrelatedField = null;
+        classTypeTestFinalUnrelated();
+        finalUnrelatedField = new FinalUnrelated();
+        try {
+            classTypeTestFinalUnrelated();
+            throw new Error("ClassCastException expected");
+        } catch (ClassCastException e) {}
 
-    interfaceTypeTestNull();
-    try {
-      interfaceTypeTestExactMain();
-      throw new Error("ClassCastException expected");
-    } catch (ClassCastException e) {}
-    interfaceTypeTestExactSubMain();
+        interfaceTypeTestNull();
+        try {
+            interfaceTypeTestExactMain();
+            throw new Error("ClassCastException expected");
+        } catch (ClassCastException e) {}
+        interfaceTypeTestExactSubMain();
 
-    subMain = null;
-    interfaceTypeTestSubMainOrNull();
-    subMain = new SubMain();
-    interfaceTypeTestSubMainOrNull();
+        subMain = null;
+        interfaceTypeTestSubMainOrNull();
+        subMain = new SubMain();
+        interfaceTypeTestSubMainOrNull();
 
-    mainField = null;
-    interfaceTypeTestMainOrNull();
-    mainField = new Main();
-    try {
-      interfaceTypeTestMainOrNull();
-      throw new Error("ClassCastException expected");
-    } catch (ClassCastException e) {}
-    mainField = new SubMain();
-    interfaceTypeTestMainOrNull();
+        mainField = null;
+        interfaceTypeTestMainOrNull();
+        mainField = new Main();
+        try {
+            interfaceTypeTestMainOrNull();
+            throw new Error("ClassCastException expected");
+        } catch (ClassCastException e) {}
+        mainField = new SubMain();
+        interfaceTypeTestMainOrNull();
 
-    unrelatedField = null;
-    interfaceTypeTestUnrelated();
-    unrelatedField = new Unrelated();
-    try {
-      interfaceTypeTestUnrelated();
-      throw new Error("ClassCastException expected");
-    } catch (ClassCastException e) {}
+        unrelatedField = null;
+        interfaceTypeTestUnrelated();
+        unrelatedField = new Unrelated();
+        try {
+            interfaceTypeTestUnrelated();
+            throw new Error("ClassCastException expected");
+        } catch (ClassCastException e) {}
 
-    finalUnrelatedField = null;
-    interfaceTypeTestFinalUnrelated();
-    finalUnrelatedField = new FinalUnrelated();
-    try {
-      interfaceTypeTestFinalUnrelated();
-      throw new Error("ClassCastException expected");
-    } catch (ClassCastException e) {}
-  }
+        finalUnrelatedField = null;
+        interfaceTypeTestFinalUnrelated();
+        finalUnrelatedField = new FinalUnrelated();
+        try {
+            interfaceTypeTestFinalUnrelated();
+            throw new Error("ClassCastException expected");
+        } catch (ClassCastException e) {}
+    }
 }
 
 interface Itf {
diff --git a/test/496-checker-inlining-class-loader/src/FirstSeenByMyClassLoader.java b/test/496-checker-inlining-class-loader/src/FirstSeenByMyClassLoader.java
index e97b4e3..fe0582e 100644
--- a/test/496-checker-inlining-class-loader/src/FirstSeenByMyClassLoader.java
+++ b/test/496-checker-inlining-class-loader/src/FirstSeenByMyClassLoader.java
@@ -15,12 +15,12 @@
  */
 
 public class FirstSeenByMyClassLoader {
-  public static void $inline$bar() {
-  }
+    public static void $inline$bar() {
+    }
 
-  public static void $noinline$bar() {
-    try {
-      System.out.println("In $noinline$bar");
-    } catch (Throwable t) { /* Ignore */ }
-  }
+    public static void $noinline$bar() {
+        try {
+            System.out.println("In $noinline$bar");
+        } catch (Throwable t) { /* Ignore */ }
+    }
 }
diff --git a/test/496-checker-inlining-class-loader/src/Main.java b/test/496-checker-inlining-class-loader/src/Main.java
index 4fe4723..feb8fdc 100644
--- a/test/496-checker-inlining-class-loader/src/Main.java
+++ b/test/496-checker-inlining-class-loader/src/Main.java
@@ -20,115 +20,116 @@
 import java.util.List;
 
 class MyClassLoader extends ClassLoader {
-  MyClassLoader() throws Exception {
-    super(MyClassLoader.class.getClassLoader());
+    MyClassLoader() throws Exception {
+        super(MyClassLoader.class.getClassLoader());
 
-    // Some magic to get access to the pathList field of BaseDexClassLoader.
-    ClassLoader loader = getClass().getClassLoader();
-    Class<?> baseDexClassLoader = loader.getClass().getSuperclass();
-    Field f = baseDexClassLoader.getDeclaredField("pathList");
-    f.setAccessible(true);
-    Object pathList = f.get(loader);
+        // Some magic to get access to the pathList field of BaseDexClassLoader.
+        ClassLoader loader = getClass().getClassLoader();
+        Class<?> baseDexClassLoader = loader.getClass().getSuperclass();
+        Field f = baseDexClassLoader.getDeclaredField("pathList");
+        f.setAccessible(true);
+        Object pathList = f.get(loader);
 
-    // Some magic to get access to the dexField field of pathList.
-    // Need to make a copy of the dex elements since we don't want an app image with pre-resolved
-    // things.
-    f = pathList.getClass().getDeclaredField("dexElements");
-    f.setAccessible(true);
-    Object[] dexElements = (Object[]) f.get(pathList);
-    f = dexElements[0].getClass().getDeclaredField("dexFile");
-    f.setAccessible(true);
-    for (Object element : dexElements) {
-      Object dexFile = f.get(element);
-      // Make copy.
-      Field fileNameField = dexFile.getClass().getDeclaredField("mFileName");
-      fileNameField.setAccessible(true);
-      dexFiles.add(dexFile.getClass().getDeclaredConstructor(String.class).newInstance(
-        fileNameField.get(dexFile)));
-    }
-  }
-
-  ArrayList<Object> dexFiles = new ArrayList<Object>();
-  Field dexFileField;
-
-  protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
-    // Other classes may also get loaded, ignore those.
-    if (className.equals("LoadedByMyClassLoader") || className.equals("FirstSeenByMyClassLoader")) {
-      System.out.println("Request for " + className);
-    }
-
-    // We're only going to handle LoadedByMyClassLoader.
-    if (className != "LoadedByMyClassLoader") {
-      return getParent().loadClass(className);
-    }
-
-    // Mimic what DexPathList.findClass is doing.
-    try {
-      for (Object dexFile : dexFiles) {
-        Method method = dexFile.getClass().getDeclaredMethod(
-            "loadClassBinaryName", String.class, ClassLoader.class, List.class);
-
-        if (dexFile != null) {
-          Class<?> clazz = (Class<?>)method.invoke(dexFile, className, this, null);
-          if (clazz != null) {
-            return clazz;
-          }
+        // Some magic to get access to the dexField field of pathList.
+        // Need to make a copy of the dex elements since we don't want an app image
+        // with pre-resolved things.
+        f = pathList.getClass().getDeclaredField("dexElements");
+        f.setAccessible(true);
+        Object[] dexElements = (Object[]) f.get(pathList);
+        f = dexElements[0].getClass().getDeclaredField("dexFile");
+        f.setAccessible(true);
+        for (Object element : dexElements) {
+            Object dexFile = f.get(element);
+            // Make copy.
+            Field fileNameField = dexFile.getClass().getDeclaredField("mFileName");
+            fileNameField.setAccessible(true);
+            dexFiles.add(dexFile.getClass().getDeclaredConstructor(String.class).newInstance(
+                fileNameField.get(dexFile)));
         }
-      }
-    } catch (Exception e) { /* Ignore */ }
-    return null;
-  }
+    }
+
+    ArrayList<Object> dexFiles = new ArrayList<Object>();
+    Field dexFileField;
+
+    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
+        // Other classes may also get loaded, ignore those.
+        if (className.equals("LoadedByMyClassLoader")
+                || className.equals("FirstSeenByMyClassLoader")) {
+            System.out.println("Request for " + className);
+        }
+
+        // We're only going to handle LoadedByMyClassLoader.
+        if (className != "LoadedByMyClassLoader") {
+            return getParent().loadClass(className);
+        }
+
+        // Mimic what DexPathList.findClass is doing.
+        try {
+            for (Object dexFile : dexFiles) {
+                Method method = dexFile.getClass().getDeclaredMethod(
+                        "loadClassBinaryName", String.class, ClassLoader.class, List.class);
+
+                if (dexFile != null) {
+                    Class<?> clazz = (Class<?>) method.invoke(dexFile, className, this, null);
+                    if (clazz != null) {
+                        return clazz;
+                    }
+                }
+            }
+        } catch (Exception e) { /* Ignore */ }
+        return null;
+    }
 }
 
 class LoadedByMyClassLoader {
-  /// CHECK-START: void LoadedByMyClassLoader.bar() inliner (before)
-  /// CHECK:      LoadClass class_name:FirstSeenByMyClassLoader
-  /// CHECK-NEXT: ClinitCheck
-  /// CHECK-NEXT: InvokeStaticOrDirect
-  /// CHECK-NEXT: LoadClass class_name:java.lang.System
-  /// CHECK-NEXT: ClinitCheck
-  /// CHECK-NEXT: StaticFieldGet
-  /// CHECK-NEXT: LoadString
-  /// CHECK-NEXT: NullCheck
-  /// CHECK-NEXT: InvokeVirtual
+    /// CHECK-START: void LoadedByMyClassLoader.bar() inliner (before)
+    /// CHECK:      LoadClass class_name:FirstSeenByMyClassLoader
+    /// CHECK-NEXT: ClinitCheck
+    /// CHECK-NEXT: InvokeStaticOrDirect
+    /// CHECK-NEXT: LoadClass class_name:java.lang.System
+    /// CHECK-NEXT: ClinitCheck
+    /// CHECK-NEXT: StaticFieldGet
+    /// CHECK-NEXT: LoadString
+    /// CHECK-NEXT: NullCheck
+    /// CHECK-NEXT: InvokeVirtual
 
-  /// CHECK-START: void LoadedByMyClassLoader.bar() inliner (after)
-  /// CHECK:      LoadClass class_name:FirstSeenByMyClassLoader
-  /// CHECK-NEXT: ClinitCheck
-                /* We inlined FirstSeenByMyClassLoader.$inline$bar */
-  /// CHECK-NEXT: LoadClass class_name:java.lang.System
-  /// CHECK-NEXT: ClinitCheck
-  /// CHECK-NEXT: StaticFieldGet
-  /// CHECK-NEXT: LoadString
-  /// CHECK-NEXT: NullCheck
-  /// CHECK-NEXT: InvokeVirtual
+    /// CHECK-START: void LoadedByMyClassLoader.bar() inliner (after)
+    /// CHECK:      LoadClass class_name:FirstSeenByMyClassLoader
+    /// CHECK-NEXT: ClinitCheck
+                  /* We inlined FirstSeenByMyClassLoader.$inline$bar */
+    /// CHECK-NEXT: LoadClass class_name:java.lang.System
+    /// CHECK-NEXT: ClinitCheck
+    /// CHECK-NEXT: StaticFieldGet
+    /// CHECK-NEXT: LoadString
+    /// CHECK-NEXT: NullCheck
+    /// CHECK-NEXT: InvokeVirtual
 
-  /// CHECK-START: void LoadedByMyClassLoader.bar() register (before)
-                /* Load and initialize FirstSeenByMyClassLoader */
-  /// CHECK:      LoadClass class_name:FirstSeenByMyClassLoader gen_clinit_check:true
-                /* Load and initialize System */
-  // There may be HX86ComputeBaseMethodAddress here.
-  /// CHECK:      LoadClass class_name:java.lang.System
-  // The ClinitCheck may (PIC) or may not (non-PIC) be merged into the LoadClass.
-  // (The merging checks for environment match but HLoadClass/kBootImageAddress
-  // used for non-PIC mode does not have an environment at all.)
-  /// CHECK:      StaticFieldGet
-  // There may be HX86ComputeBaseMethodAddress here.
-  /// CHECK:      LoadString
-  /// CHECK-NEXT: NullCheck
-  /// CHECK-NEXT: InvokeVirtual
-  public static void bar() {
-    FirstSeenByMyClassLoader.$inline$bar();
-    System.out.println("In between the two calls.");
-    FirstSeenByMyClassLoader.$noinline$bar();
-  }
+    /// CHECK-START: void LoadedByMyClassLoader.bar() register (before)
+                   /* Load and initialize FirstSeenByMyClassLoader */
+    /// CHECK:      LoadClass class_name:FirstSeenByMyClassLoader gen_clinit_check:true
+                   /* Load and initialize System */
+    // There may be HX86ComputeBaseMethodAddress here.
+    /// CHECK:      LoadClass class_name:java.lang.System
+    // The ClinitCheck may (PIC) or may not (non-PIC) be merged into the LoadClass.
+    // (The merging checks for environment match but HLoadClass/kBootImageAddress
+    // used for non-PIC mode does not have an environment at all.)
+    /// CHECK:      StaticFieldGet
+    // There may be HX86ComputeBaseMethodAddress here.
+    /// CHECK:      LoadString
+    /// CHECK-NEXT: NullCheck
+    /// CHECK-NEXT: InvokeVirtual
+    public static void bar() {
+        FirstSeenByMyClassLoader.$inline$bar();
+        System.out.println("In between the two calls.");
+        FirstSeenByMyClassLoader.$noinline$bar();
+    }
 }
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    MyClassLoader o = new MyClassLoader();
-    Class<?> foo = o.loadClass("LoadedByMyClassLoader");
-    Method m = foo.getDeclaredMethod("bar");
-    m.invoke(null);
-  }
+    public static void main(String[] args) throws Exception {
+        MyClassLoader o = new MyClassLoader();
+        Class<?> foo = o.loadClass("LoadedByMyClassLoader");
+        Method m = foo.getDeclaredMethod("bar");
+        m.invoke(null);
+    }
 }
diff --git a/test/497-inlining-and-class-loader/clear_dex_cache.cc b/test/497-inlining-and-class-loader/clear_dex_cache.cc
index 36ec4eb..ddbcef5 100644
--- a/test/497-inlining-and-class-loader/clear_dex_cache.cc
+++ b/test/497-inlining-and-class-loader/clear_dex_cache.cc
@@ -34,7 +34,7 @@
   ScopedObjectAccess soa(Thread::Current());
   ObjPtr<mirror::DexCache> dex_cache = soa.Decode<mirror::Class>(cls)->GetDexCache();
   size_t num_methods = dex_cache->NumResolvedMethods();
-  mirror::MethodDexCacheType* methods = dex_cache->GetResolvedMethods();
+  auto* methods = dex_cache->GetResolvedMethods();
   CHECK_EQ(num_methods != 0u, methods != nullptr);
   if (num_methods == 0u) {
     return nullptr;
@@ -48,7 +48,7 @@
   CHECK(array != nullptr);
   ObjPtr<mirror::Array> decoded_array = soa.Decode<mirror::Array>(array);
   for (size_t i = 0; i != num_methods; ++i) {
-    auto pair = mirror::DexCache::GetNativePair(methods, i);
+    auto pair = methods->GetNativePair(i);
     uint32_t index = pair.index;
     ArtMethod* method = pair.object;
     if (sizeof(void*) == 4) {
@@ -69,7 +69,7 @@
   ScopedObjectAccess soa(Thread::Current());
   ObjPtr<mirror::DexCache> dex_cache = soa.Decode<mirror::Class>(cls)->GetDexCache();
   size_t num_methods = dex_cache->NumResolvedMethods();
-  mirror::MethodDexCacheType* methods = dex_cache->GetResolvedMethods();
+  auto* methods = dex_cache->GetResolvedMethods();
   CHECK_EQ(num_methods != 0u, methods != nullptr);
   ObjPtr<mirror::Array> old = soa.Decode<mirror::Array>(old_cache);
   CHECK_EQ(methods != nullptr, old != nullptr);
@@ -86,8 +86,8 @@
       index = dchecked_integral_cast<uint32_t>(long_array->Get(2u * i));
       method = reinterpret_cast64<ArtMethod*>(long_array->Get(2u * i + 1u));
     }
-    mirror::MethodDexCachePair pair(method, index);
-    mirror::DexCache::SetNativePair(methods, i, pair);
+    mirror::NativeDexCachePair<ArtMethod> pair(method, index);
+    methods->SetNativePair(i, pair);
   }
 }
 
diff --git a/test/497-inlining-and-class-loader/src/Level1.java b/test/497-inlining-and-class-loader/src/Level1.java
index 977af83..18f79ce 100644
--- a/test/497-inlining-and-class-loader/src/Level1.java
+++ b/test/497-inlining-and-class-loader/src/Level1.java
@@ -15,13 +15,13 @@
  */
 
 public class Level1 {
-  public static void $inline$bar() {
-    Level2.$inline$bar();
-  }
+    public static void $inline$bar() {
+        Level2.$inline$bar();
+    }
 }
 
 class Level2 {
-  public static void $inline$bar() {
-    Main.$noinline$bar();
-  }
+    public static void $inline$bar() {
+        Main.$noinline$bar();
+    }
 }
diff --git a/test/497-inlining-and-class-loader/src/Main.java b/test/497-inlining-and-class-loader/src/Main.java
index 01b4bcd..66a3f6e 100644
--- a/test/497-inlining-and-class-loader/src/Main.java
+++ b/test/497-inlining-and-class-loader/src/Main.java
@@ -19,112 +19,112 @@
 import java.util.List;
 
 class MyClassLoader extends ClassLoader {
-  MyClassLoader() throws Exception {
-    super(MyClassLoader.class.getClassLoader());
+    MyClassLoader() throws Exception {
+        super(MyClassLoader.class.getClassLoader());
 
-    // Some magic to get access to the pathList field of BaseDexClassLoader.
-    ClassLoader loader = getClass().getClassLoader();
-    Class<?> baseDexClassLoader = loader.getClass().getSuperclass();
-    Field f = baseDexClassLoader.getDeclaredField("pathList");
-    f.setAccessible(true);
-    Object pathList = f.get(loader);
+        // Some magic to get access to the pathList field of BaseDexClassLoader.
+        ClassLoader loader = getClass().getClassLoader();
+        Class<?> baseDexClassLoader = loader.getClass().getSuperclass();
+        Field f = baseDexClassLoader.getDeclaredField("pathList");
+        f.setAccessible(true);
+        Object pathList = f.get(loader);
 
-    // Some magic to get access to the dexField field of pathList.
-    f = pathList.getClass().getDeclaredField("dexElements");
-    f.setAccessible(true);
-    dexElements = (Object[]) f.get(pathList);
-    dexFileField = dexElements[0].getClass().getDeclaredField("dexFile");
-    dexFileField.setAccessible(true);
-  }
-
-  Object[] dexElements;
-  Field dexFileField;
-
-  static ClassLoader level1ClassLoader;
-
-  protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
-    if (this != level1ClassLoader) {
-      if (className.equals("Level1")) {
-        return level1ClassLoader.loadClass(className);
-      } else if (className.equals("Level2")) {
-        throw new ClassNotFoundException("None of my methods require Level2!");
-      } else if (!className.equals("LoadedByMyClassLoader")) {
-        // We're only going to handle LoadedByMyClassLoader.
-        return getParent().loadClass(className);
-      }
-    } else {
-      if (className != "Level1" && className != "Level2") {
-        return getParent().loadClass(className);
-      }
+        // Some magic to get access to the dexField field of pathList.
+        f = pathList.getClass().getDeclaredField("dexElements");
+        f.setAccessible(true);
+        dexElements = (Object[]) f.get(pathList);
+        dexFileField = dexElements[0].getClass().getDeclaredField("dexFile");
+        dexFileField.setAccessible(true);
     }
 
-    // Mimic what DexPathList.findClass is doing.
-    try {
-      for (Object element : dexElements) {
-        Object dex = dexFileField.get(element);
-        Method method = dex.getClass().getDeclaredMethod(
-            "loadClassBinaryName", String.class, ClassLoader.class, List.class);
+    Object[] dexElements;
+    Field dexFileField;
 
-        if (dex != null) {
-          Class<?> clazz = (Class<?>)method.invoke(dex, className, this, null);
-          if (clazz != null) {
-            return clazz;
-          }
+    static ClassLoader level1ClassLoader;
+
+    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
+        if (this != level1ClassLoader) {
+            if (className.equals("Level1")) {
+                return level1ClassLoader.loadClass(className);
+            } else if (className.equals("Level2")) {
+                throw new ClassNotFoundException("None of my methods require Level2!");
+            } else if (!className.equals("LoadedByMyClassLoader")) {
+                // We're only going to handle LoadedByMyClassLoader.
+                return getParent().loadClass(className);
+            }
+        } else {
+            if (className != "Level1" && className != "Level2") {
+                return getParent().loadClass(className);
+            }
         }
-      }
-    } catch (Exception e) { /* Ignore */ }
-    return null;
-  }
+
+        // Mimic what DexPathList.findClass is doing.
+        try {
+            for (Object element : dexElements) {
+                Object dex = dexFileField.get(element);
+                Method method = dex.getClass().getDeclaredMethod(
+                        "loadClassBinaryName", String.class, ClassLoader.class, List.class);
+
+                if (dex != null) {
+                    Class<?> clazz = (Class<?>) method.invoke(dex, className, this, null);
+                    if (clazz != null) {
+                        return clazz;
+                    }
+                }
+            }
+        } catch (Exception e) { /* Ignore */ }
+        return null;
+    }
 }
 
 class LoadedByMyClassLoader {
-  public static void bar() {
-    Level1.$inline$bar();
-  }
+    public static void bar() {
+        Level1.$inline$bar();
+    }
 }
 
 class Main {
-  public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[0]);
-    // Clone resolved methods, to restore the original version just
-    // before we walk the stack in $noinline$bar.
-    savedResolvedMethods = cloneResolvedMethods(Main.class);
+    public static void main(String[] args) throws Exception {
+        System.loadLibrary(args[0]);
+        // Clone resolved methods, to restore the original version just
+        // before we walk the stack in $noinline$bar.
+        savedResolvedMethods = cloneResolvedMethods(Main.class);
 
-    MyClassLoader o = new MyClassLoader();
-    MyClassLoader.level1ClassLoader = new MyClassLoader();
-    Class<?> foo = o.loadClass("LoadedByMyClassLoader");
-    Method m = foo.getDeclaredMethod("bar");
-    try {
-      m.invoke(null);
-    } catch (Error e) { /* Ignore */ }
-  }
+        MyClassLoader o = new MyClassLoader();
+        MyClassLoader.level1ClassLoader = new MyClassLoader();
+        Class<?> foo = o.loadClass("LoadedByMyClassLoader");
+        Method m = foo.getDeclaredMethod("bar");
+        try {
+            m.invoke(null);
+        } catch (Error e) { /* Ignore */ }
+    }
 
-  public static void $inline$bar() {
-  }
+    public static void $inline$bar() {
+    }
 
-  public static void $noinline$bar() {
-    try {
-      // Be evil and clear all dex cache entries.
-      Field f = Class.class.getDeclaredField("dexCache");
-      f.setAccessible(true);
-      Object dexCache = f.get(Main.class);
-      f = dexCache.getClass().getDeclaredField("resolvedTypes");
-      f.setAccessible(true);
-      Object[] array = (Object[]) f.get(dexCache);
-      for (int i = 0; i < array.length; i++) {
-        array[i] = null;
-      }
-      restoreResolvedMethods(Main.class, savedResolvedMethods);
-    } catch (Throwable t) { /* Ignore */ }
+    public static void $noinline$bar() {
+        try {
+            // Be evil and clear all dex cache entries.
+            Field f = Class.class.getDeclaredField("dexCache");
+            f.setAccessible(true);
+            Object dexCache = f.get(Main.class);
+            f = dexCache.getClass().getDeclaredField("resolvedTypes");
+            f.setAccessible(true);
+            Object[] array = (Object[]) f.get(dexCache);
+            for (int i = 0; i < array.length; i++) {
+                array[i] = null;
+            }
+            restoreResolvedMethods(Main.class, savedResolvedMethods);
+        } catch (Throwable t) { /* Ignore */ }
 
-    // This will walk the stack, trying to resolve methods in it.
-    // Because we cleared dex cache entries, we will have to find
-    // classes again, which require to use the correct class loader
-    // in the presence of inlining.
-    new Exception().printStackTrace(System.out);
-  }
-  static Object savedResolvedMethods;
+        // This will walk the stack, trying to resolve methods in it.
+        // Because we cleared dex cache entries, we will have to find
+        // classes again, which require to use the correct class loader
+        // in the presence of inlining.
+        new Exception().printStackTrace(System.out);
+    }
+    static Object savedResolvedMethods;
 
-  static native Object cloneResolvedMethods(Class<?> cls);
-  static native void restoreResolvedMethods(Class<?> cls, Object saved);
+    static native Object cloneResolvedMethods(Class<?> cls);
+    static native void restoreResolvedMethods(Class<?> cls, Object saved);
 }
diff --git a/test/498-type-propagation/src/Main.java b/test/498-type-propagation/src/Main.java
index b20794c..ceb35eb 100644
--- a/test/498-type-propagation/src/Main.java
+++ b/test/498-type-propagation/src/Main.java
@@ -17,11 +17,11 @@
 import java.lang.reflect.Method;
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    Class<?> c = Class.forName("TypePropagation");
-    Method m = c.getMethod("method", int[].class);
-    int[] array = new int[7];
-    Object[] arguments = { array };
-    m.invoke(null, arguments);
-  }
+    public static void main(String[] args) throws Exception {
+        Class<?> c = Class.forName("TypePropagation");
+        Method m = c.getMethod("method", int[].class);
+        int[] array = new int[7];
+        Object[] arguments = { array };
+        m.invoke(null, arguments);
+    }
 }
diff --git a/test/499-bce-phi-array-length/src/Main.java b/test/499-bce-phi-array-length/src/Main.java
index e917bc1..4e99771 100644
--- a/test/499-bce-phi-array-length/src/Main.java
+++ b/test/499-bce-phi-array-length/src/Main.java
@@ -15,50 +15,50 @@
  */
 
 public class Main {
-  public static int foo(int start, int[] array) {
-    int result = 0;
-    // We will create HDeoptimize nodes for this first loop, and a phi
-    // for the array length which will only be used within the loop.
-    for (int i = start; i < 3; i++) {
-      result += array[i];
-      for (int j = 0; j < 2; ++j) {
-        // The HBoundsCheck for this array access will be updated to access
-        // the array length phi created for the deoptimization checks of the
-        // first loop. This crashed the compiler which used to DCHECK an array
-        // length in a bounds check cannot be a phi.
-        result += array[j];
-      }
-    }
-    return result;
-  }
-
-  public static int bar(int start, int[] array) {
-    int result = 0;
-    for (int i = start; i < 3; i++) {
-      result += array[i];
-      for (int j = 0; j < 2; ++j) {
-        result += array[j];
-        // The following operations would lead to BCE wanting to add another
-        // deoptimization, but it crashed assuming the input of a `HBoundsCheck`
-        // must be a `HArrayLength`.
-        result += array[0];
-        result += array[1];
-        result += array[2];
-      }
-    }
-    return result;
-  }
-
-  public static void main(String[] args) {
-    int[] a = new int[] { 1, 2, 3, 4, 5 };
-    int result = foo(1, a);
-    if (result != 11) {
-      throw new Error("Got " + result + ", expected " + 11);
+    public static int foo(int start, int[] array) {
+        int result = 0;
+        // We will create HDeoptimize nodes for this first loop, and a phi
+        // for the array length which will only be used within the loop.
+        for (int i = start; i < 3; i++) {
+            result += array[i];
+            for (int j = 0; j < 2; ++j) {
+                // The HBoundsCheck for this array access will be updated to access
+                // the array length phi created for the deoptimization checks of the
+                // first loop. This crashed the compiler which used to DCHECK an array
+                // length in a bounds check cannot be a phi.
+                result += array[j];
+            }
+        }
+        return result;
     }
 
-    result = bar(1, a);
-    if (result != 35) {
-      throw new Error("Got " + result + ", expected " + 35);
+    public static int bar(int start, int[] array) {
+        int result = 0;
+        for (int i = start; i < 3; i++) {
+            result += array[i];
+            for (int j = 0; j < 2; ++j) {
+                result += array[j];
+                // The following operations would lead to BCE wanting to add another
+                // deoptimization, but it crashed assuming the input of a `HBoundsCheck`
+                // must be a `HArrayLength`.
+                result += array[0];
+                result += array[1];
+                result += array[2];
+            }
+        }
+        return result;
     }
-  }
+
+    public static void main(String[] args) {
+        int[] a = new int[] { 1, 2, 3, 4, 5 };
+        int result = foo(1, a);
+        if (result != 11) {
+            throw new Error("Got " + result + ", expected " + 11);
+        }
+
+        result = bar(1, a);
+        if (result != 35) {
+            throw new Error("Got " + result + ", expected " + 35);
+        }
+    }
 }
diff --git a/test/515-dce-dominator/src/Main.java b/test/515-dce-dominator/src/Main.java
index 8c6ce75..f36b90c 100644
--- a/test/515-dce-dominator/src/Main.java
+++ b/test/515-dce-dominator/src/Main.java
@@ -17,10 +17,10 @@
 import java.lang.reflect.Method;
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    Class<?> c = Class.forName("Dominator");
-    Method m = c.getMethod("method", int.class);
-    Object[] arguments = { 5 };
-    m.invoke(null, arguments);
-  }
+    public static void main(String[] args) throws Exception {
+        Class<?> c = Class.forName("Dominator");
+        Method m = c.getMethod("method", int.class);
+        Object[] arguments = { 5 };
+        m.invoke(null, arguments);
+    }
 }
diff --git a/test/516-dead-move-result/src/Main.java b/test/516-dead-move-result/src/Main.java
index 8f0891a..c2f34c7 100644
--- a/test/516-dead-move-result/src/Main.java
+++ b/test/516-dead-move-result/src/Main.java
@@ -17,10 +17,10 @@
 import java.lang.reflect.Method;
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    Class<?> c = Class.forName("MoveResult");
-    Method m = c.getMethod("method");
-    Object[] arguments = { };
-    m.invoke(null, arguments);
-  }
+    public static void main(String[] args) throws Exception {
+        Class<?> c = Class.forName("MoveResult");
+        Method m = c.getMethod("method");
+        Object[] arguments = { };
+        m.invoke(null, arguments);
+    }
 }
diff --git a/test/517-checker-builder-fallthrough/src/Main.java b/test/517-checker-builder-fallthrough/src/Main.java
index 14170f5..5bec073 100644
--- a/test/517-checker-builder-fallthrough/src/Main.java
+++ b/test/517-checker-builder-fallthrough/src/Main.java
@@ -18,15 +18,15 @@
 
 public class Main {
 
-  public static int runTest(int input) throws Exception {
-    Class<?> c = Class.forName("TestCase");
-    Method m = c.getMethod("testCase", int.class);
-    return (Integer) m.invoke(null, input);
-  }
-
-  public static void main(String[] args) throws Exception {
-    if (runTest(42) != 42) {
-      throw new Error("Expected 42");
+    public static int runTest(int input) throws Exception {
+        Class<?> c = Class.forName("TestCase");
+        Method m = c.getMethod("testCase", int.class);
+        return (Integer) m.invoke(null, input);
     }
-  }
+
+    public static void main(String[] args) throws Exception {
+        if (runTest(42) != 42) {
+            throw new Error("Expected 42");
+        }
+    }
 }
diff --git a/test/518-null-array-get/src/Main.java b/test/518-null-array-get/src/Main.java
index 7090194..12e2442 100644
--- a/test/518-null-array-get/src/Main.java
+++ b/test/518-null-array-get/src/Main.java
@@ -18,37 +18,37 @@
 import java.lang.reflect.Method;
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    checkLoad("NullArrayFailInt2Object", true);
-    checkLoad("NullArrayFailObject2Int", true);
-    checkLoad("NullArraySuccessInt", false);
-    checkLoad("NullArraySuccessInt2Float", false);
-    checkLoad("NullArraySuccessShort", false);
-    checkLoad("NullArraySuccessRef", false);
-  }
-
-  private static void checkLoad(String className, boolean expectError) throws Exception {
-    Class<?> c;
-    try {
-      c = Class.forName(className);
-      if (expectError) {
-        throw new RuntimeException("Expected error for " + className);
-      }
-      Method m = c.getMethod("method");
-      try {
-        m.invoke(null);
-        throw new RuntimeException("Expected an InvocationTargetException");
-      } catch (InvocationTargetException e) {
-        if (!(e.getCause() instanceof NullPointerException)) {
-          throw new RuntimeException("Expected a NullPointerException");
-        }
-        System.out.println(className);
-      }
-    } catch (VerifyError e) {
-      if (!expectError) {
-        throw new RuntimeException(e);
-      }
-      System.out.println(className);
+    public static void main(String[] args) throws Exception {
+        checkLoad("NullArrayFailInt2Object", true);
+        checkLoad("NullArrayFailObject2Int", true);
+        checkLoad("NullArraySuccessInt", false);
+        checkLoad("NullArraySuccessInt2Float", false);
+        checkLoad("NullArraySuccessShort", false);
+        checkLoad("NullArraySuccessRef", false);
     }
-  }
+
+    private static void checkLoad(String className, boolean expectError) throws Exception {
+        Class<?> c;
+        try {
+            c = Class.forName(className);
+            if (expectError) {
+                throw new RuntimeException("Expected error for " + className);
+            }
+            Method m = c.getMethod("method");
+            try {
+                m.invoke(null);
+                throw new RuntimeException("Expected an InvocationTargetException");
+            } catch (InvocationTargetException e) {
+                if (!(e.getCause() instanceof NullPointerException)) {
+                    throw new RuntimeException("Expected a NullPointerException");
+                }
+                System.out.println(className);
+            }
+        } catch (VerifyError e) {
+            if (!expectError) {
+                throw new RuntimeException(e);
+            }
+            System.out.println(className);
+        }
+    }
 }
diff --git a/test/519-bound-load-class/src/Main.java b/test/519-bound-load-class/src/Main.java
index cddeb09..c507aec 100644
--- a/test/519-bound-load-class/src/Main.java
+++ b/test/519-bound-load-class/src/Main.java
@@ -15,25 +15,25 @@
  */
 
 public class Main {
-  public static void main(String[] args) {
-    testInstanceOf();
-    try {
-      testNull();
-      throw new Error("Expected ClassClastException");
-    } catch (ClassCastException e) { /* ignore */ }
-  }
-
-  public static void testInstanceOf() {
-    Object o = Main.class;
-    if (o instanceof Main) {
-      System.out.println((Main)o);
+    public static void main(String[] args) {
+        testInstanceOf();
+        try {
+            testNull();
+            throw new Error("Expected ClassClastException");
+        } catch (ClassCastException e) { /* ignore */ }
     }
-  }
 
-  public static void testNull() {
-    Object o = Main.class;
-    if (o != null) {
-      System.out.println((Main)o);
+    public static void testInstanceOf() {
+        Object o = Main.class;
+        if (o instanceof Main) {
+            System.out.println((Main) o);
+        }
     }
-  }
+
+    public static void testNull() {
+        Object o = Main.class;
+        if (o != null) {
+            System.out.println((Main) o);
+        }
+    }
 }
diff --git a/test/521-checker-array-set-null/src/Main.java b/test/521-checker-array-set-null/src/Main.java
index f166b92..12a1985 100644
--- a/test/521-checker-array-set-null/src/Main.java
+++ b/test/521-checker-array-set-null/src/Main.java
@@ -16,26 +16,64 @@
 
 public class Main {
   public static void main(String[] args) {
-    testWithNull(new Object[2]);
-    testWithUnknown(new Object[2], new Object());
-    testWithSame(new Object[2]);
+    $noinline$testWithNull(new Object[2]);
+    $noinline$testWithUnknown(new Object[2], new Object());
+    $noinline$testWithSame(new Object[2]);
+    $noinline$testWithSameRTI();
   }
 
-  /// CHECK-START: void Main.testWithNull(java.lang.Object[]) disassembly (after)
-  /// CHECK:          ArraySet needs_type_check:false
-  public static void testWithNull(Object[] o) {
+  // Known null can eliminate the type check in early stages.
+
+  /// CHECK-START: void Main.$noinline$testWithNull(java.lang.Object[]) instruction_simplifier (before)
+  /// CHECK:          ArraySet needs_type_check:true can_trigger_gc:true
+
+  /// CHECK-START: void Main.$noinline$testWithNull(java.lang.Object[]) instruction_simplifier (after)
+  /// CHECK:          ArraySet needs_type_check:false can_trigger_gc:false
+
+  /// CHECK-START: void Main.$noinline$testWithNull(java.lang.Object[]) disassembly (after)
+  /// CHECK:          ArraySet needs_type_check:false can_trigger_gc:false
+  public static void $noinline$testWithNull(Object[] o) {
     o[0] = null;
   }
 
-  /// CHECK-START: void Main.testWithUnknown(java.lang.Object[], java.lang.Object) disassembly (after)
-  /// CHECK:          ArraySet needs_type_check:true
-  public static void testWithUnknown(Object[] o, Object obj) {
+  /// CHECK-START: void Main.$noinline$testWithUnknown(java.lang.Object[], java.lang.Object) disassembly (after)
+  /// CHECK:          ArraySet needs_type_check:true can_trigger_gc:true
+  public static void $noinline$testWithUnknown(Object[] o, Object obj) {
     o[0] = obj;
   }
 
-  /// CHECK-START: void Main.testWithSame(java.lang.Object[]) disassembly (after)
-  /// CHECK:          ArraySet needs_type_check:false
-  public static void testWithSame(Object[] o) {
+  // After GVN we know that we are setting values from the same array so there's no need for a type
+  // check.
+
+  /// CHECK-START: void Main.$noinline$testWithSame(java.lang.Object[]) instruction_simplifier$after_gvn (before)
+  /// CHECK:          ArraySet needs_type_check:true can_trigger_gc:true
+
+  /// CHECK-START: void Main.$noinline$testWithSame(java.lang.Object[]) instruction_simplifier$after_gvn (after)
+  /// CHECK:          ArraySet needs_type_check:false can_trigger_gc:false
+
+  /// CHECK-START: void Main.$noinline$testWithSame(java.lang.Object[]) disassembly (after)
+  /// CHECK:          ArraySet needs_type_check:false can_trigger_gc:false
+  public static void $noinline$testWithSame(Object[] o) {
     o[0] = o[1];
   }
+
+  // We know that the array and the static Object have the same RTI in early stages. No need for a
+  // type check.
+
+  /// CHECK-START: java.lang.Object[] Main.$noinline$testWithSameRTI() instruction_simplifier (before)
+  /// CHECK:          ArraySet needs_type_check:true can_trigger_gc:true
+
+  /// CHECK-START: java.lang.Object[] Main.$noinline$testWithSameRTI() instruction_simplifier (after)
+  /// CHECK:          ArraySet needs_type_check:false can_trigger_gc:false
+
+  /// CHECK-START: java.lang.Object[] Main.$noinline$testWithSameRTI() disassembly (after)
+  /// CHECK:          ArraySet needs_type_check:false can_trigger_gc:false
+  public static Object[] $noinline$testWithSameRTI() {
+    Object[] arr = new Object[1];
+    arr[0] = static_obj;
+    // Return so that LSE doesn't eliminate the ArraySet.
+    return arr;
+  }
+
+  static Object static_obj;
 }
diff --git a/test/521-regression-integer-field-set/src/Main.java b/test/521-regression-integer-field-set/src/Main.java
index 9924e09..90890a8 100644
--- a/test/521-regression-integer-field-set/src/Main.java
+++ b/test/521-regression-integer-field-set/src/Main.java
@@ -31,24 +31,12 @@
     assertIntEquals(456789, s);
   }
 
-  private static boolean doThrow = false;
-
   private void $noinline$SetInstanceField() {
-    if (doThrow) {
-      // Try defeating inlining.
-      throw new Error();
-    }
-
     // Set a value than does not fit in a 16-bit (signed) integer.
     i = 123456;
   }
 
   private static void $noinline$SetStaticField() {
-    if (doThrow) {
-      // Try defeating inlining.
-      throw new Error();
-    }
-
     // Set a value than does not fit in a 16-bit (signed) integer.
     s = 456789;
   }
diff --git a/test/526-checker-caller-callee-regs/src/Main.java b/test/526-checker-caller-callee-regs/src/Main.java
index f402c2c..9bd08ff 100644
--- a/test/526-checker-caller-callee-regs/src/Main.java
+++ b/test/526-checker-caller-callee-regs/src/Main.java
@@ -22,12 +22,8 @@
     }
   }
 
-  static boolean doThrow = false;
-
   // This function always returns 1.
-  // We use 'throw' to prevent the function from being inlined.
   public static int $opt$noinline$function_call(int arg) {
-    if (doThrow) throw new Error();
     return 1 % arg;
   }
 
diff --git a/test/526-long-regalloc/src/Main.java b/test/526-long-regalloc/src/Main.java
index e8b3096..489db05 100644
--- a/test/526-long-regalloc/src/Main.java
+++ b/test/526-long-regalloc/src/Main.java
@@ -54,12 +54,9 @@
   }
 
   public static long $noinline$bar() {
-    if (doThrow) throw new Error();
     return 42;
   }
 
-  public static boolean doThrow = false;
-
   public static int myField1 = 0;
   public static int myField2 = 0;
   public static int myField3 = 0;
diff --git a/test/527-checker-array-access-split/src/Main.java b/test/527-checker-array-access-split/src/Main.java
index f39b5e2..cc1dc6b 100644
--- a/test/527-checker-array-access-split/src/Main.java
+++ b/test/527-checker-array-access-split/src/Main.java
@@ -593,12 +593,16 @@
   /// CHECK:                        ArrayGet [<<IntAddr1>>,{{i\d+}}]
   /// CHECK: <<IntAddr2:i\d+>>      IntermediateAddress [<<Array>>,<<DataOffset>>]
   /// CHECK:                        ArrayGet [<<IntAddr2>>,{{i\d+}}]
-  /// CHECK:                        ArraySet [<<Array>>,{{i\d+}},{{l\d+}}]
+  /// CHECK:                        ArraySet [<<Array>>,{{i\d+}},{{l\d+}}] needs_type_check:false can_trigger_gc:false
   /// CHECK: <<IntAddr3:i\d+>>      IntermediateAddress [<<Array>>,<<DataOffset>>]
   /// CHECK:                        ArrayGet [<<IntAddr3>>,{{i\d+}}]
   /// CHECK:                        ArraySet [<<Array>>,{{i\d+}},{{l\d+}}]
   /// CHECK:                        ArraySet [<<Array>>,{{i\d+}},{{l\d+}}]
   //
+  /// CHECK-START-ARM64: int Main.checkObjectArrayGet(int, java.lang.Integer[], java.lang.Integer[]) instruction_simplifier_arm64 (after)
+  /// CHECK:                        IntermediateAddress
+  /// CHECK:                        IntermediateAddress
+  /// CHECK:                        IntermediateAddress
   /// CHECK-NOT:                    IntermediateAddress
 
   /// CHECK-START-ARM64: int Main.checkObjectArrayGet(int, java.lang.Integer[], java.lang.Integer[]) GVN$after_arch (after)
@@ -608,12 +612,13 @@
   /// CHECK: <<IntAddr1:i\d+>>      IntermediateAddress [<<Array>>,<<DataOffset>>]
   /// CHECK:                        ArrayGet [<<IntAddr1>>,{{i\d+}}]
   /// CHECK:                        ArrayGet [<<IntAddr1>>,{{i\d+}}]
-  /// CHECK:                        ArraySet [<<Array>>,{{i\d+}},{{l\d+}}]
-  /// CHECK: <<IntAddr3:i\d+>>      IntermediateAddress [<<Array>>,<<DataOffset>>]
-  /// CHECK:                        ArrayGet [<<IntAddr3>>,{{i\d+}}]
+  /// CHECK:                        ArraySet [<<Array>>,{{i\d+}},{{l\d+}}] needs_type_check:false can_trigger_gc:false
+  /// CHECK:                        ArrayGet [<<IntAddr1>>,{{i\d+}}]
   /// CHECK:                        ArraySet [<<Array>>,{{i\d+}},{{l\d+}}]
   /// CHECK:                        ArraySet [<<Array>>,{{i\d+}},{{l\d+}}]
   //
+  /// CHECK-START-ARM64: int Main.checkObjectArrayGet(int, java.lang.Integer[], java.lang.Integer[]) GVN$after_arch (after)
+  /// CHECK:                        IntermediateAddress
   /// CHECK-NOT:                    IntermediateAddress
   public final static int checkObjectArrayGet(int index, Integer[] a, Integer[] b) {
     Integer five = Integer.valueOf(5);
diff --git a/test/529-long-split/src/Main.java b/test/529-long-split/src/Main.java
index dc52d88..3493f77 100644
--- a/test/529-long-split/src/Main.java
+++ b/test/529-long-split/src/Main.java
@@ -167,14 +167,11 @@
   }
 
   public static void $noinline$doCall() {
-    if (doThrow) throw new Error();
   }
 
   public static void $noinline$doCall(long e) {
-    if (doThrow) throw new Error();
   }
 
-  public static boolean doThrow;
   public static int myField1;
   public static int myField2;
   public static int myField3;
diff --git a/test/530-checker-loops-try-catch/src/Main.java b/test/530-checker-loops-try-catch/src/Main.java
index 8d44b65..7927d9b 100644
--- a/test/530-checker-loops-try-catch/src/Main.java
+++ b/test/530-checker-loops-try-catch/src/Main.java
@@ -393,7 +393,7 @@
   /// CHECK-DAG: <<Add:i\d+>> Add [<<Int>>,<<Zer>>] loop:none
   /// CHECK-DAG:              Return [<<Add>>]      loop:none
 
-  /// CHECK-START: int Main.$noinline$poly1() instruction_simplifier$after_bce (after)
+  /// CHECK-START: int Main.$noinline$poly1() instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Int:i\d+>>  IntConstant 55 loop:none
   /// CHECK-DAG:               Return [<<Int>>]  loop:none
 
@@ -541,7 +541,7 @@
   /// CHECK-DAG: <<Add:i\d+>> Add [<<Int>>,<<Ini>>]   loop:none
   /// CHECK-DAG:              Return [<<Add>>]        loop:none
 
-  /// CHECK-START: int Main.$noinline$poly3() instruction_simplifier$after_bce (after)
+  /// CHECK-START: int Main.$noinline$poly3() instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Int:i\d+>>  IntConstant -2146724623 loop:none
   /// CHECK-DAG:               Return [<<Int>>]        loop:none
 
diff --git a/test/530-checker-loops3/src/Main.java b/test/530-checker-loops3/src/Main.java
index dfc4a5f..c7eaa56 100644
--- a/test/530-checker-loops3/src/Main.java
+++ b/test/530-checker-loops3/src/Main.java
@@ -132,7 +132,7 @@
   /// CHECK-DAG: Deoptimize loop:none
   /// CHECK-NOT: Deoptimize
   //
-  /// CHECK-START: void Main.multipleUnitStrides(int[], int[]) instruction_simplifier$after_bce (after)
+  /// CHECK-START: void Main.multipleUnitStrides(int[], int[]) instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: Deoptimize loop:none
   /// CHECK-DAG: Deoptimize loop:none
   /// CHECK-DAG: Deoptimize loop:none
@@ -164,7 +164,7 @@
   /// CHECK-DAG: Deoptimize loop:none
   /// CHECK-NOT: Deoptimize
   //
-  /// CHECK-START: void Main.multipleUnitStridesConditional(int[], int[]) instruction_simplifier$after_bce (after)
+  /// CHECK-START: void Main.multipleUnitStridesConditional(int[], int[]) instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: Deoptimize loop:none
   /// CHECK-DAG: Deoptimize loop:none
   /// CHECK-DAG: Deoptimize loop:none
@@ -196,7 +196,7 @@
   /// CHECK-DAG: Deoptimize loop:none
   /// CHECK-NOT: Deoptimize
   //
-  /// CHECK-START: void Main.shifter(int[]) instruction_simplifier$after_bce (after)
+  /// CHECK-START: void Main.shifter(int[]) instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: Deoptimize loop:none
   /// CHECK-DAG: Deoptimize loop:none
   /// CHECK-NOT: Deoptimize
diff --git a/test/530-checker-loops5/src/Main.java b/test/530-checker-loops5/src/Main.java
index 54b54d0..f934df3 100644
--- a/test/530-checker-loops5/src/Main.java
+++ b/test/530-checker-loops5/src/Main.java
@@ -30,7 +30,7 @@
   /// CHECK-DAG: <<Add:i\d+>> Add [<<Int>>,<<Zer>>] loop:none
   /// CHECK-DAG:              Return [<<Add>>]      loop:none
   //
-  /// CHECK-START: int Main.poly1() instruction_simplifier$after_bce (after)
+  /// CHECK-START: int Main.poly1() instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Int:i\d+>>  IntConstant 55 loop:none
   /// CHECK-DAG:               Return [<<Int>>]  loop:none
   //
@@ -83,7 +83,7 @@
   /// CHECK-DAG: <<Add:i\d+>> Add [<<Int>>,<<Ini>>]   loop:none
   /// CHECK-DAG:              Return [<<Add>>]        loop:none
   //
-  /// CHECK-START: int Main.poly3() instruction_simplifier$after_bce (after)
+  /// CHECK-START: int Main.poly3() instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Int:i\d+>>  IntConstant -2146724623 loop:none
   /// CHECK-DAG:               Return [<<Int>>]        loop:none
   //
diff --git a/test/530-checker-lse-simd/src/Main.java b/test/530-checker-lse-simd/src/Main.java
index d6049d0..619ac28 100644
--- a/test/530-checker-lse-simd/src/Main.java
+++ b/test/530-checker-lse-simd/src/Main.java
@@ -231,7 +231,6 @@
   /// CHECK:        BoundsCheck loop:none
   /// CHECK:        ArrayGet
   /// CHECK:        Add
-  /// CHECK:        ArrayLength
   //
   /// CHECK:        VecLoad loop:{{B\d+}}
   /// CHECK:        VecStore
@@ -244,7 +243,6 @@
   /// CHECK-START-ARM64: double[] Main.$noinline$test06(int) load_store_elimination (after)
   /// CHECK:        BoundsCheck loop:none
   /// CHECK:        Add
-  /// CHECK:        ArrayLength
   //
   /// CHECK:        VecLoad loop:{{B\d+}}
   /// CHECK:        VecAdd
diff --git a/test/530-checker-lse-try-catch/src/Main.java b/test/530-checker-lse-try-catch/src/Main.java
index 56ef8bd..3f4fa25 100644
--- a/test/530-checker-lse-try-catch/src/Main.java
+++ b/test/530-checker-lse-try-catch/src/Main.java
@@ -30,7 +30,19 @@
     assertEquals(1, $noinline$testTryCatchBlocking(new Point(), boolean_throw));
     assertEquals(1, $noinline$testTryCatchPhi(new Point(), boolean_throw));
     assertEquals(2, $noinline$testTryCatchPhiWithTwoCatches(new Point(), new int[0]));
-    assertEquals(1, $noinline$testKeepStoreInsideTryCatch());
+    assertEquals(1, $noinline$testKeepStoreInsideTry());
+    assertEquals(10, $noinline$testDontKeepStoreInsideCatch(new int[]{10}));
+    assertEquals(30, $noinline$testDontKeepStoreInsideCatch(new int[]{}));
+    assertEquals(10, $noinline$testKeepStoreInsideCatchWithOuterTry(new int[]{10}));
+    assertEquals(30, $noinline$testKeepStoreInsideCatchWithOuterTry(new int[]{}));
+    assertEquals(40, $noinline$testDontKeepStoreInsideFinally(new int[]{10}));
+    try {
+      assertEquals(30, $noinline$testDontKeepStoreInsideFinally(new int[]{}));
+      throw new Error("Unreachable");
+    } catch (ArrayIndexOutOfBoundsException expected) {
+    }
+    assertEquals(10, $noinline$testDontKeepStoreInsideOuterCatch(new int[]{10}));
+    assertEquals(100030, $noinline$testDontKeepStoreInsideOuterCatch(new int[]{}));
     assertEquals(150, $noinline$test40());
   }
 
@@ -205,17 +217,17 @@
   // is observable.
 
   // Consistency check to make sure the try/catch wasn't removed by an earlier pass.
-  /// CHECK-START: int Main.$noinline$testKeepStoreInsideTryCatch() load_store_elimination (after)
+  /// CHECK-START: int Main.$noinline$testKeepStoreInsideTry() load_store_elimination (after)
   /// CHECK-DAG: TryBoundary kind:entry
 
-  /// CHECK-START: int Main.$noinline$testKeepStoreInsideTryCatch() load_store_elimination (before)
+  /// CHECK-START: int Main.$noinline$testKeepStoreInsideTry() load_store_elimination (before)
   /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
   /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
 
-  /// CHECK-START: int Main.$noinline$testKeepStoreInsideTryCatch() load_store_elimination (after)
+  /// CHECK-START: int Main.$noinline$testKeepStoreInsideTry() load_store_elimination (after)
   /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
   /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
-  private static int $noinline$testKeepStoreInsideTryCatch() {
+  private static int $noinline$testKeepStoreInsideTry() {
     Main main = new Main();
     main.sumForKeepStoreInsideTryCatch = 0;
     try {
@@ -228,6 +240,118 @@
     }
   }
 
+  private static int $noinline$returnValue(int value) {
+    return value;
+  }
+
+  /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideCatch(int[]) load_store_elimination (before)
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+
+  /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideCatch(int[]) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  private static int $noinline$testDontKeepStoreInsideCatch(int[] array) {
+    Main main = new Main();
+    int value = 0;
+    try {
+      value = array[0];
+    } catch (Exception e) {
+      // These sets can be eliminated even though we have invokes since this catch is not part of an
+      // outer try.
+      main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(10);
+      main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(20);
+    }
+    return main.sumForKeepStoreInsideTryCatch + value;
+  }
+
+  /// CHECK-START: int Main.$noinline$testKeepStoreInsideCatchWithOuterTry(int[]) load_store_elimination (before)
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+
+  /// CHECK-START: int Main.$noinline$testKeepStoreInsideCatchWithOuterTry(int[]) load_store_elimination (after)
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  private static int $noinline$testKeepStoreInsideCatchWithOuterTry(int[] array) {
+    Main main = new Main();
+    int value = 0;
+    try {
+      try {
+        value = array[0];
+      } catch (Exception e) {
+        // These sets can't be eliminated since this catch is part of a outer try.
+        main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(10);
+        main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(20);
+      }
+    } catch (Exception e) {
+      value = 100000;
+    }
+
+    return main.sumForKeepStoreInsideTryCatch + value;
+  }
+
+  // Note that there are four `InstanceFieldSet` instead of two since we split the `finally` block
+  // into the normal path, and the exceptional path.
+
+  /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideFinally(int[]) load_store_elimination (before)
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+
+  /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideFinally(int[]) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  private static int $noinline$testDontKeepStoreInsideFinally(int[] array) {
+    Main main = new Main();
+    int value = 0;
+    try {
+      value = array[0];
+    } finally {
+      // These sets can be eliminated even though we have invokes since this catch is not part of an
+      // outer try.
+      main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(10);
+      main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(20);
+    }
+    return main.sumForKeepStoreInsideTryCatch + value;
+  }
+
+  // Checks that we are able to do LSE inside of catches which are outside of try blocks.
+
+  /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideOuterCatch(int[]) load_store_elimination (before)
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+
+  // This store potentially can be eliminated too, but our phi creation logic doesn't realize it can
+  // create a Phi for `main.sumForKeepStoreInsideTryCatch` and skip a store+load.
+
+  /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideOuterCatch(int[]) load_store_elimination (after)
+  /// CHECK:     InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+  /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch
+
+  private static int $noinline$testDontKeepStoreInsideOuterCatch(int[] array) {
+    Main main = new Main();
+    int value = 0;
+    try {
+      value = array[0];
+    } catch (ArrayIndexOutOfBoundsException expected) {
+      // These sets and gets are not considered to be part of a try so they are free to be
+      // eliminated.
+      main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(10);
+      main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(20);
+      try {
+        value = array[0];
+      } catch (ArrayIndexOutOfBoundsException expectedToo) {
+        value = 100000;
+      }
+    }
+
+    return main.sumForKeepStoreInsideTryCatch + value;
+  }
+
   /// CHECK-START: int Main.$noinline$test40() load_store_elimination (before)
   /// CHECK:                     ArraySet
   /// CHECK:                     DivZeroCheck
diff --git a/test/530-checker-lse/src/Main.java b/test/530-checker-lse/src/Main.java
index 5d55d3e..8754449 100644
--- a/test/530-checker-lse/src/Main.java
+++ b/test/530-checker-lse/src/Main.java
@@ -217,12 +217,9 @@
   /// CHECK-DAG: Return
 
   /// CHECK-START: int Main.test4(TestClass, boolean) load_store_elimination (after)
-  /// CHECK:     NullCheck
-  /// CHECK:     NullCheck
-  /// CHECK-NOT: NullCheck
+  /// CHECK-NOT: InstanceFieldGet
 
   /// CHECK-START: int Main.test4(TestClass, boolean) load_store_elimination (after)
-  /// CHECK-NOT: InstanceFieldGet
   /// CHECK-NOT: Phi
 
   // Set and merge the same value in two branches.
@@ -554,16 +551,15 @@
   }
 
   /// CHECK-START: void Main.test21(TestClass) load_store_elimination (before)
-  /// CHECK: NewInstance
-  /// CHECK: InstanceFieldSet
-  /// CHECK: InstanceFieldSet
-  /// CHECK: InstanceFieldSet
-  /// CHECK: InstanceFieldGet
-  /// CHECK: InstanceFieldGet
+  /// CHECK-DAG: NewInstance
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldSet
+  /// CHECK-DAG: InstanceFieldGet
+  /// CHECK-DAG: InstanceFieldGet
 
   /// CHECK-START: void Main.test21(TestClass) load_store_elimination (after)
   /// CHECK-DAG: InstanceFieldSet
-  /// CHECK-DAG: Phi
 
   /// CHECK-START: void Main.test21(TestClass) load_store_elimination (after)
   /// CHECK-NOT: NewInstance
@@ -1853,14 +1849,22 @@
   /// CHECK: ArraySet
   /// CHECK: ArrayGet
 
-  /// CHECK-START: int Main.testAllocationEliminationOfArray2() load_store_elimination (after)
+  /// CHECK-START-{ARM64,X86,X86_64}: int Main.testAllocationEliminationOfArray2() load_store_elimination (after)
+  /// CHECK-NOT: NewArray
+  /// CHECK-NOT: ArraySet
+  /// CHECK-NOT: ArraySet
+  /// CHECK-NOT: ArrayGet
+
+  // The loop optimization doesn't happen in ARM which leads to LSE not being able to optimize this
+  // case.
+  /// CHECK-START-ARM: int Main.testAllocationEliminationOfArray2() load_store_elimination (after)
   /// CHECK: NewArray
   /// CHECK: ArraySet
   /// CHECK: ArraySet
   /// CHECK: ArrayGet
   private static int testAllocationEliminationOfArray2() {
-    // Cannot eliminate array allocation since array is accessed with non-constant
-    // index (only 3 elements to prevent vectorization of the reduction).
+    // Array can be eliminated because LSE can reduce the array accesses into
+    // integer constants.
     int[] array = new int[3];
     array[1] = 4;
     array[2] = 7;
@@ -1924,6 +1928,36 @@
     return array[1];
   }
 
+  /// CHECK-START: int Main.testAllocationEliminationOfArray6(boolean) load_store_elimination (before)
+  /// CHECK: NewArray
+  /// CHECK: ArraySet
+  /// CHECK: ArraySet
+  /// CHECK: ArrayGet
+
+  /// CHECK-START: int Main.testAllocationEliminationOfArray6(boolean) load_store_elimination (after)
+  /// CHECK: NewArray
+  /// CHECK: ArraySet
+  /// CHECK: ArraySet
+  /// CHECK: ArrayGet
+  private static int testAllocationEliminationOfArray6(boolean prevent_loop_opt) {
+    // Cannot eliminate array allocation since array is accessed with non-constant
+    // index (only 3 elements to prevent vectorization of the reduction).
+    int[] array = new int[3];
+    array[1] = 4;
+    array[2] = 7;
+    int sum = 0;
+    for (int e : array) {
+      sum += e;
+
+      // Prevent the loop from being optimized away before LSE. This should
+      // never be false.
+      if (!prevent_loop_opt) {
+        return -1;
+      }
+    }
+    return sum;
+  }
+
   /// CHECK-START: int Main.testExitMerge(boolean) load_store_elimination (before)
   /// CHECK-DAG: NewInstance
   /// CHECK-DAG: InstanceFieldSet field_name:TestClass.i
@@ -4213,6 +4247,7 @@
     } catch (NegativeArraySizeException e) {
       System.out.println("Got NegativeArraySizeException.");
     }
+    assertIntEquals(testAllocationEliminationOfArray6(true), 11);
 
     assertIntEquals(testStoreStore().i, 41);
     assertIntEquals(testStoreStore().j, 43);
diff --git a/test/530-checker-peel-unroll/src/Main.java b/test/530-checker-peel-unroll/src/Main.java
index f909af3..ce3d71b 100644
--- a/test/530-checker-peel-unroll/src/Main.java
+++ b/test/530-checker-peel-unroll/src/Main.java
@@ -1015,7 +1015,7 @@
   /// CHECK:                      ArraySet
   /// CHECK-NOT:                  ArraySet
 
-  /// CHECK-START: void Main.peelingSimple(int[], boolean) dead_code_elimination$final (after)
+  /// CHECK-START: void Main.peelingSimple(int[], boolean) dead_code_elimination$before_codegen (after)
   /// CHECK-DAG: <<Param:z\d+>>     ParameterValue                            loop:none
   /// CHECK-DAG: <<Const0:i\d+>>    IntConstant 0                             loop:none
   /// CHECK-DAG: <<Const1:i\d+>>    IntConstant 1                             loop:none
@@ -1030,21 +1030,21 @@
   /// CHECK-DAG:                    ArraySet                                  loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG: <<IndAdd:i\d+>>    Add [<<Phi>>,<<Const1>>]                  loop:<<Loop>>      outer_loop:none
 
-  /// CHECK-START: void Main.peelingSimple(int[], boolean) dead_code_elimination$final (after)
+  /// CHECK-START: void Main.peelingSimple(int[], boolean) dead_code_elimination$before_codegen (after)
   /// CHECK:                      GreaterThanOrEqual
   /// CHECK-NOT:                  GreaterThanOrEqual
 
-  /// CHECK-START: void Main.peelingSimple(int[], boolean) dead_code_elimination$final (after)
+  /// CHECK-START: void Main.peelingSimple(int[], boolean) dead_code_elimination$before_codegen (after)
   /// CHECK:                      If
   /// CHECK:                      If
   /// CHECK-NOT:                  If
 
-  /// CHECK-START: void Main.peelingSimple(int[], boolean) dead_code_elimination$final (after)
+  /// CHECK-START: void Main.peelingSimple(int[], boolean) dead_code_elimination$before_codegen (after)
   /// CHECK:                      ArrayGet
   /// CHECK:                      ArrayGet
   /// CHECK-NOT:                  ArrayGet
 
-  /// CHECK-START: void Main.peelingSimple(int[], boolean) dead_code_elimination$final (after)
+  /// CHECK-START: void Main.peelingSimple(int[], boolean) dead_code_elimination$before_codegen (after)
   /// CHECK:                      ArraySet
   /// CHECK:                      ArraySet
   /// CHECK-NOT:                  ArraySet
@@ -1069,7 +1069,7 @@
   /// CHECK-DAG:                    If [<<Check>>]                        loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG:                    ArraySet                              loop:<<Loop>>      outer_loop:none
 
-  /// CHECK-START: void Main.peelingAddInts(int[]) dead_code_elimination$final (after)
+  /// CHECK-START: void Main.peelingAddInts(int[]) dead_code_elimination$before_codegen (after)
   /// CHECK-DAG: <<Param:l\d+>>     ParameterValue                        loop:none
   /// CHECK-DAG: <<ConstNull:l\d+>> NullConstant                          loop:none
   /// CHECK-DAG: <<Eq:z\d+>>        Equal [<<Param>>,<<ConstNull>>]       loop:none
@@ -1081,7 +1081,7 @@
   /// CHECK-DAG:                    ArraySet                              loop:<<Loop>>      outer_loop:none
 
   // There's a 3rd `if` due to bounds checks.
-  /// CHECK-START: void Main.peelingAddInts(int[]) dead_code_elimination$final (after)
+  /// CHECK-START: void Main.peelingAddInts(int[]) dead_code_elimination$before_codegen (after)
   /// CHECK:                        If
   /// CHECK:                        If
   /// CHECK:                        If
@@ -1118,7 +1118,7 @@
   /// CHECK:                        ArraySet
   /// CHECK-NOT:                    ArraySet
 
-  /// CHECK-START: void Main.peelingBreakFromNest(int[], boolean) dead_code_elimination$final (after)
+  /// CHECK-START: void Main.peelingBreakFromNest(int[], boolean) dead_code_elimination$before_codegen (after)
   /// CHECK-DAG: <<Param:z\d+>>     ParameterValue                          loop:none
   /// CHECK-DAG: <<Const0:i\d+>>    IntConstant 0                           loop:none
   /// CHECK-DAG: <<Const1:i\d+>>    IntConstant 1                           loop:none
@@ -1135,13 +1135,13 @@
   /// CHECK-DAG: <<IndAdd1:i\d+>>   Add [<<Phi1>>,<<Const1>>]               loop:<<Loop1>>      outer_loop:<<Loop0>>
   /// CHECK-DAG: <<IndAdd0:i\d+>>   Add [<<Phi0>>,<<Const1>>]               loop:<<Loop0>>      outer_loop:none
 
-  /// CHECK-START: void Main.peelingBreakFromNest(int[], boolean) dead_code_elimination$final (after)
+  /// CHECK-START: void Main.peelingBreakFromNest(int[], boolean) dead_code_elimination$before_codegen (after)
   /// CHECK:                        If
   /// CHECK:                        If
   /// CHECK:                        If
   /// CHECK-NOT:                    If
 
-  /// CHECK-START: void Main.peelingBreakFromNest(int[], boolean) dead_code_elimination$final (after)
+  /// CHECK-START: void Main.peelingBreakFromNest(int[], boolean) dead_code_elimination$before_codegen (after)
   /// CHECK:                        ArraySet
   /// CHECK:                        ArraySet
   /// CHECK-NOT:                    ArraySet
@@ -1170,7 +1170,7 @@
   /// CHECK:                        If
   /// CHECK-NOT:                    If
 
-  /// CHECK-START: int Main.peelingHoistOneControl(int) dead_code_elimination$final (after)
+  /// CHECK-START: int Main.peelingHoistOneControl(int) dead_code_elimination$before_codegen (after)
   /// CHECK-DAG: <<Param:i\d+>>     ParameterValue                            loop:none
   /// CHECK-DAG: <<Const0:i\d+>>    IntConstant 0                             loop:none
   /// CHECK-DAG: <<Check:z\d+>>     NotEqual [<<Param>>,<<Const0>>]           loop:none
@@ -1181,14 +1181,14 @@
   //  Check that the loop has no instruction except SuspendCheck and Goto (indefinite loop).
   /// CHECK-NOT:                                                              loop:<<Loop>>      outer_loop:none
 
-  /// CHECK-START: int Main.peelingHoistOneControl(int) dead_code_elimination$final (after)
+  /// CHECK-START: int Main.peelingHoistOneControl(int) dead_code_elimination$before_codegen (after)
   /// CHECK:                        If
   /// CHECK-NOT:                    If
 
-  /// CHECK-START: int Main.peelingHoistOneControl(int) dead_code_elimination$final (after)
+  /// CHECK-START: int Main.peelingHoistOneControl(int) dead_code_elimination$before_codegen (after)
   /// CHECK-NOT:                    Phi
 
-  /// CHECK-START: int Main.peelingHoistOneControl(int) dead_code_elimination$final (after)
+  /// CHECK-START: int Main.peelingHoistOneControl(int) dead_code_elimination$before_codegen (after)
   /// CHECK-NOT:                    Add
   private static final int peelingHoistOneControl(int x) {
     int i = 0;
@@ -1204,12 +1204,12 @@
   /// CHECK-DAG:              If   loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG:              If   loop:<<Loop>>      outer_loop:none
 
-  /// CHECK-START: int Main.peelingHoistOneControl(int, int) dead_code_elimination$final (after)
+  /// CHECK-START: int Main.peelingHoistOneControl(int, int) dead_code_elimination$before_codegen (after)
   /// CHECK-DAG: <<Phi:i\d+>> Phi  loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG:              If   loop:<<Loop>>      outer_loop:none
 
   // One `if` inside the loop (the one no longer invariant), two outside of it.
-  /// CHECK-START: int Main.peelingHoistOneControl(int, int) dead_code_elimination$final (after)
+  /// CHECK-START: int Main.peelingHoistOneControl(int, int) dead_code_elimination$before_codegen (after)
   /// CHECK:                  If
   /// CHECK:                  If
   /// CHECK:                  If
@@ -1230,12 +1230,12 @@
   /// CHECK-DAG:              If   loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG:              If   loop:<<Loop>>      outer_loop:none
 
-  /// CHECK-START: int Main.peelingHoistTwoControl(int, int, int) dead_code_elimination$final (after)
+  /// CHECK-START: int Main.peelingHoistTwoControl(int, int, int) dead_code_elimination$before_codegen (after)
   /// CHECK-DAG: <<Phi:i\d+>> Phi  loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG:              If   loop:<<Loop>>      outer_loop:none
 
   // One `if` inside the loop (the one no longer invariant), three outside of it.
-  /// CHECK-START: int Main.peelingHoistTwoControl(int, int, int) dead_code_elimination$final (after)
+  /// CHECK-START: int Main.peelingHoistTwoControl(int, int, int) dead_code_elimination$before_codegen (after)
   /// CHECK:                  If
   /// CHECK:                  If
   /// CHECK:                  If
diff --git a/test/530-checker-regression-reftyp-final/src/Main.java b/test/530-checker-regression-reftyp-final/src/Main.java
index f86b515..69d99e6 100644
--- a/test/530-checker-regression-reftyp-final/src/Main.java
+++ b/test/530-checker-regression-reftyp-final/src/Main.java
@@ -58,9 +58,6 @@
   }
 
   public static Object[] $noinline$getArray() {
-    if (doThrow) throw new Error();
     return new MyClassB[2];
   }
-
-  static boolean doThrow = false;
 }
diff --git a/test/534-checker-bce-deoptimization/src/Main.java b/test/534-checker-bce-deoptimization/src/Main.java
index c4e4cbf..45dcbce 100644
--- a/test/534-checker-bce-deoptimization/src/Main.java
+++ b/test/534-checker-bce-deoptimization/src/Main.java
@@ -89,7 +89,6 @@
     /// CHECK-NOT:          BoundsCheck
 
     public static void $noinline$FloatFill(float f1, float f2, float[] array, int n) {
-        if (doThrow) { throw new Error(); }
         for (int i = 0; i < n; ++i) {
             array[i] = ((i & 1) == 1) ? f1 : f2;
             f1 += 1.5f;
@@ -118,14 +117,11 @@
     /// CHECK-NOT:          BoundsCheck
 
     public static void $noinline$DoubleFill(double d1, double d2, double[] array, int n) {
-        if (doThrow) { throw new Error(); }
         for (int i = 0; i < n; ++i) {
             array[i] = ((i & 1) == 1) ? d1 : d2;
             d1 += 1.5;
             d2 += 2.25;
         }
     }
-
-    public static boolean doThrow = false;
 }
 
diff --git a/test/536-checker-needs-access-check/Android.bp b/test/536-checker-needs-access-check/Android.bp
new file mode 100644
index 0000000..ccf524e
--- /dev/null
+++ b/test/536-checker-needs-access-check/Android.bp
@@ -0,0 +1,53 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `536-checker-needs-access-check`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-536-checker-needs-access-check-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-536-checker-needs-access-check",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-536-checker-needs-access-check-src"
+    ],
+    data: [
+        ":art-run-test-536-checker-needs-access-check-expected-stdout",
+        ":art-run-test-536-checker-needs-access-check-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-536-checker-needs-access-check-expected-stdout",
+    out: ["art-run-test-536-checker-needs-access-check-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-536-checker-needs-access-check-expected-stderr",
+    out: ["art-run-test-536-checker-needs-access-check-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/536-checker-needs-access-check/src2/other/InaccessibleClass.java b/test/536-checker-needs-access-check/src2/other/InaccessibleClass.java
index 646cc7e..6fd99ef 100644
--- a/test/536-checker-needs-access-check/src2/other/InaccessibleClass.java
+++ b/test/536-checker-needs-access-check/src2/other/InaccessibleClass.java
@@ -31,7 +31,7 @@
     Class<?> klass = null;
     try {
       klass = GetInaccessibleClass.$inline$get();
-      throw new Error("Unreachable");
+      System.out.println("Unreachable");
     } catch (IllegalAccessError expected) {}
     return klass;
   }
diff --git a/test/537-checker-inline-and-unverified/Android.bp b/test/537-checker-inline-and-unverified/Android.bp
new file mode 100644
index 0000000..8078e92
--- /dev/null
+++ b/test/537-checker-inline-and-unverified/Android.bp
@@ -0,0 +1,53 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `537-checker-inline-and-unverified`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-537-checker-inline-and-unverified-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-537-checker-inline-and-unverified",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-537-checker-inline-and-unverified-src"
+    ],
+    data: [
+        ":art-run-test-537-checker-inline-and-unverified-expected-stdout",
+        ":art-run-test-537-checker-inline-and-unverified-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-537-checker-inline-and-unverified-expected-stdout",
+    out: ["art-run-test-537-checker-inline-and-unverified-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-537-checker-inline-and-unverified-expected-stderr",
+    out: ["art-run-test-537-checker-inline-and-unverified-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/542-bitfield-rotates/src/Main.java b/test/542-bitfield-rotates/src/Main.java
index f2bc153..4308439 100644
--- a/test/542-bitfield-rotates/src/Main.java
+++ b/test/542-bitfield-rotates/src/Main.java
@@ -48,12 +48,7 @@
     test_Long_left_constant_v();
   }
 
-  public static boolean doThrow = false;
-
   public static int $noinline$rotate_int_right_reg_v_csubv(int value, int distance) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> distance) | (value << (32 - distance));
   }
 
@@ -74,9 +69,6 @@
   }
 
   public static long $noinline$rotate_long_right_reg_v_csubv(long value, int distance) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> distance) | (value << (64 - distance));
   }
 
@@ -97,9 +89,6 @@
   }
 
   public static int $noinline$rotate_int_left_reg_csubv_v(int value, int distance) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> (32 - distance)) | (value << distance);
   }
 
@@ -120,9 +109,6 @@
   }
 
   public static long $noinline$rotate_long_left_reg_csubv_v(long value, int distance) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> (64 - distance)) | (value << distance);
   }
 
@@ -143,9 +129,6 @@
   }
 
   public static int $noinline$rotate_int_right_reg_v_negv(int value, int distance) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> distance) | (value << -distance);
   }
 
@@ -166,9 +149,6 @@
   }
 
   public static long $noinline$rotate_long_right_reg_v_negv(long value, int distance) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> distance) | (value << -distance);
   }
 
@@ -189,9 +169,6 @@
   }
 
   public static int $noinline$rotate_int_left_reg_negv_v(int value, int distance) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> -distance) | (value << distance);
   }
 
@@ -212,9 +189,6 @@
   }
 
   public static long $noinline$rotate_long_left_reg_negv_v(long value, int distance) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> -distance) | (value << distance);
   }
 
@@ -235,30 +209,18 @@
   }
 
   public static int $noinline$rotate_int_right_constant_0(int value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> 0) | (value << 0);
   }
 
   public static int $noinline$rotate_int_right_constant_1(int value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> 1) | (value << -1);
   }
 
   public static int $noinline$rotate_int_right_constant_m1(int value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> -1) | (value << 1);
   }
 
   public static int $noinline$rotate_int_right_constant_16(int value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> 16) | (value << -16);
   }
 
@@ -270,51 +232,30 @@
   }
 
   public static long $noinline$rotate_long_right_constant_0(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> 0) | (value << 0);
   }
 
   public static long $noinline$rotate_long_right_constant_1(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> 1) | (value << -1);
   }
 
   public static long $noinline$rotate_long_right_constant_m1(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> -1) | (value << 1);
   }
 
   public static long $noinline$rotate_long_right_constant_16(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> 16) | (value << -16);
   }
 
   public static long $noinline$rotate_long_right_constant_32(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> 32) | (value << -32);
   }
 
   public static long $noinline$rotate_long_right_constant_48(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> 48) | (value << -48);
   }
 
   public static long $noinline$rotate_long_right_constant_64(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value >>> 64) | (value << -64);
   }
 
@@ -328,30 +269,18 @@
   }
 
   public static int $noinline$rotate_int_left_constant_0(int value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << 0) | (value >>> 0);
   }
 
   public static int $noinline$rotate_int_left_constant_1(int value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << 1) | (value >>> -1);
   }
 
   public static int $noinline$rotate_int_left_constant_m1(int value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << -1) | (value >>> 1);
   }
 
   public static int $noinline$rotate_int_left_constant_16(int value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << 16) | (value >>> -16);
   }
 
@@ -363,51 +292,30 @@
   }
 
   public static long $noinline$rotate_long_left_constant_0(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << 0) | (value >>> 0);
   }
 
   public static long $noinline$rotate_long_left_constant_1(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << 1) | (value >>> -1);
   }
 
   public static long $noinline$rotate_long_left_constant_m1(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << -1) | (value >>> 1);
   }
 
   public static long $noinline$rotate_long_left_constant_16(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << 16) | (value >>> -16);
   }
 
   public static long $noinline$rotate_long_left_constant_32(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << 32) | (value >>> -32);
   }
 
   public static long $noinline$rotate_long_left_constant_48(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << 48) | (value >>> -48);
   }
 
   public static long $noinline$rotate_long_left_constant_64(long value) {
-    if (doThrow) {
-      throw new Error();
-    }
     return (value << 64) | (value >>> -64);
   }
 
diff --git a/test/542-inline-trycatch/src/Main.java b/test/542-inline-trycatch/src/Main.java
index 5a6e06f..5e33ed6 100644
--- a/test/542-inline-trycatch/src/Main.java
+++ b/test/542-inline-trycatch/src/Main.java
@@ -33,17 +33,6 @@
     return is_hex ? Integer.parseInt(str, 16) : Integer.parseInt(str);
   }
 
-  // We expect methods with try/catch to not be inlined. Inlined try/catch
-  // blocks are not supported at the moment.
-
-  private static int $noinline$TryCatch(String str) {
-    try {
-      return Integer.parseInt(str);
-    } catch (NumberFormatException ex) {
-      return -1;
-    }
-  }
-
   public static void testSingleBlockFromTry() {
     int val = 0;
 
@@ -117,49 +106,11 @@
     assertEquals(32, val);
   }
 
-  public static void testTryCatchFromTry() {
-    int val = 0;
-
-    try {
-      val = $noinline$TryCatch("42");
-    } catch (NumberFormatException ex) {
-      unreachable();
-    }
-    assertEquals(42, val);
-
-    try {
-      val = $noinline$TryCatch("xyz");
-    } catch (NumberFormatException ex) {
-      unreachable();
-    }
-    assertEquals(-1, val);
-  }
-
-  public static void testTryCatchFromCatch() {
-    int val = 0;
-
-    try {
-      throwException();
-    } catch (Exception ex) {
-      val = $noinline$TryCatch("42");
-    }
-    assertEquals(42, val);
-
-    try {
-      throwException();
-    } catch (Exception ex) {
-      val = $noinline$TryCatch("xyz");
-    }
-    assertEquals(-1, val);
-  }
-
   public static void main(String[] args) {
     testSingleBlockFromTry();
     testSingleBlockFromCatch();
     testMultipleBlocksFromTry();
     testMultipleBlocksFromCatch();
-    testTryCatchFromTry();
-    testTryCatchFromCatch();
   }
 
   private static void assertEquals(int expected, int actual) {
diff --git a/test/543-env-long-ref/env_long_ref.cc b/test/543-env-long-ref/env_long_ref.cc
index 1c30d46..7084714 100644
--- a/test/543-env-long-ref/env_long_ref.cc
+++ b/test/543-env-long-ref/env_long_ref.cc
@@ -36,7 +36,8 @@
           found = true;
           // For optimized non-debuggable code do not expect dex register info to be present.
           if (stack_visitor->GetCurrentShadowFrame() == nullptr &&
-              !Runtime::Current()->IsAsyncDeoptimizeable(stack_visitor->GetCurrentQuickFramePc())) {
+              !Runtime::Current()->IsAsyncDeoptimizeable(stack_visitor->GetOuterMethod(),
+                                                         stack_visitor->GetCurrentQuickFramePc())) {
             return true;
           }
           uint32_t stack_value = 0;
diff --git a/test/550-checker-multiply-accumulate/src/Main.java b/test/550-checker-multiply-accumulate/src/Main.java
index a07111b..f50968b 100644
--- a/test/550-checker-multiply-accumulate/src/Main.java
+++ b/test/550-checker-multiply-accumulate/src/Main.java
@@ -413,16 +413,16 @@
     return - (left * right);
   }
 
-  /// CHECK-START-ARM64: void Main.SimdMulAdd(int[], int[]) instruction_simplifier$after_bce (before)
+  /// CHECK-START-ARM64: void Main.SimdMulAdd(int[], int[]) instruction_simplifier$after_loop_opt (before)
   /// CHECK-DAG:     Phi                            loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG:     VecMul                         loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG:     VecAdd                         loop:<<Loop>>      outer_loop:none
 
-  /// CHECK-START-ARM64: void Main.SimdMulAdd(int[], int[]) instruction_simplifier$after_bce (after)
+  /// CHECK-START-ARM64: void Main.SimdMulAdd(int[], int[]) instruction_simplifier$after_loop_opt (after)
   /// CHECK-DAG:     Phi                            loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG:     VecMultiplyAccumulate kind:Add loop:<<Loop>>      outer_loop:none
 
-  /// CHECK-START-ARM64: void Main.SimdMulAdd(int[], int[]) instruction_simplifier$after_bce (after)
+  /// CHECK-START-ARM64: void Main.SimdMulAdd(int[], int[]) instruction_simplifier$after_loop_opt (after)
   /// CHECK-NOT:     VecMul
   /// CHECK-NOT:     VecAdd
 
@@ -438,16 +438,16 @@
     }
   }
 
-  /// CHECK-START-ARM64: void Main.SimdMulSub(int[], int[]) instruction_simplifier$after_bce (before)
+  /// CHECK-START-ARM64: void Main.SimdMulSub(int[], int[]) instruction_simplifier$after_loop_opt (before)
   /// CHECK-DAG:     Phi                            loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG:     VecMul                         loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG:     VecSub                         loop:<<Loop>>      outer_loop:none
 
-  /// CHECK-START-ARM64: void Main.SimdMulSub(int[], int[]) instruction_simplifier$after_bce (after)
+  /// CHECK-START-ARM64: void Main.SimdMulSub(int[], int[]) instruction_simplifier$after_loop_opt (after)
   /// CHECK-DAG:     Phi                            loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG:     VecMultiplyAccumulate kind:Sub loop:<<Loop>>      outer_loop:none
 
-  /// CHECK-START-ARM64: void Main.SimdMulSub(int[], int[]) instruction_simplifier$after_bce (after)
+  /// CHECK-START-ARM64: void Main.SimdMulSub(int[], int[]) instruction_simplifier$after_loop_opt (after)
   /// CHECK-NOT:     VecMul
   /// CHECK-NOT:     VecSub
 
@@ -463,12 +463,12 @@
     }
   }
 
-  /// CHECK-START-ARM64: void Main.SimdMulMultipleUses(int[], int[]) instruction_simplifier$after_bce (before)
+  /// CHECK-START-ARM64: void Main.SimdMulMultipleUses(int[], int[]) instruction_simplifier$after_loop_opt (before)
   /// CHECK-DAG:     Phi                            loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG:     VecMul                         loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG:     VecSub                         loop:<<Loop>>      outer_loop:none
 
-  /// CHECK-START-ARM64: void Main.SimdMulMultipleUses(int[], int[]) instruction_simplifier$after_bce (after)
+  /// CHECK-START-ARM64: void Main.SimdMulMultipleUses(int[], int[]) instruction_simplifier$after_loop_opt (after)
   /// CHECK-NOT: VecMultiplyAccumulate
 
   public static void SimdMulMultipleUses(int[] array1, int[] array2) {
diff --git a/test/551-checker-clinit/src/Main.java b/test/551-checker-clinit/src/Main.java
index 0eea800..77a9825 100644
--- a/test/551-checker-clinit/src/Main.java
+++ b/test/551-checker-clinit/src/Main.java
@@ -17,14 +17,14 @@
 public class Main {
 
   public static void main(String[] args) {}
-  public static int foo = 42;
+  public static long foo = 42;
 
   // Primitive array initialization is trivial for purposes of the ClinitCheck. It cannot
   // leak instances of erroneous classes or initialize subclasses of erroneous classes.
-  public static int[] array1 = new int[] { 1, 2, 3 };
-  public static int[] array2;
+  public static long[] array1 = new long[] { 1, 2, 3 };
+  public static long[] array2;
   static {
-    int[] a = new int[4];
+    long[] a = new long[4];
     a[0] = 42;
     array2 = a;
   }
@@ -46,35 +46,35 @@
   /// CHECK-NOT:                    ClinitCheck
   public void invokeSuperClass() {
     // No Class initialization check as Main.<clinit> is trivial. b/62478025
-    int a = Main.foo;
+    long a = Main.foo;
   }
 
   /// CHECK-START: void Sub.invokeItself() builder (after)
   /// CHECK-NOT:                    ClinitCheck
   public void invokeItself() {
     // No Class initialization check as Sub.<clinit> and Main.<clinit> are trivial. b/62478025
-    int a = foo;
+    long a = foo;
   }
 
   /// CHECK-START: void Sub.invokeSubClass() builder (after)
   /// CHECK:                        ClinitCheck
   public void invokeSubClass() {
-    int a = SubSub.foo;
+    long a = SubSub.foo;
   }
 
-  public static int foo = 42;
+  public static long foo = 42;
 }
 
 class SubSub {
   public static void bar() {
-    int a = Main.foo;
+    long a = Main.foo;
   }
-  public static int foo = 42;
+  public static long foo = 42;
 }
 
 class NonTrivial {
-  public static int staticFoo = 42;
-  public int instanceFoo;
+  public static long staticFoo = 42;
+  public long instanceFoo;
 
   static {
     System.out.println("NonTrivial.<clinit>");
diff --git a/test/552-checker-sharpening/src/Main.java b/test/552-checker-sharpening/src/Main.java
index 15ff8a6..b017ab0 100644
--- a/test/552-checker-sharpening/src/Main.java
+++ b/test/552-checker-sharpening/src/Main.java
@@ -34,10 +34,7 @@
     }
   }
 
-  public static boolean doThrow = false;
-
   private static int $noinline$foo(int x) {
-    if (doThrow) { throw new Error(); }
     return x;
   }
 
@@ -135,8 +132,6 @@
   /// CHECK:                LoadString load_kind:BootImageRelRo
 
   public static String $noinline$getBootImageString() {
-    // Prevent inlining to avoid the string comparison being optimized away.
-    if (doThrow) { throw new Error(); }
     // Empty string is known to be in the boot image.
     return "";
   }
@@ -152,8 +147,6 @@
   /// CHECK-DAG:            LoadString load_kind:BssEntry
 
   public static String $noinline$getNonBootImageString() {
-    // Prevent inlining to avoid the string comparison being optimized away.
-    if (doThrow) { throw new Error(); }
     // This string is not in the boot image.
     return "non-boot-image-string";
   }
@@ -162,8 +155,6 @@
   /// CHECK:                LoadClass load_kind:BootImageRelRo class_name:java.lang.String
 
   public static Class<?> $noinline$getStringClass() {
-    // Prevent inlining to avoid the string comparison being optimized away.
-    if (doThrow) { throw new Error(); }
     // String class is known to be in the boot image.
     return String.class;
   }
@@ -179,8 +170,6 @@
   /// CHECK-DAG:            LoadClass load_kind:BssEntry class_name:Other
 
   public static Class<?> $noinline$getOtherClass() {
-    // Prevent inlining to avoid the string comparison being optimized away.
-    if (doThrow) { throw new Error(); }
     // Other class is not in the boot image.
     return Other.class;
   }
diff --git a/test/553-invoke-super/src/SuperClass.java b/test/553-invoke-super/src/SuperClass.java
index 36ce093..c3e4a4e 100644
--- a/test/553-invoke-super/src/SuperClass.java
+++ b/test/553-invoke-super/src/SuperClass.java
@@ -15,12 +15,7 @@
  */
 
 public class SuperClass {
-  boolean doThrow = false;
-
   public int $noinline$returnInt() {
-    if (doThrow) {
-      throw new Error();
-    }
     return 42;
   }
 }
diff --git a/test/559-checker-irreducible-loop/expected-stdout.txt b/test/559-checker-irreducible-loop/expected-stdout.txt
index b64be7a..992fd87 100644
--- a/test/559-checker-irreducible-loop/expected-stdout.txt
+++ b/test/559-checker-irreducible-loop/expected-stdout.txt
@@ -5,3 +5,5 @@
 class Main
 42
 -42
+1
+-1
diff --git a/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali b/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali
index a30a11a..a88422f 100644
--- a/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali
+++ b/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali
@@ -140,24 +140,23 @@
 #        other_loop_entry
 #        i1 = phi(p0, i0)
 #
-## CHECK-START: int IrreducibleLoop.liveness(int) liveness (after)
+## CHECK-START: int IrreducibleLoop.liveness(int, int) liveness (after)
 ## CHECK-DAG: <<Arg:i\d+>>      ParameterValue liveness:<<ArgLiv:\d+>> ranges:{[<<ArgLiv>>,<<ArgLoopPhiUse:\d+>>)}
 ## CHECK-DAG: <<LoopPhi:i\d+>>  Phi [<<Arg>>,<<PhiInLoop:i\d+>>] liveness:<<ArgLoopPhiUse>> ranges:{[<<ArgLoopPhiUse>>,<<PhiInLoopUse:\d+>>)}
 ## CHECK-DAG: <<PhiInLoop>>     Phi [<<Arg>>,<<LoopPhi>>] liveness:<<PhiInLoopUse>> ranges:{[<<PhiInLoopUse>>,<<BackEdgeLifetimeEnd:\d+>>)}
 ## CHECK:                       Return liveness:<<ReturnLiveness:\d+>>
 ## CHECK-EVAL:    <<ReturnLiveness>> == <<BackEdgeLifetimeEnd>> + 2
-.method public static liveness(I)I
+.method public static liveness(II)I
    .registers 2
-   const/16 v0, 42
-   if-eq p0, v0, :other_loop_entry
+   if-eq p0, p1, :other_loop_entry
    :loop_entry
-   add-int v0, v0, p0
-   if-ne v1, v0, :exit
+   add-int p1, p1, p0
+   if-ne v0, p1, :exit
    :other_loop_entry
-   add-int v0, v0, v0
+   add-int p1, p1, p1
    goto :loop_entry
    :exit
-   return v0
+   return p1
 .end method
 
 # Check that we don't GVN across irreducible loops:
@@ -547,3 +546,57 @@
   :exit
   return-void
 .end method
+
+## CHECK-START: int IrreducibleLoop.testDoNotInlineIrreducible(int) inliner (before)
+## CHECK: InvokeStaticOrDirect method_name:IrreducibleLoop.doNotInlineIrreducible
+#
+## CHECK-START: int IrreducibleLoop.testDoNotInlineIrreducible(int) inliner (after)
+## CHECK: InvokeStaticOrDirect method_name:IrreducibleLoop.doNotInlineIrreducible
+.method public static testDoNotInlineIrreducible(I)I
+  .registers 2
+  invoke-static {p0}, LIrreducibleLoop;->doNotInlineIrreducible(I)I
+  move-result v0
+  return v0
+.end method
+
+# Check that doNotInlineIrreducible has a simple irreducible loop
+#
+#        entry
+#       /    \
+#      /      \
+# loop_entry   \
+#    /    \-    \
+# try_start\-    \
+#           other_loop_entry
+#
+## CHECK-START: int IrreducibleLoop.doNotInlineIrreducible(int) register (after)
+## CHECK: irreducible:true
+#
+# Check that we didn't optimized away the try.
+## CHECK-START: int IrreducibleLoop.doNotInlineIrreducible(int) register (after)
+## CHECK: TryBoundary kind:exit
+.method public static doNotInlineIrreducible(I)I
+  .registers 3
+  const/16 v0, 42
+  const/16 v1, 21
+  # Irreducible loop
+  if-eq v1, v0, :other_loop_entry
+  :loop_entry
+  if-ne v1, v0, :try_start
+  add-int v0, v0, v0
+  :other_loop_entry
+  add-int v0, v0, v0
+  goto :loop_entry
+
+  :try_start
+  # We make this division to make sure the try doesn't get optimized out
+  div-int v0, v0, p0
+  return v0
+  :try_end
+  .catchall {:try_start .. :try_end} :catch_all
+
+  :catch_all
+  # This is only reachable if the parameter is 0
+  const/4 v0, -0x1
+  return v0
+.end method
diff --git a/test/559-checker-irreducible-loop/src/Main.java b/test/559-checker-irreducible-loop/src/Main.java
index 97165ec..16b8415 100644
--- a/test/559-checker-irreducible-loop/src/Main.java
+++ b/test/559-checker-irreducible-loop/src/Main.java
@@ -38,8 +38,8 @@
     }
 
     {
-      Method m = c.getMethod("liveness", int.class);
-      Object[] arguments = { 42 };
+      Method m = c.getMethod("liveness", int.class, int.class);
+      Object[] arguments = { 42, 42 };
       System.out.println(m.invoke(null, arguments));
     }
 
@@ -60,6 +60,18 @@
       Object[] arguments = { 42 };
       System.out.println(m.invoke(null, arguments));
     }
+
+    {
+      Method m = c.getMethod("testDoNotInlineIrreducible", int.class);
+      Object[] arguments = { 42 };
+      System.out.println(m.invoke(null, arguments));
+    }
+
+    {
+      Method m = c.getMethod("testDoNotInlineIrreducible", int.class);
+      Object[] arguments = { 0 };
+      System.out.println(m.invoke(null, arguments));
+    }
   }
 
   int myField;
diff --git a/test/561-divrem/src/Main.java b/test/561-divrem/src/Main.java
index 082783d..ec125e9 100644
--- a/test/561-divrem/src/Main.java
+++ b/test/561-divrem/src/Main.java
@@ -72,32 +72,18 @@
   }
 
   public static int $noinline$divInt(int value) {
-    if (doThrow) {
-      throw new Error("");
-    }
     return value / Integer.MIN_VALUE;
   }
 
   public static int $noinline$remInt(int value) {
-    if (doThrow) {
-      throw new Error("");
-    }
     return value % Integer.MIN_VALUE;
   }
 
   public static long $noinline$divLong(long value) {
-    if (doThrow) {
-      throw new Error("");
-    }
     return value / Long.MIN_VALUE;
   }
 
   public static long $noinline$remLong(long value) {
-    if (doThrow) {
-      throw new Error("");
-    }
     return value % Long.MIN_VALUE;
   }
-
-  static boolean doThrow = false;
 }
diff --git a/test/563-checker-fakestring/smali/TestCase.smali b/test/563-checker-fakestring/smali/TestCase.smali
index 4721eca..c6561d5 100644
--- a/test/563-checker-fakestring/smali/TestCase.smali
+++ b/test/563-checker-fakestring/smali/TestCase.smali
@@ -310,7 +310,7 @@
 ## CHECK-NOT:                    NewInstance
 ## CHECK-DAG:   <<Invoke1:l\d+>> InvokeStaticOrDirect method_name:java.lang.String.<init>
 ## CHECK-DAG:   <<Invoke2:l\d+>> InvokeStaticOrDirect method_name:java.lang.String.<init>
-## CHECK-DAG:   <<Phi:l\d+>>     Phi [<<Invoke2>>,<<Invoke1>>]
+## CHECK-DAG:   <<Phi:l\d+>>     Phi [<<Invoke1>>,<<Invoke2>>]
 ## CHECK-DAG:                    Return [<<Phi>>]
 .method public static loopAndStringInitAndPhi([BZ)Ljava/lang/String;
    .registers 4
diff --git a/test/563-checker-fakestring/src/Main.java b/test/563-checker-fakestring/src/Main.java
index 7e775b3..e2773c8 100644
--- a/test/563-checker-fakestring/src/Main.java
+++ b/test/563-checker-fakestring/src/Main.java
@@ -34,6 +34,11 @@
     }
   }
 
+  // Create an empty int[] to force loading the int[] class before compiling
+  // TestCase.deoptimizeNewInstance.
+  // This makes sure the compiler can properly type int[] and not bail.
+  static int[] emptyArray = new int[0];
+
   public static void main(String[] args) throws Throwable {
     System.loadLibrary(args[0]);
     Class<?> c = Class.forName("TestCase");
@@ -160,10 +165,7 @@
     }
   }
 
-  public static boolean doThrow = false;
-
   public static Object $noinline$HiddenNull() {
-    if (doThrow) { throw new Error(); }
     return null;
   }
 }
diff --git a/test/564-checker-bitcount/src/Main.java b/test/564-checker-bitcount/src/Main.java
index e022d9d..460a31a 100644
--- a/test/564-checker-bitcount/src/Main.java
+++ b/test/564-checker-bitcount/src/Main.java
@@ -25,7 +25,6 @@
   /// CHECK-DAG:     <<Result:i\d+>>  InvokeStaticOrDirect intrinsic:IntegerBitCount
   /// CHECK-DAG:                      Return [<<Result>>]
   private static int $noinline$BitCountBoolean(boolean x) {
-    if (doThrow) { throw new Error(); }  // Try defeating inlining.
     return Integer.bitCount(x ? 1 : 0);
   }
 
@@ -33,7 +32,6 @@
   /// CHECK-DAG:     <<Result:i\d+>>  InvokeStaticOrDirect intrinsic:IntegerBitCount
   /// CHECK-DAG:                      Return [<<Result>>]
   private static int $noinline$BitCountByte(byte x) {
-    if (doThrow) { throw new Error(); }  // Try defeating inlining.
     return Integer.bitCount(x);
   }
 
@@ -41,7 +39,6 @@
   /// CHECK-DAG:     <<Result:i\d+>>  InvokeStaticOrDirect intrinsic:IntegerBitCount
   /// CHECK-DAG:                      Return [<<Result>>]
   private static int $noinline$BitCountShort(short x) {
-    if (doThrow) { throw new Error(); }  // Try defeating inlining.
     return Integer.bitCount(x);
   }
 
@@ -49,7 +46,6 @@
   /// CHECK-DAG:     <<Result:i\d+>>  InvokeStaticOrDirect intrinsic:IntegerBitCount
   /// CHECK-DAG:                      Return [<<Result>>]
   private static int $noinline$BitCountChar(char x) {
-    if (doThrow) { throw new Error(); }  // Try defeating inlining.
     return Integer.bitCount(x);
   }
 
@@ -57,7 +53,6 @@
   /// CHECK-DAG:     <<Result:i\d+>>  InvokeStaticOrDirect intrinsic:IntegerBitCount
   /// CHECK-DAG:                      Return [<<Result>>]
   private static int $noinline$BitCountInt(int x) {
-    if (doThrow) { throw new Error(); }  // Try defeating inlining.
     return Integer.bitCount(x);
   }
 
@@ -65,7 +60,6 @@
   /// CHECK-DAG:     <<Result:i\d+>>  InvokeStaticOrDirect intrinsic:LongBitCount
   /// CHECK-DAG:                      Return [<<Result>>]
   private static int $noinline$BitCountLong(long x) {
-    if (doThrow) { throw new Error(); }  // Try defeating inlining.
     return Long.bitCount(x);
   }
 
@@ -201,6 +195,4 @@
       throw new Error("Expected: " + expected + ", found: " + result);
     }
   }
-
-  private static boolean doThrow = false;
 }
diff --git a/test/564-checker-inline-loop/src/Main.java b/test/564-checker-inline-loop/src/Main.java
index 6929913..41eca35 100644
--- a/test/564-checker-inline-loop/src/Main.java
+++ b/test/564-checker-inline-loop/src/Main.java
@@ -21,9 +21,6 @@
   /// CHECK-DAG:                      Return [<<Invoke>>]
 
   /// CHECK-START: int Main.inlineLoop() inliner (after)
-  /// CHECK-NOT:                      InvokeStaticOrDirect
-
-  /// CHECK-START: int Main.inlineLoop() inliner (after)
   /// CHECK-DAG:     <<Constant:i\d+>>   IntConstant 42
   /// CHECK-DAG:                         Return [<<Constant>>]
 
@@ -31,31 +28,31 @@
   /// CHECK:                         Goto loop:{{B\d+}}
 
   public static int inlineLoop() {
-    return loopMethod();
+    return $inline$loopMethod();
   }
 
   /// CHECK-START: void Main.inlineWithinLoop() inliner (before)
   /// CHECK:      InvokeStaticOrDirect
 
-  /// CHECK-START: void Main.inlineWithinLoop() inliner (after)
-  /// CHECK-NOT:  InvokeStaticOrDirect
-
   /// CHECK-START: void Main.inlineWithinLoop() licm (after)
   /// CHECK-DAG:  Goto loop:<<OuterLoop:B\d+>> outer_loop:none
   /// CHECK-DAG:  Goto outer_loop:<<OuterLoop>>
 
   public static void inlineWithinLoop() {
     while (doLoop) {
-      loopMethod();
+      $inline$loopMethod();
     }
   }
 
-  public static int loopMethod() {
-    while (doLoop) {}
+  public static int $inline$loopMethod() {
+    // We use `otherDoLoop` here so we don't propagate the knowledge that `doLoop` is true when
+    // inlining from `inlineWithinLoop`.
+    while (otherDoLoop) {}
     return 42;
   }
 
   public static boolean doLoop = false;
+  public static boolean otherDoLoop = false;
 
   public static void main(String[] args) {
     inlineLoop();
diff --git a/test/565-checker-condition-liveness/src/Main.java b/test/565-checker-condition-liveness/src/Main.java
index 17a8613..4abf66d 100644
--- a/test/565-checker-condition-liveness/src/Main.java
+++ b/test/565-checker-condition-liveness/src/Main.java
@@ -33,8 +33,8 @@
   
   /// CHECK-START-{ARM,ARM64}: void Main.testThrowIntoCatchBlock(int, java.lang.Object, int[]) liveness (after)
   /// CHECK-DAG:  <<IntArg:i\d+>>   ParameterValue        env_uses:[23,25]
-  /// CHECK-DAG:  <<RefArg:l\d+>>   ParameterValue        env_uses:[11,23,25]
-  /// CHECK-DAG:  <<Array:l\d+>>    ParameterValue        env_uses:[11,23,25]
+  /// CHECK-DAG:  <<RefArg:l\d+>>   ParameterValue        env_uses:[11,23,25,33]
+  /// CHECK-DAG:  <<Array:l\d+>>    ParameterValue        env_uses:[11,23,25,33]
   /// CHECK-DAG:  <<Const1:i\d+>>   IntConstant 1         env_uses:[23,25]
   /// CHECK-DAG:                    SuspendCheck          env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]]           liveness:10
   /// CHECK-DAG:                    NullCheck             env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]]  liveness:20
@@ -43,10 +43,10 @@
   /// CHECK-DAG:                    TryBoundary
   
   /// CHECK-START-{ARM,ARM64}-DEBUGGABLE: void Main.testThrowIntoCatchBlock(int, java.lang.Object, int[]) liveness (after)
-  /// CHECK-DAG:  <<IntArg:i\d+>>   ParameterValue        env_uses:[11,23,25]
-  /// CHECK-DAG:  <<RefArg:l\d+>>   ParameterValue        env_uses:[11,23,25]
-  /// CHECK-DAG:  <<Array:l\d+>>    ParameterValue        env_uses:[11,23,25]
-  /// CHECK-DAG:  <<Const1:i\d+>>   IntConstant 1         env_uses:[23,25]
+  /// CHECK-DAG:  <<IntArg:i\d+>>   ParameterValue        env_uses:[11,23,25,33]
+  /// CHECK-DAG:  <<RefArg:l\d+>>   ParameterValue        env_uses:[11,23,25,33]
+  /// CHECK-DAG:  <<Array:l\d+>>    ParameterValue        env_uses:[11,23,25,33]
+  /// CHECK-DAG:  <<Const1:i\d+>>   IntConstant 1         env_uses:[23,25,33]
   /// CHECK-DAG:                    SuspendCheck          env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]]           liveness:10
   /// CHECK-DAG:                    NullCheck             env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]]  liveness:20
   /// CHECK-DAG:                    ArrayLength                                                               liveness:22
@@ -56,8 +56,8 @@
   // X86 and X86_64 generate at use site the ArrayLength, meaning only the BoundsCheck will have environment uses.
   /// CHECK-START-{X86,X86_64}: void Main.testThrowIntoCatchBlock(int, java.lang.Object, int[]) liveness (after)
   /// CHECK-DAG:  <<IntArg:i\d+>>   ParameterValue        env_uses:[25,25]
-  /// CHECK-DAG:  <<RefArg:l\d+>>   ParameterValue        env_uses:[11,25,25]
-  /// CHECK-DAG:  <<Array:l\d+>>    ParameterValue        env_uses:[11,25,25]
+  /// CHECK-DAG:  <<RefArg:l\d+>>   ParameterValue        env_uses:[11,25,25,33]
+  /// CHECK-DAG:  <<Array:l\d+>>    ParameterValue        env_uses:[11,25,25,33]
   /// CHECK-DAG:  <<Const1:i\d+>>   IntConstant 1         env_uses:[25,25]
   /// CHECK-DAG:                    SuspendCheck          env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]]           liveness:10
   /// CHECK-DAG:                    NullCheck             env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]]  liveness:20
@@ -66,10 +66,10 @@
   /// CHECK-DAG:                    TryBoundary
 
   /// CHECK-START-{X86,X86_64}-DEBUGGABLE: void Main.testThrowIntoCatchBlock(int, java.lang.Object, int[]) liveness (after)
-  /// CHECK-DAG:  <<IntArg:i\d+>>   ParameterValue        env_uses:[11,25,25]
-  /// CHECK-DAG:  <<RefArg:l\d+>>   ParameterValue        env_uses:[11,25,25]
-  /// CHECK-DAG:  <<Array:l\d+>>    ParameterValue        env_uses:[11,25,25]
-  /// CHECK-DAG:  <<Const1:i\d+>>   IntConstant 1         env_uses:[25,25]
+  /// CHECK-DAG:  <<IntArg:i\d+>>   ParameterValue        env_uses:[11,25,25,33]
+  /// CHECK-DAG:  <<RefArg:l\d+>>   ParameterValue        env_uses:[11,25,25,33]
+  /// CHECK-DAG:  <<Array:l\d+>>    ParameterValue        env_uses:[11,25,25,33]
+  /// CHECK-DAG:  <<Const1:i\d+>>   IntConstant 1         env_uses:[25,25,33]
   /// CHECK-DAG:                    SuspendCheck          env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]]           liveness:10
   /// CHECK-DAG:                    NullCheck             env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]]  liveness:20
   /// CHECK-DAG:                    ArrayLength                                                               liveness:22
diff --git a/test/565-checker-doublenegbitwise/smali/SmaliTests.smali b/test/565-checker-doublenegbitwise/smali/SmaliTests.smali
index 27412f6..549b650 100644
--- a/test/565-checker-doublenegbitwise/smali/SmaliTests.smali
+++ b/test/565-checker-doublenegbitwise/smali/SmaliTests.smali
@@ -76,11 +76,11 @@
 ## CHECK-DAG:       <<BooleanNot:z\d+>>  BooleanNot [<<Or>>]
 ## CHECK-DAG:                            Return [<<BooleanNot>>]
 
-## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-DAG:                            BooleanNot
 ## CHECK-NOT:                            BooleanNot
 
-## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-NOT:                            And
 .method public static $opt$noinline$booleanAndToOr(ZZ)Z
     .registers 4
@@ -153,11 +153,11 @@
 ## CHECK-DAG:       <<BooleanNot:z\d+>>  BooleanNot [<<And>>]
 ## CHECK-DAG:                            Return [<<BooleanNot>>]
 
-## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-DAG:                            BooleanNot
 ## CHECK-NOT:                            BooleanNot
 
-## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-NOT:                            Or
 .method public static $opt$noinline$booleanOrToAnd(ZZ)Z
     .registers 4
@@ -277,7 +277,7 @@
 ## CHECK-DAG:       <<Xor:i\d+>>         Xor [<<Cond1>>,<<Cond2>>]
 ## CHECK-DAG:                            Return [<<Xor>>]
 
-## CHECK-START: boolean SmaliTests.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-NOT:                            BooleanNot
 .method public static $opt$noinline$booleanNotXorToXor(ZZ)Z
     .registers 4
@@ -408,11 +408,11 @@
 ## CHECK-DAG:       <<BooleanNot:z\d+>>  BooleanNot [<<Or>>]
 ## CHECK-DAG:                            Return [<<BooleanNot>>]
 
-## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOrV2(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOrV2(boolean, boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-DAG:                            BooleanNot
 ## CHECK-NOT:                            BooleanNot
 
-## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOrV2(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOrV2(boolean, boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-NOT:                            And
 
 # Original java source:
@@ -530,11 +530,11 @@
 ## CHECK-DAG:       <<BooleanNot:z\d+>>  BooleanNot [<<And>>]
 ## CHECK-DAG:                            Return [<<BooleanNot>>]
 
-## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAndV2(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAndV2(boolean, boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-DAG:                            BooleanNot
 ## CHECK-NOT:                            BooleanNot
 
-## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAndV2(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAndV2(boolean, boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-NOT:                            Or
 
 # Original java source:
@@ -717,7 +717,7 @@
 ## CHECK-DAG:       <<Xor:i\d+>>         Xor [<<Cond1>>,<<Cond2>>]
 ## CHECK-DAG:                            Return [<<Xor>>]
 
-## CHECK-START: boolean SmaliTests.$opt$noinline$booleanNotXorToXorV2(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanNotXorToXorV2(boolean, boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-NOT:                            BooleanNot
 
 # Original java source:
diff --git a/test/566-checker-codegen-select/src/Main.java b/test/566-checker-codegen-select/src/Main.java
index e215ab0..7b44580 100644
--- a/test/566-checker-codegen-select/src/Main.java
+++ b/test/566-checker-codegen-select/src/Main.java
@@ -21,7 +21,6 @@
   /// CHECK-NEXT:                  Select [{{j\d+}},{{j\d+}},<<Cond>>]
 
   public long $noinline$longSelect(long param) {
-    if (doThrow) { throw new Error(); }
     long val_true = longB;
     long val_false = longC;
     return (param > longA) ? val_true : val_false;
@@ -46,7 +45,6 @@
   /// CHECK:             cmovle/ngq
 
   public long $noinline$longSelect_Constant(long param) {
-    if (doThrow) { throw new Error(); }
     long val_true = longB;
     long val_false = longC;
     return (param > 3L) ? val_true : val_false;
@@ -60,7 +58,6 @@
   /// CHECK:             cmovl/nge
 
   public int $noinline$intSelect_Constant(int param) {
-    if (doThrow) { throw new Error(); }
     int val_true = intB;
     int val_false = intC;
     return (param >= 3) ? val_true : val_false;
@@ -88,8 +85,6 @@
     }
   }
 
-  public boolean doThrow = false;
-
   public long longA = 3L;
   public long longB = 5L;
   public long longC = 7L;
diff --git a/test/566-polymorphic-inlining/run b/test/566-polymorphic-inlining/run
deleted file mode 100644
index 2919f46..0000000
--- a/test/566-polymorphic-inlining/run
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# -Xjitinitialsize:32M to prevent profiling info creation failure.
-exec ${RUN} \
-  --runtime-option -Xjitinitialsize:32M \
-  "${@}"
diff --git a/test/566-polymorphic-inlining/run.py b/test/566-polymorphic-inlining/run.py
new file mode 100644
index 0000000..0bfd5dc
--- /dev/null
+++ b/test/566-polymorphic-inlining/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # -Xjitinitialsize:32M to prevent profiling info creation failure.
+  ctx.default_run(args, runtime_option=["-Xjitinitialsize:32M"])
diff --git a/test/566-polymorphic-inlining/src/Main.java b/test/566-polymorphic-inlining/src/Main.java
index 595a056..691d48f 100644
--- a/test/566-polymorphic-inlining/src/Main.java
+++ b/test/566-polymorphic-inlining/src/Main.java
@@ -118,7 +118,6 @@
   }
 
   public static void $noinline$testInlineToSameTarget(Main m) {
-    if (doThrow) throw new Error("");
     m.increment();
   }
 
@@ -137,7 +136,6 @@
     counter++;
   }
   public static int counter = 0;
-  public static boolean doThrow = false;
 }
 
 class Subclass extends Main {
diff --git a/test/567-checker-builder-intrinsics/src/TestCompare.java b/test/567-checker-builder-intrinsics/src/TestCompare.java
index 185616a..1feb249 100644
--- a/test/567-checker-builder-intrinsics/src/TestCompare.java
+++ b/test/567-checker-builder-intrinsics/src/TestCompare.java
@@ -40,13 +40,13 @@
   /// CHECK-START: int TestCompare.compareBooleans(boolean, boolean) select_generator (after)
   /// CHECK-NOT:                     Phi
 
-  /// CHECK-START: int TestCompare.compareBooleans(boolean, boolean) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int TestCompare.compareBooleans(boolean, boolean) instruction_simplifier$before_codegen (after)
   /// CHECK:         <<ArgX:z\d+>>   ParameterValue
   /// CHECK:         <<ArgY:z\d+>>   ParameterValue
   /// CHECK-DAG:     <<Result:i\d+>> Compare [<<ArgX>>,<<ArgY>>]
   /// CHECK-DAG:                     Return [<<Result>>]
 
-  /// CHECK-START: int TestCompare.compareBooleans(boolean, boolean) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int TestCompare.compareBooleans(boolean, boolean) instruction_simplifier$before_codegen (after)
   /// CHECK-NOT:                     Select
 
   private static int compareBooleans(boolean x, boolean y) {
@@ -77,13 +77,13 @@
   ///  CHECK-START: int TestCompare.compareBooleans2(boolean, boolean) select_generator (after)
   ///  CHECK-NOT:                     Phi
 
-  ///  CHECK-START: int TestCompare.compareBooleans2(boolean, boolean) instruction_simplifier$after_bce (after)
+  ///  CHECK-START: int TestCompare.compareBooleans2(boolean, boolean) instruction_simplifier$before_codegen (after)
   ///  CHECK:         <<ArgX:z\d+>>   ParameterValue
   ///  CHECK:         <<ArgY:z\d+>>   ParameterValue
   ///  CHECK-DAG:     <<Result:i\d+>> Compare [<<ArgX>>,<<ArgY>>]
   ///  CHECK-DAG:                     Return [<<Result>>]
 
-  ///  CHECK-START: int TestCompare.compareBooleans2(boolean, boolean) instruction_simplifier$after_bce (after)
+  ///  CHECK-START: int TestCompare.compareBooleans2(boolean, boolean) instruction_simplifier$before_codegen (after)
   ///  CHECK-NOT:                     Select
 
   private static int compareBooleans2(boolean x, boolean y) {
diff --git a/test/567-checker-builder-intrinsics/src/TestFpAbs.java b/test/567-checker-builder-intrinsics/src/TestFpAbs.java
index e6c338d..f36432d 100644
--- a/test/567-checker-builder-intrinsics/src/TestFpAbs.java
+++ b/test/567-checker-builder-intrinsics/src/TestFpAbs.java
@@ -29,8 +29,6 @@
   private static final int SPQUIET = 1 << 22;
   private static final long DPQUIET = 1L << 51;
 
-  public static boolean doThrow = false;
-
   /// CHECK-START: float TestFpAbs.$opt$noinline$absSP(float) builder (after)
   /// CHECK-DAG: <<Result:f\d+>> Abs
   /// CHECK-DAG:                 Return [<<Result>>]
diff --git a/test/567-checker-builder-intrinsics/src/TestMinMax.java b/test/567-checker-builder-intrinsics/src/TestMinMax.java
index 0e88517..7207006 100644
--- a/test/567-checker-builder-intrinsics/src/TestMinMax.java
+++ b/test/567-checker-builder-intrinsics/src/TestMinMax.java
@@ -564,6 +564,16 @@
     return x;
   }
 
+  /// CHECK-START: int TestMinMax.minmax3(int) select_generator (after)
+  /// CHECK-DAG: <<Par:i\d+>>  ParameterValue
+  /// CHECK-DAG: <<P100:i\d+>> IntConstant 100
+  /// CHECK-DAG: <<M100:i\d+>> IntConstant -100
+  /// CHECK-DAG: <<Cnd1:z\d+>> LessThanOrEqual [<<Par>>,<<P100>>]
+  /// CHECK-DAG: <<Cnd2:z\d+>> GreaterThanOrEqual [<<Par>>,<<M100>>]
+  /// CHECK-DAG: <<Sel1:i\d+>> Select [<<M100>>,<<Par>>,<<Cnd2>>]
+  /// CHECK-DAG: <<Sel2:i\d+>> Select [<<P100>>,<<Sel1>>,<<Cnd1>>]
+  /// CHECK-DAG:               Return [<<Sel2>>]
+
   /// CHECK-START: int TestMinMax.minmax3(int) instruction_simplifier$after_gvn (after)
   /// CHECK-DAG: <<Par:i\d+>>  ParameterValue
   /// CHECK-DAG: <<P100:i\d+>> IntConstant 100
@@ -578,6 +588,16 @@
     return (x > 100) ? 100 : ((x < -100) ? -100 : x);
   }
 
+  /// CHECK-START: int TestMinMax.minmax4(int) select_generator (after)
+  /// CHECK-DAG: <<Par:i\d+>>  ParameterValue
+  /// CHECK-DAG: <<P100:i\d+>> IntConstant 100
+  /// CHECK-DAG: <<M100:i\d+>> IntConstant -100
+  /// CHECK-DAG: <<Cnd1:z\d+>> GreaterThanOrEqual [<<Par>>,<<M100>>]
+  /// CHECK-DAG: <<Cnd2:z\d+>> LessThanOrEqual [<<Par>>,<<P100>>]
+  /// CHECK-DAG: <<Sel1:i\d+>> Select [<<P100>>,<<Par>>,<<Cnd2>>]
+  /// CHECK-DAG: <<Sel2:i\d+>> Select [<<M100>>,<<Sel1>>,<<Cnd1>>]
+  /// CHECK-DAG:               Return [<<Sel2>>]
+
   /// CHECK-START: int TestMinMax.minmax4(int) instruction_simplifier$after_gvn (after)
   /// CHECK-DAG: <<Par:i\d+>>  ParameterValue
   /// CHECK-DAG: <<P100:i\d+>> IntConstant 100
diff --git a/test/567-checker-builder-intrinsics/src/TestRotate.java b/test/567-checker-builder-intrinsics/src/TestRotate.java
index 0593e60..2037ccf 100644
--- a/test/567-checker-builder-intrinsics/src/TestRotate.java
+++ b/test/567-checker-builder-intrinsics/src/TestRotate.java
@@ -205,14 +205,14 @@
   /// CHECK-START: int TestRotate.rotateLeftBoolean(boolean, int) select_generator (after)
   /// CHECK-NOT:                      Phi
 
-  /// CHECK-START: int TestRotate.rotateLeftBoolean(boolean, int) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int TestRotate.rotateLeftBoolean(boolean, int) instruction_simplifier$before_codegen (after)
   /// CHECK:         <<ArgVal:z\d+>>  ParameterValue
   /// CHECK:         <<ArgDist:i\d+>> ParameterValue
   /// CHECK-DAG:     <<NegDist:i\d+>> Neg [<<ArgDist>>]
   /// CHECK-DAG:     <<Result:i\d+>>  Ror [<<ArgVal>>,<<NegDist>>]
   /// CHECK-DAG:                      Return [<<Result>>]
 
-  /// CHECK-START: int TestRotate.rotateLeftBoolean(boolean, int) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int TestRotate.rotateLeftBoolean(boolean, int) instruction_simplifier$before_codegen (after)
   /// CHECK-NOT:                      Select
 
   private static int rotateLeftBoolean(boolean value, int distance) {
@@ -350,13 +350,13 @@
   /// CHECK-START: int TestRotate.rotateRightBoolean(boolean, int) select_generator (after)
   /// CHECK-NOT:                     Phi
 
-  /// CHECK-START: int TestRotate.rotateRightBoolean(boolean, int) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int TestRotate.rotateRightBoolean(boolean, int) instruction_simplifier$before_codegen (after)
   /// CHECK:         <<ArgVal:z\d+>>  ParameterValue
   /// CHECK:         <<ArgDist:i\d+>> ParameterValue
   /// CHECK-DAG:     <<Result:i\d+>>  Ror [<<ArgVal>>,<<ArgDist>>]
   /// CHECK-DAG:                      Return [<<Result>>]
 
-  /// CHECK-START: int TestRotate.rotateRightBoolean(boolean, int) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int TestRotate.rotateRightBoolean(boolean, int) instruction_simplifier$before_codegen (after)
   /// CHECK-NOT:                     Select
 
   private static int rotateRightBoolean(boolean value, int distance) {
diff --git a/test/567-checker-builder-intrinsics/src/TestSignum.java b/test/567-checker-builder-intrinsics/src/TestSignum.java
index 0a68ac2..ed3c5fe 100644
--- a/test/567-checker-builder-intrinsics/src/TestSignum.java
+++ b/test/567-checker-builder-intrinsics/src/TestSignum.java
@@ -92,13 +92,13 @@
   /// CHECK-START: int TestSignum.signBoolean(boolean) select_generator (after)
   /// CHECK-NOT:                     Phi
 
-  /// CHECK-START: int TestSignum.signBoolean(boolean) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int TestSignum.signBoolean(boolean) instruction_simplifier$before_codegen (after)
   /// CHECK-DAG:     <<Arg:z\d+>>    ParameterValue
   /// CHECK-DAG:     <<Zero:i\d+>>   IntConstant 0
   /// CHECK-DAG:     <<Result:i\d+>> Compare [<<Arg>>,<<Zero>>]
   /// CHECK-DAG:                     Return [<<Result>>]
 
-  /// CHECK-START: int TestSignum.signBoolean(boolean) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int TestSignum.signBoolean(boolean) instruction_simplifier$before_codegen (after)
   /// CHECK-NOT:                     Select
 
   private static int signBoolean(boolean x) {
diff --git a/test/569-checker-pattern-replacement/run b/test/569-checker-pattern-replacement/run
deleted file mode 100755
index 8ab6527..0000000
--- a/test/569-checker-pattern-replacement/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-exec ${RUN} "$@" \
-    -Xcompiler-option --no-inline-from="core-oj,569-checker-pattern-replacement.jar!classes2.dex"
diff --git a/test/569-checker-pattern-replacement/run.py b/test/569-checker-pattern-replacement/run.py
new file mode 100644
index 0000000..d763e54
--- /dev/null
+++ b/test/569-checker-pattern-replacement/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args,
+      Xcompiler_option=[
+          "--no-inline-from=core-oj,569-checker-pattern-replacement.jar!classes2.dex"
+      ])
diff --git a/test/570-checker-osr-locals/run b/test/570-checker-osr-locals/run
deleted file mode 100755
index 6cef13f..0000000
--- a/test/570-checker-osr-locals/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 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.
-
-# Ensure this test is not subject to code collection.
-exec ${RUN} "$@" --runtime-option -Xjitinitialsize:32M
diff --git a/test/570-checker-osr-locals/run.py b/test/570-checker-osr-locals/run.py
new file mode 100644
index 0000000..d876de2
--- /dev/null
+++ b/test/570-checker-osr-locals/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 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.
+
+
+def run(ctx, args):
+  # Ensure this test is not subject to code collection.
+  ctx.default_run(args, runtime_option=["-Xjitinitialsize:32M"])
diff --git a/test/570-checker-osr/run b/test/570-checker-osr/run
deleted file mode 100755
index 24d69b4..0000000
--- a/test/570-checker-osr/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-# Ensure this test is not subject to code collection.
-exec ${RUN} "$@" --runtime-option -Xjitinitialsize:32M
diff --git a/test/570-checker-osr/run.py b/test/570-checker-osr/run.py
new file mode 100644
index 0000000..8109c62
--- /dev/null
+++ b/test/570-checker-osr/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+
+
+def run(ctx, args):
+  # Ensure this test is not subject to code collection.
+  ctx.default_run(args, runtime_option=["-Xjitinitialsize:32M"])
diff --git a/test/570-checker-osr/smali/Osr.smali b/test/570-checker-osr/smali/Osr.smali
index 6592b7b..ee4c7ca 100644
--- a/test/570-checker-osr/smali/Osr.smali
+++ b/test/570-checker-osr/smali/Osr.smali
@@ -19,7 +19,7 @@
 # Check that blocks only havig nops are not merged when they are loop headers.
 # This ensures we can do on-stack replacement for branches to those nop blocks.
 
-## CHECK-START: int Osr.simpleLoop(int, int) dead_code_elimination$final (after)
+## CHECK-START: int Osr.simpleLoop(int, int) dead_code_elimination$before_codegen (after)
 ## CHECK-DAG:                     SuspendCheck loop:<<OuterLoop:B\d+>> outer_loop:none
 ## CHECK-DAG:                     SuspendCheck loop:{{B\d+}} outer_loop:<<OuterLoop>>
 .method public static simpleLoop(II)I
diff --git a/test/574-irreducible-and-constant-area/run b/test/574-irreducible-and-constant-area/run
deleted file mode 100755
index ffdbcc9..0000000
--- a/test/574-irreducible-and-constant-area/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-# Don't do relocation, as this affects this test.
-exec ${RUN} "$@" --no-relocate
diff --git a/test/574-irreducible-and-constant-area/run.py b/test/574-irreducible-and-constant-area/run.py
new file mode 100644
index 0000000..3c04d9e
--- /dev/null
+++ b/test/574-irreducible-and-constant-area/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+
+
+def run(ctx, args):
+  # Don't do relocation, as this affects this test.
+  ctx.default_run(args, relocate=False)
diff --git a/test/578-polymorphic-inlining/src/Main.java b/test/578-polymorphic-inlining/src/Main.java
index 22d33d0..4798922 100644
--- a/test/578-polymorphic-inlining/src/Main.java
+++ b/test/578-polymorphic-inlining/src/Main.java
@@ -44,10 +44,7 @@
   }
 
   public static void $noinline$foo() {
-    if (doThrow) throw new Error("");
   }
-
-  public static boolean doThrow;
 }
 
 class SubMain extends Main {
diff --git a/test/590-checker-arr-set-null-regression/src/Main.java b/test/590-checker-arr-set-null-regression/src/Main.java
index 792ee4e..ad47716 100644
--- a/test/590-checker-arr-set-null-regression/src/Main.java
+++ b/test/590-checker-arr-set-null-regression/src/Main.java
@@ -33,7 +33,7 @@
   /// CHECK-DAG:     <<CheckedArray:l\d+>>  NullCheck [<<Array>>]
   /// CHECK-DAG:     <<Length:i\d+>>        ArrayLength [<<CheckedArray>>]
   /// CHECK-DAG:     <<CheckedIndex:i\d+>>  BoundsCheck [<<Index>>,<<Length>>]
-  /// CHECK-DAG:     <<ArraySet:v\d+>>      ArraySet [<<CheckedArray>>,<<CheckedIndex>>,<<CheckedValue>>] needs_type_check:true
+  /// CHECK-DAG:     <<ArraySet:v\d+>>      ArraySet [<<CheckedArray>>,<<CheckedIndex>>,<<CheckedValue>>] needs_type_check:true can_trigger_gc:true
 
   /// CHECK-START: void Main.testArraySetCheckCastNull(Main$Element[]) instruction_simplifier (after)
   /// CHECK-NOT:                            CheckCast
@@ -47,7 +47,7 @@
   /// CHECK-DAG:     <<CheckedArray:l\d+>>  NullCheck [<<Array>>]
   /// CHECK-DAG:     <<Length:i\d+>>        ArrayLength [<<CheckedArray>>]
   /// CHECK-DAG:     <<CheckedIndex:i\d+>>  BoundsCheck [<<Index>>,<<Length>>]
-  /// CHECK-DAG:     <<ArraySet:v\d+>>      ArraySet [<<CheckedArray>>,<<CheckedIndex>>,<<CheckedValue>>] needs_type_check:true
+  /// CHECK-DAG:     <<ArraySet:v\d+>>      ArraySet [<<CheckedArray>>,<<CheckedIndex>>,<<CheckedValue>>] needs_type_check:true can_trigger_gc:true
 
   /// CHECK-START: void Main.testArraySetCheckCastNull(Main$Element[]) prepare_for_register_allocation (after)
   /// CHECK:         <<Array:l\d+>>         ParameterValue
@@ -55,7 +55,7 @@
   /// CHECK-DAG:     <<Null:l\d+>>          NullConstant
   /// CHECK-DAG:     <<Class:l\d+>>         LoadClass
   /// CHECK-DAG:     <<Length:i\d+>>        ArrayLength [<<Array>>]
-  /// CHECK-DAG:     <<ArraySet:v\d+>>      ArraySet [<<Array>>,<<Index>>,<<Null>>] needs_type_check:false
+  /// CHECK-DAG:     <<ArraySet:v\d+>>      ArraySet [<<Array>>,<<Index>>,<<Null>>] needs_type_check:false can_trigger_gc:false
 
   static void testArraySetCheckCastNull(Element[] elements) {
     Object object = null;
diff --git a/test/593-checker-boolean-2-integral-conv/smali/SmaliTests.smali b/test/593-checker-boolean-2-integral-conv/smali/SmaliTests.smali
index bd90fe7..f3f52a8 100644
--- a/test/593-checker-boolean-2-integral-conv/smali/SmaliTests.smali
+++ b/test/593-checker-boolean-2-integral-conv/smali/SmaliTests.smali
@@ -48,7 +48,7 @@
 ##  CHECK-DAG:     <<IToS:b\d+>>          TypeConversion [<<Sel>>]
 ##  CHECK-DAG:                            Return [<<IToS>>]
 
-##  CHECK-START: byte SmaliTests.booleanToByte(boolean) instruction_simplifier$after_bce (after)
+##  CHECK-START: byte SmaliTests.booleanToByte(boolean) instruction_simplifier$before_codegen (after)
 ##  CHECK:         <<Arg:z\d+>>           ParameterValue
 ##  CHECK-DAG:                            Return [<<Arg>>]
 .method static booleanToByte(Z)B
@@ -83,7 +83,7 @@
 ##  CHECK-DAG:     <<IToS:s\d+>>          TypeConversion [<<Sel>>]
 ##  CHECK-DAG:                            Return [<<IToS>>]
 
-##  CHECK-START: short SmaliTests.booleanToShort(boolean) instruction_simplifier$after_bce (after)
+##  CHECK-START: short SmaliTests.booleanToShort(boolean) instruction_simplifier$before_codegen (after)
 ##  CHECK:         <<Arg:z\d+>>           ParameterValue
 ##  CHECK-DAG:                            Return [<<Arg>>]
 .method static booleanToShort(Z)S
@@ -118,7 +118,7 @@
 ##  CHECK-DAG:     <<IToC:c\d+>>          TypeConversion [<<Sel>>]
 ##  CHECK-DAG:                            Return [<<IToC>>]
 
-##  CHECK-START: char SmaliTests.booleanToChar(boolean) instruction_simplifier$after_bce (after)
+##  CHECK-START: char SmaliTests.booleanToChar(boolean) instruction_simplifier$before_codegen (after)
 ##  CHECK:         <<Arg:z\d+>>           ParameterValue
 ##  CHECK-DAG:                            Return [<<Arg>>]
 .method static booleanToChar(Z)C
@@ -151,7 +151,7 @@
 ##  CHECK-DAG:     <<Sel:i\d+>>           Select [<<Zero>>,<<One>>,<<Arg>>]
 ##  CHECK-DAG:                            Return [<<Sel>>]
 
-##  CHECK-START: int SmaliTests.booleanToInt(boolean) instruction_simplifier$after_bce (after)
+##  CHECK-START: int SmaliTests.booleanToInt(boolean) instruction_simplifier$before_codegen (after)
 ##  CHECK:         <<Arg:z\d+>>           ParameterValue
 ##  CHECK-DAG:                            Return [<<Arg>>]
 .method static booleanToInt(Z)I
@@ -185,7 +185,7 @@
 ## CHECK-DAG:     <<IToJ:j\d+>>          TypeConversion [<<Sel>>]
 ## CHECK-DAG:                            Return [<<IToJ>>]
 
-## CHECK-START: long SmaliTests.booleanToLong(boolean) instruction_simplifier$after_bce (after)
+## CHECK-START: long SmaliTests.booleanToLong(boolean) instruction_simplifier$before_codegen (after)
 ## CHECK-DAG:     <<Arg:z\d+>>           ParameterValue
 ## CHECK-DAG:     <<ZToJ:j\d+>>          TypeConversion [<<Arg>>]
 ## CHECK-DAG:                            Return [<<ZToJ>>]
@@ -232,7 +232,7 @@
 ## CHECK-DAG:     <<Sel:i\d+>>           Select [<<Zero>>,<<One>>,<<Sget>>]
 ## CHECK-DAG:                            Return [<<Sel>>]
 
-## CHECK-START: int SmaliTests.longToIntOfBoolean() instruction_simplifier$after_bce (after)
+## CHECK-START: int SmaliTests.longToIntOfBoolean() instruction_simplifier$before_codegen (after)
 ## CHECK-DAG:     <<Sget:z\d+>>          StaticFieldGet
 ## CHECK-DAG:                            Return [<<Sget>>]
 .method public static longToIntOfBoolean()I
diff --git a/test/593-checker-boolean-2-integral-conv/src/Main.java b/test/593-checker-boolean-2-integral-conv/src/Main.java
index b085c42..545f16d 100644
--- a/test/593-checker-boolean-2-integral-conv/src/Main.java
+++ b/test/593-checker-boolean-2-integral-conv/src/Main.java
@@ -32,7 +32,7 @@
     System.out.println("passed");
   }
 
-  /// CHECK-START: byte Main.booleanToByte(boolean) instruction_simplifier$after_bce (after)
+  /// CHECK-START: byte Main.booleanToByte(boolean) instruction_simplifier$before_codegen (after)
   /// CHECK:         <<Arg:z\d+>>           ParameterValue
   /// CHECK-DAG:                            Return [<<Arg>>]
 
@@ -40,7 +40,7 @@
     return (byte)(b ? 1 : 0);
   }
 
-  /// CHECK-START: short Main.booleanToShort(boolean) instruction_simplifier$after_bce (after)
+  /// CHECK-START: short Main.booleanToShort(boolean) instruction_simplifier$before_codegen (after)
   /// CHECK:         <<Arg:z\d+>>           ParameterValue
   /// CHECK-DAG:                            Return [<<Arg>>]
 
@@ -48,7 +48,7 @@
     return (short)(b ? 1 : 0);
   }
 
-  /// CHECK-START: char Main.booleanToChar(boolean) instruction_simplifier$after_bce (after)
+  /// CHECK-START: char Main.booleanToChar(boolean) instruction_simplifier$before_codegen (after)
   /// CHECK:         <<Arg:z\d+>>           ParameterValue
   /// CHECK-DAG:                            Return [<<Arg>>]
 
@@ -56,7 +56,7 @@
     return (char)(b ? 1 : 0);
   }
 
-  /// CHECK-START: int Main.booleanToInt(boolean) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int Main.booleanToInt(boolean) instruction_simplifier$before_codegen (after)
   /// CHECK:         <<Arg:z\d+>>           ParameterValue
   /// CHECK-DAG:                            Return [<<Arg>>]
 
@@ -90,7 +90,7 @@
   // As of now, the code is not optimized any further than the above.
   // TODO: Re-enable checks below after simplifier is updated to handle this pattern: b/63064517
 
-  // CHECK-START: long Main.booleanToLong(boolean) instruction_simplifier$after_bce (after)
+  // CHECK-START: long Main.booleanToLong(boolean) instruction_simplifier$before_codegen (after)
   // CHECK:         <<Arg:z\d+>>           ParameterValue
   // CHECK-DAG:     <<ZToJ:j\d+>>          TypeConversion [<<Arg>>]
   // CHECK-DAG:                            Return [<<ZToJ>>]
@@ -131,7 +131,7 @@
   // As of now, the code is not optimized any further than the above.
   // TODO: Re-enable checks below after simplifier is updated to handle this pattern: b/63064517
 
-  // CHECK-START: int Main.longToIntOfBoolean() instruction_simplifier$after_bce (after)
+  // CHECK-START: int Main.longToIntOfBoolean() instruction_simplifier$before_codegen (after)
   // CHECK-DAG:     <<Sget:z\d+>>          StaticFieldGet
   // CHECK-DAG:                            Return [<<Sget>>]
 
diff --git a/test/593-checker-long-2-float-regression/src/Main.java b/test/593-checker-long-2-float-regression/src/Main.java
index b31cbde..079ebbc 100644
--- a/test/593-checker-long-2-float-regression/src/Main.java
+++ b/test/593-checker-long-2-float-regression/src/Main.java
@@ -16,7 +16,6 @@
 
 public class Main {
 
-  static boolean doThrow = false;
   static long longValue;
 
   public static void assertEquals(float expected, float result) {
@@ -26,26 +25,35 @@
   }
 
   public static void main(String[] args) {
-    assertEquals(1.0F, $noinline$longToFloat());
+    assertEquals(1.0F, $noinline$longToFloat(true));
+    assertEquals(2.0F, $noinline$longToFloat(false));
   }
 
-  /// CHECK-START: float Main.$noinline$longToFloat() register (after)
-  /// CHECK-DAG:     <<Const1:j\d+>>   LongConstant 1
-  /// CHECK-DAG:     <<Convert:f\d+>>  TypeConversion [<<Const1>>]
-  /// CHECK-DAG:                       Return [<<Convert>>]
+  /// CHECK-START: float Main.$noinline$longToFloat(boolean) register (after)
+  /// CHECK:     <<Get:j\d+>>      StaticFieldGet field_name:Main.longValue
+  /// CHECK:     <<Convert:f\d+>>  TypeConversion [<<Get>>]
+  /// CHECK:                       Return [<<Convert>>]
 
-  static float $noinline$longToFloat() {
-    if (doThrow) { throw new Error(); }
-    longValue = $inline$returnConst();
+  static float $noinline$longToFloat(boolean param) {
+    // This if else is to avoid constant folding the long constant into a float constant.
+    if (param) {
+      longValue = $inline$returnConstOne();
+    } else {
+      longValue = $inline$returnConstTwo();
+    }
     // This call prevents D8 from replacing the result of the sget instruction
-    // in line 43 by the result of the call to $inline$returnConst() in line 39.
+    // in the return below by the result of the call to $inline$returnConstOne/Two() above.
     $inline$preventRedundantFieldLoadEliminationInD8();
     return (float) longValue;
   }
 
-  static long $inline$returnConst() {
+  static long $inline$returnConstOne() {
     return 1L;
   }
 
+  static long $inline$returnConstTwo() {
+    return 2L;
+  }
+
   static void $inline$preventRedundantFieldLoadEliminationInD8() {}
 }
diff --git a/test/594-load-string-regression/src/Main.java b/test/594-load-string-regression/src/Main.java
index 0b9f7b5..5903130 100644
--- a/test/594-load-string-regression/src/Main.java
+++ b/test/594-load-string-regression/src/Main.java
@@ -15,8 +15,6 @@
  */
 
 public class Main {
-  static boolean doThrow = false;
-
   // Note: We're not doing checker tests as we cannot do them specifically for a non-PIC
   // configuration. The check here would be "prepare_for_register_allocation (before)"
   //     CHECK:         LoadClass
@@ -28,8 +26,6 @@
   //     CHECK-NEXT:    NewInstance
   // but the order of instructions for non-PIC mode is different.
   public static int $noinline$test() {
-    if (doThrow) { throw new Error(); }
-
     int r = 0x12345678;
     do {
       // LICM pulls the LoadClass and ClinitCheck out of the loop, leaves NewInstance in the loop.
@@ -67,11 +63,7 @@
 }
 
 class Helper {
-  static boolean doThrow = false;
-
   public void $noinline$printString(String s) {
-    if (doThrow) { throw new Error(); }
-
     System.out.println("String: \"" + s + "\"");
   }
 }
diff --git a/test/595-profile-saving/res/art-gtest-jars-Main.dex b/test/595-profile-saving/res/art-gtest-jars-Main.dex
new file mode 100644
index 0000000..2747919
--- /dev/null
+++ b/test/595-profile-saving/res/art-gtest-jars-Main.dex
Binary files differ
diff --git a/test/595-profile-saving/res/art-gtest-jars-MainEmptyUncompressed.jar b/test/595-profile-saving/res/art-gtest-jars-MainEmptyUncompressed.jar
new file mode 100644
index 0000000..252879f
--- /dev/null
+++ b/test/595-profile-saving/res/art-gtest-jars-MainEmptyUncompressed.jar
Binary files differ
diff --git a/test/595-profile-saving/run b/test/595-profile-saving/run
deleted file mode 100644
index 851be09..0000000
--- a/test/595-profile-saving/run
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# Use
-# --compiler-filter=quicken to make sure that the test is not compiled AOT
-# and to make sure the test is not compiled  when loaded (by PathClassLoader)
-# -Xjitsaveprofilinginfo to enable profile saving
-# -Xusejit:false to disable jit and only test profiles.
-# -Xjitinitialsize:32M to prevent profiling info creation failure.
-exec ${RUN} \
-  -Xcompiler-option --compiler-filter=quicken \
-  --runtime-option '-Xcompiler-option --compiler-filter=quicken' \
-  --runtime-option -Xjitinitialsize:32M \
-  --runtime-option -Xjitsaveprofilinginfo \
-  --runtime-option -Xusejit:false \
-  --runtime-option -Xps-profile-boot-class-path \
-  "${@}"
diff --git a/test/595-profile-saving/run.py b/test/595-profile-saving/run.py
new file mode 100644
index 0000000..96de281
--- /dev/null
+++ b/test/595-profile-saving/run.py
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  # Use
+  # --compiler-filter=quicken to make sure that the test is not compiled AOT
+  # and to make sure the test is not compiled  when loaded (by PathClassLoader)
+  # -Xjitsaveprofilinginfo to enable profile saving
+  # -Xusejit:false to disable jit and only test profiles.
+  # -Xjitinitialsize:32M to prevent profiling info creation failure.
+  ctx.default_run(
+      args,
+      Xcompiler_option=["--compiler-filter=quicken"],
+      runtime_option=[
+          "-Xcompiler-option --compiler-filter=quicken",
+          "-Xjitinitialsize:32M",
+          "-Xjitsaveprofilinginfo",
+          "-Xusejit:false",
+          "-Xps-profile-boot-class-path",
+      ])
diff --git a/test/595-profile-saving/src/Main.java b/test/595-profile-saving/src/Main.java
index 5b1a448..3229b53 100644
--- a/test/595-profile-saving/src/Main.java
+++ b/test/595-profile-saving/src/Main.java
@@ -24,9 +24,22 @@
     System.loadLibrary(args[0]);
 
     File file = null;
+    File file2 = null;
+    File file3 = null;
     try {
+      // Register `file2` with an empty jar. Even though `file2` is registered before `file`, the
+      // runtime should not write bootclasspath methods to `file2`, and it should not even create
+      // `file2`.
+      file2 = createTempFile();
+      String emptyJarPath =
+          System.getenv("DEX_LOCATION") + "/res/art-gtest-jars-MainEmptyUncompressed.jar";
+      VMRuntime.registerAppInfo("test.app",
+                                file2.getPath(),
+                                file2.getPath(),
+                                new String[] {emptyJarPath},
+                                VMRuntime.CODE_PATH_TYPE_SPLIT_APK);
+
       file = createTempFile();
-      // String codePath = getDexBaseLocation();
       String codePath = System.getenv("DEX_LOCATION") + "/595-profile-saving.jar";
       VMRuntime.registerAppInfo("test.app",
                                 file.getPath(),
@@ -34,13 +47,35 @@
                                 new String[] {codePath},
                                 VMRuntime.CODE_PATH_TYPE_PRIMARY_APK);
 
-      // Test that the profile saves an app method with a profiling info.
+      file3 = createTempFile();
+      String dexPath = System.getenv("DEX_LOCATION") + "/res/art-gtest-jars-Main.dex";
+      VMRuntime.registerAppInfo("test.app",
+                                file3.getPath(),
+                                file3.getPath(),
+                                new String[] {dexPath},
+                                VMRuntime.CODE_PATH_TYPE_SPLIT_APK);
+
+      // Delete the files so that we can check if the runtime creates them. The runtime should
+      // create `file` and `file3` but not `file2`.
+      file.delete();
+      file2.delete();
+      file3.delete();
+
+      // Test that the runtime saves the profiling info of an app method in a .jar file.
       Method appMethod = Main.class.getDeclaredMethod("testAddMethodToProfile",
           File.class, Method.class);
       testAddMethodToProfile(file, appMethod);
 
-      // Test that the profile saves a boot class path method with a profiling info.
-      Method bootMethod = File.class.getDeclaredMethod("delete");
+      // Test that the runtime saves the profiling info of an app method in a .dex file.
+      ClassLoader dexClassLoader = (ClassLoader) Class.forName("dalvik.system.PathClassLoader")
+                                           .getDeclaredConstructor(String.class, ClassLoader.class)
+                                           .newInstance(dexPath, null /* parent */);
+      Class<?> c = Class.forName("Main", true /* initialize */, dexClassLoader);
+      Method methodInDex = c.getMethod("main", (new String[0]).getClass());
+      testAddMethodToProfile(file3, methodInDex);
+
+      // Test that the runtime saves the profiling info of a bootclasspath method.
+      Method bootMethod = File.class.getDeclaredMethod("exists");
       if (bootMethod.getDeclaringClass().getClassLoader() != Object.class.getClassLoader()) {
         System.out.println("Class loader does not match boot class");
       }
@@ -51,11 +86,16 @@
       Method bootNotInProfileMethod = System.class.getDeclaredMethod("console");
       testMethodNotInProfile(file, bootNotInProfileMethod);
 
+      testProfileNotExist(file2);
+
       System.out.println("IsForBootImage: " + isForBootImage(file.getPath()));
     } finally {
       if (file != null) {
         file.delete();
       }
+      if (file2 != null) {
+        file2.delete();
+      }
     }
   }
 
@@ -79,6 +119,15 @@
     }
   }
 
+  static void testProfileNotExist(File file) {
+    // Make sure the profile saving has been attempted.
+    ensureProfileProcessing();
+    // Verify that the profile does not exist.
+    if (file.exists()) {
+      throw new RuntimeException("Did not expect " + file + " to exist");
+    }
+  }
+
   // Ensure a method has a profiling info.
   public static native void ensureProfilingInfo(Method method);
   // Ensures the profile saver does its usual processing.
@@ -109,7 +158,8 @@
   }
 
   private static class VMRuntime {
-    public static final int CODE_PATH_TYPE_PRIMARY_APK = 1;
+    public static final int CODE_PATH_TYPE_PRIMARY_APK = 1 << 0;
+    public static final int CODE_PATH_TYPE_SPLIT_APK = 1 << 1;
     private static final Method registerAppInfoMethod;
 
     static {
diff --git a/test/596-app-images/run b/test/596-app-images/run
deleted file mode 100644
index dbdcd1c..0000000
--- a/test/596-app-images/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-# We need a profile to tell dex2oat to include classes in the final app image
-exec ${RUN} --profile $@
diff --git a/test/596-app-images/run.py b/test/596-app-images/run.py
new file mode 100644
index 0000000..402fbb5
--- /dev/null
+++ b/test/596-app-images/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2020 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.
+
+
+def run(ctx, args):
+  # We need a profile to tell dex2oat to include classes in the final app image
+  ctx.default_run(args, profile=True)
diff --git a/test/596-checker-dead-phi/smali/IrreducibleLoop.smali b/test/596-checker-dead-phi/smali/IrreducibleLoop.smali
index bab2ba9..9f822bf 100644
--- a/test/596-checker-dead-phi/smali/IrreducibleLoop.smali
+++ b/test/596-checker-dead-phi/smali/IrreducibleLoop.smali
@@ -20,18 +20,19 @@
 # not adjacent. This revealed a bug in our SSA builder, where a dead loop phi would
 # be replaced by its incoming input during SsaRedundantPhiElimination.
 
-# Check that the outer loop suspend check environment only has the parameter vreg.
-## CHECK-START: int IrreducibleLoop.liveness(int) builder (after)
-## CHECK-DAG:     <<Phi:i\d+>> Phi reg:4 loop:{{B\d+}} irreducible:false
-## CHECK-DAG:     SuspendCheck env:[[_,_,_,_,<<Phi>>]] loop:{{B\d+}} irreducible:false
+# Check that the outer loop suspend check environment only has the two parameter vregs.
+## CHECK-START: int IrreducibleLoop.liveness(int, int) builder (after)
+## CHECK-DAG:     <<Phi1:i\d+>> Phi reg:3 loop:{{B\d+}} irreducible:false
+## CHECK-DAG:     <<Phi2:i\d+>> Phi reg:4 loop:{{B\d+}} irreducible:false
+## CHECK-DAG:     SuspendCheck env:[[_,_,_,<<Phi1>>,<<Phi2>>]] loop:{{B\d+}} irreducible:false
 
 # Check that the linear order has non-adjacent loop blocks.
-## CHECK-START: int IrreducibleLoop.liveness(int) liveness (after)
+## CHECK-START: int IrreducibleLoop.liveness(int, int) liveness (after)
 ## CHECK-DAG:     Mul liveness:<<LPreEntry2:\d+>>
 ## CHECK-DAG:     Add liveness:<<LBackEdge1:\d+>>
 ## CHECK-EVAL:    <<LBackEdge1>> < <<LPreEntry2>>
 
-.method public static liveness(I)I
+.method public static liveness(II)I
     .registers 5
 
     const-string v1, "MyString"
@@ -50,8 +51,9 @@
     if-ne v2, v3, :pre_header2
 
     :pre_entry2
-    # Add a marker on the irreducible loop entry.
-    mul-int/2addr p0, p0
+    # Add a marker on the irreducible loop entry. Here we use p1 because p0 is a
+    # known constant and we eliminate the Mul otherwise.
+    mul-int/2addr p1, p1
     goto :back_edge2
 
     :back_edge2
@@ -61,8 +63,9 @@
     if-eqz p0, :back_edge2
 
     :back_edge1
-    # Add a marker on the outer loop back edge.
-    add-int/2addr p0, p0
+    # Add a marker on the outer loop back edge. Here we use p1 because p0 is a
+    # known constant and we eliminate the Add otherwise.
+    add-int/2addr p1, p1
     # Set a wide register, to have v1 undefined at the back edge.
     const-wide/16 v0, 0x1
     goto :header1
diff --git a/test/596-checker-dead-phi/src/Main.java b/test/596-checker-dead-phi/src/Main.java
index f3a55df..ec2fbe5 100644
--- a/test/596-checker-dead-phi/src/Main.java
+++ b/test/596-checker-dead-phi/src/Main.java
@@ -17,13 +17,13 @@
 import java.lang.reflect.Method;
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    Class<?> c = Class.forName("IrreducibleLoop");
-    // Note that we don't actually enter the loops in the 'liveness'
-    // method, so this is just a verification that that part of the code we
-    // generated for that method is correct.
-    Method m = c.getMethod("liveness", int.class);
-    Object[] arguments = { 42 };
-    System.out.println(m.invoke(null, arguments));
-  }
+    public static void main(String[] args) throws Exception {
+        Class<?> c = Class.forName("IrreducibleLoop");
+        // Note that we don't actually enter the loops in the 'liveness'
+        // method, so this is just a verification that that part of the code we
+        // generated for that method is correct.
+        Method m = c.getMethod("liveness", int.class, int.class);
+        Object[] arguments = {42, 12};
+        System.out.println(m.invoke(null, arguments));
+    }
 }
diff --git a/test/597-app-images-same-classloader/run b/test/597-app-images-same-classloader/run
deleted file mode 100644
index 496273f..0000000
--- a/test/597-app-images-same-classloader/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-# We need a profile to tell dex2oat to include classes in the final app image
-exec ${RUN} --profile --secondary-class-loader-context "PCL[$DEX_LOCATION/$TEST_NAME.jar]" $@
diff --git a/test/597-app-images-same-classloader/run.py b/test/597-app-images-same-classloader/run.py
new file mode 100644
index 0000000..a335777
--- /dev/null
+++ b/test/597-app-images-same-classloader/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright (C) 2020 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.
+
+
+def run(ctx, args):
+  # We need a profile to tell dex2oat to include classes in the final app image
+  pcl = f"PCL[{ctx.env.DEX_LOCATION}/{ctx.env.TEST_NAME}.jar]"
+  ctx.default_run(args, profile=True, secondary_class_loader_context=pcl)
diff --git a/test/597-deopt-busy-loop/run b/test/597-deopt-busy-loop/run
deleted file mode 100644
index bc04498..0000000
--- a/test/597-deopt-busy-loop/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# We want to run in debuggable mode and compiled.
-exec ${RUN} --jit -Xcompiler-option --debuggable "${@}"
diff --git a/test/597-deopt-busy-loop/run.py b/test/597-deopt-busy-loop/run.py
new file mode 100644
index 0000000..6107702
--- /dev/null
+++ b/test/597-deopt-busy-loop/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # We want to run in debuggable mode and compiled.
+  ctx.default_run(args, jit=True, Xcompiler_option=["--debuggable"])
diff --git a/test/597-deopt-busy-loop/src/FloatLoop.java b/test/597-deopt-busy-loop/src/FloatLoop.java
index 57667a6..7aadce4 100644
--- a/test/597-deopt-busy-loop/src/FloatLoop.java
+++ b/test/597-deopt-busy-loop/src/FloatLoop.java
@@ -51,6 +51,10 @@
         }
     }
 
+    // Create an empty int[] to force loading the int[] class before compiling $noinline$busyLoop.
+    // This makes sure the compiler can properly type int[] and not bail.
+    static int[] emptyArray = new int[0];
+
     public void $noinline$busyLoop() {
         Main.assertIsManaged();
 
diff --git a/test/597-deopt-invoke-stub/run b/test/597-deopt-invoke-stub/run
deleted file mode 100644
index 990c30e..0000000
--- a/test/597-deopt-invoke-stub/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# In order to test deoptimizing at quick-to-interpreter bridge,
-# we want to run in debuggable mode with jit compilation.
-# We also bump up the jit threshold to 10000 to make sure that the method
-# that should be interpreted is not compiled.
-exec ${RUN} "${@}" --jit --runtime-option -Xjitthreshold:10000 -Xcompiler-option --debuggable
diff --git a/test/597-deopt-invoke-stub/run.py b/test/597-deopt-invoke-stub/run.py
new file mode 100644
index 0000000..b52c177
--- /dev/null
+++ b/test/597-deopt-invoke-stub/run.py
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # In order to test deoptimizing at quick-to-interpreter bridge,
+  # we want to run in debuggable mode with jit compilation.
+  # We also bump up the jit threshold to 10000 to make sure that the method
+  # that should be interpreted is not compiled.
+  ctx.default_run(
+      args,
+      jit=True,
+      runtime_option=["-Xjitthreshold:10000"],
+      Xcompiler_option=["--debuggable"])
diff --git a/test/597-deopt-new-string/run b/test/597-deopt-new-string/run
deleted file mode 100644
index 9776ab3..0000000
--- a/test/597-deopt-new-string/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-# We want to run in debuggable mode which keeps the call into StringFactory.newEmptyString().
-exec ${RUN} -Xcompiler-option --debuggable "${@}"
diff --git a/test/597-deopt-new-string/run.py b/test/597-deopt-new-string/run.py
new file mode 100644
index 0000000..fa958dc
--- /dev/null
+++ b/test/597-deopt-new-string/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+
+
+def run(ctx, args):
+  # We want to run in debuggable mode which keeps the call into StringFactory.newEmptyString().
+  ctx.default_run(args, Xcompiler_option=["--debuggable"])
diff --git a/test/598-checker-irreducible-dominance/src/Main.java b/test/598-checker-irreducible-dominance/src/Main.java
index 4b1f490..cd38d3e 100644
--- a/test/598-checker-irreducible-dominance/src/Main.java
+++ b/test/598-checker-irreducible-dominance/src/Main.java
@@ -15,8 +15,8 @@
  */
 
 public class Main {
-  public static void main(String[] args) {
-    // Nothing to run. This regression test merely makes sure the smali test
-    // case successfully compiles.
-  }
+    public static void main(String[] args) {
+        // Nothing to run. This regression test merely makes sure the smali test
+        // case successfully compiles.
+    }
 }
diff --git a/test/599-checker-irreducible-loop/src/Main.java b/test/599-checker-irreducible-loop/src/Main.java
index 643e3a1..8a428a0 100644
--- a/test/599-checker-irreducible-loop/src/Main.java
+++ b/test/599-checker-irreducible-loop/src/Main.java
@@ -17,11 +17,11 @@
 import java.lang.reflect.Method;
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    Class<?> c = Class.forName("IrreducibleLoop");
-    Method m = c.getMethod("test", int.class);
-    Object[] arguments = { 42 };
-    // Invoke the code just for validation purposes.
-    System.out.println(m.invoke(null, arguments));
-  }
+    public static void main(String[] args) throws Exception {
+        Class<?> c = Class.forName("IrreducibleLoop");
+        Method m = c.getMethod("test", int.class);
+        Object[] arguments = { 42 };
+        // Invoke the code just for validation purposes.
+        System.out.println(m.invoke(null, arguments));
+    }
 }
diff --git a/test/612-jit-dex-cache/build.py b/test/612-jit-dex-cache/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/612-jit-dex-cache/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/612-jit-dex-cache/test-metadata.json b/test/612-jit-dex-cache/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/612-jit-dex-cache/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/613-inlining-dex-cache/build.py b/test/613-inlining-dex-cache/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/613-inlining-dex-cache/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/613-inlining-dex-cache/run b/test/613-inlining-dex-cache/run
deleted file mode 100644
index 9c1e7aa..0000000
--- a/test/613-inlining-dex-cache/run
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-flags="$@"
-# We need the dex files pre-verified to avoid running the verifier
-# at runtime which will update the dex cache.
-exec ${RUN} ${flags/verify-at-runtime/interpret-only}
diff --git a/test/613-inlining-dex-cache/run.py b/test/613-inlining-dex-cache/run.py
new file mode 100644
index 0000000..f0b061b
--- /dev/null
+++ b/test/613-inlining-dex-cache/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
diff --git a/test/613-inlining-dex-cache/test-metadata.json b/test/613-inlining-dex-cache/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/613-inlining-dex-cache/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/616-cha-abstract/run b/test/616-cha-abstract/run
deleted file mode 100644
index d8b4f0d..0000000
--- a/test/616-cha-abstract/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Run without an app image to prevent the classes to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
diff --git a/test/616-cha-abstract/run.py b/test/616-cha-abstract/run.py
new file mode 100644
index 0000000..1e797a8
--- /dev/null
+++ b/test/616-cha-abstract/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Run without an app image to prevent the classes to be loaded at startup.
+  ctx.default_run(args, app_image=False)
diff --git a/test/616-cha-abstract/src/Main.java b/test/616-cha-abstract/src/Main.java
index c8093e6..1a06337 100644
--- a/test/616-cha-abstract/src/Main.java
+++ b/test/616-cha-abstract/src/Main.java
@@ -15,145 +15,145 @@
  */
 
 abstract class Base {
-  abstract void foo(int i);
+    abstract void foo(int i);
 
-  void printError(String msg) {
-    System.out.println(msg);
-  }
+    void printError(String msg) {
+        System.out.println(msg);
+    }
 }
 
 class Main1 extends Base {
-  void foo(int i) {
-    if (i != 1) {
-      printError("error1");
+    void foo(int i) {
+        if (i != 1) {
+            printError("error1");
+        }
     }
-  }
 }
 
 class Main2 extends Main1 {
-  void foo(int i) {
-    if (i != 2) {
-      printError("error2");
+    void foo(int i) {
+        if (i != 2) {
+            printError("error2");
+        }
     }
-  }
 }
 
 public class Main {
-  static Base sMain1;
-  static Base sMain2;
+    static Base sMain1;
+    static Base sMain2;
 
-  static boolean sIsOptimizing = true;
-  static boolean sHasJIT = true;
-  static volatile boolean sOtherThreadStarted;
+    static boolean sIsOptimizing = true;
+    static boolean sHasJIT = true;
+    static volatile boolean sOtherThreadStarted;
 
-  private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
-    if (hasSingleImplementation(clazz, method_name) != b) {
-      System.out.println(clazz + "." + method_name +
-          " doesn't have single implementation value of " + b);
-    }
-  }
-
-  // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
-  // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
-  // After Helper.createMain2() which links in Main2, live testOverride() on stack
-  // should be deoptimized.
-  static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) {
-    if (setHasJIT) {
-      if (isInterpreted()) {
-        sHasJIT = false;
-      }
-      return;
-    }
-
-    if (createMain2 && (sIsOptimizing || sHasJIT)) {
-      assertIsManaged();
-    }
-
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-
-    if (createMain2) {
-      // Wait for the other thread to start.
-      while (!sOtherThreadStarted);
-      // Create an Main2 instance and assign it to sMain2.
-      // sMain1 is kept the same.
-      sMain2 = Helper.createMain2();
-      // Wake up the other thread.
-      synchronized(Main.class) {
-        Main.class.notify();
-      }
-    } else if (wait) {
-      // This is the other thread.
-      synchronized(Main.class) {
-        sOtherThreadStarted = true;
-        // Wait for Main2 to be linked and deoptimization is triggered.
-        try {
-          Main.class.wait();
-        } catch (Exception e) {
+    private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
+        if (hasSingleImplementation(clazz, method_name) != b) {
+            System.out.println(clazz + "." + method_name +
+                    " doesn't have single implementation value of " + b);
         }
-      }
     }
 
-    // There should be a deoptimization here right after Main2 is linked by
-    // calling Helper.createMain2(), even though sMain1 didn't change.
-    // The behavior here would be different if inline-cache is used, which
-    // doesn't deoptimize since sMain1 still hits the type cache.
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-    if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
-      // This method should be deoptimized right after Main2 is created.
-      assertIsInterpreted();
+    // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
+    // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
+    // After Helper.createMain2() which links in Main2, live testOverride() on stack
+    // should be deoptimized.
+    static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) {
+        if (setHasJIT) {
+            if (isInterpreted()) {
+                sHasJIT = false;
+            }
+            return;
+        }
+
+        if (createMain2 && (sIsOptimizing || sHasJIT)) {
+            assertIsManaged();
+        }
+
+        sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
+
+        if (createMain2) {
+            // Wait for the other thread to start.
+            while (!sOtherThreadStarted);
+            // Create an Main2 instance and assign it to sMain2.
+            // sMain1 is kept the same.
+            sMain2 = Helper.createMain2();
+            // Wake up the other thread.
+            synchronized(Main.class) {
+                Main.class.notify();
+            }
+        } else if (wait) {
+            // This is the other thread.
+            synchronized(Main.class) {
+                sOtherThreadStarted = true;
+                // Wait for Main2 to be linked and deoptimization is triggered.
+                try {
+                    Main.class.wait();
+                } catch (Exception e) {
+                }
+            }
+        }
+
+        // There should be a deoptimization here right after Main2 is linked by
+        // calling Helper.createMain2(), even though sMain1 didn't change.
+        // The behavior here would be different if inline-cache is used, which
+        // doesn't deoptimize since sMain1 still hits the type cache.
+        sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
+        if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
+            // This method should be deoptimized right after Main2 is created.
+            assertIsInterpreted();
+        }
+
+        if (sMain2 != null) {
+            sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
+        }
     }
 
-    if (sMain2 != null) {
-      sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
-    }
-  }
+    // Test scenarios under which CHA-based devirtualization happens,
+    // and class loading that overrides a method can invalidate compiled code.
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
 
-  // Test scenarios under which CHA-based devirtualization happens,
-  // and class loading that overrides a method can invalidate compiled code.
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
+        if (isInterpreted()) {
+            sIsOptimizing = false;
+        }
 
-    if (isInterpreted()) {
-      sIsOptimizing = false;
+        // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
+        sMain1 = new Main1();
+
+        ensureJitCompiled(Main.class, "testOverride");
+        testOverride(false, false, true);
+
+        if (sHasJIT && !sIsOptimizing) {
+            assertSingleImplementation(Base.class, "foo", true);
+            assertSingleImplementation(Main1.class, "foo", true);
+        } else {
+            // Main2 is verified ahead-of-time so it's linked in already.
+        }
+
+        // Create another thread that also calls sMain1.foo().
+        // Try to test suspend and deopt another thread.
+        new Thread() {
+            public void run() {
+                testOverride(false, true, false);
+            }
+        }.start();
+
+        // This will create Main2 instance in the middle of testOverride().
+        testOverride(true, false, false);
+        assertSingleImplementation(Base.class, "foo", false);
+        assertSingleImplementation(Main1.class, "foo", false);
     }
 
-    // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
-    sMain1 = new Main1();
-
-    ensureJitCompiled(Main.class, "testOverride");
-    testOverride(false, false, true);
-
-    if (sHasJIT && !sIsOptimizing) {
-      assertSingleImplementation(Base.class, "foo", true);
-      assertSingleImplementation(Main1.class, "foo", true);
-    } else {
-      // Main2 is verified ahead-of-time so it's linked in already.
-    }
-
-    // Create another thread that also calls sMain1.foo().
-    // Try to test suspend and deopt another thread.
-    new Thread() {
-      public void run() {
-        testOverride(false, true, false);
-      }
-    }.start();
-
-    // This will create Main2 instance in the middle of testOverride().
-    testOverride(true, false, false);
-    assertSingleImplementation(Base.class, "foo", false);
-    assertSingleImplementation(Main1.class, "foo", false);
-  }
-
-  private static native void ensureJitCompiled(Class<?> itf, String method_name);
-  private static native void assertIsInterpreted();
-  private static native void assertIsManaged();
-  private static native boolean isInterpreted();
-  private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
+    private static native void ensureJitCompiled(Class<?> itf, String method_name);
+    private static native void assertIsInterpreted();
+    private static native void assertIsManaged();
+    private static native boolean isInterpreted();
+    private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
 }
 
 // Put createMain2() in another class to avoid class loading due to verifier.
 class Helper {
-  static Main1 createMain2() {
-    return new Main2();
-  }
+    static Main1 createMain2() {
+        return new Main2();
+    }
 }
diff --git a/test/616-cha-interface-default/build b/test/616-cha-interface-default/build
deleted file mode 100644
index d9654f8..0000000
--- a/test/616-cha-interface-default/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-build "$@" --experimental default-methods
diff --git a/test/616-cha-interface-default/build.py b/test/616-cha-interface-default/build.py
new file mode 100644
index 0000000..3e0ecd5
--- /dev/null
+++ b/test/616-cha-interface-default/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="default-methods")
diff --git a/test/616-cha-interface-default/run b/test/616-cha-interface-default/run
deleted file mode 100644
index d8b4f0d..0000000
--- a/test/616-cha-interface-default/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Run without an app image to prevent the classes to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
diff --git a/test/616-cha-interface-default/run.py b/test/616-cha-interface-default/run.py
new file mode 100644
index 0000000..1e797a8
--- /dev/null
+++ b/test/616-cha-interface-default/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Run without an app image to prevent the classes to be loaded at startup.
+  ctx.default_run(args, app_image=False)
diff --git a/test/616-cha-interface-default/src-multidex/Base.java b/test/616-cha-interface-default/src-multidex/Base.java
index 2cbcb50..e3222a1 100644
--- a/test/616-cha-interface-default/src-multidex/Base.java
+++ b/test/616-cha-interface-default/src-multidex/Base.java
@@ -15,27 +15,27 @@
  */
 
 interface Base {
-  default public int foo(int i) {
-    if (i != 1) {
-      return -2;
+    default public int foo(int i) {
+        if (i != 1) {
+            return -2;
+        }
+        return i + 10;
     }
-    return i + 10;
-  }
 
-  // Test default method that's not inlined.
-  default public int $noinline$bar() {
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    return -1;
-  }
+    // Test default method that's not inlined.
+    default public int $noinline$bar() {
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        return -1;
+    }
 
-  default void printError(String msg) {
-    System.out.println(msg);
-  }
+    default void printError(String msg) {
+        System.out.println(msg);
+    }
 }
diff --git a/test/616-cha-interface-default/src/Main.java b/test/616-cha-interface-default/src/Main.java
index ce02cf0..40b815a 100644
--- a/test/616-cha-interface-default/src/Main.java
+++ b/test/616-cha-interface-default/src/Main.java
@@ -18,159 +18,159 @@
 }
 
 class Main2 extends Main1 {
-  public void foobar() {}
+    public void foobar() {}
 }
 
 class Main3 implements Base {
-  public int foo(int i) {
-    if (i != 3) {
-      printError("error3");
+    public int foo(int i) {
+        if (i != 3) {
+            printError("error3");
+        }
+        return -(i + 10);
     }
-    return -(i + 10);
-  }
 }
 
 public class Main {
-  static Base sMain1;
-  static Base sMain2;
-  static Base sMain3;
+    static Base sMain1;
+    static Base sMain2;
+    static Base sMain3;
 
-  static boolean sIsOptimizing = true;
-  static boolean sHasJIT = true;
-  static volatile boolean sOtherThreadStarted;
+    static boolean sIsOptimizing = true;
+    static boolean sHasJIT = true;
+    static volatile boolean sOtherThreadStarted;
 
-  private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
-    if (hasSingleImplementation(clazz, method_name) != b) {
-      System.out.println(clazz + "." + method_name +
-          " doesn't have single implementation value of " + b);
-    }
-  }
-
-  static int getValue(Class<?> cls) {
-    if (cls == Main1.class || cls == Main2.class) {
-      return 1;
-    }
-    return 3;
-  }
-
-  // sMain1.foo()/sMain2.foo() will be always be Base.foo() before Main3 is loaded/linked.
-  // So sMain1.foo() can be devirtualized to Base.foo() and be inlined.
-  // After Helper.createMain3() which links in Main3, live testImplement() on stack
-  // should be deoptimized.
-  static void testImplement(boolean createMain3, boolean wait, boolean setHasJIT) {
-    if (setHasJIT) {
-      if (isInterpreted()) {
-        sHasJIT = false;
-      }
-      return;
-    }
-
-    if (createMain3 && (sIsOptimizing || sHasJIT)) {
-      assertIsManaged();
-    }
-
-    if (sMain1.foo(getValue(sMain1.getClass())) != 11) {
-      System.out.println("11 expected.");
-    }
-    if (sMain1.$noinline$bar() != -1) {
-      System.out.println("-1 expected.");
-    }
-    if (sMain2.foo(getValue(sMain2.getClass())) != 11) {
-      System.out.println("11 expected.");
-    }
-
-    if (createMain3) {
-      // Wait for the other thread to start.
-      while (!sOtherThreadStarted);
-      // Create an Main2 instance and assign it to sMain2.
-      // sMain1 is kept the same.
-      sMain3 = Helper.createMain3();
-      // Wake up the other thread.
-      synchronized(Main.class) {
-        Main.class.notify();
-      }
-    } else if (wait) {
-      // This is the other thread.
-      synchronized(Main.class) {
-        sOtherThreadStarted = true;
-        // Wait for Main2 to be linked and deoptimization is triggered.
-        try {
-          Main.class.wait();
-        } catch (Exception e) {
+    private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
+        if (hasSingleImplementation(clazz, method_name) != b) {
+            System.out.println(clazz + "." + method_name +
+                    " doesn't have single implementation value of " + b);
         }
-      }
     }
 
-    // There should be a deoptimization here right after Main3 is linked by
-    // calling Helper.createMain3(), even though sMain1 didn't change.
-    // The behavior here would be different if inline-cache is used, which
-    // doesn't deoptimize since sMain1 still hits the type cache.
-    if (sMain1.foo(getValue(sMain1.getClass())) != 11) {
-      System.out.println("11 expected.");
-    }
-    if ((createMain3 || wait) && sHasJIT && !sIsOptimizing) {
-      // This method should be deoptimized right after Main3 is created.
-      assertIsInterpreted();
+    static int getValue(Class<?> cls) {
+        if (cls == Main1.class || cls == Main2.class) {
+            return 1;
+        }
+        return 3;
     }
 
-    if (sMain3 != null) {
-      if (sMain3.foo(getValue(sMain3.getClass())) != -13) {
-        System.out.println("-13 expected.");
-      }
-    }
-  }
+    // sMain1.foo()/sMain2.foo() will be always be Base.foo() before Main3 is loaded/linked.
+    // So sMain1.foo() can be devirtualized to Base.foo() and be inlined.
+    // After Helper.createMain3() which links in Main3, live testImplement() on stack
+    // should be deoptimized.
+    static void testImplement(boolean createMain3, boolean wait, boolean setHasJIT) {
+        if (setHasJIT) {
+            if (isInterpreted()) {
+                sHasJIT = false;
+            }
+            return;
+        }
 
-  // Test scenarios under which CHA-based devirtualization happens,
-  // and class loading that implements a method can invalidate compiled code.
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
+        if (createMain3 && (sIsOptimizing || sHasJIT)) {
+            assertIsManaged();
+        }
 
-    if (isInterpreted()) {
-      sIsOptimizing = false;
+        if (sMain1.foo(getValue(sMain1.getClass())) != 11) {
+            System.out.println("11 expected.");
+        }
+        if (sMain1.$noinline$bar() != -1) {
+            System.out.println("-1 expected.");
+        }
+        if (sMain2.foo(getValue(sMain2.getClass())) != 11) {
+            System.out.println("11 expected.");
+        }
+
+        if (createMain3) {
+            // Wait for the other thread to start.
+            while (!sOtherThreadStarted);
+            // Create an Main2 instance and assign it to sMain2.
+            // sMain1 is kept the same.
+            sMain3 = Helper.createMain3();
+            // Wake up the other thread.
+            synchronized(Main.class) {
+                Main.class.notify();
+            }
+        } else if (wait) {
+            // This is the other thread.
+            synchronized(Main.class) {
+                sOtherThreadStarted = true;
+                // Wait for Main2 to be linked and deoptimization is triggered.
+                try {
+                    Main.class.wait();
+                } catch (Exception e) {
+                }
+            }
+        }
+
+        // There should be a deoptimization here right after Main3 is linked by
+        // calling Helper.createMain3(), even though sMain1 didn't change.
+        // The behavior here would be different if inline-cache is used, which
+        // doesn't deoptimize since sMain1 still hits the type cache.
+        if (sMain1.foo(getValue(sMain1.getClass())) != 11) {
+            System.out.println("11 expected.");
+        }
+        if ((createMain3 || wait) && sHasJIT && !sIsOptimizing) {
+            // This method should be deoptimized right after Main3 is created.
+            assertIsInterpreted();
+        }
+
+        if (sMain3 != null) {
+            if (sMain3.foo(getValue(sMain3.getClass())) != -13) {
+                System.out.println("-13 expected.");
+            }
+        }
     }
 
-    // sMain1 is an instance of Main1.
-    // sMain2 is an instance of Main2.
-    // Neither Main1 nor Main2 override default method Base.foo().
-    // Main3 hasn't bee loaded yet.
-    sMain1 = new Main1();
-    sMain2 = new Main2();
+    // Test scenarios under which CHA-based devirtualization happens,
+    // and class loading that implements a method can invalidate compiled code.
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
 
-    ensureJitCompiled(Main.class, "testImplement");
-    testImplement(false, false, true);
+        if (isInterpreted()) {
+            sIsOptimizing = false;
+        }
 
-    if (sHasJIT && !sIsOptimizing) {
-      assertSingleImplementation(Base.class, "foo", true);
-      assertSingleImplementation(Main1.class, "foo", true);
-    } else {
-      // Main3 is verified ahead-of-time so it's linked in already.
+        // sMain1 is an instance of Main1.
+        // sMain2 is an instance of Main2.
+        // Neither Main1 nor Main2 override default method Base.foo().
+        // Main3 hasn't bee loaded yet.
+        sMain1 = new Main1();
+        sMain2 = new Main2();
+
+        ensureJitCompiled(Main.class, "testImplement");
+        testImplement(false, false, true);
+
+        if (sHasJIT && !sIsOptimizing) {
+            assertSingleImplementation(Base.class, "foo", true);
+            assertSingleImplementation(Main1.class, "foo", true);
+        } else {
+            // Main3 is verified ahead-of-time so it's linked in already.
+        }
+
+        // Create another thread that also calls sMain1.foo().
+        // Try to test suspend and deopt another thread.
+        new Thread() {
+            public void run() {
+                testImplement(false, true, false);
+            }
+        }.start();
+
+        // This will create Main3 instance in the middle of testImplement().
+        testImplement(true, false, false);
+        assertSingleImplementation(Base.class, "foo", false);
+        assertSingleImplementation(Main1.class, "foo", true);
+        assertSingleImplementation(sMain3.getClass(), "foo", true);
     }
 
-    // Create another thread that also calls sMain1.foo().
-    // Try to test suspend and deopt another thread.
-    new Thread() {
-      public void run() {
-        testImplement(false, true, false);
-      }
-    }.start();
-
-    // This will create Main3 instance in the middle of testImplement().
-    testImplement(true, false, false);
-    assertSingleImplementation(Base.class, "foo", false);
-    assertSingleImplementation(Main1.class, "foo", true);
-    assertSingleImplementation(sMain3.getClass(), "foo", true);
-  }
-
-  private static native void ensureJitCompiled(Class<?> itf, String method_name);
-  private static native void assertIsInterpreted();
-  private static native void assertIsManaged();
-  private static native boolean isInterpreted();
-  private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
+    private static native void ensureJitCompiled(Class<?> itf, String method_name);
+    private static native void assertIsInterpreted();
+    private static native void assertIsManaged();
+    private static native boolean isInterpreted();
+    private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
 }
 
 // Put createMain3() in another class to avoid class loading due to verifier.
 class Helper {
-  static Base createMain3() {
-    return new Main3();
-  }
+    static Base createMain3() {
+        return new Main3();
+    }
 }
diff --git a/test/616-cha-interface/run b/test/616-cha-interface/run
deleted file mode 100644
index d8b4f0d..0000000
--- a/test/616-cha-interface/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Run without an app image to prevent the classes to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
diff --git a/test/616-cha-interface/run.py b/test/616-cha-interface/run.py
new file mode 100644
index 0000000..1e797a8
--- /dev/null
+++ b/test/616-cha-interface/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Run without an app image to prevent the classes to be loaded at startup.
+  ctx.default_run(args, app_image=False)
diff --git a/test/616-cha-interface/src/Main.java b/test/616-cha-interface/src/Main.java
index c55ed6e..0c21ea0 100644
--- a/test/616-cha-interface/src/Main.java
+++ b/test/616-cha-interface/src/Main.java
@@ -15,159 +15,159 @@
  */
 
 interface Base {
-  void foo(int i);
-  void $noinline$bar();
+    void foo(int i);
+    void $noinline$bar();
 }
 
 class Main1 implements Base {
-  public void foo(int i) {
-    if (i != 1) {
-      printError("error1");
+    public void foo(int i) {
+        if (i != 1) {
+            printError("error1");
+        }
     }
-  }
 
-  // Test rewriting invoke-interface into invoke-virtual when inlining fails.
-  public void $noinline$bar() {
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-    System.out.print("");
-  }
+    // Test rewriting invoke-interface into invoke-virtual when inlining fails.
+    public void $noinline$bar() {
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+        System.out.print("");
+    }
 
-  void printError(String msg) {
-    System.out.println(msg);
-  }
+    void printError(String msg) {
+        System.out.println(msg);
+    }
 }
 
 class Main2 extends Main1 {
-  public void foo(int i) {
-    if (i != 2) {
-      printError("error2");
+    public void foo(int i) {
+        if (i != 2) {
+            printError("error2");
+        }
     }
-  }
 }
 
 public class Main {
-  static Base sMain1;
-  static Base sMain2;
+    static Base sMain1;
+    static Base sMain2;
 
-  static boolean sIsOptimizing = true;
-  static boolean sHasJIT = true;
-  static volatile boolean sOtherThreadStarted;
+    static boolean sIsOptimizing = true;
+    static boolean sHasJIT = true;
+    static volatile boolean sOtherThreadStarted;
 
-  private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
-    if (hasSingleImplementation(clazz, method_name) != b) {
-      System.out.println(clazz + "." + method_name +
-          " doesn't have single implementation value of " + b);
-    }
-  }
-
-  // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
-  // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
-  // After Helper.createMain2() which links in Main2, live testImplement() on stack
-  // should be deoptimized.
-  static void testImplement(boolean createMain2, boolean wait, boolean setHasJIT) {
-    if (setHasJIT) {
-      if (isInterpreted()) {
-        sHasJIT = false;
-      }
-      return;
-    }
-
-    if (createMain2 && (sIsOptimizing || sHasJIT)) {
-      assertIsManaged();
-    }
-
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-    sMain1.$noinline$bar();
-
-    if (createMain2) {
-      // Wait for the other thread to start.
-      while (!sOtherThreadStarted);
-      // Create an Main2 instance and assign it to sMain2.
-      // sMain1 is kept the same.
-      sMain2 = Helper.createMain2();
-      // Wake up the other thread.
-      synchronized(Main.class) {
-        Main.class.notify();
-      }
-    } else if (wait) {
-      // This is the other thread.
-      synchronized(Main.class) {
-        sOtherThreadStarted = true;
-        // Wait for Main2 to be linked and deoptimization is triggered.
-        try {
-          Main.class.wait();
-        } catch (Exception e) {
+    private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
+        if (hasSingleImplementation(clazz, method_name) != b) {
+            System.out.println(clazz + "." + method_name +
+                    " doesn't have single implementation value of " + b);
         }
-      }
     }
 
-    // There should be a deoptimization here right after Main2 is linked by
-    // calling Helper.createMain2(), even though sMain1 didn't change.
-    // The behavior here would be different if inline-cache is used, which
-    // doesn't deoptimize since sMain1 still hits the type cache.
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-    if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
-      // This method should be deoptimized right after Main2 is created.
-      assertIsInterpreted();
+    // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
+    // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
+    // After Helper.createMain2() which links in Main2, live testImplement() on stack
+    // should be deoptimized.
+    static void testImplement(boolean createMain2, boolean wait, boolean setHasJIT) {
+        if (setHasJIT) {
+            if (isInterpreted()) {
+                sHasJIT = false;
+            }
+            return;
+        }
+
+        if (createMain2 && (sIsOptimizing || sHasJIT)) {
+            assertIsManaged();
+        }
+
+        sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
+        sMain1.$noinline$bar();
+
+        if (createMain2) {
+            // Wait for the other thread to start.
+            while (!sOtherThreadStarted);
+            // Create an Main2 instance and assign it to sMain2.
+            // sMain1 is kept the same.
+            sMain2 = Helper.createMain2();
+            // Wake up the other thread.
+            synchronized(Main.class) {
+                Main.class.notify();
+            }
+        } else if (wait) {
+            // This is the other thread.
+            synchronized(Main.class) {
+                sOtherThreadStarted = true;
+                // Wait for Main2 to be linked and deoptimization is triggered.
+                try {
+                    Main.class.wait();
+                } catch (Exception e) {
+                }
+            }
+        }
+
+        // There should be a deoptimization here right after Main2 is linked by
+        // calling Helper.createMain2(), even though sMain1 didn't change.
+        // The behavior here would be different if inline-cache is used, which
+        // doesn't deoptimize since sMain1 still hits the type cache.
+        sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
+        if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
+            // This method should be deoptimized right after Main2 is created.
+            assertIsInterpreted();
+        }
+
+        if (sMain2 != null) {
+            sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
+        }
     }
 
-    if (sMain2 != null) {
-      sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
-    }
-  }
+    // Test scenarios under which CHA-based devirtualization happens,
+    // and class loading that overrides a method can invalidate compiled code.
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
 
-  // Test scenarios under which CHA-based devirtualization happens,
-  // and class loading that overrides a method can invalidate compiled code.
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
+        if (isInterpreted()) {
+            sIsOptimizing = false;
+        }
 
-    if (isInterpreted()) {
-      sIsOptimizing = false;
+        // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
+        sMain1 = new Main1();
+
+        ensureJitCompiled(Main.class, "testImplement");
+        testImplement(false, false, true);
+
+        if (sHasJIT && !sIsOptimizing) {
+            assertSingleImplementation(Base.class, "foo", true);
+            assertSingleImplementation(Main1.class, "foo", true);
+        } else {
+            // Main2 is verified ahead-of-time so it's linked in already.
+        }
+
+        // Create another thread that also calls sMain1.foo().
+        // Try to test suspend and deopt another thread.
+        new Thread() {
+            public void run() {
+                testImplement(false, true, false);
+            }
+        }.start();
+
+        // This will create Main2 instance in the middle of testImplement().
+        testImplement(true, false, false);
+        assertSingleImplementation(Base.class, "foo", false);
+        assertSingleImplementation(Main1.class, "foo", false);
     }
 
-    // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
-    sMain1 = new Main1();
-
-    ensureJitCompiled(Main.class, "testImplement");
-    testImplement(false, false, true);
-
-    if (sHasJIT && !sIsOptimizing) {
-      assertSingleImplementation(Base.class, "foo", true);
-      assertSingleImplementation(Main1.class, "foo", true);
-    } else {
-      // Main2 is verified ahead-of-time so it's linked in already.
-    }
-
-    // Create another thread that also calls sMain1.foo().
-    // Try to test suspend and deopt another thread.
-    new Thread() {
-      public void run() {
-        testImplement(false, true, false);
-      }
-    }.start();
-
-    // This will create Main2 instance in the middle of testImplement().
-    testImplement(true, false, false);
-    assertSingleImplementation(Base.class, "foo", false);
-    assertSingleImplementation(Main1.class, "foo", false);
-  }
-
-  private static native void ensureJitCompiled(Class<?> itf, String method_name);
-  private static native void assertIsInterpreted();
-  private static native void assertIsManaged();
-  private static native boolean isInterpreted();
-  private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
+    private static native void ensureJitCompiled(Class<?> itf, String method_name);
+    private static native void assertIsInterpreted();
+    private static native void assertIsManaged();
+    private static native boolean isInterpreted();
+    private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
 }
 
 // Put createMain2() in another class to avoid class loading due to verifier.
 class Helper {
-  static Main1 createMain2() {
-    return new Main2();
-  }
+    static Main1 createMain2() {
+        return new Main2();
+    }
 }
diff --git a/test/616-cha-miranda/run b/test/616-cha-miranda/run
deleted file mode 100644
index d8b4f0d..0000000
--- a/test/616-cha-miranda/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Run without an app image to prevent the classes to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
diff --git a/test/616-cha-miranda/run.py b/test/616-cha-miranda/run.py
new file mode 100644
index 0000000..1e797a8
--- /dev/null
+++ b/test/616-cha-miranda/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Run without an app image to prevent the classes to be loaded at startup.
+  ctx.default_run(args, app_image=False)
diff --git a/test/616-cha-miranda/src/Main.java b/test/616-cha-miranda/src/Main.java
index 323e92e..eede844 100644
--- a/test/616-cha-miranda/src/Main.java
+++ b/test/616-cha-miranda/src/Main.java
@@ -15,149 +15,142 @@
  */
 
 interface Iface {
-  public void foo(int i);
+    public void foo(int i);
 }
 
 abstract class Base implements Iface {
-  // Iface.foo(int) will be added as a miranda method.
+    // Iface.foo(int) will be added as a miranda method.
 
-  void printError(String msg) {
-    System.out.println(msg);
-  }
+    void printError(String msg) {
+        System.out.println(msg);
+    }
 }
 
 class Main1 extends Base {
-  public void foo(int i) {
-    if (i != 1) {
-      printError("error1");
+    public void foo(int i) {
+        if (i != 1) {
+            printError("error1");
+        }
     }
-  }
 }
 
 class Main2 extends Main1 {
-  public void foo(int i) {
-    if (i != 2) {
-      printError("error2");
+    public void foo(int i) {
+        if (i != 2) {
+            printError("error2");
+        }
     }
-  }
 }
 
 public class Main {
-  static Base sMain1;
-  static Base sMain2;
+    static Base sMain1;
+    static Base sMain2;
 
-  static boolean sIsOptimizing = true;
-  static boolean sHasJIT = true;
-  static volatile boolean sOtherThreadStarted;
+    static boolean sIsOptimizing = true;
+    static boolean sHasJIT = true;
+    static volatile boolean sOtherThreadStarted;
 
-  private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
-    if (hasSingleImplementation(clazz, method_name) != b) {
-      System.out.println(clazz + "." + method_name +
-          " doesn't have single implementation value of " + b);
-    }
-  }
-
-  // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
-  // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
-  // After Helper.createMain2() which links in Main2, live testOverride() on stack
-  // should be deoptimized.
-  static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) {
-    if (setHasJIT) {
-      if (isInterpreted()) {
-        sHasJIT = false;
-      }
-      return;
-    }
-
-    if (createMain2 && (sIsOptimizing || sHasJIT)) {
-      assertIsManaged();
-    }
-
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-
-    if (createMain2) {
-      // Wait for the other thread to start.
-      while (!sOtherThreadStarted);
-      // Create an Main2 instance and assign it to sMain2.
-      // sMain1 is kept the same.
-      sMain2 = Helper.createMain2();
-      // Wake up the other thread.
-      synchronized(Main.class) {
-        Main.class.notify();
-      }
-    } else if (wait) {
-      // This is the other thread.
-      synchronized(Main.class) {
-        sOtherThreadStarted = true;
-        // Wait for Main2 to be linked and deoptimization is triggered.
-        try {
-          Main.class.wait();
-        } catch (Exception e) {
+    private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
+        if (hasSingleImplementation(clazz, method_name) != b) {
+            System.out.println(clazz + "." + method_name +
+                    " doesn't have single implementation value of " + b);
         }
-      }
     }
 
-    // There should be a deoptimization here right after Main2 is linked by
-    // calling Helper.createMain2(), even though sMain1 didn't change.
-    // The behavior here would be different if inline-cache is used, which
-    // doesn't deoptimize since sMain1 still hits the type cache.
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-    if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
-      // This method should be deoptimized right after Main2 is created.
-      assertIsInterpreted();
+    // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
+    // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
+    // After Helper.createMain2() which links in Main2, live testOverride() on stack
+    // should be deoptimized.
+    static void testOverride(boolean createMain2, boolean wait) {
+        if (createMain2 && (sIsOptimizing || sHasJIT)) {
+            assertIsManaged();
+        }
+
+        sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
+
+        if (createMain2) {
+            // Wait for the other thread to start.
+            while (!sOtherThreadStarted);
+            // Create an Main2 instance and assign it to sMain2.
+            // sMain1 is kept the same.
+            sMain2 = Helper.createMain2();
+            // Wake up the other thread.
+            synchronized(Main.class) {
+                Main.class.notify();
+            }
+        } else if (wait) {
+            // This is the other thread.
+            synchronized(Main.class) {
+                sOtherThreadStarted = true;
+                // Wait for Main2 to be linked and deoptimization is triggered.
+                try {
+                    Main.class.wait();
+                } catch (Exception e) {
+                }
+            }
+        }
+
+        // There should be a deoptimization here right after Main2 is linked by
+        // calling Helper.createMain2(), even though sMain1 didn't change.
+        // The behavior here would be different if inline-cache is used, which
+        // doesn't deoptimize since sMain1 still hits the type cache.
+        sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
+        if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
+            // This method should be deoptimized right after Main2 is created.
+            assertIsInterpreted();
+        }
+
+        if (sMain2 != null) {
+            sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
+        }
     }
 
-    if (sMain2 != null) {
-      sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
-    }
-  }
+    // Test scenarios under which CHA-based devirtualization happens,
+    // and class loading that overrides a method can invalidate compiled code.
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
 
-  // Test scenarios under which CHA-based devirtualization happens,
-  // and class loading that overrides a method can invalidate compiled code.
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
+        sIsOptimizing = isAotCompiled(Main.class, "testOverride");
+        sHasJIT = hasJit();
 
-    if (isInterpreted()) {
-      sIsOptimizing = false;
+        // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
+        sMain1 = new Main1();
+
+        ensureJitCompiled(Main.class, "testOverride");
+
+        if (sHasJIT && !sIsOptimizing) {
+            assertSingleImplementation(Base.class, "foo", true);
+            assertSingleImplementation(Main1.class, "foo", true);
+        } else {
+            // Main2 is verified ahead-of-time so it's linked in already.
+        }
+
+        // Create another thread that also calls sMain1.foo().
+        // Try to test suspend and deopt another thread.
+        new Thread() {
+            public void run() {
+                testOverride(false, true);
+            }
+        }.start();
+
+        // This will create Main2 instance in the middle of testOverride().
+        testOverride(true, false);
+        assertSingleImplementation(Base.class, "foo", false);
+        assertSingleImplementation(Main1.class, "foo", false);
     }
 
-    // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
-    sMain1 = new Main1();
-
-    ensureJitCompiled(Main.class, "testOverride");
-    testOverride(false, false, true);
-
-    if (sHasJIT && !sIsOptimizing) {
-      assertSingleImplementation(Base.class, "foo", true);
-      assertSingleImplementation(Main1.class, "foo", true);
-    } else {
-      // Main2 is verified ahead-of-time so it's linked in already.
-    }
-
-    // Create another thread that also calls sMain1.foo().
-    // Try to test suspend and deopt another thread.
-    new Thread() {
-      public void run() {
-        testOverride(false, true, false);
-      }
-    }.start();
-
-    // This will create Main2 instance in the middle of testOverride().
-    testOverride(true, false, false);
-    assertSingleImplementation(Base.class, "foo", false);
-    assertSingleImplementation(Main1.class, "foo", false);
-  }
-
-  private static native void ensureJitCompiled(Class<?> itf, String method_name);
-  private static native void assertIsInterpreted();
-  private static native void assertIsManaged();
-  private static native boolean isInterpreted();
-  private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
+    private static native boolean hasJit();
+    private native static boolean isAotCompiled(Class<?> cls, String methodName);
+    private static native void ensureJitCompiled(Class<?> itf, String method_name);
+    private static native void assertIsInterpreted();
+    private static native void assertIsManaged();
+    private static native boolean isInterpreted();
+    private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
 }
 
 // Put createMain2() in another class to avoid class loading due to verifier.
 class Helper {
-  static Main1 createMain2() {
-    return new Main2();
-  }
+    static Main1 createMain2() {
+        return new Main2();
+    }
 }
diff --git a/test/616-cha-native/src/Main.java b/test/616-cha-native/src/Main.java
index 53a463c..145cc42 100644
--- a/test/616-cha-native/src/Main.java
+++ b/test/616-cha-native/src/Main.java
@@ -15,19 +15,19 @@
  */
 
 abstract class A {
-  public abstract void foo();
+    public abstract void foo();
 }
 
 class B extends A {
-  public native void foo();
+    public native void foo();
 }
 
 class C extends B {
-  public void foo() {}
+    public void foo() {}
 }
 
 public class Main {
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
-  }
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
+    }
 }
diff --git a/test/616-cha-proxy-method-inline/run b/test/616-cha-proxy-method-inline/run
deleted file mode 100644
index d8b4f0d..0000000
--- a/test/616-cha-proxy-method-inline/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Run without an app image to prevent the classes to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
diff --git a/test/616-cha-proxy-method-inline/run.py b/test/616-cha-proxy-method-inline/run.py
new file mode 100644
index 0000000..1e797a8
--- /dev/null
+++ b/test/616-cha-proxy-method-inline/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Run without an app image to prevent the classes to be loaded at startup.
+  ctx.default_run(args, app_image=False)
diff --git a/test/616-cha-proxy-method-inline/src-multidex/Foo.java b/test/616-cha-proxy-method-inline/src-multidex/Foo.java
index 9deca3e..135214d 100644
--- a/test/616-cha-proxy-method-inline/src-multidex/Foo.java
+++ b/test/616-cha-proxy-method-inline/src-multidex/Foo.java
@@ -15,5 +15,5 @@
  */
 
 interface Foo {
-  public Object bar(Object obj);
+    public Object bar(Object obj);
 }
diff --git a/test/616-cha-proxy-method-inline/src/Main.java b/test/616-cha-proxy-method-inline/src/Main.java
index be7bc82..627c15c 100644
--- a/test/616-cha-proxy-method-inline/src/Main.java
+++ b/test/616-cha-proxy-method-inline/src/Main.java
@@ -18,53 +18,51 @@
 import java.lang.reflect.InvocationTargetException;
 
 class DebugProxy implements java.lang.reflect.InvocationHandler {
-  private Object obj;
-  static Class<?>[] interfaces = {Foo.class};
+    private Object obj;
+    static Class<?>[] interfaces = {Foo.class};
 
-  public static Object newInstance(Object obj) {
-    return java.lang.reflect.Proxy.newProxyInstance(
-      Foo.class.getClassLoader(),
-      interfaces,
-      new DebugProxy(obj));
-  }
-
-  private DebugProxy(Object obj) {
-    this.obj = obj;
-  }
-
-  public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
-    Object result;
-    if (obj == null) {
-      return null;
+    public static Object newInstance(Object obj) {
+        return java.lang.reflect.Proxy.newProxyInstance(
+                Foo.class.getClassLoader(), interfaces, new DebugProxy(obj));
     }
-    try {
-      System.out.println("before invoking method " + m.getName());
-      result = m.invoke(obj, args);
-    } catch (InvocationTargetException e) {
-      throw e.getTargetException();
-    } catch (Exception e) {
-      throw new RuntimeException("unexpected invocation exception: " + e.getMessage());
-    } finally {
-      System.out.println("after invoking method " + m.getName());
+
+    private DebugProxy(Object obj) {
+        this.obj = obj;
     }
-    return result;
-  }
+
+    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
+        Object result;
+        if (obj == null) {
+            return null;
+        }
+        try {
+            System.out.println("before invoking method " + m.getName());
+            result = m.invoke(obj, args);
+        } catch (InvocationTargetException e) {
+            throw e.getTargetException();
+        } catch (Exception e) {
+            throw new RuntimeException("unexpected invocation exception: " + e.getMessage());
+        } finally {
+            System.out.println("after invoking method " + m.getName());
+        }
+        return result;
+    }
 }
 
 public class Main {
-  public static void call(Foo foo) {
-    if (foo == null) {
-      return;
+    public static void call(Foo foo) {
+        if (foo == null) {
+            return;
+        }
+        foo.bar(null);
     }
-    foo.bar(null);
-  }
 
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
-    Foo foo = (Foo)DebugProxy.newInstance(null);
-    ensureJitCompiled(Main.class, "call");
-    call(foo);
-  }
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
+        Foo foo = (Foo)DebugProxy.newInstance(null);
+        ensureJitCompiled(Main.class, "call");
+        call(foo);
+    }
 
-  private static native void ensureJitCompiled(Class<?> itf, String method_name);
+    private static native void ensureJitCompiled(Class<?> itf, String method_name);
 }
diff --git a/test/616-cha-regression-proxy-method/src/Main.java b/test/616-cha-regression-proxy-method/src/Main.java
index 176f80b..14bc7a3 100644
--- a/test/616-cha-regression-proxy-method/src/Main.java
+++ b/test/616-cha-regression-proxy-method/src/Main.java
@@ -19,113 +19,113 @@
 import java.lang.reflect.Proxy;
 
 class Main1 {
-  void foo(int i) {
-    if (i != 1) {
-      printError("error1");
+    void foo(int i) {
+        if (i != 1) {
+            printError("error1");
+        }
     }
-  }
 
-  void printError(String msg) {
-    System.out.println(msg);
-  }
+    void printError(String msg) {
+        System.out.println(msg);
+    }
 }
 
 class Main2 extends Main1 {
-  void foo(int i) {
-    if (i != 2) {
-      printError("error2");
+    void foo(int i) {
+        if (i != 2) {
+            printError("error2");
+        }
     }
-  }
 }
 
 class Proxied implements Runnable {
-  public void run() {
-    synchronized(Main.class) {
-      Main.sOtherThreadStarted = true;
-      // Wait for Main2 to be linked and deoptimization is triggered.
-      try {
-        Main.class.wait();
-      } catch (Exception e) {
-      }
+    public void run() {
+        synchronized(Main.class) {
+            Main.sOtherThreadStarted = true;
+            // Wait for Main2 to be linked and deoptimization is triggered.
+            try {
+                Main.class.wait();
+            } catch (Exception e) {
+            }
+        }
     }
-  }
 }
 
 class MyInvocationHandler implements InvocationHandler {
-  private final Proxied proxied;
+    private final Proxied proxied;
 
-  public MyInvocationHandler(Proxied proxied) {
-    this.proxied = proxied;
-  }
+    public MyInvocationHandler(Proxied proxied) {
+        this.proxied = proxied;
+    }
 
-  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-    return method.invoke(proxied, args);
-  }
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        return method.invoke(proxied, args);
+    }
 }
 
 public class Main {
-  static Main1 sMain1;
-  static Main1 sMain2;
-  static volatile boolean sOtherThreadStarted;
+    static Main1 sMain1;
+    static Main1 sMain2;
+    static volatile boolean sOtherThreadStarted;
 
-  // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
-  // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
-  // After Helper.createMain2() which links in Main2, live testOverride() on stack
-  // should be deoptimized.
-  static void testOverride() {
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
+    // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
+    // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
+    // After Helper.createMain2() which links in Main2, live testOverride() on stack
+    // should be deoptimized.
+    static void testOverride() {
+        sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
 
-    // Wait for the other thread to start.
-    while (!sOtherThreadStarted);
-    // Create an Main2 instance and assign it to sMain2.
-    // sMain1 is kept the same.
-    sMain2 = Helper.createMain2();
-    // Wake up the other thread.
-    synchronized(Main.class) {
-      Main.class.notify();
+        // Wait for the other thread to start.
+        while (!sOtherThreadStarted);
+        // Create an Main2 instance and assign it to sMain2.
+        // sMain1 is kept the same.
+        sMain2 = Helper.createMain2();
+        // Wake up the other thread.
+        synchronized(Main.class) {
+            Main.class.notify();
+        }
+
+        // There should be a deoptimization here right after Main2 is linked by
+        // calling Helper.createMain2(), even though sMain1 didn't change.
+        // The behavior here would be different if inline-cache is used, which
+        // doesn't deoptimize since sMain1 still hits the type cache.
+        sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
+        if (sMain2 != null) {
+            sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
+        }
     }
 
-    // There should be a deoptimization here right after Main2 is linked by
-    // calling Helper.createMain2(), even though sMain1 didn't change.
-    // The behavior here would be different if inline-cache is used, which
-    // doesn't deoptimize since sMain1 still hits the type cache.
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-    if (sMain2 != null) {
-      sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
+    // Test scenarios under which CHA-based devirtualization happens,
+    // and class loading that overrides a method can invalidate compiled code.
+    // Also create a proxy method such that a proxy method's frame is visited
+    // during stack walking.
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
+        // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
+        sMain1 = new Main1();
+
+        // Create another thread that calls a proxy method.
+        new Thread() {
+            public void run() {
+                Runnable proxy = (Runnable)Proxy.newProxyInstance(
+                        Proxied.class.getClassLoader(),
+                        new Class[] { Runnable.class },
+                        new MyInvocationHandler(new Proxied()));
+                proxy.run();
+            }
+        }.start();
+
+        ensureJitCompiled(Main.class, "testOverride");
+        // This will create Main2 instance in the middle of testOverride().
+        testOverride();
     }
-  }
 
-  // Test scenarios under which CHA-based devirtualization happens,
-  // and class loading that overrides a method can invalidate compiled code.
-  // Also create a proxy method such that a proxy method's frame is visited
-  // during stack walking.
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
-    // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
-    sMain1 = new Main1();
-
-    // Create another thread that calls a proxy method.
-    new Thread() {
-      public void run() {
-        Runnable proxy = (Runnable)Proxy.newProxyInstance(
-            Proxied.class.getClassLoader(),
-            new Class[] { Runnable.class },
-            new MyInvocationHandler(new Proxied()));
-        proxy.run();
-      }
-    }.start();
-
-    ensureJitCompiled(Main.class, "testOverride");
-    // This will create Main2 instance in the middle of testOverride().
-    testOverride();
-  }
-
-  private static native void ensureJitCompiled(Class<?> itf, String method_name);
+    private static native void ensureJitCompiled(Class<?> itf, String method_name);
 }
 
 // Put createMain2() in another class to avoid class loading due to verifier.
 class Helper {
-  static Main1 createMain2() {
-    return new Main2();
-  }
+    static Main1 createMain2() {
+        return new Main2();
+    }
 }
diff --git a/test/616-cha-unloading/cha_unload.cc b/test/616-cha-unloading/cha_unload.cc
index f9d3874..d776023 100644
--- a/test/616-cha-unloading/cha_unload.cc
+++ b/test/616-cha-unloading/cha_unload.cc
@@ -22,7 +22,7 @@
 #include "base/casts.h"
 #include "class_linker.h"
 #include "jit/jit.h"
-#include "linear_alloc.h"
+#include "linear_alloc-inl.h"
 #include "nativehelper/ScopedUtfChars.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
@@ -79,8 +79,8 @@
   // a reused one that covers the art_method pointer.
   std::unique_ptr<LinearAlloc> alloc(Runtime::Current()->CreateLinearAlloc());
   do {
-    // Ask for a byte - it's sufficient to get an arena.
-    alloc->Alloc(Thread::Current(), 1);
+    // Ask for a word - it's sufficient to get an arena.
+    alloc->Alloc(Thread::Current(), sizeof(void*), LinearAllocKind::kNoGCRoots);
   } while (!alloc->Contains(ptr));
 }
 
diff --git a/test/616-cha-unloading/run b/test/616-cha-unloading/run
deleted file mode 100644
index d8b4f0d..0000000
--- a/test/616-cha-unloading/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Run without an app image to prevent the classes to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
diff --git a/test/616-cha-unloading/run.py b/test/616-cha-unloading/run.py
new file mode 100644
index 0000000..1e797a8
--- /dev/null
+++ b/test/616-cha-unloading/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Run without an app image to prevent the classes to be loaded at startup.
+  ctx.default_run(args, app_image=False)
diff --git a/test/616-cha/run b/test/616-cha/run
deleted file mode 100644
index 9c64c7d..0000000
--- a/test/616-cha/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-# Run without an app image to prevent the classes to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
diff --git a/test/616-cha/run.py b/test/616-cha/run.py
new file mode 100644
index 0000000..38164a3
--- /dev/null
+++ b/test/616-cha/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+
+
+def run(ctx, args):
+  # Run without an app image to prevent the classes to be loaded at startup.
+  ctx.default_run(args, app_image=False)
diff --git a/test/616-cha/src/Main.java b/test/616-cha/src/Main.java
index 39f47fa..d2d4bcf 100644
--- a/test/616-cha/src/Main.java
+++ b/test/616-cha/src/Main.java
@@ -15,239 +15,239 @@
  */
 
 class Main1 {
-  String getName() {
-    return "Main1";
-  }
-
-  void printError(String msg) {
-    System.out.println(msg);
-  }
-
-  void foo(int i) {
-    if (i != 1) {
-      printError("error1");
+    String getName() {
+        return "Main1";
     }
-  }
 
-  int getValue1() {
-    return 1;
-  }
-  int getValue2() {
-    return 2;
-  }
-  int getValue3() {
-    return 3;
-  }
-  int getValue4() {
-    return 4;
-  }
-  int getValue5() {
-    return 5;
-  }
-  int getValue6() {
-    return 6;
-  }
+    void printError(String msg) {
+        System.out.println(msg);
+    }
+
+    void foo(int i) {
+        if (i != 1) {
+            printError("error1");
+        }
+    }
+
+    int getValue1() {
+        return 1;
+    }
+    int getValue2() {
+        return 2;
+    }
+    int getValue3() {
+        return 3;
+    }
+    int getValue4() {
+        return 4;
+    }
+    int getValue5() {
+        return 5;
+    }
+    int getValue6() {
+        return 6;
+    }
 }
 
 class Main2 extends Main1 {
-  String getName() {
-    return "Main2";
-  }
-
-  void foo(int i) {
-    if (i != 2) {
-      printError("error2");
+    String getName() {
+        return "Main2";
     }
-  }
+
+    void foo(int i) {
+        if (i != 2) {
+            printError("error2");
+        }
+    }
 }
 
 class Main3 extends Main1 {
-  String getName() {
-    return "Main3";
-  }
+    String getName() {
+        return "Main3";
+    }
 }
 
 public class Main {
-  static Main1 sMain1;
-  static Main1 sMain2;
+    static Main1 sMain1;
+    static Main1 sMain2;
 
-  static boolean sIsOptimizing = true;
-  static boolean sHasJIT = true;
-  static volatile boolean sOtherThreadStarted;
+    static boolean sIsOptimizing = true;
+    static boolean sHasJIT = true;
+    static volatile boolean sOtherThreadStarted;
 
-  // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
-  // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
-  // After Helper.createMain2() which links in Main2, live testOverride() on stack
-  // should be deoptimized.
-  static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) {
-    if (setHasJIT) {
-      if (isInterpreted()) {
-        sHasJIT = false;
-      }
-      return;
-    }
-
-    if (createMain2 && (sIsOptimizing || sHasJIT)) {
-      assertIsManaged();
-    }
-
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-
-    if (createMain2) {
-      // Wait for the other thread to start.
-      while (!sOtherThreadStarted);
-      // Create an Main2 instance and assign it to sMain2.
-      // sMain1 is kept the same.
-      sMain2 = Helper.createMain2();
-      // Wake up the other thread.
-      synchronized(Main.class) {
-        Main.class.notify();
-      }
-    } else if (wait) {
-      // This is the other thread.
-      synchronized(Main.class) {
-        sOtherThreadStarted = true;
-        // Wait for Main2 to be linked and deoptimization is triggered.
-        try {
-          Main.class.wait();
-        } catch (Exception e) {
+    // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
+    // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
+    // After Helper.createMain2() which links in Main2, live testOverride() on stack
+    // should be deoptimized.
+    static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) {
+        if (setHasJIT) {
+            if (isInterpreted()) {
+                sHasJIT = false;
+            }
+            return;
         }
-      }
+
+        if (createMain2 && (sIsOptimizing || sHasJIT)) {
+            assertIsManaged();
+        }
+
+        sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
+
+        if (createMain2) {
+            // Wait for the other thread to start.
+            while (!sOtherThreadStarted);
+            // Create an Main2 instance and assign it to sMain2.
+            // sMain1 is kept the same.
+            sMain2 = Helper.createMain2();
+            // Wake up the other thread.
+            synchronized(Main.class) {
+                Main.class.notify();
+            }
+        } else if (wait) {
+            // This is the other thread.
+            synchronized(Main.class) {
+                sOtherThreadStarted = true;
+                // Wait for Main2 to be linked and deoptimization is triggered.
+                try {
+                    Main.class.wait();
+                } catch (Exception e) {
+                }
+            }
+        }
+
+        // There should be a deoptimization here right after Main2 is linked by
+        // calling Helper.createMain2(), even though sMain1 didn't change.
+        // The behavior here would be different if inline-cache is used, which
+        // doesn't deoptimize since sMain1 still hits the type cache.
+        sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
+        if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
+            // This method should be deoptimized right after Main2 is created.
+            assertIsInterpreted();
+        }
+
+        if (sMain2 != null) {
+            sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
+        }
     }
 
-    // There should be a deoptimization here right after Main2 is linked by
-    // calling Helper.createMain2(), even though sMain1 didn't change.
-    // The behavior here would be different if inline-cache is used, which
-    // doesn't deoptimize since sMain1 still hits the type cache.
-    sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
-    if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
-      // This method should be deoptimized right after Main2 is created.
-      assertIsInterpreted();
+    static Main1[] sArray;
+
+    static long calcValue(Main1 m) {
+        return m.getValue1()
+                + m.getValue2() * 2
+                + m.getValue3() * 3
+                + m.getValue4() * 4
+                + m.getValue5() * 5
+                + m.getValue6() * 6;
     }
 
-    if (sMain2 != null) {
-      sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
-    }
-  }
-
-  static Main1[] sArray;
-
-  static long calcValue(Main1 m) {
-    return m.getValue1()
-        + m.getValue2() * 2
-        + m.getValue3() * 3
-        + m.getValue4() * 4
-        + m.getValue5() * 5
-        + m.getValue6() * 6;
-  }
-
-  static long testNoOverrideLoop(int count) {
-    long sum = 0;
-    for (int i=0; i<count; i++) {
-      sum += calcValue(sArray[0]);
-      sum += calcValue(sArray[1]);
-      sum += calcValue(sArray[2]);
-    }
-    return sum;
-  }
-
-  static void testNoOverride() {
-    sArray = new Main1[3];
-    sArray[0] = new Main1();
-    sArray[1] = Helper.createMain2();
-    sArray[2] = Helper.createMain3();
-    long sum = 0;
-    // Loop enough to get methods JITed.
-    for (int i=0; i<100; i++) {
-      testNoOverrideLoop(1);
-    }
-    ensureJitCompiled(Main.class, "testNoOverrideLoop");
-    ensureJitCompiled(Main.class, "calcValue");
-
-    long t1 = System.currentTimeMillis();
-    sum = testNoOverrideLoop(100000);
-    long t2 = System.currentTimeMillis();
-    if (sum != 27300000L) {
-      System.out.println("Unexpected result.");
-    }
-  }
-
-  private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
-    if (hasSingleImplementation(clazz, method_name) != b) {
-      System.out.println(clazz + "." + method_name +
-          " doesn't have single implementation value of " + b);
-    }
-  }
-
-  // Test scenarios under which CHA-based devirtualization happens,
-  // and class loading that overrides a method can invalidate compiled code.
-  // Also test pure non-overriding case, which is more for checking generated
-  // code form.
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
-
-    // CHeck some boot-image methods.
-
-    // We would want to have this, but currently setting single-implementation in the boot image
-    // does not work well with app images. b/34193647
-    final boolean ARRAYLIST_SIZE_EXPECTED = false;
-    assertSingleImplementation(java.util.ArrayList.class, "size", ARRAYLIST_SIZE_EXPECTED);
-
-    // java.util.LinkedHashMap overrides get().
-    assertSingleImplementation(java.util.HashMap.class, "get", false);
-
-    // We don't set single-implementation modifier bit for final classes or methods
-    // since we can devirtualize without CHA for those cases. However hasSingleImplementation()
-    // should return true for those cases.
-    assertSingleImplementation(java.lang.String.class, "charAt", true);
-    assertSingleImplementation(java.lang.Thread.class, "join", true);
-
-    if (isInterpreted()) {
-      sIsOptimizing = false;
+    static long testNoOverrideLoop(int count) {
+        long sum = 0;
+        for (int i=0; i<count; i++) {
+            sum += calcValue(sArray[0]);
+            sum += calcValue(sArray[1]);
+            sum += calcValue(sArray[2]);
+        }
+        return sum;
     }
 
-    // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
-    sMain1 = new Main1();
+    static void testNoOverride() {
+        sArray = new Main1[3];
+        sArray[0] = new Main1();
+        sArray[1] = Helper.createMain2();
+        sArray[2] = Helper.createMain3();
+        long sum = 0;
+        // Loop enough to get methods JITed.
+        for (int i=0; i<100; i++) {
+            testNoOverrideLoop(1);
+        }
+        ensureJitCompiled(Main.class, "testNoOverrideLoop");
+        ensureJitCompiled(Main.class, "calcValue");
 
-    ensureJitCompiled(Main.class, "testOverride");
-    testOverride(false, false, true);
-
-    if (sHasJIT && !sIsOptimizing) {
-      assertSingleImplementation(Main1.class, "foo", true);
-    } else {
-      // Main2 is verified ahead-of-time so it's linked in already.
+        long t1 = System.currentTimeMillis();
+        sum = testNoOverrideLoop(100000);
+        long t2 = System.currentTimeMillis();
+        if (sum != 27300000L) {
+            System.out.println("Unexpected result.");
+        }
     }
-    assertSingleImplementation(Main1.class, "getValue1", true);
 
-    // Create another thread that also calls sMain1.foo().
-    // Try to test suspend and deopt another thread.
-    new Thread() {
-      public void run() {
-        testOverride(false, true, false);
-      }
-    }.start();
+    private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
+        if (hasSingleImplementation(clazz, method_name) != b) {
+            System.out.println(clazz + "." + method_name +
+                    " doesn't have single implementation value of " + b);
+        }
+    }
 
-    // This will create Main2 instance in the middle of testOverride().
-    testOverride(true, false, false);
-    assertSingleImplementation(Main1.class, "foo", false);
-    assertSingleImplementation(Main1.class, "getValue1", true);
+    // Test scenarios under which CHA-based devirtualization happens,
+    // and class loading that overrides a method can invalidate compiled code.
+    // Also test pure non-overriding case, which is more for checking generated
+    // code form.
+    public static void main(String[] args) {
+        System.loadLibrary(args[0]);
 
-    testNoOverride();
-  }
+        // CHeck some boot-image methods.
 
-  private static native void ensureJitCompiled(Class<?> itf, String method_name);
-  private static native void assertIsInterpreted();
-  private static native void assertIsManaged();
-  private static native boolean isInterpreted();
-  private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
+        // We would want to have this, but currently setting single-implementation in the boot image
+        // does not work well with app images. b/34193647
+        final boolean ARRAYLIST_SIZE_EXPECTED = false;
+        assertSingleImplementation(java.util.ArrayList.class, "size", ARRAYLIST_SIZE_EXPECTED);
+
+        // java.util.LinkedHashMap overrides get().
+        assertSingleImplementation(java.util.HashMap.class, "get", false);
+
+        // We don't set single-implementation modifier bit for final classes or methods
+        // since we can devirtualize without CHA for those cases. However hasSingleImplementation()
+        // should return true for those cases.
+        assertSingleImplementation(java.lang.String.class, "charAt", true);
+        assertSingleImplementation(java.lang.Thread.class, "join", true);
+
+        if (isInterpreted()) {
+            sIsOptimizing = false;
+        }
+
+        // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
+        sMain1 = new Main1();
+
+        ensureJitCompiled(Main.class, "testOverride");
+        testOverride(false, false, true);
+
+        if (sHasJIT && !sIsOptimizing) {
+            assertSingleImplementation(Main1.class, "foo", true);
+        } else {
+            // Main2 is verified ahead-of-time so it's linked in already.
+        }
+        assertSingleImplementation(Main1.class, "getValue1", true);
+
+        // Create another thread that also calls sMain1.foo().
+        // Try to test suspend and deopt another thread.
+        new Thread() {
+            public void run() {
+                testOverride(false, true, false);
+            }
+        }.start();
+
+        // This will create Main2 instance in the middle of testOverride().
+        testOverride(true, false, false);
+        assertSingleImplementation(Main1.class, "foo", false);
+        assertSingleImplementation(Main1.class, "getValue1", true);
+
+        testNoOverride();
+    }
+
+    private static native void ensureJitCompiled(Class<?> itf, String method_name);
+    private static native void assertIsInterpreted();
+    private static native void assertIsManaged();
+    private static native boolean isInterpreted();
+    private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
 }
 
 // Put createMain2() in another class to avoid class loading due to verifier.
 class Helper {
-  static Main1 createMain2() {
-    return new Main2();
-  }
-  static Main1 createMain3() {
-    return new Main3();
-  }
+    static Main1 createMain2() {
+        return new Main2();
+    }
+    static Main1 createMain3() {
+        return new Main3();
+    }
 }
diff --git a/test/617-clinit-oome/src/Main.java b/test/617-clinit-oome/src/Main.java
index bab344f..005f0df 100644
--- a/test/617-clinit-oome/src/Main.java
+++ b/test/617-clinit-oome/src/Main.java
@@ -15,43 +15,43 @@
  */
 
 public class Main {
-  private static int exhaustJavaHeap(Object[] data, int index, int size) {
-    Runtime.getRuntime().gc();
-    while (size > 0) {
+    private static int exhaustJavaHeap(Object[] data, int index, int size) {
+        Runtime.getRuntime().gc();
+        while (size > 0) {
+            try {
+                data[index] = new byte[size];
+                index++;
+            } catch (OutOfMemoryError e) {
+                size /= 2;
+            }
+        }
+        return index;
+    }
+
+    public static void main(String[] args) {
+        Class klass = Other.class;
+        Object[] data = new Object[100000];
         try {
-            data[index] = new byte[size];
-            index++;
+            System.out.println("Filling heap");
+
+            // Make sure that there is no reclaimable memory in the heap. Otherwise we may throw
+            // OOME to prevent GC thrashing, even if later allocations may succeed.
+            Runtime.getRuntime().gc();
+            System.runFinalization();
+            // NOTE: There is a GC invocation in the exhaustJavaHeap(). So we don't need one here.
+
+            int index = 0;
+            int initial_size = 256 * 1024 * 1024;
+            // Repeat to ensure there is no space left on the heap.
+            index = exhaustJavaHeap(data, index, initial_size);
+            index = exhaustJavaHeap(data, index, /*size*/ 4);
+            index = exhaustJavaHeap(data, index, /*size*/ 4);
+
+            // Initialize now that the heap is full.
+            Other.print();
         } catch (OutOfMemoryError e) {
-            size /= 2;
+        } catch (Exception e) {
+            System.out.println(e);
         }
     }
-    return index;
-  }
-
-  public static void main(String[] args) {
-    Class klass = Other.class;
-    Object[] data = new Object[100000];
-    try {
-        System.out.println("Filling heap");
-
-        // Make sure that there is no reclaimable memory in the heap. Otherwise we may throw
-        // OOME to prevent GC thrashing, even if later allocations may succeed.
-        Runtime.getRuntime().gc();
-        System.runFinalization();
-        // NOTE: There is a GC invocation in the exhaustJavaHeap(). So we don't need one here.
-
-        int index = 0;
-        int initial_size = 256 * 1024 * 1024;
-        // Repeat to ensure there is no space left on the heap.
-        index = exhaustJavaHeap(data, index, initial_size);
-        index = exhaustJavaHeap(data, index, /*size*/ 4);
-        index = exhaustJavaHeap(data, index, /*size*/ 4);
-
-        // Initialize now that the heap is full.
-        Other.print();
-    } catch (OutOfMemoryError e) {
-    } catch (Exception e) {
-        System.out.println(e);
-    }
-  }
 }
diff --git a/test/618-checker-induction/src/Main.java b/test/618-checker-induction/src/Main.java
index dd76e41..5dc8e98 100644
--- a/test/618-checker-induction/src/Main.java
+++ b/test/618-checker-induction/src/Main.java
@@ -19,930 +19,1027 @@
  */
 public class Main {
 
-  static int[] a = new int[10];
+    static int[] a = new int[10];
 
-  static int[] novec = new int[20];  // to prevent vectorization
+    static int[] novec = new int[20];  // to prevent vectorization
 
-  /// CHECK-START: void Main.deadSingleLoop() loop_optimization (before)
-  /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
-  //
-  /// CHECK-START: void Main.deadSingleLoop() loop_optimization (after)
-  /// CHECK-NOT: Phi
-  static void deadSingleLoop() {
-    for (int i = 0; i < 4; i++) {
-    }
-  }
-
-  /// CHECK-START: void Main.deadSingleLoop() loop_optimization (before)
-  /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
-  //
-  /// CHECK-START: void Main.deadSingleLoop() loop_optimization (after)
-  /// CHECK-NOT: Phi
-  static void deadSingleLoopN(int n) {
-    for (int i = 0; i < n; i++) {
-    }
-  }
-
-  /// CHECK-START: void Main.potentialInfiniteLoop(int) loop_optimization (before)
-  /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
-  //
-  /// CHECK-START: void Main.potentialInfiniteLoop(int) loop_optimization (after)
-  /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
-  static void potentialInfiniteLoop(int n) {
-    for (int i = 0; i <= n; i++) {  // loops forever when n = MAX_INT
-    }
-  }
-
-  /// CHECK-START: void Main.deadNestedLoops() loop_optimization (before)
-  /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: Phi loop:{{B\d+}}      outer_loop:<<Loop>>
-  //
-  /// CHECK-START: void Main.deadNestedLoops() loop_optimization (after)
-  /// CHECK-NOT: Phi
-  static void deadNestedLoops() {
-    for (int i = 0; i < 4; i++) {
-      for (int j = 0; j < 4; j++) {
-      }
-    }
-  }
-
-  /// CHECK-START: void Main.deadNestedAndFollowingLoops() loop_optimization (before)
-  /// CHECK-DAG: Phi loop:<<Loop1:B\d+>> outer_loop:none
-  /// CHECK-DAG: Phi loop:<<Loop2:B\d+>> outer_loop:<<Loop1>>
-  /// CHECK-DAG: Phi loop:{{B\d+}}       outer_loop:<<Loop2>>
-  /// CHECK-DAG: Phi loop:{{B\d+}}       outer_loop:<<Loop2>>
-  /// CHECK-DAG: Phi loop:<<Loop3:B\d+>> outer_loop:<<Loop1>>
-  /// CHECK-DAG: Phi loop:{{B\d+}}       outer_loop:<<Loop3>>
-  /// CHECK-DAG: Phi loop:{{B\d+}}       outer_loop:none
-  //
-  /// CHECK-START: void Main.deadNestedAndFollowingLoops() loop_optimization (after)
-  /// CHECK-NOT: Phi
-  static void deadNestedAndFollowingLoops() {
-    for (int i = 0; i < 4; i++) {
-      for (int j = 0; j < 4; j++) {
-        for (int k = 0; k < 4; k++) {
+    /// CHECK-START: void Main.deadSingleLoop() loop_optimization (before)
+    /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
+    //
+    /// CHECK-START: void Main.deadSingleLoop() loop_optimization (after)
+    /// CHECK-NOT: Phi
+    static void deadSingleLoop() {
+        for (int i = 0; i < 4; i++) {
         }
-        for (int k = 0; k < 4; k++) {
+    }
+
+    /// CHECK-START: void Main.deadSingleLoop() loop_optimization (before)
+    /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
+    //
+    /// CHECK-START: void Main.deadSingleLoop() loop_optimization (after)
+    /// CHECK-NOT: Phi
+    static void deadSingleLoopN(int n) {
+        for (int i = 0; i < n; i++) {
         }
-      }
-      for (int j = 0; j < 4; j++) {
-        for (int k = 0; k < 4; k++) {
+    }
+
+    /// CHECK-START: void Main.potentialInfiniteLoop(int) loop_optimization (before)
+    /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
+    //
+    /// CHECK-START: void Main.potentialInfiniteLoop(int) loop_optimization (after)
+    /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
+    static void potentialInfiniteLoop(int n) {
+        for (int i = 0; i <= n; i++) {  // loops forever when n = MAX_INT
         }
-      }
-    }
-    for (int i = 0; i < 4; i++) {
-    }
-  }
-
-  /// CHECK-START: void Main.deadConditional(int) loop_optimization (before)
-  /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
-  //
-  /// CHECK-START: void Main.deadConditional(int) loop_optimization (after)
-  /// CHECK-NOT: Phi
-  public static void deadConditional(int n) {
-    int k = 0;
-    int m = 0;
-    for (int i = 0; i < n; i++) {
-      if (i == 3)
-        k = i;
-      else
-        m = i;
-    }
-  }
-
-  /// CHECK-START: void Main.deadConditionalCycle(int) loop_optimization (before)
-  /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: Phi loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: Phi loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: Phi loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: Phi loop:<<Loop>>      outer_loop:none
-  //
-  /// CHECK-START: void Main.deadConditionalCycle(int) loop_optimization (after)
-  /// CHECK-NOT: Phi
-  public static void deadConditionalCycle(int n) {
-    int k = 0;
-    int m = 0;
-    for (int i = 0; i < n; i++) {
-      if (i == 3)
-        k--;
-      else
-        m++;
-    }
-  }
-
-
-  /// CHECK-START: void Main.deadInduction() loop_optimization (before)
-  /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
-  //
-  /// CHECK-START: void Main.deadInduction() loop_optimization (after)
-  /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-NOT: Phi      loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
-  static void deadInduction() {
-    int dead = 0;
-    for (int i = 0; i < a.length; i++) {
-      a[i] = novec[2 * i] + 1;
-      dead += 5;
-    }
-  }
-
-  /// CHECK-START: void Main.deadManyInduction() loop_optimization (before)
-  /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
-  //
-  /// CHECK-START: void Main.deadManyInduction() loop_optimization (after)
-  /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-NOT: Phi      loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
-  static void deadManyInduction() {
-    int dead1 = 0, dead2 = 1, dead3 = 3;
-    for (int i = 0; i < a.length; i++) {
-      dead1 += 5;
-      a[i] = novec[2 * i] + 2;
-      dead2 += 10;
-      dead3 += 100;
-    }
-  }
-
-  /// CHECK-START: void Main.deadSequence() loop_optimization (before)
-  /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
-  //
-  /// CHECK-START: void Main.deadSequence() loop_optimization (after)
-  /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-NOT: Phi      loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
-  static void deadSequence() {
-    int dead = 0;
-    for (int i = 0; i < a.length; i++) {
-      a[i] = novec[2 * i] + 3;
-      // Increment value defined inside loop,
-      // but sequence itself not used anywhere.
-      dead += i;
-    }
-  }
-
-  /// CHECK-START: void Main.deadCycleWithException(int) loop_optimization (before)
-  /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
-  /// CHECK-NOT: BoundsCheck
-  //
-  /// CHECK-START: void Main.deadCycleWithException(int) loop_optimization (after)
-  /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-NOT: Phi      loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
-  /// CHECK-NOT: ArrayGet loop:<<Loop>>      outer_loop:none
-  static void deadCycleWithException(int k) {
-    int dead = 0;
-    for (int i = 0; i < a.length; i++) {
-      a[i] = novec[2 * i] + 4;
-      // Increment value of dead cycle may throw exception. Dynamic
-      // BCE takes care of the bounds check though, which enables
-      // removing the ArrayGet after removing the dead cycle.
-      dead += a[k];
-    }
-  }
-
-  /// CHECK-START: int Main.closedFormInductionUp() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: int Main.closedFormInductionUp() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.closedFormInductionUp() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 12395 loop:none
-  /// CHECK-DAG:               Return [<<Int>>]  loop:none
-  static int closedFormInductionUp() {
-    int closed = 12345;
-    for (int i = 0; i < 10; i++) {
-      closed += 5;
-    }
-    return closed;  // only needs last value
-  }
-
-  /// CHECK-START: int Main.closedFormInductionInAndDown(int) loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
-  //
-  /// CHECK-START: int Main.closedFormInductionInAndDown(int) loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.closedFormInductionInAndDown(int) instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Par:i\d+>>  ParameterValue        loop:none
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant -50       loop:none
-  /// CHECK-DAG: <<Add:i\d+>>  Add [<<Int>>,<<Par>>] loop:none
-  /// CHECK-DAG:               Return [<<Add>>]      loop:none
-  static int closedFormInductionInAndDown(int closed) {
-    for (int i = 0; i < 10; i++) {
-      closed -= 5;
-    }
-    return closed;  // only needs last value
-  }
-
-  /// CHECK-START: int Main.closedFormInductionTrivialIf() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Select            loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: int Main.closedFormInductionTrivialIf() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  /// CHECK-NOT:               Select
-  //
-  /// CHECK-START: int Main.closedFormInductionTrivialIf() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 81    loop:none
-  /// CHECK-DAG:               Return [<<Int>>]  loop:none
-  static int closedFormInductionTrivialIf() {
-    int closed = 11;
-    for (int i = 0; i < 10; i++) {
-      // Trivial if becomes trivial select at HIR level.
-      // Make sure this is still recognized as induction.
-      if (i < 5) {
-        closed += 7;
-      } else {
-        closed += 7;
-      }
-    }
-    return closed;  // only needs last value
-  }
-
-  /// CHECK-START: int Main.closedFormNested() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop1:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop1>>      outer_loop:none
-  /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:<<Loop1>>
-  /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop2>>      outer_loop:<<Loop1>>
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: int Main.closedFormNested() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.closedFormNested() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 100  loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  static int closedFormNested() {
-    int closed = 0;
-    for (int i = 0; i < 10; i++) {
-      for (int j = 0; j < 10; j++) {
-        closed++;
-      }
-    }
-    return closed;  // only needs last-value
-  }
-
-  /// CHECK-START: int Main.closedFormNestedAlt() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop1:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop1>>      outer_loop:none
-  /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:<<Loop1>>
-  /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop2>>      outer_loop:<<Loop1>>
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: int Main.closedFormNestedAlt() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.closedFormNestedAlt() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 15082 loop:none
-  /// CHECK-DAG:               Return [<<Int>>]  loop:none
-  static int closedFormNestedAlt() {
-    int closed = 12345;
-    for (int i = 0; i < 17; i++) {
-      for (int j = 0; j < 23; j++) {
-        closed += 7;
-      }
-    }
-    return closed;  // only needs last-value
-  }
-
-  // TODO: taken test around closed form?
-  static int closedFormInductionUpN(int n) {
-    int closed = 12345;
-    for (int i = 0; i < n; i++) {
-      closed += 5;
-    }
-    return closed;  // only needs last value
-  }
-
-  // TODO: taken test around closed form?
-  static int closedFormInductionInAndDownN(int closed, int n) {
-    for (int i = 0; i < n; i++) {
-      closed -= 5;
-    }
-    return closed;  // only needs last value
-  }
-
-  // TODO: move closed form even further out?
-  static int closedFormNestedN(int n) {
-    int closed = 0;
-    for (int i = 0; i < n; i++) {
-      for (int j = 0; j < 10; j++) {
-        closed++;
-      }
-    }
-    return closed;  // only needs last-value
-  }
-
-  // TODO: move closed form even further out?
-  static int closedFormNestedNAlt(int n) {
-    int closed = 12345;
-    for (int i = 0; i < n; i++) {
-      for (int j = 0; j < 23; j++) {
-        closed += 7;
-      }
-    }
-    return closed;  // only needs last-value
-  }
-
-  // TODO: move closed form even further out?
-  static int closedFormNestedMN(int m, int n) {
-    int closed = 0;
-    for (int i = 0; i < m; i++) {
-      for (int j = 0; j < n; j++) {
-        closed++;
-      }
-    }
-    return closed;  // only needs last-value
-  }
-
-  // TODO: move closed form even further out?
-  static int closedFormNestedMNAlt(int m, int n) {
-    int closed = 12345;
-    for (int i = 0; i < m; i++) {
-      for (int j = 0; j < n; j++) {
-        closed += 7;
-      }
-    }
-    return closed;  // only needs last-value
-  }
-
-  /// CHECK-START: int Main.mainIndexReturned() loop_optimization (before)
-  /// CHECK-DAG: <<Phi:i\d+>> Phi              loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:              Return [<<Phi>>] loop:none
-  //
-  /// CHECK-START: int Main.mainIndexReturned() loop_optimization (after)
-  /// CHECK-NOT:              Phi
-  //
-  /// CHECK-START: int Main.mainIndexReturned() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 10   loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  static int mainIndexReturned() {
-    int i;
-    for (i = 0; i < 10; i++);
-    return i;
-  }
-
-  /// CHECK-START: int Main.periodicReturned9() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: int Main.periodicReturned9() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.periodicReturned9() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 1    loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  static int periodicReturned9() {
-    int k = 0;
-    for (int i = 0; i < 9; i++) {
-      k = 1 - k;
-    }
-    return k;
-  }
-
-  /// CHECK-START: int Main.periodicReturned10() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: int Main.periodicReturned10() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.periodicReturned10() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  static int periodicReturned10() {
-    int k = 0;
-    for (int i = 0; i < 10; i++) {
-      k = 1 - k;
-    }
-    return k;
-  }
-
-  /// CHECK-START: int Main.getSum21() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
-  //
-  /// CHECK-START: int Main.getSum21() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.getSum21() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 21   loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  private static int getSum21() {
-    int k = 0;
-    int sum = 0;
-    for (int i = 0; i < 6; i++) {
-      k++;
-      sum += k;
-    }
-    return sum;
-  }
-
-  // Ensure double induction does not "overshoot" the subscript range.
-  private static int getIncr2(int[] arr) {
-    for (int i = 0; i < 12; ) {
-      arr[i++] = 30;
-      arr[i++] = 29;
-    }
-    int sum = 0;
-    for (int i = 0; i < 12; i++) {
-      sum += arr[i];
-    }
-    return sum;
-  }
-
-  // TODO: handle as closed/empty eventually?
-  static int mainIndexReturnedN(int n) {
-    int i;
-    for (i = 0; i < n; i++);
-    return i;
-  }
-
-  // TODO: handle as closed/empty eventually?
-  static int mainIndexShort1(short s) {
-    int i = 0;
-    for (i = 0; i < s; i++) { }
-    return i;
-  }
-
-  // TODO: handle as closed/empty eventually?
-  static int mainIndexShort2(short s) {
-    int i = 0;
-    for (i = 0; s > i; i++) { }
-    return i;
-  }
-
-  /// CHECK-START: int Main.periodicReturnedN(int) loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: int Main.periodicReturnedN(int) loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  static int periodicReturnedN(int n) {
-    int k = 0;
-    for (int i = 0; i < n; i++) {
-      k = 1 - k;
-    }
-    return k;
-  }
-
-  // If ever replaced by closed form, last value should be correct!
-  private static int getSumN(int n) {
-    int k = 0;
-    int sum = 0;
-    for (int i = 0; i < n; i++) {
-      k++;
-      sum += k;
-    }
-    return sum;
-  }
-
-  // If ever replaced by closed form, last value should be correct!
-  private static int closedTwice() {
-    int closed = 0;
-    for (int i = 0; i < 10; i++) {
-      closed++;
-    }
-    // Closed form of first loop defines trip count of second loop.
-    int other_closed = 0;
-    for (int i = 0; i < closed; i++) {
-      other_closed++;
-    }
-    return other_closed;
-  }
-
-  /// CHECK-START: int Main.closedFeed() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop1:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop1>>      outer_loop:none
-  /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop2>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi3>>] loop:none
-  /// CHECK-EVAL: "<<Loop1>>" != "<<Loop2>>"
-  //
-  /// CHECK-START: int Main.closedFeed() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.closedFeed() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 20   loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  private static int closedFeed() {
-    int closed = 0;
-    for (int i = 0; i < 10; i++) {
-      closed++;
-    }
-    // Closed form of first loop feeds into initial value of second loop,
-    // used when generating closed form for the latter.
-    for (int i = 0; i < 10; i++) {
-      closed++;
-    }
-    return closed;
-  }
-
-  /// CHECK-START: int Main.closedLargeUp() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: int Main.closedLargeUp() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.closedLargeUp() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant -10  loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  private static int closedLargeUp() {
-    int closed = 0;
-    for (int i = 0; i < 10; i++) {
-      closed += 0x7fffffff;
-    }
-    return closed;
-  }
-
-  /// CHECK-START: int Main.closedLargeDown() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: int Main.closedLargeDown() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.closedLargeDown() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 10   loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  private static int closedLargeDown() {
-    int closed = 0;
-    for (int i = 0; i < 10; i++) {
-      closed -= 0x7fffffff;
-    }
-    return closed;
-  }
-
-  /// CHECK-START: int Main.waterFall() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop1:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop3:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop4:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi5:i\d+>> Phi               loop:<<Loop5:B\d+>> outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi5>>] loop:none
-  //
-  /// CHECK-START: int Main.waterFall() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: int Main.waterFall() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 50   loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  private static int waterFall() {
-    int i = 0;
-    for (; i < 10; i++);
-    for (; i < 20; i++);
-    for (; i < 30; i++);
-    for (; i < 40; i++);
-    for (; i < 50; i++);
-    return i;  // this should become just 50
-  }
-
-  /// CHECK-START: boolean Main.periodicBoolIdiom1() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: boolean Main.periodicBoolIdiom1() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: boolean Main.periodicBoolIdiom1() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  private static boolean periodicBoolIdiom1() {
-    boolean x = true;
-    for (int i = 0; i < 7; i++) {
-      x = !x;
-    }
-    return x;
-  }
-
-  /// CHECK-START: boolean Main.periodicBoolIdiom2() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: boolean Main.periodicBoolIdiom2() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: boolean Main.periodicBoolIdiom2() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  private static boolean periodicBoolIdiom2() {
-    boolean x = true;
-    for (int i = 0; i < 7; i++) {
-      x = (x != true);
-    }
-    return x;
-  }
-
-  /// CHECK-START: boolean Main.periodicBoolIdiom3() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
-  //
-  /// CHECK-START: boolean Main.periodicBoolIdiom3() loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  //
-  /// CHECK-START: boolean Main.periodicBoolIdiom3() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
-  private static boolean periodicBoolIdiom3() {
-    boolean x = true;
-    for (int i = 0; i < 7; i++) {
-      x = (x == false);
-    }
-    return x;
-  }
-
-  /// CHECK-START: boolean Main.periodicBoolIdiom1N(boolean, int) loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
-  //
-  /// CHECK-START: boolean Main.periodicBoolIdiom1N(boolean, int) loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  private static boolean periodicBoolIdiom1N(boolean x, int n) {
-    for (int i = 0; i < n; i++) {
-      x = !x;
-    }
-    return x;
-  }
-
-  /// CHECK-START: boolean Main.periodicBoolIdiom2N(boolean, int) loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
-  //
-  /// CHECK-START: boolean Main.periodicBoolIdiom2N(boolean, int) loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  private static boolean periodicBoolIdiom2N(boolean x, int n) {
-    for (int i = 0; i < n; i++) {
-      x = (x != true);
-    }
-    return x;
-  }
-
-  /// CHECK-START: boolean Main.periodicBoolIdiom3N(boolean, int) loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
-  //
-  /// CHECK-START: boolean Main.periodicBoolIdiom3N(boolean, int) loop_optimization (after)
-  /// CHECK-NOT:               Phi
-  private static boolean periodicBoolIdiom3N(boolean x, int n) {
-    for (int i = 0; i < n; i++) {
-      x = (x == false);
-    }
-    return x;
-  }
-
-  /// CHECK-START: float Main.periodicFloat10() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: <<Phi3:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: <<Phi4:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
-  //
-  /// CHECK-START: float Main.periodicFloat10() loop_optimization (after)
-  /// CHECK-NOT: Phi
-  //
-  /// CHECK-START: float Main.periodicFloat10() loop_optimization (after)
-  /// CHECK-DAG: <<Float:f\d+>>  FloatConstant 2    loop:none
-  /// CHECK-DAG:                 Return [<<Float>>] loop:none
-  private static float periodicFloat10() {
-    float r = 4.5f;
-    float s = 2.0f;
-    float t = -1.0f;
-    for (int i = 0; i < 10; i++) {
-      float tmp = t; t = r; r = s; s = tmp;
-    }
-    return r;
-  }
-
-  /// CHECK-START: float Main.periodicFloat11() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: <<Phi3:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: <<Phi4:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
-  //
-  /// CHECK-START: float Main.periodicFloat11() loop_optimization (after)
-  /// CHECK-NOT: Phi
-  //
-  /// CHECK-START: float Main.periodicFloat11() loop_optimization (after)
-  /// CHECK-DAG: <<Float:f\d+>>  FloatConstant -1   loop:none
-  /// CHECK-DAG:                 Return [<<Float>>] loop:none
-  private static float periodicFloat11() {
-    float r = 4.5f;
-    float s = 2.0f;
-    float t = -1.0f;
-    for (int i = 0; i < 11; i++) {
-      float tmp = t; t = r; r = s; s = tmp;
-    }
-    return r;
-  }
-
-  /// CHECK-START: float Main.periodicFloat12() loop_optimization (before)
-  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Phi2:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: <<Phi3:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG: <<Phi4:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
-  //
-  /// CHECK-START: float Main.periodicFloat12() loop_optimization (after)
-  /// CHECK-NOT: Phi
-  //
-  /// CHECK-START: float Main.periodicFloat12() loop_optimization (after)
-  /// CHECK-DAG: <<Float:f\d+>>  FloatConstant 4.5  loop:none
-  /// CHECK-DAG:                 Return [<<Float>>] loop:none
-  private static float periodicFloat12() {
-    float r = 4.5f;
-    float s = 2.0f;
-    float t = -1.0f;
-    for (int i = 0; i < 12; i++) {
-      float tmp = t; t = r; r = s; s = tmp;
-    }
-    return r;
-  }
-
-  private static int exceptionExitBeforeAdd() {
-    int k = 0;
-    try {
-      for (int i = 0; i < 10; i++) {
-        a[i] = 0;
-        k += 10;  // increment last
-      }
-    } catch(Exception e) {
-      // Flag error by returning current
-      // value of k negated.
-      return -k-1;
-    }
-    return k;
-  }
-
-  private static int exceptionExitAfterAdd() {
-    int k = 0;
-    try {
-      for (int i = 0; i < 10; i++) {
-        k += 10;  // increment first
-        a[i] = 0;
-      }
-    } catch(Exception e) {
-      // Flag error by returning current
-      // value of k negated.
-      return -k-1;
-    }
-    return k;
-  }
-
-  public static void main(String[] args) {
-    deadSingleLoop();
-    deadSingleLoopN(4);
-    potentialInfiniteLoop(4);
-    deadNestedLoops();
-    deadNestedAndFollowingLoops();
-    deadConditional(4);
-    deadConditionalCycle(4);
-
-    deadInduction();
-    for (int i = 0; i < a.length; i++) {
-      expectEquals(1, a[i]);
-    }
-    deadManyInduction();
-    for (int i = 0; i < a.length; i++) {
-      expectEquals(2, a[i]);
-    }
-    deadSequence();
-    for (int i = 0; i < a.length; i++) {
-      expectEquals(3, a[i]);
-    }
-    try {
-      deadCycleWithException(-1);
-      throw new Error("Expected: IOOB exception");
-    } catch (IndexOutOfBoundsException e) {
-    }
-    for (int i = 0; i < a.length; i++) {
-      expectEquals(i == 0 ? 4 : 3, a[i]);
-    }
-    deadCycleWithException(0);
-    for (int i = 0; i < a.length; i++) {
-      expectEquals(4, a[i]);
     }
 
-    expectEquals(12395, closedFormInductionUp());
-    expectEquals(12295, closedFormInductionInAndDown(12345));
-    expectEquals(81, closedFormInductionTrivialIf());
-    expectEquals(10 * 10, closedFormNested());
-    expectEquals(12345 + 17 * 23 * 7, closedFormNestedAlt());
-    for (int n = -4; n < 10; n++) {
-      int tc = (n <= 0) ? 0 : n;
-      expectEquals(12345 + tc * 5, closedFormInductionUpN(n));
-      expectEquals(12345 - tc * 5, closedFormInductionInAndDownN(12345, n));
-      expectEquals(tc * 10, closedFormNestedN(n));
-      expectEquals(12345 + tc * 23 * 7, closedFormNestedNAlt(n));
-      expectEquals(tc * (tc + 1), closedFormNestedMN(n, n + 1));
-      expectEquals(12345 + tc * (tc + 1) * 7, closedFormNestedMNAlt(n, n + 1));
+    /// CHECK-START: void Main.deadNestedLoops() loop_optimization (before)
+    /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: Phi loop:{{B\d+}}      outer_loop:<<Loop>>
+    //
+    /// CHECK-START: void Main.deadNestedLoops() loop_optimization (after)
+    /// CHECK-NOT: Phi
+    static void deadNestedLoops() {
+        for (int i = 0; i < 4; i++) {
+            for (int j = 0; j < 4; j++) {
+            }
+        }
     }
 
-    expectEquals(10, mainIndexReturned());
-    expectEquals(1, periodicReturned9());
-    expectEquals(0, periodicReturned10());
-    expectEquals(21, getSum21());
-    expectEquals(354, getIncr2(new int[12]));
-    for (int n = -4; n < 4; n++) {
-      int tc = (n <= 0) ? 0 : n;
-      expectEquals(tc, mainIndexReturnedN(n));
-      expectEquals(tc, mainIndexShort1((short) n));
-      expectEquals(tc, mainIndexShort2((short) n));
-      expectEquals(tc & 1, periodicReturnedN(n));
-      expectEquals((tc * (tc + 1)) / 2, getSumN(n));
+    /// CHECK-START: void Main.deadNestedAndFollowingLoops() loop_optimization (before)
+    /// CHECK-DAG: Phi loop:<<Loop1:B\d+>> outer_loop:none
+    /// CHECK-DAG: Phi loop:<<Loop2:B\d+>> outer_loop:<<Loop1>>
+    /// CHECK-DAG: Phi loop:{{B\d+}}       outer_loop:<<Loop2>>
+    /// CHECK-DAG: Phi loop:{{B\d+}}       outer_loop:<<Loop2>>
+    /// CHECK-DAG: Phi loop:<<Loop3:B\d+>> outer_loop:<<Loop1>>
+    /// CHECK-DAG: Phi loop:{{B\d+}}       outer_loop:<<Loop3>>
+    /// CHECK-DAG: Phi loop:{{B\d+}}       outer_loop:none
+    //
+    /// CHECK-START: void Main.deadNestedAndFollowingLoops() loop_optimization (after)
+    /// CHECK-NOT: Phi
+    static void deadNestedAndFollowingLoops() {
+        for (int i = 0; i < 4; i++) {
+            for (int j = 0; j < 4; j++) {
+                for (int k = 0; k < 4; k++) {
+                }
+                for (int k = 0; k < 4; k++) {
+                }
+            }
+            for (int j = 0; j < 4; j++) {
+                for (int k = 0; k < 4; k++) {
+                }
+            }
+        }
+        for (int i = 0; i < 4; i++) {
+        }
     }
 
-    expectEquals(10, closedTwice());
-    expectEquals(20, closedFeed());
-    expectEquals(-10, closedLargeUp());
-    expectEquals(10, closedLargeDown());
-    expectEquals(50, waterFall());
-
-    expectEquals(false, periodicBoolIdiom1());
-    expectEquals(false, periodicBoolIdiom2());
-    expectEquals(false, periodicBoolIdiom3());
-    for (int n = -4; n < 10; n++) {
-      int tc = (n <= 0) ? 0 : n;
-      boolean even = (tc & 1) == 0;
-      expectEquals(even, periodicBoolIdiom1N(true, n));
-      expectEquals(!even, periodicBoolIdiom1N(false, n));
-      expectEquals(even, periodicBoolIdiom2N(true, n));
-      expectEquals(!even, periodicBoolIdiom2N(false, n));
-      expectEquals(even, periodicBoolIdiom3N(true, n));
-      expectEquals(!even, periodicBoolIdiom3N(false, n));
+    /// CHECK-START: void Main.deadConditional(int) loop_optimization (before)
+    /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
+    //
+    /// CHECK-START: void Main.deadConditional(int) loop_optimization (after)
+    /// CHECK-NOT: Phi
+    public static void deadConditional(int n) {
+        int k = 0;
+        int m = 0;
+        for (int i = 0; i < n; i++) {
+            if (i == 3)
+                k = i;
+            else
+                m = i;
+        }
     }
 
-    expectEquals( 2.0f, periodicFloat10());
-    expectEquals(-1.0f, periodicFloat11());
-    expectEquals( 4.5f, periodicFloat12());
-
-    expectEquals(100, exceptionExitBeforeAdd());
-    expectEquals(100, exceptionExitAfterAdd());
-    a = null;
-    expectEquals(-1, exceptionExitBeforeAdd());
-    expectEquals(-11, exceptionExitAfterAdd());
-    a = new int[4];
-    expectEquals(-41, exceptionExitBeforeAdd());
-    expectEquals(-51, exceptionExitAfterAdd());
-
-    System.out.println("passed");
-  }
-
-  private static void expectEquals(float expected, float result) {
-    if (expected != result) {
-      throw new Error("Expected: " + expected + ", found: " + result);
+    /// CHECK-START: void Main.deadConditionalCycle(int) loop_optimization (before)
+    /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: Phi loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: Phi loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: Phi loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: Phi loop:<<Loop>>      outer_loop:none
+    //
+    /// CHECK-START: void Main.deadConditionalCycle(int) loop_optimization (after)
+    /// CHECK-NOT: Phi
+    public static void deadConditionalCycle(int n) {
+        int k = 0;
+        int m = 0;
+        for (int i = 0; i < n; i++) {
+            if (i == 3)
+                k--;
+            else
+                m++;
+        }
     }
-  }
 
-  private static void expectEquals(int expected, int result) {
-    if (expected != result) {
-      throw new Error("Expected: " + expected + ", found: " + result);
-    }
-  }
 
-  private static void expectEquals(boolean expected, boolean result) {
-    if (expected != result) {
-      throw new Error("Expected: " + expected + ", found: " + result);
+    /// CHECK-START: void Main.deadInduction() loop_optimization (before)
+    /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
+    //
+    /// CHECK-START: void Main.deadInduction() loop_optimization (after)
+    /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-NOT: Phi      loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
+    static void deadInduction() {
+        int dead = 0;
+        for (int i = 0; i < a.length; i++) {
+            a[i] = novec[2 * i] + 1;
+            dead += 5;
+        }
     }
-  }
+
+    /// CHECK-START: void Main.deadManyInduction() loop_optimization (before)
+    /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
+    //
+    /// CHECK-START: void Main.deadManyInduction() loop_optimization (after)
+    /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-NOT: Phi      loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
+    static void deadManyInduction() {
+        int dead1 = 0, dead2 = 1, dead3 = 3;
+        for (int i = 0; i < a.length; i++) {
+            dead1 += 5;
+            a[i] = novec[2 * i] + 2;
+            dead2 += 10;
+            dead3 += 100;
+        }
+    }
+
+    /// CHECK-START: void Main.deadSequence() loop_optimization (before)
+    /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
+    //
+    /// CHECK-START: void Main.deadSequence() loop_optimization (after)
+    /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-NOT: Phi      loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
+    static void deadSequence() {
+        int dead = 0;
+        for (int i = 0; i < a.length; i++) {
+            a[i] = novec[2 * i] + 3;
+            // Increment value defined inside loop,
+            // but sequence itself not used anywhere.
+            dead += i;
+        }
+    }
+
+    /// CHECK-START: void Main.deadCycleWithException(int) loop_optimization (before)
+    /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: Phi      loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
+    /// CHECK-NOT: BoundsCheck
+    //
+    /// CHECK-START: void Main.deadCycleWithException(int) loop_optimization (after)
+    /// CHECK-DAG: Phi      loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-NOT: Phi      loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArraySet loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: ArrayGet loop:<<Loop>>      outer_loop:none
+    /// CHECK-NOT: ArrayGet loop:<<Loop>>      outer_loop:none
+    static void deadCycleWithException(int k) {
+        int dead = 0;
+        for (int i = 0; i < a.length; i++) {
+            a[i] = novec[2 * i] + 4;
+            // Increment value of dead cycle may throw exception. Dynamic
+            // BCE takes care of the bounds check though, which enables
+            // removing the ArrayGet after removing the dead cycle.
+            dead += a[k];
+        }
+    }
+
+    /// CHECK-START: int Main.closedFormInductionUp() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.closedFormInductionUp() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.closedFormInductionUp() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 12395 loop:none
+    /// CHECK-DAG:               Return [<<Int>>]  loop:none
+    static int closedFormInductionUp() {
+        int closed = 12345;
+        for (int i = 0; i < 10; i++) {
+            closed += 5;
+        }
+        return closed;  // only needs last value
+    }
+
+    /// CHECK-START: int Main.closedFormInductionInAndDown(int) loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+    //
+    /// CHECK-START: int Main.closedFormInductionInAndDown(int) loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.closedFormInductionInAndDown(int) instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Par:i\d+>>  ParameterValue        loop:none
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant -50       loop:none
+    /// CHECK-DAG: <<Add:i\d+>>  Add [<<Int>>,<<Par>>] loop:none
+    /// CHECK-DAG:               Return [<<Add>>]      loop:none
+    static int closedFormInductionInAndDown(int closed) {
+        for (int i = 0; i < 10; i++) {
+            closed -= 5;
+        }
+        return closed;  // only needs last value
+    }
+
+    /// CHECK-START: int Main.closedFormInductionTrivialIf() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Select            loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.closedFormInductionTrivialIf() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    /// CHECK-NOT:               Select
+    //
+    /// CHECK-START: int Main.closedFormInductionTrivialIf() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 81    loop:none
+    /// CHECK-DAG:               Return [<<Int>>]  loop:none
+    static int closedFormInductionTrivialIf() {
+        int closed = 11;
+        for (int i = 0; i < 10; i++) {
+            // Trivial if becomes trivial select at HIR level.
+            // Make sure this is still recognized as induction.
+            if (i < 5) {
+                closed += 7;
+            } else {
+                closed += 7;
+            }
+        }
+        return closed;  // only needs last value
+    }
+
+    /// CHECK-START: int Main.closedFormNested() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop1:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop1>>      outer_loop:none
+    /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:<<Loop1>>
+    /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop2>>      outer_loop:<<Loop1>>
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.closedFormNested() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.closedFormNested() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 100  loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    static int closedFormNested() {
+        int closed = 0;
+        for (int i = 0; i < 10; i++) {
+            for (int j = 0; j < 10; j++) {
+                closed++;
+            }
+        }
+        return closed;  // only needs last-value
+    }
+
+    /// CHECK-START: int Main.closedFormNestedAlt() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop1:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop1>>      outer_loop:none
+    /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:<<Loop1>>
+    /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop2>>      outer_loop:<<Loop1>>
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.closedFormNestedAlt() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.closedFormNestedAlt() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 15082 loop:none
+    /// CHECK-DAG:               Return [<<Int>>]  loop:none
+    static int closedFormNestedAlt() {
+        int closed = 12345;
+        for (int i = 0; i < 17; i++) {
+            for (int j = 0; j < 23; j++) {
+                closed += 7;
+            }
+        }
+        return closed;  // only needs last-value
+    }
+
+    // TODO: taken test around closed form?
+    static int closedFormInductionUpN(int n) {
+        int closed = 12345;
+        for (int i = 0; i < n; i++) {
+            closed += 5;
+        }
+        return closed;  // only needs last value
+    }
+
+    // TODO: taken test around closed form?
+    static int closedFormInductionInAndDownN(int closed, int n) {
+        for (int i = 0; i < n; i++) {
+            closed -= 5;
+        }
+        return closed;  // only needs last value
+    }
+
+    // TODO: move closed form even further out?
+    static int closedFormNestedN(int n) {
+        int closed = 0;
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < 10; j++) {
+                closed++;
+            }
+        }
+        return closed;  // only needs last-value
+    }
+
+    // TODO: move closed form even further out?
+    static int closedFormNestedNAlt(int n) {
+        int closed = 12345;
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < 23; j++) {
+                closed += 7;
+            }
+        }
+        return closed;  // only needs last-value
+    }
+
+    // TODO: move closed form even further out?
+    static int closedFormNestedMN(int m, int n) {
+        int closed = 0;
+        for (int i = 0; i < m; i++) {
+            for (int j = 0; j < n; j++) {
+                closed++;
+            }
+        }
+        return closed;  // only needs last-value
+    }
+
+    // TODO: move closed form even further out?
+    static int closedFormNestedMNAlt(int m, int n) {
+        int closed = 12345;
+        for (int i = 0; i < m; i++) {
+            for (int j = 0; j < n; j++) {
+                closed += 7;
+            }
+        }
+        return closed;  // only needs last-value
+    }
+
+    /// CHECK-START: int Main.mainIndexReturned() loop_optimization (before)
+    /// CHECK-DAG: <<Phi:i\d+>> Phi              loop:{{B\d+}} outer_loop:none
+    /// CHECK-DAG:              Return [<<Phi>>] loop:none
+    //
+    /// CHECK-START: int Main.mainIndexReturned() loop_optimization (after)
+    /// CHECK-NOT:              Phi
+    //
+    /// CHECK-START: int Main.mainIndexReturned() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 10   loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    static int mainIndexReturned() {
+        int i;
+        for (i = 0; i < 10; i++);
+        return i;
+    }
+
+    /// CHECK-START: int Main.periodicReturned9() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.periodicReturned9() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.periodicReturned9() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 1    loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    static int periodicReturned9() {
+        int k = 0;
+        for (int i = 0; i < 9; i++) {
+            k = 1 - k;
+        }
+        return k;
+    }
+
+    /// CHECK-START: int Main.periodicReturned10() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.periodicReturned10() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.periodicReturned10() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    static int periodicReturned10() {
+        int k = 0;
+        for (int i = 0; i < 10; i++) {
+            k = 1 - k;
+        }
+        return k;
+    }
+
+    /// CHECK-START: int Main.getSum21() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+    //
+    /// CHECK-START: int Main.getSum21() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.getSum21() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 21   loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    private static int getSum21() {
+        int k = 0;
+        int sum = 0;
+        for (int i = 0; i < 6; i++) {
+            k++;
+            sum += k;
+        }
+        return sum;
+    }
+
+    // Ensure double induction does not "overshoot" the subscript range.
+    private static int getIncr2(int[] arr) {
+        for (int i = 0; i < 12; ) {
+            arr[i++] = 30;
+            arr[i++] = 29;
+        }
+        int sum = 0;
+        for (int i = 0; i < 12; i++) {
+            sum += arr[i];
+        }
+        return sum;
+    }
+
+    // TODO: handle as closed/empty eventually?
+    static int mainIndexReturnedN(int n) {
+        int i;
+        for (i = 0; i < n; i++);
+        return i;
+    }
+
+    // TODO: handle as closed/empty eventually?
+    static int mainIndexShort1(short s) {
+        int i = 0;
+        for (i = 0; i < s; i++) { }
+        return i;
+    }
+
+    // TODO: handle as closed/empty eventually?
+    static int mainIndexShort2(short s) {
+        int i = 0;
+        for (i = 0; s > i; i++) { }
+        return i;
+    }
+
+    /// CHECK-START: int Main.periodicReturnedN(int) loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.periodicReturnedN(int) loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    static int periodicReturnedN(int n) {
+        int k = 0;
+        for (int i = 0; i < n; i++) {
+            k = 1 - k;
+        }
+        return k;
+    }
+
+    // If ever replaced by closed form, last value should be correct!
+    private static int getSumN(int n) {
+        int k = 0;
+        int sum = 0;
+        for (int i = 0; i < n; i++) {
+            k++;
+            sum += k;
+        }
+        return sum;
+    }
+
+    // If ever replaced by closed form, last value should be correct!
+    private static int closedTwice() {
+        int closed = 0;
+        for (int i = 0; i < 10; i++) {
+            closed++;
+        }
+        // Closed form of first loop defines trip count of second loop.
+        int other_closed = 0;
+        for (int i = 0; i < closed; i++) {
+            other_closed++;
+        }
+        return other_closed;
+    }
+
+    /// CHECK-START: int Main.closedFeed() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop1:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop1>>      outer_loop:none
+    /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop2>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi3>>] loop:none
+    /// CHECK-EVAL: "<<Loop1>>" != "<<Loop2>>"
+    //
+    /// CHECK-START: int Main.closedFeed() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.closedFeed() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 20   loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    private static int closedFeed() {
+        int closed = 0;
+        for (int i = 0; i < 10; i++) {
+            closed++;
+        }
+        // Closed form of first loop feeds into initial value of second loop,
+        // used when generating closed form for the latter.
+        for (int i = 0; i < 10; i++) {
+            closed++;
+        }
+        return closed;
+    }
+
+    /// CHECK-START: int Main.closedLargeUp() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.closedLargeUp() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.closedLargeUp() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant -10  loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    private static int closedLargeUp() {
+        int closed = 0;
+        for (int i = 0; i < 10; i++) {
+            closed += 0x7fffffff;
+        }
+        return closed;
+    }
+
+    /// CHECK-START: int Main.closedLargeDown() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.closedLargeDown() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.closedLargeDown() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 10   loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    private static int closedLargeDown() {
+        int closed = 0;
+        for (int i = 0; i < 10; i++) {
+            closed -= 0x7fffffff;
+        }
+        return closed;
+    }
+
+    // Checks that we do not loop optimize if the calculation of the trip count would overflow.
+    /// CHECK-START: int Main.closedLinearStepOverflow() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.closedLinearStepOverflow() loop_optimization (after)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    private static int closedLinearStepOverflow() {
+        int closed = 0;
+        // Note that this isn't a "one-off" error.
+        // We are using MIN and MAX to make sure we overflow.
+        for (int i = Integer.MIN_VALUE; i < (Integer.MAX_VALUE - 80); i += 79) {
+            closed++;
+        }
+        return closed;
+    }
+
+    // Since we cannot guarantee that the start/end wouldn't overflow we do not perform loop
+    // optimization.
+    /// CHECK-START: int Main.$inline$closedByParameters(int, int) loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.$inline$closedByParameters(int, int) loop_optimization (after)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    private static int $inline$closedByParameters(int start, int end) {
+        int closed = 0;
+        for (int i = start; i < end; i++) {
+            closed++;
+        }
+        return closed;
+    }
+
+    // Since we are inlining `closedByParameters` we know that the parameters are fixed and
+    // therefore we can perform loop optimization.
+    /// CHECK-START: int Main.closedByParametersWithInline() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: int Main.closedByParametersWithInline() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.closedByParametersWithInline() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 10   loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    private static int closedByParametersWithInline() {
+        return $inline$closedByParameters(0, 10);
+    }
+
+    /// CHECK-START: int Main.waterFall() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop1:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop3:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop4:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi5:i\d+>> Phi               loop:<<Loop5:B\d+>> outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi5>>] loop:none
+    //
+    /// CHECK-START: int Main.waterFall() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: int Main.waterFall() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 50   loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    private static int waterFall() {
+        int i = 0;
+        for (; i < 10; i++);
+        for (; i < 20; i++);
+        for (; i < 30; i++);
+        for (; i < 40; i++);
+        for (; i < 50; i++);
+        return i;  // this should become just 50
+    }
+
+    /// CHECK-START: boolean Main.periodicBoolIdiom1() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: boolean Main.periodicBoolIdiom1() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: boolean Main.periodicBoolIdiom1() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    private static boolean periodicBoolIdiom1() {
+        boolean x = true;
+        for (int i = 0; i < 7; i++) {
+            x = !x;
+        }
+        return x;
+    }
+
+    /// CHECK-START: boolean Main.periodicBoolIdiom2() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: boolean Main.periodicBoolIdiom2() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: boolean Main.periodicBoolIdiom2() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    private static boolean periodicBoolIdiom2() {
+        boolean x = true;
+        for (int i = 0; i < 7; i++) {
+            x = (x != true);
+        }
+        return x;
+    }
+
+    /// CHECK-START: boolean Main.periodicBoolIdiom3() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+    //
+    /// CHECK-START: boolean Main.periodicBoolIdiom3() loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    //
+    /// CHECK-START: boolean Main.periodicBoolIdiom3() instruction_simplifier$before_codegen (after)
+    /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
+    /// CHECK-DAG:               Return [<<Int>>] loop:none
+    private static boolean periodicBoolIdiom3() {
+        boolean x = true;
+        for (int i = 0; i < 7; i++) {
+            x = (x == false);
+        }
+        return x;
+    }
+
+    /// CHECK-START: boolean Main.periodicBoolIdiom1N(boolean, int) loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+    //
+    /// CHECK-START: boolean Main.periodicBoolIdiom1N(boolean, int) loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    private static boolean periodicBoolIdiom1N(boolean x, int n) {
+        for (int i = 0; i < n; i++) {
+            x = !x;
+        }
+        return x;
+    }
+
+    /// CHECK-START: boolean Main.periodicBoolIdiom2N(boolean, int) loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+    //
+    /// CHECK-START: boolean Main.periodicBoolIdiom2N(boolean, int) loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    private static boolean periodicBoolIdiom2N(boolean x, int n) {
+        for (int i = 0; i < n; i++) {
+            x = (x != true);
+        }
+        return x;
+    }
+
+    /// CHECK-START: boolean Main.periodicBoolIdiom3N(boolean, int) loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+    //
+    /// CHECK-START: boolean Main.periodicBoolIdiom3N(boolean, int) loop_optimization (after)
+    /// CHECK-NOT:               Phi
+    private static boolean periodicBoolIdiom3N(boolean x, int n) {
+        for (int i = 0; i < n; i++) {
+            x = (x == false);
+        }
+        return x;
+    }
+
+    /// CHECK-START: float Main.periodicFloat10() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: <<Phi3:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: <<Phi4:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+    //
+    /// CHECK-START: float Main.periodicFloat10() loop_optimization (after)
+    /// CHECK-NOT: Phi
+    //
+    /// CHECK-START: float Main.periodicFloat10() loop_optimization (after)
+    /// CHECK-DAG: <<Float:f\d+>>  FloatConstant 2    loop:none
+    /// CHECK-DAG:                 Return [<<Float>>] loop:none
+    private static float periodicFloat10() {
+        float r = 4.5f;
+        float s = 2.0f;
+        float t = -1.0f;
+        for (int i = 0; i < 10; i++) {
+            float tmp = t;
+            t = r;
+            r = s;
+            s = tmp;
+        }
+        return r;
+    }
+
+    /// CHECK-START: float Main.periodicFloat11() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: <<Phi3:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: <<Phi4:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+    //
+    /// CHECK-START: float Main.periodicFloat11() loop_optimization (after)
+    /// CHECK-NOT: Phi
+    //
+    /// CHECK-START: float Main.periodicFloat11() loop_optimization (after)
+    /// CHECK-DAG: <<Float:f\d+>>  FloatConstant -1   loop:none
+    /// CHECK-DAG:                 Return [<<Float>>] loop:none
+    private static float periodicFloat11() {
+        float r = 4.5f;
+        float s = 2.0f;
+        float t = -1.0f;
+        for (int i = 0; i < 11; i++) {
+            float tmp = t;
+            t = r;
+            r = s;
+            s = tmp;
+        }
+        return r;
+    }
+
+    /// CHECK-START: float Main.periodicFloat12() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: <<Phi3:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG: <<Phi4:f\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+    //
+    /// CHECK-START: float Main.periodicFloat12() loop_optimization (after)
+    /// CHECK-NOT: Phi
+    //
+    /// CHECK-START: float Main.periodicFloat12() loop_optimization (after)
+    /// CHECK-DAG: <<Float:f\d+>>  FloatConstant 4.5  loop:none
+    /// CHECK-DAG:                 Return [<<Float>>] loop:none
+    private static float periodicFloat12() {
+        float r = 4.5f;
+        float s = 2.0f;
+        float t = -1.0f;
+        for (int i = 0; i < 12; i++) {
+            float tmp = t;
+            t = r;
+            r = s;
+            s = tmp;
+        }
+        return r;
+    }
+
+    private static int exceptionExitBeforeAdd() {
+        int k = 0;
+        try {
+            for (int i = 0; i < 10; i++) {
+                a[i] = 0;
+                k += 10;  // increment last
+            }
+        } catch (Exception e) {
+            // Flag error by returning current
+            // value of k negated.
+            return -k - 1;
+        }
+        return k;
+    }
+
+    private static int exceptionExitAfterAdd() {
+        int k = 0;
+        try {
+            for (int i = 0; i < 10; i++) {
+                k += 10;  // increment first
+                a[i] = 0;
+            }
+        } catch (Exception e) {
+            // Flag error by returning current
+            // value of k negated.
+            return -k - 1;
+        }
+        return k;
+    }
+
+    /// CHECK-START: long Main.closedLinearInductionUnmatchedTypesNotOptimized() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:j\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    //
+    /// CHECK-START: long Main.closedLinearInductionUnmatchedTypesNotOptimized() loop_optimization (after)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    /// CHECK-DAG: <<Phi2:j\d+>> Phi               loop:<<Loop>>      outer_loop:none
+    private static long closedLinearInductionUnmatchedTypesNotOptimized() {
+        long sum = 0;
+        for (int i = 0; i < 10; ++i) {
+            ++sum;
+        }
+        return sum;
+    }
+
+    /// CHECK-START: short Main.closedLinearInductionNarrowingNotOptimized() loop_optimization (before)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    //
+    /// CHECK-START: short Main.closedLinearInductionNarrowingNotOptimized() loop_optimization (after)
+    /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+    private static short closedLinearInductionNarrowingNotOptimized() {
+        short i = 0;
+        for (; i < 10; ++i);
+        return i;
+    }
+
+    public static void main(String[] args) {
+        deadSingleLoop();
+        deadSingleLoopN(4);
+        potentialInfiniteLoop(4);
+        deadNestedLoops();
+        deadNestedAndFollowingLoops();
+        deadConditional(4);
+        deadConditionalCycle(4);
+
+        deadInduction();
+        for (int i = 0; i < a.length; i++) {
+            expectEquals(1, a[i]);
+        }
+        deadManyInduction();
+        for (int i = 0; i < a.length; i++) {
+            expectEquals(2, a[i]);
+        }
+        deadSequence();
+        for (int i = 0; i < a.length; i++) {
+            expectEquals(3, a[i]);
+        }
+        try {
+            deadCycleWithException(-1);
+            throw new Error("Expected: IOOB exception");
+        } catch (IndexOutOfBoundsException e) {
+        }
+        for (int i = 0; i < a.length; i++) {
+            expectEquals(i == 0 ? 4 : 3, a[i]);
+        }
+        deadCycleWithException(0);
+        for (int i = 0; i < a.length; i++) {
+            expectEquals(4, a[i]);
+        }
+
+        expectEquals(12395, closedFormInductionUp());
+        expectEquals(12295, closedFormInductionInAndDown(12345));
+        expectEquals(81, closedFormInductionTrivialIf());
+        expectEquals(10 * 10, closedFormNested());
+        expectEquals(12345 + 17 * 23 * 7, closedFormNestedAlt());
+        for (int n = -4; n < 10; n++) {
+            int tc = (n <= 0) ? 0 : n;
+            expectEquals(12345 + tc * 5, closedFormInductionUpN(n));
+            expectEquals(12345 - tc * 5, closedFormInductionInAndDownN(12345, n));
+            expectEquals(tc * 10, closedFormNestedN(n));
+            expectEquals(12345 + tc * 23 * 7, closedFormNestedNAlt(n));
+            expectEquals(tc * (tc + 1), closedFormNestedMN(n, n + 1));
+            expectEquals(12345 + tc * (tc + 1) * 7, closedFormNestedMNAlt(n, n + 1));
+        }
+
+        expectEquals(10, mainIndexReturned());
+        expectEquals(1, periodicReturned9());
+        expectEquals(0, periodicReturned10());
+        expectEquals(21, getSum21());
+        expectEquals(354, getIncr2(new int[12]));
+        for (int n = -4; n < 4; n++) {
+            int tc = (n <= 0) ? 0 : n;
+            expectEquals(tc, mainIndexReturnedN(n));
+            expectEquals(tc, mainIndexShort1((short) n));
+            expectEquals(tc, mainIndexShort2((short) n));
+            expectEquals(tc & 1, periodicReturnedN(n));
+            expectEquals((tc * (tc + 1)) / 2, getSumN(n));
+        }
+
+        expectEquals(10, closedTwice());
+        expectEquals(20, closedFeed());
+        expectEquals(-10, closedLargeUp());
+        expectEquals(10, closedLargeDown());
+        expectEquals(54366674, closedLinearStepOverflow());
+        expectEquals(10, $inline$closedByParameters(0, 10));
+        expectEquals(10, closedByParametersWithInline());
+        expectEquals(50, waterFall());
+
+        expectEquals(false, periodicBoolIdiom1());
+        expectEquals(false, periodicBoolIdiom2());
+        expectEquals(false, periodicBoolIdiom3());
+        for (int n = -4; n < 10; n++) {
+            int tc = (n <= 0) ? 0 : n;
+            boolean even = (tc & 1) == 0;
+            expectEquals(even, periodicBoolIdiom1N(true, n));
+            expectEquals(!even, periodicBoolIdiom1N(false, n));
+            expectEquals(even, periodicBoolIdiom2N(true, n));
+            expectEquals(!even, periodicBoolIdiom2N(false, n));
+            expectEquals(even, periodicBoolIdiom3N(true, n));
+            expectEquals(!even, periodicBoolIdiom3N(false, n));
+        }
+
+        expectEquals( 2.0f, periodicFloat10());
+        expectEquals(-1.0f, periodicFloat11());
+        expectEquals( 4.5f, periodicFloat12());
+
+        expectEquals(100, exceptionExitBeforeAdd());
+        expectEquals(100, exceptionExitAfterAdd());
+        a = null;
+        expectEquals(-1, exceptionExitBeforeAdd());
+        expectEquals(-11, exceptionExitAfterAdd());
+        a = new int[4];
+        expectEquals(-41, exceptionExitBeforeAdd());
+        expectEquals(-51, exceptionExitAfterAdd());
+
+        expectEquals(10, closedLinearInductionUnmatchedTypesNotOptimized());
+        expectEquals(10, closedLinearInductionNarrowingNotOptimized());
+
+        System.out.println("passed");
+    }
+
+    private static void expectEquals(float expected, float result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+
+    private static void expectEquals(int expected, int result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
+
+    private static void expectEquals(boolean expected, boolean result) {
+        if (expected != result) {
+            throw new Error("Expected: " + expected + ", found: " + result);
+        }
+    }
 }
diff --git a/test/619-checker-current-method/src/Main.java b/test/619-checker-current-method/src/Main.java
index d829370..3ab73fa 100644
--- a/test/619-checker-current-method/src/Main.java
+++ b/test/619-checker-current-method/src/Main.java
@@ -15,19 +15,18 @@
  */
 
 public class Main {
+    // Check that there is no instruction storing to stack.
+    /// CHECK-START-X86: int Main.foo(int, int, int, int, int, int) disassembly (after)
+    /// CHECK-NOT:  mov [{{\w+}}], {{\w+}}
 
-  // Check that there is no instruction storing to stack.
-  /// CHECK-START-X86: int Main.foo(int, int, int, int, int, int) disassembly (after)
-  /// CHECK-NOT:  mov [{{\w+}}], {{\w+}}
-
-  // Use enough parameters to ensure we'll need a frame.
-  public static int foo(int a, int b, int c, int d, int e, int f) {
-    return a + b + c + d + e + f;
-  }
-
-  public static void main(String[] args) {
-    if (foo(1, 2, 3, 4, 5, 6) != 21) {
-      throw new Error("Expected 21");
+    // Use enough parameters to ensure we'll need a frame.
+    public static int foo(int a, int b, int c, int d, int e, int f) {
+        return a + b + c + d + e + f;
     }
-  }
+
+    public static void main(String[] args) {
+        if (foo(1, 2, 3, 4, 5, 6) != 21) {
+            throw new Error("Expected 21");
+        }
+    }
 }
diff --git a/test/623-checker-loop-regressions/src/Main.java b/test/623-checker-loop-regressions/src/Main.java
index 3f3a12c..2b280bb 100644
--- a/test/623-checker-loop-regressions/src/Main.java
+++ b/test/623-checker-loop-regressions/src/Main.java
@@ -155,7 +155,7 @@
   /// CHECK-START: int Main.polynomialInt() loop_optimization (after)
   /// CHECK-NOT: Phi
   //
-  /// CHECK-START: int Main.polynomialInt() instruction_simplifier$after_bce (after)
+  /// CHECK-START: int Main.polynomialInt() instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Int:i\d+>>  IntConstant -45  loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   static int polynomialInt() {
@@ -176,7 +176,7 @@
   /// CHECK-START: int Main.geoIntDivLastValue(int) loop_optimization (after)
   /// CHECK-NOT: Phi
   //
-  /// CHECK-START: int Main.geoIntDivLastValue(int) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int Main.geoIntDivLastValue(int) instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Int:i\d+>> IntConstant 0    loop:none
   /// CHECK-DAG:              Return [<<Int>>] loop:none
   static int geoIntDivLastValue(int x) {
@@ -193,7 +193,7 @@
   /// CHECK-START: int Main.geoIntMulLastValue(int) loop_optimization (after)
   /// CHECK-NOT: Phi
   //
-  /// CHECK-START: int Main.geoIntMulLastValue(int) instruction_simplifier$after_bce (after)
+  /// CHECK-START: int Main.geoIntMulLastValue(int) instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Par:i\d+>> ParameterValue         loop:none
   /// CHECK-DAG: <<Int:i\d+>> IntConstant -194211840 loop:none
   /// CHECK-DAG: <<Mul:i\d+>> Mul [<<Par>>,<<Int>>]  loop:none
@@ -212,7 +212,7 @@
   /// CHECK-START: long Main.geoLongDivLastValue(long) loop_optimization (after)
   /// CHECK-NOT: Phi
   //
-  /// CHECK-START: long Main.geoLongDivLastValue(long) instruction_simplifier$after_bce (after)
+  /// CHECK-START: long Main.geoLongDivLastValue(long) instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Long:j\d+>> LongConstant 0    loop:none
   /// CHECK-DAG:               Return [<<Long>>] loop:none
   //
@@ -231,7 +231,7 @@
   /// CHECK-START: long Main.geoLongDivLastValue() loop_optimization (after)
   /// CHECK-NOT: Phi
   //
-  /// CHECK-START: long Main.geoLongDivLastValue() instruction_simplifier$after_bce (after)
+  /// CHECK-START: long Main.geoLongDivLastValue() instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Long:j\d+>> LongConstant 0    loop:none
   /// CHECK-DAG:               Return [<<Long>>] loop:none
   //
@@ -251,7 +251,7 @@
   /// CHECK-START: long Main.geoLongMulLastValue(long) loop_optimization (after)
   /// CHECK-NOT: Phi
   //
-  /// CHECK-START: long Main.geoLongMulLastValue(long) instruction_simplifier$after_bce (after)
+  /// CHECK-START: long Main.geoLongMulLastValue(long) instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Par:j\d+>>  ParameterValue                    loop:none
   /// CHECK-DAG: <<Long:j\d+>> LongConstant -8070450532247928832 loop:none
   /// CHECK-DAG: <<Mul:j\d+>>  Mul [<<Par>>,<<Long>>]            loop:none
diff --git a/test/626-const-class-linking/clear_dex_cache_types.cc b/test/626-const-class-linking/clear_dex_cache_types.cc
index 1aa3cce..ef230ad 100644
--- a/test/626-const-class-linking/clear_dex_cache_types.cc
+++ b/test/626-const-class-linking/clear_dex_cache_types.cc
@@ -28,8 +28,7 @@
   ScopedObjectAccess soa(Thread::Current());
   ObjPtr<mirror::DexCache> dex_cache = soa.Decode<mirror::Class>(cls)->GetDexCache();
   for (size_t i = 0, num_types = dex_cache->NumResolvedTypes(); i != num_types; ++i) {
-    mirror::TypeDexCachePair cleared(nullptr, mirror::TypeDexCachePair::InvalidIndexForSlot(i));
-    dex_cache->GetResolvedTypes()[i].store(cleared, std::memory_order_relaxed);
+    dex_cache->GetResolvedTypes()->Clear(i);
   }
 }
 
diff --git a/test/627-checker-unroll/src/Main.java b/test/627-checker-unroll/src/Main.java
index 9785bdc..413de8e 100644
--- a/test/627-checker-unroll/src/Main.java
+++ b/test/627-checker-unroll/src/Main.java
@@ -29,7 +29,7 @@
   /// CHECK-START: void Main.unroll() loop_optimization (after)
   /// CHECK-DAG: StaticFieldSet loop:none
   //
-  /// CHECK-START: void Main.unroll() instruction_simplifier$after_bce (after)
+  /// CHECK-START: void Main.unroll() instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Int:i\d+>> IntConstant    68                  loop:none
   /// CHECK-DAG:              StaticFieldSet [{{l\d+}},<<Int>>]  loop:none
   //
@@ -49,7 +49,7 @@
   /// CHECK-START: int Main.unrollLV() loop_optimization (after)
   /// CHECK-DAG: StaticFieldSet loop:none
   //
-  /// CHECK-START: int Main.unrollLV() instruction_simplifier$after_bce (after)
+  /// CHECK-START: int Main.unrollLV() instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Int1:i\d+>> IntConstant    187                 loop:none
   /// CHECK-DAG: <<Int2:i\d+>> IntConstant    12                  loop:none
   /// CHECK-DAG:               StaticFieldSet [{{l\d+}},<<Int1>>] loop:none
@@ -80,7 +80,7 @@
   /// CHECK-DAG: SuspendCheck   loop:none
   /// CHECK-NOT: SuspendCheck
   //
-  /// CHECK-START: void Main.unrollNest() instruction_simplifier$after_bce (after)
+  /// CHECK-START: void Main.unrollNest() instruction_simplifier$before_codegen (after)
   /// CHECK-DAG: <<Int:i\d+>> IntConstant    6                   loop:none
   /// CHECK-DAG:              StaticFieldSet [{{l\d+}},<<Int>>]  loop:none
   //
diff --git a/test/628-vdex/run b/test/628-vdex/run
deleted file mode 100644
index bf0ac91..0000000
--- a/test/628-vdex/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-exec ${RUN} -Xcompiler-option --compiler-filter=verify --vdex "${@}"
diff --git a/test/628-vdex/run.py b/test/628-vdex/run.py
new file mode 100644
index 0000000..066152c
--- /dev/null
+++ b/test/628-vdex/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args, Xcompiler_option=["--compiler-filter=verify"], vdex=True)
diff --git a/test/629-vdex-speed/run b/test/629-vdex-speed/run
deleted file mode 100644
index 1477e3d..0000000
--- a/test/629-vdex-speed/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-exec ${RUN} --vdex --vdex-filter speed "${@}"
diff --git a/test/629-vdex-speed/run.py b/test/629-vdex-speed/run.py
new file mode 100644
index 0000000..1d4f199
--- /dev/null
+++ b/test/629-vdex-speed/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, vdex=True, vdex_filter="speed")
diff --git a/test/634-vdex-duplicate/run b/test/634-vdex-duplicate/run
deleted file mode 100644
index 571ccd9..0000000
--- a/test/634-vdex-duplicate/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2016 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.
-
-exec ${RUN} -Xcompiler-option --compiler-filter=verify --vdex-filter speed --vdex "${@}"
diff --git a/test/634-vdex-duplicate/run.py b/test/634-vdex-duplicate/run.py
new file mode 100644
index 0000000..c99522d
--- /dev/null
+++ b/test/634-vdex-duplicate/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args,
+      Xcompiler_option=["--compiler-filter=verify"],
+      vdex_filter="speed",
+      vdex=True)
diff --git a/test/636-wrong-static-access/run b/test/636-wrong-static-access/run
deleted file mode 100755
index 5e99920..0000000
--- a/test/636-wrong-static-access/run
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Make verification soft fail, to ensure the verifier does not flag
-# the method we want to compile as "non-compilable" because it sees
-# the method will throw IncompatibleClassChangeError.
-exec ${RUN} $@ --verify-soft-fail
diff --git a/test/636-wrong-static-access/run.py b/test/636-wrong-static-access/run.py
new file mode 100644
index 0000000..70a7fca
--- /dev/null
+++ b/test/636-wrong-static-access/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Make verification soft fail, to ensure the verifier does not flag
+  # the method we want to compile as "non-compilable" because it sees
+  # the method will throw IncompatibleClassChangeError.
+  ctx.default_run(args, verify_soft_fail=True)
diff --git a/test/638-checker-inline-cache-intrinsic/run b/test/638-checker-inline-cache-intrinsic/run
deleted file mode 100644
index 9016107..0000000
--- a/test/638-checker-inline-cache-intrinsic/run
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Set threshold to 1000 to match the iterations done in the test.
-# Pass --verbose-methods to only generate the CFG of these methods.
-# The test is for JIT, but we run in "optimizing" (AOT) mode, so that the Checker
-# stanzas in test/638-checker-inline-cache-intrinsic/src/Main.java will be checked.
-# Also pass a large JIT code cache size to avoid getting the inline caches GCed.
-exec ${RUN} --jit --runtime-option -Xjitinitialsize:32M --runtime-option -Xjitthreshold:1000 -Xcompiler-option --verbose-methods=inlineMonomorphic,inlinePolymorphic,knownReceiverType,stringEquals $@
diff --git a/test/638-checker-inline-cache-intrinsic/run.py b/test/638-checker-inline-cache-intrinsic/run.py
new file mode 100644
index 0000000..48f0f3f
--- /dev/null
+++ b/test/638-checker-inline-cache-intrinsic/run.py
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Set threshold to 1000 to match the iterations done in the test.
+  # Pass --verbose-methods to only generate the CFG of these methods.
+  # The test is for JIT, but we run in "optimizing" (AOT) mode, so that the Checker
+  # stanzas in test/638-checker-inline-cache-intrinsic/src/Main.java will be checked.
+  # Also pass a large JIT code cache size to avoid getting the inline caches GCed.
+  ctx.default_run(
+      args,
+      jit=True,
+      runtime_option=["-Xjitinitialsize:32M", "-Xjitthreshold:1000"],
+      Xcompiler_option=[
+          "--verbose-methods=inlineMonomorphic,inlinePolymorphic,knownReceiverType,stringEquals"
+      ])
diff --git a/test/638-checker-inline-caches/expected-stdout.txt b/test/638-checker-inline-caches/expected-stdout.txt
index e69de29..8f1cb06 100644
--- a/test/638-checker-inline-caches/expected-stdout.txt
+++ b/test/638-checker-inline-caches/expected-stdout.txt
@@ -0,0 +1 @@
+I don't throw
diff --git a/test/638-checker-inline-caches/profile b/test/638-checker-inline-caches/profile
index 7756a16..4630a8d 100644
--- a/test/638-checker-inline-caches/profile
+++ b/test/638-checker-inline-caches/profile
@@ -4,3 +4,4 @@
 HSLMain;->inlineMegamorphic(LSuper;)I+LSubA;,LSubB;,LSubC;,LSubD;,LSubE;
 HSLMain;->inlineMissingTypes(LSuper;)I+missing_types
 HSLMain;->noInlineCache(LSuper;)I
+HSLMain;->noInlineSomeSubclassesThrow(LSuper;)V+LSubA;
diff --git a/test/638-checker-inline-caches/run b/test/638-checker-inline-caches/run
deleted file mode 100644
index 146e180..0000000
--- a/test/638-checker-inline-caches/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile
diff --git a/test/638-checker-inline-caches/run.py b/test/638-checker-inline-caches/run.py
new file mode 100644
index 0000000..7678899
--- /dev/null
+++ b/test/638-checker-inline-caches/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args, profile=True, Xcompiler_option=["--compiler-filter=speed-profile"])
diff --git a/test/638-checker-inline-caches/src-multidex/SubC.java b/test/638-checker-inline-caches/src-multidex/SubC.java
index f7e3c08..e0f571e 100644
--- a/test/638-checker-inline-caches/src-multidex/SubC.java
+++ b/test/638-checker-inline-caches/src-multidex/SubC.java
@@ -16,4 +16,5 @@
 
 public class SubC extends Super   {
   public int getValue() { return 24; }
+  void someSubclassesThrow() { System.out.println("I don't throw"); }
 }
diff --git a/test/638-checker-inline-caches/src/Main.java b/test/638-checker-inline-caches/src/Main.java
index f104e6a..7a0fb26 100644
--- a/test/638-checker-inline-caches/src/Main.java
+++ b/test/638-checker-inline-caches/src/Main.java
@@ -16,18 +16,22 @@
 
 class SubA extends Super {
   int getValue() { return 42; }
+  void someSubclassesThrow() throws Error { throw new Error("I always throw"); }
 }
 
 class SubB extends Super {
   int getValue() { return 38; }
+  void someSubclassesThrow() { System.out.println("I don't throw"); }
 }
 
 class SubD extends Super {
   int getValue() { return 10; }
+  void someSubclassesThrow() { System.out.println("I don't throw"); }
 }
 
 class SubE extends Super {
   int getValue() { return -4; }
+  void someSubclassesThrow() { System.out.println("I don't throw"); }
 }
 
 public class Main {
@@ -134,6 +138,20 @@
     return a.getValue();
   }
 
+  // We shouldn't inline `someSubclassesThrow` since we are trying a monomorphic inline and it
+  // always throws for SubA. However, we shouldn't mark it as `always_throws` since we speculatively
+  // tried to inline and other subclasses (e.g. SubB) can call noInlineSomeSubclassesThrow and they
+  // don't throw.
+
+  /// CHECK-START: void Main.noInlineSomeSubclassesThrow(Super) inliner (before)
+  /// CHECK:       InvokeVirtual method_name:Super.someSubclassesThrow always_throws:false
+
+  /// CHECK-START: void Main.noInlineSomeSubclassesThrow(Super) inliner (after)
+  /// CHECK:       InvokeVirtual method_name:Super.someSubclassesThrow always_throws:false
+  public static void noInlineSomeSubclassesThrow(Super a) throws Error {
+    a.someSubclassesThrow();
+  }
+
   public static void testInlineMonomorphic() {
     if (inlineMonomorphicSubA(new SubA()) != 42) {
       throw new Error("Expected 42");
@@ -186,11 +204,20 @@
     }
   }
 
-  public static void main(String[] args) {
+  private static void $noinline$testsomeSubclassesThrow() throws Exception {
+    try {
+      noInlineSomeSubclassesThrow(new SubA());
+      throw new Exception("Unreachable");
+    } catch (Error expected) {
+    }
+    noInlineSomeSubclassesThrow(new SubB());
+  }
+
+  public static void main(String[] args) throws Exception {
     testInlineMonomorphic();
     testInlinePolymorhic();
     testInlineMegamorphic();
     testNoInlineCache();
+    $noinline$testsomeSubclassesThrow();
   }
-
 }
diff --git a/test/638-checker-inline-caches/src/Super.java b/test/638-checker-inline-caches/src/Super.java
index 30cdf30..1964866 100644
--- a/test/638-checker-inline-caches/src/Super.java
+++ b/test/638-checker-inline-caches/src/Super.java
@@ -16,4 +16,5 @@
 
 public abstract class Super {
   abstract int getValue();
+  abstract void someSubclassesThrow();
 }
diff --git a/test/638-no-line-number/build b/test/638-no-line-number/build
deleted file mode 100644
index 9cd1955..0000000
--- a/test/638-no-line-number/build
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Stop if something fails.
-set -e
-
-# Only keep the source name, to make sure we do remove it in the stack trace
-# when there is no line number mapping.
-JAVAC_ARGS="$JAVAC_ARGS -g:source" ./default-build "$@"
diff --git a/test/638-no-line-number/build.py b/test/638-no-line-number/build.py
new file mode 100644
index 0000000..3643347
--- /dev/null
+++ b/test/638-no-line-number/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+# Only keep the source name, to make sure we do remove it in the stack trace
+# when there is no line number mapping.
+def build(ctx):
+  ctx.default_build(javac_args=["-g:source"])
diff --git a/test/639-checker-code-sinking/src/Main.java b/test/639-checker-code-sinking/src/Main.java
index 5e465f2..f8c1d9d 100644
--- a/test/639-checker-code-sinking/src/Main.java
+++ b/test/639-checker-code-sinking/src/Main.java
@@ -15,8 +15,13 @@
  */
 
 public class Main {
+  static class ValueHolder {
+    int getValue() {
+      return 1;
+    }
+  }
 
-  public static void main(String[] args) {
+  public static void main(String[] args) throws Exception {
     testSimpleUse();
     testTwoUses();
     testFieldStores(doThrow);
@@ -27,6 +32,14 @@
     testPhiInput();
     testVolatileStore();
     testCatchBlock();
+    $noinline$testTwoThrowingPathsAndStringBuilderAppend();
+    try {
+      $noinline$testSinkNewInstanceWithClinitCheck();
+      throw new Exception("Unreachable");
+    } catch (Error e) {
+      // expected
+    }
+    $noinline$testMethodEndsWithTryBoundary();
     doThrow = true;
     try {
       testInstanceSideEffects();
@@ -289,6 +302,66 @@
     }
   }
 
+  private static void $noinline$testMethodEndsWithTryBoundary() throws Exception {
+    assertEquals(0, $noinline$testDontSinkToReturnBranch(0, 0, false, new Object()));
+    assertEquals(1, $noinline$testSinkToThrowBranch(0, 0, true, new Object()));
+    try {
+      $noinline$testSinkToThrowBranch(0, 0, false, new Object());
+      throw new Exception("Unreachable");
+    } catch (Error expected) {
+    }
+  }
+
+  // Consistency check: only one add
+  /// CHECK-START: int Main.$noinline$testDontSinkToReturnBranch(int, int, boolean, java.lang.Object) code_sinking (before)
+  /// CHECK:     Add
+  /// CHECK-NOT: Add
+
+  /// CHECK-START: int Main.$noinline$testDontSinkToReturnBranch(int, int, boolean, java.lang.Object) code_sinking (before)
+  /// CHECK:      Add
+  /// CHECK-NEXT: If
+
+  /// CHECK-START: int Main.$noinline$testDontSinkToReturnBranch(int, int, boolean, java.lang.Object) code_sinking (after)
+  /// CHECK:      Add
+  /// CHECK-NEXT: If
+  private static int $noinline$testDontSinkToReturnBranch(int a, int b, boolean flag, Object obj) {
+    int c = a + b;
+    if (flag) {
+      return 1;
+    }
+
+    synchronized (obj) {
+      return $noinline$returnSameValue(c);
+    }
+  }
+
+  private static int $noinline$returnSameValue(int value) {
+    return value;
+  }
+
+  // Consistency check: only one add
+  /// CHECK-START: int Main.$noinline$testSinkToThrowBranch(int, int, boolean, java.lang.Object) code_sinking (before)
+  /// CHECK:     Add
+  /// CHECK-NOT: Add
+
+  /// CHECK-START: int Main.$noinline$testSinkToThrowBranch(int, int, boolean, java.lang.Object) code_sinking (before)
+  /// CHECK:      Add
+  /// CHECK:      If
+
+  /// CHECK-START: int Main.$noinline$testSinkToThrowBranch(int, int, boolean, java.lang.Object) code_sinking (after)
+  /// CHECK:      If
+  /// CHECK:      Add
+  private static int $noinline$testSinkToThrowBranch(int a, int b, boolean flag, Object obj) {
+    int c = a + b;
+    if (flag) {
+      return 1;
+    }
+
+    synchronized (obj) {
+      throw new Error(Integer.toString(c));
+    }
+  }
+
   public static void testInstanceSideEffects() {
     int a = mainField.intField;
     $noinline$changeIntField();
@@ -392,45 +465,13 @@
   }
 
   private static void testCatchBlock() {
-    assertEquals(456, testSinkToCatchBlock());
     assertEquals(456, testDoNotSinkToTry());
-    assertEquals(456, testDoNotSinkToCatchInsideTry());
     assertEquals(456, testSinkWithinTryBlock());
     assertEquals(456, testSinkRightBeforeTryBlock());
-    assertEquals(456, testSinkToSecondCatch());
     assertEquals(456, testDoNotSinkToCatchInsideTryWithMoreThings(false, false));
-    assertEquals(456, testSinkToCatchBlockCustomClass());
     assertEquals(456, DoNotSinkWithOOMThrow());
   }
 
-  /// CHECK-START: int Main.testSinkToCatchBlock() code_sinking (before)
-  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
-  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
-  /// CHECK:                         TryBoundary kind:entry
-
-  /// CHECK-START: int Main.testSinkToCatchBlock() code_sinking (after)
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
-  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
-
-  // Consistency check to make sure there's only one entry TryBoundary.
-  /// CHECK-START: int Main.testSinkToCatchBlock() code_sinking (after)
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK-NOT:                     TryBoundary kind:entry
-
-  // Tests that we can sink the Object creation to the catch block.
-  private static int testSinkToCatchBlock() {
-    Object o = new Object();
-    try {
-      if (doEarlyReturn) {
-        return 123;
-      }
-    } catch (Error e) {
-      throw new Error(o.toString());
-    }
-    return 456;
-  }
-
   /// CHECK-START: int Main.testDoNotSinkToTry() code_sinking (before)
   /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
   /// CHECK:                         NewInstance [<<ObjLoadClass>>]
@@ -459,41 +500,6 @@
     return 456;
   }
 
-  /// CHECK-START: int Main.testDoNotSinkToCatchInsideTry() code_sinking (before)
-  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
-  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK:                         TryBoundary kind:entry
-
-  /// CHECK-START: int Main.testDoNotSinkToCatchInsideTry() code_sinking (after)
-  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
-  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK:                         TryBoundary kind:entry
-
-  // Consistency check to make sure there's exactly two entry TryBoundary.
-  /// CHECK-START: int Main.testDoNotSinkToCatchInsideTry() code_sinking (after)
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK-NOT:                     TryBoundary kind:entry
-
-  // Tests that we don't sink the Object creation into a catch handler surrounded by try/catch.
-  private static int testDoNotSinkToCatchInsideTry() {
-    Object o = new Object();
-    try {
-      try {
-        if (doEarlyReturn) {
-          return 123;
-        }
-      } catch (Error e) {
-        throw new Error(o.toString());
-      }
-    } catch (Error e) {
-      throw new Error();
-    }
-    return 456;
-  }
-
   /// CHECK-START: int Main.testSinkWithinTryBlock() code_sinking (before)
   /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
   /// CHECK:                         NewInstance [<<ObjLoadClass>>]
@@ -538,46 +544,6 @@
     return 456;
   }
 
-  /// CHECK-START: int Main.testSinkToSecondCatch() code_sinking (before)
-  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
-  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK:                         TryBoundary kind:entry
-
-  /// CHECK-START: int Main.testSinkToSecondCatch() code_sinking (after)
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
-  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
-
-  // Consistency check to make sure there's exactly two entry TryBoundary.
-  /// CHECK-START: int Main.testSinkToSecondCatch() code_sinking (after)
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK-NOT:                     TryBoundary kind:entry
-  private static int testSinkToSecondCatch() {
-    Object o = new Object();
-    try {
-      if (doEarlyReturn) {
-        return 123;
-      }
-    } catch (Error e) {
-      throw new Error();
-    }
-
-    try {
-      // We need a different boolean to the one above, so that the compiler cannot optimize this
-      // return away.
-      if (doOtherEarlyReturn) {
-        return 789;
-      }
-    } catch (Error e) {
-      throw new Error(o.toString());
-    }
-
-    return 456;
-  }
-
   /// CHECK-START: int Main.testDoNotSinkToCatchInsideTryWithMoreThings(boolean, boolean) code_sinking (before)
   /// CHECK-NOT:                     TryBoundary kind:entry
   /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
@@ -616,38 +582,6 @@
     int x;
   }
 
-  /// CHECK-START: int Main.testSinkToCatchBlockCustomClass() code_sinking (before)
-  /// CHECK: <<LoadClass:l\d+>>      LoadClass class_name:Main$ObjectWithInt
-  /// CHECK: <<Clinit:l\d+>>         ClinitCheck [<<LoadClass>>]
-  /// CHECK:                         NewInstance [<<Clinit>>]
-  /// CHECK:                         TryBoundary kind:entry
-
-  /// CHECK-START: int Main.testSinkToCatchBlockCustomClass() code_sinking (after)
-  /// CHECK: <<LoadClass:l\d+>>      LoadClass class_name:Main$ObjectWithInt
-  /// CHECK: <<Clinit:l\d+>>         ClinitCheck [<<LoadClass>>]
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK:                         NewInstance [<<Clinit>>]
-
-  // Consistency check to make sure there's only one entry TryBoundary.
-  /// CHECK-START: int Main.testSinkToCatchBlockCustomClass() code_sinking (after)
-  /// CHECK:                         TryBoundary kind:entry
-  /// CHECK-NOT:                     TryBoundary kind:entry
-
-  // Similar to testSinkToCatchBlock, but using a custom class. CLinit check is not an instruction
-  // that we sink since it can throw and it is not in the allow list. We can sink the NewInstance
-  // nevertheless.
-  private static int testSinkToCatchBlockCustomClass() {
-    ObjectWithInt obj = new ObjectWithInt();
-    try {
-      if (doEarlyReturn) {
-        return 123;
-      }
-    } catch (Error e) {
-      throw new Error(Integer.toString(obj.x));
-    }
-    return 456;
-  }
-
   /// CHECK-START: int Main.DoNotSinkWithOOMThrow() code_sinking (before)
   /// CHECK: <<LoadClass:l\d+>>      LoadClass class_name:Main$ObjectWithInt
   /// CHECK: <<Clinit:l\d+>>         ClinitCheck [<<LoadClass>>]
@@ -687,12 +621,122 @@
     return x;
   }
 
+  private static void $noinline$testTwoThrowingPathsAndStringBuilderAppend() {
+    try {
+      $noinline$twoThrowingPathsAndStringBuilderAppend(null);
+      throw new Error("Unreachable");
+    } catch (Error expected) {
+      assertEquals("Object is null", expected.getMessage());
+    }
+    try {
+      $noinline$twoThrowingPathsAndStringBuilderAppend(new Object());
+      throw new Error("Unreachable");
+    } catch (Error expected) {
+      assertEquals("s1s2", expected.getMessage());
+    }
+  }
+
+  // Consistency check: only one ClinitCheck
+  /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() code_sinking (before)
+  /// CHECK:                ClinitCheck
+  /// CHECK-NOT:            ClinitCheck
+
+  /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() code_sinking (before)
+  /// CHECK: <<Check:l\d+>> ClinitCheck
+  /// CHECK:                NewInstance [<<Check>>]
+  /// CHECK:                NewInstance
+  /// CHECK:                If
+
+  /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() code_sinking (after)
+  /// CHECK: <<Check:l\d+>> ClinitCheck
+  /// CHECK:                If
+  /// CHECK:                NewInstance
+  /// CHECK:                NewInstance [<<Check>>]
+
+  /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() prepare_for_register_allocation (before)
+  /// CHECK-NOT:            If
+
+  // We have an instruction that can throw between the ClinitCheck and its NewInstance.
+
+  /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() prepare_for_register_allocation (before)
+  /// CHECK: <<Check:l\d+>> ClinitCheck
+  /// CHECK:                NewInstance
+  /// CHECK:                NewInstance [<<Check>>]
+
+  // We can remove the ClinitCheck by merging it with the LoadClass right before.
+
+  /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() prepare_for_register_allocation (after)
+  /// CHECK-NOT: ClinitCheck
+  private static void $noinline$testSinkNewInstanceWithClinitCheck() {
+    ValueHolder vh = new ValueHolder();
+    Object o = new Object();
+
+    // The if will always be true but we don't know this after LSE. Code sinking will sink code
+    // since this is an uncommon branch, but then we will have everything in one block before
+    // prepare_for_register_allocation for the crash to appear.
+    staticIntField = 1;
+    int value = staticIntField;
+    if (value == 1) {
+      throw new Error(Integer.toString(vh.getValue()) + o.toString());
+    }
+  }
+
+  // We currently do not inline the `StringBuilder` constructor.
+  // When we did, the `StringBuilderAppend` pattern recognition was looking for
+  // the inlined `NewArray` (and its associated `LoadClass`) and checked in
+  // debug build that the `StringBuilder` has an environment use from this
+  // `NewArray` (and maybe from `LoadClass`). However, code sinking was pruning
+  // the environment of the `NewArray`, leading to a crash when compiling the
+  // code below on the device (we do not inline `core-oj` on host). b/252799691
+
+  // We currently have a heuristic that disallows inlining methods if their basic blocks end with a
+  // throw. We could add code so that `requireNonNull`'s block doesn't end with a throw but that
+  // would mean that the string builder optimization wouldn't fire as it requires all uses to be in
+  // the same block. If `requireNonNull` is inlined at some point, we need to re-mark it as $inline$
+  // so that the test is operational again.
+
+  /// CHECK-START: void Main.$noinline$twoThrowingPathsAndStringBuilderAppend(java.lang.Object) inliner (before)
+  /// CHECK: InvokeStaticOrDirect method_name:Main.requireNonNull
+
+  /// CHECK-START: void Main.$noinline$twoThrowingPathsAndStringBuilderAppend(java.lang.Object) inliner (after)
+  /// CHECK: InvokeStaticOrDirect method_name:Main.requireNonNull
+  private static void $noinline$twoThrowingPathsAndStringBuilderAppend(Object o) {
+    String s1 = "s1";
+    String s2 = "s2";
+    StringBuilder sb = new StringBuilder();
+
+    // Before inlining, the environment use from this invoke prevents the
+    // `StringBuilderAppend` pattern recognition. After inlining, we end up
+    // with two paths ending with a `Throw` and we could sink the `sb`
+    // instructions from above down to those below, enabling the
+    // `StringBuilderAppend` pattern recognition.
+    // (But that does not happen when the `StringBuilder` constructor is
+    // not inlined, see above.)
+    requireNonNull(o);
+
+    String s1s2 = sb.append(s1).append(s2).toString();
+    sb = null;
+    throw new Error(s1s2);
+  }
+
+  private static void requireNonNull(Object o) {
+    if (o == null) {
+      throw new Error("Object is null");
+    }
+  }
+
   private static void assertEquals(int expected, int actual) {
     if (expected != actual) {
       throw new AssertionError("Expected: " + expected + ", Actual: " + actual);
     }
   }
 
+  private static void assertEquals(String expected, String actual) {
+    if (!expected.equals(actual)) {
+      throw new AssertionError("Expected: " + expected + ", Actual: " + actual);
+    }
+  }
+
   volatile int volatileField;
   int intField;
   int intField2;
@@ -701,6 +745,7 @@
   static boolean doLoop;
   static boolean doEarlyReturn;
   static boolean doOtherEarlyReturn;
+  static int staticIntField;
   static Main mainField = new Main();
   static Object obj = new Object();
 }
diff --git a/test/643-checker-bogus-ic/run b/test/643-checker-bogus-ic/run
deleted file mode 100644
index 146e180..0000000
--- a/test/643-checker-bogus-ic/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile
diff --git a/test/643-checker-bogus-ic/run.py b/test/643-checker-bogus-ic/run.py
new file mode 100644
index 0000000..7678899
--- /dev/null
+++ b/test/643-checker-bogus-ic/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args, profile=True, Xcompiler_option=["--compiler-filter=speed-profile"])
diff --git a/test/646-checker-long-const-to-int/src/Main.java b/test/646-checker-long-const-to-int/src/Main.java
index 85738dc..2133588 100644
--- a/test/646-checker-long-const-to-int/src/Main.java
+++ b/test/646-checker-long-const-to-int/src/Main.java
@@ -15,42 +15,47 @@
  */
 
 public class Main {
-
-  public static void main(String[] args) {
-    System.out.println(test());
-  }
-
-  public static long testField = 0;
-  public static long longField0 = 0;
-  public static long longField1 = 0;
-  public static long longField2 = 0;
-  public static long longField3 = 0;
-  public static long longField4 = 0;
-  public static long longField5 = 0;
-  public static long longField6 = 0;
-  public static long longField7 = 0;
-
-  /// CHECK-START-ARM: int Main.test() register (after)
-  /// CHECK: TypeConversion locations:[#-8690466096623102344]->{{.*}}
-  public static int test() {
-    // To avoid constant folding TypeConversion(const), hide the constant in a field.
-    // We do not run constant folding after load-store-elimination.
-    testField = 0x8765432112345678L;
-    long value = testField;
-    // Now, the `value` is in a register because of the store but we need
-    // a constant location to trigger the bug, so load a bunch of other fields.
-    long l0 = longField0;
-    long l1 = longField1;
-    long l2 = longField2;
-    long l3 = longField3;
-    long l4 = longField4;
-    long l5 = longField5;
-    long l6 = longField6;
-    long l7 = longField7;
-    if (l0 != 0 || l1 != 0 || l2 != 0 || l3 != 0 || l4 != 0 || l5 != 0 || l6 != 0 || l7 != 0) {
-      throw new Error();
+    public static void main(String[] args) {
+        System.out.println(test());
     }
-    // Do the conversion from constant location.
-    return (int)value;
-  }
+
+    public static long testField = 0;
+    public static long longField0 = 0;
+    public static long longField1 = 0;
+    public static long longField2 = 0;
+    public static long longField3 = 0;
+    public static long longField4 = 0;
+    public static long longField5 = 0;
+    public static long longField6 = 0;
+    public static long longField7 = 0;
+
+    /// CHECK-START-ARM: int Main.test() register (after)
+    /// CHECK: TypeConversion locations:[#-8690466096623102344]->{{.*}}
+    public static int test() {
+        // To avoid constant folding TypeConversion(const), hide the constant in a field. Then, hide
+        // it even more inside a Select that can only be reduced after LSE+InstructionSelector. We
+        // don't run constant folding after that.
+        testField = 0x8765432112345678L;
+        long value = testField;
+        if (value + 1 == 0x8765432112345679L) {
+            value = testField;
+        } else {
+            value = 0;
+        }
+        // Now, the `value` is in a register because of the store but we need
+        // a constant location to trigger the bug, so load a bunch of other fields.
+        long l0 = longField0;
+        long l1 = longField1;
+        long l2 = longField2;
+        long l3 = longField3;
+        long l4 = longField4;
+        long l5 = longField5;
+        long l6 = longField6;
+        long l7 = longField7;
+        if (l0 != 0 || l1 != 0 || l2 != 0 || l3 != 0 || l4 != 0 || l5 != 0 || l6 != 0 || l7 != 0) {
+            throw new Error();
+        }
+        // Do the conversion from constant location.
+        return (int) value;
+    }
 }
diff --git a/test/648-inline-caches-unresolved/run b/test/648-inline-caches-unresolved/run
deleted file mode 100644
index d24ef42..0000000
--- a/test/648-inline-caches-unresolved/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-exec ${RUN} $@ --profile
diff --git a/test/648-inline-caches-unresolved/run.py b/test/648-inline-caches-unresolved/run.py
new file mode 100644
index 0000000..5ef44b3
--- /dev/null
+++ b/test/648-inline-caches-unresolved/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, profile=True)
diff --git a/test/648-many-direct-methods/build b/test/648-many-direct-methods/build
deleted file mode 100755
index 7e888e5..0000000
--- a/test/648-many-direct-methods/build
+++ /dev/null
@@ -1,25 +0,0 @@
-#! /bin/bash
-#
-# Copyright 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.
-
-# Exit on a failure.
-set -e
-
-mkdir -p ./src
-
-# Generate the Java file or fail.
-./util-src/generate_java.py ./src
-
-./default-build "$@"
diff --git a/test/648-many-direct-methods/build.py b/test/648-many-direct-methods/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/648-many-direct-methods/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/648-many-direct-methods/generate-sources b/test/648-many-direct-methods/generate-sources
new file mode 100755
index 0000000..8907645
--- /dev/null
+++ b/test/648-many-direct-methods/generate-sources
@@ -0,0 +1,23 @@
+#! /bin/bash
+#
+# Copyright 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.
+
+# Exit on a failure.
+set -e
+
+mkdir -p ./src
+
+# Generate the Java file or fail.
+./util-src/generate_java.py ./src
diff --git a/test/652-deopt-intrinsic/run b/test/652-deopt-intrinsic/run
deleted file mode 100755
index 1acedf9..0000000
--- a/test/652-deopt-intrinsic/run
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Ensure this test is not subject to code collection.
-# We also need at least a few invocations of the method Main.$noinline$doCall
-# to ensure the inline cache sees the two types being passed to the method. Pass
-# a large number in case there's some weights on some invocation kinds (eg
-# compiler to interpreter transitions).
-exec ${RUN} "$@" --runtime-option -Xjitinitialsize:32M --runtime-option -Xjitthreshold:1000
diff --git a/test/652-deopt-intrinsic/run.py b/test/652-deopt-intrinsic/run.py
new file mode 100644
index 0000000..82f8ed0
--- /dev/null
+++ b/test/652-deopt-intrinsic/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Ensure this test is not subject to code collection.
+  # We also need at least a few invocations of the method Main.$noinline$doCall
+  # to ensure the inline cache sees the two types being passed to the method. Pass
+  # a large number in case there's some weights on some invocation kinds (eg
+  # compiler to interpreter transitions).
+  ctx.default_run(
+      args, runtime_option=["-Xjitinitialsize:32M", "-Xjitthreshold:1000"])
diff --git a/test/656-annotation-lookup-generic-jni/check b/test/656-annotation-lookup-generic-jni/check
deleted file mode 100755
index e02c84d..0000000
--- a/test/656-annotation-lookup-generic-jni/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# On gcstress configurations, an extra "JNI_OnUnload called" line may
-# be emitted. If so, remove it.
-sed -e '${/^JNI_OnUnload called$/d;}' "$2" > "$2.tmp"
-
-./default-check "$1" "$2.tmp" "$3" "$4"
diff --git a/test/656-annotation-lookup-generic-jni/run.py b/test/656-annotation-lookup-generic-jni/run.py
new file mode 100644
index 0000000..0ebb768
--- /dev/null
+++ b/test/656-annotation-lookup-generic-jni/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # On gcstress configurations, an extra "JNI_OnUnload called" line may
+  # be emitted. If so, remove it.
+  ctx.run(fr"sed -i '/^JNI_OnUnload called$/d' '{args.stdout_file}'")
diff --git a/test/660-clinit/run b/test/660-clinit/run
deleted file mode 100644
index a0e79ee..0000000
--- a/test/660-clinit/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-exec ${RUN} $@ --profile -Xcompiler-option --initialize-app-image-classes=true
diff --git a/test/660-clinit/run.py b/test/660-clinit/run.py
new file mode 100644
index 0000000..75c2bcb
--- /dev/null
+++ b/test/660-clinit/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args,
+      profile=True,
+      Xcompiler_option=["--initialize-app-image-classes=true"])
diff --git a/test/661-oat-writer-layout/run b/test/661-oat-writer-layout/run
deleted file mode 100644
index 087cd20..0000000
--- a/test/661-oat-writer-layout/run
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Always use the 'profile'.
-# Note that this test only works with --compiler-filter=speed
-# -- we accomplish this by blocklisting other compiler variants
-# and we also have to pass the option explicitly as dex2oat
-# defaults to speed-profile if a profile is specified.
-"${RUN}" "$@" --profile -Xcompiler-option --compiler-filter=speed
diff --git a/test/661-oat-writer-layout/run.py b/test/661-oat-writer-layout/run.py
new file mode 100644
index 0000000..838f3b1
--- /dev/null
+++ b/test/661-oat-writer-layout/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Always use the 'profile'.
+  # Note that this test only works with --compiler-filter=speed
+  # -- we accomplish this by blocklisting other compiler variants
+  # and we also have to pass the option explicitly as dex2oat
+  # defaults to speed-profile if a profile is specified.
+  ctx.default_run(
+      args, profile=True, Xcompiler_option=["--compiler-filter=speed"])
diff --git a/test/663-checker-select-generator/Android.bp b/test/663-checker-select-generator/Android.bp
new file mode 100644
index 0000000..c6b9087
--- /dev/null
+++ b/test/663-checker-select-generator/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `663-checker-select-generator`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-663-checker-select-generator",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-663-checker-select-generator-expected-stdout",
+        ":art-run-test-663-checker-select-generator-expected-stderr",
+    ],
+    // Include the Java source files in the test's artifacts, to make Checker assertions
+    // available to the TradeFed test runner.
+    include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-663-checker-select-generator-expected-stdout",
+    out: ["art-run-test-663-checker-select-generator-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-663-checker-select-generator-expected-stderr",
+    out: ["art-run-test-663-checker-select-generator-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/663-checker-select-generator/info.txt b/test/663-checker-select-generator/info.txt
index 792779f..6a5ac27 100644
--- a/test/663-checker-select-generator/info.txt
+++ b/test/663-checker-select-generator/info.txt
@@ -1,14 +1,26 @@
 Test for select generation for conditional returns.
 
-Tests the rewriting from:
-
+For example rewrites a simpled diamond pattern e.g.:
              If [ Condition ]
                /          \
      false branch        true branch
-     return FalseValue   return TrueValue
+               \          /
+     Return Phi[FalseValue, TrueValue]
 
 to:
-
      true branch
      false branch
      return Select [FalseValue, TrueValue, Condition]
+
+It tests:
+* Simple diamond pattern with:
+  * Same value on each branch
+  * Different value
+* Double diamond pattern (i.e. nested simple diamonds) with:
+  * Same value
+  * All different values
+  * Same value in some cases but not all
+
+For all cases it tests:
+* Branches merging with a Phi.
+* Branches returning instead of having a Phi.
\ No newline at end of file
diff --git a/test/663-checker-select-generator/smali/TestCase.smali b/test/663-checker-select-generator/smali/TestCase.smali
deleted file mode 100644
index 844a9cf..0000000
--- a/test/663-checker-select-generator/smali/TestCase.smali
+++ /dev/null
@@ -1,72 +0,0 @@
-# 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 LTestCase;
-
-.super Ljava/lang/Object;
-
-## CHECK-START: boolean TestCase.testCase(boolean) select_generator (before)
-## CHECK-DAG:     <<Param:z\d+>>           ParameterValue
-## CHECK-DAG:     <<Int0:i\d+>>            IntConstant 0
-## CHECK-DAG:     <<Int1:i\d+>>            IntConstant 1
-## CHECK-DAG:                              If [<<Param>>]
-## CHECK-DAG:                              Return [<<Int0>>]
-## CHECK-DAG:                              Return [<<Int1>>]
-
-## CHECK-START: boolean TestCase.testCase(boolean) select_generator (after)
-## CHECK-DAG:     <<Param:z\d+>>           ParameterValue
-## CHECK-DAG:     <<Int0:i\d+>>            IntConstant 0
-## CHECK-DAG:     <<Int1:i\d+>>            IntConstant 1
-## CHECK-DAG:     <<Select:i\d+>>          Select [<<Int0>>,<<Int1>>,<<Param>>]
-## CHECK-DAG:                              Return [<<Select>>]
-
-.method public static testCase(Z)Z
-    .registers 1
-
-    # The select generation will replace this with a select
-    # instruction and a return.
-    if-eqz v0, :else
-    const v0, 0x1
-    return v0
-
-    :else
-    const v0, 0x0
-    return v0
-.end method
-
-
-## CHECK-START: java.lang.Object TestCase.referenceTypeTestCase(Main$Sub1, Main$Sub2, boolean) select_generator (before)
-## CHECK-DAG:     <<Param0:l\d+>>          ParameterValue
-## CHECK-DAG:     <<Param1:l\d+>>          ParameterValue
-## CHECK-DAG:     <<Param2:z\d+>>          ParameterValue
-## CHECK-DAG:                              If [<<Param2>>]
-## CHECK-DAG:                              Return [<<Param1>>]
-## CHECK-DAG:                              Return [<<Param0>>]
-
-## CHECK-START: java.lang.Object TestCase.referenceTypeTestCase(Main$Sub1, Main$Sub2, boolean) select_generator (after)
-## CHECK-DAG:     <<Param0:l\d+>>          ParameterValue
-## CHECK-DAG:     <<Param1:l\d+>>          ParameterValue
-## CHECK-DAG:     <<Param2:z\d+>>          ParameterValue
-## CHECK-DAG:     <<Select:l\d+>>          Select [<<Param1>>,<<Param0>>,<<Param2>>]
-## CHECK-DAG:                              Return [<<Select>>]
-
-.method public static referenceTypeTestCase(LMain$Sub1;LMain$Sub2;Z)Ljava/lang/Object;
-    .registers 3
-
-    if-eqz v2, :else
-    return-object v0
-
-    :else
-    return-object v1
-.end method
diff --git a/test/663-checker-select-generator/src/Main.java b/test/663-checker-select-generator/src/Main.java
index c5c7a43..0ab3aef 100644
--- a/test/663-checker-select-generator/src/Main.java
+++ b/test/663-checker-select-generator/src/Main.java
@@ -14,49 +14,353 @@
  * limitations under the License.
  */
 
-import java.lang.reflect.Method;
-
 public class Main {
-  public static class Super {}
-  public static class Sub1 {}
-  public static class Sub2 {}
+  // Check that we don't generate a select since we don't have no Phi (not even at the builder
+  // stage) since both values are the same.
 
-  public static void assertTrue(boolean result) {
-    if (!result) {
-      throw new Error("Expected true");
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondSameValue(boolean) builder (after)
+  /// CHECK-NOT: Phi
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondSameValue(boolean) select_generator (before)
+  /// CHECK-NOT: Phi
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondSameValue(boolean) select_generator (after)
+  /// CHECK-NOT: Phi
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondSameValue(boolean) select_generator (after)
+  /// CHECK-NOT: Select
+  private static int $noinline$testSimpleDiamondSameValue(boolean bool_param) {
+    int return_value;
+    if (bool_param) {
+      return_value = 10;
+    } else {
+      return_value = 10;
+    }
+    return return_value;
+  }
+
+  // Check that we generate a select for a simple diamond pattern, with different values.
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondDifferentValue(boolean) select_generator (before)
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Phi:i\d+>>     Phi [<<Arg1:i\d+>>,<<Arg2:i\d+>>]
+  /// CHECK-DAG:                    Return [<<Phi>>]
+  /// CHECK-EVAL:  set(["<<Arg1>>","<<Arg2>>"]) == set(["<<Const10>>","<<Const20>>"])
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondDifferentValue(boolean) select_generator (after)
+  /// CHECK-DAG:   <<Bool:z\d+>>    ParameterValue
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Select:i\d+>>  Select [<<Const20>>,<<Const10>>,<<Bool>>]
+  /// CHECK-DAG:                    Return [<<Select>>]
+  private static int $noinline$testSimpleDiamondDifferentValue(boolean bool_param) {
+    int return_value;
+    if (bool_param) {
+      return_value = 10;
+    } else {
+      return_value = 20;
+    }
+    return return_value;
+  }
+
+  // Check that we don't generate a select since we don't have no Phi (not even at the builder
+  // stage) since all values are the same.
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValue(boolean, boolean) builder (after)
+  /// CHECK-NOT: Phi
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValue(boolean, boolean) select_generator (before)
+  /// CHECK-NOT: Phi
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValue(boolean, boolean) select_generator (after)
+  /// CHECK-NOT: Phi
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValue(boolean, boolean) select_generator (after)
+  /// CHECK-NOT: Select
+  private static int $noinline$testDoubleDiamondSameValue(boolean bool_param_1, boolean bool_param_2) {
+      int return_value;
+    if (bool_param_1) {
+      return_value = 10;
+    } else {
+      if (bool_param_2) {
+        return_value = 10;
+      } else {
+        return_value = 10;
+      }
+    }
+    return return_value;
+  }
+
+  // Check that we generate a select for a double diamond pattern, with a different value in the outer branch.
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValueButNotAllOuter(boolean, boolean) select_generator (before)
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Phi:i\d+>>     Phi [<<Arg1:i\d+>>,<<Arg2:i\d+>>,<<Arg3:i\d+>>]
+  /// CHECK-DAG:                    Return [<<Phi>>]
+  /// CHECK-EVAL:  set(["<<Arg1>>","<<Arg2>>","<<Arg3>>"]) == set(["<<Const10>>","<<Const20>>","<<Const20>>"])
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValueButNotAllOuter(boolean, boolean) select_generator (after)
+  /// CHECK-DAG:   <<Bool1:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Bool2:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Select:i\d+>>  Select [<<Const20>>,<<Const20>>,<<Bool2>>]
+  /// CHECK-DAG:   <<Select2:i\d+>> Select [<<Select>>,<<Const10>>,<<Bool1>>]
+  /// CHECK-DAG:                    Return [<<Select2>>]
+  private static int $noinline$testDoubleDiamondSameValueButNotAllOuter(boolean bool_param_1, boolean bool_param_2) {
+      int return_value;
+    if (bool_param_1) {
+      return_value = 10;
+    } else {
+      if (bool_param_2) {
+        return_value = 20;
+      } else {
+        return_value = 20;
+      }
+    }
+    return return_value;
+  }
+
+  // Check that we generate a select for a double diamond pattern, with a different value in the inner branch.
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValueButNotAllInner(boolean, boolean) select_generator (before)
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Phi:i\d+>>     Phi [<<Arg1:i\d+>>,<<Arg2:i\d+>>,<<Arg3:i\d+>>]
+  /// CHECK-DAG:                    Return [<<Phi>>]
+  /// CHECK-EVAL:  set(["<<Arg1>>","<<Arg2>>","<<Arg3>>"]) == set(["<<Const10>>","<<Const20>>","<<Const20>>"])
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValueButNotAllInner(boolean, boolean) select_generator (after)
+  /// CHECK-DAG:   <<Bool1:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Bool2:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Select:i\d+>>  Select [<<Const20>>,<<Const10>>,<<Bool2>>]
+  /// CHECK-DAG:   <<Select2:i\d+>> Select [<<Select>>,<<Const20>>,<<Bool1>>]
+  /// CHECK-DAG:                    Return [<<Select2>>]
+  private static int $noinline$testDoubleDiamondSameValueButNotAllInner(boolean bool_param_1, boolean bool_param_2) {
+      int return_value;
+    if (bool_param_1) {
+      return_value = 20;
+    } else {
+      if (bool_param_2) {
+        return_value = 10;
+      } else {
+        return_value = 20;
+      }
+    }
+    return return_value;
+  }
+
+  // Check that we generate a select for a double diamond pattern, with a all different values.
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondDifferentValue(boolean, boolean) select_generator (before)
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Const30:i\d+>> IntConstant 30
+  /// CHECK-DAG:   <<Phi:i\d+>>     Phi [<<Arg1:i\d+>>,<<Arg2:i\d+>>,<<Arg3:i\d+>>]
+  /// CHECK-DAG:                    Return [<<Phi>>]
+  /// CHECK-EVAL:  set(["<<Arg1>>","<<Arg2>>","<<Arg3>>"]) == set(["<<Const10>>","<<Const20>>","<<Const30>>"])
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondDifferentValue(boolean, boolean) select_generator (after)
+  /// CHECK-DAG:   <<Bool1:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Bool2:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Const30:i\d+>> IntConstant 30
+  /// CHECK-DAG:   <<Select:i\d+>>  Select [<<Const30>>,<<Const20>>,<<Bool2>>]
+  /// CHECK-DAG:   <<Select2:i\d+>> Select [<<Select>>,<<Const10>>,<<Bool1>>]
+  /// CHECK-DAG:                    Return [<<Select2>>]
+  private static int $noinline$testDoubleDiamondDifferentValue(boolean bool_param_1, boolean bool_param_2) {
+      int return_value;
+    if (bool_param_1) {
+      return_value = 10;
+    } else {
+      if (bool_param_2) {
+        return_value = 20;
+      } else {
+        return_value = 30;
+      }
+    }
+    return return_value;
+  }
+
+  private static void assertEquals(int expected, int actual) {
+    if (expected != actual) {
+      throw new AssertionError("Expected " + expected + " got " + actual);
     }
   }
 
-  public static void assertFalse(boolean result) {
-    if (result) {
-      throw new Error("Expected false");
+  // Check that we don't generate a select since we only have a single return.
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondSameValueWithReturn(boolean) builder (after)
+  /// CHECK:       <<Const10:i\d+>> IntConstant 10
+  /// CHECK:       Return [<<Const10>>]
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondSameValueWithReturn(boolean) builder (after)
+  /// CHECK:       Return
+  /// CHECK-NOT:   Return
+
+  private static int $noinline$testSimpleDiamondSameValueWithReturn(boolean bool_param) {
+    if (bool_param) {
+      return 10;
+    } else {
+      return 10;
     }
   }
 
-  public static void assertInstanceOfSub1(Object result) {
-    if (!(result instanceof Sub1)) {
-      throw new Error("Expected instance of Sub1");
+  // Same as testSimpleDiamondDifferentValue, but branches return.
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondDifferentValueWithReturn(boolean) select_generator (before)
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:                    Return [<<Const10>>]
+  /// CHECK-DAG:                    Return [<<Const20>>]
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondDifferentValueWithReturn(boolean) select_generator (after)
+  /// CHECK-DAG:   <<Bool:z\d+>>    ParameterValue
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Select:i\d+>>  Select [<<Const20>>,<<Const10>>,<<Bool>>]
+  /// CHECK-DAG:                    Return [<<Select>>]
+  private static int $noinline$testSimpleDiamondDifferentValueWithReturn(boolean bool_param) {
+    if (bool_param) {
+      return 10;
+    } else {
+      return 20;
     }
   }
 
-  public static void assertInstanceOfSub2(Object result) {
-    if (!(result instanceof Sub2)) {
-      throw new Error("Expected instance of Sub2");
+  // Check that we don't generate a select since we only have a single return.
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondSameValueWithReturn(boolean) builder (after)
+  /// CHECK:       <<Const10:i\d+>> IntConstant 10
+  /// CHECK:       Return [<<Const10>>]
+
+  /// CHECK-START: int Main.$noinline$testSimpleDiamondSameValueWithReturn(boolean) builder (after)
+  /// CHECK:       Return
+  /// CHECK-NOT:   Return
+  private static int $noinline$testDoubleDiamondSameValueWithReturn(boolean bool_param_1, boolean bool_param_2) {
+    if (bool_param_1) {
+      return 10;
+    } else {
+      if (bool_param_2) {
+        return 10;
+      } else {
+        return 10;
+      }
+    }
+  }
+
+  // Same as testDoubleDiamondSameValueButNotAllOuter, but branches return.
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValueButNotAllOuterWithReturn(boolean, boolean) select_generator (before)
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:                    Return [<<Const10>>]
+  /// CHECK-DAG:                    Return [<<Const20>>]
+
+  // Note that we have 2 returns instead of 3 as the two `return 20;` get merged into one before `select_generator`.
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValueButNotAllOuterWithReturn(boolean, boolean) select_generator (before)
+  /// CHECK:                    Return
+  /// CHECK:                    Return
+  /// CHECK-NOT:                Return
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValueButNotAllOuterWithReturn(boolean, boolean) select_generator (after)
+  /// CHECK-DAG:   <<Bool1:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Select2:i\d+>> Select [<<Const20>>,<<Const10>>,<<Bool1>>]
+  /// CHECK-DAG:                    Return [<<Select2>>]
+  private static int $noinline$testDoubleDiamondSameValueButNotAllOuterWithReturn(boolean bool_param_1, boolean bool_param_2) {
+    if (bool_param_1) {
+      return 10;
+    } else {
+      if (bool_param_2) {
+        return 20;
+      } else {
+        return 20;
+      }
+    }
+  }
+
+  // Same as testDoubleDiamondSameValueButNotAllInner, but branches return.
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValueButNotAllInnerWithReturn(boolean, boolean) select_generator (before)
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:                    Return [<<Const10>>]
+  /// CHECK-DAG:                    Return [<<Const20>>]
+  /// CHECK-DAG:                    Return [<<Const20>>]
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondSameValueButNotAllInnerWithReturn(boolean, boolean) select_generator (after)
+  /// CHECK-DAG:   <<Bool1:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Bool2:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Select:i\d+>>  Select [<<Const20>>,<<Const10>>,<<Bool2>>]
+  /// CHECK-DAG:   <<Select2:i\d+>> Select [<<Select>>,<<Const20>>,<<Bool1>>]
+  /// CHECK-DAG:                    Return [<<Select2>>]
+  private static int $noinline$testDoubleDiamondSameValueButNotAllInnerWithReturn(boolean bool_param_1, boolean bool_param_2) {
+    if (bool_param_1) {
+      return 20;
+    } else {
+      if (bool_param_2) {
+        return 10;
+      } else {
+        return 20;
+      }
+    }
+  }
+
+  // Same as testDoubleDiamondDifferentValue, but branches return.
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondDifferentValueWithReturn(boolean, boolean) select_generator (before)
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Const30:i\d+>> IntConstant 30
+  /// CHECK-DAG:                    Return [<<Const10>>]
+  /// CHECK-DAG:                    Return [<<Const20>>]
+  /// CHECK-DAG:                    Return [<<Const30>>]
+
+  /// CHECK-START: int Main.$noinline$testDoubleDiamondDifferentValueWithReturn(boolean, boolean) select_generator (after)
+  /// CHECK-DAG:   <<Bool1:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Bool2:z\d+>>   ParameterValue
+  /// CHECK-DAG:   <<Const10:i\d+>> IntConstant 10
+  /// CHECK-DAG:   <<Const20:i\d+>> IntConstant 20
+  /// CHECK-DAG:   <<Const30:i\d+>> IntConstant 30
+  /// CHECK-DAG:   <<Select:i\d+>>  Select [<<Const30>>,<<Const20>>,<<Bool2>>]
+  /// CHECK-DAG:   <<Select2:i\d+>> Select [<<Select>>,<<Const10>>,<<Bool1>>]
+  /// CHECK-DAG:                    Return [<<Select2>>]
+  private static int $noinline$testDoubleDiamondDifferentValueWithReturn(boolean bool_param_1, boolean bool_param_2) {
+    if (bool_param_1) {
+      return 10;
+    } else {
+      if (bool_param_2) {
+        return 20;
+      } else {
+        return 30;
+      }
     }
   }
 
   public static void main(String[] args) throws Throwable {
-    Class<?> c = Class.forName("TestCase");
-    Method m = c.getMethod("testCase", boolean.class);
-    Method m2 = c.getMethod("referenceTypeTestCase", Sub1.class, Sub2.class, boolean.class);
+    // With phi
+    assertEquals(10, $noinline$testSimpleDiamondSameValue(false));
+    assertEquals(20, $noinline$testSimpleDiamondDifferentValue(false));
+    assertEquals(10, $noinline$testDoubleDiamondSameValue(false, false));
+    assertEquals(20, $noinline$testDoubleDiamondSameValueButNotAllOuter(false, false));
+    assertEquals(20, $noinline$testDoubleDiamondSameValueButNotAllInner(false, false));
+    assertEquals(30, $noinline$testDoubleDiamondDifferentValue(false, false));
 
-    try {
-      assertTrue((Boolean) m.invoke(null, true));
-      assertFalse((Boolean) m.invoke(null, false));
-      assertInstanceOfSub1(m2.invoke(null, new Sub1(), new Sub2(), true));
-      assertInstanceOfSub2(m2.invoke(null, new Sub1(), new Sub2(), false));
-    } catch (Exception e) {
-      throw new Error(e);
-    }
+    // With return
+    assertEquals(10, $noinline$testSimpleDiamondSameValueWithReturn(false));
+    assertEquals(20, $noinline$testSimpleDiamondDifferentValueWithReturn(false));
+    assertEquals(10, $noinline$testDoubleDiamondSameValueWithReturn(false, false));
+    assertEquals(20, $noinline$testDoubleDiamondSameValueButNotAllOuterWithReturn(false, false));
+    assertEquals(20, $noinline$testDoubleDiamondSameValueButNotAllInnerWithReturn(false, false));
+    assertEquals(30, $noinline$testDoubleDiamondDifferentValueWithReturn(false, false));
   }
 }
diff --git a/test/663-odd-dex-size/run b/test/663-odd-dex-size/run
deleted file mode 100644
index 51777ca..0000000
--- a/test/663-odd-dex-size/run
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2021 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.
-
-# Run normally.
-${RUN} $@
-return_status1=$?
-
-# Run without cdex to trigger the unalignment in the vdex file.
-${RUN} ${@} -Xcompiler-option --compact-dex-level=none
-return_status2=$?
-
-(exit ${return_status1}) && (exit ${return_status2})
diff --git a/test/663-odd-dex-size/run.py b/test/663-odd-dex-size/run.py
new file mode 100644
index 0000000..635d23b
--- /dev/null
+++ b/test/663-odd-dex-size/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 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.
+
+
+def run(ctx, args):
+  # Run normally.
+  ctx.default_run(args)
+
+  # Run without cdex to trigger the unalignment in the vdex file.
+  ctx.default_run(args, Xcompiler_option=["--compact-dex-level=none"])
diff --git a/test/663-odd-dex-size2/build b/test/663-odd-dex-size2/build
deleted file mode 100644
index 5636558..0000000
--- a/test/663-odd-dex-size2/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Nothing to do
diff --git a/test/663-odd-dex-size2/build.py b/test/663-odd-dex-size2/build.py
new file mode 100644
index 0000000..f304d95
--- /dev/null
+++ b/test/663-odd-dex-size2/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  pass  # Nothing to do.
diff --git a/test/663-odd-dex-size3/build b/test/663-odd-dex-size3/build
deleted file mode 100644
index 5636558..0000000
--- a/test/663-odd-dex-size3/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Nothing to do
diff --git a/test/663-odd-dex-size3/build.py b/test/663-odd-dex-size3/build.py
new file mode 100644
index 0000000..f304d95
--- /dev/null
+++ b/test/663-odd-dex-size3/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  pass  # Nothing to do.
diff --git a/test/663-odd-dex-size4/build b/test/663-odd-dex-size4/build
deleted file mode 100644
index 5636558..0000000
--- a/test/663-odd-dex-size4/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Nothing to do
diff --git a/test/663-odd-dex-size4/build.py b/test/663-odd-dex-size4/build.py
new file mode 100644
index 0000000..f304d95
--- /dev/null
+++ b/test/663-odd-dex-size4/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  pass  # Nothing to do.
diff --git a/test/667-jit-jni-stub/run b/test/667-jit-jni-stub/run
deleted file mode 100755
index b7ce913..0000000
--- a/test/667-jit-jni-stub/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Disable AOT compilation of JNI stubs.
-# Ensure this test is not subject to unexpected code collection.
-${RUN} "${@}" --no-prebuild --runtime-option -Xjitinitialsize:32M
diff --git a/test/667-jit-jni-stub/run.py b/test/667-jit-jni-stub/run.py
new file mode 100644
index 0000000..e211a9b
--- /dev/null
+++ b/test/667-jit-jni-stub/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Disable AOT compilation of JNI stubs.
+  # Ensure this test is not subject to unexpected code collection.
+  ctx.default_run(args, prebuild=False, runtime_option=["-Xjitinitialsize:32M"])
diff --git a/test/670-bitstring-type-check/build b/test/670-bitstring-type-check/build
deleted file mode 100644
index 38307f2..0000000
--- a/test/670-bitstring-type-check/build
+++ /dev/null
@@ -1,216 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Stop if something fails.
-set -e
-
-# Write out the source file.
-
-mkdir src
-cat >src/Main.java <<EOF
-/*
- * Copyright (C) 2018 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.
- */
-
-EOF
-
-for i in {0..8192}; do echo "class Level1Class$i { }" >>src/Main.java; done
-for i in {0..1024}; do echo "class Level2Class$i extends Level1Class0 { }" >>src/Main.java; done
-
-cat >>src/Main.java <<EOF
-class Level3Class0 extends Level2Class0 { }
-class Level4Class0 extends Level3Class0 { }
-class Level5Class0 extends Level4Class0 { }
-class Level6Class0 extends Level5Class0 { }
-class Level7Class0 extends Level6Class0 { }
-class Level8Class0 extends Level7Class0 { }
-class Level9Class0 extends Level8Class0 { }
-
-public class Main {
-  public static void main(String[] args) throws Exception {
-    // 8193 classes at level 1 make sure we shall have an overflow if there are 13 or
-    // less bits for the level 1 character. 1025 classes at level 2 similarly guarantees
-    // an overflow if the number of bits for level 2 character is 10 or less. To test
-    // type checks also for the depth overflow, we provide a hierarchy 9 levels deep.
-
-    // Make sure the bitstrings are initialized.
-    for (int i = 0; i <= 8192; ++i) {
-      Class.forName("Level1Class" + i).newInstance();
-    }
-    for (int i = 0; i <= 1024; ++i) {
-      Class.forName("Level2Class" + i).newInstance();
-    }
-
-    // Note: Using a different class for tests so that verification of Main.main() does
-    // not try to resolve classes used by the tests. This guarantees uninitialized type
-    // check bitstrings when we enter Main.main() and start initializing them above.
-    Helper.testInstanceOf();
-    Helper.testCheckCast();
-  }
-}
-
-class Helper {
-  public static void testInstanceOf() throws Exception {
-    for (int i = 1; i <= 9; ++i) {
-      Object o = createInstance("Level" + i + "Class0");
-      assertTrue(o instanceof Level1Class0);
-      if (o instanceof Level2Class0) {
-        assertFalse(i < 2);
-      } else {
-        assertTrue(i < 2);
-      }
-      if (o instanceof Level3Class0) {
-        assertFalse(i < 3);
-      } else {
-        assertTrue(i < 3);
-      }
-      if (o instanceof Level4Class0) {
-        assertFalse(i < 4);
-      } else {
-        assertTrue(i < 4);
-      }
-      if (o instanceof Level5Class0) {
-        assertFalse(i < 5);
-      } else {
-        assertTrue(i < 5);
-      }
-      if (o instanceof Level6Class0) {
-        assertFalse(i < 6);
-      } else {
-        assertTrue(i < 6);
-      }
-      if (o instanceof Level7Class0) {
-        assertFalse(i < 7);
-      } else {
-        assertTrue(i < 7);
-      }
-      if (o instanceof Level8Class0) {
-        assertFalse(i < 8);
-      } else {
-        assertTrue(i < 8);
-      }
-      if (o instanceof Level9Class0) {
-        assertFalse(i < 9);
-      } else {
-        assertTrue(i < 9);
-      }
-    }
-
-    assertTrue(createInstance("Level1Class8192") instanceof Level1Class8192);
-    assertFalse(createInstance("Level1Class8192") instanceof Level1Class0);
-    assertTrue(createInstance("Level2Class1024") instanceof Level2Class1024);
-    assertTrue(createInstance("Level2Class1024") instanceof Level1Class0);
-    assertFalse(createInstance("Level2Class1024") instanceof Level2Class0);
-  }
-
-  public static void testCheckCast() throws Exception {
-    for (int i = 1; i <= 9; ++i) {
-      Object o = createInstance("Level" + i + "Class0");
-      Level1Class0 l1c0 = (Level1Class0) o;
-      try {
-        Level2Class0 l2c0 = (Level2Class0) o;
-        assertFalse(i < 2);
-      } catch (ClassCastException cce) {
-        assertTrue(i < 2);
-      }
-      try {
-        Level3Class0 l3c0 = (Level3Class0) o;
-        assertFalse(i < 3);
-      } catch (ClassCastException cce) {
-        assertTrue(i < 3);
-      }
-      try {
-        Level4Class0 l4c0 = (Level4Class0) o;
-        assertFalse(i < 4);
-      } catch (ClassCastException cce) {
-        assertTrue(i < 4);
-      }
-      try {
-        Level5Class0 l5c0 = (Level5Class0) o;
-        assertFalse(i < 5);
-      } catch (ClassCastException cce) {
-        assertTrue(i < 5);
-      }
-      try {
-        Level6Class0 l6c0 = (Level6Class0) o;
-        assertFalse(i < 6);
-      } catch (ClassCastException cce) {
-        assertTrue(i < 6);
-      }
-      try {
-        Level7Class0 l7c0 = (Level7Class0) o;
-        assertFalse(i < 7);
-      } catch (ClassCastException cce) {
-        assertTrue(i < 7);
-      }
-      try {
-        Level8Class0 l8c0 = (Level8Class0) o;
-        assertFalse(i < 8);
-      } catch (ClassCastException cce) {
-        assertTrue(i < 8);
-      }
-      try {
-        Level9Class0 l9c0 = (Level9Class0) o;
-        assertFalse(i < 9);
-      } catch (ClassCastException cce) {
-        assertTrue(i < 9);
-      }
-    }
-
-    Level1Class8192 l1c8192 = (Level1Class8192) createInstance("Level1Class8192");
-    try {
-      Level1Class0 l1c0 = (Level1Class0) createInstance("Level1Class8192");
-      throw new AssertionError("Unexpected");
-    } catch (ClassCastException expected) {}
-    Level2Class1024 l2c1024 = (Level2Class1024) createInstance("Level2Class1024");
-    Level1Class0 l1c0 = (Level1Class0) createInstance("Level2Class1024");
-    try {
-      Level2Class0 l2c0 = (Level2Class0) createInstance("Level2Class1024");
-      throw new AssertionError("Unexpected");
-    } catch (ClassCastException expected) {}
-  }
-
-  public static Object createInstance(String className) throws Exception {
-    return Class.forName(className).newInstance();
-  }
-
-  public static void assertTrue(boolean value) throws Exception {
-    if (!value) {
-      throw new AssertionError();
-    }
-  }
-
-  public static void assertFalse(boolean value) throws Exception {
-    if (value) {
-      throw new AssertionError();
-    }
-  }
-}
-EOF
-
-./default-build "$@"
diff --git a/test/670-bitstring-type-check/build.py b/test/670-bitstring-type-check/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/670-bitstring-type-check/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/670-bitstring-type-check/generate-sources b/test/670-bitstring-type-check/generate-sources
new file mode 100755
index 0000000..4d88839
--- /dev/null
+++ b/test/670-bitstring-type-check/generate-sources
@@ -0,0 +1,214 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+
+# Stop if something fails.
+set -e
+
+# Write out the source file.
+
+mkdir src
+cat >src/Main.java <<EOF
+/*
+ * Copyright (C) 2018 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.
+ */
+
+EOF
+
+for i in {0..8192}; do echo "class Level1Class$i { }" >>src/Main.java; done
+for i in {0..1024}; do echo "class Level2Class$i extends Level1Class0 { }" >>src/Main.java; done
+
+cat >>src/Main.java <<EOF
+class Level3Class0 extends Level2Class0 { }
+class Level4Class0 extends Level3Class0 { }
+class Level5Class0 extends Level4Class0 { }
+class Level6Class0 extends Level5Class0 { }
+class Level7Class0 extends Level6Class0 { }
+class Level8Class0 extends Level7Class0 { }
+class Level9Class0 extends Level8Class0 { }
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    // 8193 classes at level 1 make sure we shall have an overflow if there are 13 or
+    // less bits for the level 1 character. 1025 classes at level 2 similarly guarantees
+    // an overflow if the number of bits for level 2 character is 10 or less. To test
+    // type checks also for the depth overflow, we provide a hierarchy 9 levels deep.
+
+    // Make sure the bitstrings are initialized.
+    for (int i = 0; i <= 8192; ++i) {
+      Class.forName("Level1Class" + i).newInstance();
+    }
+    for (int i = 0; i <= 1024; ++i) {
+      Class.forName("Level2Class" + i).newInstance();
+    }
+
+    // Note: Using a different class for tests so that verification of Main.main() does
+    // not try to resolve classes used by the tests. This guarantees uninitialized type
+    // check bitstrings when we enter Main.main() and start initializing them above.
+    Helper.testInstanceOf();
+    Helper.testCheckCast();
+  }
+}
+
+class Helper {
+  public static void testInstanceOf() throws Exception {
+    for (int i = 1; i <= 9; ++i) {
+      Object o = createInstance("Level" + i + "Class0");
+      assertTrue(o instanceof Level1Class0);
+      if (o instanceof Level2Class0) {
+        assertFalse(i < 2);
+      } else {
+        assertTrue(i < 2);
+      }
+      if (o instanceof Level3Class0) {
+        assertFalse(i < 3);
+      } else {
+        assertTrue(i < 3);
+      }
+      if (o instanceof Level4Class0) {
+        assertFalse(i < 4);
+      } else {
+        assertTrue(i < 4);
+      }
+      if (o instanceof Level5Class0) {
+        assertFalse(i < 5);
+      } else {
+        assertTrue(i < 5);
+      }
+      if (o instanceof Level6Class0) {
+        assertFalse(i < 6);
+      } else {
+        assertTrue(i < 6);
+      }
+      if (o instanceof Level7Class0) {
+        assertFalse(i < 7);
+      } else {
+        assertTrue(i < 7);
+      }
+      if (o instanceof Level8Class0) {
+        assertFalse(i < 8);
+      } else {
+        assertTrue(i < 8);
+      }
+      if (o instanceof Level9Class0) {
+        assertFalse(i < 9);
+      } else {
+        assertTrue(i < 9);
+      }
+    }
+
+    assertTrue(createInstance("Level1Class8192") instanceof Level1Class8192);
+    assertFalse(createInstance("Level1Class8192") instanceof Level1Class0);
+    assertTrue(createInstance("Level2Class1024") instanceof Level2Class1024);
+    assertTrue(createInstance("Level2Class1024") instanceof Level1Class0);
+    assertFalse(createInstance("Level2Class1024") instanceof Level2Class0);
+  }
+
+  public static void testCheckCast() throws Exception {
+    for (int i = 1; i <= 9; ++i) {
+      Object o = createInstance("Level" + i + "Class0");
+      Level1Class0 l1c0 = (Level1Class0) o;
+      try {
+        Level2Class0 l2c0 = (Level2Class0) o;
+        assertFalse(i < 2);
+      } catch (ClassCastException cce) {
+        assertTrue(i < 2);
+      }
+      try {
+        Level3Class0 l3c0 = (Level3Class0) o;
+        assertFalse(i < 3);
+      } catch (ClassCastException cce) {
+        assertTrue(i < 3);
+      }
+      try {
+        Level4Class0 l4c0 = (Level4Class0) o;
+        assertFalse(i < 4);
+      } catch (ClassCastException cce) {
+        assertTrue(i < 4);
+      }
+      try {
+        Level5Class0 l5c0 = (Level5Class0) o;
+        assertFalse(i < 5);
+      } catch (ClassCastException cce) {
+        assertTrue(i < 5);
+      }
+      try {
+        Level6Class0 l6c0 = (Level6Class0) o;
+        assertFalse(i < 6);
+      } catch (ClassCastException cce) {
+        assertTrue(i < 6);
+      }
+      try {
+        Level7Class0 l7c0 = (Level7Class0) o;
+        assertFalse(i < 7);
+      } catch (ClassCastException cce) {
+        assertTrue(i < 7);
+      }
+      try {
+        Level8Class0 l8c0 = (Level8Class0) o;
+        assertFalse(i < 8);
+      } catch (ClassCastException cce) {
+        assertTrue(i < 8);
+      }
+      try {
+        Level9Class0 l9c0 = (Level9Class0) o;
+        assertFalse(i < 9);
+      } catch (ClassCastException cce) {
+        assertTrue(i < 9);
+      }
+    }
+
+    Level1Class8192 l1c8192 = (Level1Class8192) createInstance("Level1Class8192");
+    try {
+      Level1Class0 l1c0 = (Level1Class0) createInstance("Level1Class8192");
+      throw new AssertionError("Unexpected");
+    } catch (ClassCastException expected) {}
+    Level2Class1024 l2c1024 = (Level2Class1024) createInstance("Level2Class1024");
+    Level1Class0 l1c0 = (Level1Class0) createInstance("Level2Class1024");
+    try {
+      Level2Class0 l2c0 = (Level2Class0) createInstance("Level2Class1024");
+      throw new AssertionError("Unexpected");
+    } catch (ClassCastException expected) {}
+  }
+
+  public static Object createInstance(String className) throws Exception {
+    return Class.forName(className).newInstance();
+  }
+
+  public static void assertTrue(boolean value) throws Exception {
+    if (!value) {
+      throw new AssertionError();
+    }
+  }
+
+  public static void assertFalse(boolean value) throws Exception {
+    if (value) {
+      throw new AssertionError();
+    }
+  }
+}
+EOF
diff --git a/test/670-bitstring-type-check/run b/test/670-bitstring-type-check/run
deleted file mode 100644
index a189dc5..0000000
--- a/test/670-bitstring-type-check/run
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2008 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.
-
-# This test can take 7-11 mins, so raise the default 10 min timeout.
-export ART_TIME_OUT_MULTIPLIER=2
-
-exec ${RUN} "$@"
diff --git a/test/670-bitstring-type-check/run.py b/test/670-bitstring-type-check/run.py
new file mode 100644
index 0000000..5bfe969
--- /dev/null
+++ b/test/670-bitstring-type-check/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (C) 2008 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.
+
+
+def run(ctx, args):
+  # This test can take 7-11 mins, so raise the default 10 min timeout.
+  ctx.env.ART_TIME_OUT_MULTIPLIER = 2
+
+  ctx.default_run(args)
diff --git a/test/674-HelloWorld-Dm/run b/test/674-HelloWorld-Dm/run
deleted file mode 100644
index b8a61c5..0000000
--- a/test/674-HelloWorld-Dm/run
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-${RUN} --dex2oat-dm "${@}"
-return_status1=$?
-
-${RUN} --runtime-dm "${@}"
-return_status2=$?
-
-(exit ${return_status1}) && (exit ${return_status2})
diff --git a/test/674-HelloWorld-Dm/run.py b/test/674-HelloWorld-Dm/run.py
new file mode 100644
index 0000000..9945fec
--- /dev/null
+++ b/test/674-HelloWorld-Dm/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, dex2oat_dm=True)
+
+  ctx.default_run(args, runtime_dm=True)
diff --git a/test/674-hiddenapi/build b/test/674-hiddenapi/build
deleted file mode 100644
index 330a6de..0000000
--- a/test/674-hiddenapi/build
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-set -e
-
-# Build the jars twice. First with applying hiddenapi, creating a boot jar, then
-# a second time without to create a normal jar. We need to do this because we
-# want to load the jar once as an app module and once as a member of the boot
-# class path. The DexFileVerifier would fail on the former as it does not allow
-# hidden API access flags in dex files. DexFileVerifier is not invoked on boot
-# class path dex files, so the boot jar loads fine in the latter case.
-
-export USE_HIDDENAPI=true
-./default-build "$@"
-
-# Move the jar file into the resource folder to be bundled with the test.
-mkdir res
-mv ${TEST_NAME}.jar res/boot.jar
-
-# Clear all intermediate files otherwise default-build would either skip
-# compilation or fail rebuilding.
-rm -rf classes*
-
-export USE_HIDDENAPI=false
-./default-build "$@"
diff --git a/test/674-hiddenapi/build.py b/test/674-hiddenapi/build.py
new file mode 100644
index 0000000..c3ebb50
--- /dev/null
+++ b/test/674-hiddenapi/build.py
@@ -0,0 +1,39 @@
+#
+# Copyright (C) 2022 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.
+
+import os
+
+# Build the jars twice. First with applying hiddenapi, creating a boot jar, then
+# a second time without to create a normal jar. We need to do this because we
+# want to load the jar once as an app module and once as a member of the boot
+# class path. The DexFileVerifier would fail on the former as it does not allow
+# hidden API access flags in dex files. DexFileVerifier is not invoked on boot
+# class path dex files, so the boot jar loads fine in the latter case.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build(use_hiddenapi=True)
+
+  # Move the jar file into the resource folder to be bundled with the test.
+  os.mkdir(ctx.test_dir / "res")
+  os.rename(ctx.test_dir / "674-hiddenapi.jar", ctx.test_dir / "res/boot.jar")
+
+  # Clear all intermediate files otherwise default-build would either skip
+  # compilation or fail rebuilding.
+  ctx.bash("rm -rf classes*")
+
+  ctx.default_build(use_hiddenapi=False)
diff --git a/test/674-hiddenapi/check b/test/674-hiddenapi/check
deleted file mode 100644
index c8afc22..0000000
--- a/test/674-hiddenapi/check
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Remove pid and date from the log messages.
-grep -v JNI_OnLoad "$2" \
-    | grep -v JNI_OnUnload \
-    > "$2.tmp"
-grep -vE '^dalvikvm(32|64) E [^]]+]' "$4" \
-    > "$4.tmp"
-
-./default-check "$1" "$2.tmp" "$3" "$4.tmp"
diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc
index ebe9d10..f1b0c18 100644
--- a/test/674-hiddenapi/hiddenapi.cc
+++ b/test/674-hiddenapi/hiddenapi.cc
@@ -15,13 +15,10 @@
  */
 
 #include "base/sdk_version.h"
-#include "class_linker.h"
 #include "dex/art_dex_file_loader.h"
 #include "hidden_api.h"
 #include "jni.h"
 #include "runtime.h"
-#include "scoped_thread_state_change-inl.h"
-#include "thread.h"
 #include "ti-agent/scoped_utf_chars.h"
 
 namespace art {
@@ -62,12 +59,10 @@
   const jint int_index = static_cast<jint>(index);
   opened_dex_files.push_back(std::vector<std::unique_ptr<const DexFile>>());
 
-  ArtDexFileLoader dex_loader;
+  DexFileLoader dex_loader(path);
   std::string error_msg;
 
-  if (!dex_loader.Open(path,
-                       path,
-                       /* verify */ false,
+  if (!dex_loader.Open(/* verify */ false,
                        /* verify_checksum */ true,
                        &error_msg,
                        &opened_dex_files[index])) {
@@ -77,10 +72,7 @@
 
   Java_Main_setDexDomain(env, klass, int_index, is_core_platform);
 
-  ScopedObjectAccess soa(Thread::Current());
-  for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files[index]) {
-    Runtime::Current()->GetClassLinker()->AppendToBootClassPath(Thread::Current(), dex_file.get());
-  }
+  Runtime::Current()->AppendToBootClassPath(path, path, opened_dex_files[index]);
 
   return int_index;
 }
diff --git a/test/674-hiddenapi/run b/test/674-hiddenapi/run
deleted file mode 100755
index 0ab4763..0000000
--- a/test/674-hiddenapi/run
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 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.
-
-# Make verification soft fail so that we can re-verify boot classpath
-# methods at runtime.
-#
-# N.B. Compilation of secondary dexes can prevent hidden API checks, e.g. if
-# a blocklisted field get is inlined.
-exec ${RUN} $@ --verify-soft-fail --no-secondary-compilation
diff --git a/test/674-hiddenapi/run.py b/test/674-hiddenapi/run.py
new file mode 100644
index 0000000..1e364fa
--- /dev/null
+++ b/test/674-hiddenapi/run.py
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 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.
+
+
+def run(ctx, args):
+  # Make verification soft fail so that we can re-verify boot classpath
+  # methods at runtime.
+  #
+  # N.B. Compilation of secondary dexes can prevent hidden API checks, e.g. if
+  # a blocklisted field get is inlined.
+  ctx.default_run(args, verify_soft_fail=True, secondary_compilation=False)
+
+  ctx.run(fr"sed -i -E '/(JNI_OnLoad|JNI_OnUnload)/d' '{args.stdout_file}'")
+  ctx.run(fr"sed -i -E '/^dalvikvm(32|64) E [^]]+]/d' '{args.stderr_file}'")
diff --git a/test/674-hotness-compiled/run b/test/674-hotness-compiled/run
deleted file mode 100755
index 85e8e3b..0000000
--- a/test/674-hotness-compiled/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-${RUN} "$@" -Xcompiler-option --count-hotness-in-compiled-code
diff --git a/test/674-hotness-compiled/run.py b/test/674-hotness-compiled/run.py
new file mode 100644
index 0000000..aca5b13
--- /dev/null
+++ b/test/674-hotness-compiled/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, Xcompiler_option=["--count-hotness-in-compiled-code"])
diff --git a/test/674-vdex-uncompress/build b/test/674-vdex-uncompress/build
deleted file mode 100755
index 7b1804d..0000000
--- a/test/674-vdex-uncompress/build
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Uncompress and align the dex files so that dex2oat will not copy the dex
-# code to the .vdex file.
-./default-build "$@" --zip-compression-method store --zip-align 4
diff --git a/test/674-vdex-uncompress/build.py b/test/674-vdex-uncompress/build.py
new file mode 100644
index 0000000..cbe1f62
--- /dev/null
+++ b/test/674-vdex-uncompress/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+# Uncompress and align the dex files so that dex2oat will not copy the dex
+# code to the .vdex file.
+def build(ctx):
+  ctx.default_build(zip_compression_method="store", zip_align_bytes="4")
diff --git a/test/674-vdex-uncompress/run b/test/674-vdex-uncompress/run
deleted file mode 100644
index edf699f..0000000
--- a/test/674-vdex-uncompress/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-exec ${RUN} -Xcompiler-option --compiler-filter=verify --vdex "${@}"
diff --git a/test/674-vdex-uncompress/run.py b/test/674-vdex-uncompress/run.py
new file mode 100644
index 0000000..7684cbd
--- /dev/null
+++ b/test/674-vdex-uncompress/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args, Xcompiler_option=["--compiler-filter=verify"], vdex=True)
diff --git a/test/675-checker-unverified-method/smali/TestCase.smali b/test/675-checker-unverified-method/smali/TestCase.smali
index a1b89cb..cd9ed1e 100644
--- a/test/675-checker-unverified-method/smali/TestCase.smali
+++ b/test/675-checker-unverified-method/smali/TestCase.smali
@@ -19,7 +19,7 @@
 #
 ## CHECK-START: void TestCase.foo(java.lang.Object) inliner (after)
 ## CHECK-DAG: InvokeStaticOrDirect method_name:TestCase.bar always_throws:true
-## CHECK-NOT: InvokeStaticOrDirect method_name:TestCase.bad
+## CHECK-DAG: InvokeStaticOrDirect method_name:TestCase.bad
 .method public static foo(Ljava/lang/Object;)V
   .registers 1
   if-nez v0, :Skip1
diff --git a/test/676-proxy-jit-at-first-use/run b/test/676-proxy-jit-at-first-use/run
deleted file mode 100644
index 16c9f76..0000000
--- a/test/676-proxy-jit-at-first-use/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Enable "jit at first use" (-Xjitthreshold:0).
-# Ensure this test is not subject to unexpected code collection.
-${RUN} "${@}" --runtime-option -Xjitthreshold:0 --runtime-option -Xjitinitialsize:32M
diff --git a/test/676-proxy-jit-at-first-use/run.py b/test/676-proxy-jit-at-first-use/run.py
new file mode 100644
index 0000000..4687094
--- /dev/null
+++ b/test/676-proxy-jit-at-first-use/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+
+
+def run(ctx, args):
+  # Enable "jit at first use" (-Xjitthreshold:0).
+  # Ensure this test is not subject to unexpected code collection.
+  ctx.default_run(
+      args, runtime_option=["-Xjitthreshold:0", "-Xjitinitialsize:32M"])
diff --git a/test/676-resolve-field-type/build.py b/test/676-resolve-field-type/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/676-resolve-field-type/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/676-resolve-field-type/test-metadata.json b/test/676-resolve-field-type/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/676-resolve-field-type/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/677-fsi/build b/test/677-fsi/build
deleted file mode 100755
index b90b408..0000000
--- a/test/677-fsi/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-./default-build "$@" --zip-compression-method store --zip-align 4
diff --git a/test/677-fsi/build.py b/test/677-fsi/build.py
new file mode 100644
index 0000000..69afbf3
--- /dev/null
+++ b/test/677-fsi/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(zip_compression_method="store", zip_align_bytes=4)
diff --git a/test/677-fsi/check b/test/677-fsi/check
deleted file mode 100644
index 8c7f18b..0000000
--- a/test/677-fsi/check
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Only keep the lines we're interested in.
-sed -s '/Hello World/!d' "$2" > "$2.tmp"
-sed -s '/^.*: oat file has dex code, but APK has uncompressed dex code/!d' "$4" > "$4.tmp"
-
-# Remove part of message containing filename.
-sed -s 's/^.*: //' "$4.tmp" > "$4.tmp2"
-
-diff --strip-trailing-cr -q "$1" "$2.tmp" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4.tmp2" >/dev/null
diff --git a/test/677-fsi/run b/test/677-fsi/run
deleted file mode 100644
index 30d925e..0000000
--- a/test/677-fsi/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# Redirect logger to stderr, as the test relies on error
-# messages being printed there.
-exec ${RUN} $@ -Xcompiler-option --copy-dex-files=always --runtime-option -Xonly-use-system-oat-files --runtime-option -Xuse-stderr-logger
diff --git a/test/677-fsi/run.py b/test/677-fsi/run.py
new file mode 100644
index 0000000..357692f
--- /dev/null
+++ b/test/677-fsi/run.py
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+
+
+def run(ctx, args):
+  # Redirect logger to stderr, as the test relies on error
+  # messages being printed there.
+  ctx.default_run(
+      args,
+      Xcompiler_option=["--copy-dex-files=always"],
+      runtime_option=["-Xonly-use-system-oat-files", "-Xuse-stderr-logger"])
+
+  # Only keep the lines we're interested in.
+  ctx.run(fr"sed -i '/Hello World/!d' '{args.stdout_file}'")
+  ctx.run(
+      fr"sed -i '/^.*: oat file has dex code, but APK has uncompressed dex code/!d' '{args.stderr_file}'"
+  )
+
+  # Remove part of message containing filename.
+  ctx.run(fr"sed -i 's/^.*: //' '{args.stderr_file}'")
diff --git a/test/677-fsi2/run b/test/677-fsi2/run
deleted file mode 100644
index 651f082..0000000
--- a/test/677-fsi2/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-${RUN} $@ --runtime-option -Xonly-use-system-oat-files
diff --git a/test/677-fsi2/run.py b/test/677-fsi2/run.py
new file mode 100644
index 0000000..0bbabb9
--- /dev/null
+++ b/test/677-fsi2/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, runtime_option=["-Xonly-use-system-oat-files"])
diff --git a/test/678-quickening/run b/test/678-quickening/run
deleted file mode 100644
index 0cc87f3..0000000
--- a/test/678-quickening/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.i
-
-# Run without an app image to prevent the class NotLoaded to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
diff --git a/test/678-quickening/run.py b/test/678-quickening/run.py
new file mode 100644
index 0000000..ea7a2bb
--- /dev/null
+++ b/test/678-quickening/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.i
+
+
+def run(ctx, args):
+  # Run without an app image to prevent the class NotLoaded to be loaded at startup.
+  ctx.default_run(args, app_image=False)
diff --git a/test/679-locks/run b/test/679-locks/run
deleted file mode 100644
index 0cc87f3..0000000
--- a/test/679-locks/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.i
-
-# Run without an app image to prevent the class NotLoaded to be loaded at startup.
-exec ${RUN} "${@}" --no-app-image
diff --git a/test/679-locks/run.py b/test/679-locks/run.py
new file mode 100644
index 0000000..ea7a2bb
--- /dev/null
+++ b/test/679-locks/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.i
+
+
+def run(ctx, args):
+  # Run without an app image to prevent the class NotLoaded to be loaded at startup.
+  ctx.default_run(args, app_image=False)
diff --git a/test/683-clinit-inline-static-invoke/src-multidex/MyCalendarUtils.java b/test/683-clinit-inline-static-invoke/src-multidex/MyCalendarUtils.java
new file mode 100644
index 0000000..fc79e32
--- /dev/null
+++ b/test/683-clinit-inline-static-invoke/src-multidex/MyCalendarUtils.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+import sun.util.calendar.CalendarUtils;
+
+public abstract class MyCalendarUtils extends CalendarUtils {
+  // Reference to MyCalendarUtils.isJulianLeapYear() shall resolve to
+  // CalendarUtils.isJulianLeapYear() which should be easily inlined.
+}
diff --git a/test/683-clinit-inline-static-invoke/src-multidex/MyModifier.java b/test/683-clinit-inline-static-invoke/src-multidex/MyModifier.java
deleted file mode 100644
index bc01940..0000000
--- a/test/683-clinit-inline-static-invoke/src-multidex/MyModifier.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-import java.lang.reflect.Modifier;
-
-public abstract class MyModifier extends Modifier {
-  // Reference to MyModifier.classModifiers() shall resolve to
-  // Modifier.classModifiers() which should be easily inlined.
-}
diff --git a/test/683-clinit-inline-static-invoke/src/Main.java b/test/683-clinit-inline-static-invoke/src/Main.java
index 6f15e27..d222539 100644
--- a/test/683-clinit-inline-static-invoke/src/Main.java
+++ b/test/683-clinit-inline-static-invoke/src/Main.java
@@ -26,6 +26,6 @@
     // TypeId in the current DexFile, we erroneously provided the type index from the
     // declaring DexFile and that caused a crash. This was fixed by changing the
     // ClinitCheck entrypoint to take the Class reference from LoadClass.
-    int placeholder = MyModifier.classModifiers();
+    boolean placeholder = MyCalendarUtils.isJulianLeapYear(-43);
   }
 }
diff --git a/test/688-shared-library/check b/test/688-shared-library/check
deleted file mode 100644
index 8501835..0000000
--- a/test/688-shared-library/check
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Finalizers of DexFile will complain not being able to close
-# the main dex file, as it's still open. That's OK to ignore.
-# Oat file manager will also complain about duplicate dex files. Ignore.
-sed -e '/^E\/System/d' "$4" | sed -e '/.*oat_file_manager.*/d' > "$4.tmp"
-
-diff --strip-trailing-cr -q "$1" "$2" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4.tmp" >/dev/null
diff --git a/test/688-shared-library/run b/test/688-shared-library/run
deleted file mode 100644
index fa6ab58..0000000
--- a/test/688-shared-library/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2018 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.
-
-# App images are incompatible with what the test is doing: loading one
-# dex file multiple times.
-exec ${RUN} "${@}" --no-app-image
diff --git a/test/688-shared-library/run.py b/test/688-shared-library/run.py
new file mode 100644
index 0000000..f71e350
--- /dev/null
+++ b/test/688-shared-library/run.py
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+
+
+def run(ctx, args):
+  # App images are incompatible with what the test is doing: loading one
+  # dex file multiple times.
+  ctx.default_run(args, app_image=False)
+
+  # Finalizers of DexFile will complain not being able to close
+  # the main dex file, as it's still open. That's OK to ignore.
+  # Oat file manager will also complain about duplicate dex files. Ignore.
+  ctx.run(
+      fr"sed -i -e '/^E\/System/d' -e '/.*oat_file_manager.*/d' '{args.stderr_file}'"
+  )
diff --git a/test/689-zygote-jit-deopt/build.py b/test/689-zygote-jit-deopt/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/689-zygote-jit-deopt/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/689-zygote-jit-deopt/run b/test/689-zygote-jit-deopt/run
deleted file mode 100644
index 7b4b7eb..0000000
--- a/test/689-zygote-jit-deopt/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-./default-run "$@" --zygote
diff --git a/test/689-zygote-jit-deopt/run.py b/test/689-zygote-jit-deopt/run.py
new file mode 100644
index 0000000..3c4f0fe
--- /dev/null
+++ b/test/689-zygote-jit-deopt/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, zygote=True)
diff --git a/test/689-zygote-jit-deopt/test-metadata.json b/test/689-zygote-jit-deopt/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/689-zygote-jit-deopt/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/690-hiddenapi-same-name-methods/build b/test/690-hiddenapi-same-name-methods/build
deleted file mode 100644
index c364b3b..0000000
--- a/test/690-hiddenapi-same-name-methods/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-USE_HIDDENAPI=true ./default-build "$@"
diff --git a/test/690-hiddenapi-same-name-methods/build.py b/test/690-hiddenapi-same-name-methods/build.py
new file mode 100644
index 0000000..942bb00
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(use_hiddenapi=True)
diff --git a/test/691-hiddenapi-proxy/build b/test/691-hiddenapi-proxy/build
deleted file mode 100644
index c364b3b..0000000
--- a/test/691-hiddenapi-proxy/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-USE_HIDDENAPI=true ./default-build "$@"
diff --git a/test/691-hiddenapi-proxy/build.py b/test/691-hiddenapi-proxy/build.py
new file mode 100644
index 0000000..942bb00
--- /dev/null
+++ b/test/691-hiddenapi-proxy/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(use_hiddenapi=True)
diff --git a/test/692-vdex-inmem-loader/build.py b/test/692-vdex-inmem-loader/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/692-vdex-inmem-loader/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/692-vdex-inmem-loader/test-metadata.json b/test/692-vdex-inmem-loader/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/692-vdex-inmem-loader/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/692-vdex-inmem-loader/vdex_inmem_loader.cc b/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
index 1c7b6e8..a478f26 100644
--- a/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
+++ b/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
@@ -45,7 +45,7 @@
 
   std::vector<const DexFile*> dex_files;
   VisitClassLoaderDexFiles(
-      soa,
+      soa.Self(),
       h_loader,
       [&](const DexFile* dex_file) {
         dex_files.push_back(dex_file);
@@ -84,7 +84,7 @@
 
   std::vector<const DexFile*> dex_files;
   VisitClassLoaderDexFiles(
-      soa,
+      soa.Self(),
       h_loader,
       [&](const DexFile* dex_file) {
         dex_files.push_back(dex_file);
@@ -116,7 +116,7 @@
   bool all_backed_by_oat = false;
 
   VisitClassLoaderDexFiles(
-      soa,
+      soa.Self(),
       h_loader,
       [&](const DexFile* dex_file) {
         bool is_backed_by_oat = (dex_file->GetOatDexFile() != nullptr);
@@ -141,7 +141,7 @@
 
   std::vector<const DexFile*> dex_files;
   VisitClassLoaderDexFiles(
-      soa,
+      soa.Self(),
       h_loader,
       [&](const DexFile* dex_file) {
         dex_files.push_back(dex_file);
diff --git a/test/692-vdex-secondary-loader/build.py b/test/692-vdex-secondary-loader/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/692-vdex-secondary-loader/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/692-vdex-secondary-loader/run b/test/692-vdex-secondary-loader/run
deleted file mode 100644
index 35b55d6..0000000
--- a/test/692-vdex-secondary-loader/run
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2021 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.
-
-# Disable dex2oat of secondary dex files.
-${RUN} "$@" --no-secondary-compilation
-return_status1=$?
-
-# Set low RAM to hit the Madvise code which used to crash
-${RUN} "$@" --runtime-option -XX:LowMemoryMode --no-secondary-compilation
-return_status2=$?
-
-# Make sure we don't silently ignore an early failure.
-(exit $return_status1) && (exit $return_status2)
diff --git a/test/692-vdex-secondary-loader/run.py b/test/692-vdex-secondary-loader/run.py
new file mode 100644
index 0000000..7c9de5c
--- /dev/null
+++ b/test/692-vdex-secondary-loader/run.py
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 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.
+
+
+def run(ctx, args):
+  # Disable dex2oat of secondary dex files.
+  ctx.default_run(args, secondary_compilation=False)
+
+  # Set low RAM to hit the Madvise code which used to crash
+  ctx.default_run(
+      args, runtime_option=["-XX:LowMemoryMode"], secondary_compilation=False)
diff --git a/test/692-vdex-secondary-loader/test-metadata.json b/test/692-vdex-secondary-loader/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/692-vdex-secondary-loader/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/693-vdex-inmem-loader-evict/build.py b/test/693-vdex-inmem-loader-evict/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/693-vdex-inmem-loader-evict/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/693-vdex-inmem-loader-evict/test-metadata.json b/test/693-vdex-inmem-loader-evict/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/693-vdex-inmem-loader-evict/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/695-simplify-throws/src/Main.java b/test/695-simplify-throws/src/Main.java
index 2799624..9a95d80 100644
--- a/test/695-simplify-throws/src/Main.java
+++ b/test/695-simplify-throws/src/Main.java
@@ -15,31 +15,31 @@
  */
 
 public class Main {
-  public static boolean alwaysThrows() {
-    throw new Error("");
-  }
-
-  public static void test() {
-    alwaysThrows();
-    while (condition) {
-      int a = 2;
-      while (otherCondition) {
-        a = 3;
-      }
-      staticField = a;
+    public static boolean alwaysThrows() {
+        throw new Error("");
     }
-  }
 
-  public static void main(String[] args) throws Exception {
-    try {
-      test();
-      throw new Exception("Unexpected exception");
-    } catch (Error e) {
-      // Expected.
+    public static void test() {
+        alwaysThrows();
+        while (condition) {
+            int a = 2;
+            while (otherCondition) {
+                a = 3;
+            }
+            staticField = a;
+        }
     }
-  }
 
-  static boolean condition = false;
-  static boolean otherCondition = false;
-  static int staticField = 1;
+    public static void main(String[] args) throws Exception {
+        try {
+            test();
+            throw new Exception("Unexpected exception");
+        } catch (Error e) {
+            // Expected.
+        }
+    }
+
+    static boolean condition = false;
+    static boolean otherCondition = false;
+    static int staticField = 1;
 }
diff --git a/test/696-loop/src/Main.java b/test/696-loop/src/Main.java
index b92166a..9763a8e 100644
--- a/test/696-loop/src/Main.java
+++ b/test/696-loop/src/Main.java
@@ -15,24 +15,24 @@
  */
 
 public class Main {
-  static int[] sA = new int[12];
-  static int a = 1;
+    static int[] sA = new int[12];
+    static int a = 1;
 
-  static void doIt(int n) {
-    for (int i = 0; i < 2; i++) {
-      n+=a;
+    static void doIt(int n) {
+        for (int i = 0; i < 2; i++) {
+            n += a;
+        }
+        for (int i = 0; i < n; i++) {
+            sA[i] += 1;
+        }
     }
-    for (int i = 0; i < n; i++) {
-      sA[i] += 1;
-    }
-  }
 
-  public static void main(String[] args) {
-    doIt(10);
-    for (int i = 0; i < sA.length; i++) {
-      if (sA[i] != 1) {
-        throw new Error("Expected 1, got " + sA[i]);
-      }
+    public static void main(String[] args) {
+        doIt(10);
+        for (int i = 0; i < sA.length; i++) {
+            if (sA[i] != 1) {
+                throw new Error("Expected 1, got " + sA[i]);
+            }
+        }
     }
-  }
 }
diff --git a/test/697-checker-string-append/src/Main.java b/test/697-checker-string-append/src/Main.java
index c63c328..e35986a 100644
--- a/test/697-checker-string-append/src/Main.java
+++ b/test/697-checker-string-append/src/Main.java
@@ -18,6 +18,9 @@
     public static void main(String[] args) {
         testAppendStringAndLong();
         testAppendStringAndInt();
+        testAppendStringAndFloat();
+        testAppendStringAndDouble();
+        testAppendDoubleAndFloat();
         testAppendStringAndString();
         testMiscelaneous();
         testNoArgs();
@@ -186,6 +189,159 @@
         }
     }
 
+    private static final String APPEND_FLOAT_PREFIX = "Float/";
+    private static final String[] APPEND_FLOAT_TEST_CASES = {
+        // We're testing only exact values here, i.e. values that do not require rounding.
+        "Float/1.0",
+        "Float/9.0",
+        "Float/10.0",
+        "Float/99.0",
+        "Float/100.0",
+        "Float/999.0",
+        "Float/1000.0",
+        "Float/9999.0",
+        "Float/10000.0",
+        "Float/99999.0",
+        "Float/100000.0",
+        "Float/999999.0",
+        "Float/1000000.0",
+        "Float/9999999.0",
+        "Float/1.0E7",
+        "Float/1.0E10",
+        "Float/-1.0",
+        "Float/-9.0",
+        "Float/-10.0",
+        "Float/-99.0",
+        "Float/-100.0",
+        "Float/-999.0",
+        "Float/-1000.0",
+        "Float/-9999.0",
+        "Float/-10000.0",
+        "Float/-99999.0",
+        "Float/-100000.0",
+        "Float/-999999.0",
+        "Float/-1000000.0",
+        "Float/-9999999.0",
+        "Float/-1.0E7",
+        "Float/-1.0E10",
+        "Float/0.25",
+        "Float/1.625",
+        "Float/9.3125",
+        "Float/-0.25",
+        "Float/-1.625",
+        "Float/-9.3125",
+    };
+
+    /// CHECK-START: java.lang.String Main.$noinline$appendStringAndFloat(java.lang.String, float) instruction_simplifier (before)
+    /// CHECK-NOT:              StringBuilderAppend
+
+    /// CHECK-START: java.lang.String Main.$noinline$appendStringAndFloat(java.lang.String, float) instruction_simplifier (after)
+    /// CHECK:                  StringBuilderAppend
+    public static String $noinline$appendStringAndFloat(String s, float f) {
+        return new StringBuilder().append(s).append(f).toString();
+    }
+
+    public static void testAppendStringAndFloat() {
+        for (String expected : APPEND_FLOAT_TEST_CASES) {
+            float f = Float.valueOf(expected.substring(APPEND_FLOAT_PREFIX.length()));
+            String result = $noinline$appendStringAndFloat(APPEND_FLOAT_PREFIX, f);
+            assertEquals(expected, result);
+        }
+        // Special values.
+        assertEquals("Float/NaN", $noinline$appendStringAndFloat(APPEND_FLOAT_PREFIX, Float.NaN));
+        assertEquals("Float/Infinity",
+                     $noinline$appendStringAndFloat(APPEND_FLOAT_PREFIX, Float.POSITIVE_INFINITY));
+        assertEquals("Float/-Infinity",
+                     $noinline$appendStringAndFloat(APPEND_FLOAT_PREFIX, Float.NEGATIVE_INFINITY));
+    }
+
+    private static final String APPEND_DOUBLE_PREFIX = "Double/";
+    private static final String[] APPEND_DOUBLE_TEST_CASES = {
+        // We're testing only exact values here, i.e. values that do not require rounding.
+        "Double/1.0",
+        "Double/9.0",
+        "Double/10.0",
+        "Double/99.0",
+        "Double/100.0",
+        "Double/999.0",
+        "Double/1000.0",
+        "Double/9999.0",
+        "Double/10000.0",
+        "Double/99999.0",
+        "Double/100000.0",
+        "Double/999999.0",
+        "Double/1000000.0",
+        "Double/9999999.0",
+        "Double/1.0E7",
+        "Double/1.0E24",
+        "Double/-1.0",
+        "Double/-9.0",
+        "Double/-10.0",
+        "Double/-99.0",
+        "Double/-100.0",
+        "Double/-999.0",
+        "Double/-1000.0",
+        "Double/-9999.0",
+        "Double/-10000.0",
+        "Double/-99999.0",
+        "Double/-100000.0",
+        "Double/-999999.0",
+        "Double/-1000000.0",
+        "Double/-9999999.0",
+        "Double/-1.0E7",
+        "Double/-1.0E24",
+        "Double/0.25",
+        "Double/1.625",
+        "Double/9.3125",
+        "Double/-0.25",
+        "Double/-1.625",
+        "Double/-9.3125",
+    };
+
+    /// CHECK-START: java.lang.String Main.$noinline$appendStringAndDouble(java.lang.String, double) instruction_simplifier (before)
+    /// CHECK-NOT:              StringBuilderAppend
+
+    /// CHECK-START: java.lang.String Main.$noinline$appendStringAndDouble(java.lang.String, double) instruction_simplifier (after)
+    /// CHECK:                  StringBuilderAppend
+    public static String $noinline$appendStringAndDouble(String s, double d) {
+        return new StringBuilder().append(s).append(d).toString();
+    }
+
+    public static void testAppendStringAndDouble() {
+        for (String expected : APPEND_DOUBLE_TEST_CASES) {
+            double f = Double.valueOf(expected.substring(APPEND_DOUBLE_PREFIX.length()));
+            String result = $noinline$appendStringAndDouble(APPEND_DOUBLE_PREFIX, f);
+            assertEquals(expected, result);
+        }
+        // Special values.
+        assertEquals(
+            "Double/NaN",
+            $noinline$appendStringAndDouble(APPEND_DOUBLE_PREFIX, Double.NaN));
+        assertEquals(
+            "Double/Infinity",
+            $noinline$appendStringAndDouble(APPEND_DOUBLE_PREFIX, Double.POSITIVE_INFINITY));
+        assertEquals(
+            "Double/-Infinity",
+            $noinline$appendStringAndDouble(APPEND_DOUBLE_PREFIX, Double.NEGATIVE_INFINITY));
+    }
+
+    /// CHECK-START: java.lang.String Main.$noinline$appendDoubleAndFloat(double, float) instruction_simplifier (before)
+    /// CHECK-NOT:              StringBuilderAppend
+
+    /// CHECK-START: java.lang.String Main.$noinline$appendDoubleAndFloat(double, float) instruction_simplifier (after)
+    /// CHECK:                  StringBuilderAppend
+    public static String $noinline$appendDoubleAndFloat(double d, float f) {
+        return new StringBuilder().append(d).append(f).toString();
+    }
+
+    public static void testAppendDoubleAndFloat() {
+        assertEquals("1.50.325", $noinline$appendDoubleAndFloat(1.5, 0.325f));
+        assertEquals("1.5E170.3125", $noinline$appendDoubleAndFloat(1.5E17, 0.3125f));
+        assertEquals("1.0E8NaN", $noinline$appendDoubleAndFloat(1.0E8, Float.NaN));
+        assertEquals("Infinity0.5", $noinline$appendDoubleAndFloat(Double.POSITIVE_INFINITY, 0.5f));
+        assertEquals("2.5-Infinity", $noinline$appendDoubleAndFloat(2.5, Float.NEGATIVE_INFINITY));
+    }
+
     public static String $noinline$appendStringAndString(String s1, String s2) {
         return new StringBuilder().append(s1).append(s2).toString();
     }
diff --git a/test/698-selects/src/Main.java b/test/698-selects/src/Main.java
index 1fadb86..514770a 100644
--- a/test/698-selects/src/Main.java
+++ b/test/698-selects/src/Main.java
@@ -15,28 +15,28 @@
  */
 
 public class Main {
-  public static int mZenMode = 0;
+    public static int mZenMode = 0;
 
-  public static int $noinline$foo(int internal, boolean check1, boolean check2) {
-    int result = internal;
-    if (check1) {
-      // This block is to ensure `result` is a phi in the return block. Without this block
-      // the compiler could just generate one block with selects.
-      if (check2) {
-        mZenMode = 42;
-      }
-      result = (internal == 1) ? 1 : 0;
+    public static int $noinline$foo(int internal, boolean check1, boolean check2) {
+        int result = internal;
+        if (check1) {
+            // This block is to ensure `result` is a phi in the return block. Without this block
+            // the compiler could just generate one block with selects.
+            if (check2) {
+                mZenMode = 42;
+            }
+            result = (internal == 1) ? 1 : 0;
+        }
+        // The optimization bug was to make the incorrect assumption that:
+        //    phi = (internal, (internal == 1))
+        // meant `internal` was a boolean.
+        return result;
     }
-    // The optimization bug was to make the incorrect assumption that:
-    //    phi = (internal, (internal == 1))
-    // meant `internal` was a boolean.
-    return result;
-  }
 
-  public static void main(String[] args) {
-    int result = $noinline$foo(2, true, true);
-    if (result != 0) {
-      throw new Error("Expected 0, got " + result);
+    public static void main(String[] args) {
+        int result = $noinline$foo(2, true, true);
+        if (result != 0) {
+            throw new Error("Expected 0, got " + result);
+        }
     }
-  }
 }
diff --git a/test/701-easy-div-rem/build b/test/701-easy-div-rem/build
deleted file mode 100644
index 6d114b6..0000000
--- a/test/701-easy-div-rem/build
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Stop if something fails.
-set -e
-
-# Write out the source file.
-mkdir src
-python3 ./genMain.py
-
-./default-build "$@"
diff --git a/test/701-easy-div-rem/build.py b/test/701-easy-div-rem/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/701-easy-div-rem/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/701-easy-div-rem/generate-sources b/test/701-easy-div-rem/generate-sources
new file mode 100755
index 0000000..efe4b05
--- /dev/null
+++ b/test/701-easy-div-rem/generate-sources
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+# Stop if something fails.
+set -e
+
+# Write out the source file.
+mkdir src
+python3 ./genMain.py
diff --git a/test/702-LargeBranchOffset/build b/test/702-LargeBranchOffset/build
deleted file mode 100644
index 2505b0a..0000000
--- a/test/702-LargeBranchOffset/build
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Stop if something fails.
-set -e
-
-# Write out the source file.
-mkdir -p src
-./generate
-
-./default-build "$@"
diff --git a/test/702-LargeBranchOffset/build.py b/test/702-LargeBranchOffset/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/702-LargeBranchOffset/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/702-LargeBranchOffset/generate-sources b/test/702-LargeBranchOffset/generate-sources
new file mode 100755
index 0000000..b01afc4
--- /dev/null
+++ b/test/702-LargeBranchOffset/generate-sources
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+# Stop if something fails.
+set -e
+
+# Write out the source file.
+mkdir -p src
+./generate
diff --git a/test/706-checker-scheduler/src/Main.java b/test/706-checker-scheduler/src/Main.java
index 1b8377d..255599d 100644
--- a/test/706-checker-scheduler/src/Main.java
+++ b/test/706-checker-scheduler/src/Main.java
@@ -66,23 +66,23 @@
   }
 
   /// CHECK-START-ARM: void Main.arrayAccessVariable(int) scheduler (before)
-  /// CHECK:     <<Param:i\d+>>        ParameterValue
+  /// CHECK-DAG: <<Param:i\d+>>        ParameterValue
   /// CHECK-DAG: <<Const1:i\d+>>       IntConstant 1
   /// CHECK-DAG: <<Const2:i\d+>>       IntConstant 2
   /// CHECK-DAG: <<Const3:i\d+>>       IntConstant -1
-  /// CHECK:     <<Add1:i\d+>>         Add [<<Param>>,<<Const1>>]
-  /// CHECK:     <<Add2:i\d+>>         Add [<<Param>>,<<Const2>>]
-  /// CHECK:     <<Add3:i\d+>>         Add [<<Param>>,<<Const3>>]
-  /// CHECK:     <<Array:i\d+>>        IntermediateAddress
-  /// CHECK:     <<ArrayGet1:i\d+>>    ArrayGet [<<Array>>,<<Add1>>]
-  /// CHECK:     <<AddArray1:i\d+>>    Add [<<ArrayGet1>>,<<Const1>>]
-  /// CHECK:     <<ArraySet1:v\d+>>    ArraySet [<<Array>>,<<Add1>>,<<AddArray1>>]
-  /// CHECK:     <<ArrayGet2:i\d+>>    ArrayGet [<<Array>>,<<Add2>>]
-  /// CHECK:     <<AddArray2:i\d+>>    Add [<<ArrayGet2>>,<<Const1>>]
-  /// CHECK:     <<ArraySet2:v\d+>>    ArraySet [<<Array>>,<<Add2>>,<<AddArray2>>]
-  /// CHECK:     <<ArrayGet3:i\d+>>    ArrayGet [<<Array>>,<<Add3>>]
-  /// CHECK:     <<AddArray3:i\d+>>    Add [<<ArrayGet3>>,<<Const1>>]
-  /// CHECK:     <<ArraySet3:v\d+>>    ArraySet [<<Array>>,<<Add3>>,<<AddArray3>>]
+  /// CHECK-DAG: <<Add1:i\d+>>         Add [<<Param>>,<<Const1>>]
+  /// CHECK-DAG: <<Add2:i\d+>>         Add [<<Param>>,<<Const2>>]
+  /// CHECK-DAG: <<Add3:i\d+>>         Add [<<Param>>,<<Const3>>]
+  /// CHECK-DAG: <<Array:i\d+>>        IntermediateAddress
+  /// CHECK-DAG: <<ArrayGet1:i\d+>>    ArrayGet [<<Array>>,<<Add1>>]
+  /// CHECK-DAG: <<ArrayGet2:i\d+>>    ArrayGet [<<Array>>,<<Add2>>]
+  /// CHECK-DAG: <<ArrayGet3:i\d+>>    ArrayGet [<<Array>>,<<Add3>>]
+  /// CHECK-DAG: <<AddArray1:i\d+>>    Add [<<ArrayGet1>>,<<Const2>>]
+  /// CHECK-DAG: {{v\d+}}              ArraySet [<<Array>>,<<Add1>>,<<AddArray1>>]
+  /// CHECK-DAG: <<AddArray2:i\d+>>    Add [<<ArrayGet2>>,<<Const2>>]
+  /// CHECK-DAG: {{v\d+}}              ArraySet [<<Array>>,<<Add2>>,<<AddArray2>>]
+  /// CHECK-DAG: <<AddArray3:i\d+>>    Add [<<ArrayGet3>>,<<Const2>>]
+  /// CHECK-DAG: {{v\d+}}              ArraySet [<<Array>>,<<Add3>>,<<AddArray3>>]
 
   /// CHECK-START-ARM: void Main.arrayAccessVariable(int) scheduler (after)
   /// CHECK:     <<Param:i\d+>>        ParameterValue
@@ -104,23 +104,23 @@
   /// CHECK:                           ArraySet
 
   /// CHECK-START-ARM64: void Main.arrayAccessVariable(int) scheduler (before)
-  /// CHECK:     <<Param:i\d+>>        ParameterValue
+  /// CHECK-DAG: <<Param:i\d+>>        ParameterValue
   /// CHECK-DAG: <<Const1:i\d+>>       IntConstant 1
   /// CHECK-DAG: <<Const2:i\d+>>       IntConstant 2
   /// CHECK-DAG: <<Const3:i\d+>>       IntConstant -1
-  /// CHECK:     <<Add1:i\d+>>         Add [<<Param>>,<<Const1>>]
-  /// CHECK:     <<Add2:i\d+>>         Add [<<Param>>,<<Const2>>]
-  /// CHECK:     <<Add3:i\d+>>         Add [<<Param>>,<<Const3>>]
-  /// CHECK:     <<Array:i\d+>>        IntermediateAddress
-  /// CHECK:     <<ArrayGet1:i\d+>>    ArrayGet [<<Array>>,<<Add1>>]
-  /// CHECK:     <<AddArray1:i\d+>>    Add [<<ArrayGet1>>,<<Const1>>]
-  /// CHECK:     <<ArraySet1:v\d+>>    ArraySet [<<Array>>,<<Add1>>,<<AddArray1>>]
-  /// CHECK:     <<ArrayGet2:i\d+>>    ArrayGet [<<Array>>,<<Add2>>]
-  /// CHECK:     <<AddArray2:i\d+>>    Add [<<ArrayGet2>>,<<Const1>>]
-  /// CHECK:     <<ArraySet2:v\d+>>    ArraySet [<<Array>>,<<Add2>>,<<AddArray2>>]
-  /// CHECK:     <<ArrayGet3:i\d+>>    ArrayGet [<<Array>>,<<Add3>>]
-  /// CHECK:     <<AddArray3:i\d+>>    Add [<<ArrayGet3>>,<<Const1>>]
-  /// CHECK:     <<ArraySet3:v\d+>>    ArraySet [<<Array>>,<<Add3>>,<<AddArray3>>]
+  /// CHECK-DAG: <<Add1:i\d+>>         Add [<<Param>>,<<Const1>>]
+  /// CHECK-DAG: <<Add2:i\d+>>         Add [<<Param>>,<<Const2>>]
+  /// CHECK-DAG: <<Add3:i\d+>>         Add [<<Param>>,<<Const3>>]
+  /// CHECK-DAG: <<Array:i\d+>>        IntermediateAddress
+  /// CHECK-DAG: <<ArrayGet1:i\d+>>    ArrayGet [<<Array>>,<<Add1>>]
+  /// CHECK-DAG: <<ArrayGet2:i\d+>>    ArrayGet [<<Array>>,<<Add2>>]
+  /// CHECK-DAG: <<ArrayGet3:i\d+>>    ArrayGet [<<Array>>,<<Add3>>]
+  /// CHECK-DAG: <<AddArray1:i\d+>>    Add [<<ArrayGet1>>,<<Const2>>]
+  /// CHECK-DAG: {{v\d+}}              ArraySet [<<Array>>,<<Add1>>,<<AddArray1>>]
+  /// CHECK-DAG: <<AddArray2:i\d+>>    Add [<<ArrayGet2>>,<<Const2>>]
+  /// CHECK-DAG: {{v\d+}}              ArraySet [<<Array>>,<<Add2>>,<<AddArray2>>]
+  /// CHECK-DAG: <<AddArray3:i\d+>>    Add [<<ArrayGet3>>,<<Const2>>]
+  /// CHECK-DAG:                       ArraySet [<<Array>>,<<Add3>>,<<AddArray3>>]
 
   /// CHECK-START-ARM64: void Main.arrayAccessVariable(int) scheduler (after)
   /// CHECK:     <<Param:i\d+>>        ParameterValue
@@ -131,9 +131,9 @@
   /// CHECK:     <<Add2:i\d+>>         Add [<<Param>>,<<Const2>>]
   /// CHECK:     <<Add3:i\d+>>         Add [<<Param>>,<<Const3>>]
   /// CHECK:     <<Array:i\d+>>        IntermediateAddress
-  /// CHECK:                           ArrayGet [<<Array>>,{{i\d+}}]
-  /// CHECK:                           ArrayGet [<<Array>>,{{i\d+}}]
-  /// CHECK:                           ArrayGet [<<Array>>,{{i\d+}}]
+  /// CHECK:                           ArrayGet [<<Array>>,<<Add3>>]
+  /// CHECK:                           ArrayGet [<<Array>>,<<Add2>>]
+  /// CHECK:                           ArrayGet [<<Array>>,<<Add1>>]
   /// CHECK:                           Add
   /// CHECK:                           Add
   /// CHECK:                           Add
diff --git a/test/707-checker-invalid-profile/check b/test/707-checker-invalid-profile/check
deleted file mode 100755
index 58d3a52..0000000
--- a/test/707-checker-invalid-profile/check
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# When profile verification fails, dex2oat logs an error. The following
-# command strips out the error message.
-grep -v -f $1 $2 > $1
-grep -v -f $3 $4 > $3
-
-./default-check "$@"
diff --git a/test/707-checker-invalid-profile/run b/test/707-checker-invalid-profile/run
deleted file mode 100644
index 146e180..0000000
--- a/test/707-checker-invalid-profile/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile
diff --git a/test/707-checker-invalid-profile/run.py b/test/707-checker-invalid-profile/run.py
new file mode 100644
index 0000000..1616f1b
--- /dev/null
+++ b/test/707-checker-invalid-profile/run.py
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args, profile=True, Xcompiler_option=["--compiler-filter=speed-profile"])
+
+  # When profile verification fails, dex2oat logs an error. The following
+  # command strips out the error message.
+  ctx.run(
+      fr"grep -v -f expected-stdout.txt '{args.stdout_file}' > expected-stdout.txt",
+      check=False)
+  ctx.run(
+      fr"grep -v -f expected-stderr.txt '{args.stderr_file}' > expected-stderr.txt",
+      check=False)
diff --git a/test/710-varhandle-creation/build b/test/710-varhandle-creation/build
deleted file mode 100644
index ca1e557..0000000
--- a/test/710-varhandle-creation/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/710-varhandle-creation/build.py b/test/710-varhandle-creation/build.py
new file mode 100644
index 0000000..7ccbe96
--- /dev/null
+++ b/test/710-varhandle-creation/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="var-handles")
diff --git a/test/710-varhandle-creation/run b/test/710-varhandle-creation/run
deleted file mode 100644
index 46b1a83..0000000
--- a/test/710-varhandle-creation/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2021 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.
-
-# Currently app images aren't unloaded when dex files are unloaded.
-exec ${RUN} $@ --no-secondary-app-image
diff --git a/test/710-varhandle-creation/run.py b/test/710-varhandle-creation/run.py
new file mode 100644
index 0000000..930869a
--- /dev/null
+++ b/test/710-varhandle-creation/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 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.
+
+
+def run(ctx, args):
+  # Currently app images aren't unloaded when dex files are unloaded.
+  ctx.default_run(args, secondary_app_image=False)
diff --git a/test/710-varhandle-creation/src/Main.java b/test/710-varhandle-creation/src/Main.java
index c4fe2ff..6ddc58a 100644
--- a/test/710-varhandle-creation/src/Main.java
+++ b/test/710-varhandle-creation/src/Main.java
@@ -271,6 +271,7 @@
     private static void checkInstantiatedVarHandles() {
         System.out.print("checkInstantiatedVarHandles...");
 
+        // clang-format off
         System.out.print("vz...");
         checkNotNull(vz);
         checkVarType(vz, boolean.class);
@@ -2052,6 +2053,7 @@
 
         System.out.print("vbbo...");
         checkNull(vbbo);
+        // clang-format on
 
         System.out.println("PASS");
     }
@@ -2090,6 +2092,7 @@
         if (VarHandle.AccessMode.values().length != expectedLength) {
             fail("VarHandle.AccessMode.value().length != " + expectedLength);
         }
+        // clang-format off
         checkAccessMode(VarHandle.AccessMode.GET, "GET", "get", 0);
         checkAccessMode(VarHandle.AccessMode.SET, "SET", "set", 1);
         checkAccessMode(VarHandle.AccessMode.GET_VOLATILE, "GET_VOLATILE", "getVolatile", 2);
@@ -2121,6 +2124,7 @@
         checkAccessMode(VarHandle.AccessMode.GET_AND_BITWISE_XOR, "GET_AND_BITWISE_XOR", "getAndBitwiseXor", 28);
         checkAccessMode(VarHandle.AccessMode.GET_AND_BITWISE_XOR_RELEASE, "GET_AND_BITWISE_XOR_RELEASE", "getAndBitwiseXorRelease", 29);
         checkAccessMode(VarHandle.AccessMode.GET_AND_BITWISE_XOR_ACQUIRE, "GET_AND_BITWISE_XOR_ACQUIRE", "getAndBitwiseXorAcquire", 30);
+        // clang-format on
         System.out.println("PASS");
     }
 
@@ -2456,4 +2460,3 @@
         checkStaticFieldVarHandleGc();
     }
 }
-
diff --git a/test/710-varhandle-creation/test-metadata.json b/test/710-varhandle-creation/test-metadata.json
new file mode 100644
index 0000000..ed29691
--- /dev/null
+++ b/test/710-varhandle-creation/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "experimental": "var-handles"
+  }
+}
diff --git a/test/712-varhandle-invocations/build b/test/712-varhandle-invocations/build
deleted file mode 100755
index 9a6e96e..0000000
--- a/test/712-varhandle-invocations/build
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Make us exit on a failure
-set -e
-
-# Set variables for source directories. Using src-art so we use
-# VarHandles in the bootclasspath and can compile with the Java 8
-# compiler.
-MANUAL_SRC=src
-GENERATED_SRC=src2
-
-# Build the Java files
-mkdir -p src2
-
-# Collate list of manual test classes
-MANUAL_TESTS=$(cd "${MANUAL_SRC}" && find . -name 'Var*Tests.java' | sed -e 's@.*\(Var.*Tests\).*@\1@g' | sort)
-
-# Generate tests and Main that covers both the generated tests and manual tests
-python3 ./util-src/generate_java.py "${GENERATED_SRC}" ${MANUAL_TESTS}
-
-./default-build "$@" --experimental var-handles
diff --git a/test/712-varhandle-invocations/build.py b/test/712-varhandle-invocations/build.py
new file mode 100644
index 0000000..511d1c1
--- /dev/null
+++ b/test/712-varhandle-invocations/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build(api_level="var-handles")
diff --git a/test/712-varhandle-invocations/generate-sources b/test/712-varhandle-invocations/generate-sources
new file mode 100755
index 0000000..d025b26
--- /dev/null
+++ b/test/712-varhandle-invocations/generate-sources
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# Copyright 2018 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.
+
+# Make us exit on a failure
+set -e
+
+# Set variables for source directories. Using src-art so we use
+# VarHandles in the bootclasspath and can compile with the Java 8
+# compiler.
+MANUAL_SRC=src
+GENERATED_SRC=src2
+
+# Build the Java files
+mkdir -p src2
+
+# Collate list of manual test classes
+MANUAL_TESTS=$(cd "${MANUAL_SRC}" && find . -name 'Var*Tests.java' | sed -e 's@.*\(Var.*Tests\).*@\1@g' | sort)
+
+# Generate tests and Main that covers both the generated tests and manual tests
+python3 ./util-src/generate_java.py "${GENERATED_SRC}" ${MANUAL_TESTS}
diff --git a/test/712-varhandle-invocations/src/VarHandleTypeConversionTests.java b/test/712-varhandle-invocations/src/VarHandleTypeConversionTests.java
index 73e3044..51c3b95 100644
--- a/test/712-varhandle-invocations/src/VarHandleTypeConversionTests.java
+++ b/test/712-varhandle-invocations/src/VarHandleTypeConversionTests.java
@@ -54,6 +54,52 @@
         }
     }
 
+    public static class ReferenceReturnTypeTest extends VarHandleUnitTest {
+        private Object o;
+        private static final VarHandle vh;
+
+        static {
+            try {
+                Class<?> cls = VoidReturnTypeTest.class;
+                vh = MethodHandles.lookup().findVarHandle(cls, "i", Object.class);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        protected void doTest() {
+            vh.set(this, null);
+            try {
+                int i = (int) vh.get(this);
+                failUnreachable();
+            } catch (NullPointerException cce) {
+            }
+
+            vh.set(this, new Object());
+            try {
+                int i = (int) vh.get(this);
+                failUnreachable();
+            } catch (ClassCastException cce) {
+            }
+
+            vh.set(this, Integer.valueOf(42));
+            {
+                int i = (int) vh.get(this);
+            }
+
+            vh.set(this, new ReferenceReturnTypeTest());
+            try {
+                int i = (int) vh.get(this);
+            } catch (ClassCastException cce) {
+            }
+        }
+
+        public static void main(String[] args) {
+            new ReferenceReturnTypeTest().run();
+        }
+    }
+
     //
     // Tests that a null reference as a boxed primitive type argument
     // throws a NullPointerException. These vary the VarHandle type
diff --git a/test/713-varhandle-invokers/build b/test/713-varhandle-invokers/build
deleted file mode 100755
index 09d376b..0000000
--- a/test/713-varhandle-invokers/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Make us exit on a failure
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/713-varhandle-invokers/build.py b/test/713-varhandle-invokers/build.py
new file mode 100644
index 0000000..7ccbe96
--- /dev/null
+++ b/test/713-varhandle-invokers/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="var-handles")
diff --git a/test/713-varhandle-invokers/test-metadata.json b/test/713-varhandle-invokers/test-metadata.json
new file mode 100644
index 0000000..ed29691
--- /dev/null
+++ b/test/713-varhandle-invokers/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "experimental": "var-handles"
+  }
+}
diff --git a/test/714-invoke-custom-lambda-metafactory/build b/test/714-invoke-custom-lambda-metafactory/build
deleted file mode 100644
index b5002ba..0000000
--- a/test/714-invoke-custom-lambda-metafactory/build
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Make us exit on a failure
-set -e
-
-# Opt-out from desugaring to ensure offending lambda is in the DEX.
-export USE_DESUGAR=false
-./default-build "$@" --experimental method-handles
diff --git a/test/714-invoke-custom-lambda-metafactory/build.py b/test/714-invoke-custom-lambda-metafactory/build.py
new file mode 100644
index 0000000..c1b1b60
--- /dev/null
+++ b/test/714-invoke-custom-lambda-metafactory/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(use_desugar=False, api_level="method-handles")
diff --git a/test/714-invoke-custom-lambda-metafactory/expected-stdout.txt b/test/714-invoke-custom-lambda-metafactory/expected-stdout.txt
index a9bf5a0..e69de29 100644
--- a/test/714-invoke-custom-lambda-metafactory/expected-stdout.txt
+++ b/test/714-invoke-custom-lambda-metafactory/expected-stdout.txt
@@ -1 +0,0 @@
-exit status: 1
diff --git a/test/714-invoke-custom-lambda-metafactory/run b/test/714-invoke-custom-lambda-metafactory/run
deleted file mode 100755
index 7a0d0d0..0000000
--- a/test/714-invoke-custom-lambda-metafactory/run
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Squash the exit status and put it in expected
-./default-run "$@"
-echo "exit status:" $?
diff --git a/test/714-invoke-custom-lambda-metafactory/run.py b/test/714-invoke-custom-lambda-metafactory/run.py
new file mode 100644
index 0000000..80a8a33
--- /dev/null
+++ b/test/714-invoke-custom-lambda-metafactory/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, expected_exit_code=1)
diff --git a/test/715-clinit-implicit-parameter-annotations/build b/test/715-clinit-implicit-parameter-annotations/build
deleted file mode 100644
index 2b5f92c..0000000
--- a/test/715-clinit-implicit-parameter-annotations/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Make us exit on a failure
-set -e
-
-./default-build "$@" --experimental parameter-annotations
diff --git a/test/715-clinit-implicit-parameter-annotations/build.py b/test/715-clinit-implicit-parameter-annotations/build.py
new file mode 100644
index 0000000..98d6c5d
--- /dev/null
+++ b/test/715-clinit-implicit-parameter-annotations/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="parameter-annotations")
diff --git a/test/716-jli-jit-samples/build b/test/716-jli-jit-samples/build
deleted file mode 100755
index 730a8a1..0000000
--- a/test/716-jli-jit-samples/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/716-jli-jit-samples/build.py b/test/716-jli-jit-samples/build.py
new file mode 100644
index 0000000..7ccbe96
--- /dev/null
+++ b/test/716-jli-jit-samples/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="var-handles")
diff --git a/test/716-jli-jit-samples/test-metadata.json b/test/716-jli-jit-samples/test-metadata.json
new file mode 100644
index 0000000..ed29691
--- /dev/null
+++ b/test/716-jli-jit-samples/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "experimental": "var-handles"
+  }
+}
diff --git a/test/719-varhandle-concurrency/build b/test/719-varhandle-concurrency/build
deleted file mode 100755
index 98a9967..0000000
--- a/test/719-varhandle-concurrency/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2022 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.
-
-# Make us exit on a failure
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/719-varhandle-concurrency/build.py b/test/719-varhandle-concurrency/build.py
new file mode 100644
index 0000000..7ccbe96
--- /dev/null
+++ b/test/719-varhandle-concurrency/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="var-handles")
diff --git a/test/719-varhandle-concurrency/src/Main.java b/test/719-varhandle-concurrency/src/Main.java
index 1c2135b..47f523f 100644
--- a/test/719-varhandle-concurrency/src/Main.java
+++ b/test/719-varhandle-concurrency/src/Main.java
@@ -33,196 +33,196 @@
  * were not run at all (skipped by all threads).
  */
 public class Main {
-  private static final VarHandle QA;
-  static {
-      QA = MethodHandles.arrayElementVarHandle(TestTask[].class);
-  }
-
-  private static final int TASK_COUNT = 10000;
-  private static final int THREAD_COUNT = 100;
-  /* Each test may need several retries before a concurrent failure is seen. In the past, for a
-   * known bug, between 5 and 10 retries were sufficient. Use RETRIES to configure how many
-   * iterations to retry for each test scenario. However, to avoid the test running for too long,
-   * for example with gcstress, set a cap duration in MAX_RETRIES_DURATION. With this at least one
-   * iteration would run, but there could be fewer retries if each of them takes too long. */
-  private static final int RETRIES = 50;
-  private static final Duration MAX_RETRIES_DURATION = Duration.ofMinutes(1);
-
-  public static void main(String[] args) throws Throwable {
-    testConcurrentProcessing(new CompareAndExchangeRunnerFactory(), "compareAndExchange");
-    testConcurrentProcessing(new CompareAndSetRunnerFactory(), "compareAndSet");
-    testConcurrentProcessing(new WeakCompareAndSetRunnerFactory(), "weakCompareAndSet");
-  }
-
-  private static void testConcurrentProcessing(RunnerFactory factory,
-          String testName) throws Throwable {
-    final Duration startTs = Duration.ofNanos(System.nanoTime());
-    final Duration endTs = startTs.plus(MAX_RETRIES_DURATION);
-    for (int i = 0; i < RETRIES; ++i) {
-      concurrentProcessingTestIteration(factory, i, testName);
-      Duration now = Duration.ofNanos(System.nanoTime());
-      if (0 < now.compareTo(endTs)) {
-          break;
-      }
+    private static final VarHandle QA;
+    static {
+        QA = MethodHandles.arrayElementVarHandle(TestTask[].class);
     }
-  }
 
-  private static void concurrentProcessingTestIteration(RunnerFactory factory,
-          int iteration, String testName) throws Throwable {
-      final TestTask[] tasks = new TestTask[TASK_COUNT];
-      final AtomicInteger result = new AtomicInteger();
+    private static final int TASK_COUNT = 10000;
+    private static final int THREAD_COUNT = 20;
+    /* Each test may need several retries before a concurrent failure is seen. In the past, for a
+     * known bug, between 5 and 10 retries were sufficient. Use RETRIES to configure how many
+     * iterations to retry for each test scenario. However, to avoid the test running for too long,
+     * for example with gcstress, set a cap duration in MAX_RETRIES_DURATION. With this at least one
+     * iteration would run, but there could be fewer retries if each of them takes too long. */
+    private static final int RETRIES = 50;
+    // b/235431387: timeout reduced from 1 minute
+    private static final Duration MAX_RETRIES_DURATION = Duration.ofSeconds(15);
 
-      for (int i = 0; i < TASK_COUNT; ++i) {
-          tasks[i] = new TestTask(Integer.valueOf(i+1), result::addAndGet);
-      }
-
-      Thread[] threads = new Thread[THREAD_COUNT];
-      for (int i = 0; i < THREAD_COUNT; ++i) {
-          threads[i] = factory.createRunner(tasks);
-      }
-
-      for (int i = 0; i < THREAD_COUNT; ++i) {
-          threads[i].start();
-      }
-
-      for (int i = 0; i < THREAD_COUNT; ++i) {
-          threads[i].join();
-      }
-
-      check(result.get(), TASK_COUNT * (TASK_COUNT + 1) / 2,
-              testName + " test result not as expected", iteration);
-  }
-
-  /**
-   * Processes the task queue until there are no tasks left.
-   *
-   * The actual task-grabbing mechanism is implemented in subclasses through grabTask(). This allows
-   * testing various mechanisms, like compareAndSet() and compareAndExchange().
-   */
-  private static abstract class TaskRunner extends Thread {
-
-      protected final TestTask[] tasks;
-
-      TaskRunner(TestTask[] tasks) {
-          this.tasks = tasks;
-      }
-
-      @Override
-      public void run() {
-          int i = 0;
-          while (i < TASK_COUNT) {
-              TestTask t = (TestTask) QA.get(tasks, i);
-              if (t == null) {
-                  ++i;
-                  continue;
-              }
-              if (!grabTask(t, i)) {
-                  continue;
-              }
-              ++i;
-              VarHandle.releaseFence();
-              t.exec();
-          }
-      }
-
-      /**
-       * Grabs the next task from the queue in an atomic way.
-       * 
-       * Once a task is retrieved successfully, the queue should no longer hold a reference to it.
-       * This would be done, for example, by swapping the task with a null value.
-       *
-       * @param t The task to get from the queue
-       * @param i The index where the task is found
-       *
-       * @return {@code true} if the task has been retrieved and is not available to any other
-       * threads. Otherwise {@code false}. If {@code false} is returned, then either the task was no
-       * longer present on the queue due to another thread grabbing it, or, in case of spurious
-       * failure, the task is still available and no other thread managed to grab it.
-       */
-      protected abstract boolean grabTask(TestTask t, int i);
-  }
-
-  private static class TaskRunnerWithCompareAndExchange extends TaskRunner {
-
-      TaskRunnerWithCompareAndExchange(TestTask[] tasks) {
-          super(tasks);
-      }
-
-      @Override
-      protected boolean grabTask(TestTask t, int i) {
-          return (t == QA.compareAndExchange(tasks, i, t, null));
-      }
-  }
-
-  private static class TaskRunnerWithCompareAndSet extends TaskRunner {
-
-      TaskRunnerWithCompareAndSet(TestTask[] tasks) {
-          super(tasks);
-      }
-
-      @Override
-      protected boolean grabTask(TestTask t, int i) {
-          return QA.compareAndSet(tasks, i, t, null);
-      }
-  }
-
-  private static class TaskRunnerWithWeakCompareAndSet extends TaskRunner {
-
-      TaskRunnerWithWeakCompareAndSet(TestTask[] tasks) {
-          super(tasks);
-      }
-
-      @Override
-      protected boolean grabTask(TestTask t, int i) {
-          return QA.weakCompareAndSet(tasks, i, t, null);
-      }
-  }
-
-
-  private interface RunnerFactory {
-      Thread createRunner(TestTask[] tasks);
-  }
-
-  private static class CompareAndExchangeRunnerFactory implements RunnerFactory {
-      @Override
-      public Thread createRunner(TestTask[] tasks) {
-          return new TaskRunnerWithCompareAndExchange(tasks);
-      }
-  }
-
-  private static class CompareAndSetRunnerFactory implements RunnerFactory {
-      @Override
-      public Thread createRunner(TestTask[] tasks) {
-          return new TaskRunnerWithCompareAndSet(tasks);
-      }
-  }
-
-  private static class WeakCompareAndSetRunnerFactory implements RunnerFactory {
-      @Override
-      public Thread createRunner(TestTask[] tasks) {
-          return new TaskRunnerWithWeakCompareAndSet(tasks);
-      }
-  }
-
-  private static class TestTask {
-      private final Integer ord;
-      private final Consumer<Integer> action;
-
-      TestTask(Integer ord, Consumer<Integer> action) {
-          this.ord = ord;
-          this.action = action;
-      }
-
-      public void exec() {
-          action.accept(ord);
-      }
-  }
-
-  private static void check(int actual, int expected, String msg, int iteration) {
-    if (actual != expected) {
-      System.err.println(String.format("[iteration %d] %s : %d != %d",
-                  iteration, msg, actual, expected));
-      System.exit(1);
+    public static void main(String[] args) throws Throwable {
+        testConcurrentProcessing(new CompareAndExchangeRunnerFactory(), "compareAndExchange");
+        testConcurrentProcessing(new CompareAndSetRunnerFactory(), "compareAndSet");
+        testConcurrentProcessing(new WeakCompareAndSetRunnerFactory(), "weakCompareAndSet");
     }
-  }
+
+    private static void testConcurrentProcessing(RunnerFactory factory, String testName)
+            throws Throwable {
+        final Duration startTs = Duration.ofNanos(System.nanoTime());
+        final Duration endTs = startTs.plus(MAX_RETRIES_DURATION);
+        for (int i = 0; i < RETRIES; ++i) {
+            concurrentProcessingTestIteration(factory, i, testName);
+            Duration now = Duration.ofNanos(System.nanoTime());
+            if (0 < now.compareTo(endTs)) {
+                break;
+            }
+        }
+    }
+
+    private static void concurrentProcessingTestIteration(
+            RunnerFactory factory, int iteration, String testName) throws Throwable {
+        final TestTask[] tasks = new TestTask[TASK_COUNT];
+        final AtomicInteger result = new AtomicInteger();
+
+        for (int i = 0; i < TASK_COUNT; ++i) {
+            tasks[i] = new TestTask(Integer.valueOf(i + 1), result::addAndGet);
+        }
+
+        Thread[] threads = new Thread[THREAD_COUNT];
+        for (int i = 0; i < THREAD_COUNT; ++i) {
+            threads[i] = factory.createRunner(tasks);
+        }
+
+        for (int i = 0; i < THREAD_COUNT; ++i) {
+            threads[i].start();
+        }
+
+        for (int i = 0; i < THREAD_COUNT; ++i) {
+            threads[i].join();
+        }
+
+        check(result.get(),
+              TASK_COUNT * (TASK_COUNT + 1) / 2,
+              testName + " test result not as expected",
+              iteration);
+    }
+
+    /**
+     * Processes the task queue until there are no tasks left.
+     *
+     * The actual task-grabbing mechanism is implemented in subclasses through grabTask().
+     * This allows testing various mechanisms, like compareAndSet() and compareAndExchange().
+     */
+    private static abstract class TaskRunner extends Thread {
+
+        protected final TestTask[] tasks;
+
+        TaskRunner(TestTask[] tasks) {
+            this.tasks = tasks;
+        }
+
+        @Override
+        public void run() {
+            int i = 0;
+            while (i < TASK_COUNT) {
+                TestTask t = (TestTask) QA.get(tasks, i);
+                if (t == null) {
+                    ++i;
+                    continue;
+                }
+                if (!grabTask(t, i)) {
+                    continue;
+                }
+                ++i;
+                VarHandle.releaseFence();
+                t.exec();
+            }
+        }
+
+        /**
+         * Grabs the next task from the queue in an atomic way.
+         *
+         * Once a task is retrieved successfully, the queue should no longer hold a reference to it.
+         * This would be done, for example, by swapping the task with a null value.
+         *
+         * @param t The task to get from the queue
+         * @param i The index where the task is found
+         *
+         * @return {@code true} if the task has been retrieved and is not available to any other
+         * threads. Otherwise {@code false}. If {@code false} is returned, then either the task was
+         * no longer present on the queue due to another thread grabbing it, or, in case of spurious
+         * failure, the task is still available and no other thread managed to grab it.
+         */
+        protected abstract boolean grabTask(TestTask t, int i);
+    }
+
+    private static class TaskRunnerWithCompareAndExchange extends TaskRunner {
+        TaskRunnerWithCompareAndExchange(TestTask[] tasks) {
+            super(tasks);
+        }
+
+        @Override
+        protected boolean grabTask(TestTask t, int i) {
+            return (t == QA.compareAndExchange(tasks, i, t, null));
+        }
+    }
+
+    private static class TaskRunnerWithCompareAndSet extends TaskRunner {
+        TaskRunnerWithCompareAndSet(TestTask[] tasks) {
+            super(tasks);
+        }
+
+        @Override
+        protected boolean grabTask(TestTask t, int i) {
+            return QA.compareAndSet(tasks, i, t, null);
+        }
+    }
+
+    private static class TaskRunnerWithWeakCompareAndSet extends TaskRunner {
+        TaskRunnerWithWeakCompareAndSet(TestTask[] tasks) {
+            super(tasks);
+        }
+
+        @Override
+        protected boolean grabTask(TestTask t, int i) {
+            return QA.weakCompareAndSet(tasks, i, t, null);
+        }
+    }
+
+
+    private interface RunnerFactory {
+        Thread createRunner(TestTask[] tasks);
+    }
+
+    private static class CompareAndExchangeRunnerFactory implements RunnerFactory {
+        @Override
+        public Thread createRunner(TestTask[] tasks) {
+            return new TaskRunnerWithCompareAndExchange(tasks);
+        }
+    }
+
+    private static class CompareAndSetRunnerFactory implements RunnerFactory {
+        @Override
+        public Thread createRunner(TestTask[] tasks) {
+            return new TaskRunnerWithCompareAndSet(tasks);
+        }
+    }
+
+    private static class WeakCompareAndSetRunnerFactory implements RunnerFactory {
+        @Override
+        public Thread createRunner(TestTask[] tasks) {
+            return new TaskRunnerWithWeakCompareAndSet(tasks);
+        }
+    }
+
+    private static class TestTask {
+        private final Integer ord;
+        private final Consumer<Integer> action;
+
+        TestTask(Integer ord, Consumer<Integer> action) {
+            this.ord = ord;
+            this.action = action;
+        }
+
+        public void exec() {
+            action.accept(ord);
+        }
+    }
+
+    private static void check(int actual, int expected, String msg, int iteration) {
+        if (actual != expected) {
+            System.err.println(String.format(
+                    "[iteration %d] %s : %d != %d", iteration, msg, actual, expected));
+            System.exit(1);
+        }
+    }
 }
diff --git a/test/719-varhandle-concurrency/test-metadata.json b/test/719-varhandle-concurrency/test-metadata.json
new file mode 100644
index 0000000..ed29691
--- /dev/null
+++ b/test/719-varhandle-concurrency/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "experimental": "var-handles"
+  }
+}
diff --git a/test/727-checker-unresolved-class/run b/test/727-checker-unresolved-class/run
deleted file mode 100644
index 1c9dd11..0000000
--- a/test/727-checker-unresolved-class/run
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2020 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.
-
-if [[ "$TEST_RUNTIME" == "jvm" ]]; then
-  exec ${RUN} $@
-else
-  # Append graphs for checker tests (we run dex2oat twice) with
-  #     --dump-cfg-append.
-  # Make some classes unresolved for AOT compilation with
-  #     --updatable-bcp-packages-file.
-  exec ${RUN} $@ \
-      -Xcompiler-option --dump-cfg-append \
-      -Xcompiler-option --updatable-bcp-packages-file="$DEX_LOCATION/res/updateable.txt"
-fi
diff --git a/test/727-checker-unresolved-class/run.py b/test/727-checker-unresolved-class/run.py
new file mode 100644
index 0000000..986897b
--- /dev/null
+++ b/test/727-checker-unresolved-class/run.py
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# Copyright (C) 2020 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.
+
+
+def run(ctx, args):
+  if args.jvm:
+    ctx.default_run(args)
+  else:
+    # Append graphs for checker tests (we run dex2oat twice) with
+    #     --dump-cfg-append.
+    # Make some classes unresolved for AOT compilation with
+    #     --updatable-bcp-packages-file.
+    ctx.default_run(
+        args,
+        Xcompiler_option=[
+            "--dump-cfg-append",
+            f"--updatable-bcp-packages-file={ctx.env.DEX_LOCATION}/res/updateable.txt"
+        ])
diff --git a/test/728-imt-conflict-zygote/run b/test/728-imt-conflict-zygote/run
deleted file mode 100644
index 8fdff6d..0000000
--- a/test/728-imt-conflict-zygote/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2020 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.
-
-./default-run "$@" --zygote
diff --git a/test/728-imt-conflict-zygote/run.py b/test/728-imt-conflict-zygote/run.py
new file mode 100644
index 0000000..d0b6e49
--- /dev/null
+++ b/test/728-imt-conflict-zygote/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2020 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, zygote=True)
diff --git a/test/729-checker-polymorphic-intrinsic/run b/test/729-checker-polymorphic-intrinsic/run
deleted file mode 100644
index 5fa72ed..0000000
--- a/test/729-checker-polymorphic-intrinsic/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2021 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.
-
-exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile
diff --git a/test/729-checker-polymorphic-intrinsic/run.py b/test/729-checker-polymorphic-intrinsic/run.py
new file mode 100644
index 0000000..43aa25b
--- /dev/null
+++ b/test/729-checker-polymorphic-intrinsic/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args, profile=True, Xcompiler_option=["--compiler-filter=speed-profile"])
diff --git a/test/800-smali/run b/test/800-smali/run
deleted file mode 100644
index c8ce0bc..0000000
--- a/test/800-smali/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2021 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.
-
-# Target 31 to have the verifier behavior the test expects.
-./default-run "$@" --runtime-option -Xtarget-sdk-version:31
diff --git a/test/800-smali/run.py b/test/800-smali/run.py
new file mode 100644
index 0000000..f20dbc0
--- /dev/null
+++ b/test/800-smali/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2021 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.
+
+
+def run(ctx, args):
+  # Target 31 to have the verifier behavior the test expects.
+  ctx.default_run(args, runtime_option=["-Xtarget-sdk-version:31"])
diff --git a/test/800-smali/src/Main.java b/test/800-smali/src/Main.java
index 90476b3..06d24d8 100644
--- a/test/800-smali/src/Main.java
+++ b/test/800-smali/src/Main.java
@@ -21,7 +21,7 @@
 import java.util.List;
 
 /**
- * Smali excercise.
+ * Smali exercise.
  */
 public class Main {
 
diff --git a/test/804-class-extends-itself/build.py b/test/804-class-extends-itself/build.py
new file mode 100644
index 0000000..2cd378a
--- /dev/null
+++ b/test/804-class-extends-itself/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build()
diff --git a/test/804-class-extends-itself/build b/test/804-class-extends-itself/generate-sources
old mode 100644
new mode 100755
similarity index 100%
rename from test/804-class-extends-itself/build
rename to test/804-class-extends-itself/generate-sources
diff --git a/test/807-method-handle-and-mr/build b/test/807-method-handle-and-mr/build
deleted file mode 100755
index 12a8e18..0000000
--- a/test/807-method-handle-and-mr/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Exit on failure.
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/807-method-handle-and-mr/build.py b/test/807-method-handle-and-mr/build.py
new file mode 100644
index 0000000..027dd53
--- /dev/null
+++ b/test/807-method-handle-and-mr/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="method-handles")
diff --git a/test/811-checker-invoke-super-secondary/build.py b/test/811-checker-invoke-super-secondary/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/811-checker-invoke-super-secondary/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/811-checker-invoke-super-secondary/test-metadata.json b/test/811-checker-invoke-super-secondary/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/811-checker-invoke-super-secondary/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/815-invokeinterface-default/src/Main.java b/test/815-invokeinterface-default/src/Main.java
index db3612b..97321c8 100644
--- a/test/815-invokeinterface-default/src/Main.java
+++ b/test/815-invokeinterface-default/src/Main.java
@@ -18,174 +18,174 @@
 
 // An interface with enough methods to trigger a conflict.
 interface Itf {
-  public void method0a();
-  public void method0b();
-  public void method0c();
-  public void method0d();
-  public void method0e();
-  public void method0f();
-  public void method0g();
-  public void method0h();
-  public void method0i();
-  public void method0j();
-  public void method0k();
-  public void method0l();
-  public void method0m();
-  public void method0n();
-  public void method0o();
-  public void method0p();
-  public void method0q();
-  public void method0r();
-  public void method0s();
-  public void method0t();
-  public void method0u();
-  public void method0v();
-  public void method0w();
-  public void method0x();
-  public void method0y();
-  public void method0z();
-  public void method1a();
-  public void method1b();
-  public void method1c();
-  public void method1d();
-  public void method1e();
-  public void method1f();
-  public void method1g();
-  public void method1h();
-  public void method1i();
-  public void method1j();
-  public void method1k();
-  public void method1l();
-  public void method1m();
-  public void method1n();
-  public void method1o();
-  public void method1p();
-  public void method1q();
-  public void method1r();
-  public void method1s();
-  public void method1t();
-  public void method1u();
-  public void method1v();
-  public void method1w();
-  public void method1x();
-  public void method1y();
-  public void method1z();
-  public void method2a();
-  public void method2b();
-  public void method2c();
-  public void method2d();
-  public void method2e();
-  public void method2f();
-  public void method2g();
-  public void method2h();
-  public void method2i();
-  public void method2j();
-  public void method2k();
-  public void method2l();
-  public void method2m();
-  public void method2n();
-  public void method2o();
-  public void method2p();
-  public void method2q();
-  public void method2r();
-  public void method2s();
-  public void method2t();
-  public void method2u();
-  public void method2v();
-  public void method2w();
-  public void method2x();
-  public void method2y();
-  public void method2z();
+    public void method0a();
+    public void method0b();
+    public void method0c();
+    public void method0d();
+    public void method0e();
+    public void method0f();
+    public void method0g();
+    public void method0h();
+    public void method0i();
+    public void method0j();
+    public void method0k();
+    public void method0l();
+    public void method0m();
+    public void method0n();
+    public void method0o();
+    public void method0p();
+    public void method0q();
+    public void method0r();
+    public void method0s();
+    public void method0t();
+    public void method0u();
+    public void method0v();
+    public void method0w();
+    public void method0x();
+    public void method0y();
+    public void method0z();
+    public void method1a();
+    public void method1b();
+    public void method1c();
+    public void method1d();
+    public void method1e();
+    public void method1f();
+    public void method1g();
+    public void method1h();
+    public void method1i();
+    public void method1j();
+    public void method1k();
+    public void method1l();
+    public void method1m();
+    public void method1n();
+    public void method1o();
+    public void method1p();
+    public void method1q();
+    public void method1r();
+    public void method1s();
+    public void method1t();
+    public void method1u();
+    public void method1v();
+    public void method1w();
+    public void method1x();
+    public void method1y();
+    public void method1z();
+    public void method2a();
+    public void method2b();
+    public void method2c();
+    public void method2d();
+    public void method2e();
+    public void method2f();
+    public void method2g();
+    public void method2h();
+    public void method2i();
+    public void method2j();
+    public void method2k();
+    public void method2l();
+    public void method2m();
+    public void method2n();
+    public void method2o();
+    public void method2p();
+    public void method2q();
+    public void method2r();
+    public void method2s();
+    public void method2t();
+    public void method2u();
+    public void method2v();
+    public void method2w();
+    public void method2x();
+    public void method2y();
+    public void method2z();
 
-  public default void $noinline$defaultRecursiveMethod(boolean callRecursive) {
-    if (callRecursive) {
-      $noinline$defaultRecursiveMethod(false);
+    public default void $noinline$defaultRecursiveMethod(boolean callRecursive) {
+        if (callRecursive) {
+            $noinline$defaultRecursiveMethod(false);
+        }
     }
-  }
 }
 
 public class Main implements Itf {
-  public static void main(String[] args) throws Exception {
-    Main main = new Main();
-    main.$noinline$defaultRecursiveMethod(true);
-  }
+    public static void main(String[] args) throws Exception {
+        Main main = new Main();
+        main.$noinline$defaultRecursiveMethod(true);
+    }
 
-  public void method0a() {}
-  public void method0b() {}
-  public void method0c() {}
-  public void method0d() {}
-  public void method0e() {}
-  public void method0f() {}
-  public void method0g() {}
-  public void method0h() {}
-  public void method0i() {}
-  public void method0j() {}
-  public void method0k() {}
-  public void method0l() {}
-  public void method0m() {}
-  public void method0n() {}
-  public void method0o() {}
-  public void method0p() {}
-  public void method0q() {}
-  public void method0r() {}
-  public void method0s() {}
-  public void method0t() {}
-  public void method0u() {}
-  public void method0v() {}
-  public void method0w() {}
-  public void method0x() {}
-  public void method0y() {}
-  public void method0z() {}
-  public void method1a() {}
-  public void method1b() {}
-  public void method1c() {}
-  public void method1d() {}
-  public void method1e() {}
-  public void method1f() {}
-  public void method1g() {}
-  public void method1h() {}
-  public void method1i() {}
-  public void method1j() {}
-  public void method1k() {}
-  public void method1l() {}
-  public void method1m() {}
-  public void method1n() {}
-  public void method1o() {}
-  public void method1p() {}
-  public void method1q() {}
-  public void method1r() {}
-  public void method1s() {}
-  public void method1t() {}
-  public void method1u() {}
-  public void method1v() {}
-  public void method1w() {}
-  public void method1x() {}
-  public void method1y() {}
-  public void method1z() {}
-  public void method2a() {}
-  public void method2b() {}
-  public void method2c() {}
-  public void method2d() {}
-  public void method2e() {}
-  public void method2f() {}
-  public void method2g() {}
-  public void method2h() {}
-  public void method2i() {}
-  public void method2j() {}
-  public void method2k() {}
-  public void method2l() {}
-  public void method2m() {}
-  public void method2n() {}
-  public void method2o() {}
-  public void method2p() {}
-  public void method2q() {}
-  public void method2r() {}
-  public void method2s() {}
-  public void method2t() {}
-  public void method2u() {}
-  public void method2v() {}
-  public void method2w() {}
-  public void method2x() {}
-  public void method2y() {}
-  public void method2z() {}
+    public void method0a() {}
+    public void method0b() {}
+    public void method0c() {}
+    public void method0d() {}
+    public void method0e() {}
+    public void method0f() {}
+    public void method0g() {}
+    public void method0h() {}
+    public void method0i() {}
+    public void method0j() {}
+    public void method0k() {}
+    public void method0l() {}
+    public void method0m() {}
+    public void method0n() {}
+    public void method0o() {}
+    public void method0p() {}
+    public void method0q() {}
+    public void method0r() {}
+    public void method0s() {}
+    public void method0t() {}
+    public void method0u() {}
+    public void method0v() {}
+    public void method0w() {}
+    public void method0x() {}
+    public void method0y() {}
+    public void method0z() {}
+    public void method1a() {}
+    public void method1b() {}
+    public void method1c() {}
+    public void method1d() {}
+    public void method1e() {}
+    public void method1f() {}
+    public void method1g() {}
+    public void method1h() {}
+    public void method1i() {}
+    public void method1j() {}
+    public void method1k() {}
+    public void method1l() {}
+    public void method1m() {}
+    public void method1n() {}
+    public void method1o() {}
+    public void method1p() {}
+    public void method1q() {}
+    public void method1r() {}
+    public void method1s() {}
+    public void method1t() {}
+    public void method1u() {}
+    public void method1v() {}
+    public void method1w() {}
+    public void method1x() {}
+    public void method1y() {}
+    public void method1z() {}
+    public void method2a() {}
+    public void method2b() {}
+    public void method2c() {}
+    public void method2d() {}
+    public void method2e() {}
+    public void method2f() {}
+    public void method2g() {}
+    public void method2h() {}
+    public void method2i() {}
+    public void method2j() {}
+    public void method2k() {}
+    public void method2l() {}
+    public void method2m() {}
+    public void method2n() {}
+    public void method2o() {}
+    public void method2p() {}
+    public void method2q() {}
+    public void method2r() {}
+    public void method2s() {}
+    public void method2t() {}
+    public void method2u() {}
+    public void method2v() {}
+    public void method2w() {}
+    public void method2x() {}
+    public void method2y() {}
+    public void method2z() {}
 }
diff --git a/test/816-illegal-new-array/src/Main.java b/test/816-illegal-new-array/src/Main.java
index 2d48c12..7a960bb 100644
--- a/test/816-illegal-new-array/src/Main.java
+++ b/test/816-illegal-new-array/src/Main.java
@@ -19,37 +19,37 @@
 
 public class Main {
 
-  public static void main(String[] args) throws Exception {
-    Class<?> c = Class.forName("TestCase");
-    Method m = c.getMethod("filledNewArray");
-    try {
-      m.invoke(null);
-      throw new Error("Expected IllegalAccessError");
-    } catch (InvocationTargetException e) {
-      if (!(e.getCause() instanceof IllegalAccessError)) {
-        throw new Error("Expected IllegalAccessError, got " + e.getCause());
-      }
-    }
+    public static void main(String[] args) throws Exception {
+        Class<?> c = Class.forName("TestCase");
+        Method m = c.getMethod("filledNewArray");
+        try {
+            m.invoke(null);
+            throw new Error("Expected IllegalAccessError");
+        } catch (InvocationTargetException e) {
+            if (!(e.getCause() instanceof IllegalAccessError)) {
+                throw new Error("Expected IllegalAccessError, got " + e.getCause());
+            }
+        }
 
-    m = c.getMethod("filledNewArrayRange");
-    try {
-      m.invoke(null);
-      throw new Error("Expected IllegalAccessError");
-    } catch (InvocationTargetException e) {
-      if (!(e.getCause() instanceof IllegalAccessError)) {
-        throw new Error("Expected IllegalAccessError, got " + e.getCause());
-      }
-    }
+        m = c.getMethod("filledNewArrayRange");
+        try {
+            m.invoke(null);
+            throw new Error("Expected IllegalAccessError");
+        } catch (InvocationTargetException e) {
+            if (!(e.getCause() instanceof IllegalAccessError)) {
+                throw new Error("Expected IllegalAccessError, got " + e.getCause());
+            }
+        }
 
-    m = c.getMethod("newArray");
-    try {
-      m.invoke(null);
-      throw new Error("Expected IllegalAccessError");
-    } catch (InvocationTargetException e) {
-      if (!(e.getCause() instanceof IllegalAccessError)) {
-        throw new Error("Expected IllegalAccessError, got " + e.getCause());
-      }
+        m = c.getMethod("newArray");
+        try {
+            m.invoke(null);
+            throw new Error("Expected IllegalAccessError");
+        } catch (InvocationTargetException e) {
+            if (!(e.getCause() instanceof IllegalAccessError)) {
+                throw new Error("Expected IllegalAccessError, got " + e.getCause());
+            }
+        }
     }
-  }
 }
 
diff --git a/test/817-hiddenapi/build b/test/817-hiddenapi/build
deleted file mode 100644
index 330a6de..0000000
--- a/test/817-hiddenapi/build
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-set -e
-
-# Build the jars twice. First with applying hiddenapi, creating a boot jar, then
-# a second time without to create a normal jar. We need to do this because we
-# want to load the jar once as an app module and once as a member of the boot
-# class path. The DexFileVerifier would fail on the former as it does not allow
-# hidden API access flags in dex files. DexFileVerifier is not invoked on boot
-# class path dex files, so the boot jar loads fine in the latter case.
-
-export USE_HIDDENAPI=true
-./default-build "$@"
-
-# Move the jar file into the resource folder to be bundled with the test.
-mkdir res
-mv ${TEST_NAME}.jar res/boot.jar
-
-# Clear all intermediate files otherwise default-build would either skip
-# compilation or fail rebuilding.
-rm -rf classes*
-
-export USE_HIDDENAPI=false
-./default-build "$@"
diff --git a/test/817-hiddenapi/build.py b/test/817-hiddenapi/build.py
new file mode 100644
index 0000000..37b8cd5
--- /dev/null
+++ b/test/817-hiddenapi/build.py
@@ -0,0 +1,40 @@
+#
+# Copyright (C) 2022 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.
+
+import os
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+
+  # Build the jars twice. First with applying hiddenapi, creating a boot jar, then
+  # a second time without to create a normal jar. We need to do this because we
+  # want to load the jar once as an app module and once as a member of the boot
+  # class path. The DexFileVerifier would fail on the former as it does not allow
+  # hidden API access flags in dex files. DexFileVerifier is not invoked on boot
+  # class path dex files, so the boot jar loads fine in the latter case.
+
+  ctx.default_build(use_hiddenapi=True)
+
+  # Move the jar file into the resource folder to be bundled with the test.
+  os.mkdir(ctx.test_dir / "res")
+  os.rename(ctx.test_dir / "817-hiddenapi.jar", ctx.test_dir / "res/boot.jar")
+
+  # Clear all intermediate files otherwise default-build would either skip
+  # compilation or fail rebuilding.
+  ctx.bash("rm -rf classes*")
+
+  ctx.default_build(use_hiddenapi=False)
diff --git a/test/817-hiddenapi/check b/test/817-hiddenapi/check
deleted file mode 100755
index 8c21ab4..0000000
--- a/test/817-hiddenapi/check
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2021 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# On gcstress configurations, an extra "JNI_OnUnload called" line may
-# be emitted. If so, remove it.
-sed -e '${/^JNI_OnUnload called$/d;}' "$2" > "$2.tmp"
-
-./default-check "$1" "$2.tmp" "$3" "$4"
diff --git a/test/817-hiddenapi/run.py b/test/817-hiddenapi/run.py
new file mode 100644
index 0000000..0ebb768
--- /dev/null
+++ b/test/817-hiddenapi/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # On gcstress configurations, an extra "JNI_OnUnload called" line may
+  # be emitted. If so, remove it.
+  ctx.run(fr"sed -i '/^JNI_OnUnload called$/d' '{args.stdout_file}'")
diff --git a/test/818-clinit-nterp/run b/test/818-clinit-nterp/run
deleted file mode 100644
index 52d2b5f..0000000
--- a/test/818-clinit-nterp/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 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.
-
-exec ${RUN} $@ --profile
diff --git a/test/818-clinit-nterp/run.py b/test/818-clinit-nterp/run.py
new file mode 100644
index 0000000..0f3a0eac
--- /dev/null
+++ b/test/818-clinit-nterp/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, profile=True)
diff --git a/test/818-clinit-nterp/src/Main.java b/test/818-clinit-nterp/src/Main.java
index 5342cec..5ebd384 100644
--- a/test/818-clinit-nterp/src/Main.java
+++ b/test/818-clinit-nterp/src/Main.java
@@ -15,20 +15,19 @@
  */
 
 public class Main {
-  public static void main(String[] args) {
-    Clinit.run();
-    if (!clinitDidRun) {
-      throw new Error("Expected Clinit.<clinit> to have run");
+    public static void main(String[] args) {
+        Clinit.run();
+        if (!clinitDidRun) {
+            throw new Error("Expected Clinit.<clinit> to have run");
+        }
     }
-  }
-  static boolean clinitDidRun = false;
+    static boolean clinitDidRun = false;
 }
 
 class Clinit {
-  public static void run() {
-  }
+    public static void run() {}
 
-  static {
-    Main.clinitDidRun = true;
-  }
+    static {
+        Main.clinitDidRun = true;
+    }
 }
diff --git a/test/819-verification-runtime/run b/test/819-verification-runtime/run
deleted file mode 100755
index c8ce0bc..0000000
--- a/test/819-verification-runtime/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2021 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.
-
-# Target 31 to have the verifier behavior the test expects.
-./default-run "$@" --runtime-option -Xtarget-sdk-version:31
diff --git a/test/819-verification-runtime/run.py b/test/819-verification-runtime/run.py
new file mode 100644
index 0000000..f20dbc0
--- /dev/null
+++ b/test/819-verification-runtime/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2021 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.
+
+
+def run(ctx, args):
+  # Target 31 to have the verifier behavior the test expects.
+  ctx.default_run(args, runtime_option=["-Xtarget-sdk-version:31"])
diff --git a/test/820-vdex-multidex/run b/test/820-vdex-multidex/run
deleted file mode 100644
index 3f6dc3c..0000000
--- a/test/820-vdex-multidex/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2021 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.
-
-exec ${RUN} -Xcompiler-option --compiler-filter=verify --vdex "${@}"
diff --git a/test/820-vdex-multidex/run.py b/test/820-vdex-multidex/run.py
new file mode 100644
index 0000000..f597e41
--- /dev/null
+++ b/test/820-vdex-multidex/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args, Xcompiler_option=["--compiler-filter=verify"], vdex=True)
diff --git a/test/821-madvise-willneed/run b/test/821-madvise-willneed/run
deleted file mode 100644
index 2c3917f..0000000
--- a/test/821-madvise-willneed/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2021 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.i
-
-# Load and run HelloWorld after madvising odex, vdex, art files to 100MB size
-# limit
-exec ${RUN} "${@}" --runtime-option -XMadviseWillNeedVdexFileSize:104857600 \
-  --runtime-option -XMadviseWillNeedOdexFileSize:104857600 \
-  --runtime-option -XMadviseWillNeedArtFileSize:104857600
diff --git a/test/821-madvise-willneed/run.py b/test/821-madvise-willneed/run.py
new file mode 100644
index 0000000..06d3bac
--- /dev/null
+++ b/test/821-madvise-willneed/run.py
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 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.i
+
+
+def run(ctx, args):
+  # Load and run HelloWorld after madvising odex, vdex, art files to 100MB size
+  # limit
+  ctx.default_run(
+      args,
+      runtime_option=[
+          "-XMadviseWillNeedVdexFileSize:104857600",
+          "-XMadviseWillNeedOdexFileSize:104857600",
+          "-XMadviseWillNeedArtFileSize:104857600"
+      ])
diff --git a/test/822-hiddenapi-future/build b/test/822-hiddenapi-future/build
deleted file mode 100644
index 02ce549..0000000
--- a/test/822-hiddenapi-future/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2021 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.
-
-USE_HIDDENAPI=true ./default-build "$@"
diff --git a/test/822-hiddenapi-future/build.py b/test/822-hiddenapi-future/build.py
new file mode 100644
index 0000000..942bb00
--- /dev/null
+++ b/test/822-hiddenapi-future/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(use_hiddenapi=True)
diff --git a/test/823-cha-inlining/Android.bp b/test/823-cha-inlining/Android.bp
new file mode 100644
index 0000000..f174b02
--- /dev/null
+++ b/test/823-cha-inlining/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `823-cha-inlining`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-823-cha-inlining-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-823-cha-inlining",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-823-cha-inlining-src"
+    ],
+    data: [
+        ":art-run-test-823-cha-inlining-expected-stdout",
+        ":art-run-test-823-cha-inlining-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-823-cha-inlining-expected-stdout",
+    out: ["art-run-test-823-cha-inlining-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-823-cha-inlining-expected-stderr",
+    out: ["art-run-test-823-cha-inlining-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/829-unresolved-enclosing/build b/test/829-unresolved-enclosing/build
deleted file mode 100644
index f378df1..0000000
--- a/test/829-unresolved-enclosing/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/829-unresolved-enclosing/javac_post.sh b/test/829-unresolved-enclosing/javac_post.sh
new file mode 100755
index 0000000..93875d4
--- /dev/null
+++ b/test/829-unresolved-enclosing/javac_post.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e # Stop on error - the caller script may not have this set.
+
+# Remove class available at compile time but not at run time.
+rm classes/MissingSuperClass.class
diff --git a/test/829-unresolved-enclosing/javac_wrapper.sh b/test/829-unresolved-enclosing/javac_wrapper.sh
deleted file mode 100755
index e3dc1e3..0000000
--- a/test/829-unresolved-enclosing/javac_wrapper.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e # Stop on error - the caller script may not have this set.
-
-$JAVAC "$@"
-
-# Remove class available at compile time but not at run time.
-rm classes/MissingSuperClass.class
diff --git a/test/831-unverified-bcp/build.py b/test/831-unverified-bcp/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/831-unverified-bcp/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/831-unverified-bcp/test-metadata.json b/test/831-unverified-bcp/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/831-unverified-bcp/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/833-background-verification/run b/test/833-background-verification/run
deleted file mode 100755
index c455473..0000000
--- a/test/833-background-verification/run
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2021 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.
-
-# Target 31 to run the background verifier.
-# Disable dex2oat of secondary dex files to ensure we always run the background
-# verifier.
-./default-run "$@" --runtime-option -Xtarget-sdk-version:31 --no-secondary-compilation
diff --git a/test/833-background-verification/run.py b/test/833-background-verification/run.py
new file mode 100644
index 0000000..44d80ac
--- /dev/null
+++ b/test/833-background-verification/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2021 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.
+
+
+def run(ctx, args):
+  # Target 31 to run the background verifier.
+  # Disable dex2oat of secondary dex files to ensure we always run the background
+  # verifier.
+  ctx.default_run(
+      args,
+      runtime_option=["-Xtarget-sdk-version:31"],
+      secondary_compilation=False)
diff --git a/test/837-deopt/Android.bp b/test/837-deopt/Android.bp
new file mode 100644
index 0000000..2e0e3aa
--- /dev/null
+++ b/test/837-deopt/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `837-deopt`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-837-deopt",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-837-deopt-expected-stdout",
+        ":art-run-test-837-deopt-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-837-deopt-expected-stdout",
+    out: ["art-run-test-837-deopt-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-837-deopt-expected-stderr",
+    out: ["art-run-test-837-deopt-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/837-deopt/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/837-deopt/expected-stderr.txt
diff --git a/test/837-deopt/expected-stdout.txt b/test/837-deopt/expected-stdout.txt
new file mode 100644
index 0000000..6a5618e
--- /dev/null
+++ b/test/837-deopt/expected-stdout.txt
@@ -0,0 +1 @@
+JNI_OnLoad called
diff --git a/test/837-deopt/info.txt b/test/837-deopt/info.txt
new file mode 100644
index 0000000..5c95277
--- /dev/null
+++ b/test/837-deopt/info.txt
@@ -0,0 +1 @@
+Tests around deoptimization.
diff --git a/test/837-deopt/src/Main.java b/test/837-deopt/src/Main.java
new file mode 100644
index 0000000..8e3ad7c
--- /dev/null
+++ b/test/837-deopt/src/Main.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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 Main {
+  int field = 42;
+
+  // Test that deoptimization preserves objects that are singletons.
+  public static int $noinline$foo(Main arg) {
+    Main m = new Main();
+    arg.returnValue();
+    return m.field;
+  }
+
+  // Test that doing OSR after deoptimization works.
+  public static int $noinline$foo2(Main arg, boolean osr) {
+    Main m = new Main();
+    arg.returnValue();
+    if (osr) {
+      while (!isInOsrCode("$noinline$foo2")) {}
+    }
+    return m.field;
+  }
+
+  public static void main(String[] args) throws Throwable {
+    System.loadLibrary(args[0]);
+    if (isDebuggable()) {
+      // We do not deoptimize with inline caches when the app is debuggable, so just don't run the
+      // test.
+      return;
+    }
+    test1();
+    test2();
+  }
+
+  public static void assertEquals(int expected, int actual) {
+    if (expected != actual) {
+      throw new Error("Expected " + expected + ", got " + actual);
+    }
+  }
+
+  public static void test1() {
+    ensureJitBaselineCompiled(Main.class, "$noinline$foo");
+    // Surround the call with GCs to increase chances we execute $noinline$foo
+    // while the GC isn't marking. This makes sure the inline cache is populated.
+    Runtime.getRuntime().gc();
+    assertEquals(42, $noinline$foo(new Main()));
+    Runtime.getRuntime().gc();
+
+    ensureJitCompiled(Main.class, "$noinline$foo");
+    assertEquals(42, $noinline$foo(new SubMain()));
+  }
+
+  public static void test2() {
+    ensureJitBaselineCompiled(Main.class, "$noinline$foo2");
+    // Surround the call with GCs to increase chances we execute $noinline$foo
+    // while the GC isn't marking. This makes sure the inline cache is populated.
+    Runtime.getRuntime().gc();
+    assertEquals(42, $noinline$foo2(new Main(), false));
+    Runtime.getRuntime().gc();
+
+    ensureJitCompiled(Main.class, "$noinline$foo2");
+    assertEquals(42, $noinline$foo2(new SubMain(), true));
+  }
+
+  public String returnValue() {
+    return "Main";
+  }
+
+  public static native void ensureJitCompiled(Class<?> cls, String methodName);
+  public static native void ensureJitBaselineCompiled(Class<?> cls, String methodName);
+  public static native boolean isInOsrCode(String methodName);
+  public static native boolean isDebuggable();
+}
+
+// Define a subclass with another implementation of returnValue to deoptimize $noinline$foo and
+// $noinline$foo2.
+class SubMain extends Main {
+  public String returnValue() {
+    return "SubMain";
+  }
+}
diff --git a/test/838-override/Android.bp b/test/838-override/Android.bp
new file mode 100644
index 0000000..2ac4e21
--- /dev/null
+++ b/test/838-override/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `838-override`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-838-override",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-838-override-expected-stdout",
+        ":art-run-test-838-override-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-838-override-expected-stdout",
+    out: ["art-run-test-838-override-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-838-override-expected-stderr",
+    out: ["art-run-test-838-override-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/838-override/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/838-override/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/838-override/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/838-override/expected-stdout.txt
diff --git a/test/838-override/info.txt b/test/838-override/info.txt
new file mode 100644
index 0000000..a33ddb2
--- /dev/null
+++ b/test/838-override/info.txt
@@ -0,0 +1 @@
+Tests for method overriding in the presence of package private methods.
diff --git a/test/838-override/src/Main.java b/test/838-override/src/Main.java
new file mode 100644
index 0000000..1e6aa49
--- /dev/null
+++ b/test/838-override/src/Main.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+
+  public static void assertEquals(Object expected, Object actual) {
+    if (expected != actual) {
+      throw new Error("Expected " + expected + ", got " + actual);
+    }
+  }
+
+  public static void main(String[] args) {
+    // Tescase 1: a class with:
+    // - a package-private pkg1 'foo'
+    // - a public pkg2 'foo'.
+    {
+      pkg2.PublicFoo obj = new pkg2.PublicFoo();
+      assertEquals(pkg1.Pkg1Foo.class, pkg1.Pkg1Foo.callFoo(obj));
+      assertEquals(pkg2.PublicFoo.class, pkg2.Pkg2Foo.callFoo(obj));
+      assertEquals(pkg2.PublicFoo.class, pkg3.Pkg3Foo.callFoo(obj));
+      assertEquals(pkg2.PublicFoo.class, obj.foo());
+    }
+    // Tescase 2: a class with:
+    // - a package-private pkg1 'foo'
+    // - a public pkg2 'foo'
+    // - a public pkg1 'foo.
+    {
+      pkg1.PublicFoo obj = new pkg1.PublicFoo();
+      assertEquals(pkg1.PublicFoo.class, pkg1.Pkg1Foo.callFoo(obj));
+      assertEquals(pkg1.PublicFoo.class, pkg2.Pkg2Foo.callFoo(obj));
+      assertEquals(pkg1.PublicFoo.class, pkg3.Pkg3Foo.callFoo(obj));
+      assertEquals(pkg1.PublicFoo.class, obj.foo());
+    }
+
+    // Tescase 3: a class with:
+    // - a package-private pkg1 'foo'
+    // - a package-private pkg2 'foo'
+    // - a public pkg3 'foo.
+    {
+      pkg3.PublicFoo obj = new pkg3.PublicFoo();
+      assertEquals(pkg1.Pkg1Foo.class, pkg1.Pkg1Foo.callFoo(obj));
+      assertEquals(pkg2.Pkg2Foo.class, pkg2.Pkg2Foo.callFoo(obj));
+      assertEquals(pkg3.PublicFoo.class, pkg3.Pkg3Foo.callFoo(obj));
+      assertEquals(pkg3.PublicFoo.class, obj.foo());
+    }
+
+    // Tescase 4: a class with:
+    // - a package-private pkg1 'foo'
+    // - a package-private pkg2 'foo'
+    // - a public pkg3 'foo.
+    // - a public pkg2 'foo'
+    {
+      pkg2.PublicFooInheritsPkg3 obj = new pkg2.PublicFooInheritsPkg3();
+      assertEquals(pkg1.Pkg1Foo.class, pkg1.Pkg1Foo.callFoo(obj));
+      assertEquals(pkg2.PublicFooInheritsPkg3.class, pkg2.Pkg2Foo.callFoo(obj));
+      assertEquals(pkg2.PublicFooInheritsPkg3.class, pkg3.Pkg3Foo.callFoo(obj));
+      assertEquals(pkg2.PublicFooInheritsPkg3.class, obj.foo());
+    }
+
+    // Tescase 5: a class with:
+    // - a package-private pkg1 'foo'
+    // - a package-private pkg2 'foo'
+    // - a public pkg3 'foo.
+    // - a public pkg2 'foo'
+    // - a public pkg1 'foo'
+    {
+      pkg1.PublicFooInheritsPkg2 obj = new pkg1.PublicFooInheritsPkg2();
+      assertEquals(pkg1.PublicFooInheritsPkg2.class, pkg1.Pkg1Foo.callFoo(obj));
+      assertEquals(pkg1.PublicFooInheritsPkg2.class, pkg2.Pkg2Foo.callFoo(obj));
+      assertEquals(pkg1.PublicFooInheritsPkg2.class, pkg3.Pkg3Foo.callFoo(obj));
+      assertEquals(pkg1.PublicFooInheritsPkg2.class, obj.foo());
+    }
+
+    // Tescase 6: a class with:
+    // - a package-private pkg1 'foo'
+    // - a package-private pkg2 'foo'
+    // - a public pkg1 'foo.
+    {
+      pkg1.LowerIndexImplementsFoo obj = new pkg1.LowerIndexImplementsFoo();
+      assertEquals(pkg1.LowerIndexPublicFoo.class, pkg1.Pkg1Foo.callFoo(obj));
+      assertEquals(pkg2.Pkg2Foo.class, pkg2.Pkg2Foo.callFoo(obj));
+      assertEquals(pkg2.Pkg2Foo.class, pkg3.Pkg3Foo.callFoo(obj));
+      assertEquals(pkg1.LowerIndexPublicFoo.class, obj.foo());
+    }
+  }
+}
diff --git a/test/838-override/src/pkg1/InterfaceFoo.java b/test/838-override/src/pkg1/InterfaceFoo.java
new file mode 100644
index 0000000..cd9e44b
--- /dev/null
+++ b/test/838-override/src/pkg1/InterfaceFoo.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg1;
+
+public interface InterfaceFoo {
+  public Class<?> foo();
+}
diff --git a/test/838-override/src/pkg1/LowerIndexImplementsFoo.java b/test/838-override/src/pkg1/LowerIndexImplementsFoo.java
new file mode 100644
index 0000000..290f07b
--- /dev/null
+++ b/test/838-override/src/pkg1/LowerIndexImplementsFoo.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg1;
+
+public class LowerIndexImplementsFoo extends LowerIndexPublicFoo implements InterfaceFoo {
+}
diff --git a/test/838-override/src/pkg1/LowerIndexPublicFoo.java b/test/838-override/src/pkg1/LowerIndexPublicFoo.java
new file mode 100644
index 0000000..87c0c72
--- /dev/null
+++ b/test/838-override/src/pkg1/LowerIndexPublicFoo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg1;
+
+public class LowerIndexPublicFoo extends pkg2.Pkg2Foo {
+  public Class<?> foo() {
+    return LowerIndexPublicFoo.class;
+  }
+}
diff --git a/test/838-override/src/pkg1/Pkg1Foo.java b/test/838-override/src/pkg1/Pkg1Foo.java
new file mode 100644
index 0000000..0ffbc27
--- /dev/null
+++ b/test/838-override/src/pkg1/Pkg1Foo.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg1;
+
+public class Pkg1Foo {
+
+  Class<?> foo() {
+    return Pkg1Foo.class;
+  }
+
+  public static Class<?> callFoo(Pkg1Foo obj) {
+    return obj.foo();
+  }
+}
diff --git a/test/838-override/src/pkg1/PublicFoo.java b/test/838-override/src/pkg1/PublicFoo.java
new file mode 100644
index 0000000..c43b282
--- /dev/null
+++ b/test/838-override/src/pkg1/PublicFoo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg1;
+
+public class PublicFoo extends pkg2.PublicFoo {
+  public Class<?> foo() {
+    return PublicFoo.class;
+  }
+}
diff --git a/test/838-override/src/pkg1/PublicFooInheritsPkg2.java b/test/838-override/src/pkg1/PublicFooInheritsPkg2.java
new file mode 100644
index 0000000..a255f36
--- /dev/null
+++ b/test/838-override/src/pkg1/PublicFooInheritsPkg2.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg1;
+
+public class PublicFooInheritsPkg2 extends pkg2.PublicFooInheritsPkg3 {
+  public Class<?> foo() {
+    return PublicFooInheritsPkg2.class;
+  }
+}
diff --git a/test/838-override/src/pkg2/Pkg2Foo.java b/test/838-override/src/pkg2/Pkg2Foo.java
new file mode 100644
index 0000000..c521914
--- /dev/null
+++ b/test/838-override/src/pkg2/Pkg2Foo.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg2;
+
+public class Pkg2Foo extends pkg1.Pkg1Foo{
+  Class<?> foo() {
+    return Pkg2Foo.class;
+  }
+
+  public static Class<?> callFoo(Pkg2Foo obj) {
+    return obj.foo();
+  }
+}
diff --git a/test/838-override/src/pkg2/PublicFoo.java b/test/838-override/src/pkg2/PublicFoo.java
new file mode 100644
index 0000000..5434234
--- /dev/null
+++ b/test/838-override/src/pkg2/PublicFoo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg2;
+
+public class PublicFoo extends Pkg2Foo {
+  public Class<?> foo() {
+    return PublicFoo.class;
+  }
+}
diff --git a/test/838-override/src/pkg2/PublicFooInheritsPkg3.java b/test/838-override/src/pkg2/PublicFooInheritsPkg3.java
new file mode 100644
index 0000000..1a983a1
--- /dev/null
+++ b/test/838-override/src/pkg2/PublicFooInheritsPkg3.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg2;
+
+public class PublicFooInheritsPkg3 extends pkg3.PublicFoo {
+  public Class<?> foo() {
+    return PublicFooInheritsPkg3.class;
+  }
+}
diff --git a/test/838-override/src/pkg3/Pkg3Foo.java b/test/838-override/src/pkg3/Pkg3Foo.java
new file mode 100644
index 0000000..72a16aa
--- /dev/null
+++ b/test/838-override/src/pkg3/Pkg3Foo.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg3;
+
+public class Pkg3Foo extends pkg2.Pkg2Foo {
+  Class<?> foo() {
+    return Pkg3Foo.class;
+  }
+
+  public static Class<?> callFoo(Pkg3Foo obj) {
+    return obj.foo();
+  }
+}
diff --git a/test/838-override/src/pkg3/PublicFoo.java b/test/838-override/src/pkg3/PublicFoo.java
new file mode 100644
index 0000000..9d37137
--- /dev/null
+++ b/test/838-override/src/pkg3/PublicFoo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg3;
+
+public class PublicFoo extends Pkg3Foo {
+  public Class<?> foo() {
+    return PublicFoo.class;
+  }
+}
diff --git a/test/839-clinit-throw/Android.bp b/test/839-clinit-throw/Android.bp
new file mode 100644
index 0000000..dad79f0
--- /dev/null
+++ b/test/839-clinit-throw/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `839-clinit-throw`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-839-clinit-throw",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-839-clinit-throw-expected-stdout",
+        ":art-run-test-839-clinit-throw-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-839-clinit-throw-expected-stdout",
+    out: ["art-run-test-839-clinit-throw-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-839-clinit-throw-expected-stderr",
+    out: ["art-run-test-839-clinit-throw-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/839-clinit-throw/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/839-clinit-throw/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/839-clinit-throw/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/839-clinit-throw/expected-stdout.txt
diff --git a/test/839-clinit-throw/info.txt b/test/839-clinit-throw/info.txt
new file mode 100644
index 0000000..36cbb6f
--- /dev/null
+++ b/test/839-clinit-throw/info.txt
@@ -0,0 +1,2 @@
+Test that a static initializer throwing doesn't contain a static method of the
+same class in the stack trace.
diff --git a/test/839-clinit-throw/src/Main.java b/test/839-clinit-throw/src/Main.java
new file mode 100644
index 0000000..bb21de1
--- /dev/null
+++ b/test/839-clinit-throw/src/Main.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+
+  static class NoPreloadHolder {
+    static Object o = null;
+
+    static {
+      o.toString();
+    }
+
+    static void $noinline$doCall() {
+    }
+  }
+
+  public static void main(String[] args) {
+    try {
+      NoPreloadHolder.$noinline$doCall();
+      throw new Error("Expected ExceptionInInitializerError");
+    } catch (ExceptionInInitializerError e) {
+      // expected
+      check(e, mainLine);
+    }
+  }
+
+  public static int mainLine = 32;
+
+  static void check(ExceptionInInitializerError ie, int mainLine) {
+    StackTraceElement[] trace = ie.getStackTrace();
+    assertEquals(trace.length, 1);
+    checkElement(trace[0], "Main", "main", "Main.java", mainLine);
+  }
+
+  static void checkElement(StackTraceElement element,
+                           String declaringClass, String methodName,
+                           String fileName, int lineNumber) {
+    assertEquals(declaringClass, element.getClassName());
+    assertEquals(methodName, element.getMethodName());
+    assertEquals(fileName, element.getFileName());
+    assertEquals(lineNumber, element.getLineNumber());
+  }
+
+  static void assertEquals(Object expected, Object actual) {
+    if (!expected.equals(actual)) {
+      String msg = "Expected \"" + expected + "\" but got \"" + actual + "\"";
+      throw new AssertionError(msg);
+    }
+  }
+
+  static void assertEquals(int expected, int actual) {
+    if (expected != actual) {
+      throw new AssertionError("Expected " + expected + " got " + actual);
+    }
+  }
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/840-resolution/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/840-resolution/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/840-resolution/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/840-resolution/expected-stdout.txt
diff --git a/test/840-resolution/info.txt b/test/840-resolution/info.txt
new file mode 100644
index 0000000..bd88f7d
--- /dev/null
+++ b/test/840-resolution/info.txt
@@ -0,0 +1,2 @@
+Various tests on interface method linking when not finding a public
+implementation.
diff --git a/test/840-resolution/jasmin/SubClass2.j b/test/840-resolution/jasmin/SubClass2.j
new file mode 100644
index 0000000..8a65e1f
--- /dev/null
+++ b/test/840-resolution/jasmin/SubClass2.j
@@ -0,0 +1,35 @@
+; Copyright (C) 2022 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 SubClass2
+.super                   SuperClass
+.implements              Interface
+
+.method                  public <init>()V
+   .limit stack          1
+   .limit locals         1
+   aload_0
+   invokespecial         SuperClass/<init>()V
+   return
+.end method
+
+.method                  foo()Ljava/lang/Class;
+   .limit stack          1
+   .limit locals         1
+   ; jasmin does not support ldc with a class, so just return null for the
+   ; purpose of this test.
+   aconst_null
+   areturn
+.end method
+
diff --git a/test/840-resolution/src/Main.java b/test/840-resolution/src/Main.java
new file mode 100644
index 0000000..23cd31d
--- /dev/null
+++ b/test/840-resolution/src/Main.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+
+  // Testcase 1: the superclass has a package private version in the same package.
+  static Interface s = new SubClass();
+
+  // Testcase 2: the class has a package private version.
+  static Interface s2;
+
+  // Testcase 3: the superclass has a package private version in a different package.
+  static Interface s3 = new SubClassFromPkg();
+
+  // Testcase 4: there is no implementation in the hierarchy.
+  static Interface s4 = new SubClassNoFoo();
+
+  // Testcase 5: there is a private method in the hierarchy.
+  static Interface s5 = new SubClassPrivateFoo();
+
+  // Testcase 6: there is a static method in the hierarchy.
+  static Interface s6 = new SubClassStaticFoo();
+
+  static {
+    try {
+      s2 = (Interface) Class.forName("SubClass2").newInstance();
+    } catch (Exception e) {
+      throw new Error(e);
+    }
+  }
+
+  public static void assertEquals(Object expected, Object actual) {
+    if (expected != actual) {
+      throw new Error("Expected " + expected + ", got " + actual);
+    }
+  }
+
+  public static void assertTrue(boolean value) {
+    if (!value) {
+      throw new Error("");
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    assertEquals(SuperClass.class, ((SubClass) s).foo());
+    assertEquals(SuperClass.class, ((SuperClass) s).foo());
+
+    try {
+      s.foo();
+      throw new Error("Expected IllegalAccessError");
+    } catch (IllegalAccessError ie) {
+      // expected
+    }
+
+    assertEquals(null, ((SuperClass) s2).foo());
+    try {
+      s2.foo();
+      throw new Error("Expected IllegalAccessError");
+    } catch (IllegalAccessError ie) {
+      // expected
+    }
+
+    try {
+      ((pkg.PkgSuperClass) s3).foo();
+      throw new Error("Expected IllegalAccessError");
+    } catch (IllegalAccessError ie) {
+      // expected
+    }
+
+    try {
+      ((SubClassFromPkg) s3).foo();
+      throw new Error("Expected IllegalAccessError");
+    } catch (IllegalAccessError ie) {
+      // expected
+    }
+
+    try {
+      s3.foo();
+      throw new Error("Expected IllegalAccessError");
+    } catch (IllegalAccessError ie) {
+      // expected
+    }
+
+    try {
+      ((SuperClassNoFoo) s4).foo();
+      throw new Error("Expected NoSuchMethodError");
+    } catch (NoSuchMethodError e) {
+      // expected
+    }
+
+    try {
+      ((SubClassNoFoo) s4).foo();
+      throw new Error("Expected AbstractMethodError");
+    } catch (AbstractMethodError e) {
+      // expected
+    }
+
+    try {
+      s4.foo();
+      throw new Error("Expected AbstractMethodError");
+    } catch (AbstractMethodError e) {
+      // expected
+    }
+
+    try {
+      ((SuperClassPrivateFoo) s5).foo();
+      throw new Error("Expected IllegalAccessError");
+    } catch (IllegalAccessError e) {
+      // expected
+    }
+
+    try {
+      ((SubClassPrivateFoo) s5).foo();
+      throw new Error("Expected IllegalAccessError");
+    } catch (IllegalAccessError e) {
+      // expected
+    }
+
+    try {
+      s5.foo();
+      throw new Error("Expected AbstractMethodError on RI, IllegalAccessError on ART");
+    } catch (AbstractMethodError | IllegalAccessError e) {
+      // expected
+    }
+
+    try {
+      ((SuperClassStaticFoo) s6).foo();
+      throw new Error("Expected IncompatibleClassChangeError");
+    } catch (IncompatibleClassChangeError e) {
+      // expected
+    }
+
+    try {
+      ((SubClassStaticFoo) s6).foo();
+      throw new Error("Expected IncompatibleClassChangeError");
+    } catch (IncompatibleClassChangeError e) {
+      // expected
+    }
+
+    try {
+      s6.foo();
+      throw new Error("Expected AbstractMethodError");
+    } catch (AbstractMethodError e) {
+      // expected
+    }
+  }
+}
+
+interface Interface {
+  public Class<?> foo();
+}
+
+class SubClass extends SuperClass implements Interface {
+}
+
+class SubClassFromPkg extends pkg.PkgSuperClass implements Interface {
+}
+
+class SubClassNoFoo extends SuperClassNoFoo implements Interface {
+}
+
+class SubClassPrivateFoo extends SuperClassPrivateFoo implements Interface {
+}
+
+class SubClassStaticFoo extends SuperClassStaticFoo implements Interface {
+}
diff --git a/test/840-resolution/src/SuperClass.java b/test/840-resolution/src/SuperClass.java
new file mode 100644
index 0000000..ece0188
--- /dev/null
+++ b/test/840-resolution/src/SuperClass.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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 SuperClass {
+  public Class<?> foo() {
+    return SuperClass.class;
+  }
+}
diff --git a/test/840-resolution/src/SuperClassNoFoo.java b/test/840-resolution/src/SuperClassNoFoo.java
new file mode 100644
index 0000000..747aaef
--- /dev/null
+++ b/test/840-resolution/src/SuperClassNoFoo.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class SuperClassNoFoo {
+  public Class<?> foo() {
+    throw new Error("Unreachable");
+  }
+}
diff --git a/test/840-resolution/src/SuperClassPrivateFoo.java b/test/840-resolution/src/SuperClassPrivateFoo.java
new file mode 100644
index 0000000..95af4f7
--- /dev/null
+++ b/test/840-resolution/src/SuperClassPrivateFoo.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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 SuperClassPrivateFoo {
+  public Class<?> foo() {
+    return SuperClass.class;
+  }
+}
diff --git a/test/840-resolution/src/SuperClassStaticFoo.java b/test/840-resolution/src/SuperClassStaticFoo.java
new file mode 100644
index 0000000..490637f
--- /dev/null
+++ b/test/840-resolution/src/SuperClassStaticFoo.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class SuperClassStaticFoo {
+  public Class<?> foo() {
+    throw new Error("Unreachable");
+  }
+}
diff --git a/test/840-resolution/src/pkg/PkgSuperClass.java b/test/840-resolution/src/pkg/PkgSuperClass.java
new file mode 100644
index 0000000..397ae28
--- /dev/null
+++ b/test/840-resolution/src/pkg/PkgSuperClass.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg;
+
+public class PkgSuperClass {
+  public Class<?> foo() {
+    return PkgSuperClass.class;
+  }
+}
diff --git a/test/840-resolution/src2/SuperClass.java b/test/840-resolution/src2/SuperClass.java
new file mode 100644
index 0000000..fe40c0a
--- /dev/null
+++ b/test/840-resolution/src2/SuperClass.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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 SuperClass {
+  Class<?> foo() {
+    return SuperClass.class;
+  }
+}
diff --git a/test/840-resolution/src2/SuperClassNoFoo.java b/test/840-resolution/src2/SuperClassNoFoo.java
new file mode 100644
index 0000000..c0e8c44
--- /dev/null
+++ b/test/840-resolution/src2/SuperClassNoFoo.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class SuperClassNoFoo {
+}
diff --git a/test/840-resolution/src2/SuperClassPrivateFoo.java b/test/840-resolution/src2/SuperClassPrivateFoo.java
new file mode 100644
index 0000000..f0c1c68
--- /dev/null
+++ b/test/840-resolution/src2/SuperClassPrivateFoo.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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 SuperClassPrivateFoo {
+  private Class<?> foo() {
+    return SuperClass.class;
+  }
+}
diff --git a/test/840-resolution/src2/SuperClassStaticFoo.java b/test/840-resolution/src2/SuperClassStaticFoo.java
new file mode 100644
index 0000000..ecf8bc1
--- /dev/null
+++ b/test/840-resolution/src2/SuperClassStaticFoo.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class SuperClassStaticFoo {
+  public static Class<?> foo() {
+    return SuperClassStaticFoo.class;
+  }
+}
diff --git a/test/840-resolution/src2/pkg/PkgSuperClass.java b/test/840-resolution/src2/pkg/PkgSuperClass.java
new file mode 100644
index 0000000..2f333ee
--- /dev/null
+++ b/test/840-resolution/src2/pkg/PkgSuperClass.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package pkg;
+
+public class PkgSuperClass {
+  Class<?> foo() {
+    return PkgSuperClass.class;
+  }
+}
diff --git a/test/841-defaults/Android.bp b/test/841-defaults/Android.bp
new file mode 100644
index 0000000..2d2fce6
--- /dev/null
+++ b/test/841-defaults/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `841-defaults`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-841-defaults",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-841-defaults-expected-stdout",
+        ":art-run-test-841-defaults-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-841-defaults-expected-stdout",
+    out: ["art-run-test-841-defaults-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-841-defaults-expected-stderr",
+    out: ["art-run-test-841-defaults-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/841-defaults/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/841-defaults/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/841-defaults/expected-stdout.txt
similarity index 100%
rename from test/089-many-methods/expected-stdout.txt
rename to test/841-defaults/expected-stdout.txt
diff --git a/test/841-defaults/info.txt b/test/841-defaults/info.txt
new file mode 100644
index 0000000..85e265f
--- /dev/null
+++ b/test/841-defaults/info.txt
@@ -0,0 +1,2 @@
+Regression test for doing an invokeinterface on a default method whose
+dex method index is greater than the imt size.
diff --git a/test/841-defaults/src/Main.java b/test/841-defaults/src/Main.java
new file mode 100644
index 0000000..c07b516
--- /dev/null
+++ b/test/841-defaults/src/Main.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+interface Itf {
+  default int defaultMethod1() { return 1; }
+  default int defaultMethod2() { return 2; }
+  default int defaultMethod3() { return 3; }
+  default int defaultMethod4() { return 4; }
+  default int defaultMethod5() { return 5; }
+  default int defaultMethod6() { return 6; }
+  default int defaultMethod7() { return 7; }
+  default int defaultMethod8() { return 8; }
+  default int defaultMethod9() { return 9; }
+  default int defaultMethod10() { return 10; }
+  default int defaultMethod11() { return 11; }
+  default int defaultMethod12() { return 12; }
+  default int defaultMethod13() { return 13; }
+  default int defaultMethod14() { return 14; }
+  default int defaultMethod15() { return 15; }
+  default int defaultMethod16() { return 16; }
+  default int defaultMethod17() { return 17; }
+  default int defaultMethod18() { return 18; }
+  default int defaultMethod19() { return 19; }
+  default int defaultMethod20() { return 20; }
+  default int defaultMethod21() { return 21; }
+  default int defaultMethod22() { return 22; }
+  default int defaultMethod23() { return 23; }
+  default int defaultMethod24() { return 24; }
+  default int defaultMethod25() { return 25; }
+  default int defaultMethod26() { return 26; }
+  default int defaultMethod27() { return 27; }
+  default int defaultMethod28() { return 28; }
+  default int defaultMethod29() { return 29; }
+  default int defaultMethod30() { return 30; }
+  default int defaultMethod31() { return 31; }
+  default int defaultMethod32() { return 32; }
+  default int defaultMethod33() { return 33; }
+  default int defaultMethod34() { return 34; }
+  default int defaultMethod35() { return 35; }
+  default int defaultMethod36() { return 36; }
+  default int defaultMethod37() { return 37; }
+  default int defaultMethod38() { return 38; }
+  default int defaultMethod39() { return 39; }
+  default int defaultMethod40() { return 40; }
+  default int defaultMethod41() { return 41; }
+  default int defaultMethod42() { return 42; }
+  default int defaultMethod43() { return 43; }
+  default int defaultMethod44() { return 44; }
+  default int defaultMethod45() { return 45; }
+  default int defaultMethod46() { return 46; }
+  default int defaultMethod47() { return 47; }
+  default int defaultMethod48() { return 48; }
+  default int defaultMethod49() { return 49; }
+  default int defaultMethod50() { return 50; }
+  default int defaultMethod51() { return 51; }
+}
+
+public class Main implements Itf {
+  static Itf itf = new Main();
+  public static void assertEquals(int value, int expected) {
+    if (value != expected) {
+      throw new Error("Expected " + expected + ", got " + value);
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    assertEquals(itf.defaultMethod1(), 1);
+    assertEquals(itf.defaultMethod2(), 2);
+    assertEquals(itf.defaultMethod3(), 3);
+    assertEquals(itf.defaultMethod4(), 4);
+    assertEquals(itf.defaultMethod5(), 5);
+    assertEquals(itf.defaultMethod6(), 6);
+    assertEquals(itf.defaultMethod7(), 7);
+    assertEquals(itf.defaultMethod8(), 8);
+    assertEquals(itf.defaultMethod9(), 9);
+    assertEquals(itf.defaultMethod10(), 10);
+    assertEquals(itf.defaultMethod11(), 11);
+    assertEquals(itf.defaultMethod12(), 12);
+    assertEquals(itf.defaultMethod13(), 13);
+    assertEquals(itf.defaultMethod14(), 14);
+    assertEquals(itf.defaultMethod15(), 15);
+    assertEquals(itf.defaultMethod16(), 16);
+    assertEquals(itf.defaultMethod17(), 17);
+    assertEquals(itf.defaultMethod18(), 18);
+    assertEquals(itf.defaultMethod19(), 19);
+    assertEquals(itf.defaultMethod20(), 20);
+    assertEquals(itf.defaultMethod21(), 21);
+    assertEquals(itf.defaultMethod22(), 22);
+    assertEquals(itf.defaultMethod23(), 23);
+    assertEquals(itf.defaultMethod24(), 24);
+    assertEquals(itf.defaultMethod25(), 25);
+    assertEquals(itf.defaultMethod26(), 26);
+    assertEquals(itf.defaultMethod27(), 27);
+    assertEquals(itf.defaultMethod28(), 28);
+    assertEquals(itf.defaultMethod29(), 29);
+    assertEquals(itf.defaultMethod30(), 30);
+    assertEquals(itf.defaultMethod31(), 31);
+    assertEquals(itf.defaultMethod32(), 32);
+    assertEquals(itf.defaultMethod33(), 33);
+    assertEquals(itf.defaultMethod34(), 34);
+    assertEquals(itf.defaultMethod35(), 35);
+    assertEquals(itf.defaultMethod36(), 36);
+    assertEquals(itf.defaultMethod37(), 37);
+    assertEquals(itf.defaultMethod38(), 38);
+    assertEquals(itf.defaultMethod39(), 39);
+    assertEquals(itf.defaultMethod40(), 40);
+    assertEquals(itf.defaultMethod41(), 41);
+    assertEquals(itf.defaultMethod42(), 42);
+    assertEquals(itf.defaultMethod43(), 43);
+    assertEquals(itf.defaultMethod44(), 44);
+    assertEquals(itf.defaultMethod45(), 45);
+    assertEquals(itf.defaultMethod46(), 46);
+    assertEquals(itf.defaultMethod47(), 47);
+    assertEquals(itf.defaultMethod48(), 48);
+    assertEquals(itf.defaultMethod49(), 49);
+    assertEquals(itf.defaultMethod50(), 50);
+    assertEquals(itf.defaultMethod51(), 51);
+  }
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/842-vdex-hard-failure/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/842-vdex-hard-failure/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/842-vdex-hard-failure/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/842-vdex-hard-failure/expected-stdout.txt
diff --git a/test/842-vdex-hard-failure/info.txt b/test/842-vdex-hard-failure/info.txt
new file mode 100644
index 0000000..21412c7
--- /dev/null
+++ b/test/842-vdex-hard-failure/info.txt
@@ -0,0 +1,2 @@
+Regression test for vdex, where we were compiling methods that had hard
+failures.
diff --git a/test/842-vdex-hard-failure/run.py b/test/842-vdex-hard-failure/run.py
new file mode 100644
index 0000000..81ce172
--- /dev/null
+++ b/test/842-vdex-hard-failure/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+
+def run(ctx, args):
+  # This test is for testing vdex when calling FastVerify and doing compilation.
+  ctx.default_run(args, vdex=True, vdex_filter="speed")
diff --git a/test/842-vdex-hard-failure/smali/HardFail.smali b/test/842-vdex-hard-failure/smali/HardFail.smali
new file mode 100644
index 0000000..52aefe0
--- /dev/null
+++ b/test/842-vdex-hard-failure/smali/HardFail.smali
@@ -0,0 +1,26 @@
+# /*
+#  * Copyright (C) 2022 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 LHardFail;
+
+.super Ljava/lang/Object;
+
+.method public static foo()V
+    .locals 1
+    # smali requires at least one instruction
+    new-instance v0, Ljava/lang/Object;
+    # No return on purprose to hard fail the class and crash the compiler.
+.end method
diff --git a/test/842-vdex-hard-failure/src/Main.java b/test/842-vdex-hard-failure/src/Main.java
new file mode 100644
index 0000000..c6f1f68
--- /dev/null
+++ b/test/842-vdex-hard-failure/src/Main.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+
+  public static void main(String[] args) throws ClassNotFoundException {
+    try {
+      Class.forName("HardFail");
+      throw new Error("Expected VerifyError");
+    } catch (VerifyError e) {
+      // expected
+    }
+  }
+}
diff --git a/test/843-default-interface/Android.bp b/test/843-default-interface/Android.bp
new file mode 100644
index 0000000..ff942e6
--- /dev/null
+++ b/test/843-default-interface/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `843-default-interface`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-843-default-interface-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-843-default-interface",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-843-default-interface-src"
+    ],
+    data: [
+        ":art-run-test-843-default-interface-expected-stdout",
+        ":art-run-test-843-default-interface-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-843-default-interface-expected-stdout",
+    out: ["art-run-test-843-default-interface-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-843-default-interface-expected-stderr",
+    out: ["art-run-test-843-default-interface-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/843-default-interface/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/843-default-interface/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/843-default-interface/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/843-default-interface/expected-stdout.txt
diff --git a/test/843-default-interface/info.txt b/test/843-default-interface/info.txt
new file mode 100644
index 0000000..3586e7f
--- /dev/null
+++ b/test/843-default-interface/info.txt
@@ -0,0 +1,2 @@
+Regression test for ArtMethod::CopyFrom, which used to wrongly override the
+imt_index_ of abstract methods with 0.
diff --git a/test/843-default-interface/src/Impl.java b/test/843-default-interface/src/Impl.java
new file mode 100644
index 0000000..8c9d76c
--- /dev/null
+++ b/test/843-default-interface/src/Impl.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Impl implements SubItf {
+  public String foo() {
+    return "Impl";
+  }
+}
diff --git a/test/843-default-interface/src/Itf.java b/test/843-default-interface/src/Itf.java
new file mode 100644
index 0000000..0625429
--- /dev/null
+++ b/test/843-default-interface/src/Itf.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public interface Itf {
+  public default String bar() {
+    return "Itf";
+  }
+}
diff --git a/test/843-default-interface/src/Main.java b/test/843-default-interface/src/Main.java
new file mode 100644
index 0000000..8b2b2da
--- /dev/null
+++ b/test/843-default-interface/src/Main.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+  static SubItf itf = new Impl();
+  public static void main(String[] args) throws Exception {
+    // Loop enough to trigger the native OOME.
+    for (int i = 0; i < 50000; ++i) {
+      // Because the imt index was overwritten to 0, this call ended up
+      // in the conflict trampoline which wrongly updated the 0th entry
+      // of the imt table. This lead to this call always calling the
+      // conflict trampoline.
+      itf.foo();
+    }
+  }
+}
diff --git a/test/843-default-interface/src/OtherItf.java b/test/843-default-interface/src/OtherItf.java
new file mode 100644
index 0000000..368631c
--- /dev/null
+++ b/test/843-default-interface/src/OtherItf.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public interface OtherItf {
+  // javac will complain when compiling SubItf if both superinterfaces Itf and OtherItf
+  // define a default method bar(), so we do not define bar() here.
+  // What will be loaded at runtime will actually be src2/OtherItf.
+  // public default String bar() {
+  //   return "OtherItf";
+  // }
+}
diff --git a/test/843-default-interface/src/SubItf.java b/test/843-default-interface/src/SubItf.java
new file mode 100644
index 0000000..6983b18
--- /dev/null
+++ b/test/843-default-interface/src/SubItf.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+// The method bar will be a default conflict for this class. That used to make
+// the class linker re-allocate its ArtMethod array, and calling CopyFrom.
+// The bug was that CopyFrom was overwriting the imt index of interface methods,
+// and for this example `foo`.
+public interface SubItf extends Itf, OtherItf {
+  public String foo();
+}
diff --git a/test/843-default-interface/src2/OtherItf.java b/test/843-default-interface/src2/OtherItf.java
new file mode 100644
index 0000000..08028f4
--- /dev/null
+++ b/test/843-default-interface/src2/OtherItf.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public interface OtherItf {
+  public default String bar() {
+    return "OtherItf";
+  }
+}
diff --git a/test/844-exception/Android.bp b/test/844-exception/Android.bp
new file mode 100644
index 0000000..92ecef4
--- /dev/null
+++ b/test/844-exception/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `844-exception`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-844-exception",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-844-exception-expected-stdout",
+        ":art-run-test-844-exception-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-844-exception-expected-stdout",
+    out: ["art-run-test-844-exception-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-844-exception-expected-stderr",
+    out: ["art-run-test-844-exception-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/844-exception/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/844-exception/expected-stderr.txt
diff --git a/test/844-exception/expected-stdout.txt b/test/844-exception/expected-stdout.txt
new file mode 100644
index 0000000..7ddf8e3
--- /dev/null
+++ b/test/844-exception/expected-stdout.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+Caught exception
diff --git a/test/844-exception/info.txt b/test/844-exception/info.txt
new file mode 100644
index 0000000..a825fbb
--- /dev/null
+++ b/test/844-exception/info.txt
@@ -0,0 +1,2 @@
+Regression test for Thread::QuickDeliverException which used to expect a quick
+frame to be the caller.
diff --git a/test/844-exception/src/Main.java b/test/844-exception/src/Main.java
new file mode 100644
index 0000000..6237844
--- /dev/null
+++ b/test/844-exception/src/Main.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+  static Main empty;
+
+  static class MyThread extends Thread {
+    public void run() {
+      // This will throw at `callMethodThatThrows` and trigger deoptimization checks which we used
+      // to crash on.
+      new Inner();
+    }
+  }
+
+  public static class Inner {
+    // Have a <clinit> method invoke another <clinit> method to ensure we execute in the
+    // interpreter.
+    static {
+      new Inner2();
+    }
+  }
+
+  public static class Inner2 {
+    static {
+      Main.callMethodThatThrows();
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
+    // Disables use of nterp.
+    Main.setAsyncExceptionsThrown();
+
+    // Execute the test in a different thread, to ensure we still
+    // return a 0 exit status.
+    Thread t = new MyThread();
+    t.setUncaughtExceptionHandler((th, e) -> {
+      System.out.println("Caught exception");
+    });
+    t.start();
+    t.join();
+  }
+
+  public static void callMethodThatThrows() {
+    // Ensures we get deoptimization requests.
+    Main.forceInterpreterOnThread();
+    throw new Error("");
+  }
+
+  public static native void forceInterpreterOnThread();
+  public static native void setAsyncExceptionsThrown();
+
+}
diff --git a/test/844-exception2/Android.bp b/test/844-exception2/Android.bp
new file mode 100644
index 0000000..50568b1
--- /dev/null
+++ b/test/844-exception2/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `844-exception2`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-844-exception2",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-844-exception2-expected-stdout",
+        ":art-run-test-844-exception2-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-844-exception2-expected-stdout",
+    out: ["art-run-test-844-exception2-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-844-exception2-expected-stderr",
+    out: ["art-run-test-844-exception2-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/844-exception2/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/844-exception2/expected-stderr.txt
diff --git a/test/844-exception2/expected-stdout.txt b/test/844-exception2/expected-stdout.txt
new file mode 100644
index 0000000..7ddf8e3
--- /dev/null
+++ b/test/844-exception2/expected-stdout.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+Caught exception
diff --git a/test/844-exception2/info.txt b/test/844-exception2/info.txt
new file mode 100644
index 0000000..641dc72
--- /dev/null
+++ b/test/844-exception2/info.txt
@@ -0,0 +1,2 @@
+Regression test for Thread::DeoptimizeWithDeoptimizationException which always
+expected to have a shadow frame to execute.
diff --git a/test/844-exception2/src/Main.java b/test/844-exception2/src/Main.java
new file mode 100644
index 0000000..400b337
--- /dev/null
+++ b/test/844-exception2/src/Main.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+  public static class Inner {
+    // Use a <clinit> method to ensure we execute in the
+    // interpreter.
+    static {
+      Main.callMethodThatThrows();
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
+    // Disables use of nterp.
+    Main.setAsyncExceptionsThrown();
+
+    Thread.currentThread().setUncaughtExceptionHandler((th, e) -> {
+      System.out.println("Caught exception");
+      // Exit the test gracefully.
+      System.exit(0);
+    });
+    // This will throw at `callMethodThatThrows` and trigger deoptimization checks which we used
+    // to crash on.
+    new Inner();
+  }
+
+  public static void callMethodThatThrows() {
+    // Ensures we get deoptimization requests.
+    Main.forceInterpreterOnThread();
+    throw new Error("");
+  }
+
+  public static native void forceInterpreterOnThread();
+  public static native void setAsyncExceptionsThrown();
+
+}
diff --git a/test/845-data-image/Android.bp b/test/845-data-image/Android.bp
new file mode 100644
index 0000000..2daae4b
--- /dev/null
+++ b/test/845-data-image/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `845-data-image`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-845-data-image",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src-art/**/*.java"],
+    data: [
+        ":art-run-test-845-data-image-expected-stdout",
+        ":art-run-test-845-data-image-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-845-data-image-expected-stdout",
+    out: ["art-run-test-845-data-image-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-845-data-image-expected-stderr",
+    out: ["art-run-test-845-data-image-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/845-data-image/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/845-data-image/expected-stderr.txt
diff --git a/test/845-data-image/expected-stdout.txt b/test/845-data-image/expected-stdout.txt
new file mode 100644
index 0000000..bebd8c0
--- /dev/null
+++ b/test/845-data-image/expected-stdout.txt
@@ -0,0 +1,4 @@
+JNI_OnLoad called
+JNI_OnLoad called
+JNI_OnLoad called
+JNI_OnLoad called
diff --git a/test/845-data-image/info.txt b/test/845-data-image/info.txt
new file mode 100644
index 0000000..dfcd0dd
--- /dev/null
+++ b/test/845-data-image/info.txt
@@ -0,0 +1 @@
+Test the generation of app image at runtime.
diff --git a/test/845-data-image/run.py b/test/845-data-image/run.py
new file mode 100644
index 0000000..7e0dbf7
--- /dev/null
+++ b/test/845-data-image/run.py
@@ -0,0 +1,28 @@
+# Copyright (C) 2022 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.
+
+import sys
+
+# We run the tests by disabling compilation with app image and forcing
+# relocation for better testing.
+# Run the test twice: one run for generating the image, a second run for using
+# the image.
+def run(ctx, args):
+  ctx.default_run(args, Xcompiler_option=["--compact-dex-level=fast"], app_image=False, relocate=True)
+  # Pass another argument to let the test know it should now expect an image.
+  ctx.default_run(args, Xcompiler_option=["--compact-dex-level=fast"], app_image=False, relocate=True, test_args=["--second-run"])
+  # Repeat the test with a different compact dex level, to make sure we don't
+  # pick up the existing image.
+  ctx.default_run(args, Xcompiler_option=["--compact-dex-level=none"], app_image=False, relocate=True)
+  ctx.default_run(args, Xcompiler_option=["--compact-dex-level=none"], app_image=False, relocate=True, test_args=["--second-run"])
diff --git a/test/845-data-image/src-art/Main.java b/test/845-data-image/src-art/Main.java
new file mode 100644
index 0000000..e74a6d6
--- /dev/null
+++ b/test/845-data-image/src-art/Main.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import dalvik.system.DexFile;
+import dalvik.system.VMRuntime;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.util.concurrent.CyclicBarrier;
+
+// This class helps testing that we don't mark `InheritsBigInteger` as initialized,
+// given we do not expect `BigInteger` to be initialized in the boot image.
+class InheritsBigInteger extends BigInteger {
+  InheritsBigInteger(String value) {
+    super(value);
+  }
+}
+
+class SuperClass {}
+
+class ClassWithStatics extends SuperClass {
+  public static final String STATIC_STRING = "foo";
+  public static final int STATIC_INT = 42;
+}
+
+class ClassWithStaticType {
+  public static final Class<?> STATIC_TYPE = Object.class;
+}
+
+// Add an interface for testing generating classes and interfaces.
+interface Itf {
+  public int someMethod();
+  public default int someDefaultMethod() { return 42; }
+}
+
+// Add a second interface with many methods to force a conflict in the IMT. We want a second
+// interface to make sure `Itf` gets entries with the imt_unimplemented_method runtime method.
+interface Itf2 {
+  default int defaultMethod1() { return 1; }
+  default int defaultMethod2() { return 2; }
+  default int defaultMethod3() { return 3; }
+  default int defaultMethod4() { return 4; }
+  default int defaultMethod5() { return 5; }
+  default int defaultMethod6() { return 6; }
+  default int defaultMethod7() { return 7; }
+  default int defaultMethod8() { return 8; }
+  default int defaultMethod9() { return 9; }
+  default int defaultMethod10() { return 10; }
+  default int defaultMethod11() { return 11; }
+  default int defaultMethod12() { return 12; }
+  default int defaultMethod13() { return 13; }
+  default int defaultMethod14() { return 14; }
+  default int defaultMethod15() { return 15; }
+  default int defaultMethod16() { return 16; }
+  default int defaultMethod17() { return 17; }
+  default int defaultMethod18() { return 18; }
+  default int defaultMethod19() { return 19; }
+  default int defaultMethod20() { return 20; }
+  default int defaultMethod21() { return 21; }
+  default int defaultMethod22() { return 22; }
+  default int defaultMethod23() { return 23; }
+  default int defaultMethod24() { return 24; }
+  default int defaultMethod25() { return 25; }
+  default int defaultMethod26() { return 26; }
+  default int defaultMethod27() { return 27; }
+  default int defaultMethod28() { return 28; }
+  default int defaultMethod29() { return 29; }
+  default int defaultMethod30() { return 30; }
+  default int defaultMethod31() { return 31; }
+  default int defaultMethod32() { return 32; }
+  default int defaultMethod33() { return 33; }
+  default int defaultMethod34() { return 34; }
+  default int defaultMethod35() { return 35; }
+  default int defaultMethod36() { return 36; }
+  default int defaultMethod37() { return 37; }
+  default int defaultMethod38() { return 38; }
+  default int defaultMethod39() { return 39; }
+  default int defaultMethod40() { return 40; }
+  default int defaultMethod41() { return 41; }
+  default int defaultMethod42() { return 42; }
+  default int defaultMethod43() { return 43; }
+  default int defaultMethod44() { return 44; }
+  default int defaultMethod45() { return 45; }
+  default int defaultMethod46() { return 46; }
+  default int defaultMethod47() { return 47; }
+  default int defaultMethod48() { return 48; }
+  default int defaultMethod49() { return 49; }
+  default int defaultMethod50() { return 50; }
+  default int defaultMethod51() { return 51; }
+}
+
+class Itf2Impl implements Itf2 {
+}
+
+public class Main implements Itf {
+  static String myString = "MyString";
+
+  static class MyThread extends Thread {
+    CyclicBarrier barrier;
+
+    public MyThread(CyclicBarrier barrier) {
+      this.barrier = barrier;
+    }
+    public void run() {
+      try {
+        synchronized (Main.myString) {
+          barrier.await();
+          barrier.reset();
+          // Infinite wait.
+          barrier.await();
+        }
+      } catch (Exception e) {
+        throw new Error(e);
+      }
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
+
+    // Register the dex file so that the runtime can pick up which
+    // dex file to compile for the image.
+    File file = null;
+    try {
+      file = createTempFile();
+      String codePath = DEX_LOCATION + "/845-data-image.jar";
+      VMRuntime.registerAppInfo(
+          "test.app",
+          file.getPath(),
+          file.getPath(),
+          new String[] {codePath},
+          VMRuntime.CODE_PATH_TYPE_PRIMARY_APK);
+    } finally {
+      if (file != null) {
+        file.delete();
+      }
+    }
+
+    if (!hasOatFile() || !hasImage()) {
+      // We only generate an app image if there is at least a vdex file and a boot image.
+      return;
+    }
+
+    if (args.length == 2 && "--second-run".equals(args[1])) {
+      DexFile.OptimizationInfo info = VMRuntime.getBaseApkOptimizationInfo();
+      if (!info.isOptimized()) {
+        throw new Error("Expected image to be loaded");
+      }
+    }
+
+    runClassTests();
+
+    // Test that we emit an empty lock word. If we are not, then this synchronized call here would
+    // block on a run with the runtime image.
+    synchronized (myString) {
+    }
+
+    // Create a thread that makes sure `myString` is locked while the main thread is generating
+    // the runtime image.
+    CyclicBarrier barrier = new CyclicBarrier(2);
+    Thread t = new MyThread(barrier);
+    t.setDaemon(true);
+    t.start();
+    barrier.await();
+
+    VMRuntime runtime = VMRuntime.getRuntime();
+    runtime.notifyStartupCompleted();
+
+    String filter = getCompilerFilter(Main.class);
+    if ("speed-profile".equals(filter) || "speed".equals(filter)) {
+      // We only generate an app image for filters that don't compile.
+      return;
+    }
+
+    String instructionSet = VMRuntime.getCurrentInstructionSet();
+    // Wait for the file to be generated.
+    File image = new File(DEX_LOCATION + "/" + instructionSet + "/845-data-image.art");
+    while (!image.exists()) {
+      Thread.yield();
+    }
+  }
+
+  static class MyProxy implements InvocationHandler {
+
+    private Object obj;
+
+    public static Object newInstance(Object obj) {
+        return java.lang.reflect.Proxy.newProxyInstance(
+            obj.getClass().getClassLoader(),
+            obj.getClass().getInterfaces(),
+            new MyProxy(obj));
+    }
+
+    private MyProxy(Object obj) {
+        this.obj = obj;
+    }
+
+    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
+      return m.invoke(obj, args);
+    }
+  }
+
+  public static Itf itf = new Main();
+  public static Itf2 itf2 = new Itf2Impl();
+  public static ClassWithStatics statics = new ClassWithStatics();
+  public static ClassWithStaticType staticType = new ClassWithStaticType();
+
+  public static void runClassTests() {
+    // Test Class.getName, app images expect all strings to have hash codes.
+    assertEquals("Main", Main.class.getName());
+
+    // Basic tests for invokes with a copied method.
+    assertEquals(3, new Main().someMethod());
+    assertEquals(42, new Main().someDefaultMethod());
+
+    assertEquals(3, itf.someMethod());
+    assertEquals(42, itf.someDefaultMethod());
+
+    // Test with a proxy class.
+    Itf foo = (Itf) MyProxy.newInstance(new Main());
+    assertEquals(3, foo.someMethod());
+    assertEquals(42, foo.someDefaultMethod());
+
+    // Test with array classes.
+    assertEquals("[LMain;", Main[].class.getName());
+    assertEquals("[[LMain;", Main[][].class.getName());
+
+    assertEquals("[LMain;", new Main[4].getClass().getName());
+    assertEquals("[[LMain;", new Main[1][2].getClass().getName());
+
+    Main array[] = new Main[] { new Main() };
+    assertEquals("[LMain;", array.getClass().getName());
+
+    assertEquals(Object[][][][].class, Array.newInstance(Object.class, 0, 0, 0, 0).getClass());
+    assertEquals("int", int.class.getName());
+    assertEquals("[I", int[].class.getName());
+
+    assertEquals("foo", statics.STATIC_STRING);
+    assertEquals(42, statics.STATIC_INT);
+
+    assertEquals(Object.class, staticType.STATIC_TYPE);
+
+    // Call all interface methods to trigger the creation of a imt conflict method.
+    itf2.defaultMethod1();
+    itf2.defaultMethod2();
+    itf2.defaultMethod3();
+    itf2.defaultMethod4();
+    itf2.defaultMethod5();
+    itf2.defaultMethod6();
+    itf2.defaultMethod7();
+    itf2.defaultMethod8();
+    itf2.defaultMethod9();
+    itf2.defaultMethod10();
+    itf2.defaultMethod11();
+    itf2.defaultMethod12();
+    itf2.defaultMethod13();
+    itf2.defaultMethod14();
+    itf2.defaultMethod15();
+    itf2.defaultMethod16();
+    itf2.defaultMethod17();
+    itf2.defaultMethod18();
+    itf2.defaultMethod19();
+    itf2.defaultMethod20();
+    itf2.defaultMethod21();
+    itf2.defaultMethod22();
+    itf2.defaultMethod23();
+    itf2.defaultMethod24();
+    itf2.defaultMethod25();
+    itf2.defaultMethod26();
+    itf2.defaultMethod27();
+    itf2.defaultMethod28();
+    itf2.defaultMethod29();
+    itf2.defaultMethod30();
+    itf2.defaultMethod31();
+    itf2.defaultMethod32();
+    itf2.defaultMethod33();
+    itf2.defaultMethod34();
+    itf2.defaultMethod35();
+    itf2.defaultMethod36();
+    itf2.defaultMethod37();
+    itf2.defaultMethod38();
+    itf2.defaultMethod39();
+    itf2.defaultMethod40();
+    itf2.defaultMethod41();
+    itf2.defaultMethod42();
+    itf2.defaultMethod43();
+    itf2.defaultMethod44();
+    itf2.defaultMethod45();
+    itf2.defaultMethod46();
+    itf2.defaultMethod47();
+    itf2.defaultMethod48();
+    itf2.defaultMethod49();
+    itf2.defaultMethod50();
+    itf2.defaultMethod51();
+
+    InheritsBigInteger bigInteger = new InheritsBigInteger("42");
+    assertEquals("42", bigInteger.toString());
+  }
+
+  private static void assertEquals(int expected, int actual) {
+    if (expected != actual) {
+      throw new Error("Expected " + expected + ", got " + actual);
+    }
+  }
+
+  private static void assertEquals(Object expected, Object actual) {
+    if (!expected.equals(actual)) {
+      throw new Error("Expected \"" + expected + "\", got \"" + actual + "\"");
+    }
+  }
+
+  public int someMethod() {
+    return 3;
+  }
+
+  private static native boolean hasOatFile();
+  private static native boolean hasImage();
+  private static native String getCompilerFilter(Class<?> cls);
+
+  private static final String TEMP_FILE_NAME_PREFIX = "temp";
+  private static final String TEMP_FILE_NAME_SUFFIX = "-file";
+  private static final String DEX_LOCATION = System.getenv("DEX_LOCATION");
+
+  private static File createTempFile() throws Exception {
+    try {
+      return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
+    } catch (IOException e) {
+      System.setProperty("java.io.tmpdir", "/data/local/tmp");
+      try {
+        return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
+      } catch (IOException e2) {
+        System.setProperty("java.io.tmpdir", "/sdcard");
+        return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
+      }
+    }
+  }
+}
diff --git a/test/845-fast-verify/845-fast-verify.jar b/test/845-fast-verify/845-fast-verify.jar
new file mode 100644
index 0000000..d94777a
--- /dev/null
+++ b/test/845-fast-verify/845-fast-verify.jar
Binary files differ
diff --git a/test/845-fast-verify/build.py b/test/845-fast-verify/build.py
new file mode 100644
index 0000000..f304d95
--- /dev/null
+++ b/test/845-fast-verify/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  pass  # Nothing to do.
diff --git a/test/845-fast-verify/classes.dm b/test/845-fast-verify/classes.dm
new file mode 100644
index 0000000..c2c8559
--- /dev/null
+++ b/test/845-fast-verify/classes.dm
Binary files differ
diff --git a/test/089-many-methods/expected-stderr.txt b/test/845-fast-verify/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/845-fast-verify/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/845-fast-verify/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/845-fast-verify/expected-stdout.txt
diff --git a/test/845-fast-verify/info.txt b/test/845-fast-verify/info.txt
new file mode 100644
index 0000000..678ec3f
--- /dev/null
+++ b/test/845-fast-verify/info.txt
@@ -0,0 +1,46 @@
+Regression test for the combination of dex2oat using:
+- jar with multidex
+- vdex file where one dex file fails to fast verify (for example because of a
+  boot classpath change)
+- dex files being compiled individually
+
+We used to crash in CompilerDriver::FastVerify, assuming that only FastVerify
+can update the compiled_classes_ map. However, this isn't the case if one of the
+dex file ended up needing full verification.
+
+We need prebuilts of the .jar and .dm file as we rely on the bootclasspath to
+change which isn't expressable in a run-test. So we locally modified
+android.system.Int32Ref to inherit java.util.HashMap.
+
+The code that was used to generate the prebuilts is as follows:
+
+
+file Main.java in classes.dex:
+
+import java.util.HashMap;
+import android.system.Int32Ref;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    try {
+      FailVerification.foo();
+      throw new Exception("Expected error");
+    } catch (Error expected) {
+    }
+  }
+}
+
+class FailVerification extends Foo {
+
+  public static void foo() {
+    Int32Ref ref = new Int32Ref(42);
+    takeHashMap(ref);
+  }
+
+  public static void takeHashMap(HashMap m) {}
+}
+
+file Foo.java in classes2.dex:
+
+public class Foo {
+}
diff --git a/test/845-fast-verify/run.py b/test/845-fast-verify/run.py
new file mode 100644
index 0000000..f27ec80
--- /dev/null
+++ b/test/845-fast-verify/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(
+      args,
+      # Disable app image to make sure we compile dex files individually.
+      app_image=False,
+      # Pass a .dm file to run FastVerify and ask to compile dex files
+      # individually in order to run the problematic code.
+      Xcompiler_option=[f"--dm-file={ctx.env.DEX_LOCATION}/classes.dm", "--compile-individually"])
diff --git a/test/089-many-methods/expected-stderr.txt b/test/846-multidex-data-image/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/846-multidex-data-image/expected-stderr.txt
diff --git a/test/846-multidex-data-image/expected-stdout.txt b/test/846-multidex-data-image/expected-stdout.txt
new file mode 100644
index 0000000..8db7853
--- /dev/null
+++ b/test/846-multidex-data-image/expected-stdout.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+JNI_OnLoad called
diff --git a/test/846-multidex-data-image/info.txt b/test/846-multidex-data-image/info.txt
new file mode 100644
index 0000000..3b83d2c
--- /dev/null
+++ b/test/846-multidex-data-image/info.txt
@@ -0,0 +1,2 @@
+Test the generation of app image at runtime, when the primary APK contains
+multiple dex files.
diff --git a/test/846-multidex-data-image/run.py b/test/846-multidex-data-image/run.py
new file mode 100644
index 0000000..3976af2
--- /dev/null
+++ b/test/846-multidex-data-image/run.py
@@ -0,0 +1,23 @@
+# Copyright (C) 2022 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.
+
+import sys
+
+# We run the tests by disabling compilation with app image.
+# Run the test twice: one run for generating the image, a second run for using
+# the image.
+def run(ctx, args):
+  ctx.default_run(args, app_image=False)
+  # Pass another argument to let the test know it should now expect an image.
+  ctx.default_run(args, app_image=False, test_args=["--second-run"])
diff --git a/test/846-multidex-data-image/src-art/Main.java b/test/846-multidex-data-image/src-art/Main.java
new file mode 100644
index 0000000..aea92a4
--- /dev/null
+++ b/test/846-multidex-data-image/src-art/Main.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import dalvik.system.DexFile;
+import dalvik.system.VMRuntime;
+import java.io.File;
+import java.io.IOException;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
+
+    // Register the dex file so that the runtime can pick up which
+    // dex file to compile for the image.
+    File file = null;
+    try {
+      file = createTempFile();
+      String codePath = DEX_LOCATION + "/846-multidex-data-image.jar";
+      VMRuntime.registerAppInfo(
+          "test.app",
+          file.getPath(),
+          file.getPath(),
+          new String[] {codePath},
+          VMRuntime.CODE_PATH_TYPE_PRIMARY_APK);
+    } finally {
+      if (file != null) {
+        file.delete();
+      }
+    }
+
+    if (!hasOatFile() || !hasImage()) {
+      // We only generate an app image if there is at least a vdex file and a boot image.
+      return;
+    }
+
+    if (args.length == 2 && "--second-run".equals(args[1])) {
+      DexFile.OptimizationInfo info = VMRuntime.getBaseApkOptimizationInfo();
+      if (!info.isOptimized()) {
+        throw new Error("Expected image to be loaded");
+      }
+    }
+
+    VMRuntime runtime = VMRuntime.getRuntime();
+    runtime.notifyStartupCompleted();
+
+    String filter = getCompilerFilter(Main.class);
+    if ("speed-profile".equals(filter) || "speed".equals(filter)) {
+      // We only generate an app image for filters that don't compile.
+      return;
+    }
+
+    String instructionSet = VMRuntime.getCurrentInstructionSet();
+    // Wait for the file to be generated.
+    File image = new File(DEX_LOCATION + "/" + instructionSet + "/846-multidex-data-image.art");
+    while (!image.exists()) {
+      Thread.yield();
+    }
+
+    // Test that we can load a class from the other dex file. We do this after creating the image to
+    // check that the runtime can deal with a missing dex cache.
+    Class.forName("Foo");
+  }
+
+  private static native boolean hasOatFile();
+  private static native boolean hasImage();
+  private static native String getCompilerFilter(Class<?> cls);
+
+  private static final String TEMP_FILE_NAME_PREFIX = "temp";
+  private static final String TEMP_FILE_NAME_SUFFIX = "-file";
+  private static final String DEX_LOCATION = System.getenv("DEX_LOCATION");
+
+  private static File createTempFile() throws Exception {
+    try {
+      return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
+    } catch (IOException e) {
+      System.setProperty("java.io.tmpdir", "/data/local/tmp");
+      try {
+        return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
+      } catch (IOException e2) {
+        System.setProperty("java.io.tmpdir", "/sdcard");
+        return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
+      }
+    }
+  }
+}
diff --git a/test/846-multidex-data-image/src-multidex/Foo.java b/test/846-multidex-data-image/src-multidex/Foo.java
new file mode 100644
index 0000000..4e36e88
--- /dev/null
+++ b/test/846-multidex-data-image/src-multidex/Foo.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Foo {
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/847-filled-new-aray/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/847-filled-new-aray/expected-stderr.txt
diff --git a/test/089-many-methods/expected-stdout.txt b/test/847-filled-new-aray/expected-stdout.txt
similarity index 100%
copy from test/089-many-methods/expected-stdout.txt
copy to test/847-filled-new-aray/expected-stdout.txt
diff --git a/test/847-filled-new-aray/info.txt b/test/847-filled-new-aray/info.txt
new file mode 100644
index 0000000..6244ffb
--- /dev/null
+++ b/test/847-filled-new-aray/info.txt
@@ -0,0 +1,3 @@
+Regression test for the verifier which used to hard fail when using
+filled-new-array with an unresolved type. We now accept it, and a
+NoClassDefFoundError will be thrown at runtime.
diff --git a/test/847-filled-new-aray/smali/Main.smali b/test/847-filled-new-aray/smali/Main.smali
new file mode 100644
index 0000000..3149e3c
--- /dev/null
+++ b/test/847-filled-new-aray/smali/Main.smali
@@ -0,0 +1,42 @@
+# Copyright 2023 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 LMain;
+.super Ljava/lang/Object;
+
+.method public static main([Ljava/lang/String;)V
+.registers 1
+    :try_start
+    new-instance v0, LMissingClass;
+    invoke-direct {v0}, LMissingClass;-><init>()V
+    # The verifier used to fail on this instruction used to type v0 as a conflict,
+    # because LSuperMissingClass was unresolved. This lead to the `move-result-object`
+    # below to make the class hard fail.
+    filled-new-array {v0}, [LSuperMissingClass;
+    move-result-object v0
+    invoke-static {v0}, LMain;->doCall([LSuperMissingClass;)V
+    # Throw a NPE to signal we don't expect to enter here.
+    const/4 v0, 0
+    throw v0
+    :try_end
+    .catch Ljava/lang/NoClassDefFoundError; {:try_start .. :try_end} :catch_0
+    :catch_0
+    # NoClassDefFoundError expected
+    return-void
+.end method
+
+.method public static doCall([LSuperMissingClass;)V
+.registers 1
+    return-void
+.end method
diff --git a/test/900-hello-plugin/run b/test/900-hello-plugin/run
deleted file mode 100755
index a19a38c..0000000
--- a/test/900-hello-plugin/run
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-plugin=libartagentd.so
-if  [[ "$@" == *"-O"* ]]; then
-  plugin=libartagent.so
-fi
-
-# Adjust the agent path when running on device.
-if  [[ "$@" != *"--host"* ]]; then
-  if [[ -z "$ANDROID_BUILD_TOP" ]]; then
-    echo 'ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?'
-    exit 1
-  fi
-
-  bitness_flag=--32
-  if  [[ "$@" == *"--64"* ]]; then
-    bitness_flag=--64
-  fi
-
-  # Path to native libraries installed on the device for testing purposes.
-  test_native_lib_path=$("$ANDROID_BUILD_TOP/art/test/utils/get-device-test-native-lib-path" \
-    "$bitness_flag")
-
-  # The linker configuration used for dalvikvm(64) in the ART APEX requires us
-  # to pass the full path to the agent to the runtime when running on device.
-  plugin=${test_native_lib_path}/${plugin}
-fi
-
-./default-run "$@" --runtime-option -agentpath:${plugin}=test_900 \
-                   --runtime-option -agentpath:${plugin}=test_900_round_2 \
-                   --android-runtime-option -Xplugin:${plugin}
diff --git a/test/900-hello-plugin/run.py b/test/900-hello-plugin/run.py
new file mode 100644
index 0000000..607a0e5
--- /dev/null
+++ b/test/900-hello-plugin/run.py
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  plugin = "libartagent.so" if args.O else "libartagentd.so"
+
+  # Adjust the agent path when running on device.
+  if not args.host:
+    for i, opt in enumerate(args.runtime_option):
+      if opt.startswith("-Djava.library.path="):
+        libpath = opt.split("=")[-1]
+        assert libpath.startswith("/data/nativetest"), libpath
+
+        # The linker configuration used for dalvikvm(64) in the ART APEX requires us
+        # to pass the full path to the agent to the runtime when running on device.
+        plugin = f"{libpath}/{plugin}"
+        break
+
+  ctx.default_run(
+      args,
+      runtime_option=[
+          f"-agentpath:{plugin}=test_900",
+          f"-agentpath:{plugin}=test_900_round_2"
+      ],
+      android_runtime_option=[f"-Xplugin:{plugin}"])
diff --git a/test/901-hello-ti-agent/run b/test/901-hello-ti-agent/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/901-hello-ti-agent/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/901-hello-ti-agent/run.py b/test/901-hello-ti-agent/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/901-hello-ti-agent/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/902-hello-transformation/run b/test/902-hello-transformation/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/902-hello-transformation/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/902-hello-transformation/run.py b/test/902-hello-transformation/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/902-hello-transformation/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/903-hello-tagging/run b/test/903-hello-tagging/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/903-hello-tagging/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/903-hello-tagging/run.py b/test/903-hello-tagging/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/903-hello-tagging/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/904-object-allocation/run b/test/904-object-allocation/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/904-object-allocation/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/904-object-allocation/run.py b/test/904-object-allocation/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/904-object-allocation/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/905-object-free/expected-stdout.txt b/test/905-object-free/expected-stdout.txt
index dfcd7b6..50f11e0 100644
--- a/test/905-object-free/expected-stdout.txt
+++ b/test/905-object-free/expected-stdout.txt
@@ -10,4 +10,4 @@
 ---
 []
 ---
-Free counts 200000 200000
+Free counts as expected
diff --git a/test/905-object-free/run b/test/905-object-free/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/905-object-free/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/905-object-free/run.py b/test/905-object-free/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/905-object-free/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/905-object-free/src/art/Test905.java b/test/905-object-free/src/art/Test905.java
index 367da99..efd83c6 100644
--- a/test/905-object-free/src/art/Test905.java
+++ b/test/905-object-free/src/art/Test905.java
@@ -23,6 +23,8 @@
 import java.util.function.BiConsumer;
 
 public class Test905 {
+  private static final boolean DALVIK_RUN = "Dalvik".equals(System.getProperty("java.vm.name"));
+
   // Taken from jdwp tests.
   public static class MarkerObj {
     public static int cnt = 0;
@@ -81,7 +83,13 @@
     run(l);
 
     enableFreeTracking(true);
-    stress();
+    if (DALVIK_RUN) {
+      stress(400000);
+    } else {
+      // For JVM the JVMTI tag handling is not running as expected for the stress test
+      // (b/252990223).
+      stress(10000);
+    }
   }
 
   private static void run(ArrayList<Object> l) {
@@ -111,6 +119,8 @@
     System.out.println("---");
   }
 
+  private static int errors = 0;
+
   private static void stressAllocate(int i, BiConsumer<Integer, Object> saver) {
     Object obj = new Object();
     Main.setTag(obj, i);
@@ -118,10 +128,10 @@
     saver.accept(i, obj);
   }
 
-  private static void stress() {
+  private static void stress(int allocations) {
     getCollectedTags(0);
     getCollectedTags(1);
-    final int num_obj = 400000;
+    final int num_obj = allocations;
     final Object[] saved = new Object[num_obj/2];
     // Allocate objects, Save every other one. We want to be sure that it's only the deleted objects
     // that get their tags cleared and non-deleted objects correctly keep track of their tags.
@@ -139,10 +149,15 @@
     Arrays.sort(freedTags1);
     Arrays.sort(freedTags2);
     // Make sure we freed all the ones we expect to and both envs agree on this.
-    System.out.println("Free counts " + freedTags1.length + " " + freedTags2.length);
+    if (freedTags1.length == num_obj / 2 && freedTags2.length == num_obj / 2) {
+      System.out.println("Free counts as expected");
+    } else {
+      System.out.println("Free counts " + freedTags1.length + " " + freedTags2.length);
+    }
     for (int i = 0; i < freedTags1.length; ++i) {
       if (freedTags1[i] + 1 != freedTags2[i]) {
         System.out.println("Mismatched tags " + (freedTags1[i] + 1) + " " + freedTags2[i]);
+        break;
       }
     }
     // Make sure the saved-tags aren't present.
diff --git a/test/906-iterate-heap/iterate_heap.cc b/test/906-iterate-heap/iterate_heap.cc
index 521f9a6..f0a6624 100644
--- a/test/906-iterate-heap/iterate_heap.cc
+++ b/test/906-iterate-heap/iterate_heap.cc
@@ -198,7 +198,7 @@
                                             void* user_data) {
       FindStringCallbacks* p = reinterpret_cast<FindStringCallbacks*>(user_data);
       if (*tag_ptr == p->tag_to_find) {
-        size_t utf_byte_count = ti::CountUtf8Bytes(value, value_length);
+        size_t utf_byte_count = ti::CountModifiedUtf8BytesInUtf16(value, value_length);
         std::unique_ptr<char[]> mod_utf(new char[utf_byte_count + 1]);
         memset(mod_utf.get(), 0, utf_byte_count + 1);
         ti::ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length);
diff --git a/test/906-iterate-heap/run b/test/906-iterate-heap/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/906-iterate-heap/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/906-iterate-heap/run.py b/test/906-iterate-heap/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/906-iterate-heap/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/907-get-loaded-classes/run b/test/907-get-loaded-classes/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/907-get-loaded-classes/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/907-get-loaded-classes/run.py b/test/907-get-loaded-classes/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/907-get-loaded-classes/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/908-gc-start-finish/run b/test/908-gc-start-finish/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/908-gc-start-finish/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/908-gc-start-finish/run.py b/test/908-gc-start-finish/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/908-gc-start-finish/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/909-attach-agent/expected-stdout.interpreter.txt b/test/909-attach-agent/expected-stdout.interpreter.txt
new file mode 100644
index 0000000..fef7cc9
--- /dev/null
+++ b/test/909-attach-agent/expected-stdout.interpreter.txt
@@ -0,0 +1,26 @@
+JNI_OnLoad called
+Hello, world!
+Attached Agent for test 909-attach-agent
+Created env for kArtTiVersion
+Attached Agent for test 909-attach-agent
+Created env for kArtTiVersion
+Goodbye!
+JNI_OnLoad called
+Hello, world!
+Attached Agent for test 909-attach-agent
+Created env for kArtTiVersion
+Attached Agent for test 909-attach-agent
+Created env for kArtTiVersion
+Goodbye!
+JNI_OnLoad called
+Hello, world!
+Attached Agent for test 909-attach-agent
+Created env for kArtTiVersion
+Attached Agent for test 909-attach-agent
+Created env for kArtTiVersion
+Goodbye!
+JNI_OnLoad called
+Hello, world!
+Can't attach agent, process is not debuggable.
+Can't attach agent, process is not debuggable.
+Goodbye!
diff --git a/test/909-attach-agent/interpreter-expected.patch b/test/909-attach-agent/interpreter-expected.patch
deleted file mode 100644
index 5035c6a..0000000
--- a/test/909-attach-agent/interpreter-expected.patch
+++ /dev/null
@@ -1,4 +0,0 @@
-19d18
-< version 0x30010000 is not valid!Unable to create env for JVMTI_VERSION_1_0
-22d20
-< version 0x30010000 is not valid!Unable to create env for JVMTI_VERSION_1_0
diff --git a/test/909-attach-agent/run b/test/909-attach-agent/run
deleted file mode 100755
index 71b1e1c..0000000
--- a/test/909-attach-agent/run
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-fi
-
-if [[ "$@" == *"--interpreter"* ]]; then
-  # On interpreter we are fully capable of providing the full jvmti api so we
-  # have a slightly different expected output.
-  # TODO We should really be changing this in the 'check' script.
-  patch -s expected-stdout.txt <interpreter-expected.patch
-fi
-
-# Provide additional runtime options when running on device.
-extra_runtime_options=
-if  [[ "$@" != *"--host"* ]]; then
-  if [[ -z "$ANDROID_BUILD_TOP" ]]; then
-    echo 'ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?'
-    exit 1
-  fi
-
-  bitness_flag=--32
-  if  [[ "$@" == *"--64"* ]]; then
-    bitness_flag=--64
-  fi
-
-  # Path to native libraries installed on the device for testing purposes.
-  test_native_lib_path=$("$ANDROID_BUILD_TOP/art/test/utils/get-device-test-native-lib-path" \
-    "$bitness_flag")
-
-  # The linker configuration used for dalvikvm(64) in the ART APEX requires us
-  # to pass the full path to the agent to the runtime when running on device.
-  agent=${test_native_lib_path}/${agent}
-
-  # The above agent path is an absolute one; append the root directory to the
-  # library path so that the agent can be found via the `java.library.path`
-  # system property (see method `Main.find` in
-  # test/909-attach-agent/src-art/Main.java).
-  extra_runtime_options="--runtime-option -Djava.library.path=${test_native_lib_path}:/"
-fi
-
-export ANDROID_LOG_TAGS='*:f'
-./default-run "$@" --android-runtime-option -Xplugin:${plugin} \
-                   --android-runtime-option -Xcompiler-option \
-                   --android-runtime-option --debuggable \
-                   $extra_runtime_options \
-                   --args agent:${agent}=909-attach-agent
-return_status1=$?
-
-./default-run "$@" --android-runtime-option -Xcompiler-option \
-                   --android-runtime-option --debuggable \
-                   $extra_runtime_options \
-                   --args agent:${agent}=909-attach-agent
-return_status2=$?
-
-./default-run "$@" $extra_runtime_options \
-                   --args agent:${agent}=909-attach-agent \
-                   --external-log-tags
-return_status3=$?
-
-./default-run "$@" $extra_runtime_options \
-                   --args agent:${agent}=909-attach-agent \
-                   --args disallow-debugging \
-                   --external-log-tags
-return_status4=$?
-
-# Make sure we don't silently ignore an early failure.
-(exit $return_status1) && \
-  (exit $return_status2) && \
-  (exit $return_status3) && \
-  (exit $return_status4)
diff --git a/test/909-attach-agent/run.py b/test/909-attach-agent/run.py
new file mode 100644
index 0000000..b5e2337
--- /dev/null
+++ b/test/909-attach-agent/run.py
@@ -0,0 +1,62 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  agent = "libtiagent.so" if args.O else "libtiagentd.so"
+  plugin = "libopenjdkjvmti.so" if args.O else "libopenjdkjvmtid.so"
+
+  if args.interpreter:
+    # On interpreter we are fully capable of providing the full jvmti api so we
+    # have a slightly different expected output.
+    ctx.expected_stdout = ctx.expected_stdout.with_suffix(".interpreter.txt")
+
+  # Provide additional runtime options when running on device.
+  if not args.host:
+    for i, opt in enumerate(args.runtime_option):
+      if opt.startswith("-Djava.library.path="):
+        libpath = opt.split("=")[-1]
+        assert libpath.startswith("/data/nativetest"), libpath
+
+        # The linker configuration used for dalvikvm(64) in the ART APEX requires us
+        # to pass the full path to the agent to the runtime when running on device.
+        agent = f"{libpath}/{agent}"
+
+        # The above agent path is an absolute one; append the root directory to the
+        # library path so that the agent can be found via the `java.library.path`
+        # system property (see method `Main.find` in
+        # test/909-attach-agent/src-art/Main.java).
+        args.runtime_option[i] += ":/"
+        break
+
+  ctx.default_run(
+      args,
+      android_runtime_option=[
+          f"-Xplugin:{plugin}", "-Xcompiler-option", "--debuggable"
+      ],
+      test_args=[f"agent:{agent}=909-attach-agent"])
+
+  ctx.default_run(args, test_args=[f"agent:{agent}=909-attach-agent"])
+
+  ctx.default_run(
+      args,
+      test_args=[f"agent:{agent}=909-attach-agent"],
+      android_log_tags="*:f")
+
+  ctx.default_run(
+      args,
+      test_args=[f"agent:{agent}=909-attach-agent", "disallow-debugging"],
+      android_log_tags="*:f")
diff --git a/test/910-methods/run b/test/910-methods/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/910-methods/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/910-methods/run.py b/test/910-methods/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/910-methods/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/911-get-stack-trace/expected-cts-version.txt b/test/911-get-stack-trace/expected-cts-version.txt
index 728ea88..25f29aa 100644
--- a/test/911-get-stack-trace/expected-cts-version.txt
+++ b/test/911-get-stack-trace/expected-cts-version.txt
@@ -79,8 +79,8 @@
 From top
 ---------
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -99,8 +99,8 @@
  foo (IIILart/ControlData;)I 0 21
  run ()V 4 28
 ---------
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -120,14 +120,14 @@
  run ()V 4 28
 ---------
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
  foo (IIILart/ControlData;)I 0 21
 ---------
- wait ()V 2 568
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -135,7 +135,7 @@
  baz (IIILart/ControlData;)Ljava/lang/Object; 8 34
  bar (IIILart/ControlData;)J 0 26
 ---------
- wait ()V 2 568
+ wait ()V 2 524
 From bottom
 ---------
  run ()V 4 28
@@ -251,8 +251,8 @@
 ---------
 ThreadListTraces Thread 0
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -261,8 +261,8 @@
 ---------
 ThreadListTraces Thread 2
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -271,8 +271,8 @@
 ---------
 ThreadListTraces Thread 4
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -281,8 +281,8 @@
 ---------
 ThreadListTraces Thread 6
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -291,8 +291,8 @@
 ---------
 ThreadListTraces Thread 8
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -308,8 +308,8 @@
 ---------
 ThreadListTraces Thread 0
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -331,8 +331,8 @@
 ---------
 ThreadListTraces Thread 2
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -354,8 +354,8 @@
 ---------
 ThreadListTraces Thread 4
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -377,8 +377,8 @@
 ---------
 ThreadListTraces Thread 6
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -400,8 +400,8 @@
 ---------
 ThreadListTraces Thread 8
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
diff --git a/test/911-get-stack-trace/expected-stdout.txt b/test/911-get-stack-trace/expected-stdout.txt
index 19735d2..1109b3f 100644
--- a/test/911-get-stack-trace/expected-stdout.txt
+++ b/test/911-get-stack-trace/expected-stdout.txt
@@ -79,8 +79,8 @@
 From top
 ---------
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -99,8 +99,8 @@
  foo (IIILart/ControlData;)I 0 21
  run ()V 4 28
 ---------
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -120,14 +120,14 @@
  run ()V 4 28
 ---------
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
  foo (IIILart/ControlData;)I 0 21
 ---------
- wait ()V 2 568
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -135,7 +135,7 @@
  baz (IIILart/ControlData;)Ljava/lang/Object; 8 34
  bar (IIILart/ControlData;)J 0 26
 ---------
- wait ()V 2 568
+ wait ()V 2 524
 From bottom
 ---------
  run ()V 4 28
@@ -276,8 +276,8 @@
 ---------
 AllTraces Thread 0
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -286,8 +286,8 @@
 ---------
 AllTraces Thread 1
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -296,8 +296,8 @@
 ---------
 AllTraces Thread 2
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -306,8 +306,8 @@
 ---------
 AllTraces Thread 3
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -316,8 +316,8 @@
 ---------
 AllTraces Thread 4
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -326,8 +326,8 @@
 ---------
 AllTraces Thread 5
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -336,8 +336,8 @@
 ---------
 AllTraces Thread 6
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -346,8 +346,8 @@
 ---------
 AllTraces Thread 7
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -356,8 +356,8 @@
 ---------
 AllTraces Thread 8
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -366,8 +366,8 @@
 ---------
 AllTraces Thread 9
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -401,8 +401,8 @@
 ---------
 AllTraces Thread 0
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -424,8 +424,8 @@
 ---------
 AllTraces Thread 1
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -447,8 +447,8 @@
 ---------
 AllTraces Thread 2
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -470,8 +470,8 @@
 ---------
 AllTraces Thread 3
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -493,8 +493,8 @@
 ---------
 AllTraces Thread 4
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -516,8 +516,8 @@
 ---------
 AllTraces Thread 5
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -539,8 +539,8 @@
 ---------
 AllTraces Thread 6
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -562,8 +562,8 @@
 ---------
 AllTraces Thread 7
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -585,8 +585,8 @@
 ---------
 AllTraces Thread 8
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -608,8 +608,8 @@
 ---------
 AllTraces Thread 9
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -685,8 +685,8 @@
 ---------
 ThreadListTraces Thread 0
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -695,8 +695,8 @@
 ---------
 ThreadListTraces Thread 2
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -705,8 +705,8 @@
 ---------
 ThreadListTraces Thread 4
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -715,8 +715,8 @@
 ---------
 ThreadListTraces Thread 6
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -725,8 +725,8 @@
 ---------
 ThreadListTraces Thread 8
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -742,8 +742,8 @@
 ---------
 ThreadListTraces Thread 0
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -765,8 +765,8 @@
 ---------
 ThreadListTraces Thread 2
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -788,8 +788,8 @@
 ---------
 ThreadListTraces Thread 4
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -811,8 +811,8 @@
 ---------
 ThreadListTraces Thread 6
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
@@ -834,8 +834,8 @@
 ---------
 ThreadListTraces Thread 8
  wait (JI)V -1 -2
- wait (J)V 1 442
- wait ()V 2 568
+ wait (J)V 1 386
+ wait ()V 2 524
  printOrWait (IILart/ControlData;)V 24 47
  baz (IIILart/ControlData;)Ljava/lang/Object; 2 32
  bar (IIILart/ControlData;)J 0 26
diff --git a/test/911-get-stack-trace/run b/test/911-get-stack-trace/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/911-get-stack-trace/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/911-get-stack-trace/run.py b/test/911-get-stack-trace/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/911-get-stack-trace/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/912-classes/expected-stdout.txt b/test/912-classes/expected-stdout.txt
index 5da9f8c..787efc5 100644
--- a/test/912-classes/expected-stdout.txt
+++ b/test/912-classes/expected-stdout.txt
@@ -23,7 +23,7 @@
 [public static final int java.lang.Integer.BYTES, static final byte[] java.lang.Integer.DigitOnes, static final byte[] java.lang.Integer.DigitTens, public static final int java.lang.Integer.MAX_VALUE, public static final int java.lang.Integer.MIN_VALUE, public static final int java.lang.Integer.SIZE, private static final java.lang.String[] java.lang.Integer.SMALL_NEG_VALUES, private static final java.lang.String[] java.lang.Integer.SMALL_NONNEG_VALUES, public static final java.lang.Class java.lang.Integer.TYPE, static final char[] java.lang.Integer.digits, private static final long java.lang.Integer.serialVersionUID, static final int[] java.lang.Integer.sizeTable, private final int java.lang.Integer.value]
 []
 []
-[java.lang.Integer(), public java.lang.Integer(int), public java.lang.Integer(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.bitCount(int), public static int java.lang.Integer.compare(int,int), public static int java.lang.Integer.compareUnsigned(int,int), public static java.lang.Integer java.lang.Integer.decode(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.divideUnsigned(int,int), static void java.lang.Integer.formatUnsignedInt(int,int,byte[],int,int), static void java.lang.Integer.formatUnsignedInt(int,int,char[],int,int), static int java.lang.Integer.getChars(int,int,byte[]), static int java.lang.Integer.getChars(int,int,char[]), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String,int), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String,java.lang.Integer), public static int java.lang.Integer.hashCode(int), public static int java.lang.Integer.highestOneBit(int), public static int java.lang.Integer.lowestOneBit(int), public static int java.lang.Integer.max(int,int), public static int java.lang.Integer.min(int,int), public static int java.lang.Integer.numberOfLeadingZeros(int), public static int java.lang.Integer.numberOfTrailingZeros(int), public static int java.lang.Integer.parseInt(java.lang.CharSequence,int,int,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseInt(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseInt(java.lang.String,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.CharSequence,int,int,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.String,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.remainderUnsigned(int,int), public static int java.lang.Integer.reverse(int), public static int java.lang.Integer.reverseBytes(int), public static int java.lang.Integer.rotateLeft(int,int), public static int java.lang.Integer.rotateRight(int,int), public static int java.lang.Integer.signum(int), static int java.lang.Integer.stringSize(int), public static int java.lang.Integer.sum(int,int), public static java.lang.String java.lang.Integer.toBinaryString(int), public static java.lang.String java.lang.Integer.toHexString(int), public static java.lang.String java.lang.Integer.toOctalString(int), public static java.lang.String java.lang.Integer.toString(int), public static java.lang.String java.lang.Integer.toString(int,int), public static long java.lang.Integer.toUnsignedLong(int), public static java.lang.String java.lang.Integer.toUnsignedString(int), public static java.lang.String java.lang.Integer.toUnsignedString(int,int), private static java.lang.String java.lang.Integer.toUnsignedString0(int,int), public static java.lang.Integer java.lang.Integer.valueOf(int), public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String) throws java.lang.NumberFormatException, public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String,int) throws java.lang.NumberFormatException, public byte java.lang.Integer.byteValue(), public int java.lang.Integer.compareTo(java.lang.Integer), public int java.lang.Integer.compareTo(java.lang.Object), public double java.lang.Integer.doubleValue(), public boolean java.lang.Integer.equals(java.lang.Object), public float java.lang.Integer.floatValue(), public int java.lang.Integer.hashCode(), public int java.lang.Integer.intValue(), public long java.lang.Integer.longValue(), public short java.lang.Integer.shortValue(), public java.lang.String java.lang.Integer.toString()]
+[java.lang.Integer(), public java.lang.Integer(int), public java.lang.Integer(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.bitCount(int), public static int java.lang.Integer.compare(int,int), public static int java.lang.Integer.compareUnsigned(int,int), public static java.lang.Integer java.lang.Integer.decode(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.divideUnsigned(int,int), private static void java.lang.Integer.formatUnsignedInt(int,int,byte[],int), static void java.lang.Integer.formatUnsignedInt(int,int,byte[],int,int), static void java.lang.Integer.formatUnsignedInt(int,int,char[],int,int), static int java.lang.Integer.getChars(int,int,byte[]), static int java.lang.Integer.getChars(int,int,char[]), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String,int), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String,java.lang.Integer), public static int java.lang.Integer.hashCode(int), public static int java.lang.Integer.highestOneBit(int), public static int java.lang.Integer.lowestOneBit(int), public static int java.lang.Integer.max(int,int), public static int java.lang.Integer.min(int,int), public static int java.lang.Integer.numberOfLeadingZeros(int), public static int java.lang.Integer.numberOfTrailingZeros(int), public static int java.lang.Integer.parseInt(java.lang.CharSequence,int,int,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseInt(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseInt(java.lang.String,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.CharSequence,int,int,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.String,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.remainderUnsigned(int,int), public static int java.lang.Integer.reverse(int), public static int java.lang.Integer.reverseBytes(int), public static int java.lang.Integer.rotateLeft(int,int), public static int java.lang.Integer.rotateRight(int,int), public static int java.lang.Integer.signum(int), static int java.lang.Integer.stringSize(int), public static int java.lang.Integer.sum(int,int), public static java.lang.String java.lang.Integer.toBinaryString(int), public static java.lang.String java.lang.Integer.toHexString(int), public static java.lang.String java.lang.Integer.toOctalString(int), public static java.lang.String java.lang.Integer.toString(int), public static java.lang.String java.lang.Integer.toString(int,int), public static long java.lang.Integer.toUnsignedLong(int), public static java.lang.String java.lang.Integer.toUnsignedString(int), public static java.lang.String java.lang.Integer.toUnsignedString(int,int), private static java.lang.String java.lang.Integer.toUnsignedString0(int,int), public static java.lang.Integer java.lang.Integer.valueOf(int), public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String) throws java.lang.NumberFormatException, public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String,int) throws java.lang.NumberFormatException, public byte java.lang.Integer.byteValue(), public int java.lang.Integer.compareTo(java.lang.Integer), public int java.lang.Integer.compareTo(java.lang.Object), public double java.lang.Integer.doubleValue(), public boolean java.lang.Integer.equals(java.lang.Object), public float java.lang.Integer.floatValue(), public int java.lang.Integer.hashCode(), public int java.lang.Integer.intValue(), public long java.lang.Integer.longValue(), public short java.lang.Integer.shortValue(), public java.lang.String java.lang.Integer.toString()]
 []
 []
 int 100000
diff --git a/test/912-classes/run b/test/912-classes/run
deleted file mode 100755
index f24db40..0000000
--- a/test/912-classes/run
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# This test checks which classes are initiated by a classloader. App images preload classes.
-# In certain configurations, the app images may be valid even in a new classloader. Turn off
-# app images to avoid the issue.
-
-./default-run "$@" --jvmti \
-                   --no-app-image
diff --git a/test/912-classes/run.py b/test/912-classes/run.py
new file mode 100644
index 0000000..2f3b9fa
--- /dev/null
+++ b/test/912-classes/run.py
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  # This test checks which classes are initiated by a classloader. App images preload classes.
+  # In certain configurations, the app images may be valid even in a new classloader. Turn off
+  # app images to avoid the issue.
+
+  ctx.default_run(args, jvmti=True, app_image=False)
diff --git a/test/913-heaps/expected-stdout.txt b/test/913-heaps/expected-stdout.txt
index 9684503..e7a372a 100644
--- a/test/913-heaps/expected-stdout.txt
+++ b/test/913-heaps/expected-stdout.txt
@@ -1,6 +1,5 @@
 ---
 true true
-root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=8,location= 31])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=120, length=-1]
 root@root --(thread)--> 3000@0 [size=120, length=-1]
@@ -44,7 +43,6 @@
 ---
 root@root --(jni-global)--> 1@1000 [size=16, length=-1]
 root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1]
-root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=10,location= 8])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=13,location= 20])--> 1@1000 [size=16, length=-1]
@@ -97,7 +95,6 @@
 ---
 3@1001 --(class)--> 1001@0 [size=123456780016, length=-1]
 ---
-root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=8,location= 31])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=120, length=-1]
 root@root --(thread)--> 3000@0 [size=120, length=-1]
@@ -109,7 +106,6 @@
 ---
 root@root --(jni-global)--> 1@1000 [size=16, length=-1]
 root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1]
-root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=10,location= 8])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=13,location= 20])--> 1@1000 [size=16, length=-1]
@@ -195,9 +191,7 @@
 ---
 ---
 ---- untagged objects
-root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=8,location= 31])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=4,method=runFollowReferences,vreg=3,location= 164])--> 1000@0 [size=123456780050, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=120, length=-1]
 root@root --(thread)--> 3000@0 [size=120, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123456780050, length=-1]
@@ -240,12 +234,10 @@
 ---
 root@root --(jni-global)--> 1@1000 [size=16, length=-1]
 root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1]
-root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=10,location= 8])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=13,location= 20])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 20])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=4,method=runFollowReferences,vreg=3,location= 164])--> 1000@0 [size=123456780055, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=120, length=-1]
 root@root --(thread)--> 3000@0 [size=120, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123456780055, length=-1]
@@ -287,8 +279,6 @@
 6@1000 --(class)--> 1000@0 [size=123456780055, length=-1]
 ---
 ---- tagged classes
-root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=4,method=runFollowReferences,vreg=3,location= 181])--> 1000@0 [size=123456780060, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=120, length=-1]
 root@root --(thread)--> 3000@0 [size=120, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123456780060, length=-1]
@@ -315,8 +305,6 @@
 5@1002 --(field@8)--> 500@0 [size=20, length=2]
 6@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
 ---
-root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=4,method=runFollowReferences,vreg=3,location= 181])--> 1000@0 [size=123456780065, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=120, length=-1]
 root@root --(thread)--> 3000@0 [size=120, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123456780065, length=-1]
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index 28a737d..671cff8 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -191,6 +191,12 @@
         return 0;
       }
 
+      // Ignore system classes, which may come from the JIT compiling a method
+      // in these classes.
+      if (reference_kind == JVMTI_HEAP_REFERENCE_SYSTEM_CLASS) {
+        return 0;
+      }
+
       // Only check tagged objects.
       if (tag == 0) {
         return JVMTI_VISIT_OBJECTS;
@@ -260,6 +266,7 @@
 
     std::vector<std::string> GetLines() const {
       std::vector<std::string> ret;
+      ret.reserve(lines_.size());
       for (const std::unique_ptr<Elem>& e : lines_) {
         ret.push_back(e->Print());
       }
@@ -586,7 +593,7 @@
                                             void* user_data) {
       FindStringCallbacks* p = reinterpret_cast<FindStringCallbacks*>(user_data);
       if (*tag_ptr != 0) {
-        size_t utf_byte_count = ti::CountUtf8Bytes(value, value_length);
+        size_t utf_byte_count = ti::CountModifiedUtf8BytesInUtf16(value, value_length);
         std::unique_ptr<char[]> mod_utf(new char[utf_byte_count + 1]);
         memset(mod_utf.get(), 0, utf_byte_count + 1);
         ti::ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length);
diff --git a/test/913-heaps/run b/test/913-heaps/run
deleted file mode 100755
index dd35526..0000000
--- a/test/913-heaps/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti -Xcompiler-option -g
diff --git a/test/913-heaps/run.py b/test/913-heaps/run.py
new file mode 100644
index 0000000..d5731ca
--- /dev/null
+++ b/test/913-heaps/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, Xcompiler_option=["-g"])
diff --git a/test/913-heaps/src/art/Test913.java b/test/913-heaps/src/art/Test913.java
index 4fffa88..8bd7cf4 100644
--- a/test/913-heaps/src/art/Test913.java
+++ b/test/913-heaps/src/art/Test913.java
@@ -317,7 +317,8 @@
       BufferedReader reader = new BufferedReader(new FileReader("/proc/" + pid + "/maps"));
       String line;
       while ((line = reader.readLine()) != null) {
-        if (line.endsWith(".art")) {
+        // On host the mappings end with .art and on device they end with .art]
+        if (line.endsWith(".art]") || line.endsWith(".art")) {
           reader.close();
           return true;
         }
diff --git a/test/914-hello-obsolescence/run b/test/914-hello-obsolescence/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/914-hello-obsolescence/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/914-hello-obsolescence/run.py b/test/914-hello-obsolescence/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/914-hello-obsolescence/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/915-obsolete-2/run b/test/915-obsolete-2/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/915-obsolete-2/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/915-obsolete-2/run.py b/test/915-obsolete-2/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/915-obsolete-2/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/915-obsolete-2/src/Main.java b/test/915-obsolete-2/src/Main.java
index be51234..58c4ab9 100644
--- a/test/915-obsolete-2/src/Main.java
+++ b/test/915-obsolete-2/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test915.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test915.run();
+    }
 }
diff --git a/test/915-obsolete-2/src/art/Test915.java b/test/915-obsolete-2/src/art/Test915.java
index 63c7f34..2cb3c8f 100644
--- a/test/915-obsolete-2/src/art/Test915.java
+++ b/test/915-obsolete-2/src/art/Test915.java
@@ -19,105 +19,104 @@
 import java.util.Base64;
 
 public class Test915 {
+    static class Transform {
+        private void Start() {
+            System.out.println("hello - private");
+        }
 
-  static class Transform {
-    private void Start() {
-      System.out.println("hello - private");
+        private void Finish() {
+            System.out.println("goodbye - private");
+        }
+
+        public void sayHi(Runnable r) {
+            System.out.println("Pre Start private method call");
+            Start();
+            System.out.println("Post Start private method call");
+            r.run();
+            System.out.println("Pre Finish private method call");
+            Finish();
+            System.out.println("Post Finish private method call");
+        }
     }
 
-    private void Finish() {
-      System.out.println("goodbye - private");
+    // static class Transform {
+    //   private void Start() {
+    //     System.out.println("Hello - private - Transformed");
+    //   }
+    //
+    //   private void Finish() {
+    //     System.out.println("Goodbye - private - Transformed");
+    //   }
+    //
+    //   public void sayHi(Runnable r) {
+    //     System.out.println("Pre Start private method call - Transformed");
+    //     Start();
+    //     System.out.println("Post Start private method call - Transformed");
+    //     r.run();
+    //     System.out.println("Pre Finish private method call - Transformed");
+    //     Finish();
+    //     System.out.println("Post Finish private method call - Transformed");
+    //   }
+    // }
+    private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+            "yv66vgAAADQANgoADgAZCQAaABsIABwKAB0AHggAHwgAIAoADQAhCAAiCwAjACQIACUKAA0AJggA" +
+            "JwcAKQcALAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVTdGFydAEA" +
+            "BkZpbmlzaAEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7KVYBAApTb3VyY2VGaWxlAQAM" +
+            "VGVzdDkxNS5qYXZhDAAPABAHAC0MAC4ALwEAHUhlbGxvIC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVk" +
+            "BwAwDAAxADIBAB9Hb29kYnllIC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVkAQArUHJlIFN0YXJ0IHBy" +
+            "aXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAwAEwAQAQAsUG9zdCBTdGFydCBwcml2YXRl" +
+            "IG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQHADMMADQAEAEALFByZSBGaW5pc2ggcHJpdmF0ZSBt" +
+            "ZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAAUABABAC1Qb3N0IEZpbmlzaCBwcml2YXRlIG1ldGhv" +
+            "ZCBjYWxsIC0gVHJhbnNmb3JtZWQHADUBABVhcnQvVGVzdDkxNSRUcmFuc2Zvcm0BAAlUcmFuc2Zv" +
+            "cm0BAAxJbm5lckNsYXNzZXMBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEA" +
+            "A291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmlu" +
+            "dGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuAQAL" +
+            "YXJ0L1Rlc3Q5MTUAIAANAA4AAAAAAAQAAAAPABAAAQARAAAAHQABAAEAAAAFKrcAAbEAAAABABIA" +
+            "AAAGAAEAAAAFAAIAEwAQAAEAEQAAACUAAgABAAAACbIAAhIDtgAEsQAAAAEAEgAAAAoAAgAAAAcA" +
+            "CAAIAAIAFAAQAAEAEQAAACUAAgABAAAACbIAAhIFtgAEsQAAAAEAEgAAAAoAAgAAAAoACAALAAEA" +
+            "FQAWAAEAEQAAAGMAAgACAAAAL7IAAhIGtgAEKrcAB7IAAhIItgAEK7kACQEAsgACEgq2AAQqtwAL" +
+            "sgACEgy2AASxAAAAAQASAAAAIgAIAAAADQAIAA4ADAAPABQAEAAaABEAIgASACYAEwAuABQAAgAX" +
+            "AAAAAgAYACsAAAAKAAEADQAoACoACA==");
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQAQ+GYcAAAAAAAAAAAAAAAAAAAAAAAAAADUBQAAcAAAAHhWNBIAAAAAAAAAABAFAAAd" +
+            "AAAAcAAAAAoAAADkAAAAAwAAAAwBAAABAAAAMAEAAAcAAAA4AQAAAQAAAHABAABEBAAAkAEAAJAB" +
+            "AACYAQAAoAEAAMEBAADgAQAA+QEAAAgCAAAsAgAATAIAAGMCAAB3AgAAjQIAAKECAAC1AgAA5AIA" +
+            "ABIDAABAAwAAbQMAAHQDAACCAwAAjQMAAJADAACUAwAAoQMAAKcDAACsAwAAtQMAALoDAADBAwAA" +
+            "BAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAAMAAAAFAAAABQAAAAJAAAAAAAAABUAAAAJ" +
+            "AAAA0AMAABUAAAAJAAAAyAMAAAgABAAYAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAARAAAAAAABABsA" +
+            "AAAEAAIAGQAAAAUAAAAAAAAABgAAABoAAAAAAAAAAAAAAAUAAAAAAAAAEgAAAAAFAADMBAAAAAAA" +
+            "AAY8aW5pdD4ABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBUcmFuc2Zvcm1lZAAdSGVsbG8g" +
+            "LSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAF0xhcnQvVGVzdDkxNSRUcmFuc2Zvcm07AA1MYXJ0L1Rl" +
+            "c3Q5MTU7ACJMZGFsdmlrL2Fubm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90" +
+            "YXRpb24vSW5uZXJDbGFzczsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmpl" +
+            "Y3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5n" +
+            "L1N5c3RlbTsALVBvc3QgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAs" +
+            "UG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQALFByZSBGaW5pc2gg" +
+            "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkACtQcmUgU3RhcnQgcHJpdmF0ZSBtZXRo" +
+            "b2QgY2FsbCAtIFRyYW5zZm9ybWVkAAVTdGFydAAMVGVzdDkxNS5qYXZhAAlUcmFuc2Zvcm0AAVYA" +
+            "AlZMAAthY2Nlc3NGbGFncwAEbmFtZQADb3V0AAdwcmludGxuAANydW4ABXNheUhpAAV2YWx1ZQAB" +
+            "AAAABwAAAAEAAAAGAAAABQAHDgAKAAcOAQgPAAcABw4BCA8ADQEABw4BCA8BAw8BCA8BAw8BCA8B" +
+            "Aw8BCA8AAQABAAEAAADYAwAABAAAAHAQBQAAAA4AAwABAAIAAADdAwAACQAAAGIAAAAbAQIAAABu" +
+            "IAQAEAAOAAAAAwABAAIAAADlAwAACQAAAGIAAAAbAQMAAABuIAQAEAAOAAAABAACAAIAAADtAwAA" +
+            "KgAAAGIAAAAbARAAAABuIAQAEABwEAIAAgBiAAAAGwEOAAAAbiAEABAAchAGAAMAYgAAABsBDwAA" +
+            "AG4gBAAQAHAQAQACAGIAAAAbAQ0AAABuIAQAEAAOAAAAAwEAgIAEiAgBAqAIAQLECAMB6AgAAAIC" +
+            "ARwYAQIDAhYECBcXEwACAAAA5AQAAOoEAAD0BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAA" +
+            "AAEAAAAdAAAAcAAAAAIAAAAKAAAA5AAAAAMAAAADAAAADAEAAAQAAAABAAAAMAEAAAUAAAAHAAAA" +
+            "OAEAAAYAAAABAAAAcAEAAAIgAAAdAAAAkAEAAAEQAAACAAAAyAMAAAMgAAAEAAAA2AMAAAEgAAAE" +
+            "AAAACAQAAAAgAAABAAAAzAQAAAQgAAACAAAA5AQAAAMQAAABAAAA9AQAAAYgAAABAAAAAAUAAAAQ" +
+            "AAABAAAAEAUAAA==");
+
+    public static void run() {
+        Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+        doTest(new Transform());
     }
 
-    public void sayHi(Runnable r) {
-      System.out.println("Pre Start private method call");
-      Start();
-      System.out.println("Post Start private method call");
-      r.run();
-      System.out.println("Pre Finish private method call");
-      Finish();
-      System.out.println("Post Finish private method call");
+    public static void doTest(Transform t) {
+        t.sayHi(() -> { System.out.println("Not doing anything here"); });
+        t.sayHi(() -> {
+            System.out.println("transforming calling function");
+            Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+        });
+        t.sayHi(() -> { System.out.println("Not doing anything here"); });
     }
-  }
-
-  // static class Transform {
-  //   private void Start() {
-  //     System.out.println("Hello - private - Transformed");
-  //   }
-  //
-  //   private void Finish() {
-  //     System.out.println("Goodbye - private - Transformed");
-  //   }
-  //
-  //   public void sayHi(Runnable r) {
-  //     System.out.println("Pre Start private method call - Transformed");
-  //     Start();
-  //     System.out.println("Post Start private method call - Transformed");
-  //     r.run();
-  //     System.out.println("Pre Finish private method call - Transformed");
-  //     Finish();
-  //     System.out.println("Post Finish private method call - Transformed");
-  //   }
-  // }
-  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
-    "yv66vgAAADQANgoADgAZCQAaABsIABwKAB0AHggAHwgAIAoADQAhCAAiCwAjACQIACUKAA0AJggA" +
-    "JwcAKQcALAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVTdGFydAEA" +
-    "BkZpbmlzaAEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7KVYBAApTb3VyY2VGaWxlAQAM" +
-    "VGVzdDkxNS5qYXZhDAAPABAHAC0MAC4ALwEAHUhlbGxvIC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVk" +
-    "BwAwDAAxADIBAB9Hb29kYnllIC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVkAQArUHJlIFN0YXJ0IHBy" +
-    "aXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAwAEwAQAQAsUG9zdCBTdGFydCBwcml2YXRl" +
-    "IG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQHADMMADQAEAEALFByZSBGaW5pc2ggcHJpdmF0ZSBt" +
-    "ZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAAUABABAC1Qb3N0IEZpbmlzaCBwcml2YXRlIG1ldGhv" +
-    "ZCBjYWxsIC0gVHJhbnNmb3JtZWQHADUBABVhcnQvVGVzdDkxNSRUcmFuc2Zvcm0BAAlUcmFuc2Zv" +
-    "cm0BAAxJbm5lckNsYXNzZXMBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEA" +
-    "A291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmlu" +
-    "dGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuAQAL" +
-    "YXJ0L1Rlc3Q5MTUAIAANAA4AAAAAAAQAAAAPABAAAQARAAAAHQABAAEAAAAFKrcAAbEAAAABABIA" +
-    "AAAGAAEAAAAFAAIAEwAQAAEAEQAAACUAAgABAAAACbIAAhIDtgAEsQAAAAEAEgAAAAoAAgAAAAcA" +
-    "CAAIAAIAFAAQAAEAEQAAACUAAgABAAAACbIAAhIFtgAEsQAAAAEAEgAAAAoAAgAAAAoACAALAAEA" +
-    "FQAWAAEAEQAAAGMAAgACAAAAL7IAAhIGtgAEKrcAB7IAAhIItgAEK7kACQEAsgACEgq2AAQqtwAL" +
-    "sgACEgy2AASxAAAAAQASAAAAIgAIAAAADQAIAA4ADAAPABQAEAAaABEAIgASACYAEwAuABQAAgAX" +
-    "AAAAAgAYACsAAAAKAAEADQAoACoACA==");
-  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
-    "ZGV4CjAzNQAQ+GYcAAAAAAAAAAAAAAAAAAAAAAAAAADUBQAAcAAAAHhWNBIAAAAAAAAAABAFAAAd" +
-    "AAAAcAAAAAoAAADkAAAAAwAAAAwBAAABAAAAMAEAAAcAAAA4AQAAAQAAAHABAABEBAAAkAEAAJAB" +
-    "AACYAQAAoAEAAMEBAADgAQAA+QEAAAgCAAAsAgAATAIAAGMCAAB3AgAAjQIAAKECAAC1AgAA5AIA" +
-    "ABIDAABAAwAAbQMAAHQDAACCAwAAjQMAAJADAACUAwAAoQMAAKcDAACsAwAAtQMAALoDAADBAwAA" +
-    "BAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAAMAAAAFAAAABQAAAAJAAAAAAAAABUAAAAJ" +
-    "AAAA0AMAABUAAAAJAAAAyAMAAAgABAAYAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAARAAAAAAABABsA" +
-    "AAAEAAIAGQAAAAUAAAAAAAAABgAAABoAAAAAAAAAAAAAAAUAAAAAAAAAEgAAAAAFAADMBAAAAAAA" +
-    "AAY8aW5pdD4ABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBUcmFuc2Zvcm1lZAAdSGVsbG8g" +
-    "LSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAF0xhcnQvVGVzdDkxNSRUcmFuc2Zvcm07AA1MYXJ0L1Rl" +
-    "c3Q5MTU7ACJMZGFsdmlrL2Fubm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90" +
-    "YXRpb24vSW5uZXJDbGFzczsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmpl" +
-    "Y3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5n" +
-    "L1N5c3RlbTsALVBvc3QgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAs" +
-    "UG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQALFByZSBGaW5pc2gg" +
-    "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkACtQcmUgU3RhcnQgcHJpdmF0ZSBtZXRo" +
-    "b2QgY2FsbCAtIFRyYW5zZm9ybWVkAAVTdGFydAAMVGVzdDkxNS5qYXZhAAlUcmFuc2Zvcm0AAVYA" +
-    "AlZMAAthY2Nlc3NGbGFncwAEbmFtZQADb3V0AAdwcmludGxuAANydW4ABXNheUhpAAV2YWx1ZQAB" +
-    "AAAABwAAAAEAAAAGAAAABQAHDgAKAAcOAQgPAAcABw4BCA8ADQEABw4BCA8BAw8BCA8BAw8BCA8B" +
-    "Aw8BCA8AAQABAAEAAADYAwAABAAAAHAQBQAAAA4AAwABAAIAAADdAwAACQAAAGIAAAAbAQIAAABu" +
-    "IAQAEAAOAAAAAwABAAIAAADlAwAACQAAAGIAAAAbAQMAAABuIAQAEAAOAAAABAACAAIAAADtAwAA" +
-    "KgAAAGIAAAAbARAAAABuIAQAEABwEAIAAgBiAAAAGwEOAAAAbiAEABAAchAGAAMAYgAAABsBDwAA" +
-    "AG4gBAAQAHAQAQACAGIAAAAbAQ0AAABuIAQAEAAOAAAAAwEAgIAEiAgBAqAIAQLECAMB6AgAAAIC" +
-    "ARwYAQIDAhYECBcXEwACAAAA5AQAAOoEAAD0BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAA" +
-    "AAEAAAAdAAAAcAAAAAIAAAAKAAAA5AAAAAMAAAADAAAADAEAAAQAAAABAAAAMAEAAAUAAAAHAAAA" +
-    "OAEAAAYAAAABAAAAcAEAAAIgAAAdAAAAkAEAAAEQAAACAAAAyAMAAAMgAAAEAAAA2AMAAAEgAAAE" +
-    "AAAACAQAAAAgAAABAAAAzAQAAAQgAAACAAAA5AQAAAMQAAABAAAA9AQAAAYgAAABAAAAAAUAAAAQ" +
-    "AAABAAAAEAUAAA==");
-
-  public static void run() {
-    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
-    doTest(new Transform());
-  }
-
-  public static void doTest(Transform t) {
-    t.sayHi(() -> { System.out.println("Not doing anything here"); });
-    t.sayHi(() -> {
-      System.out.println("transforming calling function");
-      Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
-    });
-    t.sayHi(() -> { System.out.println("Not doing anything here"); });
-  }
 }
diff --git a/test/916-obsolete-jit/run b/test/916-obsolete-jit/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/916-obsolete-jit/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/916-obsolete-jit/run.py b/test/916-obsolete-jit/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/916-obsolete-jit/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/916-obsolete-jit/src/Main.java b/test/916-obsolete-jit/src/Main.java
index d7b32ba..f893c8e 100644
--- a/test/916-obsolete-jit/src/Main.java
+++ b/test/916-obsolete-jit/src/Main.java
@@ -23,151 +23,151 @@
 
 public class Main {
 
-  // import java.util.function.Consumer;
-  //
-  // class Transform {
-  //   private void Start(Consumer<String> reporter) {
-  //     reporter.accept("Hello - private - Transformed");
-  //   }
-  //
-  //   private void Finish(Consumer<String> reporter) {
-  //     reporter.accept("Goodbye - private - Transformed");
-  //   }
-  //
-  //   public void sayHi(Runnable r, Consumer<String> reporter) {
-  //     reporter.accept("pre Start private method call - Transformed");
-  //     Start(reporter);
-  //     reporter.accept("post Start private method call - Transformed");
-  //     r.run();
-  //     reporter.accept("pre Finish private method call - Transformed");
-  //     Finish(reporter);
-  //     reporter.accept("post Finish private method call - Transformed");
-  //   }
-  // }
-  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
-    "yv66vgAAADQAMAoADQAcCAAdCwAeAB8IACAIACEKAAwAIggAIwsAJAAlCAAmCgAMACcIACgHACkH" +
-    "ACoBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFU3RhcnQBACAoTGph" +
-    "dmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjspVgEACVNpZ25hdHVyZQEANChMamF2YS91dGlsL2Z1" +
-    "bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xhbmcvU3RyaW5nOz47KVYBAAZGaW5pc2gBAAVzYXlIaQEA" +
-    "NChMamF2YS9sYW5nL1J1bm5hYmxlO0xqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7KVYBAEgo" +
-    "TGphdmEvbGFuZy9SdW5uYWJsZTtMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xh" +
-    "bmcvU3RyaW5nOz47KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAA4ADwEAHUhlbGxv" +
-    "IC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVkBwArDAAsAC0BAB9Hb29kYnllIC0gcHJpdmF0ZSAtIFRy" +
-    "YW5zZm9ybWVkAQArcHJlIFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAwA" +
-    "EgATAQAscG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQHAC4MAC8A" +
-    "DwEALHByZSBGaW5pc2ggcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAAWABMBAC1w" +
-    "b3N0IEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQBAAlUcmFuc2Zvcm0B" +
-    "ABBqYXZhL2xhbmcvT2JqZWN0AQAbamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyAQAGYWNjZXB0" +
-    "AQAVKExqYXZhL2xhbmcvT2JqZWN0OylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAADAAN" +
-    "AAAAAAAEAAAADgAPAAEAEAAAAB0AAQABAAAABSq3AAGxAAAAAQARAAAABgABAAAAEwACABIAEwAC" +
-    "ABAAAAAlAAIAAgAAAAkrEgK5AAMCALEAAAABABEAAAAKAAIAAAAVAAgAFgAUAAAAAgAVAAIAFgAT" +
-    "AAIAEAAAACUAAgACAAAACSsSBLkAAwIAsQAAAAEAEQAAAAoAAgAAABkACAAaABQAAAACABUAAQAX" +
-    "ABgAAgAQAAAAZQACAAMAAAAxLBIFuQADAgAqLLcABiwSB7kAAwIAK7kACAEALBIJuQADAgAqLLcA" +
-    "CiwSC7kAAwIAsQAAAAEAEQAAACIACAAAAB0ACAAeAA0AHwAVACAAGwAhACMAIgAoACMAMAAkABQA" +
-    "AAACABkAAQAaAAAAAgAb");
-  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
-    "ZGV4CjAzNQBc8wr9PcHqnOR61m+0kimXTSddVMToJPuYBQAAcAAAAHhWNBIAAAAAAAAAAOAEAAAc" +
-    "AAAAcAAAAAYAAADgAAAABAAAAPgAAAAAAAAAAAAAAAcAAAAoAQAAAQAAAGABAAAYBAAAgAEAAHoC" +
-    "AAB9AgAAgAIAAIgCAACOAgAAlgIAALcCAADWAgAA4wIAAAIDAAAWAwAALAMAAEADAABeAwAAfQMA" +
-    "AIQDAACUAwAAlwMAAJsDAACgAwAAqAMAALwDAADrAwAAGQQAAEcEAAB0BAAAeQQAAIAEAAAHAAAA" +
-    "CAAAAAkAAAAKAAAADQAAABAAAAAQAAAABQAAAAAAAAARAAAABQAAAGQCAAASAAAABQAAAGwCAAAR" +
-    "AAAABQAAAHQCAAAAAAAAAgAAAAAAAwAEAAAAAAADAA4AAAAAAAIAGgAAAAIAAAACAAAAAwAAABkA" +
-    "AAAEAAEAEwAAAAAAAAAAAAAAAgAAAAAAAAAPAAAAPAIAAMoEAAAAAAAAAQAAAKgEAAABAAAAuAQA" +
-    "AAEAAQABAAAAhwQAAAQAAABwEAQAAAAOAAMAAgACAAAAjAQAAAcAAAAbAAUAAAByIAYAAgAOAAAA" +
-    "AwACAAIAAACTBAAABwAAABsABgAAAHIgBgACAA4AAAAEAAMAAgAAAJoEAAAiAAAAGwAYAAAAciAG" +
-    "AAMAcCACADEAGwAWAAAAciAGAAMAchAFAAIAGwAXAAAAciAGAAMAcCABADEAGwAVAAAAciAGAAMA" +
-    "DgAAAAAAAAAAAAMAAAAAAAAAAQAAAIABAAACAAAAgAEAAAMAAACIAQAAAQAAAAIAAAACAAAAAwAE" +
-    "AAEAAAAEAAEoAAE8AAY8aW5pdD4ABD47KVYABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBU" +
-    "cmFuc2Zvcm1lZAAdSGVsbG8gLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAC0xUcmFuc2Zvcm07AB1M" +
-    "ZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9s" +
-    "YW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABxMamF2YS91dGlsL2Z1bmN0aW9uL0Nv" +
-    "bnN1bWVyAB1MamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyOwAFU3RhcnQADlRyYW5zZm9ybS5q" +
-    "YXZhAAFWAAJWTAADVkxMAAZhY2NlcHQAEmVtaXR0ZXI6IGphY2stNC4xOQAtcG9zdCBGaW5pc2gg" +
-    "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkACxwb3N0IFN0YXJ0IHByaXZhdGUgbWV0" +
-    "aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAscHJlIEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0g" +
-    "VHJhbnNmb3JtZWQAK3ByZSBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQA" +
-    "A3J1bgAFc2F5SGkABXZhbHVlABMABw4AGQEABw5pABUBAAcOaQAdAgAABw5pPGk8aTxpAAIBARsc" +
-    "BRcAFwwXARcLFwMCAQEbHAYXABcKFwwXARcLFwMAAAMBAICABJADAQKoAwECyAMDAegDDwAAAAAA" +
-    "AAABAAAAAAAAAAEAAAAcAAAAcAAAAAIAAAAGAAAA4AAAAAMAAAAEAAAA+AAAAAUAAAAHAAAAKAEA" +
-    "AAYAAAABAAAAYAEAAAMQAAACAAAAgAEAAAEgAAAEAAAAkAEAAAYgAAABAAAAPAIAAAEQAAADAAAA" +
-    "ZAIAAAIgAAAcAAAAegIAAAMgAAAEAAAAhwQAAAQgAAACAAAAqAQAAAAgAAABAAAAygQAAAAQAAAB" +
-    "AAAA4AQAAA==");
+    // import java.util.function.Consumer;
+    //
+    // class Transform {
+    //     private void Start(Consumer<String> reporter) {
+    //         reporter.accept("Hello - private - Transformed");
+    //     }
+    //
+    //     private void Finish(Consumer<String> reporter) {
+    //        reporter.accept("Goodbye - private - Transformed");
+    //     }
+    //
+    //     public void sayHi(Runnable r, Consumer<String> reporter) {
+    //         reporter.accept("pre Start private method call - Transformed");
+    //         Start(reporter);
+    //         reporter.accept("post Start private method call - Transformed");
+    //         r.run();
+    //         reporter.accept("pre Finish private method call - Transformed");
+    //         Finish(reporter);
+    //         reporter.accept("post Finish private method call - Transformed");
+    //     }
+    // }
+    private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+            "yv66vgAAADQAMAoADQAcCAAdCwAeAB8IACAIACEKAAwAIggAIwsAJAAlCAAmCgAMACcIACgHACkH" +
+            "ACoBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFU3RhcnQBACAoTGph" +
+            "dmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjspVgEACVNpZ25hdHVyZQEANChMamF2YS91dGlsL2Z1" +
+            "bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xhbmcvU3RyaW5nOz47KVYBAAZGaW5pc2gBAAVzYXlIaQEA" +
+            "NChMamF2YS9sYW5nL1J1bm5hYmxlO0xqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7KVYBAEgo" +
+            "TGphdmEvbGFuZy9SdW5uYWJsZTtMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xh" +
+            "bmcvU3RyaW5nOz47KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAA4ADwEAHUhlbGxv" +
+            "IC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVkBwArDAAsAC0BAB9Hb29kYnllIC0gcHJpdmF0ZSAtIFRy" +
+            "YW5zZm9ybWVkAQArcHJlIFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAwA" +
+            "EgATAQAscG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQHAC4MAC8A" +
+            "DwEALHByZSBGaW5pc2ggcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAAWABMBAC1w" +
+            "b3N0IEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQBAAlUcmFuc2Zvcm0B" +
+            "ABBqYXZhL2xhbmcvT2JqZWN0AQAbamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyAQAGYWNjZXB0" +
+            "AQAVKExqYXZhL2xhbmcvT2JqZWN0OylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAADAAN" +
+            "AAAAAAAEAAAADgAPAAEAEAAAAB0AAQABAAAABSq3AAGxAAAAAQARAAAABgABAAAAEwACABIAEwAC" +
+            "ABAAAAAlAAIAAgAAAAkrEgK5AAMCALEAAAABABEAAAAKAAIAAAAVAAgAFgAUAAAAAgAVAAIAFgAT" +
+            "AAIAEAAAACUAAgACAAAACSsSBLkAAwIAsQAAAAEAEQAAAAoAAgAAABkACAAaABQAAAACABUAAQAX" +
+            "ABgAAgAQAAAAZQACAAMAAAAxLBIFuQADAgAqLLcABiwSB7kAAwIAK7kACAEALBIJuQADAgAqLLcA" +
+            "CiwSC7kAAwIAsQAAAAEAEQAAACIACAAAAB0ACAAeAA0AHwAVACAAGwAhACMAIgAoACMAMAAkABQA" +
+            "AAACABkAAQAaAAAAAgAb");
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQBc8wr9PcHqnOR61m+0kimXTSddVMToJPuYBQAAcAAAAHhWNBIAAAAAAAAAAOAEAAAc" +
+            "AAAAcAAAAAYAAADgAAAABAAAAPgAAAAAAAAAAAAAAAcAAAAoAQAAAQAAAGABAAAYBAAAgAEAAHoC" +
+            "AAB9AgAAgAIAAIgCAACOAgAAlgIAALcCAADWAgAA4wIAAAIDAAAWAwAALAMAAEADAABeAwAAfQMA" +
+            "AIQDAACUAwAAlwMAAJsDAACgAwAAqAMAALwDAADrAwAAGQQAAEcEAAB0BAAAeQQAAIAEAAAHAAAA" +
+            "CAAAAAkAAAAKAAAADQAAABAAAAAQAAAABQAAAAAAAAARAAAABQAAAGQCAAASAAAABQAAAGwCAAAR" +
+            "AAAABQAAAHQCAAAAAAAAAgAAAAAAAwAEAAAAAAADAA4AAAAAAAIAGgAAAAIAAAACAAAAAwAAABkA" +
+            "AAAEAAEAEwAAAAAAAAAAAAAAAgAAAAAAAAAPAAAAPAIAAMoEAAAAAAAAAQAAAKgEAAABAAAAuAQA" +
+            "AAEAAQABAAAAhwQAAAQAAABwEAQAAAAOAAMAAgACAAAAjAQAAAcAAAAbAAUAAAByIAYAAgAOAAAA" +
+            "AwACAAIAAACTBAAABwAAABsABgAAAHIgBgACAA4AAAAEAAMAAgAAAJoEAAAiAAAAGwAYAAAAciAG" +
+            "AAMAcCACADEAGwAWAAAAciAGAAMAchAFAAIAGwAXAAAAciAGAAMAcCABADEAGwAVAAAAciAGAAMA" +
+            "DgAAAAAAAAAAAAMAAAAAAAAAAQAAAIABAAACAAAAgAEAAAMAAACIAQAAAQAAAAIAAAACAAAAAwAE" +
+            "AAEAAAAEAAEoAAE8AAY8aW5pdD4ABD47KVYABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBU" +
+            "cmFuc2Zvcm1lZAAdSGVsbG8gLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAC0xUcmFuc2Zvcm07AB1M" +
+            "ZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9s" +
+            "YW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABxMamF2YS91dGlsL2Z1bmN0aW9uL0Nv" +
+            "bnN1bWVyAB1MamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyOwAFU3RhcnQADlRyYW5zZm9ybS5q" +
+            "YXZhAAFWAAJWTAADVkxMAAZhY2NlcHQAEmVtaXR0ZXI6IGphY2stNC4xOQAtcG9zdCBGaW5pc2gg" +
+            "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkACxwb3N0IFN0YXJ0IHByaXZhdGUgbWV0" +
+            "aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAscHJlIEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0g" +
+            "VHJhbnNmb3JtZWQAK3ByZSBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQA" +
+            "A3J1bgAFc2F5SGkABXZhbHVlABMABw4AGQEABw5pABUBAAcOaQAdAgAABw5pPGk8aTxpAAIBARsc" +
+            "BRcAFwwXARcLFwMCAQEbHAYXABcKFwwXARcLFwMAAAMBAICABJADAQKoAwECyAMDAegDDwAAAAAA" +
+            "AAABAAAAAAAAAAEAAAAcAAAAcAAAAAIAAAAGAAAA4AAAAAMAAAAEAAAA+AAAAAUAAAAHAAAAKAEA" +
+            "AAYAAAABAAAAYAEAAAMQAAACAAAAgAEAAAEgAAAEAAAAkAEAAAYgAAABAAAAPAIAAAEQAAADAAAA" +
+            "ZAIAAAIgAAAcAAAAegIAAAMgAAAEAAAAhwQAAAQgAAACAAAAqAQAAAAgAAABAAAAygQAAAAQAAAB" +
+            "AAAA4AQAAA==");
 
-  // A class that we can use to keep track of the output of this test.
-  private static class TestWatcher implements Consumer<String> {
-    private StringBuilder sb;
-    public TestWatcher() {
-      sb = new StringBuilder();
+    // A class that we can use to keep track of the output of this test.
+    private static class TestWatcher implements Consumer<String> {
+        private StringBuilder sb;
+        public TestWatcher() {
+            sb = new StringBuilder();
+        }
+
+        @Override
+        public void accept(String s) {
+            sb.append(s);
+            sb.append('\n');
+        }
+
+        public String getOutput() {
+            return sb.toString();
+        }
+
+        public void clear() {
+            sb = new StringBuilder();
+        }
     }
 
-    @Override
-    public void accept(String s) {
-      sb.append(s);
-      sb.append('\n');
+    public static void main(String[] args) {
+        doTest(new Transform(), new TestWatcher());
     }
 
-    public String getOutput() {
-      return sb.toString();
+    private static boolean interpreting = true;
+    private static boolean retry = false;
+
+    public static void doTest(Transform t, TestWatcher w) {
+        // Get the methods that need to be optimized.
+        Method say_hi_method;
+        // Figure out if we can even JIT at all.
+        final boolean has_jit = hasJit();
+        try {
+            say_hi_method = Transform.class.getDeclaredMethod(
+                    "sayHi", Runnable.class, Consumer.class);
+        } catch (Exception e) {
+            System.out.println("Unable to find methods!");
+            e.printStackTrace(System.out);
+            return;
+        }
+        // Makes sure the stack is the way we want it for the test and does the redefinition.
+        // It will set the retry boolean to true if the stack does not have a JIT-compiled
+        // sayHi entry. This can only happen if the method gets GC'd.
+        Runnable do_redefinition = () -> {
+            if (has_jit && Main.isInterpretedFunction(say_hi_method, true)) {
+                // Try again. We are not running the right jitted methods/cannot redefine them now.
+                retry = true;
+            } else {
+                // Actually do the redefinition. The stack looks good.
+                retry = false;
+                w.accept("transforming calling function");
+                Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+            }
+        };
+        // This just prints something out to show we are running the Runnable.
+        Runnable say_nothing = () -> { w.accept("Not doing anything here"); };
+        do {
+            // Run ensureJitCompiled here since it might get GCd
+            ensureJitCompiled(Transform.class, "sayHi");
+            // Clear output.
+            w.clear();
+            // Try and redefine.
+            t.sayHi(say_nothing, w);
+            t.sayHi(do_redefinition, w);
+            t.sayHi(say_nothing, w);
+        } while (retry);
+        // Print output of last run.
+        System.out.print(w.getOutput());
     }
 
-    public void clear() {
-      sb = new StringBuilder();
-    }
-  }
+    private static native boolean hasJit();
 
-  public static void main(String[] args) {
-    doTest(new Transform(), new TestWatcher());
-  }
+    private static native boolean isInterpretedFunction(Method m, boolean require_deoptimizable);
 
-  private static boolean interpreting = true;
-  private static boolean retry = false;
-
-  public static void doTest(Transform t, TestWatcher w) {
-    // Get the methods that need to be optimized.
-    Method say_hi_method;
-    // Figure out if we can even JIT at all.
-    final boolean has_jit = hasJit();
-    try {
-      say_hi_method = Transform.class.getDeclaredMethod(
-          "sayHi", Runnable.class, Consumer.class);
-    } catch (Exception e) {
-      System.out.println("Unable to find methods!");
-      e.printStackTrace(System.out);
-      return;
-    }
-    // Makes sure the stack is the way we want it for the test and does the redefinition. It will
-    // set the retry boolean to true if the stack does not have a JIT-compiled sayHi entry. This can
-    // only happen if the method gets GC'd.
-    Runnable do_redefinition = () -> {
-      if (has_jit && Main.isInterpretedFunction(say_hi_method, true)) {
-        // Try again. We are not running the right jitted methods/cannot redefine them now.
-        retry = true;
-      } else {
-        // Actually do the redefinition. The stack looks good.
-        retry = false;
-        w.accept("transforming calling function");
-        Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
-      }
-    };
-    // This just prints something out to show we are running the Runnable.
-    Runnable say_nothing = () -> { w.accept("Not doing anything here"); };
-    do {
-      // Run ensureJitCompiled here since it might get GCd
-      ensureJitCompiled(Transform.class, "sayHi");
-      // Clear output.
-      w.clear();
-      // Try and redefine.
-      t.sayHi(say_nothing, w);
-      t.sayHi(do_redefinition, w);
-      t.sayHi(say_nothing, w);
-    } while (retry);
-    // Print output of last run.
-    System.out.print(w.getOutput());
-  }
-
-  private static native boolean hasJit();
-
-  private static native boolean isInterpretedFunction(Method m, boolean require_deoptimizable);
-
-  private static native void ensureJitCompiled(Class c, String name);
+    private static native void ensureJitCompiled(Class c, String name);
 }
diff --git a/test/916-obsolete-jit/src/Transform.java b/test/916-obsolete-jit/src/Transform.java
index 9c9adbc..5dbc04e 100644
--- a/test/916-obsolete-jit/src/Transform.java
+++ b/test/916-obsolete-jit/src/Transform.java
@@ -17,21 +17,21 @@
 import java.util.function.Consumer;
 
 class Transform {
-  private void Start(Consumer<String> reporter) {
-    reporter.accept("hello - private");
-  }
+    private void Start(Consumer<String> reporter) {
+        reporter.accept("hello - private");
+    }
 
-  private void Finish(Consumer<String> reporter) {
-    reporter.accept("goodbye - private");
-  }
+    private void Finish(Consumer<String> reporter) {
+        reporter.accept("goodbye - private");
+    }
 
-  public void sayHi(Runnable r, Consumer<String> reporter) {
-    reporter.accept("Pre Start private method call");
-    Start(reporter);
-    reporter.accept("Post Start private method call");
-    r.run();
-    reporter.accept("Pre Finish private method call");
-    Finish(reporter);
-    reporter.accept("Post Finish private method call");
-  }
+    public void sayHi(Runnable r, Consumer<String> reporter) {
+        reporter.accept("Pre Start private method call");
+        Start(reporter);
+        reporter.accept("Post Start private method call");
+        r.run();
+        reporter.accept("Pre Finish private method call");
+        Finish(reporter);
+        reporter.accept("Post Finish private method call");
+    }
 }
diff --git a/test/917-fields-transformation/run b/test/917-fields-transformation/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/917-fields-transformation/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/917-fields-transformation/run.py b/test/917-fields-transformation/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/917-fields-transformation/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/917-fields-transformation/src/Main.java b/test/917-fields-transformation/src/Main.java
index 289b89f..e277deb 100644
--- a/test/917-fields-transformation/src/Main.java
+++ b/test/917-fields-transformation/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test917.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test917.run();
+    }
 }
diff --git a/test/917-fields-transformation/src/art/Test917.java b/test/917-fields-transformation/src/art/Test917.java
index 245e92e..ba9a06b 100644
--- a/test/917-fields-transformation/src/art/Test917.java
+++ b/test/917-fields-transformation/src/art/Test917.java
@@ -18,80 +18,78 @@
 
 import java.util.Base64;
 public class Test917 {
+    static class Transform {
+        public String take1;
+        public String take2;
 
-  static class Transform {
-    public String take1;
-    public String take2;
+        public Transform(String take1, String take2) {
+            this.take1 = take1;
+            this.take2 = take2;
+        }
 
-    public Transform(String take1, String take2) {
-      this.take1 = take1;
-      this.take2 = take2;
+        public String getResult() {
+            return take1;
+        }
     }
 
-    public String getResult() {
-      return take1;
+
+    // base64 encoded class/dex file for
+    // static class Transform {
+    //   public String take1;
+    //   public String take2;
+    //
+    //   public Transform(String a, String b) {
+    //     take1 = a;
+    //     take2 = b;
+    //   }
+    //
+    //   public String getResult() {
+    //     return take2;
+    //   }
+    // }
+    private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+            "yv66vgAAADQAGwoABQARCQAEABIJAAQAEwcAFQcAGAEABXRha2UxAQASTGphdmEvbGFuZy9TdHJp" +
+            "bmc7AQAFdGFrZTIBAAY8aW5pdD4BACcoTGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9TdHJp" +
+            "bmc7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAJZ2V0UmVzdWx0AQAUKClMamF2YS9sYW5n" +
+            "L1N0cmluZzsBAApTb3VyY2VGaWxlAQAMVGVzdDkxNy5qYXZhDAAJABkMAAYABwwACAAHBwAaAQAV" +
+            "YXJ0L1Rlc3Q5MTckVHJhbnNmb3JtAQAJVHJhbnNmb3JtAQAMSW5uZXJDbGFzc2VzAQAQamF2YS9s" +
+            "YW5nL09iamVjdAEAAygpVgEAC2FydC9UZXN0OTE3ACAABAAFAAAAAgABAAYABwAAAAEACAAHAAAA" +
+            "AgABAAkACgABAAsAAAAzAAIAAwAAAA8qtwABKiu1AAIqLLUAA7EAAAABAAwAAAASAAQAAAAJAAQA" +
+            "CgAJAAsADgAMAAEADQAOAAEACwAAAB0AAQABAAAABSq0AAOwAAAAAQAMAAAABgABAAAADwACAA8A" +
+            "AAACABAAFwAAAAoAAQAEABQAFgAI");
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQBdcPySAAAAAAAAAAAAAAAAAAAAAAAAAACQAwAAcAAAAHhWNBIAAAAAAAAAAMwCAAAS" +
+            "AAAAcAAAAAcAAAC4AAAAAwAAANQAAAACAAAA+AAAAAMAAAAIAQAAAQAAACABAABQAgAAQAEAAEAB" +
+            "AABIAQAASwEAAGQBAABzAQAAlwEAALcBAADLAQAA3wEAAO0BAAD4AQAA+wEAAAACAAANAgAAGAIA" +
+            "AB4CAAAlAgAALAIAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAoAAAABAAAABQAAAAAAAAAKAAAA" +
+            "BgAAAAAAAAALAAAABgAAADQCAAAAAAUADwAAAAAABQAQAAAAAAACAAAAAAAAAAAADQAAAAQAAQAA" +
+            "AAAAAAAAAAAAAAAEAAAAAAAAAAgAAAC8AgAAjAIAAAAAAAAGPGluaXQ+AAFMABdMYXJ0L1Rlc3Q5" +
+            "MTckVHJhbnNmb3JtOwANTGFydC9UZXN0OTE3OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2lu" +
+            "Z0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5nL09iamVj" +
+            "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAMVGVzdDkxNy5qYXZhAAlUcmFuc2Zvcm0AAVYAA1ZMTAAL" +
+            "YWNjZXNzRmxhZ3MACWdldFJlc3VsdAAEbmFtZQAFdGFrZTEABXRha2UyAAV2YWx1ZQAAAgAAAAUA" +
+            "BQAJAgAABw4BAw8BAg8BAg8ADwAHDgAAAAADAAMAAQAAADwCAAAIAAAAcBACAAAAWwEAAFsCAQAO" +
+            "AAIAAQAAAAAATAIAAAMAAABUEAEAEQAAAAACAQEAAQEBAIGABNQEAQH0BAAAAgIBERgBAgMCDAQI" +
+            "DhcJAAIAAACgAgAApgIAALACAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAAAAAAAQAAABIAAABw" +
+            "AAAAAgAAAAcAAAC4AAAAAwAAAAMAAADUAAAABAAAAAIAAAD4AAAABQAAAAMAAAAIAQAABgAAAAEA" +
+            "AAAgAQAAAiAAABIAAABAAQAAARAAAAEAAAA0AgAAAyAAAAIAAAA8AgAAASAAAAIAAABUAgAAACAA" +
+            "AAEAAACMAgAABCAAAAIAAACgAgAAAxAAAAEAAACwAgAABiAAAAEAAAC8AgAAABAAAAEAAADMAgAA");
+
+    public static void run() {
+        Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+        doTest(new Transform("Hello", "Goodbye"), new Transform("start", "end"));
     }
-  }
 
-
-  // base64 encoded class/dex file for
-  // static class Transform {
-  //   public String take1;
-  //   public String take2;
-  //
-  //   public Transform(String a, String b) {
-  //     take1 = a;
-  //     take2 = b;
-  //   }
-  //
-  //   public String getResult() {
-  //     return take2;
-  //   }
-  // }
-  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
-    "yv66vgAAADQAGwoABQARCQAEABIJAAQAEwcAFQcAGAEABXRha2UxAQASTGphdmEvbGFuZy9TdHJp" +
-    "bmc7AQAFdGFrZTIBAAY8aW5pdD4BACcoTGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9TdHJp" +
-    "bmc7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAJZ2V0UmVzdWx0AQAUKClMamF2YS9sYW5n" +
-    "L1N0cmluZzsBAApTb3VyY2VGaWxlAQAMVGVzdDkxNy5qYXZhDAAJABkMAAYABwwACAAHBwAaAQAV" +
-    "YXJ0L1Rlc3Q5MTckVHJhbnNmb3JtAQAJVHJhbnNmb3JtAQAMSW5uZXJDbGFzc2VzAQAQamF2YS9s" +
-    "YW5nL09iamVjdAEAAygpVgEAC2FydC9UZXN0OTE3ACAABAAFAAAAAgABAAYABwAAAAEACAAHAAAA" +
-    "AgABAAkACgABAAsAAAAzAAIAAwAAAA8qtwABKiu1AAIqLLUAA7EAAAABAAwAAAASAAQAAAAJAAQA" +
-    "CgAJAAsADgAMAAEADQAOAAEACwAAAB0AAQABAAAABSq0AAOwAAAAAQAMAAAABgABAAAADwACAA8A" +
-    "AAACABAAFwAAAAoAAQAEABQAFgAI");
-  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
-    "ZGV4CjAzNQBdcPySAAAAAAAAAAAAAAAAAAAAAAAAAACQAwAAcAAAAHhWNBIAAAAAAAAAAMwCAAAS" +
-    "AAAAcAAAAAcAAAC4AAAAAwAAANQAAAACAAAA+AAAAAMAAAAIAQAAAQAAACABAABQAgAAQAEAAEAB" +
-    "AABIAQAASwEAAGQBAABzAQAAlwEAALcBAADLAQAA3wEAAO0BAAD4AQAA+wEAAAACAAANAgAAGAIA" +
-    "AB4CAAAlAgAALAIAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAoAAAABAAAABQAAAAAAAAAKAAAA" +
-    "BgAAAAAAAAALAAAABgAAADQCAAAAAAUADwAAAAAABQAQAAAAAAACAAAAAAAAAAAADQAAAAQAAQAA" +
-    "AAAAAAAAAAAAAAAEAAAAAAAAAAgAAAC8AgAAjAIAAAAAAAAGPGluaXQ+AAFMABdMYXJ0L1Rlc3Q5" +
-    "MTckVHJhbnNmb3JtOwANTGFydC9UZXN0OTE3OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2lu" +
-    "Z0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5nL09iamVj" +
-    "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAMVGVzdDkxNy5qYXZhAAlUcmFuc2Zvcm0AAVYAA1ZMTAAL" +
-    "YWNjZXNzRmxhZ3MACWdldFJlc3VsdAAEbmFtZQAFdGFrZTEABXRha2UyAAV2YWx1ZQAAAgAAAAUA" +
-    "BQAJAgAABw4BAw8BAg8BAg8ADwAHDgAAAAADAAMAAQAAADwCAAAIAAAAcBACAAAAWwEAAFsCAQAO" +
-    "AAIAAQAAAAAATAIAAAMAAABUEAEAEQAAAAACAQEAAQEBAIGABNQEAQH0BAAAAgIBERgBAgMCDAQI" +
-    "DhcJAAIAAACgAgAApgIAALACAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAAAAAAAQAAABIAAABw" +
-    "AAAAAgAAAAcAAAC4AAAAAwAAAAMAAADUAAAABAAAAAIAAAD4AAAABQAAAAMAAAAIAQAABgAAAAEA" +
-    "AAAgAQAAAiAAABIAAABAAQAAARAAAAEAAAA0AgAAAyAAAAIAAAA8AgAAASAAAAIAAABUAgAAACAA" +
-    "AAEAAACMAgAABCAAAAIAAACgAgAAAxAAAAEAAACwAgAABiAAAAEAAAC8AgAAABAAAAEAAADMAgAA");
-
-  public static void run() {
-    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
-    doTest(new Transform("Hello", "Goodbye"),
-           new Transform("start", "end"));
-  }
-
-  private static void printTransform(Transform t) {
-    System.out.println("Result is " + t.getResult());
-    System.out.println("take1 is " + t.take1);
-    System.out.println("take2 is " + t.take2);
-  }
-  public static void doTest(Transform t1, Transform t2) {
-    printTransform(t1);
-    printTransform(t2);
-    Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
-    printTransform(t1);
-    printTransform(t2);
-  }
+    private static void printTransform(Transform t) {
+        System.out.println("Result is " + t.getResult());
+        System.out.println("take1 is " + t.take1);
+        System.out.println("take2 is " + t.take2);
+    }
+    public static void doTest(Transform t1, Transform t2) {
+        printTransform(t1);
+        printTransform(t2);
+        Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+        printTransform(t1);
+        printTransform(t2);
+    }
 }
diff --git a/test/918-fields/run b/test/918-fields/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/918-fields/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/918-fields/run.py b/test/918-fields/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/918-fields/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/918-fields/src/Main.java b/test/918-fields/src/Main.java
index e0ed65c..e9f9bc9 100644
--- a/test/918-fields/src/Main.java
+++ b/test/918-fields/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test918.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test918.run();
+    }
 }
diff --git a/test/918-fields/src/art/Test918.java b/test/918-fields/src/art/Test918.java
index 5328b0b..966c093 100644
--- a/test/918-fields/src/art/Test918.java
+++ b/test/918-fields/src/art/Test918.java
@@ -21,58 +21,57 @@
 import java.util.Arrays;
 
 public class Test918 {
-  public static void run() throws Exception {
-    doTest();
-  }
-
-  public static void doTest() throws Exception {
-    testField(Math.class, "PI");
-    testField(InterruptedIOException.class, "bytesTransferred");
-    testField(Foo.class, "this$0");
-    testField(Bar.class, "VAL");
-    testField(Generics.class, "generics");
-    testField(Generics.class, "privateValue");
-  }
-
-  private static void testField(Class<?> base, String fieldName)
-      throws Exception {
-    Field f = base.getDeclaredField(fieldName);
-    String[] result = getFieldName(f);
-    System.out.println(Arrays.toString(result));
-
-    Class<?> declClass = getFieldDeclaringClass(f);
-    if (base != declClass) {
-      throw new RuntimeException("Declaring class not equal: " + base + " vs " + declClass);
+    public static void run() throws Exception {
+        doTest();
     }
-    System.out.println(declClass);
 
-    int modifiers = getFieldModifiers(f);
-    if (modifiers != f.getModifiers()) {
-      throw new RuntimeException("Modifiers not equal: " + f.getModifiers() + " vs " + modifiers);
+    public static void doTest() throws Exception {
+        testField(Math.class, "PI");
+        testField(InterruptedIOException.class, "bytesTransferred");
+        testField(Foo.class, "this$0");
+        testField(Bar.class, "VAL");
+        testField(Generics.class, "generics");
+        testField(Generics.class, "privateValue");
     }
-    System.out.println(modifiers);
 
-    boolean synth = isFieldSynthetic(f);
-    if (synth != f.isSynthetic()) {
-      throw new RuntimeException("Synthetic not equal: " + f.isSynthetic() + " vs " + synth);
+    private static void testField(Class<?> base, String fieldName) throws Exception {
+        Field f = base.getDeclaredField(fieldName);
+        String[] result = getFieldName(f);
+        System.out.println(Arrays.toString(result));
+
+        Class<?> declClass = getFieldDeclaringClass(f);
+        if (base != declClass) {
+            throw new RuntimeException("Declaring class not equal: " + base + " vs " + declClass);
+        }
+        System.out.println(declClass);
+
+        int modifiers = getFieldModifiers(f);
+        if (modifiers != f.getModifiers()) {
+            throw new RuntimeException(
+                    "Modifiers not equal: " + f.getModifiers() + " vs " + modifiers);
+        }
+        System.out.println(modifiers);
+
+        boolean synth = isFieldSynthetic(f);
+        if (synth != f.isSynthetic()) {
+            throw new RuntimeException("Synthetic not equal: " + f.isSynthetic() + " vs " + synth);
+        }
+        System.out.println(synth);
     }
-    System.out.println(synth);
-  }
 
-  private static native String[] getFieldName(Field f);
-  private static native Class<?> getFieldDeclaringClass(Field f);
-  private static native int getFieldModifiers(Field f);
-  private static native boolean isFieldSynthetic(Field f);
+    private static native String[] getFieldName(Field f);
+    private static native Class<?> getFieldDeclaringClass(Field f);
+    private static native int getFieldModifiers(Field f);
+    private static native boolean isFieldSynthetic(Field f);
 
-  private class Foo {
-  }
+    private class Foo {}
 
-  private static interface Bar {
-    public static int VAL = 1;
-  }
+    private static interface Bar {
+        public static int VAL = 1;
+    }
 
-  private static class Generics<T> {
-    T generics;
-    private int privateValue = 42;
-  }
+    private static class Generics<T> {
+        T generics;
+        private int privateValue = 42;
+    }
 }
diff --git a/test/919-obsolete-fields/run b/test/919-obsolete-fields/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/919-obsolete-fields/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/919-obsolete-fields/run.py b/test/919-obsolete-fields/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/919-obsolete-fields/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/919-obsolete-fields/src/Main.java b/test/919-obsolete-fields/src/Main.java
index 10eadb2..bb08324 100644
--- a/test/919-obsolete-fields/src/Main.java
+++ b/test/919-obsolete-fields/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test919.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test919.run();
+    }
 }
diff --git a/test/919-obsolete-fields/src/art/Test919.java b/test/919-obsolete-fields/src/art/Test919.java
index 11971ef..0ae5305 100644
--- a/test/919-obsolete-fields/src/art/Test919.java
+++ b/test/919-obsolete-fields/src/art/Test919.java
@@ -20,154 +20,153 @@
 import java.util.Base64;
 
 public class Test919 {
+    static class Transform {
+        private Consumer<String> reporter;
+        public Transform(Consumer<String> reporter) {
+            this.reporter = reporter;
+        }
 
-  static class Transform {
-    private Consumer<String> reporter;
-    public Transform(Consumer<String> reporter) {
-      this.reporter = reporter;
+        private void Start() {
+            reporter.accept("hello - private");
+        }
+
+        private void Finish() {
+            reporter.accept("goodbye - private");
+        }
+
+        public void sayHi(Runnable r) {
+            reporter.accept("Pre Start private method call");
+            Start();
+            reporter.accept("Post Start private method call");
+            r.run();
+            reporter.accept("Pre Finish private method call");
+            Finish();
+            reporter.accept("Post Finish private method call");
+        }
     }
 
-    private void Start() {
-      reporter.accept("hello - private");
+
+    // What follows is the base64 encoded representation of the following class:
+    //
+    // import java.util.function.Consumer;
+    //
+    // static class Transform {
+    //   private Consumer<String> reporter;
+    //   public Transform(Consumer<String> reporter) {
+    //     this.reporter = reporter;
+    //   }
+    //
+    //   private void Start() {
+    //     reporter.accept("Hello - private - Transformed");
+    //   }
+    //
+    //   private void Finish() {
+    //     reporter.accept("Goodbye - private - Transformed");
+    //   }
+    //
+    //   public void sayHi(Runnable r) {
+    //     reporter.accept("pre Start private method call - Transformed");
+    //     Start();
+    //     reporter.accept("post Start private method call - Transformed");
+    //     r.run();
+    //     reporter.accept("pre Finish private method call - Transformed");
+    //     Finish();
+    //     reporter.accept("post Finish private method call - Transformed");
+    //   }
+    // }
+    private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+            "yv66vgAAADQAOAoADgAfCQANACAIACELACIAIwgAJAgAJQoADQAmCAAnCwAoACkIACoKAA0AKwgA" +
+            "LAcALgcAMQEACHJlcG9ydGVyAQAdTGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjsBAAlTaWdu" +
+            "YXR1cmUBADFMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xhbmcvU3RyaW5nOz47" +
+            "AQAGPGluaXQ+AQAgKExqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7KVYBAARDb2RlAQAPTGlu" +
+            "ZU51bWJlclRhYmxlAQA0KExqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI8TGphdmEvbGFuZy9T" +
+            "dHJpbmc7PjspVgEABVN0YXJ0AQADKClWAQAGRmluaXNoAQAFc2F5SGkBABcoTGphdmEvbGFuZy9S" +
+            "dW5uYWJsZTspVgEAClNvdXJjZUZpbGUBAAxUZXN0OTE5LmphdmEMABMAGQwADwAQAQAdSGVsbG8g" +
+            "LSBwcml2YXRlIC0gVHJhbnNmb3JtZWQHADIMADMANAEAH0dvb2RieWUgLSBwcml2YXRlIC0gVHJh" +
+            "bnNmb3JtZWQBACtwcmUgU3RhcnQgcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAAY" +
+            "ABkBACxwb3N0IFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAcANQwANgAZ" +
+            "AQAscHJlIEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQMABoAGQEALXBv" +
+            "c3QgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAcANwEAFWFydC9UZXN0" +
+            "OTE5JFRyYW5zZm9ybQEACVRyYW5zZm9ybQEADElubmVyQ2xhc3NlcwEAEGphdmEvbGFuZy9PYmpl" +
+            "Y3QBABtqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXIBAAZhY2NlcHQBABUoTGphdmEvbGFuZy9P" +
+            "YmplY3Q7KVYBABJqYXZhL2xhbmcvUnVubmFibGUBAANydW4BAAthcnQvVGVzdDkxOQAgAA0ADgAA" +
+            "AAEAAgAPABAAAQARAAAAAgASAAQAAQATABQAAgAVAAAAKgACAAIAAAAKKrcAASortQACsQAAAAEA" +
+            "FgAAAA4AAwAAAAgABAAJAAkACgARAAAAAgAXAAIAGAAZAAEAFQAAACgAAgABAAAADCq0AAISA7kA" +
+            "BAIAsQAAAAEAFgAAAAoAAgAAAA0ACwAOAAIAGgAZAAEAFQAAACgAAgABAAAADCq0AAISBbkABAIA" +
+            "sQAAAAEAFgAAAAoAAgAAABEACwASAAEAGwAcAAEAFQAAAG8AAgACAAAAOyq0AAISBrkABAIAKrcA" +
+            "Byq0AAISCLkABAIAK7kACQEAKrQAAhIKuQAEAgAqtwALKrQAAhIMuQAEAgCxAAAAAQAWAAAAIgAI" +
+            "AAAAFQALABYADwAXABoAGAAgABkAKwAaAC8AGwA6ABwAAgAdAAAAAgAeADAAAAAKAAEADQAtAC8A" +
+            "CA==");
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQBeEZYBAAAAAAAAAAAAAAAAAAAAAAAAAACMBgAAcAAAAHhWNBIAAAAAAAAAAMgFAAAi" +
+            "AAAAcAAAAAkAAAD4AAAABAAAABwBAAABAAAATAEAAAcAAABUAQAAAQAAAIwBAADgBAAArAEAAKwB" +
+            "AACvAQAAsgEAALoBAAC+AQAAxAEAAMwBAADtAQAADAIAACUCAAA0AgAAWAIAAHgCAACXAgAAqwIA" +
+            "AMECAADVAgAA8wIAABIDAAAZAwAAJwMAADIDAAA1AwAAOQMAAEEDAABOAwAAVAMAAIMDAACxAwAA" +
+            "3wMAAAwEAAAWBAAAGwQAACIEAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAOAAAAEQAAABUAAAAV" +
+            "AAAACAAAAAAAAAAWAAAACAAAADQEAAAWAAAACAAAADwEAAAWAAAACAAAACwEAAAAAAcAHgAAAAAA" +
+            "AwACAAAAAAAAAAUAAAAAAAAAEgAAAAAAAgAgAAAABQAAAAIAAAAGAAAAHwAAAAcAAQAXAAAAAAAA" +
+            "AAAAAAAFAAAAAAAAABMAAACoBQAARAUAAAAAAAABKAABPAAGPGluaXQ+AAI+OwAEPjspVgAGRmlu" +
+            "aXNoAB9Hb29kYnllIC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVkAB1IZWxsbyAtIHByaXZhdGUgLSBU" +
+            "cmFuc2Zvcm1lZAAXTGFydC9UZXN0OTE5JFRyYW5zZm9ybTsADUxhcnQvVGVzdDkxOTsAIkxkYWx2" +
+            "aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNs" +
+            "YXNzOwAdTGRhbHZpay9hbm5vdGF0aW9uL1NpZ25hdHVyZTsAEkxqYXZhL2xhbmcvT2JqZWN0OwAU" +
+            "TGphdmEvbGFuZy9SdW5uYWJsZTsAEkxqYXZhL2xhbmcvU3RyaW5nOwAcTGphdmEvdXRpbC9mdW5j" +
+            "dGlvbi9Db25zdW1lcgAdTGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjsABVN0YXJ0AAxUZXN0" +
+            "OTE5LmphdmEACVRyYW5zZm9ybQABVgACVkwABmFjY2VwdAALYWNjZXNzRmxhZ3MABG5hbWUALXBv" +
+            "c3QgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAscG9zdCBTdGFydCBw" +
+            "cml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQALHByZSBGaW5pc2ggcHJpdmF0ZSBtZXRo" +
+            "b2QgY2FsbCAtIFRyYW5zZm9ybWVkACtwcmUgU3RhcnQgcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRy" +
+            "YW5zZm9ybWVkAAhyZXBvcnRlcgADcnVuAAVzYXlIaQAFdmFsdWUAAAAAAQAAAAcAAAABAAAABQAA" +
+            "AAEAAAAGAAAACAEABw4BAw8BAg8AEQAHDgEIDwANAAcOAQgPABUBAAcOAQgPAQMPAQgPAQMPAQgP" +
+            "AQMPAQgPAAACAAIAAQAAAEQEAAAGAAAAcBAEAAAAWwEAAA4AAwABAAIAAABQBAAACQAAAFQgAAAb" +
+            "AQYAAAByIAYAEAAOAAAAAwABAAIAAABYBAAACQAAAFQgAAAbAQcAAAByIAYAEAAOAAAABAACAAIA" +
+            "AABgBAAAKgAAAFQgAAAbAR0AAAByIAYAEABwEAIAAgBUIAAAGwEbAAAAciAGABAAchAFAAMAVCAA" +
+            "ABsBHAAAAHIgBgAQAHAQAQACAFQgAAAbARoAAAByIAYAEAAOAAABAwEAAgCBgAT8CAECmAkBArwJ" +
+            "AwHgCQICASEYAQIDAhgECBkXFAIEASEcBBcQFwEXDxcDAgQBIRwFFwAXEBcBFw8XBAAAAAIAAABc" +
+            "BQAAYgUAAAEAAABrBQAAAQAAAHkFAACMBQAAAQAAAAEAAAAAAAAAAAAAAJgFAAAAAAAAoAUAABAA" +
+            "AAAAAAAAAQAAAAAAAAABAAAAIgAAAHAAAAACAAAACQAAAPgAAAADAAAABAAAABwBAAAEAAAAAQAA" +
+            "AEwBAAAFAAAABwAAAFQBAAAGAAAAAQAAAIwBAAACIAAAIgAAAKwBAAABEAAAAwAAACwEAAADIAAA" +
+            "BAAAAEQEAAABIAAABAAAAHwEAAAAIAAAAQAAAEQFAAAEIAAABAAAAFwFAAADEAAAAwAAAIwFAAAG" +
+            "IAAAAQAAAKgFAAAAEAAAAQAAAMgFAAA=");
+
+    // A class that we can use to keep track of the output of this test.
+    private static class TestWatcher implements Consumer<String> {
+        private StringBuilder sb;
+        public TestWatcher() {
+            sb = new StringBuilder();
+        }
+
+        @Override
+        public void accept(String s) {
+            sb.append(s);
+            sb.append('\n');
+        }
+
+        public String getOutput() {
+            return sb.toString();
+        }
     }
 
-    private void Finish() {
-      reporter.accept("goodbye - private");
+    public static void run() {
+        Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+        TestWatcher w = new TestWatcher();
+        doTest(new Transform(w), w);
     }
 
-    public void sayHi(Runnable r) {
-      reporter.accept("Pre Start private method call");
-      Start();
-      reporter.accept("Post Start private method call");
-      r.run();
-      reporter.accept("Pre Finish private method call");
-      Finish();
-      reporter.accept("Post Finish private method call");
+    public static void doTest(Transform t, TestWatcher w) {
+        Runnable do_redefinition = () -> {
+            w.accept("transforming calling function");
+            Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+        };
+        // This just prints something out to show we are running the Runnable.
+        Runnable say_nothing = () -> { w.accept("Not doing anything here"); };
+
+        // Try and redefine.
+        t.sayHi(say_nothing);
+        t.sayHi(do_redefinition);
+        t.sayHi(say_nothing);
+
+        // Print output of last run.
+        System.out.print(w.getOutput());
     }
-  }
-
-
-  // What follows is the base64 encoded representation of the following class:
-  //
-  // import java.util.function.Consumer;
-  //
-  // static class Transform {
-  //   private Consumer<String> reporter;
-  //   public Transform(Consumer<String> reporter) {
-  //     this.reporter = reporter;
-  //   }
-  //
-  //   private void Start() {
-  //     reporter.accept("Hello - private - Transformed");
-  //   }
-  //
-  //   private void Finish() {
-  //     reporter.accept("Goodbye - private - Transformed");
-  //   }
-  //
-  //   public void sayHi(Runnable r) {
-  //     reporter.accept("pre Start private method call - Transformed");
-  //     Start();
-  //     reporter.accept("post Start private method call - Transformed");
-  //     r.run();
-  //     reporter.accept("pre Finish private method call - Transformed");
-  //     Finish();
-  //     reporter.accept("post Finish private method call - Transformed");
-  //   }
-  // }
-  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
-    "yv66vgAAADQAOAoADgAfCQANACAIACELACIAIwgAJAgAJQoADQAmCAAnCwAoACkIACoKAA0AKwgA" +
-    "LAcALgcAMQEACHJlcG9ydGVyAQAdTGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjsBAAlTaWdu" +
-    "YXR1cmUBADFMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xhbmcvU3RyaW5nOz47" +
-    "AQAGPGluaXQ+AQAgKExqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7KVYBAARDb2RlAQAPTGlu" +
-    "ZU51bWJlclRhYmxlAQA0KExqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI8TGphdmEvbGFuZy9T" +
-    "dHJpbmc7PjspVgEABVN0YXJ0AQADKClWAQAGRmluaXNoAQAFc2F5SGkBABcoTGphdmEvbGFuZy9S" +
-    "dW5uYWJsZTspVgEAClNvdXJjZUZpbGUBAAxUZXN0OTE5LmphdmEMABMAGQwADwAQAQAdSGVsbG8g" +
-    "LSBwcml2YXRlIC0gVHJhbnNmb3JtZWQHADIMADMANAEAH0dvb2RieWUgLSBwcml2YXRlIC0gVHJh" +
-    "bnNmb3JtZWQBACtwcmUgU3RhcnQgcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAAY" +
-    "ABkBACxwb3N0IFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAcANQwANgAZ" +
-    "AQAscHJlIEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQMABoAGQEALXBv" +
-    "c3QgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAcANwEAFWFydC9UZXN0" +
-    "OTE5JFRyYW5zZm9ybQEACVRyYW5zZm9ybQEADElubmVyQ2xhc3NlcwEAEGphdmEvbGFuZy9PYmpl" +
-    "Y3QBABtqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXIBAAZhY2NlcHQBABUoTGphdmEvbGFuZy9P" +
-    "YmplY3Q7KVYBABJqYXZhL2xhbmcvUnVubmFibGUBAANydW4BAAthcnQvVGVzdDkxOQAgAA0ADgAA" +
-    "AAEAAgAPABAAAQARAAAAAgASAAQAAQATABQAAgAVAAAAKgACAAIAAAAKKrcAASortQACsQAAAAEA" +
-    "FgAAAA4AAwAAAAgABAAJAAkACgARAAAAAgAXAAIAGAAZAAEAFQAAACgAAgABAAAADCq0AAISA7kA" +
-    "BAIAsQAAAAEAFgAAAAoAAgAAAA0ACwAOAAIAGgAZAAEAFQAAACgAAgABAAAADCq0AAISBbkABAIA" +
-    "sQAAAAEAFgAAAAoAAgAAABEACwASAAEAGwAcAAEAFQAAAG8AAgACAAAAOyq0AAISBrkABAIAKrcA" +
-    "Byq0AAISCLkABAIAK7kACQEAKrQAAhIKuQAEAgAqtwALKrQAAhIMuQAEAgCxAAAAAQAWAAAAIgAI" +
-    "AAAAFQALABYADwAXABoAGAAgABkAKwAaAC8AGwA6ABwAAgAdAAAAAgAeADAAAAAKAAEADQAtAC8A" +
-    "CA==");
-  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
-    "ZGV4CjAzNQBeEZYBAAAAAAAAAAAAAAAAAAAAAAAAAACMBgAAcAAAAHhWNBIAAAAAAAAAAMgFAAAi" +
-    "AAAAcAAAAAkAAAD4AAAABAAAABwBAAABAAAATAEAAAcAAABUAQAAAQAAAIwBAADgBAAArAEAAKwB" +
-    "AACvAQAAsgEAALoBAAC+AQAAxAEAAMwBAADtAQAADAIAACUCAAA0AgAAWAIAAHgCAACXAgAAqwIA" +
-    "AMECAADVAgAA8wIAABIDAAAZAwAAJwMAADIDAAA1AwAAOQMAAEEDAABOAwAAVAMAAIMDAACxAwAA" +
-    "3wMAAAwEAAAWBAAAGwQAACIEAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAOAAAAEQAAABUAAAAV" +
-    "AAAACAAAAAAAAAAWAAAACAAAADQEAAAWAAAACAAAADwEAAAWAAAACAAAACwEAAAAAAcAHgAAAAAA" +
-    "AwACAAAAAAAAAAUAAAAAAAAAEgAAAAAAAgAgAAAABQAAAAIAAAAGAAAAHwAAAAcAAQAXAAAAAAAA" +
-    "AAAAAAAFAAAAAAAAABMAAACoBQAARAUAAAAAAAABKAABPAAGPGluaXQ+AAI+OwAEPjspVgAGRmlu" +
-    "aXNoAB9Hb29kYnllIC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVkAB1IZWxsbyAtIHByaXZhdGUgLSBU" +
-    "cmFuc2Zvcm1lZAAXTGFydC9UZXN0OTE5JFRyYW5zZm9ybTsADUxhcnQvVGVzdDkxOTsAIkxkYWx2" +
-    "aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNs" +
-    "YXNzOwAdTGRhbHZpay9hbm5vdGF0aW9uL1NpZ25hdHVyZTsAEkxqYXZhL2xhbmcvT2JqZWN0OwAU" +
-    "TGphdmEvbGFuZy9SdW5uYWJsZTsAEkxqYXZhL2xhbmcvU3RyaW5nOwAcTGphdmEvdXRpbC9mdW5j" +
-    "dGlvbi9Db25zdW1lcgAdTGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjsABVN0YXJ0AAxUZXN0" +
-    "OTE5LmphdmEACVRyYW5zZm9ybQABVgACVkwABmFjY2VwdAALYWNjZXNzRmxhZ3MABG5hbWUALXBv" +
-    "c3QgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAscG9zdCBTdGFydCBw" +
-    "cml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQALHByZSBGaW5pc2ggcHJpdmF0ZSBtZXRo" +
-    "b2QgY2FsbCAtIFRyYW5zZm9ybWVkACtwcmUgU3RhcnQgcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRy" +
-    "YW5zZm9ybWVkAAhyZXBvcnRlcgADcnVuAAVzYXlIaQAFdmFsdWUAAAAAAQAAAAcAAAABAAAABQAA" +
-    "AAEAAAAGAAAACAEABw4BAw8BAg8AEQAHDgEIDwANAAcOAQgPABUBAAcOAQgPAQMPAQgPAQMPAQgP" +
-    "AQMPAQgPAAACAAIAAQAAAEQEAAAGAAAAcBAEAAAAWwEAAA4AAwABAAIAAABQBAAACQAAAFQgAAAb" +
-    "AQYAAAByIAYAEAAOAAAAAwABAAIAAABYBAAACQAAAFQgAAAbAQcAAAByIAYAEAAOAAAABAACAAIA" +
-    "AABgBAAAKgAAAFQgAAAbAR0AAAByIAYAEABwEAIAAgBUIAAAGwEbAAAAciAGABAAchAFAAMAVCAA" +
-    "ABsBHAAAAHIgBgAQAHAQAQACAFQgAAAbARoAAAByIAYAEAAOAAABAwEAAgCBgAT8CAECmAkBArwJ" +
-    "AwHgCQICASEYAQIDAhgECBkXFAIEASEcBBcQFwEXDxcDAgQBIRwFFwAXEBcBFw8XBAAAAAIAAABc" +
-    "BQAAYgUAAAEAAABrBQAAAQAAAHkFAACMBQAAAQAAAAEAAAAAAAAAAAAAAJgFAAAAAAAAoAUAABAA" +
-    "AAAAAAAAAQAAAAAAAAABAAAAIgAAAHAAAAACAAAACQAAAPgAAAADAAAABAAAABwBAAAEAAAAAQAA" +
-    "AEwBAAAFAAAABwAAAFQBAAAGAAAAAQAAAIwBAAACIAAAIgAAAKwBAAABEAAAAwAAACwEAAADIAAA" +
-    "BAAAAEQEAAABIAAABAAAAHwEAAAAIAAAAQAAAEQFAAAEIAAABAAAAFwFAAADEAAAAwAAAIwFAAAG" +
-    "IAAAAQAAAKgFAAAAEAAAAQAAAMgFAAA=");
-
-  // A class that we can use to keep track of the output of this test.
-  private static class TestWatcher implements Consumer<String> {
-    private StringBuilder sb;
-    public TestWatcher() {
-      sb = new StringBuilder();
-    }
-
-    @Override
-    public void accept(String s) {
-      sb.append(s);
-      sb.append('\n');
-    }
-
-    public String getOutput() {
-      return sb.toString();
-    }
-  }
-
-  public static void run() {
-    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
-    TestWatcher w = new TestWatcher();
-    doTest(new Transform(w), w);
-  }
-
-  public static void doTest(Transform t, TestWatcher w) {
-    Runnable do_redefinition = () -> {
-      w.accept("transforming calling function");
-      Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
-    };
-    // This just prints something out to show we are running the Runnable.
-    Runnable say_nothing = () -> { w.accept("Not doing anything here"); };
-
-    // Try and redefine.
-    t.sayHi(say_nothing);
-    t.sayHi(do_redefinition);
-    t.sayHi(say_nothing);
-
-    // Print output of last run.
-    System.out.print(w.getOutput());
-  }
 }
diff --git a/test/920-objects/run b/test/920-objects/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/920-objects/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/920-objects/run.py b/test/920-objects/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/920-objects/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/921-hello-failure/run b/test/921-hello-failure/run
deleted file mode 100755
index 8be0ed4..0000000
--- a/test/921-hello-failure/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-
-./default-run "$@" --jvmti
diff --git a/test/921-hello-failure/run.py b/test/921-hello-failure/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/921-hello-failure/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/922-properties/run b/test/922-properties/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/922-properties/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/922-properties/run.py b/test/922-properties/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/922-properties/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/923-monitors/run b/test/923-monitors/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/923-monitors/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/923-monitors/run.py b/test/923-monitors/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/923-monitors/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/924-threads/run b/test/924-threads/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/924-threads/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/924-threads/run.py b/test/924-threads/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/924-threads/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/925-threadgroups/run b/test/925-threadgroups/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/925-threadgroups/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/925-threadgroups/run.py b/test/925-threadgroups/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/925-threadgroups/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/926-multi-obsolescence/run b/test/926-multi-obsolescence/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/926-multi-obsolescence/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/926-multi-obsolescence/run.py b/test/926-multi-obsolescence/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/926-multi-obsolescence/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/927-timers/run b/test/927-timers/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/927-timers/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/927-timers/run.py b/test/927-timers/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/927-timers/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/928-jni-table/run b/test/928-jni-table/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/928-jni-table/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/928-jni-table/run.py b/test/928-jni-table/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/928-jni-table/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/929-search/run b/test/929-search/run
deleted file mode 100755
index fb6b1b8..0000000
--- a/test/929-search/run
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# This test checks whether dex files can be injected into parent classloaders. App images preload
-# classes, which will make the injection moot. Turn off app images to avoid the issue.
-# Pass the correct `--secondary-class-loader-context` for the "-ex" jar.
-
-./default-run "$@" --jvmti \
-                   --no-app-image \
-                   --secondary-class-loader-context "PCL[$DEX_LOCATION/$TEST_NAME.jar]"
diff --git a/test/929-search/run.py b/test/929-search/run.py
new file mode 100644
index 0000000..012e279
--- /dev/null
+++ b/test/929-search/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  # This test checks whether dex files can be injected into parent classloaders. App images preload
+  # classes, which will make the injection moot. Turn off app images to avoid the issue.
+  # Pass the correct `--secondary-class-loader-context` for the "-ex" jar.
+
+  pcl = f"PCL[{ctx.env.DEX_LOCATION}/{ctx.env.TEST_NAME}.jar]"
+  ctx.default_run(
+      args, jvmti=True, app_image=False, secondary_class_loader_context=pcl)
diff --git a/test/930-hello-retransform/run b/test/930-hello-retransform/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/930-hello-retransform/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/930-hello-retransform/run.py b/test/930-hello-retransform/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/930-hello-retransform/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/931-agent-thread/run b/test/931-agent-thread/run
deleted file mode 100755
index 67923a7..0000000
--- a/test/931-agent-thread/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# This test checks whether dex files can be injected into parent classloaders. App images preload
-# classes, which will make the injection moot. Turn off app images to avoid the issue.
-
-./default-run "$@" --jvmti \
-                   --no-app-image
diff --git a/test/931-agent-thread/run.py b/test/931-agent-thread/run.py
new file mode 100644
index 0000000..4ac9127
--- /dev/null
+++ b/test/931-agent-thread/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  # This test checks whether dex files can be injected into parent classloaders. App images preload
+  # classes, which will make the injection moot. Turn off app images to avoid the issue.
+
+  ctx.default_run(args, jvmti=True, app_image=False)
diff --git a/test/932-transform-saves/run b/test/932-transform-saves/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/932-transform-saves/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/932-transform-saves/run.py b/test/932-transform-saves/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/932-transform-saves/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/933-misc-events/run b/test/933-misc-events/run
deleted file mode 100755
index 67923a7..0000000
--- a/test/933-misc-events/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# This test checks whether dex files can be injected into parent classloaders. App images preload
-# classes, which will make the injection moot. Turn off app images to avoid the issue.
-
-./default-run "$@" --jvmti \
-                   --no-app-image
diff --git a/test/933-misc-events/run.py b/test/933-misc-events/run.py
new file mode 100644
index 0000000..5a2efa9
--- /dev/null
+++ b/test/933-misc-events/run.py
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  # This test checks whether dex files can be injected into parent classloaders. App images preload
+  # classes, which will make the injection moot. Turn off app images to avoid the issue.
+  ctx.default_run(args, jvmti=True, app_image=False)
diff --git a/test/934-load-transform/run b/test/934-load-transform/run
deleted file mode 100755
index adb1a1c..0000000
--- a/test/934-load-transform/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --no-app-image
diff --git a/test/934-load-transform/run.py b/test/934-load-transform/run.py
new file mode 100644
index 0000000..23b8dc3
--- /dev/null
+++ b/test/934-load-transform/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, app_image=False)
diff --git a/test/935-non-retransformable/run b/test/935-non-retransformable/run
deleted file mode 100755
index adb1a1c..0000000
--- a/test/935-non-retransformable/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --no-app-image
diff --git a/test/935-non-retransformable/run.py b/test/935-non-retransformable/run.py
new file mode 100644
index 0000000..23b8dc3
--- /dev/null
+++ b/test/935-non-retransformable/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, app_image=False)
diff --git a/test/936-search-onload/run b/test/936-search-onload/run
deleted file mode 100755
index fb6b1b8..0000000
--- a/test/936-search-onload/run
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# This test checks whether dex files can be injected into parent classloaders. App images preload
-# classes, which will make the injection moot. Turn off app images to avoid the issue.
-# Pass the correct `--secondary-class-loader-context` for the "-ex" jar.
-
-./default-run "$@" --jvmti \
-                   --no-app-image \
-                   --secondary-class-loader-context "PCL[$DEX_LOCATION/$TEST_NAME.jar]"
diff --git a/test/936-search-onload/run.py b/test/936-search-onload/run.py
new file mode 100644
index 0000000..012e279
--- /dev/null
+++ b/test/936-search-onload/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  # This test checks whether dex files can be injected into parent classloaders. App images preload
+  # classes, which will make the injection moot. Turn off app images to avoid the issue.
+  # Pass the correct `--secondary-class-loader-context` for the "-ex" jar.
+
+  pcl = f"PCL[{ctx.env.DEX_LOCATION}/{ctx.env.TEST_NAME}.jar]"
+  ctx.default_run(
+      args, jvmti=True, app_image=False, secondary_class_loader_context=pcl)
diff --git a/test/937-hello-retransform-package/run b/test/937-hello-retransform-package/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/937-hello-retransform-package/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/937-hello-retransform-package/run.py b/test/937-hello-retransform-package/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/937-hello-retransform-package/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/938-load-transform-bcp/run b/test/938-load-transform-bcp/run
deleted file mode 100755
index adb1a1c..0000000
--- a/test/938-load-transform-bcp/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti --no-app-image
diff --git a/test/938-load-transform-bcp/run.py b/test/938-load-transform-bcp/run.py
new file mode 100644
index 0000000..23b8dc3
--- /dev/null
+++ b/test/938-load-transform-bcp/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True, app_image=False)
diff --git a/test/939-hello-transformation-bcp/run b/test/939-hello-transformation-bcp/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/939-hello-transformation-bcp/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/939-hello-transformation-bcp/run.py b/test/939-hello-transformation-bcp/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/939-hello-transformation-bcp/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/940-recursive-obsolete/run b/test/940-recursive-obsolete/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/940-recursive-obsolete/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/940-recursive-obsolete/run.py b/test/940-recursive-obsolete/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/940-recursive-obsolete/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/941-recursive-obsolete-jit/run b/test/941-recursive-obsolete-jit/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/941-recursive-obsolete-jit/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/941-recursive-obsolete-jit/run.py b/test/941-recursive-obsolete-jit/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/941-recursive-obsolete-jit/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/942-private-recursive/run b/test/942-private-recursive/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/942-private-recursive/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/942-private-recursive/run.py b/test/942-private-recursive/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/942-private-recursive/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/943-private-recursive-jit/run b/test/943-private-recursive-jit/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/943-private-recursive-jit/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/943-private-recursive-jit/run.py b/test/943-private-recursive-jit/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/943-private-recursive-jit/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/944-transform-classloaders/run b/test/944-transform-classloaders/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/944-transform-classloaders/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/944-transform-classloaders/run.py b/test/944-transform-classloaders/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/944-transform-classloaders/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/945-obsolete-native/run b/test/945-obsolete-native/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/945-obsolete-native/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/945-obsolete-native/run.py b/test/945-obsolete-native/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/945-obsolete-native/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/946-obsolete-throw/run b/test/946-obsolete-throw/run
deleted file mode 100755
index e92b873..0000000
--- a/test/946-obsolete-throw/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/946-obsolete-throw/run.py b/test/946-obsolete-throw/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/946-obsolete-throw/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/947-reflect-method/run b/test/947-reflect-method/run
deleted file mode 100755
index e92b873..0000000
--- a/test/947-reflect-method/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/947-reflect-method/run.py b/test/947-reflect-method/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/947-reflect-method/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/948-change-annotations/build b/test/948-change-annotations/build
deleted file mode 100755
index 898e2e5..0000000
--- a/test/948-change-annotations/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-build "$@" --experimental agents
diff --git a/test/948-change-annotations/build.py b/test/948-change-annotations/build.py
new file mode 100644
index 0000000..1613d3a
--- /dev/null
+++ b/test/948-change-annotations/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="agents")
diff --git a/test/948-change-annotations/run b/test/948-change-annotations/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/948-change-annotations/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/948-change-annotations/run.py b/test/948-change-annotations/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/948-change-annotations/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/949-in-memory-transform/run b/test/949-in-memory-transform/run
deleted file mode 100755
index e92b873..0000000
--- a/test/949-in-memory-transform/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/949-in-memory-transform/run.py b/test/949-in-memory-transform/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/949-in-memory-transform/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/950-redefine-intrinsic/run b/test/950-redefine-intrinsic/run
deleted file mode 100755
index e92b873..0000000
--- a/test/950-redefine-intrinsic/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/950-redefine-intrinsic/run.py b/test/950-redefine-intrinsic/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/950-redefine-intrinsic/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/951-threaded-obsolete/run b/test/951-threaded-obsolete/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/951-threaded-obsolete/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/951-threaded-obsolete/run.py b/test/951-threaded-obsolete/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/951-threaded-obsolete/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/952-invoke-custom/build b/test/952-invoke-custom/build
deleted file mode 100755
index e835517..0000000
--- a/test/952-invoke-custom/build
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Stop on failure.
-set -e
-
-export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.2.jar"
-
-# Build the transformer to apply to compiled classes.
-mkdir classes
-${JAVAC:-javac} ${JAVAC_ARGS} -cp "${ASM_JAR}" -d classes $(find util-src -name '*.java')
-${SOONG_ZIP} --jar -o transformer.jar -C classes -D classes
-rm -rf classes
-
-# Use API level 28 for invoke-custom bytecode support.
-USE_DESUGAR=false ./default-build "$@" --api-level 28
diff --git a/test/952-invoke-custom/build.py b/test/952-invoke-custom/build.py
new file mode 100644
index 0000000..166d3a8
--- /dev/null
+++ b/test/952-invoke-custom/build.py
@@ -0,0 +1,21 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build(use_desugar=False,
+                    api_level=28,
+                    javac_classpath=[ctx.test_dir / "transformer.jar"])
diff --git a/test/952-invoke-custom/generate-sources b/test/952-invoke-custom/generate-sources
new file mode 100755
index 0000000..4244f8c
--- /dev/null
+++ b/test/952-invoke-custom/generate-sources
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Copyright 2018 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.
+
+# Stop on failure.
+set -e
+
+export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.2.jar"
+
+# Build the transformer to apply to compiled classes.
+mkdir classes
+${JAVAC:-javac} ${JAVAC_ARGS} -cp "${ASM_JAR}" -d classes $(find util-src -name '*.java')
+${SOONG_ZIP} --jar -o transformer.jar -C classes -D classes
+rm -rf classes
diff --git a/test/952-invoke-custom/javac_post.sh b/test/952-invoke-custom/javac_post.sh
new file mode 100755
index 0000000..be5d8cf
--- /dev/null
+++ b/test/952-invoke-custom/javac_post.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e # Stop on error - the caller script may not have this set.
+
+export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.2.jar"
+
+# Move original classes to intermediate location.
+mv classes intermediate-classes
+mkdir classes
+
+# Transform intermediate classes.
+transformer_args="-cp ${ASM_JAR}:transformer.jar transformer.IndyTransformer"
+for class in intermediate-classes/*.class ; do
+  transformed_class=classes/$(basename ${class})
+  ${JAVA:-java} ${transformer_args} $PWD/${class} ${transformed_class}
+done
diff --git a/test/952-invoke-custom/javac_wrapper.sh b/test/952-invoke-custom/javac_wrapper.sh
deleted file mode 100755
index 8659030..0000000
--- a/test/952-invoke-custom/javac_wrapper.sh
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e # Stop on error - the caller script may not have this set.
-
-# Update arguments to add transformer and ASM to the compiler classpath.
-classpath="./transformer.jar:$ASM_JAR"
-args=(-cp $classpath)
-while [ $# -ne 0 ] ; do
-  case $1 in
-    -cp|-classpath|--class-path)
-      shift
-      shift
-      ;;
-    *)
-      args+=("$1")
-      shift
-      ;;
-  esac
-done
-
-# Compile.
-$JAVAC "${args[@]}"
-
-# Move original classes to intermediate location.
-mv classes intermediate-classes
-mkdir classes
-
-# Transform intermediate classes.
-transformer_args="-cp ${ASM_JAR}:transformer.jar transformer.IndyTransformer"
-for class in intermediate-classes/*.class ; do
-  transformed_class=classes/$(basename ${class})
-  ${JAVA:-java} ${transformer_args} $PWD/${class} ${transformed_class}
-done
diff --git a/test/952-invoke-custom/util-src/transformer/IndyTransformer.java b/test/952-invoke-custom/util-src/transformer/IndyTransformer.java
index 6401c54..ee04ff2 100644
--- a/test/952-invoke-custom/util-src/transformer/IndyTransformer.java
+++ b/test/952-invoke-custom/util-src/transformer/IndyTransformer.java
@@ -189,7 +189,7 @@
         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
         try (InputStream is = Files.newInputStream(inputClassPath)) {
             ClassReader cr = new ClassReader(is);
-            cr.accept(new BootstrapBuilder(Opcodes.ASM7, cw, callsiteMap), 0);
+            cr.accept(new BootstrapBuilder(Opcodes.ASM9, cw, callsiteMap), 0);
         }
         try (OutputStream os = Files.newOutputStream(outputClassPath)) {
             os.write(cw.toByteArray());
diff --git a/test/953-invoke-polymorphic-compiler/build b/test/953-invoke-polymorphic-compiler/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/953-invoke-polymorphic-compiler/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/953-invoke-polymorphic-compiler/build.py b/test/953-invoke-polymorphic-compiler/build.py
new file mode 100644
index 0000000..027dd53
--- /dev/null
+++ b/test/953-invoke-polymorphic-compiler/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="method-handles")
diff --git a/test/954-invoke-polymorphic-verifier/build b/test/954-invoke-polymorphic-verifier/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/954-invoke-polymorphic-verifier/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/954-invoke-polymorphic-verifier/build.py b/test/954-invoke-polymorphic-verifier/build.py
new file mode 100644
index 0000000..027dd53
--- /dev/null
+++ b/test/954-invoke-polymorphic-verifier/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="method-handles")
diff --git a/test/954-invoke-polymorphic-verifier/check b/test/954-invoke-polymorphic-verifier/check
deleted file mode 100755
index 3f9e6dc..0000000
--- a/test/954-invoke-polymorphic-verifier/check
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# Strip out temporary file path information and indices from output.
-sed -e "s/ [(]declaration of.*//" -e "s/\[0x[0-9A-F]*\] //g" "$2" > "$2.tmp"
-diff --strip-trailing-cr -q "$1" "$2.tmp" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4" >/dev/null
diff --git a/test/954-invoke-polymorphic-verifier/run.py b/test/954-invoke-polymorphic-verifier/run.py
new file mode 100644
index 0000000..bdb6301
--- /dev/null
+++ b/test/954-invoke-polymorphic-verifier/run.py
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args)
+
+  # Strip out temporary file path information and indices from output.
+  ctx.run(
+      fr"sed -i -e 's/ [(]declaration of.*//' -e 's/\[0x[0-9A-F]*\] //g' '{args.stdout_file}'"
+  )
diff --git a/test/955-methodhandles-smali/build b/test/955-methodhandles-smali/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/955-methodhandles-smali/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/955-methodhandles-smali/build.py b/test/955-methodhandles-smali/build.py
new file mode 100644
index 0000000..027dd53
--- /dev/null
+++ b/test/955-methodhandles-smali/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="method-handles")
diff --git a/test/956-methodhandles/build b/test/956-methodhandles/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/956-methodhandles/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/956-methodhandles/build.py b/test/956-methodhandles/build.py
new file mode 100644
index 0000000..027dd53
--- /dev/null
+++ b/test/956-methodhandles/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="method-handles")
diff --git a/test/957-methodhandle-transforms/build b/test/957-methodhandle-transforms/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/957-methodhandle-transforms/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/957-methodhandle-transforms/build.py b/test/957-methodhandle-transforms/build.py
new file mode 100644
index 0000000..027dd53
--- /dev/null
+++ b/test/957-methodhandle-transforms/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="method-handles")
diff --git a/test/957-methodhandle-transforms/src/Main.java b/test/957-methodhandle-transforms/src/Main.java
index 2b16485..3ee270d 100644
--- a/test/957-methodhandle-transforms/src/Main.java
+++ b/test/957-methodhandle-transforms/src/Main.java
@@ -1557,7 +1557,7 @@
     try {
       adapter = MethodHandles.collectArguments(target, 3, filter);
       fail();
-    } catch (IndexOutOfBoundsException expected) {
+    } catch (IndexOutOfBoundsException | IllegalArgumentException expected) {
     }
 
     // Mismatch in filter return type.
diff --git a/test/958-methodhandle-stackframe/build b/test/958-methodhandle-stackframe/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/958-methodhandle-stackframe/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/958-methodhandle-stackframe/build.py b/test/958-methodhandle-stackframe/build.py
new file mode 100644
index 0000000..027dd53
--- /dev/null
+++ b/test/958-methodhandle-stackframe/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="method-handles")
diff --git a/test/959-invoke-polymorphic-accessors/build b/test/959-invoke-polymorphic-accessors/build
deleted file mode 100644
index 2b0b2c1..0000000
--- a/test/959-invoke-polymorphic-accessors/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/959-invoke-polymorphic-accessors/build.py b/test/959-invoke-polymorphic-accessors/build.py
new file mode 100644
index 0000000..027dd53
--- /dev/null
+++ b/test/959-invoke-polymorphic-accessors/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="method-handles")
diff --git a/test/959-invoke-polymorphic-accessors/src/Main.java b/test/959-invoke-polymorphic-accessors/src/Main.java
index 03fd285..73b7746 100644
--- a/test/959-invoke-polymorphic-accessors/src/Main.java
+++ b/test/959-invoke-polymorphic-accessors/src/Main.java
@@ -20,6 +20,8 @@
 
 public class Main {
 
+    private static final boolean DALVIK_RUN = "Dalvik".equals(System.getProperty("java.vm.name"));
+
     public static class ValueHolder {
         public boolean m_z = false;
         public byte m_b = 0;
@@ -793,6 +795,10 @@
                 Long z = (Long) h0.invoke(valueHolder);
                 fail();
             } catch (WrongMethodTypeException expected) {}
+            try {
+                int x = (int) h0.invokeExact((ValueHolder) null);
+                fail();
+            } catch (NullPointerException expected) {}
         }
 
         /*package*/ static Number getDoubleAsNumber() {
@@ -822,6 +828,10 @@
               h0.invoke(valueHolder, (Float) null);
               fail();
             } catch (NullPointerException expected) {}
+            try {
+                h0.invoke((ValueHolder) null, Float.valueOf(1.0f));
+                fail();
+              } catch (NullPointerException expected) {}
 
             // Test that type conversion checks work on small field types.
             short temp = (short) s0.invoke(valueHolder, new Byte((byte) 45));
@@ -951,8 +961,10 @@
                 MethodHandles.lookup().unreflectSetter(f).invokeExact(v, 'A');
                 assertEquals('A', (char) MethodHandles.lookup().unreflectGetter(f).invokeExact(v));
             }
-            {
+            if (DALVIK_RUN) {
                 // public static final field test
+                // for JVM it is not possible to get the unreflected setter for a static final
+                // field, see b/242985782
                 Field f = ValueHolder.class.getDeclaredField("s_fi");
                 try {
                     MethodHandles.lookup().unreflectSetter(f);
@@ -1003,8 +1015,10 @@
                     fail();
                 } catch (IllegalAccessException expected) {}
             }
-            {
+            if (DALVIK_RUN) {
                 // private static final field test
+                // for JVM it is not possible to get the unreflected setter for a static final
+                // field, see b/242985782
                 Field f = ValueHolder.class.getDeclaredField("s_fz");  // private static final field
                 try {
                     MethodHandles.lookup().unreflectSetter(f);
diff --git a/test/960-default-smali/build b/test/960-default-smali/build
deleted file mode 100755
index 44d6bd2..0000000
--- a/test/960-default-smali/build
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 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.
-
-# make us exit on a failure
-set -e
-
-# Generate the Main.java file or fail
-${ANDROID_BUILD_TOP}/art/test/utils/python/generate_java_main.py ./src
-
-./default-build "$@" --experimental default-methods
diff --git a/test/960-default-smali/build.py b/test/960-default-smali/build.py
new file mode 100644
index 0000000..4c6b53c
--- /dev/null
+++ b/test/960-default-smali/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build(api_level="default-methods")
diff --git a/test/960-default-smali/generate-sources b/test/960-default-smali/generate-sources
new file mode 100755
index 0000000..1fffc71
--- /dev/null
+++ b/test/960-default-smali/generate-sources
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# make us exit on a failure
+set -e
+
+# Generate the Main.java file or fail
+${ANDROID_BUILD_TOP}/art/test/utils/python/generate_java_main.py ./src
diff --git a/test/961-default-iface-resolution-gen/build b/test/961-default-iface-resolution-gen/build
deleted file mode 100755
index b9b36d0..0000000
--- a/test/961-default-iface-resolution-gen/build
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 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.
-
-# make us exit on a failure
-set -e
-
-mkdir -p ./src
-
-# Generate the smali files and expected-stdout.txt or fail
-./util-src/generate_java.py ./src ./expected-stdout.txt
-
-./default-build "$@" --experimental default-methods
diff --git a/test/961-default-iface-resolution-gen/build.py b/test/961-default-iface-resolution-gen/build.py
new file mode 100644
index 0000000..4c6b53c
--- /dev/null
+++ b/test/961-default-iface-resolution-gen/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build(api_level="default-methods")
diff --git a/test/961-default-iface-resolution-gen/expected-stdout.txt b/test/961-default-iface-resolution-gen/expected-stdout.txt
index 1ddd65d..c146358 100644
--- a/test/961-default-iface-resolution-gen/expected-stdout.txt
+++ b/test/961-default-iface-resolution-gen/expected-stdout.txt
@@ -1 +1 @@
-This file is generated by util-src/generate_smali.py do not directly modify!
+[DO_NOT_UPDATE] This file is generated by util-src/generate_smali.py do not directly modify!
diff --git a/test/961-default-iface-resolution-gen/generate-sources b/test/961-default-iface-resolution-gen/generate-sources
new file mode 100755
index 0000000..4d12e5f
--- /dev/null
+++ b/test/961-default-iface-resolution-gen/generate-sources
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# make us exit on a failure
+set -e
+
+mkdir -p ./src
+
+# Generate the smali files and expected-stdout.txt or fail
+./util-src/generate_java.py ./src ./expected-stdout.txt
diff --git a/test/961-default-iface-resolution-gen/run b/test/961-default-iface-resolution-gen/run
deleted file mode 100755
index fdcd2a8..0000000
--- a/test/961-default-iface-resolution-gen/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-# Run with a 2 minute default dex2oat timeout and a 2.5 minute hard dex2oat timeout.
-./default-run "$@" --dex2oat-timeout 120 --dex2oat-rt-timeout 180
diff --git a/test/961-default-iface-resolution-gen/run.py b/test/961-default-iface-resolution-gen/run.py
new file mode 100644
index 0000000..7de21e3
--- /dev/null
+++ b/test/961-default-iface-resolution-gen/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+
+def run(ctx, args):
+  # Run with a 2 minute default dex2oat timeout and a 2.5 minute hard dex2oat timeout.
+  ctx.default_run(args, dex2oat_timeout=120, dex2oat_rt_timeout=180)
diff --git a/test/962-iface-static/build b/test/962-iface-static/build
deleted file mode 100644
index 82f4931..0000000
--- a/test/962-iface-static/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental default-methods
diff --git a/test/962-iface-static/build.py b/test/962-iface-static/build.py
new file mode 100644
index 0000000..3e0ecd5
--- /dev/null
+++ b/test/962-iface-static/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="default-methods")
diff --git a/test/964-default-iface-init-gen/build b/test/964-default-iface-init-gen/build
deleted file mode 100755
index b9b36d0..0000000
--- a/test/964-default-iface-init-gen/build
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 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.
-
-# make us exit on a failure
-set -e
-
-mkdir -p ./src
-
-# Generate the smali files and expected-stdout.txt or fail
-./util-src/generate_java.py ./src ./expected-stdout.txt
-
-./default-build "$@" --experimental default-methods
diff --git a/test/964-default-iface-init-gen/build.py b/test/964-default-iface-init-gen/build.py
new file mode 100644
index 0000000..4c6b53c
--- /dev/null
+++ b/test/964-default-iface-init-gen/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build(api_level="default-methods")
diff --git a/test/964-default-iface-init-gen/expected-stdout.txt b/test/964-default-iface-init-gen/expected-stdout.txt
index 1ddd65d..c146358 100644
--- a/test/964-default-iface-init-gen/expected-stdout.txt
+++ b/test/964-default-iface-init-gen/expected-stdout.txt
@@ -1 +1 @@
-This file is generated by util-src/generate_smali.py do not directly modify!
+[DO_NOT_UPDATE] This file is generated by util-src/generate_smali.py do not directly modify!
diff --git a/test/964-default-iface-init-gen/generate-sources b/test/964-default-iface-init-gen/generate-sources
new file mode 100755
index 0000000..4d12e5f
--- /dev/null
+++ b/test/964-default-iface-init-gen/generate-sources
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# make us exit on a failure
+set -e
+
+mkdir -p ./src
+
+# Generate the smali files and expected-stdout.txt or fail
+./util-src/generate_java.py ./src ./expected-stdout.txt
diff --git a/test/965-default-verify/Android.bp b/test/965-default-verify/Android.bp
new file mode 100644
index 0000000..680c3a2
--- /dev/null
+++ b/test/965-default-verify/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `965-default-verify`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-965-default-verify-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-965-default-verify",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-965-default-verify-src"
+    ],
+    data: [
+        ":art-run-test-965-default-verify-expected-stdout",
+        ":art-run-test-965-default-verify-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-965-default-verify-expected-stdout",
+    out: ["art-run-test-965-default-verify-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-965-default-verify-expected-stderr",
+    out: ["art-run-test-965-default-verify-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/966-default-conflict/Android.bp b/test/966-default-conflict/Android.bp
new file mode 100644
index 0000000..19217fc
--- /dev/null
+++ b/test/966-default-conflict/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `966-default-conflict`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-966-default-conflict-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-966-default-conflict",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-966-default-conflict-src"
+    ],
+    data: [
+        ":art-run-test-966-default-conflict-expected-stdout",
+        ":art-run-test-966-default-conflict-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-966-default-conflict-expected-stdout",
+    out: ["art-run-test-966-default-conflict-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-966-default-conflict-expected-stderr",
+    out: ["art-run-test-966-default-conflict-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/967-default-ame/Android.bp b/test/967-default-ame/Android.bp
new file mode 100644
index 0000000..0268bf5
--- /dev/null
+++ b/test/967-default-ame/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `967-default-ame`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+    name: "art-run-test-967-default-ame-src",
+    defaults: ["art-run-test-defaults"],
+    srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-967-default-ame",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src2/**/*.java"],
+    static_libs: [
+        "art-run-test-967-default-ame-src"
+    ],
+    data: [
+        ":art-run-test-967-default-ame-expected-stdout",
+        ":art-run-test-967-default-ame-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-967-default-ame-expected-stdout",
+    out: ["art-run-test-967-default-ame-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-967-default-ame-expected-stderr",
+    out: ["art-run-test-967-default-ame-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/968-default-partial-compile-gen/build b/test/968-default-partial-compile-gen/build
deleted file mode 100755
index 04532b0..0000000
--- a/test/968-default-partial-compile-gen/build
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 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.
-
-# make us exit on a failure
-set -e
-
-if [[ $@ == *"--jvm"* ]]; then
-  # Build the Java files if we are running a --jvm test
-  mkdir -p classes
-  mkdir -p src
-  echo "${JAVAC} ${JAVAC_ARGS} \$@" >> ./javac_exec.sh
-  # This will use java_exec.sh to execute the javac compiler. It will place the
-  # compiled class files in ./classes and the expected values in expected-stdout.txt
-  #
-  # After this the src directory will contain the final versions of all files.
-  ./util-src/generate_java.py ./javac_exec.sh ./src ./classes ./expected-stdout.txt ./build_log
-else
-  mkdir -p ./smali
-  # Generate the smali files and expected-stdout.txt or fail
-  ./util-src/generate_smali.py ./smali ./expected-stdout.txt
-  # Use the default build script
-  ./default-build "$@" --experimental default-methods
-fi
diff --git a/test/968-default-partial-compile-gen/build.py b/test/968-default-partial-compile-gen/build.py
new file mode 100644
index 0000000..435be54
--- /dev/null
+++ b/test/968-default-partial-compile-gen/build.py
@@ -0,0 +1,23 @@
+#
+# Copyright (C) 2022 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.
+
+import os
+
+
+def build(ctx):
+  ctx.bash("./generate-sources --" + ctx.mode)
+  if ctx.jvm:
+    return
+  ctx.default_build(api_level="default-methods")
diff --git a/test/968-default-partial-compile-gen/expected-stdout.txt b/test/968-default-partial-compile-gen/expected-stdout.txt
index 1ddd65d..c146358 100644
--- a/test/968-default-partial-compile-gen/expected-stdout.txt
+++ b/test/968-default-partial-compile-gen/expected-stdout.txt
@@ -1 +1 @@
-This file is generated by util-src/generate_smali.py do not directly modify!
+[DO_NOT_UPDATE] This file is generated by util-src/generate_smali.py do not directly modify!
diff --git a/test/968-default-partial-compile-gen/generate-sources b/test/968-default-partial-compile-gen/generate-sources
new file mode 100755
index 0000000..83aad65
--- /dev/null
+++ b/test/968-default-partial-compile-gen/generate-sources
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# make us exit on a failure
+set -e
+
+if [[ $@ == *"--jvm"* ]]; then
+  # Build the Java files if we are running a --jvm test
+  mkdir -p classes
+  mkdir -p src
+  echo "${JAVAC} ${JAVAC_ARGS} \$@" >> ./javac_exec.sh
+  # This will use java_exec.sh to execute the javac compiler. It will place the
+  # compiled class files in ./classes and the expected values in expected-stdout.txt
+  #
+  # After this the src directory will contain the final versions of all files.
+  ./util-src/generate_java.py ./javac_exec.sh ./src ./classes ./expected-stdout.txt ./build_log
+else
+  mkdir -p ./smali
+  # Generate the smali files and expected-stdout.txt or fail
+  ./util-src/generate_smali.py ./smali ./expected-stdout.txt
+fi
diff --git a/test/968-default-partial-compile-gen/util-src/generate_java.py b/test/968-default-partial-compile-gen/util-src/generate_java.py
index a4a4a4d..6027df9 100755
--- a/test/968-default-partial-compile-gen/util-src/generate_java.py
+++ b/test/968-default-partial-compile-gen/util-src/generate_java.py
@@ -69,7 +69,6 @@
     files = list(map(str, files))
     cmd = ['sh', '-a', '-e', '--', str(self.javac)] + args + sorted(files)
     subprocess.check_call(cmd)
-    print("Compiled {} files".format(len(files)))
 
   def execute(self):
     """
@@ -104,7 +103,6 @@
       self.compile_files("-d {outdir} -cp {outdir}".format(outdir = self.classes_dir), files)
       # Remove these from the set of interfaces to be compiled.
       ifaces -= tops
-    print("Finished compiling all files.")
     return
 
 def main(argv):
@@ -125,7 +123,6 @@
 
   with expected_txt.open('w') as out:
     print(mainclass.get_expected(), file=out)
-  print("Wrote expected output")
 
   Compiler(all_files, javac_exec, temp_dir, classes_dir).execute()
 
diff --git a/test/969-iface-super/build b/test/969-iface-super/build
deleted file mode 100755
index 44d6bd2..0000000
--- a/test/969-iface-super/build
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 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.
-
-# make us exit on a failure
-set -e
-
-# Generate the Main.java file or fail
-${ANDROID_BUILD_TOP}/art/test/utils/python/generate_java_main.py ./src
-
-./default-build "$@" --experimental default-methods
diff --git a/test/969-iface-super/build.py b/test/969-iface-super/build.py
new file mode 100644
index 0000000..4c6b53c
--- /dev/null
+++ b/test/969-iface-super/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build(api_level="default-methods")
diff --git a/test/969-iface-super/generate-sources b/test/969-iface-super/generate-sources
new file mode 100755
index 0000000..1fffc71
--- /dev/null
+++ b/test/969-iface-super/generate-sources
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# make us exit on a failure
+set -e
+
+# Generate the Main.java file or fail
+${ANDROID_BUILD_TOP}/art/test/utils/python/generate_java_main.py ./src
diff --git a/test/970-iface-super-resolution-gen/build b/test/970-iface-super-resolution-gen/build
deleted file mode 100755
index 6eecd71..0000000
--- a/test/970-iface-super-resolution-gen/build
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 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.
-
-# make us exit on a failure
-set -e
-
-# Should we compile with Java source code. By default we will use Smali.
-USES_JAVA_SOURCE="false"
-if [[ $@ == *"--jvm"* ]]; then
-  # Build the Java files
-  mkdir -p src
-  mkdir -p src2
-  ./util-src/generate_java.py ./src2 ./src ./expected-stdout.txt
-else
-  # Generate the smali files and expected-stdout.txt or fail
-  mkdir -p smali
-  ./util-src/generate_smali.py ./smali ./expected-stdout.txt
-fi
-
-./default-build "$@" --experimental default-methods
diff --git a/test/970-iface-super-resolution-gen/build.py b/test/970-iface-super-resolution-gen/build.py
new file mode 100644
index 0000000..114e452
--- /dev/null
+++ b/test/970-iface-super-resolution-gen/build.py
@@ -0,0 +1,21 @@
+#
+# Copyright (C) 2022 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.
+
+import os
+
+
+def build(ctx):
+  ctx.bash("./generate-sources --" + ctx.mode)
+  ctx.default_build(api_level="default-methods")
diff --git a/test/970-iface-super-resolution-gen/expected-stdout.txt b/test/970-iface-super-resolution-gen/expected-stdout.txt
index 1ddd65d..c146358 100644
--- a/test/970-iface-super-resolution-gen/expected-stdout.txt
+++ b/test/970-iface-super-resolution-gen/expected-stdout.txt
@@ -1 +1 @@
-This file is generated by util-src/generate_smali.py do not directly modify!
+[DO_NOT_UPDATE] This file is generated by util-src/generate_smali.py do not directly modify!
diff --git a/test/970-iface-super-resolution-gen/generate-sources b/test/970-iface-super-resolution-gen/generate-sources
new file mode 100755
index 0000000..175497f
--- /dev/null
+++ b/test/970-iface-super-resolution-gen/generate-sources
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# make us exit on a failure
+set -e
+
+# Should we compile with Java source code. By default we will use Smali.
+USES_JAVA_SOURCE="false"
+if [[ $@ == *"--jvm"* ]]; then
+  # Build the Java files
+  mkdir -p src
+  mkdir -p src2
+  ./util-src/generate_java.py ./src2 ./src ./expected-stdout.txt
+else
+  # Generate the smali files and expected-stdout.txt or fail
+  mkdir -p smali
+  ./util-src/generate_smali.py ./smali ./expected-stdout.txt
+fi
diff --git a/test/971-iface-super/build b/test/971-iface-super/build
deleted file mode 100755
index 04532b0..0000000
--- a/test/971-iface-super/build
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 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.
-
-# make us exit on a failure
-set -e
-
-if [[ $@ == *"--jvm"* ]]; then
-  # Build the Java files if we are running a --jvm test
-  mkdir -p classes
-  mkdir -p src
-  echo "${JAVAC} ${JAVAC_ARGS} \$@" >> ./javac_exec.sh
-  # This will use java_exec.sh to execute the javac compiler. It will place the
-  # compiled class files in ./classes and the expected values in expected-stdout.txt
-  #
-  # After this the src directory will contain the final versions of all files.
-  ./util-src/generate_java.py ./javac_exec.sh ./src ./classes ./expected-stdout.txt ./build_log
-else
-  mkdir -p ./smali
-  # Generate the smali files and expected-stdout.txt or fail
-  ./util-src/generate_smali.py ./smali ./expected-stdout.txt
-  # Use the default build script
-  ./default-build "$@" --experimental default-methods
-fi
diff --git a/test/971-iface-super/build.py b/test/971-iface-super/build.py
new file mode 100644
index 0000000..435be54
--- /dev/null
+++ b/test/971-iface-super/build.py
@@ -0,0 +1,23 @@
+#
+# Copyright (C) 2022 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.
+
+import os
+
+
+def build(ctx):
+  ctx.bash("./generate-sources --" + ctx.mode)
+  if ctx.jvm:
+    return
+  ctx.default_build(api_level="default-methods")
diff --git a/test/971-iface-super/expected-stdout.txt b/test/971-iface-super/expected-stdout.txt
index 1ddd65d..c146358 100644
--- a/test/971-iface-super/expected-stdout.txt
+++ b/test/971-iface-super/expected-stdout.txt
@@ -1 +1 @@
-This file is generated by util-src/generate_smali.py do not directly modify!
+[DO_NOT_UPDATE] This file is generated by util-src/generate_smali.py do not directly modify!
diff --git a/test/971-iface-super/generate-sources b/test/971-iface-super/generate-sources
new file mode 100755
index 0000000..83aad65
--- /dev/null
+++ b/test/971-iface-super/generate-sources
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# make us exit on a failure
+set -e
+
+if [[ $@ == *"--jvm"* ]]; then
+  # Build the Java files if we are running a --jvm test
+  mkdir -p classes
+  mkdir -p src
+  echo "${JAVAC} ${JAVAC_ARGS} \$@" >> ./javac_exec.sh
+  # This will use java_exec.sh to execute the javac compiler. It will place the
+  # compiled class files in ./classes and the expected values in expected-stdout.txt
+  #
+  # After this the src directory will contain the final versions of all files.
+  ./util-src/generate_java.py ./javac_exec.sh ./src ./classes ./expected-stdout.txt ./build_log
+else
+  mkdir -p ./smali
+  # Generate the smali files and expected-stdout.txt or fail
+  ./util-src/generate_smali.py ./smali ./expected-stdout.txt
+fi
diff --git a/test/971-iface-super/util-src/generate_java.py b/test/971-iface-super/util-src/generate_java.py
index dafe7f5..c043a4a 100755
--- a/test/971-iface-super/util-src/generate_java.py
+++ b/test/971-iface-super/util-src/generate_java.py
@@ -69,7 +69,6 @@
     files = list(map(str, files))
     cmd = ['sh', '-a', '-e', '--', str(self.javac)] + args + sorted(files)
     subprocess.check_call(cmd)
-    print("Compiled {} files".format(len(files)))
 
   def execute(self):
     """
@@ -108,7 +107,6 @@
         self.compile_files("-d {outdir} -cp {outdir}".format(outdir = self.classes_dir), files)
         # Remove these from the set of interfaces to be compiled.
         ifaces -= tops
-    print("Finished compiling all files.")
     return
 
 def main(argv):
@@ -129,7 +127,6 @@
 
   with expected_txt.open('w') as out:
     print(mainclass.get_expected(), file=out)
-  print("Wrote expected output")
 
   Compiler(all_files, javac_exec, temp_dir, classes_dir).execute()
 
diff --git a/test/975-iface-private/build b/test/975-iface-private/build
deleted file mode 100755
index 14230c2..0000000
--- a/test/975-iface-private/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental default-methods
diff --git a/test/975-iface-private/build.py b/test/975-iface-private/build.py
new file mode 100644
index 0000000..3e0ecd5
--- /dev/null
+++ b/test/975-iface-private/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="default-methods")
diff --git a/test/978-virtual-interface/build b/test/978-virtual-interface/build
deleted file mode 100755
index 14230c2..0000000
--- a/test/978-virtual-interface/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental default-methods
diff --git a/test/978-virtual-interface/build.py b/test/978-virtual-interface/build.py
new file mode 100644
index 0000000..3e0ecd5
--- /dev/null
+++ b/test/978-virtual-interface/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(api_level="default-methods")
diff --git a/test/979-const-method-handle/build b/test/979-const-method-handle/build
deleted file mode 100755
index fa6a0ea..0000000
--- a/test/979-const-method-handle/build
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# make us exit on a failure
-set -e
-
-export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.2.jar"
-
-# Build the transformer to apply to compiled classes.
-mkdir classes
-${JAVAC:-javac} ${JAVAC_ARGS} -cp "${ASM_JAR}" -d classes $(find util-src -name '*.java')
-${SOONG_ZIP} --jar -o transformer.jar -C classes -D classes
-rm -rf classes
-
-# Use API level 28 for DEX file support constant method handles.
-./default-build "$@" --api-level 28
diff --git a/test/979-const-method-handle/build.py b/test/979-const-method-handle/build.py
new file mode 100644
index 0000000..4eb0ccb
--- /dev/null
+++ b/test/979-const-method-handle/build.py
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.bash("./generate-sources")
+  ctx.default_build(api_level=28)
diff --git a/test/979-const-method-handle/generate-sources b/test/979-const-method-handle/generate-sources
new file mode 100755
index 0000000..0cd94d1
--- /dev/null
+++ b/test/979-const-method-handle/generate-sources
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# Copyright 2018 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.
+
+# make us exit on a failure
+set -e
+
+export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.2.jar"
+
+# Build the transformer to apply to compiled classes.
+mkdir classes
+${JAVAC:-javac} ${JAVAC_ARGS} -cp "${ASM_JAR}" -d classes $(find util-src -name '*.java')
+${SOONG_ZIP} --jar -o transformer.jar -C classes -D classes
+rm -rf classes
+
+# Add annotation src files to our compiler inputs.
+cp -r util-src/annotations src/
diff --git a/test/979-const-method-handle/javac_post.sh b/test/979-const-method-handle/javac_post.sh
new file mode 100755
index 0000000..1be31d5
--- /dev/null
+++ b/test/979-const-method-handle/javac_post.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e
+
+export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.2.jar"
+
+# Move original classes to intermediate location.
+mv classes intermediate-classes
+mkdir classes
+
+# Transform intermediate classes.
+transformer_args="-cp ${ASM_JAR}:$PWD/transformer.jar transformer.ConstantTransformer"
+for class in intermediate-classes/*.class ; do
+  transformed_class=classes/$(basename ${class})
+  ${JAVA:-java} ${transformer_args} ${class} ${transformed_class}
+done
+
+# Remove class which we want missing at runtime.
+rm classes/MissingType.class
diff --git a/test/979-const-method-handle/javac_wrapper.sh b/test/979-const-method-handle/javac_wrapper.sh
deleted file mode 100755
index 77b6bc3..0000000
--- a/test/979-const-method-handle/javac_wrapper.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-set -e
-
-# Add annotation src files to our compiler inputs.
-asrcs=util-src/annotations/*.java
-
-# Compile.
-$JAVAC "$@" $asrcs
-
-# Move original classes to intermediate location.
-mv classes intermediate-classes
-mkdir classes
-
-# Transform intermediate classes.
-transformer_args="-cp ${ASM_JAR}:$PWD/transformer.jar transformer.ConstantTransformer"
-for class in intermediate-classes/*.class ; do
-  transformed_class=classes/$(basename ${class})
-  ${JAVA:-java} ${transformer_args} ${class} ${transformed_class}
-done
diff --git a/test/979-const-method-handle/src/Main.java b/test/979-const-method-handle/src/Main.java
index 5368a22..72d529b 100644
--- a/test/979-const-method-handle/src/Main.java
+++ b/test/979-const-method-handle/src/Main.java
@@ -72,6 +72,14 @@
         return null;
     }
 
+    @ConstantMethodType(
+            returnType = void.class,
+            parameterTypes = {MissingType.class})
+    private static MethodType missingType() {
+        unreachable();
+        return null;
+    }
+
     private static void repeatConstMethodType0(MethodType expected) {
         System.out.print("repeatConstMethodType0(");
         System.out.print(expected);
@@ -189,6 +197,16 @@
         return null;
     }
 
+    @ConstantMethodHandle(
+            kind = ConstantMethodHandle.STATIC_GET,
+            owner = "PrivateMember",
+            fieldOrMethodName = "privateField",
+            descriptor = "I")
+    private static MethodHandle getPrivateField() {
+        unreachable();
+        return null;
+    }
+
     private static void repeatConstMethodHandle() throws Throwable {
         System.out.println("repeatConstMethodHandle()");
         String[] values = {"A", "B", "C"};
@@ -243,5 +261,37 @@
         System.out.println("Stack: capacity was " + stack.capacity());
         stackTrim().invokeExact(stack);
         System.out.println("Stack: capacity is " + stack.capacity());
+
+        // We used to not report in the compiler that loading a ConstMethodHandle/ConstMethodType
+        // can throw, which meant we were not catching the exception in the situation where we
+        // inline the loading.
+        try {
+          $inline$getPrivateField();
+          System.out.println("Expected IllegalAccessError");
+        } catch (IllegalAccessError e) {
+          // expected
+        }
+
+        try {
+          $inline$missingType();
+          System.out.println("Expected NoClassDefFoundError");
+        } catch (NoClassDefFoundError e) {
+          // expected
+        }
     }
+
+    public static void $inline$getPrivateField() {
+      getPrivateField();
+    }
+
+    public static void $inline$missingType() {
+      missingType();
+    }
+}
+
+class PrivateMember {
+  private static int privateField;
+}
+
+class MissingType {
 }
diff --git a/test/980-redefine-object/check b/test/980-redefine-object/check
deleted file mode 100755
index 80a3cd7..0000000
--- a/test/980-redefine-object/check
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-# The number of paused background threads (and therefore InterruptedExceptions)
-# can change so we will just delete their lines from the log.
-
-cat "$2" \
-    | sed "/Object allocated of type 'java\.lang\.InterruptedException'/d" \
-    | sed "/Object allocated of type 'java\.lang\.Long'/d" \
-    | diff --strip-trailing-cr -q "$1" - >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4" >/dev/null
diff --git a/test/980-redefine-object/run b/test/980-redefine-object/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/980-redefine-object/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/980-redefine-object/run.py b/test/980-redefine-object/run.py
new file mode 100644
index 0000000..c45595d
--- /dev/null
+++ b/test/980-redefine-object/run.py
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
+
+  # The number of paused background threads (and therefore InterruptedExceptions)
+  # can change so we will just delete their lines from the log.
+  ctx.run(
+      fr"""sed -i -E "/Object allocated of type 'java\.lang\.(InterruptedException|Long)'/d" '{args.stdout_file}'"""
+  )
diff --git a/test/981-dedup-original-dex/run b/test/981-dedup-original-dex/run
deleted file mode 100755
index e92b873..0000000
--- a/test/981-dedup-original-dex/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/981-dedup-original-dex/run.py b/test/981-dedup-original-dex/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/981-dedup-original-dex/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/982-ok-no-retransform/run b/test/982-ok-no-retransform/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/982-ok-no-retransform/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/982-ok-no-retransform/run.py b/test/982-ok-no-retransform/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/982-ok-no-retransform/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/983-source-transform-verify/run b/test/983-source-transform-verify/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/983-source-transform-verify/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/983-source-transform-verify/run.py b/test/983-source-transform-verify/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/983-source-transform-verify/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/983-source-transform-verify/source_transform_art.cc b/test/983-source-transform-verify/source_transform_art.cc
index a1916a0..289874b 100644
--- a/test/983-source-transform-verify/source_transform_art.cc
+++ b/test/983-source-transform-verify/source_transform_art.cc
@@ -41,12 +41,9 @@
   CHECK_LE(static_cast<jint>(header_file_size), class_data_len);
   class_data_len = static_cast<jint>(header_file_size);
 
-  const ArtDexFileLoader dex_file_loader;
+  ArtDexFileLoader dex_file_loader(class_data, class_data_len, "fake_location.dex");
   std::string error;
-  std::unique_ptr<const DexFile> dex(dex_file_loader.Open(class_data,
-                                                          class_data_len,
-                                                          "fake_location.dex",
-                                                          /*location_checksum*/ 0,
+  std::unique_ptr<const DexFile> dex(dex_file_loader.Open(/*location_checksum*/ 0,
                                                           /*oat_dex_file*/ nullptr,
                                                           /*verify*/ true,
                                                           /*verify_checksum*/ true,
diff --git a/test/984-obsolete-invoke/run b/test/984-obsolete-invoke/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/984-obsolete-invoke/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/984-obsolete-invoke/run.py b/test/984-obsolete-invoke/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/984-obsolete-invoke/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/985-re-obsolete/run b/test/985-re-obsolete/run
deleted file mode 100755
index e92b873..0000000
--- a/test/985-re-obsolete/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/985-re-obsolete/run.py b/test/985-re-obsolete/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/985-re-obsolete/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/986-native-method-bind/run b/test/986-native-method-bind/run
deleted file mode 100755
index e92b873..0000000
--- a/test/986-native-method-bind/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/986-native-method-bind/run.py b/test/986-native-method-bind/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/986-native-method-bind/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/987-agent-bind/run b/test/987-agent-bind/run
deleted file mode 100755
index e92b873..0000000
--- a/test/987-agent-bind/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/987-agent-bind/run.py b/test/987-agent-bind/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/987-agent-bind/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/988-method-trace/expected-stdout.txt b/test/988-method-trace/expected-stdout.txt
index 59077b9..9460503 100644
--- a/test/988-method-trace/expected-stdout.txt
+++ b/test/988-method-trace/expected-stdout.txt
@@ -13,13 +13,14 @@
 ..=> public java.lang.Object()
 ..<= public java.lang.Object() -> <null: null>
 .<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
-...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
-...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
 fibonacci(30)=832040
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+...<= private void java.util.ArrayList.add(java.lang.Object,java.lang.Object[],int) -> <null: null>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
 <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
 => art.Test988$RecurOp()
 .=> public java.lang.Object()
@@ -62,13 +63,14 @@
 ..=> public java.lang.Object()
 ..<= public java.lang.Object() -> <null: null>
 .<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
-...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
-...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
 fibonacci(5)=5
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+...<= private void java.util.ArrayList.add(java.lang.Object,java.lang.Object[],int) -> <null: null>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
 <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
 => art.Test988$NativeOp()
 .=> public java.lang.Object()
@@ -83,13 +85,14 @@
 ..=> public java.lang.Object()
 ..<= public java.lang.Object() -> <null: null>
 .<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
-...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
-...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
 fibonacci(5)=5
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+...<= private void java.util.ArrayList.add(java.lang.Object,java.lang.Object[],int) -> <null: null>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
 <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
 => art.Test988$IterOp()
 .=> public java.lang.Object()
@@ -110,32 +113,42 @@
 .....<= public int java.lang.String.length() -> <class java.lang.Integer: 14>
 .....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
 .....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
-.....=> public void java.lang.String.getChars(int,int,char[],int)
-......=> public int java.lang.String.length()
-......<= public int java.lang.String.length() -> <class java.lang.Integer: 14>
-......=> static void java.lang.String.checkBoundsBeginEnd(int,int,int)
-......<= static void java.lang.String.checkBoundsBeginEnd(int,int,int) -> <null: null>
-......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int)
-......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null>
-.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null>
+.....=> private final void java.lang.AbstractStringBuilder.putStringAt(int,java.lang.String)
+......=> final byte java.lang.AbstractStringBuilder.getCoder()
+......<= final byte java.lang.AbstractStringBuilder.getCoder() -> <class java.lang.Byte: 0>
+......=> byte java.lang.String.coder()
+......<= byte java.lang.String.coder() -> <class java.lang.Byte: 0>
+......=> void java.lang.String.getBytes(byte[],int,byte)
+.......=> byte java.lang.String.coder()
+.......<= byte java.lang.String.coder() -> <class java.lang.Byte: 0>
+.......=> public int java.lang.String.length()
+.......<= public int java.lang.String.length() -> <class java.lang.Integer: 14>
+.......=> static void java.lang.String.checkBoundsOffCount(int,int,int)
+.......<= static void java.lang.String.checkBoundsOffCount(int,int,int) -> <null: null>
+.......=> private void java.lang.String.fillBytesLatin1(byte[],int)
+.......<= private void java.lang.String.fillBytesLatin1(byte[],int) -> <null: null>
+......<= void java.lang.String.getBytes(byte[],int,byte) -> <null: null>
+.....<= private final void java.lang.AbstractStringBuilder.putStringAt(int,java.lang.String) -> <null: null>
 ....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...=> public java.lang.StringBuilder java.lang.StringBuilder.append(int)
 ....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int)
 .....=> static int java.lang.Integer.stringSize(int)
-.....<= static int java.lang.Integer.stringSize(int) -> <class java.lang.Integer: 2>
+.....<= static int java.lang.Integer.stringSize(int) -> <class java.lang.Integer: 3>
 .....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
 ......=> private int java.lang.AbstractStringBuilder.newCapacity(int)
 ......<= private int java.lang.AbstractStringBuilder.newCapacity(int) -> <class java.lang.Integer: 34>
-......=> public static char[] java.util.Arrays.copyOf(char[],int)
+......=> public static byte[] java.util.Arrays.copyOf(byte[],int)
 .......=> public static int java.lang.Math.min(int,int)
 .......<= public static int java.lang.Math.min(int,int) -> <class java.lang.Integer: 16>
 .......=> public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int)
 .......<= public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) -> <null: null>
-......<= public static char[] java.util.Arrays.copyOf(char[],int) -> <class [C: [B, a, d,  , a, r, g, u, m, e, n, t, :,  , -, 1, 9,  , <,  , 0, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>]>
+......<= public static byte[] java.util.Arrays.copyOf(byte[],int) -> <class [B: [66, 97, 100, 32, 97, 114, 103, 117, 109, 101, 110, 116, 58, 32, 45, 49, 57, 32, 60, 32, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]>
 .....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
-.....=> static int java.lang.Integer.getChars(int,int,char[])
-.....<= static int java.lang.Integer.getChars(int,int,char[]) -> <class java.lang.Integer: 14>
+.....=> final boolean java.lang.AbstractStringBuilder.isLatin1()
+.....<= final boolean java.lang.AbstractStringBuilder.isLatin1() -> <class java.lang.Boolean: true>
+.....=> static int java.lang.Integer.getChars(int,int,byte[])
+.....<= static int java.lang.Integer.getChars(int,int,byte[]) -> <class java.lang.Integer: 14>
 ....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...<= public java.lang.StringBuilder java.lang.StringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)
@@ -144,21 +157,31 @@
 .....<= public int java.lang.String.length() -> <class java.lang.Integer: 4>
 .....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
 .....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
-.....=> public void java.lang.String.getChars(int,int,char[],int)
-......=> public int java.lang.String.length()
-......<= public int java.lang.String.length() -> <class java.lang.Integer: 4>
-......=> static void java.lang.String.checkBoundsBeginEnd(int,int,int)
-......<= static void java.lang.String.checkBoundsBeginEnd(int,int,int) -> <null: null>
-......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int)
-......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null>
-.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null>
+.....=> private final void java.lang.AbstractStringBuilder.putStringAt(int,java.lang.String)
+......=> final byte java.lang.AbstractStringBuilder.getCoder()
+......<= final byte java.lang.AbstractStringBuilder.getCoder() -> <class java.lang.Byte: 0>
+......=> byte java.lang.String.coder()
+......<= byte java.lang.String.coder() -> <class java.lang.Byte: 0>
+......=> void java.lang.String.getBytes(byte[],int,byte)
+.......=> byte java.lang.String.coder()
+.......<= byte java.lang.String.coder() -> <class java.lang.Byte: 0>
+.......=> public int java.lang.String.length()
+.......<= public int java.lang.String.length() -> <class java.lang.Integer: 4>
+.......=> static void java.lang.String.checkBoundsOffCount(int,int,int)
+.......<= static void java.lang.String.checkBoundsOffCount(int,int,int) -> <null: null>
+.......=> private void java.lang.String.fillBytesLatin1(byte[],int)
+.......<= private void java.lang.String.fillBytesLatin1(byte[],int) -> <null: null>
+......<= void java.lang.String.getBytes(byte[],int,byte) -> <null: null>
+.....<= private final void java.lang.AbstractStringBuilder.putStringAt(int,java.lang.String) -> <null: null>
 ....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...=> public java.lang.String java.lang.StringBuilder.toString()
-....=> public static java.lang.String java.lang.StringFactory.newStringFromChars(char[],int,int)
-.....=> static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[])
-.....<= static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0>
-....<= public static java.lang.String java.lang.StringFactory.newStringFromChars(char[],int,int) -> <class java.lang.String: Bad argument: -19 < 0>
+....=> final boolean java.lang.AbstractStringBuilder.isLatin1()
+....<= final boolean java.lang.AbstractStringBuilder.isLatin1() -> <class java.lang.Boolean: true>
+....=> public static java.lang.String java.lang.StringLatin1.newString(byte[],int,int)
+.....=> public static java.lang.String java.lang.StringFactory.newStringFromBytes(byte[],int,int,int)
+.....<= public static java.lang.String java.lang.StringFactory.newStringFromBytes(byte[],int,int,int) -> <class java.lang.String: Bad argument: -19 < 0>
+....<= public static java.lang.String java.lang.StringLatin1.newString(byte[],int,int) -> <class java.lang.String: Bad argument: -19 < 0>
 ...<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: Bad argument: -19 < 0>
 ...=> public java.lang.Error(java.lang.String)
 ....=> public java.lang.Throwable(java.lang.String)
@@ -170,10 +193,10 @@
 ......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace()
 ......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>
 .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0
-	art.Test988.iter_fibonacci(Test988.java:269)
-	art.Test988$IterOp.applyAsInt(Test988.java:264)
-	art.Test988.doFibTest(Test988.java:402)
-	art.Test988.run(Test988.java:358)
+	art.Test988.iter_fibonacci(Test988.java:280)
+	art.Test988$IterOp.applyAsInt(Test988.java:275)
+	art.Test988.doFibTest(Test988.java:413)
+	art.Test988.run(Test988.java:369)
 	<additional hidden frames>
 >
 ....<= public java.lang.Throwable(java.lang.String) -> <null: null>
@@ -184,19 +207,20 @@
 ..=> public java.lang.Object()
 ..<= public java.lang.Object() -> <null: null>
 .<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
-...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
-...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
 fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0
-	art.Test988.iter_fibonacci(Test988.java:269)
-	art.Test988$IterOp.applyAsInt(Test988.java:264)
-	art.Test988.doFibTest(Test988.java:402)
-	art.Test988.run(Test988.java:358)
+	art.Test988.iter_fibonacci(Test988.java:280)
+	art.Test988$IterOp.applyAsInt(Test988.java:275)
+	art.Test988.doFibTest(Test988.java:413)
+	art.Test988.run(Test988.java:369)
 	<additional hidden frames>
 
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+...<= private void java.util.ArrayList.add(java.lang.Object,java.lang.Object[],int) -> <null: null>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
 <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
 => art.Test988$RecurOp()
 .=> public java.lang.Object()
@@ -217,32 +241,42 @@
 .....<= public int java.lang.String.length() -> <class java.lang.Integer: 14>
 .....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
 .....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
-.....=> public void java.lang.String.getChars(int,int,char[],int)
-......=> public int java.lang.String.length()
-......<= public int java.lang.String.length() -> <class java.lang.Integer: 14>
-......=> static void java.lang.String.checkBoundsBeginEnd(int,int,int)
-......<= static void java.lang.String.checkBoundsBeginEnd(int,int,int) -> <null: null>
-......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int)
-......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null>
-.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null>
+.....=> private final void java.lang.AbstractStringBuilder.putStringAt(int,java.lang.String)
+......=> final byte java.lang.AbstractStringBuilder.getCoder()
+......<= final byte java.lang.AbstractStringBuilder.getCoder() -> <class java.lang.Byte: 0>
+......=> byte java.lang.String.coder()
+......<= byte java.lang.String.coder() -> <class java.lang.Byte: 0>
+......=> void java.lang.String.getBytes(byte[],int,byte)
+.......=> byte java.lang.String.coder()
+.......<= byte java.lang.String.coder() -> <class java.lang.Byte: 0>
+.......=> public int java.lang.String.length()
+.......<= public int java.lang.String.length() -> <class java.lang.Integer: 14>
+.......=> static void java.lang.String.checkBoundsOffCount(int,int,int)
+.......<= static void java.lang.String.checkBoundsOffCount(int,int,int) -> <null: null>
+.......=> private void java.lang.String.fillBytesLatin1(byte[],int)
+.......<= private void java.lang.String.fillBytesLatin1(byte[],int) -> <null: null>
+......<= void java.lang.String.getBytes(byte[],int,byte) -> <null: null>
+.....<= private final void java.lang.AbstractStringBuilder.putStringAt(int,java.lang.String) -> <null: null>
 ....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...=> public java.lang.StringBuilder java.lang.StringBuilder.append(int)
 ....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int)
 .....=> static int java.lang.Integer.stringSize(int)
-.....<= static int java.lang.Integer.stringSize(int) -> <class java.lang.Integer: 2>
+.....<= static int java.lang.Integer.stringSize(int) -> <class java.lang.Integer: 3>
 .....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
 ......=> private int java.lang.AbstractStringBuilder.newCapacity(int)
 ......<= private int java.lang.AbstractStringBuilder.newCapacity(int) -> <class java.lang.Integer: 34>
-......=> public static char[] java.util.Arrays.copyOf(char[],int)
+......=> public static byte[] java.util.Arrays.copyOf(byte[],int)
 .......=> public static int java.lang.Math.min(int,int)
 .......<= public static int java.lang.Math.min(int,int) -> <class java.lang.Integer: 16>
 .......=> public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int)
 .......<= public static void java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int) -> <null: null>
-......<= public static char[] java.util.Arrays.copyOf(char[],int) -> <class [C: [B, a, d,  , a, r, g, u, m, e, n, t, :,  , -, 1, 9,  , <,  , 0, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>]>
+......<= public static byte[] java.util.Arrays.copyOf(byte[],int) -> <class [B: [66, 97, 100, 32, 97, 114, 103, 117, 109, 101, 110, 116, 58, 32, 45, 49, 57, 32, 60, 32, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]>
 .....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
-.....=> static int java.lang.Integer.getChars(int,int,char[])
-.....<= static int java.lang.Integer.getChars(int,int,char[]) -> <class java.lang.Integer: 14>
+.....=> final boolean java.lang.AbstractStringBuilder.isLatin1()
+.....<= final boolean java.lang.AbstractStringBuilder.isLatin1() -> <class java.lang.Boolean: true>
+.....=> static int java.lang.Integer.getChars(int,int,byte[])
+.....<= static int java.lang.Integer.getChars(int,int,byte[]) -> <class java.lang.Integer: 14>
 ....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...<= public java.lang.StringBuilder java.lang.StringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)
@@ -251,21 +285,31 @@
 .....<= public int java.lang.String.length() -> <class java.lang.Integer: 4>
 .....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
 .....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
-.....=> public void java.lang.String.getChars(int,int,char[],int)
-......=> public int java.lang.String.length()
-......<= public int java.lang.String.length() -> <class java.lang.Integer: 4>
-......=> static void java.lang.String.checkBoundsBeginEnd(int,int,int)
-......<= static void java.lang.String.checkBoundsBeginEnd(int,int,int) -> <null: null>
-......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int)
-......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null>
-.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null>
+.....=> private final void java.lang.AbstractStringBuilder.putStringAt(int,java.lang.String)
+......=> final byte java.lang.AbstractStringBuilder.getCoder()
+......<= final byte java.lang.AbstractStringBuilder.getCoder() -> <class java.lang.Byte: 0>
+......=> byte java.lang.String.coder()
+......<= byte java.lang.String.coder() -> <class java.lang.Byte: 0>
+......=> void java.lang.String.getBytes(byte[],int,byte)
+.......=> byte java.lang.String.coder()
+.......<= byte java.lang.String.coder() -> <class java.lang.Byte: 0>
+.......=> public int java.lang.String.length()
+.......<= public int java.lang.String.length() -> <class java.lang.Integer: 4>
+.......=> static void java.lang.String.checkBoundsOffCount(int,int,int)
+.......<= static void java.lang.String.checkBoundsOffCount(int,int,int) -> <null: null>
+.......=> private void java.lang.String.fillBytesLatin1(byte[],int)
+.......<= private void java.lang.String.fillBytesLatin1(byte[],int) -> <null: null>
+......<= void java.lang.String.getBytes(byte[],int,byte) -> <null: null>
+.....<= private final void java.lang.AbstractStringBuilder.putStringAt(int,java.lang.String) -> <null: null>
 ....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
 ...=> public java.lang.String java.lang.StringBuilder.toString()
-....=> public static java.lang.String java.lang.StringFactory.newStringFromChars(char[],int,int)
-.....=> static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[])
-.....<= static java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0>
-....<= public static java.lang.String java.lang.StringFactory.newStringFromChars(char[],int,int) -> <class java.lang.String: Bad argument: -19 < 0>
+....=> final boolean java.lang.AbstractStringBuilder.isLatin1()
+....<= final boolean java.lang.AbstractStringBuilder.isLatin1() -> <class java.lang.Boolean: true>
+....=> public static java.lang.String java.lang.StringLatin1.newString(byte[],int,int)
+.....=> public static java.lang.String java.lang.StringFactory.newStringFromBytes(byte[],int,int,int)
+.....<= public static java.lang.String java.lang.StringFactory.newStringFromBytes(byte[],int,int,int) -> <class java.lang.String: Bad argument: -19 < 0>
+....<= public static java.lang.String java.lang.StringLatin1.newString(byte[],int,int) -> <class java.lang.String: Bad argument: -19 < 0>
 ...<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: Bad argument: -19 < 0>
 ...=> public java.lang.Error(java.lang.String)
 ....=> public java.lang.Throwable(java.lang.String)
@@ -277,10 +321,10 @@
 ......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace()
 ......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>
 .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0
-	art.Test988.fibonacci(Test988.java:291)
-	art.Test988$RecurOp.applyAsInt(Test988.java:286)
-	art.Test988.doFibTest(Test988.java:402)
-	art.Test988.run(Test988.java:359)
+	art.Test988.fibonacci(Test988.java:302)
+	art.Test988$RecurOp.applyAsInt(Test988.java:297)
+	art.Test988.doFibTest(Test988.java:413)
+	art.Test988.run(Test988.java:370)
 	<additional hidden frames>
 >
 ....<= public java.lang.Throwable(java.lang.String) -> <null: null>
@@ -291,19 +335,20 @@
 ..=> public java.lang.Object()
 ..<= public java.lang.Object() -> <null: null>
 .<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
-...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
-...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
 fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0
-	art.Test988.fibonacci(Test988.java:291)
-	art.Test988$RecurOp.applyAsInt(Test988.java:286)
-	art.Test988.doFibTest(Test988.java:402)
-	art.Test988.run(Test988.java:359)
+	art.Test988.fibonacci(Test988.java:302)
+	art.Test988$RecurOp.applyAsInt(Test988.java:297)
+	art.Test988.doFibTest(Test988.java:413)
+	art.Test988.run(Test988.java:370)
 	<additional hidden frames>
 
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+...<= private void java.util.ArrayList.add(java.lang.Object,java.lang.Object[],int) -> <null: null>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
 <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
 => art.Test988$NativeOp()
 .=> public java.lang.Object()
@@ -323,9 +368,9 @@
 ......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>
 .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: bad argument
 	art.Test988.nativeFibonacci(Native Method)
-	art.Test988$NativeOp.applyAsInt(Test988.java:301)
-	art.Test988.doFibTest(Test988.java:402)
-	art.Test988.run(Test988.java:360)
+	art.Test988$NativeOp.applyAsInt(Test988.java:312)
+	art.Test988.doFibTest(Test988.java:413)
+	art.Test988.run(Test988.java:371)
 	<additional hidden frames>
 >
 ....<= public java.lang.Throwable(java.lang.String) -> <null: null>
@@ -336,19 +381,20 @@
 ..=> public java.lang.Object()
 ..<= public java.lang.Object() -> <null: null>
 .<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
-...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
-...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
 fibonacci(-19) -> java.lang.Error: bad argument
 	art.Test988.nativeFibonacci(Native Method)
-	art.Test988$NativeOp.applyAsInt(Test988.java:301)
-	art.Test988.doFibTest(Test988.java:402)
-	art.Test988.run(Test988.java:360)
+	art.Test988$NativeOp.applyAsInt(Test988.java:312)
+	art.Test988.doFibTest(Test988.java:413)
+	art.Test988.run(Test988.java:371)
 	<additional hidden frames>
 
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+...<= private void java.util.ArrayList.add(java.lang.Object,java.lang.Object[],int) -> <null: null>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
 <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
 => public final void <non-deterministic-type 0>.run()
 .=> private static java.lang.Object java.lang.reflect.Proxy.invoke(java.lang.reflect.Proxy,java.lang.reflect.Method,java.lang.Object[]) throws java.lang.Throwable
diff --git a/test/988-method-trace/run b/test/988-method-trace/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/988-method-trace/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/988-method-trace/run.py b/test/988-method-trace/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/988-method-trace/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java
index 9c8ce4a..227ce62 100644
--- a/test/988-method-trace/src/art/Test988.java
+++ b/test/988-method-trace/src/art/Test988.java
@@ -253,11 +253,22 @@
         }
     }
 
-    private static List<Printable> results = new ArrayList<>();
+    private static ArrayList<Printable> results = new ArrayList<>();
+    private static int results_index = 0;
     // Starts with => enableMethodTracing
     //             .=> enableTracing
     private static int cnt = 2;
 
+    static void addToResults(Printable obj) {
+      // Reserve space for the current object. If any other method entry callbacks are called they
+      // will reserve more space. Without this we may get into strange problems where ArrayList::add
+      // cecks there is enough space (which involves a couple of method calls) which then use up the
+      // space and by the time we actually add this record there is no capacity left.
+      results_index++;
+      results.ensureCapacity(results_index + 1);
+      results.add(obj);
+    }
+
     // Iterative version
     static final class IterOp implements IntUnaryOperator {
       public int applyAsInt(int x) {
@@ -319,7 +330,7 @@
         if ((cnt - 1) > METHOD_TRACING_IGNORE_DEPTH && sMethodTracingIgnore) {
           return;
         }
-        results.add(new MethodEntry(m, cnt - 1));
+        addToResults(new MethodEntry(m, cnt - 1));
     }
 
     public static void notifyMethodExit(Executable m, boolean exception, Object result) {
@@ -330,9 +341,9 @@
         }
 
         if (exception) {
-            results.add(new MethodThrownThrough(m, cnt));
+            addToResults(new MethodThrownThrough(m, cnt));
         } else {
-            results.add(new MethodReturn(m, result, cnt));
+            addToResults(new MethodReturn(m, result, cnt));
         }
     }
 
@@ -400,9 +411,9 @@
     public static void doFibTest(int x, IntUnaryOperator op) {
       try {
         int y = op.applyAsInt(x);
-        results.add(new FibResult("fibonacci(%d)=%d\n", x, y));
+        addToResults(new FibResult("fibonacci(%d)=%d\n", x, y));
       } catch (Throwable t) {
-        results.add(new FibThrow("fibonacci(%d) -> %s\n", x, t));
+        addToResults(new FibThrow("fibonacci(%d) -> %s\n", x, t));
       }
     }
 
diff --git a/test/988-method-trace/src/art/Test988Intrinsics.java b/test/988-method-trace/src/art/Test988Intrinsics.java
index 3069f1a..fe68d3e 100644
--- a/test/988-method-trace/src/art/Test988Intrinsics.java
+++ b/test/988-method-trace/src/art/Test988Intrinsics.java
@@ -22,114 +22,116 @@
 
 package art;
 
-class Test988Intrinsics {
-  // Pre-initialize *all* instance variables used so that their constructors are not in the trace.
-  static java.lang.String instance_java_lang_String = "some large string";
-  static java.lang.StringBuffer instance_java_lang_StringBuffer = new java.lang.StringBuffer("some large string buffer");
-  static java.lang.StringBuilder instance_java_lang_StringBuilder = new java.lang.StringBuilder("some large string builder");
+public class Test988Intrinsics {
+    // Pre-initialize *all* instance variables used so that their constructors are not in the trace.
+    static java.lang.String instance_java_lang_String = "some large string";
+    static java.lang.StringBuffer instance_java_lang_StringBuffer =
+            new java.lang.StringBuffer("some large string buffer");
+    static java.lang.StringBuilder instance_java_lang_StringBuilder =
+            new java.lang.StringBuilder("some large string builder");
 
-  static void initialize() {
-    // Ensure all static variables are initialized.
-    // In addition, pre-load classes here so that we don't see diverging class loading traces.
-    java.lang.Double.class.toString();
-    java.lang.Float.class.toString();
-    java.lang.Integer.class.toString();
-    java.lang.Long.class.toString();
-    java.lang.Short.class.toString();
-    java.lang.Math.class.toString();
-    java.lang.Thread.class.toString();
-    java.lang.String.class.toString();
-    java.lang.StringBuffer.class.toString();
-    java.lang.StringBuilder.class.toString();
-  }
+    static void initialize() {
+        // Ensure all static variables are initialized.
+        // In addition, pre-load classes here so that we don't see diverging class loading traces.
+        java.lang.Double.class.toString();
+        java.lang.Float.class.toString();
+        java.lang.Integer.class.toString();
+        java.lang.Long.class.toString();
+        java.lang.Short.class.toString();
+        java.lang.Math.class.toString();
+        java.lang.Thread.class.toString();
+        java.lang.String.class.toString();
+        java.lang.StringBuffer.class.toString();
+        java.lang.StringBuilder.class.toString();
+    }
 
-  static void test() {
-    // Call each intrinsic from art/runtime/intrinsics_list.h to make sure they are traced.
-    java.lang.Double.doubleToRawLongBits(0.0);
-    java.lang.Double.doubleToLongBits(0.0);
-    java.lang.Double.isInfinite(0.0);
-    java.lang.Double.isNaN(0.0);
-    java.lang.Double.longBitsToDouble(0L);
-    java.lang.Float.floatToRawIntBits(0.0f);
-    java.lang.Float.floatToIntBits(0.0f);
-    java.lang.Float.isInfinite(0.0f);
-    java.lang.Float.isNaN(0.0f);
-    java.lang.Float.intBitsToFloat(0);
-    java.lang.Integer.reverse(0);
-    java.lang.Integer.reverseBytes(0);
-    java.lang.Integer.bitCount(0);
-    java.lang.Integer.compare(0, 0);
-    java.lang.Integer.highestOneBit(0);
-    java.lang.Integer.lowestOneBit(0);
-    java.lang.Integer.numberOfLeadingZeros(0);
-    java.lang.Integer.numberOfTrailingZeros(0);
-    java.lang.Integer.rotateRight(0, 0);
-    java.lang.Integer.rotateLeft(0, 0);
-    java.lang.Integer.signum(0);
-    java.lang.Long.reverse(0L);
-    java.lang.Long.reverseBytes(0L);
-    java.lang.Long.bitCount(0L);
-    java.lang.Long.compare(0L, 0L);
-    java.lang.Long.highestOneBit(0L);
-    java.lang.Long.lowestOneBit(0L);
-    java.lang.Long.numberOfLeadingZeros(0L);
-    java.lang.Long.numberOfTrailingZeros(0L);
-    java.lang.Long.rotateRight(0L, 0);
-    java.lang.Long.rotateLeft(0L, 0);
-    java.lang.Long.signum(0L);
-    java.lang.Short.reverseBytes((short)0);
-    java.lang.Math.abs(0.0);
-    java.lang.Math.abs(0.0f);
-    java.lang.Math.abs(0L);
-    java.lang.Math.abs(0);
-    java.lang.Math.min(0.0, 0.0);
-    java.lang.Math.min(0.0f, 0.0f);
-    java.lang.Math.min(0L, 0L);
-    java.lang.Math.min(0, 0);
-    java.lang.Math.max(0.0, 0.0);
-    java.lang.Math.max(0.0f, 0.0f);
-    java.lang.Math.max(0L, 0L);
-    java.lang.Math.max(0, 0);
-    java.lang.Math.cos(0.0);
-    java.lang.Math.sin(0.0);
-    java.lang.Math.acos(0.0);
-    java.lang.Math.asin(0.0);
-    java.lang.Math.atan(0.0);
-    java.lang.Math.atan2(0.0, 0.0);
-    java.lang.Math.cbrt(0.0);
-    java.lang.Math.cosh(0.0);
-    java.lang.Math.exp(0.0);
-    java.lang.Math.expm1(0.0);
-    java.lang.Math.hypot(0.0, 0.0);
-    java.lang.Math.log(0.0);
-    java.lang.Math.log10(0.0);
-    java.lang.Math.nextAfter(0.0, 0.0);
-    java.lang.Math.sinh(0.0);
-    java.lang.Math.tan(0.0);
-    java.lang.Math.tanh(0.0);
-    java.lang.Math.sqrt(0.0);
-    java.lang.Math.ceil(0.0);
-    java.lang.Math.floor(0.0);
-    java.lang.Math.rint(0.0);
-    java.lang.Math.round(0.0);
-    java.lang.Math.round(0.0f);
-    java.lang.Thread.currentThread();
-    instance_java_lang_String.charAt(0);
-    instance_java_lang_String.compareTo("hello");
-    instance_java_lang_String.equals((java.lang.Object)null);
-    instance_java_lang_String.indexOf(0);
-    instance_java_lang_String.indexOf(0, 0);
-    instance_java_lang_String.indexOf("hello");
-    instance_java_lang_String.indexOf("hello", 0);
-    instance_java_lang_String.isEmpty();
-    instance_java_lang_String.length();
-    instance_java_lang_StringBuffer.append("hello");
-    instance_java_lang_StringBuffer.length();
-    instance_java_lang_StringBuffer.toString();
-    instance_java_lang_StringBuilder.append("hello");
-    instance_java_lang_StringBuilder.length();
-    instance_java_lang_StringBuilder.toString();
-    java.lang.Integer.valueOf(0);
-    java.lang.Thread.interrupted();
-  }
+    static void test() {
+        // Call each intrinsic from art/runtime/intrinsics_list.h to make sure they are traced.
+        java.lang.Double.doubleToRawLongBits(0.0);
+        java.lang.Double.doubleToLongBits(0.0);
+        java.lang.Double.isInfinite(0.0);
+        java.lang.Double.isNaN(0.0);
+        java.lang.Double.longBitsToDouble(0L);
+        java.lang.Float.floatToRawIntBits(0.0f);
+        java.lang.Float.floatToIntBits(0.0f);
+        java.lang.Float.isInfinite(0.0f);
+        java.lang.Float.isNaN(0.0f);
+        java.lang.Float.intBitsToFloat(0);
+        java.lang.Integer.reverse(0);
+        java.lang.Integer.reverseBytes(0);
+        java.lang.Integer.bitCount(0);
+        java.lang.Integer.compare(0, 0);
+        java.lang.Integer.highestOneBit(0);
+        java.lang.Integer.lowestOneBit(0);
+        java.lang.Integer.numberOfLeadingZeros(0);
+        java.lang.Integer.numberOfTrailingZeros(0);
+        java.lang.Integer.rotateRight(0, 0);
+        java.lang.Integer.rotateLeft(0, 0);
+        java.lang.Integer.signum(0);
+        java.lang.Long.reverse(0L);
+        java.lang.Long.reverseBytes(0L);
+        java.lang.Long.bitCount(0L);
+        java.lang.Long.compare(0L, 0L);
+        java.lang.Long.highestOneBit(0L);
+        java.lang.Long.lowestOneBit(0L);
+        java.lang.Long.numberOfLeadingZeros(0L);
+        java.lang.Long.numberOfTrailingZeros(0L);
+        java.lang.Long.rotateRight(0L, 0);
+        java.lang.Long.rotateLeft(0L, 0);
+        java.lang.Long.signum(0L);
+        java.lang.Short.reverseBytes((short) 0);
+        java.lang.Math.abs(0.0);
+        java.lang.Math.abs(0.0f);
+        java.lang.Math.abs(0L);
+        java.lang.Math.abs(0);
+        java.lang.Math.min(0.0, 0.0);
+        java.lang.Math.min(0.0f, 0.0f);
+        java.lang.Math.min(0L, 0L);
+        java.lang.Math.min(0, 0);
+        java.lang.Math.max(0.0, 0.0);
+        java.lang.Math.max(0.0f, 0.0f);
+        java.lang.Math.max(0L, 0L);
+        java.lang.Math.max(0, 0);
+        java.lang.Math.cos(0.0);
+        java.lang.Math.sin(0.0);
+        java.lang.Math.acos(0.0);
+        java.lang.Math.asin(0.0);
+        java.lang.Math.atan(0.0);
+        java.lang.Math.atan2(0.0, 0.0);
+        java.lang.Math.cbrt(0.0);
+        java.lang.Math.cosh(0.0);
+        java.lang.Math.exp(0.0);
+        java.lang.Math.expm1(0.0);
+        java.lang.Math.hypot(0.0, 0.0);
+        java.lang.Math.log(0.0);
+        java.lang.Math.log10(0.0);
+        java.lang.Math.nextAfter(0.0, 0.0);
+        java.lang.Math.sinh(0.0);
+        java.lang.Math.tan(0.0);
+        java.lang.Math.tanh(0.0);
+        java.lang.Math.sqrt(0.0);
+        java.lang.Math.ceil(0.0);
+        java.lang.Math.floor(0.0);
+        java.lang.Math.rint(0.0);
+        java.lang.Math.round(0.0);
+        java.lang.Math.round(0.0f);
+        java.lang.Thread.currentThread();
+        instance_java_lang_String.charAt(0);
+        instance_java_lang_String.compareTo("hello");
+        instance_java_lang_String.equals((java.lang.Object) null);
+        instance_java_lang_String.indexOf(0);
+        instance_java_lang_String.indexOf(0, 0);
+        instance_java_lang_String.indexOf("hello");
+        instance_java_lang_String.indexOf("hello", 0);
+        instance_java_lang_String.isEmpty();
+        instance_java_lang_String.length();
+        instance_java_lang_StringBuffer.append("hello");
+        instance_java_lang_StringBuffer.length();
+        instance_java_lang_StringBuffer.toString();
+        instance_java_lang_StringBuilder.append("hello");
+        instance_java_lang_StringBuilder.length();
+        instance_java_lang_StringBuilder.toString();
+        java.lang.Integer.valueOf(0);
+        java.lang.Thread.interrupted();
+    }
 }
diff --git a/test/989-method-trace-throw/run b/test/989-method-trace-throw/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/989-method-trace-throw/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/989-method-trace-throw/run.py b/test/989-method-trace-throw/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/989-method-trace-throw/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/990-field-trace/run b/test/990-field-trace/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/990-field-trace/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/990-field-trace/run.py b/test/990-field-trace/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/990-field-trace/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/991-field-trace-2/run b/test/991-field-trace-2/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/991-field-trace-2/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/991-field-trace-2/run.py b/test/991-field-trace-2/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/991-field-trace-2/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/992-source-data/run b/test/992-source-data/run
deleted file mode 100755
index e92b873..0000000
--- a/test/992-source-data/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-./default-run "$@" --jvmti
diff --git a/test/992-source-data/run.py b/test/992-source-data/run.py
new file mode 100644
index 0000000..b596886
--- /dev/null
+++ b/test/992-source-data/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/993-breakpoints-non-debuggable/Android.bp b/test/993-breakpoints-non-debuggable/Android.bp
new file mode 100644
index 0000000..53a9f28
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `993-breakpoints-non-debuggable`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-993-breakpoints-non-debuggable",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-993-breakpoints-non-debuggable-expected-stdout",
+        ":art-run-test-993-breakpoints-non-debuggable-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-993-breakpoints-non-debuggable-expected-stdout",
+    out: ["art-run-test-993-breakpoints-non-debuggable-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-993-breakpoints-non-debuggable-expected-stderr",
+    out: ["art-run-test-993-breakpoints-non-debuggable-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/089-many-methods/expected-stderr.txt b/test/993-breakpoints-non-debuggable/expected-stderr.txt
similarity index 100%
copy from test/089-many-methods/expected-stderr.txt
copy to test/993-breakpoints-non-debuggable/expected-stderr.txt
diff --git a/test/993-breakpoints-non-debuggable/expected-stdout.txt b/test/993-breakpoints-non-debuggable/expected-stdout.txt
new file mode 100644
index 0000000..a4bd24b
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/expected-stdout.txt
@@ -0,0 +1,715 @@
+JNI_OnLoad called
+Running static invoke
+	Breaking on []
+		Native invoking: public static void art.Test993.breakpoint() args: [this: null]
+		Reflective invoking: public static void art.Test993.breakpoint() args: [this: null]
+		Invoking "Test993::breakpoint"
+	Breaking on [public static void art.Test993.breakpoint() @ 41]
+		Native invoking: public static void art.Test993.breakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993.breakpoint() @ line=41
+		Reflective invoking: public static void art.Test993.breakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993.breakpoint() @ line=41
+		Invoking "Test993::breakpoint"
+			Breakpoint: public static void art.Test993.breakpoint() @ line=41
+Running private static invoke
+	Breaking on []
+		Native invoking: private static void art.Test993.privateBreakpoint() args: [this: null]
+		Invoking "Test993::privateBreakpoint"
+	Breaking on [private static void art.Test993.privateBreakpoint() @ 45]
+		Native invoking: private static void art.Test993.privateBreakpoint() args: [this: null]
+			Breakpoint: private static void art.Test993.privateBreakpoint() @ line=45
+		Invoking "Test993::privateBreakpoint"
+			Breakpoint: private static void art.Test993.privateBreakpoint() @ line=45
+Running interface static invoke
+	Breaking on []
+		Native invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+		Reflective invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+		Invoking "Breakable::iBreakpoint"
+	Breaking on [public static void art.Test993$Breakable.iBreakpoint() @ 51]
+		Native invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51
+		Reflective invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51
+		Invoking "Breakable::iBreakpoint"
+			Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51
+Running TestClass1 invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+		Invoking "((Breakable)new TestClass1()).breakit()"
+		Invoking "new TestClass1().breakit()"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass1()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass1().breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running TestClass1ext invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+		Invoking "new TestClass1ext().breakit()"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass1ext().breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass1ext.breakit() @ 74]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Invoking "new TestClass1ext().breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass1ext.breakit() @ 74]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass1ext().breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running TestClass2 invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Invoking "((Breakable)new TestClass2()).breakit()"
+		Invoking "new TestClass2().breakit()"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Invoking "((Breakable)new TestClass2()).breakit()"
+		Invoking "new TestClass2().breakit()"
+	Breaking on [public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2().breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2().breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+Running TestClass2ext invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+		Invoking "new TestClass2ext().breakit())"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+		Invoking "new TestClass2ext().breakit())"
+	Breaking on [public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+	Breaking on [public void art.Test993$TestClass2.breakit() @ 83, public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83, public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+Running TestClass3 invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+		Invoking "((Breakable)new TestClass3()).breakit()"
+		Invoking "new TestClass3().breakit())"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3().breakit())"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((Breakable)new TestClass3()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "new TestClass3().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running TestClass3ext invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+		Invoking "new TestClass3ext().breakit())"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+	Breaking on [public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass3.breakit() @ 99, public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99, public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running private instance invoke
+	Breaking on []
+		Native invoking: private void art.Test993$TestClass4.privateMethod() args: [this: TestClass4]
+		Invoking "new TestClass4().callPrivateMethod()"
+	Breaking on [private void art.Test993$TestClass4.privateMethod() @ 118]
+		Native invoking: private void art.Test993$TestClass4.privateMethod() args: [this: TestClass4]
+			Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118
+		Invoking "new TestClass4().callPrivateMethod()"
+			Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118
+Running Vector constructor
+	Breaking on []
+		Native constructor: public java.util.Vector(), type: class java.util.Vector
+			Created: []
+		Reflective constructor: public java.util.Vector()
+			Created: []
+		Constructing: new Vector()
+			Created: []
+	Breaking on [public java.util.Vector() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Vector(), type: class java.util.Vector
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Vector()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Vector()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+Running Stack constructor
+	Breaking on []
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Created: []
+		Constructing: new Stack()
+			Created: []
+	Breaking on [public java.util.Stack() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Created: []
+	Breaking on [public java.util.Vector() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Stack()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+	Breaking on [public java.util.Stack() @ <NON-DETERMINISTIC>, public java.util.Vector() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+Running bcp static invoke
+	Breaking on []
+		Native invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+		Reflective invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+		Invoking "Optional::empty"
+	Breaking on [public static java.util.Optional java.util.Optional.empty() @ <NON-DETERMINISTIC>]
+		Native invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+			Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC>
+		Reflective invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+			Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC>
+		Invoking "Optional::empty"
+			Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC>
+Running bcp private static invoke
+	Breaking on []
+		Native invoking: private static long java.util.Random.seedUniquifier() args: [this: null]
+		Invoking "Random::seedUniquifier"
+	Breaking on [private static long java.util.Random.seedUniquifier() @ <NON-DETERMINISTIC>]
+		Native invoking: private static long java.util.Random.seedUniquifier() args: [this: null]
+			Breakpoint: private static long java.util.Random.seedUniquifier() @ line=<NON-DETERMINISTIC>
+		Invoking "Random::seedUniquifier"
+			Breakpoint: private static long java.util.Random.seedUniquifier() @ line=<NON-DETERMINISTIC>
+Running bcp private invoke
+	Breaking on []
+		Native invoking: private java.math.BigDecimal java.time.Duration.toBigDecimalSeconds() args: [this: PT336H]
+		Invoking "Duration::toBigDecimalSeconds"
+	Breaking on [private java.math.BigDecimal java.time.Duration.toBigDecimalSeconds() @ <NON-DETERMINISTIC>]
+		Native invoking: private java.math.BigDecimal java.time.Duration.toBigDecimalSeconds() args: [this: PT336H]
+			Breakpoint: private java.math.BigDecimal java.time.Duration.toBigDecimalSeconds() @ line=<NON-DETERMINISTIC>
+		Invoking "Duration::toBigDecimalSeconds"
+			Breakpoint: private java.math.BigDecimal java.time.Duration.toBigDecimalSeconds() @ line=<NON-DETERMINISTIC>
+Running bcp invoke
+	Breaking on []
+		Native invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test]]
+		Reflective invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test2]]
+		Invoking "Optional::isPresent"
+	Breaking on [public boolean java.util.Optional.isPresent() @ <NON-DETERMINISTIC>]
+		Native invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test]]
+			Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC>
+		Reflective invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test2]]
+			Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC>
+		Invoking "Optional::isPresent"
+			Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC>
+Running TestClass1 constructor
+	Breaking on []
+		Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1
+			Created: TestClass1
+		Reflective constructor: public art.Test993$TestClass1()
+			Created: TestClass1
+		Constructing: new TestClass1()
+			Created: TestClass1
+	Breaking on [public art.Test993$TestClass1() @ 62]
+		Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1
+		Reflective constructor: public art.Test993$TestClass1()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1
+		Constructing: new TestClass1()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1
+Running TestClass1ext constructor
+	Breaking on []
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Created: TestClass1Ext
+	Breaking on [public art.Test993$TestClass1() @ 62]
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+	Breaking on [public art.Test993$TestClass1ext() @ 70]
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Created: TestClass1Ext
+	Breaking on [public art.Test993$TestClass1() @ 62, public art.Test993$TestClass1ext() @ 70]
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
diff --git a/test/993-breakpoints-non-debuggable/expected_cts.txt b/test/993-breakpoints-non-debuggable/expected_cts.txt
new file mode 100644
index 0000000..6c4e881
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/expected_cts.txt
@@ -0,0 +1,696 @@
+Running static invoke
+	Breaking on []
+		Native invoking: public static void art.Test993.breakpoint() args: [this: null]
+		Reflective invoking: public static void art.Test993.breakpoint() args: [this: null]
+		Invoking "Test993::breakpoint"
+	Breaking on [public static void art.Test993.breakpoint() @ 41]
+		Native invoking: public static void art.Test993.breakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993.breakpoint() @ line=41
+		Reflective invoking: public static void art.Test993.breakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993.breakpoint() @ line=41
+		Invoking "Test993::breakpoint"
+			Breakpoint: public static void art.Test993.breakpoint() @ line=41
+Running private static invoke
+	Breaking on []
+		Native invoking: private static void art.Test993.privateBreakpoint() args: [this: null]
+		Invoking "Test993::privateBreakpoint"
+	Breaking on [private static void art.Test993.privateBreakpoint() @ 45]
+		Native invoking: private static void art.Test993.privateBreakpoint() args: [this: null]
+			Breakpoint: private static void art.Test993.privateBreakpoint() @ line=45
+		Invoking "Test993::privateBreakpoint"
+			Breakpoint: private static void art.Test993.privateBreakpoint() @ line=45
+Running interface static invoke
+	Breaking on []
+		Native invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+		Reflective invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+		Invoking "Breakable::iBreakpoint"
+	Breaking on [public static void art.Test993$Breakable.iBreakpoint() @ 51]
+		Native invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51
+		Reflective invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null]
+			Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51
+		Invoking "Breakable::iBreakpoint"
+			Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51
+Running TestClass1 invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+		Invoking "((Breakable)new TestClass1()).breakit()"
+		Invoking "new TestClass1().breakit()"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass1()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass1().breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running TestClass1ext invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+		Invoking "new TestClass1ext().breakit()"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass1ext().breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass1ext.breakit() @ 74]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+		Invoking "new TestClass1ext().breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass1ext.breakit() @ 74]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext]
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass1)new TestClass1ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass1ext().breakit()"
+			Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running TestClass2 invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Invoking "((Breakable)new TestClass2()).breakit()"
+		Invoking "new TestClass2().breakit()"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+		Invoking "((Breakable)new TestClass2()).breakit()"
+		Invoking "new TestClass2().breakit()"
+	Breaking on [public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2().breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2().breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+Running TestClass2ext invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+		Invoking "new TestClass2ext().breakit())"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+		Invoking "new TestClass2ext().breakit())"
+	Breaking on [public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+	Breaking on [public void art.Test993$TestClass2.breakit() @ 83, public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83, public void art.Test993$TestClass2ext.breakit() @ 91]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext]
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((Breakable)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "((TestClass2)new TestClass2ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+		Invoking "new TestClass2ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91
+			Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83
+Running TestClass3 invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+		Invoking "((Breakable)new TestClass3()).breakit()"
+		Invoking "new TestClass3().breakit())"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3().breakit())"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((Breakable)new TestClass3()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "new TestClass3().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running TestClass3ext invokes
+	Breaking on []
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+		Invoking "new TestClass3ext().breakit())"
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+	Breaking on [public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+	Breaking on [public void art.Test993$TestClass3.breakit() @ 99, public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+	Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99, public void art.Test993$TestClass3ext.breakit() @ 108]
+		Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext]
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((Breakable)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "((TestClass3)new TestClass3ext()).breakit()"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+		Invoking "new TestClass3ext().breakit())"
+			Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108
+			Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99
+			Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55
+Running private instance invoke
+	Breaking on []
+		Native invoking: private void art.Test993$TestClass4.privateMethod() args: [this: TestClass4]
+		Invoking "new TestClass4().callPrivateMethod()"
+	Breaking on [private void art.Test993$TestClass4.privateMethod() @ 118]
+		Native invoking: private void art.Test993$TestClass4.privateMethod() args: [this: TestClass4]
+			Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118
+		Invoking "new TestClass4().callPrivateMethod()"
+			Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118
+Running Vector constructor
+	Breaking on []
+		Native constructor: public java.util.Vector(), type: class java.util.Vector
+			Created: []
+		Reflective constructor: public java.util.Vector()
+			Created: []
+		Constructing: new Vector()
+			Created: []
+	Breaking on [public java.util.Vector() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Vector(), type: class java.util.Vector
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Vector()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Vector()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+Running Stack constructor
+	Breaking on []
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Created: []
+		Constructing: new Stack()
+			Created: []
+	Breaking on [public java.util.Stack() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Created: []
+	Breaking on [public java.util.Vector() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Stack()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+	Breaking on [public java.util.Stack() @ <NON-DETERMINISTIC>, public java.util.Vector() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+Running bcp static invoke
+	Breaking on []
+		Native invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+		Reflective invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+		Invoking "Optional::empty"
+	Breaking on [public static java.util.Optional java.util.Optional.empty() @ <NON-DETERMINISTIC>]
+		Native invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+			Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC>
+		Reflective invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+			Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC>
+		Invoking "Optional::empty"
+			Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC>
+Running bcp invoke
+	Breaking on []
+		Native invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test]]
+		Reflective invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test2]]
+		Invoking "Optional::isPresent"
+	Breaking on [public boolean java.util.Optional.isPresent() @ <NON-DETERMINISTIC>]
+		Native invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test]]
+			Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC>
+		Reflective invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test2]]
+			Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC>
+		Invoking "Optional::isPresent"
+			Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC>
+Running TestClass1 constructor
+	Breaking on []
+		Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1
+			Created: TestClass1
+		Reflective constructor: public art.Test993$TestClass1()
+			Created: TestClass1
+		Constructing: new TestClass1()
+			Created: TestClass1
+	Breaking on [public art.Test993$TestClass1() @ 62]
+		Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1
+		Reflective constructor: public art.Test993$TestClass1()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1
+		Constructing: new TestClass1()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1
+Running TestClass1ext constructor
+	Breaking on []
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Created: TestClass1Ext
+	Breaking on [public art.Test993$TestClass1() @ 62]
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+	Breaking on [public art.Test993$TestClass1ext() @ 70]
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Created: TestClass1Ext
+	Breaking on [public art.Test993$TestClass1() @ 62, public art.Test993$TestClass1ext() @ 70]
+		Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Reflective constructor: public art.Test993$TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
+		Constructing: new TestClass1ext()
+			Breakpoint: public art.Test993$TestClass1ext() @ line=70
+			Breakpoint: public art.Test993$TestClass1() @ line=62
+			Created: TestClass1Ext
diff --git a/test/993-breakpoints-non-debuggable/info.txt b/test/993-breakpoints-non-debuggable/info.txt
new file mode 100644
index 0000000..b5eb546
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/info.txt
@@ -0,0 +1,7 @@
+Test basic JVMTI breakpoint functionality.
+
+This test places a breakpoint on the first instruction of a number of functions
+that are entered in every way possible for the given class of method.
+
+It also tests that breakpoints don't interfere with each other by having
+multiple breakpoints be set at once.
diff --git a/test/993-breakpoints-non-debuggable/native_attach_agent.cc b/test/993-breakpoints-non-debuggable/native_attach_agent.cc
new file mode 100644
index 0000000..71d30eb
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/native_attach_agent.cc
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <sstream>
+
+#include "jni.h"
+#include "runtime.h"
+
+namespace art {
+namespace Test993BreakpointsNonDebuggable {
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test993AttachAgent_setupJvmti(JNIEnv* env, jclass) {
+  Runtime* runtime = Runtime::Current();
+  std::ostringstream oss;
+  oss << (kIsDebugBuild ? "libtiagentd.so" : "libtiagent.so") << "=993-non-debuggable,art";
+  LOG(INFO) << "agent " << oss.str();
+  runtime->AttachAgent(env, oss.str(), nullptr);
+}
+
+}  // namespace Test993BreakpointsNonDebuggable
+}  // namespace art
diff --git a/test/993-breakpoints-non-debuggable/onload.cc b/test/993-breakpoints-non-debuggable/onload.cc
new file mode 100644
index 0000000..dbbcadc
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/onload.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include "jvmti.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test993BreakpointsNonDebuggable {
+
+static constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000;
+
+static const jvmtiCapabilities limited_caps = {
+    .can_tag_objects = 1,
+    .can_generate_field_modification_events = 1,
+    .can_generate_field_access_events = 1,
+    .can_get_bytecodes = 1,
+    .can_get_synthetic_attribute = 1,
+    .can_get_owned_monitor_info = 0,
+    .can_get_current_contended_monitor = 1,
+    .can_get_monitor_info = 1,
+    .can_pop_frame = 0,
+    .can_redefine_classes = 0,
+    .can_signal_thread = 1,
+    .can_get_source_file_name = 1,
+    .can_get_line_numbers = 1,
+    .can_get_source_debug_extension = 1,
+    .can_access_local_variables = 0,
+    .can_maintain_original_method_order = 1,
+    .can_generate_single_step_events = 1,
+    .can_generate_exception_events = 0,
+    .can_generate_frame_pop_events = 0,
+    .can_generate_breakpoint_events = 1,
+    .can_suspend = 1,
+    .can_redefine_any_class = 0,
+    .can_get_current_thread_cpu_time = 0,
+    .can_get_thread_cpu_time = 0,
+    .can_generate_method_entry_events = 1,
+    .can_generate_method_exit_events = 1,
+    .can_generate_all_class_hook_events = 0,
+    .can_generate_compiled_method_load_events = 0,
+    .can_generate_monitor_events = 0,
+    .can_generate_vm_object_alloc_events = 1,
+    .can_generate_native_method_bind_events = 1,
+    .can_generate_garbage_collection_events = 1,
+    .can_generate_object_free_events = 1,
+    .can_force_early_return = 0,
+    .can_get_owned_monitor_stack_depth_info = 0,
+    .can_get_constant_pool = 0,
+    .can_set_native_method_prefix = 0,
+    .can_retransform_classes = 0,
+    .can_retransform_any_class = 0,
+    .can_generate_resource_exhaustion_heap_events = 0,
+    .can_generate_resource_exhaustion_threads_events = 0,
+};
+
+jint OnLoad(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), kArtTiVersion) != 0) {
+    printf("Unable to get jvmti env!\n");
+    return 1;
+  }
+
+  CheckJvmtiError(jvmti_env, jvmti_env->AddCapabilities(&limited_caps));
+  return 0;
+}
+
+}  // namespace Test993BreakpointsNonDebuggable
+}  // namespace art
diff --git a/test/993-breakpoints-non-debuggable/onload.h b/test/993-breakpoints-non-debuggable/onload.h
new file mode 100644
index 0000000..99ce0d2b
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/onload.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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_TEST_993_BREAKPOINTS_NON_DEBUGGABLE_ONLOAD_H_
+#define ART_TEST_993_BREAKPOINTS_NON_DEBUGGABLE_ONLOAD_H_
+
+#include "jni.h"
+
+namespace art {
+namespace Test993BreakpointsNonDebuggable {
+
+jint OnLoad(JavaVM* vm, char* options, void* reserved);
+
+}  // namespace Test993BreakpointsNonDebuggable
+}  // namespace art
+
+#endif  // ART_TEST_993_BREAKPOINTS_NON_DEBUGGABLE_ONLOAD_H_
diff --git a/test/993-breakpoints-non-debuggable/src/Main.java b/test/993-breakpoints-non-debuggable/src/Main.java
new file mode 100644
index 0000000..32fca46
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/src/Main.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+public class Main {
+    public static void main(String[] args) throws Exception {
+        System.loadLibrary(args[0]);
+        art.Test993AttachAgent.loadAgent();
+        art.Test993.run(true);
+    }
+}
diff --git a/test/993-breakpoints-non-debuggable/src/art/Breakpoint.java b/test/993-breakpoints-non-debuggable/src/art/Breakpoint.java
new file mode 120000
index 0000000..3673916
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/src/art/Breakpoint.java
@@ -0,0 +1 @@
+../../../jvmti-common/Breakpoint.java
\ No newline at end of file
diff --git a/test/993-breakpoints-non-debuggable/src/art/Test993.java b/test/993-breakpoints-non-debuggable/src/art/Test993.java
new file mode 120000
index 0000000..7466288
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/src/art/Test993.java
@@ -0,0 +1 @@
+../../../993-breakpoints/src/art/Test993.java
\ No newline at end of file
diff --git a/test/993-breakpoints-non-debuggable/src/art/Test993AttachAgent.java b/test/993-breakpoints-non-debuggable/src/art/Test993AttachAgent.java
new file mode 100644
index 0000000..ac82c84
--- /dev/null
+++ b/test/993-breakpoints-non-debuggable/src/art/Test993AttachAgent.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+package art;
+
+public class Test993AttachAgent {
+    public static native void setupJvmti();
+
+    public static void loadAgent() throws Exception {
+      setupJvmti();
+    }
+}
diff --git a/test/993-breakpoints/run b/test/993-breakpoints/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/993-breakpoints/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/993-breakpoints/run.py b/test/993-breakpoints/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/993-breakpoints/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/994-breakpoint-line/run b/test/994-breakpoint-line/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/994-breakpoint-line/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/994-breakpoint-line/run.py b/test/994-breakpoint-line/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/994-breakpoint-line/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/995-breakpoints-throw/run b/test/995-breakpoints-throw/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/995-breakpoints-throw/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/995-breakpoints-throw/run.py b/test/995-breakpoints-throw/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/995-breakpoints-throw/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/995-breakpoints-throw/src/Main.java b/test/995-breakpoints-throw/src/Main.java
index 6f80b43..e92cd9b 100644
--- a/test/995-breakpoints-throw/src/Main.java
+++ b/test/995-breakpoints-throw/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test995.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test995.run();
+    }
 }
diff --git a/test/995-breakpoints-throw/src/art/Test995.java b/test/995-breakpoints-throw/src/art/Test995.java
index a4023fb..5fb5e08 100644
--- a/test/995-breakpoints-throw/src/art/Test995.java
+++ b/test/995-breakpoints-throw/src/art/Test995.java
@@ -21,116 +21,119 @@
 import java.lang.reflect.Method;
 
 public class Test995 {
-  public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager();
-  public static BreakpointHandler HANDLER = null;
+    public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager();
+    public static BreakpointHandler HANDLER = null;
 
-  public static void doNothing() { }
+    public static void doNothing() { }
 
-  public static interface BreakpointHandler {
-    public void breakpointReached(Executable e, long loc);
-  }
-
-  public static void breakpoint() {
-    return;
-  }
-
-  public static void breakpointCatchLate() {
-    doNothing();
-    try {
-      doNothing();
-    } catch (Throwable t) {
-      System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\"");
+    public static interface BreakpointHandler {
+        public void breakpointReached(Executable e, long loc);
     }
-  }
 
-  public static void breakpointCatch() {
-    try {
-      doNothing();
-    } catch (Throwable t) {
-      System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\"");
+    public static void breakpoint() {
+        return;
     }
-  }
 
-  public static void notifyBreakpointReached(Thread thr, Executable e, long loc) {
-    System.out.println("\tBreakpoint: " + e + " @ line=" + Breakpoint.locationToLine(e, loc));
-    HANDLER.breakpointReached(e, loc);
-  }
-
-
-  public static BreakpointHandler makeHandler(String name, BreakpointHandler h) {
-    return new BreakpointHandler() {
-      public String toString() {
-        return name;
-      }
-      public void breakpointReached(Executable e, long loc) {
-        h.breakpointReached(e, loc);
-      }
-    };
-  }
-
-  public static Runnable makeTest(String name, Runnable test) {
-    return new Runnable() {
-      public String toString() { return name; }
-      public void run() { test.run(); }
-    };
-  }
-
-  public static void run() throws Exception {
-    // Set up breakpoints
-    Breakpoint.stopBreakpointWatch(Thread.currentThread());
-    Breakpoint.startBreakpointWatch(
-        Test995.class,
-        Test995.class.getDeclaredMethod(
-            "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE),
-        Thread.currentThread());
-
-    Method breakpoint_method = Test995.class.getDeclaredMethod("breakpoint");
-    Method breakpoint_catch_method = Test995.class.getDeclaredMethod("breakpointCatch");
-    Method breakpoint_catch_late_method = Test995.class.getDeclaredMethod("breakpointCatchLate");
-    MANAGER.setBreakpoint(breakpoint_method, Breakpoint.getStartLocation(breakpoint_method));
-    MANAGER.setBreakpoint(
-        breakpoint_catch_method, Breakpoint.getStartLocation(breakpoint_catch_method));
-    MANAGER.setBreakpoint(
-        breakpoint_catch_late_method, Breakpoint.getStartLocation(breakpoint_catch_late_method));
-
-    BreakpointHandler[] handlers = new BreakpointHandler[] {
-      makeHandler("do nothing", (e, l) -> {}),
-      makeHandler("throw", (e, l) -> { throw new Error("throwing error!"); }),
-    };
-
-    Runnable[] tests = new Runnable[] {
-      makeTest("call Test995::breakpoint", Test995::breakpoint),
-      makeTest("call Test995::breakpointCatch", Test995::breakpointCatch),
-      makeTest("call Test995::breakpointCatchLate", Test995::breakpointCatchLate),
-      makeTest("catch subroutine Test995::breakpoint",
-          () -> {
-            try {
-              breakpoint();
-            } catch (Throwable t) {
-              System.out.printf("Caught %s:\"%s\"\n", t.getClass().getName(), t.getMessage());
-            }
-          }),
-    };
-
-    for (BreakpointHandler handler : handlers) {
-      for (Runnable test : tests) {
+    public static void breakpointCatchLate() {
+        doNothing();
         try {
-          HANDLER = handler;
-          System.out.printf("Test \"%s\": Running breakpoint with handler \"%s\"\n",
-              test, handler);
-          test.run();
-          System.out.printf("Test \"%s\": No error caught with handler \"%s\"\n",
-              test, handler);
-        } catch (Throwable e) {
-          System.out.printf("Test \"%s\": Caught error %s:\"%s\" with handler \"%s\"\n",
-              test, e.getClass().getName(), e.getMessage(), handler);
+            doNothing();
+        } catch (Throwable t) {
+            System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\"");
         }
-        System.out.printf("Test \"%s\": Finished running with handler \"%s\"\n", test, handler);
-        HANDLER = null;
-      }
     }
 
-    MANAGER.clearAllBreakpoints();
-    Breakpoint.stopBreakpointWatch(Thread.currentThread());
-  }
+    public static void breakpointCatch() {
+        try {
+            doNothing();
+        } catch (Throwable t) {
+            System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\"");
+        }
+    }
+
+    public static void notifyBreakpointReached(Thread thr, Executable e, long loc) {
+        System.out.println("\tBreakpoint: " + e + " @ line=" + Breakpoint.locationToLine(e, loc));
+        HANDLER.breakpointReached(e, loc);
+    }
+
+
+    public static BreakpointHandler makeHandler(String name, BreakpointHandler h) {
+        return new BreakpointHandler() {
+            public String toString() {
+                return name;
+            }
+            public void breakpointReached(Executable e, long loc) {
+                h.breakpointReached(e, loc);
+            }
+        };
+    }
+
+    public static Runnable makeTest(String name, Runnable test) {
+        return new Runnable() {
+            public String toString() { return name; }
+            public void run() { test.run(); }
+        };
+    }
+
+    public static void run() throws Exception {
+        // Set up breakpoints
+        Breakpoint.stopBreakpointWatch(Thread.currentThread());
+        Breakpoint.startBreakpointWatch(
+                Test995.class,
+                Test995.class.getDeclaredMethod(
+                        "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE),
+                Thread.currentThread());
+
+        Method breakpoint_method = Test995.class.getDeclaredMethod("breakpoint");
+        Method breakpoint_catch_method = Test995.class.getDeclaredMethod("breakpointCatch");
+        Method breakpoint_catch_late_method =
+                Test995.class.getDeclaredMethod("breakpointCatchLate");
+        MANAGER.setBreakpoint(breakpoint_method, Breakpoint.getStartLocation(breakpoint_method));
+        MANAGER.setBreakpoint(
+                breakpoint_catch_method, Breakpoint.getStartLocation(breakpoint_catch_method));
+        MANAGER.setBreakpoint(breakpoint_catch_late_method,
+                Breakpoint.getStartLocation(breakpoint_catch_late_method));
+
+        BreakpointHandler[] handlers = new BreakpointHandler[] {
+                makeHandler("do nothing", (e, l) -> {}),
+                makeHandler("throw", (e, l) -> { throw new Error("throwing error!"); }),
+        };
+
+        Runnable[] tests = new Runnable[] {
+                makeTest("call Test995::breakpoint", Test995::breakpoint),
+                makeTest("call Test995::breakpointCatch", Test995::breakpointCatch),
+                makeTest("call Test995::breakpointCatchLate", Test995::breakpointCatchLate),
+                makeTest("catch subroutine Test995::breakpoint",
+                        () -> {
+                            try {
+                                breakpoint();
+                            } catch (Throwable t) {
+                                System.out.printf("Caught %s:\"%s\"\n", t.getClass().getName(),
+                                        t.getMessage());
+                            }
+                        }),
+        };
+
+        for (BreakpointHandler handler : handlers) {
+            for (Runnable test : tests) {
+                try {
+                    HANDLER = handler;
+                    System.out.printf(
+                            "Test \"%s\": Running breakpoint with handler \"%s\"\n", test, handler);
+                    test.run();
+                    System.out.printf(
+                            "Test \"%s\": No error caught with handler \"%s\"\n", test, handler);
+                } catch (Throwable e) {
+                    System.out.printf("Test \"%s\": Caught error %s:\"%s\" with handler \"%s\"\n",
+                            test, e.getClass().getName(), e.getMessage(), handler);
+                }
+                System.out.printf(
+                        "Test \"%s\": Finished running with handler \"%s\"\n", test, handler);
+                HANDLER = null;
+            }
+        }
+
+        MANAGER.clearAllBreakpoints();
+        Breakpoint.stopBreakpointWatch(Thread.currentThread());
+    }
 }
diff --git a/test/996-breakpoint-obsolete/run b/test/996-breakpoint-obsolete/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/996-breakpoint-obsolete/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/996-breakpoint-obsolete/run.py b/test/996-breakpoint-obsolete/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/996-breakpoint-obsolete/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/996-breakpoint-obsolete/src/Main.java b/test/996-breakpoint-obsolete/src/Main.java
index 1b9b0a9..521d7fa 100644
--- a/test/996-breakpoint-obsolete/src/Main.java
+++ b/test/996-breakpoint-obsolete/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test996.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test996.run();
+    }
 }
diff --git a/test/996-breakpoint-obsolete/src/art/Test996.java b/test/996-breakpoint-obsolete/src/art/Test996.java
index f3166c3..29dfff2 100644
--- a/test/996-breakpoint-obsolete/src/art/Test996.java
+++ b/test/996-breakpoint-obsolete/src/art/Test996.java
@@ -21,132 +21,133 @@
 import java.util.Base64;
 
 public class Test996 {
-  // The line we are going to break on. This should be the println in the Transform class. We set a
-  // breakpoint here after we have redefined the class.
-  public static final int TRANSFORM_BREAKPOINT_REDEFINED_LINE = 40;
+    // The line we are going to break on. This should be the println in the Transform class.
+    // We set a breakpoint here after we have redefined the class.
+    public static final int TRANSFORM_BREAKPOINT_REDEFINED_LINE = 40;
 
-  // The line we initially set a breakpoint on. This should be the doNothing call. This should be
-  // cleared by the redefinition and should only be caught on the initial run.
-  public static final int TRANSFORM_BREAKPOINT_INITIAL_LINE = 42;
+    // The line we initially set a breakpoint on. This should be the doNothing call. This should be
+    // cleared by the redefinition and should only be caught on the initial run.
+    public static final int TRANSFORM_BREAKPOINT_INITIAL_LINE = 42;
 
-  // A function that doesn't do anything. Used for giving places to break on in a function.
-  public static void doNothing() {}
+    // A function that doesn't do anything. Used for giving places to break on in a function.
+    public static void doNothing() {}
 
-  public static final class Transform {
-    public void run(Runnable r) {
-      r.run();
-      // Make sure we don't change anything above this line to keep all the breakpoint stuff
-      // working. We will be putting a breakpoint before this line in the runnable.
-      System.out.println("Should be after first breakpoint.");
-      // This is set as a breakpoint prior to redefinition. It should not be hit.
-      doNothing();
+    public static final class Transform {
+        public void run(Runnable r) {
+            r.run();
+            // Make sure we don't change anything above this line to keep all the breakpoint stuff
+            // working. We will be putting a breakpoint before this line in the runnable.
+            System.out.println("Should be after first breakpoint.");
+            // This is set as a breakpoint prior to redefinition. It should not be hit.
+            doNothing();
+        }
     }
-  }
 
-  /* ******************************************************************************************** */
-  // Try to keep all edits to this file below the above line. If edits need to be made above this
-  // line be sure to update the TRANSFORM_BREAKPOINT_REDEFINED_LINE and
-  // TRANSFORM_BREAKPOINT_INITIAL_LINE to their appropriate values.
+    /* ****************************************************************************************** */
+    // Try to keep all edits to this file below the above line. If edits need to be made above this
+    // line be sure to update the TRANSFORM_BREAKPOINT_REDEFINED_LINE and
+    // TRANSFORM_BREAKPOINT_INITIAL_LINE to their appropriate values.
 
-  public static final int TRANSFORM_BREAKPOINT_POST_REDEFINITION_LINE = 8;
+    public static final int TRANSFORM_BREAKPOINT_POST_REDEFINITION_LINE = 8;
 
-  // The base64 encoding of the following class. The redefined 'run' method should have the same
-  // instructions as the original. This means that the locations of each line should stay the same
-  // and the set of valid locations will not change. We use this to ensure that breakpoints are
-  // removed from the redefined method.
-  // public static final class Transform {
-  //   public void run(Runnable r) {
-  //     r.run();
-  //     System.out.println("Doing nothing transformed");
-  //     doNothing();  // try to catch non-removed breakpoints
-  //   }
-  // }
-  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
-    "yv66vgAAADQAKAoACAARCwASABMJABQAFQgAFgoAFwAYCgAZABoHABsHAB4BAAY8aW5pdD4BAAMo" +
-    "KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQADcnVuAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" +
-    "KVYBAApTb3VyY2VGaWxlAQAMVGVzdDk5Ni5qYXZhDAAJAAoHAB8MAA0ACgcAIAwAIQAiAQAZRG9p" +
-    "bmcgbm90aGluZyB0cmFuc2Zvcm1lZAcAIwwAJAAlBwAmDAAnAAoBABVhcnQvVGVzdDk5NiRUcmFu" +
-    "c2Zvcm0BAAlUcmFuc2Zvcm0BAAxJbm5lckNsYXNzZXMBABBqYXZhL2xhbmcvT2JqZWN0AQASamF2" +
-    "YS9sYW5nL1J1bm5hYmxlAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50" +
-    "U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3Ry" +
-    "aW5nOylWAQALYXJ0L1Rlc3Q5OTYBAAlkb05vdGhpbmcAMQAHAAgAAAAAAAIAAQAJAAoAAQALAAAA" +
-    "HQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAAEAAEADQAOAAEACwAAADYAAgACAAAAEiu5AAIB" +
-    "ALIAAxIEtgAFuAAGsQAAAAEADAAAABIABAAAAAYABgAHAA4ACAARAAkAAgAPAAAAAgAQAB0AAAAK" +
-    "AAEABwAZABwAGQ==");
-  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
-    "ZGV4CjAzNQBzn3TiKGAiM0fubj25v816W0k+niqj+SQcBAAAcAAAAHhWNBIAAAAAAAAAAFgDAAAW" +
-    "AAAAcAAAAAoAAADIAAAAAwAAAPAAAAABAAAAFAEAAAYAAAAcAQAAAQAAAEwBAACwAgAAbAEAANoB" +
-    "AADiAQAA/QEAABYCAAAlAgAASQIAAGkCAACAAgAAlAIAAKoCAAC+AgAA0gIAAOACAADrAgAA7gIA" +
-    "APICAAD/AgAACgMAABADAAAVAwAAHgMAACMDAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAA" +
-    "CQAAAAoAAAANAAAADQAAAAkAAAAAAAAADgAAAAkAAADMAQAADgAAAAkAAADUAQAACAAEABIAAAAA" +
-    "AAAAAAAAAAAAAQAUAAAAAQAAABAAAAAEAAIAEwAAAAUAAAAAAAAABgAAABQAAAAAAAAAEQAAAAUA" +
-    "AAAAAAAACwAAALwBAABHAwAAAAAAAAIAAAA4AwAAPgMAAAEAAQABAAAAKgMAAAQAAABwEAQAAAAO" +
-    "AAQAAgACAAAALwMAAA4AAAByEAUAAwBiAAAAGgEBAG4gAwAQAHEAAgAAAA4AbAEAAAAAAAAAAAAA" +
-    "AAAAAAEAAAAGAAAAAQAAAAcABjxpbml0PgAZRG9pbmcgbm90aGluZyB0cmFuc2Zvcm1lZAAXTGFy" +
-    "dC9UZXN0OTk2JFRyYW5zZm9ybTsADUxhcnQvVGVzdDk5NjsAIkxkYWx2aWsvYW5ub3RhdGlvbi9F" +
-    "bmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8v" +
-    "UHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJM" +
-    "amF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwAMVGVzdDk5Ni5qYXZhAAlUcmFu" +
-    "c2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFncwAJZG9Ob3RoaW5nAARuYW1lAANvdXQAB3ByaW50bG4A" +
-    "A3J1bgAFdmFsdWUABAAHDgAGAQAHDjx4PAACAgEVGAECAwIPBBkRFwwAAAEBAIGABPgCAQGQAwAA" +
-    "ABAAAAAAAAAAAQAAAAAAAAABAAAAFgAAAHAAAAACAAAACgAAAMgAAAADAAAAAwAAAPAAAAAEAAAA" +
-    "AQAAABQBAAAFAAAABgAAABwBAAAGAAAAAQAAAEwBAAADEAAAAQAAAGwBAAABIAAAAgAAAHgBAAAG" +
-    "IAAAAQAAALwBAAABEAAAAgAAAMwBAAACIAAAFgAAANoBAAADIAAAAgAAACoDAAAEIAAAAgAAADgD" +
-    "AAAAIAAAAQAAAEcDAAAAEAAAAQAAAFgDAAA=");
+    // The base64 encoding of the following class. The redefined 'run' method should have the same
+    // instructions as the original. This means that the locations of each line should stay the same
+    // and the set of valid locations will not change. We use this to ensure that breakpoints are
+    // removed from the redefined method.
+    // public static final class Transform {
+    //     public void run(Runnable r) {
+    //         r.run();
+    //         System.out.println("Doing nothing transformed");
+    //         doNothing();  // try to catch non-removed breakpoints
+    //     }
+    // }
+    private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+            "yv66vgAAADQAKAoACAARCwASABMJABQAFQgAFgoAFwAYCgAZABoHABsHAB4BAAY8aW5pdD4BAAMo" +
+            "KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQADcnVuAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" +
+            "KVYBAApTb3VyY2VGaWxlAQAMVGVzdDk5Ni5qYXZhDAAJAAoHAB8MAA0ACgcAIAwAIQAiAQAZRG9p" +
+            "bmcgbm90aGluZyB0cmFuc2Zvcm1lZAcAIwwAJAAlBwAmDAAnAAoBABVhcnQvVGVzdDk5NiRUcmFu" +
+            "c2Zvcm0BAAlUcmFuc2Zvcm0BAAxJbm5lckNsYXNzZXMBABBqYXZhL2xhbmcvT2JqZWN0AQASamF2" +
+            "YS9sYW5nL1J1bm5hYmxlAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50" +
+            "U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3Ry" +
+            "aW5nOylWAQALYXJ0L1Rlc3Q5OTYBAAlkb05vdGhpbmcAMQAHAAgAAAAAAAIAAQAJAAoAAQALAAAA" +
+            "HQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAAEAAEADQAOAAEACwAAADYAAgACAAAAEiu5AAIB" +
+            "ALIAAxIEtgAFuAAGsQAAAAEADAAAABIABAAAAAYABgAHAA4ACAARAAkAAgAPAAAAAgAQAB0AAAAK" +
+            "AAEABwAZABwAGQ==");
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQBzn3TiKGAiM0fubj25v816W0k+niqj+SQcBAAAcAAAAHhWNBIAAAAAAAAAAFgDAAAW" +
+            "AAAAcAAAAAoAAADIAAAAAwAAAPAAAAABAAAAFAEAAAYAAAAcAQAAAQAAAEwBAACwAgAAbAEAANoB" +
+            "AADiAQAA/QEAABYCAAAlAgAASQIAAGkCAACAAgAAlAIAAKoCAAC+AgAA0gIAAOACAADrAgAA7gIA" +
+            "APICAAD/AgAACgMAABADAAAVAwAAHgMAACMDAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAA" +
+            "CQAAAAoAAAANAAAADQAAAAkAAAAAAAAADgAAAAkAAADMAQAADgAAAAkAAADUAQAACAAEABIAAAAA" +
+            "AAAAAAAAAAAAAQAUAAAAAQAAABAAAAAEAAIAEwAAAAUAAAAAAAAABgAAABQAAAAAAAAAEQAAAAUA" +
+            "AAAAAAAACwAAALwBAABHAwAAAAAAAAIAAAA4AwAAPgMAAAEAAQABAAAAKgMAAAQAAABwEAQAAAAO" +
+            "AAQAAgACAAAALwMAAA4AAAByEAUAAwBiAAAAGgEBAG4gAwAQAHEAAgAAAA4AbAEAAAAAAAAAAAAA" +
+            "AAAAAAEAAAAGAAAAAQAAAAcABjxpbml0PgAZRG9pbmcgbm90aGluZyB0cmFuc2Zvcm1lZAAXTGFy" +
+            "dC9UZXN0OTk2JFRyYW5zZm9ybTsADUxhcnQvVGVzdDk5NjsAIkxkYWx2aWsvYW5ub3RhdGlvbi9F" +
+            "bmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8v" +
+            "UHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJM" +
+            "amF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwAMVGVzdDk5Ni5qYXZhAAlUcmFu" +
+            "c2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFncwAJZG9Ob3RoaW5nAARuYW1lAANvdXQAB3ByaW50bG4A" +
+            "A3J1bgAFdmFsdWUABAAHDgAGAQAHDjx4PAACAgEVGAECAwIPBBkRFwwAAAEBAIGABPgCAQGQAwAA" +
+            "ABAAAAAAAAAAAQAAAAAAAAABAAAAFgAAAHAAAAACAAAACgAAAMgAAAADAAAAAwAAAPAAAAAEAAAA" +
+            "AQAAABQBAAAFAAAABgAAABwBAAAGAAAAAQAAAEwBAAADEAAAAQAAAGwBAAABIAAAAgAAAHgBAAAG" +
+            "IAAAAQAAALwBAAABEAAAAgAAAMwBAAACIAAAFgAAANoBAAADIAAAAgAAACoDAAAEIAAAAgAAADgD" +
+            "AAAAIAAAAQAAAEcDAAAAEAAAAQAAAFgDAAA=");
 
-  public static void notifyBreakpointReached(Thread thr, Executable e, long loc) {
-    int line = Breakpoint.locationToLine(e, loc);
-    if (line == -1 && e.getName().equals("run") && e.getDeclaringClass().equals(Transform.class)) {
-      // RI always reports line = -1 for obsolete methods. Just replace it with the real line for
-      // consistency.
-      line = TRANSFORM_BREAKPOINT_REDEFINED_LINE;
+    public static void notifyBreakpointReached(Thread thr, Executable e, long loc) {
+        int line = Breakpoint.locationToLine(e, loc);
+        if (line == -1 && e.getName().equals("run")
+                && e.getDeclaringClass().equals(Transform.class)) {
+            // RI always reports line = -1 for obsolete methods. Just replace it with the real line
+            // for consistency.
+            line = TRANSFORM_BREAKPOINT_REDEFINED_LINE;
+        }
+        System.out.println("Breakpoint reached: " + e + " @ line=" + line);
     }
-    System.out.println("Breakpoint reached: " + e + " @ line=" + line);
-  }
 
-  public static void run() throws Exception {
-    // Set up breakpoints
-    Breakpoint.stopBreakpointWatch(Thread.currentThread());
-    Breakpoint.startBreakpointWatch(
-        Test996.class,
-        Test996.class.getDeclaredMethod(
-            "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE),
-        Thread.currentThread());
+    public static void run() throws Exception {
+        // Set up breakpoints
+        Breakpoint.stopBreakpointWatch(Thread.currentThread());
+        Breakpoint.startBreakpointWatch(
+                Test996.class,
+                Test996.class.getDeclaredMethod(
+                        "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE),
+                Thread.currentThread());
 
-    Transform t = new Transform();
-    Method non_obsolete_run_method = Transform.class.getDeclaredMethod("run", Runnable.class);
-    final long obsolete_breakpoint_location =
-        Breakpoint.lineToLocation(non_obsolete_run_method, TRANSFORM_BREAKPOINT_REDEFINED_LINE);
+        Transform t = new Transform();
+        Method non_obsolete_run_method = Transform.class.getDeclaredMethod("run", Runnable.class);
+        final long obsolete_breakpoint_location = Breakpoint.lineToLocation(
+                non_obsolete_run_method, TRANSFORM_BREAKPOINT_REDEFINED_LINE);
 
-    System.out.println("Initially setting breakpoint to line " + TRANSFORM_BREAKPOINT_INITIAL_LINE);
-    long initial_breakpoint_location =
-        Breakpoint.lineToLocation(non_obsolete_run_method, TRANSFORM_BREAKPOINT_INITIAL_LINE);
-    Breakpoint.setBreakpoint(non_obsolete_run_method, initial_breakpoint_location);
+        System.out.println(
+                "Initially setting breakpoint to line " + TRANSFORM_BREAKPOINT_INITIAL_LINE);
+        long initial_breakpoint_location = Breakpoint.lineToLocation(
+                non_obsolete_run_method, TRANSFORM_BREAKPOINT_INITIAL_LINE);
+        Breakpoint.setBreakpoint(non_obsolete_run_method, initial_breakpoint_location);
 
-    System.out.println("Running transform without redefinition.");
-    t.run(() -> {});
+        System.out.println("Running transform without redefinition.");
+        t.run(() -> {});
 
-    System.out.println("Running transform with redefinition.");
-    t.run(() -> {
-      System.out.println("Redefining calling function!");
-      // This should clear the breakpoint set to TRANSFORM_BREAKPOINT_INITIAL_LINE
-      Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
-      System.out.println("Setting breakpoint on now obsolete method to line " +
-          TRANSFORM_BREAKPOINT_REDEFINED_LINE);
-      setBreakpointOnObsoleteMethod(obsolete_breakpoint_location);
-    });
-    System.out.println("Running transform post redefinition. Should not hit any breakpoints.");
-    t.run(() -> {});
+        System.out.println("Running transform with redefinition.");
+        t.run(() -> {
+            System.out.println("Redefining calling function!");
+            // This should clear the breakpoint set to TRANSFORM_BREAKPOINT_INITIAL_LINE
+            Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+            System.out.println("Setting breakpoint on now obsolete method to line " +
+                    TRANSFORM_BREAKPOINT_REDEFINED_LINE);
+            setBreakpointOnObsoleteMethod(obsolete_breakpoint_location);
+        });
+        System.out.println("Running transform post redefinition. Should not hit any breakpoints.");
+        t.run(() -> {});
 
-    System.out.println("Setting initial breakpoint on redefined method.");
-    long final_breakpoint_location =
-        Breakpoint.lineToLocation(non_obsolete_run_method,
-                                  TRANSFORM_BREAKPOINT_POST_REDEFINITION_LINE);
-    Breakpoint.setBreakpoint(non_obsolete_run_method, final_breakpoint_location);
-    t.run(() -> {});
+        System.out.println("Setting initial breakpoint on redefined method.");
+        long final_breakpoint_location = Breakpoint.lineToLocation(
+                non_obsolete_run_method, TRANSFORM_BREAKPOINT_POST_REDEFINITION_LINE);
+        Breakpoint.setBreakpoint(non_obsolete_run_method, final_breakpoint_location);
+        t.run(() -> {});
 
-    Breakpoint.stopBreakpointWatch(Thread.currentThread());
-  }
+        Breakpoint.stopBreakpointWatch(Thread.currentThread());
+    }
 
-  public static native void setBreakpointOnObsoleteMethod(long location);
+    public static native void setBreakpointOnObsoleteMethod(long location);
 }
diff --git a/test/997-single-step/run b/test/997-single-step/run
deleted file mode 100755
index 51875a7..0000000
--- a/test/997-single-step/run
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-#
-# Copyright 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.
-
-# Ask for stack traces to be dumped to a file rather than to stdout.
-./default-run "$@" --jvmti
diff --git a/test/997-single-step/run.py b/test/997-single-step/run.py
new file mode 100644
index 0000000..ce3a55a
--- /dev/null
+++ b/test/997-single-step/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+
+def run(ctx, args):
+  # Ask for stack traces to be dumped to a file rather than to stdout.
+  ctx.default_run(args, jvmti=True)
diff --git a/test/997-single-step/src/Main.java b/test/997-single-step/src/Main.java
index 1927f04..a3e911d 100644
--- a/test/997-single-step/src/Main.java
+++ b/test/997-single-step/src/Main.java
@@ -15,7 +15,7 @@
  */
 
 public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Test997.run();
-  }
+    public static void main(String[] args) throws Exception {
+        art.Test997.run();
+    }
 }
diff --git a/test/997-single-step/src/art/Test997.java b/test/997-single-step/src/art/Test997.java
index a7a522d..88fbb87 100644
--- a/test/997-single-step/src/art/Test997.java
+++ b/test/997-single-step/src/art/Test997.java
@@ -21,62 +21,63 @@
 import java.lang.reflect.Method;
 
 public class Test997 {
-  static final int NO_LAST_LINE_NUMBER = -1;
-  static int LAST_LINE_NUMBER = NO_LAST_LINE_NUMBER;
-  static Method DO_MULTIPATH_METHOD;
+    static final int NO_LAST_LINE_NUMBER = -1;
+    static int LAST_LINE_NUMBER = NO_LAST_LINE_NUMBER;
+    static Method DO_MULTIPATH_METHOD;
 
-  static {
-    try {
-      DO_MULTIPATH_METHOD = Test997.class.getDeclaredMethod("doMultiPath", Boolean.TYPE);
-    } catch (Exception e) {
-      throw new Error("could not find method doMultiPath", e);
-    }
-  }
-
-  // Function that acts simply to ensure there are multiple lines.
-  public static void doNothing() {}
-
-  // Method with multiple paths we can break on.
-  public static void doMultiPath(boolean bit) {
-    doNothing();
-    if (bit) {
-      doNothing();
-    } else {
-      doNothing();
-    }
-    doNothing();
-  }
-
-  public static void notifySingleStep(Thread thr, Executable e, long loc) {
-    if (!e.equals(DO_MULTIPATH_METHOD)) {
-      // Only report steps in doMultiPath
-      return;
-    }
-    int cur_line = Breakpoint.locationToLine(e, loc);
-    // Only report anything when the line number changes. This is so we can run this test against
-    // both the RI and ART and also to prevent front-end compiler changes from affecting output.
-    if (LAST_LINE_NUMBER == NO_LAST_LINE_NUMBER || LAST_LINE_NUMBER != cur_line) {
-      LAST_LINE_NUMBER = cur_line;
-      System.out.println("Single step: " + e + " @ line=" + cur_line);
-    }
-  }
-
-  public static void resetTest() {
-    LAST_LINE_NUMBER = NO_LAST_LINE_NUMBER;
-  }
-
-  public static void run() throws Exception {
-    boolean[] values = new boolean[] { true, false };
-    Trace.enableSingleStepTracing(Test997.class,
-        Test997.class.getDeclaredMethod(
-            "notifySingleStep", Thread.class, Executable.class, Long.TYPE),
-        Thread.currentThread());
-    for (boolean arg : values) {
-      System.out.println("Stepping through doMultiPath(" + arg + ")");
-      resetTest();
-      doMultiPath(arg);
+    static {
+        try {
+            DO_MULTIPATH_METHOD = Test997.class.getDeclaredMethod("doMultiPath", Boolean.TYPE);
+        } catch (Exception e) {
+            throw new Error("could not find method doMultiPath", e);
+        }
     }
 
-    Trace.disableTracing(Thread.currentThread());
-  }
+    // Function that acts simply to ensure there are multiple lines.
+    public static void doNothing() {}
+
+    // Method with multiple paths we can break on.
+    public static void doMultiPath(boolean bit) {
+        doNothing();
+        if (bit) {
+            doNothing();
+        } else {
+            doNothing();
+        }
+        doNothing();
+    }
+
+    public static void notifySingleStep(Thread thr, Executable e, long loc) {
+        if (!e.equals(DO_MULTIPATH_METHOD)) {
+            // Only report steps in doMultiPath
+            return;
+        }
+        int cur_line = Breakpoint.locationToLine(e, loc);
+        // Only report anything when the line number changes. This is so we can run this test
+        // against both the RI and ART and also to prevent front-end compiler changes from
+        // affecting output.
+        if (LAST_LINE_NUMBER == NO_LAST_LINE_NUMBER || LAST_LINE_NUMBER != cur_line) {
+            LAST_LINE_NUMBER = cur_line;
+            System.out.println("Single step: " + e + " @ line=" + cur_line);
+        }
+    }
+
+    public static void resetTest() {
+        LAST_LINE_NUMBER = NO_LAST_LINE_NUMBER;
+    }
+
+    public static void run() throws Exception {
+        boolean[] values = new boolean[] { true, false };
+        Trace.enableSingleStepTracing(Test997.class,
+                Test997.class.getDeclaredMethod(
+                        "notifySingleStep", Thread.class, Executable.class, Long.TYPE),
+                Thread.currentThread());
+        for (boolean arg : values) {
+            System.out.println("Stepping through doMultiPath(" + arg + ")");
+            resetTest();
+            doMultiPath(arg);
+        }
+
+        Trace.disableTracing(Thread.currentThread());
+    }
 }
diff --git a/test/998-redefine-use-after-free/run b/test/998-redefine-use-after-free/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/998-redefine-use-after-free/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/998-redefine-use-after-free/run.py b/test/998-redefine-use-after-free/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/998-redefine-use-after-free/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/998-redefine-use-after-free/src-ex/DexCacheSmash.java b/test/998-redefine-use-after-free/src-ex/DexCacheSmash.java
index 2193a63..349b73e 100644
--- a/test/998-redefine-use-after-free/src-ex/DexCacheSmash.java
+++ b/test/998-redefine-use-after-free/src-ex/DexCacheSmash.java
@@ -18,26 +18,24 @@
 import java.util.Base64;
 
 public class DexCacheSmash {
-  static class Transform {
-    public void foo() {}
-    public void bar() {}
-    public String getId() {
-      return "TRANSFORM_INITIAL";
+    static class Transform {
+        public void foo() {}
+        public void bar() {}
+        public String getId() {
+            return "TRANSFORM_INITIAL";
+        }
     }
-  }
 
-  static class Transform2 {
-    public String getId() {
-      return "TRANSFORM2_INITIAL";
+    static class Transform2 {
+        public String getId() {
+            return "TRANSFORM2_INITIAL";
+        }
     }
-  }
 
-  /**
-   * A base64 encoding of the dex/class file of the Transform class above.
-   */
-  static final  Redefinition.CommonClassDefinition TRANSFORM_INITIAL =
-      new Redefinition.CommonClassDefinition(Transform.class,
-          Base64.getDecoder().decode(
+    /**
+     * A base64 encoding of the dex/class file of the Transform class above.
+     */
+    static final byte[] TRANSFORM_INITIAL_CLASS_FILE_BYTES = Base64.getDecoder().decode(
             "yv66vgAAADQAFwoABAAPCAAQBwASBwAVAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1i" +
             "ZXJUYWJsZQEAA2ZvbwEAA2JhcgEABWdldElkAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3Vy" +
             "Y2VGaWxlAQASRGV4Q2FjaGVTbWFzaC5qYXZhDAAFAAYBABFUUkFOU0ZPUk1fSU5JVElBTAcAFgEA" +
@@ -46,8 +44,8 @@
             "AAEAAAAFKrcAAbEAAAABAAgAAAAGAAEAAAATAAEACQAGAAEABwAAABkAAAABAAAAAbEAAAABAAgA" +
             "AAAGAAEAAAAUAAEACgAGAAEABwAAABkAAAABAAAAAbEAAAABAAgAAAAGAAEAAAAVAAEACwAMAAEA" +
             "BwAAABsAAQABAAAAAxICsAAAAAEACAAAAAYAAQAAABcAAgANAAAAAgAOABQAAAAKAAEAAwARABMA" +
-            "CA=="),
-          Base64.getDecoder().decode(
+            "CA==");
+    static final byte[] TRANSFORM_INITIAL_DEX_FILE_BYTES = Base64.getDecoder().decode(
             "ZGV4CjAzNQDhg9CfghG1SRlLClguRuFYsqihr4F7NsGQAwAAcAAAAHhWNBIAAAAAAAAAAOQCAAAS" +
             "AAAAcAAAAAcAAAC4AAAAAgAAANQAAAAAAAAAAAAAAAUAAADsAAAAAQAAABQBAABcAgAANAEAAKgB" +
             "AACwAQAAxAEAAMcBAADiAQAA8wEAABcCAAA3AgAASwIAAF8CAAByAgAAfQIAAIACAACNAgAAkgIA" +
@@ -63,59 +61,60 @@
             "AAcOABUABw4AFAAHDgAXAAcOAAICAREYAQIDAgwECBAXCgAAAQMAgIAEwAIBAdgCAQHsAgEBgAMO" +
             "AAAAAAAAAAEAAAAAAAAAAQAAABIAAABwAAAAAgAAAAcAAAC4AAAAAwAAAAIAAADUAAAABQAAAAUA" +
             "AADsAAAABgAAAAEAAAAUAQAAAxAAAAEAAAA0AQAAASAAAAQAAABAAQAABiAAAAEAAACYAQAAAiAA" +
-            "ABIAAACoAQAAAyAAAAQAAACrAgAABCAAAAIAAAC/AgAAACAAAAEAAADOAgAAABAAAAEAAADkAgAA"));
+            "ABIAAACoAQAAAyAAAAQAAACrAgAABCAAAAIAAAC/AgAAACAAAAEAAADOAgAAABAAAAEAAADkAgAA");
+    static final Redefinition.CommonClassDefinition TRANSFORM_INITIAL =
+            new Redefinition.CommonClassDefinition(Transform.class,
+                    TRANSFORM_INITIAL_CLASS_FILE_BYTES, TRANSFORM_INITIAL_DEX_FILE_BYTES);
 
-  /**
-   * A base64 encoding of the following (invalid) class.
-   *
-   *  .class LDexCacheSmash$Transform2;
-   *  .super Ljava/lang/Object;
-   *  .source "DexCacheSmash.java"
-   *
-   *  # annotations
-   *  .annotation system Ldalvik/annotation/EnclosingClass;
-   *      value = LDexCacheSmash;
-   *  .end annotation
-   *
-   *  .annotation system Ldalvik/annotation/InnerClass;
-   *      accessFlags = 0x8
-   *      name = "Transform2"
-   *  .end annotation
-   *
-   *
-   *  # direct methods
-   *  .method constructor <init>()V
-   *      .registers 1
-   *
-   *      .prologue
-   *      .line 26
-   *      invoke-direct {p0}, Ljava/lang/Object;-><init>()V
-   *
-   *      return-void
-   *  .end method
-   *
-   *
-   *  # virtual methods
-   *  .method public getId()Ljava/lang/String;
-   *      .registers 2
-   *
-   *      .prologue
-   *      .line 28
-   *      # NB Fails verification due to this function not returning a String.
-   *      return-void
-   *  .end method
-   */
-  static final  Redefinition.CommonClassDefinition TRANSFORM2_INVALID =
-      new Redefinition.CommonClassDefinition(Transform2.class,
-          Base64.getDecoder().decode(
+    /**
+     * A base64 encoding of the following (invalid) class.
+     *
+     *  .class LDexCacheSmash$Transform2;
+     *  .super Ljava/lang/Object;
+     *  .source "DexCacheSmash.java"
+     *
+     *  # annotations
+     *  .annotation system Ldalvik/annotation/EnclosingClass;
+     *      value = LDexCacheSmash;
+     *  .end annotation
+     *
+     *  .annotation system Ldalvik/annotation/InnerClass;
+     *      accessFlags = 0x8
+     *      name = "Transform2"
+     *  .end annotation
+     *
+     *
+     *  # direct methods
+     *  .method constructor <init>()V
+     *      .registers 1
+     *
+     *      .prologue
+     *      .line 26
+     *      invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+     *
+     *      return-void
+     *  .end method
+     *
+     *
+     *  # virtual methods
+     *  .method public getId()Ljava/lang/String;
+     *      .registers 2
+     *
+     *      .prologue
+     *      .line 28
+     *      # NB Fails verification due to this function not returning a String.
+     *      return-void
+     *  .end method
+     */
+    static final byte[] TRANSFORM2_INVALID_CLASS_FILE_BYTES = Base64.getDecoder().decode(
             "yv66vgAAADQAEwcAEgcAEQEABjxpbml0PgEAAygpVgEABENvZGUKAAIAEAEAD0xpbmVOdW1iZXJU" +
             "YWJsZQEABWdldElkAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQASRGV4Q2Fj" +
             "aGVTbWFzaC5qYXZhAQAMSW5uZXJDbGFzc2VzBwAPAQAKVHJhbnNmb3JtMgEADURleENhY2hlU21h" +
             "c2gMAAMABAEAEGphdmEvbGFuZy9PYmplY3QBABhEZXhDYWNoZVNtYXNoJFRyYW5zZm9ybTIAIAAB" +
             "AAIAAAAAAAIAAAADAAQAAQAFAAAAHQABAAEAAAAFKrcABrEAAAABAAcAAAAGAAEAAAAaAAEACAAJ" +
             "AAEABQAAABkAAQABAAAAAbEAAAABAAcAAAAGAAEAAAAcAAIACgAAAAIACwAMAAAACgABAAEADQAO" +
-            "AAg="),
-          Base64.getDecoder().decode(
+            "AAg=");
+    static final byte[] TRANSFORM2_INVALID_DEX_FILE_BYTES = Base64.getDecoder().decode(
             "ZGV4CjAzNQCFcegr6Ns+I7iEF4uLRkUX4yGrLhP6soEgAwAAcAAAAHhWNBIAAAAAAAAAAHQCAAAP" +
             "AAAAcAAAAAcAAACsAAAAAgAAAMgAAAAAAAAAAAAAAAMAAADgAAAAAQAAAPgAAAAIAgAAGAEAABgB" +
             "AAAgAQAANAEAADcBAABTAQAAZAEAAIgBAACoAQAAvAEAANABAADcAQAA3wEAAOwBAADzAQAA+QEA" +
@@ -130,26 +129,31 @@
             "BA4AAAAAAAAAAQAAAAAAAAABAAAADwAAAHAAAAACAAAABwAAAKwAAAADAAAAAgAAAMgAAAAFAAAA" +
             "AwAAAOAAAAAGAAAAAQAAAPgAAAACIAAADwAAABgBAAAEIAAAAgAAAAACAAADEAAAAgAAABACAAAG" +
             "IAAAAQAAACACAAADIAAAAgAAADACAAABIAAAAgAAADwCAAAAIAAAAQAAAGYCAAAAEAAAAQAAAHQC" +
-            "AAA="));
+            "AAA=");
+    static final Redefinition.CommonClassDefinition TRANSFORM2_INVALID =
+            new Redefinition.CommonClassDefinition(Transform2.class,
+                    TRANSFORM2_INVALID_CLASS_FILE_BYTES, TRANSFORM2_INVALID_DEX_FILE_BYTES);
 
-  public static void run() throws Exception {
-    try {
-      Redefinition.doMultiClassRedefinition(TRANSFORM2_INVALID);
-    } catch (Exception e) {
-      if (!e.getMessage().endsWith("JVMTI_ERROR_FAILS_VERIFICATION")) {
-        throw new Error(
-            "Unexpected error: Expected failure due to JVMTI_ERROR_FAILS_VERIFICATION", e);
-      }
+    public static void run() throws Exception {
+        try {
+            Redefinition.doMultiClassRedefinition(TRANSFORM2_INVALID);
+        } catch (Exception e) {
+            if (!e.getMessage().endsWith("JVMTI_ERROR_FAILS_VERIFICATION")) {
+                throw new Error(
+                        "Unexpected error: Expected failure due to JVMTI_ERROR_FAILS_VERIFICATION",
+                        e);
+            }
+        }
+        // Doing this redefinition after a redefinition that failed due to FAILS_VERIFICATION could
+        // cause a use-after-free of the Transform2's DexCache by the redefinition code if it
+        // happens that the native pointer of the art::DexFile created for the Transform
+        // redefinition aliases the one created for Transform2's failed redefinition.
+        //
+        // Due to the order of checks performed by the redefinition code FAILS_VERIFICATION is the
+        // only failure mode that can cause Use-after-frees in this way.
+        //
+        // This should never throw any exceptions (except perhaps OOME in very strange
+        // circumstances).
+        Redefinition.doMultiClassRedefinition(TRANSFORM_INITIAL);
     }
-    // Doing this redefinition after a redefinition that failed due to FAILS_VERIFICATION could
-    // cause a use-after-free of the Transform2's DexCache by the redefinition code if it happens
-    // that the native pointer of the art::DexFile created for the Transform redefinition aliases
-    // the one created for Transform2's failed redefinition.
-    //
-    // Due to the order of checks performed by the redefinition code FAILS_VERIFICATION is the only
-    // failure mode that can cause Use-after-frees in this way.
-    //
-    // This should never throw any exceptions (except perhaps OOME in very strange circumstances).
-    Redefinition.doMultiClassRedefinition(TRANSFORM_INITIAL);
-  }
 }
diff --git a/test/998-redefine-use-after-free/src-ex/art/Redefinition.java b/test/998-redefine-use-after-free/src-ex/art/Redefinition.java
index 56d2938..7b37afd 100644
--- a/test/998-redefine-use-after-free/src-ex/art/Redefinition.java
+++ b/test/998-redefine-use-after-free/src-ex/art/Redefinition.java
@@ -19,73 +19,69 @@
 import java.util.ArrayList;
 // Common Redefinition functions. Placed here for use by CTS
 public class Redefinition {
-  public static final class CommonClassDefinition {
-    public final Class<?> target;
-    public final byte[] class_file_bytes;
-    public final byte[] dex_file_bytes;
+    public static final class CommonClassDefinition {
+        public final Class<?> target;
+        public final byte[] class_file_bytes;
+        public final byte[] dex_file_bytes;
 
-    public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
-      this.target = target;
-      this.class_file_bytes = class_file_bytes;
-      this.dex_file_bytes = dex_file_bytes;
+        public CommonClassDefinition(
+                Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+            this.target = target;
+            this.class_file_bytes = class_file_bytes;
+            this.dex_file_bytes = dex_file_bytes;
+        }
     }
-  }
 
-  // A set of possible test configurations. Test should set this if they need to.
-  // This must be kept in sync with the defines in ti-agent/common_helper.cc
-  public static enum Config {
-    COMMON_REDEFINE(0),
-    COMMON_RETRANSFORM(1),
-    COMMON_TRANSFORM(2);
+    // A set of possible test configurations. Test should set this if they need to.
+    // This must be kept in sync with the defines in ti-agent/common_helper.cc
+    public static enum Config {
+        COMMON_REDEFINE(0),
+        COMMON_RETRANSFORM(1),
+        COMMON_TRANSFORM(2);
 
-    private final int val;
-    private Config(int val) {
-      this.val = val;
+        private final int val;
+        private Config(int val) {
+            this.val = val;
+        }
     }
-  }
 
-  public static void setTestConfiguration(Config type) {
-    nativeSetTestConfiguration(type.val);
-  }
-
-  private static native void nativeSetTestConfiguration(int type);
-
-  // Transforms the class
-  public static native void doCommonClassRedefinition(Class<?> target,
-                                                      byte[] classfile,
-                                                      byte[] dexfile);
-
-  public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
-    ArrayList<Class<?>> classes = new ArrayList<>();
-    ArrayList<byte[]> class_files = new ArrayList<>();
-    ArrayList<byte[]> dex_files = new ArrayList<>();
-
-    for (CommonClassDefinition d : defs) {
-      classes.add(d.target);
-      class_files.add(d.class_file_bytes);
-      dex_files.add(d.dex_file_bytes);
+    public static void setTestConfiguration(Config type) {
+        nativeSetTestConfiguration(type.val);
     }
-    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
-                                   class_files.toArray(new byte[0][]),
-                                   dex_files.toArray(new byte[0][]));
-  }
 
-  public static void addMultiTransformationResults(CommonClassDefinition... defs) {
-    for (CommonClassDefinition d : defs) {
-      addCommonTransformationResult(d.target.getCanonicalName(),
-                                    d.class_file_bytes,
-                                    d.dex_file_bytes);
+    private static native void nativeSetTestConfiguration(int type);
+
+    // Transforms the class
+    public static native void doCommonClassRedefinition(
+            Class<?> target, byte[] classfile, byte[] dexfile);
+
+    public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+        ArrayList<Class<?>> classes = new ArrayList<>();
+        ArrayList<byte[]> class_files = new ArrayList<>();
+        ArrayList<byte[]> dex_files = new ArrayList<>();
+
+        for (CommonClassDefinition d : defs) {
+            classes.add(d.target);
+            class_files.add(d.class_file_bytes);
+            dex_files.add(d.dex_file_bytes);
+        }
+        doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                class_files.toArray(new byte[0][]), dex_files.toArray(new byte[0][]));
     }
-  }
 
-  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
-                                                           byte[][] classfiles,
-                                                           byte[][] dexfiles);
-  public static native void doCommonClassRetransformation(Class<?>... target);
-  public static native void setPopRetransformations(boolean pop);
-  public static native void popTransformationFor(String name);
-  public static native void enableCommonRetransformation(boolean enable);
-  public static native void addCommonTransformationResult(String target_name,
-                                                          byte[] class_bytes,
-                                                          byte[] dex_bytes);
+    public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+        for (CommonClassDefinition d : defs) {
+            addCommonTransformationResult(
+                    d.target.getCanonicalName(), d.class_file_bytes, d.dex_file_bytes);
+        }
+    }
+
+    public static native void doCommonMultiClassRedefinition(
+            Class<?>[] targets, byte[][] classfiles, byte[][] dexfiles);
+    public static native void doCommonClassRetransformation(Class<?>... target);
+    public static native void setPopRetransformations(boolean pop);
+    public static native void popTransformationFor(String name);
+    public static native void enableCommonRetransformation(boolean enable);
+    public static native void addCommonTransformationResult(
+            String target_name, byte[] class_bytes, byte[] dex_bytes);
 }
diff --git a/test/998-redefine-use-after-free/src/Main.java b/test/998-redefine-use-after-free/src/Main.java
index cd3babf..fcc759f 100644
--- a/test/998-redefine-use-after-free/src/Main.java
+++ b/test/998-redefine-use-after-free/src/Main.java
@@ -17,38 +17,39 @@
 import java.lang.reflect.*;
 
 public class Main {
-  public static final String TEST_NAME = "998-redefine-use-after-free";
-  public static final int REPS = 1000;
-  public static final int STEP = 100;
+    public static final String TEST_NAME = "998-redefine-use-after-free";
+    public static final int REPS = 1000;
+    public static final int STEP = 100;
 
-  public static void main(String[] args) throws Exception {
-    for (int i = 0; i < REPS; i += STEP) {
-      runSeveralTimes(STEP);
+    public static void main(String[] args) throws Exception {
+        for (int i = 0; i < REPS; i += STEP) {
+            runSeveralTimes(STEP);
+        }
     }
-  }
 
-  public static ClassLoader getClassLoaderFor(String location) throws Exception {
-    try {
-      Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader");
-      Constructor<?> ctor = class_loader_class.getConstructor(String.class, ClassLoader.class);
-      return (ClassLoader)ctor.newInstance(location + "/" + TEST_NAME + "-ex.jar",
-                                           Main.class.getClassLoader());
-    } catch (ClassNotFoundException e) {
-      // Running on RI. Use URLClassLoader.
-      return new java.net.URLClassLoader(
-          new java.net.URL[] { new java.net.URL("file://" + location + "/classes-ex/") });
+    public static ClassLoader getClassLoaderFor(String location) throws Exception {
+        try {
+            Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader");
+            Constructor<?> ctor =
+                    class_loader_class.getConstructor(String.class, ClassLoader.class);
+            return (ClassLoader) ctor.newInstance(
+                    location + "/" + TEST_NAME + "-ex.jar", Main.class.getClassLoader());
+        } catch (ClassNotFoundException e) {
+            // Running on RI. Use URLClassLoader.
+            return new java.net.URLClassLoader(
+                    new java.net.URL[] { new java.net.URL("file://" + location + "/classes-ex/") });
+        }
     }
-  }
 
-  // Run the redefinition several times on a single class-loader to try to trigger the
-  // Use-after-free bug b/62237378
-  public static void runSeveralTimes(int times) throws Exception {
-    ClassLoader c = getClassLoaderFor(System.getenv("DEX_LOCATION"));
+    // Run the redefinition several times on a single class-loader to try to trigger the
+    // Use-after-free bug b/62237378
+    public static void runSeveralTimes(int times) throws Exception {
+        ClassLoader c = getClassLoaderFor(System.getenv("DEX_LOCATION"));
 
-    Class<?> klass = (Class<?>)c.loadClass("DexCacheSmash");
-    Method m = klass.getDeclaredMethod("run");
-    for (int i = 0 ; i < times; i++) {
-      m.invoke(null);
+        Class<?> klass = (Class<?>) c.loadClass("DexCacheSmash");
+        Method m = klass.getDeclaredMethod("run");
+        for (int i = 0; i < times; i++) {
+            m.invoke(null);
+        }
     }
-  }
 }
diff --git a/test/999-redefine-hiddenapi/build b/test/999-redefine-hiddenapi/build
deleted file mode 100644
index f4b029f..0000000
--- a/test/999-redefine-hiddenapi/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-USE_HIDDENAPI=true ./default-build "$@"
diff --git a/test/999-redefine-hiddenapi/build.py b/test/999-redefine-hiddenapi/build.py
new file mode 100644
index 0000000..942bb00
--- /dev/null
+++ b/test/999-redefine-hiddenapi/build.py
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2022 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.
+
+
+def build(ctx):
+  ctx.default_build(use_hiddenapi=True)
diff --git a/test/999-redefine-hiddenapi/run b/test/999-redefine-hiddenapi/run
deleted file mode 100755
index c6e62ae..0000000
--- a/test/999-redefine-hiddenapi/run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-run "$@" --jvmti
diff --git a/test/999-redefine-hiddenapi/run.py b/test/999-redefine-hiddenapi/run.py
new file mode 100644
index 0000000..4796039
--- /dev/null
+++ b/test/999-redefine-hiddenapi/run.py
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+
+def run(ctx, args):
+  ctx.default_run(args, jvmti=True)
diff --git a/test/999-redefine-hiddenapi/src-ex/Test999.java b/test/999-redefine-hiddenapi/src-ex/Test999.java
index 97495c5..d82619f 100644
--- a/test/999-redefine-hiddenapi/src-ex/Test999.java
+++ b/test/999-redefine-hiddenapi/src-ex/Test999.java
@@ -17,9 +17,9 @@
 package art;
 
 public class Test999 {
-  public void foo() {
-    System.out.println("hello");
-  }
+    public void foo() {
+        System.out.println("hello");
+    }
 
-  public int bar = 42;
+    public int bar = 42;
 }
diff --git a/test/999-redefine-hiddenapi/src-redefine/art/Test999.java b/test/999-redefine-hiddenapi/src-redefine/art/Test999.java
index c1b838c..3bdb2b6 100644
--- a/test/999-redefine-hiddenapi/src-redefine/art/Test999.java
+++ b/test/999-redefine-hiddenapi/src-redefine/art/Test999.java
@@ -17,9 +17,9 @@
 package art;
 
 public class Test999 {
-  public void foo() {
-    System.out.println("Goodbye");
-  }
+    public void foo() {
+        System.out.println("Goodbye");
+    }
 
-  public int bar = 64;
+    public int bar = 64;
 }
diff --git a/test/999-redefine-hiddenapi/src/Main.java b/test/999-redefine-hiddenapi/src/Main.java
index 014ea16..70dc006 100644
--- a/test/999-redefine-hiddenapi/src/Main.java
+++ b/test/999-redefine-hiddenapi/src/Main.java
@@ -19,92 +19,92 @@
 import java.util.Base64;
 
 public class Main {
-  public static void main(String[] args) throws ClassNotFoundException {
-    System.loadLibrary(args[0]);
+    public static void main(String[] args) throws ClassNotFoundException {
+        System.loadLibrary(args[0]);
 
-    // Run the initialization routine. This will enable hidden API checks in
-    // the runtime, in case they are not enabled by default.
-    init();
+        // Run the initialization routine. This will enable hidden API checks in
+        // the runtime, in case they are not enabled by default.
+        init();
 
-    // Load the '-ex' APK and attach it to the boot class path.
-    appendToBootClassLoader(DEX_EXTRA, /* isCorePlatform */ false);
+        // Load the '-ex' APK and attach it to the boot class path.
+        appendToBootClassLoader(DEX_EXTRA, /* isCorePlatform */ false);
 
-    // Find the test class in boot class loader and verify that its members are hidden.
-    Class<?> klass = Class.forName("art.Test999", true, BOOT_CLASS_LOADER);
-    assertFieldIsHidden(klass, "before redefinition");
-    assertMethodIsHidden(klass, "before redefinition");
+        // Find the test class in boot class loader and verify that its members are hidden.
+        Class<?> klass = Class.forName("art.Test999", true, BOOT_CLASS_LOADER);
+        assertFieldIsHidden(klass, "before redefinition");
+        assertMethodIsHidden(klass, "before redefinition");
 
-    // Redefine the class using JVMTI. Use dex file without hiddenapi flags.
-    art.Redefinition.setTestConfiguration(art.Redefinition.Config.COMMON_REDEFINE);
-    art.Redefinition.doCommonClassRedefinition(klass, CLASS_BYTES, DEX_BYTES);
+        // Redefine the class using JVMTI. Use dex file without hiddenapi flags.
+        art.Redefinition.setTestConfiguration(art.Redefinition.Config.COMMON_REDEFINE);
+        art.Redefinition.doCommonClassRedefinition(klass, CLASS_BYTES, DEX_BYTES);
 
-    // Verify that the class members are still hidden.
-    assertFieldIsHidden(klass, "after first redefinition");
-    assertMethodIsHidden(klass, "after first redefinition");
-  }
-
-  private static void assertMethodIsHidden(Class<?> klass, String msg) {
-    try {
-      klass.getDeclaredMethod("foo");
-      // Unexpected. Should have thrown NoSuchMethodException.
-      throw new RuntimeException("Method should not be accessible " + msg);
-    } catch (NoSuchMethodException ex) {
+        // Verify that the class members are still hidden.
+        assertFieldIsHidden(klass, "after first redefinition");
+        assertMethodIsHidden(klass, "after first redefinition");
     }
-  }
 
-  private static void assertFieldIsHidden(Class<?> klass, String msg) {
-    try {
-      klass.getDeclaredField("bar");
-      // Unexpected. Should have thrown NoSuchFieldException.
-      throw new RuntimeException("Field should not be accessible " + msg);
-    } catch (NoSuchFieldException ex) {
+    private static void assertMethodIsHidden(Class<?> klass, String msg) {
+        try {
+            klass.getDeclaredMethod("foo");
+            // Unexpected. Should have thrown NoSuchMethodException.
+            throw new RuntimeException("Method should not be accessible " + msg);
+        } catch (NoSuchMethodException ex) {
+        }
     }
-  }
 
-  private static final String DEX_EXTRA =
-      new File(System.getenv("DEX_LOCATION"), "999-redefine-hiddenapi-ex.jar").getAbsolutePath();
+    private static void assertFieldIsHidden(Class<?> klass, String msg) {
+        try {
+            klass.getDeclaredField("bar");
+            // Unexpected. Should have thrown NoSuchFieldException.
+            throw new RuntimeException("Field should not be accessible " + msg);
+        } catch (NoSuchFieldException ex) {
+        }
+    }
 
-  private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
+    private static final String DEX_EXTRA = new File(
+            System.getenv("DEX_LOCATION"), "999-redefine-hiddenapi-ex.jar").getAbsolutePath();
 
-  // Native functions. Note that these are implemented in 674-hiddenapi/hiddenapi.cc.
-  private static native void appendToBootClassLoader(String dexPath, boolean isCorePlatform);
-  private static native void init();
+    private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
 
-  /**
-   * base64 encoded class/dex file for
-   *
-   * public class Test999 {
-   *   public void foo() {
-   *     System.out.println("Goodbye");
-   *   }
-   *
-   *   public int bar = 64;
-   * }
-   */
-  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
-    "yv66vgAAADUAIAoABwARCQAGABIJABMAFAgAFQoAFgAXBwAYBwAZAQADYmFyAQABSQEABjxpbml0" +
-    "PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAANmb28BAApTb3VyY2VGaWxlAQAMVGVz" +
-    "dDk5OS5qYXZhDAAKAAsMAAgACQcAGgwAGwAcAQAHR29vZGJ5ZQcAHQwAHgAfAQALYXJ0L1Rlc3Q5" +
-    "OTkBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lv" +
-    "L1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xh" +
-    "bmcvU3RyaW5nOylWACEABgAHAAAAAQABAAgACQAAAAIAAQAKAAsAAQAMAAAAJwACAAEAAAALKrcA" +
-    "ASoQQLUAArEAAAABAA0AAAAKAAIAAAATAAQAGAABAA4ACwABAAwAAAAlAAIAAQAAAAmyAAMSBLYA" +
-    "BbEAAAABAA0AAAAKAAIAAAAVAAgAFgABAA8AAAACABA=");
-  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
-    "ZGV4CjAzNQDlfmgFfKulToQpDF+P4dsgeOkgfzzH+5lgAwAAcAAAAHhWNBIAAAAAAAAAALQCAAAQ" +
-    "AAAAcAAAAAcAAACwAAAAAgAAAMwAAAACAAAA5AAAAAQAAAD0AAAAAQAAABQBAAAsAgAANAEAAIYB" +
-    "AACOAQAAlwEAAJoBAACpAQAAwAEAANQBAADoAQAA/AEAAAoCAAANAgAAEQIAABYCAAAbAgAAIAIA" +
-    "ACkCAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAJAAAACQAAAAYAAAAAAAAACgAAAAYAAACAAQAA" +
-    "AQAAAAsAAAAFAAIADQAAAAEAAAAAAAAAAQAAAAwAAAACAAEADgAAAAMAAAAAAAAAAQAAAAEAAAAD" +
-    "AAAAAAAAAAgAAAAAAAAAoAIAAAAAAAACAAEAAQAAAHQBAAAIAAAAcBADAAEAEwBAAFkQAAAOAAMA" +
-    "AQACAAAAeQEAAAgAAABiAAEAGgEBAG4gAgAQAA4AEwAOQAAVAA54AAAAAQAAAAQABjxpbml0PgAH" +
-    "R29vZGJ5ZQABSQANTGFydC9UZXN0OTk5OwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9s" +
-    "YW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AAxUZXN0" +
-    "OTk5LmphdmEAAVYAAlZMAANiYXIAA2ZvbwADb3V0AAdwcmludGxuAHV+fkQ4eyJjb21waWxhdGlv" +
-    "bi1tb2RlIjoiZGVidWciLCJtaW4tYXBpIjoxLCJzaGEtMSI6ImQyMmFiNGYxOWI3NTYxNDQ3NTI4" +
-    "NTdjYTg2YjJjZWU0ZGQ5Y2ExNjYiLCJ2ZXJzaW9uIjoiMS40LjktZGV2In0AAAEBAQABAIGABLQC" +
-    "AQHUAgAAAAAOAAAAAAAAAAEAAAAAAAAAAQAAABAAAABwAAAAAgAAAAcAAACwAAAAAwAAAAIAAADM" +
-    "AAAABAAAAAIAAADkAAAABQAAAAQAAAD0AAAABgAAAAEAAAAUAQAAASAAAAIAAAA0AQAAAyAAAAIA" +
-    "AAB0AQAAARAAAAEAAACAAQAAAiAAABAAAACGAQAAACAAAAEAAACgAgAAAxAAAAEAAACwAgAAABAA" +
-    "AAEAAAC0AgAA");
+    // Native functions. Note that these are implemented in 674-hiddenapi/hiddenapi.cc.
+    private static native void appendToBootClassLoader(String dexPath, boolean isCorePlatform);
+    private static native void init();
+
+    /**
+     * base64 encoded class/dex file for
+     *
+     * public class Test999 {
+     *     public void foo() {
+     *         System.out.println("Goodbye");
+     *     }
+     *
+     *     public int bar = 64;
+     * }
+     */
+    private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+            "yv66vgAAADUAIAoABwARCQAGABIJABMAFAgAFQoAFgAXBwAYBwAZAQADYmFyAQABSQEABjxpbml0" +
+            "PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAANmb28BAApTb3VyY2VGaWxlAQAMVGVz" +
+            "dDk5OS5qYXZhDAAKAAsMAAgACQcAGgwAGwAcAQAHR29vZGJ5ZQcAHQwAHgAfAQALYXJ0L1Rlc3Q5" +
+            "OTkBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lv" +
+            "L1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xh" +
+            "bmcvU3RyaW5nOylWACEABgAHAAAAAQABAAgACQAAAAIAAQAKAAsAAQAMAAAAJwACAAEAAAALKrcA" +
+            "ASoQQLUAArEAAAABAA0AAAAKAAIAAAATAAQAGAABAA4ACwABAAwAAAAlAAIAAQAAAAmyAAMSBLYA" +
+            "BbEAAAABAA0AAAAKAAIAAAAVAAgAFgABAA8AAAACABA=");
+    private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+            "ZGV4CjAzNQDlfmgFfKulToQpDF+P4dsgeOkgfzzH+5lgAwAAcAAAAHhWNBIAAAAAAAAAALQCAAAQ" +
+            "AAAAcAAAAAcAAACwAAAAAgAAAMwAAAACAAAA5AAAAAQAAAD0AAAAAQAAABQBAAAsAgAANAEAAIYB" +
+            "AACOAQAAlwEAAJoBAACpAQAAwAEAANQBAADoAQAA/AEAAAoCAAANAgAAEQIAABYCAAAbAgAAIAIA" +
+            "ACkCAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAJAAAACQAAAAYAAAAAAAAACgAAAAYAAACAAQAA" +
+            "AQAAAAsAAAAFAAIADQAAAAEAAAAAAAAAAQAAAAwAAAACAAEADgAAAAMAAAAAAAAAAQAAAAEAAAAD" +
+            "AAAAAAAAAAgAAAAAAAAAoAIAAAAAAAACAAEAAQAAAHQBAAAIAAAAcBADAAEAEwBAAFkQAAAOAAMA" +
+            "AQACAAAAeQEAAAgAAABiAAEAGgEBAG4gAgAQAA4AEwAOQAAVAA54AAAAAQAAAAQABjxpbml0PgAH" +
+            "R29vZGJ5ZQABSQANTGFydC9UZXN0OTk5OwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9s" +
+            "YW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AAxUZXN0" +
+            "OTk5LmphdmEAAVYAAlZMAANiYXIAA2ZvbwADb3V0AAdwcmludGxuAHV+fkQ4eyJjb21waWxhdGlv" +
+            "bi1tb2RlIjoiZGVidWciLCJtaW4tYXBpIjoxLCJzaGEtMSI6ImQyMmFiNGYxOWI3NTYxNDQ3NTI4" +
+            "NTdjYTg2YjJjZWU0ZGQ5Y2ExNjYiLCJ2ZXJzaW9uIjoiMS40LjktZGV2In0AAAEBAQABAIGABLQC" +
+            "AQHUAgAAAAAOAAAAAAAAAAEAAAAAAAAAAQAAABAAAABwAAAAAgAAAAcAAACwAAAAAwAAAAIAAADM" +
+            "AAAABAAAAAIAAADkAAAABQAAAAQAAAD0AAAABgAAAAEAAAAUAQAAASAAAAIAAAA0AQAAAyAAAAIA" +
+            "AAB0AQAAARAAAAEAAACAAQAAAiAAABAAAACGAQAAACAAAAEAAACgAgAAAxAAAAEAAACwAgAAABAA" +
+            "AAEAAAC0AgAA");
 }
diff --git a/test/Android.bp b/test/Android.bp
index 6396f5e..5fc8cf9 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -58,7 +58,11 @@
             cflags: ["-Wno-frame-larger-than="],
         },
         host: {
-            cflags: ["-Wno-frame-larger-than="],
+            cflags: [
+                "-Wno-frame-larger-than=",
+                "-fsanitize-address-use-after-return=never",
+                "-Wno-unused-command-line-argument",
+            ],
         },
     },
 }
@@ -90,6 +94,9 @@
         android_arm64: {
             relative_install_path: "art/arm64",
         },
+        android_riscv64: {
+            relative_install_path: "art/riscv64",
+        },
         android_x86: {
             relative_install_path: "art/x86",
         },
@@ -104,10 +111,10 @@
     ],
 }
 
-// Variant of art_test_defaults that installs the library in a location which
-// will be added to the default namespace, and hence also the com_android_art
-// namespace. That allows the library to have shared_libs dependencies on all
-// ART internal libraries.
+// Variant of art_test_defaults for test libraries that installs them in a
+// location which will be added to the default namespace, and hence also the
+// com_android_art namespace. That allows them to have shared_libs
+// dependencies on all ART internal libraries.
 //
 // Currently this only works for run tests where run-test-jar sets
 // LD_LIBRARY_PATH and NATIVELOADER_DEFAULT_NAMESPACE_LIBS.
@@ -121,6 +128,9 @@
         android_arm64: {
             relative_install_path: "com.android.art/lib64",
         },
+        android_riscv64: {
+            relative_install_path: "com.android.art/lib64",
+        },
         android_x86: {
             relative_install_path: "com.android.art/lib",
         },
@@ -170,6 +180,21 @@
     // eventually.
     host_supported: false,
     test_config_template: ":art-gtests-target-standalone-template",
+
+    // Support multilib variants (using different suffix per sub-architecture),
+    // which is needed on build targets with secondary architectures, as the
+    // CTS/MTS/etc test suite packaging logic flattens all test artifacts into a
+    // single `testcases` directory. Also, there is CI testing that expects
+    // 64-bit multilib test suites to work for 32-bit devices (b/233550842).
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
 }
 
 // Properties common to `art_gtest_defaults` and `art_standalone_gtest_defaults`.
@@ -205,6 +230,10 @@
             shared_libs: [
                 "libziparchive",
             ],
+            cflags: [
+                "-fsanitize-address-use-after-return=never",
+                "-Wno-unused-command-line-argument",
+            ],
         },
     },
 }
@@ -365,6 +394,12 @@
         "libgtest_isolated",
     ],
     target: {
+        host: {
+            cflags: [
+                "-fsanitize-address-use-after-return=never",
+                "-Wno-unused-command-line-argument",
+            ],
+        },
         android32: {
             cflags: ["-DART_TARGET_NATIVETEST_DIR=/data/nativetest/art"],
         },
@@ -389,6 +424,7 @@
         // apex_available lists need to be the same for internal libs to avoid
         // stubs, and this depends on libdexfiled and others.
         "com.android.art",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -436,10 +472,15 @@
     ],
     shared_libs: [
         "libbase",
-        "libbacktrace",
         "liblog",
     ],
     target: {
+        host: {
+            cflags: [
+                "-fsanitize-address-use-after-return=never",
+                "-Wno-unused-command-line-argument",
+            ],
+        },
         darwin: {
             enabled: false,
         },
@@ -450,6 +491,7 @@
         // apex_available lists need to be the same for internal libs to avoid
         // stubs, and this depends on libdexfiled and others.
         "com.android.art",
+        "test_broken_com.android.art",
     ],
 }
 
@@ -618,7 +660,6 @@
     name: "libartagent-defaults",
     defaults: ["art_test_internal_library_defaults"],
     shared_libs: [
-        "libbacktrace",
         "libbase",
         "liblog",
         "libnativehelper",
@@ -708,6 +749,7 @@
         "991-field-trace-2/field_trace.cc",
         "992-source-data/source_file.cc",
         "993-breakpoints/breakpoints.cc",
+        "993-breakpoints-non-debuggable/onload.cc",
         "996-breakpoint-obsolete/obsolete_breakpoints.cc",
         "1900-track-alloc/alloc.cc",
         "1901-get-bytecodes/bytecodes.cc",
@@ -726,6 +768,7 @@
         "1932-monitor-events-misc/monitor_misc.cc",
         "1934-jvmti-signal-thread/signal_threads.cc",
         "1939-proxy-frames/local_instance.cc",
+        "1940-ddms-ext/ddm_ext.cc",
         "1941-dispose-stress/dispose_stress.cc",
         "1942-suspend-raw-monitor-exit/native_suspend_monitor.cc",
         "1943-suspend-raw-monitor-wait/native_suspend_monitor.cc",
@@ -746,6 +789,7 @@
         "2005-pause-all-redefine-multithreaded/pause-all.cc",
         "2009-structural-local-ref/local-ref.cc",
         "2035-structural-native-method/structural-native.cc",
+	"2243-single-step-default/single_step_helper.cc"
     ],
     // Use NDK-compatible headers for ctstiagent.
     header_libs: [
@@ -773,7 +817,7 @@
         "936-search-onload/search_onload.cc",
         "980-redefine-object/redef_object.cc",
         "983-source-transform-verify/source_transform_art.cc",
-        "1940-ddms-ext/ddm_ext.cc",
+        "993-breakpoints-non-debuggable/native_attach_agent.cc",
         // "1952-pop-frame-jit/pop_frame.cc",
         "1959-redefine-object-instrument/fake_redef_object.cc",
         "1960-obsolete-jit-multithread-native/native_say_hi.cc",
@@ -970,6 +1014,7 @@
         "800-smali/jni.cc",
         "817-hiddenapi/test_native.cc",
         "909-attach-agent/disallow_debugging.cc",
+        "993-breakpoints-non-debuggable/native_attach_agent.cc",
         "1001-app-image-regions/app_image_regions.cc",
         "1002-notify-startup/startup_interface.cc",
         "1947-breakpoint-redefine-deopt/check_deopt.cc",
@@ -982,14 +1027,15 @@
         "2037-thread-name-inherit/thread_name_inherit.cc",
         "2040-huge-native-alloc/huge_native_buf.cc",
         "2235-JdkUnsafeTest/unsafe_test.cc",
+	"2262-miranda-methods/jni_invoke.cc",
         "common/runtime_state.cc",
         "common/stack_inspect.cc",
     ],
     shared_libs: [
-        "libbacktrace",
         "libbase",
         "liblog",
         "libnativehelper",
+        "libunwindstack",
     ],
 }
 
@@ -1074,6 +1120,7 @@
         "992-source-data/src/art/Test992.java",
         "992-source-data/src/art/Target2.java",
         "993-breakpoints/src/art/Test993.java",
+        "993-breakpoints-non-debuggable/src/art/Test993AttachAgent.java",
         "994-breakpoint-line/src/art/Test994.java",
         "995-breakpoints-throw/src/art/Test995.java",
         "996-breakpoint-obsolete/src/art/Test996.java",
@@ -1112,6 +1159,7 @@
         "1936-thread-end-events/src/art/Test1936.java",
         "1937-transform-soft-fail/src/art/Test1937.java",
         "1939-proxy-frames/src/art/Test1939.java",
+        "1940-ddms-ext/src-art/art/Test1940.java",
         "1941-dispose-stress/src/art/Test1941.java",
         "1942-suspend-raw-monitor-exit/src/art/Test1942.java",
         "1943-suspend-raw-monitor-wait/src/art/Test1943.java",
@@ -1158,6 +1206,8 @@
         "2007-virtual-structural-finalizable/src-art/art/Test2007.java",
     ],
     sdk_version: "core_platform",
+    // Make sure that this will be added to the sdk snapshot for S.
+    min_sdk_version: "S",
     // Some ART run-tests contain constructs which break ErrorProne checks;
     // disable `errorprone` builds.
     errorprone: {
@@ -1219,6 +1269,7 @@
         "992-source-data/expected-stdout.txt",
         // Need to avoid using hidden-apis
         "993-breakpoints/expected_cts.txt",
+        "993-breakpoints-non-debuggable/expected_cts.txt",
         "994-breakpoint-line/expected-stdout.txt",
         "995-breakpoints-throw/expected-stdout.txt",
         "996-breakpoint-obsolete/expected-stdout.txt",
@@ -1257,6 +1308,7 @@
         "1936-thread-end-events/expected-stdout.txt",
         "1937-transform-soft-fail/expected-stdout.txt",
         "1939-proxy-frames/expected-stdout.txt",
+        "1940-ddms-ext/expected-stdout.txt",
         "1941-dispose-stress/expected-stdout.txt",
         "1942-suspend-raw-monitor-exit/expected-stdout.txt",
         "1943-suspend-raw-monitor-wait/expected-stdout.txt",
@@ -1321,6 +1373,8 @@
         "expected_cts_outputs_gen",
     ],
     sdk_version: "core_current",
+    // Make sure that this will be added to the sdk snapshot for S.
+    min_sdk_version: "S",
 }
 
 art_cc_test {
@@ -1386,38 +1440,12 @@
     test_config_template: "csuite-app-compile-launch.xml",
 }
 
-// Install run-test data in the output directory.
-prebuilt_etc_host {
-    name: "art-run-test-host-data",
-    defaults: ["art_module_source_build_prebuilt_defaults"],
-    src: ":art-run-test-host-data-merged",
-    sub_dir: "art",
-    filename: "art-run-test-host-data.zip",
-}
-
-// Install run-test data in the output directory.
-prebuilt_etc_host {
-    name: "art-run-test-jvm-data",
-    defaults: ["art_module_source_build_prebuilt_defaults"],
-    src: ":art-run-test-jvm-data-merged",
-    sub_dir: "art",
-    filename: "art-run-test-jvm-data.zip",
-}
-
-// Install run-test data in the output directory.
-prebuilt_etc_host {
-    name: "art-run-test-target-data",
-    defaults: ["art_module_source_build_prebuilt_defaults"],
-    src: ":art-run-test-target-data-merged",
-    sub_dir: "art",
-    filename: "art-run-test-target-data.zip",
-}
-
 filegroup {
     name: "art-gtest-jars",
     srcs: [
         ":art-gtest-jars-AbstractMethod",
         ":art-gtest-jars-AllFields",
+        ":art-gtest-jars-ArrayClassWithUnresolvedComponent",
         ":art-gtest-jars-DefaultMethods",
         ":art-gtest-jars-ErroneousA",
         ":art-gtest-jars-ErroneousB",
@@ -1464,6 +1492,7 @@
         ":art-gtest-jars-MainStripped",
         ":art-gtest-jars-MainUncompressedAligned",
         ":art-gtest-jars-MultiDexUncompressedAligned",
+        ":art-gtest-jars-SuperWithAccessChecks",
         ":art-gtest-jars-VerifierDeps",
         ":art-gtest-jars-VerifierDepsMulti",
         ":art-gtest-jars-VerifySoftFailDuringClinit",
@@ -1869,6 +1898,20 @@
 }
 
 genrule {
+    name: "art-gtest-jars-ArrayClassWithUnresolvedComponent",
+    defaults: ["art-gtest-jars-smali-defaults"],
+    srcs: ["ArrayClassWithUnresolvedComponent/*.smali"],
+    out: ["art-gtest-jars-ArrayClassWithUnresolvedComponent.dex"],
+}
+
+genrule {
+    name: "art-gtest-jars-SuperWithAccessChecks",
+    defaults: ["art-gtest-jars-smali-defaults"],
+    srcs: ["SuperWithAccessChecks/*.smali"],
+    out: ["art-gtest-jars-SuperWithAccessChecks.dex"],
+}
+
+genrule {
     name: "art-gtest-jars-LinkageTest",
     defaults: ["art-gtest-jars-smali-defaults"],
     srcs: ["LinkageTest/*.smali"],
@@ -1890,11 +1933,7 @@
         "art_module_source_build_genrule_defaults",
     ],
     tool_files: [
-        "run-test-build.py",
-        "buildfailures.json",
-        "etc/default-build",
-        "etc/default-run",
-        "etc/default-check",
+        "run_test_build.py",
         ":art-run-test-bootclasspath",
     ],
     tools: [
diff --git a/test/Android.run-test.bp b/test/Android.run-test.bp
index 27666f8..b5fed42 100644
--- a/test/Android.run-test.bp
+++ b/test/Android.run-test.bp
@@ -2,3031 +2,6301 @@
 // It is not necessary to regenerate it when tests are added/removed/modified.
 
 java_genrule {
-    name: "art-run-test-host-data-shard00",
+    name: "art-run-test-host-data-shard00-tmp",
     out: ["art-run-test-host-data-shard00.zip"],
-    srcs: ["*00-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 00 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?00-*/**/*", "??00-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard00",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard00-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard00.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard01",
+    name: "art-run-test-host-data-shard01-tmp",
     out: ["art-run-test-host-data-shard01.zip"],
-    srcs: ["*01-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 01 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?01-*/**/*", "??01-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard01",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard01-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard01.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard02",
+    name: "art-run-test-host-data-shard02-tmp",
     out: ["art-run-test-host-data-shard02.zip"],
-    srcs: ["*02-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 02 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?02-*/**/*", "??02-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard02",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard02-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard02.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard03",
+    name: "art-run-test-host-data-shard03-tmp",
     out: ["art-run-test-host-data-shard03.zip"],
-    srcs: ["*03-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 03 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?03-*/**/*", "??03-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard03",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard03-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard03.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard04",
+    name: "art-run-test-host-data-shard04-tmp",
     out: ["art-run-test-host-data-shard04.zip"],
-    srcs: ["*04-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 04 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?04-*/**/*", "??04-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard04",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard04-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard04.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard05",
+    name: "art-run-test-host-data-shard05-tmp",
     out: ["art-run-test-host-data-shard05.zip"],
-    srcs: ["*05-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 05 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?05-*/**/*", "??05-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard05",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard05-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard05.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard06",
+    name: "art-run-test-host-data-shard06-tmp",
     out: ["art-run-test-host-data-shard06.zip"],
-    srcs: ["*06-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 06 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?06-*/**/*", "??06-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard06",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard06-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard06.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard07",
+    name: "art-run-test-host-data-shard07-tmp",
     out: ["art-run-test-host-data-shard07.zip"],
-    srcs: ["*07-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 07 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?07-*/**/*", "??07-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard07",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard07-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard07.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard08",
+    name: "art-run-test-host-data-shard08-tmp",
     out: ["art-run-test-host-data-shard08.zip"],
-    srcs: ["*08-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 08 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?08-*/**/*", "??08-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard08",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard08-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard08.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard09",
+    name: "art-run-test-host-data-shard09-tmp",
     out: ["art-run-test-host-data-shard09.zip"],
-    srcs: ["*09-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 09 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?09-*/**/*", "??09-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard09",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard09-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard09.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard10",
+    name: "art-run-test-host-data-shard10-tmp",
     out: ["art-run-test-host-data-shard10.zip"],
-    srcs: ["*10-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 10 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?10-*/**/*", "??10-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard10",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard10-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard10.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard11",
+    name: "art-run-test-host-data-shard11-tmp",
     out: ["art-run-test-host-data-shard11.zip"],
-    srcs: ["*11-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 11 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?11-*/**/*", "??11-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard11",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard11-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard11.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard12",
+    name: "art-run-test-host-data-shard12-tmp",
     out: ["art-run-test-host-data-shard12.zip"],
-    srcs: ["*12-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 12 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?12-*/**/*", "??12-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard12",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard12-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard12.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard13",
+    name: "art-run-test-host-data-shard13-tmp",
     out: ["art-run-test-host-data-shard13.zip"],
-    srcs: ["*13-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 13 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?13-*/**/*", "??13-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard13",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard13-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard13.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard14",
+    name: "art-run-test-host-data-shard14-tmp",
     out: ["art-run-test-host-data-shard14.zip"],
-    srcs: ["*14-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 14 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?14-*/**/*", "??14-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard14",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard14-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard14.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard15",
+    name: "art-run-test-host-data-shard15-tmp",
     out: ["art-run-test-host-data-shard15.zip"],
-    srcs: ["*15-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 15 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?15-*/**/*", "??15-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard15",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard15-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard15.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard16",
+    name: "art-run-test-host-data-shard16-tmp",
     out: ["art-run-test-host-data-shard16.zip"],
-    srcs: ["*16-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 16 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?16-*/**/*", "??16-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard16",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard16-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard16.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard17",
+    name: "art-run-test-host-data-shard17-tmp",
     out: ["art-run-test-host-data-shard17.zip"],
-    srcs: ["*17-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 17 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?17-*/**/*", "??17-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard17",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard17-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard17.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard18",
+    name: "art-run-test-host-data-shard18-tmp",
     out: ["art-run-test-host-data-shard18.zip"],
-    srcs: ["*18-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 18 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?18-*/**/*", "??18-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard18",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard18-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard18.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard19",
+    name: "art-run-test-host-data-shard19-tmp",
     out: ["art-run-test-host-data-shard19.zip"],
-    srcs: ["*19-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 19 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?19-*/**/*", "??19-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard19",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard19-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard19.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard20",
+    name: "art-run-test-host-data-shard20-tmp",
     out: ["art-run-test-host-data-shard20.zip"],
-    srcs: ["*20-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 20 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?20-*/**/*", "??20-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard20",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard20-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard20.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard21",
+    name: "art-run-test-host-data-shard21-tmp",
     out: ["art-run-test-host-data-shard21.zip"],
-    srcs: ["*21-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 21 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?21-*/**/*", "??21-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard21",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard21-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard21.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard22",
+    name: "art-run-test-host-data-shard22-tmp",
     out: ["art-run-test-host-data-shard22.zip"],
-    srcs: ["*22-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 22 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?22-*/**/*", "??22-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard22",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard22-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard22.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard23",
+    name: "art-run-test-host-data-shard23-tmp",
     out: ["art-run-test-host-data-shard23.zip"],
-    srcs: ["*23-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 23 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?23-*/**/*", "??23-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard23",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard23-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard23.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard24",
+    name: "art-run-test-host-data-shard24-tmp",
     out: ["art-run-test-host-data-shard24.zip"],
-    srcs: ["*24-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 24 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?24-*/**/*", "??24-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard24",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard24-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard24.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard25",
+    name: "art-run-test-host-data-shard25-tmp",
     out: ["art-run-test-host-data-shard25.zip"],
-    srcs: ["*25-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 25 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?25-*/**/*", "??25-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard25",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard25-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard25.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard26",
+    name: "art-run-test-host-data-shard26-tmp",
     out: ["art-run-test-host-data-shard26.zip"],
-    srcs: ["*26-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 26 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?26-*/**/*", "??26-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard26",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard26-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard26.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard27",
+    name: "art-run-test-host-data-shard27-tmp",
     out: ["art-run-test-host-data-shard27.zip"],
-    srcs: ["*27-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 27 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?27-*/**/*", "??27-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard27",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard27-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard27.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard28",
+    name: "art-run-test-host-data-shard28-tmp",
     out: ["art-run-test-host-data-shard28.zip"],
-    srcs: ["*28-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 28 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?28-*/**/*", "??28-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard28",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard28-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard28.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard29",
+    name: "art-run-test-host-data-shard29-tmp",
     out: ["art-run-test-host-data-shard29.zip"],
-    srcs: ["*29-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 29 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?29-*/**/*", "??29-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard29",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard29-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard29.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard30",
+    name: "art-run-test-host-data-shard30-tmp",
     out: ["art-run-test-host-data-shard30.zip"],
-    srcs: ["*30-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 30 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?30-*/**/*", "??30-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard30",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard30-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard30.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard31",
+    name: "art-run-test-host-data-shard31-tmp",
     out: ["art-run-test-host-data-shard31.zip"],
-    srcs: ["*31-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 31 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?31-*/**/*", "??31-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard31",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard31-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard31.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard32",
+    name: "art-run-test-host-data-shard32-tmp",
     out: ["art-run-test-host-data-shard32.zip"],
-    srcs: ["*32-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 32 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?32-*/**/*", "??32-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard32",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard32-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard32.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard33",
+    name: "art-run-test-host-data-shard33-tmp",
     out: ["art-run-test-host-data-shard33.zip"],
-    srcs: ["*33-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 33 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?33-*/**/*", "??33-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard33",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard33-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard33.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard34",
+    name: "art-run-test-host-data-shard34-tmp",
     out: ["art-run-test-host-data-shard34.zip"],
-    srcs: ["*34-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 34 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?34-*/**/*", "??34-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard34",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard34-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard34.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard35",
+    name: "art-run-test-host-data-shard35-tmp",
     out: ["art-run-test-host-data-shard35.zip"],
-    srcs: ["*35-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 35 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?35-*/**/*", "??35-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard35",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard35-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard35.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard36",
+    name: "art-run-test-host-data-shard36-tmp",
     out: ["art-run-test-host-data-shard36.zip"],
-    srcs: ["*36-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 36 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?36-*/**/*", "??36-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard36",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard36-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard36.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard37",
+    name: "art-run-test-host-data-shard37-tmp",
     out: ["art-run-test-host-data-shard37.zip"],
-    srcs: ["*37-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 37 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?37-*/**/*", "??37-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard37",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard37-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard37.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard38",
+    name: "art-run-test-host-data-shard38-tmp",
     out: ["art-run-test-host-data-shard38.zip"],
-    srcs: ["*38-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 38 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?38-*/**/*", "??38-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard38",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard38-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard38.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard39",
+    name: "art-run-test-host-data-shard39-tmp",
     out: ["art-run-test-host-data-shard39.zip"],
-    srcs: ["*39-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 39 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?39-*/**/*", "??39-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard39",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard39-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard39.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard40",
+    name: "art-run-test-host-data-shard40-tmp",
     out: ["art-run-test-host-data-shard40.zip"],
-    srcs: ["*40-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 40 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?40-*/**/*", "??40-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard40",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard40-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard40.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard41",
+    name: "art-run-test-host-data-shard41-tmp",
     out: ["art-run-test-host-data-shard41.zip"],
-    srcs: ["*41-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 41 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?41-*/**/*", "??41-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard41",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard41-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard41.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard42",
+    name: "art-run-test-host-data-shard42-tmp",
     out: ["art-run-test-host-data-shard42.zip"],
-    srcs: ["*42-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 42 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?42-*/**/*", "??42-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard42",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard42-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard42.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard43",
+    name: "art-run-test-host-data-shard43-tmp",
     out: ["art-run-test-host-data-shard43.zip"],
-    srcs: ["*43-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 43 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?43-*/**/*", "??43-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard43",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard43-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard43.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard44",
+    name: "art-run-test-host-data-shard44-tmp",
     out: ["art-run-test-host-data-shard44.zip"],
-    srcs: ["*44-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 44 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?44-*/**/*", "??44-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard44",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard44-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard44.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard45",
+    name: "art-run-test-host-data-shard45-tmp",
     out: ["art-run-test-host-data-shard45.zip"],
-    srcs: ["*45-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 45 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?45-*/**/*", "??45-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard45",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard45-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard45.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard46",
+    name: "art-run-test-host-data-shard46-tmp",
     out: ["art-run-test-host-data-shard46.zip"],
-    srcs: ["*46-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 46 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?46-*/**/*", "??46-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard46",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard46-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard46.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard47",
+    name: "art-run-test-host-data-shard47-tmp",
     out: ["art-run-test-host-data-shard47.zip"],
-    srcs: ["*47-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 47 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?47-*/**/*", "??47-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard47",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard47-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard47.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard48",
+    name: "art-run-test-host-data-shard48-tmp",
     out: ["art-run-test-host-data-shard48.zip"],
-    srcs: ["*48-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 48 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?48-*/**/*", "??48-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard48",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard48-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard48.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard49",
+    name: "art-run-test-host-data-shard49-tmp",
     out: ["art-run-test-host-data-shard49.zip"],
-    srcs: ["*49-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 49 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?49-*/**/*", "??49-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard49",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard49-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard49.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard50",
+    name: "art-run-test-host-data-shard50-tmp",
     out: ["art-run-test-host-data-shard50.zip"],
-    srcs: ["*50-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 50 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?50-*/**/*", "??50-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard50",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard50-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard50.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard51",
+    name: "art-run-test-host-data-shard51-tmp",
     out: ["art-run-test-host-data-shard51.zip"],
-    srcs: ["*51-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 51 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?51-*/**/*", "??51-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard51",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard51-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard51.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard52",
+    name: "art-run-test-host-data-shard52-tmp",
     out: ["art-run-test-host-data-shard52.zip"],
-    srcs: ["*52-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 52 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?52-*/**/*", "??52-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard52",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard52-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard52.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard53",
+    name: "art-run-test-host-data-shard53-tmp",
     out: ["art-run-test-host-data-shard53.zip"],
-    srcs: ["*53-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 53 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?53-*/**/*", "??53-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard53",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard53-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard53.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard54",
+    name: "art-run-test-host-data-shard54-tmp",
     out: ["art-run-test-host-data-shard54.zip"],
-    srcs: ["*54-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 54 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?54-*/**/*", "??54-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard54",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard54-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard54.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard55",
+    name: "art-run-test-host-data-shard55-tmp",
     out: ["art-run-test-host-data-shard55.zip"],
-    srcs: ["*55-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 55 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?55-*/**/*", "??55-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard55",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard55-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard55.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard56",
+    name: "art-run-test-host-data-shard56-tmp",
     out: ["art-run-test-host-data-shard56.zip"],
-    srcs: ["*56-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 56 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?56-*/**/*", "??56-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard56",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard56-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard56.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard57",
+    name: "art-run-test-host-data-shard57-tmp",
     out: ["art-run-test-host-data-shard57.zip"],
-    srcs: ["*57-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 57 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?57-*/**/*", "??57-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard57",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard57-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard57.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard58",
+    name: "art-run-test-host-data-shard58-tmp",
     out: ["art-run-test-host-data-shard58.zip"],
-    srcs: ["*58-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 58 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?58-*/**/*", "??58-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard58",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard58-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard58.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard59",
+    name: "art-run-test-host-data-shard59-tmp",
     out: ["art-run-test-host-data-shard59.zip"],
-    srcs: ["*59-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 59 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?59-*/**/*", "??59-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard59",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard59-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard59.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard60",
+    name: "art-run-test-host-data-shard60-tmp",
     out: ["art-run-test-host-data-shard60.zip"],
-    srcs: ["*60-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 60 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?60-*/**/*", "??60-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard60",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard60-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard60.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard61",
+    name: "art-run-test-host-data-shard61-tmp",
     out: ["art-run-test-host-data-shard61.zip"],
-    srcs: ["*61-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 61 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?61-*/**/*", "??61-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard61",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard61-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard61.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard62",
+    name: "art-run-test-host-data-shard62-tmp",
     out: ["art-run-test-host-data-shard62.zip"],
-    srcs: ["*62-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 62 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?62-*/**/*", "??62-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard62",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard62-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard62.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard63",
+    name: "art-run-test-host-data-shard63-tmp",
     out: ["art-run-test-host-data-shard63.zip"],
-    srcs: ["*63-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 63 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?63-*/**/*", "??63-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard63",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard63-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard63.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard64",
+    name: "art-run-test-host-data-shard64-tmp",
     out: ["art-run-test-host-data-shard64.zip"],
-    srcs: ["*64-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 64 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?64-*/**/*", "??64-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard64",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard64-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard64.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard65",
+    name: "art-run-test-host-data-shard65-tmp",
     out: ["art-run-test-host-data-shard65.zip"],
-    srcs: ["*65-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 65 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?65-*/**/*", "??65-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard65",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard65-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard65.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard66",
+    name: "art-run-test-host-data-shard66-tmp",
     out: ["art-run-test-host-data-shard66.zip"],
-    srcs: ["*66-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 66 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?66-*/**/*", "??66-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard66",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard66-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard66.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard67",
+    name: "art-run-test-host-data-shard67-tmp",
     out: ["art-run-test-host-data-shard67.zip"],
-    srcs: ["*67-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 67 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?67-*/**/*", "??67-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard67",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard67-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard67.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard68",
+    name: "art-run-test-host-data-shard68-tmp",
     out: ["art-run-test-host-data-shard68.zip"],
-    srcs: ["*68-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 68 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?68-*/**/*", "??68-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard68",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard68-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard68.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard69",
+    name: "art-run-test-host-data-shard69-tmp",
     out: ["art-run-test-host-data-shard69.zip"],
-    srcs: ["*69-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 69 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?69-*/**/*", "??69-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard69",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard69-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard69.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard70",
+    name: "art-run-test-host-data-shard70-tmp",
     out: ["art-run-test-host-data-shard70.zip"],
-    srcs: ["*70-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 70 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?70-*/**/*", "??70-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard70",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard70-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard70.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard71",
+    name: "art-run-test-host-data-shard71-tmp",
     out: ["art-run-test-host-data-shard71.zip"],
-    srcs: ["*71-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 71 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?71-*/**/*", "??71-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard71",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard71-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard71.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard72",
+    name: "art-run-test-host-data-shard72-tmp",
     out: ["art-run-test-host-data-shard72.zip"],
-    srcs: ["*72-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 72 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?72-*/**/*", "??72-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard72",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard72-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard72.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard73",
+    name: "art-run-test-host-data-shard73-tmp",
     out: ["art-run-test-host-data-shard73.zip"],
-    srcs: ["*73-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 73 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?73-*/**/*", "??73-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard73",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard73-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard73.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard74",
+    name: "art-run-test-host-data-shard74-tmp",
     out: ["art-run-test-host-data-shard74.zip"],
-    srcs: ["*74-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 74 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?74-*/**/*", "??74-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard74",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard74-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard74.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard75",
+    name: "art-run-test-host-data-shard75-tmp",
     out: ["art-run-test-host-data-shard75.zip"],
-    srcs: ["*75-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 75 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?75-*/**/*", "??75-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard75",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard75-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard75.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard76",
+    name: "art-run-test-host-data-shard76-tmp",
     out: ["art-run-test-host-data-shard76.zip"],
-    srcs: ["*76-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 76 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?76-*/**/*", "??76-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard76",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard76-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard76.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard77",
+    name: "art-run-test-host-data-shard77-tmp",
     out: ["art-run-test-host-data-shard77.zip"],
-    srcs: ["*77-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 77 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?77-*/**/*", "??77-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard77",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard77-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard77.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard78",
+    name: "art-run-test-host-data-shard78-tmp",
     out: ["art-run-test-host-data-shard78.zip"],
-    srcs: ["*78-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 78 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?78-*/**/*", "??78-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard78",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard78-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard78.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard79",
+    name: "art-run-test-host-data-shard79-tmp",
     out: ["art-run-test-host-data-shard79.zip"],
-    srcs: ["*79-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 79 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?79-*/**/*", "??79-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard79",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard79-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard79.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard80",
+    name: "art-run-test-host-data-shard80-tmp",
     out: ["art-run-test-host-data-shard80.zip"],
-    srcs: ["*80-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 80 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?80-*/**/*", "??80-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard80",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard80-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard80.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard81",
+    name: "art-run-test-host-data-shard81-tmp",
     out: ["art-run-test-host-data-shard81.zip"],
-    srcs: ["*81-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 81 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?81-*/**/*", "??81-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard81",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard81-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard81.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard82",
+    name: "art-run-test-host-data-shard82-tmp",
     out: ["art-run-test-host-data-shard82.zip"],
-    srcs: ["*82-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 82 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?82-*/**/*", "??82-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard82",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard82-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard82.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard83",
+    name: "art-run-test-host-data-shard83-tmp",
     out: ["art-run-test-host-data-shard83.zip"],
-    srcs: ["*83-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 83 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?83-*/**/*", "??83-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard83",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard83-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard83.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard84",
+    name: "art-run-test-host-data-shard84-tmp",
     out: ["art-run-test-host-data-shard84.zip"],
-    srcs: ["*84-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 84 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?84-*/**/*", "??84-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard84",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard84-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard84.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard85",
+    name: "art-run-test-host-data-shard85-tmp",
     out: ["art-run-test-host-data-shard85.zip"],
-    srcs: ["*85-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 85 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?85-*/**/*", "??85-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard85",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard85-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard85.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard86",
+    name: "art-run-test-host-data-shard86-tmp",
     out: ["art-run-test-host-data-shard86.zip"],
-    srcs: ["*86-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 86 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?86-*/**/*", "??86-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard86",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard86-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard86.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard87",
+    name: "art-run-test-host-data-shard87-tmp",
     out: ["art-run-test-host-data-shard87.zip"],
-    srcs: ["*87-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 87 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?87-*/**/*", "??87-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard87",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard87-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard87.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard88",
+    name: "art-run-test-host-data-shard88-tmp",
     out: ["art-run-test-host-data-shard88.zip"],
-    srcs: ["*88-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 88 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?88-*/**/*", "??88-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard88",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard88-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard88.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard89",
+    name: "art-run-test-host-data-shard89-tmp",
     out: ["art-run-test-host-data-shard89.zip"],
-    srcs: ["*89-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 89 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?89-*/**/*", "??89-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard89",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard89-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard89.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard90",
+    name: "art-run-test-host-data-shard90-tmp",
     out: ["art-run-test-host-data-shard90.zip"],
-    srcs: ["*90-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 90 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?90-*/**/*", "??90-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard90",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard90-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard90.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard91",
+    name: "art-run-test-host-data-shard91-tmp",
     out: ["art-run-test-host-data-shard91.zip"],
-    srcs: ["*91-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 91 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?91-*/**/*", "??91-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard91",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard91-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard91.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard92",
+    name: "art-run-test-host-data-shard92-tmp",
     out: ["art-run-test-host-data-shard92.zip"],
-    srcs: ["*92-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 92 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?92-*/**/*", "??92-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard92",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard92-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard92.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard93",
+    name: "art-run-test-host-data-shard93-tmp",
     out: ["art-run-test-host-data-shard93.zip"],
-    srcs: ["*93-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 93 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?93-*/**/*", "??93-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard93",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard93-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard93.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard94",
+    name: "art-run-test-host-data-shard94-tmp",
     out: ["art-run-test-host-data-shard94.zip"],
-    srcs: ["*94-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 94 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?94-*/**/*", "??94-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard94",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard94-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard94.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard95",
+    name: "art-run-test-host-data-shard95-tmp",
     out: ["art-run-test-host-data-shard95.zip"],
-    srcs: ["*95-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 95 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?95-*/**/*", "??95-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard95",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard95-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard95.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard96",
+    name: "art-run-test-host-data-shard96-tmp",
     out: ["art-run-test-host-data-shard96.zip"],
-    srcs: ["*96-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 96 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?96-*/**/*", "??96-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard96",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard96-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard96.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard97",
+    name: "art-run-test-host-data-shard97-tmp",
     out: ["art-run-test-host-data-shard97.zip"],
-    srcs: ["*97-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 97 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?97-*/**/*", "??97-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard97",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard97-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard97.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard98",
+    name: "art-run-test-host-data-shard98-tmp",
     out: ["art-run-test-host-data-shard98.zip"],
-    srcs: ["*98-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 98 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?98-*/**/*", "??98-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard98",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard98-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard98.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-shard99",
+    name: "art-run-test-host-data-shard99-tmp",
     out: ["art-run-test-host-data-shard99.zip"],
-    srcs: ["*99-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode host --shard 99 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?99-*/**/*", "??99-*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shard99",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shard99-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shard99.zip",
 }
 
 java_genrule {
-    name: "art-run-test-host-data-merged",
-    defaults: ["art-run-test-data-defaults"],
+    name: "art-run-test-host-data-shardHiddenApi-tmp",
+    out: ["art-run-test-host-data-shardHiddenApi.zip"],
+    srcs: ["???-*hiddenapi*/**/*", "????-*hiddenapi*/**/*"],
+    defaults: ["art-run-test-host-data-defaults"],
+    tools: ["hiddenapi"],
+    cmd: "$(location run_test_build.py) --out $(out) --mode host " +
+         "--bootclasspath $(location :art-run-test-bootclasspath) " +
+         "--d8 $(location d8) " +
+         "--hiddenapi $(location hiddenapi) " +
+         "--jasmin $(location jasmin) " +
+         "--smali $(location smali) " +
+         "--soong_zip $(location soong_zip) " +
+         "--zipalign $(location zipalign) " +
+         "$(in)",
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-shardHiddenApi",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-shardHiddenApi-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-host-data-shardHiddenApi.zip",
+}
+
+genrule_defaults {
+    name: "art-run-test-host-data-defaults",
+    defaults: [
+        // Enable only in source builds, where com.android.art.testing is
+        // available.
+        "art_module_source_build_genrule_defaults",
+    ],
+    tool_files: [
+        "run_test_build.py",
+        ":art-run-test-bootclasspath",
+    ],
+    tools: [
+        "d8",
+        "jasmin",
+        "smali",
+        "soong_zip",
+        "zipalign",
+    ],
+    cmd: "$(location run_test_build.py) --out $(out) --mode host " +
+         "--bootclasspath $(location :art-run-test-bootclasspath) " +
+         "--d8 $(location d8) " +
+         "--jasmin $(location jasmin) " +
+         "--smali $(location smali) " +
+         "--soong_zip $(location soong_zip) " +
+         "--zipalign $(location zipalign) " +
+         "$(in)",
+}
+
+java_genrule {
+    name: "art-run-test-host-data-merged-tmp",
+    defaults: ["art_module_source_build_genrule_defaults"],
     out: ["art-run-test-host-data-merged.zip"],
     srcs: [
-        ":art-run-test-host-data-shard00",
-        ":art-run-test-host-data-shard01",
-        ":art-run-test-host-data-shard02",
-        ":art-run-test-host-data-shard03",
-        ":art-run-test-host-data-shard04",
-        ":art-run-test-host-data-shard05",
-        ":art-run-test-host-data-shard06",
-        ":art-run-test-host-data-shard07",
-        ":art-run-test-host-data-shard08",
-        ":art-run-test-host-data-shard09",
-        ":art-run-test-host-data-shard10",
-        ":art-run-test-host-data-shard11",
-        ":art-run-test-host-data-shard12",
-        ":art-run-test-host-data-shard13",
-        ":art-run-test-host-data-shard14",
-        ":art-run-test-host-data-shard15",
-        ":art-run-test-host-data-shard16",
-        ":art-run-test-host-data-shard17",
-        ":art-run-test-host-data-shard18",
-        ":art-run-test-host-data-shard19",
-        ":art-run-test-host-data-shard20",
-        ":art-run-test-host-data-shard21",
-        ":art-run-test-host-data-shard22",
-        ":art-run-test-host-data-shard23",
-        ":art-run-test-host-data-shard24",
-        ":art-run-test-host-data-shard25",
-        ":art-run-test-host-data-shard26",
-        ":art-run-test-host-data-shard27",
-        ":art-run-test-host-data-shard28",
-        ":art-run-test-host-data-shard29",
-        ":art-run-test-host-data-shard30",
-        ":art-run-test-host-data-shard31",
-        ":art-run-test-host-data-shard32",
-        ":art-run-test-host-data-shard33",
-        ":art-run-test-host-data-shard34",
-        ":art-run-test-host-data-shard35",
-        ":art-run-test-host-data-shard36",
-        ":art-run-test-host-data-shard37",
-        ":art-run-test-host-data-shard38",
-        ":art-run-test-host-data-shard39",
-        ":art-run-test-host-data-shard40",
-        ":art-run-test-host-data-shard41",
-        ":art-run-test-host-data-shard42",
-        ":art-run-test-host-data-shard43",
-        ":art-run-test-host-data-shard44",
-        ":art-run-test-host-data-shard45",
-        ":art-run-test-host-data-shard46",
-        ":art-run-test-host-data-shard47",
-        ":art-run-test-host-data-shard48",
-        ":art-run-test-host-data-shard49",
-        ":art-run-test-host-data-shard50",
-        ":art-run-test-host-data-shard51",
-        ":art-run-test-host-data-shard52",
-        ":art-run-test-host-data-shard53",
-        ":art-run-test-host-data-shard54",
-        ":art-run-test-host-data-shard55",
-        ":art-run-test-host-data-shard56",
-        ":art-run-test-host-data-shard57",
-        ":art-run-test-host-data-shard58",
-        ":art-run-test-host-data-shard59",
-        ":art-run-test-host-data-shard60",
-        ":art-run-test-host-data-shard61",
-        ":art-run-test-host-data-shard62",
-        ":art-run-test-host-data-shard63",
-        ":art-run-test-host-data-shard64",
-        ":art-run-test-host-data-shard65",
-        ":art-run-test-host-data-shard66",
-        ":art-run-test-host-data-shard67",
-        ":art-run-test-host-data-shard68",
-        ":art-run-test-host-data-shard69",
-        ":art-run-test-host-data-shard70",
-        ":art-run-test-host-data-shard71",
-        ":art-run-test-host-data-shard72",
-        ":art-run-test-host-data-shard73",
-        ":art-run-test-host-data-shard74",
-        ":art-run-test-host-data-shard75",
-        ":art-run-test-host-data-shard76",
-        ":art-run-test-host-data-shard77",
-        ":art-run-test-host-data-shard78",
-        ":art-run-test-host-data-shard79",
-        ":art-run-test-host-data-shard80",
-        ":art-run-test-host-data-shard81",
-        ":art-run-test-host-data-shard82",
-        ":art-run-test-host-data-shard83",
-        ":art-run-test-host-data-shard84",
-        ":art-run-test-host-data-shard85",
-        ":art-run-test-host-data-shard86",
-        ":art-run-test-host-data-shard87",
-        ":art-run-test-host-data-shard88",
-        ":art-run-test-host-data-shard89",
-        ":art-run-test-host-data-shard90",
-        ":art-run-test-host-data-shard91",
-        ":art-run-test-host-data-shard92",
-        ":art-run-test-host-data-shard93",
-        ":art-run-test-host-data-shard94",
-        ":art-run-test-host-data-shard95",
-        ":art-run-test-host-data-shard96",
-        ":art-run-test-host-data-shard97",
-        ":art-run-test-host-data-shard98",
-        ":art-run-test-host-data-shard99",
+        ":art-run-test-host-data-shard00-tmp",
+        ":art-run-test-host-data-shard01-tmp",
+        ":art-run-test-host-data-shard02-tmp",
+        ":art-run-test-host-data-shard03-tmp",
+        ":art-run-test-host-data-shard04-tmp",
+        ":art-run-test-host-data-shard05-tmp",
+        ":art-run-test-host-data-shard06-tmp",
+        ":art-run-test-host-data-shard07-tmp",
+        ":art-run-test-host-data-shard08-tmp",
+        ":art-run-test-host-data-shard09-tmp",
+        ":art-run-test-host-data-shard10-tmp",
+        ":art-run-test-host-data-shard11-tmp",
+        ":art-run-test-host-data-shard12-tmp",
+        ":art-run-test-host-data-shard13-tmp",
+        ":art-run-test-host-data-shard14-tmp",
+        ":art-run-test-host-data-shard15-tmp",
+        ":art-run-test-host-data-shard16-tmp",
+        ":art-run-test-host-data-shard17-tmp",
+        ":art-run-test-host-data-shard18-tmp",
+        ":art-run-test-host-data-shard19-tmp",
+        ":art-run-test-host-data-shard20-tmp",
+        ":art-run-test-host-data-shard21-tmp",
+        ":art-run-test-host-data-shard22-tmp",
+        ":art-run-test-host-data-shard23-tmp",
+        ":art-run-test-host-data-shard24-tmp",
+        ":art-run-test-host-data-shard25-tmp",
+        ":art-run-test-host-data-shard26-tmp",
+        ":art-run-test-host-data-shard27-tmp",
+        ":art-run-test-host-data-shard28-tmp",
+        ":art-run-test-host-data-shard29-tmp",
+        ":art-run-test-host-data-shard30-tmp",
+        ":art-run-test-host-data-shard31-tmp",
+        ":art-run-test-host-data-shard32-tmp",
+        ":art-run-test-host-data-shard33-tmp",
+        ":art-run-test-host-data-shard34-tmp",
+        ":art-run-test-host-data-shard35-tmp",
+        ":art-run-test-host-data-shard36-tmp",
+        ":art-run-test-host-data-shard37-tmp",
+        ":art-run-test-host-data-shard38-tmp",
+        ":art-run-test-host-data-shard39-tmp",
+        ":art-run-test-host-data-shard40-tmp",
+        ":art-run-test-host-data-shard41-tmp",
+        ":art-run-test-host-data-shard42-tmp",
+        ":art-run-test-host-data-shard43-tmp",
+        ":art-run-test-host-data-shard44-tmp",
+        ":art-run-test-host-data-shard45-tmp",
+        ":art-run-test-host-data-shard46-tmp",
+        ":art-run-test-host-data-shard47-tmp",
+        ":art-run-test-host-data-shard48-tmp",
+        ":art-run-test-host-data-shard49-tmp",
+        ":art-run-test-host-data-shard50-tmp",
+        ":art-run-test-host-data-shard51-tmp",
+        ":art-run-test-host-data-shard52-tmp",
+        ":art-run-test-host-data-shard53-tmp",
+        ":art-run-test-host-data-shard54-tmp",
+        ":art-run-test-host-data-shard55-tmp",
+        ":art-run-test-host-data-shard56-tmp",
+        ":art-run-test-host-data-shard57-tmp",
+        ":art-run-test-host-data-shard58-tmp",
+        ":art-run-test-host-data-shard59-tmp",
+        ":art-run-test-host-data-shard60-tmp",
+        ":art-run-test-host-data-shard61-tmp",
+        ":art-run-test-host-data-shard62-tmp",
+        ":art-run-test-host-data-shard63-tmp",
+        ":art-run-test-host-data-shard64-tmp",
+        ":art-run-test-host-data-shard65-tmp",
+        ":art-run-test-host-data-shard66-tmp",
+        ":art-run-test-host-data-shard67-tmp",
+        ":art-run-test-host-data-shard68-tmp",
+        ":art-run-test-host-data-shard69-tmp",
+        ":art-run-test-host-data-shard70-tmp",
+        ":art-run-test-host-data-shard71-tmp",
+        ":art-run-test-host-data-shard72-tmp",
+        ":art-run-test-host-data-shard73-tmp",
+        ":art-run-test-host-data-shard74-tmp",
+        ":art-run-test-host-data-shard75-tmp",
+        ":art-run-test-host-data-shard76-tmp",
+        ":art-run-test-host-data-shard77-tmp",
+        ":art-run-test-host-data-shard78-tmp",
+        ":art-run-test-host-data-shard79-tmp",
+        ":art-run-test-host-data-shard80-tmp",
+        ":art-run-test-host-data-shard81-tmp",
+        ":art-run-test-host-data-shard82-tmp",
+        ":art-run-test-host-data-shard83-tmp",
+        ":art-run-test-host-data-shard84-tmp",
+        ":art-run-test-host-data-shard85-tmp",
+        ":art-run-test-host-data-shard86-tmp",
+        ":art-run-test-host-data-shard87-tmp",
+        ":art-run-test-host-data-shard88-tmp",
+        ":art-run-test-host-data-shard89-tmp",
+        ":art-run-test-host-data-shard90-tmp",
+        ":art-run-test-host-data-shard91-tmp",
+        ":art-run-test-host-data-shard92-tmp",
+        ":art-run-test-host-data-shard93-tmp",
+        ":art-run-test-host-data-shard94-tmp",
+        ":art-run-test-host-data-shard95-tmp",
+        ":art-run-test-host-data-shard96-tmp",
+        ":art-run-test-host-data-shard97-tmp",
+        ":art-run-test-host-data-shard98-tmp",
+        ":art-run-test-host-data-shard99-tmp",
+        ":art-run-test-host-data-shardHiddenApi-tmp",
     ],
     tools: ["merge_zips"],
     cmd: "$(location merge_zips) $(out) $(in)",
 }
 
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-host-data-merged",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-merged-tmp",
+    required: [
+        "art-run-test-host-data-shard00",
+        "art-run-test-host-data-shard01",
+        "art-run-test-host-data-shard02",
+        "art-run-test-host-data-shard03",
+        "art-run-test-host-data-shard04",
+        "art-run-test-host-data-shard05",
+        "art-run-test-host-data-shard06",
+        "art-run-test-host-data-shard07",
+        "art-run-test-host-data-shard08",
+        "art-run-test-host-data-shard09",
+        "art-run-test-host-data-shard10",
+        "art-run-test-host-data-shard11",
+        "art-run-test-host-data-shard12",
+        "art-run-test-host-data-shard13",
+        "art-run-test-host-data-shard14",
+        "art-run-test-host-data-shard15",
+        "art-run-test-host-data-shard16",
+        "art-run-test-host-data-shard17",
+        "art-run-test-host-data-shard18",
+        "art-run-test-host-data-shard19",
+        "art-run-test-host-data-shard20",
+        "art-run-test-host-data-shard21",
+        "art-run-test-host-data-shard22",
+        "art-run-test-host-data-shard23",
+        "art-run-test-host-data-shard24",
+        "art-run-test-host-data-shard25",
+        "art-run-test-host-data-shard26",
+        "art-run-test-host-data-shard27",
+        "art-run-test-host-data-shard28",
+        "art-run-test-host-data-shard29",
+        "art-run-test-host-data-shard30",
+        "art-run-test-host-data-shard31",
+        "art-run-test-host-data-shard32",
+        "art-run-test-host-data-shard33",
+        "art-run-test-host-data-shard34",
+        "art-run-test-host-data-shard35",
+        "art-run-test-host-data-shard36",
+        "art-run-test-host-data-shard37",
+        "art-run-test-host-data-shard38",
+        "art-run-test-host-data-shard39",
+        "art-run-test-host-data-shard40",
+        "art-run-test-host-data-shard41",
+        "art-run-test-host-data-shard42",
+        "art-run-test-host-data-shard43",
+        "art-run-test-host-data-shard44",
+        "art-run-test-host-data-shard45",
+        "art-run-test-host-data-shard46",
+        "art-run-test-host-data-shard47",
+        "art-run-test-host-data-shard48",
+        "art-run-test-host-data-shard49",
+        "art-run-test-host-data-shard50",
+        "art-run-test-host-data-shard51",
+        "art-run-test-host-data-shard52",
+        "art-run-test-host-data-shard53",
+        "art-run-test-host-data-shard54",
+        "art-run-test-host-data-shard55",
+        "art-run-test-host-data-shard56",
+        "art-run-test-host-data-shard57",
+        "art-run-test-host-data-shard58",
+        "art-run-test-host-data-shard59",
+        "art-run-test-host-data-shard60",
+        "art-run-test-host-data-shard61",
+        "art-run-test-host-data-shard62",
+        "art-run-test-host-data-shard63",
+        "art-run-test-host-data-shard64",
+        "art-run-test-host-data-shard65",
+        "art-run-test-host-data-shard66",
+        "art-run-test-host-data-shard67",
+        "art-run-test-host-data-shard68",
+        "art-run-test-host-data-shard69",
+        "art-run-test-host-data-shard70",
+        "art-run-test-host-data-shard71",
+        "art-run-test-host-data-shard72",
+        "art-run-test-host-data-shard73",
+        "art-run-test-host-data-shard74",
+        "art-run-test-host-data-shard75",
+        "art-run-test-host-data-shard76",
+        "art-run-test-host-data-shard77",
+        "art-run-test-host-data-shard78",
+        "art-run-test-host-data-shard79",
+        "art-run-test-host-data-shard80",
+        "art-run-test-host-data-shard81",
+        "art-run-test-host-data-shard82",
+        "art-run-test-host-data-shard83",
+        "art-run-test-host-data-shard84",
+        "art-run-test-host-data-shard85",
+        "art-run-test-host-data-shard86",
+        "art-run-test-host-data-shard87",
+        "art-run-test-host-data-shard88",
+        "art-run-test-host-data-shard89",
+        "art-run-test-host-data-shard90",
+        "art-run-test-host-data-shard91",
+        "art-run-test-host-data-shard92",
+        "art-run-test-host-data-shard93",
+        "art-run-test-host-data-shard94",
+        "art-run-test-host-data-shard95",
+        "art-run-test-host-data-shard96",
+        "art-run-test-host-data-shard97",
+        "art-run-test-host-data-shard98",
+        "art-run-test-host-data-shard99",
+        "art-run-test-host-data-shardHiddenApi",
+    ],
+    sub_dir: "art",
+    filename: "art-run-test-host-data-merged.zip",
+}
+
+// Phony target used to build all shards
 java_genrule {
-    name: "art-run-test-target-data-shard00",
+    name: "art-run-test-host-data-tmp",
+    defaults: ["art-run-test-data-defaults"],
+    out: ["art-run-test-host-data.txt"],
+    srcs: [
+        ":art-run-test-host-data-shard00-tmp",
+        ":art-run-test-host-data-shard01-tmp",
+        ":art-run-test-host-data-shard02-tmp",
+        ":art-run-test-host-data-shard03-tmp",
+        ":art-run-test-host-data-shard04-tmp",
+        ":art-run-test-host-data-shard05-tmp",
+        ":art-run-test-host-data-shard06-tmp",
+        ":art-run-test-host-data-shard07-tmp",
+        ":art-run-test-host-data-shard08-tmp",
+        ":art-run-test-host-data-shard09-tmp",
+        ":art-run-test-host-data-shard10-tmp",
+        ":art-run-test-host-data-shard11-tmp",
+        ":art-run-test-host-data-shard12-tmp",
+        ":art-run-test-host-data-shard13-tmp",
+        ":art-run-test-host-data-shard14-tmp",
+        ":art-run-test-host-data-shard15-tmp",
+        ":art-run-test-host-data-shard16-tmp",
+        ":art-run-test-host-data-shard17-tmp",
+        ":art-run-test-host-data-shard18-tmp",
+        ":art-run-test-host-data-shard19-tmp",
+        ":art-run-test-host-data-shard20-tmp",
+        ":art-run-test-host-data-shard21-tmp",
+        ":art-run-test-host-data-shard22-tmp",
+        ":art-run-test-host-data-shard23-tmp",
+        ":art-run-test-host-data-shard24-tmp",
+        ":art-run-test-host-data-shard25-tmp",
+        ":art-run-test-host-data-shard26-tmp",
+        ":art-run-test-host-data-shard27-tmp",
+        ":art-run-test-host-data-shard28-tmp",
+        ":art-run-test-host-data-shard29-tmp",
+        ":art-run-test-host-data-shard30-tmp",
+        ":art-run-test-host-data-shard31-tmp",
+        ":art-run-test-host-data-shard32-tmp",
+        ":art-run-test-host-data-shard33-tmp",
+        ":art-run-test-host-data-shard34-tmp",
+        ":art-run-test-host-data-shard35-tmp",
+        ":art-run-test-host-data-shard36-tmp",
+        ":art-run-test-host-data-shard37-tmp",
+        ":art-run-test-host-data-shard38-tmp",
+        ":art-run-test-host-data-shard39-tmp",
+        ":art-run-test-host-data-shard40-tmp",
+        ":art-run-test-host-data-shard41-tmp",
+        ":art-run-test-host-data-shard42-tmp",
+        ":art-run-test-host-data-shard43-tmp",
+        ":art-run-test-host-data-shard44-tmp",
+        ":art-run-test-host-data-shard45-tmp",
+        ":art-run-test-host-data-shard46-tmp",
+        ":art-run-test-host-data-shard47-tmp",
+        ":art-run-test-host-data-shard48-tmp",
+        ":art-run-test-host-data-shard49-tmp",
+        ":art-run-test-host-data-shard50-tmp",
+        ":art-run-test-host-data-shard51-tmp",
+        ":art-run-test-host-data-shard52-tmp",
+        ":art-run-test-host-data-shard53-tmp",
+        ":art-run-test-host-data-shard54-tmp",
+        ":art-run-test-host-data-shard55-tmp",
+        ":art-run-test-host-data-shard56-tmp",
+        ":art-run-test-host-data-shard57-tmp",
+        ":art-run-test-host-data-shard58-tmp",
+        ":art-run-test-host-data-shard59-tmp",
+        ":art-run-test-host-data-shard60-tmp",
+        ":art-run-test-host-data-shard61-tmp",
+        ":art-run-test-host-data-shard62-tmp",
+        ":art-run-test-host-data-shard63-tmp",
+        ":art-run-test-host-data-shard64-tmp",
+        ":art-run-test-host-data-shard65-tmp",
+        ":art-run-test-host-data-shard66-tmp",
+        ":art-run-test-host-data-shard67-tmp",
+        ":art-run-test-host-data-shard68-tmp",
+        ":art-run-test-host-data-shard69-tmp",
+        ":art-run-test-host-data-shard70-tmp",
+        ":art-run-test-host-data-shard71-tmp",
+        ":art-run-test-host-data-shard72-tmp",
+        ":art-run-test-host-data-shard73-tmp",
+        ":art-run-test-host-data-shard74-tmp",
+        ":art-run-test-host-data-shard75-tmp",
+        ":art-run-test-host-data-shard76-tmp",
+        ":art-run-test-host-data-shard77-tmp",
+        ":art-run-test-host-data-shard78-tmp",
+        ":art-run-test-host-data-shard79-tmp",
+        ":art-run-test-host-data-shard80-tmp",
+        ":art-run-test-host-data-shard81-tmp",
+        ":art-run-test-host-data-shard82-tmp",
+        ":art-run-test-host-data-shard83-tmp",
+        ":art-run-test-host-data-shard84-tmp",
+        ":art-run-test-host-data-shard85-tmp",
+        ":art-run-test-host-data-shard86-tmp",
+        ":art-run-test-host-data-shard87-tmp",
+        ":art-run-test-host-data-shard88-tmp",
+        ":art-run-test-host-data-shard89-tmp",
+        ":art-run-test-host-data-shard90-tmp",
+        ":art-run-test-host-data-shard91-tmp",
+        ":art-run-test-host-data-shard92-tmp",
+        ":art-run-test-host-data-shard93-tmp",
+        ":art-run-test-host-data-shard94-tmp",
+        ":art-run-test-host-data-shard95-tmp",
+        ":art-run-test-host-data-shard96-tmp",
+        ":art-run-test-host-data-shard97-tmp",
+        ":art-run-test-host-data-shard98-tmp",
+        ":art-run-test-host-data-shard99-tmp",
+        ":art-run-test-host-data-shardHiddenApi-tmp",
+    ],
+    cmd: "echo $(in) > $(out)",
+}
+
+// Phony target used to install all shards
+prebuilt_etc_host {
+    name: "art-run-test-host-data",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-host-data-tmp",
+    required: [
+        "art-run-test-host-data-shard00",
+        "art-run-test-host-data-shard01",
+        "art-run-test-host-data-shard02",
+        "art-run-test-host-data-shard03",
+        "art-run-test-host-data-shard04",
+        "art-run-test-host-data-shard05",
+        "art-run-test-host-data-shard06",
+        "art-run-test-host-data-shard07",
+        "art-run-test-host-data-shard08",
+        "art-run-test-host-data-shard09",
+        "art-run-test-host-data-shard10",
+        "art-run-test-host-data-shard11",
+        "art-run-test-host-data-shard12",
+        "art-run-test-host-data-shard13",
+        "art-run-test-host-data-shard14",
+        "art-run-test-host-data-shard15",
+        "art-run-test-host-data-shard16",
+        "art-run-test-host-data-shard17",
+        "art-run-test-host-data-shard18",
+        "art-run-test-host-data-shard19",
+        "art-run-test-host-data-shard20",
+        "art-run-test-host-data-shard21",
+        "art-run-test-host-data-shard22",
+        "art-run-test-host-data-shard23",
+        "art-run-test-host-data-shard24",
+        "art-run-test-host-data-shard25",
+        "art-run-test-host-data-shard26",
+        "art-run-test-host-data-shard27",
+        "art-run-test-host-data-shard28",
+        "art-run-test-host-data-shard29",
+        "art-run-test-host-data-shard30",
+        "art-run-test-host-data-shard31",
+        "art-run-test-host-data-shard32",
+        "art-run-test-host-data-shard33",
+        "art-run-test-host-data-shard34",
+        "art-run-test-host-data-shard35",
+        "art-run-test-host-data-shard36",
+        "art-run-test-host-data-shard37",
+        "art-run-test-host-data-shard38",
+        "art-run-test-host-data-shard39",
+        "art-run-test-host-data-shard40",
+        "art-run-test-host-data-shard41",
+        "art-run-test-host-data-shard42",
+        "art-run-test-host-data-shard43",
+        "art-run-test-host-data-shard44",
+        "art-run-test-host-data-shard45",
+        "art-run-test-host-data-shard46",
+        "art-run-test-host-data-shard47",
+        "art-run-test-host-data-shard48",
+        "art-run-test-host-data-shard49",
+        "art-run-test-host-data-shard50",
+        "art-run-test-host-data-shard51",
+        "art-run-test-host-data-shard52",
+        "art-run-test-host-data-shard53",
+        "art-run-test-host-data-shard54",
+        "art-run-test-host-data-shard55",
+        "art-run-test-host-data-shard56",
+        "art-run-test-host-data-shard57",
+        "art-run-test-host-data-shard58",
+        "art-run-test-host-data-shard59",
+        "art-run-test-host-data-shard60",
+        "art-run-test-host-data-shard61",
+        "art-run-test-host-data-shard62",
+        "art-run-test-host-data-shard63",
+        "art-run-test-host-data-shard64",
+        "art-run-test-host-data-shard65",
+        "art-run-test-host-data-shard66",
+        "art-run-test-host-data-shard67",
+        "art-run-test-host-data-shard68",
+        "art-run-test-host-data-shard69",
+        "art-run-test-host-data-shard70",
+        "art-run-test-host-data-shard71",
+        "art-run-test-host-data-shard72",
+        "art-run-test-host-data-shard73",
+        "art-run-test-host-data-shard74",
+        "art-run-test-host-data-shard75",
+        "art-run-test-host-data-shard76",
+        "art-run-test-host-data-shard77",
+        "art-run-test-host-data-shard78",
+        "art-run-test-host-data-shard79",
+        "art-run-test-host-data-shard80",
+        "art-run-test-host-data-shard81",
+        "art-run-test-host-data-shard82",
+        "art-run-test-host-data-shard83",
+        "art-run-test-host-data-shard84",
+        "art-run-test-host-data-shard85",
+        "art-run-test-host-data-shard86",
+        "art-run-test-host-data-shard87",
+        "art-run-test-host-data-shard88",
+        "art-run-test-host-data-shard89",
+        "art-run-test-host-data-shard90",
+        "art-run-test-host-data-shard91",
+        "art-run-test-host-data-shard92",
+        "art-run-test-host-data-shard93",
+        "art-run-test-host-data-shard94",
+        "art-run-test-host-data-shard95",
+        "art-run-test-host-data-shard96",
+        "art-run-test-host-data-shard97",
+        "art-run-test-host-data-shard98",
+        "art-run-test-host-data-shard99",
+        "art-run-test-host-data-shardHiddenApi",
+    ],
+    sub_dir: "art",
+    filename: "art-run-test-host-data.txt",
+}
+
+java_genrule {
+    name: "art-run-test-target-data-shard00-tmp",
     out: ["art-run-test-target-data-shard00.zip"],
-    srcs: ["*00-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 00 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?00-*/**/*", "??00-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard00",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard00-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard00.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard01",
+    name: "art-run-test-target-data-shard01-tmp",
     out: ["art-run-test-target-data-shard01.zip"],
-    srcs: ["*01-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 01 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?01-*/**/*", "??01-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard01",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard01-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard01.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard02",
+    name: "art-run-test-target-data-shard02-tmp",
     out: ["art-run-test-target-data-shard02.zip"],
-    srcs: ["*02-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 02 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?02-*/**/*", "??02-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard02",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard02-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard02.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard03",
+    name: "art-run-test-target-data-shard03-tmp",
     out: ["art-run-test-target-data-shard03.zip"],
-    srcs: ["*03-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 03 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?03-*/**/*", "??03-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard03",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard03-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard03.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard04",
+    name: "art-run-test-target-data-shard04-tmp",
     out: ["art-run-test-target-data-shard04.zip"],
-    srcs: ["*04-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 04 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?04-*/**/*", "??04-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard04",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard04-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard04.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard05",
+    name: "art-run-test-target-data-shard05-tmp",
     out: ["art-run-test-target-data-shard05.zip"],
-    srcs: ["*05-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 05 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?05-*/**/*", "??05-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard05",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard05-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard05.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard06",
+    name: "art-run-test-target-data-shard06-tmp",
     out: ["art-run-test-target-data-shard06.zip"],
-    srcs: ["*06-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 06 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?06-*/**/*", "??06-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard06",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard06-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard06.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard07",
+    name: "art-run-test-target-data-shard07-tmp",
     out: ["art-run-test-target-data-shard07.zip"],
-    srcs: ["*07-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 07 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?07-*/**/*", "??07-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard07",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard07-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard07.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard08",
+    name: "art-run-test-target-data-shard08-tmp",
     out: ["art-run-test-target-data-shard08.zip"],
-    srcs: ["*08-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 08 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?08-*/**/*", "??08-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard08",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard08-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard08.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard09",
+    name: "art-run-test-target-data-shard09-tmp",
     out: ["art-run-test-target-data-shard09.zip"],
-    srcs: ["*09-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 09 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?09-*/**/*", "??09-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard09",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard09-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard09.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard10",
+    name: "art-run-test-target-data-shard10-tmp",
     out: ["art-run-test-target-data-shard10.zip"],
-    srcs: ["*10-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 10 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?10-*/**/*", "??10-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard10",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard10-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard10.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard11",
+    name: "art-run-test-target-data-shard11-tmp",
     out: ["art-run-test-target-data-shard11.zip"],
-    srcs: ["*11-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 11 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?11-*/**/*", "??11-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard11",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard11-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard11.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard12",
+    name: "art-run-test-target-data-shard12-tmp",
     out: ["art-run-test-target-data-shard12.zip"],
-    srcs: ["*12-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 12 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?12-*/**/*", "??12-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard12",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard12-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard12.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard13",
+    name: "art-run-test-target-data-shard13-tmp",
     out: ["art-run-test-target-data-shard13.zip"],
-    srcs: ["*13-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 13 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?13-*/**/*", "??13-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard13",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard13-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard13.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard14",
+    name: "art-run-test-target-data-shard14-tmp",
     out: ["art-run-test-target-data-shard14.zip"],
-    srcs: ["*14-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 14 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?14-*/**/*", "??14-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard14",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard14-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard14.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard15",
+    name: "art-run-test-target-data-shard15-tmp",
     out: ["art-run-test-target-data-shard15.zip"],
-    srcs: ["*15-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 15 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?15-*/**/*", "??15-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard15",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard15-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard15.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard16",
+    name: "art-run-test-target-data-shard16-tmp",
     out: ["art-run-test-target-data-shard16.zip"],
-    srcs: ["*16-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 16 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?16-*/**/*", "??16-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard16",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard16-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard16.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard17",
+    name: "art-run-test-target-data-shard17-tmp",
     out: ["art-run-test-target-data-shard17.zip"],
-    srcs: ["*17-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 17 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?17-*/**/*", "??17-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard17",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard17-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard17.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard18",
+    name: "art-run-test-target-data-shard18-tmp",
     out: ["art-run-test-target-data-shard18.zip"],
-    srcs: ["*18-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 18 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?18-*/**/*", "??18-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard18",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard18-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard18.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard19",
+    name: "art-run-test-target-data-shard19-tmp",
     out: ["art-run-test-target-data-shard19.zip"],
-    srcs: ["*19-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 19 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?19-*/**/*", "??19-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard19",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard19-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard19.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard20",
+    name: "art-run-test-target-data-shard20-tmp",
     out: ["art-run-test-target-data-shard20.zip"],
-    srcs: ["*20-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 20 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?20-*/**/*", "??20-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard20",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard20-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard20.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard21",
+    name: "art-run-test-target-data-shard21-tmp",
     out: ["art-run-test-target-data-shard21.zip"],
-    srcs: ["*21-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 21 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?21-*/**/*", "??21-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard21",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard21-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard21.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard22",
+    name: "art-run-test-target-data-shard22-tmp",
     out: ["art-run-test-target-data-shard22.zip"],
-    srcs: ["*22-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 22 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?22-*/**/*", "??22-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard22",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard22-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard22.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard23",
+    name: "art-run-test-target-data-shard23-tmp",
     out: ["art-run-test-target-data-shard23.zip"],
-    srcs: ["*23-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 23 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?23-*/**/*", "??23-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard23",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard23-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard23.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard24",
+    name: "art-run-test-target-data-shard24-tmp",
     out: ["art-run-test-target-data-shard24.zip"],
-    srcs: ["*24-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 24 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?24-*/**/*", "??24-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard24",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard24-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard24.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard25",
+    name: "art-run-test-target-data-shard25-tmp",
     out: ["art-run-test-target-data-shard25.zip"],
-    srcs: ["*25-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 25 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?25-*/**/*", "??25-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard25",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard25-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard25.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard26",
+    name: "art-run-test-target-data-shard26-tmp",
     out: ["art-run-test-target-data-shard26.zip"],
-    srcs: ["*26-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 26 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?26-*/**/*", "??26-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard26",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard26-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard26.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard27",
+    name: "art-run-test-target-data-shard27-tmp",
     out: ["art-run-test-target-data-shard27.zip"],
-    srcs: ["*27-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 27 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?27-*/**/*", "??27-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard27",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard27-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard27.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard28",
+    name: "art-run-test-target-data-shard28-tmp",
     out: ["art-run-test-target-data-shard28.zip"],
-    srcs: ["*28-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 28 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?28-*/**/*", "??28-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard28",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard28-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard28.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard29",
+    name: "art-run-test-target-data-shard29-tmp",
     out: ["art-run-test-target-data-shard29.zip"],
-    srcs: ["*29-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 29 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?29-*/**/*", "??29-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard29",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard29-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard29.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard30",
+    name: "art-run-test-target-data-shard30-tmp",
     out: ["art-run-test-target-data-shard30.zip"],
-    srcs: ["*30-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 30 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?30-*/**/*", "??30-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard30",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard30-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard30.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard31",
+    name: "art-run-test-target-data-shard31-tmp",
     out: ["art-run-test-target-data-shard31.zip"],
-    srcs: ["*31-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 31 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?31-*/**/*", "??31-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard31",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard31-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard31.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard32",
+    name: "art-run-test-target-data-shard32-tmp",
     out: ["art-run-test-target-data-shard32.zip"],
-    srcs: ["*32-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 32 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?32-*/**/*", "??32-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard32",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard32-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard32.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard33",
+    name: "art-run-test-target-data-shard33-tmp",
     out: ["art-run-test-target-data-shard33.zip"],
-    srcs: ["*33-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 33 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?33-*/**/*", "??33-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard33",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard33-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard33.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard34",
+    name: "art-run-test-target-data-shard34-tmp",
     out: ["art-run-test-target-data-shard34.zip"],
-    srcs: ["*34-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 34 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?34-*/**/*", "??34-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard34",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard34-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard34.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard35",
+    name: "art-run-test-target-data-shard35-tmp",
     out: ["art-run-test-target-data-shard35.zip"],
-    srcs: ["*35-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 35 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?35-*/**/*", "??35-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard35",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard35-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard35.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard36",
+    name: "art-run-test-target-data-shard36-tmp",
     out: ["art-run-test-target-data-shard36.zip"],
-    srcs: ["*36-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 36 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?36-*/**/*", "??36-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard36",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard36-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard36.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard37",
+    name: "art-run-test-target-data-shard37-tmp",
     out: ["art-run-test-target-data-shard37.zip"],
-    srcs: ["*37-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 37 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?37-*/**/*", "??37-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard37",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard37-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard37.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard38",
+    name: "art-run-test-target-data-shard38-tmp",
     out: ["art-run-test-target-data-shard38.zip"],
-    srcs: ["*38-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 38 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?38-*/**/*", "??38-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard38",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard38-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard38.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard39",
+    name: "art-run-test-target-data-shard39-tmp",
     out: ["art-run-test-target-data-shard39.zip"],
-    srcs: ["*39-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 39 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?39-*/**/*", "??39-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard39",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard39-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard39.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard40",
+    name: "art-run-test-target-data-shard40-tmp",
     out: ["art-run-test-target-data-shard40.zip"],
-    srcs: ["*40-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 40 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?40-*/**/*", "??40-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard40",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard40-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard40.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard41",
+    name: "art-run-test-target-data-shard41-tmp",
     out: ["art-run-test-target-data-shard41.zip"],
-    srcs: ["*41-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 41 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?41-*/**/*", "??41-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard41",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard41-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard41.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard42",
+    name: "art-run-test-target-data-shard42-tmp",
     out: ["art-run-test-target-data-shard42.zip"],
-    srcs: ["*42-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 42 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?42-*/**/*", "??42-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard42",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard42-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard42.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard43",
+    name: "art-run-test-target-data-shard43-tmp",
     out: ["art-run-test-target-data-shard43.zip"],
-    srcs: ["*43-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 43 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?43-*/**/*", "??43-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard43",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard43-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard43.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard44",
+    name: "art-run-test-target-data-shard44-tmp",
     out: ["art-run-test-target-data-shard44.zip"],
-    srcs: ["*44-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 44 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?44-*/**/*", "??44-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard44",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard44-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard44.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard45",
+    name: "art-run-test-target-data-shard45-tmp",
     out: ["art-run-test-target-data-shard45.zip"],
-    srcs: ["*45-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 45 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?45-*/**/*", "??45-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard45",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard45-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard45.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard46",
+    name: "art-run-test-target-data-shard46-tmp",
     out: ["art-run-test-target-data-shard46.zip"],
-    srcs: ["*46-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 46 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?46-*/**/*", "??46-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard46",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard46-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard46.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard47",
+    name: "art-run-test-target-data-shard47-tmp",
     out: ["art-run-test-target-data-shard47.zip"],
-    srcs: ["*47-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 47 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?47-*/**/*", "??47-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard47",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard47-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard47.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard48",
+    name: "art-run-test-target-data-shard48-tmp",
     out: ["art-run-test-target-data-shard48.zip"],
-    srcs: ["*48-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 48 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?48-*/**/*", "??48-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard48",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard48-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard48.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard49",
+    name: "art-run-test-target-data-shard49-tmp",
     out: ["art-run-test-target-data-shard49.zip"],
-    srcs: ["*49-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 49 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?49-*/**/*", "??49-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard49",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard49-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard49.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard50",
+    name: "art-run-test-target-data-shard50-tmp",
     out: ["art-run-test-target-data-shard50.zip"],
-    srcs: ["*50-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 50 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?50-*/**/*", "??50-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard50",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard50-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard50.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard51",
+    name: "art-run-test-target-data-shard51-tmp",
     out: ["art-run-test-target-data-shard51.zip"],
-    srcs: ["*51-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 51 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?51-*/**/*", "??51-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard51",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard51-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard51.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard52",
+    name: "art-run-test-target-data-shard52-tmp",
     out: ["art-run-test-target-data-shard52.zip"],
-    srcs: ["*52-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 52 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?52-*/**/*", "??52-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard52",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard52-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard52.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard53",
+    name: "art-run-test-target-data-shard53-tmp",
     out: ["art-run-test-target-data-shard53.zip"],
-    srcs: ["*53-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 53 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?53-*/**/*", "??53-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard53",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard53-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard53.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard54",
+    name: "art-run-test-target-data-shard54-tmp",
     out: ["art-run-test-target-data-shard54.zip"],
-    srcs: ["*54-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 54 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?54-*/**/*", "??54-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard54",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard54-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard54.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard55",
+    name: "art-run-test-target-data-shard55-tmp",
     out: ["art-run-test-target-data-shard55.zip"],
-    srcs: ["*55-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 55 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?55-*/**/*", "??55-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard55",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard55-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard55.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard56",
+    name: "art-run-test-target-data-shard56-tmp",
     out: ["art-run-test-target-data-shard56.zip"],
-    srcs: ["*56-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 56 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?56-*/**/*", "??56-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard56",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard56-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard56.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard57",
+    name: "art-run-test-target-data-shard57-tmp",
     out: ["art-run-test-target-data-shard57.zip"],
-    srcs: ["*57-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 57 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?57-*/**/*", "??57-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard57",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard57-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard57.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard58",
+    name: "art-run-test-target-data-shard58-tmp",
     out: ["art-run-test-target-data-shard58.zip"],
-    srcs: ["*58-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 58 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?58-*/**/*", "??58-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard58",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard58-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard58.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard59",
+    name: "art-run-test-target-data-shard59-tmp",
     out: ["art-run-test-target-data-shard59.zip"],
-    srcs: ["*59-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 59 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?59-*/**/*", "??59-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard59",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard59-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard59.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard60",
+    name: "art-run-test-target-data-shard60-tmp",
     out: ["art-run-test-target-data-shard60.zip"],
-    srcs: ["*60-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 60 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?60-*/**/*", "??60-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard60",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard60-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard60.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard61",
+    name: "art-run-test-target-data-shard61-tmp",
     out: ["art-run-test-target-data-shard61.zip"],
-    srcs: ["*61-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 61 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?61-*/**/*", "??61-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard61",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard61-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard61.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard62",
+    name: "art-run-test-target-data-shard62-tmp",
     out: ["art-run-test-target-data-shard62.zip"],
-    srcs: ["*62-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 62 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?62-*/**/*", "??62-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard62",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard62-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard62.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard63",
+    name: "art-run-test-target-data-shard63-tmp",
     out: ["art-run-test-target-data-shard63.zip"],
-    srcs: ["*63-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 63 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?63-*/**/*", "??63-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard63",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard63-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard63.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard64",
+    name: "art-run-test-target-data-shard64-tmp",
     out: ["art-run-test-target-data-shard64.zip"],
-    srcs: ["*64-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 64 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?64-*/**/*", "??64-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard64",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard64-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard64.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard65",
+    name: "art-run-test-target-data-shard65-tmp",
     out: ["art-run-test-target-data-shard65.zip"],
-    srcs: ["*65-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 65 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?65-*/**/*", "??65-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard65",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard65-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard65.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard66",
+    name: "art-run-test-target-data-shard66-tmp",
     out: ["art-run-test-target-data-shard66.zip"],
-    srcs: ["*66-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 66 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?66-*/**/*", "??66-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard66",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard66-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard66.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard67",
+    name: "art-run-test-target-data-shard67-tmp",
     out: ["art-run-test-target-data-shard67.zip"],
-    srcs: ["*67-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 67 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?67-*/**/*", "??67-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard67",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard67-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard67.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard68",
+    name: "art-run-test-target-data-shard68-tmp",
     out: ["art-run-test-target-data-shard68.zip"],
-    srcs: ["*68-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 68 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?68-*/**/*", "??68-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard68",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard68-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard68.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard69",
+    name: "art-run-test-target-data-shard69-tmp",
     out: ["art-run-test-target-data-shard69.zip"],
-    srcs: ["*69-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 69 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?69-*/**/*", "??69-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard69",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard69-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard69.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard70",
+    name: "art-run-test-target-data-shard70-tmp",
     out: ["art-run-test-target-data-shard70.zip"],
-    srcs: ["*70-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 70 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?70-*/**/*", "??70-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard70",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard70-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard70.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard71",
+    name: "art-run-test-target-data-shard71-tmp",
     out: ["art-run-test-target-data-shard71.zip"],
-    srcs: ["*71-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 71 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?71-*/**/*", "??71-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard71",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard71-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard71.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard72",
+    name: "art-run-test-target-data-shard72-tmp",
     out: ["art-run-test-target-data-shard72.zip"],
-    srcs: ["*72-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 72 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?72-*/**/*", "??72-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard72",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard72-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard72.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard73",
+    name: "art-run-test-target-data-shard73-tmp",
     out: ["art-run-test-target-data-shard73.zip"],
-    srcs: ["*73-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 73 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?73-*/**/*", "??73-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard73",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard73-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard73.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard74",
+    name: "art-run-test-target-data-shard74-tmp",
     out: ["art-run-test-target-data-shard74.zip"],
-    srcs: ["*74-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 74 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?74-*/**/*", "??74-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard74",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard74-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard74.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard75",
+    name: "art-run-test-target-data-shard75-tmp",
     out: ["art-run-test-target-data-shard75.zip"],
-    srcs: ["*75-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 75 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?75-*/**/*", "??75-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard75",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard75-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard75.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard76",
+    name: "art-run-test-target-data-shard76-tmp",
     out: ["art-run-test-target-data-shard76.zip"],
-    srcs: ["*76-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 76 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?76-*/**/*", "??76-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard76",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard76-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard76.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard77",
+    name: "art-run-test-target-data-shard77-tmp",
     out: ["art-run-test-target-data-shard77.zip"],
-    srcs: ["*77-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 77 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?77-*/**/*", "??77-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard77",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard77-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard77.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard78",
+    name: "art-run-test-target-data-shard78-tmp",
     out: ["art-run-test-target-data-shard78.zip"],
-    srcs: ["*78-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 78 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?78-*/**/*", "??78-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard78",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard78-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard78.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard79",
+    name: "art-run-test-target-data-shard79-tmp",
     out: ["art-run-test-target-data-shard79.zip"],
-    srcs: ["*79-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 79 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?79-*/**/*", "??79-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard79",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard79-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard79.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard80",
+    name: "art-run-test-target-data-shard80-tmp",
     out: ["art-run-test-target-data-shard80.zip"],
-    srcs: ["*80-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 80 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?80-*/**/*", "??80-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard80",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard80-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard80.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard81",
+    name: "art-run-test-target-data-shard81-tmp",
     out: ["art-run-test-target-data-shard81.zip"],
-    srcs: ["*81-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 81 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?81-*/**/*", "??81-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard81",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard81-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard81.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard82",
+    name: "art-run-test-target-data-shard82-tmp",
     out: ["art-run-test-target-data-shard82.zip"],
-    srcs: ["*82-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 82 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?82-*/**/*", "??82-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard82",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard82-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard82.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard83",
+    name: "art-run-test-target-data-shard83-tmp",
     out: ["art-run-test-target-data-shard83.zip"],
-    srcs: ["*83-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 83 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?83-*/**/*", "??83-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard83",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard83-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard83.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard84",
+    name: "art-run-test-target-data-shard84-tmp",
     out: ["art-run-test-target-data-shard84.zip"],
-    srcs: ["*84-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 84 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?84-*/**/*", "??84-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard84",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard84-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard84.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard85",
+    name: "art-run-test-target-data-shard85-tmp",
     out: ["art-run-test-target-data-shard85.zip"],
-    srcs: ["*85-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 85 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?85-*/**/*", "??85-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard85",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard85-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard85.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard86",
+    name: "art-run-test-target-data-shard86-tmp",
     out: ["art-run-test-target-data-shard86.zip"],
-    srcs: ["*86-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 86 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?86-*/**/*", "??86-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard86",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard86-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard86.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard87",
+    name: "art-run-test-target-data-shard87-tmp",
     out: ["art-run-test-target-data-shard87.zip"],
-    srcs: ["*87-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 87 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?87-*/**/*", "??87-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard87",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard87-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard87.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard88",
+    name: "art-run-test-target-data-shard88-tmp",
     out: ["art-run-test-target-data-shard88.zip"],
-    srcs: ["*88-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 88 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?88-*/**/*", "??88-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard88",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard88-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard88.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard89",
+    name: "art-run-test-target-data-shard89-tmp",
     out: ["art-run-test-target-data-shard89.zip"],
-    srcs: ["*89-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 89 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?89-*/**/*", "??89-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard89",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard89-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard89.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard90",
+    name: "art-run-test-target-data-shard90-tmp",
     out: ["art-run-test-target-data-shard90.zip"],
-    srcs: ["*90-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 90 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?90-*/**/*", "??90-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard90",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard90-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard90.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard91",
+    name: "art-run-test-target-data-shard91-tmp",
     out: ["art-run-test-target-data-shard91.zip"],
-    srcs: ["*91-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 91 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?91-*/**/*", "??91-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard91",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard91-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard91.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard92",
+    name: "art-run-test-target-data-shard92-tmp",
     out: ["art-run-test-target-data-shard92.zip"],
-    srcs: ["*92-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 92 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?92-*/**/*", "??92-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard92",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard92-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard92.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard93",
+    name: "art-run-test-target-data-shard93-tmp",
     out: ["art-run-test-target-data-shard93.zip"],
-    srcs: ["*93-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 93 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?93-*/**/*", "??93-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard93",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard93-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard93.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard94",
+    name: "art-run-test-target-data-shard94-tmp",
     out: ["art-run-test-target-data-shard94.zip"],
-    srcs: ["*94-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 94 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?94-*/**/*", "??94-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard94",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard94-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard94.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard95",
+    name: "art-run-test-target-data-shard95-tmp",
     out: ["art-run-test-target-data-shard95.zip"],
-    srcs: ["*95-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 95 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?95-*/**/*", "??95-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard95",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard95-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard95.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard96",
+    name: "art-run-test-target-data-shard96-tmp",
     out: ["art-run-test-target-data-shard96.zip"],
-    srcs: ["*96-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 96 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?96-*/**/*", "??96-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard96",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard96-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard96.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard97",
+    name: "art-run-test-target-data-shard97-tmp",
     out: ["art-run-test-target-data-shard97.zip"],
-    srcs: ["*97-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 97 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?97-*/**/*", "??97-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard97",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard97-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard97.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard98",
+    name: "art-run-test-target-data-shard98-tmp",
     out: ["art-run-test-target-data-shard98.zip"],
-    srcs: ["*98-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 98 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?98-*/**/*", "??98-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard98",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard98-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard98.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-shard99",
+    name: "art-run-test-target-data-shard99-tmp",
     out: ["art-run-test-target-data-shard99.zip"],
-    srcs: ["*99-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode target --shard 99 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?99-*/**/*", "??99-*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shard99",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shard99-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shard99.zip",
 }
 
 java_genrule {
-    name: "art-run-test-target-data-merged",
-    defaults: ["art-run-test-data-defaults"],
+    name: "art-run-test-target-data-shardHiddenApi-tmp",
+    out: ["art-run-test-target-data-shardHiddenApi.zip"],
+    srcs: ["???-*hiddenapi*/**/*", "????-*hiddenapi*/**/*"],
+    defaults: ["art-run-test-target-data-defaults"],
+    tools: ["hiddenapi"],
+    cmd: "$(location run_test_build.py) --out $(out) --mode target " +
+         "--bootclasspath $(location :art-run-test-bootclasspath) " +
+         "--d8 $(location d8) " +
+         "--hiddenapi $(location hiddenapi) " +
+         "--jasmin $(location jasmin) " +
+         "--smali $(location smali) " +
+         "--soong_zip $(location soong_zip) " +
+         "--zipalign $(location zipalign) " +
+         "$(in)",
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-shardHiddenApi",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-shardHiddenApi-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-target-data-shardHiddenApi.zip",
+}
+
+genrule_defaults {
+    name: "art-run-test-target-data-defaults",
+    defaults: [
+        // Enable only in source builds, where com.android.art.testing is
+        // available.
+        "art_module_source_build_genrule_defaults",
+    ],
+    tool_files: [
+        "run_test_build.py",
+        ":art-run-test-bootclasspath",
+    ],
+    tools: [
+        "d8",
+        "jasmin",
+        "smali",
+        "soong_zip",
+        "zipalign",
+    ],
+    cmd: "$(location run_test_build.py) --out $(out) --mode target " +
+         "--bootclasspath $(location :art-run-test-bootclasspath) " +
+         "--d8 $(location d8) " +
+         "--jasmin $(location jasmin) " +
+         "--smali $(location smali) " +
+         "--soong_zip $(location soong_zip) " +
+         "--zipalign $(location zipalign) " +
+         "$(in)",
+}
+
+java_genrule {
+    name: "art-run-test-target-data-merged-tmp",
+    defaults: ["art_module_source_build_genrule_defaults"],
     out: ["art-run-test-target-data-merged.zip"],
     srcs: [
-        ":art-run-test-target-data-shard00",
-        ":art-run-test-target-data-shard01",
-        ":art-run-test-target-data-shard02",
-        ":art-run-test-target-data-shard03",
-        ":art-run-test-target-data-shard04",
-        ":art-run-test-target-data-shard05",
-        ":art-run-test-target-data-shard06",
-        ":art-run-test-target-data-shard07",
-        ":art-run-test-target-data-shard08",
-        ":art-run-test-target-data-shard09",
-        ":art-run-test-target-data-shard10",
-        ":art-run-test-target-data-shard11",
-        ":art-run-test-target-data-shard12",
-        ":art-run-test-target-data-shard13",
-        ":art-run-test-target-data-shard14",
-        ":art-run-test-target-data-shard15",
-        ":art-run-test-target-data-shard16",
-        ":art-run-test-target-data-shard17",
-        ":art-run-test-target-data-shard18",
-        ":art-run-test-target-data-shard19",
-        ":art-run-test-target-data-shard20",
-        ":art-run-test-target-data-shard21",
-        ":art-run-test-target-data-shard22",
-        ":art-run-test-target-data-shard23",
-        ":art-run-test-target-data-shard24",
-        ":art-run-test-target-data-shard25",
-        ":art-run-test-target-data-shard26",
-        ":art-run-test-target-data-shard27",
-        ":art-run-test-target-data-shard28",
-        ":art-run-test-target-data-shard29",
-        ":art-run-test-target-data-shard30",
-        ":art-run-test-target-data-shard31",
-        ":art-run-test-target-data-shard32",
-        ":art-run-test-target-data-shard33",
-        ":art-run-test-target-data-shard34",
-        ":art-run-test-target-data-shard35",
-        ":art-run-test-target-data-shard36",
-        ":art-run-test-target-data-shard37",
-        ":art-run-test-target-data-shard38",
-        ":art-run-test-target-data-shard39",
-        ":art-run-test-target-data-shard40",
-        ":art-run-test-target-data-shard41",
-        ":art-run-test-target-data-shard42",
-        ":art-run-test-target-data-shard43",
-        ":art-run-test-target-data-shard44",
-        ":art-run-test-target-data-shard45",
-        ":art-run-test-target-data-shard46",
-        ":art-run-test-target-data-shard47",
-        ":art-run-test-target-data-shard48",
-        ":art-run-test-target-data-shard49",
-        ":art-run-test-target-data-shard50",
-        ":art-run-test-target-data-shard51",
-        ":art-run-test-target-data-shard52",
-        ":art-run-test-target-data-shard53",
-        ":art-run-test-target-data-shard54",
-        ":art-run-test-target-data-shard55",
-        ":art-run-test-target-data-shard56",
-        ":art-run-test-target-data-shard57",
-        ":art-run-test-target-data-shard58",
-        ":art-run-test-target-data-shard59",
-        ":art-run-test-target-data-shard60",
-        ":art-run-test-target-data-shard61",
-        ":art-run-test-target-data-shard62",
-        ":art-run-test-target-data-shard63",
-        ":art-run-test-target-data-shard64",
-        ":art-run-test-target-data-shard65",
-        ":art-run-test-target-data-shard66",
-        ":art-run-test-target-data-shard67",
-        ":art-run-test-target-data-shard68",
-        ":art-run-test-target-data-shard69",
-        ":art-run-test-target-data-shard70",
-        ":art-run-test-target-data-shard71",
-        ":art-run-test-target-data-shard72",
-        ":art-run-test-target-data-shard73",
-        ":art-run-test-target-data-shard74",
-        ":art-run-test-target-data-shard75",
-        ":art-run-test-target-data-shard76",
-        ":art-run-test-target-data-shard77",
-        ":art-run-test-target-data-shard78",
-        ":art-run-test-target-data-shard79",
-        ":art-run-test-target-data-shard80",
-        ":art-run-test-target-data-shard81",
-        ":art-run-test-target-data-shard82",
-        ":art-run-test-target-data-shard83",
-        ":art-run-test-target-data-shard84",
-        ":art-run-test-target-data-shard85",
-        ":art-run-test-target-data-shard86",
-        ":art-run-test-target-data-shard87",
-        ":art-run-test-target-data-shard88",
-        ":art-run-test-target-data-shard89",
-        ":art-run-test-target-data-shard90",
-        ":art-run-test-target-data-shard91",
-        ":art-run-test-target-data-shard92",
-        ":art-run-test-target-data-shard93",
-        ":art-run-test-target-data-shard94",
-        ":art-run-test-target-data-shard95",
-        ":art-run-test-target-data-shard96",
-        ":art-run-test-target-data-shard97",
-        ":art-run-test-target-data-shard98",
-        ":art-run-test-target-data-shard99",
+        ":art-run-test-target-data-shard00-tmp",
+        ":art-run-test-target-data-shard01-tmp",
+        ":art-run-test-target-data-shard02-tmp",
+        ":art-run-test-target-data-shard03-tmp",
+        ":art-run-test-target-data-shard04-tmp",
+        ":art-run-test-target-data-shard05-tmp",
+        ":art-run-test-target-data-shard06-tmp",
+        ":art-run-test-target-data-shard07-tmp",
+        ":art-run-test-target-data-shard08-tmp",
+        ":art-run-test-target-data-shard09-tmp",
+        ":art-run-test-target-data-shard10-tmp",
+        ":art-run-test-target-data-shard11-tmp",
+        ":art-run-test-target-data-shard12-tmp",
+        ":art-run-test-target-data-shard13-tmp",
+        ":art-run-test-target-data-shard14-tmp",
+        ":art-run-test-target-data-shard15-tmp",
+        ":art-run-test-target-data-shard16-tmp",
+        ":art-run-test-target-data-shard17-tmp",
+        ":art-run-test-target-data-shard18-tmp",
+        ":art-run-test-target-data-shard19-tmp",
+        ":art-run-test-target-data-shard20-tmp",
+        ":art-run-test-target-data-shard21-tmp",
+        ":art-run-test-target-data-shard22-tmp",
+        ":art-run-test-target-data-shard23-tmp",
+        ":art-run-test-target-data-shard24-tmp",
+        ":art-run-test-target-data-shard25-tmp",
+        ":art-run-test-target-data-shard26-tmp",
+        ":art-run-test-target-data-shard27-tmp",
+        ":art-run-test-target-data-shard28-tmp",
+        ":art-run-test-target-data-shard29-tmp",
+        ":art-run-test-target-data-shard30-tmp",
+        ":art-run-test-target-data-shard31-tmp",
+        ":art-run-test-target-data-shard32-tmp",
+        ":art-run-test-target-data-shard33-tmp",
+        ":art-run-test-target-data-shard34-tmp",
+        ":art-run-test-target-data-shard35-tmp",
+        ":art-run-test-target-data-shard36-tmp",
+        ":art-run-test-target-data-shard37-tmp",
+        ":art-run-test-target-data-shard38-tmp",
+        ":art-run-test-target-data-shard39-tmp",
+        ":art-run-test-target-data-shard40-tmp",
+        ":art-run-test-target-data-shard41-tmp",
+        ":art-run-test-target-data-shard42-tmp",
+        ":art-run-test-target-data-shard43-tmp",
+        ":art-run-test-target-data-shard44-tmp",
+        ":art-run-test-target-data-shard45-tmp",
+        ":art-run-test-target-data-shard46-tmp",
+        ":art-run-test-target-data-shard47-tmp",
+        ":art-run-test-target-data-shard48-tmp",
+        ":art-run-test-target-data-shard49-tmp",
+        ":art-run-test-target-data-shard50-tmp",
+        ":art-run-test-target-data-shard51-tmp",
+        ":art-run-test-target-data-shard52-tmp",
+        ":art-run-test-target-data-shard53-tmp",
+        ":art-run-test-target-data-shard54-tmp",
+        ":art-run-test-target-data-shard55-tmp",
+        ":art-run-test-target-data-shard56-tmp",
+        ":art-run-test-target-data-shard57-tmp",
+        ":art-run-test-target-data-shard58-tmp",
+        ":art-run-test-target-data-shard59-tmp",
+        ":art-run-test-target-data-shard60-tmp",
+        ":art-run-test-target-data-shard61-tmp",
+        ":art-run-test-target-data-shard62-tmp",
+        ":art-run-test-target-data-shard63-tmp",
+        ":art-run-test-target-data-shard64-tmp",
+        ":art-run-test-target-data-shard65-tmp",
+        ":art-run-test-target-data-shard66-tmp",
+        ":art-run-test-target-data-shard67-tmp",
+        ":art-run-test-target-data-shard68-tmp",
+        ":art-run-test-target-data-shard69-tmp",
+        ":art-run-test-target-data-shard70-tmp",
+        ":art-run-test-target-data-shard71-tmp",
+        ":art-run-test-target-data-shard72-tmp",
+        ":art-run-test-target-data-shard73-tmp",
+        ":art-run-test-target-data-shard74-tmp",
+        ":art-run-test-target-data-shard75-tmp",
+        ":art-run-test-target-data-shard76-tmp",
+        ":art-run-test-target-data-shard77-tmp",
+        ":art-run-test-target-data-shard78-tmp",
+        ":art-run-test-target-data-shard79-tmp",
+        ":art-run-test-target-data-shard80-tmp",
+        ":art-run-test-target-data-shard81-tmp",
+        ":art-run-test-target-data-shard82-tmp",
+        ":art-run-test-target-data-shard83-tmp",
+        ":art-run-test-target-data-shard84-tmp",
+        ":art-run-test-target-data-shard85-tmp",
+        ":art-run-test-target-data-shard86-tmp",
+        ":art-run-test-target-data-shard87-tmp",
+        ":art-run-test-target-data-shard88-tmp",
+        ":art-run-test-target-data-shard89-tmp",
+        ":art-run-test-target-data-shard90-tmp",
+        ":art-run-test-target-data-shard91-tmp",
+        ":art-run-test-target-data-shard92-tmp",
+        ":art-run-test-target-data-shard93-tmp",
+        ":art-run-test-target-data-shard94-tmp",
+        ":art-run-test-target-data-shard95-tmp",
+        ":art-run-test-target-data-shard96-tmp",
+        ":art-run-test-target-data-shard97-tmp",
+        ":art-run-test-target-data-shard98-tmp",
+        ":art-run-test-target-data-shard99-tmp",
+        ":art-run-test-target-data-shardHiddenApi-tmp",
     ],
     tools: ["merge_zips"],
     cmd: "$(location merge_zips) $(out) $(in)",
 }
 
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-target-data-merged",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-merged-tmp",
+    required: [
+        "art-run-test-target-data-shard00",
+        "art-run-test-target-data-shard01",
+        "art-run-test-target-data-shard02",
+        "art-run-test-target-data-shard03",
+        "art-run-test-target-data-shard04",
+        "art-run-test-target-data-shard05",
+        "art-run-test-target-data-shard06",
+        "art-run-test-target-data-shard07",
+        "art-run-test-target-data-shard08",
+        "art-run-test-target-data-shard09",
+        "art-run-test-target-data-shard10",
+        "art-run-test-target-data-shard11",
+        "art-run-test-target-data-shard12",
+        "art-run-test-target-data-shard13",
+        "art-run-test-target-data-shard14",
+        "art-run-test-target-data-shard15",
+        "art-run-test-target-data-shard16",
+        "art-run-test-target-data-shard17",
+        "art-run-test-target-data-shard18",
+        "art-run-test-target-data-shard19",
+        "art-run-test-target-data-shard20",
+        "art-run-test-target-data-shard21",
+        "art-run-test-target-data-shard22",
+        "art-run-test-target-data-shard23",
+        "art-run-test-target-data-shard24",
+        "art-run-test-target-data-shard25",
+        "art-run-test-target-data-shard26",
+        "art-run-test-target-data-shard27",
+        "art-run-test-target-data-shard28",
+        "art-run-test-target-data-shard29",
+        "art-run-test-target-data-shard30",
+        "art-run-test-target-data-shard31",
+        "art-run-test-target-data-shard32",
+        "art-run-test-target-data-shard33",
+        "art-run-test-target-data-shard34",
+        "art-run-test-target-data-shard35",
+        "art-run-test-target-data-shard36",
+        "art-run-test-target-data-shard37",
+        "art-run-test-target-data-shard38",
+        "art-run-test-target-data-shard39",
+        "art-run-test-target-data-shard40",
+        "art-run-test-target-data-shard41",
+        "art-run-test-target-data-shard42",
+        "art-run-test-target-data-shard43",
+        "art-run-test-target-data-shard44",
+        "art-run-test-target-data-shard45",
+        "art-run-test-target-data-shard46",
+        "art-run-test-target-data-shard47",
+        "art-run-test-target-data-shard48",
+        "art-run-test-target-data-shard49",
+        "art-run-test-target-data-shard50",
+        "art-run-test-target-data-shard51",
+        "art-run-test-target-data-shard52",
+        "art-run-test-target-data-shard53",
+        "art-run-test-target-data-shard54",
+        "art-run-test-target-data-shard55",
+        "art-run-test-target-data-shard56",
+        "art-run-test-target-data-shard57",
+        "art-run-test-target-data-shard58",
+        "art-run-test-target-data-shard59",
+        "art-run-test-target-data-shard60",
+        "art-run-test-target-data-shard61",
+        "art-run-test-target-data-shard62",
+        "art-run-test-target-data-shard63",
+        "art-run-test-target-data-shard64",
+        "art-run-test-target-data-shard65",
+        "art-run-test-target-data-shard66",
+        "art-run-test-target-data-shard67",
+        "art-run-test-target-data-shard68",
+        "art-run-test-target-data-shard69",
+        "art-run-test-target-data-shard70",
+        "art-run-test-target-data-shard71",
+        "art-run-test-target-data-shard72",
+        "art-run-test-target-data-shard73",
+        "art-run-test-target-data-shard74",
+        "art-run-test-target-data-shard75",
+        "art-run-test-target-data-shard76",
+        "art-run-test-target-data-shard77",
+        "art-run-test-target-data-shard78",
+        "art-run-test-target-data-shard79",
+        "art-run-test-target-data-shard80",
+        "art-run-test-target-data-shard81",
+        "art-run-test-target-data-shard82",
+        "art-run-test-target-data-shard83",
+        "art-run-test-target-data-shard84",
+        "art-run-test-target-data-shard85",
+        "art-run-test-target-data-shard86",
+        "art-run-test-target-data-shard87",
+        "art-run-test-target-data-shard88",
+        "art-run-test-target-data-shard89",
+        "art-run-test-target-data-shard90",
+        "art-run-test-target-data-shard91",
+        "art-run-test-target-data-shard92",
+        "art-run-test-target-data-shard93",
+        "art-run-test-target-data-shard94",
+        "art-run-test-target-data-shard95",
+        "art-run-test-target-data-shard96",
+        "art-run-test-target-data-shard97",
+        "art-run-test-target-data-shard98",
+        "art-run-test-target-data-shard99",
+        "art-run-test-target-data-shardHiddenApi",
+    ],
+    sub_dir: "art",
+    filename: "art-run-test-target-data-merged.zip",
+}
+
+// Phony target used to build all shards
 java_genrule {
-    name: "art-run-test-jvm-data-shard00",
+    name: "art-run-test-target-data-tmp",
+    defaults: ["art-run-test-data-defaults"],
+    out: ["art-run-test-target-data.txt"],
+    srcs: [
+        ":art-run-test-target-data-shard00-tmp",
+        ":art-run-test-target-data-shard01-tmp",
+        ":art-run-test-target-data-shard02-tmp",
+        ":art-run-test-target-data-shard03-tmp",
+        ":art-run-test-target-data-shard04-tmp",
+        ":art-run-test-target-data-shard05-tmp",
+        ":art-run-test-target-data-shard06-tmp",
+        ":art-run-test-target-data-shard07-tmp",
+        ":art-run-test-target-data-shard08-tmp",
+        ":art-run-test-target-data-shard09-tmp",
+        ":art-run-test-target-data-shard10-tmp",
+        ":art-run-test-target-data-shard11-tmp",
+        ":art-run-test-target-data-shard12-tmp",
+        ":art-run-test-target-data-shard13-tmp",
+        ":art-run-test-target-data-shard14-tmp",
+        ":art-run-test-target-data-shard15-tmp",
+        ":art-run-test-target-data-shard16-tmp",
+        ":art-run-test-target-data-shard17-tmp",
+        ":art-run-test-target-data-shard18-tmp",
+        ":art-run-test-target-data-shard19-tmp",
+        ":art-run-test-target-data-shard20-tmp",
+        ":art-run-test-target-data-shard21-tmp",
+        ":art-run-test-target-data-shard22-tmp",
+        ":art-run-test-target-data-shard23-tmp",
+        ":art-run-test-target-data-shard24-tmp",
+        ":art-run-test-target-data-shard25-tmp",
+        ":art-run-test-target-data-shard26-tmp",
+        ":art-run-test-target-data-shard27-tmp",
+        ":art-run-test-target-data-shard28-tmp",
+        ":art-run-test-target-data-shard29-tmp",
+        ":art-run-test-target-data-shard30-tmp",
+        ":art-run-test-target-data-shard31-tmp",
+        ":art-run-test-target-data-shard32-tmp",
+        ":art-run-test-target-data-shard33-tmp",
+        ":art-run-test-target-data-shard34-tmp",
+        ":art-run-test-target-data-shard35-tmp",
+        ":art-run-test-target-data-shard36-tmp",
+        ":art-run-test-target-data-shard37-tmp",
+        ":art-run-test-target-data-shard38-tmp",
+        ":art-run-test-target-data-shard39-tmp",
+        ":art-run-test-target-data-shard40-tmp",
+        ":art-run-test-target-data-shard41-tmp",
+        ":art-run-test-target-data-shard42-tmp",
+        ":art-run-test-target-data-shard43-tmp",
+        ":art-run-test-target-data-shard44-tmp",
+        ":art-run-test-target-data-shard45-tmp",
+        ":art-run-test-target-data-shard46-tmp",
+        ":art-run-test-target-data-shard47-tmp",
+        ":art-run-test-target-data-shard48-tmp",
+        ":art-run-test-target-data-shard49-tmp",
+        ":art-run-test-target-data-shard50-tmp",
+        ":art-run-test-target-data-shard51-tmp",
+        ":art-run-test-target-data-shard52-tmp",
+        ":art-run-test-target-data-shard53-tmp",
+        ":art-run-test-target-data-shard54-tmp",
+        ":art-run-test-target-data-shard55-tmp",
+        ":art-run-test-target-data-shard56-tmp",
+        ":art-run-test-target-data-shard57-tmp",
+        ":art-run-test-target-data-shard58-tmp",
+        ":art-run-test-target-data-shard59-tmp",
+        ":art-run-test-target-data-shard60-tmp",
+        ":art-run-test-target-data-shard61-tmp",
+        ":art-run-test-target-data-shard62-tmp",
+        ":art-run-test-target-data-shard63-tmp",
+        ":art-run-test-target-data-shard64-tmp",
+        ":art-run-test-target-data-shard65-tmp",
+        ":art-run-test-target-data-shard66-tmp",
+        ":art-run-test-target-data-shard67-tmp",
+        ":art-run-test-target-data-shard68-tmp",
+        ":art-run-test-target-data-shard69-tmp",
+        ":art-run-test-target-data-shard70-tmp",
+        ":art-run-test-target-data-shard71-tmp",
+        ":art-run-test-target-data-shard72-tmp",
+        ":art-run-test-target-data-shard73-tmp",
+        ":art-run-test-target-data-shard74-tmp",
+        ":art-run-test-target-data-shard75-tmp",
+        ":art-run-test-target-data-shard76-tmp",
+        ":art-run-test-target-data-shard77-tmp",
+        ":art-run-test-target-data-shard78-tmp",
+        ":art-run-test-target-data-shard79-tmp",
+        ":art-run-test-target-data-shard80-tmp",
+        ":art-run-test-target-data-shard81-tmp",
+        ":art-run-test-target-data-shard82-tmp",
+        ":art-run-test-target-data-shard83-tmp",
+        ":art-run-test-target-data-shard84-tmp",
+        ":art-run-test-target-data-shard85-tmp",
+        ":art-run-test-target-data-shard86-tmp",
+        ":art-run-test-target-data-shard87-tmp",
+        ":art-run-test-target-data-shard88-tmp",
+        ":art-run-test-target-data-shard89-tmp",
+        ":art-run-test-target-data-shard90-tmp",
+        ":art-run-test-target-data-shard91-tmp",
+        ":art-run-test-target-data-shard92-tmp",
+        ":art-run-test-target-data-shard93-tmp",
+        ":art-run-test-target-data-shard94-tmp",
+        ":art-run-test-target-data-shard95-tmp",
+        ":art-run-test-target-data-shard96-tmp",
+        ":art-run-test-target-data-shard97-tmp",
+        ":art-run-test-target-data-shard98-tmp",
+        ":art-run-test-target-data-shard99-tmp",
+        ":art-run-test-target-data-shardHiddenApi-tmp",
+    ],
+    cmd: "echo $(in) > $(out)",
+}
+
+// Phony target used to install all shards
+prebuilt_etc_host {
+    name: "art-run-test-target-data",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-target-data-tmp",
+    required: [
+        "art-run-test-target-data-shard00",
+        "art-run-test-target-data-shard01",
+        "art-run-test-target-data-shard02",
+        "art-run-test-target-data-shard03",
+        "art-run-test-target-data-shard04",
+        "art-run-test-target-data-shard05",
+        "art-run-test-target-data-shard06",
+        "art-run-test-target-data-shard07",
+        "art-run-test-target-data-shard08",
+        "art-run-test-target-data-shard09",
+        "art-run-test-target-data-shard10",
+        "art-run-test-target-data-shard11",
+        "art-run-test-target-data-shard12",
+        "art-run-test-target-data-shard13",
+        "art-run-test-target-data-shard14",
+        "art-run-test-target-data-shard15",
+        "art-run-test-target-data-shard16",
+        "art-run-test-target-data-shard17",
+        "art-run-test-target-data-shard18",
+        "art-run-test-target-data-shard19",
+        "art-run-test-target-data-shard20",
+        "art-run-test-target-data-shard21",
+        "art-run-test-target-data-shard22",
+        "art-run-test-target-data-shard23",
+        "art-run-test-target-data-shard24",
+        "art-run-test-target-data-shard25",
+        "art-run-test-target-data-shard26",
+        "art-run-test-target-data-shard27",
+        "art-run-test-target-data-shard28",
+        "art-run-test-target-data-shard29",
+        "art-run-test-target-data-shard30",
+        "art-run-test-target-data-shard31",
+        "art-run-test-target-data-shard32",
+        "art-run-test-target-data-shard33",
+        "art-run-test-target-data-shard34",
+        "art-run-test-target-data-shard35",
+        "art-run-test-target-data-shard36",
+        "art-run-test-target-data-shard37",
+        "art-run-test-target-data-shard38",
+        "art-run-test-target-data-shard39",
+        "art-run-test-target-data-shard40",
+        "art-run-test-target-data-shard41",
+        "art-run-test-target-data-shard42",
+        "art-run-test-target-data-shard43",
+        "art-run-test-target-data-shard44",
+        "art-run-test-target-data-shard45",
+        "art-run-test-target-data-shard46",
+        "art-run-test-target-data-shard47",
+        "art-run-test-target-data-shard48",
+        "art-run-test-target-data-shard49",
+        "art-run-test-target-data-shard50",
+        "art-run-test-target-data-shard51",
+        "art-run-test-target-data-shard52",
+        "art-run-test-target-data-shard53",
+        "art-run-test-target-data-shard54",
+        "art-run-test-target-data-shard55",
+        "art-run-test-target-data-shard56",
+        "art-run-test-target-data-shard57",
+        "art-run-test-target-data-shard58",
+        "art-run-test-target-data-shard59",
+        "art-run-test-target-data-shard60",
+        "art-run-test-target-data-shard61",
+        "art-run-test-target-data-shard62",
+        "art-run-test-target-data-shard63",
+        "art-run-test-target-data-shard64",
+        "art-run-test-target-data-shard65",
+        "art-run-test-target-data-shard66",
+        "art-run-test-target-data-shard67",
+        "art-run-test-target-data-shard68",
+        "art-run-test-target-data-shard69",
+        "art-run-test-target-data-shard70",
+        "art-run-test-target-data-shard71",
+        "art-run-test-target-data-shard72",
+        "art-run-test-target-data-shard73",
+        "art-run-test-target-data-shard74",
+        "art-run-test-target-data-shard75",
+        "art-run-test-target-data-shard76",
+        "art-run-test-target-data-shard77",
+        "art-run-test-target-data-shard78",
+        "art-run-test-target-data-shard79",
+        "art-run-test-target-data-shard80",
+        "art-run-test-target-data-shard81",
+        "art-run-test-target-data-shard82",
+        "art-run-test-target-data-shard83",
+        "art-run-test-target-data-shard84",
+        "art-run-test-target-data-shard85",
+        "art-run-test-target-data-shard86",
+        "art-run-test-target-data-shard87",
+        "art-run-test-target-data-shard88",
+        "art-run-test-target-data-shard89",
+        "art-run-test-target-data-shard90",
+        "art-run-test-target-data-shard91",
+        "art-run-test-target-data-shard92",
+        "art-run-test-target-data-shard93",
+        "art-run-test-target-data-shard94",
+        "art-run-test-target-data-shard95",
+        "art-run-test-target-data-shard96",
+        "art-run-test-target-data-shard97",
+        "art-run-test-target-data-shard98",
+        "art-run-test-target-data-shard99",
+        "art-run-test-target-data-shardHiddenApi",
+    ],
+    sub_dir: "art",
+    filename: "art-run-test-target-data.txt",
+}
+
+java_genrule {
+    name: "art-run-test-jvm-data-shard00-tmp",
     out: ["art-run-test-jvm-data-shard00.zip"],
-    srcs: ["*00-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 00 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?00-*/**/*", "??00-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard00",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard00-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard00.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard01",
+    name: "art-run-test-jvm-data-shard01-tmp",
     out: ["art-run-test-jvm-data-shard01.zip"],
-    srcs: ["*01-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 01 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?01-*/**/*", "??01-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard01",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard01-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard01.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard02",
+    name: "art-run-test-jvm-data-shard02-tmp",
     out: ["art-run-test-jvm-data-shard02.zip"],
-    srcs: ["*02-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 02 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?02-*/**/*", "??02-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard02",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard02-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard02.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard03",
+    name: "art-run-test-jvm-data-shard03-tmp",
     out: ["art-run-test-jvm-data-shard03.zip"],
-    srcs: ["*03-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 03 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?03-*/**/*", "??03-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard03",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard03-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard03.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard04",
+    name: "art-run-test-jvm-data-shard04-tmp",
     out: ["art-run-test-jvm-data-shard04.zip"],
-    srcs: ["*04-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 04 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?04-*/**/*", "??04-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard04",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard04-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard04.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard05",
+    name: "art-run-test-jvm-data-shard05-tmp",
     out: ["art-run-test-jvm-data-shard05.zip"],
-    srcs: ["*05-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 05 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?05-*/**/*", "??05-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard05",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard05-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard05.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard06",
+    name: "art-run-test-jvm-data-shard06-tmp",
     out: ["art-run-test-jvm-data-shard06.zip"],
-    srcs: ["*06-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 06 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?06-*/**/*", "??06-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard06",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard06-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard06.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard07",
+    name: "art-run-test-jvm-data-shard07-tmp",
     out: ["art-run-test-jvm-data-shard07.zip"],
-    srcs: ["*07-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 07 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?07-*/**/*", "??07-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard07",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard07-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard07.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard08",
+    name: "art-run-test-jvm-data-shard08-tmp",
     out: ["art-run-test-jvm-data-shard08.zip"],
-    srcs: ["*08-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 08 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?08-*/**/*", "??08-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard08",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard08-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard08.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard09",
+    name: "art-run-test-jvm-data-shard09-tmp",
     out: ["art-run-test-jvm-data-shard09.zip"],
-    srcs: ["*09-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 09 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?09-*/**/*", "??09-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard09",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard09-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard09.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard10",
+    name: "art-run-test-jvm-data-shard10-tmp",
     out: ["art-run-test-jvm-data-shard10.zip"],
-    srcs: ["*10-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 10 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?10-*/**/*", "??10-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard10",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard10-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard10.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard11",
+    name: "art-run-test-jvm-data-shard11-tmp",
     out: ["art-run-test-jvm-data-shard11.zip"],
-    srcs: ["*11-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 11 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?11-*/**/*", "??11-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard11",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard11-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard11.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard12",
+    name: "art-run-test-jvm-data-shard12-tmp",
     out: ["art-run-test-jvm-data-shard12.zip"],
-    srcs: ["*12-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 12 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?12-*/**/*", "??12-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard12",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard12-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard12.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard13",
+    name: "art-run-test-jvm-data-shard13-tmp",
     out: ["art-run-test-jvm-data-shard13.zip"],
-    srcs: ["*13-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 13 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?13-*/**/*", "??13-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard13",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard13-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard13.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard14",
+    name: "art-run-test-jvm-data-shard14-tmp",
     out: ["art-run-test-jvm-data-shard14.zip"],
-    srcs: ["*14-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 14 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?14-*/**/*", "??14-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard14",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard14-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard14.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard15",
+    name: "art-run-test-jvm-data-shard15-tmp",
     out: ["art-run-test-jvm-data-shard15.zip"],
-    srcs: ["*15-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 15 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?15-*/**/*", "??15-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard15",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard15-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard15.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard16",
+    name: "art-run-test-jvm-data-shard16-tmp",
     out: ["art-run-test-jvm-data-shard16.zip"],
-    srcs: ["*16-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 16 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?16-*/**/*", "??16-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard16",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard16-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard16.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard17",
+    name: "art-run-test-jvm-data-shard17-tmp",
     out: ["art-run-test-jvm-data-shard17.zip"],
-    srcs: ["*17-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 17 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?17-*/**/*", "??17-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard17",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard17-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard17.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard18",
+    name: "art-run-test-jvm-data-shard18-tmp",
     out: ["art-run-test-jvm-data-shard18.zip"],
-    srcs: ["*18-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 18 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?18-*/**/*", "??18-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard18",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard18-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard18.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard19",
+    name: "art-run-test-jvm-data-shard19-tmp",
     out: ["art-run-test-jvm-data-shard19.zip"],
-    srcs: ["*19-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 19 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?19-*/**/*", "??19-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard19",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard19-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard19.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard20",
+    name: "art-run-test-jvm-data-shard20-tmp",
     out: ["art-run-test-jvm-data-shard20.zip"],
-    srcs: ["*20-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 20 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?20-*/**/*", "??20-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard20",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard20-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard20.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard21",
+    name: "art-run-test-jvm-data-shard21-tmp",
     out: ["art-run-test-jvm-data-shard21.zip"],
-    srcs: ["*21-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 21 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?21-*/**/*", "??21-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard21",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard21-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard21.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard22",
+    name: "art-run-test-jvm-data-shard22-tmp",
     out: ["art-run-test-jvm-data-shard22.zip"],
-    srcs: ["*22-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 22 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?22-*/**/*", "??22-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard22",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard22-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard22.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard23",
+    name: "art-run-test-jvm-data-shard23-tmp",
     out: ["art-run-test-jvm-data-shard23.zip"],
-    srcs: ["*23-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 23 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?23-*/**/*", "??23-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard23",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard23-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard23.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard24",
+    name: "art-run-test-jvm-data-shard24-tmp",
     out: ["art-run-test-jvm-data-shard24.zip"],
-    srcs: ["*24-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 24 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?24-*/**/*", "??24-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard24",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard24-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard24.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard25",
+    name: "art-run-test-jvm-data-shard25-tmp",
     out: ["art-run-test-jvm-data-shard25.zip"],
-    srcs: ["*25-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 25 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?25-*/**/*", "??25-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard25",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard25-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard25.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard26",
+    name: "art-run-test-jvm-data-shard26-tmp",
     out: ["art-run-test-jvm-data-shard26.zip"],
-    srcs: ["*26-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 26 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?26-*/**/*", "??26-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard26",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard26-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard26.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard27",
+    name: "art-run-test-jvm-data-shard27-tmp",
     out: ["art-run-test-jvm-data-shard27.zip"],
-    srcs: ["*27-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 27 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?27-*/**/*", "??27-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard27",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard27-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard27.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard28",
+    name: "art-run-test-jvm-data-shard28-tmp",
     out: ["art-run-test-jvm-data-shard28.zip"],
-    srcs: ["*28-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 28 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?28-*/**/*", "??28-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard28",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard28-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard28.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard29",
+    name: "art-run-test-jvm-data-shard29-tmp",
     out: ["art-run-test-jvm-data-shard29.zip"],
-    srcs: ["*29-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 29 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?29-*/**/*", "??29-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard29",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard29-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard29.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard30",
+    name: "art-run-test-jvm-data-shard30-tmp",
     out: ["art-run-test-jvm-data-shard30.zip"],
-    srcs: ["*30-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 30 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?30-*/**/*", "??30-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard30",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard30-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard30.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard31",
+    name: "art-run-test-jvm-data-shard31-tmp",
     out: ["art-run-test-jvm-data-shard31.zip"],
-    srcs: ["*31-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 31 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?31-*/**/*", "??31-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard31",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard31-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard31.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard32",
+    name: "art-run-test-jvm-data-shard32-tmp",
     out: ["art-run-test-jvm-data-shard32.zip"],
-    srcs: ["*32-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 32 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?32-*/**/*", "??32-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard32",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard32-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard32.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard33",
+    name: "art-run-test-jvm-data-shard33-tmp",
     out: ["art-run-test-jvm-data-shard33.zip"],
-    srcs: ["*33-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 33 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?33-*/**/*", "??33-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard33",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard33-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard33.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard34",
+    name: "art-run-test-jvm-data-shard34-tmp",
     out: ["art-run-test-jvm-data-shard34.zip"],
-    srcs: ["*34-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 34 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?34-*/**/*", "??34-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard34",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard34-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard34.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard35",
+    name: "art-run-test-jvm-data-shard35-tmp",
     out: ["art-run-test-jvm-data-shard35.zip"],
-    srcs: ["*35-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 35 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?35-*/**/*", "??35-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard35",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard35-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard35.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard36",
+    name: "art-run-test-jvm-data-shard36-tmp",
     out: ["art-run-test-jvm-data-shard36.zip"],
-    srcs: ["*36-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 36 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?36-*/**/*", "??36-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard36",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard36-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard36.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard37",
+    name: "art-run-test-jvm-data-shard37-tmp",
     out: ["art-run-test-jvm-data-shard37.zip"],
-    srcs: ["*37-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 37 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?37-*/**/*", "??37-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard37",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard37-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard37.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard38",
+    name: "art-run-test-jvm-data-shard38-tmp",
     out: ["art-run-test-jvm-data-shard38.zip"],
-    srcs: ["*38-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 38 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?38-*/**/*", "??38-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard38",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard38-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard38.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard39",
+    name: "art-run-test-jvm-data-shard39-tmp",
     out: ["art-run-test-jvm-data-shard39.zip"],
-    srcs: ["*39-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 39 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?39-*/**/*", "??39-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard39",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard39-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard39.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard40",
+    name: "art-run-test-jvm-data-shard40-tmp",
     out: ["art-run-test-jvm-data-shard40.zip"],
-    srcs: ["*40-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 40 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?40-*/**/*", "??40-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard40",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard40-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard40.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard41",
+    name: "art-run-test-jvm-data-shard41-tmp",
     out: ["art-run-test-jvm-data-shard41.zip"],
-    srcs: ["*41-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 41 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?41-*/**/*", "??41-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard41",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard41-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard41.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard42",
+    name: "art-run-test-jvm-data-shard42-tmp",
     out: ["art-run-test-jvm-data-shard42.zip"],
-    srcs: ["*42-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 42 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?42-*/**/*", "??42-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard42",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard42-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard42.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard43",
+    name: "art-run-test-jvm-data-shard43-tmp",
     out: ["art-run-test-jvm-data-shard43.zip"],
-    srcs: ["*43-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 43 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?43-*/**/*", "??43-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard43",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard43-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard43.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard44",
+    name: "art-run-test-jvm-data-shard44-tmp",
     out: ["art-run-test-jvm-data-shard44.zip"],
-    srcs: ["*44-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 44 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?44-*/**/*", "??44-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard44",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard44-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard44.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard45",
+    name: "art-run-test-jvm-data-shard45-tmp",
     out: ["art-run-test-jvm-data-shard45.zip"],
-    srcs: ["*45-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 45 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?45-*/**/*", "??45-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard45",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard45-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard45.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard46",
+    name: "art-run-test-jvm-data-shard46-tmp",
     out: ["art-run-test-jvm-data-shard46.zip"],
-    srcs: ["*46-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 46 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?46-*/**/*", "??46-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard46",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard46-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard46.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard47",
+    name: "art-run-test-jvm-data-shard47-tmp",
     out: ["art-run-test-jvm-data-shard47.zip"],
-    srcs: ["*47-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 47 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?47-*/**/*", "??47-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard47",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard47-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard47.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard48",
+    name: "art-run-test-jvm-data-shard48-tmp",
     out: ["art-run-test-jvm-data-shard48.zip"],
-    srcs: ["*48-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 48 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?48-*/**/*", "??48-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard48",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard48-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard48.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard49",
+    name: "art-run-test-jvm-data-shard49-tmp",
     out: ["art-run-test-jvm-data-shard49.zip"],
-    srcs: ["*49-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 49 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?49-*/**/*", "??49-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard49",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard49-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard49.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard50",
+    name: "art-run-test-jvm-data-shard50-tmp",
     out: ["art-run-test-jvm-data-shard50.zip"],
-    srcs: ["*50-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 50 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?50-*/**/*", "??50-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard50",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard50-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard50.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard51",
+    name: "art-run-test-jvm-data-shard51-tmp",
     out: ["art-run-test-jvm-data-shard51.zip"],
-    srcs: ["*51-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 51 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?51-*/**/*", "??51-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard51",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard51-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard51.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard52",
+    name: "art-run-test-jvm-data-shard52-tmp",
     out: ["art-run-test-jvm-data-shard52.zip"],
-    srcs: ["*52-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 52 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?52-*/**/*", "??52-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard52",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard52-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard52.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard53",
+    name: "art-run-test-jvm-data-shard53-tmp",
     out: ["art-run-test-jvm-data-shard53.zip"],
-    srcs: ["*53-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 53 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?53-*/**/*", "??53-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard53",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard53-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard53.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard54",
+    name: "art-run-test-jvm-data-shard54-tmp",
     out: ["art-run-test-jvm-data-shard54.zip"],
-    srcs: ["*54-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 54 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?54-*/**/*", "??54-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard54",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard54-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard54.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard55",
+    name: "art-run-test-jvm-data-shard55-tmp",
     out: ["art-run-test-jvm-data-shard55.zip"],
-    srcs: ["*55-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 55 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?55-*/**/*", "??55-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard55",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard55-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard55.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard56",
+    name: "art-run-test-jvm-data-shard56-tmp",
     out: ["art-run-test-jvm-data-shard56.zip"],
-    srcs: ["*56-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 56 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?56-*/**/*", "??56-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard56",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard56-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard56.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard57",
+    name: "art-run-test-jvm-data-shard57-tmp",
     out: ["art-run-test-jvm-data-shard57.zip"],
-    srcs: ["*57-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 57 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?57-*/**/*", "??57-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard57",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard57-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard57.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard58",
+    name: "art-run-test-jvm-data-shard58-tmp",
     out: ["art-run-test-jvm-data-shard58.zip"],
-    srcs: ["*58-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 58 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?58-*/**/*", "??58-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard58",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard58-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard58.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard59",
+    name: "art-run-test-jvm-data-shard59-tmp",
     out: ["art-run-test-jvm-data-shard59.zip"],
-    srcs: ["*59-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 59 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?59-*/**/*", "??59-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard59",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard59-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard59.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard60",
+    name: "art-run-test-jvm-data-shard60-tmp",
     out: ["art-run-test-jvm-data-shard60.zip"],
-    srcs: ["*60-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 60 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?60-*/**/*", "??60-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard60",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard60-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard60.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard61",
+    name: "art-run-test-jvm-data-shard61-tmp",
     out: ["art-run-test-jvm-data-shard61.zip"],
-    srcs: ["*61-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 61 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?61-*/**/*", "??61-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard61",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard61-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard61.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard62",
+    name: "art-run-test-jvm-data-shard62-tmp",
     out: ["art-run-test-jvm-data-shard62.zip"],
-    srcs: ["*62-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 62 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?62-*/**/*", "??62-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard62",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard62-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard62.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard63",
+    name: "art-run-test-jvm-data-shard63-tmp",
     out: ["art-run-test-jvm-data-shard63.zip"],
-    srcs: ["*63-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 63 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?63-*/**/*", "??63-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard63",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard63-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard63.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard64",
+    name: "art-run-test-jvm-data-shard64-tmp",
     out: ["art-run-test-jvm-data-shard64.zip"],
-    srcs: ["*64-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 64 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?64-*/**/*", "??64-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard64",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard64-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard64.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard65",
+    name: "art-run-test-jvm-data-shard65-tmp",
     out: ["art-run-test-jvm-data-shard65.zip"],
-    srcs: ["*65-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 65 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?65-*/**/*", "??65-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard65",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard65-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard65.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard66",
+    name: "art-run-test-jvm-data-shard66-tmp",
     out: ["art-run-test-jvm-data-shard66.zip"],
-    srcs: ["*66-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 66 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?66-*/**/*", "??66-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard66",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard66-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard66.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard67",
+    name: "art-run-test-jvm-data-shard67-tmp",
     out: ["art-run-test-jvm-data-shard67.zip"],
-    srcs: ["*67-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 67 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?67-*/**/*", "??67-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard67",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard67-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard67.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard68",
+    name: "art-run-test-jvm-data-shard68-tmp",
     out: ["art-run-test-jvm-data-shard68.zip"],
-    srcs: ["*68-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 68 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?68-*/**/*", "??68-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard68",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard68-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard68.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard69",
+    name: "art-run-test-jvm-data-shard69-tmp",
     out: ["art-run-test-jvm-data-shard69.zip"],
-    srcs: ["*69-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 69 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?69-*/**/*", "??69-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard69",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard69-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard69.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard70",
+    name: "art-run-test-jvm-data-shard70-tmp",
     out: ["art-run-test-jvm-data-shard70.zip"],
-    srcs: ["*70-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 70 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?70-*/**/*", "??70-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard70",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard70-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard70.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard71",
+    name: "art-run-test-jvm-data-shard71-tmp",
     out: ["art-run-test-jvm-data-shard71.zip"],
-    srcs: ["*71-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 71 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?71-*/**/*", "??71-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard71",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard71-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard71.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard72",
+    name: "art-run-test-jvm-data-shard72-tmp",
     out: ["art-run-test-jvm-data-shard72.zip"],
-    srcs: ["*72-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 72 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?72-*/**/*", "??72-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard72",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard72-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard72.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard73",
+    name: "art-run-test-jvm-data-shard73-tmp",
     out: ["art-run-test-jvm-data-shard73.zip"],
-    srcs: ["*73-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 73 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?73-*/**/*", "??73-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard73",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard73-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard73.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard74",
+    name: "art-run-test-jvm-data-shard74-tmp",
     out: ["art-run-test-jvm-data-shard74.zip"],
-    srcs: ["*74-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 74 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?74-*/**/*", "??74-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard74",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard74-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard74.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard75",
+    name: "art-run-test-jvm-data-shard75-tmp",
     out: ["art-run-test-jvm-data-shard75.zip"],
-    srcs: ["*75-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 75 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?75-*/**/*", "??75-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard75",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard75-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard75.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard76",
+    name: "art-run-test-jvm-data-shard76-tmp",
     out: ["art-run-test-jvm-data-shard76.zip"],
-    srcs: ["*76-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 76 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?76-*/**/*", "??76-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard76",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard76-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard76.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard77",
+    name: "art-run-test-jvm-data-shard77-tmp",
     out: ["art-run-test-jvm-data-shard77.zip"],
-    srcs: ["*77-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 77 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?77-*/**/*", "??77-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard77",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard77-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard77.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard78",
+    name: "art-run-test-jvm-data-shard78-tmp",
     out: ["art-run-test-jvm-data-shard78.zip"],
-    srcs: ["*78-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 78 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?78-*/**/*", "??78-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard78",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard78-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard78.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard79",
+    name: "art-run-test-jvm-data-shard79-tmp",
     out: ["art-run-test-jvm-data-shard79.zip"],
-    srcs: ["*79-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 79 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?79-*/**/*", "??79-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard79",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard79-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard79.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard80",
+    name: "art-run-test-jvm-data-shard80-tmp",
     out: ["art-run-test-jvm-data-shard80.zip"],
-    srcs: ["*80-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 80 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?80-*/**/*", "??80-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard80",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard80-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard80.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard81",
+    name: "art-run-test-jvm-data-shard81-tmp",
     out: ["art-run-test-jvm-data-shard81.zip"],
-    srcs: ["*81-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 81 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?81-*/**/*", "??81-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard81",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard81-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard81.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard82",
+    name: "art-run-test-jvm-data-shard82-tmp",
     out: ["art-run-test-jvm-data-shard82.zip"],
-    srcs: ["*82-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 82 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?82-*/**/*", "??82-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard82",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard82-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard82.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard83",
+    name: "art-run-test-jvm-data-shard83-tmp",
     out: ["art-run-test-jvm-data-shard83.zip"],
-    srcs: ["*83-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 83 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?83-*/**/*", "??83-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard83",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard83-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard83.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard84",
+    name: "art-run-test-jvm-data-shard84-tmp",
     out: ["art-run-test-jvm-data-shard84.zip"],
-    srcs: ["*84-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 84 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?84-*/**/*", "??84-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard84",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard84-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard84.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard85",
+    name: "art-run-test-jvm-data-shard85-tmp",
     out: ["art-run-test-jvm-data-shard85.zip"],
-    srcs: ["*85-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 85 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?85-*/**/*", "??85-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard85",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard85-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard85.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard86",
+    name: "art-run-test-jvm-data-shard86-tmp",
     out: ["art-run-test-jvm-data-shard86.zip"],
-    srcs: ["*86-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 86 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?86-*/**/*", "??86-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard86",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard86-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard86.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard87",
+    name: "art-run-test-jvm-data-shard87-tmp",
     out: ["art-run-test-jvm-data-shard87.zip"],
-    srcs: ["*87-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 87 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?87-*/**/*", "??87-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard87",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard87-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard87.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard88",
+    name: "art-run-test-jvm-data-shard88-tmp",
     out: ["art-run-test-jvm-data-shard88.zip"],
-    srcs: ["*88-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 88 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?88-*/**/*", "??88-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard88",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard88-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard88.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard89",
+    name: "art-run-test-jvm-data-shard89-tmp",
     out: ["art-run-test-jvm-data-shard89.zip"],
-    srcs: ["*89-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 89 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?89-*/**/*", "??89-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard89",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard89-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard89.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard90",
+    name: "art-run-test-jvm-data-shard90-tmp",
     out: ["art-run-test-jvm-data-shard90.zip"],
-    srcs: ["*90-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 90 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?90-*/**/*", "??90-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard90",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard90-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard90.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard91",
+    name: "art-run-test-jvm-data-shard91-tmp",
     out: ["art-run-test-jvm-data-shard91.zip"],
-    srcs: ["*91-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 91 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?91-*/**/*", "??91-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard91",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard91-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard91.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard92",
+    name: "art-run-test-jvm-data-shard92-tmp",
     out: ["art-run-test-jvm-data-shard92.zip"],
-    srcs: ["*92-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 92 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?92-*/**/*", "??92-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard92",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard92-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard92.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard93",
+    name: "art-run-test-jvm-data-shard93-tmp",
     out: ["art-run-test-jvm-data-shard93.zip"],
-    srcs: ["*93-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 93 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?93-*/**/*", "??93-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard93",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard93-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard93.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard94",
+    name: "art-run-test-jvm-data-shard94-tmp",
     out: ["art-run-test-jvm-data-shard94.zip"],
-    srcs: ["*94-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 94 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?94-*/**/*", "??94-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard94",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard94-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard94.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard95",
+    name: "art-run-test-jvm-data-shard95-tmp",
     out: ["art-run-test-jvm-data-shard95.zip"],
-    srcs: ["*95-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 95 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?95-*/**/*", "??95-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard95",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard95-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard95.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard96",
+    name: "art-run-test-jvm-data-shard96-tmp",
     out: ["art-run-test-jvm-data-shard96.zip"],
-    srcs: ["*96-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 96 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?96-*/**/*", "??96-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard96",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard96-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard96.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard97",
+    name: "art-run-test-jvm-data-shard97-tmp",
     out: ["art-run-test-jvm-data-shard97.zip"],
-    srcs: ["*97-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 97 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?97-*/**/*", "??97-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard97",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard97-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard97.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard98",
+    name: "art-run-test-jvm-data-shard98-tmp",
     out: ["art-run-test-jvm-data-shard98.zip"],
-    srcs: ["*98-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 98 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?98-*/**/*", "??98-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard98",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard98-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard98.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-shard99",
+    name: "art-run-test-jvm-data-shard99-tmp",
     out: ["art-run-test-jvm-data-shard99.zip"],
-    srcs: ["*99-*/**/*"],
-    defaults: ["art-run-test-data-defaults"],
-    cmd: "$(location run-test-build.py) --out $(out) --mode jvm --shard 99 " +
-        "--bootclasspath $(location :art-run-test-bootclasspath)",
+    srcs: ["?99-*/**/*", "??99-*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shard99",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shard99-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shard99.zip",
 }
 
 java_genrule {
-    name: "art-run-test-jvm-data-merged",
-    defaults: ["art-run-test-data-defaults"],
+    name: "art-run-test-jvm-data-shardHiddenApi-tmp",
+    out: ["art-run-test-jvm-data-shardHiddenApi.zip"],
+    srcs: ["???-*hiddenapi*/**/*", "????-*hiddenapi*/**/*"],
+    defaults: ["art-run-test-jvm-data-defaults"],
+    tools: ["hiddenapi"],
+    cmd: "$(location run_test_build.py) --out $(out) --mode jvm " +
+         "--bootclasspath $(location :art-run-test-bootclasspath) " +
+         "--d8 $(location d8) " +
+         "--hiddenapi $(location hiddenapi) " +
+         "--jasmin $(location jasmin) " +
+         "--smali $(location smali) " +
+         "--soong_zip $(location soong_zip) " +
+         "--zipalign $(location zipalign) " +
+         "$(in)",
+}
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-shardHiddenApi",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-shardHiddenApi-tmp",
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-shardHiddenApi.zip",
+}
+
+genrule_defaults {
+    name: "art-run-test-jvm-data-defaults",
+    defaults: [
+        // Enable only in source builds, where com.android.art.testing is
+        // available.
+        "art_module_source_build_genrule_defaults",
+    ],
+    tool_files: [
+        "run_test_build.py",
+        ":art-run-test-bootclasspath",
+    ],
+    tools: [
+        "d8",
+        "jasmin",
+        "smali",
+        "soong_zip",
+        "zipalign",
+    ],
+    cmd: "$(location run_test_build.py) --out $(out) --mode jvm " +
+         "--bootclasspath $(location :art-run-test-bootclasspath) " +
+         "--d8 $(location d8) " +
+         "--jasmin $(location jasmin) " +
+         "--smali $(location smali) " +
+         "--soong_zip $(location soong_zip) " +
+         "--zipalign $(location zipalign) " +
+         "$(in)",
+}
+
+java_genrule {
+    name: "art-run-test-jvm-data-merged-tmp",
+    defaults: ["art_module_source_build_genrule_defaults"],
     out: ["art-run-test-jvm-data-merged.zip"],
     srcs: [
-        ":art-run-test-jvm-data-shard00",
-        ":art-run-test-jvm-data-shard01",
-        ":art-run-test-jvm-data-shard02",
-        ":art-run-test-jvm-data-shard03",
-        ":art-run-test-jvm-data-shard04",
-        ":art-run-test-jvm-data-shard05",
-        ":art-run-test-jvm-data-shard06",
-        ":art-run-test-jvm-data-shard07",
-        ":art-run-test-jvm-data-shard08",
-        ":art-run-test-jvm-data-shard09",
-        ":art-run-test-jvm-data-shard10",
-        ":art-run-test-jvm-data-shard11",
-        ":art-run-test-jvm-data-shard12",
-        ":art-run-test-jvm-data-shard13",
-        ":art-run-test-jvm-data-shard14",
-        ":art-run-test-jvm-data-shard15",
-        ":art-run-test-jvm-data-shard16",
-        ":art-run-test-jvm-data-shard17",
-        ":art-run-test-jvm-data-shard18",
-        ":art-run-test-jvm-data-shard19",
-        ":art-run-test-jvm-data-shard20",
-        ":art-run-test-jvm-data-shard21",
-        ":art-run-test-jvm-data-shard22",
-        ":art-run-test-jvm-data-shard23",
-        ":art-run-test-jvm-data-shard24",
-        ":art-run-test-jvm-data-shard25",
-        ":art-run-test-jvm-data-shard26",
-        ":art-run-test-jvm-data-shard27",
-        ":art-run-test-jvm-data-shard28",
-        ":art-run-test-jvm-data-shard29",
-        ":art-run-test-jvm-data-shard30",
-        ":art-run-test-jvm-data-shard31",
-        ":art-run-test-jvm-data-shard32",
-        ":art-run-test-jvm-data-shard33",
-        ":art-run-test-jvm-data-shard34",
-        ":art-run-test-jvm-data-shard35",
-        ":art-run-test-jvm-data-shard36",
-        ":art-run-test-jvm-data-shard37",
-        ":art-run-test-jvm-data-shard38",
-        ":art-run-test-jvm-data-shard39",
-        ":art-run-test-jvm-data-shard40",
-        ":art-run-test-jvm-data-shard41",
-        ":art-run-test-jvm-data-shard42",
-        ":art-run-test-jvm-data-shard43",
-        ":art-run-test-jvm-data-shard44",
-        ":art-run-test-jvm-data-shard45",
-        ":art-run-test-jvm-data-shard46",
-        ":art-run-test-jvm-data-shard47",
-        ":art-run-test-jvm-data-shard48",
-        ":art-run-test-jvm-data-shard49",
-        ":art-run-test-jvm-data-shard50",
-        ":art-run-test-jvm-data-shard51",
-        ":art-run-test-jvm-data-shard52",
-        ":art-run-test-jvm-data-shard53",
-        ":art-run-test-jvm-data-shard54",
-        ":art-run-test-jvm-data-shard55",
-        ":art-run-test-jvm-data-shard56",
-        ":art-run-test-jvm-data-shard57",
-        ":art-run-test-jvm-data-shard58",
-        ":art-run-test-jvm-data-shard59",
-        ":art-run-test-jvm-data-shard60",
-        ":art-run-test-jvm-data-shard61",
-        ":art-run-test-jvm-data-shard62",
-        ":art-run-test-jvm-data-shard63",
-        ":art-run-test-jvm-data-shard64",
-        ":art-run-test-jvm-data-shard65",
-        ":art-run-test-jvm-data-shard66",
-        ":art-run-test-jvm-data-shard67",
-        ":art-run-test-jvm-data-shard68",
-        ":art-run-test-jvm-data-shard69",
-        ":art-run-test-jvm-data-shard70",
-        ":art-run-test-jvm-data-shard71",
-        ":art-run-test-jvm-data-shard72",
-        ":art-run-test-jvm-data-shard73",
-        ":art-run-test-jvm-data-shard74",
-        ":art-run-test-jvm-data-shard75",
-        ":art-run-test-jvm-data-shard76",
-        ":art-run-test-jvm-data-shard77",
-        ":art-run-test-jvm-data-shard78",
-        ":art-run-test-jvm-data-shard79",
-        ":art-run-test-jvm-data-shard80",
-        ":art-run-test-jvm-data-shard81",
-        ":art-run-test-jvm-data-shard82",
-        ":art-run-test-jvm-data-shard83",
-        ":art-run-test-jvm-data-shard84",
-        ":art-run-test-jvm-data-shard85",
-        ":art-run-test-jvm-data-shard86",
-        ":art-run-test-jvm-data-shard87",
-        ":art-run-test-jvm-data-shard88",
-        ":art-run-test-jvm-data-shard89",
-        ":art-run-test-jvm-data-shard90",
-        ":art-run-test-jvm-data-shard91",
-        ":art-run-test-jvm-data-shard92",
-        ":art-run-test-jvm-data-shard93",
-        ":art-run-test-jvm-data-shard94",
-        ":art-run-test-jvm-data-shard95",
-        ":art-run-test-jvm-data-shard96",
-        ":art-run-test-jvm-data-shard97",
-        ":art-run-test-jvm-data-shard98",
-        ":art-run-test-jvm-data-shard99",
+        ":art-run-test-jvm-data-shard00-tmp",
+        ":art-run-test-jvm-data-shard01-tmp",
+        ":art-run-test-jvm-data-shard02-tmp",
+        ":art-run-test-jvm-data-shard03-tmp",
+        ":art-run-test-jvm-data-shard04-tmp",
+        ":art-run-test-jvm-data-shard05-tmp",
+        ":art-run-test-jvm-data-shard06-tmp",
+        ":art-run-test-jvm-data-shard07-tmp",
+        ":art-run-test-jvm-data-shard08-tmp",
+        ":art-run-test-jvm-data-shard09-tmp",
+        ":art-run-test-jvm-data-shard10-tmp",
+        ":art-run-test-jvm-data-shard11-tmp",
+        ":art-run-test-jvm-data-shard12-tmp",
+        ":art-run-test-jvm-data-shard13-tmp",
+        ":art-run-test-jvm-data-shard14-tmp",
+        ":art-run-test-jvm-data-shard15-tmp",
+        ":art-run-test-jvm-data-shard16-tmp",
+        ":art-run-test-jvm-data-shard17-tmp",
+        ":art-run-test-jvm-data-shard18-tmp",
+        ":art-run-test-jvm-data-shard19-tmp",
+        ":art-run-test-jvm-data-shard20-tmp",
+        ":art-run-test-jvm-data-shard21-tmp",
+        ":art-run-test-jvm-data-shard22-tmp",
+        ":art-run-test-jvm-data-shard23-tmp",
+        ":art-run-test-jvm-data-shard24-tmp",
+        ":art-run-test-jvm-data-shard25-tmp",
+        ":art-run-test-jvm-data-shard26-tmp",
+        ":art-run-test-jvm-data-shard27-tmp",
+        ":art-run-test-jvm-data-shard28-tmp",
+        ":art-run-test-jvm-data-shard29-tmp",
+        ":art-run-test-jvm-data-shard30-tmp",
+        ":art-run-test-jvm-data-shard31-tmp",
+        ":art-run-test-jvm-data-shard32-tmp",
+        ":art-run-test-jvm-data-shard33-tmp",
+        ":art-run-test-jvm-data-shard34-tmp",
+        ":art-run-test-jvm-data-shard35-tmp",
+        ":art-run-test-jvm-data-shard36-tmp",
+        ":art-run-test-jvm-data-shard37-tmp",
+        ":art-run-test-jvm-data-shard38-tmp",
+        ":art-run-test-jvm-data-shard39-tmp",
+        ":art-run-test-jvm-data-shard40-tmp",
+        ":art-run-test-jvm-data-shard41-tmp",
+        ":art-run-test-jvm-data-shard42-tmp",
+        ":art-run-test-jvm-data-shard43-tmp",
+        ":art-run-test-jvm-data-shard44-tmp",
+        ":art-run-test-jvm-data-shard45-tmp",
+        ":art-run-test-jvm-data-shard46-tmp",
+        ":art-run-test-jvm-data-shard47-tmp",
+        ":art-run-test-jvm-data-shard48-tmp",
+        ":art-run-test-jvm-data-shard49-tmp",
+        ":art-run-test-jvm-data-shard50-tmp",
+        ":art-run-test-jvm-data-shard51-tmp",
+        ":art-run-test-jvm-data-shard52-tmp",
+        ":art-run-test-jvm-data-shard53-tmp",
+        ":art-run-test-jvm-data-shard54-tmp",
+        ":art-run-test-jvm-data-shard55-tmp",
+        ":art-run-test-jvm-data-shard56-tmp",
+        ":art-run-test-jvm-data-shard57-tmp",
+        ":art-run-test-jvm-data-shard58-tmp",
+        ":art-run-test-jvm-data-shard59-tmp",
+        ":art-run-test-jvm-data-shard60-tmp",
+        ":art-run-test-jvm-data-shard61-tmp",
+        ":art-run-test-jvm-data-shard62-tmp",
+        ":art-run-test-jvm-data-shard63-tmp",
+        ":art-run-test-jvm-data-shard64-tmp",
+        ":art-run-test-jvm-data-shard65-tmp",
+        ":art-run-test-jvm-data-shard66-tmp",
+        ":art-run-test-jvm-data-shard67-tmp",
+        ":art-run-test-jvm-data-shard68-tmp",
+        ":art-run-test-jvm-data-shard69-tmp",
+        ":art-run-test-jvm-data-shard70-tmp",
+        ":art-run-test-jvm-data-shard71-tmp",
+        ":art-run-test-jvm-data-shard72-tmp",
+        ":art-run-test-jvm-data-shard73-tmp",
+        ":art-run-test-jvm-data-shard74-tmp",
+        ":art-run-test-jvm-data-shard75-tmp",
+        ":art-run-test-jvm-data-shard76-tmp",
+        ":art-run-test-jvm-data-shard77-tmp",
+        ":art-run-test-jvm-data-shard78-tmp",
+        ":art-run-test-jvm-data-shard79-tmp",
+        ":art-run-test-jvm-data-shard80-tmp",
+        ":art-run-test-jvm-data-shard81-tmp",
+        ":art-run-test-jvm-data-shard82-tmp",
+        ":art-run-test-jvm-data-shard83-tmp",
+        ":art-run-test-jvm-data-shard84-tmp",
+        ":art-run-test-jvm-data-shard85-tmp",
+        ":art-run-test-jvm-data-shard86-tmp",
+        ":art-run-test-jvm-data-shard87-tmp",
+        ":art-run-test-jvm-data-shard88-tmp",
+        ":art-run-test-jvm-data-shard89-tmp",
+        ":art-run-test-jvm-data-shard90-tmp",
+        ":art-run-test-jvm-data-shard91-tmp",
+        ":art-run-test-jvm-data-shard92-tmp",
+        ":art-run-test-jvm-data-shard93-tmp",
+        ":art-run-test-jvm-data-shard94-tmp",
+        ":art-run-test-jvm-data-shard95-tmp",
+        ":art-run-test-jvm-data-shard96-tmp",
+        ":art-run-test-jvm-data-shard97-tmp",
+        ":art-run-test-jvm-data-shard98-tmp",
+        ":art-run-test-jvm-data-shard99-tmp",
+        ":art-run-test-jvm-data-shardHiddenApi-tmp",
     ],
     tools: ["merge_zips"],
     cmd: "$(location merge_zips) $(out) $(in)",
 }
+
+// Install in the output directory to make it accessible for tests.
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data-merged",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-merged-tmp",
+    required: [
+        "art-run-test-jvm-data-shard00",
+        "art-run-test-jvm-data-shard01",
+        "art-run-test-jvm-data-shard02",
+        "art-run-test-jvm-data-shard03",
+        "art-run-test-jvm-data-shard04",
+        "art-run-test-jvm-data-shard05",
+        "art-run-test-jvm-data-shard06",
+        "art-run-test-jvm-data-shard07",
+        "art-run-test-jvm-data-shard08",
+        "art-run-test-jvm-data-shard09",
+        "art-run-test-jvm-data-shard10",
+        "art-run-test-jvm-data-shard11",
+        "art-run-test-jvm-data-shard12",
+        "art-run-test-jvm-data-shard13",
+        "art-run-test-jvm-data-shard14",
+        "art-run-test-jvm-data-shard15",
+        "art-run-test-jvm-data-shard16",
+        "art-run-test-jvm-data-shard17",
+        "art-run-test-jvm-data-shard18",
+        "art-run-test-jvm-data-shard19",
+        "art-run-test-jvm-data-shard20",
+        "art-run-test-jvm-data-shard21",
+        "art-run-test-jvm-data-shard22",
+        "art-run-test-jvm-data-shard23",
+        "art-run-test-jvm-data-shard24",
+        "art-run-test-jvm-data-shard25",
+        "art-run-test-jvm-data-shard26",
+        "art-run-test-jvm-data-shard27",
+        "art-run-test-jvm-data-shard28",
+        "art-run-test-jvm-data-shard29",
+        "art-run-test-jvm-data-shard30",
+        "art-run-test-jvm-data-shard31",
+        "art-run-test-jvm-data-shard32",
+        "art-run-test-jvm-data-shard33",
+        "art-run-test-jvm-data-shard34",
+        "art-run-test-jvm-data-shard35",
+        "art-run-test-jvm-data-shard36",
+        "art-run-test-jvm-data-shard37",
+        "art-run-test-jvm-data-shard38",
+        "art-run-test-jvm-data-shard39",
+        "art-run-test-jvm-data-shard40",
+        "art-run-test-jvm-data-shard41",
+        "art-run-test-jvm-data-shard42",
+        "art-run-test-jvm-data-shard43",
+        "art-run-test-jvm-data-shard44",
+        "art-run-test-jvm-data-shard45",
+        "art-run-test-jvm-data-shard46",
+        "art-run-test-jvm-data-shard47",
+        "art-run-test-jvm-data-shard48",
+        "art-run-test-jvm-data-shard49",
+        "art-run-test-jvm-data-shard50",
+        "art-run-test-jvm-data-shard51",
+        "art-run-test-jvm-data-shard52",
+        "art-run-test-jvm-data-shard53",
+        "art-run-test-jvm-data-shard54",
+        "art-run-test-jvm-data-shard55",
+        "art-run-test-jvm-data-shard56",
+        "art-run-test-jvm-data-shard57",
+        "art-run-test-jvm-data-shard58",
+        "art-run-test-jvm-data-shard59",
+        "art-run-test-jvm-data-shard60",
+        "art-run-test-jvm-data-shard61",
+        "art-run-test-jvm-data-shard62",
+        "art-run-test-jvm-data-shard63",
+        "art-run-test-jvm-data-shard64",
+        "art-run-test-jvm-data-shard65",
+        "art-run-test-jvm-data-shard66",
+        "art-run-test-jvm-data-shard67",
+        "art-run-test-jvm-data-shard68",
+        "art-run-test-jvm-data-shard69",
+        "art-run-test-jvm-data-shard70",
+        "art-run-test-jvm-data-shard71",
+        "art-run-test-jvm-data-shard72",
+        "art-run-test-jvm-data-shard73",
+        "art-run-test-jvm-data-shard74",
+        "art-run-test-jvm-data-shard75",
+        "art-run-test-jvm-data-shard76",
+        "art-run-test-jvm-data-shard77",
+        "art-run-test-jvm-data-shard78",
+        "art-run-test-jvm-data-shard79",
+        "art-run-test-jvm-data-shard80",
+        "art-run-test-jvm-data-shard81",
+        "art-run-test-jvm-data-shard82",
+        "art-run-test-jvm-data-shard83",
+        "art-run-test-jvm-data-shard84",
+        "art-run-test-jvm-data-shard85",
+        "art-run-test-jvm-data-shard86",
+        "art-run-test-jvm-data-shard87",
+        "art-run-test-jvm-data-shard88",
+        "art-run-test-jvm-data-shard89",
+        "art-run-test-jvm-data-shard90",
+        "art-run-test-jvm-data-shard91",
+        "art-run-test-jvm-data-shard92",
+        "art-run-test-jvm-data-shard93",
+        "art-run-test-jvm-data-shard94",
+        "art-run-test-jvm-data-shard95",
+        "art-run-test-jvm-data-shard96",
+        "art-run-test-jvm-data-shard97",
+        "art-run-test-jvm-data-shard98",
+        "art-run-test-jvm-data-shard99",
+        "art-run-test-jvm-data-shardHiddenApi",
+    ],
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data-merged.zip",
+}
+
+// Phony target used to build all shards
+java_genrule {
+    name: "art-run-test-jvm-data-tmp",
+    defaults: ["art-run-test-data-defaults"],
+    out: ["art-run-test-jvm-data.txt"],
+    srcs: [
+        ":art-run-test-jvm-data-shard00-tmp",
+        ":art-run-test-jvm-data-shard01-tmp",
+        ":art-run-test-jvm-data-shard02-tmp",
+        ":art-run-test-jvm-data-shard03-tmp",
+        ":art-run-test-jvm-data-shard04-tmp",
+        ":art-run-test-jvm-data-shard05-tmp",
+        ":art-run-test-jvm-data-shard06-tmp",
+        ":art-run-test-jvm-data-shard07-tmp",
+        ":art-run-test-jvm-data-shard08-tmp",
+        ":art-run-test-jvm-data-shard09-tmp",
+        ":art-run-test-jvm-data-shard10-tmp",
+        ":art-run-test-jvm-data-shard11-tmp",
+        ":art-run-test-jvm-data-shard12-tmp",
+        ":art-run-test-jvm-data-shard13-tmp",
+        ":art-run-test-jvm-data-shard14-tmp",
+        ":art-run-test-jvm-data-shard15-tmp",
+        ":art-run-test-jvm-data-shard16-tmp",
+        ":art-run-test-jvm-data-shard17-tmp",
+        ":art-run-test-jvm-data-shard18-tmp",
+        ":art-run-test-jvm-data-shard19-tmp",
+        ":art-run-test-jvm-data-shard20-tmp",
+        ":art-run-test-jvm-data-shard21-tmp",
+        ":art-run-test-jvm-data-shard22-tmp",
+        ":art-run-test-jvm-data-shard23-tmp",
+        ":art-run-test-jvm-data-shard24-tmp",
+        ":art-run-test-jvm-data-shard25-tmp",
+        ":art-run-test-jvm-data-shard26-tmp",
+        ":art-run-test-jvm-data-shard27-tmp",
+        ":art-run-test-jvm-data-shard28-tmp",
+        ":art-run-test-jvm-data-shard29-tmp",
+        ":art-run-test-jvm-data-shard30-tmp",
+        ":art-run-test-jvm-data-shard31-tmp",
+        ":art-run-test-jvm-data-shard32-tmp",
+        ":art-run-test-jvm-data-shard33-tmp",
+        ":art-run-test-jvm-data-shard34-tmp",
+        ":art-run-test-jvm-data-shard35-tmp",
+        ":art-run-test-jvm-data-shard36-tmp",
+        ":art-run-test-jvm-data-shard37-tmp",
+        ":art-run-test-jvm-data-shard38-tmp",
+        ":art-run-test-jvm-data-shard39-tmp",
+        ":art-run-test-jvm-data-shard40-tmp",
+        ":art-run-test-jvm-data-shard41-tmp",
+        ":art-run-test-jvm-data-shard42-tmp",
+        ":art-run-test-jvm-data-shard43-tmp",
+        ":art-run-test-jvm-data-shard44-tmp",
+        ":art-run-test-jvm-data-shard45-tmp",
+        ":art-run-test-jvm-data-shard46-tmp",
+        ":art-run-test-jvm-data-shard47-tmp",
+        ":art-run-test-jvm-data-shard48-tmp",
+        ":art-run-test-jvm-data-shard49-tmp",
+        ":art-run-test-jvm-data-shard50-tmp",
+        ":art-run-test-jvm-data-shard51-tmp",
+        ":art-run-test-jvm-data-shard52-tmp",
+        ":art-run-test-jvm-data-shard53-tmp",
+        ":art-run-test-jvm-data-shard54-tmp",
+        ":art-run-test-jvm-data-shard55-tmp",
+        ":art-run-test-jvm-data-shard56-tmp",
+        ":art-run-test-jvm-data-shard57-tmp",
+        ":art-run-test-jvm-data-shard58-tmp",
+        ":art-run-test-jvm-data-shard59-tmp",
+        ":art-run-test-jvm-data-shard60-tmp",
+        ":art-run-test-jvm-data-shard61-tmp",
+        ":art-run-test-jvm-data-shard62-tmp",
+        ":art-run-test-jvm-data-shard63-tmp",
+        ":art-run-test-jvm-data-shard64-tmp",
+        ":art-run-test-jvm-data-shard65-tmp",
+        ":art-run-test-jvm-data-shard66-tmp",
+        ":art-run-test-jvm-data-shard67-tmp",
+        ":art-run-test-jvm-data-shard68-tmp",
+        ":art-run-test-jvm-data-shard69-tmp",
+        ":art-run-test-jvm-data-shard70-tmp",
+        ":art-run-test-jvm-data-shard71-tmp",
+        ":art-run-test-jvm-data-shard72-tmp",
+        ":art-run-test-jvm-data-shard73-tmp",
+        ":art-run-test-jvm-data-shard74-tmp",
+        ":art-run-test-jvm-data-shard75-tmp",
+        ":art-run-test-jvm-data-shard76-tmp",
+        ":art-run-test-jvm-data-shard77-tmp",
+        ":art-run-test-jvm-data-shard78-tmp",
+        ":art-run-test-jvm-data-shard79-tmp",
+        ":art-run-test-jvm-data-shard80-tmp",
+        ":art-run-test-jvm-data-shard81-tmp",
+        ":art-run-test-jvm-data-shard82-tmp",
+        ":art-run-test-jvm-data-shard83-tmp",
+        ":art-run-test-jvm-data-shard84-tmp",
+        ":art-run-test-jvm-data-shard85-tmp",
+        ":art-run-test-jvm-data-shard86-tmp",
+        ":art-run-test-jvm-data-shard87-tmp",
+        ":art-run-test-jvm-data-shard88-tmp",
+        ":art-run-test-jvm-data-shard89-tmp",
+        ":art-run-test-jvm-data-shard90-tmp",
+        ":art-run-test-jvm-data-shard91-tmp",
+        ":art-run-test-jvm-data-shard92-tmp",
+        ":art-run-test-jvm-data-shard93-tmp",
+        ":art-run-test-jvm-data-shard94-tmp",
+        ":art-run-test-jvm-data-shard95-tmp",
+        ":art-run-test-jvm-data-shard96-tmp",
+        ":art-run-test-jvm-data-shard97-tmp",
+        ":art-run-test-jvm-data-shard98-tmp",
+        ":art-run-test-jvm-data-shard99-tmp",
+        ":art-run-test-jvm-data-shardHiddenApi-tmp",
+    ],
+    cmd: "echo $(in) > $(out)",
+}
+
+// Phony target used to install all shards
+prebuilt_etc_host {
+    name: "art-run-test-jvm-data",
+    defaults: ["art_module_source_build_prebuilt_defaults"],
+    src: ":art-run-test-jvm-data-tmp",
+    required: [
+        "art-run-test-jvm-data-shard00",
+        "art-run-test-jvm-data-shard01",
+        "art-run-test-jvm-data-shard02",
+        "art-run-test-jvm-data-shard03",
+        "art-run-test-jvm-data-shard04",
+        "art-run-test-jvm-data-shard05",
+        "art-run-test-jvm-data-shard06",
+        "art-run-test-jvm-data-shard07",
+        "art-run-test-jvm-data-shard08",
+        "art-run-test-jvm-data-shard09",
+        "art-run-test-jvm-data-shard10",
+        "art-run-test-jvm-data-shard11",
+        "art-run-test-jvm-data-shard12",
+        "art-run-test-jvm-data-shard13",
+        "art-run-test-jvm-data-shard14",
+        "art-run-test-jvm-data-shard15",
+        "art-run-test-jvm-data-shard16",
+        "art-run-test-jvm-data-shard17",
+        "art-run-test-jvm-data-shard18",
+        "art-run-test-jvm-data-shard19",
+        "art-run-test-jvm-data-shard20",
+        "art-run-test-jvm-data-shard21",
+        "art-run-test-jvm-data-shard22",
+        "art-run-test-jvm-data-shard23",
+        "art-run-test-jvm-data-shard24",
+        "art-run-test-jvm-data-shard25",
+        "art-run-test-jvm-data-shard26",
+        "art-run-test-jvm-data-shard27",
+        "art-run-test-jvm-data-shard28",
+        "art-run-test-jvm-data-shard29",
+        "art-run-test-jvm-data-shard30",
+        "art-run-test-jvm-data-shard31",
+        "art-run-test-jvm-data-shard32",
+        "art-run-test-jvm-data-shard33",
+        "art-run-test-jvm-data-shard34",
+        "art-run-test-jvm-data-shard35",
+        "art-run-test-jvm-data-shard36",
+        "art-run-test-jvm-data-shard37",
+        "art-run-test-jvm-data-shard38",
+        "art-run-test-jvm-data-shard39",
+        "art-run-test-jvm-data-shard40",
+        "art-run-test-jvm-data-shard41",
+        "art-run-test-jvm-data-shard42",
+        "art-run-test-jvm-data-shard43",
+        "art-run-test-jvm-data-shard44",
+        "art-run-test-jvm-data-shard45",
+        "art-run-test-jvm-data-shard46",
+        "art-run-test-jvm-data-shard47",
+        "art-run-test-jvm-data-shard48",
+        "art-run-test-jvm-data-shard49",
+        "art-run-test-jvm-data-shard50",
+        "art-run-test-jvm-data-shard51",
+        "art-run-test-jvm-data-shard52",
+        "art-run-test-jvm-data-shard53",
+        "art-run-test-jvm-data-shard54",
+        "art-run-test-jvm-data-shard55",
+        "art-run-test-jvm-data-shard56",
+        "art-run-test-jvm-data-shard57",
+        "art-run-test-jvm-data-shard58",
+        "art-run-test-jvm-data-shard59",
+        "art-run-test-jvm-data-shard60",
+        "art-run-test-jvm-data-shard61",
+        "art-run-test-jvm-data-shard62",
+        "art-run-test-jvm-data-shard63",
+        "art-run-test-jvm-data-shard64",
+        "art-run-test-jvm-data-shard65",
+        "art-run-test-jvm-data-shard66",
+        "art-run-test-jvm-data-shard67",
+        "art-run-test-jvm-data-shard68",
+        "art-run-test-jvm-data-shard69",
+        "art-run-test-jvm-data-shard70",
+        "art-run-test-jvm-data-shard71",
+        "art-run-test-jvm-data-shard72",
+        "art-run-test-jvm-data-shard73",
+        "art-run-test-jvm-data-shard74",
+        "art-run-test-jvm-data-shard75",
+        "art-run-test-jvm-data-shard76",
+        "art-run-test-jvm-data-shard77",
+        "art-run-test-jvm-data-shard78",
+        "art-run-test-jvm-data-shard79",
+        "art-run-test-jvm-data-shard80",
+        "art-run-test-jvm-data-shard81",
+        "art-run-test-jvm-data-shard82",
+        "art-run-test-jvm-data-shard83",
+        "art-run-test-jvm-data-shard84",
+        "art-run-test-jvm-data-shard85",
+        "art-run-test-jvm-data-shard86",
+        "art-run-test-jvm-data-shard87",
+        "art-run-test-jvm-data-shard88",
+        "art-run-test-jvm-data-shard89",
+        "art-run-test-jvm-data-shard90",
+        "art-run-test-jvm-data-shard91",
+        "art-run-test-jvm-data-shard92",
+        "art-run-test-jvm-data-shard93",
+        "art-run-test-jvm-data-shard94",
+        "art-run-test-jvm-data-shard95",
+        "art-run-test-jvm-data-shard96",
+        "art-run-test-jvm-data-shard97",
+        "art-run-test-jvm-data-shard98",
+        "art-run-test-jvm-data-shard99",
+        "art-run-test-jvm-data-shardHiddenApi",
+    ],
+    sub_dir: "art",
+    filename: "art-run-test-jvm-data.txt",
+}
diff --git a/test/Android.run-test.bp.py b/test/Android.run-test.bp.py
index a051a1d..d1e3027 100755
--- a/test/Android.run-test.bp.py
+++ b/test/Android.run-test.bp.py
@@ -35,27 +35,140 @@
         names.append(name)
         f.write(textwrap.dedent("""
           java_genrule {{
-              name: "{name}",
+              name: "{name}-tmp",
               out: ["{name}.zip"],
-              srcs: ["*{shard}-*/**/*"],
-              defaults: ["art-run-test-data-defaults"],
-              cmd: "$(location run-test-build.py) --out $(out) --mode {mode} --shard {shard} " +
-                  "--bootclasspath $(location :art-run-test-bootclasspath)",
+              srcs: ["?{shard}-*/**/*", "??{shard}-*/**/*"],
+              defaults: ["art-run-test-{mode}-data-defaults"],
+          }}
+
+          // Install in the output directory to make it accessible for tests.
+          prebuilt_etc_host {{
+              name: "{name}",
+              defaults: ["art_module_source_build_prebuilt_defaults"],
+              src: ":{name}-tmp",
+              sub_dir: "art",
+              filename: "{name}.zip",
           }}
           """.format(name=name, mode=mode, shard=shard)))
-      srcs = ("\n"+" "*8).join('":{}",'.format(n) for n in names)
+
+      # Build all hiddenapi tests in their own shard.
+      # This removes the dependency on hiddenapi from all other shards,
+      # which in turn removes dependency on ART C++ source code.
+      name = "art-run-test-{mode}-data-shardHiddenApi".format(mode=mode)
+      names.append(name)
       f.write(textwrap.dedent("""
         java_genrule {{
-            name: "art-run-test-{mode}-data-merged",
-            defaults: ["art-run-test-data-defaults"],
-            out: ["art-run-test-{mode}-data-merged.zip"],
+            name: "{name}-tmp",
+            out: ["{name}.zip"],
+            srcs: ["???-*hiddenapi*/**/*", "????-*hiddenapi*/**/*"],
+            defaults: ["art-run-test-{mode}-data-defaults"],
+            tools: ["hiddenapi"],
+            cmd: "$(location run_test_build.py) --out $(out) --mode {mode} " +
+                 "--bootclasspath $(location :art-run-test-bootclasspath) " +
+                 "--d8 $(location d8) " +
+                 "--hiddenapi $(location hiddenapi) " +
+                 "--jasmin $(location jasmin) " +
+                 "--smali $(location smali) " +
+                 "--soong_zip $(location soong_zip) " +
+                 "--zipalign $(location zipalign) " +
+                 "$(in)",
+        }}
+
+        // Install in the output directory to make it accessible for tests.
+        prebuilt_etc_host {{
+            name: "{name}",
+            defaults: ["art_module_source_build_prebuilt_defaults"],
+            src: ":{name}-tmp",
+            sub_dir: "art",
+            filename: "{name}.zip",
+        }}
+        """.format(name=name, mode=mode)))
+
+      f.write(textwrap.dedent("""
+        genrule_defaults {{
+            name: "art-run-test-{mode}-data-defaults",
+            defaults: [
+                // Enable only in source builds, where com.android.art.testing is
+                // available.
+                "art_module_source_build_genrule_defaults",
+            ],
+            tool_files: [
+                "run_test_build.py",
+                ":art-run-test-bootclasspath",
+            ],
+            tools: [
+                "d8",
+                "jasmin",
+                "smali",
+                "soong_zip",
+                "zipalign",
+            ],
+            cmd: "$(location run_test_build.py) --out $(out) --mode {mode} " +
+                 "--bootclasspath $(location :art-run-test-bootclasspath) " +
+                 "--d8 $(location d8) " +
+                 "--jasmin $(location jasmin) " +
+                 "--smali $(location smali) " +
+                 "--soong_zip $(location soong_zip) " +
+                 "--zipalign $(location zipalign) " +
+                 "$(in)",
+        }}
+        """).format(mode=mode))
+
+      name = "art-run-test-{mode}-data-merged".format(mode=mode)
+      srcs = ("\n"+" "*8).join('":{}-tmp",'.format(n) for n in names)
+      deps = ("\n"+" "*8).join('"{}",'.format(n) for n in names)
+      f.write(textwrap.dedent("""
+        java_genrule {{
+            name: "{name}-tmp",
+            defaults: ["art_module_source_build_genrule_defaults"],
+            out: ["{name}.zip"],
             srcs: [
                 {srcs}
             ],
             tools: ["merge_zips"],
             cmd: "$(location merge_zips) $(out) $(in)",
         }}
-        """).format(mode=mode, srcs=srcs))
+
+        // Install in the output directory to make it accessible for tests.
+        prebuilt_etc_host {{
+            name: "{name}",
+            defaults: ["art_module_source_build_prebuilt_defaults"],
+            src: ":{name}-tmp",
+            required: [
+                {deps}
+            ],
+            sub_dir: "art",
+            filename: "{name}.zip",
+        }}
+        """).format(name=name, srcs=srcs, deps=deps))
+
+      name = "art-run-test-{mode}-data".format(mode=mode)
+      srcs = ("\n"+" "*8).join('":{}-tmp",'.format(n) for n in names)
+      deps = ("\n"+" "*8).join('"{}",'.format(n) for n in names)
+      f.write(textwrap.dedent("""
+        // Phony target used to build all shards
+        java_genrule {{
+            name: "{name}-tmp",
+            defaults: ["art-run-test-data-defaults"],
+            out: ["{name}.txt"],
+            srcs: [
+                {srcs}
+            ],
+            cmd: "echo $(in) > $(out)",
+        }}
+
+        // Phony target used to install all shards
+        prebuilt_etc_host {{
+            name: "{name}",
+            defaults: ["art_module_source_build_prebuilt_defaults"],
+            src: ":{name}-tmp",
+            required: [
+                {deps}
+            ],
+            sub_dir: "art",
+            filename: "{name}.txt",
+        }}
+        """).format(name=name, srcs=srcs, deps=deps))
 
 if __name__ == "__main__":
   main()
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index ea305ce..9dec0c5 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -48,14 +48,10 @@
 # Also need signal_dumper.
 ART_TEST_TARGET_RUN_TEST_DEPENDENCIES += signal_dumper-target
 
-ART_TEST_TARGET_RUN_TEST_DEPENDENCIES += art-run-test-target-data
-
 # All tests require the host executables. The tests also depend on the core images, but on
 # specific version depending on the compiler.
 ART_TEST_HOST_RUN_TEST_DEPENDENCIES := \
   $(ART_HOST_EXECUTABLES) \
-  art-run-test-host-data \
-  art-run-test-jvm-data \
   $(HOST_OUT_EXECUTABLES)/hprof-conv \
   $(HOST_OUT_EXECUTABLES)/signal_dumper \
   $(ART_TEST_LIST_host_$(ART_HOST_ARCH)_libtiagent) \
@@ -104,6 +100,9 @@
 .PHONY: test-art-host-run-test-dependencies
 test-art-run-test-dependencies : test-art-host-run-test-dependencies
 
+test-art-jvm-run-test-dependencies : test-art-host-run-test-dependencies
+.PHONY: test-art-jvm-run-test-dependencies
+
 test-art-target-run-test-dependencies :
 .PHONY: test-art-target-run-test-dependencies
 test-art-run-test-dependencies : test-art-target-run-test-dependencies
diff --git a/test/ArrayClassWithUnresolvedComponent/ClassWithMissingInterface.smali b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingInterface.smali
new file mode 100644
index 0000000..f68d70c
--- /dev/null
+++ b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingInterface.smali
@@ -0,0 +1,18 @@
+# Copyright (C) 2022 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 LClassWithMissingInterface;
+
+.super Ljava/lang/Object;
+.implements LMissingInterface;
diff --git a/test/ArrayClassWithUnresolvedComponent/ClassWithMissingSuper.smali b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingSuper.smali
new file mode 100644
index 0000000..134302c
--- /dev/null
+++ b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingSuper.smali
@@ -0,0 +1,17 @@
+# Copyright (C) 2022 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 LClassWithMissingSuper;
+
+.super LMissingClass;
diff --git a/test/ArrayClassWithUnresolvedComponent/ClassWithStatic.smali b/test/ArrayClassWithUnresolvedComponent/ClassWithStatic.smali
new file mode 100644
index 0000000..f35e5f0
--- /dev/null
+++ b/test/ArrayClassWithUnresolvedComponent/ClassWithStatic.smali
@@ -0,0 +1,28 @@
+# Copyright (C) 2022 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 LClassWithStatic;
+
+.super Ljava/lang/Object;
+
+.method static constructor <clinit>()V
+    .registers 1
+    const-string v0, "[LClassWithMissingInterface;"
+    invoke-static {v0}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;
+    move-result-object v0
+    sput-object v0, LClassWithStatic;->field:Ljava/lang/Class;
+    return-void
+.end method
+
+.field public static field:Ljava/lang/Class;
diff --git a/test/ArrayClassWithUnresolvedComponent/ClassWithStaticConst.smali b/test/ArrayClassWithUnresolvedComponent/ClassWithStaticConst.smali
new file mode 100644
index 0000000..68aecfb
--- /dev/null
+++ b/test/ArrayClassWithUnresolvedComponent/ClassWithStaticConst.smali
@@ -0,0 +1,26 @@
+# Copyright (C) 2022 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 LClassWithStaticConst;
+
+.super Ljava/lang/Object;
+
+.method static constructor <clinit>()V
+    .registers 1
+    const-class v0, [LClassWithMissingInterface;
+    sput-object v0, LClassWithStaticConst;->field:Ljava/lang/Class;
+    return-void
+.end method
+
+.field public static field:Ljava/lang/Class;
diff --git a/test/Dex2oatVdexTestDex/Dex2oatVdexTestDex.java b/test/Dex2oatVdexTestDex/Dex2oatVdexTestDex.java
index e4862bd..ef6b4ef 100644
--- a/test/Dex2oatVdexTestDex/Dex2oatVdexTestDex.java
+++ b/test/Dex2oatVdexTestDex/Dex2oatVdexTestDex.java
@@ -30,6 +30,7 @@
   }
 }
 
+@SuppressWarnings("LockOnBoxedPrimitive")
 class AccessPublicMethodFromParent {
   public void foo(Integer i) {
     i.notify();
@@ -64,6 +65,7 @@
   }
 }
 
+@SuppressWarnings("LockOnBoxedPrimitive")
 class AccessNonPublicMethodFromParent {
   public void foo(Integer i) {
     i.notifyAll();
diff --git a/test/README.arm_fvp.md b/test/README.arm_fvp.md
index 47ef715..5fc3c1b 100644
--- a/test/README.arm_fvp.md
+++ b/test/README.arm_fvp.md
@@ -1,165 +1,65 @@
-# Build and Run ART tests on ARM FVP
+# Testing ART on a model (QEMU or Arm FVP)
 
-This document describes how to build and run an Android system image targeting
-the ARM Fixed Virtual Platform and to use it as a target platform for running
-ART tests via ADB.
+This document describes how to test ART on a model - QEMU or the ARM Fixed Virtual Platform.
 
-This instruction was checked to be working for the AOSP master tree on
-2021-01-13; the up-to-date instruction on how to build the kernel and firmware
-could be found here: device/generic/goldfish/fvpbase/README.md.
+It covers steps on how to build and run an Android system image targeting a model
+and to use it as a target platform for running ART tests via ADB in chroot mode. The guide
+covers both QEMU and the ARM Fixed Virtual Platform; the setup is very similar.
 
-## Configuring and Building AOSP
+More information on QEMU and Arm FVP could be found in
+{AOSP}/device/generic/goldfish/fvpbase/README.md.
 
-First, an AOSP image should be configured and built, including the kernel and
-firmware.
+One would need two AOSP trees for this setup:
+ - a full stable (tagged) tree - to be used to build AOSP image for the model.
+   - android-13.0.0_r12 was tested successfully to run QEMU:
+     ```repo init  -u https://android.googlesource.com/platform/manifest -b android-13.0.0_r12```
+ - a full or minimal tree - the one to be tested as part of ART test run.
 
-### Generating build system configs
+## Setting up the QEMU/Arm FVP
+
+Once a full AOSP tree is downloaded, please follow the instructions in
+${AOSP}/device/generic/goldfish/fvpbase/README.md; they should cover:
+ - fetching, configuring and building the model.
+ - building AOSP image for it.
+ - launching the model.
+
+Once the model is started and reachable via adb, ART tests could be run.
+
+Notes:
+ - fvp_mini lunch target should be used as we don't need graphics to run ART tests.
+ - 'Running the image in QEMU' mentions that a special commit should be checked out for QEMU
+   for GUI runs. Actually it is recommended to use it even for non-GUI runs (fvp_mini).
+
+### Running the Arm FVP with SVE enabled
+
+To test SVE on Arm FVP, one extra step is needed when following the instructions above;
+for QEMU run this is not needed. When launching the model some extra cmdline options should
+be provided for 'run_model':
 
 ```
-cd $AOSP
-
-. build/envsetup.sh
-# fvp_mini target is used as we don't need a GUI for ART tests.
-lunch fvp_mini-eng
-
-# This is expected to fail; it generates all the build rules files.
-m
-```
-
-### Building the kernel
-
-```
-cd $SOME_DIRECTORY_OUTSIDE_AOSP
-
-mkdir android-kernel-mainline
-cd android-kernel-mainline
-repo init -u https://android.googlesource.com/kernel/manifest -b common-android-mainline
-repo sync
-BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
-BUILD_CONFIG=common-modules/virtual-device/build.config.fvp build/build.sh
-```
-
-The resulting kernel image and DTB (Device Tree Binary) must then be copied into
-the product output directory:
-
-```
-cp out/android-mainline/dist/Image $ANDROID_PRODUCT_OUT/kernel
-cp out/android-mainline/dist/fvp-base-revc.dtb out/android-mainline/dist/initramfs.img $ANDROID_PRODUCT_OUT/
-```
-
-### Building the firmware (ARM Trusted Firmware and U-Boot)
-
-First, install ``dtc``, the device tree compiler. On Debian, this is in the
-``device-tree-compiler`` package.
-
-```
-sudo apt-get install device-tree-compiler
-```
-
-Then run:
-
-```
-mkdir platform
-cd platform
-repo init -u https://git.linaro.org/landing-teams/working/arm/manifest.git -m pinned-uboot.xml -b 20.01
-repo sync
-
-# The included copy of U-Boot is incompatible with this version of AOSP, switch to a recent upstream checkout.
-cd u-boot
-git fetch https://gitlab.denx.de/u-boot/u-boot.git/ master
-git checkout 18b9c98024ec89e00a57707f07ff6ada06089d26
-cd ..
-
-mkdir -p tools/gcc
-cd tools/gcc
-wget https://releases.linaro.org/components/toolchain/binaries/6.2-2016.11/aarch64-linux-gnu/gcc-linaro-6.2.1-2016.11-x86_64_aarch64-linux-gnu.tar.xz
-tar -xJf gcc-linaro-6.2.1-2016.11-x86_64_aarch64-linux-gnu.tar.xz
-cd ../..
-
-build-scripts/build-test-uboot.sh -p fvp all
-```
-
-These components must then be copied into the product output directory:
-
-```
-cp output/fvp/fvp-uboot/uboot/{bl1,fip}.bin $ANDROID_PRODUCT_OUT/
-```
-
-## Setting up the FVP model
-
-### Obtaining the model
-
-The public Arm FVP could be obtained from https://developer.arm.com/; one would
-need to create an account there and accept EULA to download and install it.
-A link for the latest version:
-
-https://developer.arm.com/tools-and-software/simulation-models/fixed-virtual-platforms/arm-ecosystem-models: "Armv8-A Base RevC AEM FVP"
-
-The AEMv8-A Base Platform FVP is a free of charge Fixed Virtual Platform of the
-latest Arm v8-A architecture features and has been validated with compatible
-Open Source software, which can be found on the reference open source software
-stacks page along with instructions for running the software
-
-### Running the model
-
-From a lunched environment:
-
-```
-export MODEL_PATH=/path/to/model/dir
-export MODEL_BIN=${MODEL_PATH}/models/Linux64_GCC-6.4/FVP_Base_RevC-2xAEMv8A
-./device/generic/goldfish/fvpbase/run_model
-```
-
-If any extra parameters are needed for the model (e.g. specifying plugins) they
-should be specified as cmdline options for 'run_model'. E.g. to run a model
-which support SVE:
-
-```
-export SVE_PLUGIN=${MODEL_PATH}/plugins/Linux64_GCC-6.4/ScalableVectorExtension.so
+export SVE_PLUGIN=${MODEL_PATH}/plugins/<os_and_toolchain>/ScalableVectorExtension.so
 $ ./device/generic/goldfish/fvpbase/run_model --plugin ${SVE_PLUGIN} -C SVE.ScalableVectorExtension.veclen=2
 ```
 
 Note: SVE vector length is passed in units of 64-bit blocks. So "2" would stand
 for 128-bit vector length.
 
-The model will start and will have fully booted to shell in around 20 minutes
-(you will see "sys.boot_completed=1" in the log). It can be accessed as a
-regular device with adb:
+## Running ART test
 
-```
-adb connect localhost:5555
-```
+QEMU/FVP behaves as a regular adb device so running ART tests is possible using
+the standard chroot method described in test/README.chroot.md with an additional step,
+described below. A separate AOSP tree (not the one used for the model itself), should
+be used - full or minimal.
 
-To terminate the model, press ``Ctrl-] Ctrl-D`` to terminate the telnet
-connection.
-
-## Running ART test on FVP
-
-The model behaves as a regular adb device so running ART tests could be done using
-the standard chroot method described in test/README.chroot.md; the steps are
-also described below. A separate AOSP tree (not the one used for the model
-itself), should be used - full or minimal.
-
-Then the regular ART testing routine could be performed; the regular "lunch"
+Then the regular ART testing routine should be performed; the regular "lunch"
 target ("armv8" and other targets, not "fvp-eng").
 
-
 ```
-export ART_TEST_CHROOT=/data/local/art-test-chroot
-export OVERRIDE_TARGET_FLATTEN_APEX=true
-export SOONG_ALLOW_MISSING_DEPENDENCIES=true
-export TARGET_BUILD_UNBUNDLED=true
+# Config the test run for QEMU/FVP.
 export ART_TEST_RUN_ON_ARM_FVP=true
 
-. ./build/envsetup.sh
-lunch armv8-userdebug
-art/tools/buildbot-build.sh --target
-
-art/tools/buildbot-teardown-device.sh
-art/tools/buildbot-cleanup-device.sh
-art/tools/buildbot-setup-device.sh
-art/tools/buildbot-sync.sh
-
-art/test/testrunner/testrunner.py --target --64 --optimizing -j1
-
+# Build, sync ART tests to the model and run, see test/README.chroot.md.
 ```
+
+Note: ART scripts only support one adb device at a time. If you have other adb devices
+connected, use `export ANDROID_SERIAL=localhost:5555` to run scripts on QEMU/FVP."
diff --git a/test/README.chroot.md b/test/README.chroot.md
index 49b76ba..ce4670a 100644
--- a/test/README.chroot.md
+++ b/test/README.chroot.md
@@ -50,19 +50,62 @@
     ```
 2. Set lunch target and ADB:
     * With a minimal `aosp/master-art` tree:
-        ```bash
-        export SOONG_ALLOW_MISSING_DEPENDENCIES=true
-        . ./build/envsetup.sh
-        lunch armv8-eng  # or arm_krait-eng for 32-bit ARM
-        export PATH="$(pwd)/prebuilts/runtime:$PATH"
-        export ADB="$ANDROID_BUILD_TOP/prebuilts/runtime/adb"
-        ```
+        1. Initialize the environment:
+            ```bash
+            export SOONG_ALLOW_MISSING_DEPENDENCIES=true
+            export BUILD_BROKEN_DISABLE_BAZEL=true
+            . ./build/envsetup.sh
+            ```
+        2. Select a lunch target corresponding to the architecture you want to
+           build and test:
+            * For (32-bit) Arm:
+                ```bash
+                lunch arm_krait-eng
+                ```
+            * For (64-bit only) Arm64:
+                ```bash
+                lunch armv8-eng
+                ```
+            * For (32- and 64-bit) Arm64:
+                ```bash
+                lunch arm_v7_v8-eng
+                ```
+            * For (32-bit) Intel x86:
+                ```bash
+                lunch silvermont-eng
+                ```
+        3. Set up the environment to use a pre-built ADB:
+            ```bash
+            export PATH="$(pwd)/prebuilts/runtime:$PATH"
+            export ADB="$ANDROID_BUILD_TOP/prebuilts/runtime/adb"
+            ```
     * With a full Android (AOSP) `aosp/master` tree:
-        ```bash
-        . ./build/envsetup.sh
-        lunch aosp_arm64-eng  # or aosp_arm-eng for 32-bit ARM
-        m adb
-        ```
+        1. Initialize the environment:
+            ```bash
+            . ./build/envsetup.sh
+            ```
+        2. Select a lunch target corresponding to the architecture you want to
+           build and test:
+            * For (32-bit) Arm:
+                ```bash
+                lunch aosp_arm-eng
+                ```
+            * For (32- and 64-bit) Arm64:
+                ```bash
+                lunch aosp_arm64-eng
+                ```
+            * For (32-bit) Intel x86:
+                ```bash
+                lunch aosp_x86-eng
+                ```
+            * For (32- and 64-bit) Intel x86-64:
+                ```bash
+                lunch aosp_x86_64-eng
+                ```
+        3. Build ADB:
+            ```bash
+            m adb
+            ```
 3. Build ART and required dependencies:
     ```bash
     art/tools/buildbot-build.sh --target
diff --git a/test/README.chroot_vm.md b/test/README.chroot_vm.md
new file mode 100644
index 0000000..2f163de
--- /dev/null
+++ b/test/README.chroot_vm.md
@@ -0,0 +1,74 @@
+# ART chroot-based testing on a Linux VM
+
+This doc describes how to set up a Linux VM and how to run ART tests on it.
+
+## Set up the VM
+
+Use script art/build/buildbot-vm.sh. It has various commands (actions) described
+below. First, set up some environment variables used by the script (change as
+you see fit):
+```
+export ART_TEST_SSH_USER=ubuntu
+export ART_TEST_SSH_HOST=localhost
+export ART_TEST_SSH_PORT=10001
+```
+Create the VM (download it and do some initial setup):
+```
+art/tools/buildbot-vm.sh create
+```
+Boot the VM (login is `$ART_TEST_SSH_USER`, password is `ubuntu`):
+```
+art/tools/buildbot-vm.sh boot
+```
+Configure SSH (enter `yes` to add VM to `known_hosts` and then the password):
+```
+art/tools/buildbot-vm.sh setup-ssh
+```
+Now you have the shell (no need to enter password every time):
+```
+art/tools/buildbot-vm.sh connect
+```
+To power off the VM, do:
+```
+art/tools/buildbot-vm.sh quit
+```
+To speed up SSH access, set `UseDNS no` in /etc/ssh/sshd_config on the VM (and
+apply other tweaks described in https://jrs-s.net/2017/07/01/slow-ssh-logins).
+
+# Run ART tests
+```
+This is done in the same way as you would run tests in chroot on device (except
+for a few extra environment variables):
+
+export ANDROID_SERIAL=nonexistent
+export ART_TEST_SSH_USER=ubuntu
+export ART_TEST_SSH_HOST=localhost
+export ART_TEST_SSH_PORT=10001
+export ART_TEST_ON_VM=true
+
+. ./build/envsetup.sh
+lunch armv8-eng  # or aosp_riscv64-userdebug, etc.
+art/tools/buildbot-build.sh --target # --installclean
+
+art/tools/buildbot-cleanup-device.sh
+
+# The following two steps can be skipped for faster iteration, but it doesn't
+# always track and update dependencies correctly (e.g. if only an assembly file
+# has been modified).
+art/tools/buildbot-setup-device.sh
+art/tools/buildbot-sync.sh
+
+art/test/run-test --chroot $ART_TEST_CHROOT --64 --interpreter -O 001-HelloWorld
+art/test.py --target -r --ndebug --no-image --64 --interpreter  # specify tests
+art/tools/run-gtests.sh
+
+art/tools/buildbot-cleanup-device.sh
+```
+Both test.py and run-test scripts can be used. Tweak options as necessary.
+
+# Limitations
+
+Limitations are mostly related to the absence of system properties on the Linux.
+They are not really needed for ART tests, but they are used for test-related
+things, e.g. to find out if the tests should run in debug configuration (option
+`ro.debuggable`). Therefore debug configuration is currently broken.
diff --git a/test/README.md b/test/README.md
index da9a1f1..dc9e9b7 100644
--- a/test/README.md
+++ b/test/README.md
@@ -201,6 +201,17 @@
 ## Running one gtest on the build host
 
 ```sh
+$ m test-art-host-gtest-art_runtime_tests
+```
+
+Note: Although this is a build command, it actually builds the test with
+dependencies and runs the test.
+
+If you want to run the test with more options, use the following commands
+instead. Note that you need to run the test with the command above at least once
+before you run the commands below.
+
+```sh
 $ find out/host/ -type f -name art_runtime_tests  # Find the path of the test.
 $ out/host/linux-x86/nativetest/art_runtime_tests/art_runtime_tests
 ```
diff --git a/test/SuperWithAccessChecks/ImplementsClass.smali b/test/SuperWithAccessChecks/ImplementsClass.smali
new file mode 100644
index 0000000..e39877f
--- /dev/null
+++ b/test/SuperWithAccessChecks/ImplementsClass.smali
@@ -0,0 +1,18 @@
+# Copyright (C) 2022 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 LImplementsClass;
+
+.super Ljava/lang/Object;
+.implements LItf;
diff --git a/test/SuperWithAccessChecks/Itf.smali b/test/SuperWithAccessChecks/Itf.smali
new file mode 100644
index 0000000..c91686e
--- /dev/null
+++ b/test/SuperWithAccessChecks/Itf.smali
@@ -0,0 +1,23 @@
+# Copyright (C) 2022 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 LItf;
+
+.super Ljava/lang/Object;
+
+.method public foo()V
+    .registers 1
+    invoke-static {}, LMissingClass;->forName()V
+    return-void
+.end method
diff --git a/test/SuperWithAccessChecks/SubClass.smali b/test/SuperWithAccessChecks/SubClass.smali
new file mode 100644
index 0000000..21c8f1c
--- /dev/null
+++ b/test/SuperWithAccessChecks/SubClass.smali
@@ -0,0 +1,17 @@
+# Copyright (C) 2022 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 LSubClass;
+
+.super LSuperClass;
diff --git a/test/SuperWithAccessChecks/SuperClass.smali b/test/SuperWithAccessChecks/SuperClass.smali
new file mode 100644
index 0000000..8e12521
--- /dev/null
+++ b/test/SuperWithAccessChecks/SuperClass.smali
@@ -0,0 +1,24 @@
+# Copyright (C) 2022 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 LSuperClass;
+
+.super Ljava/lang/Object;
+
+.method static foo()V
+    .registers 0
+    invoke-static {}, LMissingClass;->forName()V
+    return-void
+.end method
+
diff --git a/test/art-gtests-target-chroot.xml b/test/art-gtests-target-chroot.xml
index 5fd76f8..88c45b7 100644
--- a/test/art-gtests-target-chroot.xml
+++ b/test/art-gtests-target-chroot.xml
@@ -37,7 +37,7 @@
 
     <test class="com.android.tradefed.testtype.ArtGTest" >
         <!-- TODO(b/147821328): These tests do not work since they need to write to /system -->
-        <option name="exclude-filter" value="HiddenApiTest.DexDomain_System*:OatFileAssistantTest.SystemFrameworkDir" />
+        <option name="exclude-filter" value="HiddenApiTest.DexDomain_System*:OatFileAssistantBaseTest.SystemFrameworkDir" />
         <option name="native-test-timeout" value="600000" />
         <option name="native-test-device-path" value="/data/local/tmp/art-test-chroot/apex/com.android.art/bin/art" />
     </test>
diff --git a/test/art-gtests-target-install-apex.xml b/test/art-gtests-target-install-apex.xml
index 240b441..b030f26 100644
--- a/test/art-gtests-target-install-apex.xml
+++ b/test/art-gtests-target-install-apex.xml
@@ -23,7 +23,7 @@
 
     <test class="com.android.tradefed.testtype.GTest" >
         <!-- TODO(b/147821328): These tests do not work since they need to write to /system -->
-        <option name="exclude-filter" value="HiddenApiTest.DexDomain_System*:OatFileAssistantTest.SystemFrameworkDir" />
+        <option name="exclude-filter" value="HiddenApiTest.DexDomain_System*:OatFileAssistantBaseTest.SystemFrameworkDir" />
         <option name="native-test-timeout" value="600000" /> <!-- 10 min -->
         <option name="native-test-device-path" value="/apex/com.android.art/bin/art" />
     </test>
diff --git a/test/art-gtests-target-standalone-template.xml b/test/art-gtests-target-standalone-template.xml
index cc2f76e..194c1e3 100644
--- a/test/art-gtests-target-standalone-template.xml
+++ b/test/art-gtests-target-standalone-template.xml
@@ -15,6 +15,8 @@
 -->
 <!-- Note: This test config file for {MODULE} is generated from a template. -->
 <configuration description="Runs {MODULE}.">
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
+
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}/{MODULE}" />
diff --git a/test/buildfailures.json b/test/buildfailures.json
deleted file mode 100644
index c926298..0000000
--- a/test/buildfailures.json
+++ /dev/null
@@ -1,31 +0,0 @@
-[
-    {
-        "description": ["The following tests don't build on RI."],
-        "tests": [
-            "004-UnsafeTest",
-            "030-bad-finalizer",
-            "122-npe",
-            "160-read-barrier-stress",
-            "178-app-image-native-method",
-            "612-jit-dex-cache",
-            "613-inlining-dex-cache",
-            "674-hiddenapi",
-            "676-resolve-field-type",
-            "689-zygote-jit-deopt",
-            "692-vdex-inmem-loader",
-            "692-vdex-secondary-loader",
-            "693-vdex-inmem-loader-evict",
-            "811-checker-invoke-super-secondary",
-            "817-hiddenapi",
-            "831-unverified-bcp",
-            "1336-short-finalizer-timeout",
-            "1339-dead-reference-safe",
-            "2005-pause-all-redefine-multithreaded",
-            "2040-huge-native-alloc",
-            "2041-bad-cleaner",
-            "2235-JdkUnsafeTest",
-            "2239-varhandle-perf"
-        ],
-        "variant": "jvm"
-    }
-]
diff --git a/test/common/gtest_main.cc b/test/common/gtest_main.cc
index 95dadcf..e491ffc 100644
--- a/test/common/gtest_main.cc
+++ b/test/common/gtest_main.cc
@@ -38,12 +38,6 @@
 extern "C" __attribute__((visibility("default"))) __attribute__((weak)) void ArtTestGlobalInit();
 
 int main(int argc, char** argv, char** envp) {
-  // Gtests can be very noisy. For example, an executable with multiple tests will trigger native
-  // bridge warnings. The following line reduces the minimum log severity to ERROR and suppresses
-  // everything else. In case you want to see all messages, comment out the line.
-  const char* log_tag_override = getenv("ART_GTEST_OVERRIDE_LOG_TAGS");
-  setenv("ANDROID_LOG_TAGS", log_tag_override == nullptr ? "*:e" : log_tag_override, 1);
-
   art::Locks::Init();
   art::InitLogging(argv, art::Runtime::Abort);
   art::MemMap::Init();
diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc
index 0b72612..46f3828 100644
--- a/test/common/runtime_state.cc
+++ b/test/common/runtime_state.cc
@@ -18,6 +18,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/macros.h>
+#include <sys/resource.h>
 
 #include "art_field.h"
 #include "art_method-inl.h"
@@ -234,7 +235,7 @@
 
   {
     ScopedObjectAccess soa(self);
-    if (Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method)) {
+    if (Runtime::Current()->GetInstrumentation()->IsDeoptimized(method)) {
       std::string msg(method->PrettyMethod());
       msg += ": is not safe to jit!";
       ThrowIllegalStateException(msg.c_str());
@@ -276,7 +277,20 @@
     // this before checking if we will execute JIT code in case the request
     // is for an 'optimized' compilation.
     jit->CompileMethod(method, self, kind, /*prejit=*/ false);
-  } while (!code_cache->ContainsPc(method->GetEntryPointFromQuickCompiledCode()));
+    const void* entry_point = method->GetEntryPointFromQuickCompiledCode();
+    if (code_cache->ContainsPc(entry_point)) {
+      // If we're running baseline or not requesting optimized, we're good to go.
+      if (jit->GetJitCompiler()->IsBaselineCompiler() || kind != CompilationKind::kOptimized) {
+        break;
+      }
+      // If we're requesting optimized, check that we did get the method
+      // compiled optimized.
+      OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromEntryPoint(entry_point);
+      if (!CodeInfo::IsBaseline(method_header->GetOptimizedCodeInfoPtr())) {
+        break;
+      }
+    }
+  } while (true);
 }
 
 extern "C" JNIEXPORT void JNICALL Java_Main_ensureMethodJitCompiled(JNIEnv*, jclass, jobject meth) {
@@ -438,4 +452,21 @@
   return soa.Decode<mirror::Class>(c)->IsObsoleteObject();
 }
 
+extern "C" JNIEXPORT void JNICALL Java_Main_forceInterpreterOnThread(JNIEnv* env,
+                                                                     jclass cls ATTRIBUTE_UNUSED) {
+  ScopedObjectAccess soa(env);
+  MutexLock thread_list_mu(soa.Self(), *Locks::thread_list_lock_);
+  soa.Self()->IncrementForceInterpreterCount();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_setAsyncExceptionsThrown(JNIEnv* env ATTRIBUTE_UNUSED,
+                                                                     jclass cls ATTRIBUTE_UNUSED) {
+  Runtime::Current()->SetAsyncExceptionsThrown();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_setRlimitNoFile(JNIEnv*, jclass, jint value) {
+  rlimit limit { static_cast<rlim_t>(value), static_cast<rlim_t>(value) };
+  setrlimit(RLIMIT_NOFILE, &limit);
+}
+
 }  // namespace art
diff --git a/test/common/stack_inspect.cc b/test/common/stack_inspect.cc
index 79c7a36..33f31e2 100644
--- a/test/common/stack_inspect.cc
+++ b/test/common/stack_inspect.cc
@@ -195,7 +195,8 @@
         if (stack_visitor->GetMethod() == nullptr ||
             stack_visitor->GetMethod()->IsNative() ||
             (stack_visitor->GetCurrentShadowFrame() == nullptr &&
-             !Runtime::Current()->IsAsyncDeoptimizeable(stack_visitor->GetCurrentQuickFramePc()))) {
+             !Runtime::Current()->IsAsyncDeoptimizeable(stack_visitor->GetOuterMethod(),
+                                                        stack_visitor->GetCurrentQuickFramePc()))) {
           return true;
         }
         result = soa.AddLocalReference<jobject>(stack_visitor->GetThisObject());
diff --git a/test/csuite-app-compile-launch.xml b/test/csuite-app-compile-launch.xml
index 890c242..4ff7098 100644
--- a/test/csuite-app-compile-launch.xml
+++ b/test/csuite-app-compile-launch.xml
@@ -20,6 +20,9 @@
     </target_preparer>
     <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- We will fail if any command in RunCommandTargetPreparer fails.
+            This is useful to know if we failed to compile a package. -->
+        <option name="throw-if-cmd-fail" value="true"/>
         <option name="run-command" value="cmd package compile -m speed {package}"/>
         <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/>
         <option name="run-command" value="input keyevent KEYCODE_MENU"/>
diff --git a/test/default_run.py b/test/default_run.py
new file mode 100755
index 0000000..e3e5d21
--- /dev/null
+++ b/test/default_run.py
@@ -0,0 +1,1206 @@
+#
+# Copyright (C) 2022 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.
+
+import sys, os, shutil, shlex, re, subprocess, glob
+from argparse import ArgumentParser, BooleanOptionalAction, Namespace
+from os import path
+from os.path import isfile, isdir, basename
+from subprocess import check_output, DEVNULL, PIPE, STDOUT
+from tempfile import NamedTemporaryFile
+from testrunner import env
+from typing import List
+
+COLOR = (os.environ.get("LUCI_CONTEXT") == None)  # Disable colors on LUCI.
+COLOR_BLUE = '\033[94m' if COLOR else ''
+COLOR_GREEN = '\033[92m' if COLOR else ''
+COLOR_NORMAL = '\033[0m' if COLOR else ''
+COLOR_RED = '\033[91m' if COLOR else ''
+
+def parse_args(argv):
+  argp, opt_bool = ArgumentParser(), BooleanOptionalAction
+  argp.add_argument("--64", dest="is64", action="store_true")
+  argp.add_argument("--O", action="store_true")
+  argp.add_argument("--Xcompiler-option", default=[], action="append")
+  argp.add_argument("--add-libdir-argument", action="store_true")
+  argp.add_argument("--android-art-root", default="/apex/com.android.art")
+  argp.add_argument("--android-i18n-root", default="/apex/com.android.i18n")
+  argp.add_argument("--android-log-tags", default="*:i")
+  argp.add_argument("--android-root", default="/system")
+  argp.add_argument("--android-runtime-option", default=[], action="append")
+  argp.add_argument("--android-tzdata-root", default="/apex/com.android.tzdata")
+  argp.add_argument("--app-image", default=True, action=opt_bool)
+  argp.add_argument("--baseline", action="store_true")
+  argp.add_argument("--bionic", action="store_true")
+  argp.add_argument("--boot", default="")
+  argp.add_argument("--chroot", default="")
+  argp.add_argument("--compact-dex-level")
+  argp.add_argument("--compiler-only-option", default=[], action="append")
+  argp.add_argument("--create-runner", action="store_true")
+  argp.add_argument("--diff-min-log-tag", default="E")
+  argp.add_argument("--debug", action="store_true")
+  argp.add_argument("--debug-agent")
+  argp.add_argument("--debug-wrap-agent", action="store_true")
+  argp.add_argument("--dex2oat-dm", action="store_true")
+  argp.add_argument(
+      "--dex2oat-rt-timeout", type=int,
+      default=360)  # The *hard* timeout.  6 min.
+  argp.add_argument(
+      "--dex2oat-timeout", type=int, default=300)  # The "soft" timeout.  5 min.
+  argp.add_argument("--dry-run", action="store_true")
+  argp.add_argument("--experimental", default=[], action="append")
+  argp.add_argument("--external-log-tags", action="store_true")
+  argp.add_argument("--gc-stress", action="store_true")
+  argp.add_argument("--gdb", action="store_true")
+  argp.add_argument("--gdb-arg", default=[], action="append")
+  argp.add_argument("--gdb-dex2oat", action="store_true")
+  argp.add_argument("--gdb-dex2oat-args", default=[], action="append")
+  argp.add_argument("--gdbserver", action="store_true")
+  argp.add_argument("--gdbserver-bin")
+  argp.add_argument("--gdbserver-port", default=":5039")
+  argp.add_argument("--host", action="store_true")
+  argp.add_argument("--image", default=True, action=opt_bool)
+  argp.add_argument("--instruction-set-features", default="")
+  argp.add_argument("--interpreter", action="store_true")
+  argp.add_argument("--invoke-with", default=[], action="append")
+  argp.add_argument("--jit", action="store_true")
+  argp.add_argument("--jvm", action="store_true")
+  argp.add_argument("--jvmti", action="store_true")
+  argp.add_argument("--jvmti-field-stress", action="store_true")
+  argp.add_argument("--jvmti-redefine-stress", action="store_true")
+  argp.add_argument("--jvmti-step-stress", action="store_true")
+  argp.add_argument("--jvmti-trace-stress", action="store_true")
+  argp.add_argument("--lib", default="")
+  argp.add_argument("--optimize", default=True, action=opt_bool)
+  argp.add_argument("--prebuild", default=True, action=opt_bool)
+  argp.add_argument("--profile", action="store_true")
+  argp.add_argument("--random-profile", action="store_true")
+  argp.add_argument("--relocate", default=False, action=opt_bool)
+  argp.add_argument("--runtime-dm", action="store_true")
+  argp.add_argument("--runtime-extracted-zipapex", default="")
+  argp.add_argument("--runtime-option", default=[], action="append")
+  argp.add_argument("--runtime-zipapex", default="")
+  argp.add_argument("--secondary", action="store_true")
+  argp.add_argument("--secondary-app-image", default=True, action=opt_bool)
+  argp.add_argument("--secondary-class-loader-context", default="")
+  argp.add_argument("--secondary-compilation", default=True, action=opt_bool)
+  argp.add_argument("--simpleperf", action="store_true")
+  argp.add_argument("--sync", action="store_true")
+  argp.add_argument("--testlib", default=[], action="append")
+  argp.add_argument("--timeout", default=0, type=int)
+  argp.add_argument("--vdex", action="store_true")
+  argp.add_argument("--vdex-arg", default=[], action="append")
+  argp.add_argument("--vdex-filter", default="")
+  argp.add_argument("--verify", default=True, action=opt_bool)
+  argp.add_argument("--verify-soft-fail", action="store_true")
+  argp.add_argument("--with-agent", default=[], action="append")
+  argp.add_argument("--zygote", action="store_true")
+  argp.add_argument("--test_args", default=[], action="append")
+  argp.add_argument("--stdout_file", default="")
+  argp.add_argument("--stderr_file", default="")
+  argp.add_argument("--main", default="Main")
+  argp.add_argument("--expected_exit_code", default=0)
+
+  # Python parser requires the format --key=--value, since without the equals symbol
+  # it looks like the required value has been omitted and there is just another flag.
+  # For example, '--args --foo --host --64' will become '--arg=--foo --host --64'
+  # because otherwise the --args is missing its value and --foo is unknown argument.
+  for i, arg in reversed(list(enumerate(argv))):
+    if arg in [
+        "--args", "--runtime-option", "--android-runtime-option",
+        "-Xcompiler-option", "--compiler-only-option"
+    ]:
+      argv[i] += "=" + argv.pop(i + 1)
+
+  # Accept single-dash arguments as if they were double-dash arguments.
+  # For exmpample, '-Xcompiler-option' becomes '--Xcompiler-option'
+  # became single-dash can be used only with single-letter arguments.
+  for i, arg in list(enumerate(argv)):
+    if arg.startswith("-") and not arg.startswith("--"):
+      argv[i] = "-" + arg
+    if arg == "--":
+      break
+
+  return argp.parse_args(argv)
+
+def get_target_arch(is64: bool) -> str:
+  # We may build for two arches. Get the one with the expected bitness.
+  arches = [a for a in [env.TARGET_ARCH, env.TARGET_2ND_ARCH] if a]
+  assert len(arches) > 0, "TARGET_ARCH/TARGET_2ND_ARCH not set"
+  if is64:
+    arches = [a for a in arches if a.endswith("64")]
+    assert len(arches) == 1, f"Can not find (unique) 64-bit arch in {arches}"
+  else:
+    arches = [a for a in arches if not a.endswith("64")]
+    assert len(arches) == 1, f"Can not find (unique) 32-bit arch in {arches}"
+  return arches[0]
+
+# Note: This must start with the CORE_IMG_JARS in Android.common_path.mk
+# because that's what we use for compiling the boot.art image.
+# It may contain additional modules from TEST_CORE_JARS.
+bpath_modules = ("core-oj core-libart okhttp bouncycastle apache-xml core-icu4j"
+                 " conscrypt")
+
+
+# Helper function to construct paths for apex modules (for both -Xbootclasspath and
+# -Xbootclasspath-location).
+def get_apex_bootclasspath_impl(bpath_prefix: str):
+  bpath_separator = ""
+  bpath = ""
+  bpath_jar = ""
+  for bpath_module in bpath_modules.split(" "):
+    apex_module = "com.android.art"
+    if bpath_module == "conscrypt":
+      apex_module = "com.android.conscrypt"
+    if bpath_module == "core-icu4j":
+      apex_module = "com.android.i18n"
+    bpath_jar = f"/apex/{apex_module}/javalib/{bpath_module}.jar"
+    bpath += f"{bpath_separator}{bpath_prefix}{bpath_jar}"
+    bpath_separator = ":"
+  return bpath
+
+
+# Gets a -Xbootclasspath paths with the apex modules.
+def get_apex_bootclasspath(host: bool):
+  bpath_prefix = ""
+
+  if host:
+    bpath_prefix = os.environ["ANDROID_HOST_OUT"]
+
+  return get_apex_bootclasspath_impl(bpath_prefix)
+
+
+# Gets a -Xbootclasspath-location paths with the apex modules.
+def get_apex_bootclasspath_locations(host: bool):
+  bpath_location_prefix = ""
+
+  if host:
+    ANDROID_BUILD_TOP=os.environ["ANDROID_BUILD_TOP"]
+    ANDROID_HOST_OUT=os.environ["ANDROID_HOST_OUT"]
+    if ANDROID_HOST_OUT[0:len(ANDROID_BUILD_TOP)+1] == f"{ANDROID_BUILD_TOP}/":
+      bpath_location_prefix=ANDROID_HOST_OUT[len(ANDROID_BUILD_TOP)+1:]
+    else:
+      print(f"ANDROID_BUILD_TOP/ is not a prefix of ANDROID_HOST_OUT"\
+            "\nANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"\
+            "\nANDROID_HOST_OUT={ANDROID_HOST_OUT}")
+      sys.exit(1)
+
+  return get_apex_bootclasspath_impl(bpath_location_prefix)
+
+
+def default_run(ctx, args, **kwargs):
+  # Clone the args so we can modify them without affecting args in the caller.
+  args = Namespace(**vars(args))
+
+  # Overwrite args based on the named parameters.
+  # E.g. the caller can do `default_run(args, jvmti=True)` to modify args.jvmti.
+  for name, new_value in kwargs.items():
+    old_value = getattr(args, name)
+    assert isinstance(new_value, old_value.__class__), name + " should have type " + str(old_value.__class__)
+    if isinstance(old_value, list):
+      setattr(args, name, old_value + new_value)  # Lists get merged.
+    else:
+      setattr(args, name, new_value)
+
+  ON_VM = os.environ.get("ART_TEST_ON_VM")
+
+  # Store copy of stdout&stderr of command in files so that we can diff them later.
+  # This may run under 'adb shell' so we are limited only to 'sh' shell feature set.
+  def tee(cmd: str):
+    # 'tee' works on stdout only, so we need to temporarily swap stdout and stderr.
+    cmd = f"({cmd} | tee -a {DEX_LOCATION}/{basename(args.stdout_file)}) 3>&1 1>&2 2>&3"
+    cmd = f"({cmd} | tee -a {DEX_LOCATION}/{basename(args.stderr_file)}) 3>&1 1>&2 2>&3"
+    return f"set -o pipefail; {cmd}"  # Use exit code of first failure in piped command.
+
+  local_path = os.path.dirname(__file__)
+
+  ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP")
+  ANDROID_DATA = os.environ.get("ANDROID_DATA")
+  ANDROID_HOST_OUT = os.environ["ANDROID_HOST_OUT"]
+  ANDROID_LOG_TAGS = os.environ.get("ANDROID_LOG_TAGS", "")
+  ART_TIME_OUT_MULTIPLIER = int(os.environ.get("ART_TIME_OUT_MULTIPLIER", 1))
+  DEX2OAT = os.environ.get("DEX2OAT", "")
+  DEX_LOCATION = os.environ["DEX_LOCATION"]
+  JAVA = os.environ.get("JAVA")
+  OUT_DIR = os.environ.get("OUT_DIR")
+  PATH = os.environ.get("PATH", "")
+  SANITIZE_HOST = os.environ.get("SANITIZE_HOST", "")
+  TEST_NAME = os.environ["TEST_NAME"]
+  USE_EXRACTED_ZIPAPEX = os.environ.get("USE_EXRACTED_ZIPAPEX", "")
+
+  assert ANDROID_BUILD_TOP, "Did you forget to run `lunch`?"
+
+  ANDROID_ROOT = args.android_root
+  ANDROID_ART_ROOT = args.android_art_root
+  ANDROID_I18N_ROOT = args.android_i18n_root
+  ANDROID_TZDATA_ROOT = args.android_tzdata_root
+  ARCHITECTURES_32 = "(arm|x86|none)"
+  ARCHITECTURES_64 = "(arm64|x86_64|riscv64|none)"
+  ARCHITECTURES_PATTERN = ARCHITECTURES_32
+  GET_DEVICE_ISA_BITNESS_FLAG = "--32"
+  BOOT_IMAGE = args.boot
+  CHROOT = args.chroot
+  COMPILE_FLAGS = ""
+  DALVIKVM = "dalvikvm32"
+  DEBUGGER = "n"
+  WITH_AGENT = args.with_agent
+  DEBUGGER_AGENT = args.debug_agent
+  WRAP_DEBUGGER_AGENT = args.debug_wrap_agent
+  DEX2OAT_NDEBUG_BINARY = "dex2oat32"
+  DEX2OAT_DEBUG_BINARY = "dex2oatd32"
+  EXPERIMENTAL = args.experimental
+  FALSE_BIN = "false"
+  FLAGS = ""
+  ANDROID_FLAGS = ""
+  GDB = ""
+  GDB_ARGS = ""
+  GDB_DEX2OAT = ""
+  GDB_DEX2OAT_ARGS = ""
+  GDBSERVER_DEVICE = "gdbserver"
+  GDBSERVER_HOST = "gdbserver"
+  HAVE_IMAGE = args.image
+  HOST = args.host
+  BIONIC = args.bionic
+  CREATE_ANDROID_ROOT = False
+  USE_ZIPAPEX = (args.runtime_zipapex != "")
+  ZIPAPEX_LOC = args.runtime_zipapex
+  USE_EXTRACTED_ZIPAPEX = (args.runtime_extracted_zipapex != "")
+  EXTRACTED_ZIPAPEX_LOC = args.runtime_extracted_zipapex
+  INTERPRETER = args.interpreter
+  JIT = args.jit
+  INVOKE_WITH = " ".join(args.invoke_with)
+  USE_JVMTI = args.jvmti
+  IS_JVMTI_TEST = False
+  ADD_LIBDIR_ARGUMENTS = args.add_libdir_argument
+  SUFFIX64 = ""
+  ISA = "x86"
+  LIBRARY_DIRECTORY = "lib"
+  TEST_DIRECTORY = "nativetest"
+  MAIN = args.main
+  OPTIMIZE = args.optimize
+  PREBUILD = args.prebuild
+  RELOCATE = args.relocate
+  SECONDARY_DEX = ""
+  TIME_OUT = "timeout"  # "n" (disabled), "timeout" (use timeout), "gdb" (use gdb)
+  TIMEOUT_DUMPER = "signal_dumper"
+  # Values in seconds.
+  TIME_OUT_EXTRA = 0
+  TIME_OUT_VALUE = args.timeout
+  USE_GDB = args.gdb
+  USE_GDBSERVER = args.gdbserver
+  GDBSERVER_PORT = args.gdbserver_port
+  USE_GDB_DEX2OAT = args.gdb_dex2oat
+  USE_JVM = args.jvm
+  VERIFY = "y" if args.verify else "n"  # y=yes,n=no,s=softfail
+  ZYGOTE = ""
+  DEX_VERIFY = ""
+  INSTRUCTION_SET_FEATURES = args.instruction_set_features
+  ARGS = ""
+  VDEX_ARGS = ""
+  DRY_RUN = args.dry_run
+  TEST_VDEX = args.vdex
+  TEST_DEX2OAT_DM = args.dex2oat_dm
+  TEST_RUNTIME_DM = args.runtime_dm
+  TEST_IS_NDEBUG = args.O
+  APP_IMAGE = args.app_image
+  SECONDARY_APP_IMAGE = args.secondary_app_image
+  SECONDARY_CLASS_LOADER_CONTEXT = args.secondary_class_loader_context
+  SECONDARY_COMPILATION = args.secondary_compilation
+  JVMTI_STRESS = False
+  JVMTI_REDEFINE_STRESS = args.jvmti_redefine_stress
+  JVMTI_STEP_STRESS = args.jvmti_step_stress
+  JVMTI_FIELD_STRESS = args.jvmti_field_stress
+  JVMTI_TRACE_STRESS = args.jvmti_trace_stress
+  PROFILE = args.profile
+  RANDOM_PROFILE = args.random_profile
+  DEX2OAT_TIMEOUT = args.dex2oat_timeout
+  DEX2OAT_RT_TIMEOUT = args.dex2oat_rt_timeout
+  CREATE_RUNNER = args.create_runner
+  INT_OPTS = ""
+  SIMPLEPERF = args.simpleperf
+  DEBUGGER_OPTS = ""
+  JVM_VERIFY_ARG = ""
+  LIB = args.lib
+
+  # if True, run 'sync' before dalvikvm to make sure all files from
+  # build step (e.g. dex2oat) were finished writing.
+  SYNC_BEFORE_RUN = args.sync
+
+  # When running a debug build, we want to run with all checks.
+  ANDROID_FLAGS += " -XX:SlowDebug=true"
+  # The same for dex2oatd, both prebuild and runtime-driven.
+  ANDROID_FLAGS += (" -Xcompiler-option --runtime-arg -Xcompiler-option "
+                    "-XX:SlowDebug=true")
+  COMPILER_FLAGS = "  --runtime-arg -XX:SlowDebug=true"
+
+  # Let the compiler and runtime know that we are running tests.
+  COMPILE_FLAGS += " --compile-art-test"
+  ANDROID_FLAGS += " -Xcompiler-option --compile-art-test"
+
+  if USE_JVMTI:
+    IS_JVMTI_TEST = True
+    # Secondary images block some tested behavior.
+    SECONDARY_APP_IMAGE = False
+  if args.gc_stress:
+    # Give an extra 20 mins if we are gc-stress.
+    TIME_OUT_EXTRA += 1200
+  for arg in args.testlib:
+    ARGS += f" {arg}"
+  for arg in args.test_args:
+    ARGS += f" {arg}"
+  for arg in args.compiler_only_option:
+    COMPILE_FLAGS += f" {arg}"
+  for arg in args.Xcompiler_option:
+    FLAGS += f" -Xcompiler-option {arg}"
+    COMPILE_FLAGS += f" {arg}"
+  if args.secondary:
+    SECONDARY_DEX = f":{DEX_LOCATION}/{TEST_NAME}-ex.jar"
+    # Enable cfg-append to make sure we get the dump for both dex files.
+    # (otherwise the runtime compilation of the secondary dex will overwrite
+    # the dump of the first one).
+    FLAGS += " -Xcompiler-option --dump-cfg-append"
+    COMPILE_FLAGS += " --dump-cfg-append"
+  for arg in args.android_runtime_option:
+    ANDROID_FLAGS += f" {arg}"
+  for arg in args.runtime_option:
+    FLAGS += f" {arg}"
+    if arg == "-Xmethod-trace":
+      # Method tracing can slow some tests down a lot.
+      TIME_OUT_EXTRA += 1200
+  if args.compact_dex_level:
+    arg = args.compact_dex_level
+    COMPILE_FLAGS += f" --compact-dex-level={arg}"
+  if JVMTI_REDEFINE_STRESS:
+    # APP_IMAGE doesn't really work with jvmti redefine stress
+    SECONDARY_APP_IMAGE = False
+    JVMTI_STRESS = True
+  if JVMTI_REDEFINE_STRESS or JVMTI_STEP_STRESS or JVMTI_FIELD_STRESS or JVMTI_TRACE_STRESS:
+    USE_JVMTI = True
+    JVMTI_STRESS = True
+  if HOST:
+    ANDROID_ROOT = ANDROID_HOST_OUT
+    ANDROID_ART_ROOT = f"{ANDROID_HOST_OUT}/com.android.art"
+    ANDROID_I18N_ROOT = f"{ANDROID_HOST_OUT}/com.android.i18n"
+    ANDROID_TZDATA_ROOT = f"{ANDROID_HOST_OUT}/com.android.tzdata"
+    # On host, we default to using the symlink, as the PREFER_32BIT
+    # configuration is the only configuration building a 32bit version of
+    # dex2oat.
+    DEX2OAT_DEBUG_BINARY = "dex2oatd"
+    DEX2OAT_NDEBUG_BINARY = "dex2oat"
+  if BIONIC:
+    # We need to create an ANDROID_ROOT because currently we cannot create
+    # the frameworks/libcore with linux_bionic so we need to use the normal
+    # host ones which are in a different location.
+    CREATE_ANDROID_ROOT = True
+  if USE_ZIPAPEX:
+    # TODO (b/119942078): Currently apex does not support
+    # symlink_preferred_arch so we will not have a dex2oatd to execute and
+    # need to manually provide
+    # dex2oatd64.
+    DEX2OAT_DEBUG_BINARY = "dex2oatd64"
+  if WITH_AGENT:
+    USE_JVMTI = True
+  if DEBUGGER_AGENT:
+    DEBUGGER = "agent"
+    USE_JVMTI = True
+    TIME_OUT = "n"
+  if args.debug:
+    USE_JVMTI = True
+    DEBUGGER = "y"
+    TIME_OUT = "n"
+  if args.gdbserver_bin:
+    arg = args.gdbserver_bin
+    GDBSERVER_HOST = arg
+    GDBSERVER_DEVICE = arg
+  if args.gdbserver or args.gdb or USE_GDB_DEX2OAT:
+    TIME_OUT = "n"
+  for arg in args.gdb_arg:
+    GDB_ARGS += f" {arg}"
+  if args.gdb_dex2oat_args:
+    for arg in arg.split(";"):
+      GDB_DEX2OAT_ARGS += f"{arg} "
+  if args.zygote:
+    ZYGOTE = "-Xzygote"
+    print("Spawning from zygote")
+  if args.baseline:
+    FLAGS += " -Xcompiler-option --baseline"
+    COMPILE_FLAGS += " --baseline"
+  if args.verify_soft_fail:
+    VERIFY = "s"
+  if args.is64:
+    SUFFIX64 = "64"
+    ISA = "x86_64"
+    GDBSERVER_DEVICE = "gdbserver64"
+    DALVIKVM = "dalvikvm64"
+    LIBRARY_DIRECTORY = "lib64"
+    TEST_DIRECTORY = "nativetest64"
+    ARCHITECTURES_PATTERN = ARCHITECTURES_64
+    GET_DEVICE_ISA_BITNESS_FLAG = "--64"
+    DEX2OAT_NDEBUG_BINARY = "dex2oat64"
+    DEX2OAT_DEBUG_BINARY = "dex2oatd64"
+  if args.vdex_filter:
+    option = args.vdex_filter
+    VDEX_ARGS += f" --compiler-filter={option}"
+  if args.vdex_arg:
+    arg = args.vdex_arg
+    VDEX_ARGS += f" {arg}"
+
+# HACK: Force the use of `signal_dumper` on host.
+  if HOST or ON_VM:
+    TIME_OUT = "timeout"
+
+# If you change this, update the timeout in testrunner.py as well.
+  if not TIME_OUT_VALUE:
+    # 10 minutes is the default.
+    TIME_OUT_VALUE = 600
+
+    # For sanitized builds use a larger base.
+    # TODO: Consider sanitized target builds?
+    if SANITIZE_HOST != "":
+      TIME_OUT_VALUE = 1500  # 25 minutes.
+
+    TIME_OUT_VALUE += TIME_OUT_EXTRA
+
+# Escape hatch for slow hosts or devices. Accept an environment variable as a timeout factor.
+  if ART_TIME_OUT_MULTIPLIER:
+    TIME_OUT_VALUE *= ART_TIME_OUT_MULTIPLIER
+
+# The DEX_LOCATION with the chroot prefix, if any.
+  CHROOT_DEX_LOCATION = f"{CHROOT}{DEX_LOCATION}"
+
+  # If running on device, determine the ISA of the device.
+  if not HOST and not USE_JVM:
+    ISA = get_target_arch(args.is64)
+
+  if not USE_JVM:
+    FLAGS += f" {ANDROID_FLAGS}"
+    # we don't want to be trying to get adbconnections since the plugin might
+    # not have been built.
+    FLAGS += " -XjdwpProvider:none"
+    for feature in EXPERIMENTAL:
+      FLAGS += f" -Xexperimental:{feature} -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:{feature}"
+      COMPILE_FLAGS = f"{COMPILE_FLAGS} --runtime-arg -Xexperimental:{feature}"
+
+  if CREATE_ANDROID_ROOT:
+    ANDROID_ROOT = f"{DEX_LOCATION}/android-root"
+
+  if ZYGOTE == "":
+    if OPTIMIZE:
+      if VERIFY == "y":
+        DEX_OPTIMIZE = "-Xdexopt:verified"
+      else:
+        DEX_OPTIMIZE = "-Xdexopt:all"
+    else:
+      DEX_OPTIMIZE = "-Xdexopt:none"
+
+    if VERIFY == "y":
+      JVM_VERIFY_ARG = "-Xverify:all"
+    elif VERIFY == "s":
+      JVM_VERIFY_ARG = "Xverify:all"
+      DEX_VERIFY = "-Xverify:softfail"
+    else:  # VERIFY == "n"
+      DEX_VERIFY = "-Xverify:none"
+      JVM_VERIFY_ARG = "-Xverify:none"
+
+  if DEBUGGER == "y":
+    # Use this instead for ddms and connect by running 'ddms':
+    # DEBUGGER_OPTS="-XjdwpOptions=server=y,suspend=y -XjdwpProvider:adbconnection"
+    # TODO: add a separate --ddms option?
+
+    PORT = 12345
+    print("Waiting for jdb to connect:")
+    if not HOST:
+      print(f"    adb forward tcp:{PORT} tcp:{PORT}")
+    print(f"    jdb -attach localhost:{PORT}")
+    if not USE_JVM:
+      # Use the default libjdwp agent. Use --debug-agent to use a custom one.
+      DEBUGGER_OPTS = f"-agentpath:libjdwp.so=transport=dt_socket,address={PORT},server=y,suspend=y -XjdwpProvider:internal"
+    else:
+      DEBUGGER_OPTS = f"-agentlib:jdwp=transport=dt_socket,address={PORT},server=y,suspend=y"
+  elif DEBUGGER == "agent":
+    PORT = 12345
+    # TODO Support ddms connection and support target.
+    assert HOST, "--debug-agent not supported yet for target!"
+    AGENTPATH = DEBUGGER_AGENT
+    if WRAP_DEBUGGER_AGENT:
+      WRAPPROPS = f"{ANDROID_ROOT}/{LIBRARY_DIRECTORY}/libwrapagentpropertiesd.so"
+      if TEST_IS_NDEBUG:
+        WRAPPROPS = f"{ANDROID_ROOT}/{LIBRARY_DIRECTORY}/libwrapagentproperties.so"
+      AGENTPATH = f"{WRAPPROPS}={ANDROID_BUILD_TOP}/art/tools/libjdwp-compat.props,{AGENTPATH}"
+    print(f"Connect to localhost:{PORT}")
+    DEBUGGER_OPTS = f"-agentpath:{AGENTPATH}=transport=dt_socket,address={PORT},server=y,suspend=y"
+
+  for agent in WITH_AGENT:
+    FLAGS += f" -agentpath:{agent}"
+
+  if USE_JVMTI:
+    if not USE_JVM:
+      plugin = "libopenjdkjvmtid.so"
+      if TEST_IS_NDEBUG:
+        plugin = "libopenjdkjvmti.so"
+      # We used to add flags here that made the runtime debuggable but that is not
+      # needed anymore since the plugin can do it for us now.
+      FLAGS += f" -Xplugin:{plugin}"
+
+      # For jvmti tests, set the threshold of compilation to 1, so we jit early to
+      # provide better test coverage for jvmti + jit. This means we won't run
+      # the default --jit configuration but it is not too important test scenario for
+      # jvmti tests. This is art specific flag, so don't use it with jvm.
+      FLAGS += " -Xjitthreshold:1"
+
+# Add the libdir to the argv passed to the main function.
+  if ADD_LIBDIR_ARGUMENTS:
+    if HOST:
+      ARGS += f" {ANDROID_HOST_OUT}/{TEST_DIRECTORY}/"
+    else:
+      ARGS += f" /data/{TEST_DIRECTORY}/art/{ISA}/"
+  if IS_JVMTI_TEST:
+    agent = "libtiagentd.so"
+    lib = "tiagentd"
+    if TEST_IS_NDEBUG:
+      agent = "libtiagent.so"
+      lib = "tiagent"
+
+    ARGS += f" {lib}"
+    if USE_JVM:
+      FLAGS += f" -agentpath:{ANDROID_HOST_OUT}/nativetest64/{agent}={TEST_NAME},jvm"
+    else:
+      FLAGS += f" -agentpath:{agent}={TEST_NAME},art"
+
+  if JVMTI_STRESS:
+    agent = "libtistressd.so"
+    if TEST_IS_NDEBUG:
+      agent = "libtistress.so"
+
+    # Just give it a default start so we can always add ',' to it.
+    agent_args = "jvmti-stress"
+    if JVMTI_REDEFINE_STRESS:
+      # We really cannot do this on RI so don't both passing it in that case.
+      if not USE_JVM:
+        agent_args = f"{agent_args},redefine"
+    if JVMTI_FIELD_STRESS:
+      agent_args = f"{agent_args},field"
+    if JVMTI_STEP_STRESS:
+      agent_args = f"{agent_args},step"
+    if JVMTI_TRACE_STRESS:
+      agent_args = f"{agent_args},trace"
+    # In the future add onto this;
+    if USE_JVM:
+      FLAGS += f" -agentpath:{ANDROID_HOST_OUT}/nativetest64/{agent}={agent_args}"
+    else:
+      FLAGS += f" -agentpath:{agent}={agent_args}"
+
+  if USE_JVM:
+    ctx.export(
+      ANDROID_I18N_ROOT = ANDROID_I18N_ROOT,
+      DEX_LOCATION = DEX_LOCATION,
+      JAVA_HOME = os.environ["JAVA_HOME"],
+      LANG = "en_US.UTF-8",  # Needed to enable unicode and make the output is deterministic.
+      LD_LIBRARY_PATH = f"{ANDROID_HOST_OUT}/lib64",
+    )
+    # Some jvmti tests are flaky without -Xint on the RI.
+    if IS_JVMTI_TEST:
+      FLAGS += " -Xint"
+    # Xmx is necessary since we don't pass down the ART flags to JVM.
+    # We pass the classes2 path whether it's used (src-multidex) or not.
+    cmdline = f"{JAVA} {DEBUGGER_OPTS} {JVM_VERIFY_ARG} -Xmx256m -classpath classes:classes2 {FLAGS} {MAIN} {ARGS}"
+    ctx.run(tee(cmdline), expected_exit_code=args.expected_exit_code)
+    return
+
+  b_path = get_apex_bootclasspath(HOST)
+  b_path_locations = get_apex_bootclasspath_locations(HOST)
+
+  BCPEX = ""
+  if isfile(f"{TEST_NAME}-bcpex.jar"):
+    BCPEX = f":{DEX_LOCATION}/{TEST_NAME}-bcpex.jar"
+
+  # Pass down the bootclasspath
+  FLAGS += f" -Xbootclasspath:{b_path}{BCPEX}"
+  FLAGS += f" -Xbootclasspath-locations:{b_path_locations}{BCPEX}"
+  COMPILE_FLAGS += f" --runtime-arg -Xbootclasspath:{b_path}"
+  COMPILE_FLAGS += f" --runtime-arg -Xbootclasspath-locations:{b_path_locations}"
+
+  if not HAVE_IMAGE:
+    # Disable image dex2oat - this will forbid the runtime to patch or compile an image.
+    FLAGS += " -Xnoimage-dex2oat"
+
+    # We'll abuse a second flag here to test different behavior. If --relocate, use the
+    # existing image - relocation will fail as patching is disallowed. If --no-relocate,
+    # pass a non-existent image - compilation will fail as dex2oat is disallowed.
+    if not RELOCATE:
+      BOOT_IMAGE = "/system/non-existent/boot.art"
+    # App images cannot be generated without a boot image.
+    APP_IMAGE = False
+  DALVIKVM_BOOT_OPT = f"-Ximage:{BOOT_IMAGE}"
+
+  if USE_GDB_DEX2OAT:
+    assert HOST, "The --gdb-dex2oat option is not yet implemented for target."
+
+  assert not USE_GDBSERVER, "Not supported"
+  if USE_GDB:
+    if not HOST:
+      # We might not have any hostname resolution if we are using a chroot.
+      GDB = f"{GDBSERVER_DEVICE} --no-startup-with-shell 127.0.0.1{GDBSERVER_PORT}"
+    else:
+      GDB = "gdb"
+      GDB_ARGS += f" --args {DALVIKVM}"
+
+  if INTERPRETER:
+    INT_OPTS += " -Xint"
+
+  if JIT:
+    INT_OPTS += " -Xusejit:true"
+  else:
+    INT_OPTS += " -Xusejit:false"
+
+  if INTERPRETER or JIT:
+    if VERIFY == "y":
+      INT_OPTS += " -Xcompiler-option --compiler-filter=verify"
+      COMPILE_FLAGS += " --compiler-filter=verify"
+    elif VERIFY == "s":
+      INT_OPTS += " -Xcompiler-option --compiler-filter=verify"
+      COMPILE_FLAGS += " --compiler-filter=verify"
+      DEX_VERIFY = f"{DEX_VERIFY} -Xverify:softfail"
+    else:  # VERIFY == "n"
+      INT_OPTS += " -Xcompiler-option --compiler-filter=assume-verified"
+      COMPILE_FLAGS += " --compiler-filter=assume-verified"
+      DEX_VERIFY = f"{DEX_VERIFY} -Xverify:none"
+
+  JNI_OPTS = "-Xjnigreflimit:512 -Xcheck:jni"
+
+  COMPILE_FLAGS += " --runtime-arg -Xnorelocate"
+  if RELOCATE:
+    FLAGS += " -Xrelocate"
+  else:
+    FLAGS += " -Xnorelocate"
+
+  if BIONIC and not ON_VM:
+    # This is the location that soong drops linux_bionic builds. Despite being
+    # called linux_bionic-x86 the build is actually amd64 (x86_64) only.
+    assert path.exists(f"{OUT_DIR}/soong/host/linux_bionic-x86"), (
+        "linux_bionic-x86 target doesn't seem to have been built!")
+    # Set TIMEOUT_DUMPER manually so it works even with apex's
+    TIMEOUT_DUMPER = f"{OUT_DIR}/soong/host/linux_bionic-x86/bin/signal_dumper"
+
+  # Prevent test from silently falling back to interpreter in no-prebuild mode. This happens
+  # when DEX_LOCATION path is too long, because vdex/odex filename is constructed by taking
+  # full path to dex, stripping leading '/', appending '@classes.vdex' and changing every
+  # remaining '/' into '@'.
+  if HOST:
+    max_filename_size = int(check_output(f"getconf NAME_MAX {DEX_LOCATION}", shell=True))
+  else:
+    # There is no getconf on device, fallback to standard value.
+    # See NAME_MAX in kernel <linux/limits.h>
+    max_filename_size = 255
+  # Compute VDEX_NAME.
+  DEX_LOCATION_STRIPPED = DEX_LOCATION.lstrip("/")
+  VDEX_NAME = f"{DEX_LOCATION_STRIPPED}@{TEST_NAME}[email protected]".replace(
+      "/", "@")
+  assert len(VDEX_NAME) <= max_filename_size, "Dex location path too long"
+
+  if HOST:
+    # On host, run binaries (`dex2oat(d)`, `dalvikvm`, `profman`) from the `bin`
+    # directory under the "Android Root" (usually `out/host/linux-x86`).
+    #
+    # TODO(b/130295968): Adjust this if/when ART host artifacts are installed
+    # under the ART root (usually `out/host/linux-x86/com.android.art`).
+    ANDROID_ART_BIN_DIR = f"{ANDROID_ROOT}/bin"
+  else:
+    # On target, run binaries (`dex2oat(d)`, `dalvikvm`, `profman`) from the ART
+    # APEX's `bin` directory. This means the linker will observe the ART APEX
+    # linker configuration file (`/apex/com.android.art/etc/ld.config.txt`) for
+    # these binaries.
+    ANDROID_ART_BIN_DIR = f"{ANDROID_ART_ROOT}/bin"
+
+  profman_cmdline = "true"
+  dex2oat_cmdline = "true"
+  vdex_cmdline = "true"
+  dm_cmdline = "true"
+  mkdir_locations = f"{DEX_LOCATION}/dalvik-cache/{ISA}"
+  strip_cmdline = "true"
+  sync_cmdline = "true"
+  linkroot_cmdline = "true"
+  linkroot_overlay_cmdline = "true"
+  setupapex_cmdline = "true"
+  installapex_cmdline = "true"
+  installapex_test_cmdline = "true"
+
+  def linkdirs(host_out: str, root: str):
+    dirs = list(filter(os.path.isdir, glob.glob(os.path.join(host_out, "*"))))
+    # Also create a link for the boot image.
+    dirs.append(f"{ANDROID_HOST_OUT}/apex/art_boot_images")
+    return " && ".join(f"ln -sf {dir} {root}" for dir in dirs)
+
+  if CREATE_ANDROID_ROOT:
+    mkdir_locations += f" {ANDROID_ROOT}"
+    linkroot_cmdline = linkdirs(ANDROID_HOST_OUT, ANDROID_ROOT)
+    if BIONIC:
+      # TODO Make this overlay more generic.
+      linkroot_overlay_cmdline = linkdirs(
+          f"{OUT_DIR}/soong/host/linux_bionic-x86", ANDROID_ROOT)
+    # Replace the boot image to a location expected by the runtime.
+    DALVIKVM_BOOT_OPT = f"-Ximage:{ANDROID_ROOT}/art_boot_images/javalib/boot.art"
+
+  if USE_ZIPAPEX:
+    # TODO Currently this only works for linux_bionic zipapexes because those are
+    # stripped and so small enough that the ulimit doesn't kill us.
+    mkdir_locations += f" {DEX_LOCATION}/zipapex"
+    setupapex_cmdline = f"unzip -o -u {ZIPAPEX_LOC} apex_payload.zip -d {DEX_LOCATION}"
+    installapex_cmdline = f"unzip -o -u {DEX_LOCATION}/apex_payload.zip -d {DEX_LOCATION}/zipapex"
+    ANDROID_ART_BIN_DIR = f"{DEX_LOCATION}/zipapex/bin"
+  elif USE_EXTRACTED_ZIPAPEX:
+    # Just symlink the zipapex binaries
+    ANDROID_ART_BIN_DIR = f"{DEX_LOCATION}/zipapex/bin"
+    # Force since some tests manually run this file twice.
+    # If the {RUN} is executed multiple times we don't need to recreate the link
+    installapex_cmdline = f"ln -sfTv {EXTRACTED_ZIPAPEX_LOC} {DEX_LOCATION}/zipapex"
+
+  # PROFILE takes precedence over RANDOM_PROFILE, since PROFILE tests require a
+  # specific profile to run properly.
+  if PROFILE or RANDOM_PROFILE:
+    profman_cmdline = f"{ANDROID_ART_BIN_DIR}/profman  \
+      --apk={DEX_LOCATION}/{TEST_NAME}.jar \
+      --dex-location={DEX_LOCATION}/{TEST_NAME}.jar"
+
+    if isfile(f"{TEST_NAME}-ex.jar") and SECONDARY_COMPILATION:
+      profman_cmdline = f"{profman_cmdline} \
+        --apk={DEX_LOCATION}/{TEST_NAME}-ex.jar \
+        --dex-location={DEX_LOCATION}/{TEST_NAME}-ex.jar"
+
+    COMPILE_FLAGS = f"{COMPILE_FLAGS} --profile-file={DEX_LOCATION}/{TEST_NAME}.prof"
+    FLAGS = f"{FLAGS} -Xcompiler-option --profile-file={DEX_LOCATION}/{TEST_NAME}.prof"
+    if PROFILE:
+      profman_cmdline = f"{profman_cmdline} --create-profile-from={DEX_LOCATION}/profile \
+          --reference-profile-file={DEX_LOCATION}/{TEST_NAME}.prof"
+
+    else:
+      profman_cmdline = f"{profman_cmdline} --generate-test-profile={DEX_LOCATION}/{TEST_NAME}.prof \
+          --generate-test-profile-seed=0"
+
+  def get_prebuilt_lldb_path():
+    CLANG_BASE = "prebuilts/clang/host"
+    CLANG_VERSION = check_output(
+        f"{ANDROID_BUILD_TOP}/build/soong/scripts/get_clang_version.py"
+    ).strip()
+    uname = check_output("uname -s", shell=True).strip()
+    if uname == "Darwin":
+      PREBUILT_NAME = "darwin-x86"
+    elif uname == "Linux":
+      PREBUILT_NAME = "linux-x86"
+    else:
+      print(
+          "Unknown host $(uname -s). Unsupported for debugging dex2oat with LLDB.",
+          file=sys.stderr)
+      return
+    CLANG_PREBUILT_HOST_PATH = f"{ANDROID_BUILD_TOP}/{CLANG_BASE}/{PREBUILT_NAME}/{CLANG_VERSION}"
+    # If the clang prebuilt directory exists and the reported clang version
+    # string does not, then it is likely that the clang version reported by the
+    # get_clang_version.py script does not match the expected directory name.
+    if isdir(f"{ANDROID_BUILD_TOP}/{CLANG_BASE}/{PREBUILT_NAME}"):
+      assert isdir(CLANG_PREBUILT_HOST_PATH), (
+          "The prebuilt clang directory exists, but the specific "
+          "clang\nversion reported by get_clang_version.py does not exist in "
+          "that path.\nPlease make sure that the reported clang version "
+          "resides in the\nprebuilt clang directory!")
+
+    # The lldb-server binary is a dependency of lldb.
+    os.environ[
+        "LLDB_DEBUGSERVER_PATH"] = f"{CLANG_PREBUILT_HOST_PATH}/runtimes_ndk_cxx/x86_64/lldb-server"
+
+    # Set the current terminfo directory to TERMINFO so that LLDB can read the
+    # termcap database.
+    terminfo = re.search("/.*/terminfo/", check_output("infocmp"))
+    if terminfo:
+      os.environ["TERMINFO"] = terminfo[0]
+
+    return f"{CLANG_PREBUILT_HOST_PATH}/bin/lldb.sh"
+
+  def write_dex2oat_cmdlines(name: str):
+    nonlocal dex2oat_cmdline, dm_cmdline, vdex_cmdline
+
+    class_loader_context = ""
+    enable_app_image = False
+    if APP_IMAGE:
+      enable_app_image = True
+
+    # If the name ends in -ex then this is a secondary dex file
+    if name.endswith("-ex"):
+      # Lazily realize the default value in case DEX_LOCATION/TEST_NAME change
+      nonlocal SECONDARY_CLASS_LOADER_CONTEXT
+      if SECONDARY_CLASS_LOADER_CONTEXT == "":
+        if SECONDARY_DEX == "":
+          # Tests without `--secondary` load the "-ex" jar in a separate PathClassLoader
+          # that is a child of the main PathClassLoader. If the class loader is constructed
+          # in any other way, the test needs to specify the secondary CLC explicitly.
+          SECONDARY_CLASS_LOADER_CONTEXT = f"PCL[];PCL[{DEX_LOCATION}/{TEST_NAME}.jar]"
+        else:
+          # Tests with `--secondary` load the `-ex` jar a part of the main PathClassLoader.
+          SECONDARY_CLASS_LOADER_CONTEXT = f"PCL[{DEX_LOCATION}/{TEST_NAME}.jar]"
+      class_loader_context = f"'--class-loader-context={SECONDARY_CLASS_LOADER_CONTEXT}'"
+      enable_app_image = enable_app_image and SECONDARY_APP_IMAGE
+
+    app_image = ""
+    if enable_app_image:
+      app_image = f"--app-image-file={DEX_LOCATION}/oat/{ISA}/{name}.art --resolve-startup-const-strings=true"
+
+    nonlocal GDB_DEX2OAT, GDB_DEX2OAT_ARGS
+    if USE_GDB_DEX2OAT:
+      prebuilt_lldb_path = get_prebuilt_lldb_path()
+      GDB_DEX2OAT = f"{prebuilt_lldb_path} -f"
+      GDB_DEX2OAT_ARGS += " -- "
+
+    dex2oat_binary = DEX2OAT_DEBUG_BINARY
+    if TEST_IS_NDEBUG:
+      dex2oat_binary = DEX2OAT_NDEBUG_BINARY
+    dex2oat_cmdline = f"{INVOKE_WITH} {GDB_DEX2OAT} \
+                        {ANDROID_ART_BIN_DIR}/{dex2oat_binary} \
+                        {GDB_DEX2OAT_ARGS} \
+                        {COMPILE_FLAGS} \
+                        --boot-image={BOOT_IMAGE} \
+                        --dex-file={DEX_LOCATION}/{name}.jar \
+                        --oat-file={DEX_LOCATION}/oat/{ISA}/{name}.odex \
+                        {app_image} \
+                        --generate-mini-debug-info \
+                        --instruction-set={ISA} \
+                        {class_loader_context}"
+
+    if INSTRUCTION_SET_FEATURES != "":
+      dex2oat_cmdline += f" --instruction-set-features={INSTRUCTION_SET_FEATURES}"
+
+    # Add in a timeout. This is important for testing the compilation/verification time of
+    # pathological cases. We do not append a timeout when debugging dex2oat because we
+    # do not want it to exit while debugging.
+    # Note: as we don't know how decent targets are (e.g., emulator), only do this on the host for
+    #       now. We should try to improve this.
+    #       The current value is rather arbitrary. run-tests should compile quickly.
+    # Watchdog timeout is in milliseconds so add 3 '0's to the dex2oat timeout.
+    if HOST and not USE_GDB_DEX2OAT:
+      # Use SIGRTMIN+2 to try to dump threads.
+      # Use -k 1m to SIGKILL it a minute later if it hasn't ended.
+      dex2oat_cmdline = f"timeout -k {DEX2OAT_TIMEOUT}s -s SIGRTMIN+2 {DEX2OAT_RT_TIMEOUT}s {dex2oat_cmdline} --watchdog-timeout={DEX2OAT_TIMEOUT}000"
+    if PROFILE or RANDOM_PROFILE:
+      vdex_cmdline = f"{dex2oat_cmdline} {VDEX_ARGS} --input-vdex={DEX_LOCATION}/oat/{ISA}/{name}.vdex --output-vdex={DEX_LOCATION}/oat/{ISA}/{name}.vdex"
+    elif TEST_VDEX:
+      if VDEX_ARGS == "":
+        # If no arguments need to be passed, just delete the odex file so that the runtime only picks up the vdex file.
+        vdex_cmdline = f"rm {DEX_LOCATION}/oat/{ISA}/{name}.odex"
+      else:
+        vdex_cmdline = f"{dex2oat_cmdline} {VDEX_ARGS} --compact-dex-level=none --input-vdex={DEX_LOCATION}/oat/{ISA}/{name}.vdex"
+    elif TEST_DEX2OAT_DM:
+      vdex_cmdline = f"{dex2oat_cmdline} {VDEX_ARGS} --dump-timings --dm-file={DEX_LOCATION}/oat/{ISA}/{name}.dm"
+      dex2oat_cmdline = f"{dex2oat_cmdline} --copy-dex-files=false --output-vdex={DEX_LOCATION}/oat/{ISA}/primary.vdex"
+      dm_cmdline = f"zip -qj {DEX_LOCATION}/oat/{ISA}/{name}.dm {DEX_LOCATION}/oat/{ISA}/primary.vdex"
+    elif TEST_RUNTIME_DM:
+      dex2oat_cmdline = f"{dex2oat_cmdline} --copy-dex-files=false --output-vdex={DEX_LOCATION}/oat/{ISA}/primary.vdex"
+      dm_cmdline = f"zip -qj {DEX_LOCATION}/{name}.dm {DEX_LOCATION}/oat/{ISA}/primary.vdex"
+
+# Enable mini-debug-info for JIT (if JIT is used).
+
+  FLAGS += " -Xcompiler-option --generate-mini-debug-info"
+
+  if PREBUILD:
+    mkdir_locations += f" {DEX_LOCATION}/oat/{ISA}"
+
+    # "Primary".
+    write_dex2oat_cmdlines(TEST_NAME)
+    dex2oat_cmdline = re.sub(" +", " ", dex2oat_cmdline)
+    dm_cmdline = re.sub(" +", " ", dm_cmdline)
+    vdex_cmdline = re.sub(" +", " ", vdex_cmdline)
+
+    # Enable mini-debug-info for JIT (if JIT is used).
+    FLAGS += " -Xcompiler-option --generate-mini-debug-info"
+
+    if isfile(f"{TEST_NAME}-ex.jar") and SECONDARY_COMPILATION:
+      # "Secondary" for test coverage.
+
+      # Store primary values.
+      base_dex2oat_cmdline = dex2oat_cmdline
+      base_dm_cmdline = dm_cmdline
+      base_vdex_cmdline = vdex_cmdline
+
+      write_dex2oat_cmdlines(f"{TEST_NAME}-ex")
+      dex2oat_cmdline = re.sub(" +", " ", dex2oat_cmdline)
+      dm_cmdline = re.sub(" +", " ", dm_cmdline)
+      vdex_cmdline = re.sub(" +", " ", vdex_cmdline)
+
+      # Concatenate.
+      dex2oat_cmdline = f"{base_dex2oat_cmdline} && {dex2oat_cmdline}"
+      dm_cmdline = base_dm_cmdline  # Only use primary dm.
+      vdex_cmdline = f"{base_vdex_cmdline} && {vdex_cmdline}"
+
+  if SYNC_BEFORE_RUN:
+    sync_cmdline = "sync"
+
+  DALVIKVM_ISA_FEATURES_ARGS = ""
+  if INSTRUCTION_SET_FEATURES != "":
+    DALVIKVM_ISA_FEATURES_ARGS = f"-Xcompiler-option --instruction-set-features={INSTRUCTION_SET_FEATURES}"
+
+# java.io.tmpdir can only be set at launch time.
+  TMP_DIR_OPTION = ""
+  if not HOST:
+    TMP_DIR_OPTION = "-Djava.io.tmpdir=/data/local/tmp"
+
+# The build servers have an ancient version of bash so we cannot use @Q.
+  QUOTED_DALVIKVM_BOOT_OPT = shlex.quote(DALVIKVM_BOOT_OPT)
+
+  DALVIKVM_CLASSPATH = f"{DEX_LOCATION}/{TEST_NAME}.jar"
+  if isfile(f"{TEST_NAME}-aotex.jar"):
+    DALVIKVM_CLASSPATH = f"{DALVIKVM_CLASSPATH}:{DEX_LOCATION}/{TEST_NAME}-aotex.jar"
+  DALVIKVM_CLASSPATH = f"{DALVIKVM_CLASSPATH}{SECONDARY_DEX}"
+
+  # We set DumpNativeStackOnSigQuit to false to avoid stressing libunwind.
+  # b/27185632
+  # b/24664297
+
+  dalvikvm_logger = ""
+  if ON_VM:
+    dalvikvm_logger = "-Xuse-stderr-logger"
+
+  dalvikvm_cmdline = f"{INVOKE_WITH} {GDB} {ANDROID_ART_BIN_DIR}/{DALVIKVM} \
+                       {GDB_ARGS} \
+                       {FLAGS} \
+                       {DEX_VERIFY} \
+                       -XXlib:{LIB} \
+                       {DEX2OAT} \
+                       {DALVIKVM_ISA_FEATURES_ARGS} \
+                       {ZYGOTE} \
+                       {JNI_OPTS} \
+                       {INT_OPTS} \
+                       {DEBUGGER_OPTS} \
+                       {QUOTED_DALVIKVM_BOOT_OPT} \
+                       {TMP_DIR_OPTION} \
+                       {dalvikvm_logger} \
+                       -XX:DumpNativeStackOnSigQuit:false \
+                       -cp {DALVIKVM_CLASSPATH} {MAIN} {ARGS}"
+
+  if SIMPLEPERF:
+    dalvikvm_cmdline = f"simpleperf record {dalvikvm_cmdline} && simpleperf report"
+
+  def sanitize_dex2oat_cmdline(cmdline: str) -> str:
+    args = []
+    for arg in cmdline.split(" "):
+      if arg == "--class-loader-context=&":
+        arg = "--class-loader-context=\&"
+      args.append(arg)
+    return " ".join(args)
+
+  # Remove whitespace.
+  dex2oat_cmdline = sanitize_dex2oat_cmdline(dex2oat_cmdline)
+  dalvikvm_cmdline = re.sub(" +", " ", dalvikvm_cmdline)
+  dm_cmdline = re.sub(" +", " ", dm_cmdline)
+  vdex_cmdline = sanitize_dex2oat_cmdline(vdex_cmdline)
+  profman_cmdline = re.sub(" +", " ", profman_cmdline)
+
+  # Use an empty ASAN_OPTIONS to enable defaults.
+  # Note: this is required as envsetup right now exports detect_leaks=0.
+  RUN_TEST_ASAN_OPTIONS = ""
+
+  # Multiple shutdown leaks. b/38341789
+  if RUN_TEST_ASAN_OPTIONS != "":
+    RUN_TEST_ASAN_OPTIONS = f"{RUN_TEST_ASAN_OPTIONS}:"
+  RUN_TEST_ASAN_OPTIONS = f"{RUN_TEST_ASAN_OPTIONS}detect_leaks=0"
+
+  assert not args.external_log_tags, "Deprecated: use --android-log-tags=*:v"
+
+  ANDROID_LOG_TAGS = args.android_log_tags
+
+  def filter_output():
+    # Remove unwanted log messages from stderr before diffing with the expected output.
+    # NB: The unwanted log line can be interleaved in the middle of wanted stderr printf.
+    #     In particular, unhandled exception is printed using several unterminated printfs.
+    ALL_LOG_TAGS = ["V", "D", "I", "W", "E", "F", "S"]
+    skip_tag_set = "|".join(ALL_LOG_TAGS[:ALL_LOG_TAGS.index(args.diff_min_log_tag.upper())])
+    skip_reg_exp = fr'[[:alnum:]]+ ({skip_tag_set}) #-# #:#:# [^\n]*\n'.replace('#', '[0-9]+')
+    ctx.run(fr"sed -i -z -E 's/{skip_reg_exp}//g' '{args.stderr_file}'")
+    if not HAVE_IMAGE:
+      message = "(Unable to open file|Could not create image space)"
+      ctx.run(fr"sed -i -E '/^dalvikvm(|32|64) E .* {message}/d' '{args.stderr_file}'")
+    if ANDROID_LOG_TAGS != "*:i" and "D" in skip_tag_set:
+      ctx.run(fr"sed -i -E '/^(Time zone|I18n) APEX ICU file found/d' '{args.stderr_file}'")
+    if ON_VM:
+      messages = "|".join([
+        "failed to connect to tombstoned",
+        "Failed to write stack traces to tombstoned",
+        "Failed to setpriority to :0"])
+      ctx.run(fr"sed -i -E '/({messages})/d' '{args.stderr_file}'")
+
+  if not HOST:
+    # Populate LD_LIBRARY_PATH.
+    LD_LIBRARY_PATH = ""
+    if ANDROID_ROOT != "/system":
+      # Current default installation is dalvikvm 64bits and dex2oat 32bits,
+      # so we can only use LD_LIBRARY_PATH when testing on a local
+      # installation.
+      LD_LIBRARY_PATH = f"{ANDROID_ROOT}/{LIBRARY_DIRECTORY}"
+
+    # This adds libarttest(d).so to the default linker namespace when dalvikvm
+    # is run from /apex/com.android.art/bin. Since that namespace is essentially
+    # an alias for the com_android_art namespace, that gives libarttest(d).so
+    # full access to the internal ART libraries.
+    LD_LIBRARY_PATH = f"/data/{TEST_DIRECTORY}/com.android.art/lib{SUFFIX64}:{LD_LIBRARY_PATH}"
+    dlib = ("" if TEST_IS_NDEBUG else "d")
+    art_test_internal_libraries = [
+        f"libartagent{dlib}.so",
+        f"libarttest{dlib}.so",
+        f"libtiagent{dlib}.so",
+        f"libtistress{dlib}.so",
+    ]
+    NATIVELOADER_DEFAULT_NAMESPACE_LIBS = ":".join(art_test_internal_libraries)
+    dlib = ""
+    art_test_internal_libraries = []
+
+    if not ON_VM:
+      # Needed to access the test's Odex files.
+      LD_LIBRARY_PATH = f"{DEX_LOCATION}/oat/{ISA}:{LD_LIBRARY_PATH}"
+    # Needed to access the test's native libraries (see e.g. 674-hiddenapi,
+    # which generates `libhiddenapitest_*.so` libraries in `{DEX_LOCATION}`).
+    LD_LIBRARY_PATH = f"{DEX_LOCATION}:{LD_LIBRARY_PATH}"
+
+    # Prepend directories to the path on device.
+    PREPEND_TARGET_PATH = ANDROID_ART_BIN_DIR
+    if ANDROID_ROOT != "/system":
+      PREPEND_TARGET_PATH = f"{PREPEND_TARGET_PATH}:{ANDROID_ROOT}/bin"
+
+    timeout_dumper_cmd = ""
+
+    if TIMEOUT_DUMPER:
+      # Use "-l" to dump to logcat. That is convenience for the build bot crash symbolization.
+      # Use exit code 124 for toybox timeout (b/141007616).
+      timeout_dumper_cmd = f"{TIMEOUT_DUMPER} -l -s 15 -e 124"
+
+    timeout_prefix = ""
+    if TIME_OUT == "timeout":
+      # Add timeout command if time out is desired.
+      #
+      # Note: We first send SIGTERM (the timeout default, signal 15) to the signal dumper, which
+      #       will induce a full thread dump before killing the process. To ensure any issues in
+      #       dumping do not lead to a deadlock, we also use the "-k" option to definitely kill the
+      #       child.
+      # Note: Using "--foreground" to not propagate the signal to children, i.e., the runtime.
+      if ON_VM:
+        timeout_prefix = f"timeout -k 120s {TIME_OUT_VALUE}s"
+      else:
+        timeout_prefix = f"timeout --foreground -k 120s {TIME_OUT_VALUE}s {timeout_dumper_cmd}"
+
+    ctx.export(
+      ASAN_OPTIONS = RUN_TEST_ASAN_OPTIONS,
+      ANDROID_DATA = DEX_LOCATION,
+      DEX_LOCATION = DEX_LOCATION,
+      ANDROID_ROOT = ANDROID_ROOT,
+      ANDROID_I18N_ROOT = ANDROID_I18N_ROOT,
+      ANDROID_ART_ROOT = ANDROID_ART_ROOT,
+      ANDROID_TZDATA_ROOT = ANDROID_TZDATA_ROOT,
+      ANDROID_LOG_TAGS = ANDROID_LOG_TAGS,
+      LD_LIBRARY_PATH = LD_LIBRARY_PATH,
+      NATIVELOADER_DEFAULT_NAMESPACE_LIBS = NATIVELOADER_DEFAULT_NAMESPACE_LIBS,
+      PATH = f"{PREPEND_TARGET_PATH}:$PATH",
+    )
+
+    if USE_GDB or USE_GDBSERVER:
+      print(f"Forward {GDBSERVER_PORT} to local port and connect GDB")
+
+    ctx.run(f"rm -rf {DEX_LOCATION}/{{oat,dalvik-cache}}/ && mkdir -p {mkdir_locations}")
+    ctx.run(f"{profman_cmdline}")
+    ctx.run(f"{dex2oat_cmdline}", desc="Dex2oat")
+    ctx.run(f"{dm_cmdline}")
+    ctx.run(f"{vdex_cmdline}")
+    ctx.run(f"{strip_cmdline}")
+    ctx.run(f"{sync_cmdline}")
+    ctx.run(tee(f"{timeout_prefix} {dalvikvm_cmdline}"),
+            expected_exit_code=args.expected_exit_code, desc="DalvikVM")
+
+    if ON_VM:
+      filter_output()
+
+  else:
+    # Host run.
+    if USE_ZIPAPEX or USE_EXRACTED_ZIPAPEX:
+      # Put the zipapex files in front of the ld-library-path
+      LD_LIBRARY_PATH = f"{ANDROID_DATA}/zipapex/{LIBRARY_DIRECTORY}:{ANDROID_ROOT}/{TEST_DIRECTORY}"
+    else:
+      LD_LIBRARY_PATH = f"{ANDROID_ROOT}/{LIBRARY_DIRECTORY}:{ANDROID_ROOT}/{TEST_DIRECTORY}"
+
+    ctx.export(
+      ANDROID_PRINTF_LOG = "brief",
+      ASAN_OPTIONS = RUN_TEST_ASAN_OPTIONS,
+      ANDROID_DATA = DEX_LOCATION,
+      DEX_LOCATION = DEX_LOCATION,
+      ANDROID_ROOT = ANDROID_ROOT,
+      ANDROID_I18N_ROOT = ANDROID_I18N_ROOT,
+      ANDROID_ART_ROOT = ANDROID_ART_ROOT,
+      ANDROID_TZDATA_ROOT = ANDROID_TZDATA_ROOT,
+      ANDROID_LOG_TAGS = ANDROID_LOG_TAGS,
+      LD_LIBRARY_PATH = LD_LIBRARY_PATH,
+      PATH = f"{PATH}:{ANDROID_ART_BIN_DIR}",
+      # Temporarily disable address space layout randomization (ASLR).
+      # This is needed on the host so that the linker loads core.oat at the necessary address.
+      LD_USE_LOAD_BIAS = "1",
+      TERM = os.environ.get("TERM", ""),  # Needed for GDB
+    )
+
+    cmdline = dalvikvm_cmdline
+
+    if TIME_OUT == "gdb":
+      if run("uname").stdout.strip() == "Darwin":
+        # Fall back to timeout on Mac.
+        TIME_OUT = "timeout"
+      elif ISA == "x86":
+        # prctl call may fail in 32-bit on an older (3.2) 64-bit Linux kernel. Fall back to timeout.
+        TIME_OUT = "timeout"
+      else:
+        # Check if gdb is available.
+        proc = run('gdb --eval-command="quit"', check=False, save_cmd=False)
+        if proc.returncode != 0:
+          # gdb isn't available. Fall back to timeout.
+          TIME_OUT = "timeout"
+
+    if TIME_OUT == "timeout":
+      # Add timeout command if time out is desired.
+      #
+      # Note: We first send SIGTERM (the timeout default, signal 15) to the signal dumper, which
+      #       will induce a full thread dump before killing the process. To ensure any issues in
+      #       dumping do not lead to a deadlock, we also use the "-k" option to definitely kill the
+      #       child.
+      # Note: Using "--foreground" to not propagate the signal to children, i.e., the runtime.
+      cmdline = f"timeout --foreground -k 120s {TIME_OUT_VALUE}s {TIMEOUT_DUMPER} -s 15 {cmdline}"
+
+    os.chdir(ANDROID_BUILD_TOP)
+
+    # Make sure we delete any existing compiler artifacts.
+    # This enables tests to call the RUN script multiple times in a row
+    # without worrying about interference.
+    ctx.run(f"rm -rf {DEX_LOCATION}/{{oat,dalvik-cache}}/")
+
+    ctx.run(f"mkdir -p {mkdir_locations}")
+    ctx.run(setupapex_cmdline)
+    if USE_EXTRACTED_ZIPAPEX:
+      ctx.run(installapex_cmdline)
+    ctx.run(linkroot_cmdline)
+    ctx.run(linkroot_overlay_cmdline)
+    ctx.run(profman_cmdline)
+    ctx.run(dex2oat_cmdline, desc="Dex2oat")
+    ctx.run(dm_cmdline)
+    ctx.run(vdex_cmdline)
+    ctx.run(strip_cmdline)
+    ctx.run(sync_cmdline)
+
+    if DRY_RUN:
+      return
+
+    if USE_GDB:
+      # When running under gdb, we cannot do piping and grepping...
+      ctx.run(cmdline)
+    else:
+      ctx.run(tee(cmdline), expected_exit_code=args.expected_exit_code, desc="DalvikVM")
+      filter_output()
diff --git a/test/dexpreopt/Android.bp b/test/dexpreopt/Android.bp
index b39565b..f5ffd89 100644
--- a/test/dexpreopt/Android.bp
+++ b/test/dexpreopt/Android.bp
@@ -36,5 +36,5 @@
         "libgmock",
         "libprocinfo",
     ],
-    test_config_template: "//art/test:art-gtests-target-standalone-root-template",
+    test_config_template: "art_standalone_dexpreopt_tests.xml",
 }
diff --git a/test/dexpreopt/art_standalone_dexpreopt_tests.xml b/test/dexpreopt/art_standalone_dexpreopt_tests.xml
new file mode 100644
index 0000000..873b6a2
--- /dev/null
+++ b/test/dexpreopt/art_standalone_dexpreopt_tests.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<!-- Note: This test config file for {MODULE} is generated from a template. -->
+<configuration description="Runs {MODULE} as root.">
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}/{MODULE}" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp/{MODULE}" />
+        <option name="module-name" value="{MODULE}" />
+        <option name="ld-library-path-32" value="/apex/com.android.art/lib" />
+        <option name="ld-library-path-64" value="/apex/com.android.art/lib64" />
+    </test>
+
+    <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+         one of the Mainline modules below is present on the device used for testing. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <!-- ART Mainline Module (internal version). -->
+        <option name="mainline-module-package-name" value="com.google.android.art" />
+        <!-- ART Mainline Module (external (AOSP) version). -->
+        <option name="mainline-module-package-name" value="com.android.art" />
+    </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/test/dexpreopt/dexpreopt_test.cc b/test/dexpreopt/dexpreopt_test.cc
index 55593ea..9641e0a 100644
--- a/test/dexpreopt/dexpreopt_test.cc
+++ b/test/dexpreopt/dexpreopt_test.cc
@@ -46,7 +46,8 @@
 
 namespace art {
 
-using ::testing::IsSubsetOf;
+using ::android::base::Error;
+using ::testing::IsSupersetOf;
 
 constexpr const char* kZygote32 = "zygote";
 constexpr const char* kZygote64 = "zygote64";
@@ -99,6 +100,12 @@
   if (jars.empty()) {
     return Errorf("Environment variable `DEX2OATBOOTCLASSPATH` is not defined or empty");
   }
+  std::string error_msg;
+  std::string first_mainline_jar = GetFirstMainlineFrameworkLibraryFilename(&error_msg);
+  if (first_mainline_jar.empty()) {
+    return Error() << error_msg;
+  }
+  jars.push_back(std::move(first_mainline_jar));
   std::string art_root = GetArtRoot();
   std::string android_root = GetAndroidRoot();
   std::vector<std::string> artifacts;
@@ -218,7 +225,7 @@
         GetZygoteMappedOatFiles(zygote_name);
     ASSERT_RESULT_OK(mapped_oat_files);
 
-    EXPECT_THAT(expected_artifacts.value(), IsSubsetOf(mapped_oat_files.value()));
+    EXPECT_THAT(mapped_oat_files.value(), IsSupersetOf(expected_artifacts.value()));
   }
 }
 
@@ -236,7 +243,7 @@
       GetSystemServerArtifactsMappedOdexes();
   ASSERT_RESULT_OK(mapped_odexes);
 
-  EXPECT_THAT(expected_artifacts.value(), IsSubsetOf(mapped_odexes.value()));
+  EXPECT_THAT(mapped_odexes.value(), IsSupersetOf(expected_artifacts.value()));
 }
 
 }  // namespace art
diff --git a/test/etc/apex-bootclasspath-utils.sh b/test/etc/apex-bootclasspath-utils.sh
deleted file mode 100755
index 5a0873d..0000000
--- a/test/etc/apex-bootclasspath-utils.sh
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-# This file contains utils for constructing -Xbootclasspath and -Xbootclasspath-location
-# for dex2oat and dalvikvm from apex modules list.
-#
-# Those utils could be used outside of art/test/ to run ART in chroot setup.
-
-# Note: This must start with the CORE_IMG_JARS in Android.common_path.mk
-# because that's what we use for compiling the boot.art image.
-# It may contain additional modules from TEST_CORE_JARS.
-readonly bpath_modules="core-oj core-libart okhttp bouncycastle apache-xml core-icu4j conscrypt"
-
-# Helper function to construct paths for apex modules (for both -Xbootclasspath and
-# -Xbootclasspath-location).
-#
-#  Arguments.
-#   ${1}: path prefix.
-get_apex_bootclasspath_impl() {
-  local -r bpath_prefix="$1"
-  local bpath_separator=""
-  local bpath=""
-  local bpath_jar=""
-  for bpath_module in ${bpath_modules}; do
-    local apex_module="com.android.art"
-    case "$bpath_module" in
-      (conscrypt)  apex_module="com.android.conscrypt";;
-      (core-icu4j) apex_module="com.android.i18n";;
-      (*)          apex_module="com.android.art";;
-    esac
-    bpath_jar="/apex/${apex_module}/javalib/${bpath_module}.jar"
-    bpath+="${bpath_separator}${bpath_prefix}${bpath_jar}"
-    bpath_separator=":"
-  done
-  echo "${bpath}"
-}
-
-# Gets a -Xbootclasspath paths with the apex modules.
-#
-#  Arguments.
-#   ${1}: host (y|n).
-get_apex_bootclasspath() {
-  local -r host="${1}"
-  local bpath_prefix=""
-
-  if [[ "${host}" == "y" ]]; then
-    bpath_prefix="${ANDROID_HOST_OUT}"
-  fi
-
-  get_apex_bootclasspath_impl "${bpath_prefix}"
-}
-
-# Gets a -Xbootclasspath-location paths with the apex modules.
-#
-#  Arguments.
-#   ${1}: host (y|n).
-get_apex_bootclasspath_locations() {
-  local -r host="${1}"
-  local bpath_location_prefix=""
-
-  if [[ "${host}" == "y" ]]; then
-    if [[ "${ANDROID_HOST_OUT:0:${#ANDROID_BUILD_TOP}+1}" == "${ANDROID_BUILD_TOP}/" ]]; then
-      bpath_location_prefix="${ANDROID_HOST_OUT:${#ANDROID_BUILD_TOP}+1}"
-    else
-      error_msg "ANDROID_BUILD_TOP/ is not a prefix of ANDROID_HOST_OUT"\
-                "\nANDROID_BUILD_TOP=${ANDROID_BUILD_TOP}"\
-                "\nANDROID_HOST_OUT=${ANDROID_HOST_OUT}"
-      exit
-    fi
-  fi
-
-  get_apex_bootclasspath_impl "${bpath_location_prefix}"
-}
diff --git a/test/etc/default-build b/test/etc/default-build
deleted file mode 100755
index 26820f2..0000000
--- a/test/etc/default-build
+++ /dev/null
@@ -1,430 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021 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.
-
-"""This is the default build script for run-tests.
-
-It can be overwrite by specific run-tests if needed.
-It is used from soong build and not intended to be called directly.
-"""
-
-import argparse
-import functools
-import glob
-import os
-from os import path
-import shlex
-import shutil
-import subprocess
-import tempfile
-import zipfile
-
-if not os.sys.argv:
-  print(
-      'Error: default-build should have the parameters from the "build" script forwarded to it'
-  )
-  print('Error: An example of how do it correctly is ./default-build "$@"')
-  os.sys.exit(1)
-
-
-def parse_bool(text):
-  return {"true": True, "false": False}[text.lower()]
-
-
-TEST_NAME = os.environ["TEST_NAME"]
-ART_TEST_RUN_TEST_BOOTCLASSPATH = os.environ["ART_TEST_RUN_TEST_BOOTCLASSPATH"]
-NEED_DEX = parse_bool(os.environ["NEED_DEX"])
-
-# Set default values for directories.
-HAS_SMALI = path.exists("smali")
-HAS_JASMIN = path.exists("jasmin")
-HAS_SRC = path.exists("src")
-HAS_SRC_ART = path.exists("src-art")
-HAS_SRC2 = path.exists("src2")
-HAS_SRC_MULTIDEX = path.exists("src-multidex")
-HAS_SMALI_MULTIDEX = path.exists("smali-multidex")
-HAS_JASMIN_MULTIDEX = path.exists("jasmin-multidex")
-HAS_SMALI_EX = path.exists("smali-ex")
-HAS_SRC_EX = path.exists("src-ex")
-HAS_SRC_EX2 = path.exists("src-ex2")
-HAS_SRC_AOTEX = path.exists("src-aotex")
-HAS_SRC_BCPEX = path.exists("src-bcpex")
-HAS_HIDDENAPI_SPEC = path.exists("hiddenapi-flags.csv")
-
-# USE_HIDDENAPI=false run-test... will disable hiddenapi.
-USE_HIDDENAPI = parse_bool(os.environ.get("USE_HIDDENAPI", "true"))
-
-# USE_DESUGAR=false run-test... will disable desugaring.
-USE_DESUGAR = parse_bool(os.environ.get("USE_DESUGAR", "true"))
-
-JAVAC_ARGS = shlex.split(os.environ.get("JAVAC_ARGS", ""))
-SMALI_ARGS = shlex.split(os.environ.get("SMALI_ARGS", ""))
-D8_FLAGS = shlex.split(os.environ.get("D8_FLAGS", ""))
-
-# Allow overriding ZIP_COMPRESSION_METHOD with e.g. 'store'
-ZIP_COMPRESSION_METHOD = "deflate"
-# Align every ZIP file made by calling $ZIPALIGN command?
-ZIP_ALIGN_BYTES = None
-
-DEV_MODE = False
-BUILD_MODE = "target"
-API_LEVEL = None
-DEFAULT_EXPERIMENT = "no-experiment"
-EXPERIMENTAL = DEFAULT_EXPERIMENT
-
-# Setup experimental API level mappings in a bash associative array.
-EXPERIMENTAL_API_LEVEL = {}
-EXPERIMENTAL_API_LEVEL[DEFAULT_EXPERIMENT] = "26"
-EXPERIMENTAL_API_LEVEL["default-methods"] = "24"
-EXPERIMENTAL_API_LEVEL["parameter-annotations"] = "25"
-EXPERIMENTAL_API_LEVEL["agents"] = "26"
-EXPERIMENTAL_API_LEVEL["method-handles"] = "26"
-EXPERIMENTAL_API_LEVEL["var-handles"] = "28"
-
-# Parse command line arguments.
-opt_bool = argparse.BooleanOptionalAction  # Bool also accepts the --no- prefix.
-parser = argparse.ArgumentParser(description=__doc__)
-parser.add_argument("--src", dest="HAS_SRC", action=opt_bool)
-parser.add_argument("--src2", dest="HAS_SRC2", action=opt_bool)
-parser.add_argument("--src-multidex", dest="HAS_SRC_MULTIDEX", action=opt_bool)
-parser.add_argument(
-    "--smali-multidex", dest="HAS_SMALI_MULTIDEX", action=opt_bool)
-parser.add_argument("--src-ex", dest="HAS_SRC_EX", action=opt_bool)
-parser.add_argument("--src-ex2", dest="HAS_SRC_EX2", action=opt_bool)
-parser.add_argument("--smali", dest="HAS_SMALI", action=opt_bool)
-parser.add_argument("--jasmin", dest="HAS_JASMIN", action=opt_bool)
-parser.add_argument("--api-level", dest="API_LEVEL", type=int)
-parser.add_argument(
-    "--experimental", dest="EXPERIMENTAL", type=str)
-parser.add_argument(
-    "--zip-compression-method", dest="ZIP_COMPRESSION_METHOD", type=str)
-parser.add_argument("--zip-align", dest="ZIP_ALIGN_BYTES", type=int)
-parser.add_argument(
-    "--host", dest="BUILD_MODE", action="store_const", const="host")
-parser.add_argument(
-    "--target", dest="BUILD_MODE", action="store_const", const="target")
-parser.add_argument(
-    "--jvm", dest="BUILD_MODE", action="store_const", const="jvm")
-parser.add_argument("--dev", dest="DEV_MODE", action=opt_bool)
-# Update variables with command line arguments that were set.
-globals().update(
-    {k: v for k, v in parser.parse_args().__dict__.items() if v is not None})
-
-if BUILD_MODE == "jvm":
-  # No desugaring on jvm because it supports the latest functionality.
-  USE_DESUGAR = False
-  # Do not attempt to build src-art directories on jvm,
-  # since it would fail without libcore.
-  HAS_SRC_ART = False
-
-# Set API level for smali and d8.
-if not API_LEVEL:
-  API_LEVEL = EXPERIMENTAL_API_LEVEL[EXPERIMENTAL]
-
-# Add API level arguments to smali and dx
-SMALI_ARGS.extend(["--api", str(API_LEVEL)])
-D8_FLAGS.extend(["--min-api", str(API_LEVEL)])
-
-
-def run(executable, args):
-  cmd = shlex.split(executable) + args
-  if executable.endswith(".sh"):
-    cmd = ["/bin/bash"] + cmd
-  if DEV_MODE:
-    print("Run:", " ".join(cmd))
-  p = subprocess.run(cmd, check=True)
-  if p.returncode != 0:
-    raise Exception("Failed command: " + " ".join(cmd))
-
-
-# Helper functions to execute tools.
-soong_zip = functools.partial(run, os.environ["SOONG_ZIP"])
-zipalign = functools.partial(run, os.environ["ZIPALIGN"])
-javac = functools.partial(run, os.environ["JAVAC"])
-jasmin = functools.partial(run, os.environ["JASMIN"])
-smali = functools.partial(run, os.environ["SMALI"])
-d8 = functools.partial(run, os.environ["D8"])
-hiddenapi = functools.partial(run, os.environ["HIDDENAPI"])
-
-# If wrapper script exists, use it instead of the default javac.
-if os.path.exists("javac_wrapper.sh"):
-  javac = functools.partial(run, "javac_wrapper.sh")
-
-def find(root, name):
-  return sorted(glob.glob(path.join(root, "**", name), recursive=True))
-
-
-def zip(zip_target, *files):
-  zip_args = ["-o", zip_target]
-  if ZIP_COMPRESSION_METHOD == "store":
-    zip_args.extend(["-L", "0"])
-  for f in files:
-    zip_args.extend(["-f", f])
-  soong_zip(zip_args)
-
-  if ZIP_ALIGN_BYTES:
-    # zipalign does not operate in-place, so write results to a temp file.
-    with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
-      tmp_file = path.join(tmp_dir, "aligned.zip")
-      zipalign(["-f", str(ZIP_ALIGN_BYTES), zip_target, tmp_file])
-      # replace original zip target with our temp file.
-      os.rename(tmp_file, zip_target)
-
-
-def make_jasmin(out_directory, jasmin_sources):
-  os.makedirs(out_directory, exist_ok=True)
-  jasmin(["-d", out_directory] + sorted(jasmin_sources))
-
-
-# Like regular javac but may include libcore on the bootclasspath.
-def javac_with_bootclasspath(args):
-  flags = JAVAC_ARGS + ["-encoding", "utf8"]
-  if BUILD_MODE != "jvm":
-    flags.extend(["-bootclasspath", ART_TEST_RUN_TEST_BOOTCLASSPATH])
-  javac(flags + args)
-
-
-# Make a "dex" file given a directory of classes. This will be
-# packaged in a jar file.
-def make_dex(name):
-  d8_inputs = find(name, "*.class")
-  d8_output = name + ".jar"
-  dex_output = name + ".dex"
-  if USE_DESUGAR:
-    flags = ["--lib", ART_TEST_RUN_TEST_BOOTCLASSPATH]
-  else:
-    flags = ["--no-desugaring"]
-  assert d8_inputs
-  d8(D8_FLAGS + flags + ["--output", d8_output] + d8_inputs)
-
-  # D8 outputs to JAR files today rather than DEX files as DX used
-  # to. To compensate, we extract the DEX from d8's output to meet the
-  # expectations of make_dex callers.
-  with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
-    zipfile.ZipFile(d8_output, "r").extractall(tmp_dir)
-    os.rename(path.join(tmp_dir, "classes.dex"), dex_output)
-
-
-# Merge all the dex files.
-# Skip non-existing files, but at least 1 file must exist.
-def make_dexmerge(*dex_files_to_merge):
-  # Dex file that acts as the destination.
-  dst_file = dex_files_to_merge[0]
-
-  # Skip any non-existing files.
-  dex_files_to_merge = list(filter(path.exists, dex_files_to_merge))
-
-  # NB: We merge even if there is just single input.
-  # It is useful to normalize non-deterministic smali output.
-
-  with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
-    d8(["--min-api", API_LEVEL, "--output", tmp_dir] + dex_files_to_merge)
-    assert not path.exists(path.join(tmp_dir, "classes2.dex"))
-    for input_dex in dex_files_to_merge:
-      os.remove(input_dex)
-    os.rename(path.join(tmp_dir, "classes.dex"), dst_file)
-
-
-def make_hiddenapi(*dex_files):
-  args = ["encode"]
-  for dex_file in dex_files:
-    args.extend(["--input-dex=" + dex_file, "--output-dex=" + dex_file])
-  args.append("--api-flags=hiddenapi-flags.csv")
-  args.append("--no-force-assign-all")
-  hiddenapi(args)
-
-
-if path.exists("classes.dex"):
-  zip(TEST_NAME + ".jar", "classes.dex")
-  os.sys.exit(0)
-
-
-def has_multidex():
-  return HAS_SRC_MULTIDEX or HAS_JASMIN_MULTIDEX or HAS_SMALI_MULTIDEX
-
-
-def add_to_cp_args(old_cp_args, path):
-  if len(old_cp_args) == 0:
-    return ["-cp", path]
-  else:
-    return ["-cp", old_cp_args[1] + ":" + path]
-
-
-src_tmp_all = []
-
-if HAS_JASMIN:
-  make_jasmin("jasmin_classes", find("jasmin", "*.j"))
-  src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes")
-
-if HAS_JASMIN_MULTIDEX:
-  make_jasmin("jasmin_classes2", find("jasmin-multidex", "*.j"))
-  src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes2")
-
-if HAS_SRC and (HAS_SRC_MULTIDEX or HAS_SRC_AOTEX or HAS_SRC_BCPEX or
-                HAS_SRC_EX or HAS_SRC_ART or HAS_SRC2 or HAS_SRC_EX2):
-  # To allow circular references, compile src/, src-multidex/, src-aotex/,
-  # src-bcpex/, src-ex/ together and pass the output as class path argument.
-  # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols
-  # used by the other src-* sources we compile here but everything needed to
-  # compile the other src-* sources should be present in src/ (and jasmin*/).
-  os.makedirs("classes-tmp-all")
-  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
-                           ["-d", "classes-tmp-all"] +
-                           find("src", "*.java") +
-                           find("src-multidex", "*.java") +
-                           find("src-aotex", "*.java") +
-                           find("src-bcpex", "*.java") +
-                           find("src-ex", "*.java"))
-  src_tmp_all = add_to_cp_args(src_tmp_all, "classes-tmp-all")
-
-if HAS_SRC_AOTEX:
-  os.makedirs("classes-aotex")
-  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
-                           ["-d", "classes-aotex"] +
-                           find("src-aotex", "*.java"))
-  if NEED_DEX:
-    make_dex("classes-aotex")
-    # rename it so it shows up as "classes.dex" in the zip file.
-    os.rename("classes-aotex.dex", "classes.dex")
-    zip(TEST_NAME + "-aotex.jar", "classes.dex")
-
-if HAS_SRC_BCPEX:
-  os.makedirs("classes-bcpex")
-  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
-                           ["-d", "classes-bcpex"] +
-                           find("src-bcpex", "*.java"))
-  if NEED_DEX:
-    make_dex("classes-bcpex")
-    # rename it so it shows up as "classes.dex" in the zip file.
-    os.rename("classes-bcpex.dex", "classes.dex")
-    zip(TEST_NAME + "-bcpex.jar", "classes.dex")
-
-if HAS_SRC:
-  os.makedirs("classes", exist_ok=True)
-  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
-                           ["-d", "classes"] + find("src", "*.java"))
-
-if HAS_SRC_ART:
-  os.makedirs("classes", exist_ok=True)
-  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
-                           ["-d", "classes"] + find("src-art", "*.java"))
-
-if HAS_SRC_MULTIDEX:
-  os.makedirs("classes2")
-  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
-                           ["-d", "classes2"] +
-                           find("src-multidex", "*.java"))
-  if NEED_DEX:
-    make_dex("classes2")
-
-if HAS_SRC2:
-  os.makedirs("classes", exist_ok=True)
-  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
-                           ["-d", "classes"] +
-                           find("src2", "*.java"))
-
-# If the classes directory is not-empty, package classes in a DEX file.
-# NB: some tests provide classes rather than java files.
-if find("classes", "*"):
-  if NEED_DEX:
-    make_dex("classes")
-
-if HAS_JASMIN:
-  # Compile Jasmin classes as if they were part of the classes.dex file.
-  if NEED_DEX:
-    make_dex("jasmin_classes")
-    make_dexmerge("classes.dex", "jasmin_classes.dex")
-  else:
-    # Move jasmin classes into classes directory so that they are picked up
-    # with -cp classes.
-    os.makedirs("classes", exist_ok=True)
-    shutil.copytree("jasmin_classes", "classes", dirs_exist_ok=True)
-
-if HAS_SMALI and NEED_DEX:
-  # Compile Smali classes
-  smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
-        ["--output", "smali_classes.dex"] + find("smali", "*.smali"))
-  assert path.exists("smali_classes.dex")
-  # Merge smali files into classes.dex,
-  # this takes priority over any jasmin files.
-  make_dexmerge("classes.dex", "smali_classes.dex")
-
-# Compile Jasmin classes in jasmin-multidex as if they were part of
-# the classes2.jar
-if HAS_JASMIN_MULTIDEX:
-  if NEED_DEX:
-    make_dex("jasmin_classes2")
-    make_dexmerge("classes2.dex", "jasmin_classes2.dex")
-  else:
-    # Move jasmin classes into classes2 directory so that
-    # they are picked up with -cp classes2.
-    os.makedirs("classes2", exist_ok=True)
-    shutil.copytree("jasmin_classes2", "classes2", dirs_exist_ok=True)
-    shutil.rmtree("jasmin_classes2")
-
-if HAS_SMALI_MULTIDEX and NEED_DEX:
-  # Compile Smali classes
-  smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
-        ["--output", "smali_classes2.dex"] + find("smali-multidex", "*.smali"))
-
-  # Merge smali_classes2.dex into classes2.dex
-  make_dexmerge("classes2.dex", "smali_classes2.dex")
-
-if HAS_SRC_EX:
-  os.makedirs("classes-ex", exist_ok=True)
-  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
-                           ["-d", "classes-ex"] + find("src-ex", "*.java"))
-
-if HAS_SRC_EX2:
-  os.makedirs("classes-ex", exist_ok=True)
-  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
-                           ["-d", "classes-ex"] + find("src-ex2", "*.java"))
-
-if path.exists("classes-ex") and NEED_DEX:
-  make_dex("classes-ex")
-
-if HAS_SMALI_EX and NEED_DEX:
-  # Compile Smali classes
-  smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
-        ["--output", "smali_classes-ex.dex"] + find("smali-ex", "*.smali"))
-  assert path.exists("smali_classes-ex.dex")
-  # Merge smali files into classes-ex.dex.
-  make_dexmerge("classes-ex.dex", "smali_classes-ex.dex")
-
-if path.exists("classes-ex.dex"):
-  # Apply hiddenapi on the dex files if the test has API list file(s).
-  if USE_HIDDENAPI and HAS_HIDDENAPI_SPEC:
-    make_hiddenapi("classes-ex.dex")
-
-  # quick shuffle so that the stored name is "classes.dex"
-  os.rename("classes.dex", "classes-1.dex")
-  os.rename("classes-ex.dex", "classes.dex")
-  zip(TEST_NAME + "-ex.jar", "classes.dex")
-  os.rename("classes.dex", "classes-ex.dex")
-  os.rename("classes-1.dex", "classes.dex")
-
-# Apply hiddenapi on the dex files if the test has API list file(s).
-if NEED_DEX and USE_HIDDENAPI and HAS_HIDDENAPI_SPEC:
-  if has_multidex():
-    make_hiddenapi("classes.dex", "classes2.dex")
-  else:
-    make_hiddenapi("classes.dex")
-
-# Create a single dex jar with two dex files for multidex.
-if NEED_DEX:
-  if path.exists("classes2.dex"):
-    zip(TEST_NAME + ".jar", "classes.dex", "classes2.dex")
-  else:
-    zip(TEST_NAME + ".jar", "classes.dex")
diff --git a/test/etc/default-check b/test/etc/default-check
deleted file mode 100755
index f6f7bf4..0000000
--- a/test/etc/default-check
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Inputs:
-# $1: Test's expected standard output
-# $2: Test's actual standard output
-# $3: Test's expected standard error
-# $4: Test's actual standard error
-
-diff --strip-trailing-cr -q "$1" "$2" >/dev/null \
-  && diff --strip-trailing-cr -q "$3" "$4" >/dev/null
diff --git a/test/etc/default-run b/test/etc/default-run
deleted file mode 100755
index ecbbbc7..0000000
--- a/test/etc/default-run
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2008 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.
-
-exec ${RUN} "$@"
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
deleted file mode 100755
index ef168ca..0000000
--- a/test/etc/run-test-jar
+++ /dev/null
@@ -1,1546 +0,0 @@
-#!/bin/bash
-#
-# Runner for an individual run-test.
-
-readonly local_path=$(dirname "$0")
-source "${local_path}/apex-bootclasspath-utils.sh"
-
-# Check how many colors the terminal can display.
-ncolors=$(tput colors 2>/dev/null)
-
-# Check that stdout is connected to a terminal and that we have at least 1 color.
-# This ensures that if the stdout is not connected to a terminal and instead
-# the stdout will be used for a log, it will not append the color characters.
-if [[ -t 1 && ${ncolors} && ${ncolors} -ge 1 ]]; then
-  bold_red="$(tput bold)$(tput setaf 1)"
-fi
-
-readonly bold_red
-
-error_msg() {
-  echo -e "${bold_red}ERROR: $@" 1>&2
-}
-
-if [[ -z "$ANDROID_BUILD_TOP" ]]; then
-  error_msg 'ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?'
-  exit 1
-fi
-
-msg() {
-    if [ "$QUIET" = "n" ]; then
-        echo "$@"
-    fi
-}
-
-ANDROID_ROOT="/system"
-ANDROID_ART_ROOT="/apex/com.android.art"
-ANDROID_I18N_ROOT="/apex/com.android.i18n"
-ANDROID_TZDATA_ROOT="/apex/com.android.tzdata"
-ARCHITECTURES_32="(arm|x86|none)"
-ARCHITECTURES_64="(arm64|x86_64|none)"
-ARCHITECTURES_PATTERN="${ARCHITECTURES_32}"
-GET_DEVICE_ISA_BITNESS_FLAG="--32"
-BOOT_IMAGE=""
-CHROOT=
-COMPILE_FLAGS=""
-DALVIKVM="dalvikvm32"
-DEBUGGER="n"
-WITH_AGENT=()
-DEBUGGER_AGENT=""
-WRAP_DEBUGGER_AGENT="n"
-DEV_MODE="n"
-DEX2OAT_NDEBUG_BINARY="dex2oat32"
-DEX2OAT_DEBUG_BINARY="dex2oatd32"
-EXPERIMENTAL=""
-FALSE_BIN="false"
-FLAGS=""
-ANDROID_FLAGS=""
-GDB=""
-GDB_ARGS=""
-GDB_DEX2OAT=""
-GDB_DEX2OAT_ARGS=""
-GDBSERVER_DEVICE="gdbserver"
-GDBSERVER_HOST="gdbserver"
-HAVE_IMAGE="y"
-HOST="n"
-BIONIC="n"
-CREATE_ANDROID_ROOT="n"
-USE_ZIPAPEX="n"
-ZIPAPEX_LOC=""
-USE_EXTRACTED_ZIPAPEX="n"
-EXTRACTED_ZIPAPEX_LOC=""
-INTERPRETER="n"
-JIT="n"
-INVOKE_WITH=""
-IS_JVMTI_TEST="n"
-ADD_LIBDIR_ARGUMENTS="n"
-SUFFIX64=""
-ISA=x86
-LIBRARY_DIRECTORY="lib"
-TEST_DIRECTORY="nativetest"
-MAIN=""
-OPTIMIZE="y"
-PREBUILD="y"
-QUIET="n"
-RELOCATE="n"
-SECONDARY_DEX=""
-TIME_OUT="n"  # "n" (disabled), "timeout" (use timeout), "gdb" (use gdb)
-TIMEOUT_DUMPER=signal_dumper
-# Values in seconds.
-TIME_OUT_EXTRA=0
-TIME_OUT_VALUE=
-USE_GDB="n"
-USE_GDBSERVER="n"
-GDBSERVER_PORT=":5039"
-USE_GDB_DEX2OAT="n"
-USE_JVM="n"
-USE_JVMTI="n"
-VERIFY="y" # y=yes,n=no,s=softfail
-ZYGOTE=""
-DEX_VERIFY=""
-INSTRUCTION_SET_FEATURES=""
-ARGS=""
-VDEX_ARGS=""
-EXTERNAL_LOG_TAGS="n" # if y respect externally set ANDROID_LOG_TAGS.
-DRY_RUN="n" # if y prepare to run the test but don't run it.
-TEST_VDEX="n"
-TEST_DEX2OAT_DM="n"
-TEST_RUNTIME_DM="n"
-TEST_IS_NDEBUG="n"
-APP_IMAGE="y"
-SECONDARY_APP_IMAGE="y"
-SECONDARY_CLASS_LOADER_CONTEXT=""
-SECONDARY_COMPILATION="y"
-JVMTI_STRESS="n"
-JVMTI_STEP_STRESS="n"
-JVMTI_FIELD_STRESS="n"
-JVMTI_TRACE_STRESS="n"
-JVMTI_REDEFINE_STRESS="n"
-PROFILE="n"
-RANDOM_PROFILE="n"
-# The normal dex2oat timeout.
-DEX2OAT_TIMEOUT="300" # 5 mins
-# The *hard* timeout where we really start trying to kill the dex2oat.
-DEX2OAT_RT_TIMEOUT="360" # 6 mins
-CREATE_RUNNER="n"
-
-# if "y", run 'sync' before dalvikvm to make sure all files from
-# build step (e.g. dex2oat) were finished writing.
-SYNC_BEFORE_RUN="n"
-
-# When running a debug build, we want to run with all checks.
-ANDROID_FLAGS="${ANDROID_FLAGS} -XX:SlowDebug=true"
-# The same for dex2oatd, both prebuild and runtime-driven.
-ANDROID_FLAGS="${ANDROID_FLAGS} -Xcompiler-option --runtime-arg -Xcompiler-option -XX:SlowDebug=true"
-COMPILER_FLAGS="${COMPILER_FLAGS} --runtime-arg -XX:SlowDebug=true"
-
-# Let the compiler and runtime know that we are running tests.
-COMPILE_FLAGS="${COMPILE_FLAGS} --compile-art-test"
-ANDROID_FLAGS="${ANDROID_FLAGS} -Xcompiler-option --compile-art-test"
-
-while true; do
-    if [ "x$1" = "x--quiet" ]; then
-        QUIET="y"
-        shift
-    elif [ "x$1" = "x--dex2oat-rt-timeout" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            error_msg "$0 missing argument to --dex2oat-rt-timeout"
-            exit 1
-        fi
-        DEX2OAT_RT_TIMEOUT="$1"
-        shift
-    elif [ "x$1" = "x--dex2oat-timeout" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            error_msg "$0 missing argument to --dex2oat-timeout"
-            exit 1
-        fi
-        DEX2OAT_TIMEOUT="$1"
-        shift
-    elif [ "x$1" = "x--jvmti" ]; then
-        USE_JVMTI="y"
-        IS_JVMTI_TEST="y"
-        # Secondary images block some tested behavior.
-        SECONDARY_APP_IMAGE="n"
-        shift
-    elif [ "x$1" = "x--add-libdir-argument" ]; then
-        ADD_LIBDIR_ARGUMENTS="y"
-        shift
-    elif [ "x$1" = "x-O" ]; then
-        TEST_IS_NDEBUG="y"
-        shift
-    elif [ "x$1" = "x--lib" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            error_msg "$0 missing argument to --lib"
-            exit 1
-        fi
-        LIB="$1"
-        shift
-    elif [ "x$1" = "x--gc-stress" ]; then
-        # Give an extra 20 mins if we are gc-stress.
-        TIME_OUT_EXTRA=$((${TIME_OUT_EXTRA} + 1200))
-        shift
-    elif [ "x$1" = "x--testlib" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            error_msg "$0 missing argument to --testlib"
-            exit 1
-        fi
-        ARGS="${ARGS} $1"
-        shift
-    elif [ "x$1" = "x--args" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            error_msg "$0 missing argument to --args"
-            exit 1
-        fi
-        ARGS="${ARGS} $1"
-        shift
-    elif [ "x$1" = "x--compiler-only-option" ]; then
-        shift
-        option="$1"
-        COMPILE_FLAGS="${COMPILE_FLAGS} $option"
-        shift
-    elif [ "x$1" = "x-Xcompiler-option" ]; then
-        shift
-        option="$1"
-        FLAGS="${FLAGS} -Xcompiler-option $option"
-        COMPILE_FLAGS="${COMPILE_FLAGS} $option"
-        shift
-    elif [ "x$1" = "x--create-runner" ]; then
-        CREATE_RUNNER="y"
-        shift
-    elif [ "x$1" = "x--android-runtime-option" ]; then
-        shift
-        option="$1"
-        ANDROID_FLAGS="${ANDROID_FLAGS} $option"
-        shift
-    elif [ "x$1" = "x--runtime-option" ]; then
-        shift
-        option="$1"
-        FLAGS="${FLAGS} $option"
-        if [ "x$option" = "x-Xmethod-trace" ]; then
-            # Method tracing can slow some tests down a lot.
-            TIME_OUT_EXTRA=$((${TIME_OUT_EXTRA} + 1200))
-        fi
-        shift
-    elif [ "x$1" = "x--boot" ]; then
-        shift
-        BOOT_IMAGE="$1"
-        shift
-    elif [ "x$1" = "x--relocate" ]; then
-        RELOCATE="y"
-        shift
-    elif [ "x$1" = "x--no-relocate" ]; then
-        RELOCATE="n"
-        shift
-    elif [ "x$1" = "x--prebuild" ]; then
-        PREBUILD="y"
-        shift
-    elif [ "x$1" = "x--compact-dex-level" ]; then
-        shift
-        COMPILE_FLAGS="${COMPILE_FLAGS} --compact-dex-level=$1"
-        shift
-    elif [ "x$1" = "x--jvmti-redefine-stress" ]; then
-        # APP_IMAGE doesn't really work with jvmti redefine stress
-        USE_JVMTI="y"
-        APP_IMAGE="n"
-        SECONDARY_APP_IMAGE="n"
-        JVMTI_STRESS="y"
-        JVMTI_REDEFINE_STRESS="y"
-        shift
-    elif [ "x$1" = "x--jvmti-step-stress" ]; then
-        USE_JVMTI="y"
-        JVMTI_STRESS="y"
-        JVMTI_STEP_STRESS="y"
-        shift
-    elif [ "x$1" = "x--jvmti-field-stress" ]; then
-        USE_JVMTI="y"
-        JVMTI_STRESS="y"
-        JVMTI_FIELD_STRESS="y"
-        shift
-    elif [ "x$1" = "x--jvmti-trace-stress" ]; then
-        USE_JVMTI="y"
-        JVMTI_STRESS="y"
-        JVMTI_TRACE_STRESS="y"
-        shift
-    elif [ "x$1" = "x--no-app-image" ]; then
-        APP_IMAGE="n"
-        shift
-    elif [ "x$1" = "x--no-secondary-app-image" ]; then
-        SECONDARY_APP_IMAGE="n"
-        shift
-    elif [ "x$1" = "x--secondary-class-loader-context" ]; then
-        shift
-        SECONDARY_CLASS_LOADER_CONTEXT="$1"
-        shift
-    elif [ "x$1" = "x--no-secondary-compilation" ]; then
-        SECONDARY_COMPILATION="n"
-        shift
-    elif [ "x$1" = "x--host" ]; then
-        HOST="y"
-        ANDROID_ROOT="${ANDROID_HOST_OUT}"
-        ANDROID_ART_ROOT="${ANDROID_HOST_OUT}/com.android.art"
-        ANDROID_I18N_ROOT="${ANDROID_HOST_OUT}/com.android.i18n"
-        ANDROID_TZDATA_ROOT="${ANDROID_HOST_OUT}/com.android.tzdata"
-        # On host, we default to using the symlink, as the PREFER_32BIT
-        # configuration is the only configuration building a 32bit version of
-        # dex2oat.
-        DEX2OAT_DEBUG_BINARY="dex2oatd"
-        DEX2OAT_NDEBUG_BINARY="dex2oat"
-        shift
-    elif [ "x$1" = "x--bionic" ]; then
-        BIONIC="y"
-        # We need to create an ANDROID_ROOT because currently we cannot create
-        # the frameworks/libcore with linux_bionic so we need to use the normal
-        # host ones which are in a different location.
-        CREATE_ANDROID_ROOT="y"
-        shift
-    elif [ "x$1" = "x--runtime-extracted-zipapex" ]; then
-        shift
-        USE_EXTRACTED_ZIPAPEX="y"
-        EXTRACTED_ZIPAPEX_LOC="$1"
-        shift
-    elif [ "x$1" = "x--runtime-zipapex" ]; then
-        shift
-        USE_ZIPAPEX="y"
-        ZIPAPEX_LOC="$1"
-        # TODO (b/119942078): Currently apex does not support
-        # symlink_preferred_arch so we will not have a dex2oatd to execute and
-        # need to manually provide
-        # dex2oatd64.
-        DEX2OAT_DEBUG_BINARY="dex2oatd64"
-        shift
-    elif [ "x$1" = "x--no-prebuild" ]; then
-        PREBUILD="n"
-        shift
-    elif [ "x$1" = "x--no-image" ]; then
-        HAVE_IMAGE="n"
-        shift
-    elif [ "x$1" = "x--secondary" ]; then
-        SECONDARY_DEX=":$DEX_LOCATION/$TEST_NAME-ex.jar"
-        # Enable cfg-append to make sure we get the dump for both dex files.
-        # (otherwise the runtime compilation of the secondary dex will overwrite
-        # the dump of the first one).
-        FLAGS="${FLAGS} -Xcompiler-option --dump-cfg-append"
-        COMPILE_FLAGS="${COMPILE_FLAGS} --dump-cfg-append"
-        shift
-    elif [ "x$1" = "x--with-agent" ]; then
-        shift
-        USE_JVMTI="y"
-        WITH_AGENT+=("$1")
-        shift
-    elif [ "x$1" = "x--debug-wrap-agent" ]; then
-        WRAP_DEBUGGER_AGENT="y"
-        shift
-    elif [ "x$1" = "x--debug-agent" ]; then
-        shift
-        DEBUGGER="agent"
-        USE_JVMTI="y"
-        DEBUGGER_AGENT="$1"
-        TIME_OUT="n"
-        shift
-    elif [ "x$1" = "x--debug" ]; then
-        USE_JVMTI="y"
-        DEBUGGER="y"
-        TIME_OUT="n"
-        shift
-    elif [ "x$1" = "x--gdbserver-port" ]; then
-        shift
-        GDBSERVER_PORT=$1
-        shift
-    elif [ "x$1" = "x--gdbserver-bin" ]; then
-        shift
-        GDBSERVER_HOST=$1
-        GDBSERVER_DEVICE=$1
-        shift
-    elif [ "x$1" = "x--gdbserver" ]; then
-        USE_GDBSERVER="y"
-        DEV_MODE="y"
-        TIME_OUT="n"
-        shift
-    elif [ "x$1" = "x--gdb" ]; then
-        USE_GDB="y"
-        DEV_MODE="y"
-        TIME_OUT="n"
-        shift
-    elif [ "x$1" = "x--gdb-arg" ]; then
-        shift
-        gdb_arg="$1"
-        GDB_ARGS="${GDB_ARGS} $gdb_arg"
-        shift
-    elif [ "x$1" = "x--gdb-dex2oat" ]; then
-        USE_GDB_DEX2OAT="y"
-        DEV_MODE="y"
-        TIME_OUT="n"
-        shift
-    elif [ "x$1" = "x--gdb-dex2oat-args" ]; then
-        shift
-        for arg in $(echo $1 | tr ";" " "); do
-          GDB_DEX2OAT_ARGS+="$arg "
-        done
-        shift
-    elif [ "x$1" = "x--zygote" ]; then
-        ZYGOTE="-Xzygote"
-        msg "Spawning from zygote"
-        shift
-    elif [ "x$1" = "x--dev" ]; then
-        DEV_MODE="y"
-        shift
-    elif [ "x$1" = "x--interpreter" ]; then
-        INTERPRETER="y"
-        shift
-    elif [ "x$1" = "x--jit" ]; then
-        JIT="y"
-        shift
-    elif [ "x$1" = "x--baseline" ]; then
-        FLAGS="${FLAGS} -Xcompiler-option --baseline"
-        COMPILE_FLAGS="${COMPILE_FLAGS} --baseline"
-        shift
-    elif [ "x$1" = "x--jvm" ]; then
-        USE_JVM="y"
-        shift
-    elif [ "x$1" = "x--invoke-with" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            error_msg "$0 missing argument to --invoke-with"
-            exit 1
-        fi
-        if [ "x$INVOKE_WITH" = "x" ]; then
-            INVOKE_WITH="$1"
-        else
-            INVOKE_WITH="$INVOKE_WITH $1"
-        fi
-        shift
-    elif [ "x$1" = "x--no-verify" ]; then
-        VERIFY="n"
-        shift
-    elif [ "x$1" = "x--verify-soft-fail" ]; then
-        VERIFY="s"
-        shift
-    elif [ "x$1" = "x--no-optimize" ]; then
-        OPTIMIZE="n"
-        shift
-    elif [ "x$1" = "x--chroot" ]; then
-        shift
-        CHROOT="$1"
-        shift
-    elif [ "x$1" = "x--simpleperf" ]; then
-        SIMPLEPERF="yes"
-        shift
-    elif [ "x$1" = "x--android-root" ]; then
-        shift
-        ANDROID_ROOT="$1"
-        shift
-    elif [ "x$1" = "x--android-i18n-root" ]; then
-        shift
-        ANDROID_I18N_ROOT="$1"
-        shift
-    elif [ "x$1" = "x--android-art-root" ]; then
-        shift
-        ANDROID_ART_ROOT="$1"
-        shift
-    elif [ "x$1" = "x--android-tzdata-root" ]; then
-        shift
-        ANDROID_TZDATA_ROOT="$1"
-        shift
-    elif [ "x$1" = "x--instruction-set-features" ]; then
-        shift
-        INSTRUCTION_SET_FEATURES="$1"
-        shift
-    elif [ "x$1" = "x--timeout" ]; then
-        shift
-        TIME_OUT_VALUE="$1"
-        shift
-    elif [ "x$1" = "x--" ]; then
-        shift
-        break
-    elif [ "x$1" = "x--64" ]; then
-        SUFFIX64="64"
-        ISA="x86_64"
-        GDBSERVER_DEVICE="gdbserver64"
-        DALVIKVM="dalvikvm64"
-        LIBRARY_DIRECTORY="lib64"
-        TEST_DIRECTORY="nativetest64"
-        ARCHITECTURES_PATTERN="${ARCHITECTURES_64}"
-        GET_DEVICE_ISA_BITNESS_FLAG="--64"
-        DEX2OAT_NDEBUG_BINARY="dex2oat64"
-        DEX2OAT_DEBUG_BINARY="dex2oatd64"
-        shift
-    elif [ "x$1" = "x--experimental" ]; then
-        if [ "$#" -lt 2 ]; then
-            error_msg "missing --experimental option"
-            exit 1
-        fi
-        EXPERIMENTAL="$EXPERIMENTAL $2"
-        shift 2
-    elif [ "x$1" = "x--external-log-tags" ]; then
-        EXTERNAL_LOG_TAGS="y"
-        shift
-    elif [ "x$1" = "x--dry-run" ]; then
-        DRY_RUN="y"
-        shift
-    elif [ "x$1" = "x--vdex" ]; then
-        TEST_VDEX="y"
-        shift
-    elif [ "x$1" = "x--dex2oat-dm" ]; then
-        TEST_DEX2OAT_DM="y"
-        shift
-    elif [ "x$1" = "x--runtime-dm" ]; then
-        TEST_RUNTIME_DM="y"
-        shift
-    elif [ "x$1" = "x--vdex-filter" ]; then
-        shift
-        option="$1"
-        VDEX_ARGS="${VDEX_ARGS} --compiler-filter=$option"
-        shift
-    elif [ "x$1" = "x--vdex-arg" ]; then
-        shift
-        VDEX_ARGS="${VDEX_ARGS} $1"
-        shift
-    elif [ "x$1" = "x--sync" ]; then
-        SYNC_BEFORE_RUN="y"
-        shift
-    elif [ "x$1" = "x--profile" ]; then
-        PROFILE="y"
-        shift
-    elif [ "x$1" = "x--random-profile" ]; then
-        RANDOM_PROFILE="y"
-        shift
-    elif expr "x$1" : "x--" >/dev/null 2>&1; then
-        error_msg "unknown $0 option: $1"
-        exit 1
-    else
-        break
-    fi
-done
-
-# HACK: Force the use of `signal_dumper` on host.
-if [[ "$HOST" = "y" ]]; then
-  TIME_OUT="timeout"
-fi
-
-# If you change this, update the timeout in testrunner.py as well.
-if [ -z "$TIME_OUT_VALUE" ] ; then
-  # 10 minutes is the default.
-  TIME_OUT_VALUE=600
-
-  # For sanitized builds use a larger base.
-  # TODO: Consider sanitized target builds?
-  if [ "x$SANITIZE_HOST" != "x" ] ; then
-    TIME_OUT_VALUE=1500  # 25 minutes.
-  fi
-
-  TIME_OUT_VALUE=$((${TIME_OUT_VALUE} + ${TIME_OUT_EXTRA}))
-fi
-
-# Escape hatch for slow hosts or devices. Accept an environment variable as a timeout factor.
-if [ ! -z "$ART_TIME_OUT_MULTIPLIER" ] ; then
-  TIME_OUT_VALUE=$((${TIME_OUT_VALUE} * ${ART_TIME_OUT_MULTIPLIER}))
-fi
-
-# The DEX_LOCATION with the chroot prefix, if any.
-CHROOT_DEX_LOCATION="$CHROOT$DEX_LOCATION"
-
-# If running on device, determine the ISA of the device.
-if [ "$HOST" = "n" -a "$USE_JVM" = "n" ]; then
-  ISA=$("$ANDROID_BUILD_TOP/art/test/utils/get-device-isa" "$GET_DEVICE_ISA_BITNESS_FLAG")
-fi
-
-if [ "$USE_JVM" = "n" ]; then
-    FLAGS="${FLAGS} ${ANDROID_FLAGS}"
-    # we don't want to be trying to get adbconnections since the plugin might
-    # not have been built.
-    FLAGS="${FLAGS} -XjdwpProvider:none"
-    for feature in ${EXPERIMENTAL}; do
-        FLAGS="${FLAGS} -Xexperimental:${feature} -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:${feature}"
-        COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xexperimental:${feature}"
-    done
-fi
-
-if [ "$CREATE_ANDROID_ROOT" = "y" ]; then
-    ANDROID_ROOT=$DEX_LOCATION/android-root
-fi
-
-if [ "x$1" = "x" ] ; then
-  MAIN="Main"
-else
-  MAIN="$1"
-  shift
-fi
-
-if [ "$ZYGOTE" = "" ]; then
-    if [ "$OPTIMIZE" = "y" ]; then
-        if [ "$VERIFY" = "y" ]; then
-            DEX_OPTIMIZE="-Xdexopt:verified"
-        else
-            DEX_OPTIMIZE="-Xdexopt:all"
-        fi
-        msg "Performing optimizations"
-    else
-        DEX_OPTIMIZE="-Xdexopt:none"
-        msg "Skipping optimizations"
-    fi
-
-    if [ "$VERIFY" = "y" ]; then
-        JVM_VERIFY_ARG="-Xverify:all"
-        msg "Performing verification"
-    elif [ "$VERIFY" = "s" ]; then
-        JVM_VERIFY_ARG="Xverify:all"
-        DEX_VERIFY="-Xverify:softfail"
-        msg "Forcing verification to be soft fail"
-    else # VERIFY = "n"
-        DEX_VERIFY="-Xverify:none"
-        JVM_VERIFY_ARG="-Xverify:none"
-        msg "Skipping verification"
-    fi
-fi
-
-msg "------------------------------"
-
-if [ "$DEBUGGER" = "y" ]; then
-  # Use this instead for ddms and connect by running 'ddms':
-  # DEBUGGER_OPTS="-XjdwpOptions=server=y,suspend=y -XjdwpProvider:adbconnection"
-  # TODO: add a separate --ddms option?
-
-  PORT=12345
-  msg "Waiting for jdb to connect:"
-  if [ "$HOST" = "n" ]; then
-    msg "    adb forward tcp:$PORT tcp:$PORT"
-  fi
-  msg "    jdb -attach localhost:$PORT"
-  if [ "$USE_JVM" = "n" ]; then
-    # Use the default libjdwp agent. Use --debug-agent to use a custom one.
-    DEBUGGER_OPTS="-agentpath:libjdwp.so=transport=dt_socket,address=$PORT,server=y,suspend=y -XjdwpProvider:internal"
-  else
-    DEBUGGER_OPTS="-agentlib:jdwp=transport=dt_socket,address=$PORT,server=y,suspend=y"
-  fi
-elif [ "$DEBUGGER" = "agent" ]; then
-  PORT=12345
-  # TODO Support ddms connection and support target.
-  if [ "$HOST" = "n" ]; then
-    error_msg "--debug-agent not supported yet for target!"
-    exit 1
-  fi
-  AGENTPATH=${DEBUGGER_AGENT}
-  if [ "$WRAP_DEBUGGER_AGENT" = "y" ]; then
-    WRAPPROPS="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}/libwrapagentpropertiesd.so"
-    if [ "$TEST_IS_NDEBUG" = "y" ]; then
-      WRAPPROPS="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}/libwrapagentproperties.so"
-    fi
-    AGENTPATH="${WRAPPROPS}=${ANDROID_BUILD_TOP}/art/tools/libjdwp-compat.props,${AGENTPATH}"
-  fi
-  msg "Connect to localhost:$PORT"
-  DEBUGGER_OPTS="-agentpath:${AGENTPATH}=transport=dt_socket,address=$PORT,server=y,suspend=y"
-fi
-
-for agent in "${WITH_AGENT[@]}"; do
-  FLAGS="${FLAGS} -agentpath:${agent}"
-done
-
-if [ "$USE_JVMTI" = "y" ]; then
-  if [ "$USE_JVM" = "n" ]; then
-    plugin=libopenjdkjvmtid.so
-    if  [[ "$TEST_IS_NDEBUG" = "y" ]]; then
-      plugin=libopenjdkjvmti.so
-    fi
-    # We used to add flags here that made the runtime debuggable but that is not
-    # needed anymore since the plugin can do it for us now.
-    FLAGS="${FLAGS} -Xplugin:${plugin}"
-
-    # For jvmti tests, set the threshold of compilation to 1, so we jit early to
-    # provide better test coverage for jvmti + jit. This means we won't run
-    # the default --jit configuration but it is not too important test scenario for
-    # jvmti tests. This is art specific flag, so don't use it with jvm.
-    FLAGS="${FLAGS} -Xjitthreshold:1"
-  fi
-fi
-
-# Add the libdir to the argv passed to the main function.
-if [ "$ADD_LIBDIR_ARGUMENTS" = "y" ]; then
-  if [[ "$HOST" = "y" ]]; then
-    ARGS="${ARGS} ${ANDROID_HOST_OUT}/${TEST_DIRECTORY}/"
-  else
-    ARGS="${ARGS} /data/${TEST_DIRECTORY}/art/${ISA}/"
-  fi
-fi
-if [ "$IS_JVMTI_TEST" = "y" ]; then
-  agent=libtiagentd.so
-  lib=tiagentd
-  if  [[ "$TEST_IS_NDEBUG" = "y" ]]; then
-    agent=libtiagent.so
-    lib=tiagent
-  fi
-
-  ARGS="${ARGS} ${lib}"
-  if [[ "$USE_JVM" = "y" ]]; then
-    FLAGS="${FLAGS} -agentpath:${ANDROID_HOST_OUT}/nativetest64/${agent}=${TEST_NAME},jvm"
-  else
-    FLAGS="${FLAGS} -agentpath:${agent}=${TEST_NAME},art"
-  fi
-fi
-
-if [[ "$JVMTI_STRESS" = "y" ]]; then
-  agent=libtistressd.so
-  if  [[ "$TEST_IS_NDEBUG" = "y" ]]; then
-    agent=libtistress.so
-  fi
-
-  # Just give it a default start so we can always add ',' to it.
-  agent_args="jvmti-stress"
-  if [[ "$JVMTI_REDEFINE_STRESS" = "y" ]]; then
-    # We really cannot do this on RI so don't both passing it in that case.
-    if [[ "$USE_JVM" = "n" ]]; then
-      agent_args="${agent_args},redefine"
-    fi
-  fi
-  if [[ "$JVMTI_FIELD_STRESS" = "y" ]]; then
-    agent_args="${agent_args},field"
-  fi
-  if [[ "$JVMTI_STEP_STRESS" = "y" ]]; then
-    agent_args="${agent_args},step"
-  fi
-  if [[ "$JVMTI_TRACE_STRESS" = "y" ]]; then
-    agent_args="${agent_args},trace"
-  fi
-  # In the future add onto this;
-  if [[ "$USE_JVM" = "y" ]]; then
-    FLAGS="${FLAGS} -agentpath:${ANDROID_HOST_OUT}/nativetest64/${agent}=${agent_args}"
-  else
-    FLAGS="${FLAGS} -agentpath:${agent}=${agent_args}"
-  fi
-fi
-
-if [ "$USE_JVM" = "y" ]; then
-  export LD_LIBRARY_PATH=${ANDROID_HOST_OUT}/lib64
-  # Some jvmti tests are flaky without -Xint on the RI.
-  if [ "$IS_JVMTI_TEST" = "y" ]; then
-    FLAGS="${FLAGS} -Xint"
-  fi
-  # Xmx is necessary since we don't pass down the ART flags to JVM.
-  # We pass the classes2 path whether it's used (src-multidex) or not.
-  cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes:classes2 ${FLAGS} $MAIN $@ ${ARGS}"
-  if [ "$DEV_MODE" = "y" ]; then
-    echo $cmdline
-  fi
-  if [ "$CREATE_RUNNER" = "y" ]; then
-    echo "#!/bin/bash" > runit.sh
-    echo "export LD_LIBRARY_PATH=\"$LD_LIBRARY_PATH\""
-    echo $cmdline >> runit.sh
-    chmod u+x runit.sh
-    echo "Runnable test script written to $PWD/runit.sh"
-  else
-    $cmdline
-  fi
-  exit
-fi
-
-readonly b_path=$(get_apex_bootclasspath ${HOST})
-readonly b_path_locations=$(get_apex_bootclasspath_locations ${HOST})
-
-BCPEX=
-if [ -f "$TEST_NAME-bcpex.jar" ] ; then
-  BCPEX=":$DEX_LOCATION/$TEST_NAME-bcpex.jar"
-fi
-
-# Pass down the bootclasspath
-FLAGS="${FLAGS} -Xbootclasspath:${b_path}${BCPEX}"
-FLAGS="${FLAGS} -Xbootclasspath-locations:${b_path_locations}${BCPEX}"
-COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xbootclasspath:${b_path}"
-COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xbootclasspath-locations:${b_path_locations}"
-
-if [ "$HAVE_IMAGE" = "n" ]; then
-    # Disable image dex2oat - this will forbid the runtime to patch or compile an image.
-    FLAGS="${FLAGS} -Xnoimage-dex2oat"
-
-    # We'll abuse a second flag here to test different behavior. If --relocate, use the
-    # existing image - relocation will fail as patching is disallowed. If --no-relocate,
-    # pass a non-existent image - compilation will fail as dex2oat is disallowed.
-    if [ "${RELOCATE}" = "n" ] ; then
-      BOOT_IMAGE="/system/non-existent/boot.art"
-    fi
-    # App images cannot be generated without a boot image.
-    APP_IMAGE="n"
-fi
-DALVIKVM_BOOT_OPT="-Ximage:${BOOT_IMAGE}"
-
-if [ "$USE_GDB_DEX2OAT" = "y" ]; then
-  if [ "$HOST" = "n" ]; then
-    echo "The --gdb-dex2oat option is not yet implemented for target." >&2
-    exit 1
-  fi
-fi
-
-if [ "$USE_GDB" = "y" ]; then
-  if [ "$USE_GDBSERVER" = "y" ]; then
-    error_msg "Cannot pass both --gdb and --gdbserver at the same time!"
-    exit 1
-  elif [ "$HOST" = "n" ]; then
-    # We might not have any hostname resolution if we are using a chroot.
-    GDB="$GDBSERVER_DEVICE --no-startup-with-shell 127.0.0.1$GDBSERVER_PORT"
-  else
-    if [ `uname` = "Darwin" ]; then
-        GDB=lldb
-        GDB_ARGS="$GDB_ARGS -- $DALVIKVM"
-        DALVIKVM=
-    else
-        GDB=gdb
-        GDB_ARGS="$GDB_ARGS --args $DALVIKVM"
-        # Enable for Emacs "M-x gdb" support. TODO: allow extra gdb arguments on command line.
-        # gdbargs="--annotate=3 $gdbargs"
-    fi
-  fi
-elif [ "$USE_GDBSERVER" = "y" ]; then
-  if [ "$HOST" = "n" ]; then
-    # We might not have any hostname resolution if we are using a chroot.
-    GDB="$GDBSERVER_DEVICE --no-startup-with-shell 127.0.0.1$GDBSERVER_PORT"
-  else
-    GDB="$GDBSERVER_HOST $GDBSERVER_PORT"
-  fi
-fi
-
-if [ "$INTERPRETER" = "y" ]; then
-    INT_OPTS="${INT_OPTS} -Xint"
-fi
-
-if [ "$JIT" = "y" ]; then
-    INT_OPTS="${INT_OPTS} -Xusejit:true"
-else
-    INT_OPTS="${INT_OPTS} -Xusejit:false"
-fi
-
-if [ "$INTERPRETER" = "y" ] || [ "$JIT" = "y" ]; then
-  if [ "$VERIFY" = "y" ] ; then
-    INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=verify"
-    COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=verify"
-  elif [ "$VERIFY" = "s" ]; then
-    INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=extract"
-    COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=extract"
-    DEX_VERIFY="${DEX_VERIFY} -Xverify:softfail"
-  else # VERIFY = "n"
-    INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=assume-verified"
-    COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=assume-verified"
-    DEX_VERIFY="${DEX_VERIFY} -Xverify:none"
-  fi
-fi
-
-JNI_OPTS="-Xjnigreflimit:512 -Xcheck:jni"
-
-COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xnorelocate"
-if [ "$RELOCATE" = "y" ]; then
-    FLAGS="${FLAGS} -Xrelocate"
-else
-    FLAGS="$FLAGS -Xnorelocate"
-fi
-
-if [ "$BIONIC" = "y" ]; then
-  # This is the location that soong drops linux_bionic builds. Despite being
-  # called linux_bionic-x86 the build is actually amd64 (x86_64) only.
-  if [ ! -e "$OUT_DIR/soong/host/linux_bionic-x86" ]; then
-    error_msg "linux_bionic-x86 target doesn't seem to have been built!"
-    exit 1
-  fi
-  # Set TIMEOUT_DUMPER manually so it works even with apex's
-  TIMEOUT_DUMPER=$OUT_DIR/soong/host/linux_bionic-x86/bin/signal_dumper
-fi
-
-# Prevent test from silently falling back to interpreter in no-prebuild mode. This happens
-# when DEX_LOCATION path is too long, because vdex/odex filename is constructed by taking
-# full path to dex, stripping leading '/', appending '@classes.vdex' and changing every
-# remaining '/' into '@'.
-if [ "$HOST" = "y" ]; then
-  max_filename_size=$(getconf NAME_MAX $DEX_LOCATION)
-else
-  # There is no getconf on device, fallback to standard value.
-  # See NAME_MAX in kernel <linux/limits.h>
-  max_filename_size=255
-fi
-# Compute VDEX_NAME.
-DEX_LOCATION_STRIPPED="${DEX_LOCATION#/}"
-VDEX_NAME="${DEX_LOCATION_STRIPPED//\//@}@[email protected]"
-if [ ${#VDEX_NAME} -gt $max_filename_size ]; then
-    echo "Dex location path too long:"
-    error_msg "$VDEX_NAME is ${#VDEX_NAME} character long, and the limit is $max_filename_size."
-    exit 1
-fi
-
-if [ "$HOST" = "y" ]; then
-  # On host, run binaries (`dex2oat(d)`, `dalvikvm`, `profman`) from the `bin`
-  # directory under the "Android Root" (usually `out/host/linux-x86`).
-  #
-  # TODO(b/130295968): Adjust this if/when ART host artifacts are installed
-  # under the ART root (usually `out/host/linux-x86/com.android.art`).
-  ANDROID_ART_BIN_DIR=$ANDROID_ROOT/bin
-else
-  # On target, run binaries (`dex2oat(d)`, `dalvikvm`, `profman`) from the ART
-  # APEX's `bin` directory. This means the linker will observe the ART APEX
-  # linker configuration file (`/apex/com.android.art/etc/ld.config.txt`) for
-  # these binaries.
-  ANDROID_ART_BIN_DIR=$ANDROID_ART_ROOT/bin
-fi
-
-profman_cmdline="true"
-dex2oat_cmdline="true"
-vdex_cmdline="true"
-dm_cmdline="true"
-mkdir_locations="${DEX_LOCATION}/dalvik-cache/$ISA"
-strip_cmdline="true"
-sync_cmdline="true"
-linkroot_cmdline="true"
-linkroot_overlay_cmdline="true"
-setupapex_cmdline="true"
-installapex_cmdline="true"
-installapex_test_cmdline="true"
-
-linkdirs() {
-  find "$1" -maxdepth 1 -mindepth 1 -type d | xargs -i ln -sf '{}' "$2"
-  # Also create a link for the boot image.
-  ln -sf $ANDROID_HOST_OUT/apex/art_boot_images "$2"
-}
-
-if [ "$CREATE_ANDROID_ROOT" = "y" ]; then
-  mkdir_locations="${mkdir_locations} ${ANDROID_ROOT}"
-  linkroot_cmdline="linkdirs ${ANDROID_HOST_OUT} ${ANDROID_ROOT}"
-  if [ "${BIONIC}" = "y" ]; then
-    # TODO Make this overlay more generic.
-    linkroot_overlay_cmdline="linkdirs $OUT_DIR/soong/host/linux_bionic-x86 ${ANDROID_ROOT}"
-  fi
-  # Replace the boot image to a location expected by the runtime.
-  DALVIKVM_BOOT_OPT="-Ximage:${ANDROID_ROOT}/art_boot_images/javalib/boot.art"
-fi
-
-if [ "$USE_ZIPAPEX" = "y" ]; then
-  # TODO Currently this only works for linux_bionic zipapexes because those are
-  # stripped and so small enough that the ulimit doesn't kill us.
-  mkdir_locations="${mkdir_locations} $DEX_LOCATION/zipapex"
-  zip_options="-qq"
-  if [ "$DEV_MODE" = "y" ]; then
-    zip_options=""
-  fi
-  setupapex_cmdline="unzip -o -u ${zip_options} ${ZIPAPEX_LOC} apex_payload.zip -d ${DEX_LOCATION}"
-  installapex_cmdline="unzip -o -u ${zip_options} ${DEX_LOCATION}/apex_payload.zip -d ${DEX_LOCATION}/zipapex"
-  ANDROID_ART_BIN_DIR=$DEX_LOCATION/zipapex/bin
-elif [ "$USE_EXTRACTED_ZIPAPEX" = "y" ]; then
-  # Just symlink the zipapex binaries
-  ANDROID_ART_BIN_DIR=$DEX_LOCATION/zipapex/bin
-  # Force since some tests manually run this file twice.
-  ln_options=""
-  if [ "$DEV_MODE" = "y" ]; then
-    ln_options="--verbose"
-  fi
-  # If the ${RUN} is executed multiple times we don't need to recreate the link
-  installapex_test_cmdline="test -L ${DEX_LOCATION}/zipapex"
-  installapex_cmdline="ln -s -f ${ln_options} ${EXTRACTED_ZIPAPEX_LOC} ${DEX_LOCATION}/zipapex"
-fi
-
-# PROFILE takes precedence over RANDOM_PROFILE, since PROFILE tests require a
-# specific profile to run properly.
-if [ "$PROFILE" = "y" ] || [ "$RANDOM_PROFILE" = "y" ]; then
-  profman_cmdline="$ANDROID_ART_BIN_DIR/profman  \
-    --apk=$DEX_LOCATION/$TEST_NAME.jar \
-    --dex-location=$DEX_LOCATION/$TEST_NAME.jar"
-  if [ -f "$TEST_NAME-ex.jar" ] && [ "$SECONDARY_COMPILATION" = "y" ] ; then
-    profman_cmdline="${profman_cmdline} \
-      --apk=$DEX_LOCATION/$TEST_NAME-ex.jar \
-      --dex-location=$DEX_LOCATION/$TEST_NAME-ex.jar"
-  fi
-  COMPILE_FLAGS="${COMPILE_FLAGS} --profile-file=$DEX_LOCATION/$TEST_NAME.prof"
-  FLAGS="${FLAGS} -Xcompiler-option --profile-file=$DEX_LOCATION/$TEST_NAME.prof"
-  if [ "$PROFILE" = "y" ]; then
-    profman_cmdline="${profman_cmdline} --create-profile-from=$DEX_LOCATION/profile \
-        --reference-profile-file=$DEX_LOCATION/$TEST_NAME.prof"
-  else
-    profman_cmdline="${profman_cmdline} --generate-test-profile=$DEX_LOCATION/$TEST_NAME.prof \
-        --generate-test-profile-seed=0"
-  fi
-fi
-
-function get_prebuilt_lldb_path {
-  local CLANG_BASE="prebuilts/clang/host"
-  local CLANG_VERSION="$("$ANDROID_BUILD_TOP/build/soong/scripts/get_clang_version.py")"
-  case "$(uname -s)" in
-    Darwin)
-      local PREBUILT_NAME="darwin-x86"
-      ;;
-    Linux)
-      local PREBUILT_NAME="linux-x86"
-      ;;
-    *)
-      >&2 echo "Unknown host $(uname -s). Unsupported for debugging dex2oat with LLDB."
-      return
-      ;;
-  esac
-  local CLANG_PREBUILT_HOST_PATH="$ANDROID_BUILD_TOP/$CLANG_BASE/$PREBUILT_NAME/$CLANG_VERSION"
-  # If the clang prebuilt directory exists and the reported clang version
-  # string does not, then it is likely that the clang version reported by the
-  # get_clang_version.py script does not match the expected directory name.
-  if [ -d "${ANDROID_BUILD_TOP}/${CLANG_BASE}/${PREBUILT_NAME}" ] && \
-     [ ! -d "${CLANG_PREBUILT_HOST_PATH}" ]; then
-    error_msg "The prebuilt clang directory exists, but the specific clang"\
-    "\nversion reported by get_clang_version.py does not exist in that path."\
-    "\nPlease make sure that the reported clang version resides in the"\
-    "\nprebuilt clang directory!"
-    exit 1
-  fi
-
-  # The lldb-server binary is a dependency of lldb.
-  export LLDB_DEBUGSERVER_PATH="${CLANG_PREBUILT_HOST_PATH}/runtimes_ndk_cxx/x86_64/lldb-server"
-
-  # Set the current terminfo directory to TERMINFO so that LLDB can read the
-  # termcap database.
-  local terminfo_regexp_path='\/.*\/*terminfo\/'
-  if [[ $(infocmp) =~ $terminfo_regexp_path ]] ; then
-    export TERMINFO="${BASH_REMATCH[0]}"
-  fi
-
-  prebuilt_lldb_path="$CLANG_PREBUILT_HOST_PATH/bin/lldb.sh"
-}
-
-function write_dex2oat_cmdlines {
-  local name="$1"
-
-  local class_loader_context=""
-  local enable_app_image=false
-  if [ "$APP_IMAGE" = "y" ]; then
-    enable_app_image=true
-  fi
-
-  # If the name ends in -ex then this is a secondary dex file
-  if [ "${name:${#name}-3}" = "-ex" ]; then
-    # Lazily realize the default value in case DEX_LOCATION/TEST_NAME change
-    if [ "x$SECONDARY_CLASS_LOADER_CONTEXT" = "x" ]; then
-      if [ "x$SECONDARY_DEX" = "x" ]; then
-        # Tests without `--secondary` load the "-ex" jar in a separate PathClassLoader
-        # that is a child of the main PathClassLoader. If the class loader is constructed
-        # in any other way, the test needs to specify the secondary CLC explicitly.
-        SECONDARY_CLASS_LOADER_CONTEXT="PCL[];PCL[$DEX_LOCATION/$TEST_NAME.jar]"
-      else
-        # Tests with `--secondary` load the `-ex` jar a part of the main PathClassLoader.
-        SECONDARY_CLASS_LOADER_CONTEXT="PCL[$DEX_LOCATION/$TEST_NAME.jar]"
-      fi
-    fi
-    class_loader_context="'--class-loader-context=$SECONDARY_CLASS_LOADER_CONTEXT'"
-    $enable_app_image && [ "$SECONDARY_APP_IMAGE" = "y" ] || enable_app_image=false
-  fi
-
-  local app_image=""
-  $enable_app_image && app_image="--app-image-file=$DEX_LOCATION/oat/$ISA/$name.art --resolve-startup-const-strings=true"
-
-  if [ "$USE_GDB_DEX2OAT" = "y" ]; then
-    get_prebuilt_lldb_path
-    GDB_DEX2OAT="$prebuilt_lldb_path -f"
-    GDB_DEX2OAT_ARGS+=" -- "
-  fi
-
-  local dex2oat_binary
-  dex2oat_binary=${DEX2OAT_DEBUG_BINARY}
-  if  [[ "$TEST_IS_NDEBUG" = "y" ]]; then
-    dex2oat_binary=${DEX2OAT_NDEBUG_BINARY}
-  fi
-  dex2oat_cmdline="$INVOKE_WITH $GDB_DEX2OAT \
-                      $ANDROID_ART_BIN_DIR/$dex2oat_binary \
-                      $GDB_DEX2OAT_ARGS \
-                      $COMPILE_FLAGS \
-                      --boot-image=${BOOT_IMAGE} \
-                      --dex-file=$DEX_LOCATION/$name.jar \
-                      --oat-file=$DEX_LOCATION/oat/$ISA/$name.odex \
-                      "$app_image" \
-                      --generate-mini-debug-info \
-                      --instruction-set=$ISA \
-                      $class_loader_context"
-  if [ "x$INSTRUCTION_SET_FEATURES" != "x" ] ; then
-    dex2oat_cmdline="${dex2oat_cmdline} --instruction-set-features=${INSTRUCTION_SET_FEATURES}"
-  fi
-
-  # Add in a timeout. This is important for testing the compilation/verification time of
-  # pathological cases. We do not append a timeout when debugging dex2oat because we
-  # do not want it to exit while debugging.
-  # Note: as we don't know how decent targets are (e.g., emulator), only do this on the host for
-  #       now. We should try to improve this.
-  #       The current value is rather arbitrary. run-tests should compile quickly.
-  # Watchdog timeout is in milliseconds so add 3 '0's to the dex2oat timeout.
-  if [ "$HOST" != "n" ] && [ "$USE_GDB_DEX2OAT" != "y" ]; then
-    # Use SIGRTMIN+2 to try to dump threads.
-    # Use -k 1m to SIGKILL it a minute later if it hasn't ended.
-    dex2oat_cmdline="timeout -k ${DEX2OAT_TIMEOUT}s -s SIGRTMIN+2 ${DEX2OAT_RT_TIMEOUT}s ${dex2oat_cmdline} --watchdog-timeout=${DEX2OAT_TIMEOUT}000"
-  fi
-  if [ "$PROFILE" = "y" ] || [ "$RANDOM_PROFILE" = "y" ]; then
-    vdex_cmdline="${dex2oat_cmdline} ${VDEX_ARGS} --input-vdex=$DEX_LOCATION/oat/$ISA/$name.vdex --output-vdex=$DEX_LOCATION/oat/$ISA/$name.vdex"
-  elif [ "$TEST_VDEX" = "y" ]; then
-    if [ "$VDEX_ARGS" = "" ]; then
-      # If no arguments need to be passed, just delete the odex file so that the runtime only picks up the vdex file.
-      vdex_cmdline="rm $DEX_LOCATION/oat/$ISA/$name.odex"
-    else
-      vdex_cmdline="${dex2oat_cmdline} ${VDEX_ARGS} --input-vdex=$DEX_LOCATION/oat/$ISA/$name.vdex"
-    fi
-  elif [ "$TEST_DEX2OAT_DM" = "y" ]; then
-    vdex_cmdline="${dex2oat_cmdline} ${VDEX_ARGS} --dump-timings --dm-file=$DEX_LOCATION/oat/$ISA/$name.dm"
-    dex2oat_cmdline="${dex2oat_cmdline} --copy-dex-files=false --output-vdex=$DEX_LOCATION/oat/$ISA/primary.vdex"
-    dm_cmdline="zip -qj $DEX_LOCATION/oat/$ISA/$name.dm $DEX_LOCATION/oat/$ISA/primary.vdex"
-  elif [ "$TEST_RUNTIME_DM" = "y" ]; then
-    dex2oat_cmdline="${dex2oat_cmdline} --copy-dex-files=false --output-vdex=$DEX_LOCATION/oat/$ISA/primary.vdex"
-    dm_cmdline="zip -qj $DEX_LOCATION/$name.dm $DEX_LOCATION/oat/$ISA/primary.vdex"
-  fi
-}
-
-# Enable mini-debug-info for JIT (if JIT is used).
-FLAGS="$FLAGS -Xcompiler-option --generate-mini-debug-info"
-
-if [ "$PREBUILD" = "y" ]; then
-  mkdir_locations="${mkdir_locations} ${DEX_LOCATION}/oat/$ISA"
-
-  # "Primary".
-  write_dex2oat_cmdlines "$TEST_NAME"
-  dex2oat_cmdline=$(echo $dex2oat_cmdline)
-  dm_cmdline=$(echo $dm_cmdline)
-  vdex_cmdline=$(echo $vdex_cmdline)
-
-  # Enable mini-debug-info for JIT (if JIT is used).
-  FLAGS="$FLAGS -Xcompiler-option --generate-mini-debug-info"
-
-  if [ -f "$TEST_NAME-ex.jar" ] && [ "$SECONDARY_COMPILATION" = "y" ] ; then
-    # "Secondary" for test coverage.
-
-    # Store primary values.
-    base_dex2oat_cmdline="$dex2oat_cmdline"
-    base_dm_cmdline="$dm_cmdline"
-    base_vdex_cmdline="$vdex_cmdline"
-
-    write_dex2oat_cmdlines "$TEST_NAME-ex"
-    dex2oat_cmdline=$(echo $dex2oat_cmdline)
-    dm_cmdline=$(echo $dm_cmdline)
-    vdex_cmdline=$(echo $vdex_cmdline)
-
-    # Concatenate.
-    dex2oat_cmdline="$base_dex2oat_cmdline && $dex2oat_cmdline"
-    dm_cmdline="$base_dm_cmdline" # Only use primary dm.
-    vdex_cmdline="$base_vdex_cmdline && $vdex_cmdline"
-  fi
-fi
-
-if [ "$SYNC_BEFORE_RUN" = "y" ]; then
-  sync_cmdline="sync"
-fi
-
-DALVIKVM_ISA_FEATURES_ARGS=""
-if [ "x$INSTRUCTION_SET_FEATURES" != "x" ] ; then
-  DALVIKVM_ISA_FEATURES_ARGS="-Xcompiler-option --instruction-set-features=${INSTRUCTION_SET_FEATURES}"
-fi
-
-# java.io.tmpdir can only be set at launch time.
-TMP_DIR_OPTION=""
-if [ "$HOST" = "n" ]; then
-  TMP_DIR_OPTION="-Djava.io.tmpdir=/data/local/tmp"
-fi
-
-# The build servers have an ancient version of bash so we cannot use @Q.
-if [ "$USE_GDBSERVER" == "y" ]; then
-  printf -v QUOTED_DALVIKVM_BOOT_OPT "%q" "$DALVIKVM_BOOT_OPT"
-else
-  QUOTED_DALVIKVM_BOOT_OPT="$DALVIKVM_BOOT_OPT"
-fi
-
-DALVIKVM_CLASSPATH=$DEX_LOCATION/$TEST_NAME.jar
-if [ -f "$TEST_NAME-aotex.jar" ] ; then
-  DALVIKVM_CLASSPATH=$DALVIKVM_CLASSPATH:$DEX_LOCATION/$TEST_NAME-aotex.jar
-fi
-DALVIKVM_CLASSPATH=$DALVIKVM_CLASSPATH$SECONDARY_DEX
-
-# We set DumpNativeStackOnSigQuit to false to avoid stressing libunwind.
-# b/27185632
-# b/24664297
-
-dalvikvm_cmdline="$INVOKE_WITH $GDB $ANDROID_ART_BIN_DIR/$DALVIKVM \
-                  $GDB_ARGS \
-                  $FLAGS \
-                  $DEX_VERIFY \
-                  -XXlib:$LIB \
-                  $DEX2OAT \
-                  $DALVIKVM_ISA_FEATURES_ARGS \
-                  $ZYGOTE \
-                  $JNI_OPTS \
-                  $INT_OPTS \
-                  $DEBUGGER_OPTS \
-                  ${QUOTED_DALVIKVM_BOOT_OPT} \
-                  $TMP_DIR_OPTION \
-                  -XX:DumpNativeStackOnSigQuit:false \
-                  -cp $DALVIKVM_CLASSPATH $MAIN $ARGS"
-
-if [ "x$SIMPLEPERF" == xyes ]; then
-  dalvikvm_cmdline="simpleperf record ${dalvikvm_cmdline} && simpleperf report"
-fi
-
-sanitize_dex2oat_cmdline() {
-  local args=()
-  for arg in "$@"; do
-    if [ "$arg" = "--class-loader-context=&" ]; then
-      arg="--class-loader-context=\&"
-    fi
-    args+=("$arg")
-  done
-  echo -n "${args[@]}"
-}
-
-# Remove whitespace.
-dex2oat_cmdline=$(sanitize_dex2oat_cmdline $(echo $dex2oat_cmdline))
-dalvikvm_cmdline=$(echo $dalvikvm_cmdline)
-dm_cmdline=$(echo $dm_cmdline)
-vdex_cmdline=$(sanitize_dex2oat_cmdline $(echo $vdex_cmdline))
-profman_cmdline=$(echo $profman_cmdline)
-
-# Use an empty ASAN_OPTIONS to enable defaults.
-# Note: this is required as envsetup right now exports detect_leaks=0.
-RUN_TEST_ASAN_OPTIONS=""
-
-# Multiple shutdown leaks. b/38341789
-if [ "x$RUN_TEST_ASAN_OPTIONS" != "x" ] ; then
-  RUN_TEST_ASAN_OPTIONS="${RUN_TEST_ASAN_OPTIONS}:"
-fi
-RUN_TEST_ASAN_OPTIONS="${RUN_TEST_ASAN_OPTIONS}detect_leaks=0"
-
-# For running, we must turn off logging when dex2oat is missing. Otherwise we use
-# the same defaults as for prebuilt: everything when --dev, otherwise errors and above only.
-if [ "$EXTERNAL_LOG_TAGS" = "n" ]; then
-  if [ "$DEV_MODE" = "y" ]; then
-      export ANDROID_LOG_TAGS='*:d'
-  elif [ "$HAVE_IMAGE" = "n" ]; then
-      # All tests would log the error of missing image. Be silent here and only log fatal
-      # events.
-      export ANDROID_LOG_TAGS='*:s'
-  else
-      # We are interested in LOG(ERROR) output.
-      export ANDROID_LOG_TAGS='*:e'
-  fi
-fi
-
-if [ "$HOST" = "n" ]; then
-    adb root > /dev/null
-    adb wait-for-device
-    if [ "$QUIET" = "n" ]; then
-      adb shell rm -rf $CHROOT_DEX_LOCATION
-      adb shell mkdir -p $CHROOT_DEX_LOCATION
-      adb push $TEST_NAME.jar $CHROOT_DEX_LOCATION
-      adb push $TEST_NAME-ex.jar $CHROOT_DEX_LOCATION
-      adb push $TEST_NAME-aotex.jar $CHROOT_DEX_LOCATION
-      adb push $TEST_NAME-bcpex.jar $CHROOT_DEX_LOCATION
-      if [ "$PROFILE" = "y" ] || [ "$RANDOM_PROFILE" = "y" ]; then
-        adb push profile $CHROOT_DEX_LOCATION
-      fi
-      # Copy resource folder
-      if [ -d res ]; then
-        adb push res $CHROOT_DEX_LOCATION
-      fi
-    else
-      adb shell rm -rf $CHROOT_DEX_LOCATION >/dev/null 2>&1
-      adb shell mkdir -p $CHROOT_DEX_LOCATION >/dev/null 2>&1
-      adb push $TEST_NAME.jar $CHROOT_DEX_LOCATION >/dev/null 2>&1
-      adb push $TEST_NAME-ex.jar $CHROOT_DEX_LOCATION >/dev/null 2>&1
-      adb push $TEST_NAME-aotex.jar $CHROOT_DEX_LOCATION >/dev/null 2>&1
-      adb push $TEST_NAME-bcpex.jar $CHROOT_DEX_LOCATION >/dev/null 2>&1
-      if [ "$PROFILE" = "y" ] || [ "$RANDOM_PROFILE" = "y" ]; then
-        adb push profile $CHROOT_DEX_LOCATION >/dev/null 2>&1
-      fi
-      # Copy resource folder
-      if [ -d res ]; then
-        adb push res $CHROOT_DEX_LOCATION >/dev/null 2>&1
-      fi
-    fi
-
-    # Populate LD_LIBRARY_PATH.
-    LD_LIBRARY_PATH=
-    if [ "$ANDROID_ROOT" != "/system" ]; then
-      # Current default installation is dalvikvm 64bits and dex2oat 32bits,
-      # so we can only use LD_LIBRARY_PATH when testing on a local
-      # installation.
-      LD_LIBRARY_PATH="$ANDROID_ROOT/$LIBRARY_DIRECTORY"
-    fi
-
-    # This adds libarttest(d).so to the default linker namespace when dalvikvm
-    # is run from /apex/com.android.art/bin. Since that namespace is essentially
-    # an alias for the com_android_art namespace, that gives libarttest(d).so
-    # full access to the internal ART libraries.
-    LD_LIBRARY_PATH="/data/$TEST_DIRECTORY/com.android.art/lib${SUFFIX64}:$LD_LIBRARY_PATH"
-    if [ "$TEST_IS_NDEBUG" = "y" ]; then dlib=""; else dlib="d"; fi
-    art_test_internal_libraries=(
-      libartagent${dlib}.so
-      libarttest${dlib}.so
-      libtiagent${dlib}.so
-      libtistress${dlib}.so
-    )
-    art_test_internal_libraries="${art_test_internal_libraries[*]}"
-    NATIVELOADER_DEFAULT_NAMESPACE_LIBS="${art_test_internal_libraries// /:}"
-    dlib=
-    art_test_internal_libraries=
-
-    # Needed to access the test's Odex files.
-    LD_LIBRARY_PATH="$DEX_LOCATION/oat/$ISA:$LD_LIBRARY_PATH"
-    # Needed to access the test's native libraries (see e.g. 674-hiddenapi,
-    # which generates `libhiddenapitest_*.so` libraries in `$DEX_LOCATION`).
-    LD_LIBRARY_PATH="$DEX_LOCATION:$LD_LIBRARY_PATH"
-
-    # Prepend directories to the path on device.
-    PREPEND_TARGET_PATH=$ANDROID_ART_BIN_DIR
-    if [ "$ANDROID_ROOT" != "/system" ]; then
-      PREPEND_TARGET_PATH="$PREPEND_TARGET_PATH:$ANDROID_ROOT/bin"
-    fi
-
-    timeout_dumper_cmd=
-
-    # Check whether signal_dumper is available.
-    if [ "$TIMEOUT_DUMPER" = signal_dumper ] ; then
-      # Chroot? Use as prefix for tests.
-      TIMEOUT_DUMPER_PATH_PREFIX=
-      if [ -n "$CHROOT" ]; then
-        TIMEOUT_DUMPER_PATH_PREFIX="$CHROOT/"
-      fi
-
-      # Testing APEX?
-      if adb shell "test -x ${TIMEOUT_DUMPER_PATH_PREFIX}/apex/com.android.art/bin/signal_dumper" ; then
-        TIMEOUT_DUMPER="/apex/com.android.art/bin/signal_dumper"
-      # Is it in /system/bin?
-      elif adb shell "test -x ${TIMEOUT_DUMPER_PATH_PREFIX}/system/bin/signal_dumper" ; then
-        TIMEOUT_DUMPER="/system/bin/signal_dumper"
-      else
-        TIMEOUT_DUMPER=
-      fi
-    else
-      TIMEOUT_DUMPER=
-    fi
-
-    if [ ! -z "$TIMEOUT_DUMPER" ] ; then
-      # Use "-l" to dump to logcat. That is convenience for the build bot crash symbolization.
-      # Use exit code 124 for toybox timeout (b/141007616).
-      timeout_dumper_cmd="${TIMEOUT_DUMPER} -l -s 15 -e 124"
-    fi
-
-    timeout_prefix=
-    if [ "$TIME_OUT" = "timeout" ]; then
-      # Add timeout command if time out is desired.
-      #
-      # Note: We first send SIGTERM (the timeout default, signal 15) to the signal dumper, which
-      #       will induce a full thread dump before killing the process. To ensure any issues in
-      #       dumping do not lead to a deadlock, we also use the "-k" option to definitely kill the
-      #       child.
-      # Note: Using "--foreground" to not propagate the signal to children, i.e., the runtime.
-      timeout_prefix="timeout --foreground -k 120s ${TIME_OUT_VALUE}s ${timeout_dumper_cmd} $cmdline"
-    fi
-
-    # Create a script with the command. The command can get longer than the longest
-    # allowed adb command and there is no way to get the exit status from a adb shell
-    # command. Dalvik cache is cleaned before running to make subsequent executions
-    # of the script follow the same runtime path.
-    cmdline="cd $DEX_LOCATION && \
-             export ASAN_OPTIONS=$RUN_TEST_ASAN_OPTIONS && \
-             export ANDROID_DATA=$DEX_LOCATION && \
-             export DEX_LOCATION=$DEX_LOCATION && \
-             export ANDROID_ROOT=$ANDROID_ROOT && \
-             export ANDROID_I18N_ROOT=$ANDROID_I18N_ROOT && \
-             export ANDROID_ART_ROOT=$ANDROID_ART_ROOT && \
-             export ANDROID_TZDATA_ROOT=$ANDROID_TZDATA_ROOT && \
-             export ANDROID_LOG_TAGS=$ANDROID_LOG_TAGS && \
-             rm -rf ${DEX_LOCATION}/dalvik-cache/ && \
-             mkdir -p ${mkdir_locations} && \
-             export LD_LIBRARY_PATH=$LD_LIBRARY_PATH && \
-             export NATIVELOADER_DEFAULT_NAMESPACE_LIBS=$NATIVELOADER_DEFAULT_NAMESPACE_LIBS && \
-             export PATH=$PREPEND_TARGET_PATH:\$PATH && \
-             $profman_cmdline && \
-             $dex2oat_cmdline && \
-             $dm_cmdline && \
-             $vdex_cmdline && \
-             $strip_cmdline && \
-             $sync_cmdline && \
-             $timeout_prefix $dalvikvm_cmdline"
-
-    cmdfile=$(mktemp cmd-XXXX --suffix "-$TEST_NAME")
-    echo "$cmdline" >> $cmdfile
-
-    if [ "$DEV_MODE" = "y" ]; then
-      echo $cmdline
-      if [ "$USE_GDB" = "y" ] || [ "$USE_GDBSERVER" = "y" ]; then
-        echo "Forward ${GDBSERVER_PORT} to local port and connect GDB"
-      fi
-    fi
-
-    if [ "$QUIET" = "n" ]; then
-      adb push $cmdfile $CHROOT_DEX_LOCATION/cmdline.sh
-    else
-      adb push $cmdfile $CHROOT_DEX_LOCATION/cmdline.sh >/dev/null 2>&1
-    fi
-
-    exit_status=0
-    if [ "$DRY_RUN" != "y" ]; then
-      if [ -n "$CHROOT" ]; then
-        adb shell chroot "$CHROOT" sh $DEX_LOCATION/cmdline.sh
-      else
-        adb shell sh $DEX_LOCATION/cmdline.sh
-      fi
-      exit_status=$?
-    fi
-
-    rm -f $cmdfile
-    exit $exit_status
-else
-    # Host run.
-    export ANDROID_PRINTF_LOG=brief
-
-    export ANDROID_DATA="$DEX_LOCATION"
-    export ANDROID_ROOT="${ANDROID_ROOT}"
-    export ANDROID_I18N_ROOT="${ANDROID_I18N_ROOT}"
-    export ANDROID_ART_ROOT="${ANDROID_ART_ROOT}"
-    export ANDROID_TZDATA_ROOT="${ANDROID_TZDATA_ROOT}"
-    if [ "$USE_ZIPAPEX" = "y" ] || [ "$USE_EXRACTED_ZIPAPEX" = "y" ]; then
-      # Put the zipapex files in front of the 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 PATH="$PATH:$ANDROID_ART_BIN_DIR"
-
-    # Temporarily disable address space layout randomization (ASLR).
-    # This is needed on the host so that the linker loads core.oat at the necessary address.
-    export LD_USE_LOAD_BIAS=1
-
-    cmdline="$dalvikvm_cmdline"
-
-    if [ "$TIME_OUT" = "gdb" ]; then
-      if [ `uname` = "Darwin" ]; then
-        # Fall back to timeout on Mac.
-        TIME_OUT="timeout"
-      elif [ "$ISA" = "x86" ]; then
-        # prctl call may fail in 32-bit on an older (3.2) 64-bit Linux kernel. Fall back to timeout.
-        TIME_OUT="timeout"
-      else
-        # Check if gdb is available.
-        gdb --eval-command="quit" > /dev/null 2>&1
-        if [ $? != 0 ]; then
-          # gdb isn't available. Fall back to timeout.
-          TIME_OUT="timeout"
-        fi
-      fi
-    fi
-
-    if [ "$TIME_OUT" = "timeout" ]; then
-      # Add timeout command if time out is desired.
-      #
-      # Note: We first send SIGTERM (the timeout default, signal 15) to the signal dumper, which
-      #       will induce a full thread dump before killing the process. To ensure any issues in
-      #       dumping do not lead to a deadlock, we also use the "-k" option to definitely kill the
-      #       child.
-      # Note: Using "--foreground" to not propagate the signal to children, i.e., the runtime.
-      cmdline="timeout --foreground -k 120s ${TIME_OUT_VALUE}s ${TIMEOUT_DUMPER} -s 15 $cmdline"
-    fi
-
-    if [ "$DEV_MODE" = "y" ]; then
-      for var in ANDROID_PRINTF_LOG ANDROID_DATA ANDROID_ROOT ANDROID_I18N_ROOT ANDROID_TZDATA_ROOT ANDROID_ART_ROOT LD_LIBRARY_PATH DYLD_LIBRARY_PATH PATH LD_USE_LOAD_BIAS; do
-        echo EXPORT $var=${!var}
-      done
-      echo "$(declare -f linkdirs)"
-      echo "mkdir -p ${mkdir_locations} && $setupapex_cmdline && ( $installapex_test_cmdline || $installapex_cmdline ) && $linkroot_cmdline && $linkroot_overlay_cmdline && $profman_cmdline && $dex2oat_cmdline && $dm_cmdline && $vdex_cmdline && $strip_cmdline && $sync_cmdline && $cmdline"
-    fi
-
-    cd $ANDROID_BUILD_TOP
-
-    # Make sure we delete any existing compiler artifacts.
-    # This enables tests to call the RUN script multiple times in a row
-    # without worrying about interference.
-    rm -rf ${DEX_LOCATION}/oat
-    rm -rf ${DEX_LOCATION}/dalvik-cache/
-
-    export ASAN_OPTIONS=$RUN_TEST_ASAN_OPTIONS
-
-    mkdir -p ${mkdir_locations} || exit 1
-    $setupapex_cmdline || { echo "zipapex extraction failed." >&2 ; exit 2; }
-    $installapex_test_cmdline || $installapex_cmdline || { echo "zipapex install failed. cmd was: ${installapex_test_cmdline} || ${installapex_cmdline}." >&2; find ${mkdir_locations} -type f >&2; exit 2; }
-    $linkroot_cmdline || { echo "create symlink android-root failed." >&2 ; exit 2; }
-    $linkroot_overlay_cmdline || { echo "overlay android-root failed." >&2 ; exit 2; }
-    $profman_cmdline || { echo "Profman failed." >&2 ; exit 2; }
-    eval "$dex2oat_cmdline" || { echo "Dex2oat failed." >&2 ; exit 2; }
-    eval "$dm_cmdline" || { echo "Dex2oat failed." >&2 ; exit 2; }
-    eval "$vdex_cmdline" || { echo "Dex2oat failed." >&2 ; exit 2; }
-    $strip_cmdline || { echo "Strip failed." >&2 ; exit 3; }
-    $sync_cmdline || { echo "Sync failed." >&2 ; exit 4; }
-
-    if [ "$CREATE_RUNNER" = "y" ]; then
-      echo "#!/bin/bash" > ${DEX_LOCATION}/runit.sh
-      for var in ANDROID_PRINTF_LOG ANDROID_DATA ANDROID_ROOT ANDROID_I18N_ROOT ANDROID_TZDATA_ROOT ANDROID_ART_ROOT LD_LIBRARY_PATH DYLD_LIBRARY_PATH PATH LD_USE_LOAD_BIAS; do
-        echo export $var="${!var}" >> ${DEX_LOCATION}/runit.sh
-      done
-      if [ "$DEV_MODE" = "y" ]; then
-        echo $cmdline >> ${DEX_LOCATION}/runit.sh
-      else
-        echo 'STDERR=$(mktemp)' >> ${DEX_LOCATION}/runit.sh
-        echo 'STDOUT=$(mktemp)' >> ${DEX_LOCATION}/runit.sh
-        echo $cmdline '>${STDOUT} 2>${STDERR}' >> ${DEX_LOCATION}/runit.sh
-        echo 'if diff ${STDOUT} $ANDROID_DATA/expected-stdout.txt; then' \
-          >> ${DEX_LOCATION}/runit.sh
-        echo '  rm -f ${STDOUT} ${STDERR}' >> ${DEX_LOCATION}/runit.sh
-        echo '  exit 0' >> ${DEX_LOCATION}/runit.sh
-        echo 'elif diff ${STDERR} $ANDROID_DATA/expected-stderr.txt; then' \
-          >> ${DEX_LOCATION}/runit.sh
-        echo '  rm -f ${STDOUT} ${STDERR}' >> ${DEX_LOCATION}/runit.sh
-        echo '  exit 0' >> ${DEX_LOCATION}/runit.sh
-        echo 'else' >> ${DEX_LOCATION}/runit.sh
-        echo '  echo  STDOUT:' >> ${DEX_LOCATION}/runit.sh
-        echo '  cat ${STDOUT}' >> ${DEX_LOCATION}/runit.sh
-        echo '  echo  STDERR:' >> ${DEX_LOCATION}/runit.sh
-        echo '  cat ${STDERR}' >> ${DEX_LOCATION}/runit.sh
-        echo '  rm -f ${STDOUT} ${STDERR}' >> ${DEX_LOCATION}/runit.sh
-        echo '  exit 1' >> ${DEX_LOCATION}/runit.sh
-        echo 'fi' >> ${DEX_LOCATION}/runit.sh
-      fi
-      chmod u+x $DEX_LOCATION/runit.sh
-      echo "Runnable test script written to ${DEX_LOCATION}/runit.sh"
-    fi
-    if [ "$DRY_RUN" = "y" ]; then
-      exit 0
-    fi
-
-    if [ "$USE_GDB" = "y" ]; then
-      # When running under gdb, we cannot do piping and grepping...
-      $cmdline "$@"
-    elif [ "$USE_GDBSERVER" = "y" ]; then
-      echo "Connect to $GDBSERVER_PORT"
-      # When running under gdb, we cannot do piping and grepping...
-      $cmdline "$@"
-    else
-      if [ "$TIME_OUT" != "gdb" ]; then
-        trap 'kill -INT -$pid' INT
-        $cmdline "$@" & pid=$!
-        wait $pid
-        exit_value=$?
-        # Add extra detail if time out is enabled.
-        if [ $exit_value = 124 ] && [ "$TIME_OUT" = "timeout" ]; then
-          echo -e "\e[91mTEST TIMED OUT!\e[0m" >&2
-        fi
-        exit $exit_value
-      else
-        # With a thread dump that uses gdb if a timeout.
-        trap 'kill -INT -$pid' INT
-        $cmdline "$@" & pid=$!
-        # Spawn a watcher process.
-        ( sleep $TIME_OUT_VALUE && \
-          echo "##### Thread dump using gdb on test timeout" && \
-          ( gdb -q -p $pid --eval-command="info thread" --eval-command="thread apply all bt" \
-                           --eval-command="call exit(124)" --eval-command=quit || \
-            kill $pid )) 2> /dev/null & watcher=$!
-        wait $pid
-        test_exit_status=$?
-        pkill -P $watcher 2> /dev/null # kill the sleep which will in turn end the watcher as well
-        if [ $test_exit_status = 0 ]; then
-          # The test finished normally.
-          exit 0
-        else
-          # The test failed or timed out.
-          if [ $test_exit_status = 124 ]; then
-            # The test timed out.
-            echo -e "\e[91mTEST TIMED OUT!\e[0m" >&2
-          fi
-          exit $test_exit_status
-        fi
-      fi
-    fi
-fi
diff --git a/test/generate-boot-image/generate-boot-image.cc b/test/generate-boot-image/generate-boot-image.cc
index 475bfb1..1b3eccf 100644
--- a/test/generate-boot-image/generate-boot-image.cc
+++ b/test/generate-boot-image/generate-boot-image.cc
@@ -109,7 +109,8 @@
   android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
 
   std::string dir = "";
-  // Set the compiler filter to `verify` by default to make test preparation faster.
+  // Set the compiler filter to `verify` by default to make test preparation
+  // faster.
   std::string compiler_filter = "verify";
   for (int i = 1; i < argc; i++) {
     std::string_view arg{argv[i]};
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 3e485f6..f6c0e25 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -81,7 +81,7 @@
     },
     {
         "tests" : "629-vdex-speed",
-        "variant": "interp-ac | interpreter | jit",
+        "variant": "interp-ac | interpreter | no-prebuild | debuggable | trace | stream",
         "description": "629 requires compilation."
     },
     {
@@ -183,9 +183,10 @@
                   "138-duplicate-classes-check",
                   "018-stack-overflow",
                   "961-default-iface-resolution-gen",
-                  "964-default-iface-init-gen"],
+                  "964-default-iface-init-gen",
+                  "2047-checker-const-string-length"],
         "variant": "no-image",
-        "description": ["This test fails without an image. 018, 961, 964 often",
+        "description": ["137, 138, 2047 fail without an image. 018, 961, 964 often ",
                         "time out."],
         "bug": "http://b/34369284"
     },
@@ -339,12 +340,6 @@
         "variant": "optimizing | regalloc_gc"
     },
     {
-        "tests": "089-many-methods",
-        "description": "The test tests a build failure",
-        "env_vars": {"ART_TEST_BISECTION": "true"},
-        "variant": "optimizing | regalloc_gc"
-    },
-    {
         "tests": ["018-stack-overflow",
                   "116-nodex2oat",
                   "118-noimage-dex2oat",
@@ -383,16 +378,10 @@
         "variant": "interp-ac"
     },
     {
-        "tests": ["629-vdex-speed",
-                  "634-vdex-duplicate"],
-        "description": ["Profile driven dexlayout does not work with vdex or dex verifier."],
-        "variant": "speed-profile"
-    },
-    {
         "test_patterns": ["616-cha.*"],
         "description": ["cha tests rely on knowing the exact set of optimizations available. ",
                         "Debuggable runtimes change the set of optimizations."],
-        "variant": "debuggable"
+        "variant": "debuggable | trace | stream"
     },
     {
         "test_patterns": ["616-cha.*"],
@@ -550,6 +539,7 @@
             "674-hiddenapi",
             "690-hiddenapi-same-name-methods",
             "804-class-extends-itself",
+            "842-vdex-hard-failure",
             "921-hello-failure",
             "999-redefine-hiddenapi"
         ],
@@ -712,10 +702,9 @@
         "description": "Test disabled due to redefine-stress disabling intrinsics which changes the trace output slightly."
     },
     {
-        "tests": ["137-cfi", "629-vdex-speed"],
-        "description": [ "Tests require speed compilation which is no longer the default for",
-                          "no-prebuild or no-image configs."],
-        "variant": "no-prebuild | no-image"
+        "tests": ["137-cfi"],
+        "description": [ "Tests require speed compilation which is no longer the default for no-prebuild"],
+        "variant": "no-prebuild"
     },
     {
         "tests": ["059-finalizer-throw", "063-process-manager"],
@@ -760,20 +749,13 @@
         "bug": "b/64683522"
     },
     {
-        "tests": ["628-vdex",
-                  "629-vdex-speed",
-                  "634-vdex-duplicate"],
-        "variant": "cdex-fast",
-        "description": ["Tests that depend on input-vdex are not supported with compact dex"]
-    },
-    {
         "tests": ["661-oat-writer-layout"],
         "variant": "interp-ac | interpreter | jit | jit-on-first-use | no-prebuild | no-image | trace | redefine-stress | jvmti-stress",
         "description": ["Test is designed to only check --optimizing"]
     },
     {
         "tests": ["004-StackWalk"],
-        "variant": "speed-profile | interp-ac | interpreter | jit | no-prebuild | no-image | trace | redefine-stress | jvmti-stress | debuggable",
+        "variant": "speed-profile | interp-ac | interpreter | jit | no-prebuild | no-image | trace | redefine-stress | jvmti-stress | debuggable | stream",
         "description": ["Test is designed to only check --optimizing"]
     },
     {
@@ -1030,6 +1012,7 @@
           "816-illegal-new-array",
           "819-verification-runtime",
           "823-cha-inlining",
+          "842-vdex-hard-failure",
           "900-hello-plugin",
           "901-hello-ti-agent",
           "903-hello-tagging",
@@ -1073,6 +1056,7 @@
           "988-method-trace",
           "989-method-trace-throw",
           "993-breakpoints",
+          "993-breakpoints-non-debuggable",
           "1002-notify-startup",
           "1003-metadata-section-strings",
           "1336-short-finalizer-timeout",
@@ -1090,13 +1074,21 @@
           "1946-list-descriptors",
           "1947-breakpoint-redefine-deopt",
           "2041-bad-cleaner",
-          "2230-profile-save-hotness"
+          "2230-profile-save-hotness",
+          "2245-checker-smali-instance-of-comparison",
+          "2251-checker-irreducible-loop-do-not-inline"
         ],
         "variant": "jvm",
         "bug": "b/73888836",
         "description": ["Failing on RI. Needs further investigating. Some of these use smali."]
     },
     {
+        "tests": ["2042-reference-processing",
+                  "2043-reference-pauses"],
+        "variant": "jvm",
+        "description": ["Flakey behavior of RI."]
+    },
+    {
       "tests": [
                   "1974-resize-array",
                   "1975-hello-structural-transformation",
@@ -1133,7 +1125,7 @@
                   "2006-virtual-structural-finalizing",
                   "2007-virtual-structural-finalizable"
                 ],
-        "env_vars": {"ART_USE_READ_BARRIER": "false"},
+        "env_vars": {"ART_USE_READ_BARRIER": "false", "ART_DEFAULT_GC_TYPE": "CMS"},
         "description": ["Relies on the accuracy of the Heap::VisitObjects function which is broken",
                         " when READ_BARRIER==false (I.e. On CMS collector)."],
         "bug": "b/147207934"
@@ -1168,6 +1160,13 @@
                   "831-unresolved-field",
                   "833-background-verification",
                   "836-32768classes",
+                  "837-deopt",
+                  "844-exception",
+                  "844-exception2",
+                  "845-fast-verify",
+                  "845-data-image",
+                  "846-multidex-data-image",
+                  "847-filled-new-aray",
                   "999-redefine-hiddenapi",
                   "1000-non-moving-space-stress",
                   "1001-app-image-regions",
@@ -1218,7 +1217,11 @@
                   "2036-structural-subclass-shadow",
                   "2038-hiddenapi-jvmti-ext",
                   "2040-huge-native-alloc",
-                  "2238-checker-polymorphic-recursive-inlining"],
+                  "2238-checker-polymorphic-recursive-inlining",
+                  "2240-tracing-non-invokable-method",
+                  "2246-trace-stream",
+                  "2254-class-value-before-and-after-u",
+                  "2261-badcleaner-in-systemcleaner"],
         "variant": "jvm",
         "description": ["Doesn't run on RI."]
     },
@@ -1261,7 +1264,7 @@
         "tests": ["141-class-unload", "071-dexfile"],
         "variant": "gcstress",
         "bug": "b/111543628",
-        "description" : ["Test seems to timeout when run with gcstress due to slower unwinding by libbacktrace"]
+        "description" : ["Test seems to timeout when run with gcstress due to slower unwinding by libunwindstack"]
     },
     {
         "tests": ["708-jit-cache-churn"],
@@ -1273,7 +1276,7 @@
         "tests": ["712-varhandle-invocations"],
         "variant": "gcstress",
         "bug": "b/111630237",
-        "description": ["Test timing out under gcstress possibly due to slower unwinding by libbacktrace"]
+        "description": ["Test timing out under gcstress possibly due to slower unwinding by libunwindstack"]
     },
     {
         "tests": ["1336-short-finalizer-timeout"],
@@ -1307,7 +1310,7 @@
     },
     {
         "tests": ["1339-dead-reference-safe"],
-        "variant": "debuggable",
+        "variant": "debuggable | trace | stream",
         "description": [ "Fails to eliminate dead reference when debuggable." ]
     },
     {
@@ -1326,6 +1329,13 @@
         "description": ["Test containing Checker assertions expecting Baker read barriers."]
     },
     {
+        "tests": ["2040-huge-native-alloc"],
+        "env_vars": {"ART_USE_READ_BARRIER": "false"},
+        "variant": "debug",
+        "bug": "b/242181443",
+        "description": ["Test fails due to delay delebrately added in the userfaultfd GC between marking and compaction."]
+    },
+    {
         "tests": ["1004-checker-volatile-ref-load"],
         "env_vars": {"ART_READ_BARRIER_TYPE": "TABLELOOKUP"},
         "bug": "b/140507091",
@@ -1358,11 +1368,23 @@
         "description": "Interpreting BigInteger.add() is too slow (timeouts)"
     },
     {
-        "tests": ["2029-contended-monitors"],
-        "variant": "interpreter | interp-ac | gcstress | trace",
+        "tests": ["2029-contended-monitors", "2043-reference-pauses"],
+        "variant": "interpreter | interp-ac | gcstress | trace | stream",
+        "description": ["Slow tests. Prone to timeouts."]
+    },
+    {
+        "tests": ["2042-reference-processing"],
+        "variant": "interpreter | interp-ac | gcstress | trace | debuggable | stream",
         "description": ["Slow test. Prone to timeouts."]
     },
     {
+        "tests": ["2043-reference-pauses"],
+        "env_vars": {"ART_USE_READ_BARRIER": "false", "ART_DEFAULT_GC_TYPE": "CMS"},
+        "variant": "host",
+        "bug": "b/232459100",
+        "description": ["Fails intermittently for CMS."]
+    },
+    {
         "tests": ["096-array-copy-concurrent-gc"],
         "variant": "gcstress & debuggable & debug & host",
         "bug": "b/149708943",
@@ -1420,7 +1442,7 @@
     },
     {
         "tests": ["692-vdex-secondary-loader"],
-        "env_vars": {"ART_USE_READ_BARRIER": "false"},
+        "env_vars": {"ART_USE_READ_BARRIER": "false", "ART_DEFAULT_GC_TYPE": "CMS"},
         "description": ["Uses the low-ram flag which does not work with CMS"]
     },
     {
@@ -1497,5 +1519,55 @@
         "variant": "target & ndebug & 64",
         "bug": "b/224733324",
         "description": ["segfault in VarHandle::GetMethodTypeMatchForAccessMode"]
+    },
+    {
+        "tests": ["2043-reference-pauses"],
+        "env_vars": {"ART_TEST_DEBUG_GC": "true"},
+        "description": ["Test timing out on debug gc."]
+    },
+    {
+        "tests": ["837-deopt"],
+        "description": ["Tests deoptimization and OSR, which never happens when tracing."],
+        "variant": "trace | stream | jit-on-first-use"
+    },
+    {
+        "tests": ["1912-get-set-local-primitive"],
+        "description": ["JVMTI error code used changed in JDK 17."],
+        "bug": "b/243356199",
+        "variant": "jvm"
+    },
+    {
+        "tests": ["715-clinit-implicit-parameter-annotations"],
+        "description": ["Change for Annotation.toString() in JDK 17."],
+        "bug": "b/243500721",
+        "variant": "jvm"
+    },
+    {
+        "tests": ["1907-suspend-list-self-twice"],
+        "description": ["Change of behavior when used with JDK 17."],
+        "bug": "b/243139124",
+        "variant": "jvm"
+    },
+    {
+        "tests": ["1921-suspend-native-recursive-monitor"],
+        "description": ["Change of behavior when used with JDK 17."],
+        "bug": "b/242985234",
+        "variant": "jvm"
+    },
+    {
+        "tests": ["2246-trace-stream"],
+        "env_vars": {"ART_TEST_DEBUG_GC": "true"},
+        "bug": "b/264844668",
+        "description": ["Test timing out on debug gc."]
+    },
+    {
+        "tests": ["845-data-image", "846-multidex-data-image"],
+        "variant": "debuggable",
+        "description": ["Runtime app images are not supported with debuggable."]
+    },
+    {
+        "tests": ["2262-miranda-methods"],
+        "variant": "jvm",
+        "description": ["jvm doesn't seem to support calling miranda methods via CallNonVirtual."]
     }
 ]
diff --git a/test/odsign/Android.bp b/test/odsign/Android.bp
index 511f5a1..eb09587 100644
--- a/test/odsign/Android.bp
+++ b/test/odsign/Android.bp
@@ -50,6 +50,9 @@
     data: [
         ":odsign_e2e_test_app",
     ],
+    java_resources: [
+        ":art-gtest-jars-Main",
+    ],
     test_config: "odsign-e2e-tests-full.xml",
     test_suites: [
         "general-tests",
diff --git a/test/odsign/test-src/com/android/tests/odsign/ActivationTest.java b/test/odsign/test-src/com/android/tests/odsign/ActivationTest.java
index 05db530..b8ef792 100644
--- a/test/odsign/test-src/com/android/tests/odsign/ActivationTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/ActivationTest.java
@@ -81,7 +81,8 @@
         // artifacts compiled and signed by odrefresh and odsign. We check both here rather than
         // having a separate test because the device reboots between each @Test method and
         // that is an expensive use of time.
-        mTestUtils.verifyZygotesLoadedArtifacts("boot");
+        mTestUtils.verifyZygotesLoadedPrimaryBootImage();
+        mTestUtils.verifyZygotesLoadedBootImageMainlineExtension();
         mTestUtils.verifySystemServerLoadedArtifacts();
     }
 
diff --git a/test/odsign/test-src/com/android/tests/odsign/CompOsDenialHostTest.java b/test/odsign/test-src/com/android/tests/odsign/CompOsDenialHostTest.java
index 16ef562..83e4a39 100644
--- a/test/odsign/test-src/com/android/tests/odsign/CompOsDenialHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/CompOsDenialHostTest.java
@@ -96,18 +96,6 @@
     }
 
     @Test
-    public void vmLogCollector() throws Exception {
-        // This is not a test. The purpose is to collect VM's log, which is generated once per
-        // class, in beforeClassWithDevice before any tests run. It's implemented as a test methond
-        // because TestLogData doesn't seem to work in a class method.
-        OdsignTestUtils testUtils = new OdsignTestUtils(getTestInformation());
-        testUtils.archiveLogThenDelete(mTestLogs, CompOsTestUtils.APEXDATA_DIR + "/vm.log",
-                "vm.log-CompOsDenialHostTest");
-        testUtils.archiveLogThenDelete(mTestLogs, CompOsTestUtils.APEXDATA_DIR + "/vm_console.log",
-                "vm_console.log-CompOsDenialHostTest");
-    }
-
-    @Test
     public void denyDueToInconsistentFileName() throws Exception {
         // Attack emulation: swap file names
         String[] paths = getAllPendingOdexPaths();
diff --git a/test/odsign/test-src/com/android/tests/odsign/CompOsSigningHostTest.java b/test/odsign/test-src/com/android/tests/odsign/CompOsSigningHostTest.java
index bb382d6..134cff9 100644
--- a/test/odsign/test-src/com/android/tests/odsign/CompOsSigningHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/CompOsSigningHostTest.java
@@ -20,12 +20,8 @@
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tests.odsign.annotation.CtsTestCase;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -36,8 +32,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.concurrent.TimeUnit;
-
 /**
  * Test to check if CompOS works properly.
  *
@@ -89,21 +83,6 @@
     }
 
     @Test
-    public void vmLogCollector() throws Exception {
-        // This is not a test. The purpose is to collect VM's log, which is generated once per
-        // class. It's implemented as a test methond because TestLogData doesn't seem to work in a
-        // class method.
-
-        // The log files are currently only available through a rooted shell.
-        OdsignTestUtils testUtils = new OdsignTestUtils(getTestInformation());
-
-        testUtils.archiveLogThenDelete(mTestLogs, CompOsTestUtils.APEXDATA_DIR + "/vm.log",
-                        "vm.log-CompOsSigningHostTest");
-        testUtils.archiveLogThenDelete(mTestLogs, CompOsTestUtils.APEXDATA_DIR + "/vm_console.log",
-                        "vm_console.log-CompOsSigningHostTest");
-    }
-
-    @Test
     @CtsTestCase
     public void checkFileChecksums() throws Exception {
         CompOsTestUtils compOsTestUtils = new CompOsTestUtils(getDevice());
diff --git a/test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java b/test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java
index 8c2c5b2..60d7642 100644
--- a/test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java
+++ b/test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java
@@ -16,7 +16,6 @@
 
 package com.android.tests.odsign;
 
-import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.fail;
@@ -36,7 +35,8 @@
             "/data/misc/apexdata/com.android.art/compos-pending";
 
     /** Maximum time for a slow VM like cuttlefish to boot and finish odrefresh. */
-    private static final int VM_ODREFRESH_MAX_SECONDS = 360;
+    // odrefresh overall timeout is currently 480s; add some generous padding for VM startup.
+    private static final int VM_ODREFRESH_MAX_SECONDS = 480 + 60;
 
     /** Waiting time for the job to be scheduled after staging an APEX */
     private static final int JOB_CREATION_MAX_SECONDS = 5;
@@ -74,8 +74,6 @@
         // Sort by filename (second column) to make comparison easier.
         // Filter out compos.info* (which will be deleted at boot) and cache-info.xml
         // compos.info.signature since it's only generated by CompOS.
-        // TODO(b/210473615): Remove irrelevant APEXes (i.e. those aren't contributing to the
-        // classpaths, thus not in the VM) from cache-info.xml.
         return assertCommandSucceeds("cd " + path + "; find -type f -exec sha256sum {} \\;"
                 + "| grep -v cache-info.xml | grep -v compos.info"
                 + "| sort -k2");
diff --git a/test/odsign/test-src/com/android/tests/odsign/DeviceState.java b/test/odsign/test-src/com/android/tests/odsign/DeviceState.java
new file mode 100644
index 0000000..9295831
--- /dev/null
+++ b/test/odsign/test-src/com/android/tests/odsign/DeviceState.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package com.android.tests.odsign;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.invoker.TestInformation;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+/** A helper class that can mutate the device state and restore it afterwards. */
+public class DeviceState {
+    private static final String TEST_JAR_RESOURCE_NAME = "/art-gtest-jars-Main.jar";
+    private static final String PHENOTYPE_FLAG_NAMESPACE = "runtime_native_boot";
+    private static final String ART_APEX_DALVIK_CACHE_BACKUP_DIRNAME =
+            OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME + ".bak";
+
+    private final TestInformation mTestInfo;
+    private final OdsignTestUtils mTestUtils;
+
+    private Set<String> mTempFiles = new HashSet<>();
+    private Set<String> mMountPoints = new HashSet<>();
+    private Map<String, String> mMutatedProperties = new HashMap<>();
+    private Set<String> mMutatedPhenotypeFlags = new HashSet<>();
+    private Map<String, String> mDeletedFiles = new HashMap<>();
+    private boolean mHasArtifactsBackup = false;
+
+    public DeviceState(TestInformation testInfo) throws Exception {
+        mTestInfo = testInfo;
+        mTestUtils = new OdsignTestUtils(testInfo);
+    }
+
+    /** Restores the device state. */
+    public void restore() throws Exception {
+        for (String mountPoint : mMountPoints) {
+            mTestInfo.getDevice().executeShellV2Command(String.format("umount '%s'", mountPoint));
+        }
+
+        for (String tempFile : mTempFiles) {
+            mTestInfo.getDevice().deleteFile(tempFile);
+        }
+
+        for (var entry : mMutatedProperties.entrySet()) {
+            mTestInfo.getDevice().setProperty(
+                    entry.getKey(), entry.getValue() != null ? entry.getValue() : "");
+        }
+
+        for (String flag : mMutatedPhenotypeFlags) {
+            mTestInfo.getDevice().executeShellV2Command(String.format(
+                    "device_config delete '%s' '%s'", PHENOTYPE_FLAG_NAMESPACE, flag));
+        }
+
+        if (!mMutatedPhenotypeFlags.isEmpty()) {
+            mTestInfo.getDevice().executeShellV2Command(
+                    "device_config set_sync_disabled_for_tests none");
+        }
+
+        for (var entry : mDeletedFiles.entrySet()) {
+            mTestInfo.getDevice().executeShellV2Command(
+                    String.format("cp '%s' '%s'", entry.getValue(), entry.getKey()));
+            mTestInfo.getDevice().executeShellV2Command(String.format("rm '%s'", entry.getValue()));
+            mTestInfo.getDevice().executeShellV2Command(
+                    String.format("restorecon '%s'", entry.getKey()));
+        }
+
+        if (mHasArtifactsBackup) {
+            mTestInfo.getDevice().executeShellV2Command(
+                    String.format("rm -rf '%s'", OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME));
+            mTestInfo.getDevice().executeShellV2Command(
+                    String.format("mv '%s' '%s'", ART_APEX_DALVIK_CACHE_BACKUP_DIRNAME,
+                            OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME));
+        }
+    }
+
+    /** Simulates that the ART APEX has been upgraded. */
+    public void simulateArtApexUpgrade() throws Exception {
+        updateApexInfo("com.android.art", false /* isFactory */);
+    }
+
+    /**
+     * Simulates that the new ART APEX has been uninstalled (i.e., the ART module goes back to the
+     * factory version).
+     */
+    public void simulateArtApexUninstall() throws Exception {
+        updateApexInfo("com.android.art", true /* isFactory */);
+    }
+
+    /**
+     * Simulates that an APEX has been upgraded. We could install a real APEX, but that would
+     * introduce an extra dependency to this test, which we want to avoid.
+     */
+    public void simulateApexUpgrade() throws Exception {
+        updateApexInfo("com.android.wifi", false /* isFactory */);
+    }
+
+    /**
+     * Simulates that the new APEX has been uninstalled (i.e., the module goes back to the factory
+     * version).
+     */
+    public void simulateApexUninstall() throws Exception {
+        updateApexInfo("com.android.wifi", true /* isFactory */);
+    }
+
+    private void updateApexInfo(String moduleName, boolean isFactory) throws Exception {
+        try (var xmlMutator = new XmlMutator(OdsignTestUtils.APEX_INFO_FILE)) {
+            NodeList list = xmlMutator.getDocument().getElementsByTagName("apex-info");
+            for (int i = 0; i < list.getLength(); i++) {
+                Element node = (Element) list.item(i);
+                if (node.getAttribute("moduleName").equals(moduleName)
+                        && node.getAttribute("isActive").equals("true")) {
+                    node.setAttribute("isFactory", String.valueOf(isFactory));
+                    node.setAttribute(
+                            "lastUpdateMillis", String.valueOf(System.currentTimeMillis()));
+                }
+            }
+        }
+    }
+
+    /** Simulates that there is an OTA that updates a boot classpath jar. */
+    public void simulateBootClasspathOta() throws Exception {
+        File localFile = mTestUtils.copyResourceToFile(TEST_JAR_RESOURCE_NAME);
+        pushAndBindMount(localFile, "/system/framework/framework.jar");
+    }
+
+    /** Simulates that there is an OTA that updates a system server jar. */
+    public void simulateSystemServerOta() throws Exception {
+        File localFile = mTestUtils.copyResourceToFile(TEST_JAR_RESOURCE_NAME);
+        pushAndBindMount(localFile, "/system/framework/services.jar");
+    }
+
+    public void makeDex2oatFail() throws Exception {
+        setProperty("dalvik.vm.boot-dex2oat-threads", "-1");
+    }
+
+    /** Sets a system property. */
+    public void setProperty(String key, String value) throws Exception {
+        if (!mMutatedProperties.containsKey(key)) {
+            // Backup the original value.
+            mMutatedProperties.put(key, mTestInfo.getDevice().getProperty(key));
+        }
+
+        mTestInfo.getDevice().setProperty(key, value);
+    }
+
+    /** Sets a phenotype flag. */
+    public void setPhenotypeFlag(String key, String value) throws Exception {
+        if (!mMutatedPhenotypeFlags.contains(key)) {
+            // Tests assume that phenotype flags are initially not set. Check if the assumption is
+            // true.
+            assertThat(mTestUtils.assertCommandSucceeds(String.format(
+                               "device_config get '%s' '%s'", PHENOTYPE_FLAG_NAMESPACE, key)))
+                    .isEqualTo("null");
+            mMutatedPhenotypeFlags.add(key);
+        }
+
+        // Disable phenotype flag syncing. Potentially, we can set `set_sync_disabled_for_tests` to
+        // `until_reboot`, but setting it to `persistent` prevents unrelated system crashes/restarts
+        // from affecting the test. `set_sync_disabled_for_tests` is reset in `restore` anyway.
+        mTestUtils.assertCommandSucceeds("device_config set_sync_disabled_for_tests persistent");
+
+        if (value != null) {
+            mTestUtils.assertCommandSucceeds(String.format(
+                    "device_config put '%s' '%s' '%s'", PHENOTYPE_FLAG_NAMESPACE, key, value));
+        } else {
+            mTestUtils.assertCommandSucceeds(
+                    String.format("device_config delete '%s' '%s'", PHENOTYPE_FLAG_NAMESPACE, key));
+        }
+    }
+
+    public void backupAndDeleteFile(String remotePath) throws Exception {
+        String tempFile = "/data/local/tmp/odsign_e2e_tests_" + UUID.randomUUID() + ".tmp";
+        // Backup the file before deleting it.
+        mTestUtils.assertCommandSucceeds(String.format("cp '%s' '%s'", remotePath, tempFile));
+        mTestUtils.assertCommandSucceeds(String.format("rm '%s'", remotePath));
+        mDeletedFiles.put(remotePath, tempFile);
+    }
+
+    public void backupArtifacts() throws Exception {
+        mTestInfo.getDevice().executeShellV2Command(
+                String.format("rm -rf '%s'", ART_APEX_DALVIK_CACHE_BACKUP_DIRNAME));
+        mTestUtils.assertCommandSucceeds(
+                String.format("cp -r '%s' '%s'", OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME,
+                        ART_APEX_DALVIK_CACHE_BACKUP_DIRNAME));
+        mHasArtifactsBackup = true;
+    }
+
+    /**
+     * Pushes the file to a temporary location and bind-mount it at the given path. This is useful
+     * when the path is readonly.
+     */
+    private void pushAndBindMount(File localFile, String remotePath) throws Exception {
+        String tempFile = "/data/local/tmp/odsign_e2e_tests_" + UUID.randomUUID() + ".tmp";
+        assertThat(mTestInfo.getDevice().pushFile(localFile, tempFile)).isTrue();
+        mTempFiles.add(tempFile);
+
+        // If the path has already been bind-mounted by this method before, unmount it first.
+        if (mMountPoints.contains(remotePath)) {
+            mTestUtils.assertCommandSucceeds(String.format("umount '%s'", remotePath));
+            mMountPoints.remove(remotePath);
+        }
+
+        mTestUtils.assertCommandSucceeds(
+                String.format("mount --bind '%s' '%s'", tempFile, remotePath));
+        mMountPoints.add(remotePath);
+        mTestUtils.assertCommandSucceeds(String.format("restorecon '%s'", remotePath));
+    }
+
+    /** A helper class for mutating an XML file. */
+    private class XmlMutator implements AutoCloseable {
+        private final Document mDocument;
+        private final String mRemoteXmlFile;
+        private final File mLocalFile;
+
+        public XmlMutator(String remoteXmlFile) throws Exception {
+            // Load the XML file.
+            mRemoteXmlFile = remoteXmlFile;
+            mLocalFile = mTestInfo.getDevice().pullFile(remoteXmlFile);
+            assertThat(mLocalFile).isNotNull();
+            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+            mDocument = builder.parse(mLocalFile);
+        }
+
+        @Override
+        public void close() throws Exception {
+            // Save the XML file.
+            Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            transformer.transform(new DOMSource(mDocument), new StreamResult(mLocalFile));
+            pushAndBindMount(mLocalFile, mRemoteXmlFile);
+        }
+
+        /** Returns a mutable XML document. */
+        public Document getDocument() {
+            return mDocument;
+        }
+    }
+}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryHostTestBase.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryHostTestBase.java
new file mode 100644
index 0000000..900e978
--- /dev/null
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryHostTestBase.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package com.android.tests.odsign;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class tests odrefresh for the cases where all the APEXes are initially factory-installed.
+ * Similar to OdrefreshHostTest, it does not involve odsign and fs-verity.
+ *
+ * The tests are run by derived classes with different conditions: with and without the cache info.
+ */
+@Ignore("See derived classes")
+abstract public class OdrefreshFactoryHostTestBase extends BaseHostJUnit4Test {
+    protected OdsignTestUtils mTestUtils;
+    protected DeviceState mDeviceState;
+
+    @BeforeClassWithInfo
+    public static void beforeClassWithDeviceBase(TestInformation testInfo) throws Exception {
+        OdsignTestUtils testUtils = new OdsignTestUtils(testInfo);
+        assumeTrue(testUtils.areAllApexesFactoryInstalled());
+        testUtils.maybeDisableVerity();
+        testUtils.removeCompilationLogToAvoidBackoff();
+        testUtils.reboot();
+        testUtils.assertCommandSucceeds("remount");
+    }
+
+    @AfterClassWithInfo
+    public static void afterClassWithDeviceBase(TestInformation testInfo) throws Exception {
+        OdsignTestUtils testUtils = new OdsignTestUtils(testInfo);
+        testUtils.maybeEnableVerity();
+        testUtils.removeCompilationLogToAvoidBackoff();
+        testUtils.reboot();
+    }
+
+    @Before
+    public void setUpBase() throws Exception {
+        mTestUtils = new OdsignTestUtils(getTestInformation());
+        mDeviceState = new DeviceState(getTestInformation());
+        mDeviceState.backupArtifacts();
+    }
+
+    @After
+    public void tearDownBase() throws Exception {
+        mDeviceState.restore();
+    }
+
+    @Test
+    public void verifyArtSamegradeUpdateTriggersCompilation() throws Exception {
+        mDeviceState.simulateArtApexUpgrade();
+        long timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // It should recompile everything.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+        // Generated artifacts should be loaded.
+        mTestUtils.restartZygote();
+        mTestUtils.verifyZygotesLoadedPrimaryBootImage();
+        mTestUtils.verifyZygotesLoadedBootImageMainlineExtension();
+        mTestUtils.verifySystemServerLoadedArtifacts();
+
+        mDeviceState.simulateArtApexUninstall();
+        mTestUtils.runOdrefresh();
+
+        // It should delete all compilation artifacts and update the cache info.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedPrimaryBootImage());
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedBootImageMainlineExtension());
+        mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+    }
+
+    @Test
+    public void verifyOtherApexSamegradeUpdateTriggersCompilation() throws Exception {
+        mDeviceState.simulateApexUpgrade();
+        long timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // It should only recompile boot image mainline extension and system server.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedPrimaryBootImage());
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+        // Generated artifacts should be loaded.
+        mTestUtils.restartZygote();
+        mTestUtils.verifyZygotesLoadedBootImageMainlineExtension();
+        mTestUtils.verifySystemServerLoadedArtifacts();
+
+        mDeviceState.simulateApexUninstall();
+        mTestUtils.runOdrefresh();
+
+        // It should delete all compilation artifacts and update the cache info.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedPrimaryBootImage());
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedBootImageMainlineExtension());
+        mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+    }
+
+    @Test
+    public void verifyMissingArtifactTriggersCompilation() throws Exception {
+        // Simulate that an artifact is missing from /system.
+        mDeviceState.backupAndDeleteFile(
+                "/system/framework/oat/" + mTestUtils.getSystemServerIsa() + "/services.odex");
+
+        mTestUtils.removeCompilationLogToAvoidBackoff();
+        long timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        Set<String> expectedArtifacts = OdsignTestUtils.getApexDataDalvikCacheFilenames(
+                "/system/framework/services.jar", mTestUtils.getSystemServerIsa());
+
+        Set<String> nonExpectedArtifacts = new HashSet<>();
+        nonExpectedArtifacts.addAll(mTestUtils.getExpectedPrimaryBootImage());
+        nonExpectedArtifacts.addAll(mTestUtils.getExpectedBootImageMainlineExtension());
+        nonExpectedArtifacts.addAll(mTestUtils.getSystemServerExpectedArtifacts());
+        nonExpectedArtifacts.removeAll(expectedArtifacts);
+
+        // It should only generate artifacts that are missing from /system.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertFilesNotExist(nonExpectedArtifacts);
+        mTestUtils.assertModifiedAfter(expectedArtifacts, timeMs);
+
+        // Generated artifacts should be loaded.
+        mTestUtils.restartZygote();
+        mTestUtils.verifySystemServerLoadedArtifacts(expectedArtifacts);
+
+        mDeviceState.simulateArtApexUpgrade();
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // It should recompile everything.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+        mDeviceState.simulateArtApexUninstall();
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // It should only re-generate artifacts that are missing from /system.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertFilesNotExist(nonExpectedArtifacts);
+        mTestUtils.assertModifiedAfter(expectedArtifacts, timeMs);
+    }
+
+    @Test
+    public void verifyEnableUffdGcChangeTriggersCompilation() throws Exception {
+        mDeviceState.setPhenotypeFlag("enable_uffd_gc", "true");
+
+        long timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // It should recompile everything.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+        // Run odrefresh again with the flag unchanged.
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // Nothing should change.
+        mTestUtils.assertNotModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertNotModifiedAfter(
+                mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+        mDeviceState.setPhenotypeFlag("enable_uffd_gc", null);
+
+        mTestUtils.runOdrefresh();
+
+        // It should delete all compilation artifacts and update the cache info.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedPrimaryBootImage());
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedBootImageMainlineExtension());
+        mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+    }
+}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithCacheInfoHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithCacheInfoHostTest.java
new file mode 100644
index 0000000..00eda90
--- /dev/null
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithCacheInfoHostTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package com.android.tests.odsign;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * This class tests odrefresh for the cases where all the APEXes are initially factory-installed
+ * and the cache info exists, which is the normal case.
+ *
+ * Both the tests in the base class and the tests in this class are run with the setup of this
+ * class.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OdrefreshFactoryWithCacheInfoHostTest extends OdrefreshFactoryHostTestBase {
+    @Test
+    public void verifyNoCompilationWhenSystemIsGood() throws Exception {
+        // Only the cache info should exist.
+        mTestUtils.assertFilesExist(Set.of(OdsignTestUtils.CACHE_INFO_FILE));
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedPrimaryBootImage());
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedBootImageMainlineExtension());
+        mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+
+        // Run again.
+        long timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // Nothing should change.
+        mTestUtils.assertNotModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedPrimaryBootImage());
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedBootImageMainlineExtension());
+        mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+    }
+}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithoutCacheInfoHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithoutCacheInfoHostTest.java
new file mode 100644
index 0000000..ccf3e9f
--- /dev/null
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithoutCacheInfoHostTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package com.android.tests.odsign;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * This class tests odrefresh for the cases where all the APEXes are initially factory-installed
+ * and the cache info does not exist.
+ *
+ * The cache info can be missing due to various reasons (corrupted files deleted by odsign, odsign
+ * failure, etc.), so this test makes sure that odrefresh doesn't rely on the cache info when
+ * checking artifacts on /system.
+ *
+ * Both the tests in the base class and the tests in this class are run with the setup of this
+ * class.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OdrefreshFactoryWithoutCacheInfoHostTest extends OdrefreshFactoryHostTestBase {
+    @Before
+    public void setUp() throws Exception {
+        getDevice().deleteFile(OdsignTestUtils.CACHE_INFO_FILE);
+    }
+
+    @Test
+    public void verifyNoCompilationWhenSystemIsGood() throws Exception {
+        long timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // It should only generate the missing cache info.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedPrimaryBootImage());
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedBootImageMainlineExtension());
+        mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+    }
+}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
index 731ea38..c5af9d8 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
@@ -17,9 +17,7 @@
 package com.android.tests.odsign;
 
 import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -27,52 +25,27 @@
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
- * Test to check end-to-end odrefresh invocations, but without odsign, fs-verity, and ART runtime
- * involved.
+ * Test to check end-to-end odrefresh invocations, but without odsign amd fs-verity involved.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class OdrefreshHostTest extends BaseHostJUnit4Test {
-    private static final String CACHE_INFO_FILE =
-            OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME + "/cache-info.xml";
-    private static final String ODREFRESH_BIN = "odrefresh";
-    private static final String ODREFRESH_COMMAND =
-            ODREFRESH_BIN + " --partial-compilation --no-refresh --compile";
-    private static final String ODREFRESH_MINIMAL_COMMAND =
-            ODREFRESH_BIN + " --partial-compilation --no-refresh --minimal --compile";
-
-    private static final String TAG = "OdrefreshHostTest";
-    private static final String ZYGOTE_ARTIFACTS_KEY = TAG + ":ZYGOTE_ARTIFACTS";
-    private static final String SYSTEM_SERVER_ARTIFACTS_KEY = TAG + ":SYSTEM_SERVER_ARTIFACTS";
-
     private OdsignTestUtils mTestUtils;
+    private DeviceState mDeviceState;
 
     @BeforeClassWithInfo
     public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
         OdsignTestUtils testUtils = new OdsignTestUtils(testInfo);
         testUtils.installTestApex();
         testUtils.reboot();
-
-        HashSet<String> zygoteArtifacts = new HashSet<>();
-        for (String zygoteName : testUtils.ZYGOTE_NAMES) {
-            zygoteArtifacts.addAll(
-                    testUtils.getZygoteLoadedArtifacts(zygoteName).orElse(new HashSet<>()));
-        }
-        Set<String> systemServerArtifacts = testUtils.getSystemServerLoadedArtifacts();
-
-        testInfo.properties().put(ZYGOTE_ARTIFACTS_KEY, String.join(":", zygoteArtifacts));
-        testInfo.properties()
-                .put(SYSTEM_SERVER_ARTIFACTS_KEY, String.join(":", systemServerArtifacts));
     }
 
     @AfterClassWithInfo
@@ -85,197 +58,252 @@
     @Before
     public void setUp() throws Exception {
         mTestUtils = new OdsignTestUtils(getTestInformation());
+        mDeviceState = new DeviceState(getTestInformation());
+        mDeviceState.backupArtifacts();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mDeviceState.restore();
     }
 
     @Test
     public void verifyArtSamegradeUpdateTriggersCompilation() throws Exception {
-        simulateArtApexUpgrade();
+        mDeviceState.simulateArtApexUpgrade();
         long timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
-        assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
     }
 
     @Test
     public void verifyOtherApexSamegradeUpdateTriggersCompilation() throws Exception {
-        simulateApexUpgrade();
+        mDeviceState.simulateApexUpgrade();
         long timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
-        assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
     }
 
     @Test
     public void verifyBootClasspathOtaTriggersCompilation() throws Exception {
-        simulateBootClasspathOta();
+        mDeviceState.simulateBootClasspathOta();
         long timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
-        assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
     }
 
     @Test
     public void verifySystemServerOtaTriggersCompilation() throws Exception {
-        simulateSystemServerOta();
+        mDeviceState.simulateSystemServerOta();
         long timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
-        assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertNotModifiedAfter(
+                mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
     }
 
     @Test
     public void verifyMissingArtifactTriggersCompilation() throws Exception {
         Set<String> missingArtifacts = simulateMissingArtifacts();
         Set<String> remainingArtifacts = new HashSet<>();
-        remainingArtifacts.addAll(getZygoteArtifacts());
-        remainingArtifacts.addAll(getSystemServerArtifacts());
+        remainingArtifacts.addAll(mTestUtils.getExpectedPrimaryBootImage());
+        remainingArtifacts.addAll(mTestUtils.getExpectedBootImageMainlineExtension());
+        remainingArtifacts.addAll(mTestUtils.getSystemServerExpectedArtifacts());
         remainingArtifacts.removeAll(missingArtifacts);
 
         mTestUtils.removeCompilationLogToAvoidBackoff();
         long timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
-        assertArtifactsNotModifiedAfter(remainingArtifacts, timeMs);
-        assertArtifactsModifiedAfter(missingArtifacts, timeMs);
+        mTestUtils.assertNotModifiedAfter(remainingArtifacts, timeMs);
+        mTestUtils.assertModifiedAfter(missingArtifacts, timeMs);
     }
 
     @Test
     public void verifyEnableUffdGcChangeTriggersCompilation() throws Exception {
-        try {
-            // Disable phenotype flag syncing. Potentially, we can set
-            // `set_sync_disabled_for_tests` to `until_reboot`, but setting it to
-            // `persistent` prevents unrelated system crashes/restarts from affecting the
-            // test. `set_sync_disabled_for_tests` is reset in the `finally` block anyway.
-            getDevice().executeShellV2Command(
-                    "device_config set_sync_disabled_for_tests persistent");
+        mDeviceState.setPhenotypeFlag("enable_uffd_gc", "false");
 
-            // Simulate that the phenotype flag is set to the default value.
-            getDevice().executeShellV2Command(
-                    "device_config put runtime_native_boot enable_uffd_gc false");
+        long timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
 
-            long timeMs = mTestUtils.getCurrentTimeMs();
-            getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        // Artifacts should be re-compiled.
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
 
-            // Artifacts should not be re-compiled.
-            assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
-            assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mDeviceState.setPhenotypeFlag("enable_uffd_gc", "true");
 
-            // Simulate that the phenotype flag is set to true.
-            getDevice().executeShellV2Command(
-                    "device_config put runtime_native_boot enable_uffd_gc true");
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
 
-            timeMs = mTestUtils.getCurrentTimeMs();
-            getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        // Artifacts should be re-compiled.
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
 
-            // Artifacts should be re-compiled.
-            assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
-            assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+        // Run odrefresh again with the flag unchanged.
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
 
-            // Run odrefresh again with the flag unchanged.
-            timeMs = mTestUtils.getCurrentTimeMs();
-            getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        // Artifacts should not be re-compiled.
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertNotModifiedAfter(
+                mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
 
-            // Artifacts should not be re-compiled.
-            assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
-            assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mDeviceState.setPhenotypeFlag("enable_uffd_gc", null);
 
-            // Simulate that the phenotype flag is set to false.
-            getDevice().executeShellV2Command(
-                    "device_config put runtime_native_boot enable_uffd_gc false");
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
 
-            timeMs = mTestUtils.getCurrentTimeMs();
-            getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        // Artifacts should be re-compiled.
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+    }
 
-            // Artifacts should be re-compiled.
-            assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
-            assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
-        } finally {
-            getDevice().executeShellV2Command("device_config set_sync_disabled_for_tests none");
-            getDevice().executeShellV2Command(
-                    "device_config delete runtime_native_boot enable_uffd_gc");
-        }
+    @Test
+    public void verifySystemServerCompilerFilterOverrideChangeTriggersCompilation()
+            throws Exception {
+        mDeviceState.setPhenotypeFlag("systemservercompilerfilter_override", null);
+
+        long timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // Artifacts should not be re-compiled.
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertNotModifiedAfter(
+                mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+        mDeviceState.setPhenotypeFlag("systemservercompilerfilter_override", "speed");
+
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // Artifacts should be re-compiled.
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+        // Run odrefresh again with the flag unchanged.
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // Artifacts should not be re-compiled.
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertNotModifiedAfter(
+                mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+        mDeviceState.setPhenotypeFlag("systemservercompilerfilter_override", "verify");
+
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // Artifacts should be re-compiled.
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
     }
 
     @Test
     public void verifySystemPropertyMismatchTriggersCompilation() throws Exception {
         // Change a system property from empty to a value.
-        getDevice().setProperty("dalvik.vm.foo", "1");
+        mDeviceState.setProperty("dalvik.vm.foo", "1");
         long timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
         // Artifacts should be re-compiled.
-        assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
 
         // Run again with the same value.
         timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
         // Artifacts should not be re-compiled.
-        assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertNotModifiedAfter(
+                mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
 
         // Change the system property to another value.
-        getDevice().setProperty("dalvik.vm.foo", "2");
+        mDeviceState.setProperty("dalvik.vm.foo", "2");
         timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
         // Artifacts should be re-compiled.
-        assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
 
         // Run again with the same value.
         timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
         // Artifacts should not be re-compiled.
-        assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertNotModifiedAfter(
+                mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
 
         // Change the system property to empty.
-        getDevice().setProperty("dalvik.vm.foo", "");
+        mDeviceState.setProperty("dalvik.vm.foo", "");
         timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
         // Artifacts should be re-compiled.
-        assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
 
         // Run again with the same value.
         timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
         // Artifacts should not be re-compiled.
-        assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertNotModifiedAfter(
+                mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
     }
 
     @Test
     public void verifyNoCompilationWhenCacheIsGood() throws Exception {
         mTestUtils.removeCompilationLogToAvoidBackoff();
         long timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
-        assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertNotModifiedAfter(
+                mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
     }
 
     @Test
     public void verifyUnexpectedFilesAreCleanedUp() throws Exception {
         String unexpected = OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME + "/unexpected";
-        getDevice().pushString(/*contents=*/"", unexpected);
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        getDevice().pushString("" /* contents */, unexpected);
+        mTestUtils.runOdrefresh();
 
-        assertFalse(getDevice().doesFileExist(unexpected));
+        assertThat(getDevice().doesFileExist(unexpected)).isFalse();
     }
 
     @Test
     public void verifyCacheInfoOmitsIrrelevantApexes() throws Exception {
-        String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+        String cacheInfo = getDevice().pullFileContents(OdsignTestUtils.CACHE_INFO_FILE);
 
         // cacheInfo should list all APEXes that have compilable JARs and
         // none that do not.
@@ -290,16 +318,15 @@
     @Test
     public void verifyCompilationOsMode() throws Exception {
         mTestUtils.removeCompilationLogToAvoidBackoff();
-        simulateApexUpgrade();
+        mDeviceState.simulateApexUpgrade();
         long timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(
-                ODREFRESH_BIN + " --no-refresh --partial-compilation"
-                        + " --compilation-os-mode --compile");
+        mTestUtils.runOdrefresh("--compilation-os-mode");
 
-        assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
 
-        String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+        String cacheInfo = getDevice().pullFileContents(OdsignTestUtils.CACHE_INFO_FILE);
         assertThat(cacheInfo).contains("compilationOsMode=\"true\"");
 
         // Compilation OS does not write the compilation log to the host.
@@ -307,185 +334,93 @@
 
         // Simulate the odrefresh invocation on the next boot.
         timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
         // odrefresh should not re-compile anything.
-        assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
-        assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertNotModifiedAfter(
+                mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
+        mTestUtils.assertNotModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
     }
 
     @Test
     public void verifyMinimalCompilation() throws Exception {
         mTestUtils.removeCompilationLogToAvoidBackoff();
         getDevice().executeShellV2Command(
-            "rm -rf " + OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME);
-        getDevice().executeShellV2Command(ODREFRESH_MINIMAL_COMMAND);
+                "rm -rf " + OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME);
+        mTestUtils.runOdrefresh("--minimal");
 
         mTestUtils.restartZygote();
 
         // The minimal boot image should be loaded.
-        Set<String> minimalZygoteArtifacts =
-                mTestUtils.verifyZygotesLoadedArtifacts("boot_minimal");
+        mTestUtils.verifyZygotesLoadedMinimalBootImage();
 
         // Running the command again should not overwrite the minimal boot image.
         mTestUtils.removeCompilationLogToAvoidBackoff();
         long timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_MINIMAL_COMMAND);
+        mTestUtils.runOdrefresh("--minimal");
 
-        assertArtifactsNotModifiedAfter(minimalZygoteArtifacts, timeMs);
-
-        // `odrefresh --check` should keep the minimal boot image.
-        mTestUtils.removeCompilationLogToAvoidBackoff();
-        timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_BIN + " --check");
-
-        assertArtifactsNotModifiedAfter(minimalZygoteArtifacts, timeMs);
+        Set<String> minimalZygoteArtifacts = mTestUtils.getExpectedMinimalBootImage();
+        mTestUtils.assertNotModifiedAfter(minimalZygoteArtifacts, timeMs);
 
         // A normal odrefresh invocation should replace the minimal boot image with a full one.
         mTestUtils.removeCompilationLogToAvoidBackoff();
         timeMs = mTestUtils.getCurrentTimeMs();
-        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+        mTestUtils.runOdrefresh();
 
         for (String artifact : minimalZygoteArtifacts) {
-            assertFalse(
+            assertWithMessage(
                     String.format(
-                            "Artifact %s should be cleaned up while it still exists", artifact),
-                    getDevice().doesFileExist(artifact));
+                            "Artifact %s should be cleaned up while it still exists", artifact))
+                    .that(getDevice().doesFileExist(artifact))
+                    .isFalse();
         }
 
-        assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedPrimaryBootImage(), timeMs);
+        mTestUtils.assertModifiedAfter(mTestUtils.getExpectedBootImageMainlineExtension(), timeMs);
     }
 
-    /**
-     * Checks the input line by line and replaces all lines that match the regex with the given
-     * replacement.
-     */
-    private String replaceLine(String input, String regex, String replacement) {
-        StringBuffer output = new StringBuffer();
-        Pattern p = Pattern.compile(regex);
-        for (String line : input.split("\n")) {
-            Matcher m = p.matcher(line);
-            if (m.matches()) {
-                m.appendReplacement(output, replacement);
-                output.append("\n");
-            } else {
-                output.append(line + "\n");
-            }
-        }
-        return output.toString();
-    }
+    @Test
+    public void verifyCompilationFailureBackoff() throws Exception {
+        mDeviceState.makeDex2oatFail();
+        mDeviceState.simulateArtApexUpgrade();
 
-    /**
-     * Simulates that there is an OTA that updates a boot classpath jar.
-     */
-    private void simulateBootClasspathOta() throws Exception {
-        String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
-        // Replace the cached checksum of /system/framework/framework.jar with "aaaaaaaa".
-        cacheInfo = replaceLine(
-                cacheInfo,
-                "(.*/system/framework/framework\\.jar.*checksums=\").*?(\".*)",
-                "$1aaaaaaaa$2");
-        getDevice().pushString(cacheInfo, CACHE_INFO_FILE);
-    }
+        // Run odrefresh. It should encounter dex2oat failures.
+        long timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
 
-    /**
-     * Simulates that there is an OTA that updates a system server jar.
-     */
-    private void simulateSystemServerOta() throws Exception {
-        String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
-        // Replace the cached checksum of /system/framework/services.jar with "aaaaaaaa".
-        cacheInfo = replaceLine(
-                cacheInfo,
-                "(.*/system/framework/services\\.jar.*checksums=\").*?(\".*)",
-                "$1aaaaaaaa$2");
-        getDevice().pushString(cacheInfo, CACHE_INFO_FILE);
-    }
+        // Artifacts don't exist because the compilation failed.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedPrimaryBootImage());
+        mTestUtils.assertFilesNotExist(mTestUtils.getExpectedBootImageMainlineExtension());
+        mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
 
-    /**
-     * Simulates that an ART APEX has been upgraded.
-     */
-    private void simulateArtApexUpgrade() throws Exception {
-        String apexInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
-        // Replace the lastUpdateMillis of com.android.art with "1".
-        apexInfo = replaceLine(
-                apexInfo,
-                "(.*com\\.android\\.art.*lastUpdateMillis=\").*?(\".*)",
-                "$11$2");
-        getDevice().pushString(apexInfo, CACHE_INFO_FILE);
-    }
+        // Run odrefresh again.
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
 
-    /**
-     * Simulates that an APEX has been upgraded. We could install a real APEX, but that would
-     * introduce an extra dependency to this test, which we want to avoid.
-     */
-    private void simulateApexUpgrade() throws Exception {
-        String apexInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
-        // Replace the lastUpdateMillis of com.android.wifi with "1".
-        apexInfo = replaceLine(
-                apexInfo,
-                "(.*com\\.android\\.wifi.*lastUpdateMillis=\").*?(\".*)",
-                "$11$2");
-        getDevice().pushString(apexInfo, CACHE_INFO_FILE);
+        // It should not retry.
+        mTestUtils.assertNotModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+
+        // Simulate that the backoff time has passed.
+        mTestUtils.removeCompilationLogToAvoidBackoff();
+
+        // Run odrefresh again.
+        timeMs = mTestUtils.getCurrentTimeMs();
+        mTestUtils.runOdrefresh();
+
+        // Now it should retry.
+        mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
     }
 
     private Set<String> simulateMissingArtifacts() throws Exception {
         Set<String> missingArtifacts = new HashSet<>();
-        String sample = getSystemServerArtifacts().iterator().next();
+        String sample = mTestUtils.getSystemServerExpectedArtifacts().iterator().next();
         for (String extension : OdsignTestUtils.APP_ARTIFACT_EXTENSIONS) {
-            String artifact = replaceExtension(sample, extension);
+            String artifact = OdsignTestUtils.replaceExtension(sample, extension);
             getDevice().deleteFile(artifact);
             missingArtifacts.add(artifact);
         }
         return missingArtifacts;
     }
-
-    private void assertArtifactsModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
-        for (String artifact : artifacts) {
-            long modifiedTime = mTestUtils.getModifiedTimeMs(artifact);
-            assertTrue(
-                    String.format(
-                            "Artifact %s is not re-compiled. Modified time: %d, Reference time: %d",
-                            artifact,
-                            modifiedTime,
-                            timeMs),
-                    modifiedTime > timeMs);
-        }
-    }
-
-    private void assertArtifactsNotModifiedAfter(Set<String> artifacts, long timeMs)
-            throws Exception {
-        for (String artifact : artifacts) {
-            long modifiedTime = mTestUtils.getModifiedTimeMs(artifact);
-            assertTrue(
-                    String.format(
-                            "Artifact %s is unexpectedly re-compiled. " +
-                                    "Modified time: %d, Reference time: %d",
-                            artifact,
-                            modifiedTime,
-                            timeMs),
-                    modifiedTime < timeMs);
-        }
-    }
-
-    private String replaceExtension(String filename, String extension) throws Exception {
-        int index = filename.lastIndexOf(".");
-        assertTrue("Extension not found in filename: " + filename, index != -1);
-        return filename.substring(0, index) + extension;
-    }
-
-    private Set<String> getColonSeparatedSet(String key) {
-        String value = getTestInformation().properties().get(key);
-        if (value == null || value.isEmpty()) {
-            return new HashSet<>();
-        }
-        return new HashSet<>(Arrays.asList(value.split(":")));
-    }
-
-    private Set<String> getZygoteArtifacts() {
-        return getColonSeparatedSet(ZYGOTE_ARTIFACTS_KEY);
-    }
-
-    private Set<String> getSystemServerArtifacts() {
-        return getColonSeparatedSet(SYSTEM_SERVER_ARTIFACTS_KEY);
-    }
 }
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
index caf94a7..5471c1f 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
@@ -16,45 +16,55 @@
 
 package com.android.tests.odsign;
 
-import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
-
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import android.cts.install.lib.host.InstallUtilsHost;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.ITestDevice.ApexInfo;
 import com.android.tradefed.device.TestDeviceOptions;
 import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.util.CommandResult;
 
+import com.google.common.io.ByteStreams;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
 import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.time.Duration;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Optional;
+import java.util.Map;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
 public class OdsignTestUtils {
     public static final String ART_APEX_DALVIK_CACHE_DIRNAME =
             "/data/misc/apexdata/com.android.art/dalvik-cache";
+    public static final String CACHE_INFO_FILE = ART_APEX_DALVIK_CACHE_DIRNAME + "/cache-info.xml";
+    public static final String APEX_INFO_FILE = "/apex/apex-info-list.xml";
 
-    public static final List<String> ZYGOTE_NAMES = List.of("zygote", "zygote64");
+    private static final String ODREFRESH_BIN = "odrefresh";
+
+    public static final String ZYGOTE_32_NAME = "zygote";
+    public static final String ZYGOTE_64_NAME = "zygote64";
 
     public static final List<String> APP_ARTIFACT_EXTENSIONS = List.of(".art", ".odex", ".vdex");
     public static final List<String> BCP_ARTIFACT_EXTENSIONS = List.of(".art", ".oat", ".vdex");
@@ -67,12 +77,19 @@
 
     private static final String TAG = "OdsignTestUtils";
     private static final String PACKAGE_NAME_KEY = TAG + ":PACKAGE_NAME";
+    private static final String VERITY_DISABLED_BY_TEST_KEY = TAG + ":VERITY_DISABLED_BY_TEST";
+
+    // Keep in sync with `ABI_TO_INSTRUCTION_SET_MAP` in
+    // libcore/libart/src/main/java/dalvik/system/VMRuntime.java.
+    private static final Map<String, String> ABI_TO_INSTRUCTION_SET_MAP =
+            Map.of("armeabi", "arm", "armeabi-v7a", "arm", "x86", "x86", "x86_64", "x86_64",
+                    "arm64-v8a", "arm64", "arm64-v8a-hwasan", "arm64", "riscv64", "riscv64");
 
     private final InstallUtilsHost mInstallUtils;
     private final TestInformation mTestInfo;
 
     public OdsignTestUtils(TestInformation testInfo) throws Exception {
-        assertNotNull(testInfo.getDevice());
+        assertThat(testInfo.getDevice()).isNotNull();
         mInstallUtils = new InstallUtilsHost(testInfo);
         mTestInfo = testInfo;
     }
@@ -86,17 +103,13 @@
         String packagesOutput =
                 mTestInfo.getDevice().executeShellCommand("pm list packages -f --apex-only");
         Pattern p = Pattern.compile(
-                "^package:(.*)=(com(?:\\.google)?\\.android(?:\\.go)?\\.art)$",
-                Pattern.MULTILINE);
+                "^package:(.*)=(com(?:\\.google)?\\.android(?:\\.go)?\\.art)$", Pattern.MULTILINE);
         Matcher m = p.matcher(packagesOutput);
         assertTrue("ART module not found. Packages are:\n" + packagesOutput, m.find());
         String artApexPath = m.group(1);
         String artApexName = m.group(2);
 
-        CommandResult result = mTestInfo.getDevice().executeShellV2Command(
-                "pm install --apex " + artApexPath);
-        assertWithMessage("Failed to install APEX. Reason: " + result.toString())
-            .that(result.getExitCode()).isEqualTo(0);
+        assertCommandSucceeds("pm install --apex " + artApexPath);
 
         mTestInfo.properties().put(PACKAGE_NAME_KEY, artApexName);
 
@@ -112,14 +125,14 @@
     }
 
     public Set<String> getMappedArtifacts(String pid, String grepPattern) throws Exception {
-        final String grepCommand = String.format("grep \"%s\" /proc/%s/maps", grepPattern, pid);
-        CommandResult result = mTestInfo.getDevice().executeShellV2Command(grepCommand);
-        assertTrue(result.toString(), result.getExitCode() == 0);
+        String grepCommand = String.format("grep \"%s\" /proc/%s/maps", grepPattern, pid);
         Set<String> mappedFiles = new HashSet<>();
-        for (String line : result.getStdout().split("\\R")) {
+        for (String line : assertCommandSucceeds(grepCommand).split("\\R")) {
             int start = line.indexOf(ART_APEX_DALVIK_CACHE_DIRNAME);
-            if (line.contains("[")) {
-                continue; // ignore anonymously mapped sections which are quoted in square braces.
+            if (line.contains("[") || line.contains("(deleted)")) {
+                // Ignore anonymously mapped sections, which are quoted in square braces, and
+                // deleted mapped files.
+                continue;
             }
             mappedFiles.add(line.substring(start));
         }
@@ -127,110 +140,111 @@
     }
 
     /**
-     * Returns the mapped artifacts of the Zygote process, or {@code Optional.empty()} if the
-     * process does not exist.
+     * Returns the mapped artifacts of the Zygote process.
      */
-    public Optional<Set<String>> getZygoteLoadedArtifacts(String zygoteName) throws Exception {
-        final CommandResult result =
-                mTestInfo.getDevice().executeShellV2Command("pidof " + zygoteName);
-        if (result.getExitCode() != 0) {
-            return Optional.empty();
-        }
+    public Set<String> getZygoteLoadedArtifacts(String zygoteName) throws Exception {
         // There may be multiple Zygote processes when Zygote just forks and has not executed any
         // app binary. We can take any of the pids.
         // We can't use the "-s" flag when calling `pidof` because the Toybox's `pidof`
         // implementation is wrong and it outputs multiple pids regardless of the "-s" flag, so we
         // split the output and take the first pid ourselves.
-        final String zygotePid = result.getStdout().trim().split("\\s+")[0];
+        String zygotePid = assertCommandSucceeds("pidof " + zygoteName).split("\\s+")[0];
         assertTrue(!zygotePid.isEmpty());
 
-        final String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + ".*boot";
-        return Optional.of(getMappedArtifacts(zygotePid, grepPattern));
+        String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + "/.*/boot";
+        return getMappedArtifacts(zygotePid, grepPattern);
     }
 
     public Set<String> getSystemServerLoadedArtifacts() throws Exception {
-        final CommandResult result =
-                mTestInfo.getDevice().executeShellV2Command("pidof system_server");
-        assertTrue(result.toString(), result.getExitCode() == 0);
-        final String systemServerPid = result.getStdout().trim();
+        String systemServerPid = assertCommandSucceeds("pidof system_server");
         assertTrue(!systemServerPid.isEmpty());
-        assertTrue(
-                "There should be exactly one `system_server` process",
+        assertTrue("There should be exactly one `system_server` process",
                 systemServerPid.matches("\\d+"));
 
         // system_server artifacts are in the APEX data dalvik cache and names all contain
         // the word "@classes". Look for mapped files that match this pattern in the proc map for
         // system_server.
-        final String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + ".*@classes";
+        String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + "/.*@classes";
         return getMappedArtifacts(systemServerPid, grepPattern);
     }
 
-    public void verifyZygoteLoadedArtifacts(String zygoteName, Set<String> mappedArtifacts,
-            String bootImageStem) throws Exception {
-        assertTrue("Expect 3 bootclasspath artifacts", mappedArtifacts.size() == 3);
-
-        String allArtifacts = mappedArtifacts.stream().collect(Collectors.joining(","));
+    private Set<String> getExpectedBootImage(String bootImageStem, String isa) throws Exception {
+        Set<String> artifacts = new HashSet<>();
         for (String extension : BCP_ARTIFACT_EXTENSIONS) {
-            final String artifact = bootImageStem + extension;
-            final boolean found = mappedArtifacts.stream().anyMatch(a -> a.endsWith(artifact));
-            assertTrue(zygoteName + " " + artifact + " not found: '" + allArtifacts + "'", found);
+            artifacts.add(String.format(
+                    "%s/%s/%s%s", ART_APEX_DALVIK_CACHE_DIRNAME, isa, bootImageStem, extension));
         }
+        return artifacts;
     }
 
-    // Verifies that boot image files with the given stem are loaded by Zygote for each instruction
-    // set. Returns the verified files.
-    public HashSet<String> verifyZygotesLoadedArtifacts(String bootImageStem) throws Exception {
-        // There are potentially two zygote processes "zygote" and "zygote64". These are
-        // instances 32-bit and 64-bit unspecialized app_process processes.
-        // (frameworks/base/cmds/app_process).
-        int zygoteCount = 0;
-        HashSet<String> verifiedArtifacts = new HashSet<>();
-        for (String zygoteName : ZYGOTE_NAMES) {
-            final Optional<Set<String>> mappedArtifacts = getZygoteLoadedArtifacts(zygoteName);
-            if (!mappedArtifacts.isPresent()) {
-                continue;
-            }
-            verifyZygoteLoadedArtifacts(zygoteName, mappedArtifacts.get(), bootImageStem);
-            zygoteCount += 1;
-            verifiedArtifacts.addAll(mappedArtifacts.get());
+    private Set<String> getExpectedBootImage(String bootImageStem) throws Exception {
+        Set<String> artifacts = new HashSet<>();
+        for (String isa : getZygoteNamesAndIsas().values()) {
+            artifacts.addAll(getExpectedBootImage(bootImageStem, isa));
         }
-        assertTrue("No zygote processes found", zygoteCount > 0);
-        return verifiedArtifacts;
+        return artifacts;
     }
 
-    public void verifySystemServerLoadedArtifacts() throws Exception {
+    public Set<String> getExpectedPrimaryBootImage() throws Exception {
+        return getExpectedBootImage("boot");
+    }
+
+    public Set<String> getExpectedMinimalBootImage() throws Exception {
+        return getExpectedBootImage("boot_minimal");
+    }
+
+    public Set<String> getExpectedBootImageMainlineExtension() throws Exception {
+        return getExpectedBootImage("boot-" + getFirstMainlineFrameworkLibraryName());
+    }
+
+    public Set<String> getSystemServerExpectedArtifacts() throws Exception {
         String[] classpathElements = getListFromEnvironmentVariable("SYSTEMSERVERCLASSPATH");
         assertTrue("SYSTEMSERVERCLASSPATH is empty", classpathElements.length > 0);
         String[] standaloneJars = getListFromEnvironmentVariable("STANDALONE_SYSTEMSERVER_JARS");
-        String[] allSystemServerJars = Stream
-                .concat(Arrays.stream(classpathElements), Arrays.stream(standaloneJars))
-                .toArray(String[]::new);
+        String[] allSystemServerJars =
+                Stream.concat(Arrays.stream(classpathElements), Arrays.stream(standaloneJars))
+                        .toArray(String[] ::new);
+        String isa = getSystemServerIsa();
 
-        final Set<String> mappedArtifacts = getSystemServerLoadedArtifacts();
-        assertTrue(
-                "No mapped artifacts under " + ART_APEX_DALVIK_CACHE_DIRNAME,
-                mappedArtifacts.size() > 0);
-        final String isa = getSystemServerIsa(mappedArtifacts.iterator().next());
-        final String isaCacheDirectory = String.format("%s/%s", ART_APEX_DALVIK_CACHE_DIRNAME, isa);
-
-        // Check components in the system_server classpath have mapped artifacts.
-        for (String element : allSystemServerJars) {
-          String escapedPath = element.substring(1).replace('/', '@');
-          for (String extension : APP_ARTIFACT_EXTENSIONS) {
-            final String fullArtifactPath =
-                    String.format("%s/%s@classes%s", isaCacheDirectory, escapedPath, extension);
-            assertTrue("Missing " + fullArtifactPath, mappedArtifacts.contains(fullArtifactPath));
-          }
+        Set<String> artifacts = new HashSet<>();
+        for (String jar : allSystemServerJars) {
+            artifacts.addAll(getApexDataDalvikCacheFilenames(jar, isa));
         }
 
-        for (String mappedArtifact : mappedArtifacts) {
-          // Check the mapped artifact has a .art, .odex or .vdex extension.
-          final boolean knownArtifactKind =
-                    APP_ARTIFACT_EXTENSIONS.stream().anyMatch(e -> mappedArtifact.endsWith(e));
-          assertTrue("Unknown artifact kind: " + mappedArtifact, knownArtifactKind);
+        return artifacts;
+    }
+
+    // Verifies that boot image files with the given stem are loaded by Zygote for each instruction
+    // set.
+    private void verifyZygotesLoadedBootImage(String bootImageStem) throws Exception {
+        for (var entry : getZygoteNamesAndIsas().entrySet()) {
+            assertThat(getZygoteLoadedArtifacts(entry.getKey()))
+                    .containsAtLeastElementsIn(
+                            getExpectedBootImage(bootImageStem, entry.getValue()));
         }
     }
 
+    public void verifyZygotesLoadedPrimaryBootImage() throws Exception {
+        verifyZygotesLoadedBootImage("boot");
+    }
+
+    public void verifyZygotesLoadedMinimalBootImage() throws Exception {
+        verifyZygotesLoadedBootImage("boot_minimal");
+    }
+
+    public void verifyZygotesLoadedBootImageMainlineExtension() throws Exception {
+        verifyZygotesLoadedBootImage("boot-" + getFirstMainlineFrameworkLibraryName());
+    }
+
+    public void verifySystemServerLoadedArtifacts(Set<String> expectedArtifacts) throws Exception {
+        assertThat(getSystemServerLoadedArtifacts())
+                .containsAtLeastElementsIn(expectedArtifacts);
+    }
+
+    public void verifySystemServerLoadedArtifacts() throws Exception {
+        verifySystemServerLoadedArtifacts(getSystemServerExpectedArtifacts());
+    }
+
     public boolean haveCompilationLog() throws Exception {
         CommandResult result =
                 mTestInfo.getDevice().executeShellV2Command("stat " + ODREFRESH_COMPILATION_LOG);
@@ -246,7 +260,7 @@
         // store default value and increase time-out for reboot
         int rebootTimeout = options.getRebootTimeout();
         long onlineTimeout = options.getOnlineTimeout();
-        options.setRebootTimeout((int)BOOT_COMPLETE_TIMEOUT.toMillis());
+        options.setRebootTimeout((int) BOOT_COMPLETE_TIMEOUT.toMillis());
         options.setOnlineTimeout(BOOT_COMPLETE_TIMEOUT.toMillis());
         mTestInfo.getDevice().setOptions(options);
 
@@ -266,9 +280,10 @@
         // `waitForBootComplete` relies on `dev.bootcomplete`.
         mTestInfo.getDevice().executeShellCommand("setprop dev.bootcomplete 0");
         mTestInfo.getDevice().executeShellCommand("setprop ctl.restart zygote");
-        boolean success = mTestInfo.getDevice()
-                .waitForBootComplete(RESTART_ZYGOTE_COMPLETE_TIMEOUT.toMillis());
-        assertWithMessage("Zygote didn't start in %s", BOOT_COMPLETE_TIMEOUT).that(success)
+        boolean success = mTestInfo.getDevice().waitForBootComplete(
+                RESTART_ZYGOTE_COMPLETE_TIMEOUT.toMillis());
+        assertWithMessage("Zygote didn't start in %s", BOOT_COMPLETE_TIMEOUT)
+                .that(success)
                 .isTrue();
     }
 
@@ -296,16 +311,63 @@
         return new String[0];
     }
 
-    private String getSystemServerIsa(String mappedArtifact) {
-        // Artifact path for system server artifacts has the form:
-        //    ART_APEX_DALVIK_CACHE_DIRNAME + "/<arch>/system@[email protected]@classes.odex"
-        String[] pathComponents = mappedArtifact.split("/");
-        return pathComponents[pathComponents.length - 2];
+    private static String getInstructionSet(String abi) {
+        String instructionSet = ABI_TO_INSTRUCTION_SET_MAP.get(abi);
+        assertThat(instructionSet).isNotNull();
+        return instructionSet;
+    }
+
+    public Map<String, String> getZygoteNamesAndIsas() throws Exception {
+        Map<String, String> namesAndIsas = new HashMap<>();
+        String abiList64 = mTestInfo.getDevice().getProperty("ro.product.cpu.abilist64");
+        if (abiList64 != null && !abiList64.isEmpty()) {
+            namesAndIsas.put(ZYGOTE_64_NAME, getInstructionSet(abiList64.split(",")[0]));
+        }
+        String abiList32 = mTestInfo.getDevice().getProperty("ro.product.cpu.abilist32");
+        if (abiList32 != null && !abiList32.isEmpty()) {
+            namesAndIsas.put(ZYGOTE_32_NAME, getInstructionSet(abiList32.split(",")[0]));
+        }
+        return namesAndIsas;
+    }
+
+    public String getSystemServerIsa() throws Exception {
+        return getInstructionSet(
+                mTestInfo.getDevice().getProperty("ro.product.cpu.abilist").split(",")[0]);
+    }
+
+    // Keep in sync with `GetApexDataDalvikCacheFilename` in art/libartbase/base/file_utils.cc.
+    public static Set<String> getApexDataDalvikCacheFilenames(String dexLocation, String isa)
+            throws Exception {
+        Set<String> filenames = new HashSet<>();
+        String escapedPath = dexLocation.substring(1).replace('/', '@');
+        for (String extension : APP_ARTIFACT_EXTENSIONS) {
+            filenames.add(String.format("%s/%s/%s@classes%s", ART_APEX_DALVIK_CACHE_DIRNAME, isa,
+                    escapedPath, extension));
+        }
+        return filenames;
+    }
+
+    // Keep in sync with `GetFirstMainlineFrameworkLibraryName` in
+    // art/libartbase/base/file_utils.cc.
+    private String getFirstMainlineFrameworkLibraryName() throws Exception {
+        String[] bcpElements = getListFromEnvironmentVariable("BOOTCLASSPATH");
+        assertTrue("BOOTCLASSPATH is empty", bcpElements.length > 0);
+        String[] dex2oatBcpElements = getListFromEnvironmentVariable("DEX2OATBOOTCLASSPATH");
+        assertTrue("DEX2OATBOOTCLASSPATH is empty", dex2oatBcpElements.length > 0);
+        assertTrue("DEX2OATBOOTCLASSPATH must be a prefix of BOOTCLASSPATH",
+                bcpElements.length > dex2oatBcpElements.length
+                        && Arrays.equals(
+                                Arrays.copyOfRange(bcpElements, 0, dex2oatBcpElements.length),
+                                dex2oatBcpElements));
+
+        String filename = bcpElements[dex2oatBcpElements.length];
+        String basename = basename(filename);
+        return replaceExtension(basename, "");
     }
 
     private long parseFormattedDateTime(String dateTimeStr) throws Exception {
-        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
-                "yyyy-MM-dd HH:mm:ss.nnnnnnnnn Z");
+        DateTimeFormatter formatter =
+                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnnnn Z");
         ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeStr, formatter);
         return zonedDateTime.toInstant().toEpochMilli();
     }
@@ -314,18 +376,14 @@
         // We can't use the "-c '%.3Y'" flag when to get the timestamp because the Toybox's `stat`
         // implementation truncates the timestamp to seconds, which is not accurate enough, so we
         // use "-c '%%y'" and parse the time ourselves.
-        String dateTimeStr = mTestInfo.getDevice()
-                .executeShellCommand(String.format("stat -c '%%y' '%s'", filename))
-                .trim();
+        String dateTimeStr = assertCommandSucceeds(String.format("stat -c '%%y' '%s'", filename));
         return parseFormattedDateTime(dateTimeStr);
     }
 
     public long getCurrentTimeMs() throws Exception {
         // We can't use getDevice().getDeviceDate() because it truncates the timestamp to seconds,
         // which is not accurate enough.
-        String dateTimeStr = mTestInfo.getDevice()
-                .executeShellCommand("date +'%Y-%m-%d %H:%M:%S.%N %z'")
-                .trim();
+        String dateTimeStr = assertCommandSucceeds("date +'%Y-%m-%d %H:%M:%S.%N %z'");
         return parseFormattedDateTime(dateTimeStr);
     }
 
@@ -357,15 +415,112 @@
         return result.getStdout().trim();
     }
 
-    public void archiveLogThenDelete(TestLogData logs, String remotePath, String localName)
-            throws DeviceNotAvailableException {
-        ITestDevice device = mTestInfo.getDevice();
-        File logFile = device.pullFile(remotePath);
-        if (logFile != null) {
-            logs.addTestLog(localName, LogDataType.TEXT, new FileInputStreamSource(logFile));
-            // Delete to avoid confusing logs from a previous run, just in case.
-            device.deleteFile(remotePath);
+    public File copyResourceToFile(String resourceName) throws Exception {
+        File file = File.createTempFile("odsign_e2e_tests", ".tmp");
+        file.deleteOnExit();
+        try (OutputStream outputStream = new FileOutputStream(file);
+                InputStream inputStream = getClass().getResourceAsStream(resourceName)) {
+            assertThat(ByteStreams.copy(inputStream, outputStream)).isGreaterThan(0);
+        }
+        return file;
+    }
+
+    public void assertModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
+        for (String artifact : artifacts) {
+            long modifiedTime = getModifiedTimeMs(artifact);
+            assertTrue(
+                    String.format(
+                            "Artifact %s is not re-compiled. Modified time: %d, Reference time: %d",
+                            artifact, modifiedTime, timeMs),
+                    modifiedTime > timeMs);
         }
     }
 
+    public void assertNotModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
+        for (String artifact : artifacts) {
+            long modifiedTime = getModifiedTimeMs(artifact);
+            assertTrue(String.format("Artifact %s is unexpectedly re-compiled. "
+                                       + "Modified time: %d, Reference time: %d",
+                               artifact, modifiedTime, timeMs),
+                    modifiedTime < timeMs);
+        }
+    }
+
+    public void assertFilesExist(Set<String> files) throws Exception {
+        assertThat(getExistingFiles(files)).containsExactlyElementsIn(files);
+    }
+
+    public void assertFilesNotExist(Set<String> files) throws Exception {
+        assertThat(getExistingFiles(files)).isEmpty();
+    }
+
+    private Set<String> getExistingFiles(Set<String> files) throws Exception {
+        Set<String> existingFiles = new HashSet<>();
+        for (String file : files) {
+            if (mTestInfo.getDevice().doesFileExist(file)) {
+                existingFiles.add(file);
+            }
+        }
+        return existingFiles;
+    }
+
+    public static String replaceExtension(String filename, String extension) throws Exception {
+        int index = filename.lastIndexOf(".");
+        assertTrue("Extension not found in filename: " + filename, index != -1);
+        return filename.substring(0, index) + extension;
+    }
+
+    public static String basename(String filename) throws Exception {
+        int index = filename.lastIndexOf("/");
+        assertTrue("Slash not found in filename: " + filename, index != -1);
+        return filename.substring(index + 1);
+    }
+
+    public void runOdrefresh() throws Exception {
+        runOdrefresh("" /* extraArgs */);
+    }
+
+    public void runOdrefresh(String extraArgs) throws Exception {
+        mTestInfo.getDevice().executeShellV2Command(ODREFRESH_BIN + " --check");
+        mTestInfo.getDevice().executeShellV2Command(
+                ODREFRESH_BIN + " --partial-compilation --no-refresh " + extraArgs + " --compile");
+    }
+
+    public boolean areAllApexesFactoryInstalled() throws Exception {
+        Document doc = loadXml(APEX_INFO_FILE);
+        NodeList list = doc.getElementsByTagName("apex-info");
+        for (int i = 0; i < list.getLength(); i++) {
+            Element node = (Element) list.item(i);
+            if (node.getAttribute("isActive").equals("true")
+                    && node.getAttribute("isFactory").equals("false")) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private Document loadXml(String remoteXmlFile) throws Exception {
+        File localFile = mTestInfo.getDevice().pullFile(remoteXmlFile);
+        assertThat(localFile).isNotNull();
+        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        return builder.parse(localFile);
+    }
+
+    /** Disables dm-verity if it's enabled. */
+    public void maybeDisableVerity() throws Exception {
+        boolean disabled =
+                mTestInfo.getDevice().getProperty("ro.boot.veritymode").equals("disabled");
+        if (!disabled) {
+            assertCommandSucceeds("disable-verity");
+            setBoolean(VERITY_DISABLED_BY_TEST_KEY, true);
+        }
+    }
+
+    /** Enables dm-verity if it's disabled by {@link #maybeDisableVerity}. */
+    public void maybeEnableVerity() throws Exception {
+        boolean disabledByTest = getBooleanOrDefault(VERITY_DISABLED_BY_TEST_KEY);
+        if (disabledByTest) {
+            assertCommandSucceeds("enable-verity");
+        }
+    }
 }
diff --git a/test/run-test b/test/run-test
index dccc9f6..f2be1d8 100755
--- a/test/run-test
+++ b/test/run-test
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env python3
 #
 # Copyright (C) 2007 The Android Open Source Project
 #
@@ -14,1059 +14,1085 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Set up prog to be the path of this script, including following symlinks,
-# and set up progdir to be the fully-qualified pathname of its directory.
-prog="$0"
-args="$@"
-while [ -h "${prog}" ]; do
-    newProg=`/bin/ls -ld "${prog}"`
-    newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
-    if expr "x${newProg}" : 'x/' >/dev/null; then
-        prog="${newProg}"
-    else
-        progdir=`dirname "${prog}"`
-        prog="${progdir}/${newProg}"
-    fi
-done
-oldwd=`pwd`
-progdir=`dirname "${prog}"`
-cd "${progdir}"
-progdir=`pwd`
-prog="${progdir}"/`basename "${prog}"`
-test_dir="test-$$"
-if [ -z "$TMPDIR" ]; then
-  tmp_dir="/tmp/$USER/${test_dir}"
-else
-  tmp_dir="${TMPDIR}/${test_dir}"
-fi
-checker="${progdir}/../tools/checker/checker.py"
-export JAVA="java"
-export JAVAC="javac -g -Xlint:-options -source 1.8 -target 1.8"
-export RUN="${progdir}/etc/run-test-jar"
-export DEX_LOCATION=/data/run-test/${test_dir}
+import os, sys, glob, re, shutil, subprocess, shlex, resource, atexit
 
-# ANDROID_BUILD_TOP is not set in a build environment.
-if [ -z "$ANDROID_BUILD_TOP" ]; then
-    export ANDROID_BUILD_TOP=$oldwd
-fi
+import default_run as default_run_module
 
-# OUT_DIR defaults to out, and may be relative to $ANDROID_BUILD_TOP.
-# Convert it to an absolute path, since we cd into the tmp_dir to run the tests.
-export OUT_DIR=${OUT_DIR:-out}
-if [[ "$OUT_DIR" != /* ]]; then
-    export OUT_DIR=$ANDROID_BUILD_TOP/$OUT_DIR
-fi
+from default_run import get_target_arch
+from importlib.machinery import SourceFileLoader
+from inspect import currentframe, getframeinfo, FrameInfo
+from pathlib import Path
+from shutil import copyfile
+from testrunner import env
+from typing import Optional, Dict, List
+from zipfile import ZipFile
+
+COLOR = (os.environ.get("LUCI_CONTEXT") == None)  # Disable colors on LUCI.
+COLOR_BLUE = '\033[94m' if COLOR else ''
+COLOR_GREEN = '\033[92m' if COLOR else ''
+COLOR_NORMAL = '\033[0m' if COLOR else ''
+COLOR_RED = '\033[91m' if COLOR else ''
+
+# Helper class which allows us to access the environment using syntax sugar.
+# E.g. `env.ANDROID_BUILD_TOP` instead of `os.environ["ANDROID_BUILD_TOP"]`.
+class Environment:
+
+  def __getattr__(self, name):
+    return os.environ.get(name)
+
+  def __setattr__(self, name, value):
+    os.environ[name] = str(value)
+
+
+# Context passed to individual tests to let them customize the behaviour.
+class RunTestContext:
+
+  def __init__(self, tmp_dir: Path, target: bool, chroot, dex_location, test_name) -> None:
+    self.env = Environment()
+    self.target = target
+    self.chroot = chroot
+    self.dex_location = dex_location
+    self.test_name = test_name
+
+    # Note: The expected path can be modified by the tests.
+    self.expected_stdout = tmp_dir / "expected-stdout.txt"
+    self.expected_stderr = tmp_dir / "expected-stderr.txt"
+
+    self.runner: List[str] = ["#!/bin/bash"]
+
+  def echo(self, text):
+    self.run(f"echo {text} > {test_stdout}")
+
+  def export(self, **env: str) -> None:
+    self.runner.append("")
+    for name, value in env.items():
+      self.runner.append(f"export {name}={value}")
+
+  # Add "runner" script command. It is not executed now.
+  # All "runner" commands are executed later via single bash call.
+  def run(self, cmd: str, check: bool=True, expected_exit_code: int=0, desc:str = None) -> None:
+    if cmd == "true":
+      return
+    cmd_esc = cmd.replace("'", r"'\''")
+    self.runner.append("")
+    self.runner.append(f"echo '{COLOR_BLUE}$$ {cmd_esc}{COLOR_NORMAL}'")
+    self.runner.append(cmd)
+
+    # Check the exit code.
+    if check:
+      caller = getframeinfo(currentframe().f_back)  # type: ignore
+      source = "{}:{}".format(Path(caller.filename).name, caller.lineno)
+      msg = f"{self.test_name} FAILED: [{source}] "
+      msg += "{} returned exit code ${{exit_code}}.".format(desc or "Command")
+      if expected_exit_code:
+        msg += f" Expected {expected_exit_code}."
+      self.runner.append(
+        f"exit_code=$?; if [ $exit_code -ne {expected_exit_code} ]; then "
+        f"echo {COLOR_RED}{msg}{COLOR_NORMAL}; exit 100; "
+        f"fi; ")
+    else:
+      self.runner.append("true; # Ignore previous exit code")
+
+  # Execute the default runner (possibly with modified arguments).
+  def default_run(self, args, **kwargs):
+    default_run_module.default_run(self, args, **kwargs)
+
+
+# TODO: Replace with 'def main():' (which might change variables from globals to locals)
+if True:
+  progdir = os.path.dirname(__file__)
+  oldwd = os.getcwd()
+  os.chdir(progdir)
+  test_dir = "test-{}".format(os.getpid())
+  TMPDIR = os.environ.get("TMPDIR")
+  USER = os.environ.get("USER")
+  PYTHON3 = os.environ.get("PYTHON3")
+  if not TMPDIR:
+    tmp_dir = f"/tmp/{USER}/{test_dir}"
+  else:
+    tmp_dir = f"{TMPDIR}/{test_dir}"
+  checker = f"{progdir}/../tools/checker/checker.py"
+
+  ON_VM = os.environ.get("ART_TEST_ON_VM")
+  SSH_USER = os.environ.get("ART_TEST_SSH_USER")
+  SSH_HOST = os.environ.get("ART_TEST_SSH_HOST")
+  SSH_PORT = os.environ.get("ART_TEST_SSH_PORT")
+  SSH_CMD = os.environ.get("ART_SSH_CMD")
+  SCP_CMD = os.environ.get("ART_SCP_CMD")
+  CHROOT = os.environ.get("ART_TEST_CHROOT")
+  CHROOT_CMD = os.environ.get("ART_CHROOT_CMD")
+
+  def fail(message: str, caller:Optional[FrameInfo]=None):
+    caller = caller or getframeinfo(currentframe().f_back)  # type: ignore
+    assert caller
+    source = "{}:{}".format(Path(caller.filename).name, caller.lineno)
+    print(f"{COLOR_RED}{TEST_NAME} FAILED: [{source}] {message}{COLOR_NORMAL}",
+          file=sys.stderr)
+    sys.exit(1)
+
+  def run(cmdline: str, check=True, fail_message=None) -> subprocess.CompletedProcess:
+    print(f"{COLOR_BLUE}$ {cmdline}{COLOR_NORMAL}", flush=True)
+    proc = subprocess.run([cmdline],
+                          shell=True,
+                          executable="/bin/bash",
+                          stderr=subprocess.STDOUT)
+    if (check and proc.returncode != 0):
+      if fail_message:
+        # If we have custom fail message, exit without printing the full backtrace.
+        fail(fail_message, getframeinfo(currentframe().f_back))  # type: ignore
+      raise Exception(f"Command failed (exit code {proc.returncode})")
+    return proc
+
+  def export(env: str, value: str) -> None:
+    os.environ[env] = value
+    globals()[env] = value
+
+  def error(msg) -> None:
+    print(msg, file=sys.stderr, flush=True)
+
+  # ANDROID_BUILD_TOP is not set in a build environment.
+  ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP")
+  if not ANDROID_BUILD_TOP:
+    export("ANDROID_BUILD_TOP", oldwd)
+
+  export("JAVA", "java")
+  export("JAVAC", "javac -g -Xlint:-options -source 1.8 -target 1.8")
+  export("PYTHON3",
+         f"{ANDROID_BUILD_TOP}/prebuilts/build-tools/path/linux-x86/python3")
+  export("RUN", f"{PYTHON3} {progdir}/etc/run-test-jar")
+  export("DEX_LOCATION", f"/data/run-test/{test_dir}")
+
+  # OUT_DIR defaults to out, and may be relative to ANDROID_BUILD_TOP.
+  # Convert it to an absolute path, since we cd into the tmp_dir to run the tests.
+  OUT_DIR = os.environ.get("OUT_DIR", "")
+  export("OUT_DIR", OUT_DIR or "out")
+  if not OUT_DIR.startswith("/"):
+    export("OUT_DIR", f"{ANDROID_BUILD_TOP}/{OUT_DIR}")
 
 # ANDROID_HOST_OUT is not set in a build environment.
-if [ -z "$ANDROID_HOST_OUT" ]; then
-    export ANDROID_HOST_OUT=${OUT_DIR}/host/linux-x86
-fi
+  ANDROID_HOST_OUT = os.environ.get("ANDROID_HOST_OUT")
+  if not ANDROID_HOST_OUT:
+    export("ANDROID_HOST_OUT", f"{OUT_DIR}/host/linux-x86")
 
-host_lib_root=${ANDROID_HOST_OUT}
-chroot=
-info="info.txt"
-run="run"
-expected_stdout="expected-stdout.txt"
-expected_stderr="expected-stderr.txt"
-check_cmd="check"
-test_stdout="test-stdout.txt"
-test_stderr="test-stderr.txt"
-cfg_output="graph.cfg"
-strace_output="strace-output.txt"
-lib="libartd.so"
-testlib="arttestd"
-run_args=(--quiet)
+  host_lib_root = ANDROID_HOST_OUT
+  chroot = ""
+  info = "info.txt"
+  run_cmd = "run"
+  test_stdout = "test-stdout.txt"
+  test_stderr = "test-stderr.txt"
+  cfg_output = "graph.cfg"
+  strace_output = "strace-output.txt"
+  lib = "libartd.so"
+  testlib = "arttestd"
+  run_args = []
+  run_checker = "no"
 
-quiet="no"
-debuggable="no"
-prebuild_mode="yes"
-target_mode="yes"
-dev_mode="no"
-create_runner="no"
-update_mode="no"
-debug_mode="no"
-relocate="no"
-runtime="art"
-usage="no"
-suffix64=""
-trace="false"
-trace_stream="false"
-basic_verify="false"
-gc_verify="false"
-gc_stress="false"
-jvmti_trace_stress="false"
-jvmti_field_stress="false"
-jvmti_step_stress="false"
-jvmti_redefine_stress="false"
-strace="false"
-always_clean="no"
-never_clean="no"
-have_image="yes"
-android_root="/system"
-bisection_search="no"
-timeout=""
-suspend_timeout="500000"
-run_optimizing="false"
-dump_cfg="false"
-dump_cfg_path=""
-# To cause tests to fail fast, limit the file sizes created by dx, dex2oat and
-# ART output to approximately 128MB. This should be more than sufficient
-# for any test while still catching cases of runaway output.
-# Set a hard limit to encourage ART developers to increase the ulimit here if
-# needed to support a test case rather than resetting the limit in the run
-# script for the particular test in question. Adjust this if needed for
-# particular configurations.
-file_ulimit=128000
+  quiet = "no"
+  debuggable = "no"
+  prebuild_mode = "yes"
+  target_mode = "yes"
+  dev_mode = "no"
+  create_runner = "no"
+  update_mode = "no"
+  debug_mode = "no"
+  relocate = "no"
+  runtime = "art"
+  usage = "no"
+  suffix64 = ""
+  trace = "false"
+  trace_stream = "false"
+  basic_verify = "false"
+  gc_verify = "false"
+  gc_stress = "false"
+  jvmti_trace_stress = "false"
+  jvmti_field_stress = "false"
+  jvmti_step_stress = "false"
+  jvmti_redefine_stress = "false"
+  strace = "false"
+  always_clean = "no"
+  never_clean = "no"
+  have_image = "yes"
+  android_root = "/system"
+  bisection_search = "no"
+  timeout = ""
+  suspend_timeout = "500000"
+  run_optimizing = "false"
+  dump_cfg = "false"
+  dump_cfg_path = ""
+  # To cause tests to fail fast, limit the file sizes created by dx, dex2oat and
+  # ART output to approximately 128MB. This should be more than sufficient
+  # for any test while still catching cases of runaway output.
+  # Set a hard limit to encourage ART developers to increase the ulimit here if
+  # needed to support a test case rather than resetting the limit in the run
+  # script for the particular test in question. Adjust this if needed for
+  # particular configurations.
+  file_ulimit = 128000
 
+  args = list(sys.argv)
+  arg = args[0]
 
-while true; do
-    if [ "x$1" = "x--host" ]; then
-        target_mode="no"
-        DEX_LOCATION=$tmp_dir
-        run_args+=(--host)
-        shift
-    elif [ "x$1" = "x--quiet" ]; then
-        quiet="yes"
-        shift
-    elif [ "x$1" = "x--use-java-home" ]; then
-        if [ -n "${JAVA_HOME}" ]; then
-          export JAVA="${JAVA_HOME}/bin/java"
-          export JAVAC="${JAVA_HOME}/bin/javac -g"
-        else
-          echo "Passed --use-java-home without JAVA_HOME variable set!"
-          usage="yes"
-        fi
-        shift
-    elif [ "x$1" = "x--jvm" ]; then
-        target_mode="no"
-        DEX_LOCATION="$tmp_dir"
-        runtime="jvm"
-        prebuild_mode="no"
-        run_args+=(--jvm)
-        shift
-    elif [ "x$1" = "x-O" ]; then
-        lib="libart.so"
-        testlib="arttest"
-        run_args+=(-O)
-        shift
-    elif [ "x$1" = "x--dalvik" ]; then
-        lib="libdvm.so"
-        runtime="dalvik"
-        shift
-    elif [ "x$1" = "x--no-image" ]; then
-        have_image="no"
-        shift
-    elif [ "x$1" = "x--relocate" ]; then
-        relocate="yes"
-        shift
-    elif [ "x$1" = "x--no-relocate" ]; then
-        relocate="no"
-        shift
-    elif [ "x$1" = "x--prebuild" ]; then
-        run_args+=(--prebuild)
-        prebuild_mode="yes"
-        shift;
-    elif [ "x$1" = "x--compact-dex-level" ]; then
-        option="$1"
-        shift
-        run_args+=("$option" "$1")
-        shift;
-    elif [ "x$1" = "x--strip-dex" ]; then
-        run_args+=(--strip-dex)
-        shift;
-    elif [ "x$1" = "x--debuggable" ]; then
-        run_args+=(-Xcompiler-option --debuggable)
-        debuggable="yes"
-        shift;
-    elif [ "x$1" = "x--no-prebuild" ]; then
-        run_args+=(--no-prebuild)
-        prebuild_mode="no"
-        shift;
-    elif [ "x$1" = "x--gcverify" ]; then
-        basic_verify="true"
-        gc_verify="true"
-        shift
-    elif [ "x$1" = "x--gcstress" ]; then
-        basic_verify="true"
-        gc_stress="true"
-        shift
-    elif [ "x$1" = "x--jvmti-step-stress" ]; then
-        jvmti_step_stress="true"
-        shift
-    elif [ "x$1" = "x--jvmti-redefine-stress" ]; then
-        jvmti_redefine_stress="true"
-        shift
-    elif [ "x$1" = "x--jvmti-field-stress" ]; then
-        jvmti_field_stress="true"
-        shift
-    elif [ "x$1" = "x--jvmti-trace-stress" ]; then
-        jvmti_trace_stress="true"
-        shift
-    elif [ "x$1" = "x--suspend-timeout" ]; then
-        shift
-        suspend_timeout="$1"
-        shift
-    elif [ "x$1" = "x--image" ]; then
-        shift
-        image="$1"
-        run_args+=(--image "$image")
-        shift
-    elif [ "x$1" = "x-Xcompiler-option" ]; then
-        shift
-        option="$1"
-        run_args+=(-Xcompiler-option "$option")
-        shift
-    elif [ "x$1" = "x--runtime-option" ]; then
-        shift
-        option="$1"
-        run_args+=(--runtime-option "$option")
-        shift
-    elif [ "x$1" = "x--gdb-arg" ]; then
-        shift
-        gdb_arg="$1"
-        run_args+=(--gdb-arg "$gdb_arg")
-        shift
-    elif [ "x$1" = "x--gdb-dex2oat-args" ]; then
-        shift
-        gdb_dex2oat_args="$1"
-        run_args+=(--gdb-dex2oat-args "$gdb_dex2oat_args")
-        shift
-    elif [ "x$1" = "x--debug" ]; then
-        run_args+=(--debug)
-        shift
-    elif [ "x$1" = "x--debug-wrap-agent" ]; then
-        run_args+=(--debug-wrap-agent)
-        shift
-    elif [ "x$1" = "x--with-agent" ]; then
-        shift
-        option="$1"
-        run_args+=(--with-agent "$1")
-        shift
-    elif [ "x$1" = "x--debug-agent" ]; then
-        shift
-        option="$1"
-        run_args+=(--debug-agent "$1")
-        shift
-    elif [ "x$1" = "x--dump-cfg" ]; then
-        shift
-        dump_cfg="true"
-        dump_cfg_path="$1"
-        shift
-    elif [ "x$1" = "x--gdb" ]; then
-        run_args+=(--gdb)
-        dev_mode="yes"
-        shift
-    elif [ "x$1" = "x--gdb-dex2oat" ]; then
-        run_args+=(--gdb-dex2oat)
-        dev_mode="yes"
-        shift
-    elif [ "x$1" = "x--gdbserver-bin" ]; then
-        shift
-        run_args+=(--gdbserver-bin "$1")
-        shift
-    elif [ "x$1" = "x--gdbserver-port" ]; then
-        shift
-        run_args+=(--gdbserver-port "$1")
-        shift
-    elif [ "x$1" = "x--gdbserver" ]; then
-        run_args+=(--gdbserver)
-        dev_mode="yes"
-        shift
-    elif [ "x$1" = "x--strace" ]; then
-        strace="yes"
-        run_args+=(--invoke-with strace --invoke-with -o --invoke-with "$tmp_dir/$strace_output")
-        timeout="${timeout:-1800}"
-        shift
-    elif [ "x$1" = "x--zygote" ]; then
-        run_args+=(--zygote)
-        shift
-    elif [ "x$1" = "x--interpreter" ]; then
-        run_args+=(--interpreter)
-        shift
-    elif [ "x$1" = "x--jit" ]; then
-        run_args+=(--jit)
-        shift
-    elif [ "x$1" = "x--baseline" ]; then
-        run_args+=(--baseline)
-        shift
-    elif [ "x$1" = "x--optimizing" ]; then
-        run_optimizing="true"
-        shift
-    elif [ "x$1" = "x--no-verify" ]; then
-        run_args+=(--no-verify)
-        shift
-    elif [ "x$1" = "x--verify-soft-fail" ]; then
-        run_args+=(--verify-soft-fail)
-        shift
-    elif [ "x$1" = "x--no-optimize" ]; then
-        run_args+=(--no-optimize)
-        shift
-    elif [ "x$1" = "x--no-precise" ]; then
-        run_args+=(--no-precise)
-        shift
-    elif [ "x$1" = "x--external-log-tags" ]; then
-        run_args+=(--external-log-tags)
-        shift
-    elif [ "x$1" = "x--invoke-with" ]; then
-        shift
-        what="$1"
-        if [ "x$what" = "x" ]; then
-            echo "$0 missing argument to --invoke-with" 1>&2
-            usage="yes"
-            break
-        fi
-        run_args+=(--invoke-with "${what}")
-        shift
-    elif [ "x$1" = "x--create-runner" ]; then
-        run_args+=(--create-runner --dry-run)
-        dev_mode="yes"
-        never_clean="yes"
-        create_runner="yes"
-        shift
-    elif [ "x$1" = "x--dev" ]; then
-        run_args+=(--dev)
-        dev_mode="yes"
-        shift
-    elif [ "x$1" = "x--temp-path" ]; then
-        shift
-        tmp_dir=$1
-        if [ "x$tmp_dir" = "x" ]; then
-            echo "$0 missing argument to --temp-path" 1>&2
-            usage="yes"
-            break
-        fi
-        shift
-    elif [ "x$1" = "x--chroot" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            echo "$0 missing argument to --chroot" 1>&2
-            usage="yes"
-            break
-        fi
-        chroot="$1"
-        run_args+=(--chroot "$1")
-        shift
-    elif [ "x$1" = "x--simpleperf" ]; then
-        run_args+=(--simpleperf)
-        shift
-    elif [ "x$1" = "x--android-root" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            echo "$0 missing argument to --android-root" 1>&2
-            usage="yes"
-            break
-        fi
-        android_root="$1"
-        run_args+=(--android-root "$1")
-        shift
-    elif [ "x$1" = "x--android-art-root" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            echo "$0 missing argument to --android-art-root" 1>&2
-            usage="yes"
-            break
-        fi
-        run_args+=(--android-art-root "$1")
-        shift
-    elif [ "x$1" = "x--android-tzdata-root" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            echo "$0 missing argument to --android-tzdata-root" 1>&2
-            usage="yes"
-            break
-        fi
-        run_args+=(--android-tzdata-root "$1")
-        shift
-    elif [ "x$1" = "x--update" ]; then
-        update_mode="yes"
-        shift
-    elif [ "x$1" = "x--help" ]; then
-        usage="yes"
-        shift
-    elif [ "x$1" = "x--64" ]; then
-        run_args+=(--64)
-        suffix64="64"
-        shift
-    elif [ "x$1" = "x--bionic" ]; then
-        # soong linux_bionic builds are 64bit only.
-        run_args+=(--bionic --host --64)
-        suffix64="64"
-        target_mode="no"
-        DEX_LOCATION=$tmp_dir
-        host_lib_root=$OUT_DIR/soong/host/linux_bionic-x86
-        shift
-    elif [ "x$1" = "x--runtime-extracted-zipapex" ]; then
-        shift
-        # TODO Should we allow the java.library.path to search the zipapex too?
-        # Not needed at the moment and adding it will be complicated so for now
-        # we'll ignore this.
-        run_args+=(--host --runtime-extracted-zipapex "$1")
-        target_mode="no"
-        DEX_LOCATION=$tmp_dir
-        shift
-    elif [ "x$1" = "x--runtime-zipapex" ]; then
-        shift
-        # TODO Should we allow the java.library.path to search the zipapex too?
-        # Not needed at the moment and adding it will be complicated so for now
-        # we'll ignore this.
-        run_args+=(--host --runtime-zipapex "$1")
-        target_mode="no"
-        DEX_LOCATION=$tmp_dir
-        # apex_payload.zip is quite large we need a high enough ulimit to
-        # extract it. 512mb should be good enough.
-        file_ulimit=512000
-        shift
-    elif [ "x$1" = "x--timeout" ]; then
-        shift
-        if [ "x$1" = "x" ]; then
-            echo "$0 missing argument to --timeout" 1>&2
-            usage="yes"
-            break
-        fi
-        timeout="$1"
-        shift
-    elif [ "x$1" = "x--trace" ]; then
-        trace="true"
-        shift
-    elif [ "x$1" = "x--stream" ]; then
-        trace_stream="true"
-        shift
-    elif [ "x$1" = "x--always-clean" ]; then
-        always_clean="yes"
-        shift
-    elif [ "x$1" = "x--never-clean" ]; then
-        never_clean="yes"
-        shift
-    elif [ "x$1" = "x--dex2oat-swap" ]; then
-        run_args+=(--dex2oat-swap)
-        shift
-    elif [ "x$1" = "x--instruction-set-features" ]; then
-        shift
-        run_args+=(--instruction-set-features "$1")
-        shift
-    elif [ "x$1" = "x--bisection-search" ]; then
-        bisection_search="yes"
-        shift
-    elif [ "x$1" = "x--vdex" ]; then
-        run_args+=(--vdex)
-        shift
-    elif [ "x$1" = "x--dm" ]; then
-        run_args+=(--dm)
-        shift
-    elif [ "x$1" = "x--vdex-filter" ]; then
-        shift
-        filter=$1
-        run_args+=(--vdex-filter "$filter")
-        shift
-    elif [ "x$1" = "x--random-profile" ]; then
-        run_args+=(--random-profile)
-        shift
-    elif [ "x$1" = "x--dex2oat-jobs" ]; then
-        shift
-        run_args+=(-Xcompiler-option "-j$1")
-        shift
-    elif expr "x$1" : "x--" >/dev/null 2>&1; then
-        echo "unknown $0 option: $1" 1>&2
-        usage="yes"
+  def shift():
+    global arg
+    args.pop(0)
+    arg = args[0] if args else ""
+
+  shift()
+
+  while True:
+    if arg == "--host":
+      target_mode = "no"
+      DEX_LOCATION = tmp_dir
+      run_args += ["--host"]
+      os.environ["RUN_MODE"] = "host"
+      shift()
+    elif arg == "--quiet":
+      quiet = "yes"
+      shift()
+    elif arg == "--use-java-home":
+      JAVA_HOME = os.environ.get("JAVA_HOME")
+      if JAVA_HOME:
+        export("JAVA", f"{JAVA_HOME}/bin/java")
+        export("JAVAC", f"{JAVA_HOME}/bin/javac -g")
+      else:
+        error("Passed --use-java-home without JAVA_HOME variable set!")
+        usage = "yes"
+      shift()
+    elif arg == "--jvm":
+      target_mode = "no"
+      DEX_LOCATION = tmp_dir
+      runtime = "jvm"
+      prebuild_mode = "no"
+      run_args += ["--jvm"]
+      shift()
+    elif arg == "-O":
+      lib = "libart.so"
+      testlib = "arttest"
+      run_args += ["-O"]
+      shift()
+    elif arg == "--dalvik":
+      lib = "libdvm.so"
+      runtime = "dalvik"
+      shift()
+    elif arg == "--no-image":
+      have_image = "no"
+      shift()
+    elif arg == "--relocate":
+      relocate = "yes"
+      shift()
+    elif arg == "--no-relocate":
+      relocate = "no"
+      shift()
+    elif arg == "--prebuild":
+      run_args += ["--prebuild"]
+      prebuild_mode = "yes"
+      shift()
+    elif arg == "--compact-dex-level":
+      option = arg
+      shift()
+      run_args += [f'"{option}" "{arg}"']
+      shift()
+    elif arg == "--strip-dex":
+      run_args += ["--strip-dex"]
+      shift()
+    elif arg == "--debuggable":
+      run_args += ["-Xcompiler-option --debuggable"]
+      debuggable = "yes"
+      shift()
+    elif arg == "--no-prebuild":
+      run_args += ["--no-prebuild"]
+      prebuild_mode = "no"
+      shift()
+    elif arg == "--gcverify":
+      basic_verify = "true"
+      gc_verify = "true"
+      shift()
+    elif arg == "--gcstress":
+      basic_verify = "true"
+      gc_stress = "true"
+      shift()
+    elif arg == "--jvmti-step-stress":
+      jvmti_step_stress = "true"
+      os.environ["JVMTI_STEP_STRESS"] = "true"
+      shift()
+    elif arg == "--jvmti-redefine-stress":
+      jvmti_redefine_stress = "true"
+      os.environ["JVMTI_REDEFINE_STRESS"] = "true"
+      shift()
+    elif arg == "--jvmti-field-stress":
+      jvmti_field_stress = "true"
+      os.environ["JVMTI_FIELD_STRESS"] = "true"
+      shift()
+    elif arg == "--jvmti-trace-stress":
+      jvmti_trace_stress = "true"
+      os.environ["JVMTI_TRACE_STRESS"] = "true"
+      shift()
+    elif arg == "--suspend-timeout":
+      shift()
+      suspend_timeout = arg
+      shift()
+    elif arg == "--image":
+      shift()
+      image = arg
+      run_args += [f'--image "{image}"']
+      shift()
+    elif arg == "-Xcompiler-option":
+      shift()
+      option = arg
+      run_args += [f'-Xcompiler-option "{option}"']
+      shift()
+    elif arg == "--runtime-option":
+      shift()
+      option = arg
+      run_args += [f'--runtime-option "{option}"']
+      shift()
+    elif arg == "--gdb-arg":
+      shift()
+      gdb_arg = arg
+      run_args += [f'--gdb-arg "{gdb_arg}"']
+      shift()
+    elif arg == "--gdb-dex2oat-args":
+      shift()
+      gdb_dex2oat_args = arg
+      run_args += ['--gdb-dex2oat-args "{gdb_dex2oat_args}"']
+      shift()
+    elif arg == "--debug":
+      run_args += ["--debug"]
+      shift()
+    elif arg == "--debug-wrap-agent":
+      run_args += ["--debug-wrap-agent"]
+      shift()
+    elif arg == "--with-agent":
+      shift()
+      option = arg
+      run_args += [f'--with-agent "{arg}"']
+      shift()
+    elif arg == "--debug-agent":
+      shift()
+      option = arg
+      run_args += [f'--debug-agent "{arg}"']
+      shift()
+    elif arg == "--dump-cfg":
+      shift()
+      dump_cfg = "true"
+      dump_cfg_path = arg
+      shift()
+    elif arg == "--gdb":
+      run_args += ["--gdb"]
+      dev_mode = "yes"
+      shift()
+    elif arg == "--gdb-dex2oat":
+      run_args += ["--gdb-dex2oat"]
+      dev_mode = "yes"
+      shift()
+    elif arg == "--gdbserver-bin":
+      shift()
+      run_args += [f'--gdbserver-bin "{arg}"']
+      shift()
+    elif arg == "--gdbserver-port":
+      shift()
+      run_args += [f'--gdbserver-port "{arg}"']
+      shift()
+    elif arg == "--gdbserver":
+      run_args += ["--gdbserver"]
+      dev_mode = "yes"
+      shift()
+    elif arg == "--strace":
+      strace = "yes"
+      run_args += [
+          f'--invoke-with=strace --invoke-with=-o --invoke-with="{tmp_dir}/{strace_output}"'
+      ]
+      timeout = timeout or "1800"
+      shift()
+    elif arg == "--zygote":
+      run_args += ["--zygote"]
+      shift()
+    elif arg == "--interpreter":
+      run_args += ["--interpreter"]
+      shift()
+    elif arg == "--jit":
+      run_args += ["--jit"]
+      shift()
+    elif arg == "--baseline":
+      run_args += ["--baseline"]
+      shift()
+    elif arg == "--optimizing":
+      run_optimizing = "true"
+      shift()
+    elif arg == "--no-verify":
+      run_args += ["--no-verify"]
+      shift()
+    elif arg == "--verify-soft-fail":
+      run_args += ["--verify-soft-fail"]
+      os.environ["VERIFY_SOFT_FAIL"] = "true"
+      shift()
+    elif arg == "--no-optimize":
+      run_args += ["--no-optimize"]
+      shift()
+    elif arg == "--no-precise":
+      run_args += ["--no-precise"]
+      shift()
+    elif arg.startswith("--android-log-tags"):
+      run_args += [arg]
+      shift()
+    elif arg == "--external-log-tags":
+      run_args += ["--external-log-tags"]
+      shift()
+    elif arg == "--invoke-with":
+      shift()
+      what = arg
+      if not arg:
+        error("missing argument to --invoke-with")
+        usage = "yes"
         break
-    else
+      run_args += [f'--invoke-with "{what}"']
+      shift()
+    elif arg == "--create-runner":
+      run_args += ["--create-runner --dry-run"]
+      dev_mode = "yes"
+      never_clean = "yes"
+      create_runner = "yes"
+      shift()
+    elif arg == "--dev":
+      dev_mode = "yes"
+      shift()
+    elif arg == "--temp-path":
+      shift()
+      if not arg:
+        error("missing argument to --temp-path")
+        usage = "yes"
         break
-    fi
-done
+      shift()
+    elif arg == "--chroot":
+      shift()
+      if not arg:
+        error("missing argument to --chroot")
+        usage = "yes"
+        break
+      chroot = arg
+      run_args += [f'--chroot "{arg}"']
+      shift()
+    elif arg == "--simpleperf":
+      run_args += ["--simpleperf"]
+      shift()
+    elif arg == "--android-root":
+      shift()
+      if not arg:
+        error("missing argument to --android-root")
+        usage = "yes"
+        break
+      android_root = arg
+      run_args += [f'--android-root "{arg}"']
+      shift()
+    elif arg == "--android-art-root":
+      shift()
+      if not arg:
+        error("missing argument to --android-art-root")
+        usage = "yes"
+        break
+      run_args += [f'--android-art-root "{arg}"']
+      shift()
+    elif arg == "--android-tzdata-root":
+      shift()
+      if not arg:
+        error("missing argument to --android-tzdata-root")
+        usage = "yes"
+        break
+      run_args += [f'--android-tzdata-root "{arg}"']
+      shift()
+    elif arg == "--update":
+      update_mode = "yes"
+      shift()
+    elif arg == "--help":
+      usage = "yes"
+      shift()
+    elif arg == "--64":
+      run_args += ["--64"]
+      suffix64 = "64"
+      shift()
+    elif arg == "--bionic":
+      # soong linux_bionic builds are 64bit only.
+      run_args += ["--bionic --host --64"]
+      suffix64 = "64"
+      target_mode = "no"
+      DEX_LOCATION = tmp_dir
+      host_lib_root = f"{OUT_DIR}/soong/host/linux_bionic-x86"
+      shift()
+    elif arg == "--runtime-extracted-zipapex":
+      shift()
+      # TODO Should we allow the java.library.path to search the zipapex too?
+      # Not needed at the moment and adding it will be complicated so for now
+      # we'll ignore this.
+      run_args += [f'--host --runtime-extracted-zipapex "{arg}"']
+      target_mode = "no"
+      DEX_LOCATION = tmp_dir
+      shift()
+    elif arg == "--runtime-zipapex":
+      shift()
+      # TODO Should we allow the java.library.path to search the zipapex too?
+      # Not needed at the moment and adding it will be complicated so for now
+      # we'll ignore this.
+      run_args += [f'--host --runtime-zipapex "{arg}"']
+      target_mode = "no"
+      DEX_LOCATION = tmp_dir
+      # apex_payload.zip is quite large we need a high enough ulimit to
+      # extract it. 512mb should be good enough.
+      file_ulimit = 512000
+      shift()
+    elif arg == "--timeout":
+      shift()
+      if not arg:
+        error("missing argument to --timeout")
+        usage = "yes"
+        break
+      timeout = arg
+      shift()
+    elif arg == "--trace":
+      trace = "true"
+      shift()
+    elif arg == "--stream":
+      trace_stream = "true"
+      shift()
+    elif arg == "--always-clean":
+      always_clean = "yes"
+      shift()
+    elif arg == "--never-clean":
+      never_clean = "yes"
+      shift()
+    elif arg == "--dex2oat-swap":
+      run_args += ["--dex2oat-swap"]
+      shift()
+    elif arg == "--instruction-set-features":
+      shift()
+      run_args += [f'--instruction-set-features "{arg}"']
+      shift()
+    elif arg == "--bisection-search":
+      bisection_search = "yes"
+      shift()
+    elif arg == "--vdex":
+      run_args += ["--vdex"]
+      shift()
+    elif arg == "--dm":
+      run_args += ["--dm"]
+      shift()
+    elif arg == "--vdex-filter":
+      shift()
+      filter = arg
+      run_args += ['--vdex-filter "{filter}"']
+      shift()
+    elif arg == "--random-profile":
+      run_args += ["--random-profile"]
+      shift()
+    elif arg == "--dex2oat-jobs":
+      shift()
+      run_args += [f'-Xcompiler-option "-j{arg}"']
+      shift()
+    elif arg.startswith("--"):
+      error(f"unknown option: {arg}")
+      usage = "yes"
+      break
+    else:
+      break
 
-if [ "$usage" = "no" -a "x$1" = "x" ]; then
-  echo "missing test to run" 1>&2
-  usage="yes"
-fi
+  export("DEX_LOCATION", DEX_LOCATION)
+
+  if usage == "no" and not arg:
+    error("missing test to run")
+    usage = "yes"
 
 # The DEX_LOCATION with the chroot prefix, if any.
-chroot_dex_location="$chroot$DEX_LOCATION"
+  chroot_dex_location = f"{chroot}{DEX_LOCATION}"
 
-# Allocate file descriptor real_stderr and redirect it to the shell's error
-# output (fd 2).
-if [ ${BASH_VERSINFO[1]} -ge 4 ] && [ ${BASH_VERSINFO[2]} -ge 1 ]; then
-  exec {real_stderr}>&2
-else
-  # In bash before version 4.1 we need to do a manual search for free file
-  # descriptors.
-  FD=3
-  while [ -e /dev/fd/$FD ]; do FD=$((FD + 1)); done
-  real_stderr=$FD
-  eval "exec ${real_stderr}>&2"
-fi
-if [ "$quiet" = "yes" ]; then
-  # Force the default standard output and error to go to /dev/null so we will
-  # not print them.
-  exec 1>/dev/null
-  exec 2>/dev/null
-fi
+  # tmp_dir may be relative, resolve.
+  os.chdir(oldwd)
+  tmp_dir = os.path.realpath(tmp_dir)
+  os.chdir(progdir)
+  if not tmp_dir:
+    error(f"Failed to resolve {tmp_dir}")
+    sys.exit(1)
+  os.makedirs(tmp_dir, exist_ok=True)
 
-function err_echo() {
-  echo "$@" 1>&${real_stderr}
-}
+  # Add thread suspend timeout flag
+  if runtime != "jvm":
+    run_args += [
+        f'--runtime-option "-XX:ThreadSuspendTimeout={suspend_timeout}"'
+    ]
 
-# tmp_dir may be relative, resolve.
-#
-# Cannot use realpath, as it does not exist on Mac.
-# Cannot use a simple "cd", as the path might not be created yet.
-# Cannot use readlink -m, as it does not exist on Mac.
-# Fallback to nuclear option:
-noncanonical_tmp_dir=$tmp_dir
-tmp_dir="`cd $oldwd ; python3 -c "import os; import sys; sys.stdout.write(os.path.realpath('$tmp_dir'))"`"
-if [ -z $tmp_dir ] ; then
-  err_echo "Failed to resolve $tmp_dir"
-  exit 1
-fi
-mkdir -p $tmp_dir
-
-# Add thread suspend timeout flag
-if [ ! "$runtime" = "jvm" ]; then
-  run_args+=(--runtime-option "-XX:ThreadSuspendTimeout=$suspend_timeout")
-fi
-
-if [ "$basic_verify" = "true" ]; then
-  # Set HspaceCompactForOOMMinIntervalMs to zero to run hspace compaction for OOM more frequently in tests.
-  run_args+=(--runtime-option -Xgc:preverify --runtime-option -Xgc:postverify --runtime-option -XX:HspaceCompactForOOMMinIntervalMs=0)
-fi
-if [ "$gc_verify" = "true" ]; then
-  run_args+=(--runtime-option -Xgc:preverify_rosalloc --runtime-option -Xgc:postverify_rosalloc)
-fi
-if [ "$gc_stress" = "true" ]; then
-  run_args+=(--gc-stress --runtime-option -Xgc:gcstress --runtime-option -Xms2m --runtime-option -Xmx16m)
-fi
-if [ "$jvmti_redefine_stress" = "true" ]; then
-    run_args+=(--no-app-image --jvmti-redefine-stress)
-fi
-if [ "$jvmti_step_stress" = "true" ]; then
-    run_args+=(--no-app-image --jvmti-step-stress)
-fi
-if [ "$jvmti_field_stress" = "true" ]; then
-    run_args+=(--no-app-image --jvmti-field-stress)
-fi
-if [ "$jvmti_trace_stress" = "true" ]; then
-    run_args+=(--no-app-image --jvmti-trace-stress)
-fi
-if [ "$trace" = "true" ]; then
-    run_args+=(--runtime-option -Xmethod-trace --runtime-option -Xmethod-trace-file-size:2000000)
-    if [ "$trace_stream" = "true" ]; then
-        # Streaming mode uses the file size as the buffer size. So output gets really large. Drop
-        # the ability to analyze the file and just write to /dev/null.
-        run_args+=(--runtime-option -Xmethod-trace-file:/dev/null)
-        # Enable streaming mode.
-        run_args+=(--runtime-option -Xmethod-trace-stream)
-    else
-        run_args+=(--runtime-option "-Xmethod-trace-file:${DEX_LOCATION}/trace.bin")
-    fi
-elif [ "$trace_stream" = "true" ]; then
-    err_echo "Cannot use --stream without --trace."
-    exit 1
-fi
-if [ -n "$timeout" ]; then
-    run_args+=(--timeout "$timeout")
-fi
+  if basic_verify == "true":
+    # Set HspaceCompactForOOMMinIntervalMs to zero to run hspace compaction for OOM more frequently in tests.
+    run_args += [
+        "--runtime-option -Xgc:preverify --runtime-option -Xgc:postverify "
+        "--runtime-option -XX:HspaceCompactForOOMMinIntervalMs=0"
+    ]
+  if gc_verify == "true":
+    run_args += [
+        "--runtime-option -Xgc:preverify_rosalloc --runtime-option "
+        "-Xgc:postverify_rosalloc"
+    ]
+  if gc_stress == "true":
+    run_args += [
+        "--gc-stress --runtime-option -Xgc:gcstress --runtime-option -Xms2m "
+        "--runtime-option -Xmx16m"
+    ]
+  if jvmti_redefine_stress == "true":
+    run_args += ["--no-app-image --jvmti-redefine-stress"]
+  if jvmti_step_stress == "true":
+    run_args += ["--no-app-image --jvmti-step-stress"]
+  if jvmti_field_stress == "true":
+    run_args += ["--no-app-image --jvmti-field-stress"]
+  if jvmti_trace_stress == "true":
+    run_args += ["--no-app-image --jvmti-trace-stress"]
+  if trace == "true":
+    run_args += [
+        "--runtime-option -Xmethod-trace --runtime-option "
+        "-Xmethod-trace-file-size:2000000"
+    ]
+    if trace_stream == "true":
+      # Streaming mode uses the file size as the buffer size. So output gets really large. Drop
+      # the ability to analyze the file and just write to /dev/null.
+      run_args += ["--runtime-option -Xmethod-trace-file:/dev/null"]
+      # Enable streaming mode.
+      run_args += ["--runtime-option -Xmethod-trace-stream"]
+    else:
+      run_args += [
+          f'--runtime-option "-Xmethod-trace-file:{DEX_LOCATION}/trace.bin"'
+      ]
+  elif trace_stream == "true":
+    error("Cannot use --stream without --trace.")
+    sys.exit(1)
+  if timeout:
+    run_args += [f'--timeout "{timeout}"']
 
 # Most interesting target architecture variables are Makefile variables, not environment variables.
-# Try to map the suffix64 flag and what we find in ${ANDROID_PRODUCT_OUT}/data/art-test to an architecture name.
-function guess_target_arch_name() {
-    # Check whether this is a device with native bridge. Currently this is hardcoded
-    # to x86 + arm.
-    local guess_path=$chroot/system/framework/art_boot_images
-    local x86_arm=`adb shell ls ${guess_path} | sort | grep -E '^(arm|x86)$'`
-    # Collapse line-breaks into spaces
-    x86_arm=$(echo $x86_arm)
-    if [ "x$x86_arm" = "xarm x86" ] ; then
-        err_echo "Native-bridge configuration detected."
-        # We only support the main arch for tests.
-        if [ "x${suffix64}" = "x64" ]; then
-            target_arch_name=""
-        else
-            target_arch_name=x86
-        fi
-    else
-        local grep32bit=`adb shell ls ${guess_path} | grep -E '^(arm|x86)$'`
-        local grep64bit=`adb shell ls ${guess_path} | grep -E '^(arm64|x86_64)$'`
-        if [ "x${suffix64}" = "x64" ]; then
-            target_arch_name=${grep64bit}
-        else
-            target_arch_name=${grep32bit}
-        fi
-    fi
-}
+# Try to map the suffix64 flag and what we find in {ANDROID_PRODUCT_OUT}/data/art-test to an architecture name.
 
-function guess_host_arch_name() {
-    if [ "x${suffix64}" = "x64" ]; then
-        host_arch_name="x86_64"
-    else
-        host_arch_name="x86"
-    fi
-}
+  def guess_target_arch_name():
+    return get_target_arch(suffix64 == "64")
 
-if [ "$target_mode" = "no" ]; then
-    if [ "$runtime" = "jvm" ]; then
-        if [ "$prebuild_mode" = "yes" ]; then
-            err_echo "--prebuild with --jvm is unsupported"
-            exit 1
-        fi
-    else
-        # ART/Dalvik host mode.
-        if [ -n "$chroot" ]; then
-            err_echo "--chroot with --host is unsupported"
-            exit 1
-        fi
-    fi
-fi
+  def guess_host_arch_name():
+    if suffix64 == "64":
+      return "x86_64"
+    else:
+      return "x86"
 
-if [ ! "$runtime" = "jvm" ]; then
-  run_args+=(--lib "$lib")
-fi
+  if target_mode == "no":
+    if runtime == "jvm":
+      if prebuild_mode == "yes":
+        error("--prebuild with --jvm is unsupported")
+        sys.exit(1)
+    else:
+      # ART/Dalvik host mode.
+      if chroot:
+        error("--chroot with --host is unsupported")
+        sys.exit(1)
 
-if [ "$runtime" = "dalvik" ]; then
-    if [ "$target_mode" = "no" ]; then
-        framework="${ANDROID_PRODUCT_OUT}/system/framework"
-        bpath="${framework}/core-icu4j.jar:${framework}/core-libart.jar:${framework}/core-oj.jar:${framework}/conscrypt.jar:${framework}/okhttp.jar:${framework}/bouncycastle.jar:${framework}/ext.jar"
-        run_args+=(--boot --runtime-option "-Xbootclasspath:${bpath}")
-    else
-        true # defaults to using target BOOTCLASSPATH
-    fi
-elif [ "$runtime" = "art" ]; then
-    if [ "$target_mode" = "no" ]; then
-        guess_host_arch_name
-        run_args+=(--boot "${ANDROID_HOST_OUT}/apex/art_boot_images/javalib/boot.art")
-        run_args+=(--runtime-option "-Djava.library.path=${host_lib_root}/lib${suffix64}:${host_lib_root}/nativetest${suffix64}")
-    else
-        guess_target_arch_name
-        # Note that libarttest(d).so and other test libraries that depend on ART
-        # internal libraries must not be in this path for JNI libraries - they
-        # need to be loaded through LD_LIBRARY_PATH and
-        # NATIVELOADER_DEFAULT_NAMESPACE_LIBS instead.
-        run_args+=(--runtime-option "-Djava.library.path=/data/nativetest${suffix64}/art/${target_arch_name}")
-        run_args+=(--boot "/system/framework/art_boot_images/boot.art")
-    fi
-    if [ "$relocate" = "yes" ]; then
-      run_args+=(--relocate)
-    else
-      run_args+=(--no-relocate)
-    fi
-elif [ "$runtime" = "jvm" ]; then
+  if runtime != "jvm":
+    run_args += [f'--lib "{lib}"']
+
+  ANDROID_PRODUCT_OUT = os.environ.get("ANDROID_PRODUCT_OUT")
+  if runtime == "dalvik":
+    if target_mode == "no":
+      framework = f"{ANDROID_PRODUCT_OUT}/system/framework"
+      bpath = f"{framework}/core-icu4j.jar:{framework}/core-libart.jar:{framework}/core-oj.jar:{framework}/conscrypt.jar:{framework}/okhttp.jar:{framework}/bouncycastle.jar:{framework}/ext.jar"
+      run_args += [f'--boot --runtime-option "-Xbootclasspath:{bpath}"']
+    else:
+      pass  # defaults to using target BOOTCLASSPATH
+  elif runtime == "art":
+    if target_mode == "no":
+      host_arch_name = guess_host_arch_name()
+      run_args += [
+          f'--boot "{ANDROID_HOST_OUT}/apex/art_boot_images/javalib/boot.art"'
+      ]
+      run_args += [
+          f'--runtime-option "-Djava.library.path={host_lib_root}/lib{suffix64}:{host_lib_root}/nativetest{suffix64}"'
+      ]
+    else:
+      target_arch_name = guess_target_arch_name()
+      # Note that libarttest(d).so and other test libraries that depend on ART
+      # internal libraries must not be in this path for JNI libraries - they
+      # need to be loaded through LD_LIBRARY_PATH and
+      # NATIVELOADER_DEFAULT_NAMESPACE_LIBS instead.
+      run_args += [
+          f'--runtime-option "-Djava.library.path=/data/nativetest{suffix64}/art/{target_arch_name}"'
+      ]
+      run_args += ['--boot "/system/framework/art_boot_images/boot.art"']
+    if relocate == "yes":
+      run_args += ["--relocate"]
+    else:
+      run_args += ["--no-relocate"]
+  elif runtime == "jvm":
     # TODO: Detect whether the host is 32-bit or 64-bit.
-    run_args+=(--runtime-option "-Djava.library.path=${ANDROID_HOST_OUT}/lib64:${ANDROID_HOST_OUT}/nativetest64")
-fi
+    run_args += [
+        f'--runtime-option "-Djava.library.path={ANDROID_HOST_OUT}/lib64:{ANDROID_HOST_OUT}/nativetest64"'
+    ]
 
-if [ "$have_image" = "no" ]; then
-    if [ "$runtime" != "art" ]; then
-        err_echo "--no-image is only supported on the art runtime"
-        exit 1
-    fi
-    run_args+=(--no-image)
-fi
+  if have_image == "no":
+    if runtime != "art":
+      error("--no-image is only supported on the art runtime")
+      sys.exit(1)
+    run_args += ["--no-image"]
 
-if [ "$create_runner" = "yes" -a "$target_mode" = "yes" ]; then
-    err_echo "--create-runner does not function for non --host tests"
-    usage="yes"
-fi
+  if create_runner == "yes" and target_mode == "yes":
+    error("--create-runner does not function for non --host tests")
+    usage = "yes"
 
-if [ "$dev_mode" = "yes" -a "$update_mode" = "yes" ]; then
-    err_echo "--dev and --update are mutually exclusive"
-    usage="yes"
-fi
+  if dev_mode == "yes" and update_mode == "yes":
+    error("--dev and --update are mutually exclusive")
+    usage = "yes"
 
-if [ "$dev_mode" = "yes" -a "$quiet" = "yes" ]; then
-    err_echo "--dev and --quiet are mutually exclusive"
-    usage="yes"
-fi
+  if dev_mode == "yes" and quiet == "yes":
+    error("--dev and --quiet are mutually exclusive")
+    usage = "yes"
 
-if [ "$bisection_search" = "yes" -a "$prebuild_mode" = "yes" ]; then
-    err_echo "--bisection-search and --prebuild are mutually exclusive"
-    usage="yes"
-fi
+  if bisection_search == "yes" and prebuild_mode == "yes":
+    error("--bisection-search and --prebuild are mutually exclusive")
+    usage = "yes"
 
 # TODO: Chroot-based bisection search is not supported yet (see below); implement it.
-if [ "$bisection_search" = "yes" -a -n "$chroot" ]; then
-  err_echo "--chroot with --bisection-search is unsupported"
-  exit 1
-fi
+  if bisection_search == "yes" and chroot:
+    error("--chroot with --bisection-search is unsupported")
+    sys.exit(1)
 
-if [ "$usage" = "no" ]; then
-    if [ "x$1" = "x" -o "x$1" = "x-" ]; then
-        test_dir=`basename "$oldwd"`
-    else
-        test_dir="$1"
-    fi
+  if usage == "no":
+    if not arg or arg == "-":
+      test_dir = os.path.basename(oldwd)
+    else:
+      test_dir = arg
 
-    if [ '!' -d "$test_dir" ]; then
-        td2=`echo ${test_dir}-*`
-        if [ '!' -d "$td2" ]; then
-            err_echo "${test_dir}: no such test directory"
-            usage="yes"
-        fi
-        test_dir="$td2"
-    fi
+    if not os.path.isdir(test_dir):
+      td2 = glob.glob(f"{test_dir}-*")
+      if len(td2) == 1 and os.path.isdir(td2[0]):
+        test_dir = td2[0]
+      else:
+        error(f"{test_dir}: no such test directory")
+        usage = "yes"
     # Shift to get rid of the test name argument. The rest of the arguments
     # will get passed to the test run.
-    shift
-fi
+    shift()
 
-if [ "$usage" = "yes" ]; then
-    prog=`basename $prog`
-    (
-        echo "usage:"
-        echo "  $prog --help                          Print this message."
-        echo "  $prog [options] [test-name]           Run test normally."
-        echo "  $prog --dev [options] [test-name]     Development mode" \
-             "(dumps to stdout)."
-        echo "  $prog --create-runner [options] [test-name]"
-        echo "              Creates a runner script for use with other " \
-             "tools (e.g. parallel_run.py)."
-        echo "              The script will only run the test portion, and " \
-             "share oat and dex files."
-        echo "  $prog --update [options] [test-name]  Update mode" \
-             "(replaces expected-stdout.txt and expected-stderr.txt)."
-        echo '  Omitting the test name or specifying "-" will use the' \
-             "current directory."
-        echo "  Runtime Options:"
-        echo "    -O                    Run non-debug rather than debug build (off by default)."
-        echo "    -Xcompiler-option     Pass an option to the compiler."
-        echo "    --runtime-option      Pass an option to the runtime."
-        echo "    --compact-dex-level   Specify a compact dex level to the compiler."
-        echo "    --debug               Wait for the default debugger to attach."
-        echo "    --debug-agent <agent-path>"
-        echo "                          Wait for the given debugger agent to attach. Currently"
-        echo "                          only supported on host."
-        echo "    --debug-wrap-agent    use libwrapagentproperties and tools/libjdwp-compat.props"
-        echo "                          to load the debugger agent specified by --debug-agent."
-        echo "    --with-agent <agent>  Run the test with the given agent loaded with -agentpath:"
-        echo "    --debuggable          Whether to compile Java code for a debugger."
-        echo "    --gdb                 Run under gdb; incompatible with some tests."
-        echo "    --gdb-dex2oat         Run dex2oat under the prebuilt lldb."
-        echo "    --gdbserver           Start gdbserver (defaults to port :5039)."
-        echo "    --gdbserver-port <port>"
-        echo "                          Start gdbserver with the given COMM (see man gdbserver)."
-        echo "    --gdbserver-bin <binary>"
-        echo "                          Use the given binary as gdbserver."
-        echo "    --gdb-arg             Pass an option to gdb or gdbserver."
-        echo "    --gdb-dex2oat-args    Pass options separated by ';' to lldb for dex2oat."
-        echo "    --simpleperf          Wraps the dalvikvm invocation in 'simpleperf record ..."
-        echo "                          ... simpleperf report' and dumps stats to stdout."
-        echo "    --temp-path [path]    Location where to execute the tests."
-        echo "    --interpreter         Enable interpreter only mode (off by default)."
-        echo "    --jit                 Enable jit (off by default)."
-        echo "    --optimizing          Enable optimizing compiler (default)."
-        echo "    --no-verify           Turn off verification (on by default)."
-        echo "    --verify-soft-fail    Force soft fail verification (off by default)."
-        echo "                          Verification is enabled if neither --no-verify"
-        echo "                          nor --verify-soft-fail is specified."
-        echo "    --no-optimize         Turn off optimization (on by default)."
-        echo "    --no-precise          Turn off precise GC (on by default)."
-        echo "    --zygote              Spawn the process from the Zygote." \
-             "If used, then the"
-        echo "                          other runtime options are ignored."
-        echo "    --prebuild            Run dex2oat on the files before starting test. (default)"
-        echo "    --no-prebuild         Do not run dex2oat on the files before starting"
-        echo "                          the test."
-        echo "    --strip-dex           Strip the dex files before starting test."
-        echo "    --relocate            Force the use of relocating in the test, making"
-        echo "                          the image and oat files be relocated to a random"
-        echo "                          address before running."
-        echo "    --no-relocate         Force the use of no relocating in the test. (default)"
-        echo "    --image               Run the test using a precompiled boot image. (default)"
-        echo "    --no-image            Run the test without a precompiled boot image."
-        echo "    --host                Use the host-mode virtual machine."
-        echo "    --invoke-with         Pass --invoke-with option to runtime."
-        echo "    --dalvik              Use Dalvik (off by default)."
-        echo "    --jvm                 Use a host-local RI virtual machine."
-        echo "    --use-java-home       Use the JAVA_HOME environment variable"
-        echo "                          to find the java compiler and runtime"
-        echo "                          (if applicable) to run the test with."
-        echo "    --64                  Run the test in 64-bit mode"
-        echo "    --bionic              Use the (host, 64-bit only) linux_bionic libc runtime"
-        echo "    --runtime-zipapex [file]"
-        echo "                          Use the given zipapex file to provide runtime binaries"
-        echo "    --runtime-extracted-zipapex [dir]"
-        echo "                          Use the given extracted zipapex directory to provide"
-        echo "                          runtime binaries"
-        echo "    --timeout n           Test timeout in seconds"
-        echo "    --trace               Run with method tracing"
-        echo "    --strace              Run with syscall tracing from strace."
-        echo "    --stream              Run method tracing in streaming mode (requires --trace)"
-        echo "    --gcstress            Run with gc stress testing"
-        echo "    --gcverify            Run with gc verification"
-        echo "    --jvmti-trace-stress  Run with jvmti method tracing stress testing"
-        echo "    --jvmti-step-stress   Run with jvmti single step stress testing"
-        echo "    --jvmti-redefine-stress"
-        echo "                          Run with jvmti method redefinition stress testing"
-        echo "    --always-clean        Delete the test files even if the test fails."
-        echo "    --never-clean         Keep the test files even if the test succeeds."
-        echo "    --chroot [newroot]    Run with root directory set to newroot."
-        echo "    --android-root [path] The path on target for the android root. (/system by default)."
-        echo "    --android-i18n-root [path]"
-        echo "                          The path on target for the i18n module root."
-        echo "                          (/apex/com.android.i18n by default)."
-        echo "    --android-art-root [path]"
-        echo "                          The path on target for the ART module root."
-        echo "                          (/apex/com.android.art by default)."
-        echo "    --android-tzdata-root [path]"
-        echo "                          The path on target for the Android Time Zone Data root."
-        echo "                          (/apex/com.android.tzdata by default)."
-        echo "    --dex2oat-swap        Use a dex2oat swap file."
-        echo "    --instruction-set-features [string]"
-        echo "                          Set instruction-set-features for compilation."
-        echo "    --quiet               Don't print anything except failure messages"
-        echo "    --external-log-tags   Use ANDROID_LOG_TAGS to set a custom logging level for"
-        echo "                          a test run."
-        echo "    --bisection-search    Perform bisection bug search."
-        echo "    --vdex                Test using vdex as in input to dex2oat. Only works with --prebuild."
-        echo "    --suspend-timeout     Change thread suspend timeout ms (default 500000)."
-        echo "    --dex2oat-jobs        Number of dex2oat jobs."
-    ) 1>&2  # Direct to stderr so usage is not printed if --quiet is set.
-    exit 1
-fi
+  if usage == "yes":
+    prog = os.path.basename(__file__)
+    # pyformat: disable
+    help=(
+        "usage:\n"
+        f"  $prog --help                          Print this message.\n"
+        f"  $prog [options] [test-name]           Run test normally.\n"
+        f"  $prog --dev [options] [test-name]     Development mode\n"
+        "(dumps to stdout).\n"
+        f"  $prog --create-runner [options] [test-name]\n"
+        "              Creates a runner script for use with other \n"
+        "tools (e.g. parallel_run.py).\n"
+        "              The script will only run the test portion, and \n"
+        "share oat and dex files.\n"
+        f"  $prog --update [options] [test-name]  Update mode\n"
+        "(replaces expected-stdout.txt and expected-stderr.txt).\n"
+        '  Omitting the test name or specifying "-" will use the\n'
+        "current directory.\n"
+        "  Runtime Options:\n"
+        "    -O                    Run non-debug rather than debug build (off by default).\n"
+        "    -Xcompiler-option     Pass an option to the compiler.\n"
+        "    --runtime-option      Pass an option to the runtime.\n"
+        "    --compact-dex-level   Specify a compact dex level to the compiler.\n"
+        "    --debug               Wait for the default debugger to attach.\n"
+        "    --debug-agent <agent-path>\n"
+        "                          Wait for the given debugger agent to attach. Currently\n"
+        "                          only supported on host.\n"
+        "    --debug-wrap-agent    use libwrapagentproperties and tools/libjdwp-compat.props\n"
+        "                          to load the debugger agent specified by --debug-agent.\n"
+        "    --with-agent <agent>  Run the test with the given agent loaded with -agentpath:\n"
+        "    --debuggable          Whether to compile Java code for a debugger.\n"
+        "    --gdb                 Run under gdb; incompatible with some tests.\n"
+        "    --gdb-dex2oat         Run dex2oat under the prebuilt lldb.\n"
+        "    --gdbserver           Start gdbserver (defaults to port :5039).\n"
+        "    --gdbserver-port <port>\n"
+        "                          Start gdbserver with the given COMM (see man gdbserver).\n"
+        "    --gdbserver-bin <binary>\n"
+        "                          Use the given binary as gdbserver.\n"
+        "    --gdb-arg             Pass an option to gdb or gdbserver.\n"
+        "    --gdb-dex2oat-args    Pass options separated by ';' to lldb for dex2oat.\n"
+        "    --simpleperf          Wraps the dalvikvm invocation in 'simpleperf record ...\n"
+        "                          ... simpleperf report' and dumps stats to stdout.\n"
+        "    --temp-path [path]    Location where to execute the tests.\n"
+        "    --interpreter         Enable interpreter only mode (off by default).\n"
+        "    --jit                 Enable jit (off by default).\n"
+        "    --optimizing          Enable optimizing compiler (default).\n"
+        "    --no-verify           Turn off verification (on by default).\n"
+        "    --verify-soft-fail    Force soft fail verification (off by default).\n"
+        "                          Verification is enabled if neither --no-verify\n"
+        "                          nor --verify-soft-fail is specified.\n"
+        "    --no-optimize         Turn off optimization (on by default).\n"
+        "    --no-precise          Turn off precise GC (on by default).\n"
+        "    --zygote              Spawn the process from the Zygote.\n"
+        "If used, then the\n"
+        "                          other runtime options are ignored.\n"
+        "    --prebuild            Run dex2oat on the files before starting test. (default)\n"
+        "    --no-prebuild         Do not run dex2oat on the files before starting\n"
+        "                          the test.\n"
+        "    --strip-dex           Strip the dex files before starting test.\n"
+        "    --relocate            Force the use of relocating in the test, making\n"
+        "                          the image and oat files be relocated to a random\n"
+        "                          address before running.\n"
+        "    --no-relocate         Force the use of no relocating in the test. (default)\n"
+        "    --image               Run the test using a precompiled boot image. (default)\n"
+        "    --no-image            Run the test without a precompiled boot image.\n"
+        "    --host                Use the host-mode virtual machine.\n"
+        "    --invoke-with         Pass --invoke-with option to runtime.\n"
+        "    --dalvik              Use Dalvik (off by default).\n"
+        "    --jvm                 Use a host-local RI virtual machine.\n"
+        "    --use-java-home       Use the JAVA_HOME environment variable\n"
+        "                          to find the java compiler and runtime\n"
+        "                          (if applicable) to run the test with.\n"
+        "    --64                  Run the test in 64-bit mode\n"
+        "    --bionic              Use the (host, 64-bit only) linux_bionic libc runtime\n"
+        "    --runtime-zipapex [file]\n"
+        "                          Use the given zipapex file to provide runtime binaries\n"
+        "    --runtime-extracted-zipapex [dir]\n"
+        "                          Use the given extracted zipapex directory to provide\n"
+        "                          runtime binaries\n"
+        "    --timeout n           Test timeout in seconds\n"
+        "    --trace               Run with method tracing\n"
+        "    --strace              Run with syscall tracing from strace.\n"
+        "    --stream              Run method tracing in streaming mode (requires --trace)\n"
+        "    --gcstress            Run with gc stress testing\n"
+        "    --gcverify            Run with gc verification\n"
+        "    --jvmti-trace-stress  Run with jvmti method tracing stress testing\n"
+        "    --jvmti-step-stress   Run with jvmti single step stress testing\n"
+        "    --jvmti-redefine-stress\n"
+        "                          Run with jvmti method redefinition stress testing\n"
+        "    --always-clean        Delete the test files even if the test fails.\n"
+        "    --never-clean         Keep the test files even if the test succeeds.\n"
+        "    --chroot [newroot]    Run with root directory set to newroot.\n"
+        "    --android-root [path] The path on target for the android root. (/system by default).\n"
+        "    --android-i18n-root [path]\n"
+        "                          The path on target for the i18n module root.\n"
+        "                          (/apex/com.android.i18n by default).\n"
+        "    --android-art-root [path]\n"
+        "                          The path on target for the ART module root.\n"
+        "                          (/apex/com.android.art by default).\n"
+        "    --android-tzdata-root [path]\n"
+        "                          The path on target for the Android Time Zone Data root.\n"
+        "                          (/apex/com.android.tzdata by default).\n"
+        "    --dex2oat-swap        Use a dex2oat swap file.\n"
+        "    --instruction-set-features [string]\n"
+        "                          Set instruction-set-features for compilation.\n"
+        "    --quiet               Don't print anything except failure messages\n"
+        "    --external-log-tags   Use ANDROID_LOG_TAGS to set a custom logging level for\n"
+        "                          a test run.\n"
+        "    --bisection-search    Perform bisection bug search.\n"
+        "    --vdex                Test using vdex as in input to dex2oat. Only works with --prebuild.\n"
+        "    --suspend-timeout     Change thread suspend timeout ms (default 500000).\n"
+        "    --dex2oat-jobs        Number of dex2oat jobs.\n"
+    )
+    # pyformat: enable
+    error(help)
+    sys.exit(1)
 
-cd "$test_dir"
-test_dir=`pwd`
+  os.chdir(test_dir)
+  test_dir = os.getcwd()
 
-td_info="${test_dir}/${info}"
-td_expected_stdout="${test_dir}/${expected_stdout}"
-td_expected_stderr="${test_dir}/${expected_stderr}"
+  TEST_NAME = os.path.basename(test_dir)
+  export("TEST_NAME", TEST_NAME)
 
-for td_file in "$td_info" "$td_expected_stdout" "$td_expected_stderr"; do
-    if [ ! -r "$td_file" ]; then
-        err_echo "${test_dir}: missing file $td_file"
-        exit 1
-    fi
-done
+  # Tests named '<number>-checker-*' will also have their CFGs verified with
+  # Checker when compiled with Optimizing on host.
+  # Additionally, if the user specifies that the CFG must be dumped, it will
+  # run the checker for any type of test to generate the CFG.
+  if re.match("[0-9]+-checker-", TEST_NAME) or dump_cfg == "true":
+    if runtime == "art" and run_optimizing == "true":
+      # In no-prebuild or no-image mode, the compiler only quickens so disable the checker.
+      if prebuild_mode == "yes":
+        run_checker = "yes"
 
-export TEST_NAME=`basename ${test_dir}`
+        if target_mode == "no":
+          cfg_output_dir = tmp_dir
+          checker_args = f"--arch={host_arch_name.upper()}"
+        else:
+          cfg_output_dir = DEX_LOCATION
+          checker_args = f"--arch={target_arch_name.upper()}"
 
-# Tests named '<number>-checker-*' will also have their CFGs verified with
-# Checker when compiled with Optimizing on host.
-# Additionally, if the user specifies that the CFG must be dumped, it will
-# run the checker for any type of test to generate the CFG.
-if [[ "$TEST_NAME" =~ ^[0-9]+-checker- ]] || [ "$dump_cfg" = "true" ]; then
-  if [ "$runtime" = "art" -a "$run_optimizing" = "true" ]; then
-    # In no-prebuild or no-image mode, the compiler only quickens so disable the checker.
-    if [ "$prebuild_mode" = "yes" ]; then
-      run_checker="yes"
+        if debuggable == "yes":
+          checker_args += " --debuggable"
 
-      if [ "$target_mode" = "no" ]; then
-        cfg_output_dir="$tmp_dir"
-        checker_args="--arch=${host_arch_name^^}"
-      else
-        cfg_output_dir="$DEX_LOCATION"
-        checker_args="--arch=${target_arch_name^^}"
-      fi
+        run_args += [
+            f'-Xcompiler-option "--dump-cfg={cfg_output_dir}/{cfg_output}" -Xcompiler-option -j1'
+        ]
+        checker_args = f"{checker_args} --print-cfg"
 
-      if [ "$debuggable" = "yes" ]; then
-        checker_args="$checker_args --debuggable"
-      fi
+  run_args += [f'--testlib "{testlib}"']
 
-      run_args+=(-Xcompiler-option "--dump-cfg=$cfg_output_dir/$cfg_output" -Xcompiler-option -j1)
-      checker_args="$checker_args --print-cfg"
-    fi
-  fi
-fi
+  resource.setrlimit(resource.RLIMIT_FSIZE, (file_ulimit * 1024, resource.RLIM_INFINITY))
 
-run_args+=(--testlib "${testlib}")
+  # Extract run-test data from the zip file.
+  shutil.rmtree(tmp_dir)
+  os.makedirs(f"{tmp_dir}/.unzipped")
+  os.chdir(tmp_dir)
+  m = re.match("[0-9]*([0-9][0-9])-.*", TEST_NAME)
+  assert m, "Can not find test number in " + TEST_NAME
+  SHARD = "HiddenApi" if "hiddenapi" in TEST_NAME else m.group(1)
+  if target_mode == "yes":
+    zip_file = f"{ANDROID_HOST_OUT}/etc/art/art-run-test-target-data-shard{SHARD}.zip"
+    zip_entry = f"target/{TEST_NAME}/"
+  elif runtime == "jvm":
+    zip_file = f"{ANDROID_HOST_OUT}/etc/art/art-run-test-jvm-data-shard{SHARD}.zip"
+    zip_entry = f"jvm/{TEST_NAME}/"
+  else:
+    zip_file = f"{ANDROID_HOST_OUT}/etc/art/art-run-test-host-data-shard{SHARD}.zip"
+    zip_entry = f"host/{TEST_NAME}/"
+  zip = ZipFile(zip_file, "r")
+  zip_entries = [e for e in zip.namelist() if e.startswith(zip_entry)]
+  zip.extractall(Path(tmp_dir) / ".unzipped", members=zip_entries)
+  for entry in (Path(tmp_dir) / ".unzipped" / zip_entry).iterdir():
+    entry.rename(Path(tmp_dir) / entry.name)
 
-if ! ulimit -f ${file_ulimit}; then
-  err_echo "ulimit file size setting failed"
-fi
+  def clean_up(passed: bool):
+    if always_clean == "yes" or (passed and never_clean == "no"):
+      os.chdir(oldwd)
+      shutil.rmtree(tmp_dir)
+      if target_mode == "yes":
+        if ON_VM:
+          run(f"{SSH_CMD} \"rm -rf {chroot_dex_location}\"")
+        else:
+          run(f"adb shell rm -rf {chroot_dex_location}")
+      print(f"{TEST_NAME} files deleted from host" +
+            (" and from target" if target_mode == "yes" else ""))
+    else:
+      print(f"{TEST_NAME} files left in {tmp_dir} on host" +
+            (f" and in {chroot_dex_location} on target" if target_mode == "yes" else ""))
+    atexit.unregister(clean_up)
+  # TODO: Run this in global try-finally once the script is more refactored.
+  atexit.register(clean_up, passed=False)
 
-# Extract run-test data from the zip file.
-rm -rf "$tmp_dir"
-mkdir -p "$tmp_dir/.unzipped"
-cd "$tmp_dir"
-if [[ "$target_mode" == "yes" ]]; then
-  zip_file="${ANDROID_HOST_OUT}/etc/art/art-run-test-target-data.zip"
-  zip_entry="target/${TEST_NAME}"
-elif [[ $runtime == "jvm" ]]; then
-  zip_file="${ANDROID_HOST_OUT}/etc/art/art-run-test-jvm-data.zip"
-  zip_entry="jvm/${TEST_NAME}"
-else
-  zip_file="${ANDROID_HOST_OUT}/etc/art/art-run-test-host-data.zip"
-  zip_entry="host/${TEST_NAME}"
-fi
-unzip -q "${zip_file}" "${zip_entry}/*" -d "$tmp_dir/.unzipped"
-mv "$tmp_dir"/.unzipped/${zip_entry}/* "$tmp_dir"
+  ctx = RunTestContext(Path(tmp_dir), target_mode == "yes", chroot, DEX_LOCATION, TEST_NAME)
+  td_info = f"{test_dir}/{info}"
+  for td_file in [td_info, ctx.expected_stdout, ctx.expected_stderr]:
+    assert os.access(td_file, os.R_OK)
 
-good="no"
-good_run="yes"
-export TEST_RUNTIME="${runtime}"
-if [ "$dev_mode" = "yes" ]; then
-    echo "${test_dir}: running..." 1>&2
-    "./${run}" "${run_args[@]}" "$@"
-    run_exit="$?"
+  joined_run_args = " ".join(run_args)
+  joined_args = " ".join(args)
 
-    if [ "$run_exit" = "0" ]; then
-        if [ "$run_checker" = "yes" ]; then
-            if [ "$target_mode" = "yes" ]; then
-              adb pull "$chroot/$cfg_output_dir/$cfg_output" &> /dev/null
-            fi
-            "$checker" $checker_args "$cfg_output" "$tmp_dir" 2>&1
-            checker_exit="$?"
-            if [ "$checker_exit" = "0" ]; then
-                good="yes"
-            fi
-            err_echo "checker exit status: $checker_exit"
-        else
-            good="yes"
-        fi
-    fi
-    echo "run exit status: $run_exit" 1>&2
-elif [ "$update_mode" = "yes" ]; then
-    echo "${test_dir}: running..." 1>&2
-    "./${run}" "${run_args[@]}" "$@" >"$test_stdout" 2>"$test_stderr"
-    if [ "$run_checker" = "yes" ]; then
-      if [ "$target_mode" = "yes" ]; then
-        adb pull "$chroot/$cfg_output_dir/$cfg_output" &> /dev/null
-      fi
-      "$checker" -q $checker_args "$cfg_output" "$tmp_dir" >>"$test_stdout" 2>>"$test_stderr"
-    fi
-    sed -e 's/[[:cntrl:]]$//g' <"$test_stdout" >"${td_expected_stdout}"
-    sed -e 's/[[:cntrl:]]$//g' <"$test_stderr" >"${td_expected_stderr}"
-    good="yes"
-else
-    echo "${test_dir}: running..." 1>&2
-    "./${run}" "${run_args[@]}" "$@" >"$test_stdout" 2>"$test_stderr"
-    run_exit="$?"
-    if [ "$run_exit" != "0" ]; then
-        err_echo "run exit status: $run_exit"
-        good_run="no"
-    elif [ "$run_checker" = "yes" ]; then
-        if [ "$target_mode" = "yes" ]; then
-          adb pull "$chroot/$cfg_output_dir/$cfg_output" &> /dev/null
-        fi
-        "$checker" -q $checker_args "$cfg_output" "$tmp_dir" >>"$test_stdout" 2>>"$test_stderr"
-        checker_exit="$?"
-        if [ "$checker_exit" != "0" ]; then
-            err_echo "checker exit status: $checker_exit"
-            good_run="no"
-        else
-            good_run="yes"
-        fi
-    else
-        good_run="yes"
-    fi
-    ./$check_cmd "$expected_stdout" "$test_stdout" "$expected_stderr" "$test_stderr"
-    if [ "$?" = "0" ]; then
-        if [ "$good_run" = "yes" ]; then
-          # test_stdout == expected_stdout && test_stderr == expected_stderr
-          good="yes"
-          echo "${test_dir}: succeeded!" 1>&2
-        fi
-    fi
-fi
+  # Create runner (bash script that executes the whole test)
+  def create_runner_script() -> Path:
+    parsed_args = default_run_module.parse_args(shlex.split(" ".join(run_args + args)))
+    parsed_args.stdout_file = os.path.join(DEX_LOCATION, test_stdout)
+    parsed_args.stderr_file = os.path.join(DEX_LOCATION, test_stderr)
 
-(
-    if [ "$good" != "yes" -a "$update_mode" != "yes" ]; then
-        echo "${test_dir}: FAILED!"
-        echo ' '
-        echo '#################### info'
-        cat "${td_info}" | sed 's/^/# /g'
-        echo '#################### stdout diffs'
-        if [ "$run_checker" == "yes" ]; then
-          # Checker failures dump the whole CFG, so we output the whole diff.
-          diff --strip-trailing-cr -u "$expected_stdout" "$test_stdout"
-        else
-          diff --strip-trailing-cr -u "$expected_stdout" "$test_stdout" | tail -n 10000
-        fi
-        echo '####################'
-        echo '#################### stderr diffs'
-        diff --strip-trailing-cr -u "$expected_stderr" "$test_stderr" | tail -n 10000
-        echo '####################'
-        if [ "$strace" = "yes" ]; then
-            echo '#################### strace output'
-            tail -n 3000 "$tmp_dir/$strace_output"
-            echo '####################'
-        fi
-        if [ "x$target_mode" = "xno" -a "x$SANITIZE_HOST" = "xaddress" ]; then
-            # Run the stack script to symbolize any ASAN aborts on the host for SANITIZE_HOST. The
-            # tools used by the given ABI work for both x86 and x86-64.
-            echo "ABI: 'x86_64'" | cat - "$test_stdout" "$test_stderr" \
-              | $ANDROID_BUILD_TOP/development/scripts/stack | tail -n 3000
-        fi
-        echo ' '
-    fi
+    ctx.run(f"cd {DEX_LOCATION}")
+    if target_mode != "yes":
+      # Make "out" directory accessible from test directory.
+      ctx.run(f"ln -s -f -t {DEX_LOCATION} {ANDROID_BUILD_TOP}/out")
+    # Clear the stdout/stderr files (create empty files).
+    ctx.run(f"echo -n > {test_stdout} && echo -n > {test_stderr}")
 
-) 2>&${real_stderr} 1>&2
+    script = Path(tmp_dir) / "run.py"
+    if script.exists():
+      module = SourceFileLoader("run_" + TEST_NAME, str(script)).load_module()
+      module.run(ctx, parsed_args)
+    else:
+      default_run_module.default_run(ctx, parsed_args)
 
-# Copy the generated CFG to the specified path.
-if [ $dump_cfg = "true" ]; then
-    if [ $run_optimizing != "true" ]; then
-        err_echo "Can't dump the .cfg if the compiler type isn't set to \"optimizing\"."
-    else
-        if [ "$target_mode" = "yes" ]; then
-            adb pull $chroot/$cfg_output_dir/$cfg_output $dump_cfg_path
-        else
-            cp $cfg_output_dir/$cfg_output $dump_cfg_path
-        fi
-    fi
-fi
+    runner = Path(tmp_dir) / "run.sh"
+    runner.write_text("\n".join(ctx.runner))
+    runner.chmod(0o777)
+    return runner
 
-# Attempt bisection only if the test failed.
-# TODO: Implement support for chroot-based bisection search.
-if [ "$bisection_search" = "yes" -a "$good" != "yes" ]; then
-    # Bisecting works by skipping different optimization passes which breaks checker assertions.
-    if [ "$run_checker" == "yes" ]; then
-      echo "${test_dir}: not bisecting, checker test." 1>&2
-    else
-      # Increase file size limit, bisection search can generate large logfiles.
-      echo "${test_dir}: bisecting..." 1>&2
-      cwd=`pwd`
-      maybe_device_mode=""
-      raw_cmd=""
-      if [ "$target_mode" = "yes" ]; then
-        # Produce cmdline.sh in $chroot_dex_location. "$@" is passed as a runtime option
-        # so that cmdline.sh forwards its arguments to dalvikvm. invoke-with is set
-        # to exec in order to preserve pid when calling dalvikvm. This is required
-        # for bisection search to correctly retrieve logs from device.
-        "./${run}" "${run_args[@]}" --runtime-option '"$@"' --invoke-with exec --dry-run "$@" &> /dev/null
-        adb shell chmod u+x "$chroot_dex_location/cmdline.sh"
-        maybe_device_mode="--device"
-        raw_cmd="$DEX_LOCATION/cmdline.sh"
-      else
-        raw_cmd="$cwd/${run} --external-log-tags "${run_args[@]}" $@"
-      fi
-      # TODO: Pass a `--chroot` option to the bisection_search.py script and use it there.
-      $ANDROID_BUILD_TOP/art/tools/bisection_search/bisection_search.py \
-        $maybe_device_mode \
-        --raw-cmd="$raw_cmd" \
-        --check-script="$cwd/check" \
-        --expected-output="$cwd/expected-stdout.txt" \
-        --logfile="$cwd/bisection_log.txt" \
-        --timeout=${timeout:-300}
-    fi
-fi
+  # Test might not execute anything but we still expect the output files to exist.
+  Path(test_stdout).touch()
+  Path(test_stderr).touch()
 
-# Clean up test files.
-if [ "$always_clean" = "yes" -o "$good" = "yes" ] && [ "$never_clean" = "no" ]; then
-    cd "$oldwd"
-    rm -rf "$tmp_dir"
-    if [ "$target_mode" = "yes" -a "$build_exit" = "0" ]; then
-        adb shell rm -rf $chroot_dex_location
-    fi
-    if [ "$good" = "yes" ]; then
-        exit 0
-    fi
-fi
+  export("TEST_RUNTIME", runtime)
 
+  print(f"{test_dir}: Create runner script...")
+  runner = create_runner_script()
 
-(
-    if [ "$always_clean" = "yes" ]; then
-        echo "${TEST_NAME} files deleted from host "
-        if [ "$target_mode" == "yes" ]; then
-            echo "and from target"
-        fi
-    else
-        echo "${TEST_NAME} files left in ${tmp_dir} on host"
-        if [ "$target_mode" == "yes" ]; then
-            echo "and in ${chroot_dex_location} on target"
-        fi
-    fi
+  print(f"{test_dir}: Run...")
+  if target_mode == "yes":
+    # Prepare the on-device test directory
+    if ON_VM:
+      run(f"{SSH_CMD} 'rm -rf {chroot_dex_location} && mkdir -p {chroot_dex_location}'")
+    else:
+      run("adb root")
+      run("adb wait-for-device")
+      run(f"adb shell 'rm -rf {chroot_dex_location} && mkdir -p {chroot_dex_location}'")
+    push_files = [Path(runner.name)]
+    push_files += list(Path(".").glob(f"{TEST_NAME}*.jar"))
+    push_files += list(Path(".").glob(f"expected-*.txt"))
+    push_files += [p for p in [Path("profile"), Path("res")] if p.exists()]
+    push_files = " ".join(map(str, push_files))
+    if ON_VM:
+      run(f"{SCP_CMD} {push_files} {SSH_USER}@{SSH_HOST}:{chroot_dex_location}")
+    else:
+      run("adb push {} {}".format(push_files, chroot_dex_location))
 
-) 2>&${real_stderr} 1>&2
+    if ON_VM:
+      run(f"{SSH_CMD} {CHROOT_CMD} bash {DEX_LOCATION}/run.sh",
+          fail_message=f"Runner {chroot_dex_location}/run.sh failed")
+    else:
+      chroot_prefix = f"chroot {chroot}" if chroot else ""
+      run(f"adb shell {chroot_prefix} sh {DEX_LOCATION}/run.sh",
+          fail_message=f"Runner {chroot_dex_location}/run.sh failed")
 
-if [ "$never_clean" = "yes" ] && [ "$good" = "yes" ]; then
-  exit 0
-else
-  exit 1
-fi
+    # Copy the on-device stdout/stderr to host.
+    pull_files = [test_stdout, test_stderr, "expected-stdout.txt", "expected-stderr.txt"]
+    if ON_VM:
+      srcs = " ".join(f"{SSH_USER}@{SSH_HOST}:{chroot_dex_location}/{f}" for f in pull_files)
+      run(f"{SCP_CMD} {srcs} .")
+    else:
+      run("adb pull {} .".format(" ".join(f"{chroot_dex_location}/{f}" for f in pull_files)))
+  else:
+    run(str(runner), fail_message=f"Runner {str(runner)} failed")
+
+  # NB: There is no exit code or return value.
+  # Failing tests just raise python exception.
+  os.chdir(tmp_dir)
+  if update_mode == "yes":
+    for src, dst in [(test_stdout, os.path.join(test_dir, ctx.expected_stdout.name)),
+                     (test_stderr, os.path.join(test_dir, ctx.expected_stderr.name))]:
+      if "[DO_NOT_UPDATE]" not in open(dst).readline():
+        copyfile(src, dst)
+
+  print("#################### info")
+  run(f'cat "{td_info}" | sed "s/^/# /g"')
+  print("#################### stdout diff")
+  proc_out = run(f'diff --strip-trailing-cr -u '
+                 f'"{ctx.expected_stdout}" "{test_stdout}"', check=False)
+  print("#################### stderr diff")
+  proc_err = run(f'diff --strip-trailing-cr -u '
+                 f'"{ctx.expected_stderr}" "{test_stderr}"', check=False)
+  if strace == "yes":
+    print("#################### strace output (trimmed to 3000 lines)")
+    # Some tests do not run dalvikvm, in which case the trace does not exist.
+    run(f'tail -n 3000 "{tmp_dir}/{strace_output}"', check=False)
+  SANITIZE_HOST = os.environ.get("SANITIZE_HOST")
+  if target_mode == "no" and SANITIZE_HOST == "address":
+    # Run the stack script to symbolize any ASAN aborts on the host for SANITIZE_HOST. The
+    # tools used by the given ABI work for both x86 and x86-64.
+    print("#################### symbolizer (trimmed to 3000 lines)")
+    run(f'''echo "ABI: 'x86_64'" | cat - "{test_stdout}" "{test_stderr}"'''
+        f"""| {ANDROID_BUILD_TOP}/development/scripts/stack | tail -n 3000""")
+  print("####################", flush=True)
+  if proc_out.returncode != 0 or proc_err.returncode != 0:
+    kind = ((["stdout"] if proc_out.returncode != 0 else []) +
+            (["stderr"] if proc_err.returncode != 0 else []))
+    fail("{} did not match the expected file".format(" and ".join(kind)))
+
+  if run_checker == "yes":
+    if target_mode == "yes":
+      if ON_VM:
+        run(f'{SCP_CMD} "{SSH_USER}@${SSH_HOST}:{CHROOT}/{cfg_output_dir}/{cfg_output}"')
+      else:
+        run(f'adb pull "{chroot}/{cfg_output_dir}/{cfg_output}"')
+    run(f'"{checker}" -q {checker_args} "{cfg_output}" "{tmp_dir}"',
+        fail_message="CFG checker failed")
+
+  # Copy the generated CFG to the specified path.
+  if dump_cfg == "true":
+    assert run_optimizing == "true", "The CFG can be dumped only in optimizing mode"
+    if target_mode == "yes":
+      if ON_VM:
+        run(f'{SCP_CMD} "{SSH_USER}@${SSH_HOST}:{CHROOT}/{cfg_output_dir}/{cfg_output} {dump_cfg_output}"')
+      else:
+        run(f"adb pull {chroot}/{cfg_output_dir}/{cfg_output} {dump_cfg_path}")
+    else:
+      run(f"cp {cfg_output_dir}/{cfg_output} {dump_cfg_path}")
+
+  clean_up(passed=True)
+  print(f"{COLOR_GREEN}{test_dir}: PASSED{COLOR_NORMAL}")
diff --git a/test/run-test-build.py b/test/run-test-build.py
deleted file mode 100755
index f8eb283..0000000
--- a/test/run-test-build.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021 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.
-
-"""
-This scripts compiles Java files which are needed to execute run-tests.
-It is intended to be used only from soong genrule.
-"""
-
-import argparse, os, tempfile, shutil, subprocess, glob, textwrap, re, json, concurrent.futures
-
-ZIP = "prebuilts/build-tools/linux-x86/bin/soong_zip"
-BUILDFAILURES = json.loads(open(os.path.join("art", "test", "buildfailures.json"), "rt").read())
-
-def copy_sources(args, tmp, mode, srcdir):
-  """Copy test files from Android tree into the build sandbox and return its path."""
-
-  join = os.path.join
-  test = os.path.basename(srcdir)
-  dstdir = join(tmp, mode, test)
-
-  # Don't build tests that are disabled since they might not compile (e.g. on jvm).
-  def is_buildfailure(kf):
-    return test in kf.get("tests", []) and mode == kf.get("variant") and not kf.get("env_vars")
-  if any(is_buildfailure(kf) for kf in BUILDFAILURES):
-    return None
-
-  # Copy all source files to the temporary directory.
-  shutil.copytree(srcdir, dstdir)
-
-  # Copy the default scripts if the test does not have a custom ones.
-  for name in ["build", "run", "check"]:
-    src, dst = f"art/test/etc/default-{name}", join(dstdir, name)
-    if os.path.exists(dst):
-      shutil.copy2(src, dstdir)  # Copy default script next to the custom script.
-    else:
-      shutil.copy2(src, dst)  # Use just the default script.
-    os.chmod(dst, 0o755)
-
-  return dstdir
-
-def build_test(args, mode, dstdir):
-  """Run the build script for single run-test"""
-
-  join = os.path.join
-  build_top = os.getcwd()
-  java_home = os.environ.get("JAVA_HOME")
-  tools_dir = os.path.abspath(join(os.path.dirname(__file__), "../../../out/bin"))
-  env = {
-    "PATH": os.environ.get("PATH"),
-    "ANDROID_BUILD_TOP": build_top,
-    "ART_TEST_RUN_TEST_BOOTCLASSPATH": join(build_top, args.bootclasspath),
-    "TEST_NAME":   os.path.basename(dstdir),
-    "SOONG_ZIP":   join(build_top, "prebuilts/build-tools/linux-x86/bin/soong_zip"),
-    "ZIPALIGN":    join(build_top, "prebuilts/build-tools/linux-x86/bin/zipalign"),
-    "JAVA":        join(java_home, "bin/java"),
-    "JAVAC":       join(java_home, "bin/javac"),
-    "JAVAC_ARGS":  "-g -Xlint:-options -source 1.8 -target 1.8",
-    "D8":          join(tools_dir, "d8"),
-    "HIDDENAPI":   join(tools_dir, "hiddenapi"),
-    "JASMIN":      join(tools_dir, "jasmin"),
-    "SMALI":       join(tools_dir, "smali"),
-    "NEED_DEX":    {"host": "true", "target": "true", "jvm": "false"}[mode],
-    "USE_DESUGAR": "true",
-  }
-  proc = subprocess.run([join(dstdir, "build"), "--" + mode],
-                        cwd=dstdir,
-                        env=env,
-                        encoding=os.sys.stdout.encoding,
-                        stderr=subprocess.STDOUT,
-                        stdout=subprocess.PIPE)
-  return proc.stdout, proc.returncode
-
-def main():
-  parser = argparse.ArgumentParser(description=__doc__)
-  parser.add_argument("--out", help="Path of the generated ZIP file with the build data")
-  parser.add_argument('--mode', choices=['host', 'jvm', 'target'])
-  parser.add_argument("--shard", help="Identifies subset of tests to build (00..99)")
-  parser.add_argument("--bootclasspath", help="JAR files used for javac compilation")
-  args = parser.parse_args()
-
-  with tempfile.TemporaryDirectory(prefix=os.path.basename(__file__)) as tmp:
-    srcdirs = sorted(glob.glob(os.path.join("art", "test", "*")))
-    srcdirs = filter(lambda srcdir: re.match(".*/\d*{}-.*".format(args.shard), srcdir), srcdirs)
-    dstdirs = [copy_sources(args, tmp, args.mode, srcdir) for srcdir in srcdirs]
-    dstdirs = filter(lambda dstdir: dstdir, dstdirs)  # Remove None (skipped tests).
-    with concurrent.futures.ThreadPoolExecutor() as pool:
-      for stdout, exitcode in pool.map(lambda dstdir: build_test(args, args.mode, dstdir), dstdirs):
-        if stdout:
-          print(stdout.strip())
-        assert(exitcode == 0) # Build failed. Add test to buildfailures.json if this is expected.
-
-    # Create the final zip file which contains the content of the temporary directory.
-    proc = subprocess.run([ZIP, "-o", args.out, "-C", tmp, "-D", tmp], check=True)
-
-if __name__ == "__main__":
-  main()
diff --git a/test/run_test_build.py b/test/run_test_build.py
new file mode 100755
index 0000000..75cd64f
--- /dev/null
+++ b/test/run_test_build.py
@@ -0,0 +1,522 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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.
+
+"""
+This scripts compiles Java files which are needed to execute run-tests.
+It is intended to be used only from soong genrule.
+"""
+
+import argparse
+import functools
+import glob
+import os
+import pathlib
+import shlex
+import shutil
+import subprocess
+import sys
+import zipfile
+
+from argparse import ArgumentParser
+from fcntl import lockf, LOCK_EX, LOCK_NB
+from importlib.machinery import SourceFileLoader
+from concurrent.futures import ThreadPoolExecutor
+from os import environ, getcwd, chdir, cpu_count, chmod
+from os.path import relpath
+from pathlib import Path
+from pprint import pprint
+from re import match
+from shutil import copytree, rmtree
+from subprocess import run
+from tempfile import TemporaryDirectory, NamedTemporaryFile
+from typing import Dict, List, Union, Set, Optional
+
+USE_RBE = 100  # Percentage of tests that can use RBE (between 0 and 100)
+
+lock_file = None  # Keep alive as long as this process is alive.
+
+RBE_D8_DISABLED_FOR = {
+  "952-invoke-custom",        # b/228312861: RBE uses wrong inputs.
+  "979-const-method-handle",  # b/228312861: RBE uses wrong inputs.
+}
+
+class BuildTestContext:
+  def __init__(self, args, android_build_top, test_dir):
+    self.android_build_top = android_build_top.absolute()
+    self.bootclasspath = args.bootclasspath.absolute()
+    self.test_name = test_dir.name
+    self.test_dir = test_dir.absolute()
+    self.mode = args.mode
+    self.jvm = (self.mode == "jvm")
+    self.host = (self.mode == "host")
+    self.target = (self.mode == "target")
+    assert self.jvm or self.host or self.target
+
+    self.java_home = Path(os.environ.get("JAVA_HOME")).absolute()
+    self.java_path = self.java_home / "bin/java"
+    self.javac_path = self.java_home / "bin/javac"
+    self.javac_args = "-g -Xlint:-options -source 1.8 -target 1.8"
+
+    # Helper functions to execute tools.
+    self.d8 = functools.partial(self.run, args.d8.absolute())
+    self.jasmin = functools.partial(self.run, args.jasmin.absolute())
+    self.javac = functools.partial(self.run, self.javac_path)
+    self.smali = functools.partial(self.run, args.smali.absolute())
+    self.soong_zip = functools.partial(self.run, args.soong_zip.absolute())
+    self.zipalign = functools.partial(self.run, args.zipalign.absolute())
+    if args.hiddenapi:
+      self.hiddenapi = functools.partial(self.run, args.hiddenapi.absolute())
+
+    # RBE wrapper for some of the tools.
+    if "RBE_server_address" in os.environ and USE_RBE > (hash(self.test_name) % 100):
+      self.rbe_exec_root = os.environ.get("RBE_exec_root")
+      self.rbe_rewrapper = self.android_build_top / "prebuilts/remoteexecution-client/live/rewrapper"
+      if self.test_name not in RBE_D8_DISABLED_FOR:
+        self.d8 = functools.partial(self.rbe_d8, args.d8.absolute())
+      self.javac = functools.partial(self.rbe_javac, self.javac_path)
+      self.smali = functools.partial(self.rbe_smali, args.smali.absolute())
+
+    # Minimal environment needed for bash commands that we execute.
+    self.bash_env = {
+      "ANDROID_BUILD_TOP": self.android_build_top,
+      "D8": args.d8.absolute(),
+      "JAVA": self.java_path,
+      "JAVAC": self.javac_path,
+      "JAVAC_ARGS": self.javac_args,
+      "JAVA_HOME": self.java_home,
+      "PATH": os.environ["PATH"],
+      "PYTHONDONTWRITEBYTECODE": "1",
+      "SMALI": args.smali.absolute(),
+      "SOONG_ZIP": args.soong_zip.absolute(),
+      "TEST_NAME": self.test_name,
+    }
+
+  def bash(self, cmd):
+    return subprocess.run(cmd,
+                          shell=True,
+                          cwd=self.test_dir,
+                          env=self.bash_env,
+                          check=True)
+
+  def run(self, executable: pathlib.Path, args: List[Union[pathlib.Path, str]]):
+    assert isinstance(executable, pathlib.Path), executable
+    cmd: List[Union[pathlib.Path, str]] = []
+    if executable.suffix == ".sh":
+      cmd += ["/bin/bash"]
+    cmd += [executable]
+    cmd += args
+    env = self.bash_env
+    env.update({k: v for k, v in os.environ.items() if k.startswith("RBE_")})
+    # Make paths relative as otherwise we could create too long command line.
+    for i, arg in enumerate(cmd):
+      if isinstance(arg, pathlib.Path):
+        assert arg.absolute(), arg
+        cmd[i] = relpath(arg, self.test_dir)
+      elif isinstance(arg, list):
+        assert all(p.absolute() for p in arg), arg
+        cmd[i] = ":".join(relpath(p, self.test_dir) for p in arg)
+      else:
+        assert isinstance(arg, str), arg
+    p = subprocess.run(cmd,
+                       encoding=sys.stdout.encoding,
+                       cwd=self.test_dir,
+                       env=self.bash_env,
+                       stderr=subprocess.STDOUT,
+                       stdout=subprocess.PIPE)
+    if p.returncode != 0:
+      raise Exception("Command failed with exit code {}\n$ {}\n{}".format(
+                      p.returncode, " ".join(map(str, cmd)), p.stdout))
+    return p
+
+  def rbe_wrap(self, args, inputs: Set[pathlib.Path]=None):
+    with NamedTemporaryFile(mode="w+t") as input_list:
+      inputs = inputs or set()
+      for i, arg in enumerate(args):
+        if isinstance(arg, pathlib.Path):
+          assert arg.absolute(), arg
+          inputs.add(arg)
+        elif isinstance(arg, list):
+          assert all(p.absolute() for p in arg), arg
+          inputs.update(arg)
+      input_list.writelines([relpath(i, self.rbe_exec_root)+"\n" for i in inputs])
+      input_list.flush()
+      return self.run(self.rbe_rewrapper, [
+        "--platform=" + os.environ["RBE_platform"],
+        "--input_list_paths=" + input_list.name,
+      ] + args)
+
+  def rbe_javac(self, javac_path:Path, args):
+    output = relpath(Path(args[args.index("-d") + 1]), self.rbe_exec_root)
+    return self.rbe_wrap(["--output_directories", output, javac_path] + args)
+
+  def rbe_d8(self, d8_path:Path, args):
+    inputs = set([d8_path.parent.parent / "framework/d8.jar"])
+    output = relpath(Path(args[args.index("--output") + 1]), self.rbe_exec_root)
+    return self.rbe_wrap([
+      "--output_files" if output.endswith(".jar") else "--output_directories", output,
+      "--toolchain_inputs=prebuilts/jdk/jdk17/linux-x86/bin/java",
+      d8_path] + args, inputs)
+
+  def rbe_smali(self, smali_path:Path, args):
+    inputs = set([smali_path.parent.parent / "framework/smali.jar"])
+    output = relpath(Path(args[args.index("--output") + 1]), self.rbe_exec_root)
+    return self.rbe_wrap([
+      "--output_files", output,
+      "--toolchain_inputs=prebuilts/jdk/jdk17/linux-x86/bin/java",
+      smali_path] + args, inputs)
+
+  def build(self) -> None:
+    script = self.test_dir / "build.py"
+    if script.exists():
+      module = SourceFileLoader("build_" + self.test_name,
+                                str(script)).load_module()
+      module.build(self)
+    else:
+      self.default_build()
+
+  def default_build(
+      self,
+      use_desugar=True,
+      use_hiddenapi=True,
+      need_dex=None,
+      zip_compression_method="deflate",
+      zip_align_bytes=None,
+      api_level:Union[int, str]=26,  # Can also be named alias (string).
+      javac_args=[],
+      javac_classpath: List[Path]=[],
+      d8_flags=[],
+      smali_args=[],
+      use_smali=True,
+      use_jasmin=True,
+    ):
+    javac_classpath = javac_classpath.copy()  # Do not modify default value.
+
+    # Wrap "pathlib.Path" with our own version that ensures all paths are absolute.
+    # Plain filenames are assumed to be relative to self.test_dir and made absolute.
+    class Path(pathlib.Path):
+      def __new__(cls, filename: str):
+        path = pathlib.Path(filename)
+        return path if path.is_absolute() else (self.test_dir / path)
+
+    need_dex = (self.host or self.target) if need_dex is None else need_dex
+
+    if self.jvm:
+      # No desugaring on jvm because it supports the latest functionality.
+      use_desugar = False
+
+    # Set API level for smali and d8.
+    if isinstance(api_level, str):
+      API_LEVEL = {
+        "default-methods": 24,
+        "parameter-annotations": 25,
+        "agents": 26,
+        "method-handles": 26,
+        "var-handles": 28,
+      }
+      api_level = API_LEVEL[api_level]
+    assert isinstance(api_level, int), api_level
+
+    def zip(zip_target: Path, *files: Path):
+      zip_args = ["-o", zip_target, "-C", zip_target.parent]
+      if zip_compression_method == "store":
+        zip_args.extend(["-L", "0"])
+      for f in files:
+        zip_args.extend(["-f", f])
+      self.soong_zip(zip_args)
+
+      if zip_align_bytes:
+        # zipalign does not operate in-place, so write results to a temp file.
+        with TemporaryDirectory() as tmp_dir:
+          tmp_file = Path(tmp_dir) / "aligned.zip"
+          self.zipalign(["-f", str(zip_align_bytes), zip_target, tmp_file])
+          # replace original zip target with our temp file.
+          tmp_file.rename(zip_target)
+
+
+    def make_jasmin(dst_dir: Path, src_dir: Path) -> Optional[Path]:
+      if not use_jasmin or not src_dir.exists():
+        return None  # No sources to compile.
+      dst_dir.mkdir()
+      self.jasmin(["-d", dst_dir] + sorted(src_dir.glob("**/*.j")))
+      return dst_dir
+
+    def make_smali(dst_dex: Path, src_dir: Path) -> Optional[Path]:
+      if not use_smali or not src_dir.exists():
+        return None  # No sources to compile.
+      self.smali(["-JXmx512m", "assemble"] + smali_args + ["--api", str(api_level)] +
+                 ["--output", dst_dex] + sorted(src_dir.glob("**/*.smali")))
+      return dst_dex
+
+    def make_java(dst_dir: Path, *src_dirs: Path) -> Optional[Path]:
+      if not any(src_dir.exists() for src_dir in src_dirs):
+        return None  # No sources to compile.
+      dst_dir.mkdir(exist_ok=True)
+      args = self.javac_args.split(" ") + javac_args
+      args += ["-implicit:none", "-encoding", "utf8", "-d", dst_dir]
+      if not self.jvm:
+        args += ["-bootclasspath", self.bootclasspath]
+      if javac_classpath:
+        args += ["-classpath", javac_classpath]
+      for src_dir in src_dirs:
+        args += sorted(src_dir.glob("**/*.java"))
+      self.javac(args)
+      javac_post = Path("javac_post.sh")
+      if javac_post.exists():
+        self.run(javac_post, [dst_dir])
+      return dst_dir
+
+
+    # Make a "dex" file given a directory of classes. This will be
+    # packaged in a jar file.
+    def make_dex(src_dir: Path):
+      dst_jar = Path(src_dir.name + ".jar")
+      args = d8_flags + ["--min-api", str(api_level), "--output", dst_jar]
+      args += ["--lib", self.bootclasspath] if use_desugar else ["--no-desugaring"]
+      args += sorted(src_dir.glob("**/*.class"))
+      self.d8(args)
+
+      # D8 outputs to JAR files today rather than DEX files as DX used
+      # to. To compensate, we extract the DEX from d8's output to meet the
+      # expectations of make_dex callers.
+      dst_dex = Path(src_dir.name + ".dex")
+      with TemporaryDirectory() as tmp_dir:
+        zipfile.ZipFile(dst_jar, "r").extractall(tmp_dir)
+        (Path(tmp_dir) / "classes.dex").rename(dst_dex)
+
+    # Merge all the dex files.
+    # Skip non-existing files, but at least 1 file must exist.
+    def make_dexmerge(dst_dex: Path, *src_dexs: Path):
+      # Include destination. Skip any non-existing files.
+      srcs = [f for f in [dst_dex] + list(src_dexs) if f.exists()]
+
+      # NB: We merge even if there is just single input.
+      # It is useful to normalize non-deterministic smali output.
+      tmp_dir = self.test_dir / "dexmerge"
+      tmp_dir.mkdir()
+      self.d8(["--min-api", str(api_level), "--output", tmp_dir] + srcs)
+      assert not (tmp_dir / "classes2.dex").exists()
+      for src_file in srcs:
+        src_file.unlink()
+      (tmp_dir / "classes.dex").rename(dst_dex)
+      tmp_dir.rmdir()
+
+
+    def make_hiddenapi(*dex_files: Path):
+      if not use_hiddenapi or not Path("hiddenapi-flags.csv").exists():
+        return  # Nothing to do.
+      args: List[Union[str, Path]] = ["encode"]
+      for dex_file in dex_files:
+        args.extend(["--input-dex=" + str(dex_file), "--output-dex=" + str(dex_file)])
+      args.append("--api-flags=hiddenapi-flags.csv")
+      args.append("--no-force-assign-all")
+      self.hiddenapi(args)
+
+
+    if Path("classes.dex").exists():
+      zip(Path(self.test_name + ".jar"), Path("classes.dex"))
+      return
+
+    if Path("classes.dm").exists():
+      zip(Path(self.test_name + ".jar"), Path("classes.dm"))
+      return
+
+    if make_jasmin(Path("jasmin_classes"), Path("jasmin")):
+      javac_classpath.append(Path("jasmin_classes"))
+
+    if make_jasmin(Path("jasmin_classes2"), Path("jasmin-multidex")):
+      javac_classpath.append(Path("jasmin_classes2"))
+
+    # To allow circular references, compile src/, src-multidex/, src-aotex/,
+    # src-bcpex/, src-ex/ together and pass the output as class path argument.
+    # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols
+    # used by the other src-* sources we compile here but everything needed to
+    # compile the other src-* sources should be present in src/ (and jasmin*/).
+    extra_srcs = ["src-multidex", "src-aotex", "src-bcpex", "src-ex"]
+    replacement_srcs = ["src2", "src-ex2"] + ([] if self.jvm else ["src-art"])
+    if (Path("src").exists() and
+        any(Path(p).exists() for p in extra_srcs + replacement_srcs)):
+      make_java(Path("classes-tmp-all"), Path("src"), *map(Path, extra_srcs))
+      javac_classpath.append(Path("classes-tmp-all"))
+
+    if make_java(Path("classes-aotex"), Path("src-aotex")) and need_dex:
+      make_dex(Path("classes-aotex"))
+      # rename it so it shows up as "classes.dex" in the zip file.
+      Path("classes-aotex.dex").rename(Path("classes.dex"))
+      zip(Path(self.test_name + "-aotex.jar"), Path("classes.dex"))
+
+    if make_java(Path("classes-bcpex"), Path("src-bcpex")) and need_dex:
+      make_dex(Path("classes-bcpex"))
+      # rename it so it shows up as "classes.dex" in the zip file.
+      Path("classes-bcpex.dex").rename(Path("classes.dex"))
+      zip(Path(self.test_name + "-bcpex.jar"), Path("classes.dex"))
+
+    make_java(Path("classes"), Path("src"))
+
+    if not self.jvm:
+      # Do not attempt to build src-art directories on jvm,
+      # since it would fail without libcore.
+      make_java(Path("classes"), Path("src-art"))
+
+    if make_java(Path("classes2"), Path("src-multidex")) and need_dex:
+      make_dex(Path("classes2"))
+
+    make_java(Path("classes"), Path("src2"))
+
+    # If the classes directory is not-empty, package classes in a DEX file.
+    # NB: some tests provide classes rather than java files.
+    if any(Path("classes").glob("*")) and need_dex:
+      make_dex(Path("classes"))
+
+    if Path("jasmin_classes").exists():
+      # Compile Jasmin classes as if they were part of the classes.dex file.
+      if need_dex:
+        make_dex(Path("jasmin_classes"))
+        make_dexmerge(Path("classes.dex"), Path("jasmin_classes.dex"))
+      else:
+        # Move jasmin classes into classes directory so that they are picked up
+        # with -cp classes.
+        Path("classes").mkdir(exist_ok=True)
+        copytree(Path("jasmin_classes"), Path("classes"), dirs_exist_ok=True)
+
+    if need_dex and make_smali(Path("smali_classes.dex"), Path("smali")):
+      # Merge smali files into classes.dex,
+      # this takes priority over any jasmin files.
+      make_dexmerge(Path("classes.dex"), Path("smali_classes.dex"))
+
+    # Compile Jasmin classes in jasmin-multidex as if they were part of
+    # the classes2.jar
+    if Path("jasmin-multidex").exists():
+      if need_dex:
+        make_dex(Path("jasmin_classes2"))
+        make_dexmerge(Path("classes2.dex"), Path("jasmin_classes2.dex"))
+      else:
+        # Move jasmin classes into classes2 directory so that
+        # they are picked up with -cp classes2.
+        Path("classes2").mkdir()
+        copytree(Path("jasmin_classes2"), Path("classes2"), dirs_exist_ok=True)
+        rmtree(Path("jasmin_classes2"))
+
+    if need_dex and make_smali(Path("smali_classes2.dex"), Path("smali-multidex")):
+      # Merge smali_classes2.dex into classes2.dex
+      make_dexmerge(Path("classes2.dex"), Path("smali_classes2.dex"))
+
+    make_java(Path("classes-ex"), Path("src-ex"))
+
+    make_java(Path("classes-ex"), Path("src-ex2"))
+
+    if Path("classes-ex").exists() and need_dex:
+      make_dex(Path("classes-ex"))
+
+    if need_dex and make_smali(Path("smali_classes-ex.dex"), Path("smali-ex")):
+      # Merge smali files into classes-ex.dex.
+      make_dexmerge(Path("classes-ex.dex"), Path("smali_classes-ex.dex"))
+
+    if Path("classes-ex.dex").exists():
+      # Apply hiddenapi on the dex files if the test has API list file(s).
+      make_hiddenapi(Path("classes-ex.dex"))
+
+      # quick shuffle so that the stored name is "classes.dex"
+      Path("classes.dex").rename(Path("classes-1.dex"))
+      Path("classes-ex.dex").rename(Path("classes.dex"))
+      zip(Path(self.test_name + "-ex.jar"), Path("classes.dex"))
+      Path("classes.dex").rename(Path("classes-ex.dex"))
+      Path("classes-1.dex").rename(Path("classes.dex"))
+
+    # Apply hiddenapi on the dex files if the test has API list file(s).
+    if need_dex:
+      if any(Path(".").glob("*-multidex")):
+        make_hiddenapi(Path("classes.dex"), Path("classes2.dex"))
+      else:
+        make_hiddenapi(Path("classes.dex"))
+
+    # Create a single dex jar with two dex files for multidex.
+    if need_dex:
+      if Path("classes2.dex").exists():
+        zip(Path(self.test_name + ".jar"), Path("classes.dex"), Path("classes2.dex"))
+      else:
+        zip(Path(self.test_name + ".jar"), Path("classes.dex"))
+
+
+# If we build just individual shard, we want to split the work among all the cores,
+# but if the build system builds all shards, we don't want to overload the machine.
+# We don't know which situation we are in, so as simple work-around, we use a lock
+# file to allow only one shard to use multiprocessing at the same time.
+def use_multiprocessing(mode: str) -> bool:
+  if "RBE_server_address" in os.environ:
+    return True
+  global lock_file
+  lock_path = Path(environ["TMPDIR"]) / ("art-test-run-test-build-py-" + mode)
+  lock_file = open(lock_path, "w")
+  try:
+    lockf(lock_file, LOCK_EX | LOCK_NB)
+    return True  # We are the only instance of this script in the build system.
+  except BlockingIOError:
+    return False  # Some other instance is already running.
+
+
+def main() -> None:
+  parser = ArgumentParser(description=__doc__)
+  parser.add_argument("--out", type=Path, help="Final zip file")
+  parser.add_argument("--mode", choices=["host", "jvm", "target"])
+  parser.add_argument("--bootclasspath", type=Path)
+  parser.add_argument("--d8", type=Path)
+  parser.add_argument("--hiddenapi", type=Path)
+  parser.add_argument("--jasmin", type=Path)
+  parser.add_argument("--smali", type=Path)
+  parser.add_argument("--soong_zip", type=Path)
+  parser.add_argument("--zipalign", type=Path)
+  parser.add_argument("srcs", nargs="+", type=Path)
+  args = parser.parse_args()
+
+  android_build_top = Path(getcwd()).absolute()
+  ziproot = args.out.absolute().parent / "zip"
+  srcdirs = set(s.parents[-4].absolute() for s in args.srcs)
+
+  # Special hidden-api shard: If the --hiddenapi flag is provided, build only
+  # hiddenapi tests. Otherwise exclude all hiddenapi tests from normal shards.
+  def filter_by_hiddenapi(srcdir: Path) -> bool:
+    return (args.hiddenapi != None) == ("hiddenapi" in srcdir.name)
+
+  # Initialize the test objects.
+  # We need to do this before we change the working directory below.
+  tests: List[BuildTestContext] = []
+  for srcdir in filter(filter_by_hiddenapi, srcdirs):
+    dstdir = ziproot / args.mode / srcdir.name
+    copytree(srcdir, dstdir)
+    tests.append(BuildTestContext(args, android_build_top, dstdir))
+
+  # We can not change the working directory per each thread since they all run in parallel.
+  # Create invalid read-only directory to catch accidental use of current working directory.
+  with TemporaryDirectory("-do-not-use-cwd") as invalid_tmpdir:
+    os.chdir(invalid_tmpdir)
+    os.chmod(invalid_tmpdir, 0)
+    with ThreadPoolExecutor(cpu_count() if use_multiprocessing(args.mode) else 1) as pool:
+      jobs = {}
+      for ctx in tests:
+        jobs[ctx.test_name] = pool.submit(ctx.build)
+      for test_name, job in jobs.items():
+        try:
+          job.result()
+        except Exception as e:
+          raise Exception("Failed to build " + test_name) from e
+
+  # Create the final zip file which contains the content of the temporary directory.
+  proc = run([android_build_top / args.soong_zip, "-o", android_build_top / args.out,
+              "-C", ziproot, "-D", ziproot], check=True)
+
+
+if __name__ == "__main__":
+  main()
diff --git a/test/testrunner/device_config.py b/test/testrunner/device_config.py
index 1fad7d2..c56a05d 100644
--- a/test/testrunner/device_config.py
+++ b/test/testrunner/device_config.py
@@ -11,10 +11,11 @@
 #
 ##########################################
     # Fugu's don't have enough memory to support a 128m heap with normal concurrency.
+    # Also update timeout value as some tests can go beyond the default 600s.
     'aosp_fugu' : {
-        'run-test-args': [ "--runtime-option", "-Xmx128m" ],
+        'run-test-args': [ "--runtime-option", "-Xmx128m", "--timeout", "900" ],
     },
     'fugu' : {
-        'run-test-args': [ "--runtime-option", "-Xmx128m" ],
+        'run-test-args': [ "--runtime-option", "-Xmx128m", "--timeout", "900" ],
     },
 }
diff --git a/test/testrunner/env.py b/test/testrunner/env.py
index 319e1a7..44d0db6 100644
--- a/test/testrunner/env.py
+++ b/test/testrunner/env.py
@@ -132,8 +132,8 @@
 HOST_OUT_EXECUTABLES = os.path.join(ANDROID_BUILD_TOP,
                                     _get_build_var("HOST_OUT_EXECUTABLES"))
 
-# Set up default values for $DX, $SMALI, etc to the $HOST_OUT_EXECUTABLES/$name path.
-for tool in ['dx', 'smali', 'jasmin', 'd8']:
+# Set up default values for $D8, $SMALI, etc to the $HOST_OUT_EXECUTABLES/$name path.
+for tool in ['smali', 'jasmin', 'd8']:
   os.environ.setdefault(tool.upper(), HOST_OUT_EXECUTABLES + '/' + tool)
 
 ANDROID_JAVA_TOOLCHAIN = os.path.join(ANDROID_BUILD_TOP,
@@ -146,3 +146,6 @@
 SOONG_OUT_DIR = _get_build_var('SOONG_OUT_DIR')
 
 ART_TEST_RUN_ON_ARM_FVP = _getEnvBoolean('ART_TEST_RUN_ON_ARM_FVP', False)
+
+ART_TEST_ON_VM = _env.get('ART_TEST_ON_VM')
+ART_SSH_CMD = _env.get('ART_SSH_CMD')
diff --git a/test/testrunner/run_build_test_target.py b/test/testrunner/run_build_test_target.py
index 4191771..d271b80 100755
--- a/test/testrunner/run_build_test_target.py
+++ b/test/testrunner/run_build_test_target.py
@@ -29,12 +29,18 @@
 import argparse
 import os
 import pathlib
+import re
 import subprocess
 import sys
 
 from target_config import target_config
 import env
 
+# Check that we are using reasonably recent version of python
+print("Using", sys.executable, sys.version, flush=True)
+version = tuple(map(int, re.match(r"(\d*)\.(\d*)", sys.version).groups()))
+assert (version >= (3, 9)), "Python version is too old"
+
 parser = argparse.ArgumentParser()
 parser.add_argument('-j', default='1', dest='n_threads')
 # either -l/--list OR build-target is required (but not both).
@@ -60,6 +66,7 @@
 n_threads = options.n_threads
 custom_env = target.get('env', {})
 custom_env['SOONG_ALLOW_MISSING_DEPENDENCIES'] = 'true'
+custom_env['BUILD_BROKEN_DISABLE_BAZEL'] = 'true'
 # Switch the build system to unbundled mode in the reduced manifest branch.
 if not os.path.isdir(env.ANDROID_BUILD_TOP + '/frameworks/base'):
   custom_env['TARGET_BUILD_UNBUNDLED'] = 'true'
@@ -81,7 +88,7 @@
 if 'build' in target:
   build_command = target.get('build').format(
       ANDROID_BUILD_TOP = env.ANDROID_BUILD_TOP,
-      MAKE_OPTIONS='DX=  -j{threads}'.format(threads = n_threads))
+      MAKE_OPTIONS='D8= -j{threads}'.format(threads = n_threads))
   sys.stdout.write(str(build_command) + '\n')
   sys.stdout.flush()
   if subprocess.call(build_command.split()):
@@ -90,7 +97,7 @@
 # make runs soong/kati to build the target listed in the entry.
 if 'make' in target:
   build_command = 'build/soong/soong_ui.bash --make-mode'
-  build_command += ' DX='
+  build_command += ' D8='
   build_command += ' -j' + str(n_threads)
   build_command += ' ' + target.get('make')
   if env.DIST_DIR:
@@ -119,7 +126,8 @@
     sys.exit(1)
 
 if 'run-test' in target:
-  run_test_command = [os.path.join(env.ANDROID_BUILD_TOP,
+  run_test_command = [sys.executable, # Use the same python as we are using now.
+                      os.path.join(env.ANDROID_BUILD_TOP,
                                    'art/test/testrunner/testrunner.py')]
   test_flags = target.get('run-test', [])
   out_dir = pathlib.PurePath(env.SOONG_OUT_DIR)
diff --git a/test/testrunner/target_config.py b/test/testrunner/target_config.py
index 907f4ec..eaf33b7 100644
--- a/test/testrunner/target_config.py
+++ b/test/testrunner/target_config.py
@@ -138,7 +138,8 @@
         }
     },
     'art-tracing' : {
-        'run-test' : ['--trace']
+        'run-test' : ['--trace',
+                      '--stream']
     },
     'art-interpreter-tracing' : {
         'run-test' : ['--interpreter',
@@ -246,6 +247,20 @@
             'ASAN_OPTIONS' : 'detect_leaks=0'
         }
     },
+    'art-gtest-asan32': {
+        'make' : 'test-art-host-gtest32',
+        'env': {
+            'SANITIZE_HOST' : 'address',
+            'ASAN_OPTIONS' : 'detect_leaks=0'
+        }
+    },
+    'art-gtest-asan64': {
+        'make' : 'test-art-host-gtest64',
+        'env': {
+            'SANITIZE_HOST' : 'address',
+            'ASAN_OPTIONS' : 'detect_leaks=0'
+        }
+    },
     'art-asan': {
         'run-test' : ['--interpreter',
                       '--interp-ac',
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 935ce0c..4499ffd 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -354,8 +354,13 @@
 
 def get_device_name():
   """
-  Gets the value of ro.product.name from remote device.
+  Gets the value of ro.product.name from remote device (unless running on a VM).
   """
+  if env.ART_TEST_ON_VM:
+    return subprocess.Popen(f"{env.ART_SSH_CMD} uname -a".split(),
+                            stdout = subprocess.PIPE,
+                            universal_newlines=True).stdout.read().strip()
+
   proc = subprocess.Popen(['adb', 'shell', 'getprop', 'ro.product.name'],
                           stderr=subprocess.STDOUT,
                           stdout = subprocess.PIPE,
@@ -581,7 +586,9 @@
       temp_path = tempfile.mkdtemp(dir=env.ART_HOST_TEST_DIR)
       options_test = '--temp-path {} '.format(temp_path) + options_test
 
-      run_test_sh = env.ANDROID_BUILD_TOP + '/art/test/run-test'
+      # Run the run-test script using the prebuilt python.
+      python3_bin = env.ANDROID_BUILD_TOP + "/prebuilts/build-tools/path/linux-x86/python3"
+      run_test_sh = python3_bin + ' ' + env.ANDROID_BUILD_TOP + '/art/test/run-test'
       command = ' '.join((run_test_sh, options_test, ' '.join(extra_arguments[target]), test))
       return executor.submit(run_test, command, test, variant_set, test_name)
 
@@ -665,9 +672,12 @@
       test_start_time = time.monotonic()
       if verbose:
         print_text("Starting %s at %s\n" % (test_name, test_start_time))
+      env = dict(os.environ)
+      env["FULL_TEST_NAME"] = test_name
       if gdb or gdb_dex2oat:
         proc = _popen(
           args=command.split(),
+          env=env,
           stderr=subprocess.STDOUT,
           universal_newlines=True,
           start_new_session=True
@@ -675,6 +685,7 @@
       else:
         proc = _popen(
           args=command.split(),
+          env=env,
           stderr=subprocess.STDOUT,
           stdout = subprocess.PIPE,
           universal_newlines=True,
@@ -704,7 +715,7 @@
     failed_tests.append((test_name, 'Timed out in %d seconds' % timeout))
 
     # HACK(b/142039427): Print extra backtraces on timeout.
-    if "-target-" in test_name:
+    if "-target-" in test_name and not env.ART_TEST_ON_VM:
       for i in range(8):
         proc_name = "dalvikvm" + test_name[-2:]
         pidof = subprocess.run(["adb", "shell", "pidof", proc_name], stdout=subprocess.PIPE)
@@ -1064,8 +1075,11 @@
 
 
 def get_target_cpu_count():
-  adb_command = 'adb shell cat /sys/devices/system/cpu/present'
-  cpu_info_proc = subprocess.Popen(adb_command.split(), stdout=subprocess.PIPE)
+  if env.ART_TEST_ON_VM:
+    command = f"{env.ART_SSH_CMD} cat /sys/devices/system/cpu/present"
+  else:
+    command = 'adb shell cat /sys/devices/system/cpu/present'
+  cpu_info_proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
   cpu_info = cpu_info_proc.stdout.read()
   if type(cpu_info) is bytes:
     cpu_info = cpu_info.decode('utf-8')
@@ -1104,7 +1118,9 @@
   global csv_result
 
   parser = argparse.ArgumentParser(description="Runs all or a subset of the ART test suite.")
-  parser.add_argument('-t', '--test', action='append', dest='tests', help='name(s) of the test(s)')
+  parser.add_argument('tests', action='extend', nargs="*", help='name(s) of the test(s)')
+  parser.add_argument('-t', '--test', action='append', dest='tests', help='name(s) of the test(s)'
+      ' (deprecated: use positional arguments at the end without any option instead)')
   global_group = parser.add_argument_group('Global options',
                                            'Options that affect all tests being run')
   global_group.add_argument('-j', type=int, dest='n_thread', help="""Number of CPUs to use.
@@ -1225,26 +1241,31 @@
   if options['run_all']:
     run_all_configs = True
 
-  return tests
+  return tests or RUN_TEST_SET
 
 def main():
   gather_test_info()
-  user_requested_tests = parse_option()
+  tests = parse_option()
   setup_test_env()
   gather_disabled_test_info()
   if build:
-    build_targets = ''
-    if 'host' in _user_input_variants['target']:
-      build_targets += 'test-art-host-run-test-dependencies '
-    if 'target' in _user_input_variants['target']:
-      build_targets += 'test-art-target-run-test-dependencies '
-    if 'jvm' in _user_input_variants['target']:
-      build_targets += 'test-art-host-run-test-dependencies '
+    build_targets = []
+    # Build only the needed shards (depending on the selected tests).
+    shards = set(re.search("(\d\d)-", t).group(1) for t in tests)
+    if any("hiddenapi" in t for t in tests):
+      shards.add("HiddenApi")  # Include special HiddenApi shard.
+    for mode in ['host', 'target', 'jvm']:
+      if mode in _user_input_variants['target']:
+        build_targets += ['test-art-{}-run-test-dependencies'.format(mode)]
+        if len(shards) >= 100:
+          build_targets += ["art-run-test-{}-data".format(mode)]  # Build all.
+        else:
+          build_targets += ["art-run-test-{}-data-shard{}".format(mode, s) for s in shards]
     build_command = env.ANDROID_BUILD_TOP + '/build/soong/soong_ui.bash --make-mode'
-    build_command += ' DX='
+    build_command += ' D8='
     if dist:
       build_command += ' dist'
-    build_command += ' ' + build_targets
+    build_command += ' ' + ' '.join(build_targets)
     print_text('Build command: %s\n' % build_command)
     if subprocess.call(build_command.split()):
       # Debugging for b/62653020
@@ -1252,10 +1273,7 @@
         shutil.copyfile(env.SOONG_OUT_DIR + '/build.ninja', env.DIST_DIR + '/soong.ninja')
       sys.exit(1)
 
-  if user_requested_tests:
-    run_tests(user_requested_tests)
-  else:
-    run_tests(RUN_TEST_SET)
+  run_tests(tests)
 
   print_analysis()
   close_csv_file()
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index cc83ad3..ff8b3a8 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -29,6 +29,7 @@
 #include "909-attach-agent/attach.h"
 #include "936-search-onload/search_onload.h"
 #include "1919-vminit-thread-start-timing/vminit.h"
+#include "993-breakpoints-non-debuggable/onload.h"
 
 namespace art {
 
@@ -82,6 +83,7 @@
   { "939-hello-transformation-bcp", common_redefine::OnLoad, nullptr },
   { "941-recursive-obsolete-jit", common_redefine::OnLoad, nullptr },
   { "943-private-recursive-jit", common_redefine::OnLoad, nullptr },
+  { "993-non-debuggable", nullptr, Test993BreakpointsNonDebuggable::OnLoad },
   { "1919-vminit-thread-start-timing", Test1919VMInitThreadStart::OnLoad, nullptr },
   { "2031-zygote-compiled-frame-deopt", nullptr, MinimalOnLoad },
   { "2039-load-transform-larger", common_retransform::OnLoad, nullptr },
diff --git a/test/ti-agent/jni_helper.h b/test/ti-agent/jni_helper.h
index 0cbc634..f99e627 100644
--- a/test/ti-agent/jni_helper.h
+++ b/test/ti-agent/jni_helper.h
@@ -61,7 +61,7 @@
 
   ScopedLocalRef<jclass> exc_class(env, env->FindClass("java/lang/NullPointerException"));
   if (exc_class.get() == nullptr) {
-    return -1;
+    return false;
   }
 
   return env->ThrowNew(exc_class.get(), msg) == JNI_OK;
diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc
index 0baa9fe..706531e 100644
--- a/test/ti-agent/redefinition_helper.cc
+++ b/test/ti-agent/redefinition_helper.cc
@@ -392,6 +392,7 @@
 static void DoClassRetransformation(jvmtiEnv* jvmti_env, JNIEnv* env, jobjectArray targets) {
   std::vector<jclass> classes;
   jint len = env->GetArrayLength(targets);
+  classes.reserve(len);
   for (jint i = 0; i < len; i++) {
     classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i)));
   }
diff --git a/test/ti-agent/scoped_utf_chars.h b/test/ti-agent/scoped_utf_chars.h
index 422caaf..ddf1bd5 100644
--- a/test/ti-agent/scoped_utf_chars.h
+++ b/test/ti-agent/scoped_utf_chars.h
@@ -38,7 +38,7 @@
     }
   }
 
-  ScopedUtfChars(ScopedUtfChars&& rhs) :
+  ScopedUtfChars(ScopedUtfChars&& rhs) noexcept :
       env_(rhs.env_), string_(rhs.string_), utf_chars_(rhs.utf_chars_) {
     rhs.env_ = nullptr;
     rhs.string_ = nullptr;
@@ -51,7 +51,7 @@
     }
   }
 
-  ScopedUtfChars& operator=(ScopedUtfChars&& rhs) {
+  ScopedUtfChars& operator=(ScopedUtfChars&& rhs) noexcept {
     if (this != &rhs) {
       // Delete the currently owned UTF chars.
       this->~ScopedUtfChars();
diff --git a/test/ti-agent/suspension_helper.cc b/test/ti-agent/suspension_helper.cc
index b685cb2..2b1ab67 100644
--- a/test/ti-agent/suspension_helper.cc
+++ b/test/ti-agent/suspension_helper.cc
@@ -37,6 +37,7 @@
 static std::vector<jthread> CopyToVector(JNIEnv* env, jobjectArray thrs) {
   jsize len = env->GetArrayLength(thrs);
   std::vector<jthread> ret;
+  ret.reserve(len);
   for (jsize i = 0; i < len; i++) {
     ret.push_back(reinterpret_cast<jthread>(env->GetObjectArrayElement(thrs, i)));
   }
diff --git a/test/ti-agent/ti_macros.h b/test/ti-agent/ti_macros.h
index a871270..abd54e0 100644
--- a/test/ti-agent/ti_macros.h
+++ b/test/ti-agent/ti_macros.h
@@ -21,4 +21,10 @@
 
 #define UNREACHABLE  __builtin_unreachable
 
+#ifndef NDEBUG
+#define ALWAYS_INLINE
+#else
+#define ALWAYS_INLINE  __attribute__ ((always_inline))
+#endif
+
 #endif  // ART_TEST_TI_AGENT_TI_MACROS_H_
diff --git a/test/ti-agent/ti_utf.h b/test/ti-agent/ti_utf.h
index 341e106..15fe22c 100644
--- a/test/ti-agent/ti_utf.h
+++ b/test/ti-agent/ti_utf.h
@@ -21,6 +21,7 @@
 #include <string.h>
 
 #include "android-base/logging.h"
+#include "ti_macros.h"
 
 namespace art {
 namespace ti {
@@ -104,6 +105,56 @@
   return surrogate_pair;
 }
 
+// Note: This is a copy of the code in `libdexfile`.
+template <bool kUseShortZero, bool kUse4ByteSequence, bool kReplaceBadSurrogates, typename Append>
+inline void ConvertUtf16ToUtf8(const uint16_t* utf16, size_t char_count, Append&& append) {
+  static_assert(kUse4ByteSequence || !kReplaceBadSurrogates);
+
+  // Use local helpers instead of macros from `libicu` to avoid the dependency on `libicu`.
+  auto is_lead = [](uint16_t ch) ALWAYS_INLINE { return (ch & 0xfc00u) == 0xd800u; };
+  auto is_trail = [](uint16_t ch) ALWAYS_INLINE { return (ch & 0xfc00u) == 0xdc00u; };
+  auto is_surrogate = [](uint16_t ch) ALWAYS_INLINE { return (ch & 0xf800u) == 0xd800u; };
+  auto is_surrogate_lead = [](uint16_t ch) ALWAYS_INLINE { return (ch & 0x0400u) == 0u; };
+  auto get_supplementary = [](uint16_t lead, uint16_t trail) ALWAYS_INLINE {
+    constexpr uint32_t offset = (0xd800u << 10) + 0xdc00u - 0x10000u;
+    return (static_cast<uint32_t>(lead) << 10) + static_cast<uint32_t>(trail) - offset;
+  };
+
+  for (size_t i = 0u; i < char_count; ++i) {
+    auto has_trail = [&]() { return i + 1u != char_count && is_trail(utf16[i + 1u]); };
+
+    uint16_t ch = utf16[i];
+    if (ch < 0x80u && (kUseShortZero || ch != 0u)) {
+      // One byte.
+      append(ch);
+    } else if (ch < 0x800u) {
+      // Two bytes.
+      append((ch >> 6) | 0xc0);
+      append((ch & 0x3f) | 0x80);
+    } else if (kReplaceBadSurrogates
+                   ? is_surrogate(ch)
+                   : kUse4ByteSequence && is_lead(ch) && has_trail()) {
+      if (kReplaceBadSurrogates && (!is_surrogate_lead(ch) || !has_trail())) {
+        append('?');
+      } else {
+        // We have a *valid* surrogate pair.
+        uint32_t code_point = get_supplementary(ch, utf16[i + 1u]);
+        ++i;  //  Consume the leading surrogate.
+        // Four bytes.
+        append((code_point >> 18) | 0xf0);
+        append(((code_point >> 12) & 0x3f) | 0x80);
+        append(((code_point >> 6) & 0x3f) | 0x80);
+        append((code_point & 0x3f) | 0x80);
+      }
+    } else {
+      // Three bytes.
+      append((ch >> 12) | 0xe0);
+      append(((ch >> 6) & 0x3f) | 0x80);
+      append((ch & 0x3f) | 0x80);
+    }
+  }
+}
+
 inline void ConvertUtf16ToModifiedUtf8(char* utf8_out,
                                        size_t byte_count,
                                        const uint16_t* utf16_in,
@@ -118,75 +169,20 @@
   }
 
   // String contains non-ASCII characters.
-  while (char_count--) {
-    const uint16_t ch = *utf16_in++;
-    if (ch > 0 && ch <= 0x7f) {
-      *utf8_out++ = ch;
-    } else {
-      // Char_count == 0 here implies we've encountered an unpaired
-      // surrogate and we have no choice but to encode it as 3-byte UTF
-      // sequence. Note that unpaired surrogates can occur as a part of
-      // "normal" operation.
-      if ((ch >= 0xd800 && ch <= 0xdbff) && (char_count > 0)) {
-        const uint16_t ch2 = *utf16_in;
-
-        // Check if the other half of the pair is within the expected
-        // range. If it isn't, we will have to emit both "halves" as
-        // separate 3 byte sequences.
-        if (ch2 >= 0xdc00 && ch2 <= 0xdfff) {
-          utf16_in++;
-          char_count--;
-          const uint32_t code_point = (ch << 10) + ch2 - 0x035fdc00;
-          *utf8_out++ = (code_point >> 18) | 0xf0;
-          *utf8_out++ = ((code_point >> 12) & 0x3f) | 0x80;
-          *utf8_out++ = ((code_point >> 6) & 0x3f) | 0x80;
-          *utf8_out++ = (code_point & 0x3f) | 0x80;
-          continue;
-        }
-      }
-
-      if (ch > 0x07ff) {
-        // Three byte encoding.
-        *utf8_out++ = (ch >> 12) | 0xe0;
-        *utf8_out++ = ((ch >> 6) & 0x3f) | 0x80;
-        *utf8_out++ = (ch & 0x3f) | 0x80;
-      } else /*(ch > 0x7f || ch == 0)*/ {
-        // Two byte encoding.
-        *utf8_out++ = (ch >> 6) | 0xc0;
-        *utf8_out++ = (ch & 0x3f) | 0x80;
-      }
-    }
-  }
+  // FIXME: We should not emit 4-byte sequences. Bug: 192935764
+  auto append = [&](char c) { *utf8_out++ = c; };
+  ConvertUtf16ToUtf8</*kUseShortZero=*/ false,
+                     /*kUse4ByteSequence=*/ true,
+                     /*kReplaceBadSurrogates=*/ false>(utf16_in, char_count, append);
 }
 
-inline size_t CountUtf8Bytes(const uint16_t* chars, size_t char_count) {
+inline size_t CountModifiedUtf8BytesInUtf16(const uint16_t* chars, size_t char_count) {
+  // FIXME: We should not emit 4-byte sequences. Bug: 192935764
   size_t result = 0;
-  const uint16_t *end = chars + char_count;
-  while (chars < end) {
-    const uint16_t ch = *chars++;
-    if (LIKELY(ch != 0 && ch < 0x80)) {
-      result++;
-      continue;
-    }
-    if (ch < 0x800) {
-      result += 2;
-      continue;
-    }
-    if (ch >= 0xd800 && ch < 0xdc00) {
-      if (chars < end) {
-        const uint16_t ch2 = *chars;
-        // If we find a properly paired surrogate, we emit it as a 4 byte
-        // UTF sequence. If we find an unpaired leading or trailing surrogate,
-        // we emit it as a 3 byte sequence like would have done earlier.
-        if (ch2 >= 0xdc00 && ch2 < 0xe000) {
-          chars++;
-          result += 4;
-          continue;
-        }
-      }
-    }
-    result += 3;
-  }
+  auto append = [&](char c ATTRIBUTE_UNUSED) { ++result; };
+  ConvertUtf16ToUtf8</*kUseShortZero=*/ false,
+                     /*kUse4ByteSequence=*/ true,
+                     /*kReplaceBadSurrogates=*/ false>(chars, char_count, append);
   return result;
 }
 
diff --git a/test/update-rollback/Android.bp b/test/update-rollback/Android.bp
index 3713328..ad14988 100644
--- a/test/update-rollback/Android.bp
+++ b/test/update-rollback/Android.bp
@@ -23,5 +23,9 @@
     libs: ["tradefed"],
     static_libs: ["cts-install-lib-host"],
     data: [":test_broken_com.android.art"],
-    test_suites: ["general-tests"],
+    // Add this test to `device-tests` rather than `general-tests` to ensure
+    // that the type of ART APEX -- public (`com.android.art`) or internal
+    // (`com.google.android.art`) -- used in the test matches the one bundled
+    // with the Android platform used in the device-under-test.
+    test_suites: ["device-tests"],
 }
diff --git a/test/utils/get-device-isa b/test/utils/get-device-isa
deleted file mode 100755
index 5f9c2a4..0000000
--- a/test/utils/get-device-isa
+++ /dev/null
@@ -1,77 +0,0 @@
-#! /bin/bash
-#
-# Copyright 2019 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.
-
-usage() {
-  cat >&2 <<EOF
-Determine and print the 32- or 64-bit architecture of a device.
-
-Usage:
-  $0 --32    Select the 32-bit architecture
-  $0 --64    Select the 64-bit architecture
-EOF
-  exit 1
-}
-
-check_32bit() {
-  if ! adb shell test -e /system/bin/linker; then
-    echo >&2 "Device does not have 32-bit support"
-    exit 1
-  fi
-}
-
-if [[ $# -ne 1 ]]; then
-  usage
-fi
-
-uname_m="$(adb shell uname -m)"
-
-case "$1" in
-  (--32)
-    case $uname_m in
-      (armv*)
-        echo arm
-        ;;
-      (i?86)
-        echo x86
-        ;;
-      (aarch64)
-        check_32bit
-        echo arm
-        ;;
-      (x86_64)
-        check_32bit
-        echo x86
-        ;;
-      (*)
-        echo >&2 "Unknown ISA: $uname_m"
-        exit 1
-    esac
-    ;;
-  (--64)
-    case $uname_m in
-      (aarch64)
-        echo arm64
-        ;;
-      (x86_64)
-        echo x86_64
-        ;;
-      (*)
-        echo >&2 "Unknown ISA: $uname_m"
-        exit 1
-    esac
-    ;;
-  (*) usage;;
-esac
diff --git a/test/utils/get-device-test-native-lib-path b/test/utils/get-device-test-native-lib-path
deleted file mode 100755
index 21ea98c..0000000
--- a/test/utils/get-device-test-native-lib-path
+++ /dev/null
@@ -1,47 +0,0 @@
-#! /bin/bash
-#
-# Copyright 2019 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.
-
-usage() {
-  cat >&2 <<EOF
-Determine the 32- or 64-bit architecture of a device and print the path to
-native libraries installed on the device for testing purposes.
-
-Usage:
-  $0 --32    Select the 32-bit architecture
-  $0 --64    Select the 64-bit architecture
-EOF
-  exit 1
-}
-
-if [[ $# -ne 1 ]]; then
-  usage
-fi
-
-case "$1" in
-  (--32) TEST_DIRECTORY="nativetest";;
-  (--64) TEST_DIRECTORY="nativetest64";;
-  (*) usage;;
-esac
-
-if [[ -z "$ANDROID_BUILD_TOP" ]]; then
-  echo 'ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?'
-  exit 1
-fi
-
-bitness_flag=$1
-ISA=$("$ANDROID_BUILD_TOP/art/test/utils/get-device-isa" "$bitness_flag")
-
-echo "/data/${TEST_DIRECTORY}/art/${ISA}"
diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files
index 237ec96..d047c23 100755
--- a/test/utils/regen-test-files
+++ b/test/utils/regen-test-files
@@ -94,14 +94,12 @@
   "1004-checker-volatile-ref-load",
   "133-static-invoke-super",
   "1338-gc-no-los",
-  "151-OpenFileLimit",
   "159-app-image-fields",
   "160-read-barrier-stress",
   "163-app-image-methods",
   "165-lock-owner-proxy",
   "168-vmstack-annotated",
   "176-app-image-string",
-  "2232-write-metrics-to-log",
   "304-method-tracing",
   "628-vdex",
   "643-checker-bogus-ic",
@@ -197,17 +195,38 @@
   #           at Main.main(Main.java:20)
   #
   "821-many-args",
+  # 823-cha-inlining: Dependency on `libarttest`.
+  "823-cha-inlining",
   # 826-infinite-loop: The test expects an argument passed to `Main.main` (the test library,
   # usually `arttestd` or `arttest)`, but the ART run-test TradeFed test runner
   # (`com.android.tradefed.testtype.ArtRunTest`) does not implement this yet.
   "826-infinite-loop",
   # 832-cha-recursive: Dependency on `libarttest`.
   "832-cha-recursive",
+  # 837-deopt: Dependency on `libarttest`.
+  "837-deopt",
+  # 844-exception: Dependency on `libarttest`.
+  "844-exception",
+  # 844-exception2: Dependency on `libarttest`.
+  "844-exception2",
+  # 966-default-conflict: Dependency on `libarttest`.
+  "966-default-conflict",
+  # These tests need native code.
+  "993-breakpoints-non-debuggable",
+  "2243-single-step-default",
+  "2262-miranda-methods",
+  "2262-default-conflict-methods"
+])
+
+known_failing_on_hwasan_tests = frozenset([
+  "BootImageProfileTest", # b/232012605
+  "CtsJdwpTestCases", # times out
 ])
 
 # ART gtests that do not need root access to the device.
 art_gtest_user_module_names = [
     "art_libnativebridge_cts_tests",
+    "art_standalone_artd_tests",
     "art_standalone_cmdline_tests",
     "art_standalone_compiler_tests",
     "art_standalone_dex2oat_tests",
@@ -222,7 +241,6 @@
     "art_standalone_libprofile_tests",
     "art_standalone_oatdump_tests",
     "art_standalone_odrefresh_tests",
-    "art_standalone_runtime_compiler_tests",
     "art_standalone_runtime_tests",
     "art_standalone_sigchain_tests",
     "libnativeloader_test",
@@ -232,6 +250,7 @@
 art_gtest_eng_only_module_names = [
     "art_standalone_dexoptanalyzer_tests",
     "art_standalone_profman_tests",
+    "libnativeloader_e2e_tests",
 ]
 
 # All supported ART gtests.
@@ -239,57 +258,54 @@
 
 # ART gtests supported in MTS that do not need root access to the device.
 art_gtest_mts_user_module_names = copy.copy(art_gtest_user_module_names)
-# Temporarily disable `art_standalone_odrefresh_tests` in MTS,
-# as it is currently failing in Mainline testing
-# (b/206335809); a fix is in the works but may take some time
-# to land.
-#
-# TODO(b/206335809): Re-enable this test when the fix has landed.
-art_gtest_mts_user_module_names.remove("art_standalone_odrefresh_tests")
 
 # ART gtests supported in Mainline presubmits.
 art_gtests_mainline_presubmit_module_names = copy.copy(art_gtest_module_names)
-# Temporarily disable `art_standalone_odrefresh_tests` in Mainline
-# presubmits, as it is currently failing in Mainline testing
-# (b/206335809); a fix is in the works but may take some time to
-# land.
-#
-# TODO(b/206335809): Re-enable this test when the fix has landed.
-art_gtests_mainline_presubmit_module_names.remove("art_standalone_odrefresh_tests")
 
 # Tests exhibiting a flaky behavior, currently exluded from MTS for
 # the stake of stability / confidence (b/209958457).
-flaky_tests_excluded_from_mts = [
-    ("CtsLibcoreFileIOTestCases" +
-     " android.cts.FileChannelInterProcessLockTest#" + m) for m in [
+flaky_tests_excluded_from_mts = {
+    "CtsLibcoreFileIOTestCases": [
+        ("android.cts.FileChannelInterProcessLockTest#" + m) for m in [
          "test_lockJJZ_Exclusive_asyncChannel",
          "test_lockJJZ_Exclusive_syncChannel",
          "test_lock_differentChannelTypes",
          "test_lockJJZ_Shared_asyncChannel",
          "test_lockJJZ_Shared_syncChannel",
+        ]
+    ],
+    "CtsLibcoreTestCases": [
+        ("com.android.org.conscrypt.javax.net.ssl.SSLSocketVersionCompatibilityTest#" + m + c)
+        for (m, c) in itertools.product(
+            [
+                "test_SSLSocket_interrupt_read_withoutAutoClose",
+                "test_SSLSocket_setSoWriteTimeout",
+            ],
+            [
+                "[0: TLSv1.2 client, TLSv1.2 server]",
+                "[1: TLSv1.2 client, TLSv1.3 server]",
+                "[2: TLSv1.3 client, TLSv1.2 server]",
+                "[3: TLSv1.3 client, TLSv1.3 server]",
+            ]
+        )
+    ] + [
+        ("libcore.dalvik.system.DelegateLastClassLoaderTest#" + m) for m in [
+            "testLookupOrderNodelegate_getResource",
+            "testLookupOrder_getResource",
+        ]
     ]
-] + [
-    ("CtsLibcoreTestCases" +
-     " com.android.org.conscrypt.javax.net.ssl.SSLSocketVersionCompatibilityTest#" + m + c)
-    for (m, c) in itertools.product(
-         [
-             "test_SSLSocket_interrupt_read_withoutAutoClose",
-             "test_SSLSocket_setSoWriteTimeout",
-         ],
-         [
-             "[0: TLSv1.2 client, TLSv1.2 server]",
-             "[1: TLSv1.2 client, TLSv1.3 server]",
-             "[2: TLSv1.3 client, TLSv1.2 server]",
-             "[3: TLSv1.3 client, TLSv1.3 server]",
-         ]
-     )
-] + [
-    ("CtsLibcoreTestCases" +
-     " libcore.dalvik.system.DelegateLastClassLoaderTest#" + m) for m in [
-         "testLookupOrderNodelegate_getResource",
-         "testLookupOrder_getResource",
-    ]
-]
+}
+
+# Tests failing because of linking issues, currently exluded from MTS
+# and Mainline Presubmits to minimize noise in continuous runs while
+# we investigate.
+#
+# TODO(b/247108425): Address the linking issues and re-enable these
+# tests.
+failing_tests_excluded_from_mts_and_mainline_presubmits = {
+    "art_standalone_compiler_tests": ["JniCompilerTest*"],
+    "art_standalone_libartpalette_tests": ["PaletteClientJniTest*"],
+}
 
 # Is `run_test` a Checker test (i.e. a test containing Checker
 # assertions)?
@@ -315,37 +331,37 @@
                    for run_test in os.listdir(self.art_test_dir)
                    if re.match("^[0-9]{3,}-", run_test)])
 
-  # Read build file (Bash script) and return a canonized version of it
-  # (without comments, blank lines, "debugging" statements, etc.).
-  def canonize_build_script(self, build_file):
+  # Return the metadata of a test, if any.
+  def get_test_metadata(self, run_test):
+    run_test_path = os.path.join(self.art_test_dir, run_test)
+    metadata_file = os.path.join(run_test_path, "test-metadata.json")
+    metadata = {}
+    if os.path.exists(metadata_file):
+      with open(metadata_file, "r") as f:
+        try:
+          metadata = json.load(f)
+        except json.decoder.JSONDecodeError:
+          logging.error(f"Unable to parse test metadata file `{metadata_file}`")
+          raise
+    return metadata
 
-    def is_comment(line):
-      return re.match("^\\s*#", line)
-
-    def is_blank(line):
-      return re.match("^\\s*$", line)
-
-    # Is `line` a `set -e` statement?
-    def is_set_e(line):
-      return re.match("^\\s*set -e\\s*", line)
-
-    # Should `line` be kept in the canonized build script?
-    def keep_line(line):
-      return not (is_comment(line) or is_blank(line) or is_set_e(line))
-
-    with open(build_file, "r") as f:
-      lines = f.readlines()
-    return list(filter(keep_line, lines))
-
-  # Can the build script in `build_file` be safely ignored?
-  def can_ignore_build_script(self, build_file):
-    build_script = self.canonize_build_script(build_file)
-    if len(build_script) == 1:
-      if build_script[0] == "./default-build \"$@\" --experimental var-handles\n":
-        # Soong builds JARs with VarHandle support by default (i.e. by
-        # using an API level greater or equal to 28), so we can ignore
-        # build scripts that just request support for this feature.
-        return True
+  # Can the build script of `run_test` be safely ignored?
+  def can_ignore_build_script(self, run_test):
+    # Check whether there are test metadata with build parameters
+    # enabling us to safely ignore the build script.
+    metadata = self.get_test_metadata(run_test)
+    build_param = metadata.get("build-param", {})
+    # Ignore build scripts that are just about preventing building for
+    # the JVM and/or using VarHandles (Soong builds JARs with
+    # VarHandle support by default (i.e. by using an API level greater
+    # or equal to 28), so we can ignore build scripts that just
+    # request support for this feature.)
+    experimental_var_handles = {"experimental": "var-handles"}
+    jvm_supported_false = {"jvm-supported": "false"}
+    if (build_param == experimental_var_handles or
+        build_param == jvm_supported_false or
+        build_param == experimental_var_handles | jvm_supported_false):
+      return True
     return False
 
   # Is building `run_test` supported?
@@ -355,8 +371,11 @@
 
     # Skip tests with non-default build rules, unless these build
     # rules can be safely ignored.
-    if os.path.isfile(os.path.join(run_test_path, "build")):
-      if not self.can_ignore_build_script(os.path.join(run_test_path, "build")):
+    if (os.path.isfile(os.path.join(run_test_path, "generate-sources")) or
+        os.path.isfile(os.path.join(run_test_path, "javac_post.sh"))):
+      return False
+    if os.path.isfile(os.path.join(run_test_path, "build.py")):
+      if not self.can_ignore_build_script(run_test):
         return False
     # Skip tests with sources outside the `src` directory.
     for subdir in ["jasmin",
@@ -368,8 +387,7 @@
                    "src-bcpex",
                    "src-ex",
                    "src-ex2",
-                   "src-multidex",
-                   "src2"]:
+                   "src-multidex"]:
       if os.path.isdir(os.path.join(run_test_path, subdir)):
         return False
     # Skip tests that have both an `src` directory and an `src-art` directory.
@@ -389,19 +407,60 @@
     # All other tests are considered buildable.
     return True
 
-  # Is (successfully) running `run_test` supported?
-  # TODO(b/147812905): Add run-time support for more tests.
-  def is_runnable(self, run_test):
-    run_test_path = os.path.join(self.art_test_dir, run_test)
+  # Can the run script of `run_test` be safely ignored?
+  def can_ignore_run_script(self, run_test):
     # Unconditionally consider some identified tests that have a
     # (not-yet-handled) custom `run` script as runnable.
+    #
     # TODO(rpl): Get rid of this exception mechanism by supporting
     # these tests' `run` scripts properly.
     if run_test in runnable_test_exceptions:
       return True
-    # Skip tests with a custom `run` script.
-    if os.path.isfile(os.path.join(run_test_path, "run")):
-      return False
+    # Check whether there are test metadata with run parameters
+    # enabling us to safely ignore the run script.
+    metadata = self.get_test_metadata(run_test)
+    run_param = metadata.get("run-param", {})
+    if run_param.get("default-run", ""):
+      return True
+    return False
+
+  def gen_libs_list_impl(self, library_type, libraries):
+    if len(libraries) == 0:
+      return ""
+    libraries_joined = """,
+              """.join(libraries)
+    return f"""
+          {library_type}: [
+              {libraries_joined}
+          ],"""
+
+  def gen_libs_list(self, libraries):
+    return self.gen_libs_list_impl("libs", libraries);
+
+  def gen_static_libs_list(self, libraries):
+    return self.gen_libs_list_impl("static_libs", libraries);
+
+  def gen_java_library_rule(self, name, src_dir, libraries):
+    return f"""\
+
+
+      // Library with {src_dir}/ sources for the test.
+      java_library {{
+          name: "{name}",
+          defaults: ["art-run-test-defaults"],{self.gen_libs_list(libraries)}
+          srcs: ["{src_dir}/**/*.java"],
+      }}"""
+
+  # Is (successfully) running `run_test` supported?
+  # TODO(b/147812905): Add run-time support for more tests.
+  def is_runnable(self, run_test):
+    run_test_path = os.path.join(self.art_test_dir, run_test)
+
+    # Skip tests with non-default run rules, unless these run rules
+    # can be safely ignored.
+    if os.path.isfile(os.path.join(run_test_path, "run.py")):
+      if not self.can_ignore_run_script(run_test):
+        return False
     # Skip tests known to fail.
     if run_test in known_failing_tests:
       return False
@@ -429,11 +488,7 @@
     bp_file = os.path.join(run_test_path, "Android.bp")
 
     # Optional test metadata (JSON file).
-    metadata_file = os.path.join(run_test_path, "test-metadata.json")
-    metadata = {}
-    if os.path.exists(metadata_file):
-      with open(metadata_file, "r") as f:
-        metadata = json.load(f)
+    metadata = self.get_test_metadata(run_test)
 
     run_test_module_name = ART_RUN_TEST_MODULE_NAME_PREFIX + run_test
 
@@ -470,6 +525,16 @@
     else:
       source_dir = "src"
 
+    src_library_rules = []
+    test_libraries = []
+    if os.path.isdir(os.path.join(run_test_path, "src2")):
+      src_library_rules.append(self.gen_java_library_rule(
+          f"{run_test_module_name}-{source_dir}",
+          source_dir,
+          test_libraries))
+      test_libraries.append(f"\"{run_test_module_name}-src\"")
+      source_dir = "src2"
+
     with open(bp_file, "w") as f:
       logging.debug(f"Writing `{bp_file}`.")
       f.write(textwrap.dedent(f"""\
@@ -484,14 +549,14 @@
           // to get the below license kinds:
           //   SPDX-license-identifier-Apache-2.0
           default_applicable_licenses: ["art_license"],
-      }}
+      }}{''.join(src_library_rules)}
 
       // Test's Dex code.
       java_test {{
           name: "{run_test_module_name}",
           defaults: ["art-run-test-defaults"],
           test_config_template: ":{test_config_template}",
-          srcs: ["{source_dir}/**/*.java"],
+          srcs: ["{source_dir}/**/*.java"],{self.gen_static_libs_list(test_libraries)}
           data: [
               ":{run_test_module_name}-expected-stdout",
               ":{run_test_module_name}-expected-stderr",
@@ -521,26 +586,37 @@
     run_test_module_names = [ART_RUN_TEST_MODULE_NAME_PREFIX + t for t in art_run_tests]
 
     # Mainline presubmits.
-    mainline_other_presubmit_tests = [
-        "ComposHostTestCases",
-    ]
+    mainline_presubmit_apex_suffix = "[com.google.android.art.apex]"
+    mainline_other_presubmit_tests = []
     mainline_presubmit_tests = (mainline_other_presubmit_tests + run_test_module_names +
                                 art_gtests_mainline_presubmit_module_names)
-    mainline_presubmit_tests_with_apex = [t + "[com.google.android.art.apex]"
-                                          for t
-                                          in mainline_presubmit_tests]
-    mainline_presubmit_tests_dict = [{"name": t} for t in mainline_presubmit_tests_with_apex]
+    mainline_presubmit_tests_dict = [
+        ({"name": t + mainline_presubmit_apex_suffix,
+          "options": [
+              {"exclude-filter": e}
+              for e in failing_tests_excluded_from_mts_and_mainline_presubmits[t]
+          ]}
+         if t in failing_tests_excluded_from_mts_and_mainline_presubmits
+         else {"name": t + mainline_presubmit_apex_suffix})
+        for t in mainline_presubmit_tests
+    ]
+
+    # Android Virtualization Framework presubmits
+    avf_presubmit_tests = ["ComposHostTestCases"]
+    avf_presubmit_tests_dict = [{"name": t} for t in avf_presubmit_tests]
 
     # Presubmits.
     other_presubmit_tests = [
-        "CtsJdwpTestCases",
-        "BootImageProfileTest",
         "ArtServiceTests",
-        "ComposHostTestCases",
+        "BootImageProfileTest",
+        "CtsJdwpTestCases",
+        "art-apex-update-rollback",
         "art_standalone_dexpreopt_tests",
     ]
     presubmit_tests = other_presubmit_tests + run_test_module_names + art_gtest_module_names
     presubmit_tests_dict = [{"name": t} for t in presubmit_tests]
+    hwasan_presubmit_tests_dict = [{"name": t} for t in presubmit_tests
+                                   if t not in known_failing_on_hwasan_tests]
 
     # Use an `OrderedDict` container to preserve the order in which items are inserted.
     # Do not produce an entry for a test group if it is empty.
@@ -550,6 +626,8 @@
         in [
             ("mainline-presubmit", mainline_presubmit_tests_dict),
             ("presubmit", presubmit_tests_dict),
+            ("hwasan-presubmit", hwasan_presubmit_tests_dict),
+            ("avf-presubmit", avf_presubmit_tests_dict),
         ]
         if test_group_dict
     ])
@@ -699,18 +777,25 @@
       include.setAttribute("name", f"mts-art-tests-list-user-shard-{s:02}")
       configuration.appendChild(include)
 
-    # Excluded flaky tests.
-    xml_comment = root.createComment(f" Excluded flaky tests (b/209958457). ")
-    configuration.appendChild(xml_comment)
-
     def append_test_exclusion(test):
       option = root.createElement("option")
       option.setAttribute("name", "compatibility:exclude-filter")
       option.setAttribute("value", test)
       configuration.appendChild(option)
 
-    for t in flaky_tests_excluded_from_mts:
-      append_test_exclusion(t)
+    # Excluded flaky tests.
+    xml_comment = root.createComment(" Excluded flaky tests (b/209958457). ")
+    configuration.appendChild(xml_comment)
+    for module in flaky_tests_excluded_from_mts:
+      for testcase in flaky_tests_excluded_from_mts[module]:
+        append_test_exclusion(f"{module} {testcase}")
+
+    # Excluded failing tests.
+    xml_comment = root.createComment(" Excluded failing tests (b/247108425). ")
+    configuration.appendChild(xml_comment)
+    for module in failing_tests_excluded_from_mts_and_mainline_presubmits:
+      for testcase in failing_tests_excluded_from_mts_and_mainline_presubmits[module]:
+        append_test_exclusion(f"{module} {testcase}")
 
     xml_str = root.toprettyxml(indent = XML_INDENT, encoding = "utf-8")
 
@@ -736,18 +821,11 @@
 
     mts_test_shards = []
 
-    # ART test (gtest & run-test) shard(s).
-    # TODO: Also handle the case of gtests requiring root access to the device
-    # (`art_gtest_eng_only_module_names`).
+    # ART run-tests shard(s).
     art_run_test_module_names = [ART_RUN_TEST_MODULE_NAME_PREFIX + t for t in art_run_tests]
     art_run_test_shards = split_list(art_run_test_module_names, NUM_MTS_ART_RUN_TEST_SHARDS)
     for i in range(len(art_run_test_shards)):
       art_tests_shard_i_tests = art_run_test_shards[i]
-      # Append ART gtests to the last ART run-test shard for now.
-      # If needed, consider moving them to their own shard to increase
-      # the parallelization of code coverage runs.
-      if i + 1 == len(art_run_test_shards):
-        art_tests_shard_i_tests.extend(art_gtest_mts_user_module_names)
       art_tests_shard_i = self.create_mts_test_shard(
           "ART run-tests", art_tests_shard_i_tests, i, 2020,
           ["TODO(rpl): Find a way to express this list in a more concise fashion."])
@@ -775,6 +853,15 @@
         other_cts_libcore_tests_shard_num, 2021)
     mts_test_shards.append(other_cts_libcore_tests_shard)
 
+    # ART gtests shard.
+    # TODO: Also handle the case of gtests requiring root access to the device
+    # (`art_gtest_eng_only_module_names`).
+    art_gtests_shard_num = len(mts_test_shards)
+    art_gtests_shard_tests = art_gtest_mts_user_module_names
+    art_gtests_shard = self.create_mts_test_shard(
+        "ART gtests", art_gtests_shard_tests, art_gtests_shard_num, 2022)
+    mts_test_shards.append(art_gtests_shard)
+
     for s in mts_test_shards:
       s.regen_test_plan_file()
       s.regen_test_list_file()
diff --git a/tools/Android.bp b/tools/Android.bp
index b7f5c1b..207a121 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -37,6 +37,17 @@
     ],
 }
 
+cc_binary {
+    name: "art_boot",
+    defaults: ["art_defaults"],
+    srcs: ["art_boot.cc"],
+    shared_libs: ["libbase"],
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
 // Copy the art shell script to the host and target's bin directory
 art_module_sh_binary {
     name: "art-script",
@@ -97,23 +108,3 @@
         },
     },
 }
-
-python_binary_host {
-    name: "art-run-test-checker",
-    srcs: [
-        "checker/**/*.py",
-    ],
-    main: "checker/checker.py",
-    version: {
-        py2: {
-            enabled: false,
-        },
-        py3: {
-            enabled: true,
-        },
-    },
-    test_suites: [
-        "general-tests",
-        "mts-art",
-    ],
-}
diff --git a/tools/PresubmitJsonLinter.java b/tools/PresubmitJsonLinter.java
new file mode 100644
index 0000000..334d200
--- /dev/null
+++ b/tools/PresubmitJsonLinter.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import com.google.gson.stream.JsonReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Pre upload hook that ensures art-buildbot expectation files (files under //art/tools ending with
+ * "_failures.txt", e.g. //art/tools/libcore_failures.txt) are well-formed json files.
+ *
+ * It makes basic validation of the keys but does not cover all the cases. Parser structure is
+ * based on external/vogar/src/vogar/ExpectationStore.java.
+ *
+ * Hook is set up in //art/PREUPLOAD.cfg See also //tools/repohooks/README.md
+ */
+class PresubmitJsonLinter {
+
+    private static final int FLAGS = Pattern.MULTILINE | Pattern.DOTALL;
+    private static final Set<String> RESULTS = new HashSet<>();
+
+    static {
+        RESULTS.addAll(List.of(
+                "UNSUPPORTED",
+                "COMPILE_FAILED",
+                "EXEC_FAILED",
+                "EXEC_TIMEOUT",
+                "ERROR",
+                "SUCCESS"
+        ));
+    }
+
+    public static void main(String[] args) {
+        for (String arg : args) {
+            info("Checking " + arg);
+            checkExpectationFile(arg);
+        }
+    }
+
+    private static void info(String message) {
+        System.err.println(message);
+    }
+
+    private static void error(String message) {
+        System.err.println(message);
+        System.exit(1);
+    }
+
+    private static void checkExpectationFile(String arg) {
+        JsonReader reader;
+        try {
+            reader = new JsonReader(new FileReader(arg));
+        } catch (FileNotFoundException e) {
+            error("File '" + arg + "' is not found");
+            return;
+        }
+        reader.setLenient(true);
+        try {
+            reader.beginArray();
+            while (reader.hasNext()) {
+                readExpectation(reader);
+            }
+            reader.endArray();
+        } catch (IOException e) {
+            error("Malformed json: " + reader);
+        }
+    }
+
+    private static void readExpectation(JsonReader reader) throws IOException {
+        Set<String> names = new LinkedHashSet<String>();
+        Set<String> tags = new LinkedHashSet<String>();
+        boolean readResult = false;
+        boolean readDescription = false;
+
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String name = reader.nextName();
+            switch (name) {
+                case "result":
+                    String result = reader.nextString();
+                    if (!RESULTS.contains(result)) {
+                        error("Invalid 'result' value: '" + result +
+                                "'. Expected one of " + String.join(", ", RESULTS) +
+                                ". " + reader);
+                    }
+                    readResult = true;
+                    break;
+                case "substring": {
+                    try {
+                        Pattern.compile(
+                                ".*" + Pattern.quote(reader.nextString()) + ".*", FLAGS);
+                    } catch (PatternSyntaxException e) {
+                        error("Malformed 'substring' value: " + reader);
+                    }
+                }
+                case "pattern": {
+                    try {
+                        Pattern.compile(reader.nextString(), FLAGS);
+                    } catch (PatternSyntaxException e) {
+                        error("Malformed 'pattern' value: " + reader);
+                    }
+                    break;
+                }
+                case "failure":
+                    names.add(reader.nextString());
+                    break;
+                case "description":
+                    reader.nextString();
+                    readDescription = true;
+                    break;
+                case "name":
+                    names.add(reader.nextString());
+                    break;
+                case "names":
+                    readStrings(reader, names);
+                    break;
+                case "tags":
+                    readStrings(reader, tags);
+                    break;
+                case "bug":
+                    reader.nextLong();
+                    break;
+                case "modes":
+                    readModes(reader);
+                    break;
+                case "modes_variants":
+                    readModesAndVariants(reader);
+                    break;
+                default:
+                    error("Unknown key '" + name + "' in expectations file");
+                    reader.skipValue();
+                    break;
+            }
+        }
+        reader.endObject();
+
+        if (names.isEmpty()) {
+            error("Missing 'name' or 'failure' key in " + reader);
+        }
+        if (!readResult) {
+            error("Missing 'result' key in " + reader);
+        }
+        if (!readDescription) {
+            error("Missing 'description' key in " + reader);
+        }
+    }
+
+    private static void readStrings(JsonReader reader, Set<String> output) throws IOException {
+        reader.beginArray();
+        while (reader.hasNext()) {
+            output.add(reader.nextString());
+        }
+        reader.endArray();
+    }
+
+    private static void readModes(JsonReader reader) throws IOException {
+        reader.beginArray();
+        while (reader.hasNext()) {
+            reader.nextString();
+        }
+        reader.endArray();
+    }
+
+    /**
+     * Expected format: mode_variants: [["host", "X32"], ["host", "X64"]]
+     */
+    private static void readModesAndVariants(JsonReader reader) throws IOException {
+        reader.beginArray();
+        while (reader.hasNext()) {
+            reader.beginArray();
+            reader.nextString();
+            reader.nextString();
+            reader.endArray();
+        }
+        reader.endArray();
+    }
+}
\ No newline at end of file
diff --git a/tools/ahat/.clang-format b/tools/ahat/.clang-format
new file mode 120000
index 0000000..88ab38e
--- /dev/null
+++ b/tools/ahat/.clang-format
@@ -0,0 +1 @@
+../../.clang-format-java-2
\ No newline at end of file
diff --git a/tools/ahat/Android.bp b/tools/ahat/Android.bp
index affa2e0..5f6ba81 100644
--- a/tools/ahat/Android.bp
+++ b/tools/ahat/Android.bp
@@ -27,7 +27,7 @@
     visibility: [
         "//libcore/metrictests/memory/host",
     ],
-    wrapper: "ahat",
+    wrapper: "ahat.sh",
     srcs: ["src/main/**/*.java"],
     manifest: "etc/ahat.mf",
     java_resources: ["etc/style.css"],
diff --git a/tools/ahat/ahat b/tools/ahat/ahat.sh
similarity index 100%
rename from tools/ahat/ahat
rename to tools/ahat/ahat.sh
diff --git a/tools/ahat/etc/style.css b/tools/ahat/etc/style.css
index 47fae1d..83e5b20 100644
--- a/tools/ahat/etc/style.css
+++ b/tools/ahat/etc/style.css
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
+html {
+    /* Roboto has tabular numbers, use it if available, fallback to other sans. */
+    font-family: "Roboto", "Arial", "sans-serif";
+}
+
 div.menu {
   background-color: #eeffff;
 }
@@ -39,3 +44,23 @@
   padding-left: 8px;
   padding-right: 8px;
 }
+
+.sidebar {
+  position: fixed;
+  right: 0;
+  top: 48px;
+  padding-left: 12px;
+  padding-right: 24px;
+  font-size: small;
+  border-left: 4px solid #dcedc8;
+}
+
+.sidebar a {
+  text-decoration: none;
+  color: #4285f4;
+}
+
+.sidebar a:hover {
+  text-decoration: underline;
+  color: #073042;
+}
\ No newline at end of file
diff --git a/tools/ahat/src/main/com/android/ahat/DocString.java b/tools/ahat/src/main/com/android/ahat/DocString.java
index eda9b38..ca5dbf0 100644
--- a/tools/ahat/src/main/com/android/ahat/DocString.java
+++ b/tools/ahat/src/main/com/android/ahat/DocString.java
@@ -136,7 +136,7 @@
     if (isPlaceHolder) {
       string.append(DocString.removed("del"));
     } else if (size != 0) {
-      string.appendFormat("%,14d", size);
+      string.appendFormat("%,d", size);
     }
     return string;
   }
@@ -162,13 +162,13 @@
   public DocString appendDelta(boolean noCurrent, boolean noBaseline,
       long current, long baseline) {
     if (noCurrent) {
-      append(removed(format("%+,14d", 0 - baseline)));
+      append(removed(format("%+,d", 0 - baseline)));
     } else if (noBaseline) {
       append(added("new"));
     } else if (current > baseline) {
-      append(added(format("%+,14d", current - baseline)));
+      append(added(format("%+,d", current - baseline)));
     } else if (current < baseline) {
-      append(removed(format("%+,14d", current - baseline)));
+      append(removed(format("%+,d", current - baseline)));
     }
     return this;
   }
diff --git a/tools/ahat/src/main/com/android/ahat/HtmlDoc.java b/tools/ahat/src/main/com/android/ahat/HtmlDoc.java
index d5106dc..6c3ab2f 100644
--- a/tools/ahat/src/main/com/android/ahat/HtmlDoc.java
+++ b/tools/ahat/src/main/com/android/ahat/HtmlDoc.java
@@ -18,6 +18,7 @@
 
 import java.io.PrintStream;
 import java.net.URI;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -26,6 +27,7 @@
 class HtmlDoc implements Doc {
   private PrintStream ps;
   private Column[] mCurrentTableColumns;
+  private List<String> mSections;
 
   /**
    * Create an HtmlDoc that writes to the given print stream.
@@ -34,6 +36,7 @@
    */
   public HtmlDoc(PrintStream ps, DocString title, URI style) {
     this.ps = ps;
+    mSections = new ArrayList<>();
 
     ps.println("<!DOCTYPE html>");
     ps.println("<html>");
@@ -59,9 +62,10 @@
 
   @Override
   public void section(String title) {
-    ps.print("<h2>");
+    ps.format("<h2 id=\"%d\">", mSections.size());
     ps.print(DocString.text(title).html());
     ps.println(":</h2>");
+    mSections.add(title);
   }
 
   @Override
@@ -182,8 +186,17 @@
     mCurrentTableColumns = null;
   }
 
+  private void sidebar() {
+    ps.println("<div class=\"sidebar\">");
+    for (int i = 0; i < mSections.size(); i++) {
+      ps.format("<p><a href=\"#%d\">%s</a></p>", i, mSections.get(i));
+    }
+    ps.println("</div>");
+  }
+
   @Override
   public void close() {
+    sidebar();
     ps.println("</body>");
     ps.println("</html>");
     ps.close();
diff --git a/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java b/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java
index 81611b6..4cdbaf4 100644
--- a/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java
+++ b/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java
@@ -111,7 +111,7 @@
     heapChoice.append(")");
     doc.description(DocString.text("Heap"), heapChoice);
 
-    doc.description(DocString.text("Count"), DocString.format("%,14d", insts.size()));
+    doc.description(DocString.text("Count"), DocString.format("%,d", insts.size()));
     doc.end();
     doc.println(DocString.text(""));
 
diff --git a/tools/ahat/src/main/com/android/ahat/OverviewHandler.java b/tools/ahat/src/main/com/android/ahat/OverviewHandler.java
index 5f0b473..c6f4a54 100644
--- a/tools/ahat/src/main/com/android/ahat/OverviewHandler.java
+++ b/tools/ahat/src/main/com/android/ahat/OverviewHandler.java
@@ -57,8 +57,6 @@
 
     doc.section("Bytes Retained by Heap");
     printHeapSizes(doc);
-
-    doc.big(Menu.getMenu());
   }
 
   private void printHeapSizes(Doc doc) {
diff --git a/tools/ahat/src/main/com/android/ahat/SiteHandler.java b/tools/ahat/src/main/com/android/ahat/SiteHandler.java
index 5093f0d..671784e 100644
--- a/tools/ahat/src/main/com/android/ahat/SiteHandler.java
+++ b/tools/ahat/src/main/com/android/ahat/SiteHandler.java
@@ -102,7 +102,7 @@
           DocString.link(
             DocString.formattedUri("objects?id=%d&heap=%s&class=%s",
               site.getId(), info.heap.getName(), className),
-            DocString.format("%,14d", info.numInstances)),
+            DocString.format("%,d", info.numInstances)),
           DocString.delta(false, false, info.numInstances, baseinfo.numInstances),
           DocString.text(info.heap.getName()),
           Summarizer.summarize(info.classObj));
diff --git a/tools/ahat/src/test-dump/Main.java b/tools/ahat/src/test-dump/Main.java
index 2e29076..711d662 100644
--- a/tools/ahat/src/test-dump/Main.java
+++ b/tools/ahat/src/test-dump/Main.java
@@ -43,6 +43,10 @@
     // Allocate the instance of DumpedStuff.
     stuff = new DumpedStuff(baseline);
 
+    // Preemptively garbage collect to avoid an inopportune GC triggering
+    // after this.
+    Runtime.getRuntime().gc();
+
     // Create a bunch of unreachable objects pointing to basicString for the
     // reverseReferencesAreNotUnreachable test
     for (int i = 0; i < 100; i++) {
diff --git a/tools/ahat/src/test/com/android/ahat/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
index 376122b..1f29030 100644
--- a/tools/ahat/src/test/com/android/ahat/InstanceTest.java
+++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
@@ -224,6 +224,7 @@
   @Test
   public void reachability() throws IOException {
     TestDump dump = TestDump.getTestDump();
+    // We were careful to avoid GC before dumping, so nothing here should be null.
     AhatInstance strong1 = dump.getDumpedAhatInstance("reachabilityReferenceChain");
     AhatInstance soft1 = strong1.getField("referent").asAhatInstance();
     AhatInstance strong2 = soft1.getField("referent").asAhatInstance();
diff --git a/tools/art_boot.cc b/tools/art_boot.cc
new file mode 100644
index 0000000..f725fc8
--- /dev/null
+++ b/tools/art_boot.cc
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+// This binary is run on boot as a oneshot service. It should not be run at any
+// other point.
+
+#include <string>
+
+#include "android-base/logging.h"
+#include "android-base/properties.h"
+
+// Copies the value of one system property to another if it isn't empty and
+// passes the predicate test_fn.
+static void CopyPropertyIf(const char* src, const char* dst, bool (*test_fn)(const std::string&)) {
+  std::string prop = android::base::GetProperty(src, "");
+  if (prop.empty()) {
+    LOG(INFO) << "Property " << src << " not set";
+  } else if (!test_fn(prop)) {
+    LOG(INFO) << "Property " << src << " has ignored value " << prop;
+  } else {
+    if (android::base::SetProperty(dst, prop)) {
+      LOG(INFO) << "Set property " << dst << " to " << prop << " from " << src;
+    } else {
+      LOG(ERROR) << "Failed to set property " << dst << " to " << prop;
+    }
+  }
+}
+
+int main(int, char** argv) {
+  android::base::InitLogging(argv);
+
+  // Copy properties that must only be set at boot and not change value later.
+  // Note that P/H can change the properties in the experiment namespaces at any
+  // time.
+  CopyPropertyIf("persist.device_config.runtime_native_boot.useartservice",
+                 "dalvik.vm.useartservice",
+                 // If an OEM has set dalvik.vm.useartservice to false we
+                 // shouldn't override it to true from the P/H property.
+                 [](const std::string& prop) { return prop == "false"; });
+
+  return 0;
+}
diff --git a/tools/art_verifier/art_verifier.cc b/tools/art_verifier/art_verifier.cc
index 2f2562b..e68d2ca 100644
--- a/tools/art_verifier/art_verifier.cc
+++ b/tools/art_verifier/art_verifier.cc
@@ -42,11 +42,9 @@
 
 bool LoadDexFile(const std::string& dex_filename,
                  std::vector<std::unique_ptr<const DexFile>>* dex_files) {
-  const ArtDexFileLoader dex_file_loader;
+  ArtDexFileLoader dex_file_loader(dex_filename);
   std::string error_msg;
-  if (!dex_file_loader.Open(dex_filename.c_str(),
-                            dex_filename.c_str(),
-                            /* verify= */ true,
+  if (!dex_file_loader.Open(/* verify= */ true,
                             /* verify_checksum= */ true,
                             &error_msg,
                             dex_files)) {
diff --git a/tools/build_linux_bionic.sh b/tools/build_linux_bionic.sh
index 8992512..bbe71b6 100755
--- a/tools/build_linux_bionic.sh
+++ b/tools/build_linux_bionic.sh
@@ -16,83 +16,38 @@
 
 # This will build a target using linux_bionic. It can be called with normal make
 # flags.
-#
-# TODO This runs a 'm clean' prior to building the targets in order to ensure
-# that obsolete kati files don't mess up the build.
 
-if [[ -z $ANDROID_BUILD_TOP ]]; then
-  pushd .
-else
-  pushd $ANDROID_BUILD_TOP
-fi
+set -e
 
 if [ ! -d art ]; then
   echo "Script needs to be run at the root of the android tree"
   exit 1
 fi
 
+export TARGET_PRODUCT=linux_bionic
+
+# Avoid Soong error about invalid dependencies on disabled libLLVM_android,
+# which we get due to the --soong-only mode. (Another variant is to set
+# SOONG_ALLOW_MISSING_DEPENDENCIES).
+export FORCE_BUILD_LLVM_COMPONENTS=true
+
 # TODO(b/194433871): Set MODULE_BUILD_FROM_SOURCE to disable prebuilt modules,
 # which Soong otherwise can create duplicate install rules for in --soong-only
 # mode.
-soong_args="MODULE_BUILD_FROM_SOURCE=true"
+export MODULE_BUILD_FROM_SOURCE=true
 
 # Switch the build system to unbundled mode in the reduced manifest branch.
 if [ ! -d frameworks/base ]; then
-  soong_args="$soong_args TARGET_BUILD_UNBUNDLED=true"
+  export TARGET_BUILD_UNBUNDLED=true
 fi
 
-source build/envsetup.sh >&/dev/null # for get_build_var
-# Soong needs a bunch of variables set and will not run if they are missing.
-# The default values of these variables is only contained in make, so use
-# nothing to create the variables then remove all the other artifacts.
-# Lunch since it seems we cannot find the build-number otherwise.
-lunch aosp_x86-eng
-build/soong/soong_ui.bash --make-mode $soong_args nothing
+vars="$(build/soong/soong_ui.bash --dumpvars-mode --vars="OUT_DIR BUILD_NUMBER")"
+# Assign to a variable and eval that, since bash ignores any error status from
+# the command substitution if it's directly on the eval line.
+eval $vars
 
-if [ $? != 0 ]; then
-  exit 1
-fi
+# This file is currently not created in --soong-only mode, but some build
+# targets depend on it.
+printf %s "${BUILD_NUMBER}" > ${OUT_DIR}/soong/build_number.txt
 
-out_dir=$(get_build_var OUT_DIR)
-host_out=$(get_build_var HOST_OUT)
-
-# TODO(b/31559095) Figure out a better way to do this.
-#
-# There is no good way to force soong to generate host-bionic builds currently
-# so this is a hacky workaround.
-tmp_soong_var=$(mktemp --tmpdir soong.variables.bak.XXXXXX)
-tmp_build_number=$(cat ${out_dir}/soong/build_number.txt)
-
-cat $out_dir/soong/soong.variables > ${tmp_soong_var}
-
-# See comment above about b/123645297 for why we cannot just do m clean. Clear
-# out all files except for intermediates and installed files and dexpreopt.config.
-find $out_dir/ -maxdepth 1 -mindepth 1 \
-               -not -name soong        \
-               -not -name host         \
-               -not -name target | xargs -I '{}' rm -rf '{}'
-find $out_dir/soong/ -maxdepth 1 -mindepth 1   \
-                     -not -name .intermediates \
-                     -not -name host           \
-                     -not -name dexpreopt.config \
-                     -not -name target | xargs -I '{}' rm -rf '{}'
-
-python3 <<END - ${tmp_soong_var} ${out_dir}/soong/soong.variables
-import json
-import sys
-x = json.load(open(sys.argv[1]))
-x['Allow_missing_dependencies'] = True
-x['HostArch'] = 'x86_64'
-x['CrossHost'] = 'linux_bionic'
-x['CrossHostArch'] = 'x86_64'
-if 'CrossHostSecondaryArch' in x:
-  del x['CrossHostSecondaryArch']
-json.dump(x, open(sys.argv[2], mode='w'))
-END
-
-rm $tmp_soong_var
-
-# Write a new build-number
-echo ${tmp_build_number}_SOONG_ONLY_BUILD > ${out_dir}/soong/build_number.txt
-
-build/soong/soong_ui.bash --make-mode --skip-config --soong-only $soong_args $@
+build/soong/soong_ui.bash --make-mode --soong-only "$@"
diff --git a/tools/build_linux_bionic_tests.sh b/tools/build_linux_bionic_tests.sh
index 7379e9a..0470d6d 100755
--- a/tools/build_linux_bionic_tests.sh
+++ b/tools/build_linux_bionic_tests.sh
@@ -14,64 +14,36 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-if [[ -z $ANDROID_BUILD_TOP ]]; then
-  pushd .
-else
-  pushd $ANDROID_BUILD_TOP
-fi
+set -e
 
 if [ ! -d art ]; then
   echo "Script needs to be run at the root of the android tree"
   exit 1
 fi
 
-soong_args=""
-
 # Switch the build system to unbundled mode in the reduced manifest branch.
 if [ ! -d frameworks/base ]; then
-  soong_args="$soong_args TARGET_BUILD_UNBUNDLED=true"
+  export TARGET_BUILD_UNBUNDLED=true
 fi
 
-source build/envsetup.sh >&/dev/null # for get_build_var
-
-out_dir=$(get_build_var OUT_DIR)
-host_out=$(get_build_var HOST_OUT)
-
-# TODO(b/31559095) Figure out a better way to do this.
-#
-# There is no good way to force soong to generate host-bionic builds currently
-# so this is a hacky workaround.
+vars="$(build/soong/soong_ui.bash --dumpvars-mode --vars="OUT_DIR HOST_OUT")"
+# Assign to a variable and eval that, since bash ignores any error status from
+# the command substitution if it's directly on the eval line.
+eval $vars
 
 # First build all the targets still in .mk files (also build normal glibc host
 # targets so we know what's needed to run the tests).
-build/soong/soong_ui.bash --make-mode $soong_args "$@" test-art-host-run-test-dependencies build-art-host-tests
-if [ $? != 0 ]; then
-  exit 1
-fi
+build/soong/soong_ui.bash --make-mode "$@" test-art-host-run-test-dependencies build-art-host-tests
 
-tmp_soong_var=$(mktemp --tmpdir soong.variables.bak.XXXXXX)
+# Next build the Linux host Bionic targets in --soong-only mode.
+export TARGET_PRODUCT=linux_bionic
 
-echo "Saving soong.variables to " $tmp_soong_var
-cat $out_dir/soong/soong.variables > ${tmp_soong_var}
-python3 <<END - ${tmp_soong_var} ${out_dir}/soong/soong.variables
-import json
-import sys
-x = json.load(open(sys.argv[1]))
-x['Allow_missing_dependencies'] = True
-x['HostArch'] = 'x86_64'
-x['CrossHost'] = 'linux_bionic'
-x['CrossHostArch'] = 'x86_64'
-if 'CrossHostSecondaryArch' in x:
-  del x['CrossHostSecondaryArch']
-json.dump(x, open(sys.argv[2], mode='w'))
-END
-if [ $? != 0 ]; then
-  mv $tmp_soong_var $out_dir/soong/soong.variables
-  exit 2
-fi
+# Avoid Soong error about invalid dependencies on disabled libLLVM_android,
+# which we get due to the --soong-only mode. (Another variant is to set
+# SOONG_ALLOW_MISSING_DEPENDENCIES).
+export FORCE_BUILD_LLVM_COMPONENTS=true
 
-soong_out=$out_dir/soong/host/linux_bionic-x86
+soong_out=$OUT_DIR/soong/host/linux_bionic-x86
 declare -a bionic_targets
 # These are the binaries actually used in tests. Since some of the files are
 # java targets or 32 bit we cannot just do the same find for the bin files.
@@ -89,24 +61,10 @@
   $soong_out/bin/hprof-conv
   $soong_out/bin/signal_dumper
   $soong_out/lib64/libclang_rt.ubsan_standalone-x86_64-android.so
-  $(find $host_out/apex/com.android.art.host.zipapex -type f | sed "s:$host_out:$soong_out:g")
-  $(find $host_out/lib64 -type f | sed "s:$host_out:$soong_out:g")
-  $(find $host_out/nativetest64 -type f | sed "s:$host_out:$soong_out:g"))
+  $(find $HOST_OUT/apex/com.android.art.host.zipapex -type f | sed "s:$HOST_OUT:$soong_out:g")
+  $(find $HOST_OUT/lib64 -type f | sed "s:$HOST_OUT:$soong_out:g")
+  $(find $HOST_OUT/nativetest64 -type f | sed "s:$HOST_OUT:$soong_out:g"))
 
 echo building ${bionic_targets[*]}
 
-build/soong/soong_ui.bash --make-mode --skip-config --soong-only $soong_args "$@" ${bionic_targets[*]}
-ret=$?
-
-mv $tmp_soong_var $out_dir/soong/soong.variables
-
-# Having built with host-bionic confuses soong somewhat by making it think the
-# linux_bionic targets are needed for art phony targets like
-# test-art-host-run-test-dependencies. To work around this blow away all
-# ninja files in OUT_DIR. The build system is smart enough to not need to
-# rebuild stuff so this should be fine.
-rm -f $OUT_DIR/*.ninja $OUT_DIR/soong/*.ninja
-
-popd
-
-exit $ret
+build/soong/soong_ui.bash --make-mode --soong-only "$@" ${bionic_targets[*]}
diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh
index 48fc004..1467037 100755
--- a/tools/buildbot-build.sh
+++ b/tools/buildbot-build.sh
@@ -25,8 +25,6 @@
   exit 1
 fi
 
-TARGET_ARCH=$(source build/envsetup.sh > /dev/null; get_build_var TARGET_ARCH)
-
 # Logic for setting out_dir from build/make/core/envsetup.mk:
 if [[ -z $OUT_DIR ]]; then
   if [[ -z $OUT_DIR_COMMON_BASE ]]; then
@@ -69,6 +67,9 @@
   elif [[ "$1" == "--showcommands" ]]; then
     showcommands="showcommands"
     shift
+  elif [[ "$1" == "--dist" ]]; then
+    common_targets="$common_targets dist"
+    shift
   elif [[ "$1" == "" ]]; then
     break
   else
@@ -83,11 +84,25 @@
   build_target="yes"
 fi
 
-# Allow to build successfully in master-art.
-extra_args="SOONG_ALLOW_MISSING_DEPENDENCIES=true"
+implementation_libs=(
+  "heapprofd_client_api"
+  "libandroid_runtime_lazy"
+  "libartpalette-system"
+  "libbinder"
+  "libbinder_ndk"
+  "libcutils"
+  "libutils"
+  "libvndksupport"
+)
 
-# Switch the build system to unbundled mode in the reduced manifest branch.
-if [ ! -d frameworks/base ]; then
+if [ -d frameworks/base ]; then
+  # In full manifest branches, build the implementation libraries from source
+  # instead of using prebuilts.
+  common_targets="$common_targets ${implementation_libs[*]}"
+else
+  # Allow to build successfully in master-art.
+  extra_args="SOONG_ALLOW_MISSING_DEPENDENCIES=true BUILD_BROKEN_DISABLE_BAZEL=true"
+  # Switch the build system to unbundled mode in the reduced manifest branch.
   extra_args="$extra_args TARGET_BUILD_UNBUNDLED=true"
 fi
 
@@ -120,13 +135,12 @@
   # Indirect dependencies in the platform, e.g. through heapprofd_client_api.
   # These are built to go into system/lib(64) to be part of the system linker
   # namespace.
-  make_command+=" libbacktrace libnetd_client-target libprocinfo libtombstoned_client libunwindstack"
+  make_command+=" libnetd_client-target libprocinfo libtombstoned_client libunwindstack"
   # Stubs for other APEX SDKs, for use by vogar. Referenced from DEVICE_JARS in
   # external/vogar/src/vogar/ModeId.java.
   # Note these go into out/target/common/obj/JAVA_LIBRARIES which isn't removed
   # by "m installclean".
   make_command+=" i18n.module.public.api.stubs conscrypt.module.public.api.stubs"
-  make_command+=" ${ANDROID_PRODUCT_OUT#"${ANDROID_BUILD_TOP}/"}/system/etc/public.libraries.txt"
   # Targets required to generate a linker configuration for device within the
   # chroot environment. The *.libraries.txt targets are required by
   # the source linkerconfig but not included in the prebuilt one.
@@ -166,6 +180,8 @@
 
   # Extract prebuilt APEXes.
   debugfs=$ANDROID_HOST_OUT/bin/debugfs_static
+  fsckerofs=$ANDROID_HOST_OUT/bin/fsck.erofs
+  blkid=$ANDROID_HOST_OUT/bin/blkid_static
   for apex in ${apexes[@]}; do
     dir="$ANDROID_PRODUCT_OUT/system/apex/${apex}"
     apexbase="$ANDROID_PRODUCT_OUT/system/apex/${apex}"
@@ -179,20 +195,16 @@
       msginfo "Extracting APEX file:" "${file}"
       rm -rf $dir
       mkdir -p $dir
-      $ANDROID_HOST_OUT/bin/deapexer --debugfs_path $debugfs extract $file $dir
+      $ANDROID_HOST_OUT/bin/deapexer --debugfs_path $debugfs --fsckerofs_path $fsckerofs \
+        --blkid_path $blkid extract $file $dir
     fi
   done
 
-  # Replace stub libraries with implemenation libraries: because we do chroot
+  # Replace stub libraries with implementation libraries: because we do chroot
   # testing, we need to install an implementation of the libraries (and cannot
   # rely on the one already installed on the device, if the device is post R and
   # has it).
-  implementation_libs=(
-    "heapprofd_client_api.so"
-    "libartpalette-system.so"
-    "liblog.so"
-  )
-  if [ -d prebuilts/runtime/mainline/platform/impl ]; then
+  if [ -d prebuilts/runtime/mainline/platform/impl -a ! -d frameworks/base ]; then
     if [[ $TARGET_ARCH = arm* ]]; then
       arch32=arm
       arch64=arm64
@@ -200,18 +212,22 @@
       arch32=x86
       arch64=x86_64
     fi
-    for so in ${implementation_libs[@]}; do
-      if [ -d "$ANDROID_PRODUCT_OUT/system/lib" ]; then
-        cmd="cp -p prebuilts/runtime/mainline/platform/impl/$arch32/$so $ANDROID_PRODUCT_OUT/system/lib/$so"
-        msginfo "Executing" "$cmd"
-        eval "$cmd"
-      fi
-      if [ -d "$ANDROID_PRODUCT_OUT/system/lib64" ]; then
-        cmd="cp -p prebuilts/runtime/mainline/platform/impl/$arch64/$so $ANDROID_PRODUCT_OUT/system/lib64/$so"
-        msginfo "Executing" "$cmd"
-        eval "$cmd"
-      fi
-   done
+    if [ "$TARGET_ARCH" = riscv64 ]; then
+      true # no 32-bit arch for RISC-V
+    else
+      for so in ${implementation_libs[@]}; do
+        if [ -d "$ANDROID_PRODUCT_OUT/system/lib" ]; then
+          cmd="cp -p prebuilts/runtime/mainline/platform/impl/$arch32/${so}.so $ANDROID_PRODUCT_OUT/system/lib/${so}.so"
+          msginfo "Executing" "$cmd"
+          eval "$cmd"
+        fi
+        if [ -d "$ANDROID_PRODUCT_OUT/system/lib64" ]; then
+          cmd="cp -p prebuilts/runtime/mainline/platform/impl/$arch64/${so}.so $ANDROID_PRODUCT_OUT/system/lib64/${so}.so"
+          msginfo "Executing" "$cmd"
+          eval "$cmd"
+        fi
+      done
+    fi
   fi
 
   # Create canonical name -> file name symlink in the symbol directory for the
@@ -299,6 +315,11 @@
   mkdir -p $linkerconfig_root/system
   cp -r $ANDROID_PRODUCT_OUT/system/etc $linkerconfig_root/system
 
+  # Use our smaller public.libraries.txt that contains only the public libraries
+  # pushed to the chroot directory.
+  cp $ANDROID_BUILD_TOP/art/tools/public.libraries.buildbot.txt \
+    $linkerconfig_root/system/etc/public.libraries.txt
+
   # For linkerconfig to pick up the APEXes correctly we need to make them
   # available in $linkerconfig_root/apex.
   mkdir -p $linkerconfig_root/apex
@@ -390,6 +411,7 @@
   msginfo "Generating linkerconfig" "in $linkerconfig_out"
   rm -rf $linkerconfig_out
   mkdir -p $linkerconfig_out
-  $ANDROID_HOST_OUT/bin/linkerconfig --target $linkerconfig_out --root $linkerconfig_root --vndk $platform_version
+  # TODO(b/300291157): Remove VNDK versions and enable Treble once VNDK deprecation is set as default
+  $ANDROID_HOST_OUT/bin/linkerconfig --target $linkerconfig_out --root $linkerconfig_root --vndk $platform_version --product_vndk $platform_version
   msgnote "Don't be scared by \"Unable to access VNDK APEX\" message, it's not fatal"
 fi
diff --git a/tools/buildbot-cleanup-device.sh b/tools/buildbot-cleanup-device.sh
index 7dee149..7fd57b4 100755
--- a/tools/buildbot-cleanup-device.sh
+++ b/tools/buildbot-cleanup-device.sh
@@ -16,7 +16,22 @@
 
 . "$(dirname $0)/buildbot-utils.sh"
 
-# Setup as root, as device cleanup requires it.
+# Testing on a Linux VM requires special cleanup.
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+  [[ -d "$ART_TEST_VM_DIR" ]] || { msgfatal "no VM found in $ART_TEST_VM_DIR"; }
+  $ART_SSH_CMD "true" || { msgfatal "VM not responding (tried \"$ART_SSH_CMD true\""; }
+  $ART_SSH_CMD "
+    sudo umount $ART_TEST_CHROOT/proc
+    sudo umount $ART_TEST_CHROOT/sys
+    sudo umount $ART_TEST_CHROOT/dev
+    sudo umount $ART_TEST_CHROOT/bin
+    sudo umount $ART_TEST_CHROOT/lib
+    rm -rf $ART_TEST_CHROOT
+  "
+  exit 0
+fi
+
+# Regular Android device. Setup as root, as device cleanup requires it.
 adb root
 adb wait-for-device
 
diff --git a/tools/buildbot-setup-device.sh b/tools/buildbot-setup-device.sh
index ad2c59c..90d680b 100755
--- a/tools/buildbot-setup-device.sh
+++ b/tools/buildbot-setup-device.sh
@@ -25,7 +25,39 @@
   verbose=false
 fi
 
-# Setup as root, as some actions performed here require it.
+# Testing on a Linux VM requires special setup.
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+  [[ -d "$ART_TEST_VM_DIR" ]] || { msgfatal "no VM found in $ART_TEST_VM_DIR"; }
+  $ART_SSH_CMD "true" || { msgerror "no VM (tried \"$ART_SSH_CMD true\""; }
+  $ART_SSH_CMD "
+    mkdir $ART_TEST_CHROOT
+
+    mkdir $ART_TEST_CHROOT/apex
+    mkdir $ART_TEST_CHROOT/bin
+    mkdir $ART_TEST_CHROOT/data
+    mkdir $ART_TEST_CHROOT/data/local
+    mkdir $ART_TEST_CHROOT/data/local/tmp
+    mkdir $ART_TEST_CHROOT/dev
+    mkdir $ART_TEST_CHROOT/etc
+    mkdir $ART_TEST_CHROOT/lib
+    mkdir $ART_TEST_CHROOT/linkerconfig
+    mkdir $ART_TEST_CHROOT/proc
+    mkdir $ART_TEST_CHROOT/sys
+    mkdir $ART_TEST_CHROOT/system
+    mkdir $ART_TEST_CHROOT/tmp
+
+    sudo mount -t proc /proc art-test-chroot/proc
+    sudo mount -t sysfs /sys art-test-chroot/sys
+    sudo mount --bind /dev art-test-chroot/dev
+    sudo mount --bind /bin art-test-chroot/bin
+    sudo mount --bind /lib art-test-chroot/lib
+    $ART_CHROOT_CMD echo \"Hello from chroot! I am \$(uname -a).\"
+  "
+  exit 0
+fi
+
+# Regular Android device. Setup as root, as some actions performed here require it.
+adb version
 adb root
 adb wait-for-device
 
@@ -36,7 +68,11 @@
 adb shell date
 
 host_seconds_since_epoch=$(date -u +%s)
-device_seconds_since_epoch=$(adb shell date -u +%s)
+
+# Get the device time in seconds, but filter the output as some
+# devices emit CRLF at the end of the command which then breaks the
+# time comparisons in this script (Hammerhead, MRA59G 2457013).
+device_seconds_since_epoch=$(adb shell date -u +%s | tr -c -d '[:digit:]')
 
 abs_time_difference_in_seconds=$(expr $host_seconds_since_epoch - $device_seconds_since_epoch)
 if [ $abs_time_difference_in_seconds -lt 0 ]; then
@@ -173,6 +209,8 @@
     || adb shell mount -o bind /dev "$ART_TEST_CHROOT/dev"
   adb shell mount | grep -q "^devpts on $ART_TEST_CHROOT/dev/pts type devpts " \
     || adb shell mount -o bind /dev/pts "$ART_TEST_CHROOT/dev/pts"
+  adb shell mount | grep -q " on $ART_TEST_CHROOT/dev/cpuset type cgroup " \
+    || adb shell mount -o bind /dev/cpuset "$ART_TEST_CHROOT/dev/cpuset"
 
   # Create /apex directory in chroot.
   adb shell mkdir -p "$ART_TEST_CHROOT/apex"
diff --git a/tools/buildbot-sync.sh b/tools/buildbot-sync.sh
index 28dab0c..ef9ec8b 100755
--- a/tools/buildbot-sync.sh
+++ b/tools/buildbot-sync.sh
@@ -20,27 +20,21 @@
 
 . "$(dirname $0)/buildbot-utils.sh"
 
-# Setup as root, as some actions performed here require it.
-adb root
-adb wait-for-device
+if [[ -z "$ART_TEST_ON_VM" ]]; then
+  # Setup as root, as some actions performed here require it.
+  adb root
+  adb wait-for-device
+fi
 
 if [[ -z "$ANDROID_BUILD_TOP" ]]; then
-  msgerror 'ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?'
-  exit 1
-fi
-
-if [[ -z "$ANDROID_PRODUCT_OUT" ]]; then
-  msgerror 'ANDROID_PRODUCT_OUT environment variable is empty; did you forget to run `lunch`?'
-  exit 1
-fi
-
-if [[ -z "$ART_TEST_CHROOT" ]]; then
-  msgerror 'ART_TEST_CHROOT environment variable is empty; ' \
+  msgfatal 'ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?'
+elif [[ -z "$ANDROID_PRODUCT_OUT" ]]; then
+  msgfatal 'ANDROID_PRODUCT_OUT environment variable is empty; did you forget to run `lunch`?'
+elif [[ -z "$ART_TEST_CHROOT" ]]; then
+  msgfatal 'ART_TEST_CHROOT environment variable is empty; ' \
       'please set it before running this script.'
-  exit 1
 fi
 
-
 # Sync relevant product directories
 # ---------------------------------
 
@@ -53,23 +47,40 @@
       continue
     fi
     msginfo "Syncing $dir directory..."
-    adb shell mkdir -p "$ART_TEST_CHROOT/$dir"
-    adb push $dir "$ART_TEST_CHROOT/$(dirname $dir)"
+    if [[ -n "$ART_TEST_ON_VM" ]]; then
+      $ART_RSYNC_CMD -R $dir "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST:$ART_TEST_CHROOT"
+    else
+      adb shell mkdir -p "$ART_TEST_CHROOT/$dir"
+      adb push $dir "$ART_TEST_CHROOT/$(dirname $dir)"
+    fi
   done
 )
 
 # Overwrite the default public.libraries.txt file with a smaller one that
 # contains only the public libraries pushed to the chroot directory.
-adb push "$ANDROID_BUILD_TOP/art/tools/public.libraries.buildbot.txt" \
-  "$ART_TEST_CHROOT/system/etc/public.libraries.txt"
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+  $ART_RSYNC_CMD "$ANDROID_BUILD_TOP/art/tools/public.libraries.buildbot.txt" \
+    "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST:$ART_TEST_CHROOT/system/etc/public.libraries.txt"
+else
+  adb push "$ANDROID_BUILD_TOP/art/tools/public.libraries.buildbot.txt" \
+    "$ART_TEST_CHROOT/system/etc/public.libraries.txt"
+fi
 
 # Create the framework directory if it doesn't exist. Some gtests need it.
-adb shell mkdir -p "$ART_TEST_CHROOT/system/framework"
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+  $ART_SSH_CMD "$ART_CHROOT_CMD mkdir -p $ART_TEST_CHROOT/system/framework"
+else
+  adb shell mkdir -p "$ART_TEST_CHROOT/system/framework"
+fi
 
 # APEX packages activation.
 # -------------------------
 
-adb shell mkdir -p "$ART_TEST_CHROOT/apex"
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+  $ART_SSH_CMD "$ART_CHROOT_CMD mkdir -p $ART_TEST_CHROOT/apex"
+else
+  adb shell mkdir -p "$ART_TEST_CHROOT/apex"
+fi
 
 # Manually "activate" the flattened APEX $1 by syncing it to /apex/$2 in the
 # chroot. $2 defaults to $1.
@@ -95,12 +106,19 @@
     msginfo "Extracting APEX ${src_apex_file}..."
     mkdir -p $src_apex_path
     $ANDROID_HOST_OUT/bin/deapexer --debugfs_path $ANDROID_HOST_OUT/bin/debugfs_static \
+      --fsckerofs_path $ANDROID_HOST_OUT/bin/fsck.erofs \
+      --blkid_path $ANDROID_HOST_OUT/bin/blkid_static \
       extract ${src_apex_file} $src_apex_path
   fi
 
   msginfo "Activating APEX ${src_apex} as ${dst_apex}..."
-  adb shell rm -rf "$ART_TEST_CHROOT/apex/${dst_apex}"
-  adb push $src_apex_path "$ART_TEST_CHROOT/apex/${dst_apex}"
+  if [[ -n "$ART_TEST_ON_VM" ]]; then
+    $ART_RSYNC_CMD $src_apex_path/* \
+      "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST:$ART_TEST_CHROOT/apex/${dst_apex}"
+  else
+    adb shell rm -rf "$ART_TEST_CHROOT/apex/${dst_apex}"
+    adb push $src_apex_path "$ART_TEST_CHROOT/apex/${dst_apex}"
+  fi
 }
 
 # "Activate" the required APEX modules.
@@ -111,19 +129,34 @@
 activate_apex com.android.conscrypt
 activate_apex com.android.os.statsd
 
-# Generate primary boot images on device for testing.
-for b in {32,64}; do
-  basename="generate-boot-image$b"
-  bin_on_host="$ANDROID_PRODUCT_OUT/system/bin/$basename"
-  bin_on_device="/data/local/tmp/$basename"
-  output_dir="/system/framework/art_boot_images"
-  if [ -f $bin_on_host ]; then
-    msginfo "Generating the primary boot image ($b-bit)..."
-    adb push "$bin_on_host" "$ART_TEST_CHROOT$bin_on_device"
-    adb shell mkdir -p "$ART_TEST_CHROOT$output_dir"
-    # `compiler-filter=speed-profile` is required because OatDumpTest checks the compiled code in
-    # the boot image.
-    adb shell chroot "$ART_TEST_CHROOT" \
-      "$bin_on_device" --output-dir=$output_dir --compiler-filter=speed-profile
-  fi
-done
+if [[ "$TARGET_ARCH" = "riscv64" ]]; then
+  true # Skip boot image generation for RISC-V; it's not supported.
+else
+  # Generate primary boot images on device for testing.
+  for b in {32,64}; do
+    basename="generate-boot-image$b"
+    bin_on_host="$ANDROID_PRODUCT_OUT/system/bin/$basename"
+    bin_on_device="/data/local/tmp/$basename"
+    output_dir="/system/framework/art_boot_images"
+    if [ -f $bin_on_host ]; then
+      msginfo "Generating the primary boot image ($b-bit)..."
+      if [[ -n "$ART_TEST_ON_VM" ]]; then
+        $ART_RSYNC_CMD "$bin_on_host" \
+          "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST:$ART_TEST_CHROOT$bin_on_device"
+        $ART_SSH_CMD "mkdir -p $ART_TEST_CHROOT$output_dir"
+      else
+        adb push "$bin_on_host" "$ART_TEST_CHROOT$bin_on_device"
+        adb shell mkdir -p "$ART_TEST_CHROOT$output_dir"
+      fi
+      # `compiler-filter=speed-profile` is required because OatDumpTest checks the compiled code in
+      # the boot image.
+      if [[ -n "$ART_TEST_ON_VM" ]]; then
+        $ART_SSH_CMD \
+          "$ART_CHROOT_CMD $bin_on_device --output-dir=$output_dir --compiler-filter=speed-profile"
+      else
+        adb shell chroot "$ART_TEST_CHROOT" \
+          "$bin_on_device" --output-dir=$output_dir --compiler-filter=speed-profile
+      fi
+    fi
+  done
+fi
diff --git a/tools/buildbot-teardown-device.sh b/tools/buildbot-teardown-device.sh
index 927e3c5..e71dcbe 100755
--- a/tools/buildbot-teardown-device.sh
+++ b/tools/buildbot-teardown-device.sh
@@ -19,6 +19,8 @@
 
 . "$(dirname $0)/buildbot-utils.sh"
 
+[[ -n "$ART_TEST_ON_VM" ]] && exit 0
+
 # Setup as root, as some actions performed here require it.
 adb root
 adb wait-for-device
@@ -79,7 +81,7 @@
       local remove_dir=$3
       local dir="$ART_TEST_CHROOT/$dir_in_chroot"
       adb shell test -d "$dir" \
-        && adb shell mount | grep -q "^$fstype on $dir type $fstype " \
+        && adb shell mount | grep -q " on $dir type $fstype " \
         && if adb shell umount "$dir"; then
              $remove_dir && adb shell rmdir "$dir"
            else
@@ -95,6 +97,7 @@
     adb shell rm -rf "$ART_TEST_CHROOT/apex"
 
     # Remove /dev from chroot.
+    remove_filesystem_from_chroot dev/cpuset cgroup false
     remove_filesystem_from_chroot dev/pts devpts false
     remove_filesystem_from_chroot dev tmpfs true
 
diff --git a/tools/buildbot-utils.sh b/tools/buildbot-utils.sh
index 32ed234..6a0714d 100755
--- a/tools/buildbot-utils.sh
+++ b/tools/buildbot-utils.sh
@@ -53,7 +53,43 @@
   echo -e "${boldred}Error: ${nc}${message}"
 }
 
+function msgfatal() {
+  local message="$*"
+  echo -e "${boldred}Fatal: ${nc}${message}"
+  exit 1
+}
+
 function msgnote() {
   local message="$*"
   echo -e "${boldcyan}Note: ${nc}${message}"
 }
+
+export TARGET_ARCH=$(build/soong/soong_ui.bash --dumpvar-mode TARGET_ARCH)
+
+# Do some checks and prepare environment for tests that run on Linux (not on Android).
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+  if [[ -z $ANDROID_BUILD_TOP ]]; then
+    msgfatal "ANDROID_BUILD_TOP is not set"
+  elif [[ -z "$ART_TEST_SSH_USER" ]]; then
+    msgfatal "ART_TEST_SSH_USER not set"
+  elif [[ -z "$ART_TEST_SSH_HOST" ]]; then
+    msgfatal "ART_TEST_SSH_HOST not set"
+  elif [[ -z "$ART_TEST_SSH_PORT" ]]; then
+    msgfatal "ART_TEST_SSH_PORT not set"
+  fi
+
+  export ART_TEST_CHROOT="/home/$ART_TEST_SSH_USER/art-test-chroot"
+  export ART_CHROOT_CMD="unshare --user --map-root-user chroot art-test-chroot"
+  export ART_SSH_CMD="ssh -q -p $ART_TEST_SSH_PORT $ART_TEST_SSH_USER@$ART_TEST_SSH_HOST -o IdentityAgent=none"
+  export ART_SCP_CMD="scp -P $ART_TEST_SSH_PORT -p -r -o IdentityAgent=none"
+  export ART_RSYNC_CMD="rsync -az"
+  export RSYNC_RSH="ssh -p $ART_TEST_SSH_PORT -o IdentityAgent=none" # don't prefix with "ART_", rsync expects this name
+
+  if [[ "$TARGET_ARCH" =~ ^(arm64|riscv64)$ ]]; then
+    export ART_TEST_VM_IMG="ubuntu-22.04-server-cloudimg-$TARGET_ARCH.img"
+    export ART_TEST_VM_DIR="$ANDROID_BUILD_TOP/vm/$TARGET_ARCH"
+    export ART_TEST_VM="$ART_TEST_VM_DIR/$ART_TEST_VM_IMG"
+  else
+    msgfatal "unexpected TARGET_ARCH=$TARGET_ARCH; expected one of {arm64,riscv64}"
+  fi
+fi
diff --git a/tools/buildbot-vm.sh b/tools/buildbot-vm.sh
new file mode 100755
index 0000000..9574c9f
--- /dev/null
+++ b/tools/buildbot-vm.sh
@@ -0,0 +1,128 @@
+#! /bin/bash
+#
+# Copyright (C) 2023 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.
+
+set -e
+
+ART_TEST_ON_VM=true . "$(dirname $0)/buildbot-utils.sh"
+
+known_actions="create|boot|setup-ssh|connect|quit"
+
+if [[ -z $ANDROID_BUILD_TOP ]]; then
+    msgfatal "ANDROID_BUILD_TOP is not set"
+elif [[ ( $# -ne 1 ) || ! ( "$1" =~ ^($known_actions)$ ) ]]; then
+    msgfatal "usage: $0 <$known_actions>"
+fi
+
+action="$1"
+
+get_stable_binary() {
+    mkdir tmp && cd tmp
+    wget "http://security.ubuntu.com/ubuntu/pool/main/$1"
+    7z x "$(basename $1)" && zstd -d data.tar.zst && tar -xf data.tar
+    mv "$2" ..
+    cd .. && rm -rf tmp
+}
+
+if [[ $action = create ]]; then
+(
+    rm -rf "$ART_TEST_VM_DIR"
+    mkdir -p "$ART_TEST_VM_DIR"
+    cd "$ART_TEST_VM_DIR"
+
+    # sudo apt install qemu-system-<arch> qemu-efi cloud-image-utils
+
+    # Get the cloud image for Ubunty 22.04 (Jammy)
+    wget "http://cloud-images.ubuntu.com/releases/22.04/release/$ART_TEST_VM_IMG"
+
+    if [[ "$TARGET_ARCH" = "riscv64" ]]; then
+        # Get U-Boot for Ubuntu 22.04 (Jammy)
+        get_stable_binary \
+            u/u-boot/u-boot-qemu_2022.01+dfsg-2ubuntu2.3_all.deb \
+            usr/lib/u-boot/qemu-riscv64_smode/uboot.elf
+
+        # Get OpenSBI for Ubuntu 22.04 (Jammy)
+        get_stable_binary \
+            o/opensbi/opensbi_1.1-0ubuntu0.22.04.1_all.deb \
+            usr/lib/riscv64-linux-gnu/opensbi/generic/fw_jump.elf
+
+    elif [[ "$TARGET_ARCH" = "arm64" ]]; then
+        # Get EFI (ARM64) for Ubuntu 22.04 (Jammy)
+        get_stable_binary \
+            e/edk2/qemu-efi-aarch64_2022.02-3ubuntu0.22.04.1_all.deb \
+            usr/share/qemu-efi-aarch64/QEMU_EFI.fd
+
+        dd if=/dev/zero of=flash0.img bs=1M count=64
+        dd if=QEMU_EFI.fd of=flash0.img conv=notrunc
+        dd if=/dev/zero of=flash1.img bs=1M count=64
+    fi
+
+    qemu-img resize "$ART_TEST_VM_IMG" +128G
+
+    # https://help.ubuntu.com/community/CloudInit
+    cat >user-data <<EOF
+#cloud-config
+ssh_pwauth: true
+chpasswd:
+  expire: false
+  list:
+    - $ART_TEST_SSH_USER:ubuntu
+EOF
+    cloud-localds user-data.img user-data
+)
+elif [[ $action = boot ]]; then
+(
+    cd "$ART_TEST_VM_DIR"
+    if [[ "$TARGET_ARCH" = "riscv64" ]]; then
+        qemu-system-riscv64 \
+            -m 16G \
+            -smp 8 \
+            -M virt \
+            -nographic \
+            -bios fw_jump.elf \
+            -kernel uboot.elf \
+            -drive file="$ART_TEST_VM_IMG",if=virtio \
+            -drive file=user-data.img,format=raw,if=virtio \
+            -device virtio-net-device,netdev=usernet \
+            -netdev user,id=usernet,hostfwd=tcp::$ART_TEST_SSH_PORT-:22
+    elif [[ "$TARGET_ARCH" = "arm64" ]]; then
+        qemu-system-aarch64 \
+            -m 16G \
+            -smp 8 \
+            -cpu cortex-a57 \
+            -M virt \
+            -nographic \
+            -drive if=none,file="$ART_TEST_VM_IMG",id=hd0 \
+            -pflash flash0.img \
+            -pflash flash1.img \
+            -drive file=user-data.img,format=raw,id=cloud \
+            -device virtio-blk-device,drive=hd0 \
+            -device virtio-net-device,netdev=usernet \
+            -netdev user,id=usernet,hostfwd=tcp::$ART_TEST_SSH_PORT-:22
+    fi
+
+)
+elif [[ $action = setup-ssh ]]; then
+    # Clean up mentions of this VM from known_hosts
+    sed -i -E "/\[$ART_TEST_SSH_HOST.*\]:$ART_TEST_SSH_PORT .*/d" $HOME/.ssh/known_hosts
+    ssh-copy-id -p "$ART_TEST_SSH_PORT" -o IdentityAgent=none "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST"
+
+elif [[ $action = connect ]]; then
+    $ART_SSH_CMD
+
+elif [[ $action = quit ]]; then
+    $ART_SSH_CMD "sudo poweroff"
+
+fi
diff --git a/tools/check_presubmit_json_expectations.sh b/tools/check_presubmit_json_expectations.sh
new file mode 100755
index 0000000..ecb1e3e
--- /dev/null
+++ b/tools/check_presubmit_json_expectations.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e
+
+REPO_ROOT="$1"
+
+FILES_TO_CHECK=()
+for i in "${@:2}"; do
+  if [[ $i == *_failures.txt ]]; then
+    FILES_TO_CHECK+=($i)
+  fi
+done
+
+# if no libcore_*_failures.txt files were changed
+if [ ${#FILES_TO_CHECK[@]} -eq 0 ]; then
+  exit 0
+fi
+
+TMP_DIR=`mktemp -d`
+# check if tmp dir was created
+if [[ ! "$TMP_DIR" || ! -d "$TMP_DIR" ]]; then
+  echo "Could not create temp dir"
+  exit 1
+fi
+
+function cleanup {
+  rm -rf "$TMP_DIR"
+}
+
+# register the cleanup function to be called on the EXIT signal
+trap cleanup EXIT
+
+GSON_JAR="${REPO_ROOT}/external/caliper/lib/gson-2.2.2.jar"
+
+javac --class-path "$GSON_JAR" "${REPO_ROOT}/art/tools/PresubmitJsonLinter.java" -d "$TMP_DIR"
+java --class-path "$TMP_DIR:$GSON_JAR" PresubmitJsonLinter "${FILES_TO_CHECK[@]}"
diff --git a/tools/checker/Android.bp b/tools/checker/Android.bp
new file mode 100644
index 0000000..db2c597
--- /dev/null
+++ b/tools/checker/Android.bp
@@ -0,0 +1,41 @@
+//
+// Copyright (C) 2022 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.
+//
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+python_binary_host {
+    name: "art-run-test-checker",
+    srcs: [
+        "**/*.py",
+    ],
+    main: "checker.py",
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
+    test_suites: [
+        "general-tests",
+        "mts-art",
+    ],
+}
diff --git a/tools/compile-jar.py b/tools/compile-jar.py
index 56a07d5..d348446 100755
--- a/tools/compile-jar.py
+++ b/tools/compile-jar.py
@@ -105,19 +105,17 @@
         "art/tools/host_bcp.sh",
         os.path.expandvars(
             "${{OUT}}/system/framework/oat/{}/services.odex".format(arch)),
-        "--use-first-dir"
     ]
     print("Running: {}".format(run_print(args)))
     print("=START=======================================")
     res = subprocess.run(args, capture_output=True, text=True)
     print("=END=========================================")
     if res.returncode != 0:
-      print("Falling back to com.android.art BCP")
+      print("Falling back to ART boot image: {}".format(res))
       args = [
           "art/tools/host_bcp.sh",
           os.path.expandvars(
-              "${{OUT}}/apex/com.android.art.debug/javalib/{}/boot.oat".format(arch)),
-          "--use-first-dir"
+              "${{OUT}}/apex/art_boot_images/javalib/{}/boot.oat".format(arch)),
       ]
       print("Running: {}".format(run_print(args)))
       print("=START=======================================")
diff --git a/tools/cpp-define-generator/globals.def b/tools/cpp-define-generator/globals.def
index 2572ea6..459e5a8 100644
--- a/tools/cpp-define-generator/globals.def
+++ b/tools/cpp-define-generator/globals.def
@@ -28,6 +28,7 @@
 #include "mirror/object_reference.h"
 #include "runtime_globals.h"
 #include "stack.h"
+#include "entrypoints/quick/callee_save_frame.h"
 #endif
 
 ASM_DEFINE(ACCESS_FLAGS_METHOD_IS_NATIVE,
@@ -82,3 +83,11 @@
            std::memory_order_relaxed)
 ASM_DEFINE(STACK_OVERFLOW_RESERVED_BYTES,
            GetStackOverflowReservedBytes(art::kRuntimeISA))
+ASM_DEFINE(CALLEE_SAVE_EVERYTHING_NUM_CORE_SPILLS,
+           art::POPCOUNT(art::RuntimeCalleeSaveFrame::GetCoreSpills(
+               art::CalleeSaveType::kSaveEverything)))
+ASM_DEFINE(TAGGED_JNI_SP_MASK, art::ManagedStack::kTaggedJniSpMask)
+ASM_DEFINE(TAGGED_JNI_SP_MASK_TOGGLED32,
+           ~static_cast<uint32_t>(art::ManagedStack::kTaggedJniSpMask))
+ASM_DEFINE(TAGGED_JNI_SP_MASK_TOGGLED64,
+           ~static_cast<uint64_t>(art::ManagedStack::kTaggedJniSpMask))
diff --git a/tools/cpp-define-generator/lockword.def b/tools/cpp-define-generator/lockword.def
index a170c15..5494d59 100644
--- a/tools/cpp-define-generator/lockword.def
+++ b/tools/cpp-define-generator/lockword.def
@@ -30,10 +30,8 @@
            art::LockWord::kMarkBitStateMaskShifted)
 ASM_DEFINE(LOCK_WORD_MARK_BIT_SHIFT,
            art::LockWord::kMarkBitStateShift)
-ASM_DEFINE(LOCK_WORD_READ_BARRIER_STATE_MASK,
+ASM_DEFINE(LOCK_WORD_READ_BARRIER_STATE_MASK_SHIFTED,
            art::LockWord::kReadBarrierStateMaskShifted)
-ASM_DEFINE(LOCK_WORD_READ_BARRIER_STATE_MASK_TOGGLED,
-           art::LockWord::kReadBarrierStateMaskShiftedToggled)
 ASM_DEFINE(LOCK_WORD_READ_BARRIER_STATE_SHIFT,
            art::LockWord::kReadBarrierStateShift)
 ASM_DEFINE(LOCK_WORD_STATE_FORWARDING_ADDRESS,
diff --git a/tools/cpp-define-generator/mirror_class.def b/tools/cpp-define-generator/mirror_class.def
index 8cfd54e..062a7aa 100644
--- a/tools/cpp-define-generator/mirror_class.def
+++ b/tools/cpp-define-generator/mirror_class.def
@@ -16,6 +16,7 @@
 
 #if ASM_DEFINE_INCLUDE_DEPENDENCIES
 #include "mirror/class.h"
+#include "subtype_check.h"
 #endif
 
 ASM_DEFINE(MIRROR_CLASS_ACCESS_FLAGS_OFFSET,
@@ -49,3 +50,17 @@
 ASM_DEFINE(MIRROR_CLASS_IS_INTERFACE_FLAG, art::kAccInterface)
 ASM_DEFINE(MIRROR_CLASS_IS_INTERFACE_FLAG_BIT,
            art::WhichPowerOf2(art::kAccInterface))
+ASM_DEFINE(MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET,
+           art::mirror::Class::StatusOffset().SizeValue() +
+               (art::SubtypeCheckBits::BitStructSizeOf() / art::kBitsPerByte))
+ASM_DEFINE(MIRROR_CLASS_IS_VISIBLY_INITIALIZED_VALUE,
+           art::enum_cast<uint32_t>(art::ClassStatus::kVisiblyInitialized) <<
+               (art::SubtypeCheckBits::BitStructSizeOf() % art::kBitsPerByte))
+ASM_DEFINE(MIRROR_CLASS_IS_INITIALIZING_VALUE,
+           art::enum_cast<uint32_t>(art::ClassStatus::kInitializing) <<
+               (art::SubtypeCheckBits::BitStructSizeOf() % art::kBitsPerByte))
+ASM_DEFINE(MIRROR_CLASS_IS_INITIALIZED_VALUE,
+           art::enum_cast<uint32_t>(art::ClassStatus::kInitialized) <<
+               (art::SubtypeCheckBits::BitStructSizeOf() % art::kBitsPerByte))
+ASM_DEFINE(MIRROR_CLASS_CLINIT_THREAD_ID_OFFSET,
+           art::mirror::Class::ClinitThreadIdOffset().Int32Value())
diff --git a/tools/cpp-define-generator/runtime.def b/tools/cpp-define-generator/runtime.def
index 2a2e303..fd6567d 100644
--- a/tools/cpp-define-generator/runtime.def
+++ b/tools/cpp-define-generator/runtime.def
@@ -30,3 +30,7 @@
            art::Runtime::GetCalleeSaveMethodOffset(art::CalleeSaveType::kSaveRefsAndArgs))
 ASM_DEFINE(RUNTIME_SAVE_REFS_ONLY_METHOD_OFFSET,
            art::Runtime::GetCalleeSaveMethodOffset(art::CalleeSaveType::kSaveRefsOnly))
+ASM_DEFINE(RUNTIME_INSTRUMENTATION_OFFSET, art::Runtime::GetInstrumentationOffset().Int32Value())
+ASM_DEFINE(RUN_EXIT_HOOKS_OFFSET_FROM_RUNTIME_INSTANCE,
+           art::Runtime::GetInstrumentationOffset().Int32Value() +
+           art::instrumentation::Instrumentation::RunExitHooksOffset().Int32Value())
diff --git a/tools/cpp-define-generator/thread.def b/tools/cpp-define-generator/thread.def
index bae9200..97033fc 100644
--- a/tools/cpp-define-generator/thread.def
+++ b/tools/cpp-define-generator/thread.def
@@ -37,6 +37,8 @@
            (art::WhichPowerOf2(sizeof(art::InterpreterCache::Entry)) - 2))
 ASM_DEFINE(THREAD_IS_GC_MARKING_OFFSET,
            art::Thread::IsGcMarkingOffset<art::kRuntimePointerSize>().Int32Value())
+ASM_DEFINE(THREAD_DEOPT_CHECK_REQUIRED_OFFSET,
+           art::Thread::DeoptCheckRequiredOffset<art::kRuntimePointerSize>().Int32Value())
 ASM_DEFINE(THREAD_LOCAL_ALLOC_STACK_END_OFFSET,
            art::Thread::ThreadLocalAllocStackEndOffset<art::kRuntimePointerSize>().Int32Value())
 ASM_DEFINE(THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET,
@@ -69,3 +71,5 @@
            art::Thread::ReadBarrierMarkEntryPointsOffset<art::kRuntimePointerSize>(0))
 ASM_DEFINE(THREAD_SHARED_METHOD_HOTNESS_OFFSET,
            art::Thread::SharedMethodHotnessOffset<art::kRuntimePointerSize>().Int32Value())
+ASM_DEFINE(THREAD_TID_OFFSET,
+           art::Thread::TidOffset<art::kRuntimePointerSize>().Int32Value())
diff --git a/tools/dexanalyze/Android.bp b/tools/dexanalyze/Android.bp
index 2a625d6..5b87559 100644
--- a/tools/dexanalyze/Android.bp
+++ b/tools/dexanalyze/Android.bp
@@ -49,6 +49,7 @@
     apex_available: [
         "com.android.art",
         "com.android.art.debug",
+        "test_broken_com.android.art",
     ],
 }
 
diff --git a/tools/dexanalyze/dexanalyze.cc b/tools/dexanalyze/dexanalyze.cc
index f0ce4c4..79602aa 100644
--- a/tools/dexanalyze/dexanalyze.cc
+++ b/tools/dexanalyze/dexanalyze.cc
@@ -14,20 +14,21 @@
  * limitations under the License.
  */
 
+#include <android-base/file.h>
+
 #include <cstdint>
 #include <iostream>
 #include <set>
 #include <sstream>
 
-#include <android-base/file.h>
-
-#include "dexanalyze_bytecode.h"
-#include "dexanalyze_experiments.h"
-#include "dexanalyze_strings.h"
+#include "base/mem_map.h"
 #include "dex/code_item_accessors-inl.h"
 #include "dex/dex_file.h"
 #include "dex/dex_file_loader.h"
 #include "dex/dex_instruction-inl.h"
+#include "dexanalyze_bytecode.h"
+#include "dexanalyze_experiments.h"
+#include "dexanalyze_strings.h"
 
 namespace art {
 namespace dexanalyze {
@@ -202,20 +203,18 @@
     for (const std::string& filename : options.filenames_) {
       std::string content;
       // TODO: once added, use an API to android::base to read a std::vector<uint8_t>.
-      if (!android::base::ReadFileToString(filename.c_str(), &content)) {
+      if (!android::base::ReadFileToString(filename, &content)) {
         LOG(ERROR) << "ReadFileToString failed for " + filename << std::endl;
         return kExitCodeFailedToOpenFile;
       }
       std::vector<std::unique_ptr<const DexFile>> dex_files;
-      const DexFileLoader dex_file_loader;
-      if (!dex_file_loader.OpenAll(reinterpret_cast<const uint8_t*>(content.data()),
-                                   content.size(),
-                                   filename.c_str(),
-                                   options.run_dex_file_verifier_,
-                                   options.verify_checksum_,
-                                   &error_code,
-                                   &error_msg,
-                                   &dex_files)) {
+      DexFileLoader dex_file_loader(
+          reinterpret_cast<const uint8_t*>(content.data()), content.size(), filename);
+      if (!dex_file_loader.Open(options.run_dex_file_verifier_,
+                                options.verify_checksum_,
+                                &error_code,
+                                &error_msg,
+                                &dex_files)) {
         LOG(ERROR) << "OpenAll failed for " + filename << " with " << error_msg << std::endl;
         return kExitCodeFailedToOpenDex;
       }
@@ -240,6 +239,7 @@
 }  // namespace art
 
 int main(int argc, char** argv) {
+  art::MemMap::Init();
   return art::dexanalyze::DexAnalyze::Run(argc, argv);
 }
 
diff --git a/tools/dexanalyze/dexanalyze_experiments.cc b/tools/dexanalyze/dexanalyze_experiments.cc
index b124f43..384ab37 100644
--- a/tools/dexanalyze/dexanalyze_experiments.cc
+++ b/tools/dexanalyze/dexanalyze_experiments.cc
@@ -459,6 +459,7 @@
     }
     // Count uses of top 16n.
     std::vector<size_t> uses;
+    uses.reserve(types_accessed.size());
     for (auto&& p : types_accessed) {
       uses.push_back(p.second);
     }
diff --git a/tools/dexanalyze/dexanalyze_test.cc b/tools/dexanalyze/dexanalyze_test.cc
index 9e6ed6d..474615d 100644
--- a/tools/dexanalyze/dexanalyze_test.cc
+++ b/tools/dexanalyze/dexanalyze_test.cc
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#include "common_runtime_test.h"
+#include "base/common_art_test.h"
 #include "exec_utils.h"
 
 namespace art {
 
-class DexAnalyzeTest : public CommonRuntimeTest {
+class DexAnalyzeTest : public CommonArtTest {
  public:
   std::string GetDexAnalyzePath() {
     return GetArtBinDir() + "/dexanalyze";
diff --git a/tools/dexfuzz/.clang-format b/tools/dexfuzz/.clang-format
new file mode 120000
index 0000000..88ab38e
--- /dev/null
+++ b/tools/dexfuzz/.clang-format
@@ -0,0 +1 @@
+../../.clang-format-java-2
\ No newline at end of file
diff --git a/tools/dexfuzz/Android.bp b/tools/dexfuzz/Android.bp
index 02bda0e..083ecd7 100644
--- a/tools/dexfuzz/Android.bp
+++ b/tools/dexfuzz/Android.bp
@@ -33,6 +33,6 @@
 // --- dexfuzz script ----------------
 sh_binary_host {
     name: "dexfuzz-script",
-    src: "dexfuzz",
-    filename_from_src: true,
+    src: "dexfuzz.sh",
+    filename: "dexfuzz",
 }
diff --git a/tools/dexfuzz/dexfuzz b/tools/dexfuzz/dexfuzz.sh
similarity index 100%
rename from tools/dexfuzz/dexfuzz
rename to tools/dexfuzz/dexfuzz.sh
diff --git a/tools/dist_linux_bionic.sh b/tools/dist_linux_bionic.sh
index 4c7ba1c..f710310 100755
--- a/tools/dist_linux_bionic.sh
+++ b/tools/dist_linux_bionic.sh
@@ -19,12 +19,6 @@
 # Builds the given targets using linux-bionic and moves the output files to the
 # DIST_DIR. Takes normal make arguments.
 
-if [[ -z $ANDROID_BUILD_TOP ]]; then
-  pushd .
-else
-  pushd $ANDROID_BUILD_TOP
-fi
-
 if [[ -z $DIST_DIR ]]; then
   echo "DIST_DIR must be set!"
   exit 1
@@ -35,10 +29,12 @@
   exit 1
 fi
 
-source build/envsetup.sh >&/dev/null # for get_build_var
-out_dir=$(get_build_var OUT_DIR)
+vars="$(build/soong/soong_ui.bash --dumpvars-mode --vars="OUT_DIR")"
+# Assign to a variable and eval that, since bash ignores any error status from
+# the command substitution if it's directly on the eval line.
+eval $vars
 
-./art/tools/build_linux_bionic.sh $@
+./art/tools/build_linux_bionic.sh "$@"
 
 mkdir -p $DIST_DIR
-cp -R ${out_dir}/soong/host/* $DIST_DIR/
+cp -R ${OUT_DIR}/soong/host/* $DIST_DIR/
diff --git a/tools/dmtracedump/createtesttrace.cc b/tools/dmtracedump/createtesttrace.cc
index 444cce4..7bb5a7f 100644
--- a/tools/dmtracedump/createtesttrace.cc
+++ b/tools/dmtracedump/createtesttrace.cc
@@ -22,6 +22,7 @@
 #include <assert.h>
 #include <ctype.h>
 #include <errno.h>
+#include <memory>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -114,7 +115,7 @@
  * 2 and increments by 2 for each line.
  */
 void parseInputFile(const char* inputFileName) {
-  FILE* inputFp = fopen(inputFileName, "r");
+  FILE* inputFp = fopen(inputFileName, "re");
   if (inputFp == nullptr) {
     perror(inputFileName);
     exit(1);
@@ -143,7 +144,7 @@
   /* Add space for a sentinel record at the end */
   numRecords += 1;
   records = new dataRecord[numRecords];
-  stack* callStack = new stack[numThreads];
+  std::unique_ptr<stack[]> callStack(new stack[numThreads]);
   for (int32_t ii = 0; ii < numThreads; ++ii) {
     callStack[ii].frames = nullptr;
     callStack[ii].indentLevel = 0;
@@ -395,7 +396,7 @@
 }
 
 void writeTrace(const char* traceFileName) {
-  FILE* fp = fopen(traceFileName, "w");
+  FILE* fp = fopen(traceFileName, "we");
   if (fp == nullptr) {
     perror(traceFileName);
     exit(1);
diff --git a/tools/dmtracedump/tracedump.cc b/tools/dmtracedump/tracedump.cc
index 3cb7374..3385f4a 100644
--- a/tools/dmtracedump/tracedump.cc
+++ b/tools/dmtracedump/tracedump.cc
@@ -1046,7 +1046,7 @@
   for (int32_t i = 0; i < MAX_THREADS; i++)
     traceData.depth[i] = 2;  // adjust for return from start function
 
-  FILE* dataFp = fopen(gOptions.traceFileName, "rb");
+  FILE* dataFp = fopen(gOptions.traceFileName, "rbe");
   if (dataFp == nullptr) return;
 
   DataKeys* pKeys = parseKeys(dataFp, 1);
@@ -1465,7 +1465,7 @@
     snprintf(path, FILENAME_MAX, "dot-%d-%d.dot", (int32_t)time(nullptr), rand());
   }
 
-  FILE* file = fopen(path, "w+");
+  FILE* file = fopen(path, "we+");
 
   fprintf(file, "digraph g {\nnode [shape = record,height=.1];\n");
 
@@ -1490,10 +1490,8 @@
   char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE];
   char signatureBuf[HTML_BUFSIZE];
   char anchor_buf[80];
-  const char* anchor_close = "";
   anchor_buf[0] = 0;
   if (gOptions.outputHtml) {
-    anchor_close = "</a>";
     printf("<a name=\"inclusive\"></a>\n");
     printf("<hr>\n");
     outputNavigationBar();
@@ -2035,7 +2033,7 @@
 DataKeys* parseDataKeys(TraceData* traceData, const char* traceFileName, uint64_t* threadTime) {
   MethodEntry* caller;
 
-  FILE* dataFp = fopen(traceFileName, "rb");
+  FILE* dataFp = fopen(traceFileName, "rbe");
   if (dataFp == nullptr) return nullptr;
 
   DataKeys* dataKeys = parseKeys(dataFp, 0);
diff --git a/tools/external_oj_libjdwp_art_no_read_barrier_failures.txt b/tools/external_oj_libjdwp_art_no_read_barrier_failures.txt
new file mode 100644
index 0000000..920b611
--- /dev/null
+++ b/tools/external_oj_libjdwp_art_no_read_barrier_failures.txt
@@ -0,0 +1,9 @@
+/*
+ * This file contains expectations for ART's buildbot. The purpose of this file is
+ * to temporarily list failing tests and not break the bots.
+ *
+ * This file contains the expectations for the 'libjdwp-aot' and 'libjdwp-jit'
+ * test groups on the chromium buildbot running without read-barrier.
+ */
+[
+]
diff --git a/tools/generate_cmake_lists.py b/tools/generate_cmake_lists.py
index b19c292..3fda003 100755
--- a/tools/generate_cmake_lists.py
+++ b/tools/generate_cmake_lists.py
@@ -32,6 +32,7 @@
 (Also, exclude projects that you don't bother about. This will make
 the indexing faster).
 """
+from __future__ import print_function
 
 import sys
 import os
@@ -47,7 +48,7 @@
     path_to_top = os.path.realpath(path_to_top)
 
   if not os.path.exists(os.path.join(path_to_top, 'build/envsetup.sh')):
-    print path_to_top
+    print(path_to_top)
     raise AssertionError("geneate_cmake_lists.py must be located inside an android source tree")
 
   return path_to_top
diff --git a/tools/generate_operator_out.py b/tools/generate_operator_out.py
index f1491d8..f3de61c 100755
--- a/tools/generate_operator_out.py
+++ b/tools/generate_operator_out.py
@@ -65,7 +65,7 @@
                     continue
 
                 # Is this the start or end of a namespace?
-                m = re.search(r'^namespace (\S+) \{', raw_line)
+                m = re.search(r'^namespace (\S+) (HIDDEN |EXPORT )?\{', raw_line)
                 if m:
                     namespaces.append(m.group(1))
                     continue
diff --git a/tools/golem/build-target.sh b/tools/golem/build-target.sh
index d8ec58b..1ebd7c9 100755
--- a/tools/golem/build-target.sh
+++ b/tools/golem/build-target.sh
@@ -269,6 +269,8 @@
   execute lunch "$lunch_target"
   # Golem uses master-art repository which is missing a lot of other libraries.
   setenv SOONG_ALLOW_MISSING_DEPENDENCIES true
+  # master-art cannot build with Bazel.
+  setenv BUILD_BROKEN_DISABLE_BAZEL true
   # Let the build system know we're not aiming to do a full platform build.
   if [ ! -d frameworks/base ]; then
     setenv TARGET_BUILD_UNBUNDLED true
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index aee3f9a..5c750af 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -16,14 +16,15 @@
 
 #include <fstream>
 #include <iostream>
+#include <iterator>
 #include <map>
 #include <set>
 #include <string>
 #include <string_view>
+#include <vector>
 
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
-
 #include "base/bit_utils.h"
 #include "base/hiddenapi_flags.h"
 #include "base/mem_map.h"
@@ -34,6 +35,7 @@
 #include "dex/art_dex_file_loader.h"
 #include "dex/class_accessor-inl.h"
 #include "dex/dex_file-inl.h"
+#include "dex/dex_file_structs.h"
 
 namespace art {
 namespace hiddenapi {
@@ -244,8 +246,15 @@
 
 class ClassPath final {
  public:
-  ClassPath(const std::vector<std::string>& dex_paths, bool open_writable, bool ignore_empty) {
-    OpenDexFiles(dex_paths, open_writable, ignore_empty);
+  ClassPath(const std::vector<std::string>& dex_paths, bool ignore_empty) {
+    OpenDexFiles(dex_paths, ignore_empty);
+  }
+
+  template <typename Fn>
+  void ForEachDexClass(const DexFile* dex_file, Fn fn) {
+    for (ClassAccessor accessor : dex_file->GetClasses()) {
+      fn(DexClass(accessor));
+    }
   }
 
   template<typename Fn>
@@ -283,47 +292,18 @@
   }
 
  private:
-  void OpenDexFiles(const std::vector<std::string>& dex_paths,
-                    bool open_writable,
-                    bool ignore_empty) {
-    ArtDexFileLoader dex_loader;
+  void OpenDexFiles(const std::vector<std::string>& dex_paths, bool ignore_empty) {
     std::string error_msg;
 
-    if (open_writable) {
-      for (const std::string& filename : dex_paths) {
-        File fd(filename.c_str(), O_RDWR, /* check_usage= */ false);
-        CHECK_NE(fd.Fd(), -1) << "Unable to open file '" << filename << "': " << strerror(errno);
-
-        // Memory-map the dex file with MAP_SHARED flag so that changes in memory
-        // propagate to the underlying file. We run dex file verification as if
-        // the dex file was not in boot claass path to check basic assumptions,
-        // such as that at most one of public/private/protected flag is set.
-        // We do those checks here and skip them when loading the processed file
-        // into boot class path.
-        std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex(fd.Release(),
-                                                                   /* location= */ filename,
-                                                                   /* verify= */ true,
-                                                                   /* verify_checksum= */ true,
-                                                                   /* mmap_shared= */ true,
-                                                                   &error_msg));
-        CHECK(dex_file.get() != nullptr) << "Open failed for '" << filename << "' " << error_msg;
-        CHECK(dex_file->IsStandardDexFile()) << "Expected a standard dex file '" << filename << "'";
-        CHECK(dex_file->EnableWrite())
-            << "Failed to enable write permission for '" << filename << "'";
-        dex_files_.push_back(std::move(dex_file));
-      }
-    } else {
-      for (const std::string& filename : dex_paths) {
-        bool success = dex_loader.Open(filename.c_str(),
-                                       /* location= */ filename,
-                                       /* verify= */ true,
-                                       /* verify_checksum= */ true,
-                                       &error_msg,
-                                       &dex_files_);
-        // If requested ignore a jar with no classes.dex files.
-        if (!success && ignore_empty && error_msg != "Entry not found") {
-          CHECK(success) << "Open failed for '" << filename << "' " << error_msg;
-        }
+    for (const std::string& filename : dex_paths) {
+      DexFileLoader dex_file_loader(filename);
+      bool success = dex_file_loader.Open(/* verify= */ true,
+                                          /* verify_checksum= */ true,
+                                          &error_msg,
+                                          &dex_files_);
+      // If requested ignore a jar with no classes.dex files.
+      if (!success && ignore_empty && error_msg != "Entry not found") {
+        CHECK(success) << "Open failed for '" << filename << "' " << error_msg;
       }
     }
   }
@@ -669,215 +649,125 @@
 // Edits a dex file, inserting a new HiddenapiClassData section.
 class DexFileEditor final {
  public:
-  DexFileEditor(const DexFile& old_dex, const std::vector<uint8_t>& hiddenapi_class_data)
-      : old_dex_(old_dex),
-        hiddenapi_class_data_(hiddenapi_class_data),
-        loaded_dex_header_(nullptr),
-        loaded_dex_maplist_(nullptr) {}
-
-  // Copies dex file into a backing data vector, appends the given HiddenapiClassData
-  // and updates the MapList.
-  void Encode() {
+  // Add dex file to copy to output (possibly several files for multi-dex).
+  void Add(const DexFile* dex, const std::vector<uint8_t>&& hiddenapi_data) {
     // We do not support non-standard dex encodings, e.g. compact dex.
-    CHECK(old_dex_.IsStandardDexFile());
-
-    // If there are no data to append, copy the old dex file and return.
-    if (hiddenapi_class_data_.empty()) {
-      AllocateMemory(old_dex_.Size());
-      Append(old_dex_.Begin(), old_dex_.Size(), /* update_header= */ false);
-      return;
-    }
-
-    // Find the old MapList, find its size.
-    const dex::MapList* old_map = old_dex_.GetMapList();
-    CHECK_LT(old_map->size_, std::numeric_limits<uint32_t>::max());
-
-    // Compute the size of the new dex file. We append the HiddenapiClassData,
-    // one MapItem and possibly some padding to align the new MapList.
-    CHECK(IsAligned<kMapListAlignment>(old_dex_.Size()))
-        << "End of input dex file is not 4-byte aligned, possibly because its MapList is not "
-        << "at the end of the file.";
-    size_t size_delta =
-        RoundUp(hiddenapi_class_data_.size(), kMapListAlignment) + sizeof(dex::MapItem);
-    size_t new_size = old_dex_.Size() + size_delta;
-    AllocateMemory(new_size);
-
-    // Copy the old dex file into the backing data vector. Load the copied
-    // dex file to obtain pointers to its header and MapList.
-    Append(old_dex_.Begin(), old_dex_.Size(), /* update_header= */ false);
-    ReloadDex(/* verify= */ false);
-
-    // Truncate the new dex file before the old MapList. This assumes that
-    // the MapList is the last entry in the dex file. This is currently true
-    // for our tooling.
-    // TODO: Implement the general case by zero-ing the old MapList (turning
-    // it into padding.
-    RemoveOldMapList();
-
-    // Append HiddenapiClassData.
-    size_t payload_offset = AppendHiddenapiClassData();
-
-    // Wrute new MapList with an entry for HiddenapiClassData.
-    CreateMapListWithNewItem(payload_offset);
-
-    // Check that the pre-computed size matches the actual size.
-    CHECK_EQ(offset_, new_size);
-
-    // Reload to all data structures.
-    ReloadDex(/* verify= */ false);
-
-    // Update the dex checksum.
-    UpdateChecksum();
-
-    // Run DexFileVerifier on the new dex file as a CHECK.
-    ReloadDex(/* verify= */ true);
+    CHECK(dex->IsStandardDexFile());
+    inputs_.emplace_back(dex, std::move(hiddenapi_data));
   }
 
   // Writes the edited dex file into a file.
   void WriteTo(const std::string& path) {
-    CHECK(!data_.empty());
+    std::vector<uint8_t> output;
+
+    // Copy the old dex files into the backing data vector.
+    size_t truncated_size = 0;
+    std::vector<size_t> header_offset;
+    for (size_t i = 0; i < inputs_.size(); i++) {
+      const DexFile* dex = inputs_[i].first;
+      header_offset.push_back(output.size());
+      std::copy(
+          dex->Begin(), dex->Begin() + dex->GetHeader().file_size_, std::back_inserter(output));
+
+      // Clear the old map list (make it into padding).
+      const dex::MapList* map = dex->GetMapList();
+      size_t map_off = dex->GetHeader().map_off_;
+      size_t map_size = sizeof(map->size_) + map->size_ * sizeof(map->list_[0]);
+      CHECK_LE(map_off, output.size()) << "Map list past the end of file";
+      CHECK_EQ(map_size, output.size() - map_off) << "Map list expected at the end of file";
+      std::fill_n(output.data() + map_off, map_size, 0);
+      truncated_size = output.size() - map_size;
+    }
+    output.resize(truncated_size);  // Truncate last map list.
+
+    // Append the hidden api data into the backing data vector.
+    std::vector<size_t> hiddenapi_offset;
+    for (size_t i = 0; i < inputs_.size(); i++) {
+      const std::vector<uint8_t>& hiddenapi_data = inputs_[i].second;
+      output.resize(RoundUp(output.size(), kHiddenapiClassDataAlignment));  // Align.
+      hiddenapi_offset.push_back(output.size());
+      std::copy(hiddenapi_data.begin(), hiddenapi_data.end(), std::back_inserter(output));
+    }
+
+    // Update the dex headers and map lists.
+    for (size_t i = 0; i < inputs_.size(); i++) {
+      output.resize(RoundUp(output.size(), kMapListAlignment));  // Align.
+
+      const DexFile* dex = inputs_[i].first;
+      const dex::MapList* map = dex->GetMapList();
+      std::vector<dex::MapItem> items(map->list_, map->list_ + map->size_);
+
+      // Check the header entry.
+      CHECK(!items.empty());
+      CHECK_EQ(items[0].type_, DexFile::kDexTypeHeaderItem);
+      CHECK_EQ(items[0].offset_, header_offset[i]);
+
+      // Check and remove the old map list entry (it does not have to be last).
+      auto is_map_list = [](auto it) { return it.type_ == DexFile::kDexTypeMapList; };
+      auto it = std::find_if(items.begin(), items.end(), is_map_list);
+      CHECK(it != items.end());
+      CHECK_EQ(it->offset_, dex->GetHeader().map_off_);
+      items.erase(it);
+
+      // Write new map list.
+      if (!inputs_[i].second.empty()) {
+        uint32_t payload_offset = hiddenapi_offset[i];
+        items.push_back(dex::MapItem{DexFile::kDexTypeHiddenapiClassData, 0, 1u, payload_offset});
+      }
+      uint32_t map_offset = output.size();
+      items.push_back(dex::MapItem{DexFile::kDexTypeMapList, 0, 1u, map_offset});
+      uint32_t item_count = items.size();
+      Append(&output, &item_count, 1);
+      Append(&output, items.data(), items.size());
+
+      // Update header.
+      uint8_t* begin = output.data() + header_offset[i];
+      auto* header = reinterpret_cast<DexFile::Header*>(begin);
+      header->map_off_ = map_offset;
+      if (i + 1 < inputs_.size()) {
+        CHECK_EQ(header->file_size_, header_offset[i + 1] - header_offset[i]);
+      } else {
+        // Extend last dex file until the end of the file.
+        header->data_size_ = output.size() - header->data_off_;
+        header->file_size_ = output.size() - header_offset[i];
+      }
+      header->checksum_ = DexFile::CalculateChecksum(begin, header->file_size_);
+      // TODO: We should also update the SHA1 signature.
+    }
+
+    // Write the output file.
+    CHECK(!output.empty());
     std::ofstream ofs(path.c_str(), std::ofstream::out | std::ofstream::binary);
-    ofs.write(reinterpret_cast<const char*>(data_.data()), data_.size());
+    ofs.write(reinterpret_cast<const char*>(output.data()), output.size());
     ofs.flush();
     CHECK(ofs.good());
     ofs.close();
+
+    ReloadDex(path.c_str());
   }
 
  private:
   static constexpr size_t kMapListAlignment = 4u;
   static constexpr size_t kHiddenapiClassDataAlignment = 4u;
 
-  void ReloadDex(bool verify) {
+  void ReloadDex(const char* filename) {
     std::string error_msg;
-    DexFileLoader loader;
-    loaded_dex_ = loader.Open(
-        data_.data(),
-        data_.size(),
-        "test_location",
-        old_dex_.GetLocationChecksum(),
-        /* oat_dex_file= */ nullptr,
-        /* verify= */ verify,
-        /* verify_checksum= */ verify,
-        &error_msg);
-    if (loaded_dex_.get() == nullptr) {
-      LOG(FATAL) << "Failed to load edited dex file: " << error_msg;
-      UNREACHABLE();
-    }
-
-    // Load the location of header and map list before we start editing the file.
-    loaded_dex_header_ = const_cast<DexFile::Header*>(&loaded_dex_->GetHeader());
-    loaded_dex_maplist_ = const_cast<dex::MapList*>(loaded_dex_->GetMapList());
+    ArtDexFileLoader loader(filename);
+    std::vector<std::unique_ptr<const DexFile>> dex_files;
+    bool ok = loader.Open(/*verify*/ true,
+                          /*verify_checksum*/ true,
+                          &error_msg,
+                          &dex_files);
+    CHECK(ok) << "Failed to load edited dex file: " << error_msg;
   }
 
-  DexFile::Header& GetHeader() const {
-    CHECK(loaded_dex_header_ != nullptr);
-    return *loaded_dex_header_;
+  template <typename T>
+  void Append(std::vector<uint8_t>* output, const T* src, size_t len) {
+    const uint8_t* ptr = reinterpret_cast<const uint8_t*>(src);
+    std::copy(ptr, ptr + len * sizeof(T), std::back_inserter(*output));
   }
 
-  dex::MapList& GetMapList() const {
-    CHECK(loaded_dex_maplist_ != nullptr);
-    return *loaded_dex_maplist_;
-  }
-
-  void AllocateMemory(size_t total_size) {
-    data_.clear();
-    data_.resize(total_size);
-    CHECK(IsAligned<kMapListAlignment>(data_.data()));
-    CHECK(IsAligned<kHiddenapiClassDataAlignment>(data_.data()));
-    offset_ = 0;
-  }
-
-  uint8_t* GetCurrentDataPtr() {
-    return data_.data() + offset_;
-  }
-
-  void UpdateDataSize(off_t delta, bool update_header) {
-    offset_ += delta;
-    if (update_header) {
-      DexFile::Header& header = GetHeader();
-      header.file_size_ += delta;
-      header.data_size_ += delta;
-    }
-  }
-
-  template<typename T>
-  T* Append(const T* src, size_t len, bool update_header = true) {
-    CHECK_LE(offset_ + len, data_.size());
-    uint8_t* dst = GetCurrentDataPtr();
-    memcpy(dst, src, len);
-    UpdateDataSize(len, update_header);
-    return reinterpret_cast<T*>(dst);
-  }
-
-  void InsertPadding(size_t alignment) {
-    size_t len = RoundUp(offset_, alignment) - offset_;
-    std::vector<uint8_t> padding(len, 0);
-    Append(padding.data(), padding.size());
-  }
-
-  void RemoveOldMapList() {
-    size_t map_size = GetMapList().Size();
-    uint8_t* map_start = reinterpret_cast<uint8_t*>(&GetMapList());
-    CHECK_EQ(map_start + map_size, GetCurrentDataPtr()) << "MapList not at the end of dex file";
-    UpdateDataSize(-static_cast<off_t>(map_size), /* update_header= */ true);
-    CHECK_EQ(map_start, GetCurrentDataPtr());
-    loaded_dex_maplist_ = nullptr;  // do not use this map list any more
-  }
-
-  void CreateMapListWithNewItem(size_t payload_offset) {
-    InsertPadding(/* alignment= */ kMapListAlignment);
-
-    size_t new_map_offset = offset_;
-    dex::MapList* map = Append(old_dex_.GetMapList(), old_dex_.GetMapList()->Size());
-
-    // Check last map entry is a pointer to itself.
-    dex::MapItem& old_item = map->list_[map->size_ - 1];
-    CHECK(old_item.type_ == DexFile::kDexTypeMapList);
-    CHECK_EQ(old_item.size_, 1u);
-    CHECK_EQ(old_item.offset_, GetHeader().map_off_);
-
-    // Create a new MapItem entry with new MapList details.
-    dex::MapItem new_item;
-    new_item.type_ = old_item.type_;
-    new_item.unused_ = 0u;  // initialize to ensure dex output is deterministic (b/119308882)
-    new_item.size_ = old_item.size_;
-    new_item.offset_ = new_map_offset;
-
-    // Update pointer in the header.
-    GetHeader().map_off_ = new_map_offset;
-
-    // Append a new MapItem and return its pointer.
-    map->size_++;
-    Append(&new_item, sizeof(dex::MapItem));
-
-    // Change penultimate entry to point to metadata.
-    old_item.type_ = DexFile::kDexTypeHiddenapiClassData;
-    old_item.size_ = 1u;  // there is only one section
-    old_item.offset_ = payload_offset;
-  }
-
-  size_t AppendHiddenapiClassData() {
-    size_t payload_offset = offset_;
-    CHECK_EQ(kMapListAlignment, kHiddenapiClassDataAlignment);
-    CHECK(IsAligned<kHiddenapiClassDataAlignment>(payload_offset))
-        << "Should not need to align the section, previous data was already aligned";
-    Append(hiddenapi_class_data_.data(), hiddenapi_class_data_.size());
-    return payload_offset;
-  }
-
-  void UpdateChecksum() {
-    GetHeader().checksum_ = loaded_dex_->CalculateChecksum();
-  }
-
-  const DexFile& old_dex_;
-  const std::vector<uint8_t>& hiddenapi_class_data_;
-
-  std::vector<uint8_t> data_;
-  size_t offset_;
-
-  std::unique_ptr<const DexFile> loaded_dex_;
-  DexFile::Header* loaded_dex_header_;
-  dex::MapList* loaded_dex_maplist_;
+  std::vector<std::pair<const DexFile*, const std::vector<uint8_t>>> inputs_;
 };
 
 class HiddenApi final {
@@ -991,48 +881,41 @@
       const std::string& input_path = boot_dex_paths_[i];
       const std::string& output_path = output_dex_paths_[i];
 
-      ClassPath boot_classpath({ input_path },
-                               /* open_writable= */ false,
-                               /* ignore_empty= */ false);
-      std::vector<const DexFile*> input_dex_files = boot_classpath.GetDexFiles();
-      CHECK_EQ(input_dex_files.size(), 1u);
-      const DexFile& input_dex = *input_dex_files[0];
-
-      HiddenapiClassDataBuilder builder(input_dex);
-      boot_classpath.ForEachDexClass([&](const DexClass& boot_class) {
-        builder.BeginClassDef(boot_class.GetClassDefIndex());
-        if (boot_class.GetData() != nullptr) {
-          auto fn_shared = [&](const DexMember& boot_member) {
-            auto signature = boot_member.GetApiEntry();
-            auto it = api_list.find(signature);
-            bool api_list_found = (it != api_list.end());
-            CHECK(!force_assign_all_ || api_list_found)
-                << "Could not find hiddenapi flags for dex entry: " << signature;
-            if (api_list_found && it->second.GetIntValue() > max_hiddenapi_level_.GetIntValue()) {
-              ApiList without_domain(it->second.GetIntValue());
-              LOG(ERROR) << "Hidden api flag " << without_domain
-                         << " for member " << signature
-                         << " in " << input_path
-                         << " exceeds maximum allowable flag "
-                         << max_hiddenapi_level_;
-              max_hiddenapi_level_error = true;
-            } else {
-              builder.WriteFlags(api_list_found ? it->second : ApiList::Sdk());
-            }
-          };
-          auto fn_field = [&](const ClassAccessor::Field& boot_field) {
-            fn_shared(DexMember(boot_class, boot_field));
-          };
-          auto fn_method = [&](const ClassAccessor::Method& boot_method) {
-            fn_shared(DexMember(boot_class, boot_method));
-          };
-          boot_class.VisitFieldsAndMethods(fn_field, fn_field, fn_method, fn_method);
-        }
-        builder.EndClassDef(boot_class.GetClassDefIndex());
-      });
-
-      DexFileEditor dex_editor(input_dex, builder.GetData());
-      dex_editor.Encode();
+      ClassPath boot_classpath({input_path}, /* ignore_empty= */ false);
+      DexFileEditor dex_editor;
+      for (const DexFile* input_dex : boot_classpath.GetDexFiles()) {
+        HiddenapiClassDataBuilder builder(*input_dex);
+        boot_classpath.ForEachDexClass(input_dex, [&](const DexClass& boot_class) {
+          builder.BeginClassDef(boot_class.GetClassDefIndex());
+          if (boot_class.GetData() != nullptr) {
+            auto fn_shared = [&](const DexMember& boot_member) {
+              auto signature = boot_member.GetApiEntry();
+              auto it = api_list.find(signature);
+              bool api_list_found = (it != api_list.end());
+              CHECK(!force_assign_all_ || api_list_found)
+                  << "Could not find hiddenapi flags for dex entry: " << signature;
+              if (api_list_found && it->second.GetIntValue() > max_hiddenapi_level_.GetIntValue()) {
+                ApiList without_domain(it->second.GetIntValue());
+                LOG(ERROR) << "Hidden api flag " << without_domain << " for member " << signature
+                           << " in " << input_path << " exceeds maximum allowable flag "
+                           << max_hiddenapi_level_;
+                max_hiddenapi_level_error = true;
+              } else {
+                builder.WriteFlags(api_list_found ? it->second : ApiList::Sdk());
+              }
+            };
+            auto fn_field = [&](const ClassAccessor::Field& boot_field) {
+              fn_shared(DexMember(boot_class, boot_field));
+            };
+            auto fn_method = [&](const ClassAccessor::Method& boot_method) {
+              fn_shared(DexMember(boot_class, boot_method));
+            };
+            boot_class.VisitFieldsAndMethods(fn_field, fn_field, fn_method, fn_method);
+          }
+          builder.EndClassDef(boot_class.GetClassDefIndex());
+        });
+        dex_editor.Add(input_dex, std::move(builder.GetData()));
+      }
       dex_editor.WriteTo(output_path);
     }
 
@@ -1057,6 +940,7 @@
     std::map<std::string, ApiList> api_flag_map;
 
     size_t line_number = 1;
+    bool errors = false;
     for (std::string line; std::getline(api_file, line); line_number++) {
       // Every line contains a comma separated list with the signature as the
       // first element and the api flags as the rest
@@ -1074,13 +958,21 @@
       std::vector<std::string>::iterator apiListBegin = values.begin() + 1;
       std::vector<std::string>::iterator apiListEnd = values.end();
       bool success = ApiList::FromNames(apiListBegin, apiListEnd, &membership);
-      CHECK(success) << path << ":" << line_number
-          << ": Some flags were not recognized: " << line << kErrorHelp;
-      CHECK(membership.IsValid()) << path << ":" << line_number
-          << ": Invalid combination of flags: " << line << kErrorHelp;
+      if (!success) {
+        LOG(ERROR) << path << ":" << line_number
+            << ": Some flags were not recognized: " << line << kErrorHelp;
+        errors = true;
+        continue;
+      } else if (!membership.IsValid()) {
+        LOG(ERROR) << path << ":" << line_number
+            << ": Invalid combination of flags: " << line << kErrorHelp;
+        errors = true;
+        continue;
+      }
 
       api_flag_map.emplace(signature, membership);
     }
+    CHECK(!errors) << "Errors encountered while parsing file " << path;
 
     api_file.close();
     return api_flag_map;
@@ -1107,9 +999,7 @@
     std::set<std::string> unresolved;
 
     // Open all dex files.
-    ClassPath boot_classpath(boot_dex_paths_,
-                             /* open_writable= */ false,
-                             /* ignore_empty= */ false);
+    ClassPath boot_classpath(boot_dex_paths_, /* ignore_empty= */ false);
     Hierarchy boot_hierarchy(boot_classpath, fragment_, verbose_);
 
     // Mark all boot dex members private.
@@ -1118,9 +1008,7 @@
     });
 
     // Open all dependency API stub dex files.
-    ClassPath dependency_classpath(dependency_stub_dex_paths_,
-                                   /* open_writable= */ false,
-                                   /* ignore_empty= */ false);
+    ClassPath dependency_classpath(dependency_stub_dex_paths_, /* ignore_empty= */ false);
 
     // Mark all dependency API stub dex members as coming from the dependency.
     dependency_classpath.ForEachDexMember([&](const DexMember& boot_member) {
@@ -1132,9 +1020,7 @@
       // Ignore any empty stub jars as it just means that they provide no APIs
       // for the current kind, e.g. framework-sdkextensions does not provide
       // any public APIs.
-      ClassPath stub_classpath(android::base::Split(cp_entry.first, ":"),
-                               /* open_writable= */ false,
-                               /* ignore_empty= */ true);
+      ClassPath stub_classpath(android::base::Split(cp_entry.first, ":"), /*ignore_empty=*/true);
       Hierarchy stub_hierarchy(stub_classpath, fragment_, verbose_);
       const ApiStubs::Kind stub_api = cp_entry.second;
 
diff --git a/tools/hiddenapi/hiddenapi_test.cc b/tools/hiddenapi/hiddenapi_test.cc
index 3a0e625..36e80c5 100644
--- a/tools/hiddenapi/hiddenapi_test.cc
+++ b/tools/hiddenapi/hiddenapi_test.cc
@@ -106,7 +106,6 @@
   }
 
   std::unique_ptr<const DexFile> OpenDex(const ScratchFile& file) {
-    ArtDexFileLoader dex_loader;
     std::string error_msg;
 
     File fd(file.GetFilename(), O_RDONLY, /* check_usage= */ false);
@@ -115,9 +114,9 @@
       UNREACHABLE();
     }
 
-    std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex(
-        fd.Release(), /* location= */ file.GetFilename(), /* verify= */ true,
-        /* verify_checksum= */ true, /* mmap_shared= */ false, &error_msg));
+    ArtDexFileLoader dex_loader(fd.Release(), file.GetFilename());
+    std::unique_ptr<const DexFile> dex_file(dex_loader.Open(
+        /* verify= */ true, /* verify_checksum= */ true, /* mmap_shared= */ false, &error_msg));
     if (dex_file.get() == nullptr) {
       LOG(FATAL) << "Open failed for '" << file.GetFilename() << "' " << error_msg;
       UNREACHABLE();
@@ -143,7 +142,7 @@
     std::map<std::string, std::string> flags;
 
     for (std::string line; std::getline(ifs, line);) {
-      std::size_t comma = line.find(",");
+      std::size_t comma = line.find(',');
       if (comma == std::string::npos) {
         flags.emplace(line, "");
       } else {
diff --git a/tools/host_bcp.sh b/tools/host_bcp.sh
index 26231cd..62ebae7 100755
--- a/tools/host_bcp.sh
+++ b/tools/host_bcp.sh
@@ -14,27 +14,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-if [[ ${#@} != 1 ]] && [[ ${#@} != 2 ]]; then
+if [[ ${#@} != 1 ]]; then
   cat <<EOF
 Usage
-  host_bcp <image> [--use-first-dir] | xargs <art-host-tool> ...
+  host_bcp <image> | xargs <art-host-tool> ...
 Extracts boot class path locations from <image> and outputs the appropriate
   --runtime-arg -Xbootclasspath:...
   --runtime-arg -Xbootclasspath-locations:...
 arguments for many ART host tools based on the \$ANDROID_PRODUCT_OUT variable
-and existing \$ANDROID_PRODUCT_OUT/apex/com.android.art* paths.
-If --use-first-dir is specified, the script will use the first apex dir instead
-of resulting in an error.
+and existing \$ANDROID_PRODUCT_OUT/apex/* paths.
 EOF
   exit 1
 fi
 
 IMAGE=$1
-USE_FIRST_DIR=false
-
-if [[ $2 == "--use-first-dir" ]]; then
-  USE_FIRST_DIR=true
-fi
 
 if [[ ! -e ${IMAGE} ]]; then
   IMAGE=${ANDROID_PRODUCT_OUT}/$1
@@ -50,34 +43,33 @@
   exit 1
 fi
 
-MANIFEST=/apex_manifest.pb
-ART_APEX=/apex/com.android.art
-ART_APEX_SELECTED=
-for m in `ls -1 -d ${ANDROID_PRODUCT_OUT}{,/system}${ART_APEX}*${MANIFEST} 2>/dev/null`; do
-  d=${m:0:-${#MANIFEST}}
-  if [[ "x${ART_APEX_SELECTED}" != "x" ]]; then
-    if [[ $USE_FIRST_DIR == true ]]; then
-      break
-    fi
-    echo "Multiple ART APEX dirs: ${ART_APEX_SELECTED}, ${d}."
-    exit 1
-  fi
-  ART_APEX_SELECTED=${d}
-done
-if [[ "x${ART_APEX_SELECTED}" == "x" ]]; then
-  echo "No ART APEX dir."
+APEX_INFO_LIST=${ANDROID_PRODUCT_OUT}/apex/apex-info-list.xml
+if [[ ! -e ${APEX_INFO_LIST} ]]; then
+  echo "Failed to locate apex info at ${APEX_INFO_LIST}."
   exit 1
 fi
 
 BCP=
 OLD_IFS=${IFS}
 IFS=:
+APEX_PREFIX=/apex/
 for COMPONENT in ${BCPL}; do
   HEAD=${ANDROID_PRODUCT_OUT}
   TAIL=${COMPONENT}
-  if [[ ${COMPONENT:0:${#ART_APEX}} = ${ART_APEX} ]]; then
-    HEAD=${ART_APEX_SELECTED}
-    TAIL=${COMPONENT:${#ART_APEX}}
+  # Apex module paths aren't symlinked on the host, so map from the symbolic
+  # device path to the prebuilt (host) module path using the apex info table.
+  if [[ ${COMPONENT:0:${#APEX_PREFIX}} = ${APEX_PREFIX} ]]; then
+    # First extract the symbolic module name and its (internal) jar path.
+    COMPONENT=${COMPONENT#${APEX_PREFIX}}
+    MODULE_NAME=${COMPONENT%%/*}
+    MODULE_JAR=${COMPONENT#*/}
+    # Use the module name to look up the preinstalled module path..
+    HOST_MODULE=`xmllint --xpath "string(//apex-info[@moduleName=\"${MODULE_NAME}\"]/@preinstalledModulePath)" ${APEX_INFO_LIST}`
+    # Extract the preinstalled module name from the full path (strip prefix/suffix).
+    HOST_MODULE_NAME=${HOST_MODULE#*${APEX_PREFIX}}
+    HOST_MODULE_NAME=${HOST_MODULE_NAME%.*apex}
+    # Rebuild the host path using the preinstalled module name.
+    TAIL="${APEX_PREFIX}${HOST_MODULE_NAME}/${MODULE_JAR}"
   fi
   if [[ ! -e $HEAD$TAIL ]]; then
     echo "File does not exist: $HEAD$TAIL"
diff --git a/tools/jvmti-agents/chain-agents/chainagents.cc b/tools/jvmti-agents/chain-agents/chainagents.cc
index 1242409..d272fc1 100644
--- a/tools/jvmti-agents/chain-agents/chainagents.cc
+++ b/tools/jvmti-agents/chain-agents/chainagents.cc
@@ -53,7 +53,7 @@
   OnLoad,
 };
 
-static std::pair<std::string, std::string> Split(std::string source, char delim) {
+static std::pair<std::string, std::string> Split(const std::string& source, char delim) {
   std::string first(source.substr(0, source.find(delim)));
   if (source.find(delim) == std::string::npos) {
     return std::pair(first, "");
diff --git a/tools/jvmti-agents/field-counts/fieldcount.cc b/tools/jvmti-agents/field-counts/fieldcount.cc
index c31a973..5a4b00e 100644
--- a/tools/jvmti-agents/field-counts/fieldcount.cc
+++ b/tools/jvmti-agents/field-counts/fieldcount.cc
@@ -182,7 +182,7 @@
               << "\t" << "<ALL_TYPES>"
               << "\t" << obj_len
               << "\t" << total_size;
-    for (auto sz : class_sizes) {
+    for (const std::pair<std::string, size_t> sz : class_sizes) {
       size_t count = class_counts[sz.first];
       LOG(INFO) << "\t" << field_class_name << "." << field_name << ":" << field_sig
                 << "\t" << sz.first
diff --git a/tools/jvmti-agents/simple-force-redefine/forceredefine.cc b/tools/jvmti-agents/simple-force-redefine/forceredefine.cc
index 055fb8a..3474238 100644
--- a/tools/jvmti-agents/simple-force-redefine/forceredefine.cc
+++ b/tools/jvmti-agents/simple-force-redefine/forceredefine.cc
@@ -94,7 +94,7 @@
   jvmtiEnv* jvmti_;
 };
 
-static void Transform(std::shared_ptr<ir::DexFile> ir) {
+static void Transform(const std::shared_ptr<ir::DexFile>& ir) {
   std::unique_ptr<ir::Builder> builder;
   for (auto& method : ir->encoded_methods) {
     // Do not look into abstract/bridge/native/synthetic methods.
diff --git a/tools/jvmti-agents/simple-profile/simple_profile.cc b/tools/jvmti-agents/simple-profile/simple_profile.cc
index 5ead97e..7161142 100644
--- a/tools/jvmti-agents/simple-profile/simple_profile.cc
+++ b/tools/jvmti-agents/simple-profile/simple_profile.cc
@@ -26,6 +26,7 @@
 #include <sstream>
 #include <string>
 #include <unordered_map>
+#include <utility>
 #include <vector>
 
 #include "android-base/unique_fd.h"
@@ -55,7 +56,7 @@
   SimpleProfileData(
       jvmtiEnv* env, std::string out_fd_name, int fd, bool dump_on_shutdown, bool dump_on_main_stop)
       : dump_id_(0),
-        out_fd_name_(out_fd_name),
+        out_fd_name_(std::move(out_fd_name)),
         out_fd_(fd),
         shutdown_(false),
         dump_on_shutdown_(dump_on_shutdown || dump_on_main_stop),
@@ -79,7 +80,7 @@
   void Shutdown(jvmtiEnv* jvmti, JNIEnv* jni);
 
  private:
-  void DoDump(jvmtiEnv* jvmti, JNIEnv* jni, std::unordered_map<jmethodID, uint64_t> copy);
+  void DoDump(jvmtiEnv* jvmti, JNIEnv* jni, const std::unordered_map<jmethodID, uint64_t>& copy);
 
   jlong dump_id_;
   jrawMonitorID mon_;
@@ -320,7 +321,7 @@
 
 void SimpleProfileData::DoDump(jvmtiEnv* jvmti,
                                JNIEnv* jni,
-                               std::unordered_map<jmethodID, uint64_t> copy) {
+                               const std::unordered_map<jmethodID, uint64_t>& copy) {
   std::ostringstream oss;
   oss << "[";
   bool is_first = true;
diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt
index 6e6ccb8..fc28acd 100644
--- a/tools/libcore_failures.txt
+++ b/tools/libcore_failures.txt
@@ -330,5 +330,27 @@
   bug: 228441328,
   names: ["tck.java.time",
           "test.java.time"]
+},
+{
+  description: "Timing out after ojluni tests were enabled",
+  result: ERROR,
+  bug: 231439593,
+  names: ["org.apache.harmony.tests.java.math.BigIntegerConstructorsTest#testConstructorPrime"]
+},
+{
+  description: "libcore.android.system.OsConstantsTest CAP constants tests work on device only",
+  result: EXEC_FAILED,
+  modes: [host],
+  names: ["libcore.android.system.OsConstantsTest#test_CAP_TO_INDEX",
+          "libcore.android.system.OsConstantsTest#test_CAP_TO_MASK",
+          "libcore.android.system.OsConstantsTest#test_CAP_constants"]
+},
+{
+   description: "Record test doens't work properly on vogar/",
+   result: EXEC_FAILED,
+   bug: 272698028,
+   names: ["libcore.java.lang.RecordTest",
+           "crossvmtest.java.lang.RecordComponentTest",
+           "crossvmtest.java.lang.RecordTest"]
 }
 ]
diff --git a/tools/libcore_fugu_failures.txt b/tools/libcore_fugu_failures.txt
index 0fff814..60b43d0 100644
--- a/tools/libcore_fugu_failures.txt
+++ b/tools/libcore_fugu_failures.txt
@@ -25,6 +25,8 @@
   names: [
     "libcore.java.math.BigIntegerTest#test_Constructor_IILjava_util_Random",
     "libcore.java.math.BigIntegerTest#test_probablePrime",
+    "libcore.java.util.UUIDTest#testJava11Implementation_invalidInputs",
+    "libcore.java.util.UUIDTest#testJava8Implementation_allowsLongInputs",
     "libcore.javax.crypto.CipherInputStreamTest#testDecryptCorruptGCM",
     "libcore.javax.crypto.CipherOutputStreamTest#testDecryptCorruptGCM",
     "libcore.libcore.timezone.TelephonyLookupTest#createInstanceWithFallback",
@@ -112,7 +114,6 @@
     "org.apache.harmony.crypto.tests.javax.crypto.func.CipherRSATest#test_RSANoPadding",
     "org.apache.harmony.crypto.tests.javax.crypto.func.CipherRSATest#test_RSAShortKey",
     "org.apache.harmony.crypto.tests.javax.crypto.func.KeyGeneratorFunctionalTest#test_",
-    "org.apache.harmony.tests.java.math.BigIntegerConstructorsTest#testConstructorPrime",
     "org.apache.harmony.tests.java.math.BigIntegerTest#test_isProbablePrimeI",
     "org.apache.harmony.tests.java.math.OldBigIntegerTest#test_ConstructorIILjava_util_Random",
     "org.apache.harmony.tests.java.math.OldBigIntegerTest#test_isProbablePrimeI",
@@ -127,5 +128,109 @@
     "org.apache.harmony.tests.javax.security.OldSHA1PRNGSecureRandomTest#testNextBytesbyteArray03",
     "org.apache.harmony.tests.javax.security.OldSHA1PRNGSecureRandomTest#testSetSeedbyteArray02"
   ]
+},
+{
+  description: "Test using the getrandom() syscall, only available from Linux 3.17.",
+  result: ERROR,
+  bug: 141230711,
+  modes: [device],
+  names: [
+    "test.java.awt",
+    "test.java.io.ByteArrayInputStream",
+    "test.java.io.ByteArrayOutputStream",
+    "test.java.io.FileReader",
+    "test.java.io.FileWriter",
+    "test.java.io.InputStream",
+    "test.java.io.OutputStream",
+    "test.java.io.PrintStream",
+    "test.java.io.PrintWriter",
+    "test.java.io.Reader",
+    "test.java.io.Writer",
+    "test.java.lang.Boolean",
+    "test.java.lang.ClassLoader",
+    "test.java.lang.Double",
+    "test.java.lang.Float",
+    "test.java.lang.Integer",
+    "test.java.lang.Long",
+    "test.java.lang.StackWalker#main",
+    "test.java.lang.StrictMath.CubeRootTests",
+    "test.java.lang.StrictMath.ExactArithTests",
+    "test.java.lang.StrictMath.Expm1Tests",
+    "test.java.lang.StrictMath.ExpTests",
+    "test.java.lang.StrictMath.HyperbolicTests",
+    "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard1",
+    "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard2",
+    "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard3",
+    "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard4",
+    "test.java.lang.StrictMath.HypotTests#testHypot",
+    "test.java.lang.StrictMath.Log1pTests",
+    "test.java.lang.StrictMath.Log10Tests",
+    "test.java.lang.StrictMath.MultiplicationTests",
+    "test.java.lang.StrictMath.PowTests",
+    "test.java.lang.String",
+    "test.java.lang.Thread",
+    "test.java.lang.invoke",
+    "test.java.lang.ref.SoftReference",
+    "test.java.lang.ref.BasicTest",
+    "test.java.lang.ref.EnqueueNullRefTest",
+    "test.java.lang.ref.EnqueuePollRaceTest",
+    "test.java.lang.ref.ReferenceCloneTest",
+    "test.java.lang.ref.ReferenceEnqueuePendingTest",
+    "test.java.math.BigDecimal",
+    "test.java.math.BigInteger#testArithmetic",
+    "test.java.math.BigInteger#testBitCount",
+    "test.java.math.BigInteger#testBitLength",
+    "test.java.math.BigInteger#testbitOps",
+    "test.java.math.BigInteger#testBitwise",
+    "test.java.math.BigInteger#testByteArrayConv",
+    "test.java.math.BigInteger#testConstructor",
+    "test.java.math.BigInteger#testDivideAndReminder",
+    "test.java.math.BigInteger#testDivideLarge",
+    "test.java.math.BigInteger#testModExp",
+    "test.java.math.BigInteger#testMultiplyLarge",
+    "test.java.math.BigInteger#testNextProbablePrime",
+    "test.java.math.BigInteger#testPow",
+    "test.java.math.BigInteger#testSerialize",
+    "test.java.math.BigInteger#testShift",
+    "test.java.math.BigInteger#testSquare",
+    "test.java.math.BigInteger#testSquareLarge",
+    "test.java.math.BigInteger#testSquareRootAndReminder",
+    "test.java.math.BigInteger#testStringConv_generic",
+    "test.java.math.RoundingMode",
+    "test.java.net.DatagramSocket",
+    "test.java.net.Socket",
+    "test.java.net.SocketOptions",
+    "test.java.net.URLDecoder",
+    "test.java.net.URLEncoder",
+    "test.java.nio.channels.Channels",
+    "test.java.nio.channels.SelectionKey",
+    "test.java.nio.channels.Selector",
+    "test.java.nio.file",
+    "test.java.security.cert",
+    "test.java.security.KeyAgreement.KeyAgreementTest",
+    "test.java.security.KeyAgreement.KeySizeTest#testECDHKeySize",
+    "test.java.security.KeyAgreement.KeySpecTest",
+    "test.java.security.KeyAgreement.MultiThreadTest",
+    "test.java.security.KeyAgreement.NegativeTest",
+    "test.java.security.KeyStore",
+    "test.java.security.Provider",
+    "test.java.util.Arrays",
+    "test.java.util.Collection",
+    "test.java.util.Collections",
+    "test.java.util.Date",
+    "test.java.util.EnumMap",
+    "test.java.util.EnumSet",
+    "test.java.util.GregorianCalendar",
+    "test.java.util.LinkedHashMap",
+    "test.java.util.LinkedHashSet",
+    "test.java.util.List",
+    "test.java.util.Map",
+    "test.java.util.Optional",
+    "test.java.util.TimeZone",
+    "test.java.util.concurrent",
+    "test.java.util.function",
+    "test.java.util.stream",
+    "test.java.util.zip.ZipFile"
+  ]
 }
 ]
diff --git a/tools/libcore_gcstress_debug_failures.txt b/tools/libcore_gcstress_debug_failures.txt
index 2193189..c931610 100644
--- a/tools/libcore_gcstress_debug_failures.txt
+++ b/tools/libcore_gcstress_debug_failures.txt
@@ -50,7 +50,6 @@
           "org.apache.harmony.luni.tests.internal.net.www.protocol.https.HttpsURLConnectionTest#testConsequentProxyConnection",
           "org.apache.harmony.tests.java.lang.ref.ReferenceQueueTest#test_removeJ",
           "org.apache.harmony.tests.java.lang.ProcessManagerTest#testSleep",
-          "org.apache.harmony.tests.java.math.BigIntegerConstructorsTest#testConstructorPrime",
           "org.apache.harmony.tests.java.util.TimerTest#testOverdueTaskExecutesImmediately",
           "org.apache.harmony.tests.java.util.WeakHashMapTest#test_keySet_hasNext"
   ]
@@ -71,5 +70,11 @@
   names: ["jsr166.CompletableFutureTest#testCompleteOnTimeout_completed",
           "jsr166.CompletableFutureTest#testDelayedExecutor"
          ]
+},
+{
+  description: "SocketTimeout test gcstress and debug.",
+  result: EXEC_FAILED,
+  bug: 259530489,
+  names: ["org.apache.harmony.luni.tests.java.net.URLConnectionTest#test_setReadTimeoutI_SocketTimeoutException"]
 }
 ]
diff --git a/tools/libcore_gcstress_failures.txt b/tools/libcore_gcstress_failures.txt
index 55bba72..81d6ca0 100644
--- a/tools/libcore_gcstress_failures.txt
+++ b/tools/libcore_gcstress_failures.txt
@@ -36,7 +36,6 @@
           "libcore.java.util.stream.CollectorsTest#counting_largeStream",
           "org.apache.harmony.tests.java.lang.ref.ReferenceQueueTest#test_remove",
           "org.apache.harmony.tests.java.lang.String2Test#test_getBytes",
-          "org.apache.harmony.tests.java.math.BigIntegerConstructorsTest#testConstructorPrime",
           "org.apache.harmony.tests.java.text.DateFormatTest#test_getAvailableLocales",
           "org.apache.harmony.tests.java.util.TimerTest#testOverdueTaskExecutesImmediately",
           "org.apache.harmony.tests.java.util.WeakHashMapTest#test_keySet_hasNext"]
diff --git a/tools/luci/config/generated/cr-buildbucket.cfg b/tools/luci/config/generated/cr-buildbucket.cfg
index e4a5923..1da398c 100644
--- a/tools/luci/config/generated/cr-buildbucket.cfg
+++ b/tools/luci/config/generated/cr-buildbucket.cfg
@@ -17,7 +17,8 @@
     builders {
       name: "angler-armv7-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:angler-armv7-debug"
+      dimensions: "device_type:bonito|oriole|walleye"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -35,13 +36,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "angler-armv7-ndebug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:angler-armv7-ndebug"
+      dimensions: "device_type:bonito|oriole|walleye"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -59,13 +61,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "angler-armv7-non-gen-cc"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:angler-armv7-non-gen-cc"
+      dimensions: "device_type:oriole"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -83,13 +86,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "angler-armv8-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:angler-armv8-debug"
+      dimensions: "device_type:bonito|oriole|walleye"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -107,13 +111,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "angler-armv8-ndebug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:angler-armv8-ndebug"
+      dimensions: "device_type:bonito|oriole|walleye"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -131,13 +136,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "angler-armv8-non-gen-cc"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:angler-armv8-non-gen-cc"
+      dimensions: "device_type:oriole"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -155,13 +161,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "bullhead-armv7-gcstress-ndebug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:bullhead-armv7-gcstress-ndebug"
+      dimensions: "device_type:bonito|oriole|walleye"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -179,13 +186,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "bullhead-armv8-gcstress-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:bullhead-armv8-gcstress-debug"
+      dimensions: "device_type:bonito|oriole|walleye"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -203,13 +211,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "bullhead-armv8-gcstress-ndebug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:bullhead-armv8-gcstress-ndebug"
+      dimensions: "device_type:bonito|oriole|walleye"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -227,13 +236,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "fugu-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:fugu-debug"
+      dimensions: "device_type:fugu"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -251,13 +261,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "fugu-ndebug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:fugu-ndebug"
+      dimensions: "device_type:fugu"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -275,13 +286,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86-cms"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86-cms"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -299,13 +310,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86-debug"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -323,13 +334,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86-gcstress-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86-gcstress-debug"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -347,13 +358,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86-ndebug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86-ndebug"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -371,13 +382,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86-poison-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86-poison-debug"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -395,13 +406,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86_64-cdex-fast"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86_64-cdex-fast"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -419,13 +430,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86_64-cms"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86_64-cms"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -443,13 +454,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86_64-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86_64-debug"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -467,13 +478,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86_64-ndebug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86_64-ndebug"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -491,13 +502,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86_64-non-gen-cc"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86_64-non-gen-cc"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -515,13 +526,13 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "host-x86_64-poison-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:host-x86_64-poison-debug"
+      dimensions: "os:Linux"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -539,13 +550,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "walleye-armv7-poison-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:walleye-armv7-poison-debug"
+      dimensions: "device_type:bonito|oriole|walleye"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -563,13 +575,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "walleye-armv8-poison-debug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:walleye-armv8-poison-debug"
+      dimensions: "device_type:bonito|oriole|walleye"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -587,13 +600,14 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
     builders {
       name: "walleye-armv8-poison-ndebug"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:walleye-armv8-poison-ndebug"
+      dimensions: "device_type:bonito|oriole|walleye"
+      dimensions: "os:Android"
       dimensions: "pool:luci.art.ci"
       recipe {
         name: "art"
@@ -611,7 +625,7 @@
       service_account: "[email protected]"
       experiments {
         key: "luci.recipes.use_python3"
-        value: 10
+        value: 100
       }
     }
   }
diff --git a/tools/luci/config/generated/luci-scheduler.cfg b/tools/luci/config/generated/luci-scheduler.cfg
index d6c6ab5..ba7bcee 100644
--- a/tools/luci/config/generated/luci-scheduler.cfg
+++ b/tools/luci/config/generated/luci-scheduler.cfg
@@ -356,6 +356,40 @@
     refs: "regexp:refs/heads/master-art"
   }
 }
+trigger {
+  id: "vogar"
+  realm: "ci"
+  acl_sets: "ci"
+  triggers: "angler-armv7-debug"
+  triggers: "angler-armv7-ndebug"
+  triggers: "angler-armv7-non-gen-cc"
+  triggers: "angler-armv8-debug"
+  triggers: "angler-armv8-ndebug"
+  triggers: "angler-armv8-non-gen-cc"
+  triggers: "bullhead-armv7-gcstress-ndebug"
+  triggers: "bullhead-armv8-gcstress-debug"
+  triggers: "bullhead-armv8-gcstress-ndebug"
+  triggers: "fugu-debug"
+  triggers: "fugu-ndebug"
+  triggers: "host-x86-cms"
+  triggers: "host-x86-debug"
+  triggers: "host-x86-gcstress-debug"
+  triggers: "host-x86-ndebug"
+  triggers: "host-x86-poison-debug"
+  triggers: "host-x86_64-cdex-fast"
+  triggers: "host-x86_64-cms"
+  triggers: "host-x86_64-debug"
+  triggers: "host-x86_64-ndebug"
+  triggers: "host-x86_64-non-gen-cc"
+  triggers: "host-x86_64-poison-debug"
+  triggers: "walleye-armv7-poison-debug"
+  triggers: "walleye-armv8-poison-debug"
+  triggers: "walleye-armv8-poison-ndebug"
+  gitiles {
+    repo: "https://android.googlesource.com/platform/external/vogar"
+    refs: "regexp:refs/heads/master"
+  }
+}
 acl_sets {
   name: "ci"
   acls {
diff --git a/tools/luci/config/generated/project.cfg b/tools/luci/config/generated/project.cfg
index 4844ab4..f9f7877 100644
--- a/tools/luci/config/generated/project.cfg
+++ b/tools/luci/config/generated/project.cfg
@@ -7,7 +7,7 @@
 name: "art"
 access: "group:all"
 lucicfg {
-  version: "1.30.11"
+  version: "1.33.7"
   package_dir: ".."
   config_dir: "generated"
   entry_point: "main.star"
diff --git a/tools/luci/config/main.star b/tools/luci/config/main.star
index f4ad488..b1ecde1 100755
--- a/tools/luci/config/main.star
+++ b/tools/luci/config/main.star
@@ -23,7 +23,7 @@
 lucicfg.check_version("1.30.9", "Please update depot_tools")
 
 luci.builder.defaults.experiments.set({
-    "luci.recipes.use_python3": 10,
+    "luci.recipes.use_python3": 100,
 })
 
 # Use LUCI Scheduler BBv2 names and add Scheduler realms configs.
@@ -141,13 +141,20 @@
 )
 
 luci.gitiles_poller(
+    name = "vogar",
+    bucket = "ci",
+    repo = "https://android.googlesource.com/platform/external/vogar",
+    refs = ["refs/heads/master"],
+)
+
+luci.gitiles_poller(
     name = "manifest",
     bucket = "ci",
     repo = "https://android.googlesource.com/platform/manifest",
     refs = ["refs/heads/master-art"],
 )
 
-def ci_builder(name, category, short_name):
+def ci_builder(name, category, short_name, dimensions):
     luci.builder(
         name = name,
         bucket = "ci",
@@ -156,11 +163,8 @@
             cipd_version = "refs/heads/main",
             name = "art",
         ),
-        dimensions = {
+        dimensions = dimensions | {
             "pool": "luci.art.ci",
-
-            # Some builders require specific hardware, so we make the assignment in bots.cfg
-            "builder": name,
         },
         service_account = "[email protected]",
 
@@ -185,6 +189,7 @@
             "art",
             "libcore",
             "manifest",
+            "vogar",
         ],
     )
     luci.console_view_entry(
@@ -194,28 +199,37 @@
         short_name = short_name,
     )
 
-ci_builder("angler-armv7-debug", "angler|armv7", "dbg")
-ci_builder("angler-armv7-non-gen-cc", "angler|armv7", "ngen")
-ci_builder("angler-armv7-ndebug", "angler|armv7", "ndbg")
-ci_builder("angler-armv8-debug", "angler|armv8", "dbg")
-ci_builder("angler-armv8-non-gen-cc", "angler|armv8", "ngen")
-ci_builder("angler-armv8-ndebug", "angler|armv8", "ndbg")
-ci_builder("bullhead-armv7-gcstress-ndebug", "bullhead|armv7|gcstress", "dbg")
-ci_builder("bullhead-armv8-gcstress-debug", "bullhead|armv8|gcstress", "dbg")
-ci_builder("bullhead-armv8-gcstress-ndebug", "bullhead|armv8|gcstress", "ndbg")
-ci_builder("fugu-debug", "fugu", "dbg")
-ci_builder("fugu-ndebug", "fugu", "ndbg")
-ci_builder("host-x86-cms", "host|x86", "cms")
-ci_builder("host-x86-debug", "host|x86", "dbg")
-ci_builder("host-x86-ndebug", "host|x86", "ndbg")
-ci_builder("host-x86-gcstress-debug", "host|x86", "gcs")
-ci_builder("host-x86-poison-debug", "host|x86", "psn")
-ci_builder("host-x86_64-cdex-fast", "host|x64", "cdx")
-ci_builder("host-x86_64-cms", "host|x64", "cms")
-ci_builder("host-x86_64-debug", "host|x64", "dbg")
-ci_builder("host-x86_64-non-gen-cc", "host|x64", "ngen")
-ci_builder("host-x86_64-ndebug", "host|x64", "ndbg")
-ci_builder("host-x86_64-poison-debug", "host|x64", "psn")
-ci_builder("walleye-armv7-poison-debug", "walleye|armv7|poison", "dbg")
-ci_builder("walleye-armv8-poison-debug", "walleye|armv8|poison", "dbg")
-ci_builder("walleye-armv8-poison-ndebug", "walleye|armv8|poison", "ndbg")
+# Dimensions specify which bots we can run on.
+host_dims = {"os": "Linux"}
+target_dims = {"os": "Android"}
+arm_target_dims = target_dims | {"device_type": "bonito|oriole|walleye"}
+x86_target_dims = target_dims | {"device_type": "fugu"}
+
+# userfault-GC configurations must be run on Pixel 6.
+userfault_gc_target_dims = target_dims | {"device_type": "oriole"}
+
+ci_builder("angler-armv7-debug", "angler|armv7", "dbg", arm_target_dims)
+ci_builder("angler-armv7-non-gen-cc", "angler|armv7", "ngen", userfault_gc_target_dims)
+ci_builder("angler-armv7-ndebug", "angler|armv7", "ndbg", arm_target_dims)
+ci_builder("angler-armv8-debug", "angler|armv8", "dbg", arm_target_dims)
+ci_builder("angler-armv8-non-gen-cc", "angler|armv8", "ngen", userfault_gc_target_dims)
+ci_builder("angler-armv8-ndebug", "angler|armv8", "ndbg", arm_target_dims)
+ci_builder("bullhead-armv7-gcstress-ndebug", "bullhead|armv7|gcstress", "dbg", arm_target_dims)
+ci_builder("bullhead-armv8-gcstress-debug", "bullhead|armv8|gcstress", "dbg", arm_target_dims)
+ci_builder("bullhead-armv8-gcstress-ndebug", "bullhead|armv8|gcstress", "ndbg", arm_target_dims)
+ci_builder("fugu-debug", "fugu", "dbg", x86_target_dims)
+ci_builder("fugu-ndebug", "fugu", "ndbg", x86_target_dims)
+ci_builder("host-x86-cms", "host|x86", "cms", host_dims)
+ci_builder("host-x86-debug", "host|x86", "dbg", host_dims)
+ci_builder("host-x86-ndebug", "host|x86", "ndbg", host_dims)
+ci_builder("host-x86-gcstress-debug", "host|x86", "gcs", host_dims)
+ci_builder("host-x86-poison-debug", "host|x86", "psn", host_dims)
+ci_builder("host-x86_64-cdex-fast", "host|x64", "cdx", host_dims)
+ci_builder("host-x86_64-cms", "host|x64", "cms", host_dims)
+ci_builder("host-x86_64-debug", "host|x64", "dbg", host_dims)
+ci_builder("host-x86_64-non-gen-cc", "host|x64", "ngen", host_dims)
+ci_builder("host-x86_64-ndebug", "host|x64", "ndbg", host_dims)
+ci_builder("host-x86_64-poison-debug", "host|x64", "psn", host_dims)
+ci_builder("walleye-armv7-poison-debug", "walleye|armv7|poison", "dbg", arm_target_dims)
+ci_builder("walleye-armv8-poison-debug", "walleye|armv8|poison", "dbg", arm_target_dims)
+ci_builder("walleye-armv8-poison-ndebug", "walleye|armv8|poison", "ndbg", arm_target_dims)
diff --git a/tools/prebuilt_libjdwp_art_failures.txt b/tools/prebuilt_libjdwp_art_failures.txt
index ee59315..4ded7d5 100644
--- a/tools/prebuilt_libjdwp_art_failures.txt
+++ b/tools/prebuilt_libjdwp_art_failures.txt
@@ -106,5 +106,5 @@
   result: EXEC_FAILED,
   bug: 69169846,
   name: "org.apache.harmony.jpda.tests.jdwp.DDM_DDMTest#testChunk001"
-},
+}
 ]
diff --git a/tools/public.libraries.buildbot.txt b/tools/public.libraries.buildbot.txt
index e23cf2c..9b0dc68 100644
--- a/tools/public.libraries.buildbot.txt
+++ b/tools/public.libraries.buildbot.txt
@@ -1,6 +1,6 @@
-libbacktrace.so
 libc.so
 libc++.so
 libdl.so
 libm.so
 libnativehelper.so
+libunwindstack.so
diff --git a/tools/run-gtests.sh b/tools/run-gtests.sh
index 21064c1..da61c7e 100755
--- a/tools/run-gtests.sh
+++ b/tools/run-gtests.sh
@@ -59,20 +59,47 @@
 
 options="$@"
 
+run_in_chroot() {
+  if [ -n "$ART_TEST_ON_VM" ]; then
+    $ART_SSH_CMD $ART_CHROOT_CMD $@
+  else
+    "$adb" shell chroot "$ART_TEST_CHROOT" $@
+  fi
+}
+
 if [[ ${#tests[@]} -eq 0 ]]; then
   # Search for executables under the `bin/art` directory of the ART APEX.
-  readarray -t tests <<<$("$adb" shell chroot "$ART_TEST_CHROOT" \
+  readarray -t tests <<<$(run_in_chroot \
     find "$android_art_root/bin/art" -type f -perm /ugo+x | sort)
 fi
 
+maybe_get_fake_dex2oatbootclasspath() {
+  if [ -n "$ART_TEST_ON_VM" ]; then
+    return
+  fi
+  dex2oatbootclasspath=$("$adb" shell echo \$DEX2OATBOOTCLASSPATH)
+  if [ -n "$dex2oatbootclasspath" ]; then
+    # The device has a real DEX2OATBOOTCLASSPATH.
+    # This is the usual case.
+    return
+  fi
+  bootclasspath=$("$adb" shell echo \$BOOTCLASSPATH)
+  # Construct a fake DEX2OATBOOTCLASSPATH from the elements in BOOTCLASSPATH except the last one.
+  # BOOTCLASSPATH cannot be used by the runtime in chroot anyway, so it doesn't hurt to construct a
+  # fake DEX2OATBOOTCLASSPATH just to make the runtime happy.
+  # This is only needed on old Android platforms such as Android P.
+  echo "DEX2OATBOOTCLASSPATH=${bootclasspath%:*}"
+}
+
 failing_tests=()
 
 for t in ${tests[@]}; do
   echo "$t"
-  "$adb" shell chroot "$ART_TEST_CHROOT" \
+  run_in_chroot \
     env ANDROID_ART_ROOT="$android_art_root" \
         ANDROID_I18N_ROOT="$android_i18n_root" \
         ANDROID_TZDATA_ROOT="$android_tzdata_root" \
+        $(maybe_get_fake_dex2oatbootclasspath) \
         $t $options \
     || failing_tests+=("$t")
 done
diff --git a/tools/run-libcore-tests.py b/tools/run-libcore-tests.py
index 1e6070a..c6958f1 100755
--- a/tools/run-libcore-tests.py
+++ b/tools/run-libcore-tests.py
@@ -42,6 +42,7 @@
                       help='Enable GC stress configuration (device|host only).')
   parser.add_argument('tests', nargs="*",
                       help='Name(s) of the test(s) to run')
+  parser.add_argument('--verbose', action='store_true', help='Print verbose output from vogar.')
   return parser.parse_args()
 
 ART_TEST_ANDROID_ROOT = os.environ.get("ART_TEST_ANDROID_ROOT", "/system")
@@ -91,7 +92,10 @@
   "libcore.sun.util",
   "libcore.xml",
   "org.apache.harmony.annotation",
-  "org.apache.harmony.luni",
+  "org.apache.harmony.luni.tests.internal.net.www.protocol.http.HttpURLConnection",
+  "org.apache.harmony.luni.tests.internal.net.www.protocol.https.HttpsURLConnection",
+  "org.apache.harmony.luni.tests.java.io",
+  "org.apache.harmony.luni.tests.java.net",
   "org.apache.harmony.nio",
   "org.apache.harmony.regex",
   "org.apache.harmony.testframework",
@@ -122,7 +126,8 @@
   "test.java.lang.Long",
   # Sharded test.java.lang.StrictMath
   "test.java.lang.StrictMath.CubeRootTests",
-  "test.java.lang.StrictMath.ExactArithTests",
+  # TODO: disable the test until b/248208762 is fixed.
+  # "test.java.lang.StrictMath.ExactArithTests",
   "test.java.lang.StrictMath.Expm1Tests",
   "test.java.lang.StrictMath.ExpTests",
   "test.java.lang.StrictMath.HyperbolicTests",
@@ -159,21 +164,15 @@
   "test.java.math.BigInteger#testDivideAndReminder",
   "test.java.math.BigInteger#testDivideLarge",
   "test.java.math.BigInteger#testModExp",
-  "test.java.math.BigInteger#testModInv",
   "test.java.math.BigInteger#testMultiplyLarge",
   "test.java.math.BigInteger#testNextProbablePrime",
   "test.java.math.BigInteger#testPow",
-  "test.java.math.BigInteger#testPrime",
   "test.java.math.BigInteger#testSerialize",
   "test.java.math.BigInteger#testShift",
   "test.java.math.BigInteger#testSquare",
   "test.java.math.BigInteger#testSquareLarge",
-  "test.java.math.BigInteger#testSquareRoot",
   "test.java.math.BigInteger#testSquareRootAndReminder",
   "test.java.math.BigInteger#testStringConv_generic",
-  "test.java.math.BigInteger#testStringConv_schoenhage_threshold_pow0",
-  "test.java.math.BigInteger#testStringConv_schoenhage_threshold_pow1",
-  "test.java.math.BigInteger#testStringConv_schoenhage_threshold_pow2",
   "test.java.math.RoundingMode",
   # test.java.net
   "test.java.net.DatagramSocket",
@@ -190,7 +189,6 @@
   "test.java.security.cert",
   # Sharded test.java.security.KeyAgreement
   "test.java.security.KeyAgreement.KeyAgreementTest",
-  "test.java.security.KeyAgreement.KeySizeTest#testDHKeySize",
   "test.java.security.KeyAgreement.KeySizeTest#testECDHKeySize",
   "test.java.security.KeyAgreement.KeySpecTest",
   "test.java.security.KeyAgreement.MultiThreadTest",
@@ -242,6 +240,130 @@
 
 CLASSPATH = ["core-tests", "core-ojtests", "jsr166-tests", "mockito-target"]
 
+SLOW_OJLUNI_TESTS = {
+  "test.java.awt",
+  "test.java.lang.String",
+  "test.java.lang.invoke",
+  "test.java.nio.channels.Selector",
+  "test.java.time",
+  "test.java.util.Arrays",
+  "test.java.util.Map",
+  "test.java.util.concurrent",
+  "test.java.util.stream",
+  "test.java.util.zip.ZipFile",
+  "tck.java.time",
+}
+
+# Disabled to unblock art-buildbot
+# These tests fail with "java.io.IOException: Stream closed", tracked in
+# http://b/235566533 and http://b/208639267
+DISABLED_GCSTRESS_DEBUG_TESTS = {
+  "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard1",
+  "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard2",
+  "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard3",
+  "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard4",
+  "test.java.math.BigDecimal",
+  "test.java.math.BigInteger#testConstructor",
+}
+
+DISABLED_FUGU_TESTS = {
+  "org.apache.harmony.luni.tests.internal.net.www.protocol.http.HttpURLConnection",
+  "org.apache.harmony.luni.tests.internal.net.www.protocol.https.HttpsURLConnection",
+  "test.java.awt",
+  "test.java.io.ByteArrayInputStream",
+  "test.java.io.ByteArrayOutputStream",
+  "test.java.io.InputStream",
+  "test.java.io.OutputStream",
+  "test.java.io.PrintStream",
+  "test.java.io.PrintWriter",
+  "test.java.io.Reader",
+  "test.java.io.Writer",
+  "test.java.lang.Boolean",
+  "test.java.lang.ClassLoader",
+  "test.java.lang.Double",
+  "test.java.lang.Float",
+  "test.java.lang.Integer",
+  "test.java.lang.Long",
+  "test.java.lang.StrictMath.CubeRootTests",
+  "test.java.lang.StrictMath.Expm1Tests",
+  "test.java.lang.StrictMath.ExpTests",
+  "test.java.lang.StrictMath.HyperbolicTests",
+  "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard1",
+  "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard2",
+  "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard3",
+  "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard4",
+  "test.java.lang.StrictMath.HypotTests#testHypot",
+  "test.java.lang.StrictMath.Log1pTests",
+  "test.java.lang.StrictMath.Log10Tests",
+  "test.java.lang.StrictMath.MultiplicationTests",
+  "test.java.lang.StrictMath.PowTests",
+  "test.java.lang.String",
+  "test.java.lang.Thread",
+  "test.java.lang.invoke",
+  "test.java.lang.ref.SoftReference",
+  "test.java.lang.ref.BasicTest",
+  "test.java.lang.ref.EnqueueNullRefTest",
+  "test.java.lang.ref.EnqueuePollRaceTest",
+  "test.java.lang.ref.ReferenceCloneTest",
+  "test.java.lang.ref.ReferenceEnqueuePendingTest",
+  "test.java.math.BigDecimal",
+  "test.java.math.BigInteger#testArithmetic",
+  "test.java.math.BigInteger#testBitCount",
+  "test.java.math.BigInteger#testBitLength",
+  "test.java.math.BigInteger#testbitOps",
+  "test.java.math.BigInteger#testBitwise",
+  "test.java.math.BigInteger#testByteArrayConv",
+  "test.java.math.BigInteger#testConstructor",
+  "test.java.math.BigInteger#testDivideAndReminder",
+  "test.java.math.BigInteger#testDivideLarge",
+  "test.java.math.BigInteger#testModExp",
+  "test.java.math.BigInteger#testMultiplyLarge",
+  "test.java.math.BigInteger#testNextProbablePrime",
+  "test.java.math.BigInteger#testPow",
+  "test.java.math.BigInteger#testSerialize",
+  "test.java.math.BigInteger#testShift",
+  "test.java.math.BigInteger#testSquare",
+  "test.java.math.BigInteger#testSquareLarge",
+  "test.java.math.BigInteger#testSquareRootAndReminder",
+  "test.java.math.BigInteger#testStringConv_generic",
+  "test.java.math.RoundingMode",
+  "test.java.net.DatagramSocket",
+  "test.java.net.Socket",
+  "test.java.net.SocketOptions",
+  "test.java.net.URLDecoder",
+  "test.java.net.URLEncoder",
+  "test.java.nio.channels.Channels",
+  "test.java.nio.channels.SelectionKey",
+  "test.java.nio.channels.Selector",
+  "test.java.nio.file",
+  "test.java.security.cert",
+  "test.java.security.KeyAgreement.KeyAgreementTest",
+  "test.java.security.KeyAgreement.KeySizeTest#testECDHKeySize",
+  "test.java.security.KeyAgreement.KeySpecTest",
+  "test.java.security.KeyAgreement.MultiThreadTest",
+  "test.java.security.KeyAgreement.NegativeTest",
+  "test.java.security.KeyStore",
+  "test.java.security.Provider",
+  "test.java.time",
+  "test.java.util.Arrays",
+  "test.java.util.Collection",
+  "test.java.util.Collections",
+  "test.java.util.Date",
+  "test.java.util.EnumMap",
+  "test.java.util.EnumSet",
+  "test.java.util.GregorianCalendar",
+  "test.java.util.LinkedHashMap",
+  "test.java.util.LinkedHashSet",
+  "test.java.util.List",
+  "test.java.util.Map",
+  "test.java.util.Optional",
+  "test.java.util.TestFormatter",
+  "test.java.util.TimeZone",
+  "test.java.util.function",
+  "test.java.util.stream",
+  "tck.java.time",
+}
+
 def get_jar_filename(classpath):
   base_path = (ANDROID_PRODUCT_OUT + "/../..") if ANDROID_PRODUCT_OUT else "out/target"
   base_path = os.path.normpath(base_path)  # Normalize ".." components for readability.
@@ -275,6 +397,13 @@
   # See b/78228743 and b/178351808.
   if args.gcstress or args.debug or args.mode == "jvm":
     test_names = list(t for t in test_names if not t.startswith("libcore.highmemorytest"))
+    test_names = list(filter(lambda x: x not in SLOW_OJLUNI_TESTS, test_names))
+  if args.gcstress and args.debug:
+    test_names = list(filter(lambda x: x not in DISABLED_GCSTRESS_DEBUG_TESTS, test_names))
+  if not args.getrandom:
+    # Disable libcore.highmemorytest due to limited ram on fugu. http://b/258173036
+    test_names = list(filter(lambda x: x not in DISABLED_FUGU_TESTS and
+                                       not x.startswith("libcore.highmemorytest"), test_names))
   return test_names
 
 def get_vogar_command(test_name):
@@ -282,6 +411,7 @@
   if args.mode == "device":
     cmd.append("--mode=device --vm-arg -Ximage:/system/framework/art_boot_images/boot.art")
     cmd.append("--vm-arg -Xbootclasspath:" + ":".join(BOOT_CLASSPATH))
+
   if args.mode == "host":
     # We explicitly give a wrong path for the image, to ensure vogar
     # will create a boot image with the default compiler. Note that
@@ -298,11 +428,16 @@
   if args.debug:
     cmd.append("--vm-arg -XXlib:libartd.so --vm-arg -XX:SlowDebug=true")
 
+  # The only device in go/art-buildbot without getrandom is fugu. We limit the amount of memory
+  # per runtime for fugu to avoid low memory killer, fugu has 4-cores 1GB RAM (b/258171768).
+  if not args.getrandom:
+    cmd.append("--vm-arg -Xmx128M")
+
   if args.mode == "device":
     if ART_TEST_CHROOT:
       cmd.append(f"--chroot {ART_TEST_CHROOT} --device-dir=/tmp/vogar/test-{test_name}")
     else:
-      cmd.append("--device-dir=/data/local/tmp/vogar/test-{test_name}")
+      cmd.append(f"--device-dir=/data/local/tmp/vogar/test-{test_name}")
     cmd.append(f"--vm-command={ART_TEST_ANDROID_ROOT}/bin/art")
   else:
     cmd.append(f"--device-dir=/tmp/vogar/test-{test_name}")
@@ -314,6 +449,9 @@
       cmd.append("--vm-arg -Xcompiler-option --vm-arg --compiler-filter=quicken")
     cmd.append("--vm-arg -Xusejit:{}".format(str(args.jit).lower()))
 
+  if args.verbose:
+    cmd.append("--verbose")
+
   # Suppress color codes if not attached to a terminal
   if not sys.stdout.isatty():
     cmd.append("--no-color")
@@ -370,7 +508,7 @@
     print(f"Running {len(futures)} tasks on {args.jobs} core(s)...\n")
     for i, future in enumerate(concurrent.futures.as_completed(futures)):
       test_name, cmd, stdout, exit_code = future.result()
-      if exit_code != 0 or args.dry_run:
+      if exit_code != 0 or args.dry_run or args.verbose:
         print(cmd)
         print(stdout.strip())
       else:
diff --git a/tools/run-libjdwp-tests.sh b/tools/run-libjdwp-tests.sh
index efb2737..06e34f9 100755
--- a/tools/run-libjdwp-tests.sh
+++ b/tools/run-libjdwp-tests.sh
@@ -138,6 +138,10 @@
   expectations="$expectations --expectations $PWD/art/tools/external_oj_libjdwp_art_gcstress_debug_failures.txt"
 fi
 
+if [[ "${ART_USE_READ_BARRIER}" = "false" ]]; then
+  expectations="$expectations --expectations $PWD/art/tools/external_oj_libjdwp_art_no_read_barrier_failures.txt"
+fi
+
 function verbose_run() {
   echo "$@"
   env "$@"
diff --git a/tools/signal_dumper/Android.bp b/tools/signal_dumper/Android.bp
index 33450d1..00948b8 100644
--- a/tools/signal_dumper/Android.bp
+++ b/tools/signal_dumper/Android.bp
@@ -48,18 +48,6 @@
     },
 }
 
-cc_defaults {
-    name: "signal_dumper_libbacktrace_static_deps",
-    defaults: [
-        "signal_dumper_libbase_static_deps",
-        "signal_dumper_libunwindstack_static_deps",
-    ],
-    static_libs: [
-        "libbase",
-        "libunwindstack",
-    ],
-}
-
 art_cc_binary {
     name: "signal_dumper",
 
@@ -73,14 +61,14 @@
 
     defaults: [
         "art_defaults",
-        "signal_dumper_libbacktrace_static_deps",
         "signal_dumper_libbase_static_deps",
+        "signal_dumper_libunwindstack_static_deps",
     ],
 
     srcs: ["signal_dumper.cc"],
 
     static_libs: [
-        "libbacktrace",
         "libbase",
+        "libunwindstack",
     ],
 }
diff --git a/tools/signal_dumper/signal_dumper.cc b/tools/signal_dumper/signal_dumper.cc
index e9a589e..bedb8dc 100644
--- a/tools/signal_dumper/signal_dumper.cc
+++ b/tools/signal_dumper/signal_dumper.cc
@@ -15,6 +15,7 @@
  */
 
 #include <dirent.h>
+#include <inttypes.h>
 #include <poll.h>
 #include <sys/prctl.h>
 #include <sys/ptrace.h>
@@ -38,8 +39,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <backtrace/Backtrace.h>
-#include <backtrace/BacktraceMap.h>
+#include <unwindstack/AndroidUnwinder.h>
 
 namespace art {
 namespace {
@@ -368,11 +368,13 @@
 }
 
 void DumpABI(pid_t forked_pid) {
-  enum class ABI { kArm, kArm64, kX86, kX86_64 };
+  enum class ABI { kArm, kArm64, kRiscv64, kX86, kX86_64 };
 #if defined(__arm__)
   constexpr ABI kDumperABI = ABI::kArm;
 #elif defined(__aarch64__)
   constexpr ABI kDumperABI = ABI::kArm64;
+#elif defined(__riscv)
+  constexpr ABI kDumperABI = ABI::kRiscv64;
 #elif defined(__i386__)
   constexpr ABI kDumperABI = ABI::kX86;
 #elif defined(__x86_64__)
@@ -394,6 +396,9 @@
       case ABI::kArm64:
         to_print = ABI::kArm64;
         break;
+      case ABI::kRiscv64:
+        to_print = ABI::kRiscv64;
+        break;
       case ABI::kX86:
       case ABI::kX86_64:
         to_print = ABI::kX86_64;
@@ -408,6 +413,9 @@
       case ABI::kArm64:
         to_print = io_vec.iov_len == 18 * sizeof(uint32_t) ? ABI::kArm : ABI::kArm64;
         break;
+      case ABI::kRiscv64:
+        to_print = ABI::kRiscv64;
+        break;
       case ABI::kX86:
       case ABI::kX86_64:
         to_print = io_vec.iov_len == 17 * sizeof(uint32_t) ? ABI::kX86 : ABI::kX86_64;
@@ -424,6 +432,9 @@
     case ABI::kArm64:
       abi_str = "arm64";
       break;
+    case ABI::kRiscv64:
+      abi_str = "riscv64";
+      break;
     case ABI::kX86:
       abi_str = "x86";
       break;
@@ -492,11 +503,10 @@
 constexpr bool kIs64Bit = false;
 #endif
 
-void DumpThread(pid_t pid,
+void DumpThread(unwindstack::AndroidRemoteUnwinder& unwinder, pid_t pid,
                 pid_t tid,
                 const std::string* addr2line_path,
-                const char* prefix,
-                BacktraceMap* map) {
+                const char* prefix) {
   LOG(ERROR) << std::endl << "=== pid: " << pid << " tid: " << tid << " ===" << std::endl;
 
   constexpr uint32_t kMaxWaitMicros = 1000 * 1000;  // 1s.
@@ -504,50 +514,41 @@
     LOG(ERROR) << "Failed to wait for sigstop on " << tid;
   }
 
-  std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map));
-  if (backtrace == nullptr) {
-    LOG(ERROR) << prefix << "(failed to create Backtrace for thread " << tid << ")";
-    return;
-  }
-  backtrace->SetSkipFrames(false);
-  if (!backtrace->Unwind(0, nullptr)) {
-    LOG(ERROR) << prefix << "(backtrace::Unwind failed for thread " << tid
-               << ": " <<  backtrace->GetErrorString(backtrace->GetError()) << ")";
-    return;
-  }
-  if (backtrace->NumFrames() == 0) {
-    LOG(ERROR) << prefix << "(no native stack frames for thread " << tid << ")";
+  unwindstack::AndroidUnwinderData data;
+  if (!unwinder.Unwind(tid, data)) {
+    LOG(ERROR) << prefix << "(Unwind failed for thread " << tid << ": "
+               <<  data.GetErrorString() << ")";
     return;
   }
 
   std::unique_ptr<addr2line::Addr2linePipe> addr2line_state;
-
-  for (Backtrace::const_iterator it = backtrace->begin();
-      it != backtrace->end(); ++it) {
+  data.DemangleFunctionNames();
+  for (const unwindstack::FrameData& frame : data.frames) {
     std::ostringstream oss;
-    oss << prefix << StringPrintf("#%02zu pc ", it->num);
+    oss << prefix << StringPrintf("#%02zu pc ", frame.num);
     bool try_addr2line = false;
-    if (!BacktraceMap::IsValid(it->map)) {
-      oss << StringPrintf(kIs64Bit ? "%016" PRIx64 "  ???" : "%08" PRIx64 "  ???", it->pc);
+    if (frame.map_info == nullptr) {
+      oss << StringPrintf(kIs64Bit ? "%016" PRIx64 "  ???" : "%08" PRIx64 "  ???", frame.pc);
     } else {
-      oss << StringPrintf(kIs64Bit ? "%016" PRIx64 "  " : "%08" PRIx64 "  ", it->rel_pc);
-      if (it->map.name.empty()) {
-        oss << StringPrintf("<anonymous:%" PRIx64 ">", it->map.start);
+      oss << StringPrintf(kIs64Bit ? "%016" PRIx64 "  " : "%08" PRIx64 "  ", frame.rel_pc);
+      if (frame.map_info->name().empty()) {
+        oss << StringPrintf("<anonymous:%" PRIx64 ">", frame.map_info->start());
       } else {
-        oss << it->map.name;
+        oss << frame.map_info->name().c_str();
       }
-      if (it->map.offset != 0) {
-        oss << StringPrintf(" (offset %" PRIx64 ")", it->map.offset);
+      if (frame.map_info->offset() != 0) {
+        oss << StringPrintf(" (offset %" PRIx64 ")", frame.map_info->offset());
       }
       oss << " (";
-      if (!it->func_name.empty()) {
-        oss << it->func_name;
-        if (it->func_offset != 0) {
-          oss << "+" << it->func_offset;
+      const std::string& function_name = frame.function_name;
+      if (!function_name.empty()) {
+        oss << function_name;
+        if (frame.function_offset != 0) {
+          oss << "+" << frame.function_offset;
         }
         // Functions found using the gdb jit interface will be in an empty
         // map that cannot be found using addr2line.
-        if (!it->map.name.empty()) {
+        if (!frame.map_info->name().empty()) {
           try_addr2line = true;
         }
       } else {
@@ -558,8 +559,8 @@
     LOG(ERROR) << oss.str() << std::endl;
     if (try_addr2line && addr2line_path != nullptr) {
       addr2line::Addr2line(*addr2line_path,
-                           it->map.name,
-                           it->rel_pc,
+                           frame.map_info->name(),
+                           frame.rel_pc,
                            LOG_STREAM(ERROR),
                            prefix,
                            &addr2line_state);
@@ -593,14 +594,9 @@
     LOG(ERROR) << "Did not receive SIGSTOP for pid " << forked_pid;
   }
 
-  std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(forked_pid));
-  if (backtrace_map == nullptr) {
-    LOG(ERROR) << "Could not create BacktraceMap";
-    return;
-  }
-
+  unwindstack::AndroidRemoteUnwinder unwinder(forked_pid);
   for (pid_t tid : tids) {
-    DumpThread(forked_pid, tid, addr2line_path.get(), "  ", backtrace_map.get());
+    DumpThread(unwinder, forked_pid, tid, addr2line_path.get(), "  ");
   }
 }
 
diff --git a/tools/tracefast-plugin/tracefast.cc b/tools/tracefast-plugin/tracefast.cc
index 618742d..abc871d 100644
--- a/tools/tracefast-plugin/tracefast.cc
+++ b/tools/tracefast-plugin/tracefast.cc
@@ -60,7 +60,6 @@
       override REQUIRES_SHARED(art::Locks::mutator_lock_) { }
 
   void MethodUnwind(art::Thread* thread ATTRIBUTE_UNUSED,
-                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
                     art::ArtMethod* method ATTRIBUTE_UNUSED,
                     uint32_t dex_pc ATTRIBUTE_UNUSED)
       override REQUIRES_SHARED(art::Locks::mutator_lock_) { }
@@ -131,7 +130,8 @@
                                              art::instrumentation::Instrumentation::kMethodEntered |
                                              art::instrumentation::Instrumentation::kMethodExited |
                                              art::instrumentation::Instrumentation::kMethodUnwind);
-  runtime->GetInstrumentation()->EnableMethodTracing(kTracerInstrumentationKey, kNeedsInterpreter);
+  runtime->GetInstrumentation()->EnableMethodTracing(
+      kTracerInstrumentationKey, &gEmptyTracer, kNeedsInterpreter);
 }
 
 class TraceFastPhaseCB : public art::RuntimePhaseCallback {
diff --git a/tools/veridex/Android.bp b/tools/veridex/Android.bp
index d5f5162..7b120cf 100644
--- a/tools/veridex/Android.bp
+++ b/tools/veridex/Android.bp
@@ -40,6 +40,7 @@
     static_libs: [
         "libdexfile",
         "libartbase",
+        "libartpalette",
         "libbase",
         "liblog",
         "libz",
diff --git a/tools/veridex/Android.mk b/tools/veridex/Android.mk
index f7c8d50..9ea9b3a 100644
--- a/tools/veridex/Android.mk
+++ b/tools/veridex/Android.mk
@@ -23,14 +23,14 @@
 
 system_stub_dex := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/core_dex_intermediates/classes.dex
 $(system_stub_dex): PRIVATE_MIN_SDK_VERSION := 1000
-$(system_stub_dex): $(call resolve-prebuilt-sdk-jar-path,system_current) | $(ZIP2ZIP) $(DX)
+$(system_stub_dex): $(call resolve-prebuilt-sdk-jar-path,system_current) | $(ZIP2ZIP) $(D8)
 	$(transform-classes.jar-to-dex)
 
 $(call declare-1p-target,$(system_stub_dex),art)
 
 oahl_stub_dex := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/oahl_dex_intermediates/classes.dex
 $(oahl_stub_dex): PRIVATE_MIN_SDK_VERSION := 1000
-$(oahl_stub_dex): $(call get-prebuilt-sdk-dir,current)/org.apache.http.legacy.jar | $(ZIP2ZIP) $(DX)
+$(oahl_stub_dex): $(call get-prebuilt-sdk-dir,current)/org.apache.http.legacy.jar | $(ZIP2ZIP) $(D8)
 	$(transform-classes.jar-to-dex)
 
 $(call declare-1p-target,$(oahl_stub_dex),art)
diff --git a/tools/veridex/flow_analysis.cc b/tools/veridex/flow_analysis.cc
index 2a8b8a0..6a4d351 100644
--- a/tools/veridex/flow_analysis.cc
+++ b/tools/veridex/flow_analysis.cc
@@ -540,7 +540,7 @@
     case Instruction::FILLED_NEW_ARRAY: {
       dex::TypeIndex type_index(instruction.VRegB_35c());
       VeriClass* cls = resolver_->GetVeriClass(type_index);
-      UpdateRegister(instruction.VRegA_22c(), cls);
+      UpdateRegister(instruction.VRegA_35c(), cls);
       break;
     }
 
@@ -602,7 +602,7 @@
       if (VeriClass::sdkInt_ != nullptr && resolver_->GetField(field_index) == VeriClass::sdkInt_) {
         UpdateRegister(dest_reg, gTargetSdkVersion, VeriClass::integer_);
       } else {
-        UpdateRegister(dest_reg, GetFieldType(instruction.VRegC_22c()));
+        UpdateRegister(dest_reg, GetFieldType(field_index));
       }
       break;
     }
diff --git a/tools/veridex/hidden_api.cc b/tools/veridex/hidden_api.cc
index 71ea56b5..69e6fe4 100644
--- a/tools/veridex/hidden_api.cc
+++ b/tools/veridex/hidden_api.cc
@@ -29,14 +29,22 @@
   CHECK(filename != nullptr);
 
   std::ifstream in(filename);
+  bool errors = false;
   for (std::string str; std::getline(in, str);) {
     std::vector<std::string> values = android::base::Split(str, ",");
     const std::string& signature = values[0];
 
     hiddenapi::ApiList membership;
     bool success = hiddenapi::ApiList::FromNames(values.begin() + 1, values.end(), &membership);
-    CHECK(success) << "Unknown ApiList flag: " << str;
-    CHECK(membership.IsValid()) << "Invalid ApiList: " << membership;
+    if (!success) {
+      LOG(ERROR) << "Unknown ApiList flag: " << str;
+      errors = true;
+      continue;
+    } else if (!membership.IsValid()) {
+      LOG(ERROR) << "Invalid ApiList: " << membership;
+      errors = true;
+      continue;
+    }
 
     AddSignatureToApiList(signature, membership);
     size_t pos = signature.find("->");
@@ -55,6 +63,7 @@
       }
     }
   }
+  CHECK(!errors) << "Errors encountered while parsing file " << filename;
 }
 
 void HiddenApi::AddSignatureToApiList(const std::string& signature, hiddenapi::ApiList membership) {
diff --git a/tools/veridex/veridex.cc b/tools/veridex/veridex.cc
index ae1c33e..d2a32e6 100644
--- a/tools/veridex/veridex.cc
+++ b/tools/veridex/veridex.cc
@@ -19,6 +19,10 @@
 #include <android-base/file.h>
 #include <android-base/strings.h>
 
+#include <cstdlib>
+#include <sstream>
+
+#include "base/mem_map.h"
 #include "dex/dex_file.h"
 #include "dex/dex_file_loader.h"
 #include "hidden_api.h"
@@ -26,9 +30,6 @@
 #include "precise_hidden_api_finder.h"
 #include "resolver.h"
 
-#include <cstdlib>
-#include <sstream>
-
 namespace art {
 
 static VeriClass z_(Primitive::Type::kPrimBoolean, 0, nullptr);
@@ -296,23 +297,18 @@
     }
 
     // TODO: once added, use an api to android::base to read a std::vector<uint8_t>.
-    if (!android::base::ReadFileToString(filename.c_str(), &content)) {
+    if (!android::base::ReadFileToString(filename, &content)) {
       *error_msg = "ReadFileToString failed for " + filename;
       return false;
     }
 
-    const DexFileLoader dex_file_loader;
     DexFileLoaderErrorCode error_code;
     static constexpr bool kVerifyChecksum = true;
     static constexpr bool kRunDexFileVerifier = true;
-    if (!dex_file_loader.OpenAll(reinterpret_cast<const uint8_t*>(content.data()),
-                                 content.size(),
-                                 filename.c_str(),
-                                 kRunDexFileVerifier,
-                                 kVerifyChecksum,
-                                 &error_code,
-                                 error_msg,
-                                 dex_files)) {
+    DexFileLoader dex_file_loader(
+        reinterpret_cast<const uint8_t*>(content.data()), content.size(), filename);
+    if (!dex_file_loader.Open(
+            kRunDexFileVerifier, kVerifyChecksum, &error_code, error_msg, dex_files)) {
       if (error_code == DexFileLoaderErrorCode::kEntryNotFound) {
         LOG(INFO) << "No .dex found, skipping analysis.";
         return true;
@@ -343,5 +339,6 @@
 }  // namespace art
 
 int main(int argc, char** argv) {
+  art::MemMap::Init();
   return art::Veridex::Run(argc, argv);
 }